Merge #177: Check for key duplicates when creating a descriptor

9cc748cd2acb3338f230378b827104fee4e56318 descriptors: make the satisfaction size helpers part of the multi desc (Antoine Poinsot)
929c79a56a82566359bb9c19587d4ddf9ed2e04d descriptors: check for duplicates when creating a descriptor (Antoine Poinsot)

Pull request description:

  Fixes #149.

  Also contains a drive-by fix for the sat size API in the second commit.

ACKs for top commit:
  darosior:
    self-ACK 9cc748cd2acb3338f230378b827104fee4e56318

Tree-SHA512: 72be90ff49895546d5c83060650418243d43d48c19d781a209dc12cb6bcff0d880ffe0aa34b8f124b9fea6c0629c7e20328579e72072cad8e39a45be4b176471
This commit is contained in:
Antoine Poinsot 2022-12-08 15:00:47 +01:00
commit 4aeae407c7
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304

View File

@ -30,6 +30,7 @@ fn wu_to_vb(vb: usize) -> usize {
pub enum DescCreationError {
InsaneTimelock(u32),
InvalidKey(Box<descriptor::DescriptorPublicKey>),
DuplicateKey(Box<descriptor::DescriptorPublicKey>),
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::<usize>();
assert_eq!(
receive_desc.spender_input_size(),
desc.spender_input_size(),
32 + 4 + 1 + 4 + wu_to_vb(witness_size),
);
}