[descriptor] Improve the descriptor macro, add traits for key and descriptor types

This commit is contained in:
Alekos Filini 2020-09-18 16:31:03 +02:00
parent 9832ecb660
commit 751a553925
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
7 changed files with 368 additions and 103 deletions

View File

@ -22,15 +22,121 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
//! Descriptor DSL //! Descriptors DSL
/// Domain specific language to write descriptors with code #[doc(hidden)]
#[macro_export]
macro_rules! impl_top_level_sh {
( $descriptor_variant:ident, $( $minisc:tt )* ) => {
$crate::fragment!($( $minisc )*)
.map(|(minisc, keymap)|($crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::$descriptor_variant(minisc), keymap))
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_top_level_pk {
( $descriptor_variant:ident, $key:expr ) => {{
use $crate::keys::ToDescriptorKey;
$key.to_descriptor_key()
.into_key_and_secret()
.map(|(pk, key_map)| {
(
$crate::miniscript::Descriptor::<
$crate::miniscript::descriptor::DescriptorPublicKey,
>::$descriptor_variant(pk),
key_map,
)
})
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_modifier {
( $terminal_variant:ident, $( $inner:tt )* ) => {
$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)))
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_leaf_opcode {
( $terminal_variant:ident ) => {
$crate::miniscript::Miniscript::from_ast(
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
)
.map_err($crate::Error::Miniscript)
.map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_leaf_opcode_value {
( $terminal_variant:ident, $value:expr ) => {
$crate::miniscript::Miniscript::from_ast(
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
)
.map_err($crate::Error::Miniscript)
.map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_leaf_opcode_value_two {
( $terminal_variant:ident, $one:expr, $two:expr ) => {
$crate::miniscript::Miniscript::from_ast(
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
)
.map_err($crate::Error::Miniscript)
.map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_node_opcode_two {
( $terminal_variant:ident, ( $( $a:tt )* ), ( $( $b:tt )* ) ) => {
$crate::fragment!($( $a )*)
.and_then(|a| Ok((a, $crate::fragment!($( $b )*)?)))
.and_then(|((a_minisc, mut a_keymap), (b_minisc, b_keymap))| {
// join key_maps
a_keymap.extend(b_keymap.into_iter());
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
std::sync::Arc::new(a_minisc),
std::sync::Arc::new(b_minisc),
))?, a_keymap))
})
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_node_opcode_three {
( $terminal_variant:ident, ( $( $a:tt )* ), ( $( $b:tt )* ), ( $( $c:tt )* ) ) => {
$crate::fragment!($( $a )*)
.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))| {
// join key_maps
a_keymap.extend(b_keymap.into_iter());
a_keymap.extend(c_keymap.into_iter());
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
std::sync::Arc::new(a_minisc),
std::sync::Arc::new(b_minisc),
std::sync::Arc::new(c_minisc),
))?, a_keymap))
})
};
}
/// Macro to write full descriptors with code
/// ///
/// This macro must be called in a function that returns a `Result<_, E: From<miniscript::Error>>`. /// This macro expands to an object of type `Result<(Descriptor<DescriptorPublicKey>, KeyMap), Error>`.
///
/// The `pk()` descriptor type (single sig with a bare public key) is not available, since it could
/// cause conflicts with the equally-named `pk()` descriptor opcode. It has been replaced by `pkr()`,
/// which stands for "public key root".
/// ///
/// ## Example /// ## Example
/// ///
@ -38,13 +144,14 @@
/// ///
/// ``` /// ```
/// # use std::str::FromStr; /// # use std::str::FromStr;
/// # use miniscript::descriptor::DescriptorPublicKey; /// let my_key = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
/// let my_key = DescriptorPublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c").unwrap();
/// let my_timelock = 50; /// let my_timelock = 50;
/// let my_descriptor = bdk::desc!(sh ( wsh ( and_v (+v pk my_key), ( older my_timelock )))); /// let (my_descriptor, my_keys_map) = bdk::descriptor!(sh ( wsh ( and_v (+v pk my_key), ( older my_timelock ))))?;
/// # Ok::<(), bdk::Error>(()) /// # Ok::<(), Box<dyn std::error::Error>>(())
/// ``` /// ```
/// ///
/// -------
///
/// 2-of-3 that becomes a 1-of-3 after a timelock has expired. Both `descriptor_a` and `descriptor_b` are equivalent: the first /// 2-of-3 that becomes a 1-of-3 after a timelock has expired. Both `descriptor_a` and `descriptor_b` are equivalent: the first
/// syntax is more suitable for a fixed number of items known at compile time, while the other accepts a /// syntax is more suitable for a fixed number of items known at compile time, while the other accepts a
/// [`Vec`] of items, which makes it more suitable for writing dynamic descriptors. /// [`Vec`] of items, which makes it more suitable for writing dynamic descriptors.
@ -53,182 +160,203 @@
/// ///
/// ``` /// ```
/// # use std::str::FromStr; /// # use std::str::FromStr;
/// # use miniscript::descriptor::DescriptorPublicKey; /// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
/// let my_key = DescriptorPublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c").unwrap(); /// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
/// let my_timelock = 50; /// let my_timelock = 50;
/// ///
/// let descriptor_a = bdk::desc! { /// let (descriptor_a, key_map_a) = bdk::descriptor! {
/// wsh ( /// wsh (
/// thresh 2, (pk my_key.clone()), (+s pk my_key.clone()), (+s+d+v older my_timelock) /// thresh 2, (pk my_key_1), (+s pk my_key_2), (+s+d+v older my_timelock)
/// ) /// )
/// }; /// }?;
/// ///
/// let b_items = vec![ /// let b_items = vec![
/// bdk::desc!(pk my_key.clone()), /// bdk::fragment!(pk my_key_1)?,
/// bdk::desc!(+s pk my_key.clone()), /// bdk::fragment!(+s pk my_key_2)?,
/// bdk::desc!(+s+d+v older my_timelock), /// bdk::fragment!(+s+d+v older my_timelock)?,
/// ]; /// ];
/// let descriptor_b = bdk::desc!( wsh ( thresh_vec 2, b_items ) ); /// let (descriptor_b, mut key_map_b) = bdk::descriptor!( wsh ( thresh_vec 2, b_items ) )?;
/// ///
/// assert_eq!(descriptor_a, descriptor_b); /// assert_eq!(descriptor_a, descriptor_b);
/// # Ok::<(), bdk::Error>(()) /// assert_eq!(key_map_a.len(), key_map_b.len());
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! desc { macro_rules! descriptor {
// Descriptor
( bare ( $( $minisc:tt )* ) ) => ({ ( bare ( $( $minisc:tt )* ) ) => ({
$crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Bare($crate::desc!($( $minisc )*)) $crate::impl_top_level_sh!(Bare, $( $minisc )*)
}); });
( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({ ( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({
$crate::desc!(shwsh ($( $minisc )*)) $crate::descriptor!(shwsh ($( $minisc )*))
}); });
( shwsh ( $( $minisc:tt )* ) ) => ({ ( shwsh ( $( $minisc:tt )* ) ) => ({
$crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::ShWsh($crate::desc!($( $minisc )*)) $crate::impl_top_level_sh!(ShWsh, $( $minisc )*)
}); });
( pkr $key:expr ) => ({ ( pk $key:expr ) => ({
$crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Pk($key) $crate::impl_top_level_pk!(Pk, $key)
}); });
( pkh $key:expr ) => ({ ( pkh $key:expr ) => ({
$crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Pkh($key) $crate::impl_top_level_pk!(Pkh, $key)
}); });
( wpkh $key:expr ) => ({ ( wpkh $key:expr ) => ({
$crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Wpkh($key) $crate::impl_top_level_pk!(Wpkh, $key)
}); });
( sh ( wpkh ( $key:expr ) ) ) => ({ ( sh ( wpkh ( $key:expr ) ) ) => ({
$crate::desc!(shwpkh ($( $minisc )*)) $crate::descriptor!(shwpkh ($( $minisc )*))
}); });
( shwpkh ( $key:expr ) ) => ({ ( shwpkh ( $key:expr ) ) => ({
$crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::ShWpkh($key) $crate::impl_top_level_pk!(ShWpkh, $key)
}); });
( sh ( $( $minisc:tt )* ) ) => ({ ( sh ( $( $minisc:tt )* ) ) => ({
$crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Sh($crate::desc!($( $minisc )*)) $crate::impl_top_level_sh!(Sh, $( $minisc )*)
}); });
( wsh ( $( $minisc:tt )* ) ) => ({ ( wsh ( $( $minisc:tt )* ) ) => ({
$crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Wsh($crate::desc!($( $minisc )*)) $crate::impl_top_level_sh!(Wsh, $( $minisc )*)
}); });
}
/// 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
/// fragments of larger descriptors that can be pieced together using `fragment!(thresh_vec ...)`.
#[macro_export]
macro_rules! fragment {
// Modifiers // Modifiers
( +a $( $inner:tt )* ) => ({ ( +a $( $inner:tt )* ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Alt(std::sync::Arc::new($crate::desc!($( $inner )*))))? $crate::impl_modifier!(Alt, $( $inner )*)
}); });
( +s $( $inner:tt )* ) => ({ ( +s $( $inner:tt )* ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Swap(std::sync::Arc::new($crate::desc!($( $inner )*))))? $crate::impl_modifier!(Swap, $( $inner )*)
}); });
( +c $( $inner:tt )* ) => ({ ( +c $( $inner:tt )* ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Check(std::sync::Arc::new($crate::desc!($( $inner )*))))? $crate::impl_modifier!(Check, $( $inner )*)
}); });
( +d $( $inner:tt )* ) => ({ ( +d $( $inner:tt )* ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::DupIf(std::sync::Arc::new($crate::desc!($( $inner )*))))? $crate::impl_modifier!(DupIf, $( $inner )*)
}); });
( +v $( $inner:tt )* ) => ({ ( +v $( $inner:tt )* ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Verify(std::sync::Arc::new($crate::desc!($( $inner )*))))? $crate::impl_modifier!(Verify, $( $inner )*)
}); });
( +j $( $inner:tt )* ) => ({ ( +j $( $inner:tt )* ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::NonZero(std::sync::Arc::new($crate::desc!($( $inner )*))))? $crate::impl_modifier!(NonZero, $( $inner )*)
}); });
( +n $( $inner:tt )* ) => ({ ( +n $( $inner:tt )* ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::ZeroNotEqual(std::sync::Arc::new($crate::desc!($( $inner )*))))? $crate::impl_modifier!(ZeroNotEqual, $( $inner )*)
}); });
( +t $( $inner:tt )* ) => ({ ( +t $( $inner:tt )* ) => ({
$crate::desc!(and_v ( $( $inner )* ), ( true ) ) $crate::fragment!(and_v ( $( $inner )* ), ( true ) )
}); });
( +l $( $inner:tt )* ) => ({ ( +l $( $inner:tt )* ) => ({
$crate::desc!(or_i ( false ), ( $( $inner )* ) ) $crate::fragment!(or_i ( false ), ( $( $inner )* ) )
}); });
( +u $( $inner:tt )* ) => ({ ( +u $( $inner:tt )* ) => ({
$crate::desc!(or_i ( $( $inner )* ), ( false ) ) $crate::fragment!(or_i ( $( $inner )* ), ( false ) )
}); });
// Miniscript // Miniscript
( true ) => ({ ( true ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::True)? $crate::impl_leaf_opcode!(True)
}); });
( false ) => ({ ( false ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::False)? $crate::impl_leaf_opcode!(False)
}); });
( pk_k $key:expr ) => ({ ( pk_k $key:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::PkK($key))? use $crate::keys::ToDescriptorKey;
$key.to_descriptor_key().into_key_and_secret()
.and_then(|(pk, key_map)| Ok(($crate::impl_leaf_opcode_value!(PkK, pk)?.0, key_map)))
}); });
( pk $key:expr ) => ({ ( pk $key:expr ) => ({
$crate::desc!(+c pk_k $key) $crate::fragment!(+c pk_k $key)
}); });
( pk_h $key_hash:expr ) => ({ ( pk_h $key_hash:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::PkH($key_hash))? $crate::impl_leaf_opcode_value!(PkH, $key_hash)
}); });
( after $value:expr ) => ({ ( after $value:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::After($value))? $crate::impl_leaf_opcode_value!(After, $value)
}); });
( older $value:expr ) => ({ ( older $value:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Older($value))? $crate::impl_leaf_opcode_value!(Older, $value)
}); });
( sha256 $hash:expr ) => ({ ( sha256 $hash:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Sha256($hash))? $crate::impl_leaf_opcode_value!(Sha256, $hash)
}); });
( hash256 $hash:expr ) => ({ ( hash256 $hash:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Hash256($hash))? $crate::impl_leaf_opcode_value!(Hash256, $hash)
}); });
( ripemd160 $hash:expr ) => ({ ( ripemd160 $hash:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Ripemd160($hash))? $crate::impl_leaf_opcode_value!(Ripemd160, $hash)
}); });
( hash160 $hash:expr ) => ({ ( hash160 $hash:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Hash160($hash))? $crate::impl_leaf_opcode_value!(Hash160, $hash)
}); });
( and_v ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({ ( and_v ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::AndV( $crate::impl_node_opcode_two!(AndV, ( $( $a )* ), ( $( $b )* ))
std::sync::Arc::new($crate::desc!($( $a )*)),
std::sync::Arc::new($crate::desc!($( $b )*)),
))?
}); });
( and_b ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({ ( and_b ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::AndB( $crate::impl_node_opcode_two!(AndB, ( $( $a )* ), ( $( $b )* ))
std::sync::Arc::new($crate::desc!($( $a )*)),
std::sync::Arc::new($crate::desc!($( $b )*))),
)?
}); });
( and_or ( $( $a:tt )* ), ( $( $b:tt )* ), ( $( $c:tt )* ) ) => ({ ( and_or ( $( $a:tt )* ), ( $( $b:tt )* ), ( $( $c:tt )* ) ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::AndOr( $crate::impl_node_opcode_three!(AndOr, ( $( $a )* ), ( $( $b )* ), ( $( $c )* ))
std::sync::Arc::new($crate::desc!($( $a )*)),
std::sync::Arc::new($crate::desc!($( $b )*)),
std::sync::Arc::new($crate::desc!($( $c )*)),
))?
}); });
( or_b ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({ ( or_b ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::OrB( $crate::impl_node_opcode_two!(OrB, ( $( $a )* ), ( $( $b )* ))
std::sync::Arc::new($crate::desc!($( $a )*)),
std::sync::Arc::new($crate::desc!($( $b )*)),
))?
}); });
( or_d ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({ ( or_d ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::OrD( $crate::impl_node_opcode_two!(OrD, ( $( $a )* ), ( $( $b )* ))
std::sync::Arc::new($crate::desc!($( $a )*)),
std::sync::Arc::new($crate::desc!($( $b )*)),
))?
}); });
( or_c ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({ ( or_c ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::OrC( $crate::impl_node_opcode_two!(OrC, ( $( $a )* ), ( $( $b )* ))
std::sync::Arc::new($crate::desc!($( $a )*)),
std::sync::Arc::new($crate::desc!($( $b )*)),
))?
}); });
( or_i ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({ ( or_i ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::OrI( $crate::impl_node_opcode_two!(OrI, ( $( $a )* ), ( $( $b )* ))
std::sync::Arc::new($crate::desc!($( $a )*)),
std::sync::Arc::new($crate::desc!($( $b )*)),
))?
}); });
( thresh_vec $thresh:expr, $items:expr ) => ({ ( thresh_vec $thresh:expr, $items:expr ) => ({
let items = $items.into_iter().map(std::sync::Arc::new).collect(); use $crate::miniscript::descriptor::KeyMap;
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Thresh($thresh, items))?
let (items, key_maps): (Vec<_>, Vec<_>) = $items.into_iter().unzip();
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());
acc
});
$crate::impl_leaf_opcode_value_two!(Thresh, $thresh, items)
.map(|(minisc, _)| (minisc, key_maps))
}); });
( thresh $thresh:expr $(, ( $( $item:tt )* ) )+ ) => ({ ( thresh $thresh:expr $(, ( $( $item:tt )* ) )+ ) => ({
let mut items = vec![]; let mut items = vec![];
$( $(
items.push(std::sync::Arc::new($crate::desc!($( $item )*))); items.push($crate::fragment!($( $item )*));
)* )*
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Thresh($thresh, items))? items.into_iter().collect::<Result<Vec<_>, _>>()
.and_then(|items| $crate::fragment!(thresh_vec $thresh, items))
}); });
( multi $thresh:expr, $keys:expr ) => ({ ( multi_vec $thresh:expr, $keys:expr ) => ({
$crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Multi($thresh, $keys))? use $crate::miniscript::descriptor::KeyMap;
use $crate::keys::{ToDescriptorKey, DescriptorKey};
$keys.into_iter()
.map(ToDescriptorKey::to_descriptor_key)
.map(DescriptorKey::into_key_and_secret)
.collect::<Result<Vec<_>, _>>()
.map(|items| items.into_iter().unzip())
.and_then(|(keys, key_maps): (Vec<_>, Vec<_>)| {
let key_maps = key_maps.into_iter().fold(KeyMap::default(), |mut acc, map| {
acc.extend(map.into_iter());
acc
});
Ok(($crate::impl_leaf_opcode_value_two!(Multi, $thresh, keys)?.0, key_maps))
})
}); });
( multi $thresh:expr $(, $key:expr )+ ) => ({
let mut keys = vec![];
$(
keys.push($key);
)*
$crate::fragment!(multi_vec $thresh, keys)
});
} }

