Skip to content

Commit 0cb0c21

Browse files
committed
feat(signer): add new SignerWrapper for KeyMap
- adds a new `SignerWrapper` for `KeyMap` as a test utility, with new methods: `get_wallet_signer` and `get_wallet_signer_single`. - adds an implementation of `GetKey` trait for `SignerWrapper`, in order to retrieve the private key for software signers, and successfully use `Psbt::sign` method. - the new methods added are necessary, in order to update the existing tests and examples to use `Psbt::sign` instead of `Wallet::sign`, as it further allows the signing APIs and related types to be fully removed.
1 parent 41e59a7 commit 0cb0c21

File tree

1 file changed

+156
-3
lines changed

1 file changed

+156
-3
lines changed

wallet/src/test_utils.rs

Lines changed: 156 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,167 @@
33
use alloc::string::ToString;
44
use alloc::sync::Arc;
55
use core::str::FromStr;
6+
use miniscript::descriptor::{DescriptorSecretKey, KeyMap};
7+
use std::collections::BTreeMap;
68

79
use bdk_chain::{BlockId, ConfirmationBlockTime, TxUpdate};
810
use bitcoin::{
9-
absolute, hashes::Hash, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint,
10-
Transaction, TxIn, TxOut, Txid,
11+
absolute,
12+
bip32::DerivationPath,
13+
hashes::Hash,
14+
key::Secp256k1,
15+
psbt::{GetKey, GetKeyError, KeyRequest},
16+
transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut,
17+
Txid,
1118
};
1219

