From: Vihiga Tyonum Date: Wed, 24 Sep 2025 13:57:33 +0000 (+0100) Subject: fix descriptors generation X-Git-Url: http://internal-gitweb-vhost/?a=commitdiff_plain;h=1f77842ee7b415af581ca482de07cddf8cd5968c;p=bdk-cli fix descriptors generation --- diff --git a/src/commands.rs b/src/commands.rs index 09cbc5d..6983d6d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -17,7 +17,11 @@ use bdk_wallet::bitcoin::{ Address, Network, OutPoint, ScriptBuf, bip32::{DerivationPath, Xpriv}, }; -use clap::{Args, Parser, Subcommand, ValueEnum, builder::TypedValueParser, value_parser}; +use clap::{ + Args, Parser, Subcommand, ValueEnum, + builder::{PossibleValuesParser, TypedValueParser}, + value_parser, +}; #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))] use crate::utils::parse_proxy_auth; @@ -488,7 +492,7 @@ pub enum DescriptorSubCommand { #[arg( long = "type", short = 't', - value_parser = clap::builder::PossibleValuesParser::new(["44", "49", "84", "86"]) + value_parser = PossibleValuesParser::new(["44", "49", "84", "86"]) .map(|s| s.parse::().unwrap()), default_value = "84" )] @@ -499,7 +503,4 @@ pub enum DescriptorSubCommand { /// Optional key input key: Option, }, - - /// Show info about a given descriptor - Info { descriptor: String }, } diff --git a/src/error.rs b/src/error.rs index 3916f26..1b8b5b4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,8 +5,8 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum BDKCliError { - #[error("BIP39 error: {0}")] - BIP39Error(#[from] bdk_wallet::bip39::Error), + #[error("BIP39 error: {0:?}")] + BIP39Error(#[from] Option), #[error("BIP32 error: {0}")] BIP32Error(#[from] bdk_wallet::bitcoin::bip32::Error), @@ -103,30 +103,6 @@ pub enum BDKCliError { #[cfg(feature = "cbf")] #[error("BDK-Kyoto update error: {0}")] KyotoUpdateError(#[from] bdk_kyoto::UpdateError), - - #[error("Mnemonic generation failed: {0}")] - MnemonicGenerationError(String), - - #[error("Xpriv creation failed: {0}")] - XprivCreationError(String), - - #[error("Descriptor parsing failed: {0}")] - DescriptorParsingError(String), - - #[error("Invalid extended key (xpub): {0}")] - InvalidKey(String), - - #[error("Invalid derivation path: {0}")] - InvalidDerivationPath(String), - - #[error("Unsupported script type: {0}")] - UnsupportedScriptType(u8), - - #[error("Descriptor key conversion failed: {0}")] - DescriptorKeyError(String), - - #[error("Invalid arguments: {0}")] - InvalidArguments(String), } impl From for BDKCliError { diff --git a/src/handlers.rs b/src/handlers.rs index 9264e47..3787eeb 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -15,8 +15,6 @@ use crate::commands::*; use crate::error::BDKCliError as Error; #[cfg(any(feature = "sqlite", feature = "redb"))] use crate::persister::Persister; -#[cfg(feature = "cbf")] -use crate::utils::BlockchainClient::KyotoClient; use crate::utils::*; #[cfg(feature = "redb")] use bdk_redb::Store as RedbStore; @@ -35,27 +33,25 @@ use bdk_wallet::keys::{ bip39::WordCount, }; use bdk_wallet::miniscript::miniscript; -use bdk_wallet::bitcoin::bip32::{DerivationPath, KeySource}; -use bdk_wallet::bitcoin::consensus::encode::serialize_hex; -use bdk_wallet::bitcoin::script::PushBytesBuf; -use bdk_wallet::bitcoin::Network; -use bdk_wallet::bitcoin::{secp256k1::Secp256k1, Txid}; -use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, Sequence}; -use bdk_wallet::descriptor::{Descriptor, Segwitv0}; -use bdk_wallet::keys::bip39::WordCount; #[cfg(feature = "sqlite")] use bdk_wallet::rusqlite::Connection; use bdk_wallet::{KeychainKind, SignOptions, Wallet}; #[cfg(feature = "compiler")] use bdk_wallet::{ + bitcoin::XOnlyPublicKey, descriptor::{Descriptor, Legacy, Miniscript}, - miniscript::{Tap, descriptor::TapTree, policy::Concrete}, descriptor::{Legacy, Miniscript}, miniscript::policy::Concrete, + miniscript::{Tap, descriptor::TapTree, policy::Concrete}, }; use cli_table::{Cell, CellStruct, Style, Table, format::Justify}; use serde_json::json; -use bdk_wallet::{KeychainKind, SignOptions, Wallet}; +#[cfg(feature = "cbf")] +use { + crate::utils::BlockchainClient::KyotoClient, + bdk_kyoto::{Info, LightClient}, + tokio::select, +}; #[cfg(feature = "electrum")] use crate::utils::BlockchainClient::Electrum; @@ -64,29 +60,16 @@ use bdk_kyoto::LightClient; #[cfg(feature = "compiler")] use bdk_wallet::bitcoin::XOnlyPublicKey; use bdk_wallet::bitcoin::base64::prelude::*; -use bdk_wallet::keys::DescriptorKey::Secret; -use bdk_wallet::keys::{ - DerivableKey, DescriptorKey, DescriptorKey::Secret, DescriptorPublicKey, ExtendedKey, - GeneratableKey, GeneratedKey, bip39::WordCount, -}; -use bdk_wallet::miniscript::miniscript; -use serde_json::{Value, json}; +use serde_json::Value; use std::collections::BTreeMap; #[cfg(any(feature = "electrum", feature = "esplora"))] use std::collections::HashSet; use std::convert::TryFrom; -use std::fmt; #[cfg(any(feature = "repl", feature = "electrum", feature = "esplora"))] use std::io::Write; use std::str::FromStr; - -#[cfg(feature = "electrum")] -use crate::utils::BlockchainClient::Electrum; -#[cfg(feature = "cbf")] -use bdk_kyoto::{Info, LightClient}; -use bdk_wallet::bitcoin::base64::prelude::*; -#[cfg(feature = "cbf")] -use tokio::select; +#[cfg(any(feature = "redb", feature = "compiler"))] +use std::sync::Arc; #[cfg(any( feature = "electrum", feature = "esplora", @@ -95,7 +78,7 @@ use tokio::select; ))] use { crate::commands::OnlineWalletSubCommand::*, - bdk_wallet::bitcoin::{consensus::Decodable, hex::FromHex, Transaction}, + bdk_wallet::bitcoin::{Transaction, consensus::Decodable, hex::FromHex}, }; #[cfg(feature = "esplora")] use {crate::utils::BlockchainClient::Esplora, bdk_esplora::EsploraAsyncExt}; @@ -1287,8 +1270,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result { subcommand: descriptor_subcommand, } => { let network = cli_opts.network; - let descriptor = handle_descriptor_subcommand(network, descriptor_subcommand) - .map_err(|e| Error::Generic(e.to_string()))?; + let descriptor = handle_descriptor_subcommand(network, descriptor_subcommand)?; let json = serde_json::to_string_pretty(&descriptor)?; Ok(json) } @@ -1375,84 +1357,35 @@ pub fn handle_descriptor_subcommand( multipath, key, } => { - let (descriptor_type, derivation_path_str) = match r#type { - 44 => (DescriptorType::Bip44, "m/44h/1h/0h"), - 49 => (DescriptorType::Bip49, "m/49h/1h/0h"), - 84 => (DescriptorType::Bip84, "m/84h/1h/0h"), - 86 => (DescriptorType::Bip86, "m/86h/1h/0h"), - _ => return Err(Error::UnsupportedScriptType(r#type)), + let descriptor_type = match r#type { + 44 => DescriptorType::Bip44, + 49 => DescriptorType::Bip49, + 84 => DescriptorType::Bip84, + 86 => DescriptorType::Bip86, + _ => { + return Err(Error::Generic( + "Unsupported script type: {r#type}".to_string(), + )); + } }; match (multipath, key.as_ref()) { - (true, Some(k)) => generate_multipath_descriptor(&network, r#type, k), + // generate multipath descriptors + (true, Some(k)) => generate_descriptors(&network, descriptor_type, k, true), (false, Some(k)) => { if is_mnemonic(k) { - generate_descriptor_from_mnemonic_string( - k, - network, - derivation_path_str, - descriptor_type, - ) + // generate descriptors from given mnemonic string + generate_descriptor_from_mnemonic_string(k, network, descriptor_type) } else { - generate_standard_descriptor(&network, r#type, k) + // generate descriptors from key + generate_descriptors(&network, descriptor_type, k, false) } } + // generate mnemonic and descriptors (false, None) => generate_new_descriptor_with_mnemonic(network, descriptor_type), - _ => Err(Error::InvalidArguments( - "Provide a key or weak string".to_string(), - )), + _ => Err(Error::Generic("Provide a key or string".to_string())), } } - DescriptorSubCommand::Info { descriptor } => { - let parsed: Descriptor = descriptor - .parse() - .map_err(|e| Error::Generic(format!("Failed to parse descriptor: {}", e)))?; - - let checksum = parsed.to_string(); - let script_type = match parsed { - Descriptor::Wpkh(_) => "wpkh", - Descriptor::Pkh(_) => "pkh", - Descriptor::Sh(_) => "sh", - Descriptor::Tr(_) => "tr", - _ => "other", - }; - - let json = json!({ - "descriptor": checksum, - "type": script_type, - "is_multipath": descriptor.contains("/*"), - }); - - Ok(json) - } - } -} - -pub fn generate_standard_descriptor( - network: &Network, - script_type: u8, - key: &str, -) -> Result { - let descriptor_type = match script_type { - 44 => DescriptorType::Bip44, - 49 => DescriptorType::Bip49, - 84 => DescriptorType::Bip84, - 86 => DescriptorType::Bip86, - _ => return Err(Error::UnsupportedScriptType(script_type)), - }; - - generate_descriptor_from_key_by_type(network, key, descriptor_type) -} - -impl fmt::Display for DescriptorType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - DescriptorType::Bip44 => "bip44", - DescriptorType::Bip49 => "bip49", - DescriptorType::Bip84 => "bip84", - DescriptorType::Bip86 => "bip86", - }; - write!(f, "{}", s) } } diff --git a/src/utils.rs b/src/utils.rs index 93392ba..3a4d2cb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,10 +10,11 @@ //! //! This module includes all the utility tools used by the App. use crate::error::BDKCliError as Error; -use std::{fmt::Display, path::{Path, PathBuf}, str::FromStr}; -use std::str::FromStr; - -use std::path::{Path, PathBuf}; +use std::{ + fmt::{Display, Formatter}, + path::{Path, PathBuf}, + str::FromStr, +}; use crate::commands::WalletOpts; #[cfg(feature = "cbf")] @@ -22,6 +23,7 @@ use bdk_kyoto::{ builder::Builder, }; use bdk_wallet::bitcoin::{Address, Network, OutPoint, ScriptBuf}; +use bdk_wallet::miniscript::Legacy; #[cfg(any( feature = "electrum", @@ -36,7 +38,7 @@ use bdk_wallet::Wallet; use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister}; use bdk_wallet::bip39::{Language, Mnemonic}; -use bdk_wallet::bitcoin::bip32::ChildNumber; +use bdk_wallet::bitcoin::bip32::Fingerprint; use bdk_wallet::bitcoin::{ bip32::{DerivationPath, Xpriv, Xpub}, secp256k1::Secp256k1, @@ -378,161 +380,49 @@ pub async fn sync_kyoto_client(wallet: &mut Wallet, client: Box) -> Ok(()) } -pub(crate) fn shorten(displayable: impl Display, start: u8, end: u8) -> String { - let displayable = displayable.to_string(); - let start_str: &str = &displayable[0..start as usize]; - let end_str: &str = &displayable[displayable.len() - end as usize..]; - format!("{start_str}...{end_str}") -pub fn generate_descriptor_from_key_by_type( - network: &Network, - key: &str, - descriptor_type: DescriptorType, -) -> Result { - let derivation_path = match descriptor_type { - DescriptorType::Bip44 => "m/44h/1h/0h", - DescriptorType::Bip49 => "m/49h/1h/0h", - DescriptorType::Bip84 => "m/84h/1h/0h", - DescriptorType::Bip86 => "m/86h/1h/0h", - }; - - generate_bip_descriptor_from_key(network, key, derivation_path, descriptor_type) -} - -pub fn generate_new_descriptor_with_mnemonic( - network: Network, - descriptor_type: DescriptorType, -) -> Result { - let secp = Secp256k1::new(); - - // Generate a new BIP39 mnemonic - let mnemonic: GeneratedKey = - Mnemonic::generate((WordCount::Words12, Language::English)).map_err(|e| { - Error::MnemonicGenerationError(format!("Mnemonic generation failed: {:?}", e)) - })?; - - let seed = mnemonic.to_seed(""); - let xprv = - Xpriv::new_master(network, &seed).map_err(|e| Error::XprivCreationError(e.to_string()))?; - - let origin = xprv.fingerprint(&secp); - - let (derivation_base, external_fmt, internal_fmt) = match descriptor_type { - DescriptorType::Bip44 => ("/44h/1h/0h", "pkh", "pkh"), - DescriptorType::Bip49 => ("/49h/1h/0h", "sh(wpkh", "sh(wpkh"), - DescriptorType::Bip84 => ("/84h/1h/0h", "wpkh", "wpkh"), - DescriptorType::Bip86 => ("/86h/1h/0h", "tr", "tr"), - }; - let path = DerivationPath::from_str(&format!("m{}", derivation_base)) - .map_err(|e| Error::Generic(e.to_string()))?; - - let derived_xprv = xprv - .derive_priv(&secp, &path) - .map_err(|e| Error::Generic(e.to_string()))?; - - let xprv_str = derived_xprv.to_string(); - - // Construct descriptors - let external_desc = match descriptor_type { - DescriptorType::Bip49 => format!( - "{}([{}{}]{}{}))", - external_fmt, origin, derivation_base, xprv_str, "/0" - ), - _ => format!( - "{}([{}{}]{}{})", - external_fmt, origin, derivation_base, xprv_str, "/0" - ), - }; - - let internal_desc = match descriptor_type { - DescriptorType::Bip49 => format!( - "{}([{}{}]{}{}))", - internal_fmt, origin, derivation_base, xprv_str, "/1" - ), - _ => format!( - "{}([{}{}]{}{})", - internal_fmt, origin, derivation_base, xprv_str, "/1" - ), - }; - - // Parse descriptors - let (ext_desc, ext_keymap) = - Descriptor::::parse_descriptor(&secp, &external_desc) - .map_err(|e| Error::DescriptorParsingError(e.to_string()))?; - - let (int_desc, int_keymap) = - Descriptor::::parse_descriptor(&secp, &internal_desc).map_err( - |e| { - Error::DescriptorParsingError(format!("Failed to parse internal descriptor: {}", e)) - }, - )?; - - Ok(serde_json::json!({ - "type": descriptor_type.to_string(), - "mnemonic": mnemonic.to_string(), - "private_descriptors": { - "external": ext_desc.to_string_with_secret(&ext_keymap), - "internal": int_desc.to_string_with_secret(&int_keymap), - }, - "public_descriptors": { - "external": ext_desc.to_string(), - "internal": int_desc.to_string(), - } - })) +pub fn is_mnemonic(s: &str) -> bool { + let word_count = s.split_whitespace().count(); + (12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace()) } -pub fn generate_multipath_descriptor( +pub fn generate_descriptors( network: &Network, - script_type: u8, + descriptor_type: DescriptorType, key: &str, + multipath_label: bool, ) -> Result { - use DescriptorType::*; - - let descriptor_type = match script_type { - 44 => Bip44, - 49 => Bip49, - 84 => Bip84, - 86 => Bip86, - _ => return Err(Error::UnsupportedScriptType(script_type)), - }; - type DescriptorConstructor = fn(DescriptorPublicKey) -> Result, Error>; - let (derivation_base, descriptor_constructor): (&str, DescriptorConstructor) = - match descriptor_type { - Bip44 => ("/44h/1h/0h", |key| { - Descriptor::new_pkh(key).map_err(Error::from) - }), - Bip49 => ("/49h/1h/0h", |key| { - Descriptor::new_sh_wpkh(key).map_err(Error::from) - }), - Bip84 => ("/84h/1h/0h", |key| { - Descriptor::new_wpkh(key).map_err(Error::from) - }), - Bip86 => ("/86h/1h/0h", |key| { - Descriptor::new_tr(key, None).map_err(Error::from) - }), - }; + let purpose = match descriptor_type { + DescriptorType::Bip44 => 44, + DescriptorType::Bip49 => 49, + DescriptorType::Bip84 => 84, + DescriptorType::Bip86 => 86, + }; + + let derivation_base = format!("/{purpose}h/1h/0h"); + + let descriptor_constructor: DescriptorConstructor = match descriptor_type { + DescriptorType::Bip44 => |key| Descriptor::new_pkh(key).map_err(Error::from), + DescriptorType::Bip49 => |key| Descriptor::new_sh_wpkh(key).map_err(Error::from), + DescriptorType::Bip84 => |key| Descriptor::new_wpkh(key).map_err(Error::from), + DescriptorType::Bip86 => |key| Descriptor::new_tr(key, None).map_err(Error::from), + }; let secp = Secp256k1::new(); - let derivation_path = DerivationPath::from_str(&format!("m{}", derivation_base)) - .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; + let derivation_path = DerivationPath::from_str(&format!("m{derivation_base}"))?; - // Determine if it's an xprv or xpub let is_private = key.starts_with("xprv") || key.starts_with("tprv"); - // Use xprv or xpub accordingly type DescriptorBuilderFn = Box Result<(String, Option), Error>>; - let (fingerprint, make_desc): (_, DescriptorBuilderFn) = if is_private { - let xprv: Xpriv = key - .parse() - .map_err(|e| Error::InvalidKey(format!("Invalid xprv: {e}")))?; + let (fingerprint, make_desc): (Fingerprint, DescriptorBuilderFn) = if is_private { + let xprv: Xpriv = key.parse()?; let fingerprint = xprv.fingerprint(&secp); let closure = move |change: u32| -> Result<(String, Option), Error> { - let branch_path = DerivationPath::from_str(&change.to_string()) - .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; + let branch_path = DerivationPath::from_str(&change.to_string())?; let desc_xprv = DescriptorXKey { origin: Some((fingerprint, derivation_path.clone())), @@ -543,37 +433,32 @@ pub fn generate_multipath_descriptor( let desc_secret = DescriptorSecretKey::XPrv(desc_xprv.clone()); let (desc_key, keymap, _) = match descriptor_type { - DescriptorType::Bip84 | DescriptorType::Bip49 | DescriptorType::Bip44 => { - IntoDescriptorKey::::into_descriptor_key(desc_secret) - .map_err(|e| Error::DescriptorKeyError(e.to_string()))? - .extract(&secp) - .map_err(|e| Error::DescriptorKeyError(e.to_string()))? + DescriptorType::Bip44 => { + IntoDescriptorKey::::into_descriptor_key(desc_secret)?.extract(&secp)? + } + DescriptorType::Bip84 | DescriptorType::Bip49 => { + IntoDescriptorKey::::into_descriptor_key(desc_secret)? + .extract(&secp)? + } + DescriptorType::Bip86 => { + IntoDescriptorKey::::into_descriptor_key(desc_secret)?.extract(&secp)? } - DescriptorType::Bip86 => IntoDescriptorKey::::into_descriptor_key(desc_secret) - .map_err(|e| Error::DescriptorKeyError(e.to_string()))? - .extract(&secp) - .map_err(|e| Error::DescriptorKeyError(e.to_string()))?, }; - let public_descriptor = descriptor_constructor(desc_key.clone())?; - let private_descriptor = descriptor_constructor(desc_key)?; - + let descriptor = descriptor_constructor(desc_key)?; Ok(( - public_descriptor.to_string(), - Some(private_descriptor.to_string_with_secret(&keymap)), + descriptor.to_string(), + Some(descriptor.to_string_with_secret(&keymap)), )) }; (fingerprint, Box::new(closure)) } else { - let xpub: Xpub = key - .parse() - .map_err(|e| Error::InvalidKey(format!("Invalid xpub: {e}")))?; + let xpub: Xpub = key.parse()?; let fingerprint = xpub.fingerprint(); let closure = move |change: u32| -> Result<(String, Option), Error> { - let branch_path = DerivationPath::from_str(&change.to_string()) - .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; + let branch_path = DerivationPath::from_str(&change.to_string())?; let desc_xpub = DescriptorXKey { origin: Some((fingerprint, derivation_path.clone())), @@ -590,12 +475,17 @@ pub fn generate_multipath_descriptor( (fingerprint, Box::new(closure)) }; - // Build descriptors let (external_pub, external_priv) = make_desc(0)?; let (internal_pub, internal_priv) = make_desc(1)?; + let type_label = if multipath_label { + format!("{descriptor_type}-multipath") + } else { + descriptor_type.to_string() + }; + let mut result = json!({ - "type": format!("{}-multipath", descriptor_type), + "type": type_label, "public_descriptors": { "external": external_pub, "internal": internal_pub @@ -614,118 +504,37 @@ pub fn generate_multipath_descriptor( Ok(result) } -pub fn generate_bip_descriptor_from_key( - network: &Network, - key: &str, - derivation_path_str: &str, +pub fn generate_new_descriptor_with_mnemonic( + network: Network, descriptor_type: DescriptorType, ) -> Result { - let secp = Secp256k1::new(); - - let derivation_path: DerivationPath = derivation_path_str - .parse() - .map_err(|e| Error::InvalidDerivationPath(format!("DerivationPath Error: {e}")))?; - - let xprv: Xpriv = key - .parse() - .map_err(|e| Error::InvalidKey(format!("Invalid xprv: {e}")))?; - - let fingerprint = xprv.fingerprint(&secp); - - let make_desc_key = |branch: u32| -> Result<(String, String), Error> { - let branch_path = DerivationPath::from(vec![ChildNumber::Normal { index: branch }]); - - let desc_xprv = DescriptorXKey { - origin: Some((fingerprint, derivation_path.clone())), - xkey: xprv, - derivation_path: branch_path.clone(), - wildcard: Wildcard::Unhardened, - }; - - let desc_secret = DescriptorSecretKey::XPrv(desc_xprv.clone()); - - let (desc_key, keymap, _) = - IntoDescriptorKey::::into_descriptor_key(desc_secret.clone()) - .map_err(|e| Error::DescriptorKeyError(e.to_string()))? - .extract(&secp) - .map_err(|e| Error::DescriptorKeyError(e.to_string()))?; - - let public_descriptor = match descriptor_type { - DescriptorType::Bip84 => Descriptor::new_wpkh(desc_key.clone())?, - DescriptorType::Bip86 => Descriptor::new_tr(desc_key.clone(), None)?, - DescriptorType::Bip49 => Descriptor::new_sh_wpkh(desc_key.clone())?, - DescriptorType::Bip44 => Descriptor::new_pkh(desc_key.clone())?, - }; - - let private_descriptor = match descriptor_type { - DescriptorType::Bip84 => Descriptor::new_wpkh(desc_key)?, - DescriptorType::Bip86 => Descriptor::new_tr(desc_key, None)?, - DescriptorType::Bip49 => Descriptor::new_sh_wpkh(desc_key)?, - DescriptorType::Bip44 => Descriptor::new_pkh(desc_key)?, - }; - - Ok(( - public_descriptor.to_string(), - private_descriptor.to_string_with_secret(&keymap), - )) - }; + let mnemonic: GeneratedKey = + Mnemonic::generate((WordCount::Words12, Language::English)).map_err(Error::BIP39Error)?; - let (external_pub, external_priv) = make_desc_key(0)?; - let (internal_pub, internal_priv) = make_desc_key(1)?; + let seed = mnemonic.to_seed(""); + let xprv = Xpriv::new_master(network, &seed)?; - Ok(json!({ - "type": descriptor_type.to_string(), - "fingerprint": fingerprint.to_string(), - "network": network.to_string(), - "private_descriptors": { - "external": external_priv, - "internal": internal_priv - }, - "public_descriptors": { - "external": external_pub, - "internal": internal_pub - } - })) + let mut result = generate_descriptors(&network, descriptor_type, &xprv.to_string(), false)?; + result["mnemonic"] = json!(mnemonic.to_string()); + Ok(result) } pub fn generate_descriptor_from_mnemonic_string( mnemonic_str: &str, network: Network, - derivation_path_str: &str, descriptor_type: DescriptorType, ) -> Result { - let secp = Secp256k1::new(); - - let mnemonic = Mnemonic::parse_in(Language::English, mnemonic_str) - .map_err(|e| Error::Generic(e.to_string()))?; - let ext_key: ExtendedKey = mnemonic - .into_extended_key() - .map_err(|e| Error::Generic(e.to_string()))?; + 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 _fingerprint = xprv.fingerprint(&secp); - let derivation_path: DerivationPath = derivation_path_str - .parse() - .map_err(|e| Error::InvalidDerivationPath(format!("DerivationPath Error: {e}")))?; - - let xprv = xprv - .derive_priv(&secp, &derivation_path) - .map_err(|e| Error::InvalidKey(format!("Failed to derive xprv: {e}")))?; - - generate_bip_descriptor_from_key( - &network, - &xprv.to_string(), - derivation_path_str, - descriptor_type, - ) + let mut result = generate_descriptors(&network, descriptor_type, &xprv.to_string(), false)?; + result["mnemonic"] = json!(mnemonic_str); + Ok(result) } -pub fn is_mnemonic(s: &str) -> bool { - let word_count = s.split_whitespace().count(); - (12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace()) -} // Enum for descriptor types #[derive(Debug, Clone, Copy)] pub enum DescriptorType { @@ -734,3 +543,22 @@ pub enum DescriptorType { Bip84, Bip86, } + +impl Display for DescriptorType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let s = match self { + DescriptorType::Bip44 => "bip44", + DescriptorType::Bip49 => "bip49", + DescriptorType::Bip84 => "bip84", + DescriptorType::Bip86 => "bip86", + }; + write!(f, "{s}") + } +} + +pub(crate) fn shorten(displayable: impl Display, start: u8, end: u8) -> String { + let displayable = displayable.to_string(); + let start_str: &str = &displayable[0..start as usize]; + let end_str: &str = &displayable[displayable.len() - end as usize..]; + format!("{start_str}...{end_str}") +}