Skip to content

Commit b4f940a

Browse files
committed
descriptor: add fingerprint/full path methods to DescriptorPublicKey
These are useful for working with hardware wallets which expect full keys
1 parent e519f52 commit b4f940a

File tree

1 file changed

+62
-0
lines changed

1 file changed

+62
-0
lines changed

src/descriptor/key.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ use std::{error, fmt, str::FromStr};
33
use bitcoin::{
44
self,
55
hashes::hex::FromHex,
6+
hashes::Hash,
67
secp256k1,
78
secp256k1::{Secp256k1, Signing},
89
util::bip32,
10+
XpubIdentifier,
911
};
1012

1113
use MiniscriptKey;
@@ -343,6 +345,53 @@ impl fmt::Display for ConversionError {
343345
impl error::Error for ConversionError {}
344346

345347
impl DescriptorPublicKey {
348+
/// The fingerprint of the master key associated with this key
349+
pub fn master_fingerprint(&self) -> bip32::Fingerprint {
350+
match *self {
351+
DescriptorPublicKey::XPub(ref xpub) => {
352+
if let Some((fingerprint, _)) = xpub.origin {
353+
fingerprint
354+
} else {
355+
xpub.xkey.fingerprint()
356+
}
357+
}
358+
DescriptorPublicKey::SinglePub(ref single) => {
359+
if let Some((fingerprint, _)) = single.origin {
360+
fingerprint
361+
} else {
362+
let mut engine = XpubIdentifier::engine();
363+
single.key.write_into(&mut engine);
364+
bip32::Fingerprint::from(&XpubIdentifier::from_engine(engine)[..])
365+
}
366+
}
367+
}
368+
}
369+
370+
/// Full path, from the master key
371+
///
372+
/// For wildcard keys this will return the path up to the wildcard, so you
373+
/// can get full paths by appending one additional derivation step, according
374+
/// to the wildcard type (hardened or normal)
375+
pub fn full_derivation_path(&self) -> bip32::DerivationPath {
376+
match *self {
377+
DescriptorPublicKey::XPub(ref xpub) => {
378+
let origin_path = if let Some((_, ref path)) = xpub.origin {
379+
path.clone()
380+
} else {
381+
bip32::DerivationPath::from(vec![])
382+
};
383+
origin_path.extend(&xpub.derivation_path)
384+
}
385+
DescriptorPublicKey::SinglePub(ref single) => {
386+
if let Some((_, ref path)) = single.origin {
387+
path.clone()
388+
} else {
389+
bip32::DerivationPath::from(vec![])
390+
}
391+
}
392+
}
393+
}
394+
346395
/// If this public key has a wildcard, replace it by the given index
347396
///
348397
/// Panics if given an index ≥ 2^31
@@ -706,21 +755,34 @@ mod test {
706755
let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2").unwrap();
707756
let public_key = secret_key.as_public(&secp).unwrap();
708757
assert_eq!(public_key.to_string(), "[2cbe2a6d/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2");
758+
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
759+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'/2");
709760

710761
let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2'").unwrap();
711762
let public_key = secret_key.as_public(&secp).unwrap();
712763
assert_eq!(public_key.to_string(), "[2cbe2a6d/0'/1'/2']tpubDDPuH46rv4dbFtmF6FrEtJEy1CvLZonyBoVxF6xsesHdYDdTBrq2mHhm8AbsPh39sUwL2nZyxd6vo4uWNTU9v4t893CwxjqPnwMoUACLvMV");
764+
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
765+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'/2'");
713766

714767
let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0/1/2").unwrap();
715768
let public_key = secret_key.as_public(&secp).unwrap();
716769
assert_eq!(public_key.to_string(), "tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr/0/1/2");
770+
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
771+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0/1/2");
717772

718773
let secret_key = DescriptorSecretKey::from_str("[aabbccdd]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0/1/2").unwrap();
719774
let public_key = secret_key.as_public(&secp).unwrap();
720775
assert_eq!(public_key.to_string(), "[aabbccdd]tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr/0/1/2");
776+
assert_eq!(public_key.master_fingerprint().to_string(), "aabbccdd");
777+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0/1/2");
721778

722779
let secret_key = DescriptorSecretKey::from_str("[aabbccdd/90']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2").unwrap();
723780
let public_key = secret_key.as_public(&secp).unwrap();
724781
assert_eq!(public_key.to_string(), "[aabbccdd/90'/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2");
782+
assert_eq!(public_key.master_fingerprint().to_string(), "aabbccdd");
783+
assert_eq!(
784+
public_key.full_derivation_path().to_string(),
785+
"m/90'/0'/1'/2"
786+
);
725787
}
726788
}

0 commit comments

Comments
 (0)