// 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. //! Descriptors //! //! This module contains generic utilities to work with descriptors, plus some re-exported types //! from [`miniscript`]. use std::collections::{BTreeMap, HashMap}; use std::fmt; use std::sync::Arc; use bitcoin::secp256k1::Secp256k1; use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint}; use bitcoin::util::psbt; use bitcoin::{Network, PublicKey, Script, TxOut}; use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey}; pub use miniscript::{ 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; pub use self::policy::Policy; use crate::keys::{KeyError, ToDescriptorKey, ValidNetworks}; use crate::wallet::signer::SignersContainer; use crate::wallet::utils::{descriptor_to_pk_ctx, SecpCtx}; /// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`] pub type ExtendedDescriptor = Descriptor; /// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or /// [`psbt::Output`] /// /// [`psbt::Input`]: bitcoin::util::psbt::Input /// [`psbt::Output`]: bitcoin::util::psbt::Output pub type HDKeyPaths = BTreeMap; /// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`] pub trait ToWalletDescriptor { fn to_wallet_descriptor( self, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), KeyError>; } impl ToWalletDescriptor for &str { fn to_wallet_descriptor( self, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> { let descriptor = if self.contains('#') { let parts: Vec<&str> = self.splitn(2, '#').collect(); if !get_checksum(parts[0]) .ok() .map(|computed| computed == parts[1]) .unwrap_or(false) { return Err(KeyError::InvalidChecksum); } parts[0] } else { self }; ExtendedDescriptor::parse_descriptor(descriptor)?.to_wallet_descriptor(network) } } impl ToWalletDescriptor for &String { fn to_wallet_descriptor( self, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> { self.as_str().to_wallet_descriptor(network) } } impl ToWalletDescriptor for ExtendedDescriptor { fn to_wallet_descriptor( self, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> { (self, KeyMap::default()).to_wallet_descriptor(network) } } impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) { fn to_wallet_descriptor( self, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> { use crate::keys::DescriptorKey; let secp = Secp256k1::new(); let check_key = |pk: &DescriptorPublicKey| { let (pk, _, networks) = if self.0.is_witness() { let desciptor_key: DescriptorKey = pk.clone().to_descriptor_key()?; desciptor_key.extract(&secp)? } else { let desciptor_key: DescriptorKey = pk.clone().to_descriptor_key()?; desciptor_key.extract(&secp)? }; if networks.contains(&network) { Ok(pk) } else { Err(KeyError::InvalidNetwork) } }; // check the network for the keys let translated = self.0.translate_pk(check_key, check_key)?; Ok((translated, self.1)) } } impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap, ValidNetworks) { fn to_wallet_descriptor( self, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> { let valid_networks = &self.2; let fix_key = |pk: &DescriptorPublicKey| { if valid_networks.contains(&network) { // workaround for xpubs generated by other key types, like bip39: since when the // conversion is made one network has to be chosen, what we generally choose // "mainnet", but then override the set of valid networks to specify that all of // them are valid. here we reset the network to make sure the wallet struct gets a // descriptor with the right network everywhere. let pk = match pk { DescriptorPublicKey::XPub(ref xpub) => { let mut xpub = xpub.clone(); xpub.xkey.network = network; DescriptorPublicKey::XPub(xpub) } other => other.clone(), }; Ok(pk) } else { Err(KeyError::InvalidNetwork) } }; // fixup the network for keys that need it let translated = self.0.translate_pk(fix_key, fix_key)?; Ok((translated, self.1)) } } /// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`] pub trait ExtractPolicy { fn extract_policy( &self, signers: Arc, secp: &SecpCtx, ) -> Result, Error>; } pub(crate) trait XKeyUtils { fn full_path(&self, append: &[ChildNumber]) -> DerivationPath; fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint; } impl XKeyUtils for DescriptorXKey { fn full_path(&self, append: &[ChildNumber]) -> DerivationPath { let full_path = match self.origin { Some((_, ref path)) => path .into_iter() .chain(self.derivation_path.into_iter()) .cloned() .collect(), None => self.derivation_path.clone(), }; if self.is_wildcard { full_path .into_iter() .chain(append.iter()) .cloned() .collect() } else { full_path } } fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint { match self.origin { Some((fingerprint, _)) => fingerprint, None => self.xkey.xkey_fingerprint(secp), } } } pub(crate) trait DescriptorMeta: Sized { fn is_witness(&self) -> bool; fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result; fn is_fixed(&self) -> bool; fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option; fn derive_from_psbt_input( &self, psbt_input: &psbt::Input, utxo: Option, secp: &SecpCtx, ) -> Option; } pub(crate) trait DescriptorScripts { fn psbt_redeem_script(&self, secp: &SecpCtx) -> Option