Skip to content

Commit f6f77f8

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

File tree

2 files changed

+98
-123
lines changed

2 files changed

+98
-123
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: 96 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@ use bdk_kyoto::{
2424
builder::Builder,
2525
};
2626
use bdk_wallet::{
27-
bitcoin::secp256k1::All,
28-
keys::{IntoDescriptorKey, KeyMap},
29-
miniscript::{Legacy, Miniscript, Terminal},
27+
bitcoin::bip32::{DerivationPath, Xpub},
28+
keys::DescriptorPublicKey,
29+
miniscript::{
30+
Descriptor, Miniscript, Terminal,
31+
descriptor::{DescriptorXKey, Wildcard},
32+
},
33+
template::DescriptorTemplate,
3034
};
3135
use cli_table::{Cell, CellStruct, Style, Table};
3236

@@ -44,20 +48,10 @@ use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
4448

4549
use bdk_wallet::bip39::{Language, Mnemonic};
4650
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},
51+
Address, Network, OutPoint, ScriptBuf, bip32::Xpriv, secp256k1::Secp256k1,
6052
};
53+
use bdk_wallet::descriptor::Segwitv0;
54+
use bdk_wallet::keys::{GeneratableKey, GeneratedKey, bip39::WordCount};
6155
use serde_json::{Value, json};
6256

6357
/// Parse the recipient (Address,Amount) argument from cli input.
@@ -395,111 +389,68 @@ pub fn is_mnemonic(s: &str) -> bool {
395389
(12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace())
396390
}
397391

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(
426-
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-
446392
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-
462393
let is_private = key.starts_with("xprv") || key.starts_with("tprv");
463394

464395
if is_private {
465-
generate_private_descriptors(desc_type, key, &derivation_path, &secp)
396+
generate_private_descriptors(desc_type, key, network)
466397
} else {
398+
let purpose = match desc_type.to_lowercase().as_str() {
399+
"pkh" => 44u32,
400+
"sh" => 49u32,
401+
"wpkh" | "wsh" => 84u32,
402+
"tr" => 86u32,
403+
_ => 84u32,
404+
};
405+
let coin_type = match network {
406+
Network::Bitcoin => 0u32,
407+
_ => 1u32,
408+
};
409+
let derivation_path = DerivationPath::from_str(&format!("m/{purpose}h/{coin_type}h/0h"))?;
467410
generate_public_descriptors(desc_type, key, &derivation_path)
468411
}
469412
}
470413

414+
/// Generate descriptors from private key using BIP templates
471415
fn generate_private_descriptors(
472416
desc_type: &str,
473417
key: &str,
474-
account_path: &DerivationPath,
475-
secp: &Secp256k1<All>,
418+
network: Network,
476419
) -> Result<Value, Error> {
477-
let xprv: Xpriv = key.parse()?;
478-
let fingerprint = xprv.fingerprint(secp);
420+
use bdk_wallet::template::{Bip44, Bip49, Bip84, Bip86};
479421

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);
422+
let secp = Secp256k1::new();
423+
let xprv: Xpriv = key.parse()?;
424+
let fingerprint = xprv.fingerprint(&secp);
492425

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);
426+
let (external_desc, external_keymap, _) = match desc_type.to_lowercase().as_str() {
427+
"pkh" => Bip44(xprv, KeychainKind::External).build(network)?,
428+
"sh" => Bip49(xprv, KeychainKind::External).build(network)?,
429+
"wpkh" | "wsh" => Bip84(xprv, KeychainKind::External).build(network)?,
430+
"tr" => Bip86(xprv, KeychainKind::External).build(network)?,
431+
_ => {
432+
return Err(Error::Generic(format!(
433+
"Unsupported descriptor type: {desc_type}"
434+
)));
435+
}
436+
};
497437

