From c1a02247cde46bd487c8c91305f585360624710c Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 18 Sep 2019 15:46:58 -0700 Subject: [PATCH] HD derive transparent address (infra only) --- Cargo.toml | 2 + src/lightwallet/extended_key.rs | 224 ++++++++++++++++++++++++++++++++ src/lightwallet/mod.rs | 1 + 3 files changed, 227 insertions(+) create mode 100644 src/lightwallet/extended_key.rs diff --git a/Cargo.toml b/Cargo.toml index 59c2549..d1e694b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ clap = "2.33" secp256k1 = "=0.15.0" sha2 = "0.8.0" ripemd160 = "0.8.0" +ring = "0.14.0" +lazy_static = "1.2.0" [dependencies.bellman] git = "https://github.com/adityapk00/librustzcash.git" diff --git a/src/lightwallet/extended_key.rs b/src/lightwallet/extended_key.rs new file mode 100644 index 0000000..2806566 --- /dev/null +++ b/src/lightwallet/extended_key.rs @@ -0,0 +1,224 @@ +use rand::Rng; +use ring::{ + digest, + hmac::{SigningContext, SigningKey}, +}; +use lazy_static::lazy_static; +use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly, VerifyOnly, Error}; + +lazy_static! { + static ref SECP256K1_SIGN_ONLY: Secp256k1 = Secp256k1::signing_only(); + static ref SECP256K1_VERIFY_ONLY: Secp256k1 = Secp256k1::verification_only(); +} +/// Random entropy, part of extended key. +type ChainCode = Vec; + + +const HARDENED_KEY_START_INDEX: u32 = 2_147_483_648; // 2 ** 31 + +/// KeyIndex indicates the key type and index of a child key. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum KeyIndex { + /// Normal key, index range is from 0 to 2 ** 31 - 1 + Normal(u32), + /// Hardened key, index range is from 2 ** 31 to 2 ** 32 - 1 + Hardened(u32), +} + +impl KeyIndex { + /// Return raw index value + pub fn raw_index(self) -> u32 { + match self { + KeyIndex::Normal(i) => i, + KeyIndex::Hardened(i) => i, + } + } + + /// Return normalize index, it will return index subtract 2 ** 31 for hardended key. + /// + /// # Examples + /// + /// ```rust + /// # extern crate hdwallet; + /// use hdwallet::KeyIndex; + /// + /// assert_eq!(KeyIndex::Normal(0).normalize_index(), 0); + /// assert_eq!(KeyIndex::Hardened(2_147_483_648).normalize_index(), 0); + /// ``` + pub fn normalize_index(self) -> u32 { + match self { + KeyIndex::Normal(i) => i, + KeyIndex::Hardened(i) => i - HARDENED_KEY_START_INDEX, + } + } + + /// Check index range. + /// + /// # Examples + /// + /// ```rust + /// # extern crate hdwallet; + /// use hdwallet::KeyIndex; + /// + /// assert!(KeyIndex::Normal(0).is_valid()); + /// assert!(!KeyIndex::Normal(2_147_483_648).is_valid()); + /// assert!(KeyIndex::Hardened(2_147_483_648).is_valid()); + /// ``` + pub fn is_valid(self) -> bool { + match self { + KeyIndex::Normal(i) => i < HARDENED_KEY_START_INDEX, + KeyIndex::Hardened(i) => i >= HARDENED_KEY_START_INDEX, + } + } + + /// Generate Hardened KeyIndex from normalize index value. + /// + /// # Examples + /// + /// ```rust + /// # extern crate hdwallet; + /// use hdwallet::KeyIndex; + /// + /// // hardended key from zero + /// let hardened_index_zero = KeyIndex::hardened_from_normalize_index(0).unwrap(); + /// assert_eq!(hardened_index_zero, KeyIndex::Hardened(2_147_483_648)); + /// // also allow raw index for convernient + /// let hardened_index_zero = KeyIndex::hardened_from_normalize_index(2_147_483_648).unwrap(); + /// assert_eq!(hardened_index_zero, KeyIndex::Hardened(2_147_483_648)); + /// ``` + pub fn hardened_from_normalize_index(i: u32) -> Result { + if i < HARDENED_KEY_START_INDEX { + Ok(KeyIndex::Hardened(HARDENED_KEY_START_INDEX + i)) + } else { + Ok(KeyIndex::Hardened(i)) + } + } + + /// Generate KeyIndex from raw index value. + /// + /// # Examples + /// + /// ```rust + /// # extern crate hdwallet; + /// use hdwallet::KeyIndex; + /// + /// let normal_key = KeyIndex::from_index(0).unwrap(); + /// assert_eq!(normal_key, KeyIndex::Normal(0)); + /// let hardened_key = KeyIndex::from_index(2_147_483_648).unwrap(); + /// assert_eq!(hardened_key, KeyIndex::Hardened(2_147_483_648)); + /// ``` + pub fn from_index(i: u32) -> Result { + if i < HARDENED_KEY_START_INDEX { + Ok(KeyIndex::Normal(i)) + } else { + Ok(KeyIndex::Hardened(i)) + } + } +} + +impl From for KeyIndex { + fn from(index: u32) -> Self { + KeyIndex::from_index(index).expect("KeyIndex") + } +} + + +/// ExtendedPrivKey is used for child key derivation. +/// See [secp256k1 crate documentation](https://docs.rs/secp256k1) for SecretKey signatures usage. +/// +/// # Examples +/// +/// ```rust +/// # extern crate hdwallet; +/// use hdwallet::{ExtendedPrivKey, KeyIndex}; +/// +/// let master_key = ExtendedPrivKey::random().unwrap(); +/// let hardened_key_index = KeyIndex::hardened_from_normalize_index(0).unwrap(); +/// let hardended_child_priv_key = master_key.derive_private_key(hardened_key_index).unwrap(); +/// let normal_key_index = KeyIndex::Normal(0); +/// let noamal_child_priv_key = master_key.derive_private_key(normal_key_index).unwrap(); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ExtendedPrivKey { + pub private_key: SecretKey, + 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 { + S128 = 128, + S256 = 256, + S512 = 512, +} + +impl ExtendedPrivKey { + /// Generate an ExtendedPrivKey, use 256 size random seed. + pub fn random() -> Result { + 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 { + 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 { + let signature = { + let signing_key = SigningKey::new(&digest::SHA512, b"Bitcoin seed"); + let mut h = SigningContext::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::Signature { + let signing_key = SigningKey::new(&digest::SHA512, &self.chain_code); + let mut h = SigningContext::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::Signature { + let signing_key = SigningKey::new(&digest::SHA512, &self.chain_code); + let mut h = SigningContext::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 { + if !key_index.is_valid() { + return Err(Error::InvalidTweak); + } + 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(), + }) + } +} diff --git a/src/lightwallet/mod.rs b/src/lightwallet/mod.rs index 7ee0b8b..b658599 100644 --- a/src/lightwallet/mod.rs +++ b/src/lightwallet/mod.rs @@ -36,6 +36,7 @@ use zcash_primitives::{ }; pub mod data; +pub mod extended_key; use data::{BlockData, WalletTx, Utxo, SaplingNoteData, SpendableNote};