Skip to content

Commit 48fd25a

Browse files
committed
Merge #860: feat(descriptor): backport GetKey impl from #851
08fa87a test: extend unit tests to cover KeyMapWrapper error conditions (Andrew Poelstra) 5f089aa key_map: handle lookup errors on multipath xprivs correctly (Andrew Poelstra) c7d4171 feat(descriptor): backport `GetKey` impl from #851 (Leonardo Lima) 58d1df9 chore(deps): bump `bitcoin` to `0.32.6` (Leonardo Lima) Pull request description: backports #851 to release/12.x ### Description It's an initial attempt to backport the implementation of GetKey for KeyMap introduced in #851. ### Notes to the reviewers I noticed that the type `KeyMapWrapper` is already being used internally for parse_descriptor, though, as it's not part of the public API, there's no conflict, but I'm fine with changing it to another name if needed. ### Changelog Notice ``` # Added - add `KeyMapWrapper` with `GetKey` trait for PSBT signing - implement `GetKey` for `DescriptorSecretKey` - add `From<KeyMap>` conversion - export new `KeyMapWrapper` for downstream crate usage ``` ACKs for top commit: apoelstra: ACK 08fa87a; successfully ran local tests Tree-SHA512: 73f100f9b851012debdc1c8bb8b89bc76394865c98f315831168978d0c93038932745ff58ceb52d04c02e50e6c5c6073658e16279abb2b10c5b7b6c346f18ed2
2 parents 63a4047 + 08fa87a commit 48fd25a

File tree

5 files changed

+368
-5
lines changed

5 files changed

+368
-5
lines changed

Cargo-minimal.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
5353