498-
Ok((public_str, private_str))
438+
let (internal_desc, internal_keymap, _) = match desc_type.to_lowercase().as_str() {
439+
"pkh" => Bip44(xprv, KeychainKind::Internal).build(network)?,
440+
"sh" => Bip49(xprv, KeychainKind::Internal).build(network)?,
441+
"wpkh" | "wsh" => Bip84(xprv, KeychainKind::Internal).build(network)?,
442+
"tr" => Bip86(xprv, KeychainKind::Internal).build(network)?,
443+
_ => {
444+
return Err(Error::Generic(format!(
445+
"Unsupported descriptor type: {desc_type}"
446+
)));
447+
}
499448
};
500449

501-
let (external_pub, external_priv) = build_descriptor("0")?;
502-
let (internal_pub, internal_priv) = build_descriptor("1")?;
450+
let external_priv = external_desc.to_string_with_secret(&external_keymap);
451+
let external_pub = external_desc.to_string();
452+
let internal_priv = internal_desc.to_string_with_secret(&internal_keymap);
453+
let internal_pub = internal_desc.to_string();
503454

504455
Ok(json!({
505456
"public_descriptors": {
@@ -514,21 +465,7 @@ fn generate_private_descriptors(
514465
}))
515466
}
516467

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-
468+
/// Generate descriptors from public key (xpub/tpub)
532469
pub fn generate_public_descriptors(
533470
desc_type: &str,
534471
key: &str,
@@ -562,16 +499,53 @@ pub fn generate_public_descriptors(
562499
}))
563500
}
564501

502+
/// Build a descriptor from a public key
503+
pub fn build_public_descriptor(
504+
desc_type: &str,
505+
key: DescriptorPublicKey,
506+
) -> Result<Descriptor<DescriptorPublicKey>, Error> {
507+
match desc_type.to_lowercase().as_str() {
508+
"pkh" => Descriptor::new_pkh(key).map_err(Error::from),
509+
"wpkh" => Descriptor::new_wpkh(key).map_err(Error::from),
510+
"sh" => Descriptor::new_sh_wpkh(key).map_err(Error::from),
511+
"wsh" => {
512+
let pk_k = Miniscript::from_ast(Terminal::PkK(key)).map_err(Error::from)?;
513+
let pk_ms: Miniscript<DescriptorPublicKey, Segwitv0> =
514+
Miniscript::from_ast(Terminal::Check(Arc::new(pk_k))).map_err(Error::from)?;
515+
Descriptor::new_wsh(pk_ms).map_err(Error::from)
516+
}
517+
"tr" => Descriptor::new_tr(key, None).map_err(Error::from),
518+
_ => Err(Error::Generic(format!(
519+
"Unsupported descriptor type: {desc_type}"
520+
))),
521+
}
522+
}
523+
524+
/// Generate new mnemonic and descriptors
525+
pub fn generate_descriptor_with_mnemonic(
526+
network: Network,
527+
desc_type: &str,
528+
) -> Result<serde_json::Value, Error> {
529+
let mnemonic: GeneratedKey<Mnemonic, Segwitv0> =
530+
Mnemonic::generate((WordCount::Words12, Language::English)).map_err(Error::BIP39Error)?;
531+
532+
let seed = mnemonic.to_seed("");
533+
let xprv = Xpriv::new_master(network, &seed)?;
534+
535+
let mut result = generate_descriptors(desc_type, &xprv.to_string(), network)?;
536+
result["mnemonic"] = json!(mnemonic.to_string());
537+
Ok(result)
538+
}
539+
540+
/// Generate descriptors from existing mnemonic
565541
pub fn generate_descriptor_from_mnemonic(
566542
mnemonic_str: &str,
567543
network: Network,
568544
desc_type: &str,
569545
) -> Result<serde_json::Value, Error> {
570546
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()))?;
547+
let seed = mnemonic.to_seed("");
548+
let xprv = Xpriv::new_master(network, &seed)?;
575549

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

0 commit comments

Comments
 (0)