From 9d4381c8fe636d2d1d63b394b246e939f1192de7 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 15 Jun 2025 23:14:26 -0500 Subject: [PATCH 1/4] Improve `Xpriv::derive_xpriv` and `Xpub::derive_xpub` ergonomics This change enables references to slices, arrays, and Vecs to be passed to derive methods. --- bitcoin/src/bip32.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs index 23b7d368c..b7659e072 100644 --- a/bitcoin/src/bip32.rs +++ b/bitcoin/src/bip32.rs @@ -756,7 +756,7 @@ impl Xpriv { pub fn derive_priv>( &self, secp: &Secp256k1, - path: &P, + path: P, ) -> Result { self.derive_xpriv(secp, path) } @@ -767,7 +767,7 @@ impl Xpriv { pub fn derive_xpriv>( &self, secp: &Secp256k1, - path: &P, + path: P, ) -> Result { let mut sk: Xpriv = *self; for cnum in path.as_ref() { @@ -910,7 +910,7 @@ impl Xpub { pub fn derive_pub>( &self, secp: &Secp256k1, - path: &P, + path: P, ) -> Result { self.derive_xpub(secp, path) } @@ -921,7 +921,7 @@ impl Xpub { pub fn derive_xpub>( &self, secp: &Secp256k1, - path: &P, + path: P, ) -> Result { let mut pk: Xpub = *self; for cnum in path.as_ref() { From c7bdec14fbf79097c4d73051cc0d594cf9b61e8b Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 17 Jun 2025 14:37:23 -0500 Subject: [PATCH 2/4] Fix clippy lint and formatting for `Xpriv::derive_xpriv` and `Xpriv::derive_xpub` calls --- bitcoin/examples/bip32.rs | 2 +- bitcoin/examples/ecdsa-psbt-simple.rs | 4 ++-- bitcoin/examples/ecdsa-psbt.rs | 2 +- bitcoin/examples/taproot-psbt-simple.rs | 4 ++-- bitcoin/examples/taproot-psbt.rs | 11 ++++------- bitcoin/src/psbt/mod.rs | 2 +- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/bitcoin/examples/bip32.rs b/bitcoin/examples/bip32.rs index 41f3791ea..d8edab2fc 100644 --- a/bitcoin/examples/bip32.rs +++ b/bitcoin/examples/bip32.rs @@ -46,7 +46,7 @@ fn main() { // generate first receiving address at m/0/0 // manually creating indexes this time let zero = ChildNumber::ZERO_NORMAL; - let public_key = xpub.derive_xpub(&secp, &[zero, zero]).unwrap().public_key; + let public_key = xpub.derive_xpub(&secp, [zero, zero]).unwrap().public_key; let address = Address::p2wpkh(CompressedPublicKey(public_key), KnownHrp::Mainnet); println!("First receiving address: {address}"); } diff --git a/bitcoin/examples/ecdsa-psbt-simple.rs b/bitcoin/examples/ecdsa-psbt-simple.rs index 73f209bb2..1798f6edf 100644 --- a/bitcoin/examples/ecdsa-psbt-simple.rs +++ b/bitcoin/examples/ecdsa-psbt-simple.rs @@ -67,7 +67,7 @@ fn get_external_address_xpriv( let external_index = ChildNumber::ZERO_NORMAL; let idx = ChildNumber::from_normal_idx(index).expect("valid index number"); - child_xpriv.derive_xpriv(secp, &[external_index, idx]).expect("only deriving two more steps") + child_xpriv.derive_xpriv(secp, [external_index, idx]).expect("only deriving two more steps") } // Derive the internal address xpriv. @@ -83,7 +83,7 @@ fn get_internal_address_xpriv( let internal_index = ChildNumber::ONE_NORMAL; let idx = ChildNumber::from_normal_idx(index).expect("valid index number"); - child_xpriv.derive_xpriv(secp, &[internal_index, idx]).expect("only deriving two more steps") + child_xpriv.derive_xpriv(secp, [internal_index, idx]).expect("only deriving two more steps") } // The address to send to. diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs index 0ac297654..f16a9dc12 100644 --- a/bitcoin/examples/ecdsa-psbt.rs +++ b/bitcoin/examples/ecdsa-psbt.rs @@ -260,7 +260,7 @@ impl WatchOnly { secp: &Secp256k1, ) -> Result<(CompressedPublicKey, Address, DerivationPath)> { let path = [ChildNumber::ONE_NORMAL, ChildNumber::ZERO_NORMAL]; - let derived = self.account_0_xpub.derive_xpub(secp, &path)?; + let derived = self.account_0_xpub.derive_xpub(secp, path)?; let pk = derived.to_public_key(); let addr = Address::p2wpkh(pk, NETWORK); diff --git a/bitcoin/examples/taproot-psbt-simple.rs b/bitcoin/examples/taproot-psbt-simple.rs index c35dbe463..426061072 100644 --- a/bitcoin/examples/taproot-psbt-simple.rs +++ b/bitcoin/examples/taproot-psbt-simple.rs @@ -65,7 +65,7 @@ fn get_external_address_xpriv( let external_index = ChildNumber::ZERO_NORMAL; let idx = ChildNumber::from_normal_idx(index).expect("valid index number"); - child_xpriv.derive_xpriv(secp, &[external_index, idx]).expect("only deriving two more steps") + child_xpriv.derive_xpriv(secp, [external_index, idx]).expect("only deriving two more steps") } // Derive the internal address xpriv. @@ -81,7 +81,7 @@ fn get_internal_address_xpriv( let internal_index = ChildNumber::ONE_NORMAL; let idx = ChildNumber::from_normal_idx(index).expect("valid index number"); - child_xpriv.derive_xpriv(secp, &[internal_index, idx]).expect("only deriving two more steps") + child_xpriv.derive_xpriv(secp, [internal_index, idx]).expect("only deriving two more steps") } // Get the Taproot Key Origin. diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs index 39b70f9f4..e850a4567 100644 --- a/bitcoin/examples/taproot-psbt.rs +++ b/bitcoin/examples/taproot-psbt.rs @@ -298,7 +298,7 @@ fn generate_bip86_key_spend_tx( .ok_or("missing Taproot key origin")?; let secret_key = - master_xpriv.derive_xpriv(secp, &derivation_path)?.to_private_key().inner; + master_xpriv.derive_xpriv(secp, derivation_path)?.to_private_key().inner; sign_psbt_taproot( secret_key, input.tap_internal_key.unwrap(), @@ -540,7 +540,7 @@ impl BenefactorWallet { .ok_or("missing Taproot key origin")?; let secret_key = self .master_xpriv - .derive_xpriv(&self.secp, &derivation_path) + .derive_xpriv(&self.secp, derivation_path) .expect("derivation path is short") .to_private_key() .inner; @@ -664,11 +664,8 @@ impl BeneficiaryWallet { for (x_only_pubkey, (leaf_hashes, (_, derivation_path))) in &psbt.inputs[0].tap_key_origins.clone() { - let secret_key = self - .master_xpriv - .derive_xpriv(&self.secp, &derivation_path)? - .to_private_key() - .inner; + let secret_key = + self.master_xpriv.derive_xpriv(&self.secp, derivation_path)?.to_private_key().inner; for lh in leaf_hashes { let sighash_type = TapSighashType::All; let hash = SighashCache::new(&unsigned_tx).taproot_script_spend_signature_hash( diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index e78328fee..a4a0275f1 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -814,7 +814,7 @@ impl GetKey for Xpriv { KeyRequest::XOnlyPubkey(_) => Err(GetKeyError::NotSupported), KeyRequest::Bip32((fingerprint, path)) => { let key = if self.fingerprint(secp) == *fingerprint { - let k = self.derive_xpriv(secp, &path).map_err(GetKeyError::Bip32)?; + let k = self.derive_xpriv(secp, path).map_err(GetKeyError::Bip32)?; Some(k.to_private_key()) } else if self.parent_fingerprint == *fingerprint && !path.is_empty() From bd3f4b6bf12b2832cfb3bb11bc0a0e52e1057fe7 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Thu, 19 Jun 2025 16:45:43 -0500 Subject: [PATCH 3/4] psbt: Add test for GetKey bip32 --- bitcoin/src/psbt/mod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index a4a0275f1..81f197cfc 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -2397,6 +2397,27 @@ mod tests { ); } + #[test] + fn get_key_xpriv_bip32_parent() { + let secp = Secp256k1::new(); + + let seed = hex!("000102030405060708090a0b0c0d0e0f"); + let parent_xpriv: Xpriv = Xpriv::new_master(NetworkKind::Main, &seed); + let path: DerivationPath = "m/1/2/3".parse().unwrap(); + let path_prefix: DerivationPath = "m/1".parse().unwrap(); + + let expected_private_key = + parent_xpriv.derive_xpriv(&secp, &path).unwrap().to_private_key(); + + let derived_xpriv = parent_xpriv.derive_xpriv(&secp, &path_prefix).unwrap(); + + let derived_key = derived_xpriv + .get_key(&KeyRequest::Bip32((parent_xpriv.fingerprint(&secp), path.clone())), &secp) + .unwrap(); + + assert_eq!(derived_key, Some(expected_private_key)); + } + #[test] fn fee() { let output_0_val = Amount::from_sat_u32(99_999_699); From 352712257e66c7959b80a9f6375961295a292306 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Thu, 19 Jun 2025 16:47:51 -0500 Subject: [PATCH 4/4] psbt: Use new `derive_xpriv` flexibility in GetKey --- bitcoin/src/psbt/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 81f197cfc..9de21061a 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -21,7 +21,7 @@ use std::collections::{HashMap, HashSet}; use internals::write_err; use secp256k1::{Keypair, Message, Secp256k1, Signing, Verification}; -use crate::bip32::{self, DerivationPath, KeySource, Xpriv, Xpub}; +use crate::bip32::{self, KeySource, Xpriv, Xpub}; use crate::crypto::key::{PrivateKey, PublicKey}; use crate::crypto::{ecdsa, taproot}; use crate::key::{TapTweak, XOnlyPublicKey}; @@ -820,8 +820,7 @@ impl GetKey for Xpriv { && !path.is_empty() && path[0] == self.child_number { - let path = DerivationPath::from_iter(path.into_iter().skip(1).copied()); - let k = self.derive_xpriv(secp, &path).map_err(GetKeyError::Bip32)?; + let k = self.derive_xpriv(secp, &path[1..]).map_err(GetKeyError::Bip32)?; Some(k.to_private_key()) } else { None @@ -1330,7 +1329,7 @@ mod tests { #[cfg(feature = "rand-std")] use { crate::address::script_pubkey::ScriptBufExt as _, - crate::bip32::{DerivationPath, Fingerprint}, + crate::bip32::Fingerprint, crate::locktime, crate::witness_version::WitnessVersion, crate::WitnessProgram, @@ -1339,7 +1338,7 @@ mod tests { use super::*; use crate::address::script_pubkey::ScriptExt as _; - use crate::bip32::ChildNumber; + use crate::bip32::{ChildNumber, DerivationPath}; use crate::locktime::absolute; use crate::network::NetworkKind; use crate::psbt::serialize::{Deserialize, Serialize};