diff --git a/src/descriptors.rs b/src/descriptors.rs index 7e5718da..132e2f64 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -30,6 +30,7 @@ fn wu_to_vb(vb: usize) -> usize { pub enum DescCreationError { InsaneTimelock(u32), InvalidKey(Box), + DuplicateKey(Box), Miniscript(miniscript::Error), IncompatibleDesc, DerivedKeyParsing, @@ -46,6 +47,9 @@ impl std::fmt::Display for DescCreationError { key ) } + Self::DuplicateKey(key) => { + write!(f, "Duplicate key '{}'.", key) + } Self::Miniscript(e) => write!(f, "Miniscript error: '{}'.", e), Self::IncompatibleDesc => write!(f, "Descriptor is not compatible."), Self::DerivedKeyParsing => write!(f, "Parsing derived key,"), @@ -347,6 +351,19 @@ impl MultipathDescriptor { return Err(DescCreationError::InvalidKey((**key).clone().into())); } + // Check for key duplicates. They are invalid in (nonmalleable) miniscripts. + let owner_xpub = match owner_key { + descriptor::DescriptorPublicKey::MultiXPub(ref multi_xpub) => multi_xpub.xkey, + _ => unreachable!("Just checked it was a multixpub above"), + }; + let heir_xpub = match heir_key { + descriptor::DescriptorPublicKey::MultiXPub(ref multi_xpub) => multi_xpub.xkey, + _ => unreachable!("Just checked it was a multixpub above"), + }; + if owner_xpub == heir_xpub { + return Err(DescCreationError::DuplicateKey(owner_key.into())); + } + let owner_pk = Miniscript::from_ast(Terminal::Check(sync::Arc::from( Miniscript::from_ast(Terminal::PkK(owner_key)).expect("pk_k is a valid Miniscript"), ))) @@ -455,6 +472,20 @@ impl MultipathDescriptor { assert!(csv.is_height_locked()); csv.to_consensus_u32() } + + /// Get the maximum size in WU of a satisfaction for this descriptor. + pub fn max_sat_weight(&self) -> usize { + self.multi_desc + .max_satisfaction_weight() + .expect("Cannot fail for P2WSH") + } + + /// Get the maximum size in virtual bytes of the whole input in a transaction spending + /// a coin with this Script. + pub fn spender_input_size(&self) -> usize { + // txid + vout + nSequence + empty scriptSig + witness + 32 + 4 + 4 + 1 + wu_to_vb(self.max_sat_weight()) + } } impl InheritanceDescriptor { @@ -514,20 +545,6 @@ impl InheritanceDescriptor { ), ) } - - /// Get the maximum size in WU of a satisfaction for this descriptor. - pub fn max_sat_weight(&self) -> usize { - self.0 - .max_satisfaction_weight() - .expect("Cannot fail for P2WSH") - } - - /// Get the maximum size in virtual bytes of the whole input in a transaction spending - /// a coin with this Script. - pub fn spender_input_size(&self) -> usize { - // txid + vout + nSequence + empty scriptSig + witness - 32 + 4 + 4 + 1 + wu_to_vb(self.max_sat_weight()) - } } /// Map of a raw public key to the xpub used to derive it and its derivation path @@ -613,6 +630,17 @@ mod tests { MultipathDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err(); let heir_key = descriptor::DescriptorPublicKey::from_str("xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/<0;1;2>/*'").unwrap(); MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); + + // You can't pass duplicate keys, even if they are encoded differently. + let owner_key = descriptor::DescriptorPublicKey::from_str("xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(); + let heir_key = descriptor::DescriptorPublicKey::from_str("xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(); + MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); + let owner_key = descriptor::DescriptorPublicKey::from_str("[00aabb44]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(); + let heir_key = descriptor::DescriptorPublicKey::from_str("xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(); + MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); + let owner_key = descriptor::DescriptorPublicKey::from_str("[00aabb44]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(); + let heir_key = descriptor::DescriptorPublicKey::from_str("[11223344/2/98]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(); + MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); } #[test] @@ -651,15 +679,8 @@ mod tests { let receive_desc = desc.receive_descriptor(); let change_desc = desc.change_descriptor(); - // Receive and change are the same descriptor. - assert_eq!(receive_desc.max_sat_weight(), change_desc.max_sat_weight()); - assert_eq!( - receive_desc.spender_input_size(), - change_desc.spender_input_size() - ); - // Derived or not the expected maximum satisfaction size should be the same for - // the same descriptor. + // the change and receive descriptor. assert_eq!( receive_desc.derive(999.into(), &secp).max_sat_weight(), change_desc.derive(999.into(), &secp).max_sat_weight() @@ -681,7 +702,7 @@ mod tests { .map(|item| bitcoin::VarInt(stack.len() as u64).len() + item.len()) .sum::(); assert_eq!( - receive_desc.spender_input_size(), + desc.spender_input_size(), 32 + 4 + 1 + 4 + wu_to_vb(witness_size), ); }