Update bitcoin, miniscript, electrum-client

This commit is contained in:
Alekos Filini 2021-02-02 20:06:40 -05:00
parent 4c36020e95
commit 3d9d6fee07
No known key found for this signature in database
GPG Key ID: 431401E4A4530061
17 changed files with 701 additions and 581 deletions

View File

@ -14,15 +14,15 @@ license = "MIT"
[dependencies] [dependencies]
bdk-macros = { path = "./macros" } bdk-macros = { path = "./macros" }
log = "^0.4" log = "^0.4"
miniscript = "4.0" miniscript = "5.1"
bitcoin = { version = "^0.25.2", features = ["use-serde"] } bitcoin = { version = "^0.26", features = ["use-serde"] }
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" } serde_json = { version = "^1.0" }
rand = "^0.7" rand = "^0.7"
# Optional dependencies # Optional dependencies
sled = { version = "0.34", optional = true } sled = { version = "0.34", optional = true }
electrum-client = { version = "0.5.0-beta.1", optional = true } electrum-client = { version = "0.6", optional = true }
reqwest = { version = "0.11", optional = true, features = ["json"] } reqwest = { version = "0.11", optional = true, features = ["json"] }
futures = { version = "0.3", optional = true } futures = { version = "0.3", optional = true }
async-trait = { version = "0.1", optional = true } async-trait = { version = "0.1", optional = true }
@ -68,8 +68,8 @@ env_logger = "0.7"
base64 = "^0.11" base64 = "^0.11"
clap = "2.33" clap = "2.33"
[[example]] # [[example]]
name = "parse_descriptor" # name = "parse_descriptor"
[[example]] [[example]]
name = "address_validator" name = "address_validator"

View File

@ -29,6 +29,7 @@ extern crate log;
extern crate miniscript; extern crate miniscript;
extern crate serde_json; extern crate serde_json;
use std::error::Error;
use std::str::FromStr; use std::str::FromStr;
use log::info; use log::info;
@ -42,7 +43,7 @@ use miniscript::Descriptor;
use bdk::database::memory::MemoryDatabase; use bdk::database::memory::MemoryDatabase;
use bdk::{KeychainKind, Wallet}; use bdk::{KeychainKind, Wallet};
fn main() { fn main() -> Result<(), Box<dyn Error>> {
env_logger::init_from_env( env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
); );
@ -81,12 +82,12 @@ fn main() {
let policy_str = matches.value_of("POLICY").unwrap(); let policy_str = matches.value_of("POLICY").unwrap();
info!("Compiling policy: {}", policy_str); info!("Compiling policy: {}", policy_str);
let policy = Concrete::<String>::from_str(&policy_str).unwrap(); let policy = Concrete::<String>::from_str(&policy_str)?;
let descriptor = match matches.value_of("TYPE").unwrap() { let descriptor = match matches.value_of("TYPE").unwrap() {
"sh" => Descriptor::Sh(policy.compile().unwrap()), "sh" => Descriptor::new_sh(policy.compile()?)?,
"wsh" => Descriptor::Wsh(policy.compile().unwrap()), "wsh" => Descriptor::new_wsh(policy.compile()?)?,
"sh-wsh" => Descriptor::ShWsh(policy.compile().unwrap()), "sh-wsh" => Descriptor::new_sh_wsh(policy.compile()?)?,
_ => panic!("Invalid type"), _ => panic!("Invalid type"),
}; };
@ -98,15 +99,17 @@ fn main() {
Some("regtest") => Network::Regtest, Some("regtest") => Network::Regtest,
Some("testnet") | _ => Network::Testnet, Some("testnet") | _ => Network::Testnet,
}; };
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database).unwrap(); let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?;
info!("... First address: {}", wallet.get_new_address().unwrap()); info!("... First address: {}", wallet.get_new_address()?);
if matches.is_present("parsed_policy") { if matches.is_present("parsed_policy") {
let spending_policy = wallet.policies(KeychainKind::External).unwrap(); let spending_policy = wallet.policies(KeychainKind::External)?;
info!( info!(
"... Spending policy:\n{}", "... Spending policy:\n{}",
serde_json::to_string_pretty(&spending_policy).unwrap() serde_json::to_string_pretty(&spending_policy)?
); );
} }
Ok(())
} }

View File

@ -1,60 +0,0 @@
// Magical Bitcoin Library
// Written in 2020 by
// Alekos Filini <alekos.filini@gmail.com>
//
// 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.
extern crate bdk;
extern crate serde_json;
use std::sync::Arc;
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::bip32::ChildNumber;
use bdk::bitcoin::*;
use bdk::descriptor::*;
use bdk::miniscript::DescriptorPublicKeyCtx;
fn main() {
let secp = Secp256k1::new();
let desc = "wsh(or_d(\
multi(\
2,[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*,tprv8ZgxMBicQKsPduL5QnGihpprdHyypMGi4DhimjtzYemu7se5YQNcZfAPLqXRuGHb5ZX2eTQj62oNqMnyxJ7B7wz54Uzswqw8fFqMVdcmVF7/1/*\
),\
and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\
))";
let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(desc).unwrap();
println!("{:?}", extended_desc);
let deriv_ctx = DescriptorPublicKeyCtx::new(&secp, ChildNumber::from_normal_idx(42).unwrap());
let signers = Arc::new(key_map.into());
let policy = extended_desc.extract_policy(&signers, &secp).unwrap();
println!("policy: {}", serde_json::to_string(&policy).unwrap());
let addr = extended_desc.address(Network::Testnet, deriv_ctx).unwrap();
println!("{}", addr);
let script = extended_desc.witness_script(deriv_ctx);
println!("{:?}", script);
}

163
src/descriptor/derived.rs Normal file
View File

@ -0,0 +1,163 @@
// Magical Bitcoin Library
// Written in 2020 by
// Alekos Filini <alekos.filini@gmail.com>
//
// 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.
//! Derived descriptor keys
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
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::{MiniscriptKey, ToPublicKey, TranslatePk};
use crate::wallet::utils::SecpCtx;
/// Extended [`DescriptorPublicKey`] that has been derived
///
/// Derived keys are guaranteed to never contain wildcards of any kind
#[derive(Debug, Clone)]
pub struct DerivedDescriptorKey<'s>(DescriptorPublicKey, &'s SecpCtx);
impl<'s> DerivedDescriptorKey<'s> {
/// Construct a new derived key
///
/// Panics if the key is wildcard
pub fn new(key: DescriptorPublicKey, secp: &'s SecpCtx) -> DerivedDescriptorKey<'s> {
if let DescriptorPublicKey::XPub(xpub) = &key {
assert!(xpub.wildcard == Wildcard::None)
}
DerivedDescriptorKey(key, secp)
}
}
impl<'s> Deref for DerivedDescriptorKey<'s> {
type Target = DescriptorPublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'s> PartialEq for DerivedDescriptorKey<'s> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<'s> Eq for DerivedDescriptorKey<'s> {}
impl<'s> PartialOrd for DerivedDescriptorKey<'s> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<'s> Ord for DerivedDescriptorKey<'s> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl<'s> fmt::Display for DerivedDescriptorKey<'s> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'s> Hash for DerivedDescriptorKey<'s> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<'s> MiniscriptKey for DerivedDescriptorKey<'s> {
type Hash = Self;
fn to_pubkeyhash(&self) -> Self::Hash {
DerivedDescriptorKey(self.0.to_pubkeyhash(), self.1)
}
fn is_uncompressed(&self) -> bool {
self.0.is_uncompressed()
}
fn serialized_len(&self) -> usize {
self.0.serialized_len()
}
}
impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
fn to_public_key(&self) -> PublicKey {
match &self.0 {
DescriptorPublicKey::SinglePub(ref spub) => spub.key.to_public_key(),
DescriptorPublicKey::XPub(ref xpub) => {
xpub.xkey
.derive_pub(self.1, &xpub.derivation_path)
.expect("Shouldn't fail, only normal derivations")
.public_key
}
}
}
fn hash_to_hash160(hash: &Self::Hash) -> hash160::Hash {
hash.to_public_key().to_pubkeyhash()
}
}
pub(crate) trait AsDerived {
// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx)
-> Descriptor<DerivedDescriptorKey<'s>>;
// 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<DerivedDescriptorKey<'s>>;
}
impl AsDerived for Descriptor<DescriptorPublicKey> {
fn as_derived<'s>(
&self,
index: u32,
secp: &'s SecpCtx,
) -> Descriptor<DerivedDescriptorKey<'s>> {
self.derive(index).translate_pk_infallible(
|key| DerivedDescriptorKey::new(key.clone(), secp),
|key| DerivedDescriptorKey::new(key.clone(), secp),
)
}
fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>> {
assert!(!self.is_deriveable());
self.as_derived(0, secp)
}
}

View File