View File

@ -37,7 +37,7 @@ use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
use bitcoin::util::psbt; use bitcoin::util::psbt;
use bitcoin::{PublicKey, Script, TxOut}; use bitcoin::{PublicKey, Script, TxOut};
use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey}; use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey, KeyMap};
pub use miniscript::{ pub use miniscript::{
Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, ToPublicKey, Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, ToPublicKey,
}; };
@ -62,6 +62,35 @@ 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
pub trait ToWalletDescriptor {
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error>;
}
impl ToWalletDescriptor for &str {
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
Ok(ExtendedDescriptor::parse_secret(self)?)
}
}
impl ToWalletDescriptor for &String {
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
self.as_str().to_wallet_descriptor()
}
}
impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
Ok(self)
}
}
impl ToWalletDescriptor for ExtendedDescriptor {
fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
(self, KeyMap::default()).to_wallet_descriptor()
}
}
/// 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 { pub trait ExtractPolicy {
fn extract_policy( fn extract_policy(

106
src/keys/mod.rs Normal file
View File

@ -0,0 +1,106 @@
// Magical Bitcoin Library
// Written in 2020 by
// Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020 Magical Bitcoin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Key formats
use bitcoin::util::bip32;
use bitcoin::{PrivateKey, PublicKey};
use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap};
use crate::Error;
pub enum DescriptorKey {
Public(DescriptorPublicKey),
Secret(DescriptorSecretKey),
}
impl DescriptorKey {
#[doc(hidden)]
pub fn into_key_and_secret(self) -> Result<(DescriptorPublicKey, KeyMap), Error> {
match self {
DescriptorKey::Public(public) => Ok((public, KeyMap::default())),
DescriptorKey::Secret(secret) => {
let mut key_map = KeyMap::with_capacity(1);
let public = secret
.as_public()
.map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
key_map.insert(public.clone(), secret);
Ok((public, key_map))
}
}
}
}
pub trait ToDescriptorKey {
fn to_descriptor_key(self) -> DescriptorKey;
}
impl ToDescriptorKey for DescriptorPublicKey {
fn to_descriptor_key(self) -> DescriptorKey {
DescriptorKey::Public(self)
}
}
impl ToDescriptorKey for PublicKey {
fn to_descriptor_key(self) -> DescriptorKey {
DescriptorKey::Public(DescriptorPublicKey::PubKey(self))
}
}
impl ToDescriptorKey for (bip32::ExtendedPubKey, bip32::DerivationPath, bool) {
fn to_descriptor_key(self) -> DescriptorKey {
DescriptorKey::Public(DescriptorPublicKey::XPub(DescriptorXKey {
source: None,
xkey: self.0,
derivation_path: self.1,
is_wildcard: self.2,
}))
}
}
impl ToDescriptorKey for DescriptorSecretKey {
fn to_descriptor_key(self) -> DescriptorKey {
DescriptorKey::Secret(self)
}
}
impl ToDescriptorKey for PrivateKey {
fn to_descriptor_key(self) -> DescriptorKey {
DescriptorKey::Secret(DescriptorSecretKey::PrivKey(self))
}
}
impl ToDescriptorKey for (bip32::ExtendedPrivKey, bip32::DerivationPath, bool) {
fn to_descriptor_key(self) -> DescriptorKey {
DescriptorKey::Secret(DescriptorSecretKey::XPrv(DescriptorXKey {
source: None,
xkey: self.0,
derivation_path: self.1,
is_wildcard: self.2,
}))
}
}

View File

@ -75,6 +75,7 @@ pub mod database;
pub mod descriptor; pub mod descriptor;
#[cfg(feature = "test-md-docs")] #[cfg(feature = "test-md-docs")]
mod doctest; mod doctest;
pub mod keys;
pub(crate) mod psbt; pub(crate) mod psbt;
pub(crate) mod types; pub(crate) mod types;
pub mod wallet; pub mod wallet;

View File

@ -43,7 +43,7 @@
//! }"#; //! }"#;
//! //!
//! let import = WalletExport::from_str(import)?; //! let import = WalletExport::from_str(import)?;
//! let wallet: OfflineWallet<_> = Wallet::new_offline(&import.descriptor(), import.change_descriptor().as_deref(), Network::Testnet, MemoryDatabase::default())?; //! let wallet: OfflineWallet<_> = Wallet::new_offline(&import.descriptor(), import.change_descriptor().as_ref(), Network::Testnet, MemoryDatabase::default())?;
//! # Ok::<_, bdk::Error>(()) //! # Ok::<_, bdk::Error>(())
//! ``` //! ```
//! //!

View File

@ -62,6 +62,7 @@ use crate::blockchain::{Blockchain, BlockchainMarker, OfflineBlockchain, Progres
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::descriptor::{ use crate::descriptor::{
get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy, get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
ToWalletDescriptor,
}; };
use crate::error::Error; use crate::error::Error;
use crate::psbt::PSBTUtils; use crate::psbt::PSBTUtils;
@ -106,26 +107,26 @@ where
D: BatchDatabase, D: BatchDatabase,
{ {
/// Create a new "offline" wallet /// Create a new "offline" wallet
pub fn new_offline( pub fn new_offline<E: ToWalletDescriptor>(
descriptor: &str, descriptor: E,
change_descriptor: Option<&str>, change_descriptor: Option<E>,
network: Network, network: Network,
mut database: D, mut database: D,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let (descriptor, keymap) = descriptor.to_wallet_descriptor()?;
database.check_descriptor_checksum( database.check_descriptor_checksum(
ScriptType::External, ScriptType::External,
get_checksum(descriptor)?.as_bytes(), get_checksum(&descriptor.to_string())?.as_bytes(),
)?; )?;
let (descriptor, keymap) = ExtendedDescriptor::parse_secret(descriptor)?;
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()?;
database.check_descriptor_checksum( database.check_descriptor_checksum(
ScriptType::Internal, ScriptType::Internal,
get_checksum(desc)?.as_bytes(), get_checksum(&change_descriptor.to_string())?.as_bytes(),
)?; )?;
let (change_descriptor, change_keymap) = ExtendedDescriptor::parse_secret(desc)?;
let change_signers = Arc::new(SignersContainer::from(change_keymap)); let change_signers = Arc::new(SignersContainer::from(change_keymap));
// if !parsed.same_structure(descriptor.as_ref()) { // if !parsed.same_structure(descriptor.as_ref()) {
// return Err(Error::DifferentDescriptorStructure); // return Err(Error::DifferentDescriptorStructure);
@ -1081,9 +1082,9 @@ where
{ {
/// Create a new "online" wallet /// Create a new "online" wallet
#[maybe_async] #[maybe_async]
pub fn new( pub fn new<E: ToWalletDescriptor>(
descriptor: &str, descriptor: E,
change_descriptor: Option<&str>, change_descriptor: Option<E>,
network: Network, network: Network,
database: D, database: D,
client: B, client: B,

View File

@ -92,7 +92,7 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
} }
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<#return_type, MemoryDatabase> { fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<#return_type, MemoryDatabase> {
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap() Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap()
} }
fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (String, Option<String>), TestClient) { fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (String, Option<String>), TestClient) {