From 996b4332a78e81d0f9fa0dd1372edd5f7a9ceba4 Mon Sep 17 00:00:00 2001 From: Noah Joeris Date: Sat, 11 Jul 2026 14:12:15 +0300 Subject: [PATCH 1/2] fix(key_map): do not fulfill BIP32 requests outside descriptor path Remove the early `Xpriv::get_key` return in `DescriptorSecretKey::get_key`. That path matched on fingerprint only and ignored derivation path / wildcard, so a master xprv in e.g. `.../0/*` would sign sibling paths like `.../9/7`. Rely on `matches()` only. Assisted-by: Grok 4.5 --- src/descriptor/key_map.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/descriptor/key_map.rs b/src/descriptor/key_map.rs index 3dd8c94ba..16118436d 100644 --- a/src/descriptor/key_map.rs +++ b/src/descriptor/key_map.rs @@ -128,11 +128,7 @@ impl GetKey for DescriptorSecretKey { Ok(None) } } - (Self::XPrv(descriptor_xkey), ref key_request @ KeyRequest::Bip32(ref key_source)) => { - if let Some(key) = descriptor_xkey.xkey.get_key(key_request.clone(), secp)? { - return Ok(Some(key)); - } - + (Self::XPrv(descriptor_xkey), KeyRequest::Bip32(ref key_source)) => { // A successful `matches()` already guarantees the requested key source's fingerprint equals our origin // (or, when there is no origin, the xkey's own) fingerprint. // From 7043fa08f9c6adcdb6a5c8da64061b1ebb7dbd48 Mon Sep 17 00:00:00 2001 From: Noah Joeris Date: Sat, 11 Jul 2026 14:12:16 +0300 Subject: [PATCH 2/2] test(key_map): reject sibling BIP32 path for master xprv Regression: master xprv descriptors such as `.../0/*` must not fulfill `KeyRequest::Bip32` for sibling paths like `.../9/7`. Assisted-by: Grok 4.5 --- src/descriptor/key_map.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/descriptor/key_map.rs b/src/descriptor/key_map.rs index 16118436d..f4ff24867 100644 --- a/src/descriptor/key_map.rs +++ b/src/descriptor/key_map.rs @@ -475,4 +475,23 @@ mod tests { "SHOULD get NO private key when the requested path does not match the descriptor" ); } + + // Master xprv in the descriptor must not sign sibling paths (`/9/7` vs `/0/*`). + #[test] + fn get_key_bip32_rejects_sibling_with_master_xprv() { + let secp = Secp256k1::new(); + let master_xprv = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" + .parse::() + .unwrap(); + let fp = master_xprv.fingerprint(&secp); + let account_path: DerivationPath = "86h/1h/0h".parse().unwrap(); + let descriptor = format!("tr({master_xprv}/{account_path}/0/*)"); + let request_path: DerivationPath = format!("{account_path}/9/7").parse().unwrap(); + let key_request = KeyRequest::Bip32((fp, request_path)); + let (_, keymap) = Descriptor::parse_descriptor(&secp, &descriptor).unwrap(); + let result = keymap + .get_key(key_request, &secp) + .expect("key lookup should not fail"); + assert!(result.is_none(), "expected no matching key, got: {result:?}"); + } }