@ -28,32 +28,53 @@
#[macro_export] #[macro_export]
macro_rules! impl_top_level_sh { macro_rules! impl_top_level_sh {
// disallow `sortedmulti` in `bare()` // disallow `sortedmulti` in `bare()`
( Bare, Bare, sortedmulti $( $inner:tt )* ) => { ( Bare, new, new, Legacy, sortedmulti $( $inner:tt )* ) => {
compile_error!("`bare()` descriptors can't contain any `sortedmulti()` operands"); compile_error!("`bare()` descriptors can't contain any `sortedmulti()` operands");
}; };
( Bare, Bare, sortedmulti_vec $( $inner:tt )* ) => { ( Bare, new, new, Legacy, sortedmulti_vec $( $inner:tt )* ) => {
compile_error!("`bare()` descriptors can't contain any `sortedmulti_vec()` operands"); compile_error!("`bare()` descriptors can't contain any `sortedmulti_vec()` operands");
}; };
( $descriptor_variant:ident, $sortedmulti_variant:ident, sortedmulti $( $inner:tt )* ) => { ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, sortedmulti $( $inner:tt )* ) => {{
$crate::impl_sortedmulti!(sortedmulti $( $inner )*) use std::marker::PhantomData;
.and_then(|(inner, key_map, valid_networks)| Ok(($crate::miniscript::Descriptor::$sortedmulti_variant(inner), key_map, valid_networks)))
}; use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
( $descriptor_variant:ident, $sortedmulti_variant:ident, sortedmulti_vec $( $inner:tt )* ) => { use $crate::miniscript::$ctx;
$crate::impl_sortedmulti!(sortedmulti_vec $( $inner )*)
.and_then(|(inner, key_map, valid_networks)| Ok(($crate::miniscript::Descriptor::$sortedmulti_variant(inner), key_map, valid_networks))) let build_desc = |k, pks| {
}; Ok((Descriptor::<DescriptorPublicKey>::$inner_struct($inner_struct::$sortedmulti_constructor(k, pks)?), PhantomData::<$ctx>))
};
$crate::impl_sortedmulti!(build_desc, sortedmulti $( $inner )*)
}};
( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, sortedmulti_vec $( $inner:tt )* ) => {{
use std::marker::PhantomData;
use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
use $crate::miniscript::$ctx;
let build_desc = |k, pks| {
Ok((Descriptor::<DescriptorPublicKey>::$inner_struct($inner_struct::$sortedmulti_constructor(k, pks)?), PhantomData::<$ctx>))
};
$crate::impl_sortedmulti!(build_desc, sortedmulti_vec $( $inner )*)
}};
( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, $( $minisc:tt )* ) => {{
use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
( $descriptor_variant:ident, $sortedmulti_variant:ident, $( $minisc:tt )* ) => {
$crate::fragment!($( $minisc )*) $crate::fragment!($( $minisc )*)
.map(|(minisc, keymap, networks)|($crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::$descriptor_variant(minisc), keymap, networks)) .and_then(|(minisc, keymap, networks)| Ok(($inner_struct::$constructor(minisc)?, keymap, networks)))
}; .and_then(|(inner, key_map, valid_networks)| Ok((Descriptor::<DescriptorPublicKey>::$inner_struct(inner), key_map, valid_networks)))
}};
} }
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! impl_top_level_pk { macro_rules! impl_top_level_pk {
( $descriptor_variant:ident, $ctx:ty, $key:expr ) => {{ ( $inner_type:ident, $ctx:ty, $key:expr ) => {{
use $crate::miniscript::descriptor::$inner_type;
#[allow(unused_imports)] #[allow(unused_imports)]
use $crate::keys::{DescriptorKey, ToDescriptorKey}; use $crate::keys::{DescriptorKey, ToDescriptorKey};
let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
@ -61,15 +82,7 @@ macro_rules! impl_top_level_pk {
$key.to_descriptor_key() $key.to_descriptor_key()
.and_then(|key: DescriptorKey<$ctx>| key.extract(&secp)) .and_then(|key: DescriptorKey<$ctx>| key.extract(&secp))
.map_err($crate::descriptor::DescriptorError::Key) .map_err($crate::descriptor::DescriptorError::Key)
.map(|(pk, key_map, valid_networks)| { .map(|(pk, key_map, valid_networks)| ($inner_type::new(pk), key_map, valid_networks))
(
$crate::miniscript::Descriptor::<
$crate::miniscript::descriptor::DescriptorPublicKey,
>::$descriptor_variant(pk),
key_map,
valid_networks,
)
})
}}; }};
} }
@ -207,11 +220,11 @@ macro_rules! impl_node_opcode_three {
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! impl_sortedmulti { macro_rules! impl_sortedmulti {
( sortedmulti_vec ( $thresh:expr, $keys:expr ) ) => ({ ( $build_desc:expr, sortedmulti_vec ( $thresh:expr, $keys:expr ) ) => ({
let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
$crate::keys::make_sortedmulti_inner($thresh, $keys, &secp) $crate::keys::make_sortedmulti($thresh, $keys, $build_desc, &secp)
}); });
( sortedmulti ( $thresh:expr $(, $key:expr )+ ) ) => ({ ( $build_desc:expr, sortedmulti ( $thresh:expr $(, $key:expr )+ ) ) => ({
use $crate::keys::ToDescriptorKey; use $crate::keys::ToDescriptorKey;
let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
@ -222,7 +235,7 @@ macro_rules! impl_sortedmulti {
keys.into_iter().collect::<Result<Vec<_>, _>>() keys.into_iter().collect::<Result<Vec<_>, _>>()
.map_err($crate::descriptor::DescriptorError::Key) .map_err($crate::descriptor::DescriptorError::Key)
.and_then(|keys| $crate::keys::make_sortedmulti_inner($thresh, keys, &secp)) .and_then(|keys| $crate::keys::make_sortedmulti($thresh, keys, $build_desc, &secp))
}); });
} }
@ -399,34 +412,46 @@ macro_rules! apply_modifier {
#[macro_export] #[macro_export]
macro_rules! descriptor { macro_rules! descriptor {
( bare ( $( $minisc:tt )* ) ) => ({ ( bare ( $( $minisc:tt )* ) ) => ({
$crate::impl_top_level_sh!(Bare, Bare, $( $minisc )*) $crate::impl_top_level_sh!(Bare, new, new, Legacy, $( $minisc )*)
}); });
( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({ ( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({
$crate::descriptor!(shwsh ($( $minisc )*)) $crate::descriptor!(shwsh ($( $minisc )*))
}); });
( shwsh ( $( $minisc:tt )* ) ) => ({ ( shwsh ( $( $minisc:tt )* ) ) => ({
$crate::impl_top_level_sh!(ShWsh, ShWshSortedMulti, $( $minisc )*) $crate::impl_top_level_sh!(Sh, new_wsh, new_wsh_sortedmulti, Segwitv0, $( $minisc )*)
}); });
( pk ( $key:expr ) ) => ({ ( pk ( $key:expr ) ) => ({
$crate::impl_top_level_pk!(Pk, $crate::miniscript::Legacy, $key) // `pk()` is actually implemented as `bare(pk())`
$crate::descriptor!( bare ( pk ( $key ) ) )
}); });
( pkh ( $key:expr ) ) => ({ ( pkh ( $key:expr ) ) => ({
$crate::impl_top_level_pk!(Pkh,$crate::miniscript::Legacy, $key) use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
$crate::impl_top_level_pk!(Pkh, $crate::miniscript::Legacy, $key)
.map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Pkh(a), b, c))
}); });
( wpkh ( $key:expr ) ) => ({ ( wpkh ( $key:expr ) ) => ({
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
$crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key) $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key)
.and_then(|(a, b, c)| Ok((a?, b, c)))
.map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Wpkh(a), b, c))
}); });
( sh ( wpkh ( $key:expr ) ) ) => ({ ( sh ( wpkh ( $key:expr ) ) ) => ({
$crate::descriptor!(shwpkh ( $key )) $crate::descriptor!(shwpkh ( $key ))
}); });
( shwpkh ( $key:expr ) ) => ({ ( shwpkh ( $key:expr ) ) => ({
$crate::impl_top_level_pk!(ShWpkh, $crate::miniscript::Segwitv0, $key) use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, Sh};
$crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key)
.and_then(|(a, b, c)| Ok((a?, b, c)))
.and_then(|(a, b, c)| Ok((Descriptor::<DescriptorPublicKey>::Sh(Sh::new_wpkh(a.into_inner())?), b, c)))
}); });
( sh ( $( $minisc:tt )* ) ) => ({ ( sh ( $( $minisc:tt )* ) ) => ({
$crate::impl_top_level_sh!(Sh, ShSortedMulti, $( $minisc )*) $crate::impl_top_level_sh!(Sh, new, new_sortedmulti, Legacy, $( $minisc )*)
}); });
( wsh ( $( $minisc:tt )* ) ) => ({ ( wsh ( $( $minisc:tt )* ) ) => ({
$crate::impl_top_level_sh!(Wsh, WshSortedMulti, $( $minisc )*) $crate::impl_top_level_sh!(Wsh, new, new_sortedmulti, Segwitv0, $( $minisc )*)
}); });
} }
@ -654,7 +679,7 @@ macro_rules! fragment {
mod test { mod test {
use bitcoin::hashes::hex::ToHex; use bitcoin::hashes::hex::ToHex;
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{DescriptorPublicKey, DescriptorPublicKeyCtx, KeyMap}; use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};
use miniscript::{Descriptor, Legacy, Segwitv0}; use miniscript::{Descriptor, Legacy, Segwitv0};
use std::str::FromStr; use std::str::FromStr;
@ -663,9 +688,10 @@ mod test {
use crate::keys::{DescriptorKey, ToDescriptorKey, ValidNetworks}; use crate::keys::{DescriptorKey, ToDescriptorKey, ValidNetworks};
use bitcoin::network::constants::Network::{Bitcoin, Regtest, Testnet}; use bitcoin::network::constants::Network::{Bitcoin, Regtest, Testnet};
use bitcoin::util::bip32; use bitcoin::util::bip32;
use bitcoin::util::bip32::ChildNumber;
use bitcoin::PrivateKey; use bitcoin::PrivateKey;
use crate::descriptor::derived::AsDerived;
// test the descriptor!() macro // test the descriptor!() macro
// verify descriptor generates expected script(s) (if bare or pk) or address(es) // verify descriptor generates expected script(s) (if bare or pk) or address(es)
@ -676,23 +702,22 @@ mod test {
expected: &[&str], expected: &[&str],
) { ) {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let deriv_ctx = DescriptorPublicKeyCtx::new(&secp, ChildNumber::Normal { index: 0 });
let (desc, _key_map, _networks) = desc.unwrap(); let (desc, _key_map, _networks) = desc.unwrap();
assert_eq!(desc.is_witness(), is_witness); assert_eq!(desc.is_witness(), is_witness);
assert_eq!(desc.is_fixed(), is_fixed); assert_eq!(!desc.is_deriveable(), is_fixed);
for i in 0..expected.len() { for i in 0..expected.len() {
let index = i as u32; let index = i as u32;
let child_desc = if desc.is_fixed() { let child_desc = if !desc.is_deriveable() {
desc.clone() desc.as_derived_fixed(&secp)
} else { } else {
desc.derive(ChildNumber::from_normal_idx(index).unwrap()) desc.as_derived(index, &secp)
}; };
let address = child_desc.address(Regtest, deriv_ctx); let address = child_desc.address(Regtest);
if let Some(address) = address { if let Ok(address) = address {
assert_eq!(address.to_string(), *expected.get(i).unwrap()); assert_eq!(address.to_string(), *expected.get(i).unwrap());
} else { } else {
let script = child_desc.script_pubkey(deriv_ctx); let script = child_desc.script_pubkey();
assert_eq!(script.to_hex().as_str(), *expected.get(i).unwrap()); assert_eq!(script.to_hex().as_str(), *expected.get(i).unwrap());
} }
} }
@ -1001,7 +1026,7 @@ mod test {
let desc_key: DescriptorKey<Legacy> = (xprv, path.clone()).to_descriptor_key().unwrap(); let desc_key: DescriptorKey<Legacy> = (xprv, path.clone()).to_descriptor_key().unwrap();
let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap(); let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap();
assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)"); assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)#yrnz9pp2");
// as expected this does not compile due to invalid context // as expected this does not compile due to invalid context
//let desc_key:DescriptorKey<Segwitv0> = (xprv, path.clone()).to_descriptor_key().unwrap(); //let desc_key:DescriptorKey<Segwitv0> = (xprv, path.clone()).to_descriptor_key().unwrap();
@ -1015,17 +1040,16 @@ mod test {
let (descriptor, _, _) = let (descriptor, _, _) =
descriptor!(wsh(thresh(2,d:v:older(1),s:pk(private_key),s:pk(private_key)))).unwrap(); descriptor!(wsh(thresh(2,d:v:older(1),s:pk(private_key),s:pk(private_key)))).unwrap();
assert_eq!(descriptor.to_string(), "wsh(thresh(2,dv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))") assert_eq!(descriptor.to_string(), "wsh(thresh(2,dv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))#cfdcqs3s")
} }
// TODO: uncomment once https://github.com/rust-bitcoin/rust-miniscript/pull/221 is released #[test]
// #[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")]
// #[test] fn test_dsl_miniscript_checks() {
// #[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")] let mut uncompressed_pk =
// fn test_dsl_miniscript_checks() { PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();
// let mut uncompressed_pk = PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap(); uncompressed_pk.compressed = false;
// uncompressed_pk.compressed = false;
// descriptor!(wsh(v:pk(uncompressed_pk))).unwrap(); descriptor!(wsh(v: pk(uncompressed_pk))).unwrap();
// } }
} }

View File

