]> Untitled Git - bdk/commitdiff
[descriptor] Improve the descriptor macro, add traits for key and descriptor types
authorAlekos Filini <alekos.filini@gmail.com>
Fri, 18 Sep 2020 14:31:03 +0000 (16:31 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Thu, 24 Sep 2020 07:53:42 +0000 (09:53 +0200)
src/descriptor/dsl.rs
src/descriptor/mod.rs
src/keys/mod.rs [new file with mode: 0644]
src/lib.rs
src/wallet/export.rs
src/wallet/mod.rs
testutils-macros/src/lib.rs

index a70bc75da33bbfd43aeef8ecabcb2903d984f2f5..5dd8433561078348788c471dd80f9211594a200c 100644 (file)
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 
-//! Descriptor DSL
+//! Descriptors DSL
 
-/// Domain specific language to write descriptors with code
-///
-/// This macro must be called in a function that returns a `Result<_, E: From<miniscript::Error>>`.
+#[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
 ///
-/// 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".
+/// This macro expands to an object of type `Result<(Descriptor<DescriptorPublicKey>, KeyMap), Error>`.
 ///
 /// ## Example
 ///
 ///
 /// ```
 /// # use std::str::FromStr;
-/// # use miniscript::descriptor::DescriptorPublicKey;
-/// let my_key = DescriptorPublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c").unwrap();
+/// let my_key = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
 /// let my_timelock = 50;
-/// let my_descriptor = bdk::desc!(sh ( wsh ( and_v (+v pk my_key), ( older my_timelock ))));
-/// # Ok::<(), bdk::Error>(())
+/// let (my_descriptor, my_keys_map) = bdk::descriptor!(sh ( wsh ( and_v (+v pk my_key), ( older my_timelock ))))?;
+/// # 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
 /// 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.
 ///
 /// ```
 /// # use std::str::FromStr;
-/// # use miniscript::descriptor::DescriptorPublicKey;
-/// let my_key = DescriptorPublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c").unwrap();
+/// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
+/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
 /// let my_timelock = 50;
 ///
-/// let descriptor_a = bdk::desc! {
+/// let (descriptor_a, key_map_a) = bdk::descriptor! {
 ///     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![
-///     bdk::desc!(pk my_key.clone()),
-///     bdk::desc!(+s pk my_key.clone()),
-///     bdk::desc!(+s+d+v older my_timelock),
+///     bdk::fragment!(pk my_key_1)?,
+///     bdk::fragment!(+s pk my_key_2)?,
+///     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);
-/// # Ok::<(), bdk::Error>(())
+/// assert_eq!(key_map_a.len(), key_map_b.len());
+/// # Ok::<(), Box<dyn std::error::Error>>(())
 /// ```
 #[macro_export]
-macro_rules! desc {
-    // Descriptor
+macro_rules! descriptor {
     ( 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 )* ) ) ) => ({
-        $crate::desc!(shwsh ($( $minisc )*))
+        $crate::descriptor!(shwsh ($( $minisc )*))
     });
     ( shwsh ( $( $minisc:tt )* ) ) => ({
-        $crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::ShWsh($crate::desc!($( $minisc )*))
+        $crate::impl_top_level_sh!(ShWsh, $( $minisc )*)
     });
