Merge #1212: DesKeyChecker.check() => check the change multipath is not hardened

8d495579e1f0b2bd329d3d299e707491c02a31bd lianad: DesKeyChecker.check() => check the change multipath is not hardened (pythcoiner)

Pull request description:

  This PR add a check to verify that the change descriptor is not hardened.
  without this check it allow import of invalid (not  derivable change) multipath like <0;1'> into a LianaDescriptor.

  closes #1183

ACKs for top commit:
  jp1ac4:
    ACK 8d495579e1f0b2bd329d3d299e707491c02a31bd.
  darosior:
    ACK 8d495579e1f0b2bd329d3d299e707491c02a31bd

Tree-SHA512: f003fa7b819fa03f84126794b30371419208f1ab4f9e36df2f640dd1c1d20564db7bf1abb9897942e5c43f12e7547f01f07dabf2e8ad1df2e1a3218616883444
This commit is contained in:
Antoine Poinsot 2024-08-05 11:54:28 +02:00
commit 1f8d17e414
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304

View File

@ -116,11 +116,10 @@ impl DescKeyChecker {
// without origin entirely.
if let Some(ref origin) = xpub.origin {
let der_paths = xpub.derivation_paths.paths();
let first_der_path = der_paths.first().expect("Cannot be empty");
// We also rule out xpubs with hardened derivation steps (non-normalized xpubs).
let valid = xpub.wildcard == descriptor::Wildcard::Unhardened
&& der_paths.len() == 2
&& first_der_path.into_iter().all(|step| step.is_normal());
&& der_paths.iter().flatten().all(|step| step.is_normal());
if valid {
return Ok(origin.0);
}
@ -776,3 +775,70 @@ impl PartialSpendInfo {
&self.recovery_paths
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn valid_key() {
let xpub_str =
"[8c3ffb6e/48'/1'/0'/2']tpubDEMt3bpQMa99W81K9h8f2FJH1C81eSd6bbSkBP8tcqQHAfSKvuGp2fz6xiVpfShzT9sKPx7DVBphChjxvNd15WcbsCca5oVz1AcUTWHxkdS/<0;1>/*";
let key = descriptor::DescriptorPublicKey::from_str(xpub_str).unwrap();
let mut checker = DescKeyChecker::new();
assert!(checker.check(&key).is_ok());
}
#[test]
fn invalid_key() {
// Multipath of size 3
let xpub_str =
"[8c3ffb6e/48'/1'/0'/2']tpubDEMt3bpQMa99W81K9h8f2FJH1C81eSd6bbSkBP8tcqQHAfSKvuGp2fz6xiVpfShzT9sKPx7DVBphChjxvNd15WcbsCca5oVz1AcUTWHxkdS/<0;1;2>/*";
let key = descriptor::DescriptorPublicKey::from_str(xpub_str).unwrap();
let mut checker = DescKeyChecker::new();
assert!(matches!(
checker.check(&key),
Err(LianaPolicyError::InvalidKey(k)) if k == key.into()
));
// No multipath
let xpub_str =
"[8c3ffb6e/48'/1'/0'/2']tpubDEMt3bpQMa99W81K9h8f2FJH1C81eSd6bbSkBP8tcqQHAfSKvuGp2fz6xiVpfShzT9sKPx7DVBphChjxvNd15WcbsCca5oVz1AcUTWHxkdS/0/*";
let key = descriptor::DescriptorPublicKey::from_str(xpub_str).unwrap();
let mut checker = DescKeyChecker::new();
assert!(matches!(
checker.check(&key),
Err(LianaPolicyError::InvalidKey(k)) if k == key.into()
));
// Hardened receive path
let xpub_str =
"[8c3ffb6e/48'/1'/0'/2']tpubDEMt3bpQMa99W81K9h8f2FJH1C81eSd6bbSkBP8tcqQHAfSKvuGp2fz6xiVpfShzT9sKPx7DVBphChjxvNd15WcbsCca5oVz1AcUTWHxkdS/<0';1>/*";
let key = descriptor::DescriptorPublicKey::from_str(xpub_str).unwrap();
let mut checker = DescKeyChecker::new();
assert!(matches!(
checker.check(&key),
Err(LianaPolicyError::InvalidKey(k)) if k == key.into()
));
// Hardened change path
let xpub_str =
"[8c3ffb6e/48'/1'/0'/2']tpubDEMt3bpQMa99W81K9h8f2FJH1C81eSd6bbSkBP8tcqQHAfSKvuGp2fz6xiVpfShzT9sKPx7DVBphChjxvNd15WcbsCca5oVz1AcUTWHxkdS/<0;1'>/*";
let key = descriptor::DescriptorPublicKey::from_str(xpub_str).unwrap();
let mut checker = DescKeyChecker::new();
assert!(matches!(
checker.check(&key),
Err(LianaPolicyError::InvalidKey(k)) if k == key.into()
));
// Hardened wildcard
let xpub_str =
"[8c3ffb6e/48'/1'/0'/2']tpubDEMt3bpQMa99W81K9h8f2FJH1C81eSd6bbSkBP8tcqQHAfSKvuGp2fz6xiVpfShzT9sKPx7DVBphChjxvNd15WcbsCca5oVz1AcUTWHxkdS/<0;1>/*'";
let key = descriptor::DescriptorPublicKey::from_str(xpub_str).unwrap();
let mut checker = DescKeyChecker::new();
assert!(matches!(
checker.check(&key),
Err(LianaPolicyError::InvalidKey(k)) if k == key.into()
));
}
}