From 32a2c8847f1574fa7659a568a29ecf180b175d52 Mon Sep 17 00:00:00 2001 From: Vihiga Tyonum Date: Tue, 4 Nov 2025 18:58:03 +0100 Subject: [PATCH] feat(desc):update descriptors gen to use templates --- src/handlers.rs | 3 +- src/utils.rs | 221 +++++++++++++++++++++--------------------------- 2 files changed, 100 insertions(+), 124 deletions(-) diff --git a/src/handlers.rs b/src/handlers.rs index b89ee7d..b39712e 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -1344,11 +1344,12 @@ pub fn handle_descriptor_subcommand( let result = match subcommand { DescriptorSubCommand::Generate { desc_type, key } => { match key { - // generate descriptors with a key or mnemonic Some(key) => { if is_mnemonic(&key) { + // User provided mnemonic generate_descriptor_from_mnemonic(&key, network, &desc_type) } else { + // User provided xprv/xpub generate_descriptors(&desc_type, &key, network) } } diff --git a/src/utils.rs b/src/utils.rs index 6614b78..8a3ee04 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -24,9 +24,14 @@ use bdk_kyoto::{ builder::Builder, }; use bdk_wallet::{ - bitcoin::secp256k1::All, - keys::{IntoDescriptorKey, KeyMap}, - miniscript::{Legacy, Miniscript, Terminal}, + KeychainKind, + bitcoin::bip32::{DerivationPath, Xpub}, + keys::DescriptorPublicKey, + miniscript::{ + Descriptor, Miniscript, Terminal, + descriptor::{DescriptorXKey, Wildcard}, + }, + template::DescriptorTemplate, }; use cli_table::{Cell, CellStruct, Style, Table}; @@ -40,24 +45,14 @@ use crate::commands::ClientType; use bdk_wallet::Wallet; #[cfg(any(feature = "sqlite", feature = "redb"))] -use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister}; +use bdk_wallet::{PersistedWallet, WalletPersister}; use bdk_wallet::bip39::{Language, Mnemonic}; use bdk_wallet::bitcoin::{ - Address, Network, OutPoint, ScriptBuf, - bip32::{DerivationPath, Xpriv, Xpub}, - secp256k1::Secp256k1, -}; -use bdk_wallet::descriptor::{ - Segwitv0, {Descriptor, DescriptorPublicKey}, -}; -use bdk_wallet::keys::{ - DerivableKey, DescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey, bip39::WordCount, -}; -use bdk_wallet::miniscript::{ - Tap, - descriptor::{DescriptorXKey, Wildcard}, + Address, Network, OutPoint, ScriptBuf, bip32::Xpriv, secp256k1::Secp256k1, }; +use bdk_wallet::descriptor::Segwitv0; +use bdk_wallet::keys::{GeneratableKey, GeneratedKey, bip39::WordCount}; use serde_json::{Value, json}; /// Parse the recipient (Address,Amount) argument from cli input. @@ -395,111 +390,68 @@ pub fn is_mnemonic(s: &str) -> bool { (12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace()) } -pub fn extract_keymap( - desc_type: &str, - desc_secret: DescriptorSecretKey, - secp: &Secp256k1, -) -> Result<(DescriptorPublicKey, KeyMap), Error> { - let (desc_pub, keymap, _) = match desc_type.to_lowercase().as_str() { - "pkh" => { - let descriptor_key = IntoDescriptorKey::::into_descriptor_key(desc_secret)?; - descriptor_key.extract(secp)? - } - "wpkh" | "sh" | "wsh" => { - let descriptor_key = IntoDescriptorKey::::into_descriptor_key(desc_secret)?; - descriptor_key.extract(secp)? - } - "tr" => { - let descriptor_key = IntoDescriptorKey::::into_descriptor_key(desc_secret)?; - descriptor_key.extract(secp)? - } - _ => { - return Err(Error::Generic(format!( - "Unsupported descriptor type: {desc_type}" - ))); - } - }; - Ok((desc_pub, keymap)) -} - -pub fn build_public_descriptor( - desc_type: &str, - key: DescriptorPublicKey, -) -> Result, Error> { - match desc_type.to_lowercase().as_str() { - "pkh" => Descriptor::new_pkh(key).map_err(Error::from), - "wpkh" => Descriptor::new_wpkh(key).map_err(Error::from), - "sh" => Descriptor::new_sh_wpkh(key).map_err(Error::from), - "wsh" => { - let pk_k = Miniscript::from_ast(Terminal::PkK(key)).map_err(Error::from)?; - let pk_ms: Miniscript = - Miniscript::from_ast(Terminal::Check(Arc::new(pk_k))).map_err(Error::from)?; - Descriptor::new_wsh(pk_ms).map_err(Error::from) - } - "tr" => Descriptor::new_tr(key, None).map_err(Error::from), - _ => Err(Error::Generic(format!( - "Unsupported descriptor type: {desc_type}" - ))), - } -} - pub fn generate_descriptors(desc_type: &str, key: &str, network: Network) -> Result { - let secp = Secp256k1::new(); - let purpose = match desc_type.to_lowercase().as_str() { - "pkh" => 44u32, - "sh" => 49u32, - "wpkh" | "wsh" => 84u32, - "tr" => 86u32, - _ => 84u32, - }; - let coin_type = match network { - Network::Bitcoin => 0u32, - _ => 1u32, - }; - let derivation_base = format!("/{purpose}h/{coin_type}h/0h"); - let derivation_path = DerivationPath::from_str(&format!("m{derivation_base}"))?; - let is_private = key.starts_with("xprv") || key.starts_with("tprv"); if is_private { - generate_private_descriptors(desc_type, key, &derivation_path, &secp) + generate_private_descriptors(desc_type, key, network) } else { + let purpose = match desc_type.to_lowercase().as_str() { + "pkh" => 44u32, + "sh" => 49u32, + "wpkh" | "wsh" => 84u32, + "tr" => 86u32, + _ => 84u32, + }; + let coin_type = match network { + Network::Bitcoin => 0u32, + _ => 1u32, + }; + let derivation_path = DerivationPath::from_str(&format!("m/{purpose}h/{coin_type}h/0h"))?; generate_public_descriptors(desc_type, key, &derivation_path) } } +/// Generate descriptors from private key using BIP templates fn generate_private_descriptors( desc_type: &str, key: &str, - account_path: &DerivationPath, - secp: &Secp256k1, + network: Network, ) -> Result { - let xprv: Xpriv = key.parse()?; - let fingerprint = xprv.fingerprint(secp); + use bdk_wallet::template::{Bip44, Bip49, Bip84, Bip86}; - let account_xprv = xprv.derive_priv(secp, account_path)?; - - let build_descriptor = |branch: &str| -> Result<(String, String), Error> { - let branch_path = DerivationPath::from_str(branch)?; - - let desc_xprv = DescriptorXKey { - origin: Some((fingerprint, account_path.clone())), - xkey: account_xprv, - derivation_path: branch_path, - wildcard: Wildcard::Unhardened, - }; - let desc_secret = DescriptorSecretKey::XPrv(desc_xprv); + let secp = Secp256k1::new(); + let xprv: Xpriv = key.parse()?; + let fingerprint = xprv.fingerprint(&secp); - let (desc_pub, keymap) = extract_keymap(desc_type, desc_secret, secp)?; - let descriptor = build_public_descriptor(desc_type, desc_pub)?; - let public_str = descriptor.to_string(); - let private_str = descriptor.to_string_with_secret(&keymap); + let (external_desc, external_keymap, _) = match desc_type.to_lowercase().as_str() { + "pkh" => Bip44(xprv, KeychainKind::External).build(network)?, + "sh" => Bip49(xprv, KeychainKind::External).build(network)?, + "wpkh" | "wsh" => Bip84(xprv, KeychainKind::External).build(network)?, + "tr" => Bip86(xprv, KeychainKind::External).build(network)?, + _ => { + return Err(Error::Generic(format!( + "Unsupported descriptor type: {desc_type}" + ))); + } + }; - Ok((public_str, private_str)) + let (internal_desc, internal_keymap, _) = match desc_type.to_lowercase().as_str() { + "pkh" => Bip44(xprv, KeychainKind::Internal).build(network)?, + "sh" => Bip49(xprv, KeychainKind::Internal).build(network)?, + "wpkh" | "wsh" => Bip84(xprv, KeychainKind::Internal).build(network)?, + "tr" => Bip86(xprv, KeychainKind::Internal).build(network)?, + _ => { + return Err(Error::Generic(format!( + "Unsupported descriptor type: {desc_type}" + ))); + } }; - let (external_pub, external_priv) = build_descriptor("0")?; - let (internal_pub, internal_priv) = build_descriptor("1")?; + let external_priv = external_desc.to_string_with_secret(&external_keymap); + let external_pub = external_desc.to_string(); + let internal_priv = internal_desc.to_string_with_secret(&internal_keymap); + let internal_pub = internal_desc.to_string(); Ok(json!({ "public_descriptors": { @@ -514,21 +466,7 @@ fn generate_private_descriptors( })) } -pub fn generate_descriptor_with_mnemonic( - network: Network, - desc_type: &str, -) -> Result { - let mnemonic: GeneratedKey = - Mnemonic::generate((WordCount::Words12, Language::English)).map_err(Error::BIP39Error)?; - - let seed = mnemonic.to_seed(""); - let xprv = Xpriv::new_master(network, &seed)?; - - let mut result = generate_descriptors(desc_type, &xprv.to_string(), network)?; - result["mnemonic"] = json!(mnemonic.to_string()); - Ok(result) -} - +/// Generate descriptors from public key (xpub/tpub) pub fn generate_public_descriptors( desc_type: &str, key: &str, @@ -562,16 +500,53 @@ pub fn generate_public_descriptors( })) } +/// Build a descriptor from a public key +pub fn build_public_descriptor( + desc_type: &str, + key: DescriptorPublicKey, +) -> Result, Error> { + match desc_type.to_lowercase().as_str() { + "pkh" => Descriptor::new_pkh(key).map_err(Error::from), + "wpkh" => Descriptor::new_wpkh(key).map_err(Error::from), + "sh" => Descriptor::new_sh_wpkh(key).map_err(Error::from), + "wsh" => { + let pk_k = Miniscript::from_ast(Terminal::PkK(key)).map_err(Error::from)?; + let pk_ms: Miniscript = + Miniscript::from_ast(Terminal::Check(Arc::new(pk_k))).map_err(Error::from)?; + Descriptor::new_wsh(pk_ms).map_err(Error::from) + } + "tr" => Descriptor::new_tr(key, None).map_err(Error::from), + _ => Err(Error::Generic(format!( + "Unsupported descriptor type: {desc_type}" + ))), + } +} + +/// Generate new mnemonic and descriptors +pub fn generate_descriptor_with_mnemonic( + network: Network, + desc_type: &str, +) -> Result { + let mnemonic: GeneratedKey = + Mnemonic::generate((WordCount::Words12, Language::English)).map_err(Error::BIP39Error)?; + + let seed = mnemonic.to_seed(""); + let xprv = Xpriv::new_master(network, &seed)?; + + let mut result = generate_descriptors(desc_type, &xprv.to_string(), network)?; + result["mnemonic"] = json!(mnemonic.to_string()); + Ok(result) +} + +/// Generate descriptors from existing mnemonic pub fn generate_descriptor_from_mnemonic( mnemonic_str: &str, network: Network, desc_type: &str, ) -> Result { let mnemonic = Mnemonic::parse_in(Language::English, mnemonic_str)?; - let ext_key: ExtendedKey = mnemonic.into_extended_key()?; - let xprv = ext_key - .into_xprv(network) - .ok_or_else(|| Error::Generic("No xprv found".to_string()))?; + let seed = mnemonic.to_seed(""); + let xprv = Xpriv::new_master(network, &seed)?; let mut result = generate_descriptors(desc_type, &xprv.to_string(), network)?; result["mnemonic"] = json!(mnemonic_str); -- 2.49.0