Merge bitcoindevkit/bdk#562: Expose more getters in Wallet and other useful descriptor traits

1b9014846c5cb690f5c37f70556717b788a0733b Update changelog (Alekos Filini)
86abd8698f34d91f94bfce896affb535cca27146 [descriptor] Expose utilities to deal with derived descriptors (Alekos Filini)
0d9c2f76e00af801668fa2a70fcd13f9e399c0d2 [export] Use the new getters on `Wallet` to generate export JSONs (Alekos Filini)
63d5bcee934febe4d38622b677131bbe2e1a0abe [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 8cd055090d75669c67a44467cc0281465ef31270

Tree-SHA512: 3e8833670ebc56316fce01fc572fcc9391d602ef85f0cde8edcb446295570a9012e18f6ba8af0984153e4688f66f7eea6803ef610ceb395867e464e05c01c137
This commit is contained in:
Steve Myers 2022-04-04 20:52:26 -07:00
commit 213f18f7b7
No known key found for this signature in database
GPG Key ID: 8105A46B22C2D051
5 changed files with 118 additions and 23 deletions

View File

@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
- Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, ie. for mobile platforms. - Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, ie. for mobile platforms.
- Added `Wallet::get_signers()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait.
## [v0.17.0] - [v0.16.1] ## [v0.17.0] - [v0.16.1]

View File

@ -10,6 +10,41 @@
// licenses. // licenses.
//! Derived descriptor keys //! Derived descriptor keys
//!
//! The [`DerivedDescriptorKey`] type is a wrapper over the standard [`DescriptorPublicKey`] which
//! guarantees that all the extended keys have a fixed derivation path, i.e. all the wildcards have
//! been replaced by actual derivation indexes.
//!
//! The [`AsDerived`] trait provides a quick way to derive descriptors to obtain a
//! `Descriptor<DerivedDescriptorKey>` type. This, in turn, can be used to derive public
//! keys for arbitrary derivation indexes.
//!
//! Combining this with [`Wallet::get_signers`], secret keys can also be derived.
//!
//! # Example
//!
//! ```
//! # use std::str::FromStr;
//! # use bitcoin::secp256k1::Secp256k1;
//! use bdk::descriptor::{AsDerived, DescriptorPublicKey};
//! use bdk::miniscript::{ToPublicKey, TranslatePk, MiniscriptKey};
//!
//! let secp = Secp256k1::gen_new();
//!
//! let key = DescriptorPublicKey::from_str("[aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/*")?;
//! let (descriptor, _, _) = bdk::descriptor!(wpkh(key))?;
//!
//! // derived: wpkh([aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/42)#3ladd0t2
//! let derived = descriptor.as_derived(42, &secp);
//! println!("derived: {}", derived);
//!
//! // with_pks: wpkh(02373ecb54c5e83bd7e0d40adf78b65efaf12fafb13571f0261fc90364eee22e1e)#p4jjgvll
//! let with_pks = derived.translate_pk_infallible(|pk| pk.to_public_key(), |pkh| pkh.to_public_key().to_pubkeyhash());
//! println!("with_pks: {}", with_pks);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! [`Wallet::get_signers`]: crate::wallet::Wallet::get_signers
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt; use std::fmt;
@ -19,10 +54,7 @@ use std::ops::Deref;
use bitcoin::hashes::hash160; use bitcoin::hashes::hash160;
use bitcoin::PublicKey; use bitcoin::PublicKey;
pub use miniscript::{ use miniscript::{descriptor::Wildcard, Descriptor, DescriptorPublicKey};
descriptor::KeyMap, descriptor::Wildcard, Descriptor, DescriptorPublicKey, Legacy, Miniscript,
ScriptContext, Segwitv0,
};
use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk}; use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk};
use crate::wallet::utils::SecpCtx; use crate::wallet::utils::SecpCtx;
@ -119,14 +151,19 @@ impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
} }
} }
pub(crate) trait AsDerived { /// Utilities to derive descriptors
// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey` ///
/// Check out the [module level] documentation for more.
///
/// [module level]: crate::descriptor::derived
pub trait AsDerived {
/// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx) fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx)
-> Descriptor<DerivedDescriptorKey<'s>>; -> Descriptor<DerivedDescriptorKey<'s>>;
// Transform the keys into `DerivedDescriptorKey`. /// Transform the keys into `DerivedDescriptorKey`.
// ///
// Panics if the descriptor is not "fixed", i.e. if it's derivable /// Panics if the descriptor is not "fixed", i.e. if it's derivable
fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>>; fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>>;
} }

View File