13-
use crate::{KeychainKind, Update, Wallet};
20+
use crate::{descriptor::check_wallet_descriptor, KeychainKind, Update, Wallet};
21+
22+
#[derive(Debug, Clone)]
23+
/// A wrapper over the [`KeyMap`] type that has the `GetKey` trait implementation for signing.
24+
pub struct SignerWrapper {
25+
key_map: KeyMap,
26+
}
27+
28+
impl SignerWrapper {
29+
/// Creates a new [`SignerWrapper`] for the given [`KeyMap`].
30+
pub fn new(key_map: KeyMap) -> Self {
31+
Self { key_map }
32+
}
33+
}
34+
35+
impl GetKey for SignerWrapper {
36+
type Error = GetKeyError;
37+
38+
fn get_key<C: bitcoin::secp256k1::Signing>(
39+
&self,
40+
key_request: KeyRequest,
41+
secp: &bitcoin::key::Secp256k1<C>,
42+
) -> Result<Option<bitcoin::PrivateKey>, Self::Error> {
43+
for key_map in self.key_map.iter() {
44+
let (_, desc_sk) = key_map;
45+
let wrapper = DescriptorSecretKeyWrapper::new(desc_sk.clone());
46+
match wrapper.get_key(key_request.clone(), secp) {
47+
Ok(Some(private_key)) => return Ok(Some(private_key)),
48+
Ok(None) => continue,
49+
// TODO: (@leonardo) how should we handle this ?
50+
// we can't error-out on this, because the valid signing key can be in the next
51+
// iterations.
52+
Err(_) => continue,
53+
}
54+
}
55+
Ok(None)
56+
}
57+
}
58+
59+
/// .
60+
pub struct DescriptorSecretKeyWrapper(DescriptorSecretKey);
61+
62+
impl DescriptorSecretKeyWrapper {
63+
/// .
64+
pub fn new(desc_sk: DescriptorSecretKey) -> Self {
65+
Self(desc_sk)
66+
}
67+
}
68+
69+
impl GetKey for DescriptorSecretKeyWrapper {
70+
type Error = GetKeyError;
71+
72+
fn get_key<C: bitcoin::secp256k1::Signing>(
73+
&self,
74+
key_request: KeyRequest,
75+
secp: &Secp256k1<C>,
76+
) -> Result<Option<bitcoin::PrivateKey>, Self::Error> {
77+
match (&self.0, key_request) {
78+
(DescriptorSecretKey::Single(single_priv), key_request) => {
79+
let private_key = single_priv.key;
80+
let public_key = private_key.public_key(secp);
81+
let pubkey_map = BTreeMap::from([(public_key, private_key)]);
82+
return pubkey_map.get_key(key_request, secp);
83+
}
84+
(DescriptorSecretKey::XPrv(descriptor_xkey), KeyRequest::Pubkey(public_key)) => {
85+
let private_key = descriptor_xkey.xkey.private_key;
86+
let pk = private_key.public_key(secp);
87+
if public_key.inner.eq(&pk) {
88+
return Ok(Some(
89+
descriptor_xkey
90+
.xkey
91+
.derive_priv(secp, &descriptor_xkey.derivation_path)
92+
.map_err(GetKeyError::Bip32)?
93+
.to_priv(),
94+
));
95+
}
96+
}
97+
(
98+
DescriptorSecretKey::XPrv(descriptor_xkey),
99+
ref key_request @ KeyRequest::Bip32(ref key_source),
100+
) => {
101+
if let Some(key) = descriptor_xkey.xkey.get_key(key_request.clone(), secp)? {
102+
return Ok(Some(key));
103+
}
104+
105+
if let Some(_derivation_path) = descriptor_xkey.matches(key_source, secp) {
106+
let (_fp, derivation_path) = key_source;
107+
let derivation_path: &DerivationPath = match &descriptor_xkey.origin {
108+
Some((_fp, origin_derivation_path)) => {
109+
&derivation_path[origin_derivation_path.len()..].into()
110+
}
111+
None => derivation_path,
112+
};
113+
114+
return Ok(Some(
115+
descriptor_xkey
116+
.xkey
117+
.derive_priv(secp, derivation_path)
118+
.map_err(GetKeyError::Bip32)?
119+
.to_priv(),
120+
));
121+
}
122+
}
123+
(DescriptorSecretKey::XPrv(_), KeyRequest::XOnlyPubkey(_)) => {
124+
return Err(GetKeyError::NotSupported)
125+
}
126+
(DescriptorSecretKey::MultiXPrv(_), _) => unimplemented!(),
127+
_ => unreachable!(),
128+
}
129+
Ok(None)
130+
}
131+
}
132+
133+
/// Create the [`CreateParams`] for the provided testing `descriptor` and `change_descriptor`.
134+
pub fn get_wallet_params(descriptor: &str, change_descriptor: Option<&str>) -> crate::CreateParams {
135+
if let Some(change_desc) = change_descriptor {
136+
Wallet::create(descriptor.to_string(), change_desc.to_string())
137+
} else {
138+
Wallet::create_single(descriptor.to_string())
139+
}
140+
}
141+
142+
/// Create a new [`SignerWrapper`] for the provided testing `descriptor` and `change_descriptor`.
143+
pub fn get_wallet_signer(descriptor: &str, change_descriptor: Option<&str>) -> SignerWrapper {
144+
let secp = Secp256k1::new();
145+
let params = get_wallet_params(descriptor, change_descriptor).network(Network::Regtest);
146+
147+
let network = params.network;
148+
149+
let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network).unwrap();
150+
check_wallet_descriptor(&descriptor).unwrap();
151+
descriptor_keymap.extend(params.descriptor_keymap);
152+
153+
if let Some(change_descriptor) = params.change_descriptor {
154+
let (change_descriptor, mut change_keymap) = change_descriptor(&secp, network).unwrap();
155+
check_wallet_descriptor(&change_descriptor).unwrap();
156+
change_keymap.extend(params.change_descriptor_keymap);
157+
descriptor_keymap.extend(change_keymap)
158+
}
159+
160+
SignerWrapper::new(descriptor_keymap)
161+
}
162+
163+
/// Create a new [`SignerWrapper`] for the provided testing `descriptor`.
164+
pub fn get_wallet_signer_single(descriptor: &str) -> SignerWrapper {
165+
get_wallet_signer(descriptor, None)
166+
}
14167

15168
/// Return a fake wallet that appears to be funded for testing.
16169
///

0 commit comments

Comments
 (0)