Skip to content

Commit 213f18f

Browse files
committed
Merge #562: Expose more getters in Wallet and other useful descriptor traits
1b90148 Update changelog (Alekos Filini) 86abd86 [descriptor] Expose utilities to deal with derived descriptors (Alekos Filini) 0d9c2f7 [export] Use the new getters on `Wallet` to generate export JSONs (Alekos Filini) 63d5bce [wallet] Add more getters (Alekos Filini) Pull request description: <!-- You can erase any parts of this template not applicable to your Pull Request. --> ### Description Expose more getters and internal utilities for people who need to work with descriptors. A good example of how this can be leveraged is in the second commit, which refactors the wallet export functionality to use the new public APIs rather than using members on `Wallet` directly. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [ ] I've added tests for the new feature * [x] I've added docs for the new feature * [x] I've updated `CHANGELOG.md` ACKs for top commit: notmandatory: ACK 8cd0550 Tree-SHA512: 3e8833670ebc56316fce01fc572fcc9391d602ef85f0cde8edcb446295570a9012e18f6ba8af0984153e4688f66f7eea6803ef610ceb395867e464e05c01c137
2 parents 9c0141b + 8cd0550 commit 213f18f

File tree

5 files changed

+118
-23
lines changed

5 files changed

+118
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88

99
- Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, ie. for mobile platforms.
10+
- Added `Wallet::get_signers()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait.
1011

1112
## [v0.17.0] - [v0.16.1]
1213

src/descriptor/derived.rs

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,41 @@
1010
// licenses.
1111

1212
//! Derived descriptor keys
13+
//!
14+
//! The [`DerivedDescriptorKey`] type is a wrapper over the standard [`DescriptorPublicKey`] which
15+
//! guarantees that all the extended keys have a fixed derivation path, i.e. all the wildcards have
16+
//! been replaced by actual derivation indexes.
17+
//!
18+
//! The [`AsDerived`] trait provides a quick way to derive descriptors to obtain a
19+
//! `Descriptor<DerivedDescriptorKey>` type. This, in turn, can be used to derive public
20+
//! keys for arbitrary derivation indexes.
21+
//!
22+
//! Combining this with [`Wallet::get_signers`], secret keys can also be derived.
23+
//!
24+
//! # Example
25+
//!
26+
//! ```
27+
//! # use std::str::FromStr;
28+
//! # use bitcoin::secp256k1::Secp256k1;
29+
//! use bdk::descriptor::{AsDerived, DescriptorPublicKey};
30+
//! use bdk::miniscript::{ToPublicKey, TranslatePk, MiniscriptKey};
31+
//!
32+
//! let secp = Secp256k1::gen_new();
33+
//!
34+
//! let key = DescriptorPublicKey::from_str("[aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/*")?;
35+
//! let (descriptor, _, _) = bdk::descriptor!(wpkh(key))?;
36+
//!
37+
//! // derived: wpkh([aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/42)#3ladd0t2
38+
//! let derived = descriptor.as_derived(42, &secp);
39+
//! println!("derived: {}", derived);
40+
//!
41+
//! // with_pks: wpkh(02373ecb54c5e83bd7e0d40adf78b65efaf12fafb13571f0261fc90364eee22e1e)#p4jjgvll
42+
//! let with_pks = derived.translate_pk_infallible(|pk| pk.to_public_key(), |pkh| pkh.to_public_key().to_pubkeyhash());
43+
//! println!("with_pks: {}", with_pks);
44+
//! # Ok::<(), Box<dyn std::error::Error>>(())
45+
//! ```
46+
//!
47+
//! [`Wallet::get_signers`]: crate::wallet::Wallet::get_signers
1348
1449
use std::cmp::Ordering;
1550
use std::fmt;
@@ -19,10 +54,7 @@ use std::ops::Deref;
1954
use bitcoin::hashes::hash160;
2055
use bitcoin::PublicKey;
2156

22-
pub use miniscript::{
23-
descriptor::KeyMap, descriptor::Wildcard, Descriptor, DescriptorPublicKey, Legacy, Miniscript,
24-
ScriptContext, Segwitv0,
25-
};
57+
use miniscript::{descriptor::Wildcard, Descriptor, DescriptorPublicKey};
2658
use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk};
2759

2860
use crate::wallet::utils::SecpCtx;
@@ -119,14 +151,19 @@ impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
119151
}
120152
}
121153

122-
pub(crate) trait AsDerived {
123-
// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
154+
/// Utilities to derive descriptors
155+
///
156+
/// Check out the [module level] documentation for more.
157+
///
158+
/// [module level]: crate::descriptor::derived
159+
pub trait AsDerived {
160+
/// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
124161
fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx)
125162
-> Descriptor<DerivedDescriptorKey<'s>>;
126163

127-
// Transform the keys into `DerivedDescriptorKey`.
128-
//
129-
// Panics if the descriptor is not "fixed", i.e. if it's derivable
164+
/// Transform the keys into `DerivedDescriptorKey`.
165+
///
166+
/// Panics if the descriptor is not "fixed", i.e. if it's derivable
130167
fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>>;
131168
}
132169

src/descriptor/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,25 @@ use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerpr
2121
use bitcoin::util::psbt;
2222
use bitcoin::{Network, PublicKey, Script, TxOut};
2323

