From 8d495579e1f0b2bd329d3d299e707491c02a31bd Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Fri, 19 Jul 2024 13:00:21 +0200 Subject: [PATCH] lianad: DesKeyChecker.check() => check the change multipath is not hardened --- src/descriptors/analysis.rs | 70 +++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/descriptors/analysis.rs b/src/descriptors/analysis.rs index 2420672f..8ea2a358 100644 --- a/src/descriptors/analysis.rs +++ b/src/descriptors/analysis.rs @@ -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() + )); + } +}