diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dabc409a..642bcbd03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - 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] diff --git a/src/descriptor/derived.rs b/src/descriptor/derived.rs index d503b66fc..b6556a0b6 100644 --- a/src/descriptor/derived.rs +++ b/src/descriptor/derived.rs @@ -10,6 +10,41 @@ // licenses. //! 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` 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>(()) +//! ``` +//! +//! [`Wallet::get_signers`]: crate::wallet::Wallet::get_signers use std::cmp::Ordering; use std::fmt; @@ -19,10 +54,7 @@ use std::ops::Deref; use bitcoin::hashes::hash160; use bitcoin::PublicKey; -pub use miniscript::{ - descriptor::KeyMap, descriptor::Wildcard, Descriptor, DescriptorPublicKey, Legacy, Miniscript, - ScriptContext, Segwitv0, -}; +use miniscript::{descriptor::Wildcard, Descriptor, DescriptorPublicKey}; use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk}; use crate::wallet::utils::SecpCtx; @@ -119,14 +151,19 @@ impl<'s> ToPublicKey for DerivedDescriptorKey<'s> { } } -pub(crate) trait AsDerived { - // Derive a descriptor and transform all of its keys to `DerivedDescriptorKey` +/// Utilities to derive descriptors +/// +/// 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) -> Descriptor>; - // Transform the keys into `DerivedDescriptorKey`. - // - // Panics if the descriptor is not "fixed", i.e. if it's derivable + /// Transform the keys into `DerivedDescriptorKey`. + /// + /// Panics if the descriptor is not "fixed", i.e. if it's derivable fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor>; } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 14045f42f..5b8a3babc 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -21,16 +21,17 @@ use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerpr use bitcoin::util::psbt; use bitcoin::{Network, PublicKey, Script, TxOut}; -use miniscript::descriptor::{ - DescriptorPublicKey, DescriptorType, DescriptorXKey, InnerXKey, Wildcard, +use miniscript::descriptor::{DescriptorType, InnerXKey}; +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 crate::descriptor::policy::BuildSatisfaction; pub mod checksum; -pub(crate) mod derived; +pub mod derived; #[doc(hidden)] pub mod dsl; pub mod error; @@ -38,8 +39,7 @@ pub mod policy; pub mod template; pub use self::checksum::get_checksum; -use self::derived::AsDerived; -pub use self::derived::DerivedDescriptorKey; +pub use self::derived::{AsDerived, DerivedDescriptorKey}; pub use self::error::Error as DescriptorError; pub use self::policy::Policy; use self::template::DescriptorTemplateOut; diff --git a/src/wallet/export.rs b/src/wallet/export.rs index f531989e0..69b8c63fb 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -64,9 +64,10 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; use miniscript::descriptor::{ShInner, WshInner}; -use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal}; +use miniscript::{Descriptor, ScriptContext, Terminal}; use crate::database::BatchDatabase; +use crate::types::KeychainKind; use crate::wallet::Wallet; /// Structure that contains the export of a wallet @@ -117,8 +118,12 @@ impl WalletExport { include_blockheight: bool, ) -> Result { let descriptor = wallet - .descriptor - .to_string_with_secret(&wallet.signers.as_key_map(wallet.secp_ctx())); + .get_descriptor_for_keychain(KeychainKind::External) + .to_string_with_secret( + &wallet + .get_signers(KeychainKind::External) + .as_key_map(wallet.secp_ctx()), + ); let descriptor = remove_checksum(descriptor); Self::is_compatible_with_core(&descriptor)?; @@ -142,12 +147,24 @@ impl WalletExport { blockheight, }; - let desc_to_string = |d: &Descriptor| { - let descriptor = - d.to_string_with_secret(&wallet.change_signers.as_key_map(wallet.secp_ctx())); - remove_checksum(descriptor) + let change_descriptor = match wallet + .public_descriptor(KeychainKind::Internal) + .map_err(|_| "Invalid change 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"); } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 1eb697dd8..016c0cacc 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -468,6 +468,29 @@ where 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>(()) + /// ``` + pub fn get_signers(&self, keychain: KeychainKind) -> Arc { + match keychain { + KeychainKind::External => Arc::clone(&self.signers), + KeychainKind::Internal => Arc::clone(&self.change_signers), + } + } + /// Add an address validator /// /// See [the `address_validator` module](address_validator) for an example. @@ -475,6 +498,11 @@ where self.address_validators.push(validator); } + /// Get the address validators + pub fn get_address_validators(&self) -> &[Arc] { + &self.address_validators + } + /// Start building a transaction. /// /// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction. @@ -1568,6 +1596,18 @@ where 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.