diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 3c726501a..f1fee030b 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -27,6 +27,8 @@ pub enum DescriptorPublicKey { XPub(DescriptorXKey), /// Multiple extended public keys. MultiXPub(DescriptorMultiXKey), + /// MuSig2 aggregate key expression. + Musig(DescriptorMusigKey), } /// The descriptor secret key, either a single private key or an xprv. @@ -105,6 +107,33 @@ pub struct DescriptorMultiXKey { pub wildcard: Wildcard, } +/// Instance of a BIP390 `musig()` aggregate key expression. +/// +/// X-only participants are lifted with an even y-coordinate before aggregation. +#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] +pub struct DescriptorMusigKey { + /// Participant keys. + participants: Vec, + /// Derivation paths applied to the aggregate key. Never empty. + derivation_paths: DerivPaths, + /// Whether the aggregate key expression is wildcard. + wildcard: Wildcard, +} + +/// The way a `musig()` key expression applies derivation. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MusigDerivationKind<'a> { + /// Participant keys are derived first, then aggregated. + DeriveThenAggregate, + /// Participant xpubs are aggregated first, then the BIP328 synthetic xpub is derived. + AggregateThenDerive { + /// Derivation paths applied to the aggregate key. + derivation_paths: &'a DerivPaths, + /// Wildcard applied to the aggregate key. + wildcard: Wildcard, + }, +} + /// Single public key without any origin or range information. #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] pub enum SinglePubKey { @@ -119,7 +148,7 @@ pub enum SinglePubKey { pub struct DefiniteDescriptorKey(DescriptorPublicKey); /// Network information extracted from extended keys in a descriptor. -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum XKeyNetwork { /// No extended keys are present in the descriptor. NoXKeys, @@ -456,6 +485,43 @@ impl fmt::Display for NonDefiniteKeyError { #[cfg(feature = "std")] impl error::Error for NonDefiniteKeyError {} +/// Error deriving a `musig()` aggregate public key. +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum MusigKeyAggError { + /// The `musig()` key expression is not definite. + NonDefiniteKey(NonDefiniteKeyError), + /// BIP32 aggregate-level derivation failed. + AggregateDerivation(bip32::Error), + /// BIP327 key aggregation produced the point at infinity. + Infinity, +} + +impl fmt::Display for MusigKeyAggError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NonDefiniteKey(err) => err.fmt(f), + Self::AggregateDerivation(err) => err.fmt(f), + Self::Infinity => f.write_str("musig aggregate public key is infinity"), + } + } +} + +#[cfg(feature = "std")] +impl error::Error for MusigKeyAggError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Self::NonDefiniteKey(err) => Some(err), + Self::AggregateDerivation(err) => Some(err), + Self::Infinity => None, + } + } +} + +impl From for MusigKeyAggError { + fn from(err: NonDefiniteKeyError) -> Self { Self::NonDefiniteKey(err) } +} + /// Kinds of malformed key data #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] @@ -466,10 +532,17 @@ pub enum MalformedKeyDataKind { InvalidFullPublicKeyPrefix, InvalidMasterFingerprintLength, InvalidMultiIndexStep, + InvalidMusigAggregateDerivation, + InvalidMusigExpression, + InvalidMusigParticipant, InvalidMultiXKeyDerivation, InvalidPublicKeyLength, InvalidWildcardInDerivationPath, KeyTooShort, + MusigEmpty, + MusigHardenedDerivation, + MusigMultipathLenMismatch, + MusigNested, MultipleFingerprintsInPublicKey, MultipleDerivationPathIndexSteps, NoKeyAfterOrigin, @@ -485,10 +558,17 @@ impl fmt::Display for MalformedKeyDataKind { Self::InvalidFullPublicKeyPrefix => "only full public keys with prefixes '02', '03' or '04' are allowed", Self::InvalidMasterFingerprintLength => "master fingerprint should be 8 characters long", Self::InvalidMultiIndexStep => "invalid multi index step in multipath descriptor", + Self::InvalidMusigAggregateDerivation => "musig aggregate-level derivation requires non-ranged xpub participants", + Self::InvalidMusigExpression => "invalid musig key expression", + Self::InvalidMusigParticipant => "invalid musig participant key", Self::InvalidMultiXKeyDerivation => "can't make a multi-xpriv with hardened derivation steps that are not shared among all paths into a public key", Self::InvalidPublicKeyLength => "public keys must be 64, 66 or 130 characters in size", Self::InvalidWildcardInDerivationPath => "'*' may only appear as last element in a derivation path", Self::KeyTooShort => "key too short", + Self::MusigEmpty => "musig key expression must contain at least one participant", + Self::MusigHardenedDerivation => "musig aggregate-level derivation cannot contain hardened steps", + Self::MusigMultipathLenMismatch => "musig participant multipath lengths must match", + Self::MusigNested => "musig key expressions cannot be nested", Self::MultipleFingerprintsInPublicKey => "multiple ']' in Descriptor Public Key", Self::MultipleDerivationPathIndexSteps => "'<' may only appear once in a derivation path", Self::NoKeyAfterOrigin => "no key after origin", @@ -611,10 +691,26 @@ impl fmt::Display for DescriptorPublicKey { Self::Single(ref pk) => pk.fmt(f), Self::XPub(ref xpub) => xpub.fmt(f), Self::MultiXPub(ref xpub) => xpub.fmt(f), + Self::Musig(ref musig) => musig.fmt(f), } } } +impl fmt::Display for DescriptorMusigKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("musig(")?; + for (i, participant) in self.participants.iter().enumerate() { + if i > 0 { + f.write_str(",")?; + } + participant.fmt(f)?; + } + f.write_str(")")?; + fmt_derivation_paths(f, self.derivation_paths.paths())?; + self.wildcard.fmt(f) + } +} + impl DescriptorSecretKey { /// Returns the public version of this key. /// @@ -726,6 +822,10 @@ impl FromStr for DescriptorPublicKey { type Err = DescriptorKeyParseError; fn from_str(s: &str) -> Result { + if s.starts_with("musig(") { + return parse_musig_key(s).map(DescriptorPublicKey::Musig); + } + // A "raw" public key without any origin is the least we accept. if s.len() < 64 { return Err(DescriptorKeyParseError::MalformedKeyData( @@ -783,6 +883,47 @@ impl FromStr for DescriptorPublicKey { } } +fn parse_musig_key(s: &str) -> Result { + let tree = crate::expression::Tree::from_str(s).map_err(|_| { + DescriptorKeyParseError::MalformedKeyData(MalformedKeyDataKind::InvalidMusigExpression) + })?; + let root = tree.root(); + if root.name() != "musig" { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMusigExpression, + )); + } + if root.n_children() == 0 { + return Err(DescriptorKeyParseError::MalformedKeyData(MalformedKeyDataKind::MusigEmpty)); + } + + let mut participants = Vec::with_capacity(root.n_children()); + for child in root.children() { + let participant = DescriptorPublicKey::from_str(&child.expression_string())?; + if matches!(participant, DescriptorPublicKey::Musig(_)) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MusigNested, + )); + } + participants.push(participant); + } + + let (mut derivation_paths, wildcard) = parse_derivation_suffix(root.postfix())?; + if derivation_paths.is_empty() { + derivation_paths.push(bip32::DerivationPath::from(Vec::new())); + } + + DescriptorMusigKey::new( + participants, + DerivPaths::new(derivation_paths).expect("not empty"), + wildcard, + ) +} + +fn has_hardened_step(path: &bip32::DerivationPath) -> bool { + path.into_iter().any(|step| step.is_hardened()) +} + impl From for DescriptorPublicKey { fn from(key: XOnlyPublicKey) -> Self { DescriptorPublicKey::Single(SinglePub { origin: None, key: SinglePubKey::XOnly(key) }) @@ -831,6 +972,7 @@ impl DescriptorPublicKey { ) } } + DescriptorPublicKey::Musig(_) => bip32::Fingerprint::default(), } } @@ -858,7 +1000,7 @@ impl DescriptorPublicKey { bip32::DerivationPath::from(vec![]) }) } - DescriptorPublicKey::MultiXPub(_) => None, + DescriptorPublicKey::MultiXPub(_) | DescriptorPublicKey::Musig(_) => None, } } @@ -883,6 +1025,7 @@ impl DescriptorPublicKey { .map(|p| origin_path.extend(p)) .collect() } + DescriptorPublicKey::Musig(musig) => musig.derivation_paths.paths().clone(), _ => vec![self .full_derivation_path() .expect("Must be Some for non-multipath keys")], @@ -900,7 +1043,7 @@ impl DescriptorPublicKey { match *self { DescriptorPublicKey::XPub(ref xpub) => Some(xpub.derivation_path.clone()), DescriptorPublicKey::Single(_) => Some(bip32::DerivationPath::from(vec![])), - DescriptorPublicKey::MultiXPub(_) => None, + DescriptorPublicKey::MultiXPub(_) | DescriptorPublicKey::Musig(_) => None, } } @@ -918,6 +1061,7 @@ impl DescriptorPublicKey { vec![bip32::DerivationPath::from(vec![])] } DescriptorPublicKey::MultiXPub(xpub) => xpub.derivation_paths.paths().clone(), + DescriptorPublicKey::Musig(musig) => musig.derivation_paths.paths().clone(), } } @@ -927,6 +1071,13 @@ impl DescriptorPublicKey { DescriptorPublicKey::Single(..) => false, DescriptorPublicKey::XPub(ref xpub) => xpub.wildcard != Wildcard::None, DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None, + DescriptorPublicKey::Musig(ref musig) => { + musig.wildcard != Wildcard::None + || musig + .participants + .iter() + .any(DescriptorPublicKey::has_wildcard) + } } } @@ -936,6 +1087,31 @@ impl DescriptorPublicKey { DescriptorPublicKey::Single(..) => None, DescriptorPublicKey::XPub(ref xpub) => Some(xpub.wildcard), DescriptorPublicKey::MultiXPub(ref xpub) => Some(xpub.wildcard), + DescriptorPublicKey::Musig(ref musig) => Some(musig.wildcard), + } + } + + /// Applies `pred` to this key and any underlying `musig()` participant keys. + /// + /// For a `musig()` key expression, this walks the participant keys and does not call `pred` + /// on the aggregate key expression itself. + pub fn for_each_leaf_key<'a, F>(&'a self, mut pred: F) -> bool + where + F: FnMut(&'a DescriptorPublicKey) -> bool, + { + self.for_each_leaf_key_inner(&mut pred) + } + + fn for_each_leaf_key_inner<'a, F>(&'a self, pred: &mut F) -> bool + where + F: FnMut(&'a DescriptorPublicKey) -> bool, + { + match self { + DescriptorPublicKey::Musig(musig) => musig + .participants + .iter() + .all(|participant| participant.for_each_leaf_key_inner(pred)), + _ => pred(self), } } @@ -945,6 +1121,15 @@ impl DescriptorPublicKey { DescriptorPublicKey::Single(..) => &[], DescriptorPublicKey::XPub(xpub) => core::slice::from_ref(&xpub.derivation_path), DescriptorPublicKey::MultiXPub(xpub) => &xpub.derivation_paths.paths()[..], + DescriptorPublicKey::Musig(musig) => { + if musig.derivation_paths.paths().iter().any(has_hardened_step) { + return true; + } + return musig + .participants + .iter() + .any(DescriptorPublicKey::has_hardened_step); + } }; for p in paths { for step in p.into_iter() { @@ -998,6 +1183,47 @@ impl DescriptorPublicKey { }) } DescriptorPublicKey::MultiXPub(_) => return Err(NonDefiniteKeyError::Multipath), + DescriptorPublicKey::Musig(musig) => { + let participants = musig + .participants + .into_iter() + .map(|participant| { + if participant.has_wildcard() { + participant + .at_derivation_index(index) + .map(DefiniteDescriptorKey::into_descriptor_public_key) + } else if participant.is_multipath() { + Err(NonDefiniteKeyError::Multipath) + } else { + Ok(participant) + } + }) + .collect::, _>>()?; + + let derivation_paths = musig + .derivation_paths + .into_paths() + .into_iter() + .map(|path| match musig.wildcard { + Wildcard::None => Ok(path), + Wildcard::Unhardened => Ok(path.into_child( + bip32::ChildNumber::from_normal_idx(index) + .ok() + .ok_or(NonDefiniteKeyError::HardenedStep)?, + )), + Wildcard::Hardened => Err(NonDefiniteKeyError::HardenedStep), + }) + .collect::, _>>()?; + + DescriptorPublicKey::Musig( + DescriptorMusigKey::new( + participants, + DerivPaths::new(derivation_paths).expect("not empty"), + Wildcard::None, + ) + .expect("deriving a valid musig key preserves musig invariants"), + ) + } }; DefiniteDescriptorKey::new(definite) @@ -1008,6 +1234,13 @@ impl DescriptorPublicKey { match *self { DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => false, DescriptorPublicKey::MultiXPub(_) => true, + DescriptorPublicKey::Musig(ref musig) => { + musig.derivation_paths.paths().len() > 1 + || musig + .participants + .iter() + .any(DescriptorPublicKey::is_multipath) + } } } @@ -1034,6 +1267,7 @@ impl DescriptorPublicKey { }) .collect() } + DescriptorPublicKey::Musig(musig) => musig.into_single_keys(), } } @@ -1041,11 +1275,259 @@ impl DescriptorPublicKey { /// /// Returns `None` for single keys (non-extended keys), `Some(NetworkKind)` for extended keys. pub fn xkey_network(&self) -> Option { + match self.xkey_network_summary() { + XKeyNetwork::Single(network) => Some(network), + XKeyNetwork::NoXKeys | XKeyNetwork::Mixed => None, + } + } + + pub(crate) fn xkey_network_summary(&self) -> XKeyNetwork { match self { - DescriptorPublicKey::Single(_) => None, - DescriptorPublicKey::XPub(xpub) => Some(xpub.xkey.network), - DescriptorPublicKey::MultiXPub(multi_xpub) => Some(multi_xpub.xkey.network), + DescriptorPublicKey::Single(_) => XKeyNetwork::NoXKeys, + DescriptorPublicKey::XPub(xpub) => XKeyNetwork::Single(xpub.xkey.network), + DescriptorPublicKey::MultiXPub(multi_xpub) => { + XKeyNetwork::Single(multi_xpub.xkey.network) + } + DescriptorPublicKey::Musig(musig) => musig.xkey_network_summary(), + } + } +} + +impl DescriptorMusigKey { + /// Creates a `musig()` aggregate key expression after validating BIP390 invariants. + pub fn new( + participants: Vec, + derivation_paths: DerivPaths, + wildcard: Wildcard, + ) -> Result { + Self::validate(&participants, &derivation_paths, wildcard)?; + Ok(Self { participants, derivation_paths, wildcard }) + } + + /// Participant keys in this aggregate. + pub fn participants(&self) -> &[DescriptorPublicKey] { &self.participants } + + /// Derivation paths applied to the aggregate key. + pub fn derivation_paths(&self) -> &DerivPaths { &self.derivation_paths } + + /// Wildcard applied to the aggregate key expression. + pub fn wildcard(&self) -> Wildcard { self.wildcard } + + /// Returns whether this `musig()` derives participants before aggregation or derives the + /// aggregate through the BIP328 synthetic xpub. + pub fn derivation_kind(&self) -> MusigDerivationKind<'_> { + if Self::has_aggregate_derivation(&self.derivation_paths, self.wildcard) { + MusigDerivationKind::AggregateThenDerive { + derivation_paths: &self.derivation_paths, + wildcard: self.wildcard, + } + } else { + MusigDerivationKind::DeriveThenAggregate + } + } + + /// Returns the BIP327 aggregate public key before aggregate-level BIP328 derivation. + /// + /// This requires all participants to be definite. If participant keys are ranged, first call + /// [`DescriptorPublicKey::at_derivation_index`] or + /// [`DescriptorMusigKey::derived_participants_at_index`]. + pub fn aggregate_public_key( + &self, + secp: &Secp256k1, + ) -> Result { + super::musig::aggregate_public_key(secp, self) + } + + /// Returns the BIP328 synthetic xpub for aggregate-level derivation, if this `musig()` uses it. + pub fn synthetic_xpub(&self, aggregate: PublicKey) -> Option { + if Self::has_aggregate_derivation(&self.derivation_paths, self.wildcard) { + let network = self.xkey_network().unwrap_or(NetworkKind::Main); + Some(super::musig::synthetic_xpub(aggregate.inner, network)) + } else { + None + } + } + + /// Derives the final aggregate public key for a definite `musig()` key expression. + pub fn try_derive_public_key( + &self, + secp: &Secp256k1, + ) -> Result { + DefiniteDescriptorKey::new(DescriptorPublicKey::Musig(self.clone()))?; + super::musig::derive_public_key(secp, self) + } + + /// Derives each participant key at `index`. + /// + /// Non-ranged participants are returned as-is. Ranged participants are derived at `index`. + pub fn derived_participants_at_index( + &self, + index: u32, + ) -> Result, NonDefiniteKeyError> { + self.participants + .iter() + .cloned() + .map(|participant| { + if participant.has_wildcard() { + participant.at_derivation_index(index) + } else { + DefiniteDescriptorKey::new(participant) + } + }) + .collect() + } + + pub(crate) fn derivation_suffix_string(&self) -> String { + use core::fmt::Write as _; + + let mut suffix = String::new(); + fmt_derivation_paths(&mut suffix, self.derivation_paths.paths()) + .expect("writing to a string cannot fail"); + write!(&mut suffix, "{}", self.wildcard).expect("writing to a string cannot fail"); + suffix + } + + /// Get the network of the participant xpubs, if all present xpubs agree. + pub fn xkey_network(&self) -> Option { + match self.xkey_network_summary() { + XKeyNetwork::Single(network) => Some(network), + XKeyNetwork::NoXKeys | XKeyNetwork::Mixed => None, + } + } + + pub(crate) fn xkey_network_summary(&self) -> XKeyNetwork { + let mut network = None; + for participant in &self.participants { + match participant.xkey_network_summary() { + XKeyNetwork::NoXKeys => {} + XKeyNetwork::Single(found) => match network { + None => network = Some(found), + Some(current) if current == found => {} + Some(_) => return XKeyNetwork::Mixed, + }, + XKeyNetwork::Mixed => return XKeyNetwork::Mixed, + } + } + + match network { + Some(network) => XKeyNetwork::Single(network), + None => XKeyNetwork::NoXKeys, + } + } + + fn validate( + participants: &[DescriptorPublicKey], + derivation_paths: &DerivPaths, + wildcard: Wildcard, + ) -> Result<(), DescriptorKeyParseError> { + if participants.is_empty() { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MusigEmpty, + )); + } + + if participants + .iter() + .any(|participant| matches!(participant, DescriptorPublicKey::Musig(_))) + { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MusigNested, + )); + } + + if Self::has_aggregate_derivation(derivation_paths, wildcard) { + if wildcard == Wildcard::Hardened + || derivation_paths.paths().iter().any(has_hardened_step) + { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MusigHardenedDerivation, + )); + } + + if participants.iter().any(|p| { + !matches!(p, DescriptorPublicKey::XPub(_)) || p.has_wildcard() || p.is_multipath() + }) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMusigAggregateDerivation, + )); + } + } else { + let mut multipath_len = None; + for participant in participants { + let n_paths = participant.num_der_paths(); + if n_paths > 1 { + match multipath_len { + None => multipath_len = Some(n_paths), + Some(len) if len == n_paths => {} + Some(_) => { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MusigMultipathLenMismatch, + )) + } + } + } + } } + + Ok(()) + } + + fn has_aggregate_derivation(derivation_paths: &DerivPaths, wildcard: Wildcard) -> bool { + wildcard != Wildcard::None + || derivation_paths.paths().len() > 1 + || derivation_paths.paths().iter().any(|path| !path.is_empty()) + } + + fn into_single_keys(self) -> Vec { + let DescriptorMusigKey { participants, derivation_paths, wildcard } = self; + let derivation_paths = derivation_paths.into_paths(); + if derivation_paths.len() > 1 { + return derivation_paths + .into_iter() + .map(|derivation_path| { + DescriptorPublicKey::Musig( + DescriptorMusigKey::new( + participants.clone(), + DerivPaths::new(vec![derivation_path]).expect("not empty"), + wildcard, + ) + .expect("single aggregate paths preserve musig invariants"), + ) + }) + .collect(); + } + + let participant_keys = participants + .into_iter() + .map(DescriptorPublicKey::into_single_keys) + .collect::>(); + let n_keys = participant_keys.iter().map(Vec::len).max().unwrap_or(1); + debug_assert!(participant_keys + .iter() + .all(|keys| keys.len() == 1 || keys.len() == n_keys)); + + (0..n_keys) + .map(|i| { + let participants = participant_keys + .iter() + .map(|keys| { + if keys.len() == 1 { + keys[0].clone() + } else { + keys[i].clone() + } + }) + .collect(); + DescriptorPublicKey::Musig( + DescriptorMusigKey::new( + participants, + DerivPaths::new(derivation_paths.clone()).expect("not empty"), + wildcard, + ) + .expect("single participant paths preserve musig invariants"), + ) + }) + .collect() } } @@ -1150,8 +1632,8 @@ where Key: FromStr, E: Into, { - let mut key_deriv = key_deriv.split('/'); - let xkey_str = key_deriv + let mut key_deriv_split = key_deriv.split('/'); + let xkey_str = key_deriv_split .next() .ok_or(DescriptorKeyParseError::MalformedKeyData( MalformedKeyDataKind::NoKeyAfterOrigin, @@ -1160,6 +1642,28 @@ where let xkey = Key::from_str(xkey_str).map_err(|e| DescriptorKeyParseError::XKeyParseError(e.into()))?; + let (derivation_paths, wildcard) = parse_derivation_steps(key_deriv_split)?; + + Ok((xkey, derivation_paths, wildcard)) +} + +fn parse_derivation_suffix( + suffix: &str, +) -> Result<(Vec, Wildcard), DescriptorKeyParseError> { + if suffix.is_empty() { + Ok((Vec::new(), Wildcard::None)) + } else if let Some(suffix) = suffix.strip_prefix('/') { + parse_derivation_steps(suffix.split('/')) + } else { + Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMusigExpression, + )) + } +} + +fn parse_derivation_steps<'a>( + key_deriv: impl Iterator, +) -> Result<(Vec, Wildcard), DescriptorKeyParseError> { let mut wildcard = Wildcard::None; let mut multipath = false; let derivation_paths = key_deriv @@ -1253,7 +1757,7 @@ where .map(|index_list| index_list.into_iter().collect::()) .collect::>(); - Ok((xkey, derivation_paths, wildcard)) + Ok((derivation_paths, wildcard)) } impl DescriptorXKey { @@ -1358,6 +1862,7 @@ impl MiniscriptKey for DescriptorPublicKey { fn is_x_only_key(&self) -> bool { match self { DescriptorPublicKey::Single(single_pub) => single_pub.is_x_only_key(), + DescriptorPublicKey::Musig(_) => true, _ => false, } } @@ -1367,6 +1872,16 @@ impl MiniscriptKey for DescriptorPublicKey { DescriptorPublicKey::Single(single) => single.num_der_paths(), DescriptorPublicKey::XPub(xpub) => xpub.num_der_paths(), DescriptorPublicKey::MultiXPub(xpub) => xpub.num_der_paths(), + DescriptorPublicKey::Musig(musig) => { + let aggregate_paths = musig.derivation_paths.paths().len(); + let participant_paths = musig + .participants + .iter() + .map(DescriptorPublicKey::num_der_paths) + .max() + .unwrap_or(0); + aggregate_paths.max(participant_paths) + } } } } @@ -1377,8 +1892,15 @@ impl DefiniteDescriptorKey { /// 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 [`crate::Descriptor::translate_pk`]. + /// Hardened derivation steps are rejected when constructing a `DefiniteDescriptorKey`. To + /// handle a `musig()` key aggregation error explicitly, use + /// [`DescriptorMusigKey::try_derive_public_key`]. + /// + /// # Panics + /// + /// Panics if a musig aggregate public key is the point at infinity. BIP327 specifies this as a + /// key aggregation failure, but descriptor public key derivation is currently an infallible API + /// and this failure is cryptographically unreachable for non-adversarial participant keys. pub fn derive_public_key(&self, secp: &Secp256k1) -> bitcoin::PublicKey { match self.0 { DescriptorPublicKey::Single(ref pk) => match pk.key { @@ -1400,6 +1922,9 @@ impl DefiniteDescriptorKey { DescriptorPublicKey::MultiXPub(_) => { unreachable!("impossible by construction of DefiniteDescriptorKey") } + DescriptorPublicKey::Musig(ref musig) => musig + .try_derive_public_key(secp) + .expect("definite musig aggregate public key derivation succeeds"), } } @@ -1518,12 +2043,18 @@ mod test { use serde_test::{assert_tokens, Token}; use super::{ - DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, MiniscriptKey, Wildcard, + DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, MiniscriptKey, + MusigDerivationKind, MusigKeyAggError, PublicKey, Wildcard, }; use crate::descriptor::key::NonDefiniteKeyError; use crate::prelude::*; use crate::DefiniteDescriptorKey; + const PUBKEY_1: &str = "021d4ea7132d4e1a362ee5efd8d0b59dd4d1fe8906eefa7dd812b05a46b73d829b"; + const PUBKEY_2: &str = "02a489e0ea42b56148d212d325b7c67c6460483ff931c303ea311edfef667c8f35"; + const UNCOMPRESSED_PUBKEY: &str = "0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf"; + const TPUB: &str = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi"; + #[test] fn parse_descriptor_key_errors() { // And ones with misplaced wildcard @@ -1599,6 +2130,150 @@ mod test { assert_eq!(DescriptorSecretKey::from_str(desc).unwrap_err().to_string(), "invalid base58"); } + #[test] + fn parse_musig_descriptor_public_keys() { + let desc = format!("musig({},{})", PUBKEY_1, PUBKEY_2); + let key = DescriptorPublicKey::from_str(&desc).unwrap(); + assert_eq!(key.to_string(), desc); + assert!(key.is_x_only_key()); + assert!(!key.has_wildcard()); + assert_eq!(key.num_der_paths(), 1); + assert!(matches!(DefiniteDescriptorKey::new(key), Ok(..))); + + // Bitcoin Core PR #31244 accepts a single musig() participant, and BIP390 does not + // forbid it. + let desc = format!("musig({})", PUBKEY_1); + let key = DescriptorPublicKey::from_str(&desc).unwrap(); + assert_eq!(key.to_string(), desc); + assert!(key.is_x_only_key()); + assert_eq!(key.num_der_paths(), 1); + assert!(matches!(DefiniteDescriptorKey::new(key), Ok(..))); + + let desc = format!("musig({},{})", UNCOMPRESSED_PUBKEY, PUBKEY_2); + let key = DescriptorPublicKey::from_str(&desc).unwrap(); + assert!(key.is_x_only_key()); + assert!(!key.is_uncompressed()); + + let desc = format!("musig({},{})/0/*", TPUB, TPUB); + let key = DescriptorPublicKey::from_str(&desc).unwrap(); + assert_eq!(key.to_string(), desc); + assert!(key.has_wildcard()); + assert_eq!(key.wildcard(), Some(Wildcard::Unhardened)); + assert_eq!(key.derivation_paths(), vec![bip32::DerivationPath::from_str("m/0").unwrap()]); + assert!(matches!( + DefiniteDescriptorKey::new(key.clone()), + Err(NonDefiniteKeyError::Wildcard) + )); + assert_eq!( + key.at_derivation_index(7) + .unwrap() + .into_descriptor_public_key() + .to_string(), + format!("musig({},{})/0/7", TPUB, TPUB) + ); + } + + #[test] + fn musig_helper_apis() { + let secp = bitcoin::secp256k1::Secp256k1::verification_only(); + + let desc = format!("musig({0}/*,{0})", TPUB); + let key = DescriptorPublicKey::from_str(&desc).unwrap(); + let musig = match &key { + DescriptorPublicKey::Musig(ref musig) => musig, + _ => unreachable!("parsed musig key"), + }; + assert_eq!(musig.derivation_kind(), MusigDerivationKind::DeriveThenAggregate); + assert!(musig + .synthetic_xpub(PublicKey::from_str(PUBKEY_1).unwrap()) + .is_none()); + assert!(matches!( + musig.try_derive_public_key(&secp), + Err(MusigKeyAggError::NonDefiniteKey(NonDefiniteKeyError::Wildcard)) + )); + assert_eq!( + musig + .derived_participants_at_index(7) + .unwrap() + .into_iter() + .map(|key| key.to_string()) + .collect::>(), + vec![format!("{}/7", TPUB), TPUB.to_owned()] + ); + + let mut leaves = Vec::new(); + key.for_each_leaf_key(|key| { + leaves.push(key.to_string()); + true + }); + assert_eq!(leaves, vec![format!("{}/{}", TPUB, "*"), TPUB.to_owned()]); + + let desc = format!("musig({0},{0})/2", TPUB); + let key = DescriptorPublicKey::from_str(&desc).unwrap(); + let musig = match &key { + DescriptorPublicKey::Musig(ref musig) => musig, + _ => unreachable!("parsed musig key"), + }; + assert!(matches!( + musig.derivation_kind(), + MusigDerivationKind::AggregateThenDerive { wildcard: Wildcard::None, .. } + )); + let aggregate = musig.aggregate_public_key(&secp).unwrap(); + assert_eq!(musig.synthetic_xpub(aggregate).unwrap().depth, 0); + assert_eq!( + musig.try_derive_public_key(&secp).unwrap(), + DefiniteDescriptorKey::new(key) + .unwrap() + .derive_public_key(&secp) + ); + } + + #[test] + fn parse_musig_descriptor_public_key_errors() { + for desc in [ + "musig()", + &format!("musig(musig({},{}),{})", PUBKEY_1, PUBKEY_2, PUBKEY_1), + &format!("musig({},{})/0", PUBKEY_1, PUBKEY_2), + &format!("musig({0}/*,{0})/0", TPUB), + &format!("musig({0},{0})/0'", TPUB), + &format!("musig({0},{0})/0/*h", TPUB), + &format!("musig({0}/<0;1>,{0}/<0;1;2>)", TPUB), + &format!("musig({0}/<0;1>,{0})/<2;3>", TPUB), + ] { + DescriptorPublicKey::from_str(desc).unwrap_err(); + } + } + + #[test] + fn musig_into_single_keys() { + let key = + DescriptorPublicKey::from_str(&format!("musig({0}/<0;1>,{0}/<2;3>)", TPUB)).unwrap(); + assert_eq!(key.num_der_paths(), 2); + assert_eq!( + key.into_single_keys() + .into_iter() + .map(|key| key.to_string()) + .collect::>(), + vec![ + format!("musig({0}/0,{0}/2)", TPUB), + format!("musig({0}/1,{0}/3)", TPUB) + ] + ); + + let key = DescriptorPublicKey::from_str(&format!("musig({0},{0})/<0;1>/*", TPUB)).unwrap(); + assert_eq!(key.num_der_paths(), 2); + assert_eq!( + key.into_single_keys() + .into_iter() + .map(|key| key.to_string()) + .collect::>(), + vec![ + format!("musig({0},{0})/0/*", TPUB), + format!("musig({0},{0})/1/*", TPUB) + ] + ); + } + #[test] fn test_wildcard() { let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2").unwrap(); @@ -1664,6 +2339,10 @@ mod test { .as_bytes(), b"\xb0\x59\x11\x6a" ); + + let musig = + DescriptorPublicKey::from_str(&format!("musig({},{})", PUBKEY_1, PUBKEY_2)).unwrap(); + assert_eq!(musig.master_fingerprint(), bip32::Fingerprint::default()); } fn get_multipath_xpub(key_str: &str, num_paths: usize) -> DescriptorMultiXKey { @@ -1934,22 +2613,34 @@ mod test { fn test_xkey_network() { use bitcoin::NetworkKind; + let mainnet_xpub_str = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"; + let testnet_xpub_str = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi"; + let single_key_str = "021d4ea7132d4e1a362ee5efd8d0b59dd4d1fe8906eefa7dd812b05a46b73d829b"; + // Test mainnet xpub - let mainnet_xpub = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" - .parse::() - .unwrap(); + let mainnet_xpub = mainnet_xpub_str.parse::().unwrap(); assert_eq!(mainnet_xpub.xkey_network(), Some(NetworkKind::Main)); // Test testnet xpub - let testnet_xpub = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi" - .parse::() - .unwrap(); + let testnet_xpub = testnet_xpub_str.parse::().unwrap(); assert_eq!(testnet_xpub.xkey_network(), Some(NetworkKind::Test)); // Test single public key (no extended key) - let single_key = "021d4ea7132d4e1a362ee5efd8d0b59dd4d1fe8906eefa7dd812b05a46b73d829b" - .parse::() - .unwrap(); + let single_key = single_key_str.parse::().unwrap(); assert_eq!(single_key.xkey_network(), None); + + let musig = DescriptorPublicKey::from_str(&format!( + "musig({},{})", + single_key_str, mainnet_xpub_str + )) + .unwrap(); + assert_eq!(musig.xkey_network(), Some(NetworkKind::Main)); + + let musig = DescriptorPublicKey::from_str(&format!( + "musig({},{})", + mainnet_xpub_str, testnet_xpub_str + )) + .unwrap(); + assert_eq!(musig.xkey_network(), None); } } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 5dc6e7592..0118752b6 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -51,12 +51,14 @@ pub use self::tr::{ pub mod checksum; mod key; mod key_map; +mod musig; mod wallet_policy; pub use self::key::{ DefiniteDescriptorKey, DerivPaths, DescriptorKeyParseError, DescriptorMultiXKey, - DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, InnerXKey, MalformedKeyDataKind, - NonDefiniteKeyError, SinglePriv, SinglePub, SinglePubKey, Wildcard, XKeyNetwork, + DescriptorMusigKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, InnerXKey, + MalformedKeyDataKind, MusigDerivationKind, MusigKeyAggError, NonDefiniteKeyError, SinglePriv, + SinglePub, SinglePubKey, Wildcard, XKeyNetwork, }; pub use self::key_map::KeyMap; pub use self::wallet_policy::{WalletPolicy, WalletPolicyError}; @@ -796,6 +798,35 @@ impl Descriptor { key_map: &mut KeyMap, secp: &secp256k1::Secp256k1, ) -> Result { + if s.starts_with("musig(") { + let tree = expression::Tree::from_str(s)?; + let root = tree.root(); + let mut public_participants = Vec::with_capacity(root.n_children()); + for child in root.children() { + if child.name() == "musig" { + return Err(Error::Parse(ParseError::box_from_str( + DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MusigNested, + ), + ))); + } + public_participants.push(parse_key(&child.expression_string(), key_map, secp)?); + } + + let mut public_musig = String::from("musig("); + for (i, participant) in public_participants.iter().enumerate() { + if i > 0 { + public_musig.push(','); + } + public_musig.push_str(&participant.to_string()); + } + public_musig.push(')'); + public_musig.push_str(root.postfix()); + return public_musig + .parse() + .map_err(|e| Error::Parse(ParseError::box_from_str(e))); + } + match DescriptorSecretKey::from_str(s) { Ok(sk) => { let pk = key_map @@ -891,6 +922,19 @@ impl Descriptor { pk: &DescriptorPublicKey, key_map: &KeyMap, ) -> Result { + if let DescriptorPublicKey::Musig(ref musig) = *pk { + let mut musig_string = String::from("musig("); + for (i, participant) in musig.participants().iter().enumerate() { + if i > 0 { + musig_string.push(','); + } + musig_string.push_str(&key_to_string(participant, key_map)?); + } + musig_string.push(')'); + musig_string.push_str(&musig.derivation_suffix_string()); + return Ok(musig_string); + } + Ok(match key_map.get(pk) { Some(secret) => secret.to_string(), None => pk.to_string(), @@ -962,6 +1006,17 @@ impl Descriptor { } true } + DescriptorPublicKey::Musig(musig) => { + let n_paths = key.num_der_paths(); + if n_paths > 1 || musig.derivation_paths().paths().len() > 1 { + for _ in 0..n_paths { + descriptors.push(self.clone()); + } + true + } else { + false + } + } } }) { // If there is no multipath key, return early. @@ -986,6 +1041,12 @@ impl Descriptor { .get(self.0) .cloned() .ok_or(Error::MultipathDescLenMismatch), + DescriptorPublicKey::Musig(_) => pk + .clone() + .into_single_keys() + .get(self.0) + .cloned() + .ok_or(Error::MultipathDescLenMismatch), } } translate_hash_clone!(DescriptorPublicKey); @@ -1013,12 +1074,14 @@ impl Descriptor { let mut first_network = None; for key in self.iter_pk() { - if let Some(network) = key.xkey_network() { - match first_network { + match key.xkey_network_summary() { + XKeyNetwork::NoXKeys => {} + XKeyNetwork::Single(network) => match first_network { None => first_network = Some(network), - Some(ref n) if *n != network => return XKeyNetwork::Mixed, - _ => continue, - } + Some(n) if n != network => return XKeyNetwork::Mixed, + Some(_) => {} + }, + XKeyNetwork::Mixed => return XKeyNetwork::Mixed, } } @@ -1103,12 +1166,14 @@ impl Descriptor { let mut first_network = None; for key in self.iter_pk() { - if let Some(network) = key.as_descriptor_public_key().xkey_network() { - match first_network { + match key.as_descriptor_public_key().xkey_network_summary() { + XKeyNetwork::NoXKeys => {} + XKeyNetwork::Single(network) => match first_network { None => first_network = Some(network), - Some(ref n) if *n != network => return XKeyNetwork::Mixed, - _ => continue, - } + Some(n) if n != network => return XKeyNetwork::Mixed, + Some(_) => {} + }, + XKeyNetwork::Mixed => return XKeyNetwork::Mixed, } } @@ -2105,6 +2170,33 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; assert_eq!(descriptor_str, descriptor.to_string_with_secret(&keymap)); } + #[test] + fn parse_musig_with_secrets() { + let secp = &secp256k1::Secp256k1::signing_only(); + let descriptor_str = "tr(musig(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1)/2)"; + let descriptor_str = Descriptor::::from_str(descriptor_str) + .unwrap() + .to_string(); + let (descriptor, keymap) = + Descriptor::::parse_descriptor(secp, &descriptor_str).unwrap(); + + assert_eq!(keymap.len(), 1); + assert!(descriptor + .to_string() + .contains("tr(musig([a12b02f4/44'/0'/0']")); + assert_eq!(descriptor_str, descriptor.to_string_with_secret(&keymap)); + + let descriptor_str = "tr(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))"; + let descriptor_str = Descriptor::::from_str(descriptor_str) + .unwrap() + .to_string(); + let (descriptor, keymap) = + Descriptor::::parse_descriptor(secp, &descriptor_str).unwrap(); + + assert_eq!(keymap.len(), 1); + assert_eq!(descriptor_str, descriptor.to_string_with_secret(&keymap)); + } + #[test] fn checksum_for_nested_sh() { let descriptor_str = "sh(wpkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL))"; @@ -2131,6 +2223,169 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; .unwrap_err(); } + #[test] + fn bip390_musig_script_pubkey_vectors() { + let secp = secp256k1::Secp256k1::verification_only(); + let descriptor = Descriptor::::from_str( + "tr(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", + ) + .unwrap(); + assert_eq!( + descriptor + .into_definite() + .unwrap() + .derived_descriptor(&secp) + .script_pubkey(), + ScriptBuf::from_hex( + "512079e6c3e628c9bfbce91de6b7fb28e2aec7713d377cf260ab599dcbc40e542312" + ) + .unwrap() + ); + + let descriptor = Descriptor::::from_str( + "tr(musig(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", + ) + .unwrap(); + assert_eq!( + descriptor + .into_definite() + .unwrap() + .derived_descriptor(&secp) + .script_pubkey(), + ScriptBuf::from_hex( + "512079e6c3e628c9bfbce91de6b7fb28e2aec7713d377cf260ab599dcbc40e542312" + ) + .unwrap() + ); + + let uncompressed_descriptor = Descriptor::::from_str( + "tr(musig(0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659))", + ) + .unwrap(); + let compressed_descriptor = Descriptor::::from_str( + "tr(musig(0314fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659))", + ) + .unwrap(); + assert_eq!( + uncompressed_descriptor + .into_definite() + .unwrap() + .derived_descriptor(&secp) + .script_pubkey(), + compressed_descriptor + .into_definite() + .unwrap() + .derived_descriptor(&secp) + .script_pubkey() + ); + + let descriptor = Descriptor::::from_str( + "tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1)/2)", + ) + .unwrap(); + assert_eq!( + descriptor + .into_definite() + .unwrap() + .derived_descriptor(&secp) + .script_pubkey(), + ScriptBuf::from_hex( + "5120a17ceacd6422bd5ffd9f165807b254b7d68ad39f179cc4f11545a6835227e97c" + ) + .unwrap() + ); + + let descriptor = Descriptor::::from_str( + "tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*,pk(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9))", + ) + .unwrap(); + for (index, expected) in [ + (0, "51201d377b637b5c73f670f5c8a96a2c0bb0d1a682a1fca6aba91fe673501a189782"), + (1, "51208950c83b117a6c208d5205ffefcf75b187b32512eb7f0d8577db8d9102833036"), + (2, "5120a49a477c61df73691b77fcd563a80a15ea67bb9c75470310ce5c0f25918db60d"), + ] { + assert_eq!( + descriptor + .derive_at_index(index) + .unwrap() + .derived_descriptor(&secp) + .script_pubkey(), + ScriptBuf::from_hex(expected).unwrap() + ); + } + + let descriptor = Descriptor::::from_str( + "tr(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,pk(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*))", + ) + .unwrap(); + for (index, expected) in [ + (0, "512068983d461174afc90c26f3b2821d8a9ced9534586a756763b68371a404635cc8"), + (1, "5120368e2d864115181bdc8bb5dc8684be8d0760d5c33315570d71a21afce4afd43e"), + (2, "512097a1e6270b33ad85744677418bae5f59ea9136027223bc6e282c47c167b471d5"), + ] { + assert_eq!( + descriptor + .derive_at_index(index) + .unwrap() + .derived_descriptor(&secp) + .script_pubkey(), + ScriptBuf::from_hex(expected).unwrap() + ); + } + } + + #[test] + fn bip390_musig_tap_miniscript_key_positions() { + let secp = secp256k1::Secp256k1::verification_only(); + let musig = "musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)"; + let other_key = "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c"; + + for desc in [ + format!( + "tr(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,multi_a(1,{},{}))", + musig, other_key + ), + format!( + "tr(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,sortedmulti_a(1,{},{}))", + other_key, musig + ), + ] { + let descriptor = Descriptor::::from_str(&desc).unwrap(); + assert!(descriptor.to_string().contains("musig(")); + descriptor + .into_definite() + .unwrap() + .derived_descriptor(&secp) + .script_pubkey(); + } + } + + #[test] + fn bip390_musig_invalid_placements() { + let musig = "musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)"; + for desc in [ + musig.to_owned(), + format!("pk({})", musig), + format!("pkh({})", musig), + format!("wpkh({})", musig), + format!("sh(wpkh({}))", musig), + format!("wsh(pk({}))", musig), + format!("sh(wsh(pk({})))", musig), + format!("wsh({})", musig), + format!("sh({})", musig), + format!( + "tr(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,{})", + musig + ), + format!( + "tr(musig({},02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9))", + musig + ), + ] { + Descriptor::::from_str(&desc).unwrap_err(); + } + } + #[test] fn test_find_derivation_index_for_spk() { let secp = secp256k1::Secp256k1::verification_only(); @@ -2740,6 +2995,16 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; let complex_tests = vec![ // Taproot with mixed networks (format!("tr({},pk({}))", mainnet_xpubs[0], testnet_tpubs[0]), XKeyNetwork::Mixed), + // MuSig with mixed networks + ( + format!("tr(musig({},{}))", mainnet_xpubs[0], testnet_tpubs[0]), + XKeyNetwork::Mixed, + ), + // MuSig with raw keys and one xpub + ( + format!("tr(musig({},{},{}))", single_keys[0], mainnet_xpubs[0], single_keys[1]), + XKeyNetwork::Single(NetworkKind::Main), + ), // Taproot with consistent mainnet keys (format!("tr({},pk({}))", mainnet_xpubs[0], mainnet_xpubs[1]), XKeyNetwork::Single(NetworkKind::Main)), // HTLC-like pattern with mixed networks @@ -2806,6 +3071,10 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; format!("tr({},pk({}/0))", mainnet_xpubs[0], testnet_tpubs[0]), XKeyNetwork::Mixed, ), + ( + format!("tr(musig({},{}))", mainnet_xpubs[0], testnet_tpubs[0]), + XKeyNetwork::Mixed, + ), ]; for (desc_str, expected) in definite_key_tests { diff --git a/src/descriptor/musig.rs b/src/descriptor/musig.rs new file mode 100644 index 000000000..cd3e489e1 --- /dev/null +++ b/src/descriptor/musig.rs @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: CC0-1.0 + +use bitcoin::hashes::{sha256, Hash, HashEngine}; +use bitcoin::secp256k1::{constants, PublicKey, Scalar, Secp256k1, Verification}; +use bitcoin::{bip32, NetworkKind}; + +use super::key::{DefiniteDescriptorKey, DescriptorMusigKey, MusigKeyAggError}; +use crate::prelude::*; + +const SYNTHETIC_XPUB_CHAIN_CODE: [u8; 32] = [ + 0x86, 0x80, 0x87, 0xca, 0x02, 0xa6, 0xf9, 0x74, 0xc4, 0x59, 0x89, 0x24, 0xc3, 0x6b, 0x57, 0x76, + 0x2d, 0x32, 0xcb, 0x45, 0x71, 0x71, 0x67, 0xe3, 0x00, 0x62, 0x2c, 0x71, 0x67, 0xe3, 0x89, 0x65, +]; + +pub(super) fn derive_public_key( + secp: &Secp256k1, + musig: &DescriptorMusigKey, +) -> Result { + let aggregate = aggregate_public_key(secp, musig)?.inner; + + let derivation_path = &musig.derivation_paths().paths()[0]; + if derivation_path.is_empty() { + Ok(bitcoin::PublicKey::new(aggregate)) + } else { + let network = musig.xkey_network().unwrap_or(NetworkKind::Main); + let xpub = synthetic_xpub(aggregate, network); + Ok(bitcoin::PublicKey::new( + xpub.derive_pub(secp, derivation_path) + .map_err(MusigKeyAggError::AggregateDerivation)? + .public_key, + )) + } +} + +pub(super) fn aggregate_public_key( + secp: &Secp256k1, + musig: &DescriptorMusigKey, +) -> Result { + let keys = musig + .participants() + .iter() + .map(|participant| { + Ok(DefiniteDescriptorKey::new(participant.clone())? + .derive_public_key(secp) + .inner) + }) + .collect::, MusigKeyAggError>>()?; + + aggregate_keys(secp, keys).map(bitcoin::PublicKey::new) +} + +fn aggregate_keys(secp: &Secp256k1, keys: I) -> Result +where + C: Verification, + I: IntoIterator, +{ + let mut keys = keys.into_iter().collect::>(); + keys.sort_by_key(|key| key.serialize()); + key_agg_public_key(secp, keys) +} + +fn key_agg_public_key(secp: &Secp256k1, keys: I) -> Result +where + C: Verification, + I: IntoIterator, +{ + let keys = keys.into_iter().collect::>(); + let key_bytes = keys.iter().map(PublicKey::serialize).collect::>(); + let second_key = second_distinct_key(&key_bytes); + let key_hash = hash_keys(&key_bytes); + + let mut weighted_keys = Vec::with_capacity(keys.len()); + for (key, key_bytes) in keys.into_iter().zip(key_bytes.iter()) { + let coeff = if Some(key_bytes) == second_key { + Scalar::ONE + } else { + key_agg_coeff(&key_hash, key_bytes) + }; + // A zero coefficient is cryptographically unreachable for honest inputs. `mul_tweak` + // rejects zero, while adding the identity point would not change the aggregate. + if coeff != Scalar::ZERO { + weighted_keys.push( + key.mul_tweak(secp, &coeff) + .expect("musig key coefficient produces a valid public key"), + ); + } + } + + let refs = weighted_keys.iter().collect::>(); + let aggregate = PublicKey::combine_keys(&refs); + debug_assert!(aggregate.is_ok(), "BIP327 KeyAgg output is not infinity"); + aggregate.map_err(|_| MusigKeyAggError::Infinity) +} + +pub(super) fn synthetic_xpub(public_key: PublicKey, network: NetworkKind) -> bip32::Xpub { + bip32::Xpub { + network, + depth: 0, + parent_fingerprint: Default::default(), + child_number: bip32::ChildNumber::from_normal_idx(0).expect("0 is a valid child number"), + public_key, + chain_code: SYNTHETIC_XPUB_CHAIN_CODE.into(), + } +} + +fn second_distinct_key(key_bytes: &[[u8; 33]]) -> Option<&[u8; 33]> { + key_bytes.iter().find(|key| *key != &key_bytes[0]) +} + +fn hash_keys(key_bytes: &[[u8; 33]]) -> [u8; 32] { + let mut bytes = Vec::with_capacity(key_bytes.len() * 33); + for key in key_bytes { + bytes.extend_from_slice(key); + } + tagged_hash("KeyAgg list", &bytes) +} + +fn key_agg_coeff(key_hash: &[u8; 32], key_bytes: &[u8; 33]) -> Scalar { + let mut bytes = Vec::with_capacity(65); + bytes.extend_from_slice(key_hash); + bytes.extend_from_slice(key_bytes); + scalar_from_hash_mod_n(tagged_hash("KeyAgg coefficient", &bytes)) +} + +fn tagged_hash(tag: &str, bytes: &[u8]) -> [u8; 32] { + let tag_hash = sha256::Hash::hash(tag.as_bytes()); + let mut engine = sha256::Hash::engine(); + engine.input(tag_hash.as_ref()); + engine.input(tag_hash.as_ref()); + engine.input(bytes); + sha256::Hash::from_engine(engine).to_byte_array() +} + +fn scalar_from_hash_mod_n(mut bytes: [u8; 32]) -> Scalar { + // A SHA256 digest is below 2^256, which is below twice the secp256k1 group order. + // One conditional subtraction is therefore enough to reduce this 32-byte value. + if bytes >= constants::CURVE_ORDER { + sub_assign_32(&mut bytes, &constants::CURVE_ORDER); + } + debug_assert!(bytes < constants::CURVE_ORDER); + Scalar::from_be_bytes(bytes).expect("hash reduced modulo curve order") +} + +fn sub_assign_32(lhs: &mut [u8; 32], rhs: &[u8; 32]) { + let mut borrow = 0u16; + for (a, b) in lhs.iter_mut().zip(rhs.iter()).rev() { + let subtrahend = *b as u16 + borrow; + let minuend = *a as u16; + if minuend >= subtrahend { + *a = (minuend - subtrahend) as u8; + borrow = 0; + } else { + *a = (minuend + 256 - subtrahend) as u8; + borrow = 1; + } + } + debug_assert_eq!(borrow, 0); +} + +#[cfg(test)] +mod tests { + use core::str::FromStr; + + use bitcoin::bip32; + use bitcoin::secp256k1::Secp256k1; + + use super::*; + + #[test] + fn bip328_aggregate_key_vectors() { + for (keys, expected_aggregate, xpub) in [ + ( + &[ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + ][..], + "0354240c76b8f2999143301a99c7f721ee57eee0bce401df3afeaa9ae218c70f23", + "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwXEKGEouzXE6QLLRxjatMcLLzJ5LV5Nib1BN7vJg6yp45yHHRbm", + ), + ( + &[ + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + ][..], + "0290539eede565f5d054f32cc0c220126889ed1e5d193baf15aef344fe59d4610c", + "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwVk5TFJk8Tw5WAdV3DhrGfbFA216sE9BsQQiSFTdudkETnKdg8k", + ), + ( + &[ + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + ][..], + "022479f134cdb266141dab1a023cbba30a870f8995b95a91fc8464e56a7d41f8ea", + "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwUvaZYpysLX4wN59tjwU5pBuDjNrPEJbfxjLwn7ruzbXTcUTHkZ", + ), + ] { + let secp = Secp256k1::verification_only(); + let keys = keys + .iter() + .map(|key| PublicKey::from_str(key).unwrap()) + .collect::>(); + let aggregate = key_agg_public_key(&secp, keys).unwrap(); + assert_eq!(aggregate.to_string(), expected_aggregate); + let synthetic = synthetic_xpub(aggregate, NetworkKind::Main); + assert_eq!(synthetic, bip32::Xpub::from_str(xpub).unwrap()); + assert_eq!(synthetic.depth, 0); + assert_eq!(synthetic.parent_fingerprint, bip32::Fingerprint::default()); + assert_eq!( + synthetic.child_number, + bip32::ChildNumber::from_normal_idx(0).unwrap() + ); + assert_eq!(synthetic.chain_code, SYNTHETIC_XPUB_CHAIN_CODE.into()); + } + } +} diff --git a/src/descriptor/tr/mod.rs b/src/descriptor/tr/mod.rs index 47a72cfff..48956744f 100644 --- a/src/descriptor/tr/mod.rs +++ b/src/descriptor/tr/mod.rs @@ -356,7 +356,7 @@ impl crate::expression::FromTree for Tr { let internal_key: Pk = root_children .next() .unwrap() // `verify_toplevel` above checked that first child existed - .verify_terminal("internal key") + .verify_key_expression("internal key") .map_err(Error::Parse)?; let tap_tree = match root_children.next() { diff --git a/src/expression/error.rs b/src/expression/error.rs index bd62863d4..5fb0f267b 100644 --- a/src/expression/error.rs +++ b/src/expression/error.rs @@ -83,6 +83,15 @@ pub enum ParseTreeError { /// The position of the second separator. pos: usize, }, + /// A descriptor key expression suffix appeared where only a script expression is allowed. + UnexpectedKeyExpressionSuffix { + /// The expression name. + name: String, + /// The suffix following the expression. + suffix: String, + /// The position of the suffix. + pos: usize, + }, /// Data occurred after the final ). TrailingCharacter { /// The first trailing character. @@ -163,6 +172,13 @@ impl fmt::Display for ParseTreeError { separator, pos ) } + ParseTreeError::UnexpectedKeyExpressionSuffix { name, suffix, pos } => { + write!( + f, + "unexpected key expression suffix '{}' after '{}' (position {})", + suffix, name, pos + ) + } ParseTreeError::TrailingCharacter { ch, pos } => { write!(f, "trailing data `{}...` (position {})", ch, pos) } @@ -184,6 +200,7 @@ impl std::error::Error for ParseTreeError { | ParseTreeError::IncorrectName { .. } | ParseTreeError::IncorrectNumberOfChildren { .. } | ParseTreeError::MultipleSeparators { .. } + | ParseTreeError::UnexpectedKeyExpressionSuffix { .. } | ParseTreeError::TrailingCharacter { .. } | ParseTreeError::UnknownName { .. } => None, } @@ -242,6 +259,15 @@ pub enum ParseThresholdError { IllegalOr, /// A n-of-n threshold was used in a context it was not allowed. IllegalAnd, + /// A descriptor key expression suffix appeared where only a script expression is allowed. + UnexpectedKeyExpressionSuffix { + /// The expression name. + name: String, + /// The suffix following the expression. + suffix: String, + /// The position of the suffix. + pos: usize, + }, /// Failed to parse the threshold value. ParseK(ParseNumError), /// Threshold parameters were invalid. @@ -261,6 +287,11 @@ impl fmt::Display for ParseThresholdError { IllegalAnd => f.write_str( "n-of-n thresholds not allowed here; please use an 'and' fragment instead", ), + UnexpectedKeyExpressionSuffix { ref name, ref suffix, pos } => write!( + f, + "unexpected key expression suffix '{}' after '{}' (position {})", + suffix, name, pos + ), ParseK(ref x) => write!(f, "failed to parse threshold value: {}", x), Threshold(ref e) => e.fmt(f), } @@ -273,7 +304,11 @@ impl std::error::Error for ParseThresholdError { use ParseThresholdError::*; match *self { - NoChildren | KNotTerminal | IllegalOr | IllegalAnd => None, + NoChildren + | KNotTerminal + | IllegalOr + | IllegalAnd + | UnexpectedKeyExpressionSuffix { .. } => None, ParseK(ref e) => Some(e), Threshold(ref e) => Some(e), } diff --git a/src/expression/mod.rs b/src/expression/mod.rs index 6bfdfd055..e6d2f058f 100644 --- a/src/expression/mod.rs +++ b/src/expression/mod.rs @@ -27,8 +27,8 @@ mod error; -use core::ops; use core::str::FromStr; +use core::{fmt, ops}; pub use self::error::{ParseNumError, ParseThresholdError, ParseTreeError}; use crate::blanket_traits::StaticDebugAndDisplay; @@ -48,6 +48,8 @@ pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVW struct TreeNode<'s> { name: &'s str, name_pos: usize, + postfix: &'s str, + postfix_pos: usize, parens: Parens, n_children: usize, index: usize, @@ -61,6 +63,8 @@ impl TreeNode<'_> { TreeNode { name: "", name_pos: 0, + postfix: "", + postfix_pos: 0, parens: Parens::None, n_children: 0, index, @@ -182,6 +186,9 @@ impl<'s> TreeIterItem<'s> { /// The name of this tree node. pub fn name(self) -> &'s str { self.nodes[self.index].name } + /// The suffix following a function-style key expression, such as `/0/*` in `musig(A,B)/0/*`. + pub fn postfix(self) -> &'s str { self.nodes[self.index].postfix } + /// The 0-indexed byte-position of the name in the original expression tree. pub fn name_pos(self) -> usize { self.nodes[self.index].name_pos } @@ -267,6 +274,14 @@ impl<'s> TreeIterItem<'s> { self, separator: char, ) -> Result<(Option<&'s str>, &'s str), ParseTreeError> { + if !self.postfix().is_empty() { + return Err(ParseTreeError::UnexpectedKeyExpressionSuffix { + name: self.name().to_owned(), + suffix: self.postfix().to_owned(), + pos: self.nodes[self.index].postfix_pos, + }); + } + let mut name_split = self.name().splitn(3, separator); match (name_split.next(), name_split.next(), name_split.next()) { (None, _, _) => unreachable!("'split' always yields at least one element"), @@ -288,6 +303,14 @@ impl<'s> TreeIterItem<'s> { description: &'static str, n_children: impl ops::RangeBounds, ) -> Result<(), ParseTreeError> { + if !self.postfix().is_empty() { + return Err(ParseTreeError::UnexpectedKeyExpressionSuffix { + name: self.name().to_owned(), + suffix: self.postfix().to_owned(), + pos: self.nodes[self.index].postfix_pos, + }); + } + if n_children.contains(&self.n_children()) { Ok(()) } else { @@ -330,6 +353,12 @@ impl<'s> TreeIterItem<'s> { if self.name() != name { Err(ParseTreeError::IncorrectName { actual: self.name().to_owned(), expected: name }) + } else if !self.postfix().is_empty() { + Err(ParseTreeError::UnexpectedKeyExpressionSuffix { + name: self.name().to_owned(), + suffix: self.postfix().to_owned(), + pos: self.nodes[self.index].postfix_pos, + }) } else if self.parens() == Parens::Curly { Err(ParseTreeError::IllegalCurlyBrace { pos: self.children_pos() }) } else { @@ -410,6 +439,69 @@ impl<'s> TreeIterItem<'s> { .verify_terminal(inner_description) } + /// Check that a tree node has exactly one child, which is a descriptor key expression. + /// + /// If so, serialize the child expression and parse it from a string. + pub fn verify_key_expression_parent( + &self, + description: &'static str, + inner_description: &'static str, + ) -> Result + where + T: FromStr, + T::Err: StaticDebugAndDisplay, + { + self.verify_n_children(description, 1..=1) + .map_err(ParseError::Tree)?; + self.first_child() + .unwrap() + .verify_key_expression(inner_description) + } + + /// Check that a tree node is a descriptor key expression. + /// + /// If so, serialize the expression and parse it from a string. + pub fn verify_key_expression(&self, description: &'static str) -> Result + where + T: FromStr, + T::Err: StaticDebugAndDisplay, + { + let _ = description; + let key = self.expression_string(); + T::from_str(&key).map_err(ParseError::box_from_str) + } + + /// Serialize this tree node and all descendants back to expression syntax. + pub fn expression_string(&self) -> String { + let mut ret = String::new(); + self.fmt_expression(&mut ret) + .expect("writing to a string cannot fail"); + ret + } + + fn fmt_expression(&self, f: &mut W) -> fmt::Result { + f.write_str(self.name())?; + match self.parens() { + Parens::None => {} + Parens::Round | Parens::Curly => { + let (open, close) = match self.parens() { + Parens::Round => ('(', ')'), + Parens::Curly => ('{', '}'), + Parens::None => unreachable!(), + }; + f.write_char(open)?; + for (i, child) in self.children().enumerate() { + if i > 0 { + f.write_char(',')?; + } + child.fmt_expression(f)?; + } + f.write_char(close)?; + } + } + f.write_str(self.postfix()) + } + /// Check that a tree node has exactly two children. /// /// If so, return them. @@ -444,6 +536,15 @@ impl<'s> TreeIterItem<'s> { &'s self, mut map_child: F, ) -> Result, E> { + if !self.postfix().is_empty() { + return Err(ParseThresholdError::UnexpectedKeyExpressionSuffix { + name: self.name().to_owned(), + suffix: self.postfix().to_owned(), + pos: self.nodes[self.index].postfix_pos, + } + .into()); + } + let mut child_iter = self.children(); let kchild = match child_iter.next() { Some(k) => k, @@ -516,7 +617,10 @@ impl<'a> Tree<'a> { let mut n_nodes = 1; let mut max_depth = 0; let mut open_paren_stack = Vec::with_capacity(128); - for (pos, ch) in s.bytes().enumerate() { + let mut pos = 0; + let bytes = s.as_bytes(); + while pos < s.len() { + let ch = bytes[pos]; if ch == b'(' || ch == b'{' { open_paren_stack.push((ch, pos)); if max_depth < open_paren_stack.len() { @@ -533,30 +637,31 @@ impl<'a> Tree<'a> { }); } + let next_pos = scan_key_expression_postfix(s, pos + 1)?; if let Some(&(paren_ch, paren_pos)) = open_paren_stack.last() { // not last paren; this should not be the end of the string, // and the next character should be a , ) or }. - if pos == s.len() - 1 { + if next_pos == s.len() { return Err(ParseTreeError::UnmatchedOpenParen { ch: paren_ch.into(), pos: paren_pos, }); } else { - let next_byte = s.as_bytes()[pos + 1]; + let next_byte = s.as_bytes()[next_pos]; if next_byte != b')' && next_byte != b'}' && next_byte != b',' { return Err(ParseTreeError::ExpectedParenOrComma { ch: next_byte.into(), - pos: pos + 1, + pos: next_pos, }); // } } } else { // last paren; this SHOULD be the end of the string - if pos < s.len() - 1 { + if next_pos < s.len() { return Err(ParseTreeError::TrailingCharacter { - ch: s.as_bytes()[pos + 1].into(), - pos: pos + 1, + ch: s.as_bytes()[next_pos].into(), + pos: next_pos, }); } } @@ -573,6 +678,8 @@ impl<'a> Tree<'a> { } n_nodes += 1; + pos = scan_key_expression_postfix(s, pos + 1)?; + continue; } else if ch == b',' { if open_paren_stack.is_empty() { // We consider commas outside of the tree to be "trailing characters" @@ -581,6 +688,7 @@ impl<'a> Tree<'a> { n_nodes += 1; } + pos += 1; } // Catch "early end of string" if let Some((ch, pos)) = open_paren_stack.pop() { @@ -630,7 +738,10 @@ impl<'a> Tree<'a> { // as the string serialization lists all the nodes in pre-order. let mut parent_stack = Vec::with_capacity(max_depth); let mut current_node = Some(TreeNode::null(0)); - for (pos, ch) in s.bytes().enumerate() { + let mut pos = 0; + let bytes = s.as_bytes(); + while pos < s.len() { + let ch = bytes[pos]; if ch == b'(' || ch == b'{' { let mut current = current_node.expect("'(' only occurs after a node name"); current.name = &s[current.name_pos..pos]; @@ -662,8 +773,16 @@ impl<'a> Tree<'a> { } current_node = None; - parent_stack.pop(); + let closed_idx = parent_stack.pop().expect("checked by parse_pre_check"); + let suffix_end = scan_key_expression_postfix(s, pos + 1)?; + if suffix_end > pos + 1 { + nodes[closed_idx].postfix = &s[pos + 1..suffix_end]; + nodes[closed_idx].postfix_pos = pos + 1; + pos = suffix_end; + continue; + } } + pos += 1; } if let Some(mut current) = current_node { current.name = &s[current.name_pos..]; @@ -678,6 +797,26 @@ impl<'a> Tree<'a> { } } +fn scan_key_expression_postfix(s: &str, mut pos: usize) -> Result { + let bytes = s.as_bytes(); + if bytes.get(pos) != Some(&b'/') { + return Ok(pos); + } + + while pos < s.len() { + match bytes[pos] { + b'0'..=b'9' | b'\'' | b'*' | b'/' | b';' | b'<' | b'>' | b'h' | b'H' => { + pos += 1; + } + b',' | b')' | b'}' => break, + ch => { + return Err(ParseTreeError::ExpectedParenOrComma { ch: ch.into(), pos }); + } + } + } + Ok(pos) +} + /// Parse a string as a u32, forbidding zero. pub fn parse_num_nonzero(s: &str, context: &'static str) -> Result { if s == "0" { @@ -898,6 +1037,38 @@ mod tests { ); } + #[test] + fn parse_key_expression_postfix() { + let expr = Tree::from_str("pk(musig(A,B)/0/*)").unwrap(); + let key_expr = expr.root().first_child().unwrap(); + assert_eq!(key_expr.name(), "musig"); + assert_eq!(key_expr.postfix(), "/0/*"); + assert_eq!(key_expr.expression_string(), "musig(A,B)/0/*"); + } + + #[test] + fn non_key_expression_postfix_rejected() { + let expr = Tree::from_str("or_b(pk(A),pk(B))/0/*").unwrap(); + let root = expr.root(); + + assert!(matches!( + root.name_separated(':').unwrap_err(), + ParseTreeError::UnexpectedKeyExpressionSuffix { .. } + )); + assert!(matches!( + root.verify_n_children("or_b", 2..=2).unwrap_err(), + ParseTreeError::UnexpectedKeyExpressionSuffix { .. } + )); + + let expr = Tree::from_str("multi_a(1,A,B)/0/*").unwrap(); + let root = expr.root(); + assert!(matches!( + root.verify_threshold::<20, _, (), ParseThresholdError>(|_| Ok(())) + .unwrap_err(), + ParseThresholdError::UnexpectedKeyExpressionSuffix { .. } + )); + } + #[test] fn parse_tree_desc() { let keys = [ diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index f56230014..49a994889 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -877,6 +877,31 @@ impl FromTree for Miniscript { .map_err(From::from) .map_err(Error::Parse)?; + fn is_key_expression_node(node: expression::TreeIterItem) -> bool { + let mut child = node; + while let Some(parent) = child.parent() { + let parent_name = parent + .name() + .rsplit_once(':') + .map(|(_, name)| name) + .unwrap_or(parent.name()); + + if matches!(parent_name, "pk" | "pkh" | "pk_k" | "pk_h") && parent.n_children() == 1 + { + return true; + } + + if matches!(parent_name, "multi" | "sortedmulti" | "multi_a" | "sortedmulti_a") + && !child.is_first_child() + { + return true; + } + + child = parent; + } + false + } + let mut stack = Vec::with_capacity(128); for (n, node) in root.pre_order_iter().enumerate().rev() { // Before doing anything else, check if this is the inner value of a terminal. @@ -890,6 +915,10 @@ impl FromTree for Miniscript { // We do not do this check on the root node, because its parent might be wsh or // sh or something, and actually these ARE single-child combinators, but we don't // want to skip their children. + if n > 0 && is_key_expression_node(node) { + continue; + } + if n > 0 && node.n_children() == 0 { let parent = node.parent().unwrap(); if parent.n_children() == 1 { @@ -915,108 +944,119 @@ impl FromTree for Miniscript { .map_err(Error::Parse)?; // "pk" and "pkh" are aliases for "c:pk_k" and "c:pk_h" respectively. - let new = match frag_name { - "expr_raw_pkh" => node - .verify_terminal_parent("expr_raw_pkh", "public key hash") - .map(Miniscript::expr_raw_pkh) - .map_err(Error::Parse), - "pk" => node - .verify_terminal_parent("pk", "public key") - .map(Miniscript::pk) - .map_err(Error::Parse), - "pkh" => node - .verify_terminal_parent("pkh", "public key") - .map(Miniscript::pkh) - .map_err(Error::Parse), - "pk_k" => node - .verify_terminal_parent("pk_k", "public key") - .map(Miniscript::pk_k) - .map_err(Error::Parse), - "pk_h" => node - .verify_terminal_parent("pk_h", "public key") - .map(Miniscript::pk_h) - .map_err(Error::Parse), - "after" => node - .verify_after() - .map(Miniscript::after) - .map_err(Error::Parse), - "older" => node - .verify_older() - .map(Miniscript::older) - .map_err(Error::Parse), - "sha256" => node - .verify_terminal_parent("sha256", "hash") - .map(Miniscript::sha256) - .map_err(Error::Parse), - "hash256" => node - .verify_terminal_parent("hash256", "hash") - .map(Miniscript::hash256) - .map_err(Error::Parse), - "ripemd160" => node - .verify_terminal_parent("ripemd160", "hash") - .map(Miniscript::ripemd160) - .map_err(Error::Parse), - "hash160" => node - .verify_terminal_parent("hash160", "hash") - .map(Miniscript::hash160) - .map_err(Error::Parse), - "1" => { - node.verify_n_children("1", 0..=0) - .map_err(From::from) - .map_err(Error::Parse)?; - Ok(Miniscript::TRUE) - } - "0" => { - node.verify_n_children("0", 0..=0) - .map_err(From::from) - .map_err(Error::Parse)?; - Ok(Miniscript::FALSE) - } - "and_v" => binary(node, &mut stack, "and_v", Terminal::AndV), - "and_b" => binary(node, &mut stack, "and_b", Terminal::AndB), - "and_n" => binary(node, &mut stack, "and_n", |x, y| { - Terminal::AndOr(x, y, Arc::new(Miniscript::FALSE)) - }), - "andor" => { - node.verify_n_children("andor", 3..=3) - .map_err(From::from) - .map_err(Error::Parse)?; - Miniscript::from_ast(Terminal::AndOr( - stack.pop().unwrap(), - stack.pop().unwrap(), - stack.pop().unwrap(), - )) - } - "or_b" => binary(node, &mut stack, "or_b", Terminal::OrB), - "or_d" => binary(node, &mut stack, "or_d", Terminal::OrD), - "or_c" => binary(node, &mut stack, "or_c", Terminal::OrC), - "or_i" => binary(node, &mut stack, "or_i", Terminal::OrI), - "thresh" => node - .verify_threshold(|_| Ok(stack.pop().unwrap())) - .map(Terminal::Thresh) - .and_then(Miniscript::from_ast), - "multi" => node - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) - .map(Terminal::Multi) - .and_then(Miniscript::from_ast), - "sortedmulti" => node - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) - .map(Terminal::SortedMulti) - .and_then(Miniscript::from_ast), - "multi_a" => node - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) - .map(Terminal::MultiA) - .and_then(Miniscript::from_ast), - "sortedmulti_a" => node - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) - .map(Terminal::SortedMultiA) - .and_then(Miniscript::from_ast), - x => { - Err(Error::Parse(crate::ParseError::Tree(crate::ParseTreeError::UnknownName { - name: x.to_owned(), - }))) - } - }?; + let new = + match frag_name { + "expr_raw_pkh" => node + .verify_terminal_parent("expr_raw_pkh", "public key hash") + .map(Miniscript::expr_raw_pkh) + .map_err(Error::Parse), + "pk" => node + .verify_key_expression_parent("pk", "public key") + .map(Miniscript::pk) + .map_err(Error::Parse), + "pkh" => node + .verify_key_expression_parent("pkh", "public key") + .map(Miniscript::pkh) + .map_err(Error::Parse), + "pk_k" => node + .verify_key_expression_parent("pk_k", "public key") + .map(Miniscript::pk_k) + .map_err(Error::Parse), + "pk_h" => node + .verify_key_expression_parent("pk_h", "public key") + .map(Miniscript::pk_h) + .map_err(Error::Parse), + "after" => node + .verify_after() + .map(Miniscript::after) + .map_err(Error::Parse), + "older" => node + .verify_older() + .map(Miniscript::older) + .map_err(Error::Parse), + "sha256" => node + .verify_terminal_parent("sha256", "hash") + .map(Miniscript::sha256) + .map_err(Error::Parse), + "hash256" => node + .verify_terminal_parent("hash256", "hash") + .map(Miniscript::hash256) + .map_err(Error::Parse), + "ripemd160" => node + .verify_terminal_parent("ripemd160", "hash") + .map(Miniscript::ripemd160) + .map_err(Error::Parse), + "hash160" => node + .verify_terminal_parent("hash160", "hash") + .map(Miniscript::hash160) + .map_err(Error::Parse), + "1" => { + node.verify_n_children("1", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Miniscript::TRUE) + } + "0" => { + node.verify_n_children("0", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Miniscript::FALSE) + } + "and_v" => binary(node, &mut stack, "and_v", Terminal::AndV), + "and_b" => binary(node, &mut stack, "and_b", Terminal::AndB), + "and_n" => binary(node, &mut stack, "and_n", |x, y| { + Terminal::AndOr(x, y, Arc::new(Miniscript::FALSE)) + }), + "andor" => { + node.verify_n_children("andor", 3..=3) + .map_err(From::from) + .map_err(Error::Parse)?; + Miniscript::from_ast(Terminal::AndOr( + stack.pop().unwrap(), + stack.pop().unwrap(), + stack.pop().unwrap(), + )) + } + "or_b" => binary(node, &mut stack, "or_b", Terminal::OrB), + "or_d" => binary(node, &mut stack, "or_d", Terminal::OrD), + "or_c" => binary(node, &mut stack, "or_c", Terminal::OrC), + "or_i" => binary(node, &mut stack, "or_i", Terminal::OrI), + "thresh" => node + .verify_threshold(|_| Ok(stack.pop().unwrap())) + .map(Terminal::Thresh) + .and_then(Miniscript::from_ast), + "multi" => node + .verify_threshold(|sub| { + sub.verify_key_expression("public_key") + .map_err(Error::Parse) + }) + .map(Terminal::Multi) + .and_then(Miniscript::from_ast), + "sortedmulti" => node + .verify_threshold(|sub| { + sub.verify_key_expression("public_key") + .map_err(Error::Parse) + }) + .map(Terminal::SortedMulti) + .and_then(Miniscript::from_ast), + "multi_a" => node + .verify_threshold(|sub| { + sub.verify_key_expression("public_key") + .map_err(Error::Parse) + }) + .map(Terminal::MultiA) + .and_then(Miniscript::from_ast), + "sortedmulti_a" => node + .verify_threshold(|sub| { + sub.verify_key_expression("public_key") + .map_err(Error::Parse) + }) + .map(Terminal::SortedMultiA) + .and_then(Miniscript::from_ast), + x => Err(Error::Parse(crate::ParseError::Tree( + crate::ParseTreeError::UnknownName { name: x.to_owned() }, + ))), + }?; let mut new = Arc::new(new); if let Some(frag_wrap) = frag_wrap {