Skip to content

Commit 50ad3c5

Browse files
committed
feat(signer,examples): add new SignerWrapper for KeyMap
- adds a new `SignerWrapper` for `KeyMap` as a common example utility. - 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 `SignerWrapper` is necessary temporarily, in order to update the existing examples to use `Psbt::sign` instead of `Wallet::sign`, as the native implementation of `GetKey` for the `KeyMap` type has not been released yet in `rust-miniscript.
1 parent 321c8a0 commit 50ad3c5

File tree

2 files changed

+138
-3
lines changed

2 files changed

+138
-3
lines changed

examples/common/mod.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//! Common signing utilities for bdk_wallet examples
2+
//!
3+
//! This module provides the `SignerWrapper` struct and related utilities
4+
//! that enable signing functionality for the wallet examples. These utilities
5+
//! wrap the KeyMap type to implement the GetKey trait, allowing examples
6+
//! to sign transactions and PSBTs.
7+
//!
8+
//! Note: This module is only required temporarily until miniscript 12.x is released,
9+
//! which will include signing capabilities for KeyMap natively.
10+
11+
use miniscript::descriptor::{DescriptorSecretKey, KeyMap};
12+
use std::collections::BTreeMap;
13+
14+
use bitcoin::{
15+
key::Secp256k1,
16+
psbt::{GetKey, GetKeyError, KeyRequest},
17+
};
18+
19+
#[derive(Debug, Clone)]
20+
/// A wrapper over the [`KeyMap`] type that has the `GetKey` trait implementation for signing.
21+
pub struct SignerWrapper {
22+
key_map: KeyMap,
23+
}
24+
25+
impl SignerWrapper {
26+
/// Creates a new [`SignerWrapper`] for the given [`KeyMap`].
27+
pub fn new(key_map: KeyMap) -> Self {
28+
Self { key_map }
29+
}
30+
}
31+
32+
impl GetKey for SignerWrapper {
33+
type Error = GetKeyError;
34+
35+
fn get_key<C: bitcoin::secp256k1::Signing>(
36+
&self,
37+
key_request: KeyRequest,
38+
secp: &bitcoin::key::Secp256k1<C>,
39+
) -> Result<Option<bitcoin::PrivateKey>, Self::Error> {
40+
for key_map in self.key_map.iter() {
41+
let (_, desc_sk) = key_map;
42+
let wrapper = DescriptorSecretKeyWrapper::new(desc_sk.clone());
43+
match wrapper.get_key(key_request.clone(), secp) {
44+
Ok(Some(private_key)) => return Ok(Some(private_key)),
45+
Ok(None) => continue,
46+
// TODO: (@leonardo) how should we handle this ?
47+
// we can't error-out on this, because the valid signing key can be in the next
48+
// iterations.
49+
Err(_) => continue,
50+
}
51+
}
52+
Ok(None)
53+
}
54+
}
55+
56+
/// Wrapper for DescriptorSecretKey to implement GetKey trait
57+
pub struct DescriptorSecretKeyWrapper(DescriptorSecretKey);
58+
59+
impl DescriptorSecretKeyWrapper {
60+
/// Creates a new DescriptorSecretKeyWrapper
61+
pub fn new(desc_sk: DescriptorSecretKey) -> Self {
62+
Self(desc_sk)
63+
}
64+
}
65+
66+
impl GetKey for DescriptorSecretKeyWrapper {
67+
type Error = GetKeyError;
68+
69+
fn get_key<C: bitcoin::secp256k1::Signing>(
70+
&self,
71+
key_request: KeyRequest,
72+
secp: &Secp256k1<C>,
73+
) -> Result<Option<bitcoin::PrivateKey>, Self::Error> {
74+
match (&self.0, key_request) {
75+
(DescriptorSecretKey::Single(single_priv), key_request) => {
76+
let private_key = single_priv.key;
77+
let public_key = private_key.public_key(secp);
78+
let pubkey_map = BTreeMap::from([(public_key, private_key)]);
79+
return pubkey_map.get_key(key_request, secp);
80+
}
81+
(DescriptorSecretKey::XPrv(descriptor_xkey), KeyRequest::Pubkey(public_key)) => {
82+
let private_key = descriptor_xkey.xkey.private_key;
83+
let pk = private_key.public_key(secp);
84+
if public_key.inner.eq(&pk) {
85+
return Ok(Some(
86+
descriptor_xkey
87+
.xkey
88+
.derive_priv(secp, &descriptor_xkey.derivation_path)
89+
.map_err(GetKeyError::Bip32)?
90+
.to_priv(),
91+
));
92+
}
93+
}
94+
(
95+
DescriptorSecretKey::XPrv(descriptor_xkey),
96+
ref key_request @ KeyRequest::Bip32(ref key_source),
97+
) => {
98+
if let Some(key) = descriptor_xkey.xkey.get_key(key_request.clone(), secp)? {
99+
return Ok(Some(key));
100+
}
101+
102+
if let Some(_derivation_path) = descriptor_xkey.matches(key_source, secp) {
103+
let (_fp, derivation_path) = key_source;
104+
105+
if let Some((_fp, origin_derivation_path)) = &descriptor_xkey.origin {
106+
let derivation_path = &derivation_path[origin_derivation_path.len()..];
107+
return Ok(Some(
108+
descriptor_xkey
109+
.xkey
110+
.derive_priv(secp, &derivation_path)
111+
.map_err(GetKeyError::Bip32)?
112+
.to_priv(),
113+
));
114+
} else {
115+
return Ok(Some(
116+
descriptor_xkey
117+
.xkey
118+
.derive_priv(secp, derivation_path)
119+
.map_err(GetKeyError::Bip32)?
120+
.to_priv(),
121+
));
122+
};
123+
}
124+
}
125+
(DescriptorSecretKey::XPrv(_), KeyRequest::XOnlyPubkey(_)) => {
126+
return Err(GetKeyError::NotSupported)
127+
}
128+
(DescriptorSecretKey::MultiXPrv(_), _) => unimplemented!(),
129+
_ => unreachable!(),
130+
}
131+
Ok(None)
132+
}
133+
}

src/test_utils.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use core::str::FromStr;
66

77
use bdk_chain::{BlockId, ConfirmationBlockTime, TxUpdate};
88
use bitcoin::{
9-
absolute, hashes::Hash, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint,
10-
Transaction, TxIn, TxOut, Txid,
9+
absolute,
10+
hashes::Hash,
11+
transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut,
12+
Txid,
1113
};
1214

13-
use crate::{KeychainKind, Update, Wallet};
15+
use crate::{ KeychainKind, Update, Wallet};
1416

1517
/// Return a fake wallet that appears to be funded for testing.
1618
///

0 commit comments

Comments
 (0)