]> Untitled Git - bdk-cli/commitdiff
feat: add descriptor generation
authorAmosOO7 <ayokunene@gmail.com>
Wed, 30 Apr 2025 20:31:23 +0000 (21:31 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Mon, 3 Nov 2025 10:43:33 +0000 (11:43 +0100)
- Created Subcommnds for the descriptor command;
generate
- Created function to get descriptors from mnemonics

src/commands.rs
src/error.rs
src/handlers.rs
src/utils.rs

index 54cddf044c469660fd013793aed3ef90ebb368a8..09cbc5dd342ac2d04a0e120e8de2885527b9ee52 100644 (file)
 //! All subcommands are defined in the below enums.
 
 #![allow(clippy::large_enum_variant)]
-
 use bdk_wallet::bitcoin::{
     Address, Network, OutPoint, ScriptBuf,
     bip32::{DerivationPath, Xpriv},
 };
-use clap::{Args, Parser, Subcommand, ValueEnum, value_parser};
+use clap::{Args, Parser, Subcommand, ValueEnum, builder::TypedValueParser, value_parser};
 
 #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
 use crate::utils::parse_proxy_auth;
@@ -107,8 +106,15 @@ pub enum CliSubCommand {
         #[command(flatten)]
         wallet_opts: WalletOpts,
     },
+    /// Output Descriptors operations.
+    ///
+    /// Generate output descriptors from either extended key (Xprv/Xpub) or mnemonic phrase.
+    /// This feature is intended for development and testing purposes only.
+    Descriptor {
+        #[clap(subcommand)]
+        subcommand: DescriptorSubCommand,
+    },
 }
-
 /// Wallet operation subcommands.
 #[derive(Debug, Subcommand, Clone, PartialEq)]
 pub enum WalletSubCommand {
@@ -473,3 +479,27 @@ pub enum ReplSubCommand {
     /// Exit REPL loop.
     Exit,
 }
+/// Subcommands for Key operations.
+#[derive(Debug, Subcommand, Clone, PartialEq, Eq)]
+pub enum DescriptorSubCommand {
+    /// Generate a descriptor
+    Generate {
+        /// Descriptor type (script type).
+        #[arg(
+            long = "type",
+            short = 't',
+            value_parser = clap::builder::PossibleValuesParser::new(["44", "49", "84", "86"])
+                .map(|s| s.parse::<u8>().unwrap()),
+            default_value = "84"
+        )]
+        r#type: u8,
+        /// Enable multipath descriptors
+        #[arg(long = "multipath", short = 'm', default_value_t = false)]
+        multipath: bool,
+        /// Optional key input
+        key: Option<String>,
+    },
+
+    /// Show info about a given descriptor
+    Info { descriptor: String },
+}
index 5f548d91d5f3b087b95afbb870f3bb1fda56a47b..3916f26161c8be22666507df6f2f475a14b8854b 100644 (file)
@@ -103,6 +103,30 @@ 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<ExtractTxError> for BDKCliError {
index d9d2cbef4afae8e62408ecfd3aea74f1199dce16..9264e47737deac9ab46fb69dc84fac2aad4c1fc8 100644 (file)
@@ -35,6 +35,14 @@ 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};
@@ -42,25 +50,40 @@ 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;
@@ -72,7 +95,7 @@ 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};
@@ -1260,6 +1283,15 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             }
             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
 }
@@ -1333,6 +1365,103 @@ fn readline() -> Result<String, Error> {
     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(
index cb81074376544bdb9dc900771d8b7e2b530b1fa1..93392ba5ae7f441cb84f039c51fcac8afb4e4aee 100644 (file)
@@ -10,7 +10,7 @@
 //!
 //! 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};
@@ -35,6 +35,26 @@ use bdk_wallet::Wallet;
 #[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();
@@ -363,4 +383,354 @@ pub(crate) fn shorten(displayable: impl Display, start: u8, end: u8) -> 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<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,
 }