Skip to content
This repository has been archived by the owner on Jul 27, 2022. It is now read-only.

Problem:(CRO-490) tiny_hderive uses the same secp256k1 library and it's outdated #475

Merged
merged 1 commit into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ This project contains portions of code derived from the following libraries:
* Etcommon
* Copyright: Copyright (c) 2018 ETCDEV
* License: Apache License 2.0
* Repository: https://github.com/ETCDEVTeam/etcommon-rs
* Repository: https://github.com/ETCDEVTeam/etcommon-rs

* BIP44 HDWallet
* Copyright: Jiang Jinyang <jjyruby@gmail.com>
leejw51crypto marked this conversation as resolved.
Show resolved Hide resolved
* License: The MIT License (MIT)
* Repository: https://github.com/jjyr/hdwallet
7 changes: 6 additions & 1 deletion client-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,17 @@ jsonrpc-core = "13.2"
log ="0.4.8"
serde = { version = "1.0", features = ["derive"] }
tokio="0.1.22"
tiny-hderive = "0.2.1"
tiny-bip39 = "0.6"
unicase="2.5.1"
lazy_static="1.4.0"
ring = "0.16.9"

[dev-dependencies]
chain-tx-validation = { path = "../chain-tx-validation" }
hex = "0.4.0"
base58 = "0.1.0"
ripemd160 = "0.8.0"


[features]
default = ["sled"]
Expand Down
30 changes: 30 additions & 0 deletions client-core/src/hdwallet/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! # Extended Key for HD-wallet
//! adapted from https://github.com/jjyr/hdwallet (HDWallet)
//! Copyright (c) 2018, Jiang Jinyang (licensed under the MIT License)
//! Modifications Copyright (c) 2018 - 2019, Foris Limited (licensed under the Apache License, Version 2.0)
//!

pub use crate::hdwallet::ChainPathError;
tomtau marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Debug, Clone, Eq, PartialEq)]
/// Error code for hdwallet
pub enum Error {
/// Index is out of range
KeyIndexOutOfRange,
/// ChainPathError
ChainPath(ChainPathError),
/// secp256k1 errors
Secp(secp256k1::Error),
}

impl From<ChainPathError> for Error {
fn from(err: ChainPathError) -> Error {
Error::ChainPath(err)
}
}

impl From<secp256k1::Error> for Error {
fn from(err: secp256k1::Error) -> Error {
Error::Secp(err)
}
}
282 changes: 282 additions & 0 deletions client-core/src/hdwallet/extended_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
//! # Extended Key for HD-wallet
//! adapted from https://github.com/jjyr/hdwallet (HDWallet)
//! Copyright (c) 2018, Jiang Jinyang (licensed under the MIT License)
//! Modifications Copyright (c) 2018 - 2019, Foris Limited (licensed under the Apache License, Version 2.0)
//!

/// Key-index for hdwallet
pub mod key_index;

use crate::hdwallet::{
error::Error,
traits::{Deserialize, Serialize},
};
use key_index::KeyIndex;
use rand::Rng;
use ring::hmac::{Context, Key, HMAC_SHA512};
use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly, VerifyOnly};

lazy_static! {
static ref SECP256K1_SIGN_ONLY: Secp256k1<SignOnly> = Secp256k1::signing_only();
static ref SECP256K1_VERIFY_ONLY: Secp256k1<VerifyOnly> = Secp256k1::verification_only();
}

/// Random entropy, part of extended key.
type ChainCode = Vec<u8>;

#[derive(Debug, Clone, PartialEq, Eq)]
/// extended key for hdwallet
pub struct ExtendedPrivKey {
/// privatekey for extended key in hdwallet
pub private_key: SecretKey,
/// chain kind for hdwallet
pub chain_code: ChainCode,
}

/// Indicate bits of random seed used to generate private key, 256 is recommended.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum KeySeed {
/// 128 seed
S128 = 128,
/// 256 seed
S256 = 256,
/// 512 seed
S512 = 512,
}