5454
[[package]]
5555
name = "bitcoin"
56-
version = "0.32.0"
56+
version = "0.32.6"
5757
source = "registry+https://github.com/rust-lang/crates.io-index"
58-
checksum = "7170e7750a20974246f17ece04311b4205a6155f1db564c5b224af817663c3ea"
58+
checksum = "ad8929a18b8e33ea6b3c09297b687baaa71fb1b97353243a3f1029fad5c59c5b"
5959
dependencies = [
6060
"base58ck",
6161
"base64 0.21.7",

Cargo-recent.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
5353

5454
[[package]]
5555
name = "bitcoin"
56-
version = "0.32.0"
56+
version = "0.32.6"
5757
source = "registry+https://github.com/rust-lang/crates.io-index"
58-
checksum = "7170e7750a20974246f17ece04311b4205a6155f1db564c5b224af817663c3ea"
58+
checksum = "ad8929a18b8e33ea6b3c09297b687baaa71fb1b97353243a3f1029fad5c59c5b"
5959
dependencies = [
6060
"base58ck",
6161
"base64 0.21.7",

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ base64 = ["bitcoin/base64"]
2323

2424
[dependencies]
2525
bech32 = { version = "0.11.0", default-features = false }
26-
bitcoin = { version = "0.32.0", default-features = false }
26+
bitcoin = { version = "0.32.6", default-features = false }
2727

2828
# Do NOT use this as a feature! Use the `serde` feature instead.
2929
actual-serde = { package = "serde", version = "1.0.103", optional = true }

src/descriptor/key_map.rs

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
use bitcoin::psbt::{GetKey, GetKeyError, KeyRequest};
2+
use bitcoin::secp256k1::{Secp256k1, Signing};
3+
use bitcoin::PrivateKey;
4+
5+
use crate::descriptor::{DescriptorSecretKey, KeyMap};
6+
use crate::BTreeMap;
7+
8+
/// A wrapper around KeyMap that implements GetKey for PSBT signing.
9+
#[derive(Debug, Clone, Eq, PartialEq)]
10+
pub struct KeyMapWrapper {
11+
map: KeyMap,
12+
}
13+
14+
impl From<KeyMap> for KeyMapWrapper {
15+
fn from(map: KeyMap) -> Self { KeyMapWrapper { map } }
16+
}
17+
18+
impl GetKey for KeyMapWrapper {
19+
type Error = GetKeyError;
20+
21+
fn get_key<C: Signing>(
22+
&self,
23+
key_request: KeyRequest,
24+
secp: &Secp256k1<C>,
25+
) -> Result<Option<bitcoin::PrivateKey>, Self::Error> {
26+
Ok(self
27+
.map
28+
.iter()
29+
.find_map(|(_desc_pk, desc_sk)| -> Option<PrivateKey> {
30+
match desc_sk.get_key(key_request.clone(), secp) {
31+
Ok(Some(pk)) => Some(pk),
32+
// When looking up keys in a map, we eat errors on individual keys, on
33+
// the assumption that some other key in the map might not error.
34+
Ok(None) | Err(_) => None,
35+
}
36+
}))
37+
}
38+
}
39+
40+
impl GetKey for DescriptorSecretKey {
41+
type Error = GetKeyError;
42+
43+
fn get_key<C: Signing>(
44+
&self,
45+
key_request: KeyRequest,
46+
secp: &Secp256k1<C>,
47+
) -> Result<Option<PrivateKey>, Self::Error> {
48+
match (self, key_request) {
49+
(DescriptorSecretKey::Single(single_priv), key_request) => {
50+
let sk = single_priv.key;
51+
let pk = sk.public_key(secp);
52+
let pubkey_map = BTreeMap::from([(pk, sk)]);
53+
pubkey_map.get_key(key_request, secp)
54+
}
55+
(DescriptorSecretKey::XPrv(descriptor_xkey), KeyRequest::Pubkey(public_key)) => {
56+
let xpriv = descriptor_xkey
57+
.xkey
58+
.derive_priv(secp, &descriptor_xkey.derivation_path)
59+
.map_err(GetKeyError::Bip32)?;
60+
let pk = xpriv.private_key.public_key(secp);
61+
62+
if public_key.inner.eq(&pk) {
63+
Ok(Some(xpriv.to_priv()))
64+
} else {
65+
Ok(None)
66+
}
67+
}
68+
(
69+
DescriptorSecretKey::XPrv(descriptor_xkey),
70+
ref key_request @ KeyRequest::Bip32(ref key_source),
71+
) => {
72+
if let Some(key) = descriptor_xkey.xkey.get_key(key_request.clone(), secp)? {
73+
return Ok(Some(key));
74+
}
75+
76+
if descriptor_xkey.matches(key_source, secp).is_some() {
77+
let (_, derivation_path) = key_source;
78+
return Ok(Some(
79+
descriptor_xkey
80+
.xkey
81+
.derive_priv(secp, &derivation_path)
82+
.map_err(GetKeyError::Bip32)?
83+
.to_priv(),
84+
));
85+
}
86+
87+
Ok(None)
88+
}
89+
(DescriptorSecretKey::XPrv(_), KeyRequest::XOnlyPubkey(_)) => {
90+
Err(GetKeyError::NotSupported)
91+
}
92+
(
93+
desc_multi_sk @ DescriptorSecretKey::MultiXPrv(_descriptor_multi_xkey),
94+
key_request,
95+
) => {
96+
for desc_sk in &desc_multi_sk.clone().into_single_keys() {
97+
// If any key is an error, then all of them will, so here we propagate errors with ?.
98+
if let Some(pk) = desc_sk.get_key(key_request.clone(), secp)? {
99+
return Ok(Some(pk));
100+
}
101+
}
102+
Ok(None)
103+
}
104+
_ => Ok(None),
105+
}
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
mod tests {
111+
use core::str::FromStr;
112+
113+
use bitcoin::bip32::{ChildNumber, DerivationPath, IntoDerivationPath, Xpriv};
114+
115+
use super::*;
116+
use crate::Descriptor;
117+
118+
#[test]
119+
fn get_key_single_key() {
120+
let secp = Secp256k1::new();
121+
122+
let descriptor_sk_s =
123+
"[90b6a706/44'/0'/0'/0/0]cMk8gWmj1KpjdYnAWwsEDekodMYhbyYBhG8gMtCCxucJ98JzcNij";
124+
125+
let single = match descriptor_sk_s.parse::<DescriptorSecretKey>().unwrap() {
126+
DescriptorSecretKey::Single(single) => single,
127+
_ => panic!("unexpected DescriptorSecretKey variant"),
128+
};
129+
130+
let want_sk = single.key;
131+
let descriptor_s = format!("wpkh({})", descriptor_sk_s);
132+
let (_, keymap) = Descriptor::parse_descriptor(&secp, &descriptor_s).unwrap();
133+
let keymap_wrapper = KeyMapWrapper::from(keymap);
134+
135+
let pk = want_sk.public_key(&secp);
136+
let request = KeyRequest::Pubkey(pk);
137+
let got_sk = keymap_wrapper
138+
.get_key(request, &secp)
139+
.expect("get_key call errored")
140+
.expect("failed to find the key");
141+
assert_eq!(got_sk, want_sk)
142+
}
143+
144+
#[test]
145+
fn get_key_xpriv_single_key_xpriv() {
146+
let secp = Secp256k1::new();
147+
148+
let s = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi";
149+
150+
let xpriv = s.parse::<Xpriv>().unwrap();
151+
let xpriv_fingerprint = xpriv.fingerprint(&secp);
152+
153+
// Sanity check.
154+
{
155+
let descriptor_sk_s = format!("[{}]{}", xpriv_fingerprint, xpriv);
156+
let descriptor_sk = descriptor_sk_s.parse::<DescriptorSecretKey>().unwrap();
157+
let got = match descriptor_sk {
158+
DescriptorSecretKey::XPrv(x) => x.xkey,
159+
_ => panic!("unexpected DescriptorSecretKey variant"),
160+
};
161+
assert_eq!(got, xpriv);
162+
}
163+
164+
let want_sk = xpriv.to_priv();
165+
let descriptor_s = format!("wpkh([{}]{})", xpriv_fingerprint, xpriv);
166+
let (_, keymap) = Descriptor::parse_descriptor(&secp, &descriptor_s).unwrap();
167+
let keymap_wrapper = KeyMapWrapper::from(keymap);
168+
169+
let pk = want_sk.public_key(&secp);
170+
let request = KeyRequest::Pubkey(pk);
171+
let got_sk = keymap_wrapper
172+
.get_key(request, &secp)
173+
.expect("get_key call errored")
174+
.expect("failed to find the key");
175+
assert_eq!(got_sk, want_sk)
176+
}
177+
178+
#[test]
179+
fn get_key_xpriv_child_depth_one() {
180+
let secp = Secp256k1::new();
181+
182+
let s = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi";
183+
let master = s.parse::<Xpriv>().unwrap();
184+
let master_fingerprint = master.fingerprint(&secp);
185+
186+
let child_number = ChildNumber::from_hardened_idx(44).unwrap();
187+
let child = master.derive_priv(&secp, &[child_number]).unwrap();
188+
189+
// Sanity check.
190+
{
191+
let descriptor_sk_s = format!("[{}/44']{}", master_fingerprint, child);
192+
let descriptor_sk = descriptor_sk_s.parse::<DescriptorSecretKey>().unwrap();
193+
let got = match descriptor_sk {
194+
DescriptorSecretKey::XPrv(ref x) => x.xkey,
195+
_ => panic!("unexpected DescriptorSecretKey variant"),
196+
};
197+
assert_eq!(got, child);
198+
}
199+
200+
let want_sk = child.to_priv();
201+
let descriptor_s = format!("wpkh({}/44')", s);
202+
let (_, keymap) = Descriptor::parse_descriptor(&secp, &descriptor_s).unwrap();
203+
let keymap_wrapper = KeyMapWrapper::from(keymap);
204+
205+
let pk = want_sk.public_key(&secp);
206+
let request = KeyRequest::Pubkey(pk);
207+
let got_sk = keymap_wrapper
208+
.get_key(request, &secp)
209+
.expect("get_key call errored")
210+
.expect("failed to find the key");
211+
assert_eq!(got_sk, want_sk)
212+
}
213+
214+
#[test]
215+
fn get_key_xpriv_with_path() {
216+
let secp = Secp256k1::new();
217+
218+
let s = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi";
219+
let master = s.parse::<Xpriv>().unwrap();
220+
let master_fingerprint = master.fingerprint(&secp);
221+
222+
let first_external_child = "44'/0'/0'/0/0";
223+
let derivation_path = first_external_child.into_derivation_path().unwrap();
224+
225+
let child = master.derive_priv(&secp, &derivation_path).unwrap();
226+
227+
// Sanity check.
228+
{
229+
let descriptor_sk_s =
230+
format!("[{}/{}]{}", master_fingerprint, first_external_child, child);
231+
let descriptor_sk = descriptor_sk_s.parse::<DescriptorSecretKey>().unwrap();
232+
let got = match descriptor_sk {
233+
DescriptorSecretKey::XPrv(ref x) => x.xkey,
234+
_ => panic!("unexpected DescriptorSecretKey variant"),
235+
};
236+
assert_eq!(got, child);
237+
}
238+
239+
let want_sk = child.to_priv();
240+
let descriptor_s = format!("wpkh({}/44'/0'/0'/0/*)", s);
241+
let (_, keymap) = Descriptor::parse_descriptor(&secp, &descriptor_s).unwrap();
242+
let keymap_wrapper = KeyMapWrapper::from(keymap);
243+
244+
let key_source = (master_fingerprint, derivation_path);
245+
let request = KeyRequest::Bip32(key_source);
246+
let got_sk = keymap_wrapper
247+
.get_key(request, &secp)
248+
.expect("get_key call errored")
249+
.expect("failed to find the key");
250+
251+
assert_eq!(got_sk, want_sk)
252+
}
253+
254+
#[test]
255+
fn get_key_xpriv_with_key_origin() {
256+
let secp = Secp256k1::new();
257+
258+
let descriptor_str = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)";
259+
let (_descriptor_pk, keymap) = Descriptor::parse_descriptor(&secp, descriptor_str).unwrap();
260+
let keymap_wrapper = KeyMapWrapper::from(keymap);
261+
262+
let descriptor_sk = DescriptorSecretKey::from_str("[d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*").unwrap();
263+
let xpriv = match descriptor_sk {
264+
DescriptorSecretKey::XPrv(descriptor_xkey) => descriptor_xkey,
265+
_ => unreachable!(),
266+
};
267+
268+
let path = DerivationPath::from_str("84'/1'/0'/0").unwrap();
269+
let expected_pk = xpriv.xkey.derive_priv(&secp, &path).unwrap().to_priv();
270+
271+
let (fp, _) = xpriv.origin.unwrap();
272+
let key_request = KeyRequest::Bip32((fp, path));
273+
274+
let pk = keymap_wrapper
275+
.get_key(key_request, &secp)
276+
.expect("get_key should not fail")
277+
.expect("get_key should return a `PrivateKey`");
278+
279+
assert_eq!(pk, expected_pk);
280+
}
281+
282+
#[test]
283+
fn get_key_keymap_no_match() {
284+
let secp = Secp256k1::new();
285+
286+
// Create a keymap with one key
287+
let descriptor_s = "wpkh(cMk8gWmj1KpjdYnAWwsEDekodMYhbyYBhG8gMtCCxucJ98JzcNij)";
288+
let (_, keymap) = Descriptor::parse_descriptor(&secp, descriptor_s).unwrap();
289+
let keymap_wrapper = KeyMapWrapper::from(keymap);
290+
291+
// Request a different public key that doesn't exist in the keymap
292+
let different_sk =
293+
PrivateKey::from_str("cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm").unwrap();
294+
let different_pk = different_sk.public_key(&secp);
295+
let request = KeyRequest::Pubkey(different_pk);
296+
297+
let result = keymap_wrapper.get_key(request, &secp).unwrap();
298+
assert!(result.is_none(), "Should return None when no matching key is found");
299+
}
300+
301+
#[test]
302+
fn get_key_descriptor_secret_key_xonly_not_supported() {
303+
let secp = Secp256k1::new();
304+
305+
let descriptor_sk = DescriptorSecretKey::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap();
306+
307+
// Create an x-only public key request
308+
let sk =
309+
PrivateKey::from_str("cMk8gWmj1KpjdYnAWwsEDekodMYhbyYBhG8gMtCCxucJ98JzcNij").unwrap();
310+
let xonly_pk = sk.public_key(&secp).inner.x_only_public_key().0;
311+
let request = KeyRequest::XOnlyPubkey(xonly_pk);
312+
313+
let result = descriptor_sk.get_key(request.clone(), &secp);
314+
assert!(matches!(result, Err(GetKeyError::NotSupported)));
315+
316+
// Also test with KeyMap
317+
let descriptor_s = "wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)";
318+
let (_, keymap) = Descriptor::parse_descriptor(&secp, descriptor_s).unwrap();
319+
let keymap_wrapper = KeyMapWrapper::from(keymap);
320+
321+
// While requesting an x-only key from an individual xpriv, that's an error.
322+
// But from a keymap, which might have both x-only keys and regular xprivs,
323+
// we treat errors as "key not found".
324+
let result = keymap_wrapper.get_key(request, &secp);
325+
assert!(matches!(result, Ok(None)));
326+
}
327+
328+
#[test]
329+
fn get_key_descriptor_secret_key_xonly_multipath() {
330+
let secp = Secp256k1::new();
331+
332+
let descriptor_sk = DescriptorSecretKey::from_str("[d34db33f/84h/0h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/<0;1>").unwrap();
333+
334+
// Request with a different fingerprint
335+
let different_fingerprint = bitcoin::bip32::Fingerprint::from([0x12, 0x34, 0x56, 0x78]);
336+
let path = DerivationPath::from_str("84'/1'/0'/0").unwrap();
337+
let request = KeyRequest::Bip32((different_fingerprint, path));
338+
339+
let result = descriptor_sk.get_key(request.clone(), &secp).unwrap();
340+
assert!(result.is_none(), "Should return None when fingerprint doesn't match");
341+
342+
// Create an x-only public key request -- now we get "not supported".
343+
let sk =
344+
PrivateKey::from_str("cMk8gWmj1KpjdYnAWwsEDekodMYhbyYBhG8gMtCCxucJ98JzcNij").unwrap();
345+
let xonly_pk = sk.public_key(&secp).inner.x_only_public_key().0;
346+
let request_x = KeyRequest::XOnlyPubkey(xonly_pk);
347+
348+
let result = descriptor_sk.get_key(request_x.clone(), &secp);
349+
assert!(matches!(result, Err(GetKeyError::NotSupported)));
350+
351+
// Also test with KeyMap; as in the previous test, the error turns to None.
352+
let descriptor_s = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)";
353+
let (_, keymap) = Descriptor::parse_descriptor(&secp, descriptor_s).unwrap();
354+
let keymap_wrapper = KeyMapWrapper::from(keymap);
355+
356+
let result = keymap_wrapper.get_key(request, &secp).unwrap();
357+
assert!(result.is_none(), "Should return None when fingerprint doesn't match");
358+
let result = keymap_wrapper.get_key(request_x, &secp).unwrap();
359+
assert!(result.is_none(), "Should return None even on error");
360+
}
361+
}

0 commit comments

Comments
 (0)