diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 6af0146e0..539c8dc63 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -19,6 +19,8 @@ pub enum DescriptorPublicKey { Single(SinglePub), /// Extended public key (xpub). XPub(DescriptorXKey), + /// Multiple extended public keys. + MultiXPub(DescriptorMultiXKey), } /// The descriptor secret key, either a single private key or an xprv. @@ -28,6 +30,8 @@ pub enum DescriptorSecretKey { Single(SinglePriv), /// Extended private key (xpriv). XPrv(DescriptorXKey), + /// Multiple extended private keys. + MultiXPrv(DescriptorMultiXKey), } /// A descriptor [`SinglePubKey`] with optional origin information. @@ -61,6 +65,44 @@ pub struct DescriptorXKey { pub wildcard: Wildcard, } +/// The derivation paths in a multipath key expression. +#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] +pub struct DerivPaths(Vec); + +impl DerivPaths { + /// Create a non empty derivation paths list. + pub fn new(paths: Vec) -> Option { + if paths.is_empty() { + None + } else { + Some(DerivPaths(paths)) + } + } + + /// Get the list of derivation paths. + pub fn paths(&self) -> &Vec { + &self.0 + } + + /// Get the list of derivation paths. + pub fn into_paths(self) -> Vec { + self.0 + } +} + +/// Instance of one or more extended keys, as specified in BIP 389. +#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] +pub struct DescriptorMultiXKey { + /// Origin information + pub origin: Option<(bip32::Fingerprint, bip32::DerivationPath)>, + /// The extended key + pub xkey: K, + /// The derivation paths. Never empty. + pub derivation_paths: DerivPaths, + /// Whether the descriptor is wildcard + pub wildcard: Wildcard, +} + /// Single public key without any origin or range information. #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] pub enum SinglePubKey { @@ -93,6 +135,17 @@ impl fmt::Display for DescriptorSecretKey { } Ok(()) } + DescriptorSecretKey::MultiXPrv(ref xprv) => { + maybe_fmt_master_id(f, &xprv.origin)?; + xprv.xkey.fmt(f)?; + fmt_derivation_paths(f, &xprv.derivation_paths.paths())?; + match xprv.wildcard { + Wildcard::None => {} + Wildcard::Unhardened => write!(f, "/*")?, + Wildcard::Hardened => write!(f, "/*h")?, + } + Ok(()) + } } } } @@ -246,6 +299,17 @@ impl fmt::Display for DescriptorPublicKey { } Ok(()) } + 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(()) + } } } } @@ -255,6 +319,10 @@ impl DescriptorSecretKey { /// /// If the key is an "XPrv", the hardened derivation steps will be applied /// before converting it to a public key. + /// + /// It will return an error if the key is a "multi-xpriv", as we wouldn't + /// always be able to apply hardened derivation steps if there are multiple + /// paths. pub fn to_public( &self, secp: &Secp256k1, @@ -262,10 +330,54 @@ impl DescriptorSecretKey { let pk = match self { DescriptorSecretKey::Single(prv) => DescriptorPublicKey::Single(prv.to_public(secp)), DescriptorSecretKey::XPrv(xprv) => DescriptorPublicKey::XPub(xprv.to_public(secp)?), + DescriptorSecretKey::MultiXPrv(_) => { + return Err(DescriptorKeyParseError( + "Can't make an extended private key with multiple paths into a public key.", + )) + } }; Ok(pk) } + + /// Whether or not this key has multiple derivation paths. + pub fn is_multipath(&self) -> bool { + match *self { + DescriptorSecretKey::Single(..) | DescriptorSecretKey::XPrv(..) => false, + DescriptorSecretKey::MultiXPrv(_) => true, + } + } + + /// Get as many keys as derivation paths in this key. + /// + /// For raw keys and single-path extended keys it will return the key itself. + /// For multipath extended keys it will return a single-path extended key per derivation + /// path. + pub fn into_single_keys(self) -> Vec { + match self { + DescriptorSecretKey::Single(..) | DescriptorSecretKey::XPrv(..) => vec![self], + DescriptorSecretKey::MultiXPrv(xpub) => { + let DescriptorMultiXKey { + origin, + xkey, + derivation_paths, + wildcard, + } = xpub; + derivation_paths + .into_paths() + .into_iter() + .map(|derivation_path| { + DescriptorSecretKey::XPrv(DescriptorXKey { + origin: origin.clone(), + xkey, + derivation_path, + wildcard, + }) + }) + .collect() + } + } + } } /// Writes the fingerprint of the origin, if there is one. @@ -293,6 +405,27 @@ fn fmt_derivation_path(f: &mut fmt::Formatter, path: &bip32::DerivationPath) -> Ok(()) } +/// Writes multiple derivation paths to the formatter, no leading 'm'. +/// NOTE: we assume paths only differ at a sindle index, as prescribed by BIP389. +/// Will panic if the list of paths is empty. +fn fmt_derivation_paths(f: &mut fmt::Formatter, paths: &[bip32::DerivationPath]) -> fmt::Result { + for (i, child) in paths[0].into_iter().enumerate() { + if paths.len() > 1 && child != &paths[1][i] { + write!(f, "/<")?; + for (j, p) in paths.iter().enumerate() { + write!(f, "{}", p[i])?; + if j != paths.len() - 1 { + write!(f, ";")?; + } + } + write!(f, ">")?; + } else { + write!(f, "/{}", child)?; + } + } + Ok(()) +} + impl FromStr for DescriptorPublicKey { type Err = DescriptorKeyParseError; @@ -304,18 +437,29 @@ impl FromStr for DescriptorPublicKey { )); } - let (key_part, origin) = DescriptorXKey::::parse_xkey_origin(s)?; + let (key_part, origin) = parse_key_origin(s)?; if key_part.contains("pub") { - let (xpub, derivation_path, wildcard) = - DescriptorXKey::::parse_xkey_deriv(key_part)?; - - Ok(DescriptorPublicKey::XPub(DescriptorXKey { - origin, - xkey: xpub, - derivation_path, - wildcard, - })) + let (xpub, derivation_paths, wildcard) = + parse_xkey_deriv::(key_part)?; + if derivation_paths.len() > 1 { + Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey { + origin, + xkey: xpub, + derivation_paths: DerivPaths::new(derivation_paths).expect("Not empty"), + wildcard, + })) + } else { + Ok(DescriptorPublicKey::XPub(DescriptorXKey { + origin, + xkey: xpub, + derivation_path: derivation_paths + .into_iter() + .next() + .unwrap_or_else(|| bip32::DerivationPath::default()), + wildcard, + })) + } } else { let key = match key_part.len() { 64 => { @@ -354,12 +498,15 @@ impl FromStr for DescriptorPublicKey { pub enum ConversionError { /// Attempted to convert a key with hardened derivations to a bitcoin public key HardenedChild, + /// Attempted to convert a key with multiple derivation paths to a bitcoin public key + MultiKey, } impl fmt::Display for ConversionError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ConversionError::HardenedChild => "hardened child step in bip32 path", + ConversionError::MultiKey => "multiple existing keys", }) } } @@ -370,7 +517,7 @@ impl error::Error for ConversionError { use self::ConversionError::*; match self { - HardenedChild => None, + HardenedChild | MultiKey => None, } } } @@ -386,6 +533,13 @@ impl DescriptorPublicKey { xpub.xkey.fingerprint() } } + DescriptorPublicKey::MultiXPub(ref xpub) => { + if let Some((fingerprint, _)) = xpub.origin { + fingerprint + } else { + xpub.xkey.fingerprint() + } + } DescriptorPublicKey::Single(ref single) => { if let Some((fingerprint, _)) = single.origin { fingerprint @@ -407,8 +561,10 @@ impl DescriptorPublicKey { /// /// 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) - pub fn full_derivation_path(&self) -> bip32::DerivationPath { + /// to the wildcard type (hardened or normal). + /// + /// For multipath extended keys, this returns `None`. + pub fn full_derivation_path(&self) -> Option { match *self { DescriptorPublicKey::XPub(ref xpub) => { let origin_path = if let Some((_, ref path)) = xpub.origin { @@ -416,15 +572,16 @@ impl DescriptorPublicKey { } else { bip32::DerivationPath::from(vec![]) }; - origin_path.extend(&xpub.derivation_path) + Some(origin_path.extend(&xpub.derivation_path)) } DescriptorPublicKey::Single(ref single) => { - if let Some((_, ref path)) = single.origin { + Some(if let Some((_, ref path)) = single.origin { path.clone() } else { bip32::DerivationPath::from(vec![]) - } + }) } + DescriptorPublicKey::MultiXPub(_) => None, } } @@ -439,12 +596,13 @@ impl DescriptorPublicKey { match *self { DescriptorPublicKey::Single(..) => false, DescriptorPublicKey::XPub(ref xpub) => xpub.wildcard != Wildcard::None, + DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None, } } #[deprecated(note = "use at_derivation_index instead")] /// Deprecated name of [`at_derivation_index`]. - pub fn derive(self, index: u32) -> DefiniteDescriptorKey { + pub fn derive(self, index: u32) -> Result { self.at_derivation_index(index) } @@ -457,20 +615,24 @@ impl DescriptorPublicKey { /// - If this key is an xpub but does not have a wildcard, returns `self`. /// - Otherwise, returns the xpub at derivation `index` (removing the wildcard). /// - /// # Panics + /// # Errors /// - /// If `index` ≥ 2^31 - pub fn at_derivation_index(self, index: u32) -> DefiniteDescriptorKey { + /// - If `index` is hardened. + pub fn at_derivation_index(self, index: u32) -> Result { 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).expect("index must < 2^31"), + bip32::ChildNumber::from_normal_idx(index) + .ok() + .ok_or(ConversionError::HardenedChild)?, ), Wildcard::Hardened => xpub.derivation_path.into_child( - bip32::ChildNumber::from_hardened_idx(index).expect("index must < 2^31"), + bip32::ChildNumber::from_hardened_idx(index) + .ok() + .ok_or(ConversionError::HardenedChild)?, ), }; DescriptorPublicKey::XPub(DescriptorXKey { @@ -480,10 +642,50 @@ impl DescriptorPublicKey { wildcard: Wildcard::None, }) } + DescriptorPublicKey::MultiXPub(_) => return Err(ConversionError::MultiKey), }; - DefiniteDescriptorKey::new(definite) - .expect("The key should not contain any wildcards at this point") + Ok(DefiniteDescriptorKey::new(definite) + .expect("The key should not contain any wildcards at this point")) + } + + /// Whether or not this key has multiple derivation paths. + pub fn is_multipath(&self) -> bool { + match *self { + DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => false, + DescriptorPublicKey::MultiXPub(_) => true, + } + } + + /// 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. + /// For multipath extended keys it will return a single-path extended key per derivation + /// path. + pub fn into_single_keys(self) -> Vec { + match self { + DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => vec![self], + DescriptorPublicKey::MultiXPub(xpub) => { + let DescriptorMultiXKey { + origin, + xkey, + derivation_paths, + wildcard, + } = xpub; + derivation_paths + .into_paths() + .into_iter() + .map(|derivation_path| { + DescriptorPublicKey::XPub(DescriptorXKey { + origin: origin.clone(), + xkey, + derivation_path, + wildcard, + }) + }) + .collect() + } + } } } @@ -491,7 +693,7 @@ impl FromStr for DescriptorSecretKey { type Err = DescriptorKeyParseError; fn from_str(s: &str) -> Result { - let (key_part, origin) = DescriptorXKey::::parse_xkey_origin(s)?; + let (key_part, origin) = parse_key_origin(s)?; if key_part.len() <= 52 { let sk = bitcoin::PrivateKey::from_str(key_part) @@ -501,111 +703,192 @@ impl FromStr for DescriptorSecretKey { origin: None, })) } else { - let (xprv, derivation_path, wildcard) = - DescriptorXKey::::parse_xkey_deriv(key_part)?; - Ok(DescriptorSecretKey::XPrv(DescriptorXKey { - origin, - xkey: xprv, - derivation_path, - wildcard, - })) + let (xpriv, derivation_paths, wildcard) = + parse_xkey_deriv::(key_part)?; + if derivation_paths.len() > 1 { + Ok(DescriptorSecretKey::MultiXPrv(DescriptorMultiXKey { + origin, + xkey: xpriv, + derivation_paths: DerivPaths::new(derivation_paths).expect("Not empty"), + wildcard, + })) + } else { + Ok(DescriptorSecretKey::XPrv(DescriptorXKey { + origin, + xkey: xpriv, + derivation_path: derivation_paths + .into_iter() + .next() + .unwrap_or_else(|| bip32::DerivationPath::default()), + wildcard, + })) + } } } } -impl DescriptorXKey { - fn parse_xkey_origin( - s: &str, - ) -> Result<(&str, Option), DescriptorKeyParseError> { - for ch in s.as_bytes() { - if *ch < 20 || *ch > 127 { - return Err(DescriptorKeyParseError( - "Encountered an unprintable character", - )); - } - } - - if s.is_empty() { - return Err(DescriptorKeyParseError("Empty key")); +// Parse the origin information part of a descriptor key. +fn parse_key_origin(s: &str) -> Result<(&str, Option), DescriptorKeyParseError> { + for ch in s.as_bytes() { + if *ch < 20 || *ch > 127 { + return Err(DescriptorKeyParseError( + "Encountered an unprintable character", + )); } - let mut parts = s[1..].split(']'); + } - if let Some('[') = s.chars().next() { - let mut raw_origin = parts - .next() - .ok_or(DescriptorKeyParseError("Unclosed '['"))? - .split('/'); + if s.is_empty() { + return Err(DescriptorKeyParseError("Empty key")); + } + let mut parts = s[1..].split(']'); - let origin_id_hex = raw_origin.next().ok_or(DescriptorKeyParseError( - "No master fingerprint found after '['", - ))?; + if let Some('[') = s.chars().next() { + let mut raw_origin = parts + .next() + .ok_or(DescriptorKeyParseError("Unclosed '['"))? + .split('/'); - if origin_id_hex.len() != 8 { - return Err(DescriptorKeyParseError( - "Master fingerprint should be 8 characters long", - )); - } - let parent_fingerprint = bip32::Fingerprint::from_hex(origin_id_hex).map_err(|_| { - DescriptorKeyParseError("Malformed master fingerprint, expected 8 hex chars") - })?; - let origin_path = raw_origin - .map(bip32::ChildNumber::from_str) - .collect::>() - .map_err(|_| { - DescriptorKeyParseError("Error while parsing master derivation path") - })?; - - let key = parts - .next() - .ok_or(DescriptorKeyParseError("No key after origin."))?; + let origin_id_hex = raw_origin.next().ok_or(DescriptorKeyParseError( + "No master fingerprint found after '['", + ))?; - if parts.next().is_some() { - Err(DescriptorKeyParseError( - "Multiple ']' in Descriptor Public Key", - )) - } else { - Ok((key, Some((parent_fingerprint, origin_path)))) - } + if origin_id_hex.len() != 8 { + return Err(DescriptorKeyParseError( + "Master fingerprint should be 8 characters long", + )); + } + let parent_fingerprint = bip32::Fingerprint::from_hex(origin_id_hex).map_err(|_| { + DescriptorKeyParseError("Malformed master fingerprint, expected 8 hex chars") + })?; + let origin_path = raw_origin + .map(bip32::ChildNumber::from_str) + .collect::>() + .map_err(|_| DescriptorKeyParseError("Error while parsing master derivation path"))?; + + let key = parts + .next() + .ok_or(DescriptorKeyParseError("No key after origin."))?; + + if parts.next().is_some() { + Err(DescriptorKeyParseError( + "Multiple ']' in Descriptor Public Key", + )) } else { - Ok((s, None)) + Ok((key, Some((parent_fingerprint, origin_path)))) } + } else { + Ok((s, None)) } +} - /// Parse an extended key concatenated to a derivation path. - fn parse_xkey_deriv( - key_deriv: &str, - ) -> Result<(K, bip32::DerivationPath, Wildcard), DescriptorKeyParseError> { - let mut key_deriv = key_deriv.split('/'); - let xkey_str = key_deriv.next().ok_or(DescriptorKeyParseError( - "No key found after origin description", - ))?; - let xkey = K::from_str(xkey_str) - .map_err(|_| DescriptorKeyParseError("Error while parsing xkey."))?; - - let mut wildcard = Wildcard::None; - let derivation_path = key_deriv - .filter_map(|p| { - if wildcard == Wildcard::None && p == "*" { - wildcard = Wildcard::Unhardened; - None - } else if wildcard == Wildcard::None && (p == "*'" || p == "*h") { - wildcard = Wildcard::Hardened; - None - } else if wildcard != Wildcard::None { - Some(Err(DescriptorKeyParseError( - "'*' may only appear as last element in a derivation path.", - ))) +/// Parse an extended key concatenated to a derivation path. +fn parse_xkey_deriv( + key_deriv: &str, +) -> Result<(K, Vec, Wildcard), DescriptorKeyParseError> { + let mut key_deriv = key_deriv.split('/'); + let xkey_str = key_deriv.next().ok_or(DescriptorKeyParseError( + "No key found after origin description", + ))?; + let xkey = + K::from_str(xkey_str).map_err(|_| DescriptorKeyParseError("Error while parsing xkey."))?; + + let mut wildcard = Wildcard::None; + let mut multipath = false; + let derivation_paths = key_deriv + .filter_map(|p| { + if wildcard == Wildcard::None && p == "*" { + wildcard = Wildcard::Unhardened; + None + } else if wildcard == Wildcard::None && (p == "*'" || p == "*h") { + wildcard = Wildcard::Hardened; + None + } else if wildcard != Wildcard::None { + Some(Err(DescriptorKeyParseError( + "'*' may only appear as last element in a derivation path.", + ))) + } else { + // BIP389 defines a new step in the derivation path. This step contains two or more + // derivation indexes in the form '<1;2;3';4h;5H;6>'. + if p.starts_with('<') && p.ends_with('>') { + // There may only be one occurence of this step. + if multipath { + return Some(Err(DescriptorKeyParseError( + "'<' may only appear once in a derivation path.", + ))); + } + multipath = true; + + // The step must contain at least two derivation indexes. + // So it's at least '<' + a number + ';' + a number + '>'. + if p.len() < 5 || !p.contains(';') { + return Some(Err(DescriptorKeyParseError( + "Invalid multi index step in multipath descriptor.", + ))); + } + + // Collect all derivation indexes at this step. + let indexes = p[1..p.len() - 1].split(';'); + Some( + indexes + .into_iter() + .map(|s| { + bip32::ChildNumber::from_str(s).map_err(|_| { + DescriptorKeyParseError( + "Error while parsing index in key derivation path.", + ) + }) + }) + .collect::, _>>(), + ) } else { - Some(bip32::ChildNumber::from_str(p).map_err(|_| { - DescriptorKeyParseError("Error while parsing key derivation path") - })) + // Not a BIP389 step, just a regular derivation index. + Some( + bip32::ChildNumber::from_str(p) + .map(|i| vec![i]) + .map_err(|_| { + DescriptorKeyParseError("Error while parsing key derivation path") + }), + ) } - }) - .collect::>()?; + } + }) + // Now we've got all derivation indexes in a list of vectors of indexes. If the derivation + // path was empty then this list is empty. If the derivation path didn't contain any BIP389 + // step all the vectors of indexes contain a single element. If it did though, one of the + // vectors contains more than one element. + // Now transform this list of vectors of steps into distinct derivation paths. + .into_iter() + .fold(Ok(Vec::new()), |paths, index_list| { + let mut paths = paths?; + let mut index_list = index_list?.into_iter(); + let first_index = index_list + .next() + .expect("There is always at least one element"); - Ok((xkey, derivation_path, wildcard)) - } + if paths.is_empty() { + paths.push(vec![first_index]); + } else { + for path in paths.iter_mut() { + path.push(first_index); + } + } + + // If the step is a BIP389 one, create as many paths as there is indexes. + for (i, index) in index_list.enumerate() { + paths.push(paths[0].clone()); + *paths[i + 1].last_mut().expect("Never empty") = index; + } + + Ok(paths) + })? + .into_iter() + .map(|index_list| index_list.into_iter().collect::()) + .collect::>(); + Ok((xkey, derivation_paths, wildcard)) +} + +impl DescriptorXKey { /// Compares this key with a `keysource` and returns the matching derivation path, if any. /// /// For keys that have an origin, the `keysource`'s fingerprint will be compared @@ -721,6 +1004,14 @@ impl MiniscriptKey for DescriptorPublicKey { _ => false, } } + + fn num_der_paths(&self) -> usize { + match self { + DescriptorPublicKey::Single(_) => 0, + DescriptorPublicKey::XPub(_) => 1, + DescriptorPublicKey::MultiXPub(xpub) => xpub.derivation_paths.paths().len(), + } + } } impl DefiniteDescriptorKey { @@ -754,6 +1045,9 @@ impl DefiniteDescriptorKey { Err(e) => unreachable!("cryptographically unreachable: {}", e), }, }, + DescriptorPublicKey::MultiXPub(_) => { + unreachable!("A definite key cannot contain a multipath key.") + } } } @@ -773,8 +1067,8 @@ impl DefiniteDescriptorKey { self.0.master_fingerprint() } - /// Full path, from the master key - pub fn full_derivation_path(&self) -> bip32::DerivationPath { + /// Full path from the master key if not a multipath extended key. + pub fn full_derivation_path(&self) -> Option { self.0.full_derivation_path() } } @@ -811,6 +1105,10 @@ impl MiniscriptKey for DefiniteDescriptorKey { fn is_x_only_key(&self) -> bool { self.0.is_x_only_key() } + + fn num_der_paths(&self) -> usize { + self.0.num_der_paths() + } } impl ToPublicKey for DefiniteDescriptorKey { @@ -847,8 +1145,12 @@ mod test { use core::str::FromStr; use bitcoin::secp256k1; + use bitcoin::util::bip32; - use super::{DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey}; + use super::{ + DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, + MiniscriptKey, Wildcard, + }; use crate::prelude::*; #[test] @@ -947,17 +1249,26 @@ mod test { fn test_wildcard() { let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2").unwrap(); assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00"); - assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'/2"); + assert_eq!( + public_key.full_derivation_path().unwrap().to_string(), + "m/0'/1'/2" + ); assert_eq!(public_key.has_wildcard(), false); let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/*").unwrap(); assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00"); - assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'"); + assert_eq!( + public_key.full_derivation_path().unwrap().to_string(), + "m/0'/1'" + ); assert_eq!(public_key.has_wildcard(), true); let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/*h").unwrap(); assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00"); - assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'"); + assert_eq!( + public_key.full_derivation_path().unwrap().to_string(), + "m/0'/1'" + ); assert_eq!(public_key.has_wildcard(), true); } @@ -969,33 +1280,45 @@ mod test { let public_key = secret_key.to_public(&secp).unwrap(); assert_eq!(public_key.to_string(), "[2cbe2a6d/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2"); assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d"); - assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'/2"); + assert_eq!( + public_key.full_derivation_path().unwrap().to_string(), + "m/0'/1'/2" + ); assert_eq!(public_key.has_wildcard(), false); let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2'").unwrap(); let public_key = secret_key.to_public(&secp).unwrap(); assert_eq!(public_key.to_string(), "[2cbe2a6d/0'/1'/2']tpubDDPuH46rv4dbFtmF6FrEtJEy1CvLZonyBoVxF6xsesHdYDdTBrq2mHhm8AbsPh39sUwL2nZyxd6vo4uWNTU9v4t893CwxjqPnwMoUACLvMV"); assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d"); - assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'/2'"); + assert_eq!( + public_key.full_derivation_path().unwrap().to_string(), + "m/0'/1'/2'" + ); let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0/1/2").unwrap(); let public_key = secret_key.to_public(&secp).unwrap(); assert_eq!(public_key.to_string(), "tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr/0/1/2"); assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d"); - assert_eq!(public_key.full_derivation_path().to_string(), "m/0/1/2"); + assert_eq!( + public_key.full_derivation_path().unwrap().to_string(), + "m/0/1/2" + ); let secret_key = DescriptorSecretKey::from_str("[aabbccdd]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0/1/2").unwrap(); let public_key = secret_key.to_public(&secp).unwrap(); assert_eq!(public_key.to_string(), "[aabbccdd]tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr/0/1/2"); assert_eq!(public_key.master_fingerprint().to_string(), "aabbccdd"); - assert_eq!(public_key.full_derivation_path().to_string(), "m/0/1/2"); + assert_eq!( + public_key.full_derivation_path().unwrap().to_string(), + "m/0/1/2" + ); let secret_key = DescriptorSecretKey::from_str("[aabbccdd/90']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2").unwrap(); let public_key = secret_key.to_public(&secp).unwrap(); assert_eq!(public_key.to_string(), "[aabbccdd/90'/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2"); assert_eq!(public_key.master_fingerprint().to_string(), "aabbccdd"); assert_eq!( - public_key.full_derivation_path().to_string(), + public_key.full_derivation_path().unwrap().to_string(), "m/90'/0'/1'/2" ); } @@ -1012,4 +1335,193 @@ mod test { b"\xb0\x59\x11\x6a" ); } + + fn get_multipath_xpub( + key_str: &str, + num_paths: usize, + ) -> DescriptorMultiXKey { + let desc_key = DescriptorPublicKey::from_str(key_str).unwrap(); + assert_eq!(desc_key.num_der_paths(), num_paths); + match desc_key { + DescriptorPublicKey::MultiXPub(xpub) => xpub, + _ => unreachable!(), + } + } + + fn get_multipath_xprv(key_str: &str) -> DescriptorMultiXKey { + let desc_key = DescriptorSecretKey::from_str(key_str).unwrap(); + match desc_key { + DescriptorSecretKey::MultiXPrv(xprv) => xprv, + _ => unreachable!(), + } + } + + #[test] + fn multipath_extended_keys() { + let secp = secp256k1::Secp256k1::signing_only(); + + // We can have a key in a descriptor that has multiple paths + let xpub = get_multipath_xpub("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/<0;1;42;9854>", 4); + assert_eq!( + xpub.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/2/0").unwrap(), + bip32::DerivationPath::from_str("m/2/1").unwrap(), + bip32::DerivationPath::from_str("m/2/42").unwrap(), + bip32::DerivationPath::from_str("m/2/9854").unwrap() + ], + ); + assert_eq!( + xpub, + get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string(), 4) + ); + // Even if it's in the middle of the derivation path. + let xpub = get_multipath_xpub("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/<0;1;9854>/0/5/10", 3); + assert_eq!( + xpub.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/2/0/0/5/10").unwrap(), + bip32::DerivationPath::from_str("m/2/1/0/5/10").unwrap(), + bip32::DerivationPath::from_str("m/2/9854/0/5/10").unwrap() + ], + ); + assert_eq!( + xpub, + get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string(), 3) + ); + // Even if it is a wildcard extended key. + let xpub = get_multipath_xpub("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/<0;1;9854>/3456/9876/*", 3); + assert_eq!(xpub.wildcard, Wildcard::Unhardened); + assert_eq!( + xpub.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/2/0/3456/9876").unwrap(), + bip32::DerivationPath::from_str("m/2/1/3456/9876").unwrap(), + bip32::DerivationPath::from_str("m/2/9854/3456/9876").unwrap() + ], + ); + assert_eq!( + xpub, + get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string(), 3) + ); + // Also even if it has an origin. + let xpub = get_multipath_xpub("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/<0;1>/*", 2); + assert_eq!(xpub.wildcard, Wildcard::Unhardened); + assert_eq!( + xpub.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/0").unwrap(), + bip32::DerivationPath::from_str("m/1").unwrap(), + ], + ); + assert_eq!( + xpub, + get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string(), 2) + ); + // Also if it has hardened steps in the derivation path. In fact, it can also have hardened + // indexes even at the step with multiple indexes! + let xpub = get_multipath_xpub("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/<0';1h>/8h/*'", 2); + assert_eq!(xpub.wildcard, Wildcard::Hardened); + assert_eq!( + xpub.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/9478'/0'/8'").unwrap(), + bip32::DerivationPath::from_str("m/9478h/1h/8h").unwrap(), + ], + ); + assert_eq!( + xpub, + get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string(), 2) + ); + // You can't get the "full derivation path" for a multipath extended public key. + let desc_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/<0';1>/8h/*'").unwrap(); + assert!(desc_key.full_derivation_path().is_none()); + assert!(desc_key.is_multipath()); + assert_eq!(desc_key.into_single_keys(), vec![DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/0'/8h/*'").unwrap(), DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/1/8h/*'").unwrap()]); + + // All the same but with extended private keys instead of xpubs. + let xprv = get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/2/<0;1;42;9854>"); + assert_eq!( + xprv.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/2/0").unwrap(), + bip32::DerivationPath::from_str("m/2/1").unwrap(), + bip32::DerivationPath::from_str("m/2/42").unwrap(), + bip32::DerivationPath::from_str("m/2/9854").unwrap() + ], + ); + assert_eq!( + xprv, + get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string()) + ); + let xprv = get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/2/<0;1;9854>/0/5/10"); + assert_eq!( + xprv.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/2/0/0/5/10").unwrap(), + bip32::DerivationPath::from_str("m/2/1/0/5/10").unwrap(), + bip32::DerivationPath::from_str("m/2/9854/0/5/10").unwrap() + ], + ); + assert_eq!( + xprv, + get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string()) + ); + let xprv = get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/2/<0;1;9854>/3456/9876/*"); + assert_eq!(xprv.wildcard, Wildcard::Unhardened); + assert_eq!( + xprv.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/2/0/3456/9876").unwrap(), + bip32::DerivationPath::from_str("m/2/1/3456/9876").unwrap(), + bip32::DerivationPath::from_str("m/2/9854/3456/9876").unwrap() + ], + ); + assert_eq!( + xprv, + get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string()) + ); + let xprv = get_multipath_xprv("[abcdef00/0'/1']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/<0;1>/*"); + assert_eq!(xprv.wildcard, Wildcard::Unhardened); + assert_eq!( + xprv.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/0").unwrap(), + bip32::DerivationPath::from_str("m/1").unwrap(), + ], + ); + assert_eq!( + xprv, + get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string()) + ); + let xprv = get_multipath_xprv("[abcdef00/0'/1']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/9478'/<0';1h>/8h/*'"); + assert_eq!(xprv.wildcard, Wildcard::Hardened); + assert_eq!( + xprv.derivation_paths.paths(), + &vec![ + bip32::DerivationPath::from_str("m/9478'/0'/8'").unwrap(), + bip32::DerivationPath::from_str("m/9478h/1h/8h").unwrap(), + ], + ); + assert_eq!( + xprv, + get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string()) + ); + let desc_key = DescriptorSecretKey::from_str("[abcdef00/0'/1']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/9478'/<0';1>/8h/*'").unwrap(); + assert!(desc_key.to_public(&secp).is_err()); + assert!(desc_key.is_multipath()); + assert_eq!(desc_key.into_single_keys(), vec![DescriptorSecretKey::from_str("[abcdef00/0'/1']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/9478'/0'/8h/*'").unwrap(), DescriptorSecretKey::from_str("[abcdef00/0'/1']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/9478'/1/8h/*'").unwrap()]); + + // It's invalid to: + // - Not have opening or closing brackets + // - Have multiple steps with different indexes + // - Only have one index within the brackets + DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/<0;1;42;9854").unwrap_err(); + DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/0;1;42;9854>").unwrap_err(); + DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1>/96/<0;1>").unwrap_err(); + DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0>").unwrap_err(); + DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;>").unwrap_err(); + DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<;1>").unwrap_err(); + DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1;>").unwrap_err(); + } } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 31a30b2e3..a7c8f25bc 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -58,8 +58,9 @@ mod checksum; mod key; pub use self::key::{ - ConversionError, DefiniteDescriptorKey, DescriptorKeyParseError, DescriptorPublicKey, - DescriptorSecretKey, DescriptorXKey, InnerXKey, SinglePriv, SinglePub, SinglePubKey, Wildcard, + ConversionError, DefiniteDescriptorKey, DerivPaths, DescriptorKeyParseError, + DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, InnerXKey, + SinglePriv, SinglePub, SinglePubKey, Wildcard, }; /// Alias type for a map of public key to secret key @@ -525,26 +526,30 @@ impl Descriptor { /// Replaces all wildcards (i.e. `/*`) in the descriptor with a particular derivation index, /// turning it into a *definite* descriptor. /// - /// # Panics - /// - /// If index ≥ 2^31 - pub fn at_derivation_index(&self, index: u32) -> Descriptor { + /// # Errors + /// - If index ≥ 2^31 + pub fn at_derivation_index( + &self, + index: u32, + ) -> Result, ConversionError> { struct Derivator(u32); - impl Translator for Derivator { - fn pk(&mut self, pk: &DescriptorPublicKey) -> Result { - Ok(pk.clone().at_derivation_index(self.0)) + impl Translator for Derivator { + fn pk( + &mut self, + pk: &DescriptorPublicKey, + ) -> Result { + pk.clone().at_derivation_index(self.0) } - translate_hash_clone!(DescriptorPublicKey, DescriptorPublicKey, ()); + translate_hash_clone!(DescriptorPublicKey, DescriptorPublicKey, ConversionError); } self.translate_pk(&mut Derivator(index)) - .expect("BIP 32 key index substitution cannot fail") } #[deprecated(note = "use at_derivation_index instead")] /// Deprecated name for [`at_derivation_index`]. - pub fn derive(&self, index: u32) -> Descriptor { + pub fn derive(&self, index: u32) -> Result, ConversionError> { self.at_derivation_index(index) } @@ -561,8 +566,8 @@ impl Descriptor { /// .expect("Valid ranged descriptor"); /// # let index = 42; /// # let secp = Secp256k1::verification_only(); - /// let derived_descriptor = descriptor.at_derivation_index(index).derived_descriptor(&secp); - /// # assert_eq!(descriptor.derived_descriptor(&secp, index), derived_descriptor); + /// let derived_descriptor = descriptor.at_derivation_index(index).unwrap().derived_descriptor(&secp).unwrap(); + /// # assert_eq!(descriptor.derived_descriptor(&secp, index).unwrap(), derived_descriptor); /// ``` /// /// and is only here really here for backwards compatbility. @@ -579,7 +584,7 @@ impl Descriptor { secp: &secp256k1::Secp256k1, index: u32, ) -> Result, ConversionError> { - self.at_derivation_index(index).derived_descriptor(&secp) + self.at_derivation_index(index)?.derived_descriptor(&secp) } /// Parse a descriptor that may contain secret keys @@ -723,6 +728,104 @@ impl Descriptor { Ok(None) } + + /// Whether this descriptor contains a key that has multiple derivation paths. + pub fn is_multipath(&self) -> bool { + self.for_any_key(DescriptorPublicKey::is_multipath) + } + + /// Get as many descriptors as different paths in this descriptor. + /// + /// For multipath descriptors it will return as many descriptors as there is + /// "parallel" paths. For regular descriptors it will just return itself. + pub fn into_single_descriptors(self) -> Result>, Error> { + // All single-path descriptors contained in this descriptor. + let mut descriptors = Vec::new(); + // We (ab)use `for_any_key` to gather the number of separate descriptors. + if !self.for_any_key(|key| { + // All multipath keys must have the same number of indexes at the "multi-index" + // step. So we can return early if we already populated the vector. + if !descriptors.is_empty() { + return true; + } + + match key { + DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => false, + DescriptorPublicKey::MultiXPub(xpub) => { + for _ in 0..xpub.derivation_paths.paths().len() { + descriptors.push(self.clone()); + } + true + } + } + }) { + // If there is no multipath key, return early. + return Ok(vec![self]); + } + assert!(!descriptors.is_empty()); + + // Now, transform the multipath key of each descriptor into a single-key using each index. + struct IndexChoser(usize); + impl Translator for IndexChoser { + fn pk(&mut self, pk: &DescriptorPublicKey) -> Result { + match pk { + DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => { + Ok(pk.clone()) + } + DescriptorPublicKey::MultiXPub(_) => pk + .clone() + .into_single_keys() + .get(self.0) + .cloned() + .ok_or(Error::MultipathDescLenMismatch), + } + } + translate_hash_clone!(DescriptorPublicKey, DescriptorPublicKey, Error); + } + + for (i, desc) in descriptors.iter_mut().enumerate() { + let mut index_choser = IndexChoser(i); + *desc = desc.translate_pk(&mut index_choser)?; + } + + Ok(descriptors) + } +} + +impl Descriptor { + /// Whether this descriptor is a multipath descriptor that contains any 2 multipath keys + /// with a different number of derivation paths. + /// Such a descriptor is invalid according to BIP389. + pub fn multipath_length_mismatch(&self) -> bool { + // (Ab)use `for_each_key` to record the number of derivation paths a multipath key has. + #[derive(PartialEq)] + enum MultipathLenChecker { + SinglePath, + MultipathLen(usize), + LenMismatch, + } + + let mut checker = MultipathLenChecker::SinglePath; + self.for_each_key(|key| { + match key.num_der_paths() { + 0 | 1 => {} + n => match checker { + MultipathLenChecker::SinglePath => { + checker = MultipathLenChecker::MultipathLen(n); + } + MultipathLenChecker::MultipathLen(len) => { + if len != n { + checker = MultipathLenChecker::LenMismatch; + } + } + MultipathLenChecker::LenMismatch => {} + }, + } + true + }); + + checker == MultipathLenChecker::LenMismatch + } } impl Descriptor { @@ -741,7 +844,7 @@ impl Descriptor { /// let secp = secp256k1::Secp256k1::verification_only(); /// let descriptor = Descriptor::::from_str("tr(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)") /// .expect("Valid ranged descriptor"); - /// let result = descriptor.at_derivation_index(0).derived_descriptor(&secp).expect("Non-hardened derivation"); + /// let result = descriptor.at_derivation_index(0).unwrap().derived_descriptor(&secp).expect("Non-hardened derivation"); /// assert_eq!(result.to_string(), "tr(03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115)#6qm9h8ym"); /// ``` /// @@ -795,13 +898,19 @@ impl_from_str!( // tr tree parsing has special code // Tr::from_str will check the checksum // match "tr(" to handle more extensibly - if s.starts_with("tr(") { + let desc = if s.starts_with("tr(") { Ok(Descriptor::Tr(Tr::from_str(s)?)) } else { let desc_str = verify_checksum(s)?; let top = expression::Tree::from_str(desc_str)?; expression::FromTree::from_tree(&top) + }?; + + if desc.multipath_length_mismatch() { + return Err(Error::MultipathDescLenMismatch); } + + Ok(desc) } ); @@ -1594,12 +1703,14 @@ mod tests { // Same address let addr_one = desc_one .at_derivation_index(index) + .unwrap() .derived_descriptor(&secp_ctx) .unwrap() .address(bitcoin::Network::Bitcoin) .unwrap(); let addr_two = desc_two .at_derivation_index(index) + .unwrap() .derived_descriptor(&secp_ctx) .unwrap() .address(bitcoin::Network::Bitcoin) @@ -1677,7 +1788,7 @@ pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHW pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; let policy: policy::concrete::Policy = descriptor_str.parse().unwrap(); let descriptor = Descriptor::new_sh(policy.compile().unwrap()).unwrap(); - let definite_descriptor = descriptor.at_derivation_index(42); + let definite_descriptor = descriptor.at_derivation_index(42).unwrap(); let res_descriptor_str = "thresh(2,\ pk([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/42),\ @@ -1837,4 +1948,39 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; "tr(020000000000000000000000000000000000000000000000000000000000000002)", ); } + + #[test] + fn multipath_descriptors() { + // We can parse a multipath descriptors, and make it into separate single-path descriptors. + let desc = Descriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/<7';8h;20>/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/4567/<0;1;987>/*)))").unwrap(); + assert!(desc.is_multipath()); + assert!(!desc.multipath_length_mismatch()); + assert_eq!(desc.into_single_descriptors().unwrap(), vec![ + Descriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/7'/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/4567/0/*)))").unwrap(), + Descriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/8h/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/4567/1/*)))").unwrap(), + Descriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/20/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/4567/987/*)))").unwrap() + ]); + + // Even if only one of the keys is multipath. + let desc = Descriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/<0;1>/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/4567/*)))").unwrap(); + assert!(desc.is_multipath()); + assert!(!desc.multipath_length_mismatch()); + assert_eq!(desc.into_single_descriptors().unwrap(), vec![ + Descriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/0/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/4567/*)))").unwrap(), + Descriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/1/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/4567/*)))").unwrap(), + ]); + + // We can detect regular single-path descriptors. + let notmulti_desc = Descriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/4567/*)))").unwrap(); + assert!(!notmulti_desc.is_multipath()); + assert!(!notmulti_desc.multipath_length_mismatch()); + assert_eq!( + notmulti_desc.clone().into_single_descriptors().unwrap(), + vec![notmulti_desc] + ); + + // We refuse to parse multipath descriptors with a mismatch in the number of derivation paths between keys. + Descriptor::::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/<0;1>/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/<0;1;2;3;4>/*)))").unwrap_err(); + Descriptor::::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/<0;1;2;3>/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/<0;1;2>/*)))").unwrap_err(); + } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 293fd3273..c7ff773de 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -138,6 +138,10 @@ impl MiniscriptKey for BitcoinKey { type Hash256 = hash256::Hash; type Ripemd160 = ripemd160::Hash; type Hash160 = hash160::Hash; + + fn num_der_paths(&self) -> usize { + 0 + } } impl<'txin> Interpreter<'txin> { diff --git a/src/lib.rs b/src/lib.rs index 713d66eb9..259ca8426 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,6 +162,10 @@ pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Ha false } + /// Returns the number of different derivation paths in this key. Only >1 for keys + /// in BIP389 multipath descriptors. + fn num_der_paths(&self) -> usize; + /// The associated [`sha256::Hash`] for this [`MiniscriptKey`], /// used in the hash256 fragment. type Sha256: Clone + Eq + Ord + fmt::Display + fmt::Debug + hash::Hash; @@ -183,6 +187,10 @@ impl MiniscriptKey for bitcoin::secp256k1::PublicKey { type Hash256 = hash256::Hash; type Ripemd160 = ripemd160::Hash; type Hash160 = hash160::Hash; + + fn num_der_paths(&self) -> usize { + 0 + } } impl MiniscriptKey for bitcoin::PublicKey { @@ -191,6 +199,10 @@ impl MiniscriptKey for bitcoin::PublicKey { !self.compressed } + fn num_der_paths(&self) -> usize { + 0 + } + type Sha256 = sha256::Hash; type Hash256 = hash256::Hash; type Ripemd160 = ripemd160::Hash; @@ -206,6 +218,10 @@ impl MiniscriptKey for bitcoin::secp256k1::XOnlyPublicKey { fn is_x_only_key(&self) -> bool { true } + + fn num_der_paths(&self) -> usize { + 0 + } } impl MiniscriptKey for String { @@ -213,6 +229,10 @@ impl MiniscriptKey for String { type Hash256 = String; type Ripemd160 = String; type Hash160 = String; + + fn num_der_paths(&self) -> usize { + 0 + } } /// Trait describing public key types which can be converted to bitcoin pubkeys @@ -345,6 +365,10 @@ impl MiniscriptKey for DummyKey { type Hash256 = DummyHash256Hash; type Ripemd160 = DummyRipemd160Hash; type Hash160 = DummyHash160Hash; + + fn num_der_paths(&self) -> usize { + 0 + } } impl hash::Hash for DummyKey { @@ -676,6 +700,9 @@ pub enum Error { TrNoScriptCode, /// No explicit script for Tr descriptors TrNoExplicitScript, + /// At least two BIP389 key expressions in the descriptor contain tuples of + /// derivation indexes of different lengths. + MultipathDescLenMismatch, } // https://github.com/sipa/miniscript/pull/5 for discussion on this number @@ -749,6 +776,7 @@ impl fmt::Display for Error { Error::TaprootSpendInfoUnavialable => write!(f, "Taproot Spend Info not computed."), Error::TrNoScriptCode => write!(f, "No script code for Tr descriptors"), Error::TrNoExplicitScript => write!(f, "No script code for Tr descriptors"), + Error::MultipathDescLenMismatch => write!(f, "At least two BIP389 key expressions in the descriptor contain tuples of derivation indexes of different lengths"), } } } @@ -789,7 +817,8 @@ impl error::Error for Error { | BareDescriptorAddr | TaprootSpendInfoUnavialable | TrNoScriptCode - | TrNoExplicitScript => None, + | TrNoExplicitScript + | MultipathDescLenMismatch => None, Script(e) => Some(e), AddrError(e) => Some(e), BadPubkey(e) => Some(e), diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 08c3efb1a..1f79f935f 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -1015,7 +1015,11 @@ impl Translator( ik_derived, ( vec![], - (ik_xpk.master_fingerprint(), ik_xpk.full_derivation_path()), + ( + ik_xpk.master_fingerprint(), + ik_xpk + .full_derivation_path() + .ok_or(descriptor::ConversionError::MultiKey)?, + ), ), ); @@ -1174,6 +1183,9 @@ fn update_item_with_descriptor_helper( for (pk_pkh_derived, pk_pkh_xpk) in ms_derived.iter_pk().zip(ms.iter_pk()) { let (xonly, xpk) = (pk_pkh_derived.to_x_only_pubkey(), pk_pkh_xpk); + let xpk_full_derivation_path = xpk + .full_derivation_path() + .ok_or(descriptor::ConversionError::MultiKey)?; item.tap_key_origins() .entry(xonly) .and_modify(|(tapleaf_hashes, _)| { @@ -1184,7 +1196,7 @@ fn update_item_with_descriptor_helper( .or_insert_with(|| { ( vec![tapleaf_hash], - (xpk.master_fingerprint(), xpk.full_derivation_path()), + (xpk.master_fingerprint(), xpk_full_derivation_path), ) }); } diff --git a/tests/test_desc.rs b/tests/test_desc.rs index 90a22a601..db39590b1 100644 --- a/tests/test_desc.rs +++ b/tests/test_desc.rs @@ -85,7 +85,8 @@ pub fn test_desc_satisfy( let definite_desc = test_util::parse_test_desc(&descriptor, &testdata.pubdata) .map_err(|_| DescError::DescParseError)? - .at_derivation_index(0); + .at_derivation_index(0) + .unwrap(); let derived_desc = definite_desc.derived_descriptor(&secp).unwrap(); let desc_address = derived_desc.address(bitcoin::Network::Regtest);