@ -28,20 +28,20 @@
//! from [`miniscript`]. //! from [`miniscript`].
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::fmt; use std::ops::Deref;
use bitcoin::secp256k1::Secp256k1; use bitcoin::util::bip32::{
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource}; ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource,
};
use bitcoin::util::psbt; use bitcoin::util::psbt;
use bitcoin::{Network, PublicKey, Script, TxOut}; use bitcoin::{Network, PublicKey, Script, TxOut};
use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey}; use miniscript::descriptor::{DescriptorPublicKey, DescriptorType, DescriptorXKey, Wildcard};
pub use miniscript::{ pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
descriptor::KeyMap, Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
Terminal, ToPublicKey,
};
pub mod checksum; pub mod checksum;
pub(crate) mod derived;
#[doc(hidden)] #[doc(hidden)]
pub mod dsl; pub mod dsl;
pub mod error; pub mod error;
@ -49,16 +49,21 @@ 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::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;
use crate::keys::{KeyError, ToDescriptorKey}; use crate::keys::{KeyError, ToDescriptorKey};
use crate::wallet::signer::SignersContainer; use crate::wallet::signer::SignersContainer;
use crate::wallet::utils::{descriptor_to_pk_ctx, SecpCtx}; use crate::wallet::utils::SecpCtx;
/// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`] /// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`]
pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>; pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
/// Alias for a [`Descriptor`] that contains extended **derived** keys
pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
/// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or /// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or
/// [`psbt::Output`] /// [`psbt::Output`]
/// ///
@ -71,6 +76,7 @@ pub trait ToWalletDescriptor {
/// Convert to wallet descriptor /// Convert to wallet descriptor
fn to_wallet_descriptor( fn to_wallet_descriptor(
self, self,
secp: &SecpCtx,
network: Network, network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>; ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>;
} }
@ -78,6 +84,7 @@ pub trait ToWalletDescriptor {
impl ToWalletDescriptor for &str { impl ToWalletDescriptor for &str {
fn to_wallet_descriptor( fn to_wallet_descriptor(
self, self,
secp: &SecpCtx,
network: Network, network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
let descriptor = if self.contains('#') { let descriptor = if self.contains('#') {
@ -95,37 +102,38 @@ impl ToWalletDescriptor for &str {
self self
}; };
ExtendedDescriptor::parse_descriptor(descriptor)?.to_wallet_descriptor(network) ExtendedDescriptor::parse_descriptor(secp, descriptor)?.to_wallet_descriptor(secp, network)
} }
} }
impl ToWalletDescriptor for &String { impl ToWalletDescriptor for &String {
fn to_wallet_descriptor( fn to_wallet_descriptor(
self, self,
secp: &SecpCtx,
network: Network, network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
self.as_str().to_wallet_descriptor(network) self.as_str().to_wallet_descriptor(secp, network)
} }
} }
impl ToWalletDescriptor for ExtendedDescriptor { impl ToWalletDescriptor for ExtendedDescriptor {
fn to_wallet_descriptor( fn to_wallet_descriptor(
self, self,
secp: &SecpCtx,
network: Network, network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
(self, KeyMap::default()).to_wallet_descriptor(network) (self, KeyMap::default()).to_wallet_descriptor(secp, network)
} }
} }
impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) { impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
fn to_wallet_descriptor( fn to_wallet_descriptor(
self, self,
secp: &SecpCtx,
network: Network, network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
use crate::keys::DescriptorKey; use crate::keys::DescriptorKey;
let secp = Secp256k1::new();
let check_key = |pk: &DescriptorPublicKey| { let check_key = |pk: &DescriptorPublicKey| {
let (pk, _, networks) = if self.0.is_witness() { let (pk, _, networks) = if self.0.is_witness() {
let desciptor_key: DescriptorKey<miniscript::Segwitv0> = let desciptor_key: DescriptorKey<miniscript::Segwitv0> =
@ -154,6 +162,7 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
impl ToWalletDescriptor for DescriptorTemplateOut { impl ToWalletDescriptor for DescriptorTemplateOut {
fn to_wallet_descriptor( fn to_wallet_descriptor(
self, self,
_secp: &SecpCtx,
network: Network, network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
let valid_networks = &self.2; let valid_networks = &self.2;
@ -219,7 +228,12 @@ pub(crate) trait XKeyUtils {
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint; fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint;
} }
impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> { // FIXME: `InnerXKey` was made private in rust-miniscript, so we have to implement this manually on
// both `ExtendedPubKey` and `ExtendedPrivKey`.
//
// Revert back to using the trait once https://github.com/rust-bitcoin/rust-miniscript/pull/230 is
// released
impl XKeyUtils for DescriptorXKey<ExtendedPubKey> {
fn full_path(&self, append: &[ChildNumber]) -> DerivationPath { fn full_path(&self, append: &[ChildNumber]) -> DerivationPath {
let full_path = match self.origin { let full_path = match self.origin {
Some((_, ref path)) => path Some((_, ref path)) => path
@ -230,7 +244,36 @@ impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
None => self.derivation_path.clone(), None => self.derivation_path.clone(),
}; };
if self.is_wildcard { if self.wildcard != Wildcard::None {
full_path
.into_iter()
.chain(append.iter())
.cloned()
.collect()
} else {
full_path
}
}
fn root_fingerprint(&self, _: &SecpCtx) -> Fingerprint {
match self.origin {
Some((fingerprint, _)) => fingerprint,
None => self.xkey.fingerprint(),
}
}
}
impl XKeyUtils for DescriptorXKey<ExtendedPrivKey> {
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.wildcard != Wildcard::None {
full_path full_path
.into_iter() .into_iter()
.chain(append.iter()) .chain(append.iter())
@ -244,195 +287,111 @@ impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint { fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
match self.origin { match self.origin {
Some((fingerprint, _)) => fingerprint, Some((fingerprint, _)) => fingerprint,
None => self.xkey.xkey_fingerprint(secp), None => self.xkey.fingerprint(secp),
} }
} }
} }
pub(crate) trait DescriptorMeta: Sized { pub(crate) trait DerivedDescriptorMeta {
fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError>;
}
pub(crate) trait DescriptorMeta {
fn is_witness(&self) -> bool; fn is_witness(&self) -> bool;
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError>;
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>; fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
fn is_fixed(&self) -> bool; fn derive_from_hd_keypaths<'s>(
fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self>; &self,
fn derive_from_psbt_input( hd_keypaths: &HDKeyPaths,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor<'s>>;
fn derive_from_psbt_input<'s>(
&self, &self,
psbt_input: &psbt::Input, psbt_input: &psbt::Input,
utxo: Option<TxOut>, utxo: Option<TxOut>,
secp: &SecpCtx, secp: &'s SecpCtx,
) -> Option<Self>; ) -> Option<DerivedDescriptor<'s>>;
} }
pub(crate) trait DescriptorScripts { pub(crate) trait DescriptorScripts {
fn psbt_redeem_script(&self, secp: &SecpCtx) -> Option<Script>; fn psbt_redeem_script(&self) -> Option<Script>;
fn psbt_witness_script(&self, secp: &SecpCtx) -> Option<Script>; fn psbt_witness_script(&self) -> Option<Script>;
} }
impl DescriptorScripts for Descriptor<DescriptorPublicKey> { impl<'s> DescriptorScripts for DerivedDescriptor<'s> {
fn psbt_redeem_script(&self, secp: &SecpCtx) -> Option<Script> { fn psbt_redeem_script(&self) -> Option<Script> {
let deriv_ctx = descriptor_to_pk_ctx(secp); match self.desc_type() {
DescriptorType::ShWpkh => Some(self.explicit_script()),
match self { DescriptorType::ShWsh => Some(self.explicit_script().to_v0_p2wsh()),
Descriptor::ShWpkh(_) => Some(self.witness_script(deriv_ctx)), DescriptorType::Sh => Some(self.explicit_script()),
Descriptor::ShWsh(ref script) => Some(script.encode(deriv_ctx).to_v0_p2wsh()), DescriptorType::Bare => Some(self.explicit_script()),
Descriptor::Sh(ref script) => Some(script.encode(deriv_ctx)), DescriptorType::ShSortedMulti => Some(self.explicit_script()),
Descriptor::Bare(ref script) => Some(script.encode(deriv_ctx)),
Descriptor::ShSortedMulti(ref keys) => Some(keys.encode(deriv_ctx)),
_ => None, _ => None,
} }
} }
fn psbt_witness_script(&self, secp: &SecpCtx) -> Option<Script> { fn psbt_witness_script(&self) -> Option<Script> {
let deriv_ctx = descriptor_to_pk_ctx(secp); match self.desc_type() {
DescriptorType::Wsh => Some(self.explicit_script()),
match self { DescriptorType::ShWsh => Some(self.explicit_script()),
Descriptor::Wsh(ref script) => Some(script.encode(deriv_ctx)), DescriptorType::WshSortedMulti | DescriptorType::ShWshSortedMulti => {
Descriptor::ShWsh(ref script) => Some(script.encode(deriv_ctx)), Some(self.explicit_script())
Descriptor::WshSortedMulti(ref keys) | Descriptor::ShWshSortedMulti(ref keys) => {
Some(keys.encode(deriv_ctx))
} }
_ => None, _ => None,
} }
} }
} }
impl DescriptorMeta for Descriptor<DescriptorPublicKey> { impl DescriptorMeta for ExtendedDescriptor {
fn is_witness(&self) -> bool { fn is_witness(&self) -> bool {
match self { matches!(
Descriptor::Bare(_) self.desc_type(),
| Descriptor::Pk(_) DescriptorType::Wpkh
| Descriptor::Pkh(_) | DescriptorType::ShWpkh
| Descriptor::Sh(_) | DescriptorType::Wsh
| Descriptor::ShSortedMulti(_) => false, | DescriptorType::ShWsh
Descriptor::Wpkh(_) | DescriptorType::ShWshSortedMulti
| Descriptor::ShWpkh(_) | DescriptorType::WshSortedMulti
| Descriptor::Wsh(_) )
| Descriptor::ShWsh(_)
| Descriptor::ShWshSortedMulti(_)
| Descriptor::WshSortedMulti(_) => true,
}
}
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError> {
let translate_key = |key: &DescriptorPublicKey,
index: u32,
paths: &mut HDKeyPaths|
-> Result<DummyKey, DescriptorError> {
match key {
DescriptorPublicKey::SinglePub(_) => {}
DescriptorPublicKey::XPub(xpub) => {
let derive_path = if xpub.is_wildcard {
xpub.derivation_path
.into_iter()
.chain([ChildNumber::from_normal_idx(index)?].iter())
.cloned()
.collect()
} else {
xpub.derivation_path.clone()
};
let derived_pubkey = xpub
.xkey
.derive_pub(&Secp256k1::verification_only(), &derive_path)?;
paths.insert(
derived_pubkey.public_key,
(
xpub.root_fingerprint(secp),
xpub.full_path(&[ChildNumber::from_normal_idx(index)?]),
),
);
}
}
Ok(DummyKey::default())
};
let mut answer_pk = BTreeMap::new();
let mut answer_pkh = BTreeMap::new();
self.translate_pk(
|pk| translate_key(pk, index, &mut answer_pk),
|pkh| translate_key(pkh, index, &mut answer_pkh),
)?;
answer_pk.append(&mut answer_pkh);
Ok(answer_pk)
} }
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> { fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
let get_key = |key: &DescriptorPublicKey, let mut answer = Vec::new();
keys: &mut Vec<DescriptorXKey<ExtendedPubKey>>|
-> Result<DummyKey, DescriptorError> { self.for_each_key(|pk| {
if let DescriptorPublicKey::XPub(xpub) = key { if let DescriptorPublicKey::XPub(xpub) = pk.as_key() {
keys.push(xpub.clone()) answer.push(xpub.clone());
} }
Ok(DummyKey::default()) true
}; });
let mut answer_pk = Vec::new(); Ok(answer)
let mut answer_pkh = Vec::new();
self.translate_pk(
|pk| get_key(pk, &mut answer_pk),
|pkh| get_key(pkh, &mut answer_pkh),
)?;
answer_pk.append(&mut answer_pkh);
Ok(answer_pk)
} }
fn is_fixed(&self) -> bool { fn derive_from_hd_keypaths<'s>(
fn check_key( &self,
key: &DescriptorPublicKey, hd_keypaths: &HDKeyPaths,
flag: &mut bool, secp: &'s SecpCtx,
) -> Result<DummyKey, DescriptorError> { ) -> Option<DerivedDescriptor<'s>> {
match key { let index: HashMap<_, _> = hd_keypaths.values().map(|(a, b)| (a, b)).collect();
DescriptorPublicKey::SinglePub(_) => {}
DescriptorPublicKey::XPub(xpub) => {
if xpub.is_wildcard {
*flag = true;
}
}
}
Ok(DummyKey::default()) let mut path_found = None;
} self.for_each_key(|key| {
if path_found.is_some() {
let mut found_wildcard_pk = false;
let mut found_wildcard_pkh = false;
self.translate_pk(
|pk| check_key(pk, &mut found_wildcard_pk),
|pkh| check_key(pkh, &mut found_wildcard_pkh),
)
.unwrap();
!found_wildcard_pk && !found_wildcard_pkh
}
fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self> {
let try_key = |key: &DescriptorPublicKey,
index: &HashMap<Fingerprint, DerivationPath>,
found_path: &mut Option<ChildNumber>|
-> Result<DummyKey, DescriptorError> {
if found_path.is_some() {
// already found a matching path, we are done // already found a matching path, we are done
return Ok(DummyKey::default()); return true;
} }
if let DescriptorPublicKey::XPub(xpub) = key { if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
// Check if the key matches one entry in our `index`. If it does, `matches()` will // Check if the key matches one entry in our `index`. If it does, `matches()` will
// return the "prefix" that matched, so we remove that prefix from the full path // return the "prefix" that matched, so we remove that prefix from the full path
// found in `index` and save it in `derive_path`. We expect this to be a derivation // found in `index` and save it in `derive_path`. We expect this to be a derivation
// path of length 1 if the key `is_wildcard` and an empty path otherwise. // path of length 1 if the key is `wildcard` and an empty path otherwise.
let root_fingerprint = xpub.root_fingerprint(secp); let root_fingerprint = xpub.root_fingerprint(secp);
let derivation_path: Option<Vec<ChildNumber>> = index let derivation_path: Option<Vec<ChildNumber>> = index
.get_key_value(&root_fingerprint) .get_key_value(&root_fingerprint)
.and_then(|(fingerprint, path)| { .and_then(|(fingerprint, path)| {
xpub.matches(&(*fingerprint, path.clone()), secp) xpub.matches(&(**fingerprint, (*path).clone()), secp)
}) })
.map(|prefix| { .map(|prefix| {
index index
@ -445,128 +404,90 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
}); });
match derivation_path { match derivation_path {
Some(path) if xpub.is_wildcard && path.len() == 1 => { Some(path) if xpub.wildcard != Wildcard::None && path.len() == 1 => {
*found_path = Some(path[0]) // Ignore hardened wildcards
if let ChildNumber::Normal { index } = path[0] {
path_found = Some(index)
}
} }
Some(path) if !xpub.is_wildcard && path.is_empty() => { Some(path) if xpub.wildcard == Wildcard::None && path.is_empty() => {
*found_path = Some(ChildNumber::Normal { index: 0 }) path_found = Some(0)
} }
Some(_) => return Err(DescriptorError::InvalidHDKeyPath),
_ => {} _ => {}
} }
} }
Ok(DummyKey::default()) true
}; });
let index: HashMap<_, _> = hd_keypaths.values().cloned().collect(); path_found.map(|path| self.as_derived(path, secp))
let mut found_path_pk = None;
let mut found_path_pkh = None;
if self
.translate_pk(
|pk| try_key(pk, &index, &mut found_path_pk),
|pkh| try_key(pkh, &index, &mut found_path_pkh),
)
.is_err()
{
return None;
}
// if we have found a path for both `found_path_pk` and `found_path_pkh` but they are
// different we consider this an error and return None. we only return a path either if
// they are equal or if only one of them is Some(_)
let merged_path = match (found_path_pk, found_path_pkh) {
(Some(a), Some(b)) if a != b => return None,
(a, b) => a.or(b),
};
merged_path.map(|path| self.derive(path))
} }
fn derive_from_psbt_input( fn derive_from_psbt_input<'s>(
&self, &self,
psbt_input: &psbt::Input, psbt_input: &psbt::Input,
utxo: Option<TxOut>, utxo: Option<TxOut>,
secp: &SecpCtx, secp: &'s SecpCtx,
) -> Option<Self> { ) -> Option<DerivedDescriptor<'s>> {
if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.hd_keypaths, secp) { if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.bip32_derivation, secp) {
return Some(derived); return Some(derived);
} else if !self.is_fixed() { }
// If the descriptor is not fixed we can't brute-force the derivation address, so just if self.is_deriveable() {
// exit here // We can't try to bruteforce the derivation index, exit here
return None; return None;
} }
let deriv_ctx = descriptor_to_pk_ctx(secp); let descriptor = self.as_derived_fixed(secp);
match self { match descriptor.desc_type() {
Descriptor::Pk(_) // TODO: add pk() here
| Descriptor::Pkh(_) DescriptorType::Pkh | DescriptorType::Wpkh | DescriptorType::ShWpkh
| Descriptor::Wpkh(_)
| Descriptor::ShWpkh(_)
if utxo.is_some() if utxo.is_some()
&& self.script_pubkey(deriv_ctx) == utxo.as_ref().unwrap().script_pubkey => && descriptor.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
{ {
Some(self.clone()) Some(descriptor)
} }
Descriptor::Bare(ms) DescriptorType::Bare | DescriptorType::Sh | DescriptorType::ShSortedMulti
if psbt_input.redeem_script.is_some() if psbt_input.redeem_script.is_some()
&& &ms.encode(deriv_ctx) == psbt_input.redeem_script.as_ref().unwrap() => && &descriptor.explicit_script()
== psbt_input.redeem_script.as_ref().unwrap() =>
{ {
Some(self.clone()) Some(descriptor)
} }
Descriptor::Sh(ms) DescriptorType::Wsh
if psbt_input.redeem_script.is_some() | DescriptorType::ShWsh
&& &ms.encode(deriv_ctx) == psbt_input.redeem_script.as_ref().unwrap() => | DescriptorType::ShWshSortedMulti
{ | DescriptorType::WshSortedMulti
Some(self.clone())
}
Descriptor::Wsh(ms) | Descriptor::ShWsh(ms)
if psbt_input.witness_script.is_some() if psbt_input.witness_script.is_some()
&& &ms.encode(deriv_ctx) == psbt_input.witness_script.as_ref().unwrap() => && &descriptor.explicit_script()
== psbt_input.witness_script.as_ref().unwrap() =>
{ {
Some(self.clone()) Some(descriptor)
}
Descriptor::ShSortedMulti(keys)
if psbt_input.redeem_script.is_some()
&& &keys.encode(deriv_ctx) == psbt_input.redeem_script.as_ref().unwrap() =>
{
Some(self.clone())
}
Descriptor::WshSortedMulti(keys) | Descriptor::ShWshSortedMulti(keys)
if psbt_input.witness_script.is_some()
&& &keys.encode(deriv_ctx) == psbt_input.witness_script.as_ref().unwrap() =>
{
Some(self.clone())
} }
_ => None, _ => None,
} }
} }
} }
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)] impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> {
struct DummyKey(); fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError> {
let mut answer = BTreeMap::new();
self.for_each_key(|key| {
if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
let derived_pubkey = xpub
.xkey
.derive_pub(secp, &xpub.derivation_path)
.expect("Derivation can't fail");
impl fmt::Display for DummyKey { answer.insert(
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { derived_pubkey.public_key,
write!(f, "DummyKey") (xpub.root_fingerprint(secp), xpub.full_path(&[])),
} );
} }
impl std::str::FromStr for DummyKey { true
type Err = (); });
fn from_str(_: &str) -> Result<Self, Self::Err> { Ok(answer)
Ok(DummyKey::default())
}
}
impl miniscript::MiniscriptKey for DummyKey {
type Hash = DummyKey;
fn to_pubkeyhash(&self) -> DummyKey {
DummyKey::default()
} }
} }
@ -694,6 +615,8 @@ mod test {
fn test_to_wallet_descriptor_fixup_networks() { fn test_to_wallet_descriptor_fixup_networks() {
use crate::keys::{any_network, ToDescriptorKey}; use crate::keys::{any_network, ToDescriptorKey};
let secp = Secp256k1::new();
let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap(); let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap(); let path = bip32::DerivationPath::from_str("m/0").unwrap();
@ -706,39 +629,41 @@ mod test {
// make a descriptor out of it // make a descriptor out of it
let desc = crate::descriptor!(wpkh(key)).unwrap(); let desc = crate::descriptor!(wpkh(key)).unwrap();
// this should conver the key that supports "any_network" to the right network (testnet) // this should conver the key that supports "any_network" to the right network (testnet)
let (wallet_desc, _) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, _) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)#y8p7e8kk");
} }
// test ToWalletDescriptor trait from &str with and without checksum appended // test ToWalletDescriptor trait from &str with and without checksum appended
#[test] #[test]
fn test_descriptor_from_str_with_checksum() { fn test_descriptor_from_str_with_checksum() {
let secp = Secp256k1::new();
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62" let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw" let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)" let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw" let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(matches!( assert!(matches!(
desc.err(), desc.err(),
Some(DescriptorError::InvalidDescriptorChecksum) Some(DescriptorError::InvalidDescriptorChecksum)
)); ));
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw" let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(matches!( assert!(matches!(
desc.err(), desc.err(),
Some(DescriptorError::InvalidDescriptorChecksum) Some(DescriptorError::InvalidDescriptorChecksum)
@ -748,39 +673,41 @@ mod test {
// test ToWalletDescriptor trait from &str with keys from right and wrong network // test ToWalletDescriptor trait from &str with keys from right and wrong network
#[test] #[test]
fn test_descriptor_from_str_with_keys_network() { fn test_descriptor_from_str_with_keys_network() {
let secp = Secp256k1::new();
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
.to_wallet_descriptor(Network::Regtest); .to_wallet_descriptor(&secp, Network::Regtest);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)" let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)" let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
.to_wallet_descriptor(Network::Regtest); .to_wallet_descriptor(&secp, Network::Regtest);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))" let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))"
.to_wallet_descriptor(Network::Testnet); .to_wallet_descriptor(&secp, Network::Testnet);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))" let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))"
.to_wallet_descriptor(Network::Bitcoin); .to_wallet_descriptor(&secp, Network::Bitcoin);
assert!(desc.is_ok()); assert!(desc.is_ok());
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
.to_wallet_descriptor(Network::Bitcoin); .to_wallet_descriptor(&secp, Network::Bitcoin);
assert!(matches!( assert!(matches!(
desc.err(), desc.err(),
Some(DescriptorError::Key(KeyError::InvalidNetwork)) Some(DescriptorError::Key(KeyError::InvalidNetwork))
)); ));
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)" let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
.to_wallet_descriptor(Network::Bitcoin); .to_wallet_descriptor(&secp, Network::Bitcoin);
assert!(matches!( assert!(matches!(
desc.err(), desc.err(),
Some(DescriptorError::Key(KeyError::InvalidNetwork)) Some(DescriptorError::Key(KeyError::InvalidNetwork))
@ -790,6 +717,8 @@ mod test {
// test ToWalletDescriptor trait from the output of the descriptor!() macro // test ToWalletDescriptor trait from the output of the descriptor!() macro
#[test] #[test]
fn test_descriptor_from_str_from_output_of_macro() { fn test_descriptor_from_str_from_output_of_macro() {
let secp = Secp256k1::new();
let tpub = bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK").unwrap(); let tpub = bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK").unwrap();
let path = bip32::DerivationPath::from_str("m/1/2").unwrap(); let path = bip32::DerivationPath::from_str("m/1/2").unwrap();
let key = (tpub, path).to_descriptor_key().unwrap(); let key = (tpub, path).to_descriptor_key().unwrap();
@ -797,12 +726,12 @@ mod test {
// make a descriptor out of it // make a descriptor out of it
let desc = crate::descriptor!(wpkh(key)).unwrap(); let desc = crate::descriptor!(wpkh(key)).unwrap();
let (wallet_desc, _) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, _) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let wallet_desc_str = wallet_desc.to_string(); let wallet_desc_str = wallet_desc.to_string();
assert_eq!(wallet_desc_str, "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"); assert_eq!(wallet_desc_str, "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw");
let (wallet_desc2, _) = wallet_desc_str let (wallet_desc2, _) = wallet_desc_str
.to_wallet_descriptor(Network::Testnet) .to_wallet_descriptor(&secp, Network::Testnet)
.unwrap(); .unwrap();
assert_eq!(wallet_desc, wallet_desc2) assert_eq!(wallet_desc, wallet_desc2)
} }

View File

@ -38,7 +38,7 @@
//! let secp = Secp256k1::new(); //! let secp = Secp256k1::new();
//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))"; //! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
//! //!
//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(desc)?; //! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?;
//! println!("{:?}", extended_desc); //! println!("{:?}", extended_desc);
//! //!
//! let signers = Arc::new(key_map.into()); //! let signers = Arc::new(key_map.into());
@ -58,15 +58,15 @@ use bitcoin::hashes::*;
use bitcoin::util::bip32::Fingerprint; use bitcoin::util::bip32::Fingerprint;
use bitcoin::PublicKey; use bitcoin::PublicKey;
use miniscript::descriptor::{DescriptorPublicKey, SortedMultiVec}; use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey}; use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey};
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace}; use log::{debug, error, info, trace};
use crate::descriptor::ExtractPolicy; use crate::descriptor::{DerivedDescriptorKey, ExtractPolicy};
use crate::wallet::signer::{SignerId, SignersContainer}; use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{self, descriptor_to_pk_ctx, SecpCtx}; use crate::wallet::utils::{self, SecpCtx};
use super::checksum::get_checksum; use super::checksum::get_checksum;
use super::error::Error; use super::error::Error;
@ -738,8 +738,9 @@ fn signature_key(
signers: &SignersContainer, signers: &SignersContainer,
secp: &SecpCtx, secp: &SecpCtx,
) -> Policy { ) -> Policy {
let deriv_ctx = descriptor_to_pk_ctx(secp); let key_hash = DerivedDescriptorKey::new(key.clone(), secp)
let key_hash = key.to_public_key(deriv_ctx).to_pubkeyhash(); .to_public_key()
.to_pubkeyhash();
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key_hash(key_hash)).into(); let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key_hash(key_hash)).into();
if signers.find(SignerId::PkHash(key_hash)).is_some() { if signers.find(SignerId::PkHash(key_hash)).is_some() {
@ -866,28 +867,28 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
} }
match self { match self {
Descriptor::Pk(pubkey) Descriptor::Pkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
| Descriptor::Pkh(pubkey) Descriptor::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
| Descriptor::Wpkh(pubkey) Descriptor::Sh(sh) => match sh.as_inner() {
| Descriptor::ShWpkh(pubkey) => Ok(Some(signature(pubkey, signers, secp))), ShInner::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
Descriptor::Bare(inner) => Ok(inner.extract_policy(signers, secp)?), ShInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
Descriptor::Sh(inner) => Ok(inner.extract_policy(signers, secp)?), ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => { ShInner::Wsh(wsh) => match wsh.as_inner() {
Ok(inner.extract_policy(signers, secp)?) WshInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
} WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
},
// `sortedmulti()` is handled separately },
Descriptor::ShSortedMulti(keys) => make_sortedmulti(&keys, signers, secp), Descriptor::Wsh(wsh) => match wsh.as_inner() {
Descriptor::ShWshSortedMulti(keys) | Descriptor::WshSortedMulti(keys) => { WshInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
make_sortedmulti(&keys, signers, secp) WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
} },
Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, secp)?),
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::descriptor; use crate::descriptor;
use crate::descriptor::{ExtractPolicy, ToWalletDescriptor}; use crate::descriptor::{ExtractPolicy, ToWalletDescriptor};
@ -897,7 +898,6 @@ mod test {
use crate::wallet::signer::SignersContainer; use crate::wallet::signer::SignersContainer;
use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32; use bitcoin::util::bip32;
use bitcoin::util::bip32::ChildNumber;
use bitcoin::Network; use bitcoin::Network;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
@ -925,9 +925,11 @@ mod test {
#[test] #[test]
fn test_extract_policy_for_wpkh() { fn test_extract_policy_for_wpkh() {
let secp = Secp256k1::new();
let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR);
let desc = descriptor!(wpkh(pubkey)).unwrap(); let desc = descriptor!(wpkh(pubkey)).unwrap();
let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &Secp256k1::new())
@ -940,7 +942,7 @@ mod test {
assert!(matches!(&policy.contribution, Satisfaction::None)); assert!(matches!(&policy.contribution, Satisfaction::None));
let desc = descriptor!(wpkh(prvkey)).unwrap(); let desc = descriptor!(wpkh(prvkey)).unwrap();
let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &Secp256k1::new())
@ -1018,10 +1020,12 @@ mod test {
#[test] #[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225 #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_sh_multi_complete_1of2() { fn test_extract_policy_for_sh_multi_complete_1of2() {
let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap(); let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &Secp256k1::new())
@ -1046,10 +1050,12 @@ mod test {
// 2 prv keys descriptor, required 2 prv keys // 2 prv keys descriptor, required 2 prv keys
#[test] #[test]
fn test_extract_policy_for_sh_multi_complete_2of2() { fn test_extract_policy_for_sh_multi_complete_2of2() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap(); let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &Secp256k1::new())
@ -1075,10 +1081,12 @@ mod test {
#[test] #[test]
fn test_extract_policy_for_single_wpkh() { fn test_extract_policy_for_single_wpkh() {
let secp = Secp256k1::new();
let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR);
let desc = descriptor!(wpkh(pubkey)).unwrap(); let desc = descriptor!(wpkh(pubkey)).unwrap();
let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap()); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &Secp256k1::new())
@ -1091,8 +1099,8 @@ mod test {
assert!(matches!(&policy.contribution, Satisfaction::None)); assert!(matches!(&policy.contribution, Satisfaction::None));
let desc = descriptor!(wpkh(prvkey)).unwrap(); let desc = descriptor!(wpkh(prvkey)).unwrap();
let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap()); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &Secp256k1::new())
@ -1111,11 +1119,13 @@ mod test {
#[test] #[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225 #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_single_wsh_multi_complete_1of2() { fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap(); let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap()); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &Secp256k1::new())
@ -1142,6 +1152,8 @@ mod test {
#[test] #[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225 #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_wsh_multi_timelock() { fn test_extract_policy_for_wsh_multi_timelock() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR);
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR); let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR);
let sequence = 50; let sequence = 50;
@ -1154,7 +1166,7 @@ mod test {
))) )))
.unwrap(); .unwrap();
let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &Secp256k1::new())

