Skip to content

Commit 0f535b7

Browse files
committed
feat(desc):update descriptors gen to use templates
1 parent c8f23ba commit 0f535b7

File tree

2 files changed

+91
-128
lines changed

2 files changed

+91
-128
lines changed

src/handlers.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1344,11 +1344,12 @@ pub fn handle_descriptor_subcommand(
13441344
let result = match subcommand {
13451345
DescriptorSubCommand::Generate { desc_type, key } => {
13461346
match key {
1347-
// generate descriptors with a key or mnemonic
13481347
Some(key) => {
13491348
if is_mnemonic(&key) {
1349+
// User provided mnemonic
13501350
generate_descriptor_from_mnemonic(&key, network, &desc_type)
13511351
} else {
1352+
// User provided xprv/xpub
13521353
generate_descriptors(&desc_type, &key, network)
13531354
}
13541355
}

src/utils.rs

Lines changed: 89 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ use crate::error::BDKCliError as Error;
1313
use std::{
1414
fmt::Display,
1515
path::{Path, PathBuf},
16-
str::FromStr,
17-
sync::Arc,
16+
str::FromStr, sync::Arc,
1817
};
1918

2019
use crate::commands::WalletOpts;
@@ -23,11 +22,7 @@ use bdk_kyoto::{
2322
BuilderExt, Info, LightClient, Receiver, ScanType::Sync, UnboundedReceiver, Warning,
2423
builder::Builder,
2524
};
26-
use bdk_wallet::{
27-
bitcoin::secp256k1::All,
28-
keys::{IntoDescriptorKey, KeyMap},
29-
miniscript::{Legacy, Miniscript, Terminal},
30-
};
25+
use bdk_wallet::{bitcoin::bip32::{DerivationPath, Xpub}, keys::DescriptorPublicKey, miniscript::{descriptor::{DescriptorXKey, Wildcard}, Descriptor, Miniscript, Terminal}, template::DescriptorTemplate};
3126
use cli_table::{Cell, CellStruct, Style, Table};
3227

3328
#[cfg(any(
@@ -44,20 +39,10 @@ use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
4439

4540
use bdk_wallet::bip39::{Language, Mnemonic};
4641
use bdk_wallet::bitcoin::{
47-
Address, Network, OutPoint, ScriptBuf,
48-
bip32::{DerivationPath, Xpriv, Xpub},
49-
secp256k1::Secp256k1,
50-
};
51-
use bdk_wallet::descriptor::{
52-
Segwitv0, {Descriptor, DescriptorPublicKey},
53-
};
54-
use bdk_wallet::keys::{
55-
DerivableKey, DescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey, bip39::WordCount,
56-
};
57-
use bdk_wallet::miniscript::{
58-
Tap,
59-
descriptor::{DescriptorXKey, Wildcard},
42+
Address, Network, OutPoint, ScriptBuf, bip32::Xpriv, secp256k1::Secp256k1,
6043
};
44+
use bdk_wallet::descriptor::Segwitv0;
45+
use bdk_wallet::keys::{GeneratableKey, GeneratedKey, bip39::WordCount};
6146
use serde_json::{Value, json};
6247

6348
/// Parse the recipient (Address,Amount) argument from cli input.
@@ -395,111 +380,64 @@ pub fn is_mnemonic(s: &str) -> bool {
395380
(12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace())
396381
}
397382

398-
pub fn extract_keymap(
399-
desc_type: &str,
400-
desc_secret: DescriptorSecretKey,
401-
secp: &Secp256k1<All>,
402-
) -> Result<(DescriptorPublicKey, KeyMap), Error> {
403-
let (desc_pub, keymap, _) = match desc_type.to_lowercase().as_str() {
404-
"pkh" => {
405-
let descriptor_key = IntoDescriptorKey::<Legacy>::into_descriptor_key(desc_secret)?;
406-
descriptor_key.extract(secp)?
407-
}
408-
"wpkh" | "sh" | "wsh" => {
409-
let descriptor_key = IntoDescriptorKey::<Segwitv0>::into_descriptor_key(desc_secret)?;
410-
descriptor_key.extract(secp)?
411-
}
412-
"tr" => {
413-
let descriptor_key = IntoDescriptorKey::<Tap>::into_descriptor_key(desc_secret)?;
414-
descriptor_key.extract(secp)?
415-
}
416-
_ => {
417-
return Err(Error::Generic(format!(
418-
"Unsupported descriptor type: {desc_type}"
419-
)));
420-
}
421-
};
422-
Ok((desc_pub, keymap))
423-
}
424-
425-
pub fn build_public_descriptor(
383+
pub fn generate_descriptors(
426384
desc_type: &str,
427-
key: DescriptorPublicKey,
428-
) -> Result<Descriptor<DescriptorPublicKey>, Error> {
429-
match desc_type.to_lowercase().as_str() {
430-
"pkh" => Descriptor::new_pkh(key).map_err(Error::from),
431-
"wpkh" => Descriptor::new_wpkh(key).map_err(Error::from),
432-
"sh" => Descriptor::new_sh_wpkh(key).map_err(Error::from),
433-
"wsh" => {
434-
let pk_k = Miniscript::from_ast(Terminal::PkK(key)).map_err(Error::from)?;
435-
let pk_ms: Miniscript<DescriptorPublicKey, Segwitv0> =
436-
Miniscript::from_ast(Terminal::Check(Arc::new(pk_k))).map_err(Error::from)?;
437-
Descriptor::new_wsh(pk_ms).map_err(Error::from)
438-
}
439-
"tr" => Descriptor::new_tr(key, None).map_err(Error::from),
440-
_ => Err(Error::Generic(format!(
441-
"Unsupported descriptor type: {desc_type}"
442-
))),
443-
}
444-
}
445-
446-
pub fn generate_descriptors(desc_type: &str, key: &str, network: Network) -> Result<Value, Error> {
447-
let secp = Secp256k1::new();
448-
let purpose = match desc_type.to_lowercase().as_str() {
449-
"pkh" => 44u32,
450-
"sh" => 49u32,
451-
"wpkh" | "wsh" => 84u32,
452-
"tr" => 86u32,
453-
_ => 84u32,
454-
};
455-
let coin_type = match network {
456-
Network::Bitcoin => 0u32,
457-
_ => 1u32,
458-
};
459-
let derivation_base = format!("/{purpose}h/{coin_type}h/0h");
460-
let derivation_path = DerivationPath::from_str(&format!("m{derivation_base}"))?;
461-
385+
key: &str,
386+
network: Network,
387+
) -> Result<Value, Error> {
462388
let is_private = key.starts_with("xprv") || key.starts_with("tprv");
463389

464390
if is_private {
465-
generate_private_descriptors(desc_type, key, &derivation_path, &secp)
391+
generate_private_descriptors(desc_type, key, network)
466392
} else {
393+
let purpose = match desc_type.to_lowercase().as_str() {
394+
"pkh" => 44u32,
395+
"sh" => 49u32,
396+
"wpkh" | "wsh" => 84u32,
397+
"tr" => 86u32,
398+
_ => 84u32,
399+
};
400+
let coin_type = match network {
401+
Network::Bitcoin => 0u32,
402+
_ => 1u32,
403+
};
404+
let derivation_path = DerivationPath::from_str(&format!("m/{purpose}h/{coin_type}h/0h"))?;
467405
generate_public_descriptors(desc_type, key, &derivation_path)
468406
}
469407
}
470408

409+
/// Generate descriptors from private key using BIP templates
471410
fn generate_private_descriptors(
472411
desc_type: &str,
473412
key: &str,
474-
account_path: &DerivationPath,
475-
secp: &Secp256k1<All>,
413+
network: Network,
476414
) -> Result<Value, Error> {
415+
use bdk_wallet::template::{Bip44, Bip49, Bip84, Bip86};
416+
417+
let secp = Secp256k1::new();
477418
let xprv: Xpriv = key.parse()?;
478-
let fingerprint = xprv.fingerprint(secp);
479-
480-
let account_xprv = xprv.derive_priv(secp, account_path)?;
481-
482-
let build_descriptor = |branch: &str| -> Result<(String, String), Error> {
483-
let branch_path = DerivationPath::from_str(branch)?;
484-
485-
let desc_xprv = DescriptorXKey {
486-
origin: Some((fingerprint, account_path.clone())),
487-
xkey: account_xprv,
488-
derivation_path: branch_path,
489-
wildcard: Wildcard::Unhardened,
490-
};
491-
let desc_secret = DescriptorSecretKey::XPrv(desc_xprv);
492-
493-
let (desc_pub, keymap) = extract_keymap(desc_type, desc_secret, secp)?;
494-
let descriptor = build_public_descriptor(desc_type, desc_pub)?;
495-
let public_str = descriptor.to_string();
496-
let private_str = descriptor.to_string_with_secret(&keymap);
419+
let fingerprint = xprv.fingerprint(&secp);
420+
421+
let (external_desc, external_keymap, _) = match desc_type.to_lowercase().as_str() {
422+
"pkh" => Bip44(xprv, KeychainKind::External).build(network)?,
423+
"sh" => Bip49(xprv, KeychainKind::External).build(network)?,
424+
"wpkh" | "wsh" => Bip84(xprv, KeychainKind::External).build(network)?,
425+
"tr" => Bip86(xprv, KeychainKind::External).build(network)?,
426+
_ => return Err(Error::Generic(format!("Unsupported descriptor type: {desc_type}"))),
427+
};
497428

498-
Ok((public_str, private_str))
429+
let (internal_desc, internal_keymap, _) = match desc_type.to_lowercase().as_str() {
430+
"pkh" => Bip44(xprv, KeychainKind::Internal).build(network)?,
431+
"sh" => Bip49(xprv, KeychainKind::Internal).build(network)?,
432+
"wpkh" | "wsh" => Bip84(xprv, KeychainKind::Internal).build(network)?,
433+
"tr" => Bip86(xprv, KeychainKind::Internal).build(network)?,
434+
_ => return Err(Error::Generic(format!("Unsupported descriptor type: {desc_type}"))),
499435
};
500436

501-
let (external_pub, external_priv) = build_descriptor("0")?;
502-
let (internal_pub, internal_priv) = build_descriptor("1")?;
437+
let external_priv = external_desc.to_string_with_secret(&external_keymap);
438+
let external_pub = external_desc.to_string();
439+
let internal_priv = internal_desc.to_string_with_secret(&internal_keymap);
440+
let internal_pub = internal_desc.to_string();
503441

504442
Ok(json!({
505443
"public_descriptors": {
@@ -514,21 +452,7 @@ fn generate_private_descriptors(
514452
}))
515453
}
516454

517-
pub fn generate_descriptor_with_mnemonic(
518-
network: Network,
519-
desc_type: &str,
520-
) -> Result<serde_json::Value, Error> {
521-
let mnemonic: GeneratedKey<Mnemonic, Segwitv0> =
522-
Mnemonic::generate((WordCount::Words12, Language::English)).map_err(Error::BIP39Error)?;
523-
524-
let seed = mnemonic.to_seed("");
525-
let xprv = Xpriv::new_master(network, &seed)?;
526-
527-
let mut result = generate_descriptors(desc_type, &xprv.to_string(), network)?;
528-
result["mnemonic"] = json!(mnemonic.to_string());
529-
Ok(result)
530-
}
531-
455+
/// Generate descriptors from public key (xpub/tpub)
532456
pub fn generate_public_descriptors(
533457
desc_type: &str,
534458
key: &str,
@@ -562,16 +486,54 @@ pub fn generate_public_descriptors(
562486
}))
563487
}
564488

489+
/// Build a descriptor from a public key
490+
pub fn build_public_descriptor(
491+
desc_type: &str,
492+
key: DescriptorPublicKey,
493+
) -> Result<Descriptor<DescriptorPublicKey>, Error> {
494+
match desc_type.to_lowercase().as_str() {
495+
"pkh" => Descriptor::new_pkh(key).map_err(Error::from),
496+
"wpkh" => Descriptor::new_wpkh(key).map_err(Error::from),
497+
"sh" => Descriptor::new_sh_wpkh(key).map_err(Error::from),
498+
"wsh" => {
499+
let pk_k = Miniscript::from_ast(Terminal::PkK(key)).map_err(Error::from)?;
500+
let pk_ms: Miniscript<DescriptorPublicKey, Segwitv0> =
501+
Miniscript::from_ast(Terminal::Check(Arc::new(pk_k))).map_err(Error::from)?;
502+
Descriptor::new_wsh(pk_ms).map_err(Error::from)
503+
}
504+
"tr" => Descriptor::new_tr(key, None).map_err(Error::from),
505+
_ => Err(Error::Generic(format!(
506+
"Unsupported descriptor type: {desc_type}"
507+
))),
508+
}
509+
}
510+
511+
/// Generate new mnemonic and descriptors
512+
pub fn generate_descriptor_with_mnemonic(
513+
network: Network,
514+
desc_type: &str,
515+
) -> Result<serde_json::Value, Error> {
516+
let mnemonic: GeneratedKey<Mnemonic, Segwitv0> =
517+
Mnemonic::generate((WordCount::Words12, Language::English))
518+
.map_err(Error::BIP39Error)?;
519+
520+
let seed = mnemonic.to_seed("");
521+
let xprv = Xpriv::new_master(network, &seed)?;
522+
523+
let mut result = generate_descriptors(desc_type, &xprv.to_string(), network)?;
524+
result["mnemonic"] = json!(mnemonic.to_string());
525+
Ok(result)
526+
}
527+
528+
/// Generate descriptors from existing mnemonic
565529
pub fn generate_descriptor_from_mnemonic(
566530
mnemonic_str: &str,
567531
network: Network,
568532
desc_type: &str,
569533
) -> Result<serde_json::Value, Error> {
570534
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic_str)?;
571-
let ext_key: ExtendedKey = mnemonic.into_extended_key()?;
572-
let xprv = ext_key
573-
.into_xprv(network)
574-
.ok_or_else(|| Error::Generic("No xprv found".to_string()))?;
535+
let seed = mnemonic.to_seed("");
536+
let xprv = Xpriv::new_master(network, &seed)?;
575537

576538
let mut result = generate_descriptors(desc_type, &xprv.to_string(), network)?;
577539
result["mnemonic"] = json!(mnemonic_str);

0 commit comments

Comments
 (0)