From ffe62effe70a5b06e908b75c97fa9677aed24bc1 Mon Sep 17 00:00:00 2001 From: Scott Robinson <ssr@squareup.com> Date: Sun, 23 Jul 2023 14:59:20 +1000 Subject: [PATCH 1/6] DescriptorKey trait --- src/descriptor/key.rs | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 83308e0dc..51a1ec7f3 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -525,6 +525,53 @@ impl error::Error for ConversionError { } } +pub trait DescriptorKey : Clone + fmt::Debug + fmt::Display + Eq + FromStr + std::hash::Hash + Ord { + /// The fingerprint of the master key associated with this key, `0x00000000` if none. + fn master_fingerprint(&self) -> bip32::Fingerprint; + + /// Full path, from the master key + /// + /// For wildcard keys this will return the path up to the wildcard, so you + /// can get full paths by appending one additional derivation step, according + /// to the wildcard type (hardened or normal). + /// + /// For multipath extended keys, this returns `None`. + fn full_derivation_path(&self) -> Option<bip32::DerivationPath>; + + /// Whether or not the key has a wildcard + fn has_wildcard(&self) -> bool; + + /// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a + /// *definite* key (i.e. one where all the derivation paths are set). + /// + /// # Returns + /// + /// - If this key is not an xpub, returns `self`. + /// - If this key is an xpub but does not have a wildcard, returns `self`. + /// - Otherwise, returns the xpub at derivation `index` (removing the wildcard). + /// + /// # Errors + /// + /// - If `index` is hardened. + fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError>; + + /// Whether or not this key has multiple derivation paths. + fn is_multipath(&self) -> bool; + + /// Computes the public key corresponding to this descriptor key. + /// When deriving from an XOnlyPublicKey, it adds the default 0x02 y-coordinate + /// and returns the obtained full [`bitcoin::PublicKey`]. All BIP32 derivations + /// always return a compressed key + /// + /// Will return an error if the descriptor key has any hardened derivation steps in its path. To + /// avoid this error you should replace any such public keys first with [`translate_pk`]. + /// + /// [`translate_pk`]: crate::TranslatePk::translate_pk + fn derive_public_key<C: Verification>( + &self, + secp: &Secp256k1<C>, + ) -> Result<bitcoin::PublicKey, ConversionError>; +} impl DescriptorPublicKey { /// The fingerprint of the master key associated with this key, `0x00000000` if none. pub fn master_fingerprint(&self) -> bip32::Fingerprint { From bfa946e36df0cee5903ae542e23440b0d3968dbf Mon Sep 17 00:00:00 2001 From: Scott Robinson <ssr@squareup.com> Date: Sun, 23 Jul 2023 12:40:02 +1000 Subject: [PATCH 2/6] Extract specialised DescriptorKey functionality into DescriptorExtendedPublicKey --- src/descriptor/key.rs | 228 ++++++++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 77 deletions(-) diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 51a1ec7f3..bdd1dadbf 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -18,13 +18,15 @@ use crate::prelude::*; use crate::serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{hash256, MiniscriptKey, ToPublicKey}; +type DescriptorExtendedPublicKey = DescriptorXKey<bip32::ExtendedPubKey>; + /// The descriptor pubkey, either a single pubkey or an xpub. #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] pub enum DescriptorPublicKey { /// Single public key. Single(SinglePub), /// Extended public key (xpub). - XPub(DescriptorXKey<bip32::ExtendedPubKey>), + XPub(DescriptorExtendedPublicKey), /// Multiple extended public keys. MultiXPub(DescriptorMultiXKey<bip32::ExtendedPubKey>), } @@ -283,6 +285,20 @@ impl error::Error for DescriptorKeyParseError { } } +impl fmt::Display for DescriptorExtendedPublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + maybe_fmt_master_id(f, &self.origin)?; + self.xkey.fmt(f)?; + fmt_derivation_path(f, &self.derivation_path)?; + match self.wildcard { + Wildcard::None => {} + Wildcard::Unhardened => write!(f, "/*")?, + Wildcard::Hardened => write!(f, "/*h")?, + } + Ok(()) + } +} + impl fmt::Display for DescriptorPublicKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -294,17 +310,7 @@ impl fmt::Display for DescriptorPublicKey { }?; Ok(()) } - DescriptorPublicKey::XPub(ref xpub) => { - maybe_fmt_master_id(f, &xpub.origin)?; - xpub.xkey.fmt(f)?; - fmt_derivation_path(f, &xpub.derivation_path)?; - match xpub.wildcard { - Wildcard::None => {} - Wildcard::Unhardened => write!(f, "/*")?, - Wildcard::Hardened => write!(f, "/*h")?, - } - Ok(()) - } + DescriptorPublicKey::XPub(ref xpub) => xpub.fmt(f), DescriptorPublicKey::MultiXPub(ref xpub) => { maybe_fmt_master_id(f, &xpub.origin)?; xpub.xkey.fmt(f)?; @@ -432,6 +438,30 @@ fn fmt_derivation_paths(f: &mut fmt::Formatter, paths: &[bip32::DerivationPath]) Ok(()) } +impl FromStr for DescriptorExtendedPublicKey { + type Err = DescriptorKeyParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let (key_part, origin) = parse_key_origin(s)?; + + let (xpub, derivation_paths, wildcard) = + parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?; + + if derivation_paths.len() > 1 { + return Err(DescriptorKeyParseError( + "Multiple derivation paths are not allowed for single extended keys", + )); + } + + Ok(Self { + origin, + xkey: xpub, + derivation_path: derivation_paths.into_iter().next().unwrap_or_default(), + wildcard, + }) + } +} + impl FromStr for DescriptorPublicKey { type Err = DescriptorKeyParseError; @@ -456,12 +486,7 @@ impl FromStr for DescriptorPublicKey { wildcard, })) } else { - Ok(DescriptorPublicKey::XPub(DescriptorXKey { - origin, - xkey: xpub, - derivation_path: derivation_paths.into_iter().next().unwrap_or_default(), - wildcard, - })) + DescriptorExtendedPublicKey::from_str(s).map(Self::XPub) } } else { let key = match key_part.len() { @@ -503,6 +528,8 @@ pub enum ConversionError { HardenedChild, /// Attempted to convert a key with multiple derivation paths to a bitcoin public key MultiKey, + /// Attempted to convert a key with a wildcard to a bitcoin public key + Wildcard, } impl fmt::Display for ConversionError { @@ -510,6 +537,7 @@ impl fmt::Display for ConversionError { f.write_str(match *self { ConversionError::HardenedChild => "hardened child step in bip32 path", ConversionError::MultiKey => "multiple existing keys", + ConversionError::Wildcard => "wildcard in bip32 path", }) } } @@ -520,7 +548,7 @@ impl error::Error for ConversionError { use self::ConversionError::*; match self { - HardenedChild | MultiKey => None, + HardenedChild | MultiKey | Wildcard => None, } } } @@ -572,17 +600,102 @@ pub trait DescriptorKey : Clone + fmt::Debug + fmt::Display + Eq + FromStr + std secp: &Secp256k1<C>, ) -> Result<bitcoin::PublicKey, ConversionError>; } + +impl DescriptorKey for DescriptorExtendedPublicKey { + /// The fingerprint of the master key associated with this key, `0x00000000` if none. + fn master_fingerprint(&self) -> bip32::Fingerprint { + if let Some((fingerprint, _)) = self.origin { + fingerprint + } else { + self.xkey.fingerprint() + } + } + + /// Full path, from the master key + /// + /// For wildcard keys this will return the path up to the wildcard, so you + /// can get full paths by appending one additional derivation step, according + /// to the wildcard type (hardened or normal). + /// + /// For multipath extended keys, this returns `None`. + fn full_derivation_path(&self) -> Option<bip32::DerivationPath> { + let origin_path = if let Some((_, ref path)) = self.origin { + path.clone() + } else { + bip32::DerivationPath::from(vec![]) + }; + Some(origin_path.extend(&self.derivation_path)) + } + + /// Whether or not the key has a wildcard + fn has_wildcard(&self) -> bool { + self.wildcard != Wildcard::None + } + + /// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a + /// *definite* key (i.e. one where all the derivation paths are set). + /// + /// # Returns + /// + /// - If this key is not an xpub, returns `self`. + /// - If this key is an xpub but does not have a wildcard, returns `self`. + /// - Otherwise, returns the xpub at derivation `index` (removing the wildcard). + /// + /// # Errors + /// + /// - If `index` is hardened. + fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> { + let derivation_path = match self.wildcard { + Wildcard::None => self.derivation_path, + Wildcard::Unhardened => self.derivation_path.into_child( + bip32::ChildNumber::from_normal_idx(index) + .ok() + .ok_or(ConversionError::HardenedChild)?, + ), + Wildcard::Hardened => self.derivation_path.into_child( + bip32::ChildNumber::from_hardened_idx(index) + .ok() + .ok_or(ConversionError::HardenedChild)?, + ), + }; + let definite = DescriptorPublicKey::XPub(DescriptorXKey { + origin: self.origin, + xkey: self.xkey, + derivation_path, + wildcard: Wildcard::None, + }); + + Ok(DefiniteDescriptorKey::new(definite) + .expect("The key should not contain any wildcards at this point")) + } + + /// Whether or not this key has multiple derivation paths. + fn is_multipath(&self) -> bool { + false + } + + fn derive_public_key<C: Verification>( + &self, + secp: &Secp256k1<C>, + ) -> Result<bitcoin::PublicKey, ConversionError> { + match self.wildcard { + Wildcard::Unhardened | Wildcard::Hardened => Err(ConversionError::Wildcard), + Wildcard::None => match self.xkey.derive_pub(secp, &self.derivation_path.as_ref()) { + Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)), + Err(bip32::Error::CannotDeriveFromHardenedKey) => { + Err(ConversionError::HardenedChild) + } + Err(e) => unreachable!("cryptographically unreachable: {}", e), + }, + } + } +} + impl DescriptorPublicKey { /// The fingerprint of the master key associated with this key, `0x00000000` if none. pub fn master_fingerprint(&self) -> bip32::Fingerprint { match *self { - DescriptorPublicKey::XPub(ref xpub) => { - if let Some((fingerprint, _)) = xpub.origin { - fingerprint - } else { - xpub.xkey.fingerprint() - } - } + DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(), DescriptorPublicKey::MultiXPub(ref xpub) => { if let Some((fingerprint, _)) = xpub.origin { fingerprint @@ -620,14 +733,7 @@ impl DescriptorPublicKey { /// For multipath extended keys, this returns `None`. pub fn full_derivation_path(&self) -> Option<bip32::DerivationPath> { match *self { - DescriptorPublicKey::XPub(ref xpub) => { - let origin_path = if let Some((_, ref path)) = xpub.origin { - path.clone() - } else { - bip32::DerivationPath::from(vec![]) - }; - Some(origin_path.extend(&xpub.derivation_path)) - } + DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(), DescriptorPublicKey::Single(ref single) => { Some(if let Some((_, ref path)) = single.origin { path.clone() @@ -649,7 +755,7 @@ impl DescriptorPublicKey { pub fn has_wildcard(&self) -> bool { match *self { DescriptorPublicKey::Single(..) => false, - DescriptorPublicKey::XPub(ref xpub) => xpub.wildcard != Wildcard::None, + DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(), DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None, } } @@ -673,40 +779,19 @@ impl DescriptorPublicKey { /// /// - If `index` is hardened. pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> { - let definite = match self { - DescriptorPublicKey::Single(_) => self, - DescriptorPublicKey::XPub(xpub) => { - let derivation_path = match xpub.wildcard { - Wildcard::None => xpub.derivation_path, - Wildcard::Unhardened => xpub.derivation_path.into_child( - bip32::ChildNumber::from_normal_idx(index) - .ok() - .ok_or(ConversionError::HardenedChild)?, - ), - Wildcard::Hardened => xpub.derivation_path.into_child( - bip32::ChildNumber::from_hardened_idx(index) - .ok() - .ok_or(ConversionError::HardenedChild)?, - ), - }; - DescriptorPublicKey::XPub(DescriptorXKey { - origin: xpub.origin, - xkey: xpub.xkey, - derivation_path, - wildcard: Wildcard::None, - }) - } - DescriptorPublicKey::MultiXPub(_) => return Err(ConversionError::MultiKey), - }; - - Ok(DefiniteDescriptorKey::new(definite) - .expect("The key should not contain any wildcards at this point")) + match self { + DescriptorPublicKey::Single(_) => Ok(DefiniteDescriptorKey::new(self) + .expect("The key should not contain any wildcards at this point")), + DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index), + DescriptorPublicKey::MultiXPub(_) => Err(ConversionError::MultiKey), + } } /// Whether or not this key has multiple derivation paths. pub fn is_multipath(&self) -> bool { - match *self { - DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => false, + match self { + DescriptorPublicKey::Single(..) => false, + DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(), DescriptorPublicKey::MultiXPub(_) => true, } } @@ -1083,18 +1168,7 @@ impl DefiniteDescriptorKey { SinglePubKey::FullKey(pk) => Ok(pk), SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()), }, - DescriptorPublicKey::XPub(ref xpk) => match xpk.wildcard { - Wildcard::Unhardened | Wildcard::Hardened => { - unreachable!("we've excluded this error case") - } - Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) { - Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)), - Err(bip32::Error::CannotDeriveFromHardenedKey) => { - Err(ConversionError::HardenedChild) - } - Err(e) => unreachable!("cryptographically unreachable: {}", e), - }, - }, + DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp), DescriptorPublicKey::MultiXPub(_) => { unreachable!("A definite key cannot contain a multipath key.") } From c430f70000f8d4e8ef37dc5680d6c9bb17d8758d Mon Sep 17 00:00:00 2001 From: Scott Robinson <ssr@squareup.com> Date: Sun, 23 Jul 2023 13:07:23 +1000 Subject: [PATCH 3/6] Extract specialised DescriptorKey functionality into DescriptorSinglePublicKey --- src/descriptor/key.rs | 177 ++++++++++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 69 deletions(-) diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index bdd1dadbf..f0571eb61 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -18,6 +18,7 @@ use crate::prelude::*; use crate::serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{hash256, MiniscriptKey, ToPublicKey}; +type DescriptorSinglePublicKey = SinglePub; type DescriptorExtendedPublicKey = DescriptorXKey<bip32::ExtendedPubKey>; /// The descriptor pubkey, either a single pubkey or an xpub. @@ -285,6 +286,16 @@ impl error::Error for DescriptorKeyParseError { } } +impl fmt::Display for DescriptorSinglePublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + maybe_fmt_master_id(f, &self.origin)?; + match self.key { + SinglePubKey::FullKey(full_key) => full_key.fmt(f), + SinglePubKey::XOnly(x_only_key) => x_only_key.fmt(f), + } + } +} + impl fmt::Display for DescriptorExtendedPublicKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { maybe_fmt_master_id(f, &self.origin)?; @@ -302,14 +313,7 @@ impl fmt::Display for DescriptorExtendedPublicKey { impl fmt::Display for DescriptorPublicKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - DescriptorPublicKey::Single(ref pk) => { - maybe_fmt_master_id(f, &pk.origin)?; - match pk.key { - SinglePubKey::FullKey(full_key) => full_key.fmt(f), - SinglePubKey::XOnly(x_only_key) => x_only_key.fmt(f), - }?; - Ok(()) - } + DescriptorPublicKey::Single(ref pk) => pk.fmt(f), DescriptorPublicKey::XPub(ref xpub) => xpub.fmt(f), DescriptorPublicKey::MultiXPub(ref xpub) => { maybe_fmt_master_id(f, &xpub.origin)?; @@ -438,6 +442,41 @@ fn fmt_derivation_paths(f: &mut fmt::Formatter, paths: &[bip32::DerivationPath]) Ok(()) } +impl FromStr for DescriptorSinglePublicKey { + type Err = DescriptorKeyParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let (key_part, origin) = parse_key_origin(s)?; + + let key = match key_part.len() { + 64 => { + let x_only_key = XOnlyPublicKey::from_str(key_part) + .map_err(|_| DescriptorKeyParseError("Error while parsing simple xonly key"))?; + SinglePubKey::XOnly(x_only_key) + } + 66 | 130 => { + if !(&key_part[0..2] == "02" || &key_part[0..2] == "03" || &key_part[0..2] == "04") + { + return Err(DescriptorKeyParseError( + "Only publickeys with prefixes 02/03/04 are allowed", + )); + } + let key = bitcoin::PublicKey::from_str(key_part).map_err(|_| { + DescriptorKeyParseError("Error while parsing simple public key") + })?; + SinglePubKey::FullKey(key) + } + _ => { + return Err(DescriptorKeyParseError( + "Public keys must be 64/66/130 characters in size", + )) + } + }; + + Ok(Self { key, origin }) + } +} + impl FromStr for DescriptorExtendedPublicKey { type Err = DescriptorKeyParseError; @@ -489,34 +528,7 @@ impl FromStr for DescriptorPublicKey { DescriptorExtendedPublicKey::from_str(s).map(Self::XPub) } } else { - let key = match key_part.len() { - 64 => { - let x_only_key = XOnlyPublicKey::from_str(key_part).map_err(|_| { - DescriptorKeyParseError("Error while parsing simple xonly key") - })?; - SinglePubKey::XOnly(x_only_key) - } - 66 | 130 => { - if !(&key_part[0..2] == "02" - || &key_part[0..2] == "03" - || &key_part[0..2] == "04") - { - return Err(DescriptorKeyParseError( - "Only publickeys with prefixes 02/03/04 are allowed", - )); - } - let key = bitcoin::PublicKey::from_str(key_part).map_err(|_| { - DescriptorKeyParseError("Error while parsing simple public key") - })?; - SinglePubKey::FullKey(key) - } - _ => { - return Err(DescriptorKeyParseError( - "Public keys must be 64/66/130 characters in size", - )) - } - }; - Ok(DescriptorPublicKey::Single(SinglePub { key, origin })) + DescriptorSinglePublicKey::from_str(s).map(Self::Single) } } } @@ -601,6 +613,60 @@ pub trait DescriptorKey : Clone + fmt::Debug + fmt::Display + Eq + FromStr + std ) -> Result<bitcoin::PublicKey, ConversionError>; } +impl DescriptorKey for DescriptorSinglePublicKey { + fn master_fingerprint(&self) -> bip32::Fingerprint { + if let Some((fingerprint, _)) = self.origin { + fingerprint + } else { + let mut engine = XpubIdentifier::engine(); + match self.key { + SinglePubKey::FullKey(pk) => { + pk.write_into(&mut engine).expect("engines don't error") + } + SinglePubKey::XOnly(x_only_pk) => engine.input(&x_only_pk.serialize()), + }; + bip32::Fingerprint::from( + &XpubIdentifier::from_engine(engine)[..4] + .try_into() + .expect("4 byte slice"), + ) + } + } + + fn full_derivation_path(&self) -> Option<bip32::DerivationPath> { + Some(if let Some((_, ref path)) = self.origin { + path.clone() + } else { + bip32::DerivationPath::from(vec![]) + }) + } + + fn has_wildcard(&self) -> bool { + false + } + + fn at_derivation_index(self, _index: u32) -> Result<DefiniteDescriptorKey, ConversionError> { + Ok( + DefiniteDescriptorKey::new(DescriptorPublicKey::Single(self)) + .expect("The key should not contain any wildcards at this point"), + ) + } + + fn is_multipath(&self) -> bool { + false + } + + fn derive_public_key<C: Verification>( + &self, + _secp: &Secp256k1<C>, + ) -> Result<bitcoin::PublicKey, ConversionError> { + match self.key { + SinglePubKey::FullKey(pk) => Ok(pk), + SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()), + } + } +} + impl DescriptorKey for DescriptorExtendedPublicKey { /// The fingerprint of the master key associated with this key, `0x00000000` if none. fn master_fingerprint(&self) -> bip32::Fingerprint { @@ -703,24 +769,7 @@ impl DescriptorPublicKey { xpub.xkey.fingerprint() } } - DescriptorPublicKey::Single(ref single) => { - if let Some((fingerprint, _)) = single.origin { - fingerprint - } else { - let mut engine = XpubIdentifier::engine(); - match single.key { - SinglePubKey::FullKey(pk) => { - pk.write_into(&mut engine).expect("engines don't error") - } - SinglePubKey::XOnly(x_only_pk) => engine.input(&x_only_pk.serialize()), - }; - bip32::Fingerprint::from( - &XpubIdentifier::from_engine(engine)[..4] - .try_into() - .expect("4 byte slice"), - ) - } - } + DescriptorPublicKey::Single(ref single) => single.master_fingerprint(), } } @@ -734,13 +783,7 @@ impl DescriptorPublicKey { pub fn full_derivation_path(&self) -> Option<bip32::DerivationPath> { match *self { DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(), - DescriptorPublicKey::Single(ref single) => { - Some(if let Some((_, ref path)) = single.origin { - path.clone() - } else { - bip32::DerivationPath::from(vec![]) - }) - } + DescriptorPublicKey::Single(ref single) => single.full_derivation_path(), DescriptorPublicKey::MultiXPub(_) => None, } } @@ -754,7 +797,7 @@ impl DescriptorPublicKey { /// Whether or not the key has a wildcard pub fn has_wildcard(&self) -> bool { match *self { - DescriptorPublicKey::Single(..) => false, + DescriptorPublicKey::Single(ref single) => single.has_wildcard(), DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(), DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None, } @@ -780,8 +823,7 @@ impl DescriptorPublicKey { /// - If `index` is hardened. pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> { match self { - DescriptorPublicKey::Single(_) => Ok(DefiniteDescriptorKey::new(self) - .expect("The key should not contain any wildcards at this point")), + DescriptorPublicKey::Single(single) => single.at_derivation_index(index), DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index), DescriptorPublicKey::MultiXPub(_) => Err(ConversionError::MultiKey), } @@ -790,7 +832,7 @@ impl DescriptorPublicKey { /// Whether or not this key has multiple derivation paths. pub fn is_multipath(&self) -> bool { match self { - DescriptorPublicKey::Single(..) => false, + DescriptorPublicKey::Single(single) => single.is_multipath(), DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(), DescriptorPublicKey::MultiXPub(_) => true, } @@ -1164,10 +1206,7 @@ impl DefiniteDescriptorKey { secp: &Secp256k1<C>, ) -> Result<bitcoin::PublicKey, ConversionError> { match self.0 { - DescriptorPublicKey::Single(ref pk) => match pk.key { - SinglePubKey::FullKey(pk) => Ok(pk), - SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()), - }, + DescriptorPublicKey::Single(ref pk) => pk.derive_public_key(secp), DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp), DescriptorPublicKey::MultiXPub(_) => { unreachable!("A definite key cannot contain a multipath key.") From 687fd4e7718e2ba20e15a80c4c83f4338f9f7bb4 Mon Sep 17 00:00:00 2001 From: Scott Robinson <ssr@squareup.com> Date: Sun, 23 Jul 2023 13:10:41 +1000 Subject: [PATCH 4/6] Extract specialised DescriptorKey functionality into DescriptorMultiExtendedPublicKey --- src/descriptor/key.rs | 113 ++++++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index f0571eb61..b445739cc 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -20,6 +20,7 @@ use crate::{hash256, MiniscriptKey, ToPublicKey}; type DescriptorSinglePublicKey = SinglePub; type DescriptorExtendedPublicKey = DescriptorXKey<bip32::ExtendedPubKey>; +type DescriptorMultiExtendedPublicKey = DescriptorMultiXKey<bip32::ExtendedPubKey>; /// The descriptor pubkey, either a single pubkey or an xpub. #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] @@ -29,7 +30,7 @@ pub enum DescriptorPublicKey { /// Extended public key (xpub). XPub(DescriptorExtendedPublicKey), /// Multiple extended public keys. - MultiXPub(DescriptorMultiXKey<bip32::ExtendedPubKey>), + MultiXPub(DescriptorMultiExtendedPublicKey), } /// The descriptor secret key, either a single private key or an xprv. @@ -310,22 +311,26 @@ impl fmt::Display for DescriptorExtendedPublicKey { } } +impl fmt::Display for DescriptorMultiExtendedPublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + maybe_fmt_master_id(f, &self.origin)?; + self.xkey.fmt(f)?; + fmt_derivation_paths(f, self.derivation_paths.paths())?; + match self.wildcard { + Wildcard::None => {} + Wildcard::Unhardened => write!(f, "/*")?, + Wildcard::Hardened => write!(f, "/*h")?, + } + Ok(()) + } +} + impl fmt::Display for DescriptorPublicKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { DescriptorPublicKey::Single(ref pk) => pk.fmt(f), DescriptorPublicKey::XPub(ref xpub) => xpub.fmt(f), - DescriptorPublicKey::MultiXPub(ref xpub) => { - maybe_fmt_master_id(f, &xpub.origin)?; - xpub.xkey.fmt(f)?; - fmt_derivation_paths(f, xpub.derivation_paths.paths())?; - match xpub.wildcard { - Wildcard::None => {} - Wildcard::Unhardened => write!(f, "/*")?, - Wildcard::Hardened => write!(f, "/*h")?, - } - Ok(()) - } + DescriptorPublicKey::MultiXPub(ref xpub) => xpub.fmt(f), } } } @@ -501,6 +506,30 @@ impl FromStr for DescriptorExtendedPublicKey { } } +impl FromStr for DescriptorMultiExtendedPublicKey { + type Err = DescriptorKeyParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let (key_part, origin) = parse_key_origin(s)?; + + let (xpub, derivation_paths, wildcard) = + parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?; + + if derivation_paths.len() < 2 { + return Err(DescriptorKeyParseError( + "Multiple derivation paths are required for multi extended keys", + )); + } + + Ok(Self { + origin, + xkey: xpub, + derivation_paths: DerivPaths::new(derivation_paths).expect("Not empty"), + wildcard, + }) + } +} + impl FromStr for DescriptorPublicKey { type Err = DescriptorKeyParseError; @@ -518,12 +547,7 @@ impl FromStr for DescriptorPublicKey { let (xpub, derivation_paths, wildcard) = parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?; if derivation_paths.len() > 1 { - Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey { - origin, - xkey: xpub, - derivation_paths: DerivPaths::new(derivation_paths).expect("Not empty"), - wildcard, - })) + DescriptorMultiExtendedPublicKey::from_str(s).map(Self::MultiXPub) } else { DescriptorExtendedPublicKey::from_str(s).map(Self::XPub) } @@ -757,18 +781,45 @@ impl DescriptorKey for DescriptorExtendedPublicKey { } } +impl DescriptorKey for DescriptorMultiExtendedPublicKey { + fn master_fingerprint(&self) -> bip32::Fingerprint { + if let Some((fingerprint, _)) = self.origin { + fingerprint + } else { + self.xkey.fingerprint() + } + } + + fn full_derivation_path(&self) -> Option<bip32::DerivationPath> { + None + } + + fn has_wildcard(&self) -> bool { + self.wildcard != Wildcard::None + } + + fn at_derivation_index(self, _index: u32) -> Result<DefiniteDescriptorKey, ConversionError> { + Err(ConversionError::MultiKey) + } + + fn is_multipath(&self) -> bool { + true + } + + fn derive_public_key<C: Verification>( + &self, + _secp: &Secp256k1<C>, + ) -> Result<bitcoin::PublicKey, ConversionError> { + Err(ConversionError::MultiKey) + } +} + impl DescriptorPublicKey { /// The fingerprint of the master key associated with this key, `0x00000000` if none. pub fn master_fingerprint(&self) -> bip32::Fingerprint { match *self { DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(), - DescriptorPublicKey::MultiXPub(ref xpub) => { - if let Some((fingerprint, _)) = xpub.origin { - fingerprint - } else { - xpub.xkey.fingerprint() - } - } + DescriptorPublicKey::MultiXPub(ref xpub) => xpub.master_fingerprint(), DescriptorPublicKey::Single(ref single) => single.master_fingerprint(), } } @@ -784,7 +835,7 @@ impl DescriptorPublicKey { match *self { DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(), DescriptorPublicKey::Single(ref single) => single.full_derivation_path(), - DescriptorPublicKey::MultiXPub(_) => None, + DescriptorPublicKey::MultiXPub(ref xpub) => xpub.full_derivation_path(), } } @@ -799,7 +850,7 @@ impl DescriptorPublicKey { match *self { DescriptorPublicKey::Single(ref single) => single.has_wildcard(), DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(), - DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None, + DescriptorPublicKey::MultiXPub(ref xpub) => xpub.has_wildcard(), } } @@ -825,7 +876,7 @@ impl DescriptorPublicKey { match self { DescriptorPublicKey::Single(single) => single.at_derivation_index(index), DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index), - DescriptorPublicKey::MultiXPub(_) => Err(ConversionError::MultiKey), + DescriptorPublicKey::MultiXPub(xpub) => xpub.at_derivation_index(index), } } @@ -834,7 +885,7 @@ impl DescriptorPublicKey { match self { DescriptorPublicKey::Single(single) => single.is_multipath(), DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(), - DescriptorPublicKey::MultiXPub(_) => true, + DescriptorPublicKey::MultiXPub(xpub) => xpub.is_multipath(), } } @@ -1208,9 +1259,7 @@ impl DefiniteDescriptorKey { match self.0 { DescriptorPublicKey::Single(ref pk) => pk.derive_public_key(secp), DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp), - DescriptorPublicKey::MultiXPub(_) => { - unreachable!("A definite key cannot contain a multipath key.") - } + DescriptorPublicKey::MultiXPub(ref xpk) => xpk.derive_public_key(secp), } } From 9c55ad30fedec0af9f7b59180ccbbad1896f7463 Mon Sep 17 00:00:00 2001 From: Scott Robinson <ssr@squareup.com> Date: Mon, 24 Jul 2023 20:59:23 +1000 Subject: [PATCH 5/6] Simplify DescriptorPublicKey::from_str --- src/descriptor/key.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index b445739cc..56312d190 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -534,19 +534,8 @@ impl FromStr for DescriptorPublicKey { type Err = DescriptorKeyParseError; fn from_str(s: &str) -> Result<Self, Self::Err> { - // A "raw" public key without any origin is the least we accept. - if s.len() < 64 { - return Err(DescriptorKeyParseError( - "Key too short (<66 char), doesn't match any format", - )); - } - - let (key_part, origin) = parse_key_origin(s)?; - - if key_part.contains("pub") { - let (xpub, derivation_paths, wildcard) = - parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?; - if derivation_paths.len() > 1 { + if s.contains("pub") { + if s.contains("<") { DescriptorMultiExtendedPublicKey::from_str(s).map(Self::MultiXPub) } else { DescriptorExtendedPublicKey::from_str(s).map(Self::XPub) From b18ba59d03a937f9283db0b5cc84c3a2f456a562 Mon Sep 17 00:00:00 2001 From: Scott Robinson <ssr@squareup.com> Date: Mon, 24 Jul 2023 21:11:07 +1000 Subject: [PATCH 6/6] Make DescriptorPublicKey a DescriptorKey --- src/descriptor/key.rs | 123 +++++++++++++++++++----------------------- src/descriptor/mod.rs | 1 + 2 files changed, 56 insertions(+), 68 deletions(-) diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 56312d190..c3c304f38 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -804,80 +804,18 @@ impl DescriptorKey for DescriptorMultiExtendedPublicKey { } impl DescriptorPublicKey { - /// The fingerprint of the master key associated with this key, `0x00000000` if none. - pub fn master_fingerprint(&self) -> bip32::Fingerprint { - match *self { - DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(), - DescriptorPublicKey::MultiXPub(ref xpub) => xpub.master_fingerprint(), - DescriptorPublicKey::Single(ref single) => single.master_fingerprint(), - } - } - - /// Full path, from the master key - /// - /// For wildcard keys this will return the path up to the wildcard, so you - /// can get full paths by appending one additional derivation step, according - /// to the wildcard type (hardened or normal). - /// - /// For multipath extended keys, this returns `None`. - pub fn full_derivation_path(&self) -> Option<bip32::DerivationPath> { - match *self { - DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(), - DescriptorPublicKey::Single(ref single) => single.full_derivation_path(), - DescriptorPublicKey::MultiXPub(ref xpub) => xpub.full_derivation_path(), - } - } - /// Whether or not the key has a wildcard #[deprecated(note = "use has_wildcard instead")] pub fn is_deriveable(&self) -> bool { self.has_wildcard() } - /// Whether or not the key has a wildcard - pub fn has_wildcard(&self) -> bool { - match *self { - DescriptorPublicKey::Single(ref single) => single.has_wildcard(), - DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(), - DescriptorPublicKey::MultiXPub(ref xpub) => xpub.has_wildcard(), - } - } - #[deprecated(note = "use at_derivation_index instead")] /// Deprecated name for [`Self::at_derivation_index`]. pub fn derive(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> { self.at_derivation_index(index) } - /// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a - /// *definite* key (i.e. one where all the derivation paths are set). - /// - /// # Returns - /// - /// - If this key is not an xpub, returns `self`. - /// - If this key is an xpub but does not have a wildcard, returns `self`. - /// - Otherwise, returns the xpub at derivation `index` (removing the wildcard). - /// - /// # Errors - /// - /// - If `index` is hardened. - pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> { - match self { - DescriptorPublicKey::Single(single) => single.at_derivation_index(index), - DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index), - DescriptorPublicKey::MultiXPub(xpub) => xpub.at_derivation_index(index), - } - } - - /// Whether or not this key has multiple derivation paths. - pub fn is_multipath(&self) -> bool { - match self { - DescriptorPublicKey::Single(single) => single.is_multipath(), - DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(), - DescriptorPublicKey::MultiXPub(xpub) => xpub.is_multipath(), - } - } - /// Get as many keys as derivation paths in this key. /// /// For raw public key and single-path extended keys it will return the key itself. @@ -910,6 +848,59 @@ impl DescriptorPublicKey { } } +impl DescriptorKey for DescriptorPublicKey { + fn master_fingerprint(&self) -> bip32::Fingerprint { + match *self { + DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(), + DescriptorPublicKey::MultiXPub(ref xpub) => xpub.master_fingerprint(), + DescriptorPublicKey::Single(ref single) => single.master_fingerprint(), + } + } + + fn full_derivation_path(&self) -> Option<bip32::DerivationPath> { + match *self { + DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(), + DescriptorPublicKey::Single(ref single) => single.full_derivation_path(), + DescriptorPublicKey::MultiXPub(ref xpub) => xpub.full_derivation_path(), + } + } + + fn has_wildcard(&self) -> bool { + match *self { + DescriptorPublicKey::Single(ref single) => single.has_wildcard(), + DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(), + DescriptorPublicKey::MultiXPub(ref xpub) => xpub.has_wildcard(), + } + } + + fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> { + match self { + DescriptorPublicKey::Single(single) => single.at_derivation_index(index), + DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index), + DescriptorPublicKey::MultiXPub(xpub) => xpub.at_derivation_index(index), + } + } + + fn is_multipath(&self) -> bool { + match self { + DescriptorPublicKey::Single(single) => single.is_multipath(), + DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(), + DescriptorPublicKey::MultiXPub(xpub) => xpub.is_multipath(), + } + } + + fn derive_public_key<C: Verification>( + &self, + secp: &Secp256k1<C>, + ) -> Result<bitcoin::PublicKey, ConversionError> { + match self { + DescriptorPublicKey::Single(single) => single.derive_public_key(secp), + DescriptorPublicKey::XPub(xpub) => xpub.derive_public_key(secp), + DescriptorPublicKey::MultiXPub(xpub) => xpub.derive_public_key(secp), + } + } +} + impl FromStr for DescriptorSecretKey { type Err = DescriptorKeyParseError; @@ -1245,11 +1236,7 @@ impl DefiniteDescriptorKey { &self, secp: &Secp256k1<C>, ) -> Result<bitcoin::PublicKey, ConversionError> { - match self.0 { - DescriptorPublicKey::Single(ref pk) => pk.derive_public_key(secp), - DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp), - DescriptorPublicKey::MultiXPub(ref xpk) => xpk.derive_public_key(secp), - } + self.0.derive_public_key(secp) } /// Construct an instance from a descriptor key and a derivation index @@ -1386,7 +1373,7 @@ mod test { use super::{ DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, - MiniscriptKey, Wildcard, + DescriptorKey, MiniscriptKey, Wildcard, }; use crate::prelude::*; diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 99d89f381..42b4b3b11 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -22,6 +22,7 @@ use bitcoin::{secp256k1, Address, Network, Script, ScriptBuf, TxIn, Witness}; use sync::Arc; use self::checksum::verify_checksum; +use self::key::DescriptorKey; use crate::miniscript::{Legacy, Miniscript, Segwitv0}; use crate::prelude::*; use crate::{