From f4d184f6f7fb748bff17f8a846616c589a432342 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Wed, 19 Jul 2023 18:50:34 +0200 Subject: [PATCH] descriptor: refuse non-normalized xpubs We wouldn't be able to derive them. --- src/descriptors/analysis.rs | 9 ++++++--- src/descriptors/mod.rs | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/descriptors/analysis.rs b/src/descriptors/analysis.rs index 4f4dad2b..fa0117d4 100644 --- a/src/descriptors/analysis.rs +++ b/src/descriptors/analysis.rs @@ -92,17 +92,20 @@ impl DescKeyChecker { self.keys_set.insert(xpub.xkey); // Then perform the contextless checks. let der_paths = xpub.derivation_paths.paths(); + let first_der_path = der_paths.get(0).expect("Cannot be empty"); // Rust-miniscript enforces BIP389 which states that all paths must have the same len. - let len = der_paths.get(0).expect("Cannot be empty").len(); + let len = first_der_path.len(); // Technically the xpub could be for the master xpub and not have an origin. But it's - // no unlikely (and easily fixable) while users shooting themselves in the foot by + // unlikely (and easily fixable) while users shooting themselves in the foot by // forgetting to provide the origin is so likely that it's worth ruling out xpubs // without origin entirely. + // We also rule out xpubs with hardened derivation steps (non-normalized xpubs). let valid = xpub.origin.is_some() && xpub.wildcard == descriptor::Wildcard::Unhardened && der_paths.len() == 2 && der_paths[0][len - 1] == 0.into() - && der_paths[1][len - 1] == 1.into(); + && der_paths[1][len - 1] == 1.into() + && first_der_path.into_iter().all(|step| step.is_normal()); if valid { return Ok(()); } diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index e61e4950..16c5ccea 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -577,6 +577,12 @@ mod tests { let timelock = 52560; LianaPolicy::new(owner_key, [(timelock, heir_key)].iter().cloned().collect()).unwrap_err(); + // One of the xpub isn't normalized. + let owner_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[aabbccdd]xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/42'/<0;1>/*").unwrap()); + let timelock = 52560; + LianaPolicy::new(owner_key, [(timelock, heir_key)].iter().cloned().collect()).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(); }