impl ExtendedPrivKey {
/// Generate an ExtendedPrivKey, use 256 size random seed.
pub fn random() -> Result<ExtendedPrivKey, Error> {
ExtendedPrivKey::random_with_seed_size(KeySeed::S256)
}
/// Generate an ExtendedPrivKey which use 128 or 256 or 512 bits random seed.
pub fn random_with_seed_size(seed_size: KeySeed) -> Result<ExtendedPrivKey, Error> {
let seed = {
let mut seed = vec![0u8; seed_size as usize / 8];
let mut rng = rand::thread_rng();
rng.fill(seed.as_mut_slice());
seed
};
Self::with_seed(&seed)
}

/// Generate an ExtendedPrivKey from seed
pub fn with_seed(seed: &[u8]) -> Result<ExtendedPrivKey, Error> {
let signature = {
let signing_key = Key::new(HMAC_SHA512, b"Bitcoin seed");
let mut h = Context::with_key(&signing_key);
h.update(&seed);
h.sign()
};
let sig_bytes = signature.as_ref();
let (key, chain_code) = sig_bytes.split_at(sig_bytes.len() / 2);
let private_key = SecretKey::from_slice(key)?;
Ok(ExtendedPrivKey {
private_key,
chain_code: chain_code.to_vec(),
})
}

fn sign_hardended_key(&self, index: u32) -> ring::hmac::Tag {
let signing_key = Key::new(HMAC_SHA512, &self.chain_code);
let mut h = Context::with_key(&signing_key);
h.update(&[0x00]);
h.update(&self.private_key[..]);
h.update(&index.to_be_bytes());
h.sign()
}

fn sign_normal_key(&self, index: u32) -> ring::hmac::Tag {
let signing_key = Key::new(HMAC_SHA512, &self.chain_code);
let mut h = Context::with_key(&signing_key);
let public_key = PublicKey::from_secret_key(&*SECP256K1_SIGN_ONLY, &self.private_key);
h.update(&public_key.serialize());
h.update(&index.to_be_bytes());
h.sign()
}

/// Derive a child key from ExtendedPrivKey.
pub fn derive_private_key(&self, key_index: KeyIndex) -> Result<ExtendedPrivKey, Error> {
if !key_index.is_valid() {
return Err(Error::KeyIndexOutOfRange);
}
let signature = match key_index {
KeyIndex::Hardened(index) => self.sign_hardended_key(index),
KeyIndex::Normal(index) => self.sign_normal_key(index),
};
let sig_bytes = signature.as_ref();
let (key, chain_code) = sig_bytes.split_at(sig_bytes.len() / 2);
let mut private_key = SecretKey::from_slice(key)?;
private_key.add_assign(&self.private_key[..])?;
Ok(ExtendedPrivKey {
private_key,
chain_code: chain_code.to_vec(),
})
}
}

/// ExtendedPubKey is used for public child key derivation.

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtendedPubKey {
/// publickey for extended pub key
pub public_key: PublicKey,
/// chain code for extended pub key
pub chain_code: ChainCode,
}

impl ExtendedPubKey {
/// Derive public normal child key from ExtendedPubKey,
/// will return error if key_index is a hardened key.
pub fn derive_public_key(&self, key_index: KeyIndex) -> Result<ExtendedPubKey, Error> {
if !key_index.is_valid() {
return Err(Error::KeyIndexOutOfRange);
}

let index = match key_index {
KeyIndex::Normal(i) => i,
KeyIndex::Hardened(_) => return Err(Error::KeyIndexOutOfRange),
};

let signature = {
let signing_key = Key::new(HMAC_SHA512, &self.chain_code);
let mut h = Context::with_key(&signing_key);
h.update(&self.public_key.serialize());
h.update(&index.to_be_bytes());
h.sign()
};
let sig_bytes = signature.as_ref();
let (key, chain_code) = sig_bytes.split_at(sig_bytes.len() / 2);
let private_key = SecretKey::from_slice(key)?;
let mut public_key = self.public_key.clone();
public_key.add_exp_assign(&*SECP256K1_VERIFY_ONLY, &private_key[..])?;
Ok(ExtendedPubKey {
public_key,
chain_code: chain_code.to_vec(),
})
}

/// ExtendedPubKey from ExtendedPrivKey
pub fn from_private_key(extended_key: &ExtendedPrivKey) -> Self {
let public_key =
PublicKey::from_secret_key(&*SECP256K1_SIGN_ONLY, &extended_key.private_key);
ExtendedPubKey {
public_key,
chain_code: extended_key.chain_code.clone(),
}
}
}