24-
use miniscript::descriptor::{
25-
DescriptorPublicKey, DescriptorType, DescriptorXKey, InnerXKey, Wildcard,
24+
use miniscript::descriptor::{DescriptorType, InnerXKey};
25+
pub use miniscript::{
26+
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
27+
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
2628
};
27-
pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
2829
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
2930

3031
use crate::descriptor::policy::BuildSatisfaction;
3132

3233
pub mod checksum;
33-
pub(crate) mod derived;
34+
pub mod derived;
3435
#[doc(hidden)]
3536
pub mod dsl;
3637
pub mod error;
3738
pub mod policy;
3839
pub mod template;
3940

4041
pub use self::checksum::get_checksum;
41-
use self::derived::AsDerived;
42-
pub use self::derived::DerivedDescriptorKey;
42+
pub use self::derived::{AsDerived, DerivedDescriptorKey};
4343
pub use self::error::Error as DescriptorError;
4444
pub use self::policy::Policy;
4545
use self::template::DescriptorTemplateOut;

src/wallet/export.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ use std::str::FromStr;
6464
use serde::{Deserialize, Serialize};
6565

6666
use miniscript::descriptor::{ShInner, WshInner};
67-
use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal};
67+
use miniscript::{Descriptor, ScriptContext, Terminal};
6868

6969
use crate::database::BatchDatabase;
70+
use crate::types::KeychainKind;
7071
use crate::wallet::Wallet;
7172

7273
/// Structure that contains the export of a wallet
@@ -117,8 +118,12 @@ impl WalletExport {
117118
include_blockheight: bool,
118119
) -> Result<Self, &'static str> {
119120
let descriptor = wallet
120-
.descriptor
121-
.to_string_with_secret(&wallet.signers.as_key_map(wallet.secp_ctx()));
121+
.get_descriptor_for_keychain(KeychainKind::External)
122+
.to_string_with_secret(
123+
&wallet
124+
.get_signers(KeychainKind::External)
125+
.as_key_map(wallet.secp_ctx()),
126+
);
122127
let descriptor = remove_checksum(descriptor);
123128
Self::is_compatible_with_core(&descriptor)?;
124129

@@ -142,12 +147,24 @@ impl WalletExport {
142147
blockheight,
143148
};
144149

145-
let desc_to_string = |d: &Descriptor<DescriptorPublicKey>| {
146-
let descriptor =
147-
d.to_string_with_secret(&wallet.change_signers.as_key_map(wallet.secp_ctx()));
148-
remove_checksum(descriptor)
150+
let change_descriptor = match wallet
151+
.public_descriptor(KeychainKind::Internal)
152+
.map_err(|_| "Invalid change descriptor")?
153+
.is_some()
154+
{
155+
false => None,
156+
true => {
157+
let descriptor = wallet
158+
.get_descriptor_for_keychain(KeychainKind::Internal)
159+
.to_string_with_secret(
160+
&wallet
161+
.get_signers(KeychainKind::Internal)
162+
.as_key_map(wallet.secp_ctx()),
163+
);
164+
Some(remove_checksum(descriptor))
165+
}
149166
};
150-
if export.change_descriptor() != wallet.change_descriptor.as_ref().map(desc_to_string) {
167+
if export.change_descriptor() != change_descriptor {
151168
return Err("Incompatible change descriptor");
152169
}
153170

src/wallet/mod.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,13 +468,41 @@ where
468468
signers.add_external(signer.id(&self.secp), ordering, signer);
469469
}
470470

471+
/// Get the signers
472+
///
473+
/// ## Example
474+
///
475+
/// ```
476+
/// # use bdk::{Wallet, KeychainKind};
477+
/// # use bdk::bitcoin::Network;
478+
/// # use bdk::database::MemoryDatabase;
479+
/// let wallet = Wallet::new("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet, MemoryDatabase::new())?;
480+
/// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
481+
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
482+
/// println!("secret_key: {}", secret_key);
483+
/// }
484+
///
485+
/// Ok::<(), Box<dyn std::error::Error>>(())
486+
/// ```
487+
pub fn get_signers(&self, keychain: KeychainKind) -> Arc<SignersContainer> {
488+
match keychain {
489+
KeychainKind::External => Arc::clone(&self.signers),
490+
KeychainKind::Internal => Arc::clone(&self.change_signers),
491+
}
492+
}
493+
471494
/// Add an address validator
472495
///
473496
/// See [the `address_validator` module](address_validator) for an example.
474497
pub fn add_address_validator(&mut self, validator: Arc<dyn AddressValidator>) {
475498
self.address_validators.push(validator);
476499
}
477500

501+
/// Get the address validators
502+
pub fn get_address_validators(&self) -> &[Arc<dyn AddressValidator>] {
503+
&self.address_validators
504+
}
505+
478506
/// Start building a transaction.
479507
///
480508
/// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction.
@@ -1568,6 +1596,18 @@ where
15681596

15691597
Ok(())
15701598
}
1599+
1600+
/// Return the checksum of the public descriptor associated to `keychain`
1601+
///
1602+
/// Internally calls [`Self::get_descriptor_for_keychain`] to fetch the right descriptor
1603+
pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String {
1604+
self.get_descriptor_for_keychain(keychain)
1605+
.to_string()
1606+
.splitn(2, '#')
1607+
.next()
1608+
.unwrap()
1609+
.to_string()
1610+
}
15711611
}
15721612

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

0 commit comments

Comments
 (0)