]> Untitled Git - bdk/commitdiff
[keys] Add BIP39 support
authorAlekos Filini <alekos.filini@gmail.com>
Fri, 18 Sep 2020 15:26:58 +0000 (17:26 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Thu, 24 Sep 2020 07:53:46 +0000 (09:53 +0200)
.travis.yml
Cargo.toml
src/descriptor/dsl.rs
src/keys/bip39.rs [new file with mode: 0644]
src/keys/mod.rs

index 9efcb3f0a2f82cf862f79424d14a0a5445e6768e..7eb4b61e50fc959553db2c555fa7d8a37bec909e 100644 (file)
@@ -10,6 +10,7 @@ env:
     - TARGET=x86_64-unknown-linux-gnu    CHECK_FMT=1
     - TARGET=x86_64-unknown-linux-gnu    RUN_TESTS=1
     - TARGET=x86_64-unknown-linux-gnu    FEATURES=minimal            NO_DEFAULT_FEATURES=1
+    - TARGET=x86_64-unknown-linux-gnu    FEATURES=all-keys           NO_DEFAULT_FEATURES=1
     - TARGET=x86_64-unknown-linux-gnu    FEATURES=minimal,esplora    NO_DEFAULT_FEATURES=1
     - TARGET=x86_64-unknown-linux-gnu    FEATURES=key-value-db       NO_DEFAULT_FEATURES=1
     - TARGET=x86_64-unknown-linux-gnu    FEATURES=electrum           NO_DEFAULT_FEATURES=1
index 9d12a8ebfdf73ca0631b8ff10e5641a5fa3cb4bb..8d18e379f7329e78be02596586936f3f0c6f974a 100644 (file)
@@ -24,6 +24,7 @@ async-trait = { version = "0.1", optional = true }
 rocksdb = { version = "0.14", optional = true }
 socks = { version = "0.3", optional = true }
 lazy_static = { version = "1.4", optional = true }
+tiny-bip39 = { version = "^0.7", optional = true }
 
 [patch.crates-io]
 bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin/", rev = "478e091" }
@@ -48,6 +49,8 @@ compact_filters = ["rocksdb", "socks", "lazy_static"]
 key-value-db = ["sled"]
 cli-utils = ["clap", "base64"]
 async-interface = ["async-trait"]
+all-keys = ["keys-bip39"]
+keys-bip39 = ["tiny-bip39"]
 
 # Debug/Test features
 debug-proc-macros = ["bdk-macros/debug", "bdk-testutils-macros/debug"]
index 5dd8433561078348788c471dd80f9211594a200c..d31b71935e90920997f2a5d5950b71e56230ee59 100644 (file)
@@ -39,7 +39,7 @@ macro_rules! impl_top_level_pk {
     ( $descriptor_variant:ident, $key:expr ) => {{
         use $crate::keys::ToDescriptorKey;
         $key.to_descriptor_key()
-            .into_key_and_secret()
+            .and_then(|key| key.into_key_and_secret())
             .map(|(pk, key_map)| {
                 (
                     $crate::miniscript::Descriptor::<
@@ -181,6 +181,23 @@ macro_rules! impl_node_opcode_three {
 /// assert_eq!(key_map_a.len(), key_map_b.len());
 /// # Ok::<(), Box<dyn std::error::Error>>(())
 /// ```
+///
+/// ------
+///
+/// Simple 2-of-2 multi-signature, equivalent to: `wsh(multi(2, ...))`
+///
+/// ```
+/// # use std::str::FromStr;
+/// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
+/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
+///
+/// let (descriptor, key_map) = bdk::descriptor! {
+///     wsh (
+///         multi 2, my_key_1, my_key_2
+///     )
+/// }?;
+/// # Ok::<(), Box<dyn std::error::Error>>(())
+/// ```
 #[macro_export]
 macro_rules! descriptor {
     ( bare ( $( $minisc:tt )* ) ) => ({
@@ -262,7 +279,8 @@ macro_rules! fragment {
     });
     ( pk_k $key:expr ) => ({
         use $crate::keys::ToDescriptorKey;
-        $key.to_descriptor_key().into_key_and_secret()
+        $key.to_descriptor_key()
+            .and_then(|key| key.into_key_and_secret())
             .and_then(|(pk, key_map)| Ok(($crate::impl_leaf_opcode_value!(PkK, pk)?.0, key_map)))
     });
     ( pk $key:expr ) => ({
@@ -337,8 +355,7 @@ macro_rules! fragment {
         use $crate::keys::{ToDescriptorKey, DescriptorKey};
 
         $keys.into_iter()
-            .map(ToDescriptorKey::to_descriptor_key)
-            .map(DescriptorKey::into_key_and_secret)
+            .map(|key| key.to_descriptor_key().and_then(DescriptorKey::into_key_and_secret))
             .collect::<Result<Vec<_>, _>>()
             .map(|items| items.into_iter().unzip())
             .and_then(|(keys, key_maps): (Vec<_>, Vec<_>)| {
@@ -351,12 +368,15 @@ macro_rules! fragment {
             })
     });
     ( multi $thresh:expr $(, $key:expr )+ ) => ({
+        use $crate::keys::ToDescriptorKey;
+
         let mut keys = vec![];
         $(
-            keys.push($key);
+            keys.push($key.to_descriptor_key());
         )*
 
-        $crate::fragment!(multi_vec $thresh, keys)
+        keys.into_iter().collect::<Result<Vec<_>, _>>()
+            .and_then(|keys| $crate::fragment!(multi_vec $thresh, keys))
     });
 
 }
diff --git a/src/keys/bip39.rs b/src/keys/bip39.rs
new file mode 100644 (file)
index 0000000..005d0f9
--- /dev/null
@@ -0,0 +1,94 @@
+// 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.
+
+//! BIP-0039
+
+// TODO: maybe write our own implementation of bip39? Seems stupid to have an extra dependency for
+// something that should be fairly simple to re-implement.
+
+use bitcoin::util::bip32;
+use bitcoin::Network;
+
+use bip39::{Mnemonic, Seed};
+
+use super::{DescriptorKey, ToDescriptorKey};
+use crate::Error;
+
+pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
+
+impl ToDescriptorKey for (Seed, bip32::DerivationPath) {
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.0.as_bytes())?;
+        (xprv, self.1).to_descriptor_key()
+    }
+}
+
+impl ToDescriptorKey for (MnemonicWithPassphrase, bip32::DerivationPath) {
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        let (mnemonic, passphrase) = self.0;
+        let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or(""));
+        (seed, self.1).to_descriptor_key()
+    }
+}
+
+impl ToDescriptorKey for (Mnemonic, bip32::DerivationPath) {
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        ((self.0, None), self.1).to_descriptor_key()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::str::FromStr;
+
+    use bitcoin::util::bip32;
+
+    use bip39::{Language, Mnemonic};
+
+    #[test]
+    fn test_keys_bip39_mnemonic() {
+        let mnemonic =
+            "aim bunker wash balance finish force paper analyst cabin spoon stable organ";
+        let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English).unwrap();
+        let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
+
+        let key = (mnemonic, path);
+        let (desc, keys) = crate::descriptor!(wpkh(key)).unwrap();
+        assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)");
+        assert_eq!(keys.len(), 1);
+    }
+
+    #[test]
+    fn test_keys_bip39_mnemonic_passphrase() {
+        let mnemonic =
+            "aim bunker wash balance finish force paper analyst cabin spoon stable organ";
+        let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English).unwrap();
+        let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
+
+        let key = ((mnemonic, Some("passphrase".into())), path);
+        let (desc, keys) = crate::descriptor!(wpkh(key)).unwrap();
+        assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)");
+        assert_eq!(keys.len(), 1);
+    }
+}
index be036c850f33e9dc9556c5a981b42e5abe23475e..443864a9be5b3d0ebcb55004419b805f40e802e9 100644 (file)
@@ -31,6 +31,11 @@ use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey, Descripto
 
 use crate::Error;
 
+#[cfg(feature = "keys-bip39")]
+#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
+pub mod bip39;
+
+/// Container for public or secret keys
 pub enum DescriptorKey {
     Public(DescriptorPublicKey),
     Secret(DescriptorSecretKey),
@@ -55,52 +60,64 @@ impl DescriptorKey {
     }
 }
 
+/// Trait for objects that can be turned into a public or secret [`DescriptorKey`]
 pub trait ToDescriptorKey {
-    fn to_descriptor_key(self) -> DescriptorKey;
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error>;
+}
+
+/// Identity conversion. This is used internally by [`bdk::fragment`]
+impl ToDescriptorKey for DescriptorKey {
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        Ok(self)
+    }
 }
 
 impl ToDescriptorKey for DescriptorPublicKey {
-    fn to_descriptor_key(self) -> DescriptorKey {
-        DescriptorKey::Public(self)
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        Ok(DescriptorKey::Public(self))
     }
 }
 
 impl ToDescriptorKey for PublicKey {
-    fn to_descriptor_key(self) -> DescriptorKey {
-        DescriptorKey::Public(DescriptorPublicKey::PubKey(self))
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        Ok(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 (bip32::ExtendedPubKey, bip32::DerivationPath) {
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        Ok(DescriptorKey::Public(DescriptorPublicKey::XPub(
+            DescriptorXKey {
+                source: None,
+                xkey: self.0,
+                derivation_path: self.1,
+                is_wildcard: true,
+            },
+        )))
     }
 }
 
 impl ToDescriptorKey for DescriptorSecretKey {
-    fn to_descriptor_key(self) -> DescriptorKey {
-        DescriptorKey::Secret(self)
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        Ok(DescriptorKey::Secret(self))
     }
 }
 
 impl ToDescriptorKey for PrivateKey {
-    fn to_descriptor_key(self) -> DescriptorKey {
-        DescriptorKey::Secret(DescriptorSecretKey::PrivKey(self))
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        Ok(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,
-        }))
+impl ToDescriptorKey for (bip32::ExtendedPrivKey, bip32::DerivationPath) {
+    fn to_descriptor_key(self) -> Result<DescriptorKey, Error> {
+        Ok(DescriptorKey::Secret(DescriptorSecretKey::XPrv(
+            DescriptorXKey {
+                source: None,
+                xkey: self.0,
+                derivation_path: self.1,
+                is_wildcard: true,
+            },
+        )))
     }
 }