Skip to content

Commit 3f77767

Browse files
committed
test(key_map): improve Xprv key origin tests
- backports introduced #872 table-driven test cases for `get_key_xpriv_with_key_origin` with named fields - drop redundant key parsing, and build requests from explicit key sources Assisted-by: Claude Opus 4.8
1 parent 122105d commit 3f77767

1 file changed

Lines changed: 91 additions & 61 deletions

File tree

src/descriptor/key_map.rs

Lines changed: 91 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ impl GetKey for DescriptorSecretKey {
119119
mod tests {
120120
use core::str::FromStr;
121121

122-
use bitcoin::bip32::{ChildNumber, DerivationPath, IntoDerivationPath, Xpriv};
122+
use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, IntoDerivationPath, Xpriv};
123123

124124
use super::*;
125125
use crate::Descriptor;
@@ -260,66 +260,6 @@ mod tests {
260260
assert_eq!(got_sk, want_sk)
261261
}
262262

263-
#[test]
264-
fn get_key_xpriv_with_key_origin_and_wildcard() {
265-
let secp = Secp256k1::new();
266-
267-
let descriptor_str = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)";
268-
let (_descriptor_pk, keymap) = Descriptor::parse_descriptor(&secp, descriptor_str).unwrap();
269-
let keymap_wrapper = KeyMapWrapper::from(keymap);
270-
271-
let descriptor_sk = DescriptorSecretKey::from_str("[d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*").unwrap();
272-
let xpriv = match descriptor_sk {
273-
DescriptorSecretKey::XPrv(descriptor_xkey) => descriptor_xkey,
274-
_ => unreachable!(),
275-
};
276-
277-
let path = DerivationPath::from_str("84'/1'/0'/0").unwrap();
278-
let expected_pk = xpriv.xkey.derive_priv(&secp, &path).unwrap().to_priv();
279-
280-
let (fp, _) = xpriv.origin.unwrap();
281-
let key_request = KeyRequest::Bip32((fp, path));
282-
283-
let pk = keymap_wrapper
284-
.get_key(key_request, &secp)
285-
.expect("get_key should not fail")
286-
.expect("get_key should return a `PrivateKey`");
287-
288-
assert_eq!(pk, expected_pk);
289-
}
290-
291-
#[test]
292-
fn get_key_xpriv_with_key_origin_and_no_wildcard() {
293-
let secp = Secp256k1::new();
294-
295-
let descriptor_str = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)";
296-
let (_descriptor_pk, keymap) = Descriptor::parse_descriptor(&secp, descriptor_str).unwrap();
297-
298-
let descriptor_sk = DescriptorSecretKey::from_str("[d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0").unwrap();
299-
let xpriv = match descriptor_sk {
300-
DescriptorSecretKey::XPrv(descriptor_xkey) => descriptor_xkey,
301-
_ => unreachable!(),
302-
};
303-
304-
let expected_deriv_path: DerivationPath = (&[ChildNumber::Normal { index: 0 }][..]).into();
305-
let expected_pk = xpriv
306-
.xkey
307-
.derive_priv(&secp, &expected_deriv_path)
308-
.unwrap()
309-
.to_priv();
310-
311-
let derivation_path = DerivationPath::from_str("84'/1'/0'/0").unwrap();
312-
let (fp, _) = xpriv.origin.unwrap();
313-
let key_request = KeyRequest::Bip32((fp, derivation_path));
314-
315-
let pk = keymap
316-
.get_key(key_request, &secp)
317-
.expect("get_key should not fail")
318-
.expect("get_key should return a `PrivateKey`");
319-
320-
assert_eq!(pk, expected_pk);
321-
}
322-
323263
#[test]
324264
fn get_key_keymap_no_match() {
325265
let secp = Secp256k1::new();
@@ -399,4 +339,94 @@ mod tests {
399339
let result = keymap_wrapper.get_key(request_x, &secp).unwrap();
400340
assert!(result.is_none(), "Should return None even on error");
401341
}
342+
343+
#[test]
344+
fn get_key_xpriv_with_key_origin() {
345+
// `get_key` should match the request against the key origin, strip the origin prefix
346+
// from the requested path, and derive the rest from the extended key.
347+
struct TestCase {
348+
/// Scenario description.
349+
name: &'static str,
350+
/// The descriptor under test.
351+
descriptor: &'static str,
352+
/// Requested key source: `(master fingerprint, path from the master)`.
353+
key_request: (&'static str, &'static str),
354+
/// Expected steps from the extended key (requested path minus the key origin).
355+
exp_derivation_path: &'static str,
356+
}
357+
358+
let cases = [
359+
TestCase {
360+
name: "bare wildcard",
361+
descriptor: "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)",
362+
key_request: ("d34db33f", "84'/1'/0'/0"),
363+
exp_derivation_path: "0",
364+
},
365+
TestCase {
366+
name: "single fixed step",
367+
descriptor: "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)",
368+
key_request: ("d34db33f", "84'/1'/0'/0"),
369+
exp_derivation_path: "0",
370+
},
371+
TestCase {
372+
name: "fixed step then wildcard",
373+
descriptor: "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0/*)",
374+
key_request: ("d34db33f", "84'/1'/0'/0/5"),
375+
exp_derivation_path: "0/5",
376+
},
377+
];
378+
379+
let secp = Secp256k1::new();
380+
let xpriv = Xpriv::from_str("tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS").unwrap();
381+
for test_case in &cases {
382+
let exp_derivation_path =
383+
DerivationPath::from_str(test_case.exp_derivation_path).unwrap();
384+
let exp_private_key = xpriv
385+
.derive_priv(&secp, &exp_derivation_path)
386+
.unwrap()
387+
.to_priv();
388+
389+
let (_, keymap) = Descriptor::parse_descriptor(&secp, test_case.descriptor).unwrap();
390+
let keymap_wrapper = KeyMapWrapper::from(keymap);
391+
392+
let (fingerprint, derivation_path) = test_case.key_request;
393+
let key_request = KeyRequest::Bip32((
394+
Fingerprint::from_str(fingerprint).unwrap(),
395+
DerivationPath::from_str(derivation_path).unwrap(),
396+
));
397+
398+
let private_key = keymap_wrapper
399+
.get_key(key_request, &secp)
400+
.expect("get_key SHOULD NOT fail")
401+
.expect("get_key SHOULD get a `PrivateKey`");
402+
403+
assert_eq!(private_key, exp_private_key, "{}", test_case.name);
404+
}
405+
}
406+
407+
#[test]
408+
fn get_key_xpriv_with_key_origin_and_non_matching_path() {
409+
let secp = Secp256k1::new();
410+
411+
// descriptor with a fixed derivation index of `0`.
412+
let descriptor = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)";
413+
let (_, keymap) = Descriptor::parse_descriptor(&secp, descriptor).unwrap();
414+
let keymap_wrapper = KeyMapWrapper::from(keymap);
415+
416+
// the key_request has the correct `key_origin`, but the requested derivation index (`5`) does not match the
417+
// descriptor's fixed derivation index (`0`), so the descriptor does not own this key.
418+
let key_request = KeyRequest::Bip32((
419+
Fingerprint::from_str("d34db33f").unwrap(),
420+
DerivationPath::from_str("84'/1'/0'/5").unwrap(),
421+
));
422+
423+
let private_key = keymap_wrapper
424+
.get_key(key_request, &secp)
425+
.expect("get_key SHOULD NOT fail!");
426+
427+
assert!(
428+
private_key.is_none(),
429+
"SHOULD get NO private key when the requested path does not match the descriptor"
430+
);
431+
}
402432
}

0 commit comments

Comments
 (0)