View File

@ -35,6 +35,7 @@ use miniscript::{Legacy, Segwitv0};
use super::{ExtendedDescriptor, KeyMap, ToWalletDescriptor}; use super::{ExtendedDescriptor, KeyMap, ToWalletDescriptor};
use crate::descriptor::DescriptorError; use crate::descriptor::DescriptorError;
use crate::keys::{DerivableKey, ToDescriptorKey, ValidNetworks}; use crate::keys::{DerivableKey, ToDescriptorKey, ValidNetworks};
use crate::wallet::utils::SecpCtx;
use crate::{descriptor, KeychainKind}; use crate::{descriptor, KeychainKind};
/// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others /// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others
@ -71,9 +72,10 @@ pub trait DescriptorTemplate {
impl<T: DescriptorTemplate> ToWalletDescriptor for T { impl<T: DescriptorTemplate> ToWalletDescriptor for T {
fn to_wallet_descriptor( fn to_wallet_descriptor(
self, self,
secp: &SecpCtx,
network: Network, network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
Ok(self.build()?.to_wallet_descriptor(network)?) Ok(self.build()?.to_wallet_descriptor(secp, network)?)
} }
} }
@ -201,7 +203,7 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); /// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind); pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
@ -240,7 +242,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); /// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind); pub struct BIP44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
@ -275,7 +277,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); /// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind); pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
@ -314,7 +316,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); /// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind); pub struct BIP49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
@ -349,7 +351,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); /// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind); pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
@ -388,7 +390,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); /// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind); pub struct BIP84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
@ -458,13 +460,13 @@ mod test {
// test existing descriptor templates, make sure they are expanded to the right descriptors // test existing descriptor templates, make sure they are expanded to the right descriptors
use super::*; use super::*;
use crate::descriptor::derived::AsDerived;
use crate::descriptor::{DescriptorError, DescriptorMeta}; use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks; use crate::keys::ValidNetworks;
use bitcoin::hashes::core::str::FromStr; use bitcoin::hashes::core::str::FromStr;
use bitcoin::network::constants::Network::Regtest; use bitcoin::network::constants::Network::Regtest;
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32::ChildNumber; use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};
use miniscript::descriptor::{DescriptorPublicKey, DescriptorPublicKeyCtx, KeyMap};
use miniscript::Descriptor; use miniscript::Descriptor;
// verify template descriptor generates expected address(es) // verify template descriptor generates expected address(es)
@ -475,20 +477,18 @@ mod test {
expected: &[&str], expected: &[&str],
) { ) {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let deriv_ctx =
DescriptorPublicKeyCtx::new(&secp, ChildNumber::from_normal_idx(0).unwrap());
let (desc, _key_map, _networks) = desc.unwrap(); let (desc, _key_map, _networks) = desc.unwrap();
assert_eq!(desc.is_witness(), is_witness); assert_eq!(desc.is_witness(), is_witness);
assert_eq!(desc.is_fixed(), is_fixed); assert_eq!(!desc.is_deriveable(), is_fixed);
for i in 0..expected.len() { for i in 0..expected.len() {
let index = i as u32; let index = i as u32;
let child_desc = if desc.is_fixed() { let child_desc = if !desc.is_deriveable() {
desc.clone() desc.as_derived_fixed(&secp)
} else { } else {
desc.derive(ChildNumber::from_normal_idx(index).unwrap()) desc.as_derived(index, &secp)
}; };
let address = child_desc.address(Regtest, deriv_ctx).unwrap(); let address = child_desc.address(Regtest).unwrap();
assert_eq!(address.to_string(), *expected.get(i).unwrap()); assert_eq!(address.to_string(), *expected.get(i).unwrap());
} }
} }

View File

@ -147,7 +147,7 @@ mod test {
let key = (mnemonic, path); let key = (mnemonic, path);
let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap(); let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)"); assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)#0r8v4nkv");
assert_eq!(keys.len(), 1); assert_eq!(keys.len(), 1);
assert_eq!(networks.len(), 3); assert_eq!(networks.len(), 3);
} }
@ -161,7 +161,7 @@ mod test {
let key = ((mnemonic, Some("passphrase".into())), path); let key = ((mnemonic, Some("passphrase".into())), path);
let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap(); let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)"); assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)#h0j0tg5m");
assert_eq!(keys.len(), 1); assert_eq!(keys.len(), 1);
assert_eq!(networks.len(), 3); assert_eq!(networks.len(), 3);
} }

View File

@ -35,11 +35,11 @@ use bitcoin::secp256k1::{self, Secp256k1, Signing};
use bitcoin::util::bip32; use bitcoin::util::bip32;
use bitcoin::{Network, PrivateKey, PublicKey}; use bitcoin::{Network, PrivateKey, PublicKey};
use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
pub use miniscript::descriptor::{ pub use miniscript::descriptor::{
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, KeyMap,
SortedMultiVec, SortedMultiVec,
}; };
use miniscript::descriptor::{DescriptorXKey, KeyMap};
pub use miniscript::ScriptContext; pub use miniscript::ScriptContext;
use miniscript::{Miniscript, Terminal}; use miniscript::{Miniscript, Terminal};
@ -492,14 +492,14 @@ let xprv = xkey.into_xprv(Network::Bitcoin).unwrap();
origin, origin,
xkey: xprv, xkey: xprv,
derivation_path, derivation_path,
is_wildcard: true, wildcard: Wildcard::Unhardened,
}) })
.to_descriptor_key(), .to_descriptor_key(),
ExtendedKey::Public((xpub, _)) => DescriptorPublicKey::XPub(DescriptorXKey { ExtendedKey::Public((xpub, _)) => DescriptorPublicKey::XPub(DescriptorXKey {
origin, origin,
xkey: xpub, xkey: xpub,
derivation_path, derivation_path,
is_wildcard: true, wildcard: Wildcard::Unhardened,
}) })
.to_descriptor_key(), .to_descriptor_key(),
} }
@ -776,24 +776,24 @@ pub fn make_multi<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
// Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments // Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments
#[doc(hidden)] #[doc(hidden)]
pub fn make_sortedmulti_inner<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>( pub fn make_sortedmulti<Pk, Ctx, F>(
thresh: usize, thresh: usize,
pks: Vec<Pk>, pks: Vec<Pk>,
build_desc: F,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result< ) -> Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>
( where
SortedMultiVec<DescriptorPublicKey, Ctx>, Pk: ToDescriptorKey<Ctx>,
KeyMap, Ctx: ScriptContext,
ValidNetworks, F: Fn(
), usize,
DescriptorError, Vec<DescriptorPublicKey>,
> { ) -> Result<(Descriptor<DescriptorPublicKey>, PhantomData<Ctx>), DescriptorError>,
{
let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?; let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
let minisc = SortedMultiVec::new(thresh, pks)?; let descriptor = build_desc(thresh, pks)?.0;
// TODO: should we apply the checks here as well? Ok((descriptor, key_map, valid_networks))
Ok((minisc, key_map, valid_networks))
} }
/// The "identity" conversion is used internally by some `bdk::fragment`s /// The "identity" conversion is used internally by some `bdk::fragment`s

View File

@ -76,6 +76,7 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use miniscript::descriptor::{ShInner, WshInner};
use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal}; use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal};
use crate::database::BatchDatabase; use crate::database::BatchDatabase;
@ -107,6 +108,10 @@ impl FromStr for WalletExport {
} }
} }
fn remove_checksum(s: String) -> String {
s.splitn(2, '#').next().map(String::from).unwrap()
}
impl WalletExport { impl WalletExport {
/// Export a wallet /// Export a wallet
/// ///
@ -127,6 +132,7 @@ impl WalletExport {
let descriptor = wallet let descriptor = wallet
.descriptor .descriptor
.to_string_with_secret(&wallet.signers.as_key_map(wallet.secp_ctx())); .to_string_with_secret(&wallet.signers.as_key_map(wallet.secp_ctx()));
let descriptor = remove_checksum(descriptor);
Self::is_compatible_with_core(&descriptor)?; Self::is_compatible_with_core(&descriptor)?;
let blockheight = match wallet.database.borrow().iter_txs(false) { let blockheight = match wallet.database.borrow().iter_txs(false) {
@ -150,7 +156,9 @@ impl WalletExport {
}; };
let desc_to_string = |d: &Descriptor<DescriptorPublicKey>| { let desc_to_string = |d: &Descriptor<DescriptorPublicKey>| {
d.to_string_with_secret(&wallet.change_signers.as_key_map(wallet.secp_ctx())) let descriptor =
d.to_string_with_secret(&wallet.change_signers.as_key_map(wallet.secp_ctx()));
remove_checksum(descriptor)
}; };
if export.change_descriptor() != wallet.change_descriptor.as_ref().map(desc_to_string) { if export.change_descriptor() != wallet.change_descriptor.as_ref().map(desc_to_string) {
return Err("Incompatible change descriptor"); return Err("Incompatible change descriptor");
@ -161,7 +169,7 @@ impl WalletExport {
fn is_compatible_with_core(descriptor: &str) -> Result<(), &'static str> { fn is_compatible_with_core(descriptor: &str) -> Result<(), &'static str> {
fn check_ms<Ctx: ScriptContext>( fn check_ms<Ctx: ScriptContext>(
terminal: Terminal<String, Ctx>, terminal: &Terminal<String, Ctx>,
) -> Result<(), &'static str> { ) -> Result<(), &'static str> {
if let Terminal::Multi(_, _) = terminal { if let Terminal::Multi(_, _) = terminal {
Ok(()) Ok(())
@ -170,13 +178,22 @@ impl WalletExport {
} }
} }
// pkh(), wpkh(), sh(wpkh()) are always fine, as well as multi() and sortedmulti()
match Descriptor::<String>::from_str(descriptor).map_err(|_| "Invalid descriptor")? { match Descriptor::<String>::from_str(descriptor).map_err(|_| "Invalid descriptor")? {
Descriptor::Pk(_) Descriptor::Pkh(_) | Descriptor::Wpkh(_) => Ok(()),
| Descriptor::Pkh(_) Descriptor::Sh(sh) => match sh.as_inner() {
| Descriptor::Wpkh(_) ShInner::Wpkh(_) => Ok(()),
| Descriptor::ShWpkh(_) => Ok(()), ShInner::SortedMulti(_) => Ok(()),
Descriptor::Sh(ms) => check_ms(ms.node), ShInner::Wsh(wsh) => match wsh.as_inner() {
Descriptor::Wsh(ms) | Descriptor::ShWsh(ms) => check_ms(ms.node), WshInner::SortedMulti(_) => Ok(()),
WshInner::Ms(ms) => check_ms(&ms.node),
},
ShInner::Ms(ms) => check_ms(&ms.node),
},
Descriptor::Wsh(wsh) => match wsh.as_inner() {
WshInner::SortedMulti(_) => Ok(()),
WshInner::Ms(ms) => check_ms(&ms.node),
},
_ => Err("The descriptor is not compatible with Bitcoin Core"), _ => Err("The descriptor is not compatible with Bitcoin Core"),
} }
} }

View File

@ -36,11 +36,11 @@ use bitcoin::secp256k1::Secp256k1;
use bitcoin::consensus::encode::serialize; use bitcoin::consensus::encode::serialize;
use bitcoin::util::base58; use bitcoin::util::base58;
use bitcoin::util::bip32::ChildNumber;
use bitcoin::util::psbt::raw::Key as PSBTKey; use bitcoin::util::psbt::raw::Key as PSBTKey;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::{Address, Network, OutPoint, Script, Transaction, TxOut, Txid}; use bitcoin::{Address, Network, OutPoint, Script, Transaction, TxOut, Txid};
use miniscript::descriptor::DescriptorTrait;
use miniscript::psbt::PsbtInputSatisfier; use miniscript::psbt::PsbtInputSatisfier;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -60,16 +60,14 @@ use address_validator::AddressValidator;
use coin_selection::DefaultCoinSelectionAlgorithm; use coin_selection::DefaultCoinSelectionAlgorithm;
use signer::{Signer, SignerOrdering, SignersContainer}; use signer::{Signer, SignerOrdering, SignersContainer};
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams}; use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
use utils::{ use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LIMIT_SATOSHI};
check_nlocktime, check_nsequence_rbf, descriptor_to_pk_ctx, After, Older, SecpCtx,
DUST_LIMIT_SATOSHI,
};
use crate::blockchain::{Blockchain, Progress}; use crate::blockchain::{Blockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::descriptor::derived::AsDerived;
use crate::descriptor::{ use crate::descriptor::{
get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy, get_checksum, DerivedDescriptor, DerivedDescriptorMeta, DescriptorMeta, DescriptorScripts,
ToWalletDescriptor, XKeyUtils, ExtendedDescriptor, ExtractPolicy, Policy, ToWalletDescriptor, XKeyUtils,
}; };
use crate::error::Error; use crate::error::Error;
use crate::psbt::PSBTUtils; use crate::psbt::PSBTUtils;
@ -134,7 +132,9 @@ where
client: B, client: B,
current_height: Option<u32>, current_height: Option<u32>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let (descriptor, keymap) = descriptor.to_wallet_descriptor(network)?; let secp = Secp256k1::new();
let (descriptor, keymap) = descriptor.to_wallet_descriptor(&secp, network)?;
database.check_descriptor_checksum( database.check_descriptor_checksum(
KeychainKind::External, KeychainKind::External,
get_checksum(&descriptor.to_string())?.as_bytes(), get_checksum(&descriptor.to_string())?.as_bytes(),
@ -142,7 +142,8 @@ where
let signers = Arc::new(SignersContainer::from(keymap)); let signers = Arc::new(SignersContainer::from(keymap));
let (change_descriptor, change_signers) = match change_descriptor { let (change_descriptor, change_signers) = match change_descriptor {
Some(desc) => { Some(desc) => {
let (change_descriptor, change_keymap) = desc.to_wallet_descriptor(network)?; let (change_descriptor, change_keymap) =
desc.to_wallet_descriptor(&secp, network)?;
database.check_descriptor_checksum( database.check_descriptor_checksum(
KeychainKind::Internal, KeychainKind::Internal,
get_checksum(&change_descriptor.to_string())?.as_bytes(), get_checksum(&change_descriptor.to_string())?.as_bytes(),
@ -168,7 +169,7 @@ where
current_height, current_height,
client, client,
database: RefCell::new(database), database: RefCell::new(database),
secp: Secp256k1::new(), secp,
}) })
} }
} }
@ -181,12 +182,11 @@ where
/// Return a newly generated address using the external descriptor /// Return a newly generated address using the external descriptor
pub fn get_new_address(&self) -> Result<Address, Error> { pub fn get_new_address(&self) -> Result<Address, Error> {
let index = self.fetch_and_increment_index(KeychainKind::External)?; let index = self.fetch_and_increment_index(KeychainKind::External)?;
let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
self.descriptor self.descriptor
.derive(ChildNumber::from_normal_idx(index)?) .as_derived(index, &self.secp)
.address(self.network, deriv_ctx) .address(self.network)
.ok_or(Error::ScriptDoesntHaveAddressForm) .map_err(|_| Error::ScriptDoesntHaveAddressForm)
} }
/// Return whether or not a `script` is part of this wallet (either internal or external) /// Return whether or not a `script` is part of this wallet (either internal or external)
@ -666,7 +666,6 @@ where
let vbytes = tx.get_weight() as f32 / 4.0; let vbytes = tx.get_weight() as f32 / 4.0;
let feerate = details.fees as f32 / vbytes; let feerate = details.fees as f32 / vbytes;
let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
// remove the inputs from the tx and process them // remove the inputs from the tx and process them
let original_txin = tx.input.drain(..).collect::<Vec<_>>(); let original_txin = tx.input.drain(..).collect::<Vec<_>>();
let original_utxos = original_txin let original_utxos = original_txin
@ -686,7 +685,7 @@ where
Some((keychain, _)) => ( Some((keychain, _)) => (
self._get_descriptor_for_keychain(keychain) self._get_descriptor_for_keychain(keychain)
.0 .0
.max_satisfaction_weight(deriv_ctx) .max_satisfaction_weight()
.unwrap(), .unwrap(),
keychain, keychain,
), ),
@ -855,7 +854,7 @@ where
// - Try to derive the descriptor by looking at the txout. If it's in our database, we // - Try to derive the descriptor by looking at the txout. If it's in our database, we
// know exactly which `keychain` to use, and which derivation index it is // know exactly which `keychain` to use, and which derivation index it is
// - If that fails, try to derive it by looking at the psbt input: the complete logic // - If that fails, try to derive it by looking at the psbt input: the complete logic
// is in `src/descriptor/mod.rs`, but it will basically look at `hd_keypaths`, // is in `src/descriptor/mod.rs`, but it will basically look at `bip32_derivation`,
// `redeem_script` and `witness_script` to determine the right derivation // `redeem_script` and `witness_script` to determine the right derivation
// - If that also fails, it will try it on the internal descriptor, if present // - If that also fails, it will try it on the internal descriptor, if present
let desc = psbt let desc = psbt
@ -879,7 +878,6 @@ where
match desc { match desc {
Some(desc) => { Some(desc) => {
let mut tmp_input = bitcoin::TxIn::default(); let mut tmp_input = bitcoin::TxIn::default();
let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
match desc.satisfy( match desc.satisfy(
&mut tmp_input, &mut tmp_input,
( (
@ -887,7 +885,6 @@ where
After::new(current_height, false), After::new(current_height, false),
Older::new(current_height, create_height, false), Older::new(current_height, create_height, false),
), ),
deriv_ctx,
) { ) {
Ok(_) => { Ok(_) => {
let psbt_input = &mut psbt.inputs[n]; let psbt_input = &mut psbt.inputs[n];
@ -933,31 +930,30 @@ where
} }
} }
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<ExtendedDescriptor>, Error> { fn get_descriptor_for_txout(
&self,
txout: &TxOut,
) -> Result<Option<DerivedDescriptor<'_>>, Error> {
Ok(self Ok(self
.database .database
.borrow() .borrow()
.get_path_from_script_pubkey(&txout.script_pubkey)? .get_path_from_script_pubkey(&txout.script_pubkey)?
.map(|(keychain, child)| (self.get_descriptor_for_keychain(keychain), child)) .map(|(keychain, child)| (self.get_descriptor_for_keychain(keychain), child))
.map(|(desc, child)| desc.derive(ChildNumber::from_normal_idx(child).unwrap()))) .map(|(desc, child)| desc.as_derived(child, &self.secp)))
} }
fn get_change_address(&self) -> Result<Script, Error> { fn get_change_address(&self) -> Result<Script, Error> {
let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
let (desc, keychain) = self._get_descriptor_for_keychain(KeychainKind::Internal); let (desc, keychain) = self._get_descriptor_for_keychain(KeychainKind::Internal);
let index = self.fetch_and_increment_index(keychain)?; let index = self.fetch_and_increment_index(keychain)?;
Ok(desc Ok(desc.as_derived(index, &self.secp).script_pubkey())
.derive(ChildNumber::from_normal_idx(index)?)
.script_pubkey(deriv_ctx))
} }
fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> { fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain); let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
let index = match descriptor.is_fixed() { let index = match descriptor.is_deriveable() {
true => 0, false => 0,
false => self.database.borrow_mut().increment_last_index(keychain)?, true => self.database.borrow_mut().increment_last_index(keychain)?,
}; };
if self if self
@ -969,12 +965,11 @@ where
self.cache_addresses(keychain, index, CACHE_ADDR_BATCH_SIZE)?; self.cache_addresses(keychain, index, CACHE_ADDR_BATCH_SIZE)?;
} }
let deriv_ctx = descriptor_to_pk_ctx(&self.secp); let derived_descriptor = descriptor.as_derived(index, &self.secp);
let hd_keypaths = derived_descriptor.get_hd_keypaths(&self.secp)?;
let script = derived_descriptor.script_pubkey();
let hd_keypaths = descriptor.get_hd_keypaths(index, &self.secp)?;
let script = descriptor
.derive(ChildNumber::from_normal_idx(index)?)
.script_pubkey(deriv_ctx);
for validator in &self.address_validators { for validator in &self.address_validators {
validator.validate(keychain, &hd_keypaths, &script)?; validator.validate(keychain, &hd_keypaths, &script)?;
} }
@ -989,7 +984,7 @@ where
mut count: u32, mut count: u32,
) -> Result<(), Error> { ) -> Result<(), Error> {
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain); let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
if descriptor.is_fixed() { if !descriptor.is_deriveable() {
if from > 0 { if from > 0 {
return Ok(()); return Ok(());
} }
@ -997,16 +992,12 @@ where
count = 1; count = 1;
} }
let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
let mut address_batch = self.database.borrow().begin_batch(); let mut address_batch = self.database.borrow().begin_batch();
let start_time = time::Instant::new(); let start_time = time::Instant::new();
for i in from..(from + count) { for i in from..(from + count) {
address_batch.set_script_pubkey( address_batch.set_script_pubkey(
&descriptor &descriptor.as_derived(i, &self.secp).script_pubkey(),
.derive(ChildNumber::from_normal_idx(i)?)
.script_pubkey(deriv_ctx),
keychain, keychain,
i, i,
)?; )?;
@ -1025,7 +1016,6 @@ where
} }
fn get_available_utxos(&self) -> Result<Vec<(UTXO, usize)>, Error> { fn get_available_utxos(&self) -> Result<Vec<(UTXO, usize)>, Error> {
let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
Ok(self Ok(self
.list_unspent()? .list_unspent()?
.into_iter() .into_iter()
@ -1034,7 +1024,7 @@ where
( (
utxo, utxo,
self.get_descriptor_for_keychain(keychain) self.get_descriptor_for_keychain(keychain)
.max_satisfaction_weight(deriv_ctx) .max_satisfaction_weight()
.unwrap(), .unwrap(),
) )
}) })
@ -1174,19 +1164,19 @@ where
}; };
let (desc, _) = self._get_descriptor_for_keychain(keychain); let (desc, _) = self._get_descriptor_for_keychain(keychain);
psbt_input.hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?; let derived_descriptor = desc.as_derived(child, &self.secp);
let derived_descriptor = desc.derive(ChildNumber::from_normal_idx(child)?); psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?;
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script(&self.secp); psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
psbt_input.witness_script = derived_descriptor.psbt_witness_script(&self.secp); psbt_input.witness_script = derived_descriptor.psbt_witness_script();
let prev_output = input.previous_output; let prev_output = input.previous_output;
if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? { if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? {
if derived_descriptor.is_witness() { if desc.is_witness() {
psbt_input.witness_utxo = psbt_input.witness_utxo =
Some(prev_tx.output[prev_output.vout as usize].clone()); Some(prev_tx.output[prev_output.vout as usize].clone());
} }
if !derived_descriptor.is_witness() || params.force_non_witness_utxo { if !desc.is_witness() || params.force_non_witness_utxo {
psbt_input.non_witness_utxo = Some(prev_tx); psbt_input.non_witness_utxo = Some(prev_tx);
} }
} }
@ -1207,11 +1197,12 @@ where
.get_path_from_script_pubkey(&tx_output.script_pubkey)? .get_path_from_script_pubkey(&tx_output.script_pubkey)?
{ {
let (desc, _) = self._get_descriptor_for_keychain(keychain); let (desc, _) = self._get_descriptor_for_keychain(keychain);
psbt_output.hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?; let derived_descriptor = desc.as_derived(child, &self.secp);
psbt_output.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?;
if params.include_output_redeem_witness_script { if params.include_output_redeem_witness_script {
let derived_descriptor = desc.derive(ChildNumber::from_normal_idx(child)?); psbt_output.witness_script = derived_descriptor.psbt_witness_script();
psbt_output.witness_script = derived_descriptor.psbt_witness_script(&self.secp); psbt_output.redeem_script = derived_descriptor.psbt_redeem_script();
psbt_output.redeem_script = derived_descriptor.psbt_redeem_script(&self.secp);
}; };
} }
} }
@ -1237,8 +1228,10 @@ where
// merge hd_keypaths // merge hd_keypaths
let desc = self.get_descriptor_for_keychain(keychain); let desc = self.get_descriptor_for_keychain(keychain);
let mut hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?; let mut hd_keypaths = desc
psbt_input.hd_keypaths.append(&mut hd_keypaths); .as_derived(child, &self.secp)
.get_hd_keypaths(&self.secp)?;
psbt_input.bip32_derivation.append(&mut hd_keypaths);
} }
} }
} }
@ -1283,9 +1276,9 @@ where
let mut run_setup = false; let mut run_setup = false;
let max_address = match self.descriptor.is_fixed() { let max_address = match self.descriptor.is_deriveable() {
true => 0, false => 0,
false => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE), true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
}; };
if self if self
.database .database
@ -1298,9 +1291,9 @@ where
} }
if let Some(change_descriptor) = &self.change_descriptor { if let Some(change_descriptor) = &self.change_descriptor {
let max_address = match change_descriptor.is_fixed() { let max_address = match change_descriptor.is_deriveable() {
true => 0, false => 0,
false => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE), true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
}; };
if self if self
@ -1946,9 +1939,9 @@ mod test {
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1); assert_eq!(psbt.inputs[0].bip32_derivation.len(), 1);
assert_eq!( assert_eq!(
psbt.inputs[0].hd_keypaths.values().nth(0).unwrap(), psbt.inputs[0].bip32_derivation.values().nth(0).unwrap(),
&( &(
Fingerprint::from_str("d34db33f").unwrap(), Fingerprint::from_str("d34db33f").unwrap(),
DerivationPath::from_str("m/44'/0'/0'/0/0").unwrap() DerivationPath::from_str("m/44'/0'/0'/0/0").unwrap()
@ -1972,9 +1965,9 @@ mod test {
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1); assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1);
assert_eq!( assert_eq!(
psbt.outputs[0].hd_keypaths.values().nth(0).unwrap(), psbt.outputs[0].bip32_derivation.values().nth(0).unwrap(),
&( &(
Fingerprint::from_str("d34db33f").unwrap(), Fingerprint::from_str("d34db33f").unwrap(),
DerivationPath::from_str("m/44'/0'/0'/0/5").unwrap() DerivationPath::from_str("m/44'/0'/0'/0/5").unwrap()
@ -3188,8 +3181,8 @@ mod test {
.drain_wallet(); .drain_wallet();
let (mut psbt, _) = builder.finish().unwrap(); let (mut psbt, _) = builder.finish().unwrap();
psbt.inputs[0].hd_keypaths.clear(); psbt.inputs[0].bip32_derivation.clear();
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 0); assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0);
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert_eq!(finalized, true); assert_eq!(finalized, true);
@ -3233,7 +3226,7 @@ mod test {
"wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)", "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)",
) )
.unwrap() .unwrap()
.script_pubkey(miniscript::NullCtx), .script_pubkey(),
}); });
psbt.inputs.push(dud_input); psbt.inputs.push(dud_input);
psbt.global.unsigned_tx.input.push(bitcoin::TxIn::default()); psbt.global.unsigned_tx.input.push(bitcoin::TxIn::default());

View File

@ -220,7 +220,7 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
} }
let (public_key, deriv_path) = match psbt.inputs[input_index] let (public_key, deriv_path) = match psbt.inputs[input_index]
.hd_keypaths .bip32_derivation
.iter() .iter()
.filter_map(|(pk, &(fingerprint, ref path))| { .filter_map(|(pk, &(fingerprint, ref path))| {
if self.matches(&(fingerprint, path.clone()), &secp).is_some() { if self.matches(&(fingerprint, path.clone()), &secp).is_some() {
@ -562,7 +562,7 @@ mod signers_container_tests {
use crate::descriptor; use crate::descriptor;
use crate::descriptor::ToWalletDescriptor; use crate::descriptor::ToWalletDescriptor;
use crate::keys::{DescriptorKey, ToDescriptorKey}; use crate::keys::{DescriptorKey, ToDescriptorKey};
use bitcoin::secp256k1::All; use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32; use bitcoin::util::bip32;
use bitcoin::util::psbt::PartiallySignedTransaction; use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin::Network; use bitcoin::Network;
@ -574,10 +574,12 @@ mod signers_container_tests {
// This happens usually when a set of signers is created from a descriptor with private keys. // This happens usually when a set of signers is created from a descriptor with private keys.
#[test] #[test]
fn signers_with_same_ordering() { fn signers_with_same_ordering() {
let secp = Secp256k1::new();
let (prvkey1, _, _) = setup_keys(TPRV0_STR); let (prvkey1, _, _) = setup_keys(TPRV0_STR);
let (prvkey2, _, _) = setup_keys(TPRV1_STR); let (prvkey2, _, _) = setup_keys(TPRV1_STR);
let desc = descriptor!(sh(multi(2, prvkey1, prvkey2))).unwrap(); let desc = descriptor!(sh(multi(2, prvkey1, prvkey2))).unwrap();
let (_, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let (_, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
let signers = SignersContainer::from(keymap); let signers = SignersContainer::from(keymap);
assert_eq!(signers.ids().len(), 2); assert_eq!(signers.ids().len(), 2);

View File

@ -57,6 +57,8 @@ use std::marker::PhantomData;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::{OutPoint, Script, SigHashType, Transaction}; use bitcoin::{OutPoint, Script, SigHashType, Transaction};
use miniscript::descriptor::DescriptorTrait;
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use crate::{database::BatchDatabase, Error, Wallet}; use crate::{database::BatchDatabase, Error, Wallet};
use crate::{ use crate::{
@ -276,7 +278,6 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
/// the "utxos" and the "unspendable" list, it will be spent. /// the "utxos" and the "unspendable" list, it will be spent.
pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> { pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> {
let deriv_ctx = crate::wallet::descriptor_to_pk_ctx(self.wallet.secp_ctx());
let utxos = outpoints let utxos = outpoints
.iter() .iter()
.map(|outpoint| self.wallet.get_utxo(*outpoint)?.ok_or(Error::UnknownUTXO)) .map(|outpoint| self.wallet.get_utxo(*outpoint)?.ok_or(Error::UnknownUTXO))
@ -284,7 +285,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
for utxo in utxos { for utxo in utxos {
let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain); let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain);
let satisfaction_weight = descriptor.max_satisfaction_weight(deriv_ctx).unwrap(); let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap();
self.params.utxos.push((utxo, satisfaction_weight)); self.params.utxos.push((utxo, satisfaction_weight));
} }

View File

@ -23,9 +23,7 @@
// SOFTWARE. // SOFTWARE.
use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32;
use miniscript::descriptor::DescriptorPublicKeyCtx;
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey}; use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
// De-facto standard "dust limit" (even though it should change based on the output type) // De-facto standard "dust limit" (even though it should change based on the output type)
@ -110,7 +108,7 @@ pub(crate) fn check_nlocktime(nlocktime: u32, required: u32) -> bool {
true true
} }
impl<ToPkCtx: Copy, Pk: MiniscriptKey + ToPublicKey<ToPkCtx>> Satisfier<ToPkCtx, Pk> for After { impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
fn check_after(&self, n: u32) -> bool { fn check_after(&self, n: u32) -> bool {
if let Some(current_height) = self.current_height { if let Some(current_height) = self.current_height {
current_height >= n current_height >= n
@ -140,7 +138,7 @@ impl Older {
} }
} }
impl<ToPkCtx: Copy, Pk: MiniscriptKey + ToPublicKey<ToPkCtx>> Satisfier<ToPkCtx, Pk> for Older { impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
fn check_older(&self, n: u32) -> bool { fn check_older(&self, n: u32) -> bool {
if let Some(current_height) = self.current_height { if let Some(current_height) = self.current_height {
// TODO: test >= / > // TODO: test >= / >
@ -152,12 +150,6 @@ impl<ToPkCtx: Copy, Pk: MiniscriptKey + ToPublicKey<ToPkCtx>> Satisfier<ToPkCtx,
} }
pub(crate) type SecpCtx = Secp256k1<All>; pub(crate) type SecpCtx = Secp256k1<All>;
pub(crate) fn descriptor_to_pk_ctx(secp: &SecpCtx) -> DescriptorPublicKeyCtx<'_, All> {
// Create a `to_pk_ctx` with a dummy derivation index, since we always use this on descriptor
// that have already been derived with `Descriptor::derive()`, so the child number added here
// is ignored.
DescriptorPublicKeyCtx::new(secp, bip32::ChildNumber::Normal { index: 0 })
}
pub struct ChunksIterator<I: Iterator> { pub struct ChunksIterator<I: Iterator> {
iter: I, iter: I,

View File

@ -20,6 +20,7 @@ log = "0.4.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
serial_test = "0.4" serial_test = "0.4"
bitcoin = "0.25" bitcoin = "0.26"
bitcoincore-rpc = "0.12" bitcoincore-rpc = "0.13"
electrum-client = "0.4.0-beta.1" miniscript = "5.1"
electrum-client = "0.6.0"

View File

@ -40,7 +40,11 @@ use log::{debug, error, info, trace};
use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::sha256d; use bitcoin::hashes::sha256d;
use bitcoin::{Address, Amount, Script, Transaction, Txid}; use bitcoin::secp256k1::{Secp256k1, Verification};
use bitcoin::{Address, Amount, PublicKey, Script, Transaction, Txid};
use miniscript::descriptor::DescriptorPublicKey;
use miniscript::{Descriptor, MiniscriptKey, TranslatePk};
pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType; pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi}; pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
@ -113,23 +117,62 @@ impl TestIncomingTx {
} }
} }
#[doc(hidden)]
pub trait TranslateDescriptor {
// derive and translate a `Descriptor<DescriptorPublicKey>` into a `Descriptor<PublicKey>`
fn derive_translated<C: Verification>(
&self,
secp: &Secp256k1<C>,
index: u32,
) -> Descriptor<PublicKey>;
}
impl TranslateDescriptor for Descriptor<DescriptorPublicKey> {
fn derive_translated<C: Verification>(
&self,
secp: &Secp256k1<C>,
index: u32,
) -> Descriptor<PublicKey> {
let translate = |key: &DescriptorPublicKey| -> PublicKey {
match key {
DescriptorPublicKey::XPub(xpub) => {
xpub.xkey
.derive_pub(secp, &xpub.derivation_path)
.expect("hardened derivation steps")
.public_key
}
DescriptorPublicKey::SinglePub(key) => key.key,
}
};
self.derive(index)
.translate_pk_infallible(|pk| translate(pk), |pkh| translate(pkh).to_pubkeyhash())
}
}
#[macro_export] #[macro_export]
macro_rules! testutils { macro_rules! testutils {
( @external $descriptors:expr, $child:expr ) => ({ ( @external $descriptors:expr, $child:expr ) => ({
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorPublicKeyCtx}; use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
use $crate::TranslateDescriptor;
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let deriv_ctx = DescriptorPublicKeyCtx::new(&secp, bitcoin::util::bip32::ChildNumber::from_normal_idx(0).unwrap());
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0; let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
parsed.derive(bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()).address(bitcoin::Network::Regtest, deriv_ctx).expect("No address form") parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
}); });
( @internal $descriptors:expr, $child:expr ) => ({ ( @internal $descriptors:expr, $child:expr ) => ({
use miniscript::descriptor::{Descriptor, DescriptorPublicKey}; use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0; use $crate::TranslateDescriptor;
parsed.derive(bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()).address(bitcoin::Network::Regtest).expect("No address form")
let secp = Secp256k1::new();
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
}); });
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) }); ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) }); ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
@ -202,6 +245,7 @@ macro_rules! testutils {
use std::convert::TryInto; use std::convert::TryInto;
use miniscript::descriptor::{Descriptor, DescriptorPublicKey}; use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
use miniscript::TranslatePk;
let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new(); let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
$( $(
@ -209,40 +253,39 @@ macro_rules! testutils {
)* )*
let external: Descriptor<String> = FromStr::from_str($external_descriptor).unwrap(); let external: Descriptor<String> = FromStr::from_str($external_descriptor).unwrap();
let external: Descriptor<String> = external.translate_pk::<_, _, _, &'static str>(|k| { let external: Descriptor<String> = external.translate_pk_infallible::<_, _>(|k| {
if let Some((key, ext_path, _)) = keys.get(&k.as_str()) { if let Some((key, ext_path, _)) = keys.get(&k.as_str()) {
Ok(format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))) format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
} else { } else {
Ok(k.clone()) k.clone()
} }
}, |kh| { }, |kh| {
if let Some((key, ext_path, _)) = keys.get(&kh.as_str()) { if let Some((key, ext_path, _)) = keys.get(&kh.as_str()) {
Ok(format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))) format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
} else { } else {
Ok(kh.clone()) kh.clone()
} }
}).unwrap(); });
let external = external.to_string(); let external = external.to_string();
let mut internal = None::<String>; let mut internal = None::<String>;
$( $(
let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap(); let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
let string_internal: Descriptor<String> = string_internal.translate_pk::<_, _, _, &'static str>(|k| { let string_internal: Descriptor<String> = string_internal.translate_pk_infallible::<_, _>(|k| {
if let Some((key, _, int_path)) = keys.get(&k.as_str()) { if let Some((key, _, int_path)) = keys.get(&k.as_str()) {
Ok(format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))) format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
} else { } else {
Ok(k.clone()) k.clone()
} }
}, |kh| { }, |kh| {
if let Some((key, _, int_path)) = keys.get(&kh.as_str()) { if let Some((key, _, int_path)) = keys.get(&kh.as_str()) {
Ok(format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))) format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
} else { } else {
Ok(kh.clone()) kh.clone()
} }
});
}).unwrap();
internal = Some(string_internal.to_string()); internal = Some(string_internal.to_string());
)* )*