impl Serialize<Vec<u8>> for ExtendedPrivKey {
fn serialize(&self) -> Vec<u8> {
let mut buf = self.private_key[..].to_vec();
buf.extend(&self.chain_code);
buf
}
}
impl Deserialize<&[u8], Error> for ExtendedPrivKey {
fn deserialize(data: &[u8]) -> Result<Self, Error> {
let private_key = SecretKey::from_slice(&data[..32])?;
let chain_code = data[32..].to_vec();
Ok(ExtendedPrivKey {
private_key,
chain_code,
})
}
}

impl Serialize<Vec<u8>> for ExtendedPubKey {
fn serialize(&self) -> Vec<u8> {
let mut buf = self.public_key.serialize().to_vec();
buf.extend(&self.chain_code);
buf
}
}
impl Deserialize<&[u8], Error> for ExtendedPubKey {
fn deserialize(data: &[u8]) -> Result<Self, Error> {
let public_key = PublicKey::from_slice(&data[..33])?;
let chain_code = data[33..].to_vec();
Ok(ExtendedPubKey {
public_key,
chain_code,
})
}
}

#[cfg(test)]
mod tests {
use super::{ExtendedPrivKey, ExtendedPubKey, KeyIndex};
use crate::hdwallet::traits::{Deserialize, Serialize};

fn fetch_random_key() -> ExtendedPrivKey {
loop {
if let Ok(key) = ExtendedPrivKey::random() {
return key;
}
}
}

#[test]
fn random_extended_priv_key() {
for _ in 0..10 {
if ExtendedPrivKey::random().is_ok() {
return;
}
}
panic!("can't generate valid ExtendedPrivKey");
}

#[test]
fn random_seed_not_empty() {
assert_ne!(
fetch_random_key(),
ExtendedPrivKey::with_seed(&[]).expect("privkey")
);
}

#[test]
fn extended_priv_key_derive_child_priv_key() {
let master_key = fetch_random_key();
master_key
.derive_private_key(KeyIndex::hardened_from_normalize_index(0).unwrap())
.expect("hardended_key");
master_key
.derive_private_key(KeyIndex::Normal(0))
.expect("normal_key");
}

#[test]
fn extended_pub_key_derive_child_pub_key() {
let parent_priv_key = fetch_random_key();
let child_pub_key_from_child_priv_key = {
let child_priv_key = parent_priv_key
.derive_private_key(KeyIndex::Normal(0))
.expect("hardended_key");
ExtendedPubKey::from_private_key(&child_priv_key)
};
let child_pub_key_from_parent_pub_key = {
let parent_pub_key = ExtendedPubKey::from_private_key(&parent_priv_key);
parent_pub_key
.derive_public_key(KeyIndex::Normal(0))
.expect("public key")
};
assert_eq!(
child_pub_key_from_child_priv_key,
child_pub_key_from_parent_pub_key
)
}

#[test]
fn priv_key_serialize_deserialize() {
let key = fetch_random_key();
let buf = key.serialize();
assert_eq!(ExtendedPrivKey::deserialize(&buf).expect("de"), key);
}

#[test]
fn pub_key_serialize_deserialize() {
let key = ExtendedPubKey::from_private_key(&fetch_random_key());
let buf = key.serialize();
assert_eq!(ExtendedPubKey::deserialize(&buf).expect("de"), key);
}
}
Loading