Skip to content

Commit 47d421a

Browse files
committed
Merge #861: key_map.rs: propagate "invalid key" errors from multipath keys
89b5048 test: extend unit tests to cover KeyMapWrapper error conditions (Andrew Poelstra) 78cddab key_map: handle lookup errors on multipath xprivs correctly (Andrew Poelstra) Pull request description: Since #851 we return a `NotSupported` error when attempting to request an xpriv from an x-only key. I'm not sure if this is the correct semantics, but it was deliberately written, so we'll keep it. **However** the behavior was different for multipath xprivs. There we returned `None`, i.e. "key not found/mismatch" rather than an error. This PR returns an error in both cases. ACKs for top commit: oleonardolima: ACK 89b5048 Tree-SHA512: e59b4d6877374c7e07ddf282422c42b6735f04514d2cc70610dca15dd4153cd372f9a1ccf86d3ce57ea32e8784229de0288a0e1b196405034065d92e5b134f9c
2 parents 8331d55 + 89b5048 commit 47d421a

File tree

1 file changed

+92
-6
lines changed

1 file changed

+92
-6
lines changed

src/descriptor/key_map.rs

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ impl GetKey for KeyMap {
9292
.find_map(|(_desc_pk, desc_sk)| -> Option<PrivateKey> {
9393
match desc_sk.get_key(key_request.clone(), secp) {
9494
Ok(Some(pk)) => Some(pk),
95+
// When looking up keys in a map, we eat errors on individual keys, on
96+
// the assumption that some other key in the map might not error.
9597
Ok(None) | Err(_) => None,
9698
}
9799
}))
@@ -153,12 +155,15 @@ impl GetKey for DescriptorSecretKey {
153155
(
154156
desc_multi_sk @ DescriptorSecretKey::MultiXPrv(_descriptor_multi_xkey),
155157
key_request,
156-
) => Ok(desc_multi_sk.clone().into_single_keys().iter().find_map(
157-
|desc_sk| match desc_sk.get_key(key_request.clone(), secp) {
158-
Ok(Some(pk)) => Some(pk),
159-
Ok(None) | Err(_) => None,
160-
},
161-
)),
158+
) => {
159+
for desc_sk in &desc_multi_sk.clone().into_single_keys() {
160+
// If any key is an error, then all of them will, so here we propagate errors with ?.
161+
if let Some(pk) = desc_sk.get_key(key_request.clone(), secp)? {
162+
return Ok(Some(pk));
163+
}
164+
}
165+
Ok(None)
166+
}
162167
_ => Ok(None),
163168
}
164169
}
@@ -331,4 +336,85 @@ mod tests {
331336

332337
assert_eq!(pk, expected_pk);
333338
}
339+
340+
#[test]
341+
fn get_key_keymap_no_match() {
342+
let secp = Secp256k1::new();
343+
344+
// Create a keymap with one key
345+
let descriptor_s = "wpkh(cMk8gWmj1KpjdYnAWwsEDekodMYhbyYBhG8gMtCCxucJ98JzcNij)";
346+
let (_, keymap) = Descriptor::parse_descriptor(&secp, descriptor_s).unwrap();
347+
348+
// Request a different public key that doesn't exist in the keymap
349+
let different_sk =
350+
PrivateKey::from_str("cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm").unwrap();
351+
let different_pk = different_sk.public_key(&secp);
352+
let request = KeyRequest::Pubkey(different_pk);
353+
354+
let result = keymap.get_key(request, &secp).unwrap();
355+
assert!(result.is_none(), "Should return None when no matching key is found");
356+
}
357+
358+
#[test]
359+
fn get_key_descriptor_secret_key_xonly_not_supported() {
360+
let secp = Secp256k1::new();
361+
362+
let descriptor_sk = DescriptorSecretKey::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap();
363+
364+
// Create an x-only public key request
365+
let sk =
366+
PrivateKey::from_str("cMk8gWmj1KpjdYnAWwsEDekodMYhbyYBhG8gMtCCxucJ98JzcNij").unwrap();
367+
let xonly_pk = sk.public_key(&secp).inner.x_only_public_key().0;
368+
let request = KeyRequest::XOnlyPubkey(xonly_pk);
369+
370+
let result = descriptor_sk.get_key(request.clone(), &secp);
371+
assert!(matches!(result, Err(GetKeyError::NotSupported)));
372+
373+
// Also test with KeyMap
374+
let descriptor_s = "wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)";
375+
let (_, keymap) = Descriptor::parse_descriptor(&secp, descriptor_s).unwrap();
376+
377+
// While requesting an x-only key from an individual xpriv, that's an error.
378+
// But from a keymap, which might have both x-only keys and regular xprivs,
379+
// we treat errors as "key not found".
380+
let result = keymap.get_key(request, &secp);
381+
assert!(matches!(result, Ok(None)));
382+
}
383+
384+
#[test]
385+
fn get_key_descriptor_secret_key_xonly_multipath() {
386+
let secp = Secp256k1::new();
387+
388+
let descriptor_sk = DescriptorSecretKey::from_str("[d34db33f/84h/0h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/<0;1>").unwrap();
389+
390+
// Request with a different fingerprint
391+
let different_fingerprint = bitcoin::bip32::Fingerprint::from([0x12, 0x34, 0x56, 0x78]);
392+
let path = DerivationPath::from_str("84'/1'/0'/0").unwrap();
393+
let request = KeyRequest::Bip32((different_fingerprint, path));
394+
395+
let result = descriptor_sk.get_key(request.clone(), &secp).unwrap();
396+
assert!(result.is_none(), "Should return None when fingerprint doesn't match");
397+
398+
// Create an x-only public key request -- now we get "not supported".
399+
let sk =
400+
PrivateKey::from_str("cMk8gWmj1KpjdYnAWwsEDekodMYhbyYBhG8gMtCCxucJ98JzcNij").unwrap();
401+
let xonly_pk = sk.public_key(&secp).inner.x_only_public_key().0;
402+
let request_x = KeyRequest::XOnlyPubkey(xonly_pk);
403+
404+
let result = descriptor_sk.get_key(request_x.clone(), &secp);
405+
assert!(matches!(result, Err(GetKeyError::NotSupported)));
406+
407+
// Also test with KeyMap; as in the previous test, the error turns to None.
408+
let descriptor_s = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/<0;1>/*)";
409+
let (_, keymap) = Descriptor::parse_descriptor(&secp, descriptor_s).unwrap();
410+
411+
let result = keymap.get_key(request.clone(), &secp).unwrap();
412+
assert!(result.is_none(), "Should return None when fingerprint doesn't match");
413+
let result = keymap.get_key(request, &secp).unwrap();
414+
assert!(result.is_none(), "Should return None when fingerprint doesn't match");
415+
let result = descriptor_sk.get_key(request_x.clone(), &secp);
416+
assert!(matches!(result, Err(GetKeyError::NotSupported)));
417+
let result = keymap.get_key(request_x, &secp).unwrap();
418+
assert!(result.is_none(), "Should return None even on error");
419+
}
334420
}

0 commit comments

Comments
 (0)