[bugfix] descriptors: fix parsing of descriptor with 1-of-N multisig

This commit is contained in:
Antoine Poinsot 2023-03-27 16:56:12 +02:00
parent 1a13b7a6f8
commit 9394be645c
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
2 changed files with 43 additions and 22 deletions

View File

@ -201,6 +201,17 @@ impl PathInfo {
}
}
/// Add another available key to this `PathInfo`. Note this doesn't change the threshold.
pub fn with_added_key(mut self, key: descriptor::DescriptorPublicKey) -> Self {
match self {
Self::Single(curr_key) => Self::Multi(1, vec![curr_key, key]),
Self::Multi(_, ref mut keys) => {
keys.push(key);
self
}
}
}
/// Get the required number of keys for spending through this path, and the set of keys
/// that can be used to provide a signature for this path.
pub fn thresh_origins(&self) -> (usize, HashSet<(bip32::Fingerprint, bip32::DerivationPath)>) {
@ -409,40 +420,47 @@ impl LianaPolicy {
.expect("Lifting can't fail on a Miniscript")
.normalized();
// For now we only accept a single timelocked recovery path.
// The policy must always be "1 of N spending paths" with at least an always-available
// primary path with at least one key, and at least one timelocked recovery path with at
// least one key.
let subs = match policy {
SemanticPolicy::Threshold(1, subs) => Some(subs),
_ => None,
}
.ok_or(LianaPolicyError::IncompatibleDesc)?;
if subs.len() != 2 {
return Err(LianaPolicyError::IncompatibleDesc);
}
// Fetch the two spending paths' semantic policies. The primary path is identified as the
// only one that isn't timelocked.
let (prim_path_sub, reco_path_sub) =
subs.into_iter()
.fold((None, None), |(mut prim_sub, mut reco_sub), sub| {
if is_single_key_or_multisig(&sub) {
prim_sub = Some(sub);
let (mut primary_path, mut recovery_path) = (None::<PathInfo>, None);
for sub in subs {
// This is a (multi)key check. It must be the primary path.
if is_single_key_or_multisig(&sub) {
// We only support a single primary path. But it may be that the primary path is a
// 1-of-N multisig. In this case the policy is normalized from `thresh(1, thresh(1,
// pk(A), pk(B)), thresh(2, older(42), pk(C)))` to `thresh(1, pk(A), pk(B),
// thresh(2, older(42), pk(C)))`.
if let Some(prim_path) = primary_path {
if let SemanticPolicy::Key(key) = sub {
primary_path = Some(prim_path.with_added_key(key));
} else {
reco_sub = Some(sub);
return Err(LianaPolicyError::IncompatibleDesc);
}
(prim_sub, reco_sub)
});
let (prim_path_sub, reco_path_sub) = (
prim_path_sub.ok_or(LianaPolicyError::IncompatibleDesc)?,
reco_path_sub.ok_or(LianaPolicyError::IncompatibleDesc)?,
);
// Now parse information about each spending path.
let primary_path = PathInfo::from_primary_path(prim_path_sub)?;
let recovery_path = PathInfo::from_recovery_path(reco_path_sub)?;
} else {
primary_path = Some(PathInfo::from_primary_path(sub)?);
}
} else {
// If it's not a simple (multi)key check, it must be the timelocked recovery path.
// For now, we only support a single recovery path.
if recovery_path.is_some() {
return Err(LianaPolicyError::IncompatibleDesc);
}
recovery_path = Some(PathInfo::from_recovery_path(sub)?);
}
}
Ok(LianaPolicy {
primary_path,
recovery_path,
primary_path: primary_path.ok_or(LianaPolicyError::IncompatibleDesc)?,
recovery_path: recovery_path.ok_or(LianaPolicyError::IncompatibleDesc)?,
})
}

View File

@ -509,6 +509,9 @@ mod tests {
let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*").unwrap());
let timelock = 52560;
LianaPolicy::new(owner_key, heir_key, timelock).unwrap_err();
// A 1-of-N multisig as primary path.
LianaDescriptor::from_str("wsh(or_d(multi(1,[573fb35b/48'/1'/0'/2']tpubDFKp9T7WAYDcENSjoifkrpq1gMDF47KGJcJrpxzX23Qor8wuGbrEVs9utNq1MDS8E2WXJSBk1qoPQLpwyokW7DiUNPwFuxQkL7owNkLAb9W/<0;1>/*,[573fb35b/48'/1'/1'/2']tpubDFGezyzuHJPhdP3jHGW7v7Hwes4Hihqv5W2yyCmRY9VZJCRchETvxrMC8uECeJZdxQ14V4iD4DecoArkUSDwj8ogYE9WEv4MNZr12thNHCs/<0;1>/*),and_v(v:multi(2,[573fb35b/48'/1'/2'/2']tpubDDwxQauiaU964vPzt5Vd7jnDHEUtp2Vc34PaWpEXg5TQ3bRccxnc1MKKh88Hi7xiMeZo9Tm6fBcq4UGXqnDtGUniJLjqAD8SjQ8Eci3aSR7/<0;1>/*,[573fb35b/48'/1'/3'/2']tpubDE37XAVB5CQ1x85md3BQ5uHCoMwT5fgT8X13zzCUQ3x5o2jskYxKjj7Qcxt1Jpj4QB8tqspn2dooPCekRuQDYrDHov7J1ueUNu2wcvgRDxr/<0;1>/*),older(1000))))#qjx6ycpc").unwrap();
}
#[test]