diff --git a/src/descriptor/bare.rs b/src/descriptor/bare.rs index ad0278143..1bebf4db8 100644 --- a/src/descriptor/bare.rs +++ b/src/descriptor/bare.rs @@ -36,7 +36,8 @@ pub struct Bare { impl Bare { /// Create a new raw descriptor pub fn new(ms: Miniscript) -> Result { - // do the top-level checks + // check every node, as parsing does, then do the top-level checks + ms.check_global_validity_recursive()?; BareCtx::top_level_checks(&ms)?; Ok(Self { ms }) } diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index b7a0cbf12..1b6c976aa 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. @@ -108,6 +110,34 @@ pub struct DescriptorMultiXKey { pub wildcard: Wildcard, } +/// Instance of a BIP390 `musig()` aggregate key expression. +/// +/// Participants must be compressed public keys or extended keys. X-only and uncompressed keys are +/// rejected: BIP327 KeyAgg takes 33-byte keys (matches Bitcoin Core). +#[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 { @@ -122,7 +152,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, @@ -439,6 +469,7 @@ pub enum NonDefiniteKeyError { Multipath, HardenedStep, NoWildcard, + MaximumDepthExceeded, } impl fmt::Display for NonDefiniteKeyError { @@ -452,6 +483,9 @@ impl fmt::Display for NonDefiniteKeyError { Self::NoWildcard => { f.write_str("descriptor does not have a wildcard; cannot derive at index") } + Self::MaximumDepthExceeded => f.write_str( + "key deeper than BIP32's maximum depth of 255 cannot be a DerivedDescriptorKey", + ), } } } @@ -459,6 +493,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] @@ -469,10 +540,19 @@ pub enum MalformedKeyDataKind { InvalidFullPublicKeyPrefix, InvalidMasterFingerprintLength, InvalidMultiIndexStep, + InvalidMusigAggregateDerivation, + InvalidMusigExpression, InvalidMultiXKeyDerivation, InvalidPublicKeyLength, InvalidWildcardInDerivationPath, KeyTooShort, + MaximumDepthExceeded, + MusigEmpty, + MusigHardenedDerivation, + MusigMultipathLenMismatch, + MusigNested, + MusigUncompressedParticipant, + MusigXOnlyParticipant, MultipleFingerprintsInPublicKey, MultipleDerivationPathIndexSteps, NoKeyAfterOrigin, @@ -488,10 +568,21 @@ 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::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::MaximumDepthExceeded => { + "key derivation would go deeper than BIP32's maximum depth of 255" + } + 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::MusigUncompressedParticipant => "musig participant keys must be compressed; uncompressed keys are not allowed", + Self::MusigXOnlyParticipant => "musig participant keys must be compressed public keys; x-only keys are not allowed", Self::MultipleFingerprintsInPublicKey => "multiple ']' in Descriptor Public Key", Self::MultipleDerivationPathIndexSteps => "'<' may only appear once in a derivation path", Self::NoKeyAfterOrigin => "no key after origin", @@ -619,10 +710,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. /// @@ -732,6 +839,10 @@ impl FromStr for DescriptorPublicKey { type Err = DescriptorKeyParseError; fn from_str(s: &str) -> Result { + if s.starts_with("musig(") { + return parse_musig_key(s, Self::from_str).map(DescriptorPublicKey::Musig); + } + // A "raw" public key without any origin is the least we accept. if s.len() < 64 { return Err(DescriptorKeyParseError::MalformedKeyData( @@ -744,7 +855,13 @@ impl FromStr for DescriptorPublicKey { match key_part.get(..4) { Some("xprv" | "tprv") => Err(DescriptorKeyParseError::UnexpectedXPrivateKey), Some("xpub" | "tpub") => { - let (xpub, derivation_paths, wildcard) = parse_xkey_deriv(key_part)?; + let (xpub, derivation_paths, wildcard) = + parse_xkey_deriv::(key_part)?; + if derivation_exceeds_max_depth(xpub.depth, &derivation_paths, wildcard) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MaximumDepthExceeded, + )); + } if derivation_paths.len() > 1 { Ok(Self::MultiXPub(DescriptorMultiXKey { origin, @@ -793,6 +910,93 @@ impl FromStr for DescriptorPublicKey { } } +/// Parses a `musig()` key expression, parsing each participant with `parse_participant`. +pub(super) fn parse_musig_key( + s: &str, + mut parse_participant: impl FnMut(&str) -> Result, +) -> 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() { + // Participants are single or extended keys, which are terminals. + if child.n_children() > 0 { + let kind = if child.name() == "musig" { + MalformedKeyDataKind::MusigNested + } else { + MalformedKeyDataKind::InvalidMusigExpression + }; + return Err(DescriptorKeyParseError::MalformedKeyData(kind)); + } + participants.push(parse_participant(child.name())?); + } + + 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()) +} + +/// Checks that several derivation paths form one BIP389 multipath step, as the parser produces +/// them and as [`fmt_derivation_paths`] writes them: equal lengths, exactly one step where they +/// differ, and no index repeated at that step. +fn check_multipath_shape(paths: &[bip32::DerivationPath]) -> Result<(), DescriptorKeyParseError> { + let (first, rest) = match paths.split_first() { + Some((first, rest)) if !rest.is_empty() => (first, rest), + _ => return Ok(()), + }; + if rest.iter().any(|path| path.len() != first.len()) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMultiIndexStep, + )); + } + + let mut steps = (0..first.len()).filter(|&i| rest.iter().any(|path| path[i] != first[i])); + let step = match (steps.next(), steps.next()) { + (Some(step), None) => step, + (Some(_), Some(_)) => { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MultipleDerivationPathIndexSteps, + )) + } + (None, _) => { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMultiIndexStep, + )) + } + }; + + let mut indexes = paths.iter().map(|path| path[step]).collect::>(); + indexes.sort_unstable(); + if indexes.windows(2).any(|pair| pair[0] == pair[1]) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMultiIndexStep, + )); + } + Ok(()) +} + impl From for DescriptorPublicKey { fn from(key: XOnlyPublicKey) -> Self { Self::Single(SinglePub { origin: None, key: SinglePubKey::XOnly(key) }) @@ -814,6 +1018,9 @@ impl DescriptorPublicKey { /// four bytes of the HASH160 of its serialization (33 bytes for a full key, 32 bytes for an /// x-only key). This matches the key origin Bitcoin Core reports for keys without origin /// information, and is never `0x00000000` for a key without origin. + /// + /// The exception is a `musig()` key, which has no single master key: its fingerprint is + /// `0x00000000`. pub fn master_fingerprint(&self) -> bip32::Fingerprint { match *self { Self::XPub(ref xpub) => { @@ -848,6 +1055,7 @@ impl DescriptorPublicKey { ) } } + Self::Musig(_) => bip32::Fingerprint::default(), } } @@ -857,7 +1065,8 @@ impl DescriptorPublicKey { /// can get full paths by appending one additional derivation step, according /// to the wildcard type (hardened or normal). /// - /// For multipath extended keys, this returns `None`. + /// For multipath extended keys, this returns `None`. It also returns `None` for `musig()` + /// keys, which have no single key source. pub fn full_derivation_path(&self) -> Option { match *self { Self::XPub(ref xpub) => { @@ -873,7 +1082,7 @@ impl DescriptorPublicKey { } else { bip32::DerivationPath::from(vec![]) }), - Self::MultiXPub(_) => None, + Self::MultiXPub(_) | Self::Musig(_) => None, } } @@ -884,6 +1093,9 @@ 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). + /// + /// For `musig()` keys these are the aggregate-level paths, relative to the BIP328 synthetic + /// xpub rather than to a master key. pub fn full_derivation_paths(&self) -> Vec { match self { Self::MultiXPub(xpub) => { @@ -898,6 +1110,7 @@ impl DescriptorPublicKey { .map(|p| origin_path.extend(p)) .collect() } + Self::Musig(musig) => musig.derivation_paths.paths().clone(), _ => vec![self .full_derivation_path() .expect("Must be Some for non-multipath keys")], @@ -915,7 +1128,7 @@ impl DescriptorPublicKey { match *self { Self::XPub(ref xpub) => Some(xpub.derivation_path.clone()), Self::Single(_) => Some(bip32::DerivationPath::from(vec![])), - Self::MultiXPub(_) => None, + Self::MultiXPub(_) | Self::Musig(_) => None, } } @@ -933,6 +1146,7 @@ impl DescriptorPublicKey { vec![bip32::DerivationPath::from(vec![])] } Self::MultiXPub(xpub) => xpub.derivation_paths.paths().clone(), + Self::Musig(musig) => musig.derivation_paths.paths().clone(), } } @@ -942,6 +1156,10 @@ impl DescriptorPublicKey { Self::Single(..) => false, Self::XPub(ref xpub) => xpub.wildcard != Wildcard::None, Self::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None, + Self::Musig(ref musig) => { + musig.wildcard != Wildcard::None + || musig.participants.iter().any(Self::has_wildcard) + } } } @@ -951,6 +1169,22 @@ impl DescriptorPublicKey { Self::Single(..) => None, Self::XPub(ref xpub) => Some(xpub.wildcard), Self::MultiXPub(ref xpub) => Some(xpub.wildcard), + Self::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 Self) -> bool, + { + match self { + // `DescriptorMusigKey::new` rejects nested `musig()` participants. + Self::Musig(musig) => musig.participants.iter().all(pred), + _ => pred(self), } } @@ -960,6 +1194,12 @@ impl DescriptorPublicKey { Self::Single(..) => &[], Self::XPub(xpub) => core::slice::from_ref(&xpub.derivation_path), Self::MultiXPub(xpub) => &xpub.derivation_paths.paths()[..], + Self::Musig(musig) => { + if musig.derivation_paths.paths().iter().any(has_hardened_step) { + return true; + } + return musig.participants.iter().any(Self::has_hardened_step); + } }; for p in paths { for step in p.into_iter() { @@ -971,20 +1211,44 @@ impl DescriptorPublicKey { false } + /// Whether deriving this key, including a wildcard step, would go deeper than BIP32's maximum + /// depth of 255. + fn exceeds_max_depth(&self) -> bool { + match self { + Self::Single(..) => false, + Self::XPub(xpub) => derivation_exceeds_max_depth( + xpub.xkey.depth, + core::slice::from_ref(&xpub.derivation_path), + xpub.wildcard, + ), + Self::MultiXPub(xpub) => derivation_exceeds_max_depth( + xpub.xkey.depth, + xpub.derivation_paths.paths(), + xpub.wildcard, + ), + // `DescriptorMusigKey::new` already limits the aggregate-level paths. + Self::Musig(musig) => musig.participants.iter().any(Self::exceeds_max_depth), + } + } + /// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a /// *definite* key (i.e. one where all the derivation paths are set). /// /// # Returns /// - /// - If this key is not an xpub, returns `self`. + /// - If this key is a single key, returns `self`. /// - If this key is an xpub but does not have a wildcard, returns `self`. - /// - Otherwise, returns the xpub at derivation `index` (removing the wildcard). + /// - If this key is an xpub with a wildcard, returns the xpub at derivation `index` (removing + /// the wildcard). + /// - If this key is a `musig()` key, returns it with its ranged participants and any + /// aggregate-level wildcard derived at `index`. /// /// # Errors /// /// - If `index` is hardened. /// - If derivation path contains a hardened step. /// - If the key contains multi-path derivations + /// - If deriving the key would go deeper than BIP32's maximum depth of 255 pub fn at_derivation_index( self, index: u32, @@ -1013,6 +1277,41 @@ impl DescriptorPublicKey { }) } Self::MultiXPub(_) => return Err(NonDefiniteKeyError::Multipath), + Self::Musig(musig) => { + let participants = musig + .participants + .into_iter() + .map(|participant| { + participant + .at_derivation_index(index) + .map(DefiniteDescriptorKey::into_descriptor_public_key) + }) + .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::, _>>()?; + + Self::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) @@ -1023,6 +1322,10 @@ impl DescriptorPublicKey { match *self { Self::Single(..) | Self::XPub(..) => false, Self::MultiXPub(_) => true, + Self::Musig(ref musig) => { + musig.derivation_paths.paths().len() > 1 + || musig.participants.iter().any(Self::is_multipath) + } } } @@ -1049,6 +1352,7 @@ impl DescriptorPublicKey { }) .collect() } + Self::Musig(musig) => musig.into_single_keys(), } } @@ -1056,11 +1360,278 @@ 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 { - Self::Single(_) => None, - Self::XPub(xpub) => Some(xpub.xkey.network), - Self::MultiXPub(multi_xpub) => Some(multi_xpub.xkey.network), + Self::Single(_) => XKeyNetwork::NoXKeys, + Self::XPub(xpub) => XKeyNetwork::Single(xpub.xkey.network), + Self::MultiXPub(multi_xpub) => XKeyNetwork::Single(multi_xpub.xkey.network), + Self::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 derive + /// the whole key with [`DescriptorPublicKey::at_derivation_index`], and call this on the + /// `musig()` key it returns. + 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 { + // `aggregate_public_key` checks that the participants are definite, and `new` rejects + // hardened aggregate-level steps. + if self.wildcard != Wildcard::None { + return Err(NonDefiniteKeyError::Wildcard.into()); + } + if self.derivation_paths.paths().len() > 1 { + return Err(NonDefiniteKeyError::Multipath.into()); + } + super::musig::derive_public_key(secp, self) + } + + /// Derives each participant key at `index`, as [`DescriptorPublicKey::at_derivation_index`] + /// does: ranged participants are derived at `index`, and the others are returned as-is. + pub fn derived_participants_at_index( + &self, + index: u32, + ) -> Result, NonDefiniteKeyError> { + self.participants + .iter() + .map(|participant| participant.clone().at_derivation_index(index)) + .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, + )); + } + + // BIP327 KeyAgg takes 33-byte compressed keys. Extended keys always derive compressed + // keys, so only single keys can fail these checks. + for participant in participants { + if participant.is_x_only_key() { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MusigXOnlyParticipant, + )); + } + if participant.is_uncompressed() { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MusigUncompressedParticipant, + )); + } + } + + 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, + )); + } + check_multipath_shape(derivation_paths.paths())?; + // Aggregate-level derivation starts from the BIP328 synthetic xpub, at depth 0. + if derivation_exceeds_max_depth(0, derivation_paths.paths(), wildcard) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MaximumDepthExceeded, + )); + } + + 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 Self { 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( + Self::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( + Self::new( + participants, + DerivPaths::new(derivation_paths.clone()).expect("not empty"), + wildcard, + ) + .expect("single participant paths preserve musig invariants"), + ) + }) + .collect() } } @@ -1075,7 +1646,13 @@ impl FromStr for DescriptorSecretKey { .map_err(DescriptorKeyParseError::WifPrivateKey)?; Ok(Self::Single(SinglePriv { key: sk, origin })) } else { - let (xpriv, derivation_paths, wildcard) = parse_xkey_deriv(key_part)?; + let (xpriv, derivation_paths, wildcard) = + parse_xkey_deriv::(key_part)?; + if derivation_exceeds_max_depth(xpriv.depth, &derivation_paths, wildcard) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MaximumDepthExceeded, + )); + } if derivation_paths.len() > 1 { Ok(Self::MultiXPrv(DescriptorMultiXKey { origin, @@ -1158,6 +1735,20 @@ fn parse_key_origin(s: &str) -> Result<(&str, Option), Descrip } } +/// Whether deriving from an extended key at `depth` along any of `paths`, plus one more step for a +/// wildcard, would go deeper than BIP32's maximum depth of 255. +/// +/// The paths hold only the steps after the extended key. Its depth already includes the steps of +/// its origin, so those are not counted again. +fn derivation_exceeds_max_depth( + depth: u8, + paths: &[bip32::DerivationPath], + wildcard: Wildcard, +) -> bool { + let longest = paths.iter().map(|path| path.len()).max().unwrap_or(0); + longest + usize::from(wildcard != Wildcard::None) > usize::from(u8::MAX - depth) +} + pub(crate) fn parse_xkey_deriv( key_deriv: &str, ) -> Result<(Key, Vec, Wildcard), DescriptorKeyParseError> @@ -1165,8 +1756,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, @@ -1175,6 +1766,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 @@ -1268,7 +1881,7 @@ where .map(|index_list| index_list.into_iter().collect::()) .collect::>(); - Ok((xkey, derivation_paths, wildcard)) + Ok((derivation_paths, wildcard)) } impl DescriptorXKey { @@ -1366,6 +1979,8 @@ impl MiniscriptKey for DescriptorPublicKey { fn is_uncompressed(&self) -> bool { match self { Self::Single(key) => key.is_uncompressed(), + // Extended keys derive compressed keys, and `DescriptorMusigKey::new` rejects + // uncompressed `musig()` participants. _ => false, } } @@ -1373,15 +1988,30 @@ impl MiniscriptKey for DescriptorPublicKey { fn is_x_only_key(&self) -> bool { match self { Self::Single(single_pub) => single_pub.is_x_only_key(), + // The aggregate is used as an x-only Taproot key. Non-Taproot contexts reject it, + // with their own error through `is_musig`. + Self::Musig(_) => true, _ => false, } } + fn is_musig(&self) -> bool { matches!(self, Self::Musig(_)) } + fn num_der_paths(&self) -> usize { match self { Self::Single(single) => single.num_der_paths(), Self::XPub(xpub) => xpub.num_der_paths(), Self::MultiXPub(xpub) => xpub.num_der_paths(), + Self::Musig(musig) => { + let aggregate_paths = musig.derivation_paths.paths().len(); + let participant_paths = musig + .participants + .iter() + .map(Self::num_der_paths) + .max() + .unwrap_or(0); + aggregate_paths.max(participant_paths) + } } } } @@ -1392,8 +2022,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 derivation fails. [`Self::new`] rejects keys that BIP32 cannot derive, so this + /// only happens in cryptographic edge cases that are unreachable for non-adversarial keys, + /// such as a BIP327 `musig()` aggregate at the point at infinity. pub fn derive_public_key(&self, secp: &Secp256k1) -> bitcoin::PublicKey { match self.0 { DescriptorPublicKey::Single(ref pk) => match pk.key { @@ -1415,12 +2052,16 @@ impl DefiniteDescriptorKey { DescriptorPublicKey::MultiXPub(_) => { unreachable!("impossible by construction of DefiniteDescriptorKey") } + DescriptorPublicKey::Musig(ref musig) => { + super::musig::derive_public_key(secp, musig).expect("failed to derive musig() key") + } } } - /// Construct an instance from a descriptor key and a derivation index + /// Constructs an instance from a descriptor key. /// - /// Returns `None` if the key contains a wildcard + /// Returns an error if the key has a wildcard, a hardened derivation step or several derivation + /// paths, or if deriving it would go deeper than BIP32's maximum depth of 255. pub fn new(key: DescriptorPublicKey) -> Result { if key.has_wildcard() { Err(NonDefiniteKeyError::Wildcard) @@ -1428,6 +2069,8 @@ impl DefiniteDescriptorKey { Err(NonDefiniteKeyError::HardenedStep) } else if key.is_multipath() { Err(NonDefiniteKeyError::Multipath) + } else if key.exceeds_max_depth() { + Err(NonDefiniteKeyError::MaximumDepthExceeded) } else { Ok(Self(key)) } @@ -1436,16 +2079,21 @@ impl DefiniteDescriptorKey { /// The fingerprint of the master key associated with this key. /// /// See [`DescriptorPublicKey::master_fingerprint`] for how this is computed for keys without - /// origin information. + /// origin information, including `musig()` keys. pub fn master_fingerprint(&self) -> bip32::Fingerprint { self.0.master_fingerprint() } /// Full path from the master key if not a multipath extended key. + /// + /// `None` for `musig()` keys, which have no single key source. pub fn full_derivation_path(&self) -> Option { self.0.full_derivation_path() } /// Full paths from the master key. The vector will contain just one path for single /// keys, and multiple ones for multipath extended keys + /// + /// For `musig()` keys these are the aggregate-level paths, relative to the BIP328 synthetic + /// xpub rather than to a master key. pub fn full_derivation_paths(&self) -> Vec { self.0.full_derivation_paths() } @@ -1480,6 +2128,8 @@ impl MiniscriptKey for DefiniteDescriptorKey { fn is_x_only_key(&self) -> bool { self.0.is_x_only_key() } + fn is_musig(&self) -> bool { self.0.is_musig() } + fn num_der_paths(&self) -> usize { self.0.num_der_paths() } } @@ -1536,12 +2186,19 @@ mod test { use serde_test::{assert_tokens, Token}; use super::{ - DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, - MiniscriptKey, Wildcard, + DerivPaths, DescriptorKeyParseError, DescriptorMultiXKey, DescriptorMusigKey, + DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, MalformedKeyDataKind, + MiniscriptKey, MusigDerivationKind, MusigKeyAggError, PublicKey, Wildcard, }; use crate::descriptor::key::NonDefiniteKeyError; use crate::prelude::*; - use crate::DefiniteDescriptorKey; + use crate::{DefiniteDescriptorKey, Descriptor}; + + const PUBKEY_1: &str = "021d4ea7132d4e1a362ee5efd8d0b59dd4d1fe8906eefa7dd812b05a46b73d829b"; + const PUBKEY_2: &str = "02a489e0ea42b56148d212d325b7c67c6460483ff931c303ea311edfef667c8f35"; + const XONLY_PUBKEY: &str = "1d4ea7132d4e1a362ee5efd8d0b59dd4d1fe8906eefa7dd812b05a46b73d829b"; + const UNCOMPRESSED_PUBKEY: &str = "0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf"; + const TPUB: &str = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi"; #[test] fn parse_descriptor_key_errors() { @@ -1625,6 +2282,338 @@ 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({},{})/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) + ); + + let desc = format!("musig({0}/<0;1>/*,{0})", TPUB); + let key = DescriptorPublicKey::from_str(&desc).unwrap(); + assert_eq!(key.at_derivation_index(7), Err(NonDefiniteKeyError::Multipath)); + } + + #[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()] + ); + // Ranged participants are aggregated after deriving the whole key. + assert!(matches!( + musig.aggregate_public_key(&secp), + Err(MusigKeyAggError::NonDefiniteKey(NonDefiniteKeyError::Wildcard)) + )); + let definite = key.clone().at_derivation_index(7).unwrap(); + let definite_musig = match definite.as_descriptor_public_key() { + DescriptorPublicKey::Musig(ref musig) => musig, + _ => unreachable!("derived musig key"), + }; + assert_eq!( + definite_musig.try_derive_public_key(&secp).unwrap(), + definite_musig.aggregate_public_key(&secp).unwrap() + ); + + 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) + ); + + // Aggregate-level wildcards and multipath derivation are not definite either. + for (desc, err) in [ + (format!("musig({0},{0})/0/*", TPUB), NonDefiniteKeyError::Wildcard), + (format!("musig({0},{0})/<0;1>", TPUB), NonDefiniteKeyError::Multipath), + ] { + let key = DescriptorPublicKey::from_str(&desc).unwrap(); + let musig = match &key { + DescriptorPublicKey::Musig(ref musig) => musig, + _ => unreachable!("parsed musig key"), + }; + assert_eq!( + musig.try_derive_public_key(&secp), + Err(MusigKeyAggError::NonDefiniteKey(err.clone())) + ); + assert_eq!(DefiniteDescriptorKey::new(key), Err(err)); + } + + // A participant deeper than BIP32 allows is reported as an error, not a panic. + let musig = musig_with_too_deep_participant(); + let too_deep = + Err(MusigKeyAggError::NonDefiniteKey(NonDefiniteKeyError::MaximumDepthExceeded)); + assert_eq!(musig.aggregate_public_key(&secp), too_deep); + assert_eq!(musig.try_derive_public_key(&secp), too_deep); + } + + #[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(); + } + + // Participants are plain keys. + for (desc, kind) in [ + ( + format!("musig(musig({},{}),{})", PUBKEY_1, PUBKEY_2, PUBKEY_1), + MalformedKeyDataKind::MusigNested, + ), + ( + format!("musig(pk({}),{})", PUBKEY_1, PUBKEY_2), + MalformedKeyDataKind::InvalidMusigExpression, + ), + ] { + assert_eq!( + DescriptorPublicKey::from_str(&desc), + Err(DescriptorKeyParseError::MalformedKeyData(kind)), + "{}", + desc + ); + } + + // BIP327 KeyAgg takes 33-byte compressed keys, so like Bitcoin Core we reject x-only and + // uncompressed participants, with or without origin information. + let x_only = MalformedKeyDataKind::MusigXOnlyParticipant; + let uncompressed = MalformedKeyDataKind::MusigUncompressedParticipant; + for (desc, kind) in [ + (format!("musig({},{})", XONLY_PUBKEY, PUBKEY_2), &x_only), + (format!("musig({},{})", PUBKEY_2, XONLY_PUBKEY), &x_only), + (format!("musig([deadbeef/1]{},{})", XONLY_PUBKEY, PUBKEY_2), &x_only), + (format!("musig({},{})", UNCOMPRESSED_PUBKEY, PUBKEY_2), &uncompressed), + (format!("musig({},{})", PUBKEY_2, UNCOMPRESSED_PUBKEY), &uncompressed), + ( + format!("musig([deadbeef/1]{},{})", UNCOMPRESSED_PUBKEY, PUBKEY_2), + &uncompressed, + ), + // The participant checks come before the aggregate derivation checks. + (format!("musig({},{})/0", XONLY_PUBKEY, PUBKEY_2), &x_only), + ] { + assert_eq!( + DescriptorPublicKey::from_str(&desc), + Err(DescriptorKeyParseError::MalformedKeyData(kind.clone())), + "{}", + desc + ); + } + + // The constructor enforces the same rule. + let participants = vec![ + DescriptorPublicKey::from_str(XONLY_PUBKEY).unwrap(), + DescriptorPublicKey::from_str(PUBKEY_2).unwrap(), + ]; + let derivation_paths = DerivPaths::new(vec![bip32::DerivationPath::master()]).unwrap(); + assert_eq!( + DescriptorMusigKey::new(participants, derivation_paths, Wildcard::None), + Err(DescriptorKeyParseError::MalformedKeyData(x_only)) + ); + } + + #[test] + fn musig_aggregate_multipath_shape() { + let participants = vec![ + DescriptorPublicKey::from_str(TPUB).unwrap(), + DescriptorPublicKey::from_str(TPUB).unwrap(), + ]; + let deriv_paths = |paths: &[&str]| { + let paths = paths + .iter() + .map(|path| bip32::DerivationPath::from_str(path).unwrap()) + .collect(); + DerivPaths::new(paths).unwrap() + }; + + let key = DescriptorMusigKey::new( + participants.clone(), + deriv_paths(&["m/0", "m/1"]), + Wildcard::Unhardened, + ) + .unwrap(); + assert_eq!(key.to_string(), format!("musig({0},{0})/<0;1>/*", TPUB)); + + // Aggregate-level paths must form one BIP389 multipath step, or the key could not be + // written out and parsed back. + for (paths, kind) in [ + (&["m/0", "m"][..], MalformedKeyDataKind::InvalidMultiIndexStep), + (&["m/0/0", "m/1/1"][..], MalformedKeyDataKind::MultipleDerivationPathIndexSteps), + (&["m/0", "m/0"][..], MalformedKeyDataKind::InvalidMultiIndexStep), + (&["m/0", "m/1", "m/0"][..], MalformedKeyDataKind::InvalidMultiIndexStep), + ] { + assert_eq!( + DescriptorMusigKey::new(participants.clone(), deriv_paths(paths), Wildcard::None), + Err(DescriptorKeyParseError::MalformedKeyData(kind)), + "{:?}", + paths + ); + } + + // BIP389 does not allow an index to appear twice in a multipath step. + assert_eq!( + DescriptorPublicKey::from_str(&format!("musig({0},{0})/<0;0>/*", TPUB)), + Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMultiIndexStep + )) + ); + } + + /// A `musig()` key whose first participant is deeper than BIP32 allows. The parser rejects + /// such participants, so this builds the key directly. + fn musig_with_too_deep_participant() -> DescriptorMusigKey { + let mut participant = match DescriptorPublicKey::from_str(TPUB).unwrap() { + DescriptorPublicKey::XPub(xpub) => xpub, + _ => unreachable!("parsed xpub"), + }; + participant.derivation_path = vec![bip32::ChildNumber::from(0); 256].into(); + DescriptorMusigKey::new( + vec![ + DescriptorPublicKey::XPub(participant), + DescriptorPublicKey::from_str(TPUB).unwrap(), + ], + DerivPaths::new(vec![bip32::DerivationPath::master()]).unwrap(), + Wildcard::None, + ) + .unwrap() + } + + #[test] + fn musig_maximum_depth() { + // Aggregate-level derivation starts from the synthetic xpub at depth 0, so it can take 255 + // steps, counting a wildcard as one. + let musig = |path: String| { + DescriptorPublicKey::from_str(&format!("musig({0},{0}){1}", TPUB, path)).map(|_| ()) + }; + let too_deep = Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MaximumDepthExceeded, + )); + assert_eq!(musig("/0".repeat(255)), Ok(())); + assert_eq!(musig("/0".repeat(256)), too_deep); + assert_eq!(musig(format!("{}/*", "/0".repeat(254))), Ok(())); + assert_eq!(musig(format!("{}/*", "/0".repeat(255))), too_deep); + assert_eq!(musig(format!("{}/<0;1>", "/0".repeat(255))), too_deep); + + // Deriving the deepest allowed wildcard uses the last step. + let key = + DescriptorPublicKey::from_str(&format!("musig({0},{0}){1}/*", TPUB, "/0".repeat(254))) + .unwrap(); + assert!(key.at_derivation_index(7).is_ok()); + + // Participants are checked like other keys when the key becomes definite. + let key = DescriptorPublicKey::Musig(musig_with_too_deep_participant()); + assert_eq!( + DefiniteDescriptorKey::new(key).map(|_| ()), + Err(NonDefiniteKeyError::MaximumDepthExceeded) + ); + } + + #[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(); @@ -1698,6 +2687,11 @@ mod test { assert_eq!(xonly.master_fingerprint().to_string(), "f678d9b7"); let definite = DefiniteDescriptorKey::new(xonly).unwrap(); assert_eq!(definite.master_fingerprint().to_string(), "f678d9b7"); + + // A musig() key has no single master key, so its fingerprint is zero. + 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 { @@ -1964,26 +2958,122 @@ mod test { )); } + #[test] + fn definite_key_maximum_depth() { + let key = |depth: u8, steps: usize, wildcard: Wildcard| { + let mut xkey = bip32::Xpub::from_str("xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8").unwrap(); + xkey.depth = depth; + DescriptorPublicKey::XPub(DescriptorXKey { + origin: None, + xkey, + derivation_path: vec![bip32::ChildNumber::from(0); steps].into(), + wildcard, + }) + }; + let new = |key| DefiniteDescriptorKey::new(key).map(|_| ()); + let too_deep = Err(NonDefiniteKeyError::MaximumDepthExceeded); + + // BIP32 depth stops at 255, so an xpub at `depth` can take 255 - depth more steps. + assert_eq!(new(key(0, 255, Wildcard::None)), Ok(())); + assert_eq!(new(key(0, 256, Wildcard::None)), too_deep); + assert_eq!(new(key(200, 55, Wildcard::None)), Ok(())); + assert_eq!(new(key(200, 56, Wildcard::None)), too_deep); + + // Deriving at an index turns the wildcard into one more step. + assert!(key(200, 54, Wildcard::Unhardened) + .at_derivation_index(7) + .is_ok()); + assert_eq!( + key(200, 55, Wildcard::Unhardened) + .at_derivation_index(7) + .map(|_| ()), + too_deep + ); + + // Origin steps don't count, since the xpub's depth already includes them. + let mut key_with_origin = key(0, 255, Wildcard::None); + if let DescriptorPublicKey::XPub(ref mut xpub) = key_with_origin { + xpub.origin = Some(( + bip32::Fingerprint::default(), + vec![bip32::ChildNumber::from(0); 300].into(), + )); + } + assert_eq!(new(key_with_origin), Ok(())); + + // A descriptor with such a key doesn't become definite, so it can't panic later when + // deriving its script pubkey. + let desc = Descriptor::new_tr(key(0, 256, Wildcard::None), None).unwrap(); + assert_eq!(desc.into_definite().map(|_| ()), too_deep); + } + + #[test] + fn parse_key_maximum_depth() { + // BIP32 test vector 1 master keys, at depth 0. + let xpub = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"; + let xprv = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"; + let steps = |n: usize| "/0".repeat(n); + let public = |s: String| DescriptorPublicKey::from_str(&s).map(|_| ()); + let secret = |s: String| DescriptorSecretKey::from_str(&s).map(|_| ()); + let too_deep = Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MaximumDepthExceeded, + )); + + // BIP32 depth stops at 255. A wildcard or a multipath step counts as one more step. + assert_eq!(public(format!("{xpub}{}", steps(255))), Ok(())); + assert_eq!(public(format!("{xpub}{}", steps(256))), too_deep); + assert_eq!(public(format!("{xpub}{}/*", steps(254))), Ok(())); + assert_eq!(public(format!("{xpub}{}/*", steps(255))), too_deep); + assert_eq!(public(format!("{xpub}{}/<0;1>", steps(254))), Ok(())); + assert_eq!(public(format!("{xpub}{}/<0;1>", steps(255))), too_deep); + assert_eq!(secret(format!("{xprv}{}/*", steps(254))), Ok(())); + assert_eq!(secret(format!("{xprv}{}/*", steps(255))), too_deep); + + // An xpub at depth 200 can take 55 more steps. + let mut xkey = bip32::Xpub::from_str(xpub).unwrap(); + xkey.depth = 200; + assert_eq!(public(format!("{xkey}{}", steps(55))), Ok(())); + assert_eq!(public(format!("{xkey}{}", steps(56))), too_deep); + + // Origin steps don't count, since the xpub's depth already includes them. + assert_eq!(public(format!("[d34db33f{}]{xpub}{}", steps(300), steps(255))), Ok(())); + + // A descriptor with such a key fails to parse, before anything can be derived from it. + let desc = format!("tr({xpub}{})", steps(256)); + assert!(Descriptor::::from_str(&desc).is_err()); + } + #[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 24aaaf7be..43545b21f 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::{KeyInfo, WalletPolicy, WalletPolicyError}; @@ -699,6 +701,7 @@ impl Descriptor { /// # Errors /// - If the descriptor contains wildcards /// - If the descriptor contains multi-path derivations + /// - If deriving a key would go deeper than BIP32's maximum depth of 255 pub fn into_definite(&self) -> Result, NonDefiniteKeyError> { if self.has_wildcard() { return Err(NonDefiniteKeyError::Wildcard); @@ -727,6 +730,7 @@ impl Descriptor { /// - If the descriptor contains no wildcards /// - If index >= 2^31 /// - If the descriptor contains multi-path derivations + /// - If deriving a key would go deeper than BIP32's maximum depth of 255 #[allow(deprecated)] pub fn derive_at_index(&self, index: u32) -> DerivationResult { if !self.has_wildcard() { @@ -796,8 +800,8 @@ impl Descriptor { /// /// # Errors /// - /// This function will return an error for multi-path descriptors - /// or if hardened derivation is attempted, + /// This function will return an error for multi-path descriptors, if hardened derivation is + /// attempted, or if deriving a key would go deeper than BIP32's maximum depth of 255. #[allow(deprecated)] pub fn derived_descriptor( &self, @@ -820,6 +824,21 @@ impl Descriptor { key_map: &mut KeyMap, secp: &secp256k1::Secp256k1, ) -> Result { + if s.starts_with("musig(") { + // Secret participants go into the key map one by one. There is no aggregate + // secret key to store. + let parse_participant = |participant: &str| { + if let Ok(sk) = DescriptorSecretKey::from_str(participant) { + key_map.insert(secp, sk) + } else { + DescriptorPublicKey::from_str(participant) + } + }; + return key::parse_musig_key(s, parse_participant) + .map(DescriptorPublicKey::Musig) + .map_err(|e| Error::Parse(ParseError::box_from_str(e))); + } + match DescriptorSecretKey::from_str(s) { Ok(sk) => { let pk = key_map @@ -915,6 +934,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(), @@ -989,6 +1021,13 @@ impl Descriptor { } true } + DescriptorPublicKey::Musig(_) if key.is_multipath() => { + for _ in 0..key.num_der_paths() { + descriptors.push(self.clone()); + } + true + } + DescriptorPublicKey::Musig(_) => false, } }) { // If there is no multipath key, return early. @@ -1007,7 +1046,10 @@ impl Descriptor { DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => { Ok(pk.clone()) } - DescriptorPublicKey::MultiXPub(_) => pk + // Like the keys above, a single-path `musig()` key goes into every descriptor + // unchanged. + DescriptorPublicKey::Musig(_) if !pk.is_multipath() => Ok(pk.clone()), + DescriptorPublicKey::MultiXPub(_) | DescriptorPublicKey::Musig(_) => pk .clone() .into_single_keys() .get(self.0) @@ -1040,12 +1082,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, } } @@ -1130,12 +1174,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, } } @@ -2150,6 +2196,58 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; } } + #[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)); + + // An uncompressed WIF participant is rejected like an uncompressed public key. + let descriptor_str = "tr(musig(5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659))"; + let err = Descriptor::::parse_descriptor(secp, descriptor_str) + .unwrap_err() + .to_string(); + assert!(err.contains("uncompressed keys are not allowed"), "{}", err); + + // Each secret participant gets its own key map entry. + let descriptor_str = "tr(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0))"; + 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(), 2); + assert_eq!(descriptor_str, descriptor.to_string_with_secret(&keymap)); + + // A nested musig() is rejected before its participants are parsed. + let descriptor_str = "tr(musig(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659),023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))"; + let err = Descriptor::::parse_descriptor(secp, descriptor_str) + .unwrap_err() + .to_string(); + assert!(err.contains("cannot be nested"), "{}", err); + } + #[test] fn checksum_for_nested_sh() { let descriptor_str = "sh(wpkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL))"; @@ -2176,6 +2274,262 @@ 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(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 + ), + format!( + "tr(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,pkh({}))", + musig + ), + format!( + "tr(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,or_d(pk({}),pkh({})))", + 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(); + } + + // pkh() rejects musig() outside Taproot too, also below the root of the script, with an + // error that says musig() is only allowed in tr(). + let other_key = "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c"; + for desc in [ + format!("sh(pkh({}))", musig), + format!("wsh(pkh({}))", musig), + format!("sh(wsh(pkh({})))", musig), + format!("c:pk_h({})", musig), + format!("wsh(or_d(pk({}),pkh({})))", other_key, musig), + format!("wsh(and_v(v:pkh({}),older(144)))", musig), + ] { + let err = Descriptor::::from_str(&desc).unwrap_err(); + assert!( + matches!(err, Error::ContextError(ScriptContextError::MusigNotAllowed(..))), + "{}", + desc + ); + assert!(err.to_string().contains("is only allowed in tr()"), "{}: {}", desc, err); + } + let desc = format!("wsh(pkh({}))", musig); + assert!(matches!( + Descriptor::::from_str(&desc), + Err(Error::ContextError(ScriptContextError::MusigNotAllowed(..))) + )); + + // Participants must be compressed keys in every Taproot position. + let internal_key = "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"; + let x_only = "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"; + let uncompressed = "0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf"; + let key_1 = "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c"; + let key_2 = "03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659"; + for desc in [ + format!("tr({},pk(musig({},{})))", internal_key, x_only, key_2), + format!("tr({},multi_a(1,{},musig({},{})))", internal_key, key_1, uncompressed, key_2), + format!("tr(musig({},{}))", uncompressed, key_2), + ] { + let err = Descriptor::::from_str(&desc) + .unwrap_err() + .to_string(); + assert!(err.contains("musig participant keys must be compressed"), "{}: {}", desc, err); + } + } + + #[test] + #[cfg(feature = "compiler")] + fn bip390_musig_compiler_placements() { + use crate::policy::compiler::CompilerError; + use crate::policy::concrete::DescriptorCtx; + + let musig = "musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)"; + let other_key = "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c"; + + // Neither pk_k() nor pk_h() accepts musig() outside Taproot, so there is nothing to compile + // to. + let policy = + policy::Concrete::::from_str(&format!("pk({})", musig)).unwrap(); + for result in [ + policy.compile_to_descriptor::(DescriptorCtx::Sh), + policy.compile_to_descriptor::(DescriptorCtx::Wsh), + policy.compile_to_descriptor::(DescriptorCtx::ShWsh), + policy.compile_to_descriptor::(DescriptorCtx::Bare), + ] { + assert!(matches!(result, Err(Error::CompilerError(CompilerError::LimitsExceeded)))); + } + + // In Taproot, musig() compiles as the internal key or as a script key. + let descriptor = policy + .compile_to_descriptor::(DescriptorCtx::Tr(None)) + .unwrap(); + assert_eq!(descriptor.internal_key().unwrap().to_string(), musig); + let policy = policy::Concrete::::from_str(&format!( + "or(99@pk({}),1@pk({}))", + other_key, musig + )) + .unwrap(); + let descriptor = policy + .compile_to_descriptor::(DescriptorCtx::Tr(None)) + .unwrap(); + assert_eq!(descriptor.internal_key().unwrap().to_string(), other_key); + assert!(descriptor.to_string().contains(&format!("({})", musig))); + } + + #[test] + fn bip390_musig_descriptor_constructors() { + let musig = DescriptorPublicKey::from_str("musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)").unwrap(); + let other_key = DescriptorPublicKey::from_str( + "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c", + ) + .unwrap(); + + // The constructors reject musig() outside Taproot, like parsing does. + let thresh = || Threshold::new(1, vec![other_key.clone(), musig.clone()]).unwrap(); + for result in [ + Descriptor::new_sh_sortedmulti(thresh()), + Descriptor::new_wsh_sortedmulti(thresh()), + Descriptor::new_sh_wsh_sortedmulti(thresh()), + Descriptor::new_wsh(Miniscript::multi(thresh())), + Descriptor::new_sh(Miniscript::pkh(musig.clone())), + Descriptor::new_wsh(Miniscript::pkh(musig.clone())), + Descriptor::new_sh_wsh(Miniscript::pkh(musig.clone())), + Descriptor::new_bare(Miniscript::pkh(musig.clone())), + Descriptor::new_pkh(musig.clone()), + Descriptor::new_wpkh(musig.clone()), + Descriptor::new_sh_wpkh(musig.clone()), + ] { + assert!(matches!( + result, + Err(Error::ContextError(ScriptContextError::MusigNotAllowed(..))) + )); + } + + // The Taproot constructors accept it. + assert_eq!( + Descriptor::new_tr(musig.clone(), None).unwrap(), + Descriptor::from_str(&format!("tr({})", musig)).unwrap() + ); + let leaf = TapTree::leaf(Miniscript::<_, Tap>::pkh(musig.clone())); + assert_eq!( + Descriptor::new_tr(other_key.clone(), Some(leaf)).unwrap(), + Descriptor::from_str(&format!("tr({},pkh({}))", other_key, musig)).unwrap() + ); + } + #[test] fn test_find_derivation_index_for_spk() { let secp = secp256k1::Secp256k1::verification_only(); @@ -2309,6 +2663,99 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; Descriptor::::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/0'/<0;1;2;3>/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/8/<0;1;2>/*)))").unwrap_err(); } + #[test] + fn musig_multipath_descriptors() { + let musig = "musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659)"; + let key = "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c"; + let xpub_a = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"; + let xpub_b = "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"; + + for (desc, expected) in [ + // A single-path musig() key goes into every descriptor unchanged. + ( + format!("tr({},pk({}/<0;1>/*))", musig, xpub_a), + [ + format!("tr({},pk({}/0/*))", musig, xpub_a), + format!("tr({},pk({}/1/*))", musig, xpub_a), + ], + ), + ( + format!("tr({}/<0;1>/*,pk({}))", xpub_a, musig), + [ + format!("tr({}/0/*,pk({}))", xpub_a, musig), + format!("tr({}/1/*,pk({}))", xpub_a, musig), + ], + ), + // Multipath participants and aggregate-level paths are split. + ( + format!("tr(musig({}/<0;1>/*,{}),pk({}/<2;3>/*))", xpub_a, key, xpub_b), + [ + format!("tr(musig({}/0/*,{}),pk({}/2/*))", xpub_a, key, xpub_b), + format!("tr(musig({}/1/*,{}),pk({}/3/*))", xpub_a, key, xpub_b), + ], + ), + ( + format!("tr(musig({},{})/<0;1>/*)", xpub_a, xpub_b), + [ + format!("tr(musig({},{})/0/*)", xpub_a, xpub_b), + format!("tr(musig({},{})/1/*)", xpub_a, xpub_b), + ], + ), + ] { + let desc = Descriptor::::from_str(&desc).unwrap(); + assert!(desc.is_multipath()); + let expected = expected + .iter() + .map(|desc| Descriptor::::from_str(desc).unwrap()) + .collect::>(); + assert_eq!(desc.into_single_descriptors().unwrap(), expected); + } + } + + #[test] + fn musig_policy_round_trip() { + use crate::policy::{Concrete, Liftable as _, Semantic}; + + let musig = "musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659)"; + let xpub_musig = "musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*"; + let key_1 = "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c"; + let key_2 = "023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66"; + + // Policies lifted from musig() descriptors parse back from both string notations. + for desc in [ + format!("tr({})", musig), + format!("tr({},pk({}))", musig, key_1), + format!("tr({},pk({}))", key_1, xpub_musig), + format!("tr({},multi_a(2,{},{},{}))", key_1, musig, key_2, xpub_musig), + ] { + let policy = Descriptor::::from_str(&desc) + .unwrap() + .lift() + .unwrap(); + for s in [policy.to_string(), policy.to_policy_syntax_string()] { + assert_eq!(Semantic::::from_str(&s).unwrap(), policy, "{}", s); + } + } + + let policy = format!("or(99@pk({}),1@pk({}))", xpub_musig, key_1); + assert_eq!( + Concrete::::from_str(&policy) + .unwrap() + .to_string(), + policy + ); + + #[cfg(feature = "serde")] + { + // Serde uses the string form. + let policy = "(pk(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659)) ∨ pk(02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c))"; + serde_test::assert_tokens( + &Semantic::::from_str(policy).unwrap(), + &[serde_test::Token::Str(policy)], + ); + } + } + #[test] fn regression_736() { Descriptor::::from_str( @@ -2369,11 +2816,17 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; Desc::from_str(&format!("sh(pk({}))", comp_key)).unwrap(); Desc::from_str(&format!("sh(pk({}))", uncomp_key)).unwrap(); Desc::from_str(&format!("sh(pk({}))", x_only_key)).unwrap_err(); + Desc::from_str(&format!("sh(pkh({}))", comp_key)).unwrap(); + Desc::from_str(&format!("sh(pkh({}))", uncomp_key)).unwrap(); + Desc::from_str(&format!("sh(pkh({}))", x_only_key)).unwrap_err(); // bare tests, x-only keys not supported Desc::from_str(&format!("pk({})", comp_key)).unwrap(); Desc::from_str(&format!("pk({})", uncomp_key)).unwrap(); Desc::from_str(&format!("pk({})", x_only_key)).unwrap_err(); + Desc::from_str(&format!("c:pk_h({})", comp_key)).unwrap(); + Desc::from_str(&format!("c:pk_h({})", uncomp_key)).unwrap(); + Desc::from_str(&format!("c:pk_h({})", x_only_key)).unwrap_err(); // pkh tests, x-only keys not supported Desc::from_str(&format!("pkh({})", comp_key)).unwrap(); @@ -2389,6 +2842,9 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; Desc::from_str(&format!("wsh(pk({}))", comp_key)).unwrap(); Desc::from_str(&format!("wsh(pk({}))", uncomp_key)).unwrap_err(); Desc::from_str(&format!("wsh(pk({}))", x_only_key)).unwrap_err(); + Desc::from_str(&format!("wsh(pkh({}))", comp_key)).unwrap(); + Desc::from_str(&format!("wsh(pkh({}))", uncomp_key)).unwrap_err(); + Desc::from_str(&format!("wsh(pkh({}))", x_only_key)).unwrap_err(); // Tap tests, key path Desc::from_str(&format!("tr({})", comp_key)).unwrap(); @@ -2399,6 +2855,142 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; Desc::from_str(&format!("tr({},pk({}))", x_only_key, comp_key)).unwrap(); Desc::from_str(&format!("tr({},pk({}))", x_only_key, uncomp_key)).unwrap_err(); Desc::from_str(&format!("tr({},pk({}))", x_only_key, x_only_key)).unwrap(); + Desc::from_str(&format!("tr({},pkh({}))", x_only_key, comp_key)).unwrap(); + Desc::from_str(&format!("tr({},pkh({}))", x_only_key, uncomp_key)).unwrap_err(); + Desc::from_str(&format!("tr({},pkh({}))", x_only_key, x_only_key)).unwrap(); + } + + #[test] + fn test_context_constructors() { + let comp_key = bitcoin::PublicKey::from_str( + "02015e4cb53458bf813db8c79968e76e10d13ed6426a23fa71c2f41ba021c2a7ab", + ) + .unwrap(); + let x_only_key = bitcoin::key::XOnlyPublicKey::from_str( + "015e4cb53458bf813db8c79968e76e10d13ed6426a23fa71c2f41ba021c2a7ab", + ) + .unwrap(); + let uncomp_key = bitcoin::PublicKey::from_str("04015e4cb53458bf813db8c79968e76e10d13ed6426a23fa71c2f41ba021c2a7ab0d46021e9e69ef061eb25eab41ae206187b2b05e829559df59d78319bd9267b4").unwrap(); + + // The constructors reject the keys that parsing rejects. + let x_only = || Threshold::new(1, vec![x_only_key]).unwrap(); + let uncomp = || Threshold::new(1, vec![uncomp_key]).unwrap(); + for result in [ + Descriptor::new_sh_sortedmulti(x_only()), + Descriptor::new_wsh_sortedmulti(x_only()), + Descriptor::new_sh_wsh_sortedmulti(x_only()), + Descriptor::new_sh(Miniscript::multi(x_only())), + Descriptor::new_wsh(Miniscript::multi(x_only())), + Descriptor::new_bare(Miniscript::multi(x_only())), + Descriptor::new_sh(Miniscript::pk(x_only_key)), + Descriptor::new_wsh(Miniscript::pk(x_only_key)), + Descriptor::new_bare(Miniscript::pk(x_only_key)), + ] { + assert!(matches!( + result, + Err(Error::ContextError(ScriptContextError::XOnlyKeysNotAllowed(..))) + )); + } + for result in [ + Descriptor::new_wsh_sortedmulti(uncomp()), + Descriptor::new_sh_wsh_sortedmulti(uncomp()), + Descriptor::new_wsh(Miniscript::multi(uncomp())), + Descriptor::new_sh_wsh(Miniscript::pk(uncomp_key)), + ] { + assert!(matches!( + result, + Err(Error::ContextError(ScriptContextError::UncompressedKeysNotAllowed)) + )); + } + Descriptor::new_sh_sortedmulti(uncomp()).unwrap(); + Descriptor::new_bare(Miniscript::pk(uncomp_key)).unwrap(); + + // A key below the root is checked too. + let ms = Miniscript::from_ast(Terminal::OrD( + Arc::new(Miniscript::pk(comp_key)), + Arc::new(Miniscript::pk(uncomp_key)), + )) + .unwrap(); + assert!(matches!( + Descriptor::new_wsh(ms), + Err(Error::ContextError(ScriptContextError::UncompressedKeysNotAllowed)) + )); + + // So is every node of every Taproot leaf. + for leaf in [ + Miniscript::<_, Tap>::pk(uncomp_key), + Miniscript::from_ast(Terminal::OrD( + Arc::new(Miniscript::pk(comp_key)), + Arc::new(Miniscript::pk(uncomp_key)), + )) + .unwrap(), + ] { + let tree = + TapTree::combine(TapTree::leaf(Miniscript::pk(comp_key)), TapTree::leaf(leaf)) + .unwrap(); + assert!(matches!( + Descriptor::new_tr(comp_key, Some(tree)), + Err(Error::ContextError(ScriptContextError::UncompressedKeysNotAllowed)) + )); + } + let multi = Miniscript::<_, Tap>::multi(Threshold::new(1, vec![comp_key]).unwrap()); + assert!(matches!( + Descriptor::new_tr(comp_key, Some(TapTree::leaf(multi))), + Err(Error::ContextError(ScriptContextError::TaprootMultiDisabled)) + )); + let leaf = TapTree::leaf(Miniscript::<_, Tap>::pkh(comp_key)); + assert_eq!( + Descriptor::new_tr(comp_key, Some(leaf)).unwrap(), + Descriptor::from_str(&format!("tr({},pkh({}))", comp_key, comp_key)).unwrap() + ); + + // The sortedmulti constructors build what parsing builds. The keys are out of order, + // so the script must sort them. + let g_key = bitcoin::PublicKey::from_str( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ) + .unwrap(); + let thresh = || Threshold::new(2, vec![g_key, comp_key]).unwrap(); + for (result, sortedmulti, multi) in [ + ( + Descriptor::new_sh_sortedmulti(thresh()), + format!("sh(sortedmulti(2,{},{}))", g_key, comp_key), + format!("sh(multi(2,{},{}))", comp_key, g_key), + ), + ( + Descriptor::new_wsh_sortedmulti(thresh()), + format!("wsh(sortedmulti(2,{},{}))", g_key, comp_key), + format!("wsh(multi(2,{},{}))", comp_key, g_key), + ), + ( + Descriptor::new_sh_wsh_sortedmulti(thresh()), + format!("sh(wsh(sortedmulti(2,{},{})))", g_key, comp_key), + format!("sh(wsh(multi(2,{},{})))", comp_key, g_key), + ), + ] { + let desc = result.unwrap(); + assert_eq!(desc, Descriptor::from_str(&sortedmulti).unwrap()); + let multi = Descriptor::::from_str(&multi).unwrap(); + assert_eq!(desc.explicit_script().unwrap(), multi.explicit_script().unwrap()); + } + + // A P2SH redeem script can't be larger than 520 bytes, which is 15 compressed keys + // in a multisig. + let secp = secp256k1::Secp256k1::signing_only(); + let keys: Vec = (1..=16u8) + .map(|i| { + PublicKey::new( + secp256k1::SecretKey::from_slice(&[i; 32]) + .unwrap() + .public_key(&secp), + ) + }) + .collect(); + Descriptor::new_sh_sortedmulti(Threshold::new(1, keys[..15].to_vec()).unwrap()).unwrap(); + assert!(matches!( + Descriptor::new_sh_sortedmulti(Threshold::new(1, keys).unwrap()), + Err(Error::ContextError(ScriptContextError::MaxRedeemScriptSizeExceeded { .. })) + )); } #[test] @@ -2785,6 +3377,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 @@ -2851,6 +3453,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..4302ff226 --- /dev/null +++ b/src/descriptor/musig.rs @@ -0,0 +1,221 @@ +// 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| { + let key = DefiniteDescriptorKey::new(participant.clone())?.derive_public_key(secp); + debug_assert!( + key.compressed, + "DescriptorMusigKey::new rejects uncompressed participants" + ); + Ok(key.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/segwitv0.rs b/src/descriptor/segwitv0.rs index 400c0d7ce..3665d1fe0 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -39,14 +39,15 @@ impl Wsh { /// Create a new wsh descriptor pub fn new(ms: Miniscript) -> Result { - // do the top-level checks + // check every node, as parsing does, then do the top-level checks + ms.check_global_validity_recursive()?; Segwitv0::top_level_checks(&ms)?; Ok(Self { ms }) } /// Create a new sortedmulti wsh descriptor pub fn new_sortedmulti(thresh: Threshold) -> Result { - Ok(Self { ms: Miniscript::sortedmulti(thresh) }) + Self::new(Miniscript::sortedmulti(thresh)) } /// Get the descriptor without the checksum diff --git a/src/descriptor/sh.rs b/src/descriptor/sh.rs index 3145663ad..27f4b143e 100644 --- a/src/descriptor/sh.rs +++ b/src/descriptor/sh.rs @@ -113,7 +113,8 @@ impl Sh { /// Create a new p2sh descriptor with the raw miniscript pub fn new(ms: Miniscript) -> Result { - // do the top-level checks + // check every node, as parsing does, then do the top-level checks + ms.check_global_validity_recursive()?; Legacy::top_level_checks(&ms)?; Ok(Self { inner: ShInner::Ms(ms) }) } @@ -121,7 +122,7 @@ impl Sh { /// Create a new p2sh sortedmulti descriptor with threshold `k` /// and Vec of `pks`. pub fn new_sortedmulti(thresh: Threshold) -> Result { - Ok(Self { inner: ShInner::Ms(Miniscript::sortedmulti(thresh)) }) + Self::new(Miniscript::sortedmulti(thresh)) } /// Create a new p2sh wrapped wsh descriptor with the raw miniscript @@ -137,8 +138,6 @@ impl Sh { pub fn new_wsh_sortedmulti( thresh: Threshold, ) -> Result { - // The context checks will be carried out inside new function for - // sortedMultiVec Ok(Self { inner: ShInner::Wsh(Wsh::new_sortedmulti(thresh)?) }) } diff --git a/src/descriptor/tr/mod.rs b/src/descriptor/tr/mod.rs index 227602534..0dc21038a 100644 --- a/src/descriptor/tr/mod.rs +++ b/src/descriptor/tr/mod.rs @@ -94,6 +94,12 @@ impl Tr { /// Create a new [`Tr`] descriptor from internal key and [`TapTree`] pub fn new(internal_key: Pk, tree: Option>) -> Result { Tap::check_pk(&internal_key)?; + // check every node of every leaf, as parsing does + if let Some(ref tree) = tree { + for leaf in tree.leaves() { + leaf.miniscript().check_global_validity_recursive()?; + } + } Ok(Self { internal_key, tree, spend_info: Mutex::new(None) }) } @@ -352,7 +358,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() .map_err(Error::Parse)?; let tap_tree = match root_children.next() { diff --git a/src/descriptor/wallet_policy/key_info.rs b/src/descriptor/wallet_policy/key_info.rs index cfb375270..53f7a0e09 100644 --- a/src/descriptor/wallet_policy/key_info.rs +++ b/src/descriptor/wallet_policy/key_info.rs @@ -36,7 +36,9 @@ impl TryFrom for KeyInfo { DescriptorPublicKey::XPub(_) | DescriptorPublicKey::MultiXPub(_) => { Err(WalletPolicyError::KeyInfoUnexpectedDerivation(pk.to_string())) } - DescriptorPublicKey::Single(_) => Err(WalletPolicyError::KeyInfoNotExtendedKey), + DescriptorPublicKey::Single(_) | DescriptorPublicKey::Musig(_) => { + Err(WalletPolicyError::KeyInfoNotExtendedKey) + } } } } @@ -73,7 +75,9 @@ pub(super) fn to_key_info(pk: &DescriptorPublicKey) -> Result (xpub.origin.clone(), xpub.xkey), DescriptorPublicKey::MultiXPub(xpub) => (xpub.origin.clone(), xpub.xkey), - DescriptorPublicKey::Single(_) => return Err(WalletPolicyError::KeyInfoNotExtendedKey), + DescriptorPublicKey::Single(_) | DescriptorPublicKey::Musig(_) => { + return Err(WalletPolicyError::KeyInfoNotExtendedKey) + } }; Ok(KeyInfo { origin, xkey }) } diff --git a/src/descriptor/wallet_policy/mod.rs b/src/descriptor/wallet_policy/mod.rs index 5b35e916c..85a50e438 100644 --- a/src/descriptor/wallet_policy/mod.rs +++ b/src/descriptor/wallet_policy/mod.rs @@ -56,7 +56,9 @@ impl Translator for WalletPolicyTranslator { (DerivPaths::single(x.derivation_path.clone()), x.wildcard) } DescriptorPublicKey::MultiXPub(x) => (x.derivation_paths.clone(), x.wildcard), - DescriptorPublicKey::Single(_) => return Err(WalletPolicyError::KeyInfoNotExtendedKey), + DescriptorPublicKey::Single(_) | DescriptorPublicKey::Musig(_) => { + return Err(WalletPolicyError::KeyInfoNotExtendedKey) + } }; let key = to_key_info(pk)?; let index = match self.key_info.iter().position(|k| *k == key) { diff --git a/src/expression/error.rs b/src/expression/error.rs index c0c6f088d..f6d68e750 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. @@ -158,6 +167,13 @@ impl fmt::Display for ParseTreeError { separator, pos ) } + Self::UnexpectedKeyExpressionSuffix { name, suffix, pos } => { + write!( + f, + "unexpected key expression suffix '{}' after '{}' (position {})", + suffix, name, pos + ) + } Self::TrailingCharacter { ch, pos } => { write!(f, "trailing data `{}...` (position {})", ch, pos) } @@ -179,6 +195,7 @@ impl std::error::Error for ParseTreeError { | Self::IncorrectName { .. } | Self::IncorrectNumberOfChildren { .. } | Self::MultipleSeparators { .. } + | Self::UnexpectedKeyExpressionSuffix { .. } | Self::TrailingCharacter { .. } | Self::UnknownName { .. } => None, } @@ -237,6 +254,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. @@ -256,6 +282,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), } @@ -268,7 +299,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 21a479526..d149cfb5a 100644 --- a/src/expression/mod.rs +++ b/src/expression/mod.rs @@ -28,8 +28,8 @@ mod error; use core::num::NonZeroU32; -use core::ops; use core::str::FromStr; +use core::{fmt, ops}; pub use self::error::{ParseNumError, ParseThresholdError, ParseTreeError}; use crate::blanket_traits::StaticDebugAndDisplay; @@ -49,6 +49,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, @@ -62,6 +64,8 @@ impl TreeNode<'_> { TreeNode { name: "", name_pos: 0, + postfix: "", + postfix_pos: 0, parens: Parens::None, n_children: 0, index, @@ -183,6 +187,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 } @@ -268,6 +275,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"), @@ -289,6 +304,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 { @@ -331,6 +354,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 { @@ -411,6 +440,73 @@ 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, parse the child as [`Self::verify_key_expression`] does. + /// + /// The `description` argument is only used to populate the error return, + /// and is not validated in any way. + pub fn verify_key_expression_parent( + &self, + 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() + } + + /// Parse a tree node as a descriptor key expression. + /// + /// A terminal is parsed from its name. A function-style expression, such as + /// `musig(A,B)/0/*`, is serialized back to expression syntax and parsed from that. + pub fn verify_key_expression(&self) -> Result + where + T: FromStr, + T::Err: StaticDebugAndDisplay, + { + let key = if self.n_children() == 0 { + T::from_str(self.name()) + } else { + T::from_str(&self.expression_string()) + }; + 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. @@ -445,6 +541,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, @@ -517,13 +622,17 @@ 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() { max_depth = open_paren_stack.len(); } } else if ch == b')' || ch == b'}' { + let next_pos; if let Some((open_ch, open_pos)) = open_paren_stack.pop() { if (open_ch == b'(' && ch == b'}') || (open_ch == b'{' && ch == b')') { return Err(ParseTreeError::MismatchedParens { @@ -534,30 +643,31 @@ impl<'a> Tree<'a> { }); } + 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, }); } } @@ -574,6 +684,8 @@ impl<'a> Tree<'a> { } n_nodes += 1; + pos = next_pos; + continue; } else if ch == b',' { if open_paren_stack.is_empty() { // We consider commas outside of the tree to be "trailing characters" @@ -582,6 +694,7 @@ impl<'a> Tree<'a> { n_nodes += 1; } + pos += 1; } // Catch "early end of string" if let Some((ch, pos)) = open_paren_stack.pop() { @@ -631,7 +744,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]; @@ -663,8 +779,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..]; @@ -679,6 +803,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" { @@ -899,6 +1043,47 @@ 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/*"); + assert_eq!(key_expr.verify_key_expression::().unwrap(), "musig(A,B)/0/*"); + + let expr = Tree::from_str("pk(A/0/*)").unwrap(); + assert_eq!( + expr.root() + .verify_key_expression_parent::("pk") + .unwrap(), + "A/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/lib.rs b/src/lib.rs index 9d07100ad..ff4f3cecf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,6 +166,9 @@ pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Ha /// Returns true if the key is an x-only pubkey (defaults to `false`). fn is_x_only_key(&self) -> bool; + /// Returns true if the key is a `musig()` key, which only Taproot allows (defaults to `false`). + fn is_musig(&self) -> bool { false } + /// Returns the number of different derivation paths in this key (defaults to `0`). /// /// Only >1 for keys in BIP389 multipath descriptors. diff --git a/src/miniscript/context.rs b/src/miniscript/context.rs index e0c889d0f..7e0513c8a 100644 --- a/src/miniscript/context.rs +++ b/src/miniscript/context.rs @@ -31,6 +31,9 @@ pub enum ScriptContextError { /// XOnly keys are only allowed in Tap context /// The first element is key, and second element is current script context XOnlyKeysNotAllowed(String, &'static str), + /// `musig()` keys are only allowed in Tap context + /// The first element is key, and second element is current script context + MusigNotAllowed(String, &'static str), /// Tapscript descriptors cannot contain uncompressed keys /// Tap context can contain compressed or xonly UncompressedKeysNotAllowed, @@ -72,6 +75,7 @@ impl error::Error for ScriptContextError { | MalleableDupIf | CompressedOnly(_) | XOnlyKeysNotAllowed(_, _) + | MusigNotAllowed(_, _) | UncompressedKeysNotAllowed | MaxWitnessItemsExceeded { .. } | MaxOpCountExceeded { .. } @@ -100,6 +104,9 @@ impl fmt::Display for ScriptContextError { Self::XOnlyKeysNotAllowed(ref pk, ref ctx) => { write!(f, "x-only key {} not allowed in {}", pk, ctx) } + Self::MusigNotAllowed(ref pk, ref ctx) => { + write!(f, "musig() key {} is only allowed in tr(), not in {}", pk, ctx) + } Self::UncompressedKeysNotAllowed => { write!(f, "uncompressed keys cannot be used in Taproot descriptors.") } @@ -393,7 +400,9 @@ impl ScriptContext for Legacy { // Only compressed and uncompressed public keys are allowed in Legacy context fn check_pk(pk: &Pk) -> Result<(), ScriptContextError> { - if pk.is_x_only_key() { + if pk.is_musig() { + Err(ScriptContextError::MusigNotAllowed(pk.to_string(), Self::name_str())) + } else if pk.is_x_only_key() { Err(ScriptContextError::XOnlyKeysNotAllowed(pk.to_string(), Self::name_str())) } else { Ok(()) @@ -405,7 +414,7 @@ impl ScriptContext for Legacy { ) -> Result<(), ScriptContextError> { // 1. Check the node first, throw an error on the language itself let node_checked = match ms.node { - Terminal::PkK(ref pk) => Self::check_pk(pk), + Terminal::PkK(ref pk) | Terminal::PkH(ref pk) => Self::check_pk(pk), Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { for pk in thresh.iter() { Self::check_pk(pk)?; @@ -513,7 +522,9 @@ impl ScriptContext for Segwitv0 { // No x-only keys or uncompressed keys in Segwitv0 context fn check_pk(pk: &Pk) -> Result<(), ScriptContextError> { - if pk.is_uncompressed() { + if pk.is_musig() { + Err(ScriptContextError::MusigNotAllowed(pk.to_string(), Self::name_str())) + } else if pk.is_uncompressed() { Err(ScriptContextError::UncompressedKeysNotAllowed) } else if pk.is_x_only_key() { Err(ScriptContextError::XOnlyKeysNotAllowed(pk.to_string(), Self::name_str())) @@ -527,7 +538,7 @@ impl ScriptContext for Segwitv0 { ) -> Result<(), ScriptContextError> { // 1. Check the node first, throw an error on the language itself let node_checked = match ms.node { - Terminal::PkK(ref pk) => Self::check_pk(pk), + Terminal::PkK(ref pk) | Terminal::PkH(ref pk) => Self::check_pk(pk), Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { for pk in thresh.iter() { Self::check_pk(pk)?; @@ -656,7 +667,7 @@ impl ScriptContext for Tap { ) -> Result<(), ScriptContextError> { // 1. Check the node first, throw an error on the language itself let node_checked = match ms.node { - Terminal::PkK(ref pk) => Self::check_pk(pk), + Terminal::PkK(ref pk) | Terminal::PkH(ref pk) => Self::check_pk(pk), Terminal::MultiA(ref thresh) | Terminal::SortedMultiA(ref thresh) => { for pk in thresh.iter() { Self::check_pk(pk)?; @@ -771,7 +782,9 @@ impl ScriptContext for BareCtx { // No x-only keys in Bare context fn check_pk(pk: &Pk) -> Result<(), ScriptContextError> { - if pk.is_x_only_key() { + if pk.is_musig() { + Err(ScriptContextError::MusigNotAllowed(pk.to_string(), Self::name_str())) + } else if pk.is_x_only_key() { Err(ScriptContextError::XOnlyKeysNotAllowed(pk.to_string(), Self::name_str())) } else { Ok(()) @@ -783,7 +796,7 @@ impl ScriptContext for BareCtx { ) -> Result<(), ScriptContextError> { // 1. Check the node first, throw an error on the language itself let node_checked = match ms.node { - Terminal::PkK(ref key) => Self::check_pk(key), + Terminal::PkK(ref key) | Terminal::PkH(ref key) => Self::check_pk(key), Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { for pk in thresh.iter() { Self::check_pk(pk)?; diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 2217458e1..8b8c2429c 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -519,6 +519,22 @@ impl Miniscript { /// Get a reference to the inner `AstElem` representing the root of miniscript pub fn as_inner(&self) -> &Terminal { &self.node } + /// Checks the global validity rules of the script context on every node. + /// + /// Parsing runs these checks, but constructors such as [`Miniscript::multi`] don't, and + /// [`Miniscript::from_ast`] checks only the new root. + pub(crate) fn check_global_validity_recursive( + &self, + ) -> Result<(), context::ScriptContextError> { + // It is definitely not sufficient to check only the root, since this will fail to notice + // illegal xonly keys at the leaves. But probably checking every single node is overkill. + // This may be worth optimizing. + for node in self.pre_order_iter() { + Ctx::check_global_validity(node)?; + } + Ok(()) + } + /// Encode as a Bitcoin script pub fn encode(&self) -> script::ScriptBuf where @@ -1013,15 +1029,36 @@ impl FromTree for Miniscript { .map_err(From::from) .map_err(Error::Parse)?; + // Collect the nodes in pre-order, skipping the descendants of fragments that take keys: + // these fragments parse their key arguments themselves, and a key argument may be a + // function-style expression such as `musig(A,B)/0/*` with descendants of its own. + let mut iter = root.pre_order_iter(); + let mut nodes = Vec::with_capacity(iter.len()); + while let Some(node) = iter.next() { + nodes.push(node); + let name = node + .name() + .rsplit_once(':') + .map_or(node.name(), |(_, name)| name); + let takes_keys = match name { + "pk" | "pkh" | "pk_k" | "pk_h" => node.n_children() == 1, + "multi" | "sortedmulti" | "multi_a" | "sortedmulti_a" => true, + _ => false, + }; + if takes_keys { + iter.skip_descendants(); + } + } + let mut stack = Vec::with_capacity(128); - for (n, node) in root.pre_order_iter().enumerate().rev() { + for (n, node) in nodes.into_iter().enumerate().rev() { // Before doing anything else, check if this is the inner value of a terminal. // In that case, just skip the node. Conveniently, there are no combinators // in Miniscript that have a single child that these might be confused with. // (Well, there are, but they're all serialized as wrappers.) // - // We also skip all the children of multi/multi_a and the first child of thresh - // (which will be the k value, not a real child). + // We also skip the first child of thresh (which will be the k value, not a real + // child). The children of multi/multi_a were already skipped above. // // 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 @@ -1037,9 +1074,6 @@ impl FromTree for Miniscript { .map_err(From::from) .map_err(Error::Parse)?; - if matches!(parent_name, "multi" | "sortedmulti" | "multi_a" | "sortedmulti_a") { - continue; - } if parent_name == "thresh" && node.is_first_child() { continue; } @@ -1051,102 +1085,101 @@ 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(Self::expr_raw_pkh) - .map_err(Error::Parse), - "pk" => node - .verify_terminal_parent("pk", "public key") - .map(Self::pk) - .map_err(Error::Parse), - "pkh" => node - .verify_terminal_parent("pkh", "public key") - .map(Self::pkh) - .map_err(Error::Parse), - "pk_k" => node - .verify_terminal_parent("pk_k", "public key") - .map(Self::pk_k) - .map_err(Error::Parse), - "pk_h" => node - .verify_terminal_parent("pk_h", "public key") - .map(Self::pk_h) - .map_err(Error::Parse), - "after" => node.verify_after().map(Self::after).map_err(Error::Parse), - "older" => node.verify_older().map(Self::older).map_err(Error::Parse), - "sha256" => node - .verify_terminal_parent("sha256", "hash") - .map(Self::sha256) - .map_err(Error::Parse), - "hash256" => node - .verify_terminal_parent("hash256", "hash") - .map(Self::hash256) - .map_err(Error::Parse), - "ripemd160" => node - .verify_terminal_parent("ripemd160", "hash") - .map(Self::ripemd160) - .map_err(Error::Parse), - "hash160" => node - .verify_terminal_parent("hash160", "hash") - .map(Self::hash160) - .map_err(Error::Parse), - "1" => { - node.verify_n_children("1", 0..=0) - .map_err(From::from) - .map_err(Error::Parse)?; - Ok(Self::TRUE) - } - "0" => { - node.verify_n_children("0", 0..=0) - .map_err(From::from) - .map_err(Error::Parse)?; - Ok(Self::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(Self::FALSE)) - }), - "andor" => { - node.verify_n_children("andor", 3..=3) - .map_err(From::from) - .map_err(Error::Parse)?; - Self::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(Self::from_ast), - "multi" => node - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) - .map(Terminal::Multi) - .and_then(Self::from_ast), - "sortedmulti" => node - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) - .map(Terminal::SortedMulti) - .and_then(Self::from_ast), - "multi_a" => node - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) - .map(Terminal::MultiA) - .and_then(Self::from_ast), - "sortedmulti_a" => node - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) - .map(Terminal::SortedMultiA) - .and_then(Self::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(Self::expr_raw_pkh) + .map_err(Error::Parse), + "pk" => node + .verify_key_expression_parent("pk") + .map(Self::pk) + .map_err(Error::Parse), + "pkh" => node + .verify_key_expression_parent("pkh") + .map(Self::pkh) + .map_err(Error::Parse), + "pk_k" => node + .verify_key_expression_parent("pk_k") + .map(Self::pk_k) + .map_err(Error::Parse), + "pk_h" => node + .verify_key_expression_parent("pk_h") + .map(Self::pk_h) + .map_err(Error::Parse), + "after" => node.verify_after().map(Self::after).map_err(Error::Parse), + "older" => node.verify_older().map(Self::older).map_err(Error::Parse), + "sha256" => node + .verify_terminal_parent("sha256", "hash") + .map(Self::sha256) + .map_err(Error::Parse), + "hash256" => node + .verify_terminal_parent("hash256", "hash") + .map(Self::hash256) + .map_err(Error::Parse), + "ripemd160" => node + .verify_terminal_parent("ripemd160", "hash") + .map(Self::ripemd160) + .map_err(Error::Parse), + "hash160" => node + .verify_terminal_parent("hash160", "hash") + .map(Self::hash160) + .map_err(Error::Parse), + "1" => { + node.verify_n_children("1", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Self::TRUE) + } + "0" => { + node.verify_n_children("0", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Self::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(Self::FALSE)) + }), + "andor" => { + node.verify_n_children("andor", 3..=3) + .map_err(From::from) + .map_err(Error::Parse)?; + Self::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(Self::from_ast), + "multi" => node + .verify_threshold(|sub| sub.verify_key_expression().map_err(Error::Parse)) + .map(Terminal::Multi) + .and_then(Self::from_ast), + "sortedmulti" => node + .verify_threshold(|sub| sub.verify_key_expression().map_err(Error::Parse)) + .map(Terminal::SortedMulti) + .and_then(Self::from_ast), + "multi_a" => node + .verify_threshold(|sub| sub.verify_key_expression().map_err(Error::Parse)) + .map(Terminal::MultiA) + .and_then(Self::from_ast), + "sortedmulti_a" => node + .verify_threshold(|sub| sub.verify_key_expression().map_err(Error::Parse)) + .map(Terminal::SortedMultiA) + .and_then(Self::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 { @@ -1180,13 +1213,7 @@ impl FromTree for Miniscript { assert_eq!(stack.len(), 1); let ret = stack.pop().unwrap(); - // Iterate through every node to check global validity. It is definitely not sufficient - // to check only the root, since this will fail to notice illegal xonly keys at the - // leaves. But probably checking every single node is overkill. This may be worth - // optimizing. - for node in ret.pre_order_iter() { - Ctx::check_global_validity(node)?; - } + ret.check_global_validity_recursive()?; Ok(Arc::try_unwrap(ret).unwrap()) } } @@ -2065,6 +2092,15 @@ mod tests { Tapscript::decode_consensus(&script.into_script()).unwrap_err(); } + #[test] + fn parse_subtree_under_key_fragment() { + // Parsing a subtree must not look at the fragments above it. + let tree = expression::Tree::from_str("pk(and_v(v:pk(A),pk(B)))").unwrap(); + let subtree = tree.root().first_child().unwrap(); + let ms = Miniscript::::from_tree(subtree).unwrap(); + assert_eq!(ms.to_string(), "and_v(v:pk(A),pk(B))"); + } + #[test] fn test_or_d_exec_stack_count_fix() { // Test for the or_d dissat_data.max_exec_stack_count fix diff --git a/src/plan.rs b/src/plan.rs index 178f365d3..97f7573ce 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -323,6 +323,9 @@ impl Plan { /// This will only add the metadata for items required to complete this plan. For example, if /// there are multiple keys present in the descriptor, only the few used by this plan will be /// added to the PSBT. + /// + /// `musig()` keys get no `tap_key_origins` entry: they have no single key source, and PSBT + /// MuSig2 fields (BIP373) are not supported yet. pub fn update_psbt_input(&self, input: &mut psbt::Input) { if let Descriptor::Tr(tr) = &self.descriptor { enum SpendType { @@ -363,8 +366,12 @@ impl Plan { _ => {}, } - for path in pk.full_derivation_paths() { - data.key_origins.insert(raw_pk, (pk.master_fingerprint(), path)); + // A `musig()` key would get a fabricated `00000000` origin here. + if !is_musig(pk) { + for path in pk.full_derivation_paths() { + data.key_origins + .insert(raw_pk, (pk.master_fingerprint(), path)); + } } } Placeholder::SchnorrSigPkHash(_, tap_leaf_hash, _) => { @@ -395,7 +402,7 @@ impl Plan { } } }) - .or_insert_with(|| (vec![], key_source)); + .or_insert_with(|| (leaf_hash.into_iter().collect(), key_source)); } if let (Some(tap_script), Some(control_block)) = (data.tap_script, data.control_block) { input @@ -555,16 +562,27 @@ fn is_key_direct_child_of( false } +// A `musig()` key has no single key source: its `master_fingerprint()` is `00000000` for every +// aggregate. `Assets` identify keys by key source, so they must never match it, or all +// aggregates would look like the same key. +fn is_musig(pk: &DefiniteDescriptorKey) -> bool { + matches!(pk.as_descriptor_public_key(), DescriptorPublicKey::Musig(_)) +} + impl Assets { pub(crate) fn has_ecdsa_key(&self, pk: &DefiniteDescriptorKey) -> bool { - self.keys.iter().any(|(keysource, can_sign)| { - can_sign.ecdsa - && pk.master_fingerprint() == keysource.0 - && is_key_direct_child_of(pk, &keysource.1) - }) + !is_musig(pk) + && self.keys.iter().any(|(keysource, can_sign)| { + can_sign.ecdsa + && pk.master_fingerprint() == keysource.0 + && is_key_direct_child_of(pk, &keysource.1) + }) } pub(crate) fn has_taproot_internal_key(&self, pk: &DefiniteDescriptorKey) -> Option { + if is_musig(pk) { + return None; + } self.keys.iter().find_map(|(keysource, can_sign)| { if !can_sign.taproot.key_spend || pk.master_fingerprint() != keysource.0 @@ -582,6 +600,9 @@ impl Assets { pk: &DefiniteDescriptorKey, tap_leaf_hash: &TapLeafHash, ) -> Option { + if is_musig(pk) { + return None; + } self.keys.iter().find_map(|(keysource, can_sign)| { if !can_sign.taproot.script_spend.is_available(tap_leaf_hash) || pk.master_fingerprint() != keysource.0 @@ -648,7 +669,11 @@ impl AssetProvider for Assets { impl FromIterator for Assets { fn from_iter>(iter: I) -> Self { let mut keys = BTreeSet::new(); - for pk in iter { + // `musig()` keys have no key source to record; see `is_musig`. + for pk in iter + .into_iter() + .filter(|pk| !matches!(pk, DescriptorPublicKey::Musig(_))) + { for deriv_path in pk.full_derivation_paths() { keys.insert(((pk.master_fingerprint(), deriv_path), CanSign::default())); } @@ -1135,6 +1160,7 @@ mod test { assert!(psbt_input.tap_merkle_root.is_some(), "Merkle root is missing"); assert_eq!(psbt_input.tap_key_origins.len(), 1, "Unexpected number of tap_key_origins"); assert_eq!(psbt_input.tap_scripts.len(), 0, "Unexpected number of tap_scripts"); + assert_leaf_hashes(&psbt_input, &[]); let mut psbt_input = bitcoin::psbt::Input::default(); let assets = Assets::new().add(first_branch); @@ -1146,6 +1172,7 @@ mod test { assert!(psbt_input.tap_merkle_root.is_some(), "Merkle root is missing"); assert_eq!(psbt_input.tap_key_origins.len(), 1, "Unexpected number of tap_key_origins"); assert_eq!(psbt_input.tap_scripts.len(), 1, "Unexpected number of tap_scripts"); + assert_leaf_hashes(&psbt_input, &[tap_script_leaf_hash(&psbt_input)]); let mut psbt_input = bitcoin::psbt::Input::default(); let assets = Assets::new().add(second_branch); @@ -1156,6 +1183,252 @@ mod test { assert!(psbt_input.tap_merkle_root.is_some(), "Merkle root is missing"); assert_eq!(psbt_input.tap_key_origins.len(), 2, "Unexpected number of tap_key_origins"); assert_eq!(psbt_input.tap_scripts.len(), 1, "Unexpected number of tap_scripts"); + assert_leaf_hashes(&psbt_input, &[tap_script_leaf_hash(&psbt_input)]); + } + + // Leaf hash of the only script in the input's `tap_scripts`. + fn tap_script_leaf_hash(psbt_input: &bitcoin::psbt::Input) -> TapLeafHash { + assert_eq!(psbt_input.tap_scripts.len(), 1, "Expected exactly one tap script"); + let (script, leaf_version) = psbt_input.tap_scripts.values().next().unwrap(); + TapLeafHash::from_script(script, *leaf_version) + } + + // Checks that every `tap_key_origins` entry lists exactly `expected` leaf hashes. + fn assert_leaf_hashes(psbt_input: &bitcoin::psbt::Input, expected: &[TapLeafHash]) { + for (pk, (leaf_hashes, _)) in &psbt_input.tap_key_origins { + assert_eq!(leaf_hashes, expected, "Unexpected leaf hashes for {}", pk); + } + } + + #[test] + fn test_plan_update_psbt_tr_leaf_hashes_accumulate() { + // keys taken from: https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki#Specifications + let root_xpub = Xpub::from_str("xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8").unwrap(); + let fingerprint = root_xpub.fingerprint(); + let xpub = format!("[{}/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ", fingerprint); + // The same key appears in two leaves, each satisfiable only with a different timelock, + // so the two plans below pick different leaves. + let desc = format!( + "tr({}/0/0,{{and_v(v:pk({}/0/1),after(100)),and_v(v:pk({}/0/1),older(10))}})", + xpub, xpub, xpub + ); + let desc = Descriptor::::from_str(&desc).unwrap(); + let key = DescriptorPublicKey::from_str(&format!("{}/0/1", xpub)).unwrap(); + + let plans = [ + Assets::new() + .add(key.clone()) + .after(absolute::LockTime::from_height(100).unwrap()), + Assets::new() + .add(key) + .older(relative::LockTime::from_height(10)), + ] + .iter() + .map(|assets| desc.clone().into_plan(assets).unwrap()) + .collect::>(); + + let mut expected = plans + .iter() + .map(|plan| { + let mut psbt_input = bitcoin::psbt::Input::default(); + plan.update_psbt_input(&mut psbt_input); + tap_script_leaf_hash(&psbt_input) + }) + .collect::>(); + assert_ne!(expected[0], expected[1], "The plans should use different leaves"); + + // Updating one input from both plans, twice each, lists both leaf hashes once. + let mut psbt_input = bitcoin::psbt::Input::default(); + for plan in plans.iter().chain(plans.iter()) { + plan.update_psbt_input(&mut psbt_input); + } + assert_eq!(psbt_input.tap_key_origins.len(), 1, "Unexpected number of tap_key_origins"); + let (leaf_hashes, _) = psbt_input.tap_key_origins.values().next().unwrap(); + let mut leaf_hashes = leaf_hashes.clone(); + leaf_hashes.sort(); + expected.sort(); + assert_eq!(leaf_hashes, expected); + } + + #[test] + fn test_plan_update_psbt_tr_script_path_signs() { + use bitcoin::{transaction, Amount, OutPoint, Psbt, Transaction, TxIn, TxOut}; + + // The signer only signs the leaves listed in a key's `tap_key_origins` entry, so a + // script-path plan applied to a fresh input must list its leaf there. + let secp = Secp256k1::new(); + // Root key of https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki#Specifications + let root_xpriv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap(); + let account_path = bip32::DerivationPath::from_str("m/86'/0'/0'").unwrap(); + let account_xpub = + Xpub::from_priv(&secp, &root_xpriv.derive_priv(&secp, &account_path).unwrap()); + let xpub = format!("[{}/86'/0'/0']{}", root_xpriv.fingerprint(&secp), account_xpub); + let desc = format!("tr({}/0/0,pk({}/0/1))", xpub, xpub); + let desc = Descriptor::::from_str(&desc).unwrap(); + let key = DescriptorPublicKey::from_str(&format!("{}/0/1", xpub)).unwrap(); + + let script_pubkey = desc.script_pubkey(); + let tx = Transaction { + version: transaction::Version::TWO, + lock_time: absolute::LockTime::ZERO, + input: vec![TxIn { previous_output: OutPoint::null(), ..Default::default() }], + output: vec![TxOut { + value: Amount::from_sat(1_000), + script_pubkey: script_pubkey.clone(), + }], + }; + let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); + psbt.inputs[0].witness_utxo = Some(TxOut { value: Amount::from_sat(2_000), script_pubkey }); + + desc.into_plan(&Assets::new().add(key)) + .unwrap() + .update_psbt_input(&mut psbt.inputs[0]); + psbt.sign(&root_xpriv, &secp).unwrap(); + assert_eq!(psbt.inputs[0].tap_script_sigs.len(), 1, "Script path was not signed"); + } + + // Keys for the `musig()` tests below. `XPUB_*` are the BIP390 test vector xpubs. + const MUSIG_K1: &str = "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"; + const MUSIG_K2: &str = "03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659"; + const MUSIG_K3: &str = "023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66"; + const PLAIN_KEY: &str = "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c"; + const XPUB_A: &str = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"; + const XPUB_B: &str = "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"; + + fn dummy_taproot_sig() -> bitcoin::taproot::Signature { + bitcoin::taproot::Signature { + signature: bitcoin::secp256k1::schnorr::Signature::from_slice(&[1; 64]).unwrap(), + sighash_type: bitcoin::TapSighashType::Default, + } + } + + #[test] + fn test_plan_assets_never_match_musig() { + // A `musig()` key reports fingerprint 00000000 and its aggregate-level path, so even + // assets holding exactly those key sources must not match it. + let mut assets = Assets::new(); + for path in ["m", "m/0"] { + assets.keys.insert(( + (bip32::Fingerprint::default(), bip32::DerivationPath::from_str(path).unwrap()), + CanSign::default(), + )); + } + let leaf_hash = TapLeafHash::from_script(&ScriptBuf::new(), LeafVersion::TapScript); + for key in [ + format!("musig({},{})", MUSIG_K1, MUSIG_K2), + format!("musig({},{})/0/7", XPUB_A, XPUB_B), + ] { + let pk = DefiniteDescriptorKey::from_str(&key).unwrap(); + assert_eq!(pk.master_fingerprint(), bip32::Fingerprint::default()); + assert!(!assets.has_ecdsa_key(&pk), "{}", key); + assert_eq!(assets.has_taproot_internal_key(&pk), None, "{}", key); + assert_eq!(assets.has_taproot_script_key(&pk, &leaf_hash), None, "{}", key); + } + for desc in [ + format!("tr(musig({},{}))", MUSIG_K1, MUSIG_K2), + format!("tr(musig({},{})/0/7)", XPUB_A, XPUB_B), + format!("tr({},pk(musig({},{})))", MUSIG_K3, MUSIG_K1, MUSIG_K2), + ] { + let desc = Descriptor::::from_str(&desc).unwrap(); + assert!(desc.clone().into_plan(&assets).is_err(), "{}", desc); + } + + // Assets built from one aggregate don't record it, so they can't plan another. + let ours = DescriptorPublicKey::from_str(&format!("musig({},{})", MUSIG_K1, MUSIG_K2)); + let assets = Assets::new().add(ours.unwrap()); + assert!(assets.keys.is_empty()); + let theirs = format!("tr(musig({},{}))", MUSIG_K2, MUSIG_K3); + let theirs = Descriptor::::from_str(&theirs).unwrap(); + assert!(theirs.into_plan(&assets).is_err()); + } + + #[test] + fn test_plan_musig_fallbacks() { + let assets = Assets::new().add(DescriptorPublicKey::from_str(PLAIN_KEY).unwrap()); + let plain_key = DefiniteDescriptorKey::from_str(PLAIN_KEY) + .unwrap() + .to_x_only_pubkey(); + + // Key path: a musig() key in a leaf doesn't get in the way. + let desc = format!("tr({},pk(musig({},{})))", PLAIN_KEY, MUSIG_K1, MUSIG_K2); + let desc = Descriptor::::from_str(&desc).unwrap(); + let mut psbt_input = bitcoin::psbt::Input::default(); + desc.into_plan(&assets) + .unwrap() + .update_psbt_input(&mut psbt_input); + assert_eq!(psbt_input.tap_internal_key, Some(plain_key)); + assert!(psbt_input.tap_scripts.is_empty(), "Unexpected tap_scripts"); + assert_leaf_hashes(&psbt_input, &[]); + + // Script path: a musig() internal key falls back to the plain key's leaf. + let desc = format!("tr(musig({},{}),pk({}))", MUSIG_K1, MUSIG_K2, PLAIN_KEY); + let desc = Descriptor::::from_str(&desc).unwrap(); + let mut psbt_input = bitcoin::psbt::Input::default(); + desc.into_plan(&assets) + .unwrap() + .update_psbt_input(&mut psbt_input); + assert!(psbt_input.tap_internal_key.is_none(), "Internal key is present"); + assert!(psbt_input.tap_merkle_root.is_some(), "Merkle root is missing"); + assert_eq!(psbt_input.tap_scripts.len(), 1, "Unexpected number of tap_scripts"); + assert_eq!( + psbt_input.tap_key_origins.keys().collect::>(), + vec![&plain_key], + "Only the plain key has an origin" + ); + assert_leaf_hashes(&psbt_input, &[tap_script_leaf_hash(&psbt_input)]); + } + + #[test] + fn test_plan_update_psbt_musig_no_origins() { + use bitcoin::{transaction, Psbt, Transaction, TxIn}; + + // Providers other than `Assets` can legitimately hold a signature for a musig() key, + // e.g. once a MuSig2 session has finished. Such plans still update the PSBT, but + // without a fabricated origin for the aggregate. + let musig = format!("musig({},{})", MUSIG_K2, MUSIG_K3); + + // Script path, planned from a signature map. + let desc = format!("tr({},pk({}))", MUSIG_K1, musig); + let desc = Descriptor::::from_str(&desc).unwrap(); + let leaf_hash = match desc { + Descriptor::Tr(ref tr) => tr.spend_info().leaves().next().unwrap().leaf_hash(), + _ => unreachable!(), + }; + let mut sigs = BTreeMap::new(); + sigs.insert( + (DefiniteDescriptorKey::from_str(&musig).unwrap(), leaf_hash), + dummy_taproot_sig(), + ); + let mut psbt_input = bitcoin::psbt::Input::default(); + desc.into_plan(&sigs) + .unwrap() + .update_psbt_input(&mut psbt_input); + assert_eq!(psbt_input.tap_scripts.len(), 1, "Unexpected number of tap_scripts"); + assert!(psbt_input.tap_key_origins.is_empty(), "Unexpected tap_key_origins"); + + // Key path, planned from a PSBT that holds the final MuSig2 signature. + let desc = format!("tr({})", musig); + let desc = Descriptor::::from_str(&desc).unwrap(); + let aggregate = match desc { + Descriptor::Tr(ref tr) => tr.internal_key().to_x_only_pubkey(), + _ => unreachable!(), + }; + let tx = Transaction { + version: transaction::Version::TWO, + lock_time: absolute::LockTime::ZERO, + input: vec![TxIn::default()], + output: vec![], + }; + let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); + psbt.inputs[0].tap_internal_key = Some(aggregate); + psbt.inputs[0].tap_key_sig = Some(dummy_taproot_sig()); + let satisfier = crate::psbt::PsbtInputSatisfier::new(&psbt, 0); + let mut psbt_input = bitcoin::psbt::Input::default(); + desc.into_plan(&satisfier) + .unwrap() + .update_psbt_input(&mut psbt_input); + assert_eq!(psbt_input.tap_internal_key, Some(aggregate)); + assert!(psbt_input.tap_key_origins.is_empty(), "Unexpected tap_key_origins"); } #[test] diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index d04319d9b..b8ff54f39 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -423,14 +423,14 @@ impl Policy { match policy { Self::Trivial => None, policy => { - let leaf_compilations: Vec<_> = policy + let leaf_compilations = policy .enumerate_policy_tree(PositiveF64::ONE) .into_iter() .filter(|x| x.1 != Arc::new(Self::Unsatisfiable)) .map(|(prob, pol)| { - (prob, compiler::best_compilation(pol.as_ref()).unwrap()) + Ok((prob, compiler::best_compilation(pol.as_ref())?)) }) - .collect(); + .collect::, CompilerError>>()?; if !leaf_compilations.is_empty() { let tap_tree = with_huffman_tree::(leaf_compilations)?; @@ -1022,8 +1022,25 @@ impl expression::FromTree for Policy { .map_err(From::from) .map_err(Error::Parse)?; + // Collect the nodes in pre-order, skipping the descendants of `pk`: it parses its key + // argument itself, and the key may be a function-style expression such as + // `musig(A,B)/0/*` with descendants of its own. + let mut iter = root.pre_order_iter(); + let mut nodes = Vec::with_capacity(iter.len()); + while let Some(node) = iter.next() { + nodes.push(node); + // A child of `or` may carry a probability, as in `9@pk(A)`. + let name = node + .name() + .rsplit_once('@') + .map_or(node.name(), |(_, name)| name); + if name == "pk" && node.n_children() == 1 { + iter.skip_descendants(); + } + } + let mut stack = Vec::<(NonZeroU32, _)>::with_capacity(128); - for node in root.pre_order_iter().rev() { + for node in nodes.into_iter().rev() { let allow_prob; // Before doing anything else, check if this is the inner value of a terminal. // In that case, just skip the node. Conveniently, there are no combinators @@ -1076,7 +1093,7 @@ impl expression::FromTree for Policy { Ok(Self::Trivial) } "pk" => node - .verify_terminal_parent("pk", "public key") + .verify_key_expression_parent("pk") .map(Policy::Key) .map_err(Error::Parse), "after" => node.verify_after().map_err(Error::Parse).map(Policy::After), @@ -1276,6 +1293,7 @@ mod compiler_tests { use super::*; use crate::policy::Concrete; + use crate::{BareCtx, Legacy, Segwitv0}; fn nested_equal_odds_policy(n_keys: usize) -> Policy { assert!(n_keys >= 2); @@ -1357,6 +1375,61 @@ mod compiler_tests { )); } + #[test] + fn invalid_context_key_error() { + // A key that the context doesn't allow in pk_k() must not compile to pk_h() instead. + let comp_key = bitcoin::PublicKey::from_str( + "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c", + ) + .unwrap(); + let uncomp_key = bitcoin::PublicKey::from_str( + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\ + 483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + ) + .unwrap(); + let x_only_key = bitcoin::key::XOnlyPublicKey::from_str( + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ) + .unwrap(); + + let x_only = Policy::Key(x_only_key); + assert!(matches!( + x_only.compile_to_descriptor::(DescriptorCtx::Sh), + Err(Error::CompilerError(CompilerError::LimitsExceeded)) + )); + assert!(matches!( + x_only.compile_to_descriptor::(DescriptorCtx::Wsh), + Err(Error::CompilerError(CompilerError::LimitsExceeded)) + )); + assert!(matches!( + x_only.compile_to_descriptor::(DescriptorCtx::Bare), + Err(Error::CompilerError(CompilerError::LimitsExceeded)) + )); + + let uncomp = Policy::Key(uncomp_key); + uncomp + .compile_to_descriptor::(DescriptorCtx::Sh) + .unwrap(); + assert!(matches!( + uncomp.compile_to_descriptor::(DescriptorCtx::Wsh), + Err(Error::CompilerError(CompilerError::LimitsExceeded)) + )); + assert!(matches!( + uncomp.compile_to_descriptor::(DescriptorCtx::ShWsh), + Err(Error::CompilerError(CompilerError::LimitsExceeded)) + )); + + // In Taproot, the uncompressed key can't be a leaf key either. + let tap: Policy = + Policy::from_str(&format!("or(99@pk({}),1@pk({}))", comp_key, uncomp_key)).unwrap(); + assert!(matches!(tap.compile_tr(None), Err(CompilerError::LimitsExceeded))); + assert!(matches!(tap.compile_tr_native(None, 128), Err(CompilerError::LimitsExceeded))); + assert!(matches!( + tap.compile_tr_private_experimental(None), + Err(Error::CompilerError(CompilerError::LimitsExceeded)) + )); + } + #[test] fn huffman_depth_error() { // Nesting produces geometrically decreasing Tapleaf probabilities without exceeding the diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 5d80a6ff8..c1aefa85c 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -278,6 +278,8 @@ mod tests { concrete_policy_rtt("or(1@pk(X),1@pk(Y))"); concrete_policy_rtt("or(99@pk(X),1@pk(Y))"); concrete_policy_rtt("and(pk(X),or(99@pk(Y),1@older(12960)))"); + concrete_policy_rtt("or(99@pk(musig(X,Y)/0/*),1@pk(Z))"); + concrete_policy_rtt("thresh(2,pk(musig(X,Y)),pk(Z),pk(W))"); semantic_policy_rtt("pk()"); semantic_policy_rtt("(pk(X) ∨ pk(Y))"); diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index cdbf2b48b..e214150b7 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -466,13 +466,30 @@ impl<'a> MathParser<'a> { } } - fn consume_while(&mut self, pred: impl Fn(char) -> bool) -> &'a str { + fn consume_while(&mut self, mut pred: impl FnMut(char) -> bool) -> &'a str { let start = self.pos(); while matches!(self.peek(), Some(c) if pred(c)) { self.next(); } &self.s[start..self.pos()] } + + /// Consumes a terminal's argument, up to the `)` that closes the terminal. + /// + /// A key may be a function-style expression such as `musig(A,B)/0/*`, so the argument + /// can contain balanced parentheses. + fn consume_arg(&mut self) -> &'a str { + let mut depth = 0usize; + self.consume_while(|c| { + match c { + '(' => depth += 1, + ')' if depth == 0 => return false, + ')' => depth -= 1, + _ => {} + } + true + }) + } } #[derive(Copy, Clone, PartialEq, Eq)] @@ -649,7 +666,7 @@ impl<'a> MathParser<'a> { } self.next(); - let arg = self.consume_while(|c| c != ')').trim(); + let arg = self.consume_arg().trim(); self.expect(')')?; Ok(match name { @@ -704,8 +721,20 @@ impl expression::FromTree for Policy { .map_err(From::from) .map_err(Error::Parse)?; + // Collect the nodes in pre-order, skipping the descendants of `pk`: it parses its key + // argument itself, and the key may be a function-style expression such as + // `musig(A,B)/0/*` with descendants of its own. + let mut iter = root.pre_order_iter(); + let mut nodes = Vec::with_capacity(iter.len()); + while let Some(node) = iter.next() { + nodes.push(node); + if node.name() == "pk" && node.n_children() == 1 { + iter.skip_descendants(); + } + } + let mut stack = Vec::with_capacity(128); - for node in root.pre_order_iter().rev() { + for node in nodes.into_iter().rev() { // Before doing anything else, check if this is the inner value of a terminal. // In that case, just skip the node. Conveniently, there are no combinators // in policy that have a single child that these might be confused with (we @@ -733,7 +762,7 @@ impl expression::FromTree for Policy { Ok(Self::Trivial) } "pk" => node - .verify_terminal_parent("pk", "public key") + .verify_key_expression_parent("pk") .map(Policy::Key) .map_err(Error::Parse), "after" => node.verify_after().map_err(Error::Parse).map(Policy::After), @@ -1211,6 +1240,22 @@ mod tests { roundtrip("thresh(2,pk(A),pk(B),pk(C))"); } + #[test] + fn parse_key_expressions() { + // A key may be a function-style expression, in both notations. + let musig = Policy::Key("musig(A,B)/0/*".to_owned()); + assert_eq!(StringPolicy::from_str("pk(musig(A,B)/0/*)").unwrap(), musig); + let or = + Policy::Thresh(Threshold::or(Arc::new(musig), Arc::new(Policy::Key("C".to_owned())))); + assert_eq!(StringPolicy::from_str("(pk(musig(A,B)/0/*) ∨ pk(C))").unwrap(), or); + assert_eq!(StringPolicy::from_str("or(pk(musig(A,B)/0/*),pk(C))").unwrap(), or); + + roundtrip("pk(musig(A,B)/0/*)"); + roundtrip("(pk(musig(A,B)) ∨ pk(C))"); + roundtrip("#{pk(musig(A,B)/<0;1>/*), pk(C), pk(D)} = 2"); + roundtrip("thresh(2,pk(musig(A,B)),pk(C),pk(D))"); + } + #[test] fn semantic_analysis() { let policy = StringPolicy::from_str("pk()").unwrap(); diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index fd959dbe8..bc04f139b 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -536,6 +536,9 @@ pub trait PsbtExt { /// The `descriptor` **must not have any wildcards** in it /// otherwise an error will be returned however it can (and should) have extended keys in it. /// + /// Returns an error if the descriptor contains a `musig()` key; PSBT MuSig2 fields (BIP373) + /// are not supported yet. + /// /// [`update_with_descriptor_unchecked`]: PsbtInputExt::update_with_descriptor_unchecked /// [segwit bug]: https://bitcoinhackers.org/@lukedashjr/104287698361196952 fn update_input_with_descriptor( @@ -552,6 +555,9 @@ pub trait PsbtExt { /// The `descriptor` **must not have any wildcards** in it /// otherwise an error will be returned however it can (and should) have extended keys in it. /// + /// Returns an error if the descriptor contains a `musig()` key; PSBT MuSig2 fields (BIP373) + /// are not supported yet. + /// /// [`update_with_descriptor_unchecked`]: PsbtOutputExt::update_with_descriptor_unchecked fn update_output_with_descriptor( &mut self, @@ -753,8 +759,11 @@ impl PsbtExt for Psbt { }; let (_, spk_check_passed) = - update_item_with_descriptor_helper(input, desc, Some(&expected_spk)) - .map_err(UtxoUpdateError::DerivationError)?; + update_item_with_descriptor_helper(input, desc, Some(&expected_spk)).map_err(|e| { + match e { + DescriptorUpdateError::MusigUnsupported => UtxoUpdateError::MusigUnsupported, + } + })?; if !spk_check_passed { return Err(UtxoUpdateError::MismatchedScriptPubkey); @@ -780,8 +789,11 @@ impl PsbtExt for Psbt { .ok_or(OutputUpdateError::MissingTxOut)?; let (_, spk_check_passed) = - update_item_with_descriptor_helper(output, desc, Some(&txout.script_pubkey)) - .map_err(OutputUpdateError::DerivationError)?; + update_item_with_descriptor_helper(output, desc, Some(&txout.script_pubkey)).map_err( + |e| match e { + DescriptorUpdateError::MusigUnsupported => OutputUpdateError::MusigUnsupported, + }, + )?; if !spk_check_passed { return Err(OutputUpdateError::MismatchedScriptPubkey); @@ -883,9 +895,9 @@ impl PsbtExt for Psbt { pub trait PsbtInputExt { /// Given the descriptor for a utxo being spent populate the PSBT input's fields so it can be signed. /// - /// If the descriptor contains wildcards or otherwise cannot be transformed into a concrete - /// descriptor an error will be returned. The descriptor *can* (and should) have extended keys in - /// it so PSBT fields like `bip32_derivation` and `tap_key_origins` can be populated. + /// The descriptor *can* (and should) have extended keys in it so PSBT fields like + /// `bip32_derivation` and `tap_key_origins` can be populated. Returns an error if the descriptor + /// contains a `musig()` key; PSBT MuSig2 fields (BIP373) are not supported yet. /// /// Every key in the descriptor gets an entry in `bip32_derivation` (or `tap_key_origins` for /// Taproot descriptors). Keys without origin information are recorded as their own master @@ -905,14 +917,14 @@ pub trait PsbtInputExt { fn update_with_descriptor_unchecked( &mut self, descriptor: &Descriptor, - ) -> Result, descriptor::NonDefiniteKeyError>; + ) -> Result, DescriptorUpdateError>; } impl PsbtInputExt for psbt::Input { fn update_with_descriptor_unchecked( &mut self, descriptor: &Descriptor, - ) -> Result, descriptor::NonDefiniteKeyError> { + ) -> Result, DescriptorUpdateError> { let (derived, _) = update_item_with_descriptor_helper(self, descriptor, None)?; Ok(derived) } @@ -922,9 +934,9 @@ impl PsbtInputExt for psbt::Input { pub trait PsbtOutputExt { /// Given the descriptor of a PSBT output populate the relevant metadata /// - /// If the descriptor contains wildcards or otherwise cannot be transformed into a concrete - /// descriptor an error will be returned. The descriptor *can* (and should) have extended keys in - /// it so PSBT fields like `bip32_derivation` and `tap_key_origins` can be populated. + /// The descriptor *can* (and should) have extended keys in it so PSBT fields like + /// `bip32_derivation` and `tap_key_origins` can be populated. Returns an error if the descriptor + /// contains a `musig()` key; PSBT MuSig2 fields (BIP373) are not supported yet. /// /// Every key in the descriptor gets an entry in `bip32_derivation` (or `tap_key_origins` for /// Taproot descriptors). Keys without origin information are recorded as their own master @@ -944,14 +956,14 @@ pub trait PsbtOutputExt { fn update_with_descriptor_unchecked( &mut self, descriptor: &Descriptor, - ) -> Result, descriptor::NonDefiniteKeyError>; + ) -> Result, DescriptorUpdateError>; } impl PsbtOutputExt for psbt::Output { fn update_with_descriptor_unchecked( &mut self, descriptor: &Descriptor, - ) -> Result, descriptor::NonDefiniteKeyError> { + ) -> Result, DescriptorUpdateError> { let (derived, _) = update_item_with_descriptor_helper(self, descriptor, None)?; Ok(derived) } @@ -966,9 +978,14 @@ struct KeySourceLookUp( impl Translator for KeySourceLookUp { type TargetPk = bitcoin::PublicKey; - type Error = core::convert::Infallible; + type Error = DescriptorUpdateError; fn pk(&mut self, xpk: &DefiniteDescriptorKey) -> Result { + // A `musig()` key has no single key source, and the PSBT MuSig2 fields (BIP373) that + // would describe it are not supported yet. + if let DescriptorPublicKey::Musig(_) = xpk.as_descriptor_public_key() { + return Err(DescriptorUpdateError::MusigUnsupported); + } let derived = xpk.derive_public_key(&self.1); self.0.insert( derived.to_public_key().inner, @@ -1068,19 +1085,18 @@ fn update_item_with_descriptor_helper( check_script: Option<&Script>, // We return an extra boolean here to indicate an error with `check_script`. We do this // because the error is "morally" a UtxoUpdateError::MismatchedScriptPubkey, but some - // callers expect a `descriptor::NonDefiniteKeyError`, which cannot be produced from a + // callers expect a `DescriptorUpdateError`, which cannot be produced from a // `UtxoUpdateError`, and those callers can't get this error anyway because they pass // `None` for `check_script`. -) -> Result<(Descriptor, bool), descriptor::NonDefiniteKeyError> { +) -> Result<(Descriptor, bool), DescriptorUpdateError> { let secp = Secp256k1::verification_only(); // 1. Derive the descriptor, recording each key derivation in a map from xpubs - // the keysource used to derive the key. + // the keysource used to derive the key. This fails before any PSBT field is written. let mut bip32_derivation = KeySourceLookUp(BTreeMap::new(), secp); let derived = descriptor .translate_pk(&mut bip32_derivation) - .map_err(crate::TranslateErr::into_outer_err) - .expect("No Context errors while translating"); + .map_err(|e| e.expect_translator_err("No Context errors while translating"))?; // 2. If we have a specific scriptpubkey we are targeting, bail out. if let Some(check_script) = check_script { @@ -1157,6 +1173,37 @@ fn update_item_with_descriptor_helper( Ok((derived, true)) } +/// Error returned when a descriptor cannot be used to update PSBT fields. +/// +/// Return error type for [`PsbtInputExt::update_with_descriptor_unchecked`] and +/// [`PsbtOutputExt::update_with_descriptor_unchecked`]. +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum DescriptorUpdateError { + /// The descriptor contains a `musig()` key. PSBT MuSig2 fields (BIP373) are not supported + /// yet. + MusigUnsupported, +} + +impl fmt::Display for DescriptorUpdateError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MusigUnsupported => f.write_str( + "the descriptor contains a musig() key; PSBT MuSig2 fields (BIP373) are not supported yet", + ), + } + } +} + +#[cfg(feature = "std")] +impl error::Error for DescriptorUpdateError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Self::MusigUnsupported => None, + } + } +} + /// Return error type for [`PsbtExt::update_input_with_descriptor`] #[derive(Debug, PartialEq, Eq, Clone)] pub enum UtxoUpdateError { @@ -1171,6 +1218,9 @@ pub enum UtxoUpdateError { /// The PSBT's `witness_utxo` and/or `non_witness_utxo` had a script_pubkey that did not match /// the descriptor MismatchedScriptPubkey, + /// The descriptor contains a `musig()` key. PSBT MuSig2 fields (BIP373) are not supported + /// yet. + MusigUnsupported, } impl fmt::Display for UtxoUpdateError { @@ -1190,6 +1240,7 @@ impl fmt::Display for UtxoUpdateError { Self::MismatchedScriptPubkey => { write!(f, "The input's witness_utxo and/or non_witness_utxo had a script pubkey that didn't match the descriptor") } + Self::MusigUnsupported => DescriptorUpdateError::MusigUnsupported.fmt(f), } } } @@ -1200,7 +1251,11 @@ impl error::Error for UtxoUpdateError { use self::UtxoUpdateError::*; match self { - IndexOutOfBounds(_, _) | MissingInputUtxo | UtxoCheck | MismatchedScriptPubkey => None, + IndexOutOfBounds(_, _) + | MissingInputUtxo + | UtxoCheck + | MismatchedScriptPubkey + | MusigUnsupported => None, DerivationError(e) => Some(e), } } @@ -1217,6 +1272,9 @@ pub enum OutputUpdateError { DerivationError(descriptor::NonDefiniteKeyError), /// The output's script_pubkey did not match the descriptor MismatchedScriptPubkey, + /// The descriptor contains a `musig()` key. PSBT MuSig2 fields (BIP373) are not supported + /// yet. + MusigUnsupported, } impl fmt::Display for OutputUpdateError { @@ -1232,6 +1290,7 @@ impl fmt::Display for OutputUpdateError { Self::MismatchedScriptPubkey => { write!(f, "The output's script pubkey didn't match the descriptor") } + Self::MusigUnsupported => DescriptorUpdateError::MusigUnsupported.fmt(f), } } } @@ -1242,7 +1301,9 @@ impl error::Error for OutputUpdateError { use self::OutputUpdateError::*; match self { - IndexOutOfBounds(_, _) | MissingTxOut | MismatchedScriptPubkey => None, + IndexOutOfBounds(_, _) | MissingTxOut | MismatchedScriptPubkey | MusigUnsupported => { + None + } DerivationError(e) => Some(e), } } @@ -1633,6 +1694,67 @@ mod tests { ); } + #[test] + fn test_update_with_musig_descriptor() { + let a = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"; + let b = "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"; + let k1 = "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"; + let k2 = "03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659"; + let k3 = "023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66"; + let k4 = "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c"; + + for desc in [ + format!("tr(musig({},{})/0/0)", a, b), + format!("tr(musig({}/0/0,{}/0/0))", a, b), + format!("tr({},pk(musig({},{})))", k1, k2, k3), + format!("tr({},multi_a(1,{},musig({},{})))", k1, k2, k3, k4), + ] { + let desc = Descriptor::::from_str(&desc).unwrap(); + + // The PSBT matches the descriptor, so only the musig() key can make the updates fail. + let script_pubkey = desc.script_pubkey(); + let tx = bitcoin::Transaction { + version: transaction::Version::TWO, + lock_time: absolute::LockTime::ZERO, + input: vec![TxIn::default()], + output: vec![TxOut { + value: Amount::from_sat(1_000), + script_pubkey: script_pubkey.clone(), + }], + }; + let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); + psbt.inputs[0].witness_utxo = + Some(TxOut { value: Amount::from_sat(2_000), script_pubkey }); + let original = psbt.clone(); + + assert_eq!( + psbt.update_input_with_descriptor(0, &desc), + Err(UtxoUpdateError::MusigUnsupported) + ); + assert_eq!( + psbt.update_output_with_descriptor(0, &desc), + Err(OutputUpdateError::MusigUnsupported) + ); + assert_eq!( + psbt.inputs[0].update_with_descriptor_unchecked(&desc), + Err(DescriptorUpdateError::MusigUnsupported) + ); + assert_eq!( + psbt.outputs[0].update_with_descriptor_unchecked(&desc), + Err(DescriptorUpdateError::MusigUnsupported) + ); + assert_eq!(psbt, original, "a failed update must not modify the PSBT"); + } + + let message = DescriptorUpdateError::MusigUnsupported.to_string(); + assert_eq!( + message, + "the descriptor contains a musig() key; PSBT MuSig2 fields (BIP373) are not supported yet" + ); + assert_eq!(UtxoUpdateError::MusigUnsupported.to_string(), message); + assert_eq!(OutputUpdateError::MusigUnsupported.to_string(), message); + } + #[test] fn test_update_output_checks() { let desc = "tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/0)";