Merge rust-bitcoin/rust-bitcoin#4623: Improve `Xpriv::derive_xpriv` and `Xpub::derive_xpub` ergonomics slightly
352712257e
psbt: Use new `derive_xpriv` flexibility in GetKey (Daniel Roberts)bd3f4b6bf1
psbt: Add test for GetKey bip32 (Daniel Roberts)c7bdec14fb
Fix clippy lint and formatting for `Xpriv::derive_xpriv` and `Xpriv::derive_xpub` calls (Daniel Roberts)9d4381c8fe
Improve `Xpriv::derive_xpriv` and `Xpub::derive_xpub` ergonomics (Daniel Roberts) Pull request description: This enables a couple more things to be passed to the bip32 derive methods, with (afaict) no downside (all existing call sites remain valid and work as before). Given ``` let secp = Secp256k1::new(); let path: DerivationPath = "42'/350'/0".parse().unwrap(); let xpriv: Xpriv = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi".parse().unwrap(); ``` The following *new* ways to call derive are enabled: ``` /// Derive using only part of the path xpriv.derive_xpriv(&secp, &path[1..]) ``` ``` /// Derive moving into the method xpriv.derive_xpriv(&secp, path) ``` The second case is probably of questionable usefulness, but I've ended up writing something like ``` let path: DerivationPath = path.into_iter().skip(n).cloned().collect() ``` enough that the first case would be welcome, and can be done with a minimal change. I'm reasonably confident this doesn't break anything (and it indeed doesn't break any tests that I can see) but this is definitely on the edge of my comfort with the rust type system, which is why I've created this as a draft. ACKs for top commit: tcharding: ACK352712257e
apoelstra: ACK 352712257e66c7959b80a9f6375961295a292306; successfully ran local tests; nice! Tree-SHA512: 0babdd9589d3312d4df54d77202a71dbb021e216167514c1a87f1f992f254f2b298b874411f8c7a3d56d7f730b22d104c3a78f9145715ea8df6bedad35c1ffd5
This commit is contained in:
commit
d84745fd92
|
@ -46,7 +46,7 @@ fn main() {
|
||||||
// generate first receiving address at m/0/0
|
// generate first receiving address at m/0/0
|
||||||
// manually creating indexes this time
|
// manually creating indexes this time
|
||||||
let zero = ChildNumber::ZERO_NORMAL;
|
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);
|
let address = Address::p2wpkh(CompressedPublicKey(public_key), KnownHrp::Mainnet);
|
||||||
println!("First receiving address: {address}");
|
println!("First receiving address: {address}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ fn get_external_address_xpriv<C: Signing>(
|
||||||
let external_index = ChildNumber::ZERO_NORMAL;
|
let external_index = ChildNumber::ZERO_NORMAL;
|
||||||
let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
|
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.
|
// Derive the internal address xpriv.
|
||||||
|
@ -83,7 +83,7 @@ fn get_internal_address_xpriv<C: Signing>(
|
||||||
let internal_index = ChildNumber::ONE_NORMAL;
|
let internal_index = ChildNumber::ONE_NORMAL;
|
||||||
let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
|
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.
|
// The address to send to.
|
||||||
|
|
|
@ -260,7 +260,7 @@ impl WatchOnly {
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
) -> Result<(CompressedPublicKey, Address, DerivationPath)> {
|
) -> Result<(CompressedPublicKey, Address, DerivationPath)> {
|
||||||
let path = [ChildNumber::ONE_NORMAL, ChildNumber::ZERO_NORMAL];
|
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 pk = derived.to_public_key();
|
||||||
let addr = Address::p2wpkh(pk, NETWORK);
|
let addr = Address::p2wpkh(pk, NETWORK);
|
||||||
|
|
|
@ -65,7 +65,7 @@ fn get_external_address_xpriv<C: Signing>(
|
||||||
let external_index = ChildNumber::ZERO_NORMAL;
|
let external_index = ChildNumber::ZERO_NORMAL;
|
||||||
let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
|
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.
|
// Derive the internal address xpriv.
|
||||||
|
@ -81,7 +81,7 @@ fn get_internal_address_xpriv<C: Signing>(
|
||||||
let internal_index = ChildNumber::ONE_NORMAL;
|
let internal_index = ChildNumber::ONE_NORMAL;
|
||||||
let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
|
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.
|
// Get the Taproot Key Origin.
|
||||||
|
|
|
@ -298,7 +298,7 @@ fn generate_bip86_key_spend_tx(
|
||||||
.ok_or("missing Taproot key origin")?;
|
.ok_or("missing Taproot key origin")?;
|
||||||
|
|
||||||
let secret_key =
|
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(
|
sign_psbt_taproot(
|
||||||
secret_key,
|
secret_key,
|
||||||
input.tap_internal_key.unwrap(),
|
input.tap_internal_key.unwrap(),
|
||||||
|
@ -540,7 +540,7 @@ impl BenefactorWallet {
|
||||||
.ok_or("missing Taproot key origin")?;
|
.ok_or("missing Taproot key origin")?;
|
||||||
let secret_key = self
|
let secret_key = self
|
||||||
.master_xpriv
|
.master_xpriv
|
||||||
.derive_xpriv(&self.secp, &derivation_path)
|
.derive_xpriv(&self.secp, derivation_path)
|
||||||
.expect("derivation path is short")
|
.expect("derivation path is short")
|
||||||
.to_private_key()
|
.to_private_key()
|
||||||
.inner;
|
.inner;
|
||||||
|
@ -664,11 +664,8 @@ impl BeneficiaryWallet {
|
||||||
for (x_only_pubkey, (leaf_hashes, (_, derivation_path))) in
|
for (x_only_pubkey, (leaf_hashes, (_, derivation_path))) in
|
||||||
&psbt.inputs[0].tap_key_origins.clone()
|
&psbt.inputs[0].tap_key_origins.clone()
|
||||||
{
|
{
|
||||||
let secret_key = self
|
let secret_key =
|
||||||
.master_xpriv
|
self.master_xpriv.derive_xpriv(&self.secp, derivation_path)?.to_private_key().inner;
|
||||||
.derive_xpriv(&self.secp, &derivation_path)?
|
|
||||||
.to_private_key()
|
|
||||||
.inner;
|
|
||||||
for lh in leaf_hashes {
|
for lh in leaf_hashes {
|
||||||
let sighash_type = TapSighashType::All;
|
let sighash_type = TapSighashType::All;
|
||||||
let hash = SighashCache::new(&unsigned_tx).taproot_script_spend_signature_hash(
|
let hash = SighashCache::new(&unsigned_tx).taproot_script_spend_signature_hash(
|
||||||
|
|
|
@ -756,7 +756,7 @@ impl Xpriv {
|
||||||
pub fn derive_priv<C: secp256k1::Signing, P: AsRef<[ChildNumber]>>(
|
pub fn derive_priv<C: secp256k1::Signing, P: AsRef<[ChildNumber]>>(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
path: &P,
|
path: P,
|
||||||
) -> Result<Xpriv, DerivationError> {
|
) -> Result<Xpriv, DerivationError> {
|
||||||
self.derive_xpriv(secp, path)
|
self.derive_xpriv(secp, path)
|
||||||
}
|
}
|
||||||
|
@ -767,7 +767,7 @@ impl Xpriv {
|
||||||
pub fn derive_xpriv<C: secp256k1::Signing, P: AsRef<[ChildNumber]>>(
|
pub fn derive_xpriv<C: secp256k1::Signing, P: AsRef<[ChildNumber]>>(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
path: &P,
|
path: P,
|
||||||
) -> Result<Xpriv, DerivationError> {
|
) -> Result<Xpriv, DerivationError> {
|
||||||
let mut sk: Xpriv = *self;
|
let mut sk: Xpriv = *self;
|
||||||
for cnum in path.as_ref() {
|
for cnum in path.as_ref() {
|
||||||
|
@ -910,7 +910,7 @@ impl Xpub {
|
||||||
pub fn derive_pub<C: secp256k1::Verification, P: AsRef<[ChildNumber]>>(
|
pub fn derive_pub<C: secp256k1::Verification, P: AsRef<[ChildNumber]>>(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
path: &P,
|
path: P,
|
||||||
) -> Result<Xpub, DerivationError> {
|
) -> Result<Xpub, DerivationError> {
|
||||||
self.derive_xpub(secp, path)
|
self.derive_xpub(secp, path)
|
||||||
}
|
}
|
||||||
|
@ -921,7 +921,7 @@ impl Xpub {
|
||||||
pub fn derive_xpub<C: secp256k1::Verification, P: AsRef<[ChildNumber]>>(
|
pub fn derive_xpub<C: secp256k1::Verification, P: AsRef<[ChildNumber]>>(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
path: &P,
|
path: P,
|
||||||
) -> Result<Xpub, DerivationError> {
|
) -> Result<Xpub, DerivationError> {
|
||||||
let mut pk: Xpub = *self;
|
let mut pk: Xpub = *self;
|
||||||
for cnum in path.as_ref() {
|
for cnum in path.as_ref() {
|
||||||
|
|
|
@ -21,7 +21,7 @@ use std::collections::{HashMap, HashSet};
|
||||||
use internals::write_err;
|
use internals::write_err;
|
||||||
use secp256k1::{Keypair, Message, Secp256k1, Signing, Verification};
|
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::key::{PrivateKey, PublicKey};
|
||||||
use crate::crypto::{ecdsa, taproot};
|
use crate::crypto::{ecdsa, taproot};
|
||||||
use crate::key::{TapTweak, XOnlyPublicKey};
|
use crate::key::{TapTweak, XOnlyPublicKey};
|
||||||
|
@ -814,14 +814,13 @@ impl GetKey for Xpriv {
|
||||||
KeyRequest::XOnlyPubkey(_) => Err(GetKeyError::NotSupported),
|
KeyRequest::XOnlyPubkey(_) => Err(GetKeyError::NotSupported),
|
||||||
KeyRequest::Bip32((fingerprint, path)) => {
|
KeyRequest::Bip32((fingerprint, path)) => {
|
||||||
let key = if self.fingerprint(secp) == *fingerprint {
|
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())
|
Some(k.to_private_key())
|
||||||
} else if self.parent_fingerprint == *fingerprint
|
} else if self.parent_fingerprint == *fingerprint
|
||||||
&& !path.is_empty()
|
&& !path.is_empty()
|
||||||
&& path[0] == self.child_number
|
&& path[0] == self.child_number
|
||||||
{
|
{
|
||||||
let path = DerivationPath::from_iter(path.into_iter().skip(1).copied());
|
let k = self.derive_xpriv(secp, &path[1..]).map_err(GetKeyError::Bip32)?;
|
||||||
let k = self.derive_xpriv(secp, &path).map_err(GetKeyError::Bip32)?;
|
|
||||||
Some(k.to_private_key())
|
Some(k.to_private_key())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -1330,7 +1329,7 @@ mod tests {
|
||||||
#[cfg(feature = "rand-std")]
|
#[cfg(feature = "rand-std")]
|
||||||
use {
|
use {
|
||||||
crate::address::script_pubkey::ScriptBufExt as _,
|
crate::address::script_pubkey::ScriptBufExt as _,
|
||||||
crate::bip32::{DerivationPath, Fingerprint},
|
crate::bip32::Fingerprint,
|
||||||
crate::locktime,
|
crate::locktime,
|
||||||
crate::witness_version::WitnessVersion,
|
crate::witness_version::WitnessVersion,
|
||||||
crate::WitnessProgram,
|
crate::WitnessProgram,
|
||||||
|
@ -1339,7 +1338,7 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::address::script_pubkey::ScriptExt as _;
|
use crate::address::script_pubkey::ScriptExt as _;
|
||||||
use crate::bip32::ChildNumber;
|
use crate::bip32::{ChildNumber, DerivationPath};
|
||||||
use crate::locktime::absolute;
|
use crate::locktime::absolute;
|
||||||
use crate::network::NetworkKind;
|
use crate::network::NetworkKind;
|
||||||
use crate::psbt::serialize::{Deserialize, Serialize};
|
use crate::psbt::serialize::{Deserialize, Serialize};
|
||||||
|
@ -2397,6 +2396,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]
|
#[test]
|
||||||
fn fee() {
|
fn fee() {
|
||||||
let output_0_val = Amount::from_sat_u32(99_999_699);
|
let output_0_val = Amount::from_sat_u32(99_999_699);
|
||||||
|
|
Loading…
Reference in New Issue