From e257e4e3bd58ecbec483a9edeffc335ffbc3a3be Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Sun, 5 Feb 2017 18:17:03 +0300 Subject: [PATCH] Ethkey - extended keys (#4377) * data structures initial * hard derivation * tabs, docs * more docs * soft private derivation * public derivation * finalize api, fix warnings * use simple new() * keypair api * bump byteorder * doc tweaks * remove heavyness from tests * added test vector infrastructure and examples * initialization from seed to key pair * add comment about panic --- Cargo.lock | 20 +- ethcore/Cargo.toml | 2 +- ethkey/Cargo.toml | 2 + ethkey/src/extended.rs | 448 +++++++++++++++++++++++++++++++++++++++++ ethkey/src/lib.rs | 4 + 5 files changed, 466 insertions(+), 10 deletions(-) create mode 100644 ethkey/src/extended.rs diff --git a/Cargo.lock b/Cargo.lock index 7fef9d5fb..c07e3376d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,7 +162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "byteorder" -version = "0.5.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -319,8 +319,8 @@ dependencies = [ [[package]] name = "eth-secp256k1" -version = "0.5.4" -source = "git+https://github.com/ethcore/rust-secp256k1#a9a0b1be1f39560ca86e8fc8e55e205a753ff25c" +version = "0.5.6" +source = "git+https://github.com/ethcore/rust-secp256k1#edab95f5569e4fb97579dc8947be96e7ac789c16" dependencies = [ "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -358,7 +358,7 @@ version = "1.6.0" dependencies = [ "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -661,7 +661,7 @@ dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", + "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", "ethcore-bigint 0.1.2", "ethcore-bloom-journal 0.1.0", "ethcore-devtools 1.6.0", @@ -692,7 +692,7 @@ dependencies = [ name = "ethcrypto" version = "0.1.0" dependencies = [ - "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", + "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", "ethcore-bigint 0.1.2", "ethkey 0.2.0", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", @@ -714,11 +714,13 @@ dependencies = [ name = "ethkey" version = "0.2.0" dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", - "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", + "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", "ethcore-bigint 0.1.2", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2481,7 +2483,7 @@ dependencies = [ "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum blastfig 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "09640e0509d97d5cdff03a9f5daf087a8e04c735c3b113a75139634a19cfc7b2" "checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d" -"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" +"checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" @@ -2501,7 +2503,7 @@ dependencies = [ "checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" "checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "" "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" -"checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "" +"checksum eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)" = "" "checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index e0afa1106..d31e401f6 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -24,7 +24,7 @@ semver = "0.5" bit-set = "0.4" time = "0.1" rand = "0.3" -byteorder = "0.5" +byteorder = "1.0" transient-hashmap = "0.1" linked-hash-map = "0.3.0" evmjit = { path = "../evmjit", optional = true } diff --git a/ethkey/Cargo.toml b/ethkey/Cargo.toml index d9d1c7efa..d661952a7 100644 --- a/ethkey/Cargo.toml +++ b/ethkey/Cargo.toml @@ -11,6 +11,8 @@ eth-secp256k1 = { git = "https://github.com/ethcore/rust-secp256k1" } rustc-serialize = "0.3" docopt = { version = "0.6", optional = true } ethcore-bigint = { path = "../util/bigint" } +rust-crypto = "0.2" +byteorder = "1.0" [features] default = [] diff --git a/ethkey/src/extended.rs b/ethkey/src/extended.rs new file mode 100644 index 000000000..c5426601f --- /dev/null +++ b/ethkey/src/extended.rs @@ -0,0 +1,448 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Extended keys + +use secret::Secret; +use Public; +use bigint::hash::{H256, FixedHash}; +pub use self::derivation::Error as DerivationError; + +/// Extended secret key, allows deterministic derivation of subsequent keys. +pub struct ExtendedSecret { + secret: Secret, + chain_code: H256, +} + +impl ExtendedSecret { + /// New extended key from given secret and chain code. + pub fn with_code(secret: Secret, chain_code: H256) -> ExtendedSecret { + ExtendedSecret { + secret: secret, + chain_code: chain_code, + } + } + + /// New extended key from given secret with the random chain code. + pub fn new_random(secret: Secret) -> ExtendedSecret { + ExtendedSecret::with_code(secret, H256::random()) + } + + /// New extended key from given secret. + /// Chain code will be derived from the secret itself (in a deterministic way). + pub fn new(secret: Secret) -> ExtendedSecret { + let chain_code = derivation::chain_code(*secret); + ExtendedSecret::with_code(secret, chain_code) + } + + /// Derive new private key + pub fn derive(&self, index: u32) -> ExtendedSecret { + let (derived_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index); + + let derived_secret = Secret::from_slice(&*derived_key) + .expect("Derivation always produced a valid private key; qed"); + + ExtendedSecret::with_code(derived_secret, next_chain_code) + } + + /// Private key component of the extended key. + pub fn secret(&self) -> &Secret { + &self.secret + } +} + +/// Extended public key, allows deterministic derivation of subsequent keys. +pub struct ExtendedPublic { + public: Public, + chain_code: H256, +} + +impl ExtendedPublic { + /// New extended public key from known parent and chain code + pub fn new(public: Public, chain_code: H256) -> Self { + ExtendedPublic { public: public, chain_code: chain_code } + } + + /// Create new extended public key from known secret + pub fn from_secret(secret: &ExtendedSecret) -> Result { + Ok( + ExtendedPublic::new( + derivation::point(**secret.secret())?, + secret.chain_code.clone(), + ) + ) + } + + /// Derive new public key + /// Operation is defined only for index belongs [0..2^31) + pub fn derive(&self, index: u32) -> Result { + let (derived_key, next_chain_code) = derivation::public(self.public, self.chain_code, index)?; + Ok(ExtendedPublic::new(derived_key, next_chain_code)) + } + + pub fn public(&self) -> &Public { + &self.public + } +} + +pub struct ExtendedKeyPair { + secret: ExtendedSecret, + public: ExtendedPublic, +} + +impl ExtendedKeyPair { + pub fn new(secret: Secret) -> Self { + let extended_secret = ExtendedSecret::new(secret); + let extended_public = ExtendedPublic::from_secret(&extended_secret) + .expect("Valid `Secret` always produces valid public; qed"); + ExtendedKeyPair { + secret: extended_secret, + public: extended_public, + } + } + + pub fn with_code(secret: Secret, public: Public, chain_code: H256) -> Self { + ExtendedKeyPair { + secret: ExtendedSecret::with_code(secret, chain_code.clone()), + public: ExtendedPublic::new(public, chain_code), + } + } + + pub fn with_secret(secret: Secret, chain_code: H256) -> Self { + let extended_secret = ExtendedSecret::with_code(secret, chain_code); + let extended_public = ExtendedPublic::from_secret(&extended_secret) + .expect("Valid `Secret` always produces valid public; qed"); + ExtendedKeyPair { + secret: extended_secret, + public: extended_public, + } + } + + pub fn with_seed(seed: &[u8]) -> Result { + let (master_key, chain_code) = derivation::seed_pair(seed); + Ok(ExtendedKeyPair::with_secret( + Secret::from_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?, + chain_code, + )) + } + + pub fn secret(&self) -> &ExtendedSecret { + &self.secret + } + + pub fn public(&self) -> &ExtendedPublic { + &self.public + } + + pub fn derive(&self, index: u32) -> Result { + let derived = self.secret.derive(index); + + Ok(ExtendedKeyPair { + public: ExtendedPublic::from_secret(&derived)?, + secret: derived, + }) + } +} + +// Derivation functions for private and public keys +// Work is based on BIP0032 +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +mod derivation { + + use rcrypto::hmac::Hmac; + use rcrypto::mac::Mac; + use rcrypto::sha2::Sha512; + use bigint::hash::{H512, H256, FixedHash}; + use bigint::prelude::{U256, U512, Uint}; + use byteorder::{BigEndian, ByteOrder}; + use secp256k1; + use secp256k1::key::{SecretKey, PublicKey}; + use SECP256K1; + use keccak; + + #[derive(Debug)] + pub enum Error { + InvalidHardenedUse, + InvalidPoint, + MissingIndex, + InvalidSeed, + } + + // Deterministic derivation of the key using secp256k1 elliptic curve. + // Derivation can be either hardened or not. + // For hardened derivation, pass index at least 2^31 + // + // Can panic if passed `private_key` is not a valid secp256k1 private key + // (outside of (0..curve_n()]) field + pub fn private(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { + if index < (2 << 30) { + private_soft(private_key, chain_code, index) + } + else { + private_hard(private_key, chain_code, index) + } + } + + fn hmac_pair(data: [u8; 37], private_key: H256, chain_code: H256) -> (H256, H256) { + let private: U256 = private_key.into(); + + // produces 512-bit derived hmac (I) + let mut hmac = Hmac::new(Sha512::new(), &*chain_code); + let mut i_512 = [0u8; 64]; + hmac.input(&data[..]); + hmac.raw_result(&mut i_512); + + // left most 256 bits are later added to original private key + let hmac_key: U256 = H256::from_slice(&i_512[0..32]).into(); + // right most 256 bits are new chain code for later derivations + let next_chain_code = H256::from(&i_512[32..64]); + + let child_key = private_add(hmac_key, private).into(); + (child_key, next_chain_code) + } + + // Can panic if passed `private_key` is not a valid secp256k1 private key + // (outside of (0..curve_n()]) field + fn private_soft(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { + let mut data = [0u8; 37]; + + let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key) + .expect("Caller should provide valid private key"); + let sec_public = PublicKey::from_secret_key(&SECP256K1, &sec_private) + .expect("Caller should provide valid private key"); + let public_serialized = sec_public.serialize_vec(&SECP256K1, true); + + // curve point (compressed public key) -- index + // 0.33 -- 33..37 + data[0..33].copy_from_slice(&public_serialized); + BigEndian::write_u32(&mut data[33..37], index); + + hmac_pair(data, private_key, chain_code) + } + + // Deterministic derivation of the key using secp256k1 elliptic curve + // This is hardened derivation and does not allow to associate + // corresponding public keys of the original and derived private keys + fn private_hard(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { + let mut data = [0u8; 37]; + let private: U256 = private_key.into(); + + // 0x00 (padding) -- private_key -- index + // 0 -- 1..33 -- 33..37 + private.to_big_endian(&mut data[1..33]); + BigEndian::write_u32(&mut data[33..37], index); + + hmac_pair(data, private_key, chain_code) + } + + fn private_add(k1: U256, k2: U256) -> U256 { + let sum = U512::from(k1) + U512::from(k2); + modulo(sum, curve_n()) + } + + // todo: surely can be optimized + fn modulo(u1: U512, u2: U256) -> U256 { + let dv = u1 / U512::from(u2); + let md = u1 - (dv * U512::from(u2)); + md.into() + } + + // returns n (for mod(n)) for the secp256k1 elliptic curve + // todo: maybe lazy static + fn curve_n() -> U256 { + H256::from_slice(&secp256k1::constants::CURVE_ORDER).into() + } + + pub fn public(public_key: H512, chain_code: H256, index: u32) -> Result<(H512, H256), Error> { + if index >= (2 << 30) { + // public derivation is only defined on 'soft' index space [0..2^31) + return Err(Error::InvalidHardenedUse) + } + + let mut public_sec_raw = [0u8; 65]; + public_sec_raw[0] = 4; + public_sec_raw[1..65].copy_from_slice(&*public_key); + let public_sec = PublicKey::from_slice(&SECP256K1, &public_sec_raw).map_err(|_| Error::InvalidPoint)?; + let public_serialized = public_sec.serialize_vec(&SECP256K1, true); + + let mut data = [0u8; 37]; + // curve point (compressed public key) -- index + // 0.33 -- 33..37 + data[0..33].copy_from_slice(&public_serialized); + BigEndian::write_u32(&mut data[33..37], index); + + // HMAC512SHA produces [derived private(256); new chain code(256)] + let mut hmac = Hmac::new(Sha512::new(), &*chain_code); + let mut i_512 = [0u8; 64]; + hmac.input(&data[..]); + hmac.raw_result(&mut i_512); + + let new_private = H256::from(&i_512[0..32]); + let new_chain_code = H256::from(&i_512[32..64]); + + // Generated private key can (extremely rarely) be out of secp256k1 key field + if curve_n() <= new_private.clone().into() { return Err(Error::MissingIndex); } + let new_private_sec = SecretKey::from_slice(&SECP256K1, &*new_private) + .expect("Private key belongs to the field [0..CURVE_ORDER) (checked above); So initializing can never fail; qed"); + let mut new_public = PublicKey::from_secret_key(&SECP256K1, &new_private_sec) + .expect("Valid private key produces valid public key"); + + // Adding two points on the elliptic curves (combining two public keys) + new_public.add_assign(&SECP256K1, &public_sec) + .expect("Addition of two valid points produce valid point"); + + let serialized = new_public.serialize_vec(&SECP256K1, false); + + Ok(( + H512::from(&serialized[1..65]), + new_chain_code, + )) + } + + fn sha3(slc: &[u8]) -> H256 { + keccak::Keccak256::keccak256(slc).into() + } + + pub fn chain_code(secret: H256) -> H256 { + // 10,000 rounds of sha3 + let mut running_sha3 = sha3(&*secret); + for _ in 0..99999 { running_sha3 = sha3(&*running_sha3); } + running_sha3 + } + + pub fn point(secret: H256) -> Result { + let sec = SecretKey::from_slice(&SECP256K1, &*secret) + .map_err(|_| Error::InvalidPoint)?; + let public_sec = PublicKey::from_secret_key(&SECP256K1, &sec) + .map_err(|_| Error::InvalidPoint)?; + let serialized = public_sec.serialize_vec(&SECP256K1, false); + Ok(H512::from(&serialized[1..65])) + } + + pub fn seed_pair(seed: &[u8]) -> (H256, H256) { + let mut hmac = Hmac::new(Sha512::new(), b"Bitcoin seed"); + let mut i_512 = [0u8; 64]; + hmac.input(seed); + hmac.raw_result(&mut i_512); + + let master_key = H256::from_slice(&i_512[0..32]); + let chain_code = H256::from_slice(&i_512[32..64]); + + (master_key, chain_code) + } +} + +#[cfg(test)] +mod tests { + use super::{ExtendedSecret, ExtendedPublic, ExtendedKeyPair}; + use secret::Secret; + use std::str::FromStr; + use bigint::hash::{H128, H256}; + use super::derivation; + + fn master_chain_basic() -> (H256, H256) { + let seed = H128::from_str("000102030405060708090a0b0c0d0e0f") + .expect("Seed should be valid H128") + .to_vec(); + + derivation::seed_pair(&*seed) + } + + fn test_extended(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret { + let (private_seed, chain_code) = master_chain_basic(); + let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed).unwrap(), chain_code); + let derived = f(extended_secret); + assert_eq!(**derived.secret(), test_private); + } + + #[test] + fn smoky() { + let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(); + let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into()); + + // hardened + assert_eq!(&**extended_secret.secret(), &*secret); + assert_eq!(&**extended_secret.derive(2147483648).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()); + assert_eq!(&**extended_secret.derive(2147483649).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()); + + // normal + assert_eq!(&**extended_secret.derive(0).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()); + assert_eq!(&**extended_secret.derive(1).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()); + assert_eq!(&**extended_secret.derive(2).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()); + + let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); + let derived_public = extended_public.derive(0).expect("First derivation of public should succeed"); + assert_eq!(&*derived_public.public(), &"f7b3244c96688f92372bfd4def26dc4151529747bab9f188a4ad34e141d47bd66522ff048bc6f19a0a4429b04318b1a8796c000265b4fa200dae5f6dda92dd94".into()); + + let keypair = ExtendedKeyPair::with_secret( + Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(), + 064.into(), + ); + assert_eq!(&**keypair.derive(2147483648).expect("Derivation of keypair should succeed").secret().secret(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()); + } + + #[test] + fn match_() { + let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(); + let extended_secret = ExtendedSecret::with_code(secret.clone(), 1.into()); + let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); + + let derived_secret0 = extended_secret.derive(0); + let derived_public0 = extended_public.derive(0).expect("First derivation of public should succeed"); + + let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0).expect("Extended public should be created"); + + assert_eq!(public_from_secret0.public(), derived_public0.public()); + } + + #[test] + fn test_seeds() { + let seed = H128::from_str("000102030405060708090a0b0c0d0e0f") + .expect("Seed should be valid H128") + .to_vec(); + + /// private key from bitcoin test vector + /// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs + let test_private = H256::from_str("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35") + .expect("Private should be decoded ok"); + + let (private_seed, _) = derivation::seed_pair(&*seed); + + assert_eq!(private_seed, test_private); + } + + #[test] + fn test_vector_1() { + /// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7 + /// H(0) + test_extended( + |secret| secret.derive(2147483648), + H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea") + .expect("Private should be decoded ok") + ); + } + + #[test] + fn test_vector_2() { + /// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs + /// H(0)/1 + test_extended( + |secret| secret.derive(2147483648).derive(1), + H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368") + .expect("Private should be decoded ok") + ); + } +} diff --git a/ethkey/src/lib.rs b/ethkey/src/lib.rs index bc9c4a4b9..3882b3559 100644 --- a/ethkey/src/lib.rs +++ b/ethkey/src/lib.rs @@ -21,6 +21,8 @@ extern crate tiny_keccak; extern crate secp256k1; extern crate rustc_serialize; extern crate ethcore_bigint as bigint; +extern crate crypto as rcrypto; +extern crate byteorder; mod brain; mod error; @@ -30,6 +32,7 @@ mod prefix; mod random; mod signature; mod secret; +mod extended; lazy_static! { pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); @@ -48,6 +51,7 @@ pub use self::prefix::Prefix; pub use self::random::Random; pub use self::signature::{sign, verify_public, verify_address, recover, Signature}; pub use self::secret::Secret; +pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError}; use bigint::hash::{H160, H256, H512};