// Bitcoin Dev Kit // Written in 2020 by Alekos Filini // // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license // , at your option. // You may not use this file except in accordance with one or both of these // licenses. //! 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, Tap}; use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap}; use crate::descriptor::DescriptorError; use crate::keys::{DerivableKey, IntoDescriptorKey, ValidNetworks}; use crate::wallet::utils::SecpCtx; use crate::{descriptor, KeychainKind}; /// 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 [`IntoWalletDescriptor`] is implemented for any [`DescriptorTemplate`], they can also be /// passed directly to the [`Wallet`](crate::Wallet) constructor. /// /// ## Example /// /// ``` /// use bdk::descriptor::error::Error as DescriptorError; /// use bdk::keys::{IntoDescriptorKey, KeyError}; /// use bdk::miniscript::Legacy; /// use bdk::template::{DescriptorTemplate, DescriptorTemplateOut}; /// use bitcoin::Network; /// /// struct MyP2PKH>(K); /// /// impl> DescriptorTemplate for MyP2PKH { /// fn build(self, network: Network) -> Result { /// Ok(bdk::descriptor!(pkh(self.0))?) /// } /// } /// ``` pub trait DescriptorTemplate { /// Build the complete descriptor fn build(self, network: Network) -> Result; } /// Turns a [`DescriptorTemplate`] into a valid wallet descriptor by calling its /// [`build`](DescriptorTemplate::build) method impl IntoWalletDescriptor for T { fn into_wallet_descriptor( self, secp: &SecpCtx, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { self.build(network)?.into_wallet_descriptor(secp, network) } } /// P2PKH template. Expands to a descriptor `pkh(key)` /// /// ## Example /// /// ``` /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::P2Pkh; /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// let wallet = Wallet::new( /// P2Pkh(key), /// None, /// Network::Testnet, /// MemoryDatabase::default(), /// )?; /// /// assert_eq!( /// wallet.get_address(New)?.to_string(), /// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT" /// ); /// # Ok::<_, Box>(()) /// ``` pub struct P2Pkh>(pub K); impl> DescriptorTemplate for P2Pkh { fn build(self, _network: Network) -> Result { descriptor!(pkh(self.0)) } } /// P2WPKH-P2SH template. Expands to a descriptor `sh(wpkh(key))` /// /// ## Example /// /// ``` /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::P2Wpkh_P2Sh; /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// let wallet = Wallet::new( /// P2Wpkh_P2Sh(key), /// None, /// Network::Testnet, /// MemoryDatabase::default(), /// )?; /// /// assert_eq!( /// wallet.get_address(New)?.to_string(), /// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5" /// ); /// # Ok::<_, Box>(()) /// ``` #[allow(non_camel_case_types)] pub struct P2Wpkh_P2Sh>(pub K); impl> DescriptorTemplate for P2Wpkh_P2Sh { fn build(self, _network: Network) -> Result { descriptor!(sh(wpkh(self.0))) } } /// P2WPKH template. Expands to a descriptor `wpkh(key)` /// /// ## Example /// /// ``` /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::P2Wpkh; /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// let wallet = Wallet::new( /// P2Wpkh(key), /// None, /// Network::Testnet, /// MemoryDatabase::default(), /// )?; /// /// assert_eq!( /// wallet.get_address(New)?.to_string(), /// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd" /// ); /// # Ok::<_, Box>(()) /// ``` pub struct P2Wpkh>(pub K); impl> DescriptorTemplate for P2Wpkh { fn build(self, _network: Network) -> Result { descriptor!(wpkh(self.0)) } } /// P2TR template. Expands to a descriptor `tr(key)` /// /// ## Example /// /// ``` /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::Wallet; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::P2TR; /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// let mut wallet = Wallet::new(P2TR(key), None, Network::Testnet, MemoryDatabase::default())?; /// /// assert_eq!( /// wallet.get_address(New)?.to_string(), /// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46" /// ); /// # Ok::<_, Box>(()) /// ``` pub struct P2TR>(pub K); impl> DescriptorTemplate for P2TR { fn build(self, _network: Network) -> Result { descriptor!(tr(self.0)) } } /// BIP44 template. Expands to `pkh(key/44'/{0,1}'/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, KeychainKind}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip44; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let wallet = Wallet::new( /// Bip44(key.clone(), KeychainKind::External), /// Some(Bip44(key, KeychainKind::Internal)), /// Network::Testnet, /// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New)?.to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip44>(pub K, pub KeychainKind); impl> DescriptorTemplate for Bip44 { fn build(self, network: Network) -> Result { P2Pkh(legacy::make_bipxx_private(44, self.0, self.1, network)?).build(network) } } /// BIP44 public template. Expands to `pkh(key/{0,1}/*)` /// /// This assumes that the key used has already been derived with `m/44'/0'/0'` for Mainnet or `m/44'/1'/0'` for Testnet. /// /// 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, KeychainKind}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip44Public; /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let wallet = Wallet::new( /// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, /// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip44Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); impl> DescriptorTemplate for Bip44Public { fn build(self, network: Network) -> Result { P2Pkh(legacy::make_bipxx_public( 44, self.0, self.1, self.2, network, )?) .build(network) } } /// BIP49 template. Expands to `sh(wpkh(key/49'/{0,1}'/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, KeychainKind}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip49; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let wallet = Wallet::new( /// Bip49(key.clone(), KeychainKind::External), /// Some(Bip49(key, KeychainKind::Internal)), /// Network::Testnet, /// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New)?.to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip49>(pub K, pub KeychainKind); impl> DescriptorTemplate for Bip49 { fn build(self, network: Network) -> Result { P2Wpkh_P2Sh(segwit_v0::make_bipxx_private(49, self.0, self.1, network)?).build(network) } } /// 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'` for Mainnet or `m/49'/1'/0'` for Testnet. /// /// 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, KeychainKind}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip49Public; /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let wallet = Wallet::new( /// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, /// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip49Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); impl> DescriptorTemplate for Bip49Public { fn build(self, network: Network) -> Result { P2Wpkh_P2Sh(segwit_v0::make_bipxx_public( 49, self.0, self.1, self.2, network, )?) .build(network) } } /// BIP84 template. Expands to `wpkh(key/84'/{0,1}'/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, KeychainKind}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip84; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let wallet = Wallet::new( /// Bip84(key.clone(), KeychainKind::External), /// Some(Bip84(key, KeychainKind::Internal)), /// Network::Testnet, /// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip84>(pub K, pub KeychainKind); impl> DescriptorTemplate for Bip84 { fn build(self, network: Network) -> Result { P2Wpkh(segwit_v0::make_bipxx_private(84, self.0, self.1, network)?).build(network) } } /// BIP84 public template. Expands to `wpkh(key/{0,1}/*)` /// /// This assumes that the key used has already been derived with `m/84'/0'/0'` for Mainnet or `m/84'/1'/0'` for Testnet. /// /// 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, KeychainKind}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip84Public; /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let wallet = Wallet::new( /// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, /// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip84Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); impl> DescriptorTemplate for Bip84Public { fn build(self, network: Network) -> Result { P2Wpkh(segwit_v0::make_bipxx_public( 84, self.0, self.1, self.2, network, )?) .build(network) } } /// BIP86 template. Expands to `tr(key/86'/{0,1}'/0'/{0,1}/*)` /// /// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`). /// /// See [`Bip86Public`] for a template that can work with a `xpub`/`tpub`. /// /// ## Example /// /// ``` /// # use std::str::FromStr; /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet, KeychainKind}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip86; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new( /// Bip86(key.clone(), KeychainKind::External), /// Some(Bip86(key, KeychainKind::Internal)), /// Network::Testnet, /// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New)?.to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip86>(pub K, pub KeychainKind); impl> DescriptorTemplate for Bip86 { fn build(self, network: Network) -> Result { P2TR(segwit_v1::make_bipxx_private(86, self.0, self.1, network)?).build(network) } } /// BIP86 public template. Expands to `tr(key/{0,1}/*)` /// /// This assumes that the key used has already been derived with `m/86'/0'/0'` for Mainnet or `m/86'/1'/0'` for Testnet. /// /// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. /// /// See [`Bip86`] 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, KeychainKind}; /// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip86Public; /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new( /// Bip86Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip86Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, /// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New)?.to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip86Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); impl> DescriptorTemplate for Bip86Public { fn build(self, network: Network) -> Result { P2TR(segwit_v1::make_bipxx_public( 86, self.0, self.1, self.2, network, )?) .build(network) } } 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, keychain: KeychainKind, network: Network, ) -> Result, DescriptorError> { let mut derivation_path = Vec::with_capacity(4); derivation_path.push(bip32::ChildNumber::from_hardened_idx(bip)?); match network { Network::Bitcoin => { derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?); } _ => { derivation_path.push(bip32::ChildNumber::from_hardened_idx(1)?); } } derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?); match keychain { KeychainKind::External => { derivation_path.push(bip32::ChildNumber::from_normal_idx(0)?) } KeychainKind::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, keychain: KeychainKind, network: Network, ) -> Result, DescriptorError> { let derivation_path: bip32::DerivationPath = match keychain { KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(), KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(), }; let source_path = bip32::DerivationPath::from(vec![ bip32::ChildNumber::from_hardened_idx(bip)?, match network { Network::Bitcoin => bip32::ChildNumber::from_hardened_idx(0)?, _ => bip32::ChildNumber::from_hardened_idx(1)?, }, bip32::ChildNumber::from_hardened_idx(0)?, ]); Ok((key, (parent_fingerprint, source_path), derivation_path)) } } }; } expand_make_bipxx!(legacy, Legacy); expand_make_bipxx!(segwit_v0, Segwitv0); expand_make_bipxx!(segwit_v1, Tap); #[cfg(test)] mod test { // test existing descriptor templates, make sure they are expanded to the right descriptors use std::str::FromStr; use super::*; use crate::descriptor::{DescriptorError, DescriptorMeta}; use crate::keys::ValidNetworks; use assert_matches::assert_matches; use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; use miniscript::Descriptor; // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)` #[test] fn test_bip44_template_cointype() { use bitcoin::util::bip32::ChildNumber::{self, Hardened}; let xprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap(); assert_eq!(Network::Bitcoin, xprvkey.network); let xdesc = Bip44(xprvkey, KeychainKind::Internal) .build(Network::Bitcoin) .unwrap(); if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 { let path: Vec = pkh.into_inner().full_derivation_path().into(); let purpose = path.get(0).unwrap(); assert_matches!(purpose, Hardened { index: 44 }); let coin_type = path.get(1).unwrap(); assert_matches!(coin_type, Hardened { index: 0 }); } let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); assert_eq!(Network::Testnet, tprvkey.network); let tdesc = Bip44(tprvkey, KeychainKind::Internal) .build(Network::Testnet) .unwrap(); if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 { let path: Vec = pkh.into_inner().full_derivation_path().into(); let purpose = path.get(0).unwrap(); assert_matches!(purpose, Hardened { index: 44 }); let coin_type = path.get(1).unwrap(); assert_matches!(coin_type, Hardened { index: 1 }); } } // verify template descriptor generates expected address(es) fn check( desc: Result<(Descriptor, KeyMap, ValidNetworks), DescriptorError>, is_witness: bool, is_taproot: bool, is_fixed: bool, network: Network, expected: &[&str], ) { let (desc, _key_map, _networks) = desc.unwrap(); assert_eq!(desc.is_witness(), is_witness); assert_eq!(desc.is_taproot(), is_taproot); assert_eq!(!desc.has_wildcard(), is_fixed); for i in 0..expected.len() { let index = i as u32; let child_desc = if !desc.has_wildcard() { desc.at_derivation_index(0) } else { desc.at_derivation_index(index) }; let address = child_desc.address(network).unwrap(); assert_eq!(address.to_string(), *expected.get(i).unwrap()); } } // P2PKH #[test] fn test_p2ph_template() { let prvkey = bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") .unwrap(); check( P2Pkh(prvkey).build(Network::Bitcoin), false, false, true, Network::Regtest, &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"], ); let pubkey = bitcoin::PublicKey::from_str( "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", ) .unwrap(); check( P2Pkh(pubkey).build(Network::Bitcoin), false, false, true, Network::Regtest, &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], ); } // P2WPKH-P2SH `sh(wpkh(key))` #[test] fn test_p2wphp2sh_template() { let prvkey = bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") .unwrap(); check( P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin), true, false, true, Network::Regtest, &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"], ); let pubkey = bitcoin::PublicKey::from_str( "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", ) .unwrap(); check( P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin), true, false, true, Network::Regtest, &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], ); } // P2WPKH `wpkh(key)` #[test] fn test_p2wph_template() { let prvkey = bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") .unwrap(); check( P2Wpkh(prvkey).build(Network::Bitcoin), true, false, true, Network::Regtest, &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"], ); let pubkey = bitcoin::PublicKey::from_str( "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", ) .unwrap(); check( P2Wpkh(pubkey).build(Network::Bitcoin), true, false, true, Network::Regtest, &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], ); } // P2TR `tr(key)` #[test] fn test_p2tr_template() { let prvkey = bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") .unwrap(); check( P2TR(prvkey).build(Network::Bitcoin), false, true, true, Network::Regtest, &["bcrt1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xqnwtkqq"], ); let pubkey = bitcoin::PublicKey::from_str( "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", ) .unwrap(); check( P2TR(pubkey).build(Network::Bitcoin), false, true, true, Network::Regtest, &["bcrt1pw74tdcrxlzn5r8z6ku2vztr86fgq0m245s72mjktf4afwzsf8ugs4evwdf"], ); } // BIP44 `pkh(key/44'/0'/0'/{0,1}/*)` #[test] fn test_bip44_template() { let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin), false, false, false, Network::Regtest, &[ "n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5", "mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP", "mzYvhRAuQqbdSKMVVzXNYyqihgNdRadAUQ", ], ); check( Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin), false, false, false, Network::Regtest, &[ "muHF98X9KxEzdKrnFAX85KeHv96eXopaip", "n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR", "mgvkdv1ffmsXd2B1sRKQ5dByK3SzpG42rA", ], ); } // BIP44 public `pkh(key/{0,1}/*)` #[test] fn test_bip44_public_template() { let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), false, false, false, Network::Regtest, &[ "miNG7dJTzJqNbFS19svRdTCisC65dsubtR", "n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg", "muCPpS6Ue7nkzeJMWDViw7Lkwr92Yc4K8g", ], ); check( Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), false, false, false, Network::Regtest, &[ "moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H", "ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG", "mhYiyat2rtEnV77cFfQsW32y1m2ceCGHPo", ], ); } // BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))` #[test] fn test_bip49_template() { let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin), true, false, false, Network::Regtest, &[ "2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV", "2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS", "2NAFTVtksF9T4a97M7nyCjwUBD24QevZ5Z4", ], ); check( Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin), true, false, false, Network::Regtest, &[ "2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG", "2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p", "2NA8ek4CdQ6aMkveYF6AYuEYNrftB47QGTn", ], ); } // BIP49 public `sh(wpkh(key/{0,1}/*))` #[test] fn test_bip49_public_template() { let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), true, false, false, Network::Regtest, &[ "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt", "2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX", "2MveFxAuC8BYPzTybx7FxSzW8HSd8ATT4z7", ], ); check( Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), true, false, false, Network::Regtest, &[ "2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ", "2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH", "2NBs3CTVYPr1HCzjB4YFsnWCPCtNg8uMEfp", ], ); } // BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)` #[test] fn test_bip84_template() { let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin), true, false, false, Network::Regtest, &[ "bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s", "bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp", "bcrt1q4h7fq9zhxst6e69p3n882nfj649l7w9g3zccfp", ], ); check( Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin), true, false, false, Network::Regtest, &[ "bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa", "bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45", "bcrt1qpks7n0gq74hsgsz3phn5vuazjjq0f5eqhsgyce", ], ); } // BIP84 public `wpkh(key/{0,1}/*)` #[test] fn test_bip84_public_template() { let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), true, false, false, Network::Regtest, &[ "bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h", "bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana", "bcrt1qt9800y6xl3922jy3uyl0z33jh5wfpycyhcylr9", ], ); check( Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), true, false, false, Network::Regtest, &[ "bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2", "bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp", "bcrt1qhlac3c5ranv5w5emlnqs7wxhkxt8maelylcarp", ], ); } // BIP86 `tr(key/86'/0'/0'/{0,1}/*)` // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki #[test] fn test_bip86_template() { let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap(); check( Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin), false, true, false, Network::Bitcoin, &[ "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8", ], ); check( Bip86(prvkey, KeychainKind::Internal).build(Network::Bitcoin), false, true, false, Network::Bitcoin, &[ "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj", "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5", ], ); } // BIP86 public `tr(key/{0,1}/*)` // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki #[test] fn test_bip86_public_template() { let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("73c5da0a").unwrap(); check( Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), false, true, false, Network::Bitcoin, &[ "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8", ], ); check( Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), false, true, false, Network::Bitcoin, &[ "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj", "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5", ], ); } }