diff --git a/src/ethereum.rs b/src/ethereum.rs index d5afbc3..c678fc0 100644 --- a/src/ethereum.rs +++ b/src/ethereum.rs @@ -1 +1,225 @@ // Coming soon :) +pub mod key_chain; + +use crate::ring::digest; +use crate::{ + chain_path::ChainPath, + ed25519_dalek::{PublicKey, SecretKey as Sk}, + traits::{Deserialize, Serialize}, + extended_key::{EthereumExPrivateKey, EthereumExPublicKey} +}; +use key_chain::{Derivation, KeyChain}; + +use base58::{FromBase58, ToBase58}; + +use std::rc::Rc; + +use crate::error::Error; + +#[derive(Debug, PartialEq, Eq)] +pub struct PrivKey { + pub derivation: Derivation, + pub extended_key: EthereumExPrivateKey, +} + +impl PrivKey { + pub fn from_master_key(extended_key: EthereumExPrivateKey) -> Self { + PrivKey { + extended_key, + derivation: Derivation::master(), + } + } +} + +#[derive(Debug, PartialEq, Eq)] + +pub struct PubKey { + pub derivation: Derivation, + pub extended_key: EthereumExPublicKey, +} + +impl PubKey { + pub fn from_private_key(priv_key: &PrivKey) -> PubKey { + let pub_key = EthereumExPublicKey::from_private_key(&priv_key.extended_key); + + PubKey { + derivation: priv_key.derivation.clone(), + extended_key: pub_key.unwrap(), + } + } +} + +trait DerivationExt { + fn parent_fingerprint(&self) -> Vec; +} + +impl DerivationExt for Derivation { + fn parent_fingerprint(&self) -> Vec { + match self.parent_key { + Some(ref key) => { + let pubkey = EthereumExPublicKey::from_private_key(key); + let buf = digest::digest(&digest::SHA256, &pubkey.unwrap().0.to_bytes()); + buf.as_ref()[0..4].to_vec() + } + None => vec![0; 4], + } + } +} + +fn encode_derivation(buf: &mut Vec, derivation: &Derivation) { + buf.extend_from_slice(&derivation.depth.to_be_bytes()); + buf.extend_from_slice(&derivation.parent_fingerprint()); + + match derivation.key_index { + Some(key_index) => { + buf.extend_from_slice(&key_index.raw_index().to_be_bytes()); + } + None => buf.extend_from_slice(&[0; 4]), + } +} + +fn decode_derivation(data: (&dyn KeyChain, ChainPath)) -> Result { + let slice: String = data.1.to_string(); + let chain_path = &slice[..(slice.len())]; + let (_extended_key, derivation) = data + .0 + .derive_private_key(chain_path.into()) + .expect("fetch key"); + + Ok(derivation) +} + +fn encode_checksum(buf: &mut Vec) { + let check_sum = { + let buf = digest::digest(&digest::SHA256, buf); + digest::digest(&digest::SHA256, buf.as_ref()) + }; + + buf.extend_from_slice(&check_sum.as_ref()[0..4]); +} + +impl Serialize> for PrivKey { + fn serialize(&self) -> Vec { + let mut buf: Vec = [].to_vec(); + + encode_derivation(&mut buf, &self.derivation); + + buf.extend_from_slice(&self.extended_key.chain_code); + buf.extend_from_slice(&[0]); + let private_key = Rc::try_unwrap(Rc::clone(&self.extended_key.private_key)).unwrap_err(); + buf.extend_from_slice(&private_key.to_bytes()); + assert_eq!(buf.len(), 74); + encode_checksum(&mut buf); + + buf + } +} + +impl Serialize for PrivKey { + fn serialize(&self) -> String { + Serialize::>::serialize(self).to_base58() + } +} + +impl Serialize> for PubKey { + fn serialize(&self) -> Vec { + let mut buf: Vec = [].to_vec(); + + encode_derivation(&mut buf, &self.derivation); + + buf.extend_from_slice(&self.extended_key.0.to_bytes()); + encode_checksum(&mut buf); + + buf + } +} + +impl Serialize for PubKey { + fn serialize(&self) -> String { + let serialized_key: Vec = self.serialize(); + let public_address = Serialize::>::serialize(&self.extended_key).to_base58(); + + public_address + &serialized_key.to_base58() + } +} + +impl Deserialize<(String, &dyn KeyChain, ChainPath<'_>), Error> for PrivKey { + fn deserialize(data: (String, &dyn KeyChain, ChainPath)) -> Result { + let buf = data.0.from_base58().map_err(|_| Error::InvalidBase58)?; + + let derivation = decode_derivation((data.1, data.2))?; + let chain_code = buf[9..41].to_vec(); + let private_key = Rc::new(Sk::from_bytes(&buf[42..74])?); + + Ok(PrivKey { + derivation, + extended_key: EthereumExPrivateKey { + private_key, + chain_code, + }, + }) + } +} + +impl Deserialize<(String, &dyn KeyChain, ChainPath<'_>), Error> for PubKey { + fn deserialize(data: (String, &dyn KeyChain, ChainPath)) -> Result { + let buf = data.0[44..] + .from_base58() + .map_err(|_| Error::InvalidBase58)?; + + let derivation = decode_derivation((data.1, data.2))?; + + let public_key = PublicKey::from_bytes(&buf[9..41]).unwrap(); + + Ok(PubKey { + derivation, + extended_key: EthereumExPublicKey(public_key), + }) + } +} +/* +#[cfg(test)] +mod tests { + use super::*; + use crate::mnemonic; + use crate::traits::Serialize; + use key_chain::{DefaultKeyChain, KeyChain}; + + #[test] + fn test_deserialize_priv_key() { + let new_mnemonic = mnemonic::new_mnemonic(24, "English"); + let seed = mnemonic::new_seed(new_mnemonic.unwrap(), "".to_string()); + let key_chain = + DefaultKeyChain::new(EthereumExPrivateKey::new_master_key(&seed).expect("master key")); + let (extended_key, derivation) = + key_chain.derive_private_key("m".into()).expect("fetch key"); + let private_key = PrivKey { + derivation, + extended_key, + }; + let serialized_key: String = private_key.serialize(); + let deserialized_key = + PrivKey::deserialize((serialized_key, &key_chain, "m".into())).expect("deserialize"); + assert_eq!(private_key, deserialized_key); + } + + #[test] + fn test_deserialize_pub_key() { + let new_mnemonic = mnemonic::new_mnemonic(24, "English"); + let seed = mnemonic::new_seed(new_mnemonic.unwrap(), "".to_string()); + let key_chain = + DefaultKeyChain::new(EthereumExPrivateKey::new_master_key(&seed).expect("master key")); + let (extended_key, derivation) = + key_chain.derive_private_key("m".into()).expect("fetch key"); + let private_key = PrivKey { + derivation, + extended_key, + }; + let public_key = PubKey::from_private_key(&private_key); + let serialized_key: String = public_key.serialize(); + let deserialized_key = + PubKey::deserialize((serialized_key, &key_chain, "m".into())).expect("deserialize"); + assert_eq!(public_key, deserialized_key); + } +} + */ \ No newline at end of file diff --git a/src/ethereum/key_chain.rs b/src/ethereum/key_chain.rs new file mode 100644 index 0000000..9897860 --- /dev/null +++ b/src/ethereum/key_chain.rs @@ -0,0 +1,164 @@ +use crate::{error::Error, ChainPath, ChainPathError, KeyIndex, extended_key::EthereumExPrivateKey, SubPath}; + +/// KeyChain derivation info +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Derivation { + /// depth, 0 if it is master key + pub depth: u8, + /// parent key + pub parent_key: Option, + /// key_index which used with parent key to derive this key + pub key_index: Option, +} + +impl Derivation { + pub fn master() -> Self { + Derivation { + depth: 0, + parent_key: None, + key_index: None, + } + } +} + +impl Default for Derivation { + fn default() -> Self { + Derivation::master() + } +} + +pub trait KeyChain { + fn derive_private_key( + &self, + chain_path: ChainPath, + ) -> Result<(EthereumExPrivateKey, Derivation), Error>; +} + +pub struct DefaultKeyChain { + master_key: EthereumExPrivateKey, +} + +impl DefaultKeyChain { + #[allow(dead_code)] + pub fn new(master_key: EthereumExPrivateKey) -> Self { + DefaultKeyChain { master_key } + } +} + +impl KeyChain for DefaultKeyChain { + fn derive_private_key( + &self, + chain_path: ChainPath, + ) -> Result<(EthereumExPrivateKey, Derivation), Error> { + let mut iter = chain_path.iter(); + // chain_path must start with root + if iter.next() != Some(Ok(SubPath::Root)) { + return Err(ChainPathError::Invalid.into()); + } + let mut key = self.master_key.clone(); + let mut depth = 0; + let mut parent_key = None; + let mut key_index = None; + for sub_path in iter { + match sub_path? { + SubPath::Child(child_key_index) => { + depth += 1; + key_index = Some(child_key_index); + let child_key = key.derive_private_key(child_key_index)?; + parent_key = Some(key); + key = child_key; + } + _ => return Err(ChainPathError::Invalid.into()), + } + } + + Ok(( + key, + Derivation { + depth, + parent_key, + key_index, + }, + )) + } +} +/* +#[cfg(test)] +mod tests { + use super::*; + use crate::{solana::PrivKey, solana::PubKey, traits::Serialize}; + use base58::ToBase58; + + fn from_hex(hex_string: &str) -> Vec { + let strip_prefix = hex_string.starts_with("0x"); + if strip_prefix { + hex::decode(&hex_string[2..]).expect("decode") + } else { + hex::decode(hex_string).expect("decode") + } + } + + fn to_hex(buf: Vec) -> String { + hex::encode(buf) + } + + #[test] + fn test_bip32_vector_1() { + let seed = from_hex("000102030405060708090a0b0c0d0e0f"); + let key_chain = + DefaultKeyChain::new(EthereumExPrivateKey::new_master_key(&seed).expect("master key")); + for (chain_path, hex_priv_key, hex_pub_key) in &[ + ( + "m", + "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7", + "C5ukMV73nk32h52MjxtnZXTrrr7rupD9CTDDRnYYDRYQ", + ), + ( + "m/0H", + "68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3", + "ATcCGRoY87cSJESCXbHXEX6CDWQxepAViUvVnNsELhRu", + ), + ( + "m/0H/1H", + "b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2", + "2hMz2f8WbLw5m2icKR2WVrcizvnguw8xaAnXjaeohuHQ", + ), + ( + "m/0H/1H/2H", + "92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9", + "CkYmXLvWehLXBzUAJ3g3wsfc5QjoCtWtSydquF7HDxXS", + ), + ( + "m/0H/1H/2H/2H", + "30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662", + "ALYYdMp2jVV4HGsZZPfLy1BQLMHL2CQG5XHpzr2XiHCw", + ), + ( + "m/0H/1H/2H/2H/1000000000H", + "8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793", + "53n47S4RT9ozx5KrpH6uYfdnAjrTBJri8qZJBvRfw1Bf", + ), + ] { + let (key, derivation) = key_chain + .derive_private_key(ChainPath::from(*chain_path)) + .expect("fetch key"); + let priv_key = PrivKey { + derivation, + extended_key: key, + }; + + let pub_key = PubKey::from_private_key(&priv_key); + + assert_eq!( + to_hex(priv_key.extended_key.private_key.to_bytes().to_vec()), + *hex_priv_key + ); + + assert_eq!( + &Serialize::>::serialize(&pub_key.extended_key).to_base58(), + hex_pub_key + ); + } + } +} +*/ \ No newline at end of file diff --git a/src/extended_key.rs b/src/extended_key.rs index c23e3d4..2b1e03f 100644 --- a/src/extended_key.rs +++ b/src/extended_key.rs @@ -128,6 +128,116 @@ impl Deserialize<&[u8], Error> for SolanaExPublicKey { } } +#[derive(Debug, Clone)] +pub struct EthereumExPrivateKey { + pub private_key: Rc, + pub chain_code: ChainCode, +} + +impl Eq for EthereumExPrivateKey {} + +impl PartialEq for EthereumExPrivateKey { + fn eq(&self, other: &Self) -> bool { + self.private_key.as_bytes() == other.private_key.as_bytes() + && self.chain_code == other.chain_code + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EthereumExPublicKey(pub Pk); + +impl EthereumExPrivateKey { + pub fn new_master_key(seed: &[u8]) -> Result { + let signature = { + let signing_key = Key::new(HMAC_SHA512, b"ed25519 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 = Rc::new(Sk::from_bytes(key)?); + + Ok(EthereumExPrivateKey { + 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.to_bytes()); + h.update(&index.to_be_bytes()); + h.sign() + } + + pub fn derive_private_key(&self, key_index: KeyIndex) -> Result { + if !key_index.is_valid() { + return Err(Error::KeyIndexOutOfRange); + } + + let signature = self.sign_hardended_key(key_index.raw_index()); + + let sig_bytes = signature.as_ref(); + let (key, chain_code) = sig_bytes.split_at(sig_bytes.len() / 2); + let private_key = Rc::new(Sk::from_bytes(key)?); + + Ok(EthereumExPrivateKey { + private_key, + chain_code: chain_code.to_vec(), + }) + } +} + +impl EthereumExPublicKey { + pub fn from_private_key(extended_key: &EthereumExPrivateKey) -> Result { + let private_key = Rc::try_unwrap(Rc::clone(&extended_key.private_key)).unwrap_err(); + + let public_key = Pk::from(&*private_key); + + Ok(EthereumExPublicKey(public_key)) + } + + pub fn is_on_curve(bytes: &[u8]) -> bool { + CompressedEdwardsY::from_slice(bytes).decompress().is_some() + } +} + +impl Serialize> for EthereumExPrivateKey { + fn serialize(&self) -> Vec { + let mut buf = self.private_key.to_bytes().to_vec(); + buf.extend(&self.chain_code); + buf + } +} + +impl Deserialize<&[u8], Error> for EthereumExPrivateKey { + fn deserialize(data: &[u8]) -> Result { + let private_key = Sk::from_bytes(&data[..32])?; + let chain_code = data[32..].to_vec(); + Ok(EthereumExPrivateKey { + private_key: Rc::new(private_key), + chain_code, + }) + } +} + +impl Serialize> for EthereumExPublicKey { + fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } +} + +impl Deserialize<&[u8], Error> for EthereumExPublicKey { + fn deserialize(data: &[u8]) -> Result { + let public_key = Pk::from_bytes(&data[..32]).unwrap(); + + Ok(EthereumExPublicKey(public_key)) + } +} + #[cfg(test)] mod tests { use crate::error::Error; diff --git a/src/lib.rs b/src/lib.rs index 2e2f522..ec08ba5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod mnemonic; pub mod chain_path; pub mod solana; +pub mod ethereum; pub mod traits; pub use crate::extended_key::{key_index::KeyIndex, SolanaExPrivateKey, SolanaExPublicKey}; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..97b399e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,70 @@ +use hdwallet_rs::{ + extended_key::{SolanaExPrivateKey, SolanaExPublicKey, EthereumExPrivateKey, EthereumExPublicKey}, + //solana::{ + // PrivKey, + // PubKey, + // key_chain::{DefaultKeyChain, KeyChain}, + //}, + ethereum::{ + PrivKey, + PubKey, + key_chain::{DefaultKeyChain, KeyChain}, + }, + chain_path::ChainPath, + traits::Serialize, + mnemonic +}; +/* +fn main() { + *********** SOL *********** + let new_mnemonic = mnemonic::new_mnemonic(24, "English"); + println!("{:?}", new_mnemonic); + let new_seed = mnemonic::new_seed(new_mnemonic.unwrap(), "".to_string()); + // let private_key = ExtendedPrivateKey::new_master_private_key(new_seed).unwrap(); + //let master_key = ExtendedEdDsaPrivKey::new_master_key(&new_seed).unwrap(); + let master_key = SolanaExPrivateKey::new_master_key(&new_seed); + println!("{:?}", master_key); + let seed = hex::decode("000102030405060708090a0b0c0d0e8f").expect("decode"); + let key_chain = + DefaultKeyChain::new(SolanaExPrivateKey::new_master_key(&seed).unwrap()); + let key = key_chain + .derive_private_key(ChainPath::from("m/44/501'/1'")) + .expect("fetch key"); + let priv_key = PrivKey { + derivation: key.1.clone(), + extended_key: key.0.clone(), + }; + // println!("{:?}", PubKey::from_private_key(&priv_key).extended_key.0.to_bytes()); + //println!("{:?}", &Serialize::::serialize(&PubKey::from_private_key(&priv_key))); + //println!("base58-encode -> {:?} \n\n\n extended_key -> {:?} \n\n\n derivation -> {:?} \n\n\n", &Serialize::::serialize(&priv_key), hex::encode(priv_key.extended_key.private_key.to_bytes()), priv_key.derivation); + //println!("{:?}", master_key.derive_private_key(KeyIndex::from(20))); +} +*/ + + + +fn main() { + //*********** SOL *********** + let new_mnemonic = mnemonic::new_mnemonic(24, "English"); + println!("{:?}", new_mnemonic); + let new_seed = mnemonic::new_seed(new_mnemonic.unwrap(), "".to_string()); + let private_key = EthereumExPrivateKey::new_master_key(&new_seed).unwrap(); + //let master_key = ExtendedEdDsaPrivKey::new_master_key(&new_seed).unwrap(); + let master_key = SolanaExPrivateKey::new_master_key(&new_seed); + println!("{:?}", master_key); + let seed = hex::decode("000102030405060708090a0b0c0d0e8f").expect("decode"); + let key_chain = + DefaultKeyChain::new(EthereumExPrivateKey::new_master_key(&seed).unwrap()); + let key = key_chain + .derive_private_key(ChainPath::from("m/44/60/0")) + .expect("fetch key"); + let priv_key = PrivKey { + derivation: key.1.clone(), + extended_key: key.0.clone(), + }; + println!("{:?}", PubKey::from_private_key(&priv_key).extended_key.0.to_bytes()); + println!("{:?}", &Serialize::::serialize(&PubKey::from_private_key(&priv_key))); + //println!("base58-encode -> {:?} \n\n\n extended_key -> {:?} \n\n\n derivation -> {:?} \n\n\n", &Serialize::::serialize(&priv_key), hex::encode(priv_key.extended_key.private_key.to_bytes()), priv_key.derivation); + //println!("{:?}", master_key.derive_private_key(KeyIndex::from(20))); +} +