Skip to content

Commit 20ae466

Browse files
committed
fix(key_map): derivation_path for Xprv with key origin info
- fixes the implementation of `GetKey` for `Xprv` with `KeyRequest::Bip32`, when the the key_origin information is available. - introduces a new test for a scenario with no wildcard used, a specific derivation index is used instead.
1 parent a29d089 commit 20ae466

1 file changed

Lines changed: 54 additions & 16 deletions

File tree

src/descriptor/key_map.rs

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,21 +133,27 @@ impl GetKey for DescriptorSecretKey {
133133
return Ok(Some(key));
134134
}
135135

136-
if let Some(matched_path) = descriptor_xkey.matches(key_source, secp) {
137-
let (_, full_path) = key_source;
138-
139-
let derivation_path = &full_path[matched_path.len()..];
140-
141-
return Ok(Some(
142-
descriptor_xkey
143-
.xkey
144-
.derive_priv(secp, &derivation_path)
145-
.map_err(GetKeyError::Bip32)?
146-
.to_priv(),
147-
));
148-
}
149-
150-
Ok(None)
136+
// A successful `matches()` already guarantees the requested key source's fingerprint equals our origin
137+
// (or, when there is no origin, the xkey's own) fingerprint.
138+
//
139+
// `xkey` is anchored at the origin, but the request path is master-relative, either:
140+
// - origin: strip the origin prefix and use the remaining suffix.
141+
// - no origin: use the full request path.
142+
let (_, full_path) = key_source;
143+
let derivation_path = match descriptor_xkey.matches(key_source, secp) {
144+
Some(_) => match &descriptor_xkey.origin {
145+
Some((_, origin_path)) => &full_path[origin_path.len()..],
146+
None => full_path.as_ref(),
147+
},
148+
None => return Ok(None),
149+
};
150+
151+
Ok(Some(
152+
descriptor_xkey
153+
.xkey
154+
.derive_priv(secp, &derivation_path)?
155+
.to_priv(),
156+
))
151157
}
152158
(Self::XPrv(_), KeyRequest::XOnlyPubkey(_)) => Err(GetKeyError::NotSupported),
153159
(desc_multi_sk @ Self::MultiXPrv(_descriptor_multi_xkey), key_request) => {
@@ -306,7 +312,7 @@ mod tests {
306312
}
307313

308314
#[test]
309-
fn get_key_xpriv_with_key_origin() {
315+
fn get_key_xpriv_with_key_origin_and_wildcard() {
310316
let secp = Secp256k1::new();
311317

312318
let descriptor_str = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)";
@@ -337,6 +343,38 @@ mod tests {
337343
assert_eq!(pk, expected_pk);
338344
}
339345

346+
#[test]
347+
fn get_key_xpriv_with_key_origin_and_no_wildcard() {
348+
let secp = Secp256k1::new();
349+
350+
let descriptor_str = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)";
351+
let (_descriptor_pk, keymap) = Descriptor::parse_descriptor(&secp, descriptor_str).unwrap();
352+
353+
let descriptor_sk = DescriptorSecretKey::from_str("[d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0").unwrap();
354+
let xpriv = match descriptor_sk {
355+
DescriptorSecretKey::XPrv(descriptor_xkey) => descriptor_xkey,
356+
_ => unreachable!(),
357+
};
358+
359+
let expected_deriv_path: DerivationPath = (&[ChildNumber::Normal { index: 0 }][..]).into();
360+
let expected_pk = xpriv
361+
.xkey
362+
.derive_priv(&secp, &expected_deriv_path)
363+
.unwrap()
364+
.to_priv();
365+
366+
let derivation_path = DerivationPath::from_str("84'/1'/0'/0").unwrap();
367+
let (fp, _) = xpriv.origin.unwrap();
368+
let key_request = KeyRequest::Bip32((fp, derivation_path));
369+
370+
let pk = keymap
371+
.get_key(key_request, &secp)
372+
.expect("get_key should not fail")
373+
.expect("get_key should return a `PrivateKey`");
374+
375+
assert_eq!(pk, expected_pk);
376+
}
377+
340378
#[test]
341379
fn get_key_keymap_no_match() {
342380
let secp = Secp256k1::new();

0 commit comments

Comments
 (0)