From c93cd1414a5b007541f717e6f1da185d7bf134e8 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Tue, 22 Sep 2020 16:12:09 +0200 Subject: [PATCH] [descriptor] Add descriptor templates, add `DerivableKey` --- Cargo.toml | 2 +- src/descriptor/dsl.rs | 3 +- src/descriptor/mod.rs | 6 +- src/descriptor/template.rs | 424 +++++++++++++++++++++++++++++++++++++ src/keys/bip39.rs | 38 ++-- src/keys/mod.rs | 123 ++++++++--- src/lib.rs | 1 + 7 files changed, 553 insertions(+), 44 deletions(-) create mode 100644 src/descriptor/template.rs diff --git a/Cargo.toml b/Cargo.toml index 8d18e379..3c633af5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,6 @@ members = ["macros", "testutils", "testutils-macros"] # Generate docs with nightly to add the "features required" badge # https://stackoverflow.com/questions/61417452/how-to-get-a-feature-requirement-tag-in-the-documentation-generated-by-cargo-do [package.metadata.docs.rs] -features = ["compiler", "electrum", "esplora", "compact_filters", "key-value-db"] +features = ["compiler", "electrum", "esplora", "compact_filters", "key-value-db", "all-keys"] # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] diff --git a/src/descriptor/dsl.rs b/src/descriptor/dsl.rs index 7fb28007..d53b2a4d 100644 --- a/src/descriptor/dsl.rs +++ b/src/descriptor/dsl.rs @@ -37,6 +37,7 @@ macro_rules! impl_top_level_sh { #[macro_export] macro_rules! impl_top_level_pk { ( $descriptor_variant:ident, $ctx:ty, $key:expr ) => {{ + #[allow(unused_imports)] use $crate::keys::{DescriptorKey, ToDescriptorKey}; $key.to_descriptor_key() @@ -254,7 +255,7 @@ macro_rules! descriptor { $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key) }); ( sh ( wpkh ( $key:expr ) ) ) => ({ - $crate::descriptor!(shwpkh ($( $minisc )*)) + $crate::descriptor!(shwpkh ( $key )) }); ( shwpkh ( $key:expr ) ) => ({ $crate::impl_top_level_pk!(ShWpkh, $crate::miniscript::Segwitv0, $key) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index cd76aa38..379640fc 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -37,15 +37,17 @@ use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint}; use bitcoin::util::psbt; use bitcoin::{Network, PublicKey, Script, TxOut}; -use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey, KeyMap}; +use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey}; pub use miniscript::{ - Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, ToPublicKey, + descriptor::KeyMap, Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, + Terminal, ToPublicKey, }; pub mod checksum; mod dsl; pub mod error; pub mod policy; +pub mod template; pub use self::checksum::get_checksum; use self::error::Error; diff --git a/src/descriptor/template.rs b/src/descriptor/template.rs new file mode 100644 index 00000000..b167ca45 --- /dev/null +++ b/src/descriptor/template.rs @@ -0,0 +1,424 @@ +// Magical Bitcoin Library +// Written in 2020 by +// Alekos Filini +// +// Copyright (c) 2020 Magical Bitcoin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Descriptor templates +//! +//! This module contains the definition of various common script templates that are ready to be +//! used. See the documentation of each template for an example. + +use bitcoin::util::bip32; +use bitcoin::Network; + +use miniscript::{Legacy, Segwitv0}; + +use super::{ExtendedDescriptor, KeyMap, ToWalletDescriptor}; +use crate::keys::{DerivableKey, KeyError, ToDescriptorKey, ValidNetworks}; +use crate::{descriptor, ScriptType}; + +/// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others +pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks); + +/// Trait for descriptor templates that can be built into a full descriptor +/// +/// Since [`ToWalletDescriptor`] is implemented for any [`DescriptorTemplate`], they can also be +/// passed directly to the [`Wallet`](crate::Wallet) constructor. +/// +/// ## Example +/// +/// ``` +/// use bdk::keys::{ToDescriptorKey, KeyError}; +/// use bdk::template::{DescriptorTemplate, DescriptorTemplateOut}; +/// use bdk::miniscript::Legacy; +/// +/// struct MyP2PKH>(K); +/// +/// impl> DescriptorTemplate for MyP2PKH { +/// fn build(self) -> Result { +/// Ok(bdk::descriptor!(pkh ( self.0 ) )?) +/// } +/// } +/// ``` +pub trait DescriptorTemplate { + fn build(self) -> Result; +} + +/// Turns a [`DescriptorTemplate`] into a valid wallet descriptor by calling its +/// [`build`](DescriptorTemplate::build) method +impl ToWalletDescriptor for T { + fn to_wallet_descriptor( + self, + network: Network, + ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> { + Ok(self.build()?.to_wallet_descriptor(network)?) + } +} + +/// P2PKH template. Expands to a descriptor `pkh(key)` +/// +/// ## Example +/// +/// ``` +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::P2PKH; +/// +/// let key = bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline(P2PKH(key), None, Network::Testnet, MemoryDatabase::default())?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"); +/// # Ok::<_, Box>(()) +/// ``` +pub struct P2PKH>(pub K); + +impl> DescriptorTemplate for P2PKH { + fn build(self) -> Result { + Ok(descriptor!(pkh(self.0))?) + } +} + +/// P2WPKH-P2SH template. Expands to a descriptor `sh(wpkh(key))` +/// +/// ## Example +/// +/// ``` +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::P2WPKH_P2SH; +/// +/// let key = bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline(P2WPKH_P2SH(key), None, Network::Testnet, MemoryDatabase::default())?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"); +/// # Ok::<_, Box>(()) +/// ``` +#[allow(non_camel_case_types)] +pub struct P2WPKH_P2SH>(pub K); + +impl> DescriptorTemplate for P2WPKH_P2SH { + fn build(self) -> Result { + Ok(descriptor!(sh(wpkh(self.0)))?) + } +} + +/// P2WPKH template. Expands to a descriptor `wpkh(key)` +/// +/// ## Example +/// +/// ``` +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::P2WPKH; +/// +/// let key = bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline(P2WPKH(key), None, Network::Testnet, MemoryDatabase::default())?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"); +/// # Ok::<_, Box>(()) +/// ``` +pub struct P2WPKH>(pub K); + +impl> DescriptorTemplate for P2WPKH { + fn build(self) -> Result { + Ok(descriptor!(wpkh(self.0))?) + } +} + +/// BIP44 template. Expands to `pkh(key/44'/0'/0'/{0,1}/*)` +/// +/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`). +/// +/// See [`BIP44Public`] for a template that can work with a `xpub`/`tpub`. +/// +/// ## Example +/// +/// ``` +/// # use std::str::FromStr; +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet, ScriptType}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::BIP44; +/// +/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline( +/// BIP44(key.clone(), ScriptType::External), +/// Some(BIP44(key, ScriptType::Internal)), +/// Network::Testnet, +/// MemoryDatabase::default() +/// )?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); +/// assert_eq!(wallet.public_descriptor(ScriptType::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)"); +/// # Ok::<_, Box>(()) +/// ``` +pub struct BIP44>(pub K, pub ScriptType); + +impl> DescriptorTemplate for BIP44 { + fn build(self) -> Result { + Ok(P2PKH(legacy::make_bipxx_private(44, self.0, self.1)?).build()?) + } +} + +/// BIP44 public template. Expands to `pkh(key/{0,1}/*)` +/// +/// This assumes that the key used has already been derived with `m/44'/0'/0'`. +/// +/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. +/// +/// See [`BIP44`] for a template that does the full derivation, but requires private data +/// for the key. +/// +/// ## Example +/// +/// ``` +/// # use std::str::FromStr; +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet, ScriptType}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::BIP44Public; +/// +/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; +/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline( +/// BIP44Public(key.clone(), fingerprint, ScriptType::External), +/// Some(BIP44Public(key, fingerprint, ScriptType::Internal)), +/// Network::Testnet, +/// MemoryDatabase::default() +/// )?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); +/// assert_eq!(wallet.public_descriptor(ScriptType::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)"); +/// # Ok::<_, Box>(()) +/// ``` +pub struct BIP44Public>(pub K, pub bip32::Fingerprint, pub ScriptType); + +impl> DescriptorTemplate for BIP44Public { + fn build(self) -> Result { + Ok(P2PKH(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build()?) + } +} + +/// BIP49 template. Expands to `sh(wpkh(key/49'/0'/0'/{0,1}/*))` +/// +/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`). +/// +/// See [`BIP49Public`] for a template that can work with a `xpub`/`tpub`. +/// +/// ## Example +/// +/// ``` +/// # use std::str::FromStr; +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet, ScriptType}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::BIP49; +/// +/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline( +/// BIP49(key.clone(), ScriptType::External), +/// Some(BIP49(key, ScriptType::Internal)), +/// Network::Testnet, +/// MemoryDatabase::default() +/// )?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); +/// assert_eq!(wallet.public_descriptor(ScriptType::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))"); +/// # Ok::<_, Box>(()) +/// ``` +pub struct BIP49>(pub K, pub ScriptType); + +impl> DescriptorTemplate for BIP49 { + fn build(self) -> Result { + Ok(P2WPKH_P2SH(segwit_v0::make_bipxx_private(49, self.0, self.1)?).build()?) + } +} + +/// BIP49 public template. Expands to `sh(wpkh(key/{0,1}/*))` +/// +/// This assumes that the key used has already been derived with `m/49'/0'/0'`. +/// +/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. +/// +/// See [`BIP49`] for a template that does the full derivation, but requires private data +/// for the key. +/// +/// ## Example +/// +/// ``` +/// # use std::str::FromStr; +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet, ScriptType}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::BIP49Public; +/// +/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; +/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline( +/// BIP49Public(key.clone(), fingerprint, ScriptType::External), +/// Some(BIP49Public(key, fingerprint, ScriptType::Internal)), +/// Network::Testnet, +/// MemoryDatabase::default() +/// )?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); +/// assert_eq!(wallet.public_descriptor(ScriptType::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))"); +/// # Ok::<_, Box>(()) +/// ``` +pub struct BIP49Public>(pub K, pub bip32::Fingerprint, pub ScriptType); + +impl> DescriptorTemplate for BIP49Public { + fn build(self) -> Result { + Ok(P2WPKH_P2SH(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build()?) + } +} + +/// BIP84 template. Expands to `wpkh(key/84'/0'/0'/{0,1}/*)` +/// +/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`). +/// +/// See [`BIP84Public`] for a template that can work with a `xpub`/`tpub`. +/// +/// ## Example +/// +/// ``` +/// # use std::str::FromStr; +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet, ScriptType}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::BIP84; +/// +/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline( +/// BIP84(key.clone(), ScriptType::External), +/// Some(BIP84(key, ScriptType::Internal)), +/// Network::Testnet, +/// MemoryDatabase::default() +/// )?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); +/// assert_eq!(wallet.public_descriptor(ScriptType::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)"); +/// # Ok::<_, Box>(()) +/// ``` +pub struct BIP84>(pub K, pub ScriptType); + +impl> DescriptorTemplate for BIP84 { + fn build(self) -> Result { + Ok(P2WPKH(segwit_v0::make_bipxx_private(84, self.0, self.1)?).build()?) + } +} + +/// BIP84 public template. Expands to `wpkh(key/{0,1}/*)` +/// +/// This assumes that the key used has already been derived with `m/84'/0'/0'`. +/// +/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. +/// +/// See [`BIP84`] for a template that does the full derivation, but requires private data +/// for the key. +/// +/// ## Example +/// +/// ``` +/// # use std::str::FromStr; +/// # use bdk::bitcoin::{PrivateKey, Network}; +/// # use bdk::{Wallet, OfflineWallet, ScriptType}; +/// # use bdk::database::MemoryDatabase; +/// use bdk::template::BIP84Public; +/// +/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; +/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; +/// let wallet: OfflineWallet<_> = Wallet::new_offline( +/// BIP84Public(key.clone(), fingerprint, ScriptType::External), +/// Some(BIP84Public(key, fingerprint, ScriptType::Internal)), +/// Network::Testnet, +/// MemoryDatabase::default() +/// )?; +/// +/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); +/// assert_eq!(wallet.public_descriptor(ScriptType::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)"); +/// # Ok::<_, Box>(()) +/// ``` +pub struct BIP84Public>(pub K, pub bip32::Fingerprint, pub ScriptType); + +impl> DescriptorTemplate for BIP84Public { + fn build(self) -> Result { + Ok(P2WPKH(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build()?) + } +} + +macro_rules! expand_make_bipxx { + ( $mod_name:ident, $ctx:ty ) => { + mod $mod_name { + use super::*; + + pub(super) fn make_bipxx_private>( + bip: u32, + key: K, + script_type: ScriptType, + ) -> Result, KeyError> { + let mut derivation_path = Vec::with_capacity(4); + derivation_path.push(bip32::ChildNumber::from_hardened_idx(bip)?); + derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?); + derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?); + + match script_type { + ScriptType::External => { + derivation_path.push(bip32::ChildNumber::from_normal_idx(0)?) + } + ScriptType::Internal => { + derivation_path.push(bip32::ChildNumber::from_normal_idx(1)?) + } + }; + + let derivation_path: bip32::DerivationPath = derivation_path.into(); + + Ok((key, derivation_path)) + } + pub(super) fn make_bipxx_public>( + bip: u32, + key: K, + parent_fingerprint: bip32::Fingerprint, + script_type: ScriptType, + ) -> Result, KeyError> { + let derivation_path: bip32::DerivationPath = match script_type { + ScriptType::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(), + ScriptType::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(), + }; + + let mut source_path = Vec::with_capacity(3); + source_path.push(bip32::ChildNumber::from_hardened_idx(bip)?); + source_path.push(bip32::ChildNumber::from_hardened_idx(0)?); + source_path.push(bip32::ChildNumber::from_hardened_idx(0)?); + let source_path: bip32::DerivationPath = source_path.into(); + + Ok((key, (parent_fingerprint, source_path), derivation_path)) + } + } + }; +} + +expand_make_bipxx!(legacy, Legacy); +expand_make_bipxx!(segwit_v0, Segwitv0); diff --git a/src/keys/bip39.rs b/src/keys/bip39.rs index d6e78767..7a1a0c34 100644 --- a/src/keys/bip39.rs +++ b/src/keys/bip39.rs @@ -34,33 +34,45 @@ use miniscript::ScriptContext; use bip39::{Mnemonic, Seed}; -use super::{any_network, DescriptorKey, KeyError, ToDescriptorKey}; +use super::{any_network, DerivableKey, DescriptorKey, KeyError}; pub type MnemonicWithPassphrase = (Mnemonic, Option); -impl ToDescriptorKey for (Seed, bip32::DerivationPath) { - fn to_descriptor_key(self) -> Result, KeyError> { - let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.0.as_bytes())?; - let descriptor_key = (xprv, self.1).to_descriptor_key()?; +impl DerivableKey for Seed { + fn add_metadata( + self, + source: Option<(bip32::Fingerprint, bip32::DerivationPath)>, + derivation_path: bip32::DerivationPath, + ) -> Result, KeyError> { + let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.as_bytes())?; + let descriptor_key = xprv.add_metadata(source, derivation_path)?; // here we must choose one network to build the xpub, but since the bip39 standard doesn't - // encode the network the xpub we create is actually valid everywhere. so we override the + // encode the network, the xpub we create is actually valid everywhere. so we override the // valid networks with `any_network()`. Ok(descriptor_key.override_valid_networks(any_network())) } } -impl ToDescriptorKey for (MnemonicWithPassphrase, bip32::DerivationPath) { - fn to_descriptor_key(self) -> Result, KeyError> { - let (mnemonic, passphrase) = self.0; +impl DerivableKey for MnemonicWithPassphrase { + fn add_metadata( + self, + source: Option<(bip32::Fingerprint, bip32::DerivationPath)>, + derivation_path: bip32::DerivationPath, + ) -> Result, KeyError> { + let (mnemonic, passphrase) = self; let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or("")); - (seed, self.1).to_descriptor_key() + seed.add_metadata(source, derivation_path) } } -impl ToDescriptorKey for (Mnemonic, bip32::DerivationPath) { - fn to_descriptor_key(self) -> Result, KeyError> { - ((self.0, None), self.1).to_descriptor_key() +impl DerivableKey for Mnemonic { + fn add_metadata( + self, + source: Option<(bip32::Fingerprint, bip32::DerivationPath)>, + derivation_path: bip32::DerivationPath, + ) -> Result, KeyError> { + (self, None).add_metadata(source, derivation_path) } } diff --git a/src/keys/mod.rs b/src/keys/mod.rs index 397822e8..cb153e9d 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -31,7 +31,8 @@ use std::marker::PhantomData; use bitcoin::util::bip32; use bitcoin::{Network, PrivateKey, PublicKey}; -use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap}; +pub use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey}; +use miniscript::descriptor::{DescriptorXKey, KeyMap}; pub use miniscript::ScriptContext; use miniscript::{Miniscript, Terminal}; @@ -167,6 +168,10 @@ impl ExtScriptContext for Ctx { /// single `Ctx`), the "specialized" trait can be implemented to make the compiler handle the type /// checking. /// +/// Keys also have control over the networks they support: constructing the return object with +/// [`DescriptorKey::from_public`] or [`DescriptorKey::from_secret`] allows to specify a set of +/// [`ValidNetworks`]. +/// /// ## Examples /// /// Key type valid in any context: @@ -187,6 +192,24 @@ impl ExtScriptContext for Ctx { /// } /// ``` /// +/// Key type that is only valid on mainnet: +/// +/// ``` +/// use bdk::bitcoin::PublicKey; +/// +/// use bdk::keys::{mainnet_network, ScriptContext, ToDescriptorKey, DescriptorKey, DescriptorPublicKey, KeyError}; +/// +/// pub struct MyKeyType { +/// pubkey: PublicKey, +/// } +/// +/// impl ToDescriptorKey for MyKeyType { +/// fn to_descriptor_key(self) -> Result, KeyError> { +/// Ok(DescriptorKey::from_public(DescriptorPublicKey::PubKey(self.pubkey), mainnet_network())) +/// } +/// } +/// ``` +/// /// Key type that internally encodes in which context it's valid. The context is checked at runtime: /// /// ``` @@ -246,6 +269,77 @@ pub trait ToDescriptorKey: Sized { fn to_descriptor_key(self) -> Result, KeyError>; } +/// Trait for keys that can be derived. +/// +/// When extra metadata are provided, a [`DerivableKey`] can be transofrmed into a +/// [`DescriptorKey`]: the trait [`ToDescriptorKey`] is automatically implemented +/// for `(DerivableKey, DerivationPath)` and +/// `(DerivableKey, (Fingerprint, DerivationPath), DerivationPath)` tuples. +/// +/// For key types that don't encode any indication about the path to use (like bip39), it's +/// generally recommended to implemented this trait instead of [`ToDescriptorKey`]. The same +/// rules regarding script context and valid networks apply. +/// +/// [`DerivationPath`]: (bip32::DerivationPath) +pub trait DerivableKey { + /// Add a extra metadata, consume `self` and turn it into a [`DescriptorKey`] + fn add_metadata( + self, + source: Option<(bip32::Fingerprint, bip32::DerivationPath)>, + derivation_path: bip32::DerivationPath, + ) -> Result, KeyError>; +} + +impl DerivableKey for bip32::ExtendedPubKey { + fn add_metadata( + self, + source: Option<(bip32::Fingerprint, bip32::DerivationPath)>, + derivation_path: bip32::DerivationPath, + ) -> Result, KeyError> { + DescriptorPublicKey::XPub(DescriptorXKey { + source, + xkey: self, + derivation_path, + is_wildcard: true, + }) + .to_descriptor_key() + } +} + +impl DerivableKey for bip32::ExtendedPrivKey { + fn add_metadata( + self, + source: Option<(bip32::Fingerprint, bip32::DerivationPath)>, + derivation_path: bip32::DerivationPath, + ) -> Result, KeyError> { + DescriptorSecretKey::XPrv(DescriptorXKey { + source, + xkey: self, + derivation_path, + is_wildcard: true, + }) + .to_descriptor_key() + } +} + +impl> ToDescriptorKey for (T, bip32::DerivationPath) { + fn to_descriptor_key(self) -> Result, KeyError> { + self.0.add_metadata(None, self.1) + } +} + +impl> ToDescriptorKey + for ( + T, + (bip32::Fingerprint, bip32::DerivationPath), + bip32::DerivationPath, + ) +{ + fn to_descriptor_key(self) -> Result, KeyError> { + self.0.add_metadata(Some(self.1), self.2) + } +} + // Used internally by `bdk::fragment!` to build `pk_k()` fragments #[doc(hidden)] pub fn make_pk, Ctx: ScriptContext>( @@ -320,19 +414,6 @@ impl ToDescriptorKey for PublicKey { } } -/// This assumes that "is_wildcard" is true, since this is generally the way extended keys are used -impl ToDescriptorKey for (bip32::ExtendedPubKey, bip32::DerivationPath) { - fn to_descriptor_key(self) -> Result, KeyError> { - DescriptorPublicKey::XPub(DescriptorXKey { - source: None, - xkey: self.0, - derivation_path: self.1, - is_wildcard: true, - }) - .to_descriptor_key() - } -} - impl ToDescriptorKey for DescriptorSecretKey { fn to_descriptor_key(self) -> Result, KeyError> { let networks = match self { @@ -355,19 +436,7 @@ impl ToDescriptorKey for PrivateKey { } } -/// This assumes that "is_wildcard" is true, since this is generally the way extended keys are used -impl ToDescriptorKey for (bip32::ExtendedPrivKey, bip32::DerivationPath) { - fn to_descriptor_key(self) -> Result, KeyError> { - DescriptorSecretKey::XPrv(DescriptorXKey { - source: None, - xkey: self.0, - derivation_path: self.1, - is_wildcard: true, - }) - .to_descriptor_key() - } -} - +/// Errors thrown while working with [`keys`](crate::keys) #[derive(Debug)] pub enum KeyError { InvalidScriptContext, diff --git a/src/lib.rs b/src/lib.rs index 51500454..8df950dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,7 @@ pub(crate) mod psbt; pub(crate) mod types; pub mod wallet; +pub use descriptor::template; pub use descriptor::HDKeyPaths; pub use error::Error; pub use types::*;