Skip to content

Commit 32a2c88

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

File tree

2 files changed

+100
-124
lines changed

2 files changed

+100
-124
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: 98 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@ 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+
KeychainKind,
28+
bitcoin::bip32::{DerivationPath, Xpub},
29+
keys::DescriptorPublicKey,
30+
miniscript::{
31+
Descriptor, Miniscript, Terminal,
32+
descriptor::{DescriptorXKey, Wildcard},
33+
},
34+
template::DescriptorTemplate,
3035
};
3136
use cli_table::{Cell, CellStruct, Style, Table};
3237

@@ -40,24 +45,14 @@ use crate::commands::ClientType;
4045

4146
use bdk_wallet::Wallet;
4247
#[cfg(any(feature = "sqlite", feature = "redb"))]
43-
use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
48+
use bdk_wallet::{PersistedWallet, WalletPersister};
4449

4550
use bdk_wallet::bip39::{Language, Mnemonic};
4651
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},
52+
Address, Network, OutPoint, ScriptBuf, bip32::Xpriv, secp256k1::Secp256k1,
6053
};
54+
use bdk_wallet::descriptor::Segwitv0;
55+
use bdk_wallet::keys::{GeneratableKey, GeneratedKey, bip39::WordCount};
6156
use serde_json::{Value, json};
6257

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

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-
446393
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-
462394
let is_private = key.starts_with("xprv") || key.starts_with("tprv");
463395

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

415+
/// Generate descriptors from private key using BIP templates
471416
fn generate_private_descriptors(
472417
desc_type: &str,
473418
key: &str,
474-
account_path: &DerivationPath,
475-
secp: &Secp256k1<All>,
419+
network: Network,
476420
) -> Result<Value, Error> {
477-
let xprv: Xpriv = key.parse()?;
478-
let fingerprint = xprv.fingerprint(secp);
421+
use bdk_wallet::template::{Bip44, Bip49, Bip84, Bip86};
479422

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

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

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

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

504456
Ok(json!({
505457
"public_descriptors": {
@@ -514,21 +466,7 @@ fn generate_private_descriptors(
514466
}))
515467
}
516468

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-
469+
/// Generate descriptors from public key (xpub/tpub)
532470
pub fn generate_public_descriptors(
533471
desc_type: &str,
534472
key: &str,
@@ -562,16 +500,53 @@ pub fn generate_public_descriptors(
562500
}))
563501
}
564502

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

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

0 commit comments

Comments
 (0)