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
This commit is contained in:
parent
f646ffbe61
commit
e257e4e3bd
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -162,7 +162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "0.5.3"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -319,8 +319,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eth-secp256k1"
|
name = "eth-secp256k1"
|
||||||
version = "0.5.4"
|
version = "0.5.6"
|
||||||
source = "git+https://github.com/ethcore/rust-secp256k1#a9a0b1be1f39560ca86e8fc8e55e205a753ff25c"
|
source = "git+https://github.com/ethcore/rust-secp256k1#edab95f5569e4fb97579dc8947be96e7ac789c16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -358,7 +358,7 @@ version = "1.6.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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-bigint 0.1.2",
|
||||||
"ethcore-bloom-journal 0.1.0",
|
"ethcore-bloom-journal 0.1.0",
|
||||||
"ethcore-devtools 1.6.0",
|
"ethcore-devtools 1.6.0",
|
||||||
@ -692,7 +692,7 @@ dependencies = [
|
|||||||
name = "ethcrypto"
|
name = "ethcrypto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
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",
|
"ethcore-bigint 0.1.2",
|
||||||
"ethkey 0.2.0",
|
"ethkey 0.2.0",
|
||||||
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -714,11 +714,13 @@ dependencies = [
|
|||||||
name = "ethkey"
|
name = "ethkey"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
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)",
|
"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",
|
"ethcore-bigint 0.1.2",
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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 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 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 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.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27"
|
||||||
"checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "<none>"
|
"checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "<none>"
|
||||||
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
|
"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 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)" = "<none>"
|
"checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "<none>"
|
||||||
"checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5"
|
"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)" = "<none>"
|
"checksum eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>"
|
||||||
"checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f"
|
"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 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"
|
"checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb"
|
||||||
|
@ -24,7 +24,7 @@ semver = "0.5"
|
|||||||
bit-set = "0.4"
|
bit-set = "0.4"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
byteorder = "0.5"
|
byteorder = "1.0"
|
||||||
transient-hashmap = "0.1"
|
transient-hashmap = "0.1"
|
||||||
linked-hash-map = "0.3.0"
|
linked-hash-map = "0.3.0"
|
||||||
evmjit = { path = "../evmjit", optional = true }
|
evmjit = { path = "../evmjit", optional = true }
|
||||||
|
@ -11,6 +11,8 @@ eth-secp256k1 = { git = "https://github.com/ethcore/rust-secp256k1" }
|
|||||||
rustc-serialize = "0.3"
|
rustc-serialize = "0.3"
|
||||||
docopt = { version = "0.6", optional = true }
|
docopt = { version = "0.6", optional = true }
|
||||||
ethcore-bigint = { path = "../util/bigint" }
|
ethcore-bigint = { path = "../util/bigint" }
|
||||||
|
rust-crypto = "0.2"
|
||||||
|
byteorder = "1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
448
ethkey/src/extended.rs
Normal file
448
ethkey/src/extended.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! 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<Self, DerivationError> {
|
||||||
|
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<Self, DerivationError> {
|
||||||
|
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<ExtendedKeyPair, DerivationError> {
|
||||||
|
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<Self, DerivationError> {
|
||||||
|
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<H512, Error> {
|
||||||
|
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: 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")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,8 @@ extern crate tiny_keccak;
|
|||||||
extern crate secp256k1;
|
extern crate secp256k1;
|
||||||
extern crate rustc_serialize;
|
extern crate rustc_serialize;
|
||||||
extern crate ethcore_bigint as bigint;
|
extern crate ethcore_bigint as bigint;
|
||||||
|
extern crate crypto as rcrypto;
|
||||||
|
extern crate byteorder;
|
||||||
|
|
||||||
mod brain;
|
mod brain;
|
||||||
mod error;
|
mod error;
|
||||||
@ -30,6 +32,7 @@ mod prefix;
|
|||||||
mod random;
|
mod random;
|
||||||
mod signature;
|
mod signature;
|
||||||
mod secret;
|
mod secret;
|
||||||
|
mod extended;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new();
|
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::random::Random;
|
||||||
pub use self::signature::{sign, verify_public, verify_address, recover, Signature};
|
pub use self::signature::{sign, verify_public, verify_address, recover, Signature};
|
||||||
pub use self::secret::Secret;
|
pub use self::secret::Secret;
|
||||||
|
pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError};
|
||||||
|
|
||||||
use bigint::hash::{H160, H256, H512};
|
use bigint::hash::{H160, H256, H512};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user