-
Notifications
You must be signed in to change notification settings - Fork 75
Adding descriptor generator #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
ebf6be3
ed97b4e
7198376
6b743a4
4e51df4
c6a5d84
8b44166
afead59
19460cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,6 @@ | |
//! All subcommands are defined in the below enums. | ||
|
||
#![allow(clippy::large_enum_variant)] | ||
|
||
use bdk_wallet::bitcoin::{ | ||
bip32::{DerivationPath, Xpriv}, | ||
Address, Network, OutPoint, ScriptBuf, | ||
|
@@ -104,6 +103,42 @@ pub enum CliSubCommand { | |
#[command(flatten)] | ||
wallet_opts: WalletOpts, | ||
}, | ||
/// Generate a Bitcoin descriptor either from a provided XPRV or by generating a new random mnemonic. | ||
/// | ||
/// This function supports two modes: | ||
/// | ||
/// 1. **Using a provided XPRV**: | ||
/// - Generates BIP32-based descriptors from the provided extended private key. | ||
/// - Derives both external (`/0/*`) and internal (`/1/*`) paths. | ||
/// - Automatically detects the script type from the `--type` flag (e.g., BIP44, BIP49, BIP84, BIP86). | ||
/// | ||
/// 2. **Generating a new mnemonic**: | ||
/// - Creates a new 12-word BIP39 mnemonic phrase. | ||
/// - Derives a BIP32 root XPRV using the standard derivation path based on the selected script type. | ||
/// - Constructs external and internal descriptors using that XPRV. | ||
/// | ||
/// The output is a prettified JSON object containing: | ||
/// - `mnemonic` (if generated): the 12-word seed phrase. | ||
/// - `external`: public and private descriptors for receive addresses (`/0/*`) | ||
/// - `internal`: public and private descriptors for change addresses (`/1/*`) | ||
/// - `fingerprint`: master key fingerprint used in the descriptors | ||
/// - `network`: either `mainnet`, `testnet`, `signet`, or `regtest` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. , |
||
/// - `type`: one of `bip44`, `bip49`, `bip84`, or `bip86` | ||
/// | ||
/// > ⚠️ **Security Warning**: This feature is intended for testing and development purposes. | ||
/// > Do **not** use generated descriptors or mnemonics to secure real Bitcoin funds on mainnet. | ||
/// | ||
Descriptor(GenerateDescriptorArgs), | ||
} | ||
#[derive(Debug, Clone, PartialEq, Args)] | ||
pub struct GenerateDescriptorArgs { | ||
#[clap(long, value_parser = clap::value_parser!(u8).range(44..=86))] | ||
pub r#type: u8, // 44, 49, 84, 86 | ||
|
||
#[clap(long)] | ||
pub multipath: bool, | ||
|
||
pub key: Option<String>, // Positional argument (tprv/tpub/xprv/xpub) | ||
} | ||
|
||
/// Wallet operation subcommands. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,11 +20,28 @@ use bdk_wallet::bip39::{Language, Mnemonic}; | |
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::secp256k1::Secp256k1; | ||
use bdk_wallet::bitcoin::Network; | ||
use bdk_wallet::bitcoin::{secp256k1::Secp256k1, Txid}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason why you do delete already existing imports? |
||
use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, Sequence}; | ||
use bdk_wallet::descriptor::Segwitv0; | ||
use bdk_wallet::keys::bip39::WordCount; | ||
use bdk_wallet::keys::{GeneratableKey, GeneratedKey}; | ||
use serde::ser::Error as SerdeErrorTrait; | ||
use serde_json::json; | ||
use serde_json::Error as SerdeError; | ||
use serde_json::Value; | ||
use std::fmt; | ||
|
||
use std::str::FromStr; | ||
|
||
#[cfg(any( | ||
feature = "electrum", | ||
feature = "esplora", | ||
feature = "cbf", | ||
feature = "rpc" | ||
))] | ||
use bdk_wallet::bitcoin::Transaction; | ||
use bdk_wallet::bitcoin::Txid; | ||
use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, Sequence}; | ||
#[cfg(feature = "sqlite")] | ||
use bdk_wallet::rusqlite::Connection; | ||
#[cfg(feature = "compiler")] | ||
|
@@ -35,16 +52,14 @@ use bdk_wallet::{ | |
use bdk_wallet::{KeychainKind, SignOptions, Wallet}; | ||
|
||
use bdk_wallet::keys::DescriptorKey::Secret; | ||
use bdk_wallet::keys::{DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey}; | ||
use bdk_wallet::keys::{DerivableKey, DescriptorKey, ExtendedKey}; | ||
use bdk_wallet::miniscript::miniscript; | ||
use serde_json::json; | ||
use std::collections::BTreeMap; | ||
#[cfg(any(feature = "electrum", feature = "esplora"))] | ||
use std::collections::HashSet; | ||
use std::convert::TryFrom; | ||
#[cfg(feature = "repl")] | ||
use std::io::Write; | ||
use std::str::FromStr; | ||
|
||
#[cfg(feature = "electrum")] | ||
use crate::utils::BlockchainClient::Electrum; | ||
|
@@ -820,10 +835,24 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> { | |
} | ||
Ok("".to_string()) | ||
} | ||
CliSubCommand::Descriptor(args) => { | ||
let network = cli_opts.network; // Or just use cli_opts directly | ||
let json = handle_generate_descriptor(args.clone(), network)?; | ||
Ok(json) | ||
} | ||
}; | ||
result.map_err(|e| e.into()) | ||
} | ||
|
||
pub fn handle_generate_descriptor( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there is need for this fn. You can pretty print in the top-level handler(handle_command fn) |
||
args: GenerateDescriptorArgs, | ||
network: Network, | ||
) -> Result<String, SerdeError> { | ||
let descriptor = generate_descriptor_from_args(args, network) | ||
.map_err(|e| SerdeErrorTrait::custom(e.to_string()))?; | ||
serde_json::to_string_pretty(&descriptor) | ||
} | ||
|
||
#[cfg(feature = "repl")] | ||
async fn respond( | ||
network: Network, | ||
|
@@ -915,3 +944,81 @@ mod test { | |
assert!(is_final(&full_signed_psbt).is_ok()); | ||
} | ||
} | ||
|
||
pub fn generate_descriptor_from_args( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move your code above the test module |
||
args: GenerateDescriptorArgs, | ||
network: Network, | ||
) -> Result<serde_json::Value, Error> { | ||
match (args.multipath, args.key.as_ref()) { | ||
(true, Some(key)) => generate_multipath_descriptor(&network, args.r#type, key), | ||
(false, Some(key)) => generate_standard_descriptor(&network, args.r#type, key), | ||
(false, None) => { | ||
// New default: generate descriptor from fresh mnemonic (for script_type 84 only maybe) | ||
if args.r#type == 84 { | ||
generate_new_bip84_descriptor_with_mnemonic(network) | ||
} else { | ||
Err(Error::Generic( | ||
"Only script type 84 is supported for mnemonic-based generation".to_string(), | ||
)) | ||
} | ||
} | ||
_ => Err(Error::InvalidArguments( | ||
"Invalid arguments: please provide a key or a weak string".to_string(), | ||
)), | ||
} | ||
} | ||
|
||
pub fn generate_standard_descriptor( | ||
network: &Network, | ||
script_type: u8, | ||
key: &str, | ||
) -> Result<Value, Error> { | ||
match script_type { | ||
84 => generate_bip84_descriptor_from_key(network, key), | ||
86 => generate_bip86_descriptor_from_key(network, key), | ||
49 => generate_bip49_descriptor_from_key(network, key), | ||
44 => generate_bip44_descriptor_from_key(network, key), | ||
_ => Err(Error::UnsupportedScriptType(script_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) | ||
} | ||
} | ||
|
||
// Wrapper functions for the specific BIP types | ||
pub fn generate_bip84_descriptor_from_key( | ||
network: &Network, | ||
key: &str, | ||
) -> Result<serde_json::Value, Error> { | ||
generate_bip_descriptor_from_key(network, key, "m/84h/1h/0h", DescriptorType::Bip84) | ||
} | ||
|
||
pub fn generate_bip86_descriptor_from_key( | ||
network: &Network, | ||
key: &str, | ||
) -> Result<serde_json::Value, Error> { | ||
generate_bip_descriptor_from_key(network, key, "m/86h/1h/0h", DescriptorType::Bip86) | ||
} | ||
|
||
pub fn generate_bip49_descriptor_from_key( | ||
network: &Network, | ||
key: &str, | ||
) -> Result<serde_json::Value, Error> { | ||
generate_bip_descriptor_from_key(network, key, "m/49h/1h/0h", DescriptorType::Bip49) | ||
} | ||
|
||
pub fn generate_bip44_descriptor_from_key( | ||
network: &Network, | ||
key: &str, | ||
) -> Result<serde_json::Value, Error> { | ||
generate_bip_descriptor_from_key(network, key, "m/44h/1h/0h", DescriptorType::Bip44) | ||
} | ||
AmosOO7 marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the key could either be Xprv or Xpub
/// Generate a Bitcoin descriptor either from a key (Xprv, Xpub) or by generating a random mnemonic