From e01815033e30a50db1e1e9c6beda073fbd10998f Mon Sep 17 00:00:00 2001 From: josh Date: Wed, 11 Sep 2019 21:16:51 -0700 Subject: [PATCH] bring key and mnemonic gen more inline with the js and java libraries --- Cargo.lock | 21 +++++++++- Cargo.toml | 6 ++- examples/create_account.rs | 3 +- examples/generate_key.rs | 2 +- examples/update_account.rs | 1 - src/crypto.rs | 79 ++++++++++++++++++++++++++++---------- 6 files changed, 85 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2eafe3d..4a90a4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -408,6 +408,16 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "getrandom" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "glob" version = "0.3.0" @@ -459,20 +469,22 @@ dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "grpc 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "httpbis 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "protoc-rust-grpc 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "query_interface 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1748,6 +1760,11 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.8" @@ -1852,6 +1869,7 @@ dependencies = [ "checksum futures-sink-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "e9b65a2481863d1b78e094a07e9c0eed458cc7dc6e72b22b7138b8a67d924859" "checksum futures-util-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "7df53daff1e98cc024bf2720f3ceb0414d96fbb0a94f3cad3a5c3bf3be1d261c" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" +"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" "checksum grpc 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8e530ef7894a104a1c8525ce68787b3491efa2098ce5e5454e8324ea78893548" "checksum grpc-compiler 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b07f140d998d8940880e464f3fd291052618199432e91e59ae5c5075c0c8a40c" @@ -1992,6 +2010,7 @@ dependencies = [ "checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index 503dc84..aa21757 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rand_core = "0.4.0" sha2 = "0.8.0" sha3 = "0.8.2" hex = "0.3.2" +hmac = "0.7.0" failure = "0.1.5" simple_asn1 = "0.4.0" failure_derive = "0.1.5" @@ -26,13 +27,14 @@ protobuf = "2.8.1" itertools = "0.8.0" chrono = "0.4.9" parking_lot = "0.9.0" +pbkdf2 = { version = "0.3.0", default-features = false } +getrandom = "0.1.12" grpc = "0.6.1" query_interface = "0.3.5" httpbis = "0.7.0" log = "0.4.8" try_from = "0.3.2" -tiny-bip39 = "0.6.2" -rand_chacha = "0.1.1" +tiny-bip39 = { version="0.6.2", default-features = false } tokio = { version = "0.2.0-alpha.4" } futures = { version = "0.3.0-alpha.18", package = "futures-preview", features = [ "compat" ] } diff --git a/examples/create_account.rs b/examples/create_account.rs index 74c468e..d70325c 100644 --- a/examples/create_account.rs +++ b/examples/create_account.rs @@ -1,5 +1,4 @@ use failure::{format_err, Error}; -use futures::FutureExt; use hedera::{Client, SecretKey, Status}; use std::{env, thread::sleep, time::Duration}; @@ -7,7 +6,7 @@ use std::{env, thread::sleep, time::Duration}; async fn main() -> Result<(), Error> { pretty_env_logger::try_init()?; - let (secret, _) = SecretKey::generate(""); + let secret = SecretKey::generate(); let public = secret.public(); println!("secret = {}", secret); diff --git a/examples/generate_key.rs b/examples/generate_key.rs index d9c8b4d..ba8c2f6 100644 --- a/examples/generate_key.rs +++ b/examples/generate_key.rs @@ -1,7 +1,7 @@ use hedera::SecretKey; fn main() { - let (secret, mnemonic) = SecretKey::generate(""); + let (secret, mnemonic) = SecretKey::generate_mnemonic(); let public = secret.public(); println!("secret = {}", secret); diff --git a/examples/update_account.rs b/examples/update_account.rs index 6bc31c2..c4f5044 100644 --- a/examples/update_account.rs +++ b/examples/update_account.rs @@ -1,5 +1,4 @@ use failure::{format_err, Error}; -use futures::FutureExt; use hedera::{Client, Status}; use std::{env, thread::sleep, time::Duration}; diff --git a/src/crypto.rs b/src/crypto.rs index ad98ae4..6635ba1 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,13 +1,11 @@ use crate::proto::{self, ToProto}; -use bip39::{Language, Mnemonic, MnemonicType, Seed}; +use bip39::{Language, Mnemonic}; use ed25519_dalek; use failure::{bail, err_msg, Error}; use failure_derive::Fail; use hex; use num::BigUint; use once_cell::{sync::Lazy}; -use rand_core::SeedableRng; -use rand_chacha::ChaChaRng; use simple_asn1::{ der_decode, der_encode, oid, to_der, ASN1Block, ASN1Class, ASN1DecodeErr, ASN1EncodeErr, FromASN1, ToASN1, OID, @@ -17,6 +15,7 @@ use std::{ str::FromStr, }; use try_from::{TryFrom, TryInto}; +use hmac::Hmac; // Types used for (de-)serializing public and secret keys from ASN.1 byte // streams. @@ -370,25 +369,54 @@ impl TryFrom for PublicKey { pub struct SecretKey(ed25519_dalek::SecretKey); impl SecretKey { - /// Generate a `SecretKey` with a BIP-39 mnemonic using a cryptographically + /// Generate a `SecretKey` with 32 cryptographically random bytes + pub fn generate() -> Self { + let mut buf = [0u8; 32]; + + getrandom::getrandom(&mut buf).expect("Could not retrieve entropy from the os"); + + let bytes = Self::derive_seed(&buf); + + // this should never fail unless getrandom fails which will cause a panic + SecretKey(ed25519_dalek::SecretKey::from_bytes(&bytes).unwrap()) + } + + /// Generate a `SecretKey` with 32 bytes of provided entropy + pub fn generate_from_entropy(entropy: &[u8; 32]) -> Self { + // this should never fail since entropy is required to be a 32-length u8 array + SecretKey(ed25519_dalek::SecretKey::from_bytes(entropy).unwrap()) + } + + /// Generate a `SecretKey` alongside a BIP-39 mnemonic using a cryptographically /// secure random number generator. - /// - /// The `password` is required with the mnemonic to reproduce the secret key. - pub fn generate(password: &str) -> (Self, String) { - let mnemonic = Mnemonic::new(MnemonicType::Words24, Language::English); + pub fn generate_mnemonic() -> (Self, String) { + let mut entropy = [0u8; 32]; - let secret = Self::generate_with_mnemonic(&mnemonic, password); + getrandom::getrandom(&mut entropy).expect("Could not retrieve entropy from the os"); + + // this should never fail as 32 is a valid entropy length + let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); + let secret = Self::generate_from_entropy(&entropy); (secret, mnemonic.into_phrase()) } - fn generate_with_mnemonic(mnemonic: &Mnemonic, password: &str) -> Self { - let mut seed: [u8; 32] = Default::default(); + /// Duplicating what is done here: + /// https://github.com/hashgraph/hedera-keygen-java/blob/master/src/main/java/com/hedera/sdk/keygen/CryptoUtils.java#L43 + fn derive_seed(entropy: &[u8]) -> [u8; 32] { + let password = entropy + .into_iter() + .chain([u8::max_value(); 8].iter()) + .map(|u| { *u }) + .collect::>(); + + let salt = [u8::max_value()]; - seed.copy_from_slice(&Seed::new(&mnemonic, password).as_bytes()[0..32]); + let mut seed = [0u8; 32]; - let mut rng = ChaChaRng::from_seed(seed); - SecretKey(ed25519_dalek::SecretKey::generate(&mut rng)) + pbkdf2::pbkdf2::>(&password, &salt, 2048, &mut seed); + + seed } /// Construct a `SecretKey` from a slice of bytes. @@ -421,15 +449,25 @@ impl SecretKey { } /// Re-construct a `SecretKey` from the supplied mnemonic and password. - pub fn from_mnemonic(mnemonic: &str, password: &str) -> Result { + pub fn from_mnemonic(mnemonic: &str) -> Result { let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English)?; - Ok(Self::generate_with_mnemonic(&mnemonic, password)) + let mut entropy = [0u8; 32]; + + let seed_entropy = mnemonic.entropy(); + + for i in 0..32 { + entropy[i] = seed_entropy[i]; + } + + //let entropy: &[u8; 32] = vec!(mnemonic.entropy()).try_into()?; + + Ok(Self::generate_from_entropy(&entropy)) } /// Return the `SecretKey` as raw bytes. #[inline] - pub fn as_bytes(&self) -> &[u8; ed25519_dalek::PUBLIC_KEY_LENGTH] { + pub fn as_bytes(&self) -> &[u8; ed25519_dalek::SECRET_KEY_LENGTH] { self.0.as_bytes() } @@ -646,7 +684,8 @@ mod tests { #[test] fn test_generate() -> Result<(), Error> { - let (key, _mnemonic) = SecretKey::generate(""); + let key = SecretKey::generate(); + let signature = key.sign(MESSAGE.as_bytes()); let verified = key.public().verify(MESSAGE.as_bytes(), &signature)?; @@ -671,8 +710,8 @@ mod tests { #[test] fn test_reconstruct() -> Result<(), Error> { - let (secret1, mnemonic) = SecretKey::generate("this-is-not-a-password"); - let secret2 = SecretKey::from_mnemonic(&mnemonic, "this-is-not-a-password")?; + let (secret1, mnemonic) = SecretKey::generate_mnemonic(); + let secret2 = SecretKey::from_mnemonic(&mnemonic)?; assert_eq!(secret1.as_bytes(), secret2.as_bytes());