-    ( pkr $key:expr ) => ({
-        $crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Pk($key)
+    ( pk $key:expr ) => ({
+        $crate::impl_top_level_pk!(Pk, $key)
     });
     ( pkh $key:expr ) => ({
-        $crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Pkh($key)
+        $crate::impl_top_level_pk!(Pkh, $key)
     });
     ( wpkh $key:expr ) => ({
-        $crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Wpkh($key)
+        $crate::impl_top_level_pk!(Wpkh, $key)
     });
     ( sh ( wpkh ( $key:expr ) ) ) => ({
-        $crate::desc!(shwpkh ($( $minisc )*))
+        $crate::descriptor!(shwpkh ($( $minisc )*))
     });
     ( shwpkh ( $key:expr ) ) => ({
-        $crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::ShWpkh($key)
+        $crate::impl_top_level_pk!(ShWpkh, $key)
     });
     ( sh ( $( $minisc:tt )* ) ) => ({
-        $crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::Sh($crate::desc!($( $minisc )*))
+        $crate::impl_top_level_sh!(Sh, $( $minisc )*)
     });
     ( 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
     ( +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 )* ) => ({
-        $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 )* ) => ({
-        $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 )* ) => ({
-        $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 )* ) => ({
-        $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 )* ) => ({
-        $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 )* ) => ({
-        $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 )* ) => ({
-        $crate::desc!(and_v ( $( $inner )* ), ( true ) )
+        $crate::fragment!(and_v ( $( $inner )* ), ( true ) )
     });
     ( +l $( $inner:tt )* ) => ({
-        $crate::desc!(or_i ( false ), ( $( $inner )* ) )
+        $crate::fragment!(or_i ( false ), ( $( $inner )* ) )
     });
     ( +u $( $inner:tt )* ) => ({
-        $crate::desc!(or_i ( $( $inner )* ), ( false ) )
+        $crate::fragment!(or_i ( $( $inner )* ), ( false ) )
     });
 
     // Miniscript
     ( true ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::True)?
+        $crate::impl_leaf_opcode!(True)
     });
     ( false ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::False)?
+        $crate::impl_leaf_opcode!(False)
     });
     ( 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 ) => ({
-        $crate::desc!(+c pk_k $key)
+        $crate::fragment!(+c pk_k $key)
     });
     ( 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 ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::After($value))?
+        $crate::impl_leaf_opcode_value!(After, $value)
     });
     ( 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 ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Sha256($hash))?
+        $crate::impl_leaf_opcode_value!(Sha256, $hash)
     });
     ( 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 ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Ripemd160($hash))?
+        $crate::impl_leaf_opcode_value!(Ripemd160, $hash)
     });
     ( 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 )* ) ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::AndV(
-            std::sync::Arc::new($crate::desc!($( $a )*)),
-            std::sync::Arc::new($crate::desc!($( $b )*)),
-        ))?
+        $crate::impl_node_opcode_two!(AndV, ( $( $a )* ), ( $( $b )* ))
     });
     ( and_b ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::AndB(
-            std::sync::Arc::new($crate::desc!($( $a )*)),
-            std::sync::Arc::new($crate::desc!($( $b )*))),
-        )?
+        $crate::impl_node_opcode_two!(AndB, ( $( $a )* ), ( $( $b )* ))
     });
     ( and_or ( $( $a:tt )* ), ( $( $b:tt )* ), ( $( $c:tt )* ) ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::AndOr(
-            std::sync::Arc::new($crate::desc!($( $a )*)),
-            std::sync::Arc::new($crate::desc!($( $b )*)),
-            std::sync::Arc::new($crate::desc!($( $c )*)),
-        ))?
+        $crate::impl_node_opcode_three!(AndOr, ( $( $a )* ), ( $( $b )* ), ( $( $c )* ))
     });
     ( or_b ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::OrB(
-            std::sync::Arc::new($crate::desc!($( $a )*)),
-            std::sync::Arc::new($crate::desc!($( $b )*)),
-        ))?
+        $crate::impl_node_opcode_two!(OrB, ( $( $a )* ), ( $( $b )* ))
     });
     ( or_d ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::OrD(
-            std::sync::Arc::new($crate::desc!($( $a )*)),
-            std::sync::Arc::new($crate::desc!($( $b )*)),
-        ))?
+        $crate::impl_node_opcode_two!(OrD, ( $( $a )* ), ( $( $b )* ))
     });
     ( or_c ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::OrC(
-            std::sync::Arc::new($crate::desc!($( $a )*)),
-            std::sync::Arc::new($crate::desc!($( $b )*)),
-        ))?
+        $crate::impl_node_opcode_two!(OrC, ( $( $a )* ), ( $( $b )* ))
     });
     ( or_i ( $( $a:tt )* ), ( $( $b:tt )* ) ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::OrI(
-            std::sync::Arc::new($crate::desc!($( $a )*)),
-            std::sync::Arc::new($crate::desc!($( $b )*)),
-        ))?
+        $crate::impl_node_opcode_two!(OrI, ( $( $a )* ), ( $( $b )* ))
     });
     ( thresh_vec $thresh:expr, $items:expr ) => ({
-        let items = $items.into_iter().map(std::sync::Arc::new).collect();
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Thresh($thresh, items))?
+        use $crate::miniscript::descriptor::KeyMap;
+
+        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 )* ) )+ ) => ({
         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_vec $thresh:expr, $keys:expr ) => ({
+        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, $keys:expr ) => ({
-        $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::Multi($thresh, $keys))?
+    ( multi $thresh:expr $(, $key:expr )+ ) => ({
+        let mut keys = vec![];
+        $(
+            keys.push($key);
+        )*
+
+        $crate::fragment!(multi_vec $thresh, keys)
     });
+
 }
index 51ee48d9f2a70957a8704ed1a2f71eb9bb148d4c..10283703996c3680638645abba10b919794b9404 100644 (file)
@@ -37,7 +37,7 @@ use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
 use bitcoin::util::psbt;
 use bitcoin::{PublicKey, Script, TxOut};
 
-use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey};
+use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey, KeyMap};
 pub use miniscript::{
     Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, ToPublicKey,
 };
@@ -62,6 +62,35 @@ pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
 /// [`psbt::Output`]: bitcoin::util::psbt::Output
 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`]
 pub trait ExtractPolicy {
     fn extract_policy(
diff --git a/src/keys/mod.rs b/src/keys/mod.rs
new file mode 100644 (file)
index 0000000..be036c8
--- /dev/null
@@ -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,
+        }))
+    }
+}
index b1e3d5a9f7beb801b1cc237abaace467ef8c2840..5150045473788376e5fadfd76d463542b9c33240 100644 (file)
@@ -75,6 +75,7 @@ pub mod database;
 pub mod descriptor;
 #[cfg(feature = "test-md-docs")]
 mod doctest;
+pub mod keys;
 pub(crate) mod psbt;
 pub(crate) mod types;
 pub mod wallet;
index 73ab9ca966bd1937b558237e655800050408ee29..9f4c3f97655cb25c17d1e6f1daa1d38ef7d3af5a 100644 (file)
@@ -43,7 +43,7 @@
 //! }"#;
 //!
 //! 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>(())
 //! ```
 //!
index d84a62063146060d3f33640162a2a4e318590e43..91f8ad8a380b45914e210ffbe21385d43dac3662 100644 (file)
@@ -62,6 +62,7 @@ use crate::blockchain::{Blockchain, BlockchainMarker, OfflineBlockchain, Progres
 use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
 use crate::descriptor::{
     get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
+    ToWalletDescriptor,
 };
 use crate::error::Error;
 use crate::psbt::PSBTUtils;
@@ -106,26 +107,26 @@ where
     D: BatchDatabase,
 {
     /// Create a new "offline" wallet
-    pub fn new_offline(
-        descriptor: &str,
-        change_descriptor: Option<&str>,
+    pub fn new_offline<E: ToWalletDescriptor>(
+        descriptor: E,
+        change_descriptor: Option<E>,
         network: Network,
         mut database: D,
     ) -> Result<Self, Error> {
+        let (descriptor, keymap) = descriptor.to_wallet_descriptor()?;
         database.check_descriptor_checksum(
             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 (change_descriptor, change_signers) = match change_descriptor {
             Some(desc) => {
+                let (change_descriptor, change_keymap) = desc.to_wallet_descriptor()?;
                 database.check_descriptor_checksum(
                     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));
                 // if !parsed.same_structure(descriptor.as_ref()) {
                 //     return Err(Error::DifferentDescriptorStructure);
@@ -1081,9 +1082,9 @@ where
 {
     /// Create a new "online" wallet
     #[maybe_async]
-    pub fn new(
-        descriptor: &str,
-        change_descriptor: Option<&str>,
+    pub fn new<E: ToWalletDescriptor>(
+        descriptor: E,
+        change_descriptor: Option<E>,
         network: Network,
         database: D,
         client: B,
index 18268c9b1f11d0e30d8df04cce52144f4b0aa2e0..7dfcb0ec31dea504bc2d80b1b4fb2ef1b82c5a8b 100644 (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> {
-                    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) {