From 10fd701d3c316a67c65b050f2e8aa50bf0bf11c6 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Fri, 10 Oct 2025 12:16:53 +1100 Subject: [PATCH 1/2] fix(keymap): backport use of correct `derivation_path` for keys with origin - Fixes the implementation of `GetKey` for `KeyRequest::Bip32` when there's `Xpriv` has an origin, and it matches with the given `KeyRequest::Bip32` derivation_path. It should strip the matching part, to correctly derive the key at the correct child number. - Updates the existing test to the correct and expected behavior. --- src/descriptor/key_map.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/descriptor/key_map.rs b/src/descriptor/key_map.rs index 00c797ee1..04a9a97b0 100644 --- a/src/descriptor/key_map.rs +++ b/src/descriptor/key_map.rs @@ -73,8 +73,11 @@ impl GetKey for DescriptorSecretKey { return Ok(Some(key)); } - if descriptor_xkey.matches(key_source, secp).is_some() { - let (_, derivation_path) = key_source; + if let Some(matched_path) = descriptor_xkey.matches(key_source, secp) { + let (_, full_path) = key_source; + + let derivation_path = &full_path[matched_path.len()..]; + return Ok(Some( descriptor_xkey .xkey @@ -265,11 +268,16 @@ mod tests { _ => unreachable!(), }; - let path = DerivationPath::from_str("84'/1'/0'/0").unwrap(); - let expected_pk = xpriv.xkey.derive_priv(&secp, &path).unwrap().to_priv(); + let expected_deriv_path: DerivationPath = (&[ChildNumber::Normal { index: 0 }][..]).into(); + let expected_pk = xpriv + .xkey + .derive_priv(&secp, &expected_deriv_path) + .unwrap() + .to_priv(); + let derivation_path = DerivationPath::from_str("84'/1'/0'/0").unwrap(); let (fp, _) = xpriv.origin.unwrap(); - let key_request = KeyRequest::Bip32((fp, path)); + let key_request = KeyRequest::Bip32((fp, derivation_path)); let pk = keymap_wrapper .get_key(key_request, &secp) From d6a94e319492569897af6bc14570261a8834c372 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Fri, 31 Oct 2025 12:06:49 -0300 Subject: [PATCH 2/2] fix(get_key): for `Xprv` with key origin info - fixes the implementation of `GetKey` for `Xprv` with `KeyRequest::Bip32` and a key_origin information. - adds a new test for a scenario where no wildcard is used, and an specific derivation index is used. --- src/descriptor/key_map.rs | 65 ++++++++++++++++++++++++++++++++------- src/policy/concrete.rs | 1 + 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/descriptor/key_map.rs b/src/descriptor/key_map.rs index 04a9a97b0..dd8dc7690 100644 --- a/src/descriptor/key_map.rs +++ b/src/descriptor/key_map.rs @@ -73,18 +73,28 @@ impl GetKey for DescriptorSecretKey { return Ok(Some(key)); } - if let Some(matched_path) = descriptor_xkey.matches(key_source, secp) { + if descriptor_xkey.matches(key_source, secp).is_some() { let (_, full_path) = key_source; - let derivation_path = &full_path[matched_path.len()..]; - - return Ok(Some( - descriptor_xkey - .xkey - .derive_priv(secp, &derivation_path) - .map_err(GetKeyError::Bip32)? - .to_priv(), - )); + match &descriptor_xkey.origin { + Some((_, origin_path)) => { + let derivation_path = &full_path[origin_path.len()..]; + return Ok(Some( + descriptor_xkey + .xkey + .derive_priv(secp, &derivation_path)? + .to_priv(), + )); + } + None => { + return Ok(Some( + descriptor_xkey + .xkey + .derive_priv(secp, &full_path)? + .to_priv(), + )) + } + }; } Ok(None) @@ -255,7 +265,7 @@ mod tests { } #[test] - fn get_key_xpriv_with_key_origin() { + fn get_key_xpriv_with_key_origin_and_wildcard() { let secp = Secp256k1::new(); let descriptor_str = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; @@ -287,6 +297,39 @@ mod tests { assert_eq!(pk, expected_pk); } + #[test] + fn get_key_xpriv_with_key_origin_and_no_wildcard() { + let secp = Secp256k1::new(); + + let descriptor_str = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)"; + let (_descriptor_pk, keymap) = Descriptor::parse_descriptor(&secp, descriptor_str).unwrap(); + let keymap_wrapper = KeyMapWrapper::from(keymap); + + let descriptor_sk = DescriptorSecretKey::from_str("[d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0").unwrap(); + let xpriv = match descriptor_sk { + DescriptorSecretKey::XPrv(descriptor_xkey) => descriptor_xkey, + _ => unreachable!(), + }; + + let expected_deriv_path: DerivationPath = (&[ChildNumber::Normal { index: 0 }][..]).into(); + let expected_pk = xpriv + .xkey + .derive_priv(&secp, &expected_deriv_path) + .unwrap() + .to_priv(); + + let derivation_path = DerivationPath::from_str("84'/1'/0'/0").unwrap(); + let (fp, _) = xpriv.origin.unwrap(); + let key_request = KeyRequest::Bip32((fp, derivation_path)); + + let pk = keymap_wrapper + .get_key(key_request, &secp) + .expect("get_key should not fail") + .expect("get_key should return a `PrivateKey`"); + + assert_eq!(pk, expected_pk); + } + #[test] fn get_key_keymap_no_match() { let secp = Secp256k1::new(); diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index 6c4321326..75d31a400 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -350,6 +350,7 @@ impl Policy { /// It is **not recommended** to use policy as a stable identifier for a miniscript. You should /// use the policy compiler once, and then use the miniscript output as a stable identifier. See /// the compiler document in [`doc/compiler.md`] for more details. + #[allow(rustdoc::broken_intra_doc_links)] #[cfg(feature = "compiler")] pub fn compile_to_descriptor( &self,