From b294b11c5472a17a1bafeaaa5abd7f4ea69aaa92 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Thu, 24 Sep 2020 09:52:59 +0200 Subject: [PATCH] [keys] Add a trait for keys that can be generated --- src/keys/bip39.rs | 49 +++++++++++++++++++-- src/keys/mod.rs | 109 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 3 deletions(-) diff --git a/src/keys/bip39.rs b/src/keys/bip39.rs index 7a1a0c34..c737ec01 100644 --- a/src/keys/bip39.rs +++ b/src/keys/bip39.rs @@ -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); @@ -76,13 +76,32 @@ impl DerivableKey for Mnemonic { } } +impl GeneratableKey for Mnemonic { + const ENTROPY_LENGTH: usize = 32; + + type Options = (MnemonicType, Language); + type Error = Option; + + fn generate_with_entropy>( + (mnemonic_type, language): Self::Options, + entropy: E, + ) -> Result, 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"); + } } diff --git a/src/keys/mod.rs b/src/keys/mod.rs index cb153e9d..bc7c04e6 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -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 DerivableKey for bip32::ExtendedPrivKey { } } +/// Output of a [`GeneratableKey`] key generation +pub struct GeneratedKey { + key: K, + valid_networks: ValidNetworks, + phantom: PhantomData, +} + +impl GeneratedKey { + pub fn new(key: K, valid_networks: ValidNetworks) -> Self { + GeneratedKey { + key, + valid_networks, + phantom: PhantomData, + } + } +} + +impl Deref for GeneratedKey { + type Target = K; + + fn deref(&self) -> &Self::Target { + &self.key + } +} + +impl DerivableKey for GeneratedKey +where + Ctx: ScriptContext, + K: GeneratableKey + DerivableKey, +{ + fn add_metadata( + self, + source: Option<(bip32::Fingerprint, bip32::DerivationPath)>, + derivation_path: bip32::DerivationPath, + ) -> Result, 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: 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>( + options: Self::Options, + entropy: E, + ) -> Result, Self::Error>; + + /// Generate a key given the options with a random entropy + fn generate(options: Self::Options) -> Result, Self::Error> { + use rand::{thread_rng, Rng}; + + let mut entropy = Vec::::with_capacity(Self::ENTROPY_LENGTH); + thread_rng().fill(&mut entropy[..]); + + Self::generate_with_entropy(options, &entropy) + } +} + +impl GeneratableKey for bip32::ExtendedPrivKey { + const ENTROPY_LENGTH: usize = 32; + + type Options = (); + type Error = bip32::Error; + + fn generate_with_entropy>( + _: Self::Options, + entropy: E, + ) -> Result, 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> ToDescriptorKey for (T, bip32::DerivationPath) { fn to_descriptor_key(self) -> Result, 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 { + [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"); + } +}