]> Untitled Git - bdk/commitdiff
[keys] Add a trait for keys that can be generated
authorAlekos Filini <alekos.filini@gmail.com>
Thu, 24 Sep 2020 07:52:59 +0000 (09:52 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Thu, 24 Sep 2020 07:53:56 +0000 (09:53 +0200)
src/keys/bip39.rs
src/keys/mod.rs

index 7a1a0c343ab4a362ce9810a3d7c3ad7a82ff9719..c737ec011f3f5b0c4425f5562bf0d6925b59356c 100644 (file)
@@ -32,9 +32,9 @@ use bitcoin::Network;
 
 use miniscript::ScriptContext;
 
-use bip39::{Mnemonic, Seed};
+use bip39::{Language, Mnemonic, MnemonicType, Seed};
 
-use super::{any_network, DerivableKey, DescriptorKey, KeyError};
+use super::{any_network, DerivableKey, DescriptorKey, GeneratableKey, GeneratedKey, KeyError};
 
 pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
 
@@ -76,13 +76,32 @@ impl<Ctx: ScriptContext> DerivableKey<Ctx> for Mnemonic {
     }
 }
 
+impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
+    const ENTROPY_LENGTH: usize = 32;
+
+    type Options = (MnemonicType, Language);
+    type Error = Option<bip39::ErrorKind>;
+
+    fn generate_with_entropy<E: AsRef<[u8]>>(
+        (mnemonic_type, language): Self::Options,
+        entropy: E,
+    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        let entropy = &entropy.as_ref()[..(mnemonic_type.entropy_bits() / 8)];
+        let mnemonic = Mnemonic::from_entropy(entropy, language).map_err(|e| e.downcast().ok())?;
+
+        Ok(GeneratedKey::new(mnemonic, any_network()))
+    }
+}
+
 #[cfg(test)]
 mod test {
     use std::str::FromStr;
 
     use bitcoin::util::bip32;
 
-    use bip39::{Language, Mnemonic};
+    use bip39::{Language, Mnemonic, MnemonicType};
+
+    use crate::keys::{any_network, GeneratableKey, GeneratedKey};
 
     #[test]
     fn test_keys_bip39_mnemonic() {
@@ -111,4 +130,28 @@ mod test {
         assert_eq!(keys.len(), 1);
         assert_eq!(networks.len(), 3);
     }
+
+    #[test]
+    fn test_keys_generate_bip39() {
+        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
+            Mnemonic::generate_with_entropy(
+                (MnemonicType::Words12, Language::English),
+                crate::keys::test::get_test_entropy(),
+            )
+            .unwrap();
+        assert_eq!(generated_mnemonic.valid_networks, any_network());
+        assert_eq!(
+            generated_mnemonic.to_string(),
+            "primary fetch primary fetch primary fetch primary fetch primary fetch primary fever"
+        );
+
+        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
+            Mnemonic::generate_with_entropy(
+                (MnemonicType::Words24, Language::English),
+                crate::keys::test::get_test_entropy(),
+            )
+            .unwrap();
+        assert_eq!(generated_mnemonic.valid_networks, any_network());
+        assert_eq!(generated_mnemonic.to_string(), "primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary foster");
+    }
 }
index cb153e9d8e53ef6337ca79160cadf5a01962d1cb..bc7c04e6fd06470f1ec8b3f2b07e24e7a8951f0e 100644 (file)
@@ -27,6 +27,7 @@
 use std::any::TypeId;
 use std::collections::HashSet;
 use std::marker::PhantomData;
+use std::ops::Deref;
 
 use bitcoin::util::bip32;
 use bitcoin::{Network, PrivateKey, PublicKey};
@@ -322,6 +323,94 @@ impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::ExtendedPrivKey {
     }
 }
 
+/// Output of a [`GeneratableKey`] key generation
+pub struct GeneratedKey<K, Ctx: ScriptContext> {
+    key: K,
+    valid_networks: ValidNetworks,
+    phantom: PhantomData<Ctx>,
+}
+
+impl<K, Ctx: ScriptContext> GeneratedKey<K, Ctx> {
+    pub fn new(key: K, valid_networks: ValidNetworks) -> Self {
+        GeneratedKey {
+            key,
+            valid_networks,
+            phantom: PhantomData,
+        }
+    }
+}
+
+impl<K, Ctx: ScriptContext> Deref for GeneratedKey<K, Ctx> {
+    type Target = K;
+
+    fn deref(&self) -> &Self::Target {
+        &self.key
+    }
+}
+
+impl<Ctx, K> DerivableKey<Ctx> for GeneratedKey<K, Ctx>
+where
+    Ctx: ScriptContext,
+    K: GeneratableKey<Ctx> + DerivableKey<Ctx>,
+{
+    fn add_metadata(
+        self,
+        source: Option<(bip32::Fingerprint, bip32::DerivationPath)>,
+        derivation_path: bip32::DerivationPath,
+    ) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let descriptor_key = self.key.add_metadata(source, derivation_path)?;
+        Ok(descriptor_key.override_valid_networks(self.valid_networks))
+    }
+}
+
+/// Trait for keys that can be generated
+///
+/// The same rules about [`ScriptContext`] and [`ValidNetworks`] from [`ToDescriptorKey`] apply.
+///
+/// This trait is particularly useful when combined with [`DerivableKey`]: if `Self`
+/// implements it, the returned [`GeneratedKey`] will also implement it.
+pub trait GeneratableKey<Ctx: ScriptContext>: Sized {
+    /// Lenght in bytes of the required entropy
+    const ENTROPY_LENGTH: usize;
+
+    /// Extra options required by the `generate_with_entropy`
+    type Options;
+    /// Returned error in case of failure
+    type Error: std::fmt::Debug;
+
+    /// Generate a key given the extra options and the entropy
+    fn generate_with_entropy<E: AsRef<[u8]>>(
+        options: Self::Options,
+        entropy: E,
+    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error>;
+
+    /// Generate a key given the options with a random entropy
+    fn generate(options: Self::Options) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        use rand::{thread_rng, Rng};
+
+        let mut entropy = Vec::<u8>::with_capacity(Self::ENTROPY_LENGTH);
+        thread_rng().fill(&mut entropy[..]);
+
+        Self::generate_with_entropy(options, &entropy)
+    }
+}
+
+impl<Ctx: ScriptContext> GeneratableKey<Ctx> for bip32::ExtendedPrivKey {
+    const ENTROPY_LENGTH: usize = 32;
+
+    type Options = ();
+    type Error = bip32::Error;
+
+    fn generate_with_entropy<E: AsRef<[u8]>>(
+        _: Self::Options,
+        entropy: E,
+    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        // pick a random network here, but say that we support all of them
+        let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, entropy.as_ref())?;
+        Ok(GeneratedKey::new(xprv, any_network()))
+    }
+}
+
 impl<Ctx: ScriptContext, T: DerivableKey<Ctx>> ToDescriptorKey<Ctx> for (T, bip32::DerivationPath) {
     fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
         self.0.add_metadata(None, self.1)
@@ -467,3 +556,23 @@ impl std::fmt::Display for KeyError {
 }
 
 impl std::error::Error for KeyError {}
+
+#[cfg(test)]
+pub mod test {
+    use bitcoin::util::bip32;
+
+    use super::*;
+
+    pub fn get_test_entropy() -> Vec<u8> {
+        [0xAA; 32].to_vec()
+    }
+
+    #[test]
+    fn test_keys_generate_xprv() {
+        let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> =
+            bip32::ExtendedPrivKey::generate_with_entropy((), get_test_entropy()).unwrap();
+
+        assert_eq!(generated_xprv.valid_networks, any_network());
+        assert_eq!(generated_xprv.to_string(), "xprv9s21ZrQH143K4Xr1cJyqTvuL2FWR8eicgY9boWqMBv8MDVUZ65AXHnzBrK1nyomu6wdcabRgmGTaAKawvhAno1V5FowGpTLVx3jxzE5uk3Q");
+    }
+}