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};
use bdk_wallet::{
descriptor::{Descriptor, Legacy, Miniscript},
miniscript::{Tap, descriptor::TapTree, policy::Concrete},
+ descriptor::{Legacy, Miniscript},
+ miniscript::policy::Concrete,
};
use cli_table::{Cell, CellStruct, Style, Table, format::Justify};
use serde_json::json;
+use bdk_wallet::{KeychainKind, SignOptions, Wallet};
+
+#[cfg(feature = "electrum")]
+use crate::utils::BlockchainClient::Electrum;
+#[cfg(feature = "cbf")]
+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 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(any(feature = "redb", feature = "compiler"))]
-use std::sync::Arc;
#[cfg(feature = "electrum")]
use crate::utils::BlockchainClient::Electrum;
#[cfg(feature = "cbf")]
-use bdk_kyoto::LightClient;
-#[cfg(feature = "compiler")]
-use bdk_wallet::bitcoin::XOnlyPublicKey;
+use bdk_kyoto::{Info, LightClient};
use bdk_wallet::bitcoin::base64::prelude::*;
#[cfg(feature = "cbf")]
use tokio::select;
))]
use {
crate::commands::OnlineWalletSubCommand::*,
- bdk_wallet::bitcoin::{Transaction, consensus::Decodable, hex::FromHex},
+ bdk_wallet::bitcoin::{consensus::Decodable, hex::FromHex, Transaction},
};
#[cfg(feature = "esplora")]
use {crate::utils::BlockchainClient::Esplora, bdk_esplora::EsploraAsyncExt};
}
Ok("".to_string())
}
+ CliSubCommand::Descriptor {
+ 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 json = serde_json::to_string_pretty(&descriptor)?;
+ Ok(json)
+ }
};
result
}
Ok(buffer)
}
+pub fn handle_descriptor_subcommand(
+ network: Network,
+ subcommand: DescriptorSubCommand,
+) -> Result<Value, Error> {
+ match subcommand {
+ DescriptorSubCommand::Generate {
+ r#type,
+ 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)),
+ };
+
+ match (multipath, key.as_ref()) {
+ (true, Some(k)) => generate_multipath_descriptor(&network, r#type, k),
+ (false, Some(k)) => {
+ if is_mnemonic(k) {
+ generate_descriptor_from_mnemonic_string(
+ k,
+ network,
+ derivation_path_str,
+ descriptor_type,
+ )
+ } else {
+ generate_standard_descriptor(&network, r#type, k)
+ }
+ }
+ (false, None) => generate_new_descriptor_with_mnemonic(network, descriptor_type),
+ _ => Err(Error::InvalidArguments(
+ "Provide a key or weak string".to_string(),
+ )),
+ }
+ }
+ DescriptorSubCommand::Info { descriptor } => {
+ let parsed: Descriptor<DescriptorPublicKey> = 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<Value, Error> {
+ 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)
+ }
+}
+
+#[cfg(any(
+ feature = "electrum",
+ feature = "esplora",
+ feature = "cbf",
+ feature = "rpc"
+))]
#[cfg(test)]
mod test {
#[cfg(any(
//!
//! This module includes all the utility tools used by the App.
use crate::error::BDKCliError as Error;
-use std::fmt::Display;
+use std::{fmt::Display, path::{Path, PathBuf}, str::FromStr};
use std::str::FromStr;
use std::path::{Path, PathBuf};
#[cfg(any(feature = "sqlite", feature = "redb"))]
use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
+use bdk_wallet::bip39::{Language, Mnemonic};
+use bdk_wallet::bitcoin::bip32::ChildNumber;
+use bdk_wallet::bitcoin::{
+ bip32::{DerivationPath, Xpriv, Xpub},
+ secp256k1::Secp256k1,
+};
+use bdk_wallet::descriptor::{
+ Segwitv0, {Descriptor, DescriptorPublicKey},
+};
+use bdk_wallet::keys::{
+ DerivableKey, ExtendedKey,
+ bip39::WordCount,
+ {DescriptorSecretKey, GeneratableKey, GeneratedKey, IntoDescriptorKey},
+};
+use bdk_wallet::miniscript::{
+ Tap,
+ descriptor::{DescriptorXKey, Wildcard},
+};
+use serde_json::{Value, json};
+
/// Parse the recipient (Address,Amount) argument from cli input.
pub(crate) fn parse_recipient(s: &str) -> Result<(ScriptBuf, u64), String> {
let parts: Vec<_> = s.split(':').collect();
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<serde_json::Value, Error> {
+ 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<serde_json::Value, Error> {
+ let secp = Secp256k1::new();
+
+ // Generate a new BIP39 mnemonic
+ let mnemonic: GeneratedKey<Mnemonic, Segwitv0> =
+ 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::<DescriptorPublicKey>::parse_descriptor(&secp, &external_desc)
+ .map_err(|e| Error::DescriptorParsingError(e.to_string()))?;
+
+ let (int_desc, int_keymap) =
+ Descriptor::<DescriptorPublicKey>::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 generate_multipath_descriptor(
+ network: &Network,
+ script_type: u8,
+ key: &str,
+) -> Result<Value, Error> {
+ 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<Descriptor<DescriptorPublicKey>, 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 secp = Secp256k1::new();
+ let derivation_path = DerivationPath::from_str(&format!("m{}", derivation_base))
+ .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?;
+
+ // 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<dyn Fn(u32) -> Result<(String, Option<String>), 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 = xprv.fingerprint(&secp);
+
+ let closure = move |change: u32| -> Result<(String, Option<String>), Error> {
+ let branch_path = DerivationPath::from_str(&change.to_string())
+ .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?;
+
+ let desc_xprv = DescriptorXKey {
+ origin: Some((fingerprint, derivation_path.clone())),
+ xkey: xprv,
+ derivation_path: branch_path,
+ wildcard: Wildcard::Unhardened,
+ };
+
+ let desc_secret = DescriptorSecretKey::XPrv(desc_xprv.clone());
+ let (desc_key, keymap, _) = match descriptor_type {
+ DescriptorType::Bip84 | DescriptorType::Bip49 | DescriptorType::Bip44 => {
+ IntoDescriptorKey::<Segwitv0>::into_descriptor_key(desc_secret)
+ .map_err(|e| Error::DescriptorKeyError(e.to_string()))?
+ .extract(&secp)
+ .map_err(|e| Error::DescriptorKeyError(e.to_string()))?
+ }
+ DescriptorType::Bip86 => IntoDescriptorKey::<Tap>::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)?;
+
+ Ok((
+ public_descriptor.to_string(),
+ Some(private_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 fingerprint = xpub.fingerprint();
+
+ let closure = move |change: u32| -> Result<(String, Option<String>), Error> {
+ let branch_path = DerivationPath::from_str(&change.to_string())
+ .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?;
+
+ let desc_xpub = DescriptorXKey {
+ origin: Some((fingerprint, derivation_path.clone())),
+ xkey: xpub,
+ derivation_path: branch_path,
+ wildcard: Wildcard::Unhardened,
+ };
+
+ let desc_key = DescriptorPublicKey::XPub(desc_xpub);
+ let descriptor = descriptor_constructor(desc_key)?;
+ Ok((descriptor.to_string(), None))
+ };
+
+ (fingerprint, Box::new(closure))
+ };
+
+ // Build descriptors
+ let (external_pub, external_priv) = make_desc(0)?;
+ let (internal_pub, internal_priv) = make_desc(1)?;
+
+ let mut result = json!({
+ "type": format!("{}-multipath", descriptor_type),
+ "public_descriptors": {
+ "external": external_pub,
+ "internal": internal_pub
+ },
+ "fingerprint": fingerprint.to_string(),
+ "network": network.to_string(),
+ });
+
+ if let (Some(priv_ext), Some(priv_int)) = (external_priv, internal_priv) {
+ result["private_descriptors"] = json!({
+ "external": priv_ext,
+ "internal": priv_int
+ });
+ }
+
+ Ok(result)
+}
+
+pub fn generate_bip_descriptor_from_key(
+ network: &Network,
+ key: &str,
+ derivation_path_str: &str,
+ descriptor_type: DescriptorType,
+) -> Result<serde_json::Value, Error> {
+ 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::<Segwitv0>::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 (external_pub, external_priv) = make_desc_key(0)?;
+ let (internal_pub, internal_priv) = make_desc_key(1)?;
+
+ 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
+ }
+ }))
+}
+
+pub fn generate_descriptor_from_mnemonic_string(
+ mnemonic_str: &str,
+ network: Network,
+ derivation_path_str: &str,
+ descriptor_type: DescriptorType,
+) -> Result<serde_json::Value, Error> {
+ 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 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,
+ )
+}
+
+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 {
+ Bip44,
+ Bip49,
+ Bip84,
+ Bip86,
}