Skip to content

Commit 39da21b

Browse files
committed
implemented descriptor funtion
1 parent 0fd5697 commit 39da21b

File tree

5 files changed

+89
-22
lines changed

5 files changed

+89
-22
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ serde_json = "1.0"
2121
thiserror = "2.0.11"
2222
tokio = { version = "1", features = ["full"] }
2323
anyhow = "1.0"
24+
serde = { version = "1.0", features = ["derive"] }
2425

2526
# Optional dependencies
2627
bdk_bitcoind_rpc = { version = "0.18.0", optional = true }

src/commands.rs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,54 @@ pub enum CliSubCommand {
109109
#[command(flatten)]
110110
wallet_opts: WalletOpts,
111111
},
112-
GenerateDescriptor {
113-
/// Optional BIP39 mnemonic phrase (if not provided, one will be randomly generated).
114-
#[arg(short, long)]
115-
mnemonic: Option<String>,
116-
/// Password for mnemonic (optional).
117-
#[arg(short, long)]
118-
password: Option<String>,
119-
},
112+
/// Generate a random Bitcoin descriptor and mnemonic.
113+
///
114+
/// This function generates a new 12-word BIP39 mnemonic phrase and uses it to derive a BIP32
115+
/// extended private key (XPRV). It constructs two BIP86-compatible single-sig descriptors:
116+
/// - An **external descriptor** used for receiving funds (`/0/*`)
117+
/// - An **internal descriptor** used for change outputs (`/1/*`)
118+
///
119+
/// These descriptors follow the standard derivation path `m/86h/0h/0h` for mainnet (or `86h/1h/0h`
120+
/// for testnet), and use `wpkh` (native SegWit) script format.
121+
///
122+
/// The output includes both the public and private forms of the descriptors, along with the mnemonic.
123+
///
124+
/// > ⚠️ This feature is **EXPERIMENTAL** and is intended for testing or development. Do **not** use
125+
/// > this output to secure real Bitcoin funds on mainnet.
126+
///
127+
/// Returns a JSON object containing:
128+
/// - `mnemonic`: the 12-word phrase
129+
/// - `external_descriptor`: public and private forms for receive addresses
130+
/// - `internal_descriptor`: public and private forms for change addresses
131+
132+
Descriptor(GenerateDescriptorArgs),
120133
}
134+
#[derive(Debug, Clone, PartialEq, Args)]
135+
pub struct GenerateDescriptorArgs {
136+
/// Script type: BIP44 (Legacy), BIP49 (Nested), BIP84 (Native SegWit), or BIP86 (Taproot)
137+
#[clap(value_enum)]
138+
pub script_type: ScriptType,
139+
140+
/// Use multipath derivation (e.g. m/44'/0'/0'/*/*)
141+
#[clap(long)]
142+
pub multipath: bool,
121143

144+
/// Use weak key string instead of a fingerprinted xpub
145+
#[clap(long)]
146+
pub weak_keys: bool,
147+
148+
/// Specify the network: bitcoin, testnet, regtest, signet
149+
#[clap(long, default_value = "testnet")]
150+
pub network: Network,
151+
}
152+
153+
#[derive(Debug, Clone, PartialEq, ValueEnum)]
154+
pub enum ScriptType {
155+
Bip44,
156+
Bip49,
157+
Bip84,
158+
Bip86,
159+
}
122160
/// Wallet operation subcommands.
123161
#[derive(Debug, Subcommand, Clone, PartialEq)]
124162
pub enum WalletSubCommand {

src/descriptor_handler.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::commands::{GenerateDescriptorArgs, ScriptType};
2+
use bdk_wallet::bitcoin::Network as BdkNetwork;
13
use bdk_wallet::keys::{GeneratableKey, GeneratedKey};
24
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
35
use bdk_wallet::bitcoin::Network;
@@ -7,27 +9,41 @@ use bdk_wallet::bitcoin::bip32::Xpriv;
79
use bdk_wallet::descriptor::Segwitv0;
810
use anyhow::{Result, anyhow};
911

10-
pub fn handle_descriptor_generation(
11-
network: Network,
12-
_mnemonic_opt: Option<String>,
13-
password: Option<String>,
14-
) -> Result<serde_json::Value> {
12+
pub fn generate_descriptor_from_args(args: GenerateDescriptorArgs) -> Result<serde_json::Value, anyhow::Error> {
13+
// Convert CLI network to BDK network
14+
let bdk_network = match args.network {
15+
Network::Bitcoin => BdkNetwork::Bitcoin,
16+
Network::Testnet => BdkNetwork::Testnet,
17+
Network::Testnet4 => BdkNetwork::Testnet4,
18+
Network::Regtest => BdkNetwork::Regtest,
19+
Network::Signet => BdkNetwork::Signet,
20+
_ => todo!(),
21+
};
22+
23+
match args.script_type {
24+
ScriptType::Bip84 => {
25+
let json = generate_bip84_descriptor(bdk_network)?;
26+
Ok(json)
27+
}
28+
_ => Err(anyhow!("Script type not supported yet")),
29+
}
30+
31+
}
32+
33+
pub fn generate_bip84_descriptor(network: Network) -> Result<serde_json::Value> {
1534
let secp = Secp256k1::new();
1635

1736
let mnemonic: GeneratedKey<Mnemonic, Segwitv0> = Mnemonic::generate((WordCount::Words12, Language::English))
1837
.map_err(|e| anyhow!("Mnemonic generation failed: {:?}", e))?;
1938

20-
let password = password.unwrap_or_default();
21-
let seed = mnemonic.to_seed(&password);
22-
39+
let seed = mnemonic.to_seed("");
2340
let xprv = Xpriv::new_master(network, &seed)
2441
.map_err(|e| anyhow!("Failed to create extended private key: {}", e))?;
2542

2643
let origin = xprv.fingerprint(&secp);
27-
let deriv_base = "/84h/1h/0h";
44+
let deriv_base = "/84h/1h/0h"; // You might want to dynamically compute this based on args
2845
let xprv_str = xprv.to_string();
2946

30-
// Correct format strings with 4 placeholders
3147
let external_desc = format!("wpkh([{}{}]{}{})", origin, deriv_base, xprv_str, "/0/*");
3248
let internal_desc = format!("wpkh([{}{}]{}{})", origin, deriv_base, xprv_str, "/1/*");
3349

src/handlers.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ use bdk_wallet::bitcoin::bip32::{DerivationPath, KeySource};
2929
use bdk_wallet::bitcoin::consensus::encode::serialize_hex;
3030
use bdk_wallet::bitcoin::script::PushBytesBuf;
3131
use bdk_wallet::bitcoin::Network;
32+
use serde_json::Error as SerdeError;
33+
use serde::ser::Error as SerdeErrorTrait;
34+
3235
#[cfg(any(
3336
feature = "electrum",
3437
feature = "esplora",
@@ -825,14 +828,22 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
825828
}
826829
Ok("".to_string())
827830
}
828-
CliSubCommand::GenerateDescriptor { mnemonic, password } => {
829-
let result = handle_descriptor_generation(network, mnemonic, password).map_err(|e| Error::Generic(e.to_string()))?;
830-
serde_json::to_string_pretty(&result)
831-
}
831+
CliSubCommand::Descriptor(args) => handle_generate_descriptor(args),
832832
};
833833
result.map_err(|e| e.into())
834834
}
835835

836+
837+
838+
pub fn handle_generate_descriptor(args: GenerateDescriptorArgs) -> Result<String, SerdeError> {
839+
let descriptor = generate_descriptor_from_args(args)
840+
.map_err(|e| SerdeErrorTrait::custom(e.to_string()))?;
841+
serde_json::to_string_pretty(&descriptor)
842+
}
843+
844+
845+
846+
836847
#[cfg(feature = "repl")]
837848
async fn respond(
838849
network: Network,

0 commit comments

Comments
 (0)