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:
commit
213f18f7b7
@ -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]
|
||||||
|
|
||||||
|
@ -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>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user