[keys] Add a way to restrict the networks in which keys are valid
Thanks to the `ToWalletDescriptor` trait we can also very easily validate the checksum for descriptors that are loaded from strings, if they contain one. Fixes #20.
This commit is contained in:
parent
bc8acaf088
commit
c51ba4a99f
@ -29,7 +29,7 @@
|
|||||||
macro_rules! impl_top_level_sh {
|
macro_rules! impl_top_level_sh {
|
||||||
( $descriptor_variant:ident, $( $minisc:tt )* ) => {
|
( $descriptor_variant:ident, $( $minisc:tt )* ) => {
|
||||||
$crate::fragment!($( $minisc )*)
|
$crate::fragment!($( $minisc )*)
|
||||||
.map(|(minisc, keymap)|($crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::$descriptor_variant(minisc), keymap))
|
.map(|(minisc, keymap, networks)|($crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::$descriptor_variant(minisc), keymap, networks))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,13 +40,14 @@ macro_rules! impl_top_level_pk {
|
|||||||
use $crate::keys::{DescriptorKey, ToDescriptorKey};
|
use $crate::keys::{DescriptorKey, ToDescriptorKey};
|
||||||
|
|
||||||
$key.to_descriptor_key()
|
$key.to_descriptor_key()
|
||||||
.and_then(|key: DescriptorKey<$ctx>| key.into_key_and_secret())
|
.and_then(|key: DescriptorKey<$ctx>| key.extract())
|
||||||
.map(|(pk, key_map)| {
|
.map(|(pk, key_map, valid_networks)| {
|
||||||
(
|
(
|
||||||
$crate::miniscript::Descriptor::<
|
$crate::miniscript::Descriptor::<
|
||||||
$crate::miniscript::descriptor::DescriptorPublicKey,
|
$crate::miniscript::descriptor::DescriptorPublicKey,
|
||||||
>::$descriptor_variant(pk),
|
>::$descriptor_variant(pk),
|
||||||
key_map,
|
key_map,
|
||||||
|
valid_networks,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}};
|
}};
|
||||||
@ -57,7 +58,8 @@ macro_rules! impl_top_level_pk {
|
|||||||
macro_rules! impl_modifier {
|
macro_rules! impl_modifier {
|
||||||
( $terminal_variant:ident, $( $inner:tt )* ) => {
|
( $terminal_variant:ident, $( $inner:tt )* ) => {
|
||||||
$crate::fragment!($( $inner )*)
|
$crate::fragment!($( $inner )*)
|
||||||
.and_then(|(minisc, keymap)| Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(std::sync::Arc::new(minisc)))?, keymap)))
|
.map_err(|e| -> $crate::Error { e.into() })
|
||||||
|
.and_then(|(minisc, keymap, networks)| Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(std::sync::Arc::new(minisc)))?, keymap, networks)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +71,13 @@ macro_rules! impl_leaf_opcode {
|
|||||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
|
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
|
||||||
)
|
)
|
||||||
.map_err($crate::Error::Miniscript)
|
.map_err($crate::Error::Miniscript)
|
||||||
.map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
|
.map(|minisc| {
|
||||||
|
(
|
||||||
|
minisc,
|
||||||
|
$crate::miniscript::descriptor::KeyMap::default(),
|
||||||
|
$crate::keys::any_network(),
|
||||||
|
)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +89,13 @@ macro_rules! impl_leaf_opcode_value {
|
|||||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
|
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
|
||||||
)
|
)
|
||||||
.map_err($crate::Error::Miniscript)
|
.map_err($crate::Error::Miniscript)
|
||||||
.map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
|
.map(|minisc| {
|
||||||
|
(
|
||||||
|
minisc,
|
||||||
|
$crate::miniscript::descriptor::KeyMap::default(),
|
||||||
|
$crate::keys::any_network(),
|
||||||
|
)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +107,13 @@ macro_rules! impl_leaf_opcode_value_two {
|
|||||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
|
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
|
||||||
)
|
)
|
||||||
.map_err($crate::Error::Miniscript)
|
.map_err($crate::Error::Miniscript)
|
||||||
.map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
|
.map(|minisc| {
|
||||||
|
(
|
||||||
|
minisc,
|
||||||
|
$crate::miniscript::descriptor::KeyMap::default(),
|
||||||
|
$crate::keys::any_network(),
|
||||||
|
)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,14 +123,14 @@ macro_rules! impl_node_opcode_two {
|
|||||||
( $terminal_variant:ident, ( $( $a:tt )* ), ( $( $b:tt )* ) ) => {
|
( $terminal_variant:ident, ( $( $a:tt )* ), ( $( $b:tt )* ) ) => {
|
||||||
$crate::fragment!($( $a )*)
|
$crate::fragment!($( $a )*)
|
||||||
.and_then(|a| Ok((a, $crate::fragment!($( $b )*)?)))
|
.and_then(|a| Ok((a, $crate::fragment!($( $b )*)?)))
|
||||||
.and_then(|((a_minisc, mut a_keymap), (b_minisc, b_keymap))| {
|
.and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks))| {
|
||||||
// join key_maps
|
// join key_maps
|
||||||
a_keymap.extend(b_keymap.into_iter());
|
a_keymap.extend(b_keymap.into_iter());
|
||||||
|
|
||||||
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||||
std::sync::Arc::new(a_minisc),
|
std::sync::Arc::new(a_minisc),
|
||||||
std::sync::Arc::new(b_minisc),
|
std::sync::Arc::new(b_minisc),
|
||||||
))?, a_keymap))
|
))?, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -121,23 +141,26 @@ macro_rules! impl_node_opcode_three {
|
|||||||
( $terminal_variant:ident, ( $( $a:tt )* ), ( $( $b:tt )* ), ( $( $c:tt )* ) ) => {
|
( $terminal_variant:ident, ( $( $a:tt )* ), ( $( $b:tt )* ), ( $( $c:tt )* ) ) => {
|
||||||
$crate::fragment!($( $a )*)
|
$crate::fragment!($( $a )*)
|
||||||
.and_then(|a| Ok((a, $crate::fragment!($( $b )*)?, $crate::fragment!($( $c )*)?)))
|
.and_then(|a| Ok((a, $crate::fragment!($( $b )*)?, $crate::fragment!($( $c )*)?)))
|
||||||
.and_then(|((a_minisc, mut a_keymap), (b_minisc, b_keymap), (c_minisc, c_keymap))| {
|
.and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks), (c_minisc, c_keymap, c_networks))| {
|
||||||
// join key_maps
|
// join key_maps
|
||||||
a_keymap.extend(b_keymap.into_iter());
|
a_keymap.extend(b_keymap.into_iter());
|
||||||
a_keymap.extend(c_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);
|
||||||
|
|
||||||
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||||
std::sync::Arc::new(a_minisc),
|
std::sync::Arc::new(a_minisc),
|
||||||
std::sync::Arc::new(b_minisc),
|
std::sync::Arc::new(b_minisc),
|
||||||
std::sync::Arc::new(c_minisc),
|
std::sync::Arc::new(c_minisc),
|
||||||
))?, a_keymap))
|
))?, a_keymap, networks))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro to write full descriptors with code
|
/// Macro to write full descriptors with code
|
||||||
///
|
///
|
||||||
/// This macro expands to an object of type `Result<(Descriptor<DescriptorPublicKey>, KeyMap), Error>`.
|
/// This macro expands to an object of type `Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), Error>`.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -147,7 +170,7 @@ macro_rules! impl_node_opcode_three {
|
|||||||
/// # use std::str::FromStr;
|
/// # use std::str::FromStr;
|
||||||
/// let my_key = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
|
/// let my_key = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
|
||||||
/// let my_timelock = 50;
|
/// let my_timelock = 50;
|
||||||
/// let (my_descriptor, my_keys_map) = bdk::descriptor!(sh ( wsh ( and_v (+v pk my_key), ( older my_timelock ))))?;
|
/// let (my_descriptor, my_keys_map, networks) = bdk::descriptor!(sh ( wsh ( and_v (+v pk my_key), ( older my_timelock ))))?;
|
||||||
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@ -165,7 +188,7 @@ macro_rules! impl_node_opcode_three {
|
|||||||
/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
||||||
/// let my_timelock = 50;
|
/// let my_timelock = 50;
|
||||||
///
|
///
|
||||||
/// let (descriptor_a, key_map_a) = bdk::descriptor! {
|
/// let (descriptor_a, key_map_a, networks) = bdk::descriptor! {
|
||||||
/// wsh (
|
/// wsh (
|
||||||
/// thresh 2, (pk my_key_1), (+s pk my_key_2), (+s+d+v older my_timelock)
|
/// thresh 2, (pk my_key_1), (+s pk my_key_2), (+s+d+v older my_timelock)
|
||||||
/// )
|
/// )
|
||||||
@ -176,7 +199,7 @@ macro_rules! impl_node_opcode_three {
|
|||||||
/// bdk::fragment!(+s pk my_key_2)?,
|
/// bdk::fragment!(+s pk my_key_2)?,
|
||||||
/// bdk::fragment!(+s+d+v older my_timelock)?,
|
/// bdk::fragment!(+s+d+v older my_timelock)?,
|
||||||
/// ];
|
/// ];
|
||||||
/// let (descriptor_b, mut key_map_b) = bdk::descriptor!( wsh ( thresh_vec 2, b_items ) )?;
|
/// let (descriptor_b, mut key_map_b, networks) = bdk::descriptor!( wsh ( thresh_vec 2, b_items ) )?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(descriptor_a, descriptor_b);
|
/// assert_eq!(descriptor_a, descriptor_b);
|
||||||
/// assert_eq!(key_map_a.len(), key_map_b.len());
|
/// assert_eq!(key_map_a.len(), key_map_b.len());
|
||||||
@ -192,7 +215,7 @@ macro_rules! impl_node_opcode_three {
|
|||||||
/// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
|
/// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
|
||||||
/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
||||||
///
|
///
|
||||||
/// let (descriptor, key_map) = bdk::descriptor! {
|
/// let (descriptor, key_map, networks) = bdk::descriptor! {
|
||||||
/// wsh (
|
/// wsh (
|
||||||
/// multi 2, my_key_1, my_key_2
|
/// multi 2, my_key_1, my_key_2
|
||||||
/// )
|
/// )
|
||||||
@ -205,10 +228,9 @@ macro_rules! impl_node_opcode_three {
|
|||||||
/// Native-Segwit single-sig, equivalent to: `wpkh(...)`
|
/// Native-Segwit single-sig, equivalent to: `wpkh(...)`
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use std::str::FromStr;
|
|
||||||
/// let my_key = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
/// let my_key = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
||||||
///
|
///
|
||||||
/// let (descriptor, key_map) = bdk::descriptor!(wpkh ( my_key ) )?;
|
/// let (descriptor, key_map, networks) = bdk::descriptor!(wpkh ( my_key ) )?;
|
||||||
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -247,7 +269,7 @@ macro_rules! descriptor {
|
|||||||
|
|
||||||
/// Macro to write descriptor fragments with code
|
/// Macro to write descriptor fragments with code
|
||||||
///
|
///
|
||||||
/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap), Error>`. It allows writing
|
/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap, ValidNetworks), Error>`. It allows writing
|
||||||
/// fragments of larger descriptors that can be pieced together using `fragment!(thresh_vec ...)`.
|
/// fragments of larger descriptors that can be pieced together using `fragment!(thresh_vec ...)`.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! fragment {
|
macro_rules! fragment {
|
||||||
@ -291,8 +313,7 @@ macro_rules! fragment {
|
|||||||
$crate::impl_leaf_opcode!(False)
|
$crate::impl_leaf_opcode!(False)
|
||||||
});
|
});
|
||||||
( pk_k $key:expr ) => ({
|
( pk_k $key:expr ) => ({
|
||||||
use $crate::keys::ToDescriptorKey;
|
$crate::keys::make_pk($key)
|
||||||
$key.into_miniscript_and_secret()
|
|
||||||
});
|
});
|
||||||
( pk $key:expr ) => ({
|
( pk $key:expr ) => ({
|
||||||
$crate::fragment!(+c pk_k $key)
|
$crate::fragment!(+c pk_k $key)
|
||||||
@ -342,15 +363,18 @@ macro_rules! fragment {
|
|||||||
( thresh_vec $thresh:expr, $items:expr ) => ({
|
( thresh_vec $thresh:expr, $items:expr ) => ({
|
||||||
use $crate::miniscript::descriptor::KeyMap;
|
use $crate::miniscript::descriptor::KeyMap;
|
||||||
|
|
||||||
let (items, key_maps): (Vec<_>, Vec<_>) = $items.into_iter().unzip();
|
let (items, key_maps_networks): (Vec<_>, Vec<_>) = $items.into_iter().map(|(a, b, c)| (a, (b, c))).unzip();
|
||||||
let items = items.into_iter().map(std::sync::Arc::new).collect();
|
let items = items.into_iter().map(std::sync::Arc::new).collect();
|
||||||
let key_maps = key_maps.into_iter().fold(KeyMap::default(), |mut acc, map| {
|
|
||||||
acc.extend(map.into_iter());
|
let (key_maps, valid_networks) = key_maps_networks.into_iter().fold((KeyMap::default(), $crate::keys::any_network()), |(mut keys_acc, net_acc), (key, net)| {
|
||||||
acc
|
keys_acc.extend(key.into_iter());
|
||||||
|
let net_acc = $crate::keys::merge_networks(&net_acc, &net);
|
||||||
|
|
||||||
|
(keys_acc, net_acc)
|
||||||
});
|
});
|
||||||
|
|
||||||
$crate::impl_leaf_opcode_value_two!(Thresh, $thresh, items)
|
$crate::impl_leaf_opcode_value_two!(Thresh, $thresh, items)
|
||||||
.map(|(minisc, _)| (minisc, key_maps))
|
.map(|(minisc, _, _)| (minisc, key_maps, valid_networks))
|
||||||
});
|
});
|
||||||
( thresh $thresh:expr $(, ( $( $item:tt )* ) )+ ) => ({
|
( thresh $thresh:expr $(, ( $( $item:tt )* ) )+ ) => ({
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
|
@ -31,7 +31,9 @@ pub enum Error {
|
|||||||
InvalidPrefix(Vec<u8>),
|
InvalidPrefix(Vec<u8>),
|
||||||
HardenedDerivationOnXpub,
|
HardenedDerivationOnXpub,
|
||||||
MalformedInput,
|
MalformedInput,
|
||||||
|
|
||||||
KeyParsingError(String),
|
KeyParsingError(String),
|
||||||
|
Key(crate::keys::KeyError),
|
||||||
|
|
||||||
Policy(crate::descriptor::policy::PolicyError),
|
Policy(crate::descriptor::policy::PolicyError),
|
||||||
|
|
||||||
@ -50,6 +52,16 @@ pub enum Error {
|
|||||||
Hex(bitcoin::hashes::hex::Error),
|
Hex(bitcoin::hashes::hex::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::keys::KeyError> for Error {
|
||||||
|
fn from(key_error: crate::keys::KeyError) -> Error {
|
||||||
|
match key_error {
|
||||||
|
crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
|
||||||
|
crate::keys::KeyError::BIP32(inner) => Error::BIP32(inner),
|
||||||
|
e @ _ => Error::Key(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
|
@ -35,7 +35,7 @@ use bitcoin::hashes::hash160;
|
|||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
|
use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
|
||||||
use bitcoin::util::psbt;
|
use bitcoin::util::psbt;
|
||||||
use bitcoin::{PublicKey, Script, TxOut};
|
use bitcoin::{Network, PublicKey, Script, TxOut};
|
||||||
|
|
||||||
use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey, KeyMap};
|
use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey, KeyMap};
|
||||||
pub use miniscript::{
|
pub use miniscript::{
|
||||||
@ -50,6 +50,7 @@ pub mod policy;
|
|||||||
pub use self::checksum::get_checksum;
|
pub use self::checksum::get_checksum;
|
||||||
use self::error::Error;
|
use self::error::Error;
|
||||||
pub use self::policy::Policy;
|
pub use self::policy::Policy;
|
||||||
|
use crate::keys::{KeyError, ToDescriptorKey, ValidNetworks};
|
||||||
use crate::wallet::signer::SignersContainer;
|
use crate::wallet::signer::SignersContainer;
|
||||||
|
|
||||||
/// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`]
|
/// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`]
|
||||||
@ -62,32 +63,124 @@ pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
|
|||||||
/// [`psbt::Output`]: bitcoin::util::psbt::Output
|
/// [`psbt::Output`]: bitcoin::util::psbt::Output
|
||||||
pub type HDKeyPaths = BTreeMap<PublicKey, (Fingerprint, DerivationPath)>;
|
pub type HDKeyPaths = BTreeMap<PublicKey, (Fingerprint, DerivationPath)>;
|
||||||
|
|
||||||
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet
|
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
|
||||||
pub trait ToWalletDescriptor {
|
pub trait ToWalletDescriptor {
|
||||||
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error>;
|
fn to_wallet_descriptor(
|
||||||
|
self,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<(ExtendedDescriptor, KeyMap), KeyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToWalletDescriptor for &str {
|
impl ToWalletDescriptor for &str {
|
||||||
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
|
fn to_wallet_descriptor(
|
||||||
Ok(ExtendedDescriptor::parse_secret(self)?)
|
self,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||||
|
let descriptor = if self.contains("#") {
|
||||||
|
let parts: Vec<&str> = self.splitn(2, "#").collect();
|
||||||
|
if !get_checksum(parts[0])
|
||||||
|
.ok()
|
||||||
|
.map(|computed| computed == parts[1])
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Err(KeyError::InvalidChecksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[0]
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtendedDescriptor::parse_secret(descriptor)?.to_wallet_descriptor(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToWalletDescriptor for &String {
|
impl ToWalletDescriptor for &String {
|
||||||
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
|
fn to_wallet_descriptor(
|
||||||
self.as_str().to_wallet_descriptor()
|
self,
|
||||||
}
|
network: Network,
|
||||||
}
|
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||||
|
self.as_str().to_wallet_descriptor(network)
|
||||||
impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
|
|
||||||
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToWalletDescriptor for ExtendedDescriptor {
|
impl ToWalletDescriptor for ExtendedDescriptor {
|
||||||
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
|
fn to_wallet_descriptor(
|
||||||
(self, KeyMap::default()).to_wallet_descriptor()
|
self,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||||
|
(self, KeyMap::default()).to_wallet_descriptor(network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
|
||||||
|
fn to_wallet_descriptor(
|
||||||
|
self,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||||
|
use crate::keys::DescriptorKey;
|
||||||
|
|
||||||
|
// check the network for the keys
|
||||||
|
let translated = self.0.translate_pk(
|
||||||
|
|pk| {
|
||||||
|
let (pk, _, networks) = if self.0.is_witness() {
|
||||||
|
let desciptor_key: DescriptorKey<miniscript::Segwitv0> =
|
||||||
|
pk.clone().to_descriptor_key()?;
|
||||||
|
desciptor_key.extract()?
|
||||||
|
} else {
|
||||||
|
let desciptor_key: DescriptorKey<miniscript::Legacy> =
|
||||||
|
pk.clone().to_descriptor_key()?;
|
||||||
|
desciptor_key.extract()?
|
||||||
|
};
|
||||||
|
|
||||||
|
if networks.contains(&network) {
|
||||||
|
Ok(pk)
|
||||||
|
} else {
|
||||||
|
Err(KeyError::InvalidNetwork)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|pkh| Ok::<_, KeyError>(*pkh),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((translated, self.1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap, ValidNetworks) {
|
||||||
|
fn to_wallet_descriptor(
|
||||||
|
self,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||||
|
let valid_networks = &self.2;
|
||||||
|
|
||||||
|
// fixup the network for keys that need it
|
||||||
|
let translated = self.0.translate_pk(
|
||||||
|
|pk| {
|
||||||
|
if valid_networks.contains(&network) {
|
||||||
|
// workaround for xpubs generated by other key types, like bip39: since when the
|
||||||
|
// conversion is made one network has to be chosen, what we generally choose
|
||||||
|
// "mainnet", but then override the set of valid networks to specify that all of
|
||||||
|
// them are valid. here we reset the network to make sure the wallet struct gets a
|
||||||
|
// descriptor with the right network everywhere.
|
||||||
|
let pk = match pk {
|
||||||
|
DescriptorPublicKey::XPub(ref xpub) => {
|
||||||
|
let mut xpub = xpub.clone();
|
||||||
|
xpub.xkey.network = network;
|
||||||
|
|
||||||
|
DescriptorPublicKey::XPub(xpub)
|
||||||
|
}
|
||||||
|
other @ _ => other.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(pk)
|
||||||
|
} else {
|
||||||
|
Err(KeyError::InvalidNetwork)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|pkh| Ok::<_, KeyError>(*pkh),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((translated, self.1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +448,7 @@ mod test {
|
|||||||
|
|
||||||
use bitcoin::consensus::encode::deserialize;
|
use bitcoin::consensus::encode::deserialize;
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::util::psbt;
|
use bitcoin::util::{bip32, psbt};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::psbt::PSBTUtils;
|
use crate::psbt::PSBTUtils;
|
||||||
@ -467,4 +560,25 @@ mod test {
|
|||||||
.derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0))
|
.derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0))
|
||||||
.is_some());
|
.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_wallet_descriptor_fixup_networks() {
|
||||||
|
use crate::keys::{any_network, ToDescriptorKey};
|
||||||
|
|
||||||
|
let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").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"
|
||||||
|
let key = (xpub, path).to_descriptor_key().unwrap();
|
||||||
|
// override it with any. this happens in some key conversions, like bip39
|
||||||
|
let key = key.override_valid_networks(any_network());
|
||||||
|
|
||||||
|
// make a descriptor out of it
|
||||||
|
let desc = crate::descriptor!(wpkh(key)).unwrap();
|
||||||
|
// this should conver the key that supports "any_network" to the right network (testnet)
|
||||||
|
let (wallet_desc, _) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
13
src/error.rs
13
src/error.rs
@ -48,6 +48,8 @@ pub enum Error {
|
|||||||
required: crate::types::FeeRate,
|
required: crate::types::FeeRate,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Key(crate::keys::KeyError),
|
||||||
|
|
||||||
ChecksumMismatch,
|
ChecksumMismatch,
|
||||||
DifferentDescriptorStructure,
|
DifferentDescriptorStructure,
|
||||||
|
|
||||||
@ -114,6 +116,17 @@ impl_error!(
|
|||||||
);
|
);
|
||||||
impl_error!(crate::wallet::signer::SignerError, Signer);
|
impl_error!(crate::wallet::signer::SignerError, Signer);
|
||||||
|
|
||||||
|
impl From<crate::keys::KeyError> for Error {
|
||||||
|
fn from(key_error: crate::keys::KeyError) -> Error {
|
||||||
|
match key_error {
|
||||||
|
crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
|
||||||
|
crate::keys::KeyError::BIP32(inner) => Error::BIP32(inner),
|
||||||
|
crate::keys::KeyError::InvalidChecksum => Error::ChecksumMismatch,
|
||||||
|
e @ _ => Error::Key(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl_error!(bitcoin::consensus::encode::Error, Encode);
|
impl_error!(bitcoin::consensus::encode::Error, Encode);
|
||||||
impl_error!(miniscript::Error, Miniscript);
|
impl_error!(miniscript::Error, Miniscript);
|
||||||
impl_error!(bitcoin::util::bip32::Error, BIP32);
|
impl_error!(bitcoin::util::bip32::Error, BIP32);
|
||||||
|
@ -30,30 +30,36 @@
|
|||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
|
|
||||||
|
use miniscript::ScriptContext;
|
||||||
|
|
||||||
use bip39::{Mnemonic, Seed};
|
use bip39::{Mnemonic, Seed};
|
||||||
|
|
||||||
use super::{DescriptorKey, ToDescriptorKey};
|
use super::{any_network, DescriptorKey, KeyError, ToDescriptorKey};
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
|
pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
|
||||||
|
|
||||||
impl ToDescriptorKey for (Seed, bip32::DerivationPath) {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (Seed, bip32::DerivationPath) {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.0.as_bytes())?;
|
let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.0.as_bytes())?;
|
||||||
(xprv, self.1).to_descriptor_key()
|
let descriptor_key = (xprv, self.1).to_descriptor_key()?;
|
||||||
|
|
||||||
|
// here we must choose one network to build the xpub, but since the bip39 standard doesn't
|
||||||
|
// encode the network the xpub we create is actually valid everywhere. so we override the
|
||||||
|
// valid networks with `any_network()`.
|
||||||
|
Ok(descriptor_key.override_valid_networks(any_network()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToDescriptorKey for (MnemonicWithPassphrase, bip32::DerivationPath) {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (MnemonicWithPassphrase, bip32::DerivationPath) {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
let (mnemonic, passphrase) = self.0;
|
let (mnemonic, passphrase) = self.0;
|
||||||
let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or(""));
|
let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or(""));
|
||||||
(seed, self.1).to_descriptor_key()
|
(seed, self.1).to_descriptor_key()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToDescriptorKey for (Mnemonic, bip32::DerivationPath) {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (Mnemonic, bip32::DerivationPath) {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
((self.0, None), self.1).to_descriptor_key()
|
((self.0, None), self.1).to_descriptor_key()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,9 +80,10 @@ mod test {
|
|||||||
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
|
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
|
||||||
|
|
||||||
let key = (mnemonic, path);
|
let key = (mnemonic, path);
|
||||||
let (desc, keys) = crate::descriptor!(wpkh(key)).unwrap();
|
let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
|
||||||
assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)");
|
assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)");
|
||||||
assert_eq!(keys.len(), 1);
|
assert_eq!(keys.len(), 1);
|
||||||
|
assert_eq!(networks.len(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -87,8 +94,9 @@ mod test {
|
|||||||
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
|
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
|
||||||
|
|
||||||
let key = ((mnemonic, Some("passphrase".into())), path);
|
let key = ((mnemonic, Some("passphrase".into())), path);
|
||||||
let (desc, keys) = crate::descriptor!(wpkh(key)).unwrap();
|
let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
|
||||||
assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)");
|
assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)");
|
||||||
assert_eq!(keys.len(), 1);
|
assert_eq!(keys.len(), 1);
|
||||||
|
assert_eq!(networks.len(), 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
253
src/keys/mod.rs
253
src/keys/mod.rs
@ -25,36 +25,81 @@
|
|||||||
//! Key formats
|
//! Key formats
|
||||||
|
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::{PrivateKey, PublicKey};
|
use bitcoin::{Network, PrivateKey, PublicKey};
|
||||||
|
|
||||||
use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap};
|
use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap};
|
||||||
pub use miniscript::ScriptContext;
|
pub use miniscript::ScriptContext;
|
||||||
use miniscript::{Miniscript, Terminal};
|
use miniscript::{Miniscript, Terminal};
|
||||||
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
#[cfg(feature = "keys-bip39")]
|
#[cfg(feature = "keys-bip39")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
|
||||||
pub mod bip39;
|
pub mod bip39;
|
||||||
|
|
||||||
|
/// Set of valid networks for a key
|
||||||
|
pub type ValidNetworks = HashSet<Network>;
|
||||||
|
|
||||||
|
/// Create a set containing mainnet, testnet and regtest
|
||||||
|
pub fn any_network() -> ValidNetworks {
|
||||||
|
vec![Network::Bitcoin, Network::Testnet, Network::Regtest]
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
/// Create a set only containing mainnet
|
||||||
|
pub fn mainnet_network() -> ValidNetworks {
|
||||||
|
vec![Network::Bitcoin].into_iter().collect()
|
||||||
|
}
|
||||||
|
/// Create a set containing testnet and regtest
|
||||||
|
pub fn test_networks() -> ValidNetworks {
|
||||||
|
vec![Network::Testnet, Network::Regtest]
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
/// Compute the intersection of two sets
|
||||||
|
pub fn merge_networks(a: &ValidNetworks, b: &ValidNetworks) -> ValidNetworks {
|
||||||
|
a.intersection(b).cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Container for public or secret keys
|
/// Container for public or secret keys
|
||||||
pub enum DescriptorKey<Ctx: ScriptContext> {
|
pub enum DescriptorKey<Ctx: ScriptContext> {
|
||||||
Public(DescriptorPublicKey, PhantomData<Ctx>),
|
#[doc(hidden)]
|
||||||
Secret(DescriptorSecretKey, PhantomData<Ctx>),
|
Public(DescriptorPublicKey, ValidNetworks, PhantomData<Ctx>),
|
||||||
|
#[doc(hidden)]
|
||||||
|
Secret(DescriptorSecretKey, ValidNetworks, PhantomData<Ctx>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
|
impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
|
||||||
|
/// 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 secret key and a set of valid networks
|
||||||
|
pub fn from_secret(secret: DescriptorSecretKey, networks: ValidNetworks) -> Self {
|
||||||
|
DescriptorKey::Secret(secret, networks, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override the computed set of valid networks
|
||||||
|
pub fn override_valid_networks(self, networks: ValidNetworks) -> Self {
|
||||||
|
match self {
|
||||||
|
DescriptorKey::Public(key, _, _) => DescriptorKey::Public(key, networks, PhantomData),
|
||||||
|
DescriptorKey::Secret(key, _, _) => DescriptorKey::Secret(key, networks, PhantomData),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This method is used internally by `bdk::fragment!` and `bdk::descriptor!`. It has to be
|
// This method is used internally by `bdk::fragment!` and `bdk::descriptor!`. It has to be
|
||||||
// public because it is effectively called by external crates, once the macros are expanded,
|
// 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.
|
// but since it is not meant to be part of the public api we hide it from the docs.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn into_key_and_secret(self) -> Result<(DescriptorPublicKey, KeyMap), Error> {
|
pub fn extract(self) -> Result<(DescriptorPublicKey, KeyMap, ValidNetworks), KeyError> {
|
||||||
match self {
|
match self {
|
||||||
DescriptorKey::Public(public, _) => Ok((public, KeyMap::default())),
|
DescriptorKey::Public(public, valid_networks, _) => {
|
||||||
DescriptorKey::Secret(secret, _) => {
|
Ok((public, KeyMap::default(), valid_networks))
|
||||||
|
}
|
||||||
|
DescriptorKey::Secret(secret, valid_networks, _) => {
|
||||||
let mut key_map = KeyMap::with_capacity(1);
|
let mut key_map = KeyMap::with_capacity(1);
|
||||||
|
|
||||||
let public = secret
|
let public = secret
|
||||||
@ -62,12 +107,13 @@ impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
|
|||||||
.map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
|
.map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
|
||||||
key_map.insert(public.clone(), secret);
|
key_map.insert(public.clone(), secret);
|
||||||
|
|
||||||
Ok((public, key_map))
|
Ok((public, key_map, valid_networks))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enum representation of the known valid [`ScriptContext`]s
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
pub enum ScriptContextEnum {
|
pub enum ScriptContextEnum {
|
||||||
Legacy,
|
Legacy,
|
||||||
@ -84,6 +130,7 @@ impl ScriptContextEnum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait that adds extra useful methods to [`ScriptContext`]s
|
||||||
pub trait ExtScriptContext: ScriptContext {
|
pub trait ExtScriptContext: ScriptContext {
|
||||||
fn as_enum() -> ScriptContextEnum;
|
fn as_enum() -> ScriptContextEnum;
|
||||||
|
|
||||||
@ -116,7 +163,7 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
/// For key types that do care about this, the [`ExtScriptContext`] trait provides some useful
|
/// For key types that do care about this, the [`ExtScriptContext`] trait provides some useful
|
||||||
/// methods that can be used to check at runtime which `Ctx` is being used.
|
/// methods that can be used to check at runtime which `Ctx` is being used.
|
||||||
///
|
///
|
||||||
/// For key types that that do not need to check this at runtime (because they can only work within a
|
/// For key types that can do this check statically (because they can only work within a
|
||||||
/// single `Ctx`), the "specialized" trait can be implemented to make the compiler handle the type
|
/// single `Ctx`), the "specialized" trait can be implemented to make the compiler handle the type
|
||||||
/// checking.
|
/// checking.
|
||||||
///
|
///
|
||||||
@ -127,15 +174,14 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
/// ```
|
/// ```
|
||||||
/// use bdk::bitcoin::PublicKey;
|
/// use bdk::bitcoin::PublicKey;
|
||||||
///
|
///
|
||||||
/// use bdk::keys::{ScriptContext, ToDescriptorKey, DescriptorKey};
|
/// use bdk::keys::{ScriptContext, ToDescriptorKey, DescriptorKey, KeyError};
|
||||||
/// use bdk::Error;
|
|
||||||
///
|
///
|
||||||
/// pub struct MyKeyType {
|
/// pub struct MyKeyType {
|
||||||
/// pubkey: PublicKey,
|
/// pubkey: PublicKey,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for MyKeyType {
|
/// impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for MyKeyType {
|
||||||
/// fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
/// fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
/// self.pubkey.to_descriptor_key()
|
/// self.pubkey.to_descriptor_key()
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
@ -146,8 +192,7 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
/// ```
|
/// ```
|
||||||
/// use bdk::bitcoin::PublicKey;
|
/// use bdk::bitcoin::PublicKey;
|
||||||
///
|
///
|
||||||
/// use bdk::keys::{ExtScriptContext, ScriptContext, ToDescriptorKey, DescriptorKey};
|
/// use bdk::keys::{ExtScriptContext, ScriptContext, ToDescriptorKey, DescriptorKey, KeyError};
|
||||||
/// use bdk::Error;
|
|
||||||
///
|
///
|
||||||
/// pub struct MyKeyType {
|
/// pub struct MyKeyType {
|
||||||
/// is_legacy: bool,
|
/// is_legacy: bool,
|
||||||
@ -155,11 +200,11 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl<Ctx: ScriptContext + 'static> ToDescriptorKey<Ctx> for MyKeyType {
|
/// impl<Ctx: ScriptContext + 'static> ToDescriptorKey<Ctx> for MyKeyType {
|
||||||
/// fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
/// fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
/// if Ctx::is_legacy() == self.is_legacy {
|
/// if Ctx::is_legacy() == self.is_legacy {
|
||||||
/// self.pubkey.to_descriptor_key()
|
/// self.pubkey.to_descriptor_key()
|
||||||
/// } else {
|
/// } else {
|
||||||
/// Err(Error::Generic("Invalid key context".into()))
|
/// Err(KeyError::InvalidScriptContext)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
@ -176,15 +221,14 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
/// use std::str::FromStr;
|
/// use std::str::FromStr;
|
||||||
/// use bdk::bitcoin::PublicKey;
|
/// use bdk::bitcoin::PublicKey;
|
||||||
///
|
///
|
||||||
/// use bdk::keys::{ToDescriptorKey, DescriptorKey};
|
/// use bdk::keys::{ToDescriptorKey, DescriptorKey, KeyError};
|
||||||
/// use bdk::Error;
|
|
||||||
///
|
///
|
||||||
/// pub struct MySegwitOnlyKeyType {
|
/// pub struct MySegwitOnlyKeyType {
|
||||||
/// pubkey: PublicKey,
|
/// pubkey: PublicKey,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl ToDescriptorKey<bdk::miniscript::Segwitv0> for MySegwitOnlyKeyType {
|
/// impl ToDescriptorKey<bdk::miniscript::Segwitv0> for MySegwitOnlyKeyType {
|
||||||
/// fn to_descriptor_key(self) -> Result<DescriptorKey<bdk::miniscript::Segwitv0>, Error> {
|
/// fn to_descriptor_key(self) -> Result<DescriptorKey<bdk::miniscript::Segwitv0>, KeyError> {
|
||||||
/// self.pubkey.to_descriptor_key()
|
/// self.pubkey.to_descriptor_key()
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
@ -192,25 +236,28 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
/// let key = MySegwitOnlyKeyType {
|
/// let key = MySegwitOnlyKeyType {
|
||||||
/// pubkey: PublicKey::from_str("...")?,
|
/// pubkey: PublicKey::from_str("...")?,
|
||||||
/// };
|
/// };
|
||||||
/// let (descriptor, _) = bdk::descriptor!(pkh ( key ) )?;
|
/// let (descriptor, _, _) = bdk::descriptor!(pkh ( key ) )?;
|
||||||
/// // ^^^^^ changing this to `wpkh` would make it compile
|
/// // ^^^^^ changing this to `wpkh` would make it compile
|
||||||
///
|
///
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub trait ToDescriptorKey<Ctx: ScriptContext>: Sized {
|
pub trait ToDescriptorKey<Ctx: ScriptContext>: Sized {
|
||||||
/// Turn the key into a [`DescriptorKey`] within the requested [`ScriptContext`]
|
/// Turn the key into a [`DescriptorKey`] within the requested [`ScriptContext`]
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error>;
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError>;
|
||||||
|
}
|
||||||
|
|
||||||
// Used internally by `bdk::fragment!` to build `pk_k()` fragments
|
// Used internally by `bdk::fragment!` to build `pk_k()` fragments
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn into_miniscript_and_secret(
|
pub fn make_pk<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||||
self,
|
descriptor_key: Pk,
|
||||||
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap), Error> {
|
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), KeyError> {
|
||||||
let descriptor_key = self.to_descriptor_key()?;
|
let (key, key_map, valid_networks) = descriptor_key.to_descriptor_key()?.extract()?;
|
||||||
let (key, key_map) = descriptor_key.into_key_and_secret()?;
|
|
||||||
|
|
||||||
Ok((Miniscript::from_ast(Terminal::PkK(key))?, key_map))
|
Ok((
|
||||||
}
|
Miniscript::from_ast(Terminal::PkK(key))?,
|
||||||
|
key_map,
|
||||||
|
valid_networks,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used internally by `bdk::fragment!` to build `multi()` fragments
|
// Used internally by `bdk::fragment!` to build `multi()` fragments
|
||||||
@ -218,90 +265,136 @@ pub trait ToDescriptorKey<Ctx: ScriptContext>: Sized {
|
|||||||
pub fn make_multi<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
pub fn make_multi<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||||
thresh: usize,
|
thresh: usize,
|
||||||
pks: Vec<Pk>,
|
pks: Vec<Pk>,
|
||||||
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap), Error> {
|
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), KeyError> {
|
||||||
let (pks, key_maps): (Vec<_>, Vec<_>) = pks
|
let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|key| {
|
.map(|key| Ok::<_, KeyError>(key.to_descriptor_key()?.extract()?))
|
||||||
key.to_descriptor_key()
|
|
||||||
.and_then(DescriptorKey::into_key_and_secret)
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.map(|(a, b, c)| (a, (b, c)))
|
||||||
.unzip();
|
.unzip();
|
||||||
|
|
||||||
let key_map = key_maps
|
let (key_map, valid_networks) = key_maps_networks.into_iter().fold(
|
||||||
.into_iter()
|
(KeyMap::default(), any_network()),
|
||||||
.fold(KeyMap::default(), |mut acc, map| {
|
|(mut keys_acc, net_acc), (key, net)| {
|
||||||
acc.extend(map.into_iter());
|
keys_acc.extend(key.into_iter());
|
||||||
acc
|
let net_acc = merge_networks(&net_acc, &net);
|
||||||
});
|
|
||||||
|
|
||||||
Ok((Miniscript::from_ast(Terminal::Multi(thresh, pks))?, key_map))
|
(keys_acc, net_acc)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Miniscript::from_ast(Terminal::Multi(thresh, pks))?,
|
||||||
|
key_map,
|
||||||
|
valid_networks,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The "identity" conversion is used internally by some `bdk::fragment`s
|
/// The "identity" conversion is used internally by some `bdk::fragment`s
|
||||||
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorKey<Ctx> {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorKey<Ctx> {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorPublicKey {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorPublicKey {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
Ok(DescriptorKey::Public(self, PhantomData))
|
let networks = match self {
|
||||||
|
DescriptorPublicKey::PubKey(_) => any_network(),
|
||||||
|
DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. })
|
||||||
|
if xkey.network == Network::Bitcoin =>
|
||||||
|
{
|
||||||
|
mainnet_network()
|
||||||
|
}
|
||||||
|
_ => test_networks(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(DescriptorKey::from_public(self, networks))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for PublicKey {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for PublicKey {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
Ok(DescriptorKey::Public(
|
DescriptorPublicKey::PubKey(self).to_descriptor_key()
|
||||||
DescriptorPublicKey::PubKey(self),
|
|
||||||
PhantomData,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This assumes that "is_wildcard" is true, since this is generally the way extended keys are used
|
/// This assumes that "is_wildcard" is true, since this is generally the way extended keys are used
|
||||||
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (bip32::ExtendedPubKey, bip32::DerivationPath) {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (bip32::ExtendedPubKey, bip32::DerivationPath) {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
Ok(DescriptorKey::Public(
|
DescriptorPublicKey::XPub(DescriptorXKey {
|
||||||
DescriptorPublicKey::XPub(DescriptorXKey {
|
source: None,
|
||||||
source: None,
|
xkey: self.0,
|
||||||
xkey: self.0,
|
derivation_path: self.1,
|
||||||
derivation_path: self.1,
|
is_wildcard: true,
|
||||||
is_wildcard: true,
|
})
|
||||||
}),
|
.to_descriptor_key()
|
||||||
PhantomData,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorSecretKey {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorSecretKey {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
Ok(DescriptorKey::Secret(self, PhantomData))
|
let networks = match self {
|
||||||
|
DescriptorSecretKey::PrivKey(sk) if sk.network == Network::Bitcoin => mainnet_network(),
|
||||||
|
DescriptorSecretKey::XPrv(DescriptorXKey { xkey, .. })
|
||||||
|
if xkey.network == Network::Bitcoin =>
|
||||||
|
{
|
||||||
|
mainnet_network()
|
||||||
|
}
|
||||||
|
_ => test_networks(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(DescriptorKey::from_secret(self, networks))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for PrivateKey {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for PrivateKey {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
Ok(DescriptorKey::Secret(
|
DescriptorSecretKey::PrivKey(self).to_descriptor_key()
|
||||||
DescriptorSecretKey::PrivKey(self),
|
|
||||||
PhantomData,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This assumes that "is_wildcard" is true, since this is generally the way extended keys are used
|
/// This assumes that "is_wildcard" is true, since this is generally the way extended keys are used
|
||||||
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (bip32::ExtendedPrivKey, bip32::DerivationPath) {
|
impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (bip32::ExtendedPrivKey, bip32::DerivationPath) {
|
||||||
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
|
fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
Ok(DescriptorKey::Secret(
|
DescriptorSecretKey::XPrv(DescriptorXKey {
|
||||||
DescriptorSecretKey::XPrv(DescriptorXKey {
|
source: None,
|
||||||
source: None,
|
xkey: self.0,
|
||||||
xkey: self.0,
|
derivation_path: self.1,
|
||||||
derivation_path: self.1,
|
is_wildcard: true,
|
||||||
is_wildcard: true,
|
})
|
||||||
}),
|
.to_descriptor_key()
|
||||||
PhantomData,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum KeyError {
|
||||||
|
InvalidScriptContext,
|
||||||
|
InvalidNetwork,
|
||||||
|
InvalidChecksum,
|
||||||
|
Message(String),
|
||||||
|
|
||||||
|
BIP32(bitcoin::util::bip32::Error),
|
||||||
|
Miniscript(miniscript::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<miniscript::Error> for KeyError {
|
||||||
|
fn from(inner: miniscript::Error) -> Self {
|
||||||
|
KeyError::Miniscript(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bitcoin::util::bip32::Error> for KeyError {
|
||||||
|
fn from(inner: bitcoin::util::bip32::Error) -> Self {
|
||||||
|
KeyError::BIP32(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for KeyError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for KeyError {}
|
||||||
|
@ -234,7 +234,7 @@ mod test {
|
|||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Testnet,
|
Network::Bitcoin,
|
||||||
get_test_db(),
|
get_test_db(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -256,7 +256,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
|
|
||||||
let wallet: OfflineWallet<_> =
|
let wallet: OfflineWallet<_> =
|
||||||
Wallet::new_offline(descriptor, None, Network::Testnet, get_test_db()).unwrap();
|
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
|
||||||
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ mod test {
|
|||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Testnet,
|
Network::Bitcoin,
|
||||||
get_test_db(),
|
get_test_db(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -315,7 +315,7 @@ mod test {
|
|||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Testnet,
|
Network::Bitcoin,
|
||||||
get_test_db(),
|
get_test_db(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -113,7 +113,7 @@ where
|
|||||||
network: Network,
|
network: Network,
|
||||||
mut database: D,
|
mut database: D,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let (descriptor, keymap) = descriptor.to_wallet_descriptor()?;
|
let (descriptor, keymap) = descriptor.to_wallet_descriptor(network)?;
|
||||||
database.check_descriptor_checksum(
|
database.check_descriptor_checksum(
|
||||||
ScriptType::External,
|
ScriptType::External,
|
||||||
get_checksum(&descriptor.to_string())?.as_bytes(),
|
get_checksum(&descriptor.to_string())?.as_bytes(),
|
||||||
@ -121,7 +121,7 @@ where
|
|||||||
let signers = Arc::new(SignersContainer::from(keymap));
|
let signers = Arc::new(SignersContainer::from(keymap));
|
||||||
let (change_descriptor, change_signers) = match change_descriptor {
|
let (change_descriptor, change_signers) = match change_descriptor {
|
||||||
Some(desc) => {
|
Some(desc) => {
|
||||||
let (change_descriptor, change_keymap) = desc.to_wallet_descriptor()?;
|
let (change_descriptor, change_keymap) = desc.to_wallet_descriptor(network)?;
|
||||||
database.check_descriptor_checksum(
|
database.check_descriptor_checksum(
|
||||||
ScriptType::Internal,
|
ScriptType::Internal,
|
||||||
get_checksum(&change_descriptor.to_string())?.as_bytes(),
|
get_checksum(&change_descriptor.to_string())?.as_bytes(),
|
||||||
@ -1737,7 +1737,7 @@ mod test {
|
|||||||
use bitcoin::util::bip32::{DerivationPath, Fingerprint};
|
use bitcoin::util::bip32::{DerivationPath, Fingerprint};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
|
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
|
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
|
||||||
@ -1758,7 +1758,7 @@ mod test {
|
|||||||
use bitcoin::util::bip32::{DerivationPath, Fingerprint};
|
use bitcoin::util::bip32::{DerivationPath, Fingerprint};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
|
let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
|
||||||
// cache some addresses
|
// cache some addresses
|
||||||
wallet.get_new_address().unwrap();
|
wallet.get_new_address().unwrap();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user