@ -21,16 +21,17 @@ use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerpr
use bitcoin::util::psbt; use bitcoin::util::psbt;
use bitcoin::{Network, PublicKey, Script, TxOut}; use bitcoin::{Network, PublicKey, Script, TxOut};
use miniscript::descriptor::{ use miniscript::descriptor::{DescriptorType, InnerXKey};
DescriptorPublicKey, DescriptorType, DescriptorXKey, InnerXKey, Wildcard, pub use miniscript::{
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
}; };
pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk}; use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
use crate::descriptor::policy::BuildSatisfaction; use crate::descriptor::policy::BuildSatisfaction;
pub mod checksum; pub mod checksum;
pub(crate) mod derived; pub mod derived;
#[doc(hidden)] #[doc(hidden)]
pub mod dsl; pub mod dsl;
pub mod error; pub mod error;
@ -38,8 +39,7 @@ pub mod policy;
pub mod template; pub mod template;
pub use self::checksum::get_checksum; pub use self::checksum::get_checksum;
use self::derived::AsDerived; pub use self::derived::{AsDerived, DerivedDescriptorKey};
pub use self::derived::DerivedDescriptorKey;
pub use self::error::Error as DescriptorError; pub use self::error::Error as DescriptorError;
pub use self::policy::Policy; pub use self::policy::Policy;
use self::template::DescriptorTemplateOut; use self::template::DescriptorTemplateOut;

View File

@ -64,9 +64,10 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use miniscript::descriptor::{ShInner, WshInner}; use miniscript::descriptor::{ShInner, WshInner};
use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal}; use miniscript::{Descriptor, ScriptContext, Terminal};
use crate::database::BatchDatabase; use crate::database::BatchDatabase;
use crate::types::KeychainKind;
use crate::wallet::Wallet; use crate::wallet::Wallet;
/// Structure that contains the export of a wallet /// Structure that contains the export of a wallet
@ -117,8 +118,12 @@ impl WalletExport {
include_blockheight: bool, include_blockheight: bool,
) -> Result<Self, &'static str> { ) -> Result<Self, &'static str> {
let descriptor = wallet let descriptor = wallet
.descriptor .get_descriptor_for_keychain(KeychainKind::External)
.to_string_with_secret(&wallet.signers.as_key_map(wallet.secp_ctx())); .to_string_with_secret(
&wallet
.get_signers(KeychainKind::External)
.as_key_map(wallet.secp_ctx()),
);
let descriptor = remove_checksum(descriptor); let descriptor = remove_checksum(descriptor);
Self::is_compatible_with_core(&descriptor)?; Self::is_compatible_with_core(&descriptor)?;
@ -142,12 +147,24 @@ impl WalletExport {
blockheight, blockheight,
}; };
let desc_to_string = |d: &Descriptor<DescriptorPublicKey>| { let change_descriptor = match wallet
let descriptor = .public_descriptor(KeychainKind::Internal)
d.to_string_with_secret(&wallet.change_signers.as_key_map(wallet.secp_ctx())); .map_err(|_| "Invalid change descriptor")?
remove_checksum(descriptor) .is_some()
{
false => None,
true => {
let descriptor = wallet
.get_descriptor_for_keychain(KeychainKind::Internal)
.to_string_with_secret(
&wallet
.get_signers(KeychainKind::Internal)
.as_key_map(wallet.secp_ctx()),
);
Some(remove_checksum(descriptor))
}
}; };
if export.change_descriptor() != wallet.change_descriptor.as_ref().map(desc_to_string) { if export.change_descriptor() != change_descriptor {
return Err("Incompatible change descriptor"); return Err("Incompatible change descriptor");
} }

View File

@ -468,6 +468,29 @@ where
signers.add_external(signer.id(&self.secp), ordering, signer); signers.add_external(signer.id(&self.secp), ordering, signer);
} }
/// Get the signers
///
/// ## Example
///
/// ```
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::bitcoin::Network;
/// # use bdk::database::MemoryDatabase;
/// let wallet = Wallet::new("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet, MemoryDatabase::new())?;
/// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
/// println!("secret_key: {}", secret_key);
/// }
///
/// Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn get_signers(&self, keychain: KeychainKind) -> Arc<SignersContainer> {
match keychain {
KeychainKind::External => Arc::clone(&self.signers),
KeychainKind::Internal => Arc::clone(&self.change_signers),
}
}
/// Add an address validator /// Add an address validator
/// ///
/// See [the `address_validator` module](address_validator) for an example. /// See [the `address_validator` module](address_validator) for an example.
@ -475,6 +498,11 @@ where
self.address_validators.push(validator); self.address_validators.push(validator);
} }
/// Get the address validators
pub fn get_address_validators(&self) -> &[Arc<dyn AddressValidator>] {
&self.address_validators
}
/// Start building a transaction. /// Start building a transaction.
/// ///
/// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction. /// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction.
@ -1568,6 +1596,18 @@ where
Ok(()) Ok(())
} }
/// Return the checksum of the public descriptor associated to `keychain`
///
/// Internally calls [`Self::get_descriptor_for_keychain`] to fetch the right descriptor
pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String {
self.get_descriptor_for_keychain(keychain)
.to_string()
.splitn(2, '#')
.next()
.unwrap()
.to_string()
}
} }
/// Return a fake wallet that appears to be funded for testing. /// Return a fake wallet that appears to be funded for testing.