From 0684fe7b3694f382ae89b4b157cb22022e16a471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anler=20Hern=C3=A1ndez=20Peral?= Date: Mon, 25 Feb 2019 18:44:14 +0100 Subject: [PATCH 1/2] feat(wallet): BIP39 Mnemonic and BIP32 Master Key generation --- wallet/Cargo.toml | 7 +- wallet/src/key.rs | 122 ++++++++++++++++++++++++++++++++ wallet/src/lib.rs | 3 + wallet/src/mnemonic.rs | 155 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 wallet/src/key.rs create mode 100644 wallet/src/mnemonic.rs diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 63d823b38..105019fc8 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -6,5 +6,8 @@ version = "0.1.0" workspace = ".." [dependencies] -bip39 = "*" -tiny-hderive = "*" +bip39 = "0.6.0-beta.1" +failure = "*" +hmac = "*" +secp256k1 = "*" +sha2 = "*" diff --git a/wallet/src/key.rs b/wallet/src/key.rs new file mode 100644 index 000000000..0adbcbe47 --- /dev/null +++ b/wallet/src/key.rs @@ -0,0 +1,122 @@ +//! # BIP32 Key generation and derivation +//! +//! Example +//! +//! ``` +//! # use witnet_wallet::mnemonic; +//! # use witnet_wallet::key; +//! let passphrase = ""; +//! let seed = mnemonic::MnemonicGen::new().generate().seed(passphrase); +//! let ext_key = key::MasterKeyGen::new(seed).generate(); +//! ``` + +use failure::Fail; +use hmac::{Hmac, Mac}; +use secp256k1; +use sha2; + +/// Default HMAC key used when generating a Master Key with +/// [generate_master](generate_master) +pub static DEFAULT_HMAC_KEY: &str = "Witnet seed"; + +/// secp256k1 Secret Key with Chain Code +pub type ExtendedSK = ExtendedKey; + +/// secp256k1 Public Key with Chain Code +pub type ExtendedPK = ExtendedKey; + +/// The error type for [generate_master](generate_master) +#[derive(Debug, PartialEq, Fail)] +pub enum MasterKeyGenError { + /// Invalid hmac key length + #[fail(display = "The length of the hmac key is invalid")] + InvalidKeyLength, + /// Invalid seed length + #[fail(display = "The length of the seed is invalid, must be between 128/256 bits")] + InvalidSeedLength, +} + +/// BIP32 Master Secret Key generator +pub struct MasterKeyGen<'a, S> { + seed: S, + key: &'a str, +} + +impl<'a, S> MasterKeyGen<'a, S> +where + S: AsRef<[u8]>, +{ + /// Create a new master key generator + pub fn new(seed: S) -> Self { + Self { + key: DEFAULT_HMAC_KEY, + seed: seed, + } + } + + /// Use the given key as the HMAC key + pub fn with_key(mut self, key: &'a str) -> Self { + self.key = key; + self + } + + /// Consume this generator and return the BIP32 extended Master Secret Key + /// [Extended Key](ExtendedSK) + pub fn generate(self) -> Result { + let seed_bytes = self.seed.as_ref(); + let seed_len = seed_bytes.len(); + + if seed_len < 16 || seed_len > 64 { + Err(MasterKeyGenError::InvalidSeedLength)? + } + + let key_bytes = self.key.as_ref(); + let mut mac = Hmac::::new_varkey(key_bytes) + .map_err(|_| MasterKeyGenError::InvalidKeyLength)?; + mac.input(seed_bytes); + let result = mac.result().code(); + let (sk_bytes, chain_code_bytes) = result.split_at(32); + + // secret/chain_code computation might panic if length returned by hmac is wrong + let secret = secp256k1::SecretKey::from_slice(sk_bytes).expect("Secret Key length error"); + let mut chain_code = [0u8; 32]; + chain_code.copy_from_slice(chain_code_bytes); + + Ok(ExtendedKey { secret, chain_code }) + } +} + +/// Extended Key is just a Key with a Chain Code +pub struct ExtendedKey { + pub secret: K, + pub chain_code: [u8; 32], +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_master_invalid_seed() { + let seed = "too short seed"; + let result = MasterKeyGen::new(seed).generate(); + + assert!(result.is_err()); + } + + #[test] + fn test_generate_master() { + let seed = [0; 32]; + let result = MasterKeyGen::new(seed).generate(); + + assert!(result.is_ok()); + } + + #[test] + fn test_generate_master_other_key() { + let seed = [0; 32]; + let result = MasterKeyGen::new(seed).with_key("Bitcoin seed").generate(); + + assert!(result.is_ok()); + } +} diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 45c6641ec..57826de17 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1 +1,4 @@ //! Wallet implementation for Witnet + +pub mod key; +pub mod mnemonic; diff --git a/wallet/src/mnemonic.rs b/wallet/src/mnemonic.rs new file mode 100644 index 000000000..27aff1b6e --- /dev/null +++ b/wallet/src/mnemonic.rs @@ -0,0 +1,155 @@ +//! # BIP39 Mnemonic and Seed generation +//! +//! Example +//! +//! ``` +//! # use witnet_wallet::mnemonic::MnemonicGen; +//! let mnemonic = MnemonicGen::new().generate(); +//! +//! // A Mnemonic Seed must be protected by a passphrase +//! let passphrase = ""; +//! +//! // String of mnemonic words +//! let words = mnemonic.words(); +//! // Seed that can be used to generate a master secret key +//! let seed = mnemonic.seed(passphrase); +//! ``` +use bip39; + +/// BIP39 Mnemonic +pub struct Mnemonic(bip39::Mnemonic); + +impl Mnemonic { + /// Get the list of mnemonic words + pub fn words(&self) -> &str { + self.0.phrase() + } + + /// Get the binary seed used for generating a master secret key + pub fn seed<'a, S: Into<&'a str>>(&self, passphrase: S) -> Seed { + Seed(bip39::Seed::new(&self.0, passphrase.into())) + } +} + +/// BIP39 Seed generated from a Mnemonic +pub struct Seed(bip39::Seed); + +impl Seed { + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl AsRef<[u8]> for Seed { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +/// Number of words of the Mnemonic +/// +/// The number of words of the Mnemonic is proportional to the +/// entropy: +/// +/// * `128 bits` generates `12 words` mnemonic +/// * `160 bits` generates `15 words` mnemonic +/// * `192 bits` generates `18 words` mnemonic +/// * `224 bits` generates `21 words` mnemonic +/// * `256 bits` generates `24 words` mnemonic +#[derive(Debug, PartialEq)] +pub enum Length { + Words12, + Words15, + Words18, + Words21, + Words24, +} + +/// The language in which Mnemonics are generated +#[derive(Debug, PartialEq)] +pub enum Lang { + English, +} + +/// BIP39 Mnemonic generator +pub struct MnemonicGen { + len: Length, + lang: Lang, +} + +impl MnemonicGen { + /// Create a new BIP39 Mnemonic generator + pub fn new() -> Self { + MnemonicGen { + len: Length::Words12, + lang: Lang::English, + } + } + + /// Set how many words the Mnemonic should have + pub fn with_len(mut self, len: Length) -> Self { + self.len = len; + self + } + + /// Set which language to use in the Mnemonic words + pub fn with_lang(mut self, lang: Lang) -> Self { + self.lang = lang; + self + } + + /// Consume this generator and return the BIP39 Mnemonic + pub fn generate(self) -> Mnemonic { + let mnemonic_type = match self.len { + Length::Words12 => bip39::MnemonicType::Words12, + Length::Words15 => bip39::MnemonicType::Words15, + Length::Words18 => bip39::MnemonicType::Words18, + Length::Words21 => bip39::MnemonicType::Words21, + Length::Words24 => bip39::MnemonicType::Words24, + }; + let lang = match self.lang { + Lang::English => bip39::Language::English, + }; + let mnemonic = bip39::Mnemonic::new(mnemonic_type, lang); + + Mnemonic(mnemonic) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_gen_default() { + let gen = MnemonicGen::new(); + + assert_eq!(gen.len, Length::Words12); + assert_eq!(gen.lang, Lang::English); + } + + #[test] + fn test_gen_with_len() { + let gen = MnemonicGen::new().with_len(Length::Words24); + + assert_eq!(gen.len, Length::Words24); + assert_eq!(gen.lang, Lang::English); + } + + #[test] + fn test_generate() { + let mnemonic = MnemonicGen::new().generate(); + let words: Vec<&str> = mnemonic.words().split_whitespace().collect(); + + assert_eq!(words.len(), 12); + } + + #[test] + fn test_seed_as_ref() { + let mnemonic = MnemonicGen::new().generate(); + let seed = mnemonic.seed(""); + let bytes: &[u8] = seed.as_ref(); + + assert_eq!(bytes, seed.as_bytes()); + } +} From ebf037886b48125371a2dcc8b1661e54c2f65965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anler=20Hern=C3=A1ndez=20Peral?= Date: Mon, 4 Mar 2019 00:56:13 +0100 Subject: [PATCH 2/2] wip: see wallet/slip32.rs --- wallet/Cargo.toml | 3 ++ wallet/src/key.rs | 45 +++++++++++++++++++++++++---- wallet/src/lib.rs | 1 + wallet/src/slip32.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 wallet/src/slip32.rs diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 105019fc8..39850bb86 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -11,3 +11,6 @@ failure = "*" hmac = "*" secp256k1 = "*" sha2 = "*" +bech32 = "*" +tiny-hderive = "*" +endianrw = "*" diff --git a/wallet/src/key.rs b/wallet/src/key.rs index 0adbcbe47..2447cc097 100644 --- a/wallet/src/key.rs +++ b/wallet/src/key.rs @@ -14,16 +14,48 @@ use failure::Fail; use hmac::{Hmac, Mac}; use secp256k1; use sha2; +use tiny_hderive; /// Default HMAC key used when generating a Master Key with /// [generate_master](generate_master) pub static DEFAULT_HMAC_KEY: &str = "Witnet seed"; -/// secp256k1 Secret Key with Chain Code -pub type ExtendedSK = ExtendedKey; +/// BIP32 Key +pub trait Key {} -/// secp256k1 Public Key with Chain Code -pub type ExtendedPK = ExtendedKey; +/// BIP32 Secret Key +pub struct SK(secp256k1::SecretKey); + +struct SKSlip32Serializer; + +/// BIP32 Public Key +pub struct PK(secp256k1::PublicKey); + +/// BIP32 extended Secret Key +pub type ExtendedSK = ExtendedKey; + +/// BIP32 extended Public Key +pub type ExtendedPK = ExtendedKey; + +/// BIP32 chain code which is the entropy used in extended keys +pub type ChainCode = [u8; 32]; + +/// The path of a key inside an HD Wallet +pub struct KeyPath(tiny_hderive::bip44::DerivationPath); + +/// A child number for a derived key +pub type ChildNumber = tiny_hderive::bip44::ChildNumber; + +impl KeyPath { + /// Get the depth/level of the derived key + pub fn depth(&self) -> usize { + self.0.as_ref().len() + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } +} /// The error type for [generate_master](generate_master) #[derive(Debug, PartialEq, Fail)] @@ -78,7 +110,8 @@ where let (sk_bytes, chain_code_bytes) = result.split_at(32); // secret/chain_code computation might panic if length returned by hmac is wrong - let secret = secp256k1::SecretKey::from_slice(sk_bytes).expect("Secret Key length error"); + let secret = + SK(secp256k1::SecretKey::from_slice(sk_bytes).expect("Secret Key length error")); let mut chain_code = [0u8; 32]; chain_code.copy_from_slice(chain_code_bytes); @@ -89,7 +122,7 @@ where /// Extended Key is just a Key with a Chain Code pub struct ExtendedKey { pub secret: K, - pub chain_code: [u8; 32], + pub chain_code: ChainCode, } #[cfg(test)] diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 57826de17..f73659e6b 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -2,3 +2,4 @@ pub mod key; pub mod mnemonic; +pub mod slip32; diff --git a/wallet/src/slip32.rs b/wallet/src/slip32.rs new file mode 100644 index 000000000..6a0b8c181 --- /dev/null +++ b/wallet/src/slip32.rs @@ -0,0 +1,68 @@ +//! Slip32 implementation + +use crate::key; + +/// Byte sizes for each field used in the serialization format +// pub struct SerializationFormat { +// version: u8, +// depth: u8, +// fingerpint: u8, +// child_number: u8, +// chain_code: u8, +// key: u8 +// } + +/// Export a BIP32 extended key to the SLIP32 format +pub fn export(path: key::KeyPath, extkey: key::ExtendedKey) -> String +where + K: key::Key, +{ + let depth = path.depth(); + let mut buffer = Vec::with_capacity(buffer_size(depth)); + + // 1 byte for depth + buffer[0] = depth as u8; + + // 4 bytes for each child number in the derivation path + for (level, child_number) in path.iter().enumerate() { + let start = 1 + level * 4; + let end = start + 4; + buffer.splice(start..end, child_number.to_bytes().iter().cloned()); + } + + // 32 bytes for chain code + { + let start = 1 + depth * 4; + let end = start + 32; + buffer.splice(start..end, extkey.chain_code.iter().cloned()); + } + + // 33 bytes for key + // + // TODO: Find a way to get access to secp256k1::SecretKey bytes, + // at the moment it requires creating a custom serde Serializer + { + let start = 33 + depth * 4; + let end = start + 33; + // buffer.splice(start..end, extkey.key... + } + + unimplemented!() +} + +/// Import a BIP32 extended key in the SLIP32 format +pub fn import(exported: String) -> key::ExtendedKey +where + K: key::Key, +{ + unimplemented!() +} + +/// Calculate the size of the buffer that will contain the serialized key. +/// 1 byte: depth +/// 32 bytes: chain code +/// 33 bytes: the key +/// 4 bytes for each key in the keypath +fn buffer_size(depth: usize) -> usize { + 66 + depth * 4 +}