From e442bd24e4d35a9c0867812275886d0cb09121ab Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Sat, 31 May 2025 15:17:46 -0300 Subject: [PATCH 1/4] feat!(descriptor): use `NetworkKind` --- wallet/src/descriptor/dsl.rs | 171 ++++++++++--------- wallet/src/descriptor/mod.rs | 212 ++++++++++++------------ wallet/src/descriptor/policy.rs | 127 +++++++------- wallet/src/descriptor/template.rs | 264 ++++++++++++++++++------------ 4 files changed, 427 insertions(+), 347 deletions(-) diff --git a/wallet/src/descriptor/dsl.rs b/wallet/src/descriptor/dsl.rs index 0bce9510..873cda6a 100644 --- a/wallet/src/descriptor/dsl.rs +++ b/wallet/src/descriptor/dsl.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -11,10 +11,11 @@ //! Descriptors DSL +/// Top-level script hash descriptor constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_top_level_sh { - // disallow `sortedmulti` in `bare()` + // Disallow `sortedmulti` in `bare()` ( Bare, new, new, Legacy, sortedmulti $( $inner:tt )* ) => { compile_error!("`bare()` descriptors can't contain any `sortedmulti()` operands"); }; @@ -51,11 +52,12 @@ macro_rules! impl_top_level_sh { use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey}; $crate::fragment!($( $minisc )*) - .and_then(|(minisc, keymap, networks)| Ok(($inner_struct::$constructor(minisc)?, keymap, networks))) - .and_then(|(inner, key_map, valid_networks)| Ok((Descriptor::::$inner_struct(inner), key_map, valid_networks))) + .and_then(|(minisc, keymap, network_kinds)| Ok(($inner_struct::$constructor(minisc)?, keymap, network_kinds))) + .and_then(|(inner, key_map, valid_network_kinds)| Ok((Descriptor::::$inner_struct(inner), key_map, valid_network_kinds))) }}; } +/// Top-level public key descriptor constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_top_level_pk { @@ -69,10 +71,13 @@ macro_rules! impl_top_level_pk { $key.into_descriptor_key() .and_then(|key: DescriptorKey<$ctx>| key.extract(&secp)) .map_err($crate::descriptor::DescriptorError::Key) - .map(|(pk, key_map, valid_networks)| ($inner_type::new(pk), key_map, valid_networks)) + .map(|(pk, key_map, valid_network_kinds)| { + ($inner_type::new(pk), key_map, valid_network_kinds) + }) }}; } +/// Top-level Taproot descriptor constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_top_level_tr { @@ -83,7 +88,7 @@ macro_rules! impl_top_level_tr { use $crate::miniscript::Tap; #[allow(unused_imports)] - use $crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks}; + use $crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworkKinds}; let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); @@ -91,16 +96,18 @@ macro_rules! impl_top_level_tr { .into_descriptor_key() .and_then(|key: DescriptorKey| key.extract(&secp)) .map_err($crate::descriptor::DescriptorError::Key) - .and_then(|(pk, mut key_map, mut valid_networks)| { + .and_then(|(pk, mut key_map, mut valid_network_kinds)| { let tap_tree = $tap_tree.map( - |(tap_tree, tree_keymap, tree_networks): ( + |(tap_tree, tree_keymap, tree_network_kinds): ( TapTree, KeyMap, - ValidNetworks, + ValidNetworkKinds, )| { key_map.extend(tree_keymap.into_iter()); - valid_networks = - $crate::keys::merge_networks(&valid_networks, &tree_networks); + valid_network_kinds = $crate::keys::merge_network_kinds( + &valid_network_kinds, + &tree_network_kinds, + ); tap_tree }, @@ -109,12 +116,13 @@ macro_rules! impl_top_level_tr { Ok(( Descriptor::::Tr(Tr::new(pk, tap_tree)?), key_map, - valid_networks, + valid_network_kinds, )) }) }}; } +/// Leaf opcodes with no arguments (e.g., `true`, `false`) constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_leaf_opcode { @@ -133,12 +141,13 @@ macro_rules! impl_leaf_opcode { ( minisc, $crate::miniscript::descriptor::KeyMap::default(), - $crate::keys::any_network(), + $crate::keys::any_network_kind(), ) }) }}; } +/// Leaf opcodes with one argument (e.g., `older(144)`, `after(144)`) constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_leaf_opcode_value { @@ -157,12 +166,13 @@ macro_rules! impl_leaf_opcode_value { ( minisc, $crate::miniscript::descriptor::KeyMap::default(), - $crate::keys::any_network(), + $crate::keys::any_network_kind(), ) }) }}; } +/// Leaf opcodes with two arguments (e.g., `and_v(a, b)`) constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_leaf_opcode_value_two { @@ -181,12 +191,13 @@ macro_rules! impl_leaf_opcode_value_two { ( minisc, $crate::miniscript::descriptor::KeyMap::default(), - $crate::keys::any_network(), + $crate::keys::any_network_kind(), ) }) }}; } +/// Node opcodes that take exactly two child expressions (e.g., `and_v`, `or_b`) constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_node_opcode_two { @@ -198,7 +209,7 @@ macro_rules! impl_node_opcode_two { a .and_then(|a| Ok((a, b?))) - .and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks))| { + .and_then(|((a_minisc, mut a_keymap, a_network_kinds), (b_minisc, b_keymap, b_network_kinds))| { // join key_maps a_keymap.extend(b_keymap.into_iter()); @@ -209,11 +220,12 @@ macro_rules! impl_node_opcode_two { minisc.check_miniscript()?; - Ok((minisc, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks))) + Ok((minisc, a_keymap, $crate::keys::merge_network_kinds(&a_network_kinds, &b_network_kinds))) }) }); } +/// Node opcodes that take exactly three child expressions (e.g., `and_or`) constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_node_opcode_three { @@ -225,13 +237,13 @@ macro_rules! impl_node_opcode_three { a .and_then(|a| Ok((a, b?, c?))) - .and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks), (c_minisc, c_keymap, c_networks))| { + .and_then(|((a_minisc, mut a_keymap, a_network_kinds), (b_minisc, b_keymap, b_network_kinds), (c_minisc, c_keymap, c_network_kinds))| { // join key_maps a_keymap.extend(b_keymap.into_iter()); a_keymap.extend(c_keymap.into_iter()); - let networks = $crate::keys::merge_networks(&a_networks, &b_networks); - let networks = $crate::keys::merge_networks(&networks, &c_networks); + let network_kinds = $crate::keys::merge_network_kinds(&a_network_kinds, &b_network_kinds); + let network_kinds = $crate::keys::merge_network_kinds(&network_kinds, &c_network_kinds); let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant( $crate::alloc::sync::Arc::new(a_minisc), @@ -241,11 +253,12 @@ macro_rules! impl_node_opcode_three { minisc.check_miniscript()?; - Ok((minisc, a_keymap, networks)) + Ok((minisc, a_keymap, network_kinds)) }) }); } +/// `sortedmulti` and `sortedmulti_vec` operations constructor. #[doc(hidden)] #[macro_export] macro_rules! impl_sortedmulti { @@ -270,6 +283,7 @@ macro_rules! impl_sortedmulti { } +/// Parse Taproot script trees with nested braces syntax (e.g., `{ pk(key1), pk(key2) }`). #[doc(hidden)] #[macro_export] macro_rules! parse_tap_tree { @@ -278,9 +292,9 @@ macro_rules! parse_tap_tree { $tree_a .and_then(|tree_a| Ok((tree_a, $tree_b?))) - .and_then(|((a_tree, mut a_keymap, a_networks), (b_tree, b_keymap, b_networks))| { + .and_then(|((a_tree, mut a_keymap, a_network_kinds), (b_tree, b_keymap, b_network_kinds))| { a_keymap.extend(b_keymap.into_iter()); - Ok((TapTree::combine(a_tree, b_tree), a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks))) + Ok((TapTree::combine(a_tree, b_tree), a_keymap, $crate::keys::merge_network_kinds(&a_network_kinds, &b_network_kinds))) }) }}; @@ -321,10 +335,11 @@ macro_rules! parse_tap_tree { use $crate::miniscript::descriptor::TapTree; $crate::fragment!( $op ( $( $minisc )* ) ) - .map(|(a_minisc, a_keymap, a_networks)| (TapTree::Leaf(Arc::new(a_minisc)), a_keymap, a_networks)) + .map(|(a_minisc, a_keymap, a_network_kinds)| (TapTree::Leaf(Arc::new(a_minisc)), a_keymap, a_network_kinds)) }}; } +/// Apply miniscript modifiers (e.g., `a:`, `s:`, `c:`, `d:`, `v:`, `j:`, `n:`, `t:`, `l:`, `u:`). #[doc(hidden)] #[macro_export] macro_rules! apply_modifier { @@ -333,7 +348,7 @@ macro_rules! apply_modifier { $inner .map_err(|e| -> $crate::descriptor::DescriptorError { e.into() }) - .and_then(|(minisc, keymap, networks)| { + .and_then(|(minisc, keymap, network_kinds)| { let minisc = $crate::miniscript::Miniscript::from_ast( $crate::miniscript::miniscript::decode::Terminal::$terminal_variant( $crate::alloc::sync::Arc::new(minisc), @@ -342,7 +357,7 @@ macro_rules! apply_modifier { minisc.check_miniscript()?; - Ok((minisc, keymap, networks)) + Ok((minisc, keymap, network_kinds)) }) }}; @@ -370,23 +385,23 @@ macro_rules! apply_modifier { // Modifiers expanded to other operators ( t: $inner:expr ) => {{ - $inner.and_then(|(a_minisc, a_keymap, a_networks)| { + $inner.and_then(|(a_minisc, a_keymap, a_network_kinds)| { $crate::impl_leaf_opcode_value_two!( AndV, $crate::alloc::sync::Arc::new(a_minisc), $crate::alloc::sync::Arc::new($crate::fragment!(true).unwrap().0) ) - .map(|(minisc, _, _)| (minisc, a_keymap, a_networks)) + .map(|(minisc, _, _)| (minisc, a_keymap, a_network_kinds)) }) }}; ( l: $inner:expr ) => {{ - $inner.and_then(|(a_minisc, a_keymap, a_networks)| { + $inner.and_then(|(a_minisc, a_keymap, a_networks_kinds)| { $crate::impl_leaf_opcode_value_two!( OrI, $crate::alloc::sync::Arc::new($crate::fragment!(false).unwrap().0), $crate::alloc::sync::Arc::new(a_minisc) ) - .map(|(minisc, _, _)| (minisc, a_keymap, a_networks)) + .map(|(minisc, _, _)| (minisc, a_keymap, a_network_kinds)) }) }}; ( u: $inner:expr ) => {{ @@ -396,7 +411,7 @@ macro_rules! apply_modifier { $crate::alloc::sync::Arc::new(a_minisc), $crate::alloc::sync::Arc::new($crate::fragment!(false).unwrap().0) ) - .map(|(minisc, _, _)| (minisc, a_keymap, a_networks)) + .map(|(minisc, _, _)| (minisc, a_keymap, a_network_kinds)) }) }}; } @@ -424,7 +439,7 @@ macro_rules! apply_modifier { /// /// ``` /// # use std::str::FromStr; -/// let (my_descriptor, my_keys_map, networks) = bdk_wallet::descriptor!(sh(wsh(and_v(v:pk("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"),older(50)))))?; +/// let (my_descriptor, my_keys_map, network_kinds) = bdk_wallet::descriptor!(sh(wsh(and_v(v:pk("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"),older(50)))))?; /// # Ok::<(), Box>(()) /// ``` /// @@ -446,7 +461,7 @@ macro_rules! apply_modifier { /// bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?; /// let my_timelock = 50; /// -/// let (descriptor_a, key_map_a, networks) = bdk_wallet::descriptor! { +/// let (descriptor_a, key_map_a, networks_kinds) = bdk_wallet::descriptor! { /// wsh ( /// thresh(2, pk(my_key_1), s:pk(my_key_2), s:n:d:v:older(my_timelock)) /// ) @@ -458,7 +473,7 @@ macro_rules! apply_modifier { /// bdk_wallet::fragment!(s:pk(my_key_2))?, /// bdk_wallet::fragment!(s:n:d:v:older(my_timelock))?, /// ]; -/// let (descriptor_b, mut key_map_b, networks) = +/// let (descriptor_b, mut key_map_b, network_kinds) = /// bdk_wallet::descriptor!(wsh(thresh_vec(2, b_items)))?; /// /// assert_eq!(descriptor_a, descriptor_b); @@ -478,7 +493,7 @@ macro_rules! apply_modifier { /// let my_key_2 = /// bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?; /// -/// let (descriptor, key_map, networks) = bdk_wallet::descriptor! { +/// let (descriptor, key_map, network_kinds) = bdk_wallet::descriptor! { /// wsh ( /// multi(2, my_key_1, my_key_2) /// ) @@ -494,7 +509,7 @@ macro_rules! apply_modifier { /// let my_key = /// bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?; /// -/// let (descriptor, key_map, networks) = bdk_wallet::descriptor!(wpkh(my_key))?; +/// let (descriptor, key_map, networks_kinds) = bdk_wallet::descriptor!(wpkh(my_key))?; /// # Ok::<(), Box>(()) /// ``` /// @@ -750,19 +765,19 @@ macro_rules! fragment { ( thresh_vec ( $thresh:expr, $items:expr ) ) => ({ use $crate::miniscript::descriptor::KeyMap; - let (items, key_maps_networks): ($crate::alloc::vec::Vec<_>, $crate::alloc::vec::Vec<_>) = $items.into_iter().map(|(a, b, c)| (a, (b, c))).unzip(); + let (items, key_maps_network_kinds): ($crate::alloc::vec::Vec<_>, $crate::alloc::vec::Vec<_>) = $items.into_iter().map(|(a, b, c)| (a, (b, c))).unzip(); let items = items.into_iter().map($crate::alloc::sync::Arc::new).collect(); - let (key_maps, valid_networks) = key_maps_networks.into_iter().fold((KeyMap::default(), $crate::keys::any_network()), |(mut keys_acc, net_acc), (key, net)| { + let (key_maps, valid_network_kinds) = key_maps_network_kinds.into_iter().fold((KeyMap::default(), $crate::keys::any_network_kind()), |(mut keys_acc, net_acc), (key, net)| { keys_acc.extend(key.into_iter()); - let net_acc = $crate::keys::merge_networks(&net_acc, &net); + let net_acc = $crate::keys::merge_network_kinds(&net_acc, &net); (keys_acc, net_acc) }); let thresh = $crate::miniscript::Threshold::new($thresh, items).expect("valid threshold and pks collection"); $crate::impl_leaf_opcode_value!(Thresh, thresh) - .map(|(minisc, _, _)| (minisc, key_maps, valid_networks)) + .map(|(minisc, _, _)| (minisc, key_maps, valid_network_kinds)) }); ( thresh ( $thresh:expr, $( $inner:tt )* ) ) => ({ let items = $crate::fragment_internal!( @v $( $inner )* ); @@ -818,28 +833,28 @@ mod test { use core::str::FromStr; use crate::descriptor::{DescriptorError, DescriptorMeta}; - use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks}; + use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworkKinds}; use bitcoin::bip32; - use bitcoin::Network::{Bitcoin, Regtest, Signet, Testnet, Testnet4}; + use bitcoin::NetworkKind; use bitcoin::PrivateKey; - // 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). fn check( - desc: Result<(Descriptor, KeyMap, ValidNetworks), DescriptorError>, + desc: Result<(Descriptor, KeyMap, ValidNetworkKinds), DescriptorError>, is_witness: bool, is_fixed: bool, expected: &[&str], ) { - let (desc, _key_map, _networks) = desc.unwrap(); + let (desc, _key_map, _network_kinds) = desc.unwrap(); assert_eq!(desc.is_witness(), is_witness); assert_eq!(!desc.has_wildcard(), is_fixed); for i in 0..expected.len() { let child_desc = desc .at_derivation_index(i as u32) .expect("i is not hardened"); - let address = child_desc.address(Regtest); + let address = child_desc.address(bitcoin::Network::Regtest.into()); if let Ok(address) = address { assert_eq!(address.to_string(), *expected.get(i).unwrap()); } else { @@ -849,13 +864,13 @@ mod test { } } - // - at least one of each "type" of operator; i.e. one modifier, one leaf_opcode, one - // leaf_opcode_value, etc. - // - mixing up key types that implement IntoDescriptorKey in multi() or thresh() + // - At least one of each "type" of operator (one modifier, one leaf_opcode, one + // leaf_opcode_value, etc.). Mixing up key types that implement IntoDescriptorKey in + // `multi()` or `thresh()`. - // expected script for pk and bare manually created - // expected addresses created with `bitcoin-cli getdescriptorinfo` (for hash) and `bitcoin-cli - // deriveaddresses` + // - Expected script for pk and bare manually created. + // - Expected addresses created with `bitcoin-cli getdescriptorinfo` (for hash) and `bitcoin-cli + // deriveaddresses`. #[test] fn test_fixed_legacy_descriptors() { @@ -1110,32 +1125,32 @@ mod test { ); } - // - verify the valid_networks returned is correctly computed based on the keys present in the - // descriptor + // Verify that the `valid_network_kinds` returned is correctly computed based on the keys + // present in the descriptor. #[test] - fn test_valid_networks() { + fn test_valid_network_kinds() { let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let path = bip32::DerivationPath::from_str("m/0").unwrap(); let desc_key = (xprv, path).into_descriptor_key().unwrap(); - let (_desc, _key_map, valid_networks) = descriptor!(pkh(desc_key)).unwrap(); + let (_desc, _key_map, valid_network_kinds) = descriptor!(pkh(desc_key)).unwrap(); assert_eq!( - valid_networks, - [Testnet, Testnet4, Regtest, Signet] - .iter() - .cloned() - .collect() + valid_network_kinds, + [NetworkKind::Test].iter().cloned().collect() ); let xprv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap(); let path = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap(); let desc_key = (xprv, path).into_descriptor_key().unwrap(); - let (_desc, _key_map, valid_networks) = descriptor!(wpkh(desc_key)).unwrap(); - assert_eq!(valid_networks, [Bitcoin].iter().cloned().collect()); + let (_desc, _key_map, valid_network_kinds) = descriptor!(wpkh(desc_key)).unwrap(); + assert_eq!( + valid_network_kinds, + [NetworkKind::Main].iter().cloned().collect() + ); } - // - verify the key_maps are correctly merged together + // Verify that the `key_maps` are correctly merged together. #[test] fn test_key_maps_merged() { let secp = Secp256k1::new(); @@ -1152,7 +1167,7 @@ mod test { let path3 = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap(); let desc_key3 = (xprv3, path3.clone()).into_descriptor_key().unwrap(); - let (_desc, key_map, _valid_networks) = + let (_desc, key_map, _valid_network_kinds) = descriptor!(sh(wsh(multi(2, desc_key1, desc_key2, desc_key3)))).unwrap(); assert_eq!(key_map.len(), 3); @@ -1160,30 +1175,32 @@ mod test { let desc_key2: DescriptorKey = (xprv2, path2).into_descriptor_key().unwrap(); let desc_key3: DescriptorKey = (xprv3, path3).into_descriptor_key().unwrap(); - let (key1, _key_map, _valid_networks) = desc_key1.extract(&secp).unwrap(); - let (key2, _key_map, _valid_networks) = desc_key2.extract(&secp).unwrap(); - let (key3, _key_map, _valid_networks) = desc_key3.extract(&secp).unwrap(); + let (key1, _key_map, _valid_network_kinds) = desc_key1.extract(&secp).unwrap(); + let (key2, _key_map, _valid_network_kinds) = desc_key2.extract(&secp).unwrap(); + let (key3, _key_map, _valid_network_kinds) = desc_key3.extract(&secp).unwrap(); assert_eq!(key_map.get(&key1).unwrap().to_string(), "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/0/*"); assert_eq!(key_map.get(&key2).unwrap().to_string(), "tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF/2147483647'/0/*"); assert_eq!(key_map.get(&key3).unwrap().to_string(), "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf/10/20/30/40/*"); } - // - verify the ScriptContext is correctly validated (i.e. passing a type that only impl - // IntoDescriptorKey to a pkh() descriptor should throw a compilation error + // Verify that the the `ScriptContext` is correctly validated (i.e. passing a type that only + // implements `IntoDescriptorKey` to a `pkh()` descriptor should throw a compilation + // error. #[test] fn test_script_context_validation() { - // this compiles + // This will compile let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let path = bip32::DerivationPath::from_str("m/0").unwrap(); let desc_key: DescriptorKey = (xprv, path).into_descriptor_key().unwrap(); - let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap(); + let (desc, _key_map, _valid_network_kinds) = descriptor!(pkh(desc_key)).unwrap(); assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)#yrnz9pp2"); - // as expected this does not compile due to invalid context - //let desc_key:DescriptorKey = (xprv, - // path.clone()).into_descriptor_key().unwrap(); let (desc, _key_map, - // _valid_networks) = descriptor!(pkh(desc_key)).unwrap(); + // As expected, this does not compile due to invalid context: + // ``` + // let desc_key: DescriptorKey = (xprv, path.clone()).into_descriptor_key().unwrap(); + // let (desc, _key_map, _valid_network_kinds) = descriptor!(pkh(desc_key)).unwrap(); + // ``` } #[test] diff --git a/wallet/src/descriptor/mod.rs b/wallet/src/descriptor/mod.rs index 4db28c61..6232319c 100644 --- a/wallet/src/descriptor/mod.rs +++ b/wallet/src/descriptor/mod.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -14,15 +14,14 @@ //! This module contains generic utilities to work with descriptors, plus some re-exported types //! from [`miniscript`]. -use crate::collections::BTreeMap; use alloc::string::String; use alloc::vec::Vec; -use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, KeySource, Xpub}; -use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey}; -use bitcoin::{psbt, taproot}; -use bitcoin::{Network, TxOut}; - +use bitcoin::{ + bip32::{ChildNumber, DerivationPath, Fingerprint, KeySource, Xpub}, + key::XOnlyPublicKey, + psbt, secp256k1, taproot, NetworkKind, PublicKey, TxOut, +}; use miniscript::descriptor::{ DefiniteDescriptorKey, DescriptorMultiXKey, DescriptorSecretKey, DescriptorType, DescriptorXKey, InnerXKey, KeyMap, SinglePubKey, Wildcard, @@ -32,6 +31,7 @@ pub use miniscript::{ }; use miniscript::{ForEachKey, MiniscriptKey, TranslatePk}; +use crate::collections::BTreeMap; use crate::descriptor::policy::BuildSatisfaction; pub mod checksum; @@ -46,8 +46,7 @@ pub use self::error::Error as DescriptorError; pub use self::policy::Policy; use self::template::DescriptorTemplateOut; use crate::keys::{IntoDescriptorKey, KeyError}; -use crate::wallet::signer::SignersContainer; -use crate::wallet::utils::SecpCtx; +use crate::wallet::{signer::SignersContainer, utils::SecpCtx}; /// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`] pub type ExtendedDescriptor = Descriptor; @@ -70,13 +69,13 @@ pub type HdKeyPaths = BTreeMap; pub type TapKeyOrigins = BTreeMap, KeySource)>; /// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by -/// a wallet in a specific [`Network`] +/// a wallet in a specific [`NetworkKind`]. pub trait IntoWalletDescriptor { /// Convert to wallet descriptor fn into_wallet_descriptor( self, secp: &SecpCtx, - network: Network, + network_kind: NetworkKind, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>; } @@ -84,7 +83,7 @@ impl IntoWalletDescriptor for &str { fn into_wallet_descriptor( self, secp: &SecpCtx, - network: Network, + network_kind: NetworkKind, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { let descriptor = match self.split_once('#') { Some((desc, original_checksum)) => { @@ -98,7 +97,7 @@ impl IntoWalletDescriptor for &str { }; ExtendedDescriptor::parse_descriptor(secp, descriptor)? - .into_wallet_descriptor(secp, network) + .into_wallet_descriptor(secp, network_kind) } } @@ -106,9 +105,9 @@ impl IntoWalletDescriptor for &String { fn into_wallet_descriptor( self, secp: &SecpCtx, - network: Network, + network_kind: NetworkKind, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { - self.as_str().into_wallet_descriptor(secp, network) + self.as_str().into_wallet_descriptor(secp, network_kind) } } @@ -116,9 +115,9 @@ impl IntoWalletDescriptor for String { fn into_wallet_descriptor( self, secp: &SecpCtx, - network: Network, + network_kind: NetworkKind, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { - self.as_str().into_wallet_descriptor(secp, network) + self.as_str().into_wallet_descriptor(secp, network_kind) } } @@ -126,9 +125,9 @@ impl IntoWalletDescriptor for ExtendedDescriptor { fn into_wallet_descriptor( self, secp: &SecpCtx, - network: Network, + network_kind: NetworkKind, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { - (self, KeyMap::default()).into_wallet_descriptor(secp, network) + (self, KeyMap::default()).into_wallet_descriptor(secp, network_kind) } } @@ -136,21 +135,21 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) { fn into_wallet_descriptor( self, secp: &SecpCtx, - network: Network, + network_kind: NetworkKind, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { use crate::keys::DescriptorKey; struct Translator<'s, 'd> { secp: &'s SecpCtx, descriptor: &'d ExtendedDescriptor, - network: Network, + network_kind: NetworkKind, } impl miniscript::Translator for Translator<'_, '_> { fn pk(&mut self, pk: &DescriptorPublicKey) -> Result { let secp = &self.secp; - let (_, _, networks) = if self.descriptor.is_taproot() { + let (_, _, network_kinds) = if self.descriptor.is_taproot() { let descriptor_key: DescriptorKey = pk.clone().into_descriptor_key()?; descriptor_key.extract(secp)? @@ -164,10 +163,10 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) { descriptor_key.extract(secp)? }; - if networks.contains(&self.network) { + if network_kinds.contains(&self.network_kind) { Ok(Default::default()) } else { - Err(DescriptorError::Key(KeyError::InvalidNetwork)) + Err(DescriptorError::Key(KeyError::InvalidNetworkKind)) } } fn sha256( @@ -196,11 +195,11 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) { } } - // check the network for the keys + // Check the `network_kind` for the keys. use miniscript::TranslateErr; match self.0.translate_pk(&mut Translator { secp, - network, + network_kind, descriptor: &self.0, }) { Ok(_) => {} @@ -216,10 +215,10 @@ impl IntoWalletDescriptor for DescriptorTemplateOut { fn into_wallet_descriptor( self, _secp: &SecpCtx, - network: Network, + network_kind: NetworkKind, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { struct Translator { - network: Network, + network_kind: NetworkKind, } impl miniscript::Translator @@ -229,15 +228,16 @@ impl IntoWalletDescriptor for DescriptorTemplateOut { &mut self, pk: &DescriptorPublicKey, ) -> Result { - // workaround for xpubs generated by other key types, like bip39: since when the - // conversion is made one network has to be chosen, what we generally choose - // "mainnet", but then override the set of valid networks to specify that all of - // them are valid. here we reset the network to make sure the wallet struct gets a - // descriptor with the right network everywhere. + // Workaround for xpubs generated by other key types, like BIP39: since when the + // conversion is made a [`NetworkKind`] has to be chosen, what we generally do is + // choose [`NetworkKind::Main`], but then override the set of valid + // `network_kinds` to specify that all of them are valid. Here we + // reset the `network_kind` to make sure the wallet struct gets a + // descriptor with the right `network_kind` everywhere. let pk = match pk { DescriptorPublicKey::XPub(ref xpub) => { let mut xpub = xpub.clone(); - xpub.xkey.network = self.network.into(); + xpub.xkey.network = self.network_kind; DescriptorPublicKey::XPub(xpub) } @@ -253,30 +253,30 @@ impl IntoWalletDescriptor for DescriptorTemplateOut { ); } - let (desc, keymap, networks) = self; + let (desc, keymap, network_kinds) = self; - if !networks.contains(&network) { - return Err(DescriptorError::Key(KeyError::InvalidNetwork)); + if !network_kinds.contains(&network_kind) { + return Err(DescriptorError::Key(KeyError::InvalidNetworkKind)); } - // fixup the network for keys that need it in the descriptor + // Fixup the network kind for keys that need it in the descriptor... use miniscript::TranslateErr; - let translated = match desc.translate_pk(&mut Translator { network }) { + let translated = match desc.translate_pk(&mut Translator { network_kind }) { Ok(descriptor) => descriptor, Err(TranslateErr::TranslatorErr(e)) => return Err(e), Err(TranslateErr::OuterError(e)) => return Err(e.into()), }; - // ...and in the key map + // ...and in the key map. let fixed_keymap = keymap .into_iter() .map(|(mut k, mut v)| { match (&mut k, &mut v) { (DescriptorPublicKey::XPub(xpub), DescriptorSecretKey::XPrv(xprv)) => { - xpub.xkey.network = network.into(); - xprv.xkey.network = network.into(); + xpub.xkey.network = network_kind; + xprv.xkey.network = network_kind; } (_, DescriptorSecretKey::Single(key)) => { - key.key.network = network.into(); + key.key.network = network_kind; } _ => {} } @@ -293,7 +293,7 @@ impl IntoWalletDescriptor for DescriptorTemplateOut { pub(crate) fn check_wallet_descriptor( descriptor: &Descriptor, ) -> Result<(), DescriptorError> { - // Ensure the keys don't contain any hardened derivation steps or hardened wildcards + // Ensure the keys don't contain any hardened derivation steps or hardened wildcards. let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| { if let DescriptorPublicKey::XPub(DescriptorXKey { derivation_path, @@ -316,14 +316,14 @@ pub(crate) fn check_wallet_descriptor( } // Run miniscript's sanity check, which will look for duplicated keys and other potential - // issues + // issues. descriptor.sanity_check()?; Ok(()) } #[doc(hidden)] -/// Used internally mainly by the `descriptor!()` and `fragment!()` macros +/// Used internally mainly by the `descriptor!()` and `fragment!()` macros, pub trait CheckMiniscript { fn check_miniscript(&self) -> Result<(), miniscript::Error>; } @@ -338,7 +338,7 @@ impl CheckMiniscr } } -/// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`] +/// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`]. pub trait ExtractPolicy { /// Extract the spending [`policy`] fn extract_policy( @@ -440,7 +440,7 @@ impl DescriptorMeta for ExtendedDescriptor { key_origins: BTreeMap, secp: &SecpCtx, ) -> Option { - // Ensure that deriving `xpub` with `path` yields `expected` + // Ensure that deriving `xpub` with `path` yields `expected`. let verify_key = |xpub: &DescriptorXKey, path: &DerivationPath, expected: &SinglePubKey| { let derived = xpub @@ -458,7 +458,7 @@ impl DescriptorMeta for ExtendedDescriptor { let mut path_found = None; - // using `for_any_key` should make this stop as soon as we return `true` + // Using `for_any_key` should make this stop as soon as we return `true`. self.for_any_key(|key| { if let DescriptorPublicKey::XPub(xpub) = key { // Check if the key matches one entry in our `key_origins`. If it does, `matches()` @@ -481,10 +481,9 @@ impl DescriptorMeta for ExtendedDescriptor { .collect::(); // `derive_path` only contains the replacement index for the wildcard, if - // present, or an empty path for fixed descriptors. - // To verify the key we also need the normal steps - // that come before the wildcard, so we take them directly from `xpub` and - // then append the final index + // present, or an empty path for fixed descriptors. To verify the key we + // also need the normal steps that come before the wildcard, so we take them + // directly from `xpub` and then append the final index. if verify_key( xpub, &xpub.derivation_path.extend(derive_path.clone()), @@ -498,7 +497,7 @@ impl DescriptorMeta for ExtendedDescriptor { match derive_path { Some(path) if xpub.wildcard != Wildcard::None && path.len() == 1 => { - // Ignore hardened wildcards + // Ignore hardened wildcards. if let ChildNumber::Normal { index } = path[0] { path_found = Some(index); return true; @@ -526,7 +525,7 @@ impl DescriptorMeta for ExtendedDescriptor { hd_keypaths: &HdKeyPaths, secp: &SecpCtx, ) -> Option { - // "Convert" an hd_keypaths map to the format required by `derive_from_psbt_key_origins` + // "Convert" an `hd_keypaths`` map to the format required by `derive_from_psbt_key_origins`. let key_origins = hd_keypaths .iter() .map(|(pk, (fingerprint, path))| { @@ -544,7 +543,8 @@ impl DescriptorMeta for ExtendedDescriptor { tap_key_origins: &TapKeyOrigins, secp: &SecpCtx, ) -> Option { - // "Convert" a tap_key_origins map to the format required by `derive_from_psbt_key_origins` + // "Convert" a `tap_key_origins`` map to the format required by + // `derive_from_psbt_key_origins`. let key_origins = tap_key_origins .iter() .map(|(pk, (_, (fingerprint, path)))| (*fingerprint, (path, SinglePubKey::XOnly(*pk)))) @@ -565,7 +565,7 @@ impl DescriptorMeta for ExtendedDescriptor { return Some(derived); } if self.has_wildcard() { - // We can't try to bruteforce the derivation index, exit here + // We can't try to bruteforce the derivation index, exit here. return None; } @@ -726,25 +726,26 @@ mod test { } #[test] - fn test_to_wallet_descriptor_fixup_networks() { - use crate::keys::{any_network, IntoDescriptorKey}; + fn test_to_wallet_descriptor_fixup_network_kinds() { + use crate::keys::{any_network_kind, IntoDescriptorKey}; let secp = Secp256k1::new(); let xprv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap(); let path = bip32::DerivationPath::from_str("m/0").unwrap(); - // here `to_descriptor_key` will set the valid networks for the key to only mainnet, since - // we are using an "xpub" + // Here, `to_descriptor_key` will set the valid networks for the key to only + // [`NetworkKind::Main`], since we are using an "xpub". let key = (xprv, path.clone()).into_descriptor_key().unwrap(); - // override it with any. this happens in some key conversions, like bip39 - let key = key.override_valid_networks(any_network()); + // Override it with any. This happens in some key conversions, like BIP39. + let key = key.override_valid_network_kinds(any_network_kind()); - // make a descriptor out of it + // Make a descriptor out of it. let desc = crate::descriptor!(wpkh(key)).unwrap(); - // this should convert the key that supports "any_network" to the right network (testnet) + // This should convert the key that supports "any_network_kind" to the right network kind + // ([`NetworkKind::Test`]). let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let mut xprv_testnet = xprv; @@ -773,73 +774,80 @@ mod test { let secp = Secp256k1::new(); let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum)); let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum)); } - // test IntoWalletDescriptor trait from &str with keys from right and wrong network + // Test the `IntoWalletDescriptor` trait from `&str` with keys from the right and wrong + // [`NetworkKind`]. #[test] - fn test_descriptor_from_str_with_keys_network() { + fn test_descriptor_from_str_with_keys_network_kind() { let secp = Secp256k1::new(); let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" - .into_wallet_descriptor(&secp, Network::Testnet4); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" - .into_wallet_descriptor(&secp, Network::Regtest); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)" - .into_wallet_descriptor(&secp, Network::Regtest); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))" - .into_wallet_descriptor(&secp, Network::Testnet); + .into_wallet_descriptor(&secp, NetworkKind::Test); assert!(desc.is_ok()); let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))" - .into_wallet_descriptor(&secp, Network::Bitcoin); + .into_wallet_descriptor(&secp, NetworkKind::Main); assert!(desc.is_ok()); let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" - .into_wallet_descriptor(&secp, Network::Bitcoin); - assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork))); + .into_wallet_descriptor(&secp, NetworkKind::Main); + assert_matches!( + desc, + Err(DescriptorError::Key(KeyError::InvalidNetworkKind)) + ); let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)" - .into_wallet_descriptor(&secp, Network::Bitcoin); - assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork))); + .into_wallet_descriptor(&secp, NetworkKind::Main); + assert_matches!( + desc, + Err(DescriptorError::Key(KeyError::InvalidNetworkKind)) + ); } - // test IntoWalletDescriptor trait from the output of the descriptor!() macro + // Test IntoWalletDescriptor trait from the output of the `descriptor!()` macro. #[test] fn test_descriptor_from_str_from_output_of_macro() { let secp = Secp256k1::new(); @@ -848,17 +856,17 @@ mod test { let path = bip32::DerivationPath::from_str("m/1/2").unwrap(); let key = (tpub, path).into_descriptor_key().unwrap(); - // make a descriptor out of it + // Make a descriptor out of it. let desc = crate::descriptor!(wpkh(key)).unwrap(); let (wallet_desc, _) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let wallet_desc_str = wallet_desc.to_string(); assert_eq!(wallet_desc_str, "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw"); let (wallet_desc2, _) = wallet_desc_str - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); assert_eq!(wallet_desc, wallet_desc2) } @@ -869,7 +877,7 @@ mod test { let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)"; let (descriptor, _) = descriptor - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .expect("must parse"); let result = check_wallet_descriptor(&descriptor); @@ -877,16 +885,16 @@ mod test { let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/<0;1>/*)"; let (descriptor, _) = descriptor - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .expect("must parse"); let result = check_wallet_descriptor(&descriptor); assert_matches!(result, Err(DescriptorError::MultiPath)); - // repeated pubkeys + // Repeated pubkeys. let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))"; let (descriptor, _) = descriptor - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .expect("must parse"); let result = check_wallet_descriptor(&descriptor); @@ -901,7 +909,7 @@ mod test { let descriptor = "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))"; let (descriptor, _) = descriptor - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); check_wallet_descriptor(&descriptor).expect("descriptor"); @@ -923,27 +931,27 @@ mod test { // See let secp = Secp256k1::new(); - // multipath tpub + // Multipath `tpub`. let descriptor_str = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1>/*)"; let (descriptor, _key_map) = descriptor_str - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .expect("should parse multipath tpub"); assert!(descriptor.is_multipath()); - // invalid network for descriptor + // Invalid [`NetworkKind`] for descriptor. let descriptor_str = "wpkh([9a6a2580/84'/0'/0']xpub6DEzNop46vmxR49zYWFnMwmEfawSNmAMf6dLH5YKDY463twtvw1XD7ihwJRLPRGZJz799VPFzXHpZu6WdhT29WnaeuChS6aZHZPFmqczR5K/<0;1>/*)"; - let res = descriptor_str.into_wallet_descriptor(&secp, Network::Testnet); + let res = descriptor_str.into_wallet_descriptor(&secp, NetworkKind::Test); assert!(matches!( res, - Err(DescriptorError::Key(KeyError::InvalidNetwork)) + Err(DescriptorError::Key(KeyError::InvalidNetworkKind)) )); - // multipath xpub + // Multipath xpub. let descriptor_str = "wpkh([9a6a2580/84'/0'/0']xpub6DEzNop46vmxR49zYWFnMwmEfawSNmAMf6dLH5YKDY463twtvw1XD7ihwJRLPRGZJz799VPFzXHpZu6WdhT29WnaeuChS6aZHZPFmqczR5K/<0;1>/*)"; let (descriptor, _key_map) = descriptor_str - .into_wallet_descriptor(&secp, Network::Bitcoin) + .into_wallet_descriptor(&secp, NetworkKind::Main) .expect("should parse multipath xpub"); assert!(descriptor.is_multipath()); @@ -956,7 +964,7 @@ mod test { Err(miniscript::Error::Unexpected(..)), )); let _ = descriptor_str - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Main) .unwrap_err(); Ok(()) diff --git a/wallet/src/descriptor/policy.rs b/wallet/src/descriptor/policy.rs index 4250ed34..4541f0b3 100644 --- a/wallet/src/descriptor/policy.rs +++ b/wallet/src/descriptor/policy.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -36,29 +36,30 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` -use crate::collections::{BTreeMap, HashSet, VecDeque}; use alloc::string::String; use alloc::vec::Vec; use core::cmp::max; -use miniscript::miniscript::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG}; - use core::fmt; -use serde::ser::SerializeMap; -use serde::{Serialize, Serializer}; - -use bitcoin::bip32::Fingerprint; -use bitcoin::hashes::{hash160, ripemd160, sha256}; -use bitcoin::{absolute, key::XOnlyPublicKey, relative, PublicKey, Sequence}; - +use bitcoin::{ + absolute, + bip32::Fingerprint, + hashes::{hash160, ripemd160, sha256}, + key::XOnlyPublicKey, + psbt::{self, Psbt}, + relative, PublicKey, Sequence, +}; use miniscript::descriptor::{ DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner, }; -use miniscript::{hash256, Threshold}; +use miniscript::miniscript::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG}; use miniscript::{ - Descriptor, Miniscript, Satisfier, ScriptContext, SigType, Terminal, ToPublicKey, + hash256, psbt::PsbtInputSatisfier, Descriptor, Miniscript, Satisfier, ScriptContext, SigType, + Terminal, Threshold, ToPublicKey, }; +use serde::{ser::SerializeMap, Serialize, Serializer}; +use crate::collections::{BTreeMap, HashSet, VecDeque}; use crate::descriptor::ExtractPolicy; use crate::keys::ExtScriptContext; use crate::wallet::signer::{SignerId, SignersContainer}; @@ -67,8 +68,6 @@ use crate::wallet::utils::{After, Older, SecpCtx}; use super::checksum::calc_checksum; use super::error::Error; use super::XKeyUtils; -use bitcoin::psbt::{self, Psbt}; -use miniscript::psbt::PsbtInputSatisfier; /// A unique identifier for a key #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] @@ -1169,20 +1168,24 @@ impl ExtractPolicy for Descriptor { #[cfg(test)] mod test { - use crate::descriptor; - use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor}; - use super::*; - use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh}; - use crate::keys::{DescriptorKey, IntoDescriptorKey}; - use crate::wallet::signer::SignersContainer; + use alloc::{string::ToString, sync::Arc}; use assert_matches::assert_matches; - use bitcoin::bip32; - use bitcoin::secp256k1::Secp256k1; - use bitcoin::Network; use core::str::FromStr; + use bitcoin::{bip32, secp256k1::Secp256k1, Network, NetworkKind}; + + use crate::keys::{DescriptorKey, IntoDescriptorKey}; + use crate::wallet::signer::SignersContainer; + use crate::{ + descriptor, + descriptor::{ + policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh}, + ExtractPolicy, IntoWalletDescriptor, + }, + }; + const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf"; const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N"; @@ -1203,7 +1206,7 @@ mod test { (prvkey, pubkey, fingerprint) } - // test ExtractPolicy trait for simple descriptors; wpkh(), sh(multi()) + // Test ExtractPolicy trait for simple descriptors; `wpkh()`, `sh(multi())`. #[test] fn test_extract_policy_for_wpkh() { @@ -1212,7 +1215,7 @@ mod test { let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp); let desc = descriptor!(wpkh(pubkey)).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1225,7 +1228,7 @@ mod test { let desc = descriptor!(wpkh(prvkey)).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1237,7 +1240,7 @@ mod test { assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none()); } - // 2 pub keys descriptor, required 2 prv keys + // 2 pubkey descriptor, 2 privkeys are required. #[test] fn test_extract_policy_for_sh_multi_partial_0of2() { let secp = Secp256k1::new(); @@ -1245,7 +1248,7 @@ mod test { let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp); let desc = descriptor!(sh(multi(2, pubkey0, pubkey1))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1266,7 +1269,7 @@ mod test { ); } - // 1 prv and 1 pub key descriptor, required 2 prv keys + // 1 privkey and 1 pubkey descriptor, 2 privkeys are required. #[test] fn test_extract_policy_for_sh_multi_partial_1of2() { let secp = Secp256k1::new(); @@ -1274,7 +1277,7 @@ mod test { let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp); let desc = descriptor!(sh(multi(2, prvkey0, pubkey1))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1293,7 +1296,7 @@ mod test { ); } - // 1 prv and 1 pub key descriptor, required 1 prv keys + // 1 privkey and 1 pubkey descriptor, 1 private key is required. #[test] #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225 fn test_extract_policy_for_sh_multi_complete_1of2() { @@ -1303,7 +1306,7 @@ mod test { let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp); let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1323,7 +1326,7 @@ mod test { ); } - // 2 prv keys descriptor, required 2 prv keys + // 2 privkey descriptor, 2 privkeys are required. #[test] fn test_extract_policy_for_sh_multi_complete_2of2() { let secp = Secp256k1::new(); @@ -1332,7 +1335,7 @@ mod test { let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp); let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1352,7 +1355,7 @@ mod test { ); } - // test ExtractPolicy trait with extended and single keys + // Test ExtractPolicy trait with extended and single keys. #[test] fn test_extract_policy_for_single_wpkh() { @@ -1361,7 +1364,7 @@ mod test { let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp); let desc = descriptor!(wpkh(pubkey)).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1374,7 +1377,7 @@ mod test { let desc = descriptor!(wpkh(prvkey)).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1386,7 +1389,7 @@ mod test { assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none()); } - // single key, 1 prv and 1 pub key descriptor, required 1 prv keys + // Single key, 1 privkey and 1 pubkey descriptor, 1 privkey is required. #[test] #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225 fn test_extract_policy_for_single_wsh_multi_complete_1of2() { @@ -1396,7 +1399,7 @@ mod test { let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp); let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1416,7 +1419,7 @@ mod test { ); } - // test ExtractPolicy trait with descriptors containing timelocks in a thresh() + // Test ExtractPolicy trait with descriptors containing timelocks in a `thresh()`. #[test] #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225 @@ -1436,7 +1439,7 @@ mod test { .unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc @@ -1455,7 +1458,7 @@ mod test { ); } - // - mixed timelocks should fail + // Mixed timelocks should fail. #[test] #[ignore] @@ -1471,7 +1474,7 @@ mod test { ))) .unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let _policy = wallet_desc @@ -1482,7 +1485,7 @@ mod test { // TODO how should this fail with mixed timelocks? } - // - multiple timelocks of the same type should be correctly merged together + // Multiple timelocks of the same type should be correctly merged together. #[test] #[ignore] fn test_extract_policy_for_multiple_same_timelocks() { @@ -1496,7 +1499,7 @@ mod test { ))) .unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let _policy = wallet_desc @@ -1514,7 +1517,7 @@ mod test { ))) .unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let _policy = wallet_desc @@ -1536,7 +1539,7 @@ mod test { let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); @@ -1545,21 +1548,21 @@ mod test { .unwrap() .unwrap(); - // no args, choose the default + // No argumnets, choose the default. let no_args = policy.get_condition(&vec![].into_iter().collect()); assert_eq!(no_args, Ok(Condition::default())); - // enough args + // Enough arguments. let eq_thresh = policy.get_condition(&vec![(policy.id.clone(), vec![0])].into_iter().collect()); assert_eq!(eq_thresh, Ok(Condition::default())); - // more args, it doesn't really change anything + // More arguments, it doesn't really change anything. let gt_thresh = policy.get_condition(&vec![(policy.id.clone(), vec![0, 1])].into_iter().collect()); assert_eq!(gt_thresh, Ok(Condition::default())); - // not enough args, error + // Not enough arguments, error. let lt_thresh = policy.get_condition(&vec![(policy.id.clone(), vec![])].into_iter().collect()); assert_eq!( @@ -1567,7 +1570,7 @@ mod test { Err(PolicyError::NotEnoughItemsSelected(policy.id.clone())) ); - // index out of range + // index out of range. let out_of_range = policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect()); assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5))); @@ -1592,7 +1595,7 @@ mod test { let desc = descriptor!(wsh(multi(2, prvkey_alice, prvkey_bob))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let addr = wallet_desc @@ -1646,8 +1649,8 @@ mod test { #[rustfmt::skip] #[test] fn test_extract_satisfaction_timelock() { - //const PSBT_POLICY_CONSIDER_TIMELOCK_NOT_EXPIRED: &str = "cHNidP8BAFMBAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAD/////ATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA"; - const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED: &str = "cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA"; + // const PSBT_POLICY_CONSIDER_TIMELOCK_NOT_EXPIRED: &str = "cHNidP8BAFMBAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAD/////ATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA"; + const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED: &str = "cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA"; const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED: &str ="cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstIMEUCIQCtZxNm6H3Ux3pnc64DSpgohMdBj+57xhFHcURYt2BpPAIgG3OnI7bcj/3GtWX1HHyYGSI7QGa/zq5YnsmK1Cw29NABAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEIoAQASDBFAiEArWcTZuh91Md6Z3OuA0qYKITHQY/ue8YRR3FEWLdgaTwCIBtzpyO23I/9xrVl9Rx8mBkiO0Bmv86uWJ7JitQsNvTQAQEBUnZjUrJpaHwhA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLrJN8IQL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiKyTUocAAA=="; let secp = Secp256k1::new(); @@ -1659,7 +1662,7 @@ mod test { descriptor!(wsh(thresh(2,n:d:v:older(2),s:pk(prvkey_alice),s:pk(prvkey_bob)))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); @@ -1742,7 +1745,7 @@ mod test { .unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); @@ -1758,7 +1761,7 @@ mod test { let desc = descriptor!(tr(prvkey)).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); @@ -1787,7 +1790,7 @@ mod test { let desc = descriptor!(tr(bob_pub, pk(alice_prv))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); @@ -1825,7 +1828,7 @@ mod test { let desc = descriptor!(tr(pubkey)).unwrap(); let (wallet_desc, _) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let policy_unsigned = wallet_desc @@ -1869,7 +1872,7 @@ mod test { let desc = descriptor!(tr(bob_pub, pk(alice_pub))).unwrap(); let (wallet_desc, _) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let policy_unsigned = wallet_desc diff --git a/wallet/src/descriptor/template.rs b/wallet/src/descriptor/template.rs index 6a8ad686..470bcd99 100644 --- a/wallet/src/descriptor/template.rs +++ b/wallet/src/descriptor/template.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -14,22 +14,20 @@ //! This module contains the definition of various common script templates that are ready to be //! used. See the documentation of each template for an example. -use bitcoin::bip32; -use bitcoin::Network; - +use bitcoin::{bip32, NetworkKind}; use miniscript::{Legacy, Segwitv0, Tap}; use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap}; use crate::descriptor::DescriptorError; -use crate::keys::{DerivableKey, IntoDescriptorKey, ValidNetworks}; +use crate::keys::{DerivableKey, IntoDescriptorKey, ValidNetworkKinds}; use crate::wallet::utils::SecpCtx; use crate::{descriptor, KeychainKind}; /// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) -/// and others -pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks); +/// and others. +pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworkKinds); -/// Trait for descriptor templates that can be built into a full descriptor +/// Trait for descriptor templates that can be built into a full descriptor. /// /// Since [`IntoWalletDescriptor`] is implemented for any [`DescriptorTemplate`], they can also be /// passed directly to the [`Wallet`](crate::Wallet) constructor. @@ -41,34 +39,38 @@ pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks); /// use bdk_wallet::keys::{IntoDescriptorKey, KeyError}; /// use bdk_wallet::miniscript::Legacy; /// use bdk_wallet::template::{DescriptorTemplate, DescriptorTemplateOut}; -/// use bitcoin::Network; +/// use bitcoin::NetworkKind; /// /// struct MyP2PKH>(K); /// /// impl> DescriptorTemplate for MyP2PKH { -/// fn build(self, network: Network) -> Result { +/// fn build( +/// self, +/// network_kind: NetworkKind, +/// ) -> Result { /// Ok(bdk_wallet::descriptor!(pkh(self.0))?) /// } /// } /// ``` pub trait DescriptorTemplate { - /// Build the complete descriptor - fn build(self, network: Network) -> Result; + /// Build the complete descriptor. + fn build(self, network_kind: NetworkKind) -> Result; } /// Turns a [`DescriptorTemplate`] into a valid wallet descriptor by calling its -/// [`build`](DescriptorTemplate::build) method +/// [`build`](DescriptorTemplate::build) method. impl IntoWalletDescriptor for T { fn into_wallet_descriptor( self, secp: &SecpCtx, - network: Network, + network_kind: NetworkKind, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { - self.build(network)?.into_wallet_descriptor(secp, network) + self.build(network_kind)? + .into_wallet_descriptor(secp, network_kind) } } -/// P2PKH template. Expands to a descriptor `pkh(key)` +/// P2PKH template. Expands to a descriptor `pkh(key)`. /// /// ## Example /// @@ -98,7 +100,7 @@ impl IntoWalletDescriptor for T { pub struct P2Pkh>(pub K); impl> DescriptorTemplate for P2Pkh { - fn build(self, _network: Network) -> Result { + fn build(self, _network_kind: NetworkKind) -> Result { descriptor!(pkh(self.0)) } } @@ -134,7 +136,7 @@ impl> DescriptorTemplate for P2Pkh { pub struct P2Wpkh_P2Sh>(pub K); impl> DescriptorTemplate for P2Wpkh_P2Sh { - fn build(self, _network: Network) -> Result { + fn build(self, _network_kind: NetworkKind) -> Result { descriptor!(sh(wpkh(self.0))) } } @@ -169,7 +171,7 @@ impl> DescriptorTemplate for P2Wpkh_P2Sh { pub struct P2Wpkh>(pub K); impl> DescriptorTemplate for P2Wpkh { - fn build(self, _network: Network) -> Result { + fn build(self, _network_kind: NetworkKind) -> Result { descriptor!(wpkh(self.0)) } } @@ -204,7 +206,7 @@ impl> DescriptorTemplate for P2Wpkh { pub struct P2TR>(pub K); impl> DescriptorTemplate for P2TR { - fn build(self, _network: Network) -> Result { + fn build(self, _network_kind: NetworkKind) -> Result { descriptor!(tr(self.0)) } } @@ -237,8 +239,14 @@ impl> DescriptorTemplate for P2TR { pub struct Bip44>(pub K, pub KeychainKind); impl> DescriptorTemplate for Bip44 { - fn build(self, network: Network) -> Result { - P2Pkh(legacy::make_bipxx_private(44, self.0, self.1, network)?).build(network) + fn build(self, network_kind: NetworkKind) -> Result { + P2Pkh(legacy::make_bipxx_private( + 44, + self.0, + self.1, + network_kind, + )?) + .build(network_kind) } } @@ -277,11 +285,15 @@ impl> DescriptorTemplate for Bip44 { pub struct Bip44Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); impl> DescriptorTemplate for Bip44Public { - fn build(self, network: Network) -> Result { + fn build(self, network_kind: NetworkKind) -> Result { P2Pkh(legacy::make_bipxx_public( - 44, self.0, self.1, self.2, network, + 44, + self.0, + self.1, + self.2, + network_kind, )?) - .build(network) + .build(network_kind) } } @@ -316,8 +328,14 @@ impl> DescriptorTemplate for Bip44Public { pub struct Bip49>(pub K, pub KeychainKind); impl> DescriptorTemplate for Bip49 { - fn build(self, network: Network) -> Result { - P2Wpkh_P2Sh(segwit_v0::make_bipxx_private(49, self.0, self.1, network)?).build(network) + fn build(self, network_kind: NetworkKind) -> Result { + P2Wpkh_P2Sh(segwit_v0::make_bipxx_private( + 49, + self.0, + self.1, + network_kind, + )?) + .build(network_kind) } } @@ -356,11 +374,15 @@ impl> DescriptorTemplate for Bip49 { pub struct Bip49Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); impl> DescriptorTemplate for Bip49Public { - fn build(self, network: Network) -> Result { + fn build(self, network_kind: NetworkKind) -> Result { P2Wpkh_P2Sh(segwit_v0::make_bipxx_public( - 49, self.0, self.1, self.2, network, + 49, + self.0, + self.1, + self.2, + network_kind, )?) - .build(network) + .build(network_kind) } } @@ -395,8 +417,14 @@ impl> DescriptorTemplate for Bip49Public { pub struct Bip84>(pub K, pub KeychainKind); impl> DescriptorTemplate for Bip84 { - fn build(self, network: Network) -> Result { - P2Wpkh(segwit_v0::make_bipxx_private(84, self.0, self.1, network)?).build(network) + fn build(self, network_kind: NetworkKind) -> Result { + P2Wpkh(segwit_v0::make_bipxx_private( + 84, + self.0, + self.1, + network_kind, + )?) + .build(network_kind) } } @@ -435,11 +463,15 @@ impl> DescriptorTemplate for Bip84 { pub struct Bip84Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); impl> DescriptorTemplate for Bip84Public { - fn build(self, network: Network) -> Result { + fn build(self, network_kind: NetworkKind) -> Result { P2Wpkh(segwit_v0::make_bipxx_public( - 84, self.0, self.1, self.2, network, + 84, + self.0, + self.1, + self.2, + network_kind, )?) - .build(network) + .build(network_kind) } } @@ -474,8 +506,14 @@ impl> DescriptorTemplate for Bip84Public { pub struct Bip86>(pub K, pub KeychainKind); impl> DescriptorTemplate for Bip86 { - fn build(self, network: Network) -> Result { - P2TR(segwit_v1::make_bipxx_private(86, self.0, self.1, network)?).build(network) + fn build(self, network_kind: NetworkKind) -> Result { + P2TR(segwit_v1::make_bipxx_private( + 86, + self.0, + self.1, + network_kind, + )?) + .build(network_kind) } } @@ -514,11 +552,15 @@ impl> DescriptorTemplate for Bip86 { pub struct Bip86Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); impl> DescriptorTemplate for Bip86Public { - fn build(self, network: Network) -> Result { + fn build(self, network_kind: NetworkKind) -> Result { P2TR(segwit_v1::make_bipxx_public( - 86, self.0, self.1, self.2, network, + 86, + self.0, + self.1, + self.2, + network_kind, )?) - .build(network) + .build(network_kind) } } @@ -531,13 +573,13 @@ macro_rules! expand_make_bipxx { bip: u32, key: K, keychain: KeychainKind, - network: Network, + network_kind: NetworkKind, ) -> Result, DescriptorError> { let mut derivation_path = alloc::vec::Vec::with_capacity(4); derivation_path.push(bip32::ChildNumber::from_hardened_idx(bip)?); - match network { - Network::Bitcoin => { + match network_kind { + NetworkKind::Main => { derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?); } _ => { @@ -564,7 +606,7 @@ macro_rules! expand_make_bipxx { key: K, parent_fingerprint: bip32::Fingerprint, keychain: KeychainKind, - network: Network, + network_kind: NetworkKind, ) -> Result, DescriptorError> { let derivation_path: bip32::DerivationPath = match keychain { KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(), @@ -573,8 +615,8 @@ macro_rules! expand_make_bipxx { let source_path = bip32::DerivationPath::from(vec![ bip32::ChildNumber::from_hardened_idx(bip)?, - match network { - Network::Bitcoin => bip32::ChildNumber::from_hardened_idx(0)?, + match network_kind { + NetworkKind::Main => bip32::ChildNumber::from_hardened_idx(0)?, _ => bip32::ChildNumber::from_hardened_idx(1)?, }, bip32::ChildNumber::from_hardened_idx(0)?, @@ -592,19 +634,22 @@ expand_make_bipxx!(segwit_v1, Tap); #[cfg(test)] mod test { - // test existing descriptor templates, make sure they are expanded to the right descriptors + // Test existing descriptor templates to make sure they are expanded to the right descriptors. + + use super::*; use alloc::{string::ToString, vec::Vec}; use core::str::FromStr; - use super::*; - use crate::descriptor::{DescriptorError, DescriptorMeta}; - use crate::keys::ValidNetworks; use assert_matches::assert_matches; + use bitcoin::Network; use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; use miniscript::Descriptor; - // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)` + use crate::descriptor::{DescriptorError, DescriptorMeta}; + use crate::keys::ValidNetworkKinds; + + // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)`. #[test] fn test_bip44_template_cointype() { use bitcoin::bip32::ChildNumber::{self, Hardened}; @@ -612,7 +657,7 @@ mod test { let xprvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap(); assert!(xprvkey.network.is_mainnet()); let xdesc = Bip44(xprvkey, KeychainKind::Internal) - .build(Network::Bitcoin) + .build(NetworkKind::Main) .unwrap(); if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 { @@ -626,7 +671,7 @@ mod test { let tprvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); assert!(!tprvkey.network.is_mainnet()); let tdesc = Bip44(tprvkey, KeychainKind::Internal) - .build(Network::Testnet) + .build(NetworkKind::Test) .unwrap(); if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 { @@ -638,19 +683,26 @@ mod test { } } - // verify template descriptor generates expected address(es) + // Verify template descriptor generates expected address(es). fn check( - desc: Result<(Descriptor, KeyMap, ValidNetworks), DescriptorError>, + desc: Result<(Descriptor, KeyMap, ValidNetworkKinds), DescriptorError>, is_witness: bool, is_taproot: bool, is_fixed: bool, - network: Network, + network_kind: NetworkKind, expected: &[&str], ) { - let (desc, _key_map, _networks) = desc.unwrap(); + let (desc, _key_map, _network_kinds) = desc.unwrap(); assert_eq!(desc.is_witness(), is_witness); assert_eq!(desc.is_taproot(), is_taproot); assert_eq!(!desc.has_wildcard(), is_fixed); + + // The only non-mainnet network we test is regtest. + let network = match network_kind.is_mainnet() { + true => Network::Bitcoin, + false => Network::Regtest, + }; + for i in 0..expected.len() { let index = i as u32; let child_desc = if !desc.has_wildcard() { @@ -670,11 +722,11 @@ mod test { bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") .unwrap(); check( - P2Pkh(prvkey).build(Network::Bitcoin), + P2Pkh(prvkey).build(NetworkKind::Test), false, false, true, - Network::Regtest, + NetworkKind::Test, &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"], ); @@ -683,11 +735,11 @@ mod test { ) .unwrap(); check( - P2Pkh(pubkey).build(Network::Bitcoin), + P2Pkh(pubkey).build(NetworkKind::Test), false, false, true, - Network::Regtest, + NetworkKind::Test, &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], ); } @@ -699,11 +751,11 @@ mod test { bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") .unwrap(); check( - P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin), + P2Wpkh_P2Sh(prvkey).build(NetworkKind::Main), true, false, true, - Network::Regtest, + NetworkKind::Test, &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"], ); @@ -712,11 +764,11 @@ mod test { ) .unwrap(); check( - P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin), + P2Wpkh_P2Sh(pubkey).build(NetworkKind::Main), true, false, true, - Network::Regtest, + NetworkKind::Test, &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], ); } @@ -728,11 +780,11 @@ mod test { bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") .unwrap(); check( - P2Wpkh(prvkey).build(Network::Bitcoin), + P2Wpkh(prvkey).build(NetworkKind::Main), true, false, true, - Network::Regtest, + NetworkKind::Test, &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"], ); @@ -741,11 +793,11 @@ mod test { ) .unwrap(); check( - P2Wpkh(pubkey).build(Network::Bitcoin), + P2Wpkh(pubkey).build(NetworkKind::Main), true, false, true, - Network::Regtest, + NetworkKind::Test, &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], ); } @@ -757,11 +809,11 @@ mod test { bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") .unwrap(); check( - P2TR(prvkey).build(Network::Bitcoin), + P2TR(prvkey).build(NetworkKind::Main), false, true, true, - Network::Regtest, + NetworkKind::Test, &["bcrt1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xqnwtkqq"], ); @@ -770,11 +822,11 @@ mod test { ) .unwrap(); check( - P2TR(pubkey).build(Network::Bitcoin), + P2TR(pubkey).build(NetworkKind::Main), false, true, true, - Network::Regtest, + NetworkKind::Test, &["bcrt1pw74tdcrxlzn5r8z6ku2vztr86fgq0m245s72mjktf4afwzsf8ugs4evwdf"], ); } @@ -784,11 +836,11 @@ mod test { fn test_bip44_template() { let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( - Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin), + Bip44(prvkey, KeychainKind::External).build(NetworkKind::Main), false, false, false, - Network::Regtest, + NetworkKind::Test, &[ "n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5", "mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP", @@ -796,11 +848,11 @@ mod test { ], ); check( - Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin), + Bip44(prvkey, KeychainKind::Internal).build(NetworkKind::Main), false, false, false, - Network::Regtest, + NetworkKind::Test, &[ "muHF98X9KxEzdKrnFAX85KeHv96eXopaip", "n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR", @@ -815,11 +867,11 @@ mod test { let pubkey = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( - Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), + Bip44Public(pubkey, fingerprint, KeychainKind::External).build(NetworkKind::Main), false, false, false, - Network::Regtest, + NetworkKind::Test, &[ "miNG7dJTzJqNbFS19svRdTCisC65dsubtR", "n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg", @@ -827,11 +879,11 @@ mod test { ], ); check( - Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), + Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(NetworkKind::Test), false, false, false, - Network::Regtest, + NetworkKind::Test, &[ "moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H", "ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG", @@ -845,11 +897,11 @@ mod test { fn test_bip49_template() { let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( - Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin), + Bip49(prvkey, KeychainKind::External).build(NetworkKind::Main), true, false, false, - Network::Regtest, + NetworkKind::Test, &[ "2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV", "2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS", @@ -857,11 +909,11 @@ mod test { ], ); check( - Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin), + Bip49(prvkey, KeychainKind::Internal).build(NetworkKind::Main), true, false, false, - Network::Regtest, + NetworkKind::Test, &[ "2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG", "2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p", @@ -876,11 +928,11 @@ mod test { let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( - Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), + Bip49Public(pubkey, fingerprint, KeychainKind::External).build(NetworkKind::Main), true, false, false, - Network::Regtest, + NetworkKind::Test, &[ "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt", "2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX", @@ -888,11 +940,11 @@ mod test { ], ); check( - Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), + Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(NetworkKind::Main), true, false, false, - Network::Regtest, + NetworkKind::Test, &[ "2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ", "2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH", @@ -906,11 +958,11 @@ mod test { fn test_bip84_template() { let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( - Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin), + Bip84(prvkey, KeychainKind::External).build(NetworkKind::Main), true, false, false, - Network::Regtest, + NetworkKind::Test, &[ "bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s", "bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp", @@ -918,11 +970,11 @@ mod test { ], ); check( - Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin), + Bip84(prvkey, KeychainKind::Internal).build(NetworkKind::Main), true, false, false, - Network::Regtest, + NetworkKind::Test, &[ "bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa", "bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45", @@ -937,11 +989,11 @@ mod test { let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( - Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), + Bip84Public(pubkey, fingerprint, KeychainKind::External).build(NetworkKind::Main), true, false, false, - Network::Regtest, + NetworkKind::Test, &[ "bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h", "bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana", @@ -949,11 +1001,11 @@ mod test { ], ); check( - Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), + Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(NetworkKind::Main), true, false, false, - Network::Regtest, + NetworkKind::Test, &[ "bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2", "bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp", @@ -963,16 +1015,16 @@ mod test { } // BIP86 `tr(key/86'/0'/0'/{0,1}/*)` - // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki + // Used addresses in test vector from https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki #[test] fn test_bip86_template() { let prvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap(); check( - Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin), + Bip86(prvkey, KeychainKind::External).build(NetworkKind::Main), false, true, false, - Network::Bitcoin, + NetworkKind::Main, &[ "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", @@ -980,11 +1032,11 @@ mod test { ], ); check( - Bip86(prvkey, KeychainKind::Internal).build(Network::Bitcoin), + Bip86(prvkey, KeychainKind::Internal).build(NetworkKind::Main), false, true, false, - Network::Bitcoin, + NetworkKind::Main, &[ "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj", @@ -1000,11 +1052,11 @@ mod test { let pubkey = bitcoin::bip32::Xpub::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap(); let fingerprint = bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap(); check( - Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), + Bip86Public(pubkey, fingerprint, KeychainKind::External).build(NetworkKind::Main), false, true, false, - Network::Bitcoin, + NetworkKind::Main, &[ "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", @@ -1012,11 +1064,11 @@ mod test { ], ); check( - Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), + Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(NetworkKind::Main), false, true, false, - Network::Bitcoin, + NetworkKind::Main, &[ "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj", From f0fca58d72ec83c01b2bcc32ac1901bc3e1a32af Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Sat, 31 May 2025 15:35:04 -0300 Subject: [PATCH 2/4] feat!(keys): use `NetworkKind` --- wallet/src/keys/bip39.rs | 59 ++++--- wallet/src/keys/mod.rs | 339 ++++++++++++++++++++------------------- 2 files changed, 207 insertions(+), 191 deletions(-) diff --git a/wallet/src/keys/bip39.rs b/wallet/src/keys/bip39.rs index 7ec3c0e6..76c33b23 100644 --- a/wallet/src/keys/bip39.rs +++ b/wallet/src/keys/bip39.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -15,11 +15,15 @@ // something that should be fairly simple to re-implement. use alloc::string::String; -use bitcoin::bip32; -use bitcoin::Network; +use bitcoin::{bip32, Network}; use miniscript::ScriptContext; +use super::{ + any_network_kind, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, + KeyError, +}; + pub use bip39::{Error, Language, Mnemonic}; type Seed = [u8; 64]; @@ -38,20 +42,16 @@ pub enum WordCount { Words24 = 256, } -use super::{ - any_network, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, KeyError, -}; - -fn set_valid_on_any_network( +fn set_valid_on_any_network_kind( descriptor_key: DescriptorKey, ) -> DescriptorKey { - // We have to pick one network to build the xprv, but since the bip39 standard doesn't - // encode the network, the xprv we create is actually valid everywhere. So we override the - // valid networks with `any_network()`. - descriptor_key.override_valid_networks(any_network()) + // We have to pick one network kind to build the xprv, but since the BIP39 standard doesn't + // encode the network kind, the xprv we create is actually valid everywhere. So we override the + // valid network kinds with `any_network_kind()`. + descriptor_key.override_valid_network_kinds(any_network_kind()) } -/// Type for a BIP39 mnemonic with an optional passphrase +/// Type for a BIP39 mnemonic with an optional passphrase. pub type MnemonicWithPassphrase = (Mnemonic, Option); #[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))] @@ -69,7 +69,7 @@ impl DerivableKey for Seed { .into_extended_key()? .into_descriptor_key(source, derivation_path)?; - Ok(set_valid_on_any_network(descriptor_key)) + Ok(set_valid_on_any_network_kind(descriptor_key)) } } @@ -91,7 +91,7 @@ impl DerivableKey for MnemonicWithPassphrase { .into_extended_key()? .into_descriptor_key(source, derivation_path)?; - Ok(set_valid_on_any_network(descriptor_key)) + Ok(set_valid_on_any_network_kind(descriptor_key)) } } @@ -127,7 +127,7 @@ impl DerivableKey for Mnemonic { .into_extended_key()? .into_descriptor_key(source, derivation_path)?; - Ok(set_valid_on_any_network(descriptor_key)) + Ok(set_valid_on_any_network_kind(descriptor_key)) } } @@ -145,22 +145,21 @@ impl GeneratableKey for Mnemonic { let entropy = &entropy[..(word_count as usize / 8)]; let mnemonic = Mnemonic::from_entropy_in(language, entropy)?; - Ok(GeneratedKey::new(mnemonic, any_network())) + Ok(GeneratedKey::new(mnemonic, any_network_kind())) } } #[cfg(test)] mod test { + use super::WordCount; + use alloc::string::ToString; use core::str::FromStr; - use bitcoin::bip32; - use bip39::{Language, Mnemonic}; + use bitcoin::bip32; - use crate::keys::{any_network, GeneratableKey, GeneratedKey}; - - use super::WordCount; + use crate::keys::{any_network_kind, GeneratableKey, GeneratedKey}; #[test] fn test_keys_bip39_mnemonic() { @@ -170,10 +169,10 @@ mod test { let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap(); let key = (mnemonic, path); - let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap(); + let (desc, keys, network_kinds) = crate::descriptor!(wpkh(key)).unwrap(); assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)#0r8v4nkv"); assert_eq!(keys.len(), 1); - assert_eq!(networks, any_network()); + assert_eq!(network_kinds, any_network_kind()); } #[test] @@ -184,10 +183,10 @@ mod test { let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap(); let key = ((mnemonic, Some("passphrase".into())), path); - let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap(); + let (desc, keys, network_kinds) = crate::descriptor!(wpkh(key)).unwrap(); assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)#h0j0tg5m"); assert_eq!(keys.len(), 1); - assert_eq!(networks, any_network()); + assert_eq!(network_kinds, any_network_kind()); } #[test] @@ -198,7 +197,7 @@ mod test { crate::keys::test::TEST_ENTROPY, ) .unwrap(); - assert_eq!(generated_mnemonic.valid_networks, any_network()); + assert_eq!(generated_mnemonic.valid_network_kinds, any_network_kind()); assert_eq!( generated_mnemonic.to_string(), "primary fetch primary fetch primary fetch primary fetch primary fetch primary fever" @@ -210,7 +209,7 @@ mod test { crate::keys::test::TEST_ENTROPY, ) .unwrap(); - assert_eq!(generated_mnemonic.valid_networks, any_network()); + assert_eq!(generated_mnemonic.valid_network_kinds, any_network_kind()); assert_eq!(generated_mnemonic.to_string(), "primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary foster"); } @@ -218,10 +217,10 @@ mod test { fn test_keys_generate_bip39_random() { let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> = Mnemonic::generate((WordCount::Words12, Language::English)).unwrap(); - assert_eq!(generated_mnemonic.valid_networks, any_network()); + assert_eq!(generated_mnemonic.valid_network_kinds, any_network_kind()); let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> = Mnemonic::generate((WordCount::Words24, Language::English)).unwrap(); - assert_eq!(generated_mnemonic.valid_networks, any_network()); + assert_eq!(generated_mnemonic.valid_network_kinds, any_network_kind()); } } diff --git a/wallet/src/keys/mod.rs b/wallet/src/keys/mod.rs index f239f45f..7851b977 100644 --- a/wallet/src/keys/mod.rs +++ b/wallet/src/keys/mod.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -11,7 +11,6 @@ //! Key formats -use crate::collections::HashSet; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::any::TypeId; @@ -20,105 +19,99 @@ use core::marker::PhantomData; use core::ops::Deref; use core::str::FromStr; -use rand_core::{CryptoRng, RngCore}; - -use bitcoin::secp256k1::{self, Secp256k1, Signing}; +use crate::collections::HashSet; +use crate::descriptor::{CheckMiniscript, DescriptorError}; +use crate::wallet::utils::SecpCtx; -use bitcoin::bip32; -use bitcoin::{key::XOnlyPublicKey, Network, PrivateKey, PublicKey}; +use bitcoin::{ + bip32, + key::XOnlyPublicKey, + secp256k1::{self, Secp256k1, Signing}, + Network, NetworkKind, PrivateKey, PublicKey, +}; +use miniscript::{ + descriptor::{Descriptor, DescriptorMultiXKey, DescriptorXKey, Wildcard}, + {Miniscript, Terminal}, +}; +use rand_core::{CryptoRng, RngCore}; -use miniscript::descriptor::{Descriptor, DescriptorMultiXKey, DescriptorXKey, Wildcard}; pub use miniscript::descriptor::{ DescriptorPublicKey, DescriptorSecretKey, KeyMap, SinglePriv, SinglePub, SinglePubKey, SortedMultiVec, }; pub use miniscript::ScriptContext; -use miniscript::{Miniscript, Terminal}; - -use crate::descriptor::{CheckMiniscript, DescriptorError}; -use crate::wallet::utils::SecpCtx; #[cfg(feature = "keys-bip39")] #[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))] pub mod bip39; -/// Set of valid networks for a key -pub type ValidNetworks = HashSet; - -/// Create a set containing mainnet, testnet, testnet4, signet, and regtest -pub fn any_network() -> ValidNetworks { - vec![ - Network::Bitcoin, - Network::Testnet, - Network::Testnet4, - Network::Regtest, - Network::Signet, - ] - .into_iter() - .collect() +/// Set of valid networks kinds for a key. +pub type ValidNetworkKinds = HashSet; + +/// Create a set containing the [`NetworkKind::Main`] and [`NetworkKind::Test`] network kinds. +pub fn any_network_kind() -> ValidNetworkKinds { + vec![NetworkKind::Main, NetworkKind::Test] + .into_iter() + .collect() } -/// Create a set only containing mainnet -pub fn mainnet_network() -> ValidNetworks { - vec![Network::Bitcoin].into_iter().collect() +/// Create a set containing the [`NetworkKind::Main`] kind. +pub fn mainnet_network_kind() -> ValidNetworkKinds { + vec![NetworkKind::Main].into_iter().collect() } -/// Create a set containing test networks -pub fn test_networks() -> ValidNetworks { - vec![ - Network::Testnet, - Network::Testnet4, - Network::Regtest, - Network::Signet, - ] - .into_iter() - .collect() +/// Create a set containing only the [`NetworkKind::Test`] kind. +pub fn test_network_kind() -> ValidNetworkKinds { + vec![NetworkKind::Test].into_iter().collect() } -/// Compute the intersection of two sets -pub fn merge_networks(a: &ValidNetworks, b: &ValidNetworks) -> ValidNetworks { +/// Compute the intersection of two sets. +pub fn merge_network_kinds(a: &ValidNetworkKinds, b: &ValidNetworkKinds) -> ValidNetworkKinds { a.intersection(b).cloned().collect() } -/// Container for public or secret keys +/// Container for public or secret keys. #[derive(Debug)] pub enum DescriptorKey { #[doc(hidden)] - Public(DescriptorPublicKey, ValidNetworks, PhantomData), + Public(DescriptorPublicKey, ValidNetworkKinds, PhantomData), #[doc(hidden)] - Secret(DescriptorSecretKey, ValidNetworks, PhantomData), + Secret(DescriptorSecretKey, ValidNetworkKinds, PhantomData), } impl DescriptorKey { - /// Create an instance given a public key and a set of valid networks - pub fn from_public(public: DescriptorPublicKey, networks: ValidNetworks) -> Self { - DescriptorKey::Public(public, networks, PhantomData) + /// Create an instance given a public key and a set of valid network kinds. + pub fn from_public(public: DescriptorPublicKey, network_kinds: ValidNetworkKinds) -> Self { + DescriptorKey::Public(public, network_kinds, PhantomData) } - /// Create an instance given a secret key and a set of valid networks - pub fn from_secret(secret: DescriptorSecretKey, networks: ValidNetworks) -> Self { - DescriptorKey::Secret(secret, networks, PhantomData) + /// Create an instance given a secret key and a set of valid network kinds. + pub fn from_secret(secret: DescriptorSecretKey, network_kinds: ValidNetworkKinds) -> Self { + DescriptorKey::Secret(secret, network_kinds, PhantomData) } - /// Override the computed set of valid networks - pub fn override_valid_networks(self, networks: ValidNetworks) -> Self { + /// Override the computed set of valid network kinds. + pub fn override_valid_network_kinds(self, network_kinds: ValidNetworkKinds) -> Self { match self { - DescriptorKey::Public(key, _, _) => DescriptorKey::Public(key, networks, PhantomData), - DescriptorKey::Secret(key, _, _) => DescriptorKey::Secret(key, networks, PhantomData), + DescriptorKey::Public(key, _, _) => { + DescriptorKey::Public(key, network_kinds, PhantomData) + } + DescriptorKey::Secret(key, _, _) => { + DescriptorKey::Secret(key, network_kinds, PhantomData) + } } } // This method is used internally by `bdk_wallet::fragment!` and `bdk_wallet::descriptor!`. It // has to be public because it is effectively called by external crates once the macros are - // expanded, but since it is not meant to be part of the public api we hide it from the - // docs. + // expanded, but since it is not meant to be part of the public API we hide it from the docs. #[doc(hidden)] pub fn extract( self, secp: &SecpCtx, - ) -> Result<(DescriptorPublicKey, KeyMap, ValidNetworks), KeyError> { + ) -> Result<(DescriptorPublicKey, KeyMap, ValidNetworkKinds), KeyError> { match self { - DescriptorKey::Public(public, valid_networks, _) => { - Ok((public, KeyMap::default(), valid_networks)) + DescriptorKey::Public(public, valid_network_kinds, _) => { + Ok((public, KeyMap::default(), valid_network_kinds)) } - DescriptorKey::Secret(secret, valid_networks, _) => { + DescriptorKey::Secret(secret, valid_network_kinds, _) => { let mut key_map = KeyMap::new(); let public = secret @@ -126,13 +119,13 @@ impl DescriptorKey { .map_err(|e| miniscript::Error::Unexpected(e.to_string()))?; key_map.insert(public.clone(), secret); - Ok((public, key_map, valid_networks)) + Ok((public, key_map, valid_network_kinds)) } } } } -/// Enum representation of the known valid [`ScriptContext`]s +/// Enum representation of the known valid [`ScriptContext`]s. #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub enum ScriptContextEnum { /// Legacy scripts @@ -144,38 +137,38 @@ pub enum ScriptContextEnum { } impl ScriptContextEnum { - /// Returns whether the script context is [`ScriptContextEnum::Legacy`] + /// Returns whether the script context is [`ScriptContextEnum::Legacy`]. pub fn is_legacy(&self) -> bool { self == &ScriptContextEnum::Legacy } - /// Returns whether the script context is [`ScriptContextEnum::Segwitv0`] + /// Returns whether the script context is [`ScriptContextEnum::Segwitv0`]. pub fn is_segwit_v0(&self) -> bool { self == &ScriptContextEnum::Segwitv0 } - /// Returns whether the script context is [`ScriptContextEnum::Tap`] + /// Returns whether the script context is [`ScriptContextEnum::Tap`]. pub fn is_taproot(&self) -> bool { self == &ScriptContextEnum::Tap } } -/// Trait that adds extra useful methods to [`ScriptContext`]s +/// Trait that adds extra useful methods to [`ScriptContext`]s. pub trait ExtScriptContext: ScriptContext { - /// Returns the [`ScriptContext`] as a [`ScriptContextEnum`] + /// Returns the [`ScriptContext`] as a [`ScriptContextEnum`]. fn as_enum() -> ScriptContextEnum; - /// Returns whether the script context is [`Legacy`](miniscript::Legacy) + /// Returns whether the script context is [`Legacy`](miniscript::Legacy). fn is_legacy() -> bool { Self::as_enum().is_legacy() } - /// Returns whether the script context is [`Segwitv0`](miniscript::Segwitv0) + /// Returns whether the script context is [`Segwitv0`](miniscript::Segwitv0). fn is_segwit_v0() -> bool { Self::as_enum().is_segwit_v0() } - /// Returns whether the script context is [`Tap`](miniscript::Tap), aka Taproot or Segwit V1 + /// Returns whether the script context is [`Tap`](miniscript::Tap), aka Taproot or Segwit V1. fn is_taproot() -> bool { Self::as_enum().is_taproot() } @@ -206,9 +199,9 @@ impl ExtScriptContext for Ctx { /// single `Ctx`), the "specialized" trait can be implemented to make the compiler handle the type /// checking. /// -/// Keys also have control over the networks they support: constructing the return object with +/// Keys also have control over the network kinds they support: constructing the return object with /// [`DescriptorKey::from_public`] or [`DescriptorKey::from_secret`] allows to specify a set of -/// [`ValidNetworks`]. +/// [`ValidNetworkKinds`]. /// /// ## Examples /// @@ -236,7 +229,7 @@ impl ExtScriptContext for Ctx { /// use bdk_wallet::bitcoin::PublicKey; /// /// use bdk_wallet::keys::{ -/// mainnet_network, DescriptorKey, DescriptorPublicKey, IntoDescriptorKey, KeyError, +/// mainnet_network_kind, DescriptorKey, DescriptorPublicKey, IntoDescriptorKey, KeyError, /// ScriptContext, SinglePub, SinglePubKey, /// }; /// @@ -251,7 +244,7 @@ impl ExtScriptContext for Ctx { /// origin: None, /// key: SinglePubKey::FullKey(self.pubkey), /// }), -/// mainnet_network(), +/// mainnet_network_kind(), /// )) /// } /// } @@ -332,7 +325,7 @@ pub enum ExtendedKey { } impl ExtendedKey { - /// Return whether or not the key contains the private data + /// Return whether or not the key contains the private data. pub fn has_secret(&self) -> bool { match self { ExtendedKey::Private(_) => true, @@ -341,11 +334,11 @@ impl ExtendedKey { } /// Transform the [`ExtendedKey`] into an [`Xpriv`](bip32::Xpriv) for the - /// given [`Network`], if the key contains the private data - pub fn into_xprv(self, network: Network) -> Option { + /// given [`NetworkKind`], if the key contains the private data. + pub fn into_xprv(self, network_kind: NetworkKind) -> Option { match self { ExtendedKey::Private((mut xprv, _)) => { - xprv.network = network.into(); + xprv.network = network_kind; Some(xprv) } ExtendedKey::Public(_) => None, @@ -353,10 +346,10 @@ impl ExtendedKey { } /// Transform the [`ExtendedKey`] into an [`Xpub`](bip32::Xpub) for the - /// given [`Network`] + /// given [`NetworkKind`]. pub fn into_xpub( self, - network: bitcoin::Network, + network_kind: NetworkKind, secp: &Secp256k1, ) -> bip32::Xpub { let mut xpub = match self { @@ -364,7 +357,7 @@ impl ExtendedKey { ExtendedKey::Public((xpub, _)) => xpub, }; - xpub.network = network.into(); + xpub.network = network_kind; xpub } } @@ -390,7 +383,7 @@ impl From for ExtendedKey { /// /// For key types that don't encode any indication about the path to use (like bip39), it's /// generally recommended to implement this trait instead of [`IntoDescriptorKey`]. The same -/// rules regarding script context and valid networks apply. +/// rules regarding script context and valid network kinds apply. /// /// ## Examples /// @@ -398,20 +391,19 @@ impl From for ExtendedKey { /// an [`Xpub`] can implement only the required `into_extended_key()` method. /// /// ``` -/// use bdk_wallet::bitcoin; -/// use bdk_wallet::bitcoin::bip32; +/// use bdk_wallet::bitcoin::{bip32, NetworkKind}; /// use bdk_wallet::keys::{DerivableKey, ExtendedKey, KeyError, ScriptContext}; /// /// struct MyCustomKeyType { /// key_data: bitcoin::PrivateKey, /// chain_code: [u8; 32], -/// network: bitcoin::Network, +/// network_kind: NetworkKind, /// } /// /// impl DerivableKey for MyCustomKeyType { /// fn into_extended_key(self) -> Result, KeyError> { /// let xprv = bip32::Xpriv { -/// network: self.network.into(), +/// network: self.network_kind, /// depth: 0, /// parent_fingerprint: bip32::Fingerprint::default(), /// private_key: self.key_data.inner, @@ -424,15 +416,14 @@ impl From for ExtendedKey { /// } /// ``` /// -/// Types that don't internally encode the [`Network`] in which they are valid need some extra -/// steps to override the set of valid networks, otherwise only the network specified in the -/// [`Xpriv`] or [`Xpub`] will be considered valid. +/// Types that don't internally encode the [`NetworkKind`] in which they are valid need some extra +/// steps to override the set of valid network kinds, otherwise only the network kind specified in +/// the [`Xpriv`] or [`Xpub`] will be considered valid. /// /// ``` -/// use bdk_wallet::bitcoin; -/// use bdk_wallet::bitcoin::bip32; +/// use bdk_wallet::bitcoin::{bip32, NetworkKind}; /// use bdk_wallet::keys::{ -/// any_network, DerivableKey, DescriptorKey, ExtendedKey, KeyError, ScriptContext, +/// any_network_kind, DerivableKey, DescriptorKey, ExtendedKey, KeyError, ScriptContext, /// }; /// /// struct MyCustomKeyType { @@ -443,7 +434,7 @@ impl From for ExtendedKey { /// impl DerivableKey for MyCustomKeyType { /// fn into_extended_key(self) -> Result, KeyError> { /// let xprv = bip32::Xpriv { -/// network: bitcoin::Network::Bitcoin.into(), // pick an arbitrary network here +/// network: NetworkKind::Main, // pick an arbitrary network kind /// depth: 0, /// parent_fingerprint: bip32::Fingerprint::default(), /// private_key: self.key_data.inner, @@ -463,8 +454,8 @@ impl From for ExtendedKey { /// .into_extended_key()? /// .into_descriptor_key(source, derivation_path)?; /// -/// // Override the set of valid networks here -/// Ok(descriptor_key.override_valid_networks(any_network())) +/// // Override the set of valid network kinds here +/// Ok(descriptor_key.override_valid_network_kinds(any_network_kind())) /// } /// } /// ``` @@ -480,7 +471,7 @@ pub trait DerivableKey: Sized { This can be used to get direct access to `xprv`s and `xpub`s for types that implement this trait, like [`Mnemonic`](bip39::Mnemonic) when the `keys-bip39` feature is enabled. ```rust -use bdk_wallet::bitcoin::Network; +use bdk_wallet::bitcoin::NetworkKind; use bdk_wallet::keys::{DerivableKey, ExtendedKey}; use bdk_wallet::keys::bip39::{Mnemonic, Language}; @@ -491,7 +482,7 @@ let xkey: ExtendedKey = "jelly crash boy whisper mouse ecology tuna soccer memory million news short", )? .into_extended_key()?; -let xprv = xkey.into_xprv(Network::Bitcoin).unwrap(); +let xprv = xkey.into_xprv(NetworkKind::Main).unwrap(); # Ok(()) } ``` "## @@ -543,18 +534,18 @@ impl DerivableKey for bip32::Xpriv { } } -/// Output of a [`GeneratableKey`] key generation +/// Output of a [`GeneratableKey`] key generation. pub struct GeneratedKey { key: K, - valid_networks: ValidNetworks, + valid_network_kinds: ValidNetworkKinds, phantom: PhantomData, } impl GeneratedKey { - fn new(key: K, valid_networks: ValidNetworks) -> Self { + fn new(key: K, valid_network_kinds: ValidNetworkKinds) -> Self { GeneratedKey { key, - valid_networks, + valid_network_kinds, phantom: PhantomData, } } @@ -577,7 +568,7 @@ impl Clone for GeneratedKey { fn clone(&self) -> GeneratedKey { GeneratedKey { key: self.key.clone(), - valid_networks: self.valid_networks.clone(), + valid_network_kinds: self.valid_network_kinds.clone(), phantom: self.phantom, } } @@ -600,12 +591,12 @@ where derivation_path: bip32::DerivationPath, ) -> Result, KeyError> { let descriptor_key = self.key.into_descriptor_key(origin, derivation_path)?; - Ok(descriptor_key.override_valid_networks(self.valid_networks)) + Ok(descriptor_key.override_valid_network_kinds(self.valid_network_kinds)) } } // Make generated keys directly usable in descriptors, and make sure they get assigned the right -// `valid_networks`. +// `valid_network_kinds`. impl IntoDescriptorKey for GeneratedKey where Ctx: ScriptContext, @@ -613,13 +604,14 @@ where { fn into_descriptor_key(self) -> Result, KeyError> { let desc_key = self.key.into_descriptor_key()?; - Ok(desc_key.override_valid_networks(self.valid_networks)) + Ok(desc_key.override_valid_network_kinds(self.valid_network_kinds)) } } /// Trait for keys that can be generated /// -/// The same rules about [`ScriptContext`] and [`ValidNetworks`] from [`IntoDescriptorKey`] apply. +/// The same rules about [`ScriptContext`] and [`ValidNetworkKinds`] from [`IntoDescriptorKey`] +/// apply. /// /// This trait is particularly useful when combined with [`DerivableKey`]: if `Self` /// implements it, the returned [`GeneratedKey`] will also implement it. The same is true for @@ -715,18 +707,18 @@ impl GeneratableKey for bip32::Xpriv { _: Self::Options, entropy: Self::Entropy, ) -> Result, Self::Error> { - // pick a arbitrary network here, but say that we support all of them - let xprv = bip32::Xpriv::new_master(Network::Bitcoin, entropy.as_ref())?; - Ok(GeneratedKey::new(xprv, any_network())) + // Pick an arbitrary network kind here, but say that we support all of them. + let xprv = bip32::Xpriv::new_master(NetworkKind::Main, entropy.as_ref())?; + Ok(GeneratedKey::new(xprv, any_network_kind())) } } -/// Options for generating a [`PrivateKey`] +/// Options for generating a [`PrivateKey`]. /// -/// Defaults to creating compressed keys, which save on-chain bytes and fees +/// Defaults to creating compressed keys, which save on-chain bytes and fees. #[derive(Debug, Copy, Clone)] pub struct PrivateKeyGenerateOptions { - /// Whether the generated key should be "compressed" or not + /// Whether the generated key should be "compressed" or not. pub compressed: bool, } @@ -754,7 +746,7 @@ impl GeneratableKey for PrivateKey { inner, }; - Ok(GeneratedKey::new(private_key, any_network())) + Ok(GeneratedKey::new(private_key, any_network_kind())) } } @@ -777,8 +769,8 @@ impl> IntoDescriptorKey fn expand_multi_keys, Ctx: ScriptContext>( pks: Vec, secp: &SecpCtx, -) -> Result<(Vec, KeyMap, ValidNetworks), KeyError> { - let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks +) -> Result<(Vec, KeyMap, ValidNetworkKinds), KeyError> { + let (pks, key_maps_network_kinds): (Vec<_>, Vec<_>) = pks .into_iter() .map(|key| key.into_descriptor_key()?.extract(secp)) .collect::, _>>()? @@ -786,48 +778,64 @@ fn expand_multi_keys, Ctx: ScriptContext>( .map(|(a, b, c)| (a, (b, c))) .unzip(); - let (key_map, valid_networks) = key_maps_networks.into_iter().fold( - (KeyMap::default(), any_network()), - |(mut keys_acc, net_acc), (key, net)| { + let (key_map, valid_network_kinds) = key_maps_network_kinds.into_iter().fold( + (KeyMap::default(), any_network_kind()), + |(mut keys_acc, netkind_acc), (key, netkind)| { keys_acc.extend(key); - let net_acc = merge_networks(&net_acc, &net); + let netkind_acc = merge_network_kinds(&netkind_acc, &netkind); - (keys_acc, net_acc) + (keys_acc, netkind_acc) }, ); - Ok((pks, key_map, valid_networks)) + Ok((pks, key_map, valid_network_kinds)) } -// Used internally by `bdk_wallet::fragment!` to build `pk_k()` fragments +// Used internally by `bdk_wallet::fragment!` to build `pk_k()` fragments. #[doc(hidden)] pub fn make_pk, Ctx: ScriptContext>( descriptor_key: Pk, secp: &SecpCtx, -) -> Result<(Miniscript, KeyMap, ValidNetworks), DescriptorError> { - let (key, key_map, valid_networks) = descriptor_key.into_descriptor_key()?.extract(secp)?; +) -> Result< + ( + Miniscript, + KeyMap, + ValidNetworkKinds, + ), + DescriptorError, +> { + let (key, key_map, valid_network_kinds) = + descriptor_key.into_descriptor_key()?.extract(secp)?; let minisc = Miniscript::from_ast(Terminal::PkK(key))?; minisc.check_miniscript()?; - Ok((minisc, key_map, valid_networks)) + Ok((minisc, key_map, valid_network_kinds)) } -// Used internally by `bdk_wallet::fragment!` to build `pk_h()` fragments +// Used internally by `bdk_wallet::fragment!` to build `pk_h()` fragments. #[doc(hidden)] pub fn make_pkh, Ctx: ScriptContext>( descriptor_key: Pk, secp: &SecpCtx, -) -> Result<(Miniscript, KeyMap, ValidNetworks), DescriptorError> { - let (key, key_map, valid_networks) = descriptor_key.into_descriptor_key()?.extract(secp)?; +) -> Result< + ( + Miniscript, + KeyMap, + ValidNetworkKinds, + ), + DescriptorError, +> { + let (key, key_map, valid_network_kinds) = + descriptor_key.into_descriptor_key()?.extract(secp)?; let minisc = Miniscript::from_ast(Terminal::PkH(key))?; minisc.check_miniscript()?; - Ok((minisc, key_map, valid_networks)) + Ok((minisc, key_map, valid_network_kinds)) } -// Used internally by `bdk_wallet::fragment!` to build `multi()` fragments +// Used internally by `bdk_wallet::fragment!` to build `multi()` fragments. #[doc(hidden)] pub fn make_multi< Pk: IntoDescriptorKey, @@ -838,23 +846,30 @@ pub fn make_multi< variant: V, pks: Vec, secp: &SecpCtx, -) -> Result<(Miniscript, KeyMap, ValidNetworks), DescriptorError> { - let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?; +) -> Result< + ( + Miniscript, + KeyMap, + ValidNetworkKinds, + ), + DescriptorError, +> { + let (pks, key_map, valid_network_kinds) = expand_multi_keys(pks, secp)?; let minisc = Miniscript::from_ast(variant(thresh, pks))?; minisc.check_miniscript()?; - Ok((minisc, key_map, valid_networks)) + Ok((minisc, key_map, valid_network_kinds)) } -// Used internally by `bdk_wallet::descriptor!` to build `sortedmulti()` fragments +// Used internally by `bdk_wallet::descriptor!` to build `sortedmulti()` fragments. #[doc(hidden)] pub fn make_sortedmulti( thresh: usize, pks: Vec, build_desc: F, secp: &SecpCtx, -) -> Result<(Descriptor, KeyMap, ValidNetworks), DescriptorError> +) -> Result<(Descriptor, KeyMap, ValidNetworkKinds), DescriptorError> where Pk: IntoDescriptorKey, Ctx: ScriptContext, @@ -863,13 +878,13 @@ where Vec, ) -> Result<(Descriptor, PhantomData), DescriptorError>, { - let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?; + let (pks, key_map, valid_network_kinds) = expand_multi_keys(pks, secp)?; let descriptor = build_desc(thresh, pks)?.0; - Ok((descriptor, key_map, valid_networks)) + Ok((descriptor, key_map, valid_network_kinds)) } -/// The "identity" conversion is used internally by some `bdk_wallet::fragment`s +/// The "identity" conversion is used internally by some `bdk_wallet::fragment`s. impl IntoDescriptorKey for DescriptorKey { fn into_descriptor_key(self) -> Result, KeyError> { Ok(self) @@ -878,25 +893,25 @@ impl IntoDescriptorKey for DescriptorKey { impl IntoDescriptorKey for DescriptorPublicKey { fn into_descriptor_key(self) -> Result, KeyError> { - let networks = match self { - DescriptorPublicKey::Single(_) => any_network(), + let network_kinds = match self { + DescriptorPublicKey::Single(_) => any_network_kind(), DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. }) => { if xkey.network.is_mainnet() { - mainnet_network() + mainnet_network_kind() } else { - test_networks() + test_network_kind() } } DescriptorPublicKey::MultiXPub(DescriptorMultiXKey { xkey, .. }) => { if xkey.network.is_mainnet() { - mainnet_network() + mainnet_network_kind() } else { - test_networks() + test_network_kind() } } }; - Ok(DescriptorKey::from_public(self, networks)) + Ok(DescriptorKey::from_public(self, network_kinds)) } } @@ -922,15 +937,17 @@ impl IntoDescriptorKey for XOnlyPublicKey { impl IntoDescriptorKey for DescriptorSecretKey { fn into_descriptor_key(self) -> Result, KeyError> { - let networks = match &self { - DescriptorSecretKey::Single(sk) if sk.key.network.is_mainnet() => mainnet_network(), + let network_kinds = match &self { + DescriptorSecretKey::Single(sk) if sk.key.network.is_mainnet() => { + mainnet_network_kind() + } DescriptorSecretKey::XPrv(DescriptorXKey { xkey, .. }) if xkey.network.is_mainnet() => { - mainnet_network() + mainnet_network_kind() } - _ => test_networks(), + _ => test_network_kind(), }; - Ok(DescriptorKey::from_secret(self, networks)) + Ok(DescriptorKey::from_secret(self, network_kinds)) } } @@ -952,13 +969,13 @@ impl IntoDescriptorKey for PrivateKey { } } -/// Errors thrown while working with [`keys`](crate::keys) +/// Errors thrown while working with [`keys`](crate::keys). #[derive(Debug, PartialEq)] pub enum KeyError { /// The key cannot exist in the given script context InvalidScriptContext, - /// The key is not valid for the given network - InvalidNetwork, + /// The key is not valid for the given network kind + InvalidNetworkKind, /// The key has an invalid checksum InvalidChecksum, @@ -987,7 +1004,7 @@ impl fmt::Display for KeyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidScriptContext => write!(f, "Invalid script context"), - Self::InvalidNetwork => write!(f, "Invalid network"), + Self::InvalidNetworkKind => write!(f, "Invalid network kind"), Self::InvalidChecksum => write!(f, "Invalid checksum"), Self::Message(err) => write!(f, "{}", err), Self::Bip32(err) => write!(f, "BIP32 error: {}", err), @@ -1001,10 +1018,10 @@ impl std::error::Error for KeyError {} #[cfg(test)] mod test { - use bitcoin::bip32; - use super::*; + use bitcoin::bip32; + pub const TEST_ENTROPY: [u8; 32] = [0xAA; 32]; #[test] @@ -1012,7 +1029,7 @@ mod test { let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> = bip32::Xpriv::generate_with_entropy_default(TEST_ENTROPY).unwrap(); - assert_eq!(generated_xprv.valid_networks, any_network()); + assert_eq!(generated_xprv.valid_network_kinds, any_network_kind()); assert_eq!(generated_xprv.to_string(), "xprv9s21ZrQH143K4Xr1cJyqTvuL2FWR8eicgY9boWqMBv8MDVUZ65AXHnzBrK1nyomu6wdcabRgmGTaAKawvhAno1V5FowGpTLVx3jxzE5uk3Q"); } @@ -1021,7 +1038,7 @@ mod test { let generated_wif: GeneratedKey<_, miniscript::Segwitv0> = bitcoin::PrivateKey::generate_with_entropy_default(TEST_ENTROPY).unwrap(); - assert_eq!(generated_wif.valid_networks, any_network()); + assert_eq!(generated_wif.valid_network_kinds, any_network_kind()); assert_eq!( generated_wif.to_string(), "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch" @@ -1030,7 +1047,7 @@ mod test { #[cfg(feature = "keys-bip39")] #[test] - fn test_keys_wif_network_bip39() { + fn test_keys_wif_network_kind_bip39() { let xkey: ExtendedKey = bip39::Mnemonic::parse_in( bip39::Language::English, "jelly crash boy whisper mouse ecology tuna soccer memory million news short", @@ -1038,8 +1055,8 @@ mod test { .unwrap() .into_extended_key() .unwrap(); - let xprv = xkey.into_xprv(Network::Testnet).unwrap(); + let xprv = xkey.into_xprv(NetworkKind::Test).unwrap(); - assert_eq!(xprv.network, Network::Testnet.into()); + assert_eq!(xprv.network, NetworkKind::Test); } } From 94f251869c02e44cf4f1735355329a988433d16d Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Sat, 31 May 2025 15:59:45 -0300 Subject: [PATCH 3/4] feat!(wallet): use `NetworkKind` --- wallet/src/descriptor/dsl.rs | 15 ++- wallet/src/wallet/changeset.rs | 12 +-- wallet/src/wallet/mod.rs | 173 +++++++++++++++++---------------- wallet/src/wallet/params.rs | 11 ++- wallet/src/wallet/signer.rs | 19 ++-- 5 files changed, 120 insertions(+), 110 deletions(-) diff --git a/wallet/src/descriptor/dsl.rs b/wallet/src/descriptor/dsl.rs index 873cda6a..dbf9cb49 100644 --- a/wallet/src/descriptor/dsl.rs +++ b/wallet/src/descriptor/dsl.rs @@ -826,17 +826,16 @@ macro_rules! fragment { #[cfg(test)] mod test { use alloc::string::ToString; - use bitcoin::secp256k1::Secp256k1; - use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; - use miniscript::{Descriptor, Legacy, Segwitv0}; - use core::str::FromStr; + use bitcoin::{bip32, secp256k1::Secp256k1, Network, NetworkKind, PrivateKey}; + use miniscript::{ + descriptor::{DescriptorPublicKey, KeyMap}, + Descriptor, Legacy, Segwitv0, + }; + use crate::descriptor::{DescriptorError, DescriptorMeta}; use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworkKinds}; - use bitcoin::bip32; - use bitcoin::NetworkKind; - use bitcoin::PrivateKey; // Test the `descriptor!()` macro. @@ -854,7 +853,7 @@ mod test { let child_desc = desc .at_derivation_index(i as u32) .expect("i is not hardened"); - let address = child_desc.address(bitcoin::Network::Regtest.into()); + let address = child_desc.address(Network::Regtest); if let Ok(address) = address { assert_eq!(address.to_string(), *expected.get(i).unwrap()); } else { diff --git a/wallet/src/wallet/changeset.rs b/wallet/src/wallet/changeset.rs index ebfdb9fb..d3d1ba93 100644 --- a/wallet/src/wallet/changeset.rs +++ b/wallet/src/wallet/changeset.rs @@ -34,16 +34,16 @@ type IndexedTxGraphChangeSet = /// /// ## Members and required fields /// -/// The change set has certain required fields without which a `Wallet` cannot function. -/// These include the [`descriptor`] and the [`network`] in use. These are required to be non-empty -/// *in the aggregate*, meaning the field must be present and non-null in the union of all +/// The change set has certain required fields without which a [`Wallet`] cannot function. +/// These include the [`descriptor`] and the [`bitcoin::Network`] in use. These are required to be +/// non-empty *in the aggregate*, meaning the field must be present and non-null in the union of all /// persisted changes, but may be empty in any one change set, where "empty" is defined by the /// [`Merge`](Merge::is_empty) implementation of that change set. This requirement also applies to /// the [`local_chain`] field in that the aggregate change set must include a genesis block. /// -/// For example, the descriptor and network are present in the first change set after wallet -/// creation, but are usually omitted in subsequent updates, as they are not permitted to change -/// at any point thereafter. +/// For example, the [`descriptor`] and [`bitcoin::Network`] are present in the first change set +/// after wallet creation, but are usually omitted in subsequent updates, as they are not permitted +/// to change at any point thereafter. /// /// Other fields of the change set are not required to be non-empty, that is they may be empty even /// in the aggregate. However in practice they should contain the data needed to recover a wallet diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index be3773d3..207d60a5 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -40,8 +40,8 @@ use bitcoin::{ psbt, secp256k1::Secp256k1, sighash::{EcdsaSighashType, TapSighashType}, - transaction, Address, Amount, Block, BlockHash, FeeRate, Network, OutPoint, Psbt, ScriptBuf, - Sequence, SignedAmount, Transaction, TxOut, Txid, Weight, Witness, + transaction, Address, Amount, Block, BlockHash, FeeRate, Network, NetworkKind, OutPoint, Psbt, + ScriptBuf, Sequence, SignedAmount, Transaction, TxOut, Txid, Weight, Witness, }; use miniscript::{ descriptor::KeyMap, @@ -408,12 +408,13 @@ impl Wallet { pub fn create_with_params(params: CreateParams) -> Result { let secp = SecpCtx::new(); let network = params.network; + let network_kind = NetworkKind::from(network); let genesis_hash = params .genesis_hash .unwrap_or(genesis_block(network).block_hash()); let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash); - let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network)?; + let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network_kind)?; check_wallet_descriptor(&descriptor)?; descriptor_keymap.extend(params.descriptor_keymap); @@ -425,7 +426,7 @@ impl Wallet { let (change_descriptor, change_signers) = match params.change_descriptor { Some(make_desc) => { - let (change_descriptor, mut internal_keymap) = make_desc(&secp, network)?; + let (change_descriptor, mut internal_keymap) = make_desc(&secp, network_kind)?; check_wallet_descriptor(&change_descriptor)?; internal_keymap.extend(params.change_descriptor_keymap); let change_signers = Arc::new(SignersContainer::build( @@ -532,6 +533,7 @@ impl Wallet { } let secp = Secp256k1::new(); let network = changeset.network.ok_or(LoadError::MissingNetwork)?; + let network_kind = NetworkKind::from(network); let chain = LocalChain::from_changeset(changeset.local_chain) .map_err(|_| LoadError::MissingGenesis)?; @@ -561,7 +563,7 @@ impl Wallet { if let Some(expected) = params.check_descriptor { if let Some(make_desc) = expected { let (exp_desc, keymap) = - make_desc(&secp, network).map_err(LoadError::Descriptor)?; + make_desc(&secp, network_kind).map_err(LoadError::Descriptor)?; if descriptor.descriptor_id() != exp_desc.descriptor_id() { return Err(LoadError::Mismatch(LoadMismatch::Descriptor { keychain: KeychainKind::External, @@ -586,12 +588,13 @@ impl Wallet { let mut internal_keymap = params.change_descriptor_keymap; match (changeset.change_descriptor, params.check_change_descriptor) { - // empty signer + // Empty signer. (None, None) => {} (None, Some(expect)) => { - // expected desc but none loaded + // Expected descriptor, but none is loaded. if let Some(make_desc) = expect { - let (exp_desc, _) = make_desc(&secp, network).map_err(LoadError::Descriptor)?; + let (exp_desc, _) = + make_desc(&secp, network_kind).map_err(LoadError::Descriptor)?; return Err(LoadError::Mismatch(LoadMismatch::Descriptor { keychain: KeychainKind::Internal, loaded: None, @@ -599,13 +602,13 @@ impl Wallet { })); } } - // nothing expected + // Nothing expected. (Some(desc), None) => { check_wallet_descriptor(&desc).map_err(LoadError::Descriptor)?; change_descriptor = Some(desc); } (Some(desc), Some(expect)) => match expect { - // expected none for existing + // Expected none for existing. None => { return Err(LoadError::Mismatch(LoadMismatch::Descriptor { keychain: KeychainKind::Internal, @@ -613,11 +616,11 @@ impl Wallet { expected: None, })) } - // parameters must match + // Parameters must match. Some(make_desc) => { check_wallet_descriptor(&desc).map_err(LoadError::Descriptor)?; let (exp_desc, keymap) = - make_desc(&secp, network).map_err(LoadError::Descriptor)?; + make_desc(&secp, network_kind).map_err(LoadError::Descriptor)?; if desc.descriptor_id() != exp_desc.descriptor_id() { return Err(LoadError::Mismatch(LoadMismatch::Descriptor { keychain: KeychainKind::Internal, @@ -667,7 +670,7 @@ impl Wallet { })) } - /// Get the Bitcoin network the wallet is using. + /// Get the [`Network`] the wallet is using. pub fn network(&self) -> Network { self.network } @@ -1394,7 +1397,7 @@ impl Wallet { }; let lock_time = match params.locktime { - // When no nLockTime is specified, we try to prevent fee sniping, if possible + // When no `nLockTime` is specified, we try to prevent fee sniping, if possible. None => { // Fee sniping can be partially prevented by setting the timelock // to current_height. If we don't know the current_height, @@ -1402,30 +1405,30 @@ impl Wallet { let fee_sniping_height = current_height; // We choose the biggest between the required nlocktime and the fee sniping - // height + // height. match requirements.timelock { - // No requirement, just use the fee_sniping_height + // No requirement, just use the fee_sniping_height. None => fee_sniping_height, // There's a block-based requirement, but the value is lower than the - // fee_sniping_height + // fee_sniping_height. Some(value @ absolute::LockTime::Blocks(_)) if value < fee_sniping_height => { fee_sniping_height } // There's a time-based requirement or a block-based requirement greater - // than the fee_sniping_height use that value + // than the fee_sniping_height use that value. Some(value) => value, } } - // Specific nLockTime required and we have no constraints, so just set to that value + // Specific nLockTime required and we have no constraints, so just set to that value. Some(x) if requirements.timelock.is_none() => x, - // Specific nLockTime required and it's compatible with the constraints + // Specific nLockTime required and it's compatible with the constraints. Some(x) if requirements.timelock.unwrap().is_same_unit(x) && x >= requirements.timelock.unwrap() => { x } - // Invalid nLockTime required + // Invalid nLockTime required. Some(x) => { return Err(CreateTxError::LockTime { requested: x, @@ -1434,19 +1437,19 @@ impl Wallet { } }; - // nSequence value for inputs - // When not explicitly specified, defaults to 0xFFFFFFFD, - // meaning RBF signaling is enabled + // nSequence value for inputs. + // When not explicitly specified, it defaults to 0xFFFFFFFD, meaning RBF signaling is + // enabled. let n_sequence = match (params.sequence, requirements.csv) { - // Enable RBF by default + // Enable RBF by default. (None, None) => Sequence::ENABLE_RBF_NO_LOCKTIME, - // None requested, use required + // None requested, use required. (None, Some(csv)) => csv, - // Requested sequence is incompatible with requirements + // Requested sequence is incompatible with requirements. (Some(sequence), Some(csv)) if !check_nsequence_rbf(sequence, csv) => { return Err(CreateTxError::RbfSequenceCsv { sequence, csv }) } - // Use requested nSequence value + // Use requested nSequence value. (Some(sequence), _) => sequence, }; @@ -1514,7 +1517,7 @@ impl Wallet { let mut required: Vec = params.utxos.values().cloned().collect(); let optional = self.filter_utxos(¶ms, current_height.to_consensus_u32()); - // if drain_wallet is true, all UTxOs are required + // If `drain_wallet` is true, all UTxOs are required. if params.drain_wallet { required.extend(optional); (required, vec![]) @@ -1523,7 +1526,7 @@ impl Wallet { } }; - // get drain script + // Get drain script. let mut drain_index = Option::<(KeychainKind, u32)>::None; let drain_script = match params.drain_to { Some(ref drain_recipient) => drain_recipient.clone(), @@ -1577,8 +1580,8 @@ impl Wallet { // Uh oh, our transaction has no outputs. // We allow this when: // - We have a drain_to address and the utxos we must spend (this happens, - // for example, when we RBF) - // - We have a drain_to address and drain_wallet set + // for example, when we RBF). + // - We have a drain_to address and drain_wallet set. // Otherwise, we don't know who we should send the funds to, and how much // we should send! if params.drain_to.is_some() && (params.drain_wallet || !params.utxos.is_empty()) { @@ -1600,9 +1603,9 @@ impl Wallet { } } - // if there's change, create and add a change output + // if there's change, create and add a change output. if let Excess::Change { amount, .. } = excess { - // create drain output + // Create drain output. let drain_output = TxOut { value: *amount, script_pubkey: drain_script, @@ -1610,16 +1613,16 @@ impl Wallet { // TODO: We should pay attention when adding a new output: this might increase // the length of the "number of vouts" parameter by 2 bytes, potentially making - // our feerate too low + // our feerate too low. tx.output.push(drain_output); } - // sort input/outputs according to the chosen algorithm + // Sort inputs/outputs according to the chosen algorithm. params.ordering.sort_tx_with_aux_rand(&mut tx, rng); let psbt = self.complete_transaction(tx, coin_selection.selected, params)?; - // recording changes to the change keychain + // Recording changes to the change keychain. if let (Excess::Change { .. }, Some((keychain, index))) = (excess, drain_index) { if let Some((_, index_changeset)) = self.indexed_graph.index.reveal_to_target(keychain, index) @@ -1716,16 +1719,16 @@ impl Wallet { .calculate_fee_rate(&tx) .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?; - // remove the inputs from the tx and process them + // Remove the inputs from the tx and process them. let utxos = tx .input .drain(..) .map(|txin| -> Result<_, BuildFeeBumpError> { graph - // Get previous transaction + // Get previous transaction. .get_tx(txin.previous_output.txid) .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output)) - // Get chain position + // Get chain position. .and_then(|prev_tx| { let chain_position = chain_positions .get(&txin.previous_output.txid) @@ -1857,12 +1860,12 @@ impl Wallet { /// # Ok::<(),anyhow::Error>(()) pub fn sign(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result { // This adds all the PSBT metadata for the inputs, which will help us later figure out how - // to derive our keys + // to derive our keys. self.update_psbt_with_descriptor(psbt) .map_err(SignerError::MiniscriptPsbt)?; // If we aren't allowed to use `witness_utxo`, ensure that every input (except p2tr and - // finalized ones) has the `non_witness_utxo` + // finalized ones) has the `non_witness_utxo`. if !sign_options.trust_witness_utxo && psbt .inputs @@ -1875,7 +1878,7 @@ impl Wallet { } // If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input - // is using `SIGHASH_ALL` or `SIGHASH_DEFAULT` for taproot + // is using `SIGHASH_ALL` or `SIGHASH_DEFAULT` for Taproot. if !sign_options.allow_all_sighashes && !psbt.inputs.iter().all(|i| { i.sighash_type.is_none() @@ -1896,7 +1899,7 @@ impl Wallet { signer.sign_transaction(psbt, &sign_options, &self.secp)?; } - // attempt to finalize + // Attempt to finalize. if sign_options.try_finalize { self.finalize_psbt(psbt, sign_options) } else { @@ -1904,7 +1907,7 @@ impl Wallet { } } - /// Return the spending policies for the wallet's descriptor + /// Return the spending policies for the wallet's descriptor. pub fn policies(&self, keychain: KeychainKind) -> Result, DescriptorError> { let signers = match keychain { KeychainKind::External => &self.signers, @@ -1987,11 +1990,11 @@ impl Wallet { .unwrap_or_else(|| self.chain.tip().height()); // - 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 is // in `src/descriptor/mod.rs`, but it will basically look at `bip32_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 + // `redeem_script` and `witness_script` to determine the right derivation. + // - If that also fails, it will try it on the internal descriptor, if present. let desc = psbt .get_utxo_for(n) .and_then(|txout| self.get_descriptor_for_txout(&txout)) @@ -2013,8 +2016,8 @@ impl Wallet { ), ) { Ok(_) => { - // Set the UTXO fields, final script_sig and witness - // and clear everything else. + // Set the UTXO fields, final script_sig and witness and clear + // everything else. let psbt_input = psbt .inputs .get_mut(n) @@ -2036,7 +2039,7 @@ impl Wallet { } } - // Clear derivation paths from outputs + // Clear derivation paths from outputs. if finished { for output in &mut psbt.outputs { output.bip32_derivation.clear(); @@ -2047,7 +2050,7 @@ impl Wallet { Ok(finished) } - /// Return the secp256k1 context used for all signing operations + /// Return the secp256k1 context used for all signing operations. pub fn secp_ctx(&self) -> &SecpCtx { &self.secp } @@ -2059,7 +2062,7 @@ impl Wallet { } /// The index of the next address that you would get if you were to ask the wallet for a new - /// address + /// address. pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 { self.indexed_graph .index @@ -2097,37 +2100,39 @@ impl Wallet { fn filter_utxos(&self, params: &TxParams, current_height: u32) -> Vec { if params.manually_selected_only { vec![] - // only process optional UTxOs if manually_selected_only is false + // Only process optional UTxOs if manually_selected_only is false. } else { self.indexed_graph .graph() - // get all unspent UTxOs from wallet + // Get all unspent UTxOs from wallet. // NOTE: the UTxOs returned by the following method already belong to wallet as the - // call chain uses get_tx_node infallibly + // call chain uses get_tx_node infallibly. .filter_chain_unspents( &self.chain, self.chain.tip().block_id(), CanonicalizationParams::default(), self.indexed_graph.index.outpoints().iter().cloned(), ) - // only create LocalOutput if UTxO is mature + // Only create LocalOutput if UTxO is mature. .filter_map(move |((k, i), full_txo)| { full_txo .is_mature(current_height) .then(|| new_local_utxo(k, i, full_txo)) }) - // only process UTxOs not selected manually, they will be considered later in the - // chain NOTE: this avoid UTxOs in both required and optional list + // Only process UTxOs not selected manually, they will be considered later in the + // chain. + // + // NOTE: this avoid UTxOs in both required and optional list. .filter(|may_spend| !params.utxos.contains_key(&may_spend.outpoint)) - // only add to optional UTxOs those which satisfy the change policy if we reuse - // change + // Only add to optional UTxOs those which satisfy the change policy if we reuse + // change. .filter(|local_output| { self.keychains().count() == 1 || params.change_policy.is_satisfied_by(local_output) }) - // only add to optional UTxOs those marked as spendable + // Only add to optional UTxOs those marked as spendable. .filter(|local_output| !params.unspendable.contains(&local_output.outpoint)) - // if bumping fees only add to optional UTxOs those confirmed + // If bumping fees only add to optional UTxOs those confirmed. .filter(|local_output| { params.bumping_fee.is_none() || local_output.chain_position.is_confirmed() }) @@ -2174,7 +2179,7 @@ impl Wallet { .map(|utxo| (utxo.outpoint(), utxo)) .collect::>(); - // add metadata for the inputs + // Add metadata for the inputs. for (psbt_input, input) in psbt.inputs.iter_mut().zip(psbt.unsigned_tx.input.iter()) { let utxo = match lookup_output.remove(&input.previous_output) { Some(utxo) => utxo, @@ -2221,7 +2226,7 @@ impl Wallet { Ok(psbt) } - /// get the corresponding PSBT Input for a LocalUtxo + /// Get the corresponding PSBT Input for a [`LocalOutput`]. pub fn get_psbt_input( &self, utxo: LocalOutput, @@ -2229,7 +2234,7 @@ impl Wallet { only_witness_utxo: bool, ) -> Result { // Try to find the prev_script in our db to figure out if this is internal or external, - // and the derivation index + // and the derivation index. let &(keychain, child) = self .indexed_graph .index @@ -2252,7 +2257,8 @@ impl Wallet { let prev_output = utxo.outpoint; if let Some(prev_tx) = self.indexed_graph.graph().get_tx(prev_output.txid) { - // We want to check that the prevout actually exists in the tx before continuing. + // We want to check that the prevout actually exists in the transaction before + // continuing. let prevout = prev_tx.output.get(prev_output.vout as usize).ok_or( MiniscriptPsbtError::UtxoUpdate(miniscript::psbt::UtxoUpdateError::UtxoCheck), )?; @@ -2268,7 +2274,7 @@ impl Wallet { fn update_psbt_with_descriptor(&self, psbt: &mut Psbt) -> Result<(), MiniscriptPsbtError> { // We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all - // the input utxos and outputs + // the input utxos and outputs. let utxos = (0..psbt.inputs.len()) .filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo))) .chain( @@ -2280,7 +2286,7 @@ impl Wallet { ) .collect::>(); - // Try to figure out the keychain and derivation for every input and output + // Try to figure out the keychain and derivation for every input and output. for (is_input, index, out) in utxos.into_iter() { if let Some(&(keychain, child)) = self.indexed_graph.index.index_of_spk(out.script_pubkey) @@ -2303,9 +2309,9 @@ impl Wallet { Ok(()) } - /// Return the checksum of the public descriptor associated to `keychain` + /// Return the checksum of the public descriptor associated to the `keychain`. /// - /// Internally calls [`Self::public_descriptor`] to fetch the right descriptor + /// Internally calls [`Self::public_descriptor`] to fetch the right descriptor. pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String { self.public_descriptor(keychain) .to_string() @@ -2580,27 +2586,27 @@ impl AsRef> for Wallet { } } -/// Deterministically generate a unique name given the descriptors defining the wallet +/// Deterministically generate a unique name given the descriptors defining the [`Wallet`]. /// -/// Compatible with [`wallet_name_from_descriptor`] +/// Compatible with [`wallet_name_from_descriptor`]. pub fn wallet_name_from_descriptor( descriptor: T, change_descriptor: Option, - network: Network, + network_kind: NetworkKind, secp: &SecpCtx, ) -> Result where T: IntoWalletDescriptor, { - //TODO check descriptors contains only public keys + // TODO: check descriptors contains only public keys let descriptor = descriptor - .into_wallet_descriptor(secp, network)? + .into_wallet_descriptor(secp, network_kind)? .0 .to_string(); let mut wallet_name = descriptor.split_once('#').unwrap().1.to_string(); if let Some(change_descriptor) = change_descriptor { let change_descriptor = change_descriptor - .into_wallet_descriptor(secp, network)? + .into_wallet_descriptor(secp, network_kind)? .0 .to_string(); wallet_name.push_str(change_descriptor.split_once('#').unwrap().1); @@ -2668,7 +2674,7 @@ macro_rules! floating_rate { #[macro_export] #[doc(hidden)] -/// Macro for getting a wallet for use in a doctest +/// Macro for getting a [`Wallet`] for use in a doctest. macro_rules! doctest_wallet { () => {{ use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash}; @@ -2709,6 +2715,7 @@ macro_rules! doctest_wallet { #[cfg(test)] mod test { use super::*; + use crate::test_utils::get_test_tr_single_sig_xprv_and_change_desc; use crate::test_utils::insert_tx; @@ -2716,7 +2723,7 @@ mod test { fn not_duplicated_utxos_across_optional_and_required() { let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - // create new wallet + // Create new wallet. let mut wallet = Wallet::create(external_desc, internal_desc) .network(Network::Testnet) .create_wallet_no_persist() @@ -2757,10 +2764,10 @@ mod test { utxo: Utxo::Local(output), }, ); - // enforce selection of first output in transaction + // Enforce selection of first output in transaction. let received = wallet.filter_utxos(¶ms, wallet.latest_checkpoint().block_id().height); - // notice expected doesn't include the first output from two_output_tx as it should be - // filtered out + // Notice expected doesn't include the first output from two_output_tx as it should be + // filtered out. let expected = vec![wallet .get_utxo(OutPoint { txid, vout: 1 }) .map(|utxo| WeightedUtxo { diff --git a/wallet/src/wallet/params.rs b/wallet/src/wallet/params.rs index aa608554..5f53e879 100644 --- a/wallet/src/wallet/params.rs +++ b/wallet/src/wallet/params.rs @@ -1,6 +1,7 @@ use alloc::boxed::Box; + use bdk_chain::keychain_txout::DEFAULT_LOOKAHEAD; -use bitcoin::{BlockHash, Network}; +use bitcoin::{BlockHash, Network, NetworkKind}; use miniscript::descriptor::KeyMap; use crate::{ @@ -17,7 +18,7 @@ use super::{ChangeSet, LoadError, PersistedWallet}; /// The better option would be to do `Box`, but we cannot due to Rust's /// [object safety rules](https://doc.rust-lang.org/reference/items/traits.html#object-safety). type DescriptorToExtract = Box< - dyn FnOnce(&SecpCtx, Network) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> + dyn FnOnce(&SecpCtx, NetworkKind) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> + Send + 'static, >; @@ -26,7 +27,7 @@ fn make_descriptor_to_extract(descriptor: D) -> DescriptorToExtract where D: IntoWalletDescriptor + Send + 'static, { - Box::new(|secp, network| descriptor.into_wallet_descriptor(secp, network)) + Box::new(|secp, network_kind| descriptor.into_wallet_descriptor(secp, network_kind)) } /// Parameters for [`Wallet::create`] or [`PersistedWallet::create`]. @@ -98,7 +99,7 @@ impl CreateParams { self } - /// Set `network`. + /// Set [`Self::network`]. pub fn network(mut self, network: Network) -> Self { self.network = network; self @@ -250,7 +251,7 @@ impl LoadParams { /// Use a persistent cache of indexed script pubkeys (SPKs). /// - /// **Note:** This should only be used if you have previously persisted a cache of script + /// NOTE: This should only be used if you have previously persisted a cache of script /// pubkeys using [`CreateParams::use_spk_cache`]. pub fn use_spk_cache(mut self, use_spk_cache: bool) -> Self { self.use_spk_cache = use_spk_cache; diff --git a/wallet/src/wallet/signer.rs b/wallet/src/wallet/signer.rs index 2af6b966..79dbd971 100644 --- a/wallet/src/wallet/signer.rs +++ b/wallet/src/wallet/signer.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -902,13 +902,16 @@ impl Eq for SignersContainerKey {} #[cfg(test)] mod signers_container_tests { use super::*; - use crate::descriptor; - use crate::descriptor::IntoWalletDescriptor; + use crate::keys::{DescriptorKey, IntoDescriptorKey}; + use crate::{descriptor, descriptor::IntoWalletDescriptor}; + use assert_matches::assert_matches; - use bitcoin::bip32; - use bitcoin::secp256k1::{All, Secp256k1}; - use bitcoin::Network; + use bitcoin::{ + bip32, + secp256k1::{All, Secp256k1}, + NetworkKind, + }; use core::str::FromStr; use miniscript::ScriptContext; @@ -928,7 +931,7 @@ mod signers_container_tests { let (prvkey2, _, _) = setup_keys(TPRV1_STR); let desc = descriptor!(sh(multi(2, prvkey1, prvkey2))).unwrap(); let (wallet_desc, keymap) = desc - .into_wallet_descriptor(&secp, Network::Testnet) + .into_wallet_descriptor(&secp, NetworkKind::Test) .unwrap(); let signers = SignersContainer::build(keymap, &wallet_desc, &secp); @@ -950,7 +953,7 @@ mod signers_container_tests { signers.add_external(SignerId::Dummy(1), SignerOrdering(1), signer1.clone()); signers.add_external(SignerId::Dummy(3), SignerOrdering(3), signer3.clone()); - // Check that signers are sorted from lowest to highest ordering + // Check that signers are sorted from lowest to highest ordering. let signers = signers.signers(); assert!(is_equal(signers[0], &signer1)); From ecefb4c639f28913b468cb49629b25e6cd92ce69 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Sat, 31 May 2025 16:04:33 -0300 Subject: [PATCH 4/4] feat!(examples,test): use `NetworkKind` --- wallet/examples/mnemonic_to_descriptors.rs | 18 +++++++++--------- wallet/examples/policy.rs | 14 +++++++------- wallet/tests/wallet.rs | 17 +++++++++-------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/wallet/examples/mnemonic_to_descriptors.rs b/wallet/examples/mnemonic_to_descriptors.rs index 19154c2d..f0ebb110 100644 --- a/wallet/examples/mnemonic_to_descriptors.rs +++ b/wallet/examples/mnemonic_to_descriptors.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -9,7 +9,7 @@ use anyhow::anyhow; use bdk_wallet::bitcoin::bip32::DerivationPath; use bdk_wallet::bitcoin::secp256k1::Secp256k1; -use bdk_wallet::bitcoin::Network; +use bdk_wallet::bitcoin::NetworkKind; use bdk_wallet::descriptor; use bdk_wallet::descriptor::IntoWalletDescriptor; use bdk_wallet::keys::bip39::{Language, Mnemonic, WordCount}; @@ -18,14 +18,14 @@ use bdk_wallet::miniscript::Tap; use std::str::FromStr; /// This example demonstrates how to generate a mnemonic phrase -/// using BDK and use that to generate a descriptor string. +/// using BDK and use that mnemonic phrase to generate a descriptor string. #[allow(clippy::print_stdout)] fn main() -> Result<(), anyhow::Error> { let secp = Secp256k1::new(); - // In this example we are generating a 12 words mnemonic phrase + // In this example we are generating a 12 word mnemonic phrase // but it is also possible generate 15, 18, 21 and 24 words - // using their respective `WordCount` variant. + // using the respective `WordCount` variant. let mnemonic: GeneratedKey<_, Tap> = Mnemonic::generate((WordCount::Words12, Language::English)) .map_err(|_| anyhow!("Mnemonic generation error"))?; @@ -33,17 +33,17 @@ fn main() -> Result<(), anyhow::Error> { println!("Mnemonic phrase: {}", *mnemonic); let mnemonic_with_passphrase = (mnemonic, None); - // define external and internal derivation key path + // Define the external and internal derivation key path. let external_path = DerivationPath::from_str("m/86h/1h/0h/0").unwrap(); let internal_path = DerivationPath::from_str("m/86h/1h/0h/1").unwrap(); - // generate external and internal descriptor from mnemonic + // Generate external and internal descriptors from the mnemonic phrase. let (external_descriptor, ext_keymap) = descriptor!(tr((mnemonic_with_passphrase.clone(), external_path)))? - .into_wallet_descriptor(&secp, Network::Testnet)?; + .into_wallet_descriptor(&secp, NetworkKind::Test)?; let (internal_descriptor, int_keymap) = descriptor!(tr((mnemonic_with_passphrase, internal_path)))? - .into_wallet_descriptor(&secp, Network::Testnet)?; + .into_wallet_descriptor(&secp, NetworkKind::Test)?; println!("tpub external descriptor: {}", external_descriptor); println!("tpub internal descriptor: {}", internal_descriptor); diff --git a/wallet/examples/policy.rs b/wallet/examples/policy.rs index 4ceb47b7..369aff33 100644 --- a/wallet/examples/policy.rs +++ b/wallet/examples/policy.rs @@ -1,7 +1,7 @@ // Bitcoin Dev Kit // Written in 2020 by Alekos Filini // -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license @@ -10,11 +10,12 @@ // licenses. extern crate bdk_wallet; + use std::error::Error; -use bdk_wallet::bitcoin::Network; use bdk_wallet::descriptor::{policy::BuildSatisfaction, ExtractPolicy, IntoWalletDescriptor}; use bdk_wallet::signer::SignersContainer; +use bitcoin::NetworkKind; /// This example describes the use of the BDK's [`bdk_wallet::descriptor::policy`] module. /// @@ -29,8 +30,7 @@ use bdk_wallet::signer::SignersContainer; fn main() -> Result<(), Box> { let secp = bitcoin::secp256k1::Secp256k1::new(); - // The descriptor used in the example - // The form is "wsh(multi(2, , ))" + // The descriptor used in the example. The form is "wsh(multi(2, , ))". let desc = "wsh(multi(2,tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/*))"; // Use the descriptor string to derive the full descriptor and a keymap. @@ -38,9 +38,9 @@ fn main() -> Result<(), Box> { // While the `keymap` can be used to create a `SignerContainer`. // // The `SignerContainer` can sign for `PSBT`s. - // a `bdk_wallet::Wallet` internally uses these to handle transaction signing. - // But they can be used as independent tools also. - let (wallet_desc, keymap) = desc.into_wallet_descriptor(&secp, Network::Testnet)?; + // A `bdk_wallet::Wallet` internally uses these to handle transaction signing, but they can be + // used as independent tools as well. + let (wallet_desc, keymap) = desc.into_wallet_descriptor(&secp, NetworkKind::Test)?; println!("Example Descriptor for policy analysis : {}", wallet_desc); diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index 8ed28159..b0a61f7f 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -27,8 +27,8 @@ use bitcoin::script::PushBytesBuf; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::taproot::TapNodeHash; use bitcoin::{ - absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, ScriptBuf, - Sequence, Transaction, TxIn, TxOut, Txid, Weight, + absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, NetworkKind, OutPoint, + ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, }; use bitcoin::{psbt, SignedAmount}; use miniscript::{descriptor::KeyMap, Descriptor, DescriptorPublicKey}; @@ -69,7 +69,7 @@ fn wallet_is_persisted() -> anyhow::Result<()> { let file_path = temp_dir.path().join(filename); let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - // create new wallet + // Create new wallet. let wallet_spk_index = { let mut db = create_db(&file_path)?; let mut wallet = Wallet::create(external_desc, internal_desc) @@ -78,12 +78,12 @@ fn wallet_is_persisted() -> anyhow::Result<()> { .create_wallet(&mut db)?; wallet.reveal_next_address(KeychainKind::External); - // persist new wallet changes + // Persist new wallet changes. assert!(wallet.persist(&mut db)?, "must write"); wallet.spk_index().clone() }; - // recover wallet + // Recover wallet. { let mut db = open_db(&file_path).context("failed to recover db")?; let wallet = Wallet::load() @@ -106,12 +106,12 @@ fn wallet_is_persisted() -> anyhow::Result<()> { assert_eq!( *wallet.public_descriptor(KeychainKind::External), external_desc - .into_wallet_descriptor(&secp, wallet.network()) + .into_wallet_descriptor(&secp, NetworkKind::from(wallet.network())) .unwrap() .0 ); } - // Test SPK cache + // Test SPK cache. { let mut db = open_db(&file_path).context("failed to recover db")?; let mut wallet = Wallet::load() @@ -149,7 +149,8 @@ fn wallet_is_persisted() -> anyhow::Result<()> { assert_eq!(spk_cache.len(), 1); assert_eq!(spk_cache.keys().next(), Some(&25)); } - // SPK cache requires load params + + // SPK cache requires load params. { let mut db = open_db(&file_path).context("failed to recover db")?; let mut wallet = Wallet::load()