diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c3a09c4 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,39 @@ +name: CI + +# This workflow run tests and build for each push + +on: + push: + branches: + - '*' + +jobs: + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Update local toolchain + run: | + rustup update + rustup install nightly + + - name: Toolchain info + run: | + cargo --version --verbose + rustc --version + + - name: Lint + run: | + cargo fmt -- --check + cargo clippy -- -D warnings + + - name: Test + run: | + cargo check + cargo test --all + + - name: Build + run: | + cargo build --release diff --git a/Cargo.toml b/Cargo.toml index 36f33a1..77a7e4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,12 @@ repository = "https://github.com/maciejhirsz/tiny-hderive" keywords = ["bitcoin", "ethereum", "bip32", "bip39", "bip44"] [dependencies] -libsecp256k1 = "0.3.5" -base58 = "0.1.0" -sha2 = "0.8.0" -hmac = "0.7.0" +libsecp256k1 = "0.7.1" +base58 = "0.2.0" +sha2 = "0.10.7" +hmac = "0.12.1" memzero = "0.1.0" [dev-dependencies] -ethsign = { version = "0.3", default-features = false, features = ["secp256k1-rs"] } +ethsign = { version = "0.9", default-features = false, features = ["secp256k1"] } tiny-bip39 = "0.6" diff --git a/src/bip32.rs b/src/bip32.rs index 60fb62b..4aa733c 100644 --- a/src/bip32.rs +++ b/src/bip32.rs @@ -1,11 +1,11 @@ -use secp256k1::{SecretKey, PublicKey}; use base58::FromBase58; -use sha2::Sha512; use hmac::{Hmac, Mac}; +use libsecp256k1::{PublicKey, SecretKey}; use memzero::Memzero; +use sha2::Sha512; +use std::fmt; use std::ops::Deref; use std::str::FromStr; -use std::fmt; use crate::bip44::{ChildNumber, IntoDerivationPath}; use crate::Error; @@ -49,10 +49,11 @@ impl ExtendedPrivKey { where Path: IntoDerivationPath, { - let mut hmac: Hmac = Hmac::new_varkey(b"Bitcoin seed").expect("seed is always correct; qed"); - hmac.input(seed); + let mut hmac: Hmac = + Hmac::new_from_slice(b"Bitcoin seed").expect("seed is always correct; qed"); + hmac.update(seed); - let result = hmac.result().code(); + let result = hmac.finalize().into_bytes(); let (secret_key, chain_code) = result.split_at(32); let mut sk = ExtendedPrivKey { @@ -72,27 +73,29 @@ impl ExtendedPrivKey { } pub fn child(&self, child: ChildNumber) -> Result { - let mut hmac: Hmac = Hmac::new_varkey(&self.chain_code) - .map_err(|_| Error::InvalidChildNumber)?; + let mut hmac: Hmac = + Hmac::new_from_slice(&self.chain_code).map_err(|_| Error::InvalidChildNumber)?; if child.is_normal() { - hmac.input(&PublicKey::from_secret_key(&self.secret_key).serialize_compressed()[..]); + hmac.update(&PublicKey::from_secret_key(&self.secret_key).serialize_compressed()[..]); } else { - hmac.input(&[0]); - hmac.input(&self.secret_key.serialize()[..]); + hmac.update(&[0]); + hmac.update(&self.secret_key.serialize()[..]); } - hmac.input(&child.to_bytes()); + hmac.update(&child.to_bytes()); - let result = hmac.result().code(); + let result = hmac.finalize().into_bytes(); let (secret_key, chain_code) = result.split_at(32); - let mut secret_key = SecretKey::parse_slice(&secret_key).map_err(Error::Secp256k1)?; - secret_key.tweak_add_assign(&self.secret_key).map_err(Error::Secp256k1)?; + let mut secret_key = SecretKey::parse_slice(secret_key).map_err(Error::Secp256k1)?; + secret_key + .tweak_add_assign(&self.secret_key) + .map_err(Error::Secp256k1)?; Ok(ExtendedPrivKey { secret_key, - chain_code: Protected::from(&chain_code) + chain_code: Protected::from(&chain_code), }) } } @@ -101,7 +104,9 @@ impl FromStr for ExtendedPrivKey { type Err = Error; fn from_str(xprv: &str) -> Result { - let data = xprv.from_base58().map_err(|_| Error::InvalidExtendedPrivKey)?; + let data = xprv + .from_base58() + .map_err(|_| Error::InvalidExtendedPrivKey)?; if data.len() != 82 { return Err(Error::InvalidExtendedPrivKey); @@ -109,7 +114,7 @@ impl FromStr for ExtendedPrivKey { Ok(ExtendedPrivKey { chain_code: Protected::from(&data[13..45]), - secret_key: SecretKey::parse_slice(&data[46..78]).map_err(|e| Error::Secp256k1(e))? + secret_key: SecretKey::parse_slice(&data[46..78]).map_err(Error::Secp256k1)?, }) } } @@ -117,7 +122,7 @@ impl FromStr for ExtendedPrivKey { #[cfg(test)] mod tests { use super::*; - use bip39::{Mnemonic, Language, Seed}; + use bip39::{Language, Mnemonic, Seed}; use ethsign::SecretKey; #[test] @@ -125,14 +130,19 @@ mod tests { let phrase = "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside"; let expected_secret_key = b"\xff\x1e\x68\xeb\x7b\xf2\xf4\x86\x51\xc4\x7e\xf0\x17\x7e\xb8\x15\x85\x73\x22\x25\x7c\x58\x94\xbb\x4c\xfd\x11\x76\xc9\x98\x93\x14"; - let expected_address: &[u8] = b"\x63\xF9\xA9\x2D\x8D\x61\xb4\x8a\x9f\xFF\x8d\x58\x08\x04\x25\xA3\x01\x2d\x05\xC8"; + let expected_address: &[u8] = + b"\x63\xF9\xA9\x2D\x8D\x61\xb4\x8a\x9f\xFF\x8d\x58\x08\x04\x25\xA3\x01\x2d\x05\xC8"; let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap(); let seed = Seed::new(&mnemonic, ""); let account = ExtendedPrivKey::derive(seed.as_bytes(), "m/44'/60'/0'/0/0").unwrap(); - assert_eq!(expected_secret_key, &account.secret(), "Secret key is invalid"); + assert_eq!( + expected_secret_key, + &account.secret(), + "Secret key is invalid" + ); let secret_key = SecretKey::from_raw(&account.secret()).unwrap(); let public_key = secret_key.public(); @@ -140,9 +150,16 @@ mod tests { assert_eq!(expected_address, public_key.address(), "Address is invalid"); // Test child method - let account = ExtendedPrivKey::derive(seed.as_bytes(), "m/44'/60'/0'/0").unwrap().child(ChildNumber::from_str("0").unwrap()).unwrap(); - - assert_eq!(expected_secret_key, &account.secret(), "Secret key is invalid"); + let account = ExtendedPrivKey::derive(seed.as_bytes(), "m/44'/60'/0'/0") + .unwrap() + .child(ChildNumber::from_str("0").unwrap()) + .unwrap(); + + assert_eq!( + expected_secret_key, + &account.secret(), + "Secret key is invalid" + ); let secret_key = SecretKey::from_raw(&account.secret()).unwrap(); let public_key = secret_key.public(); diff --git a/src/bip44.rs b/src/bip44.rs index b75b7e9..c915653 100644 --- a/src/bip44.rs +++ b/src/bip44.rs @@ -9,43 +9,43 @@ const HARDENED_BIT: u32 = 1 << 31; pub struct ChildNumber(u32); impl ChildNumber { - pub fn is_hardened(&self) -> bool { - self.0 & HARDENED_BIT == HARDENED_BIT - } + pub fn is_hardened(&self) -> bool { + self.0 & HARDENED_BIT == HARDENED_BIT + } - pub fn is_normal(&self) -> bool { - self.0 & HARDENED_BIT == 0 - } + pub fn is_normal(&self) -> bool { + self.0 & HARDENED_BIT == 0 + } - pub fn to_bytes(&self) -> [u8; 4] { - self.0.to_be_bytes() - } + pub fn to_bytes(&self) -> [u8; 4] { + self.0.to_be_bytes() + } - pub fn hardened_from_u32(index: u32) -> Self { - ChildNumber(index | HARDENED_BIT) - } + pub fn hardened_from_u32(index: u32) -> Self { + ChildNumber(index | HARDENED_BIT) + } - pub fn non_hardened_from_u32(index: u32) -> Self { - ChildNumber(index) - } + pub fn non_hardened_from_u32(index: u32) -> Self { + ChildNumber(index) + } } impl FromStr for ChildNumber { type Err = Error; fn from_str(child: &str) -> Result { - let (child, mask) = if child.ends_with('\'') { - (&child[..child.len() - 1], HARDENED_BIT) - } else { - (child, 0) - }; + let (child, mask) = if let Some(prefix) = child.strip_suffix('\'') { + (prefix, HARDENED_BIT) + } else { + (child, 0) + }; let index: u32 = child.parse().map_err(|_| Error::InvalidChildNumber)?; if index & HARDENED_BIT == 0 { - Ok(ChildNumber(index | mask)) + Ok(ChildNumber(index | mask)) } else { - Err(Error::InvalidChildNumber) + Err(Error::InvalidChildNumber) } } } @@ -66,52 +66,59 @@ impl FromStr for DerivationPath { } Ok(DerivationPath { - path: path.map(str::parse).collect::, Error>>()? + path: path + .map(str::parse) + .collect::, Error>>()?, }) } } impl DerivationPath { - pub fn as_ref(&self) -> &[ChildNumber] { - &self.path - } + pub fn iter(&self) -> impl Iterator { + self.path.iter() + } +} - pub fn iter(&self) -> impl Iterator { - self.path.iter() - } +impl AsRef<[ChildNumber]> for DerivationPath { + fn as_ref(&self) -> &[ChildNumber] { + &self.path + } } pub trait IntoDerivationPath { - fn into(self) -> Result; + fn into(self) -> Result; } impl IntoDerivationPath for DerivationPath { - fn into(self) -> Result { - Ok(self) - } + fn into(self) -> Result { + Ok(self) + } } impl IntoDerivationPath for &str { - fn into(self) -> Result { - self.parse() - } + fn into(self) -> Result { + self.parse() + } } #[cfg(test)] mod tests { - use super::*; - - #[test] - fn derive_path() { - let path: DerivationPath = "m/44'/60'/0'/0".parse().unwrap(); - - assert_eq!(path, DerivationPath { - path: vec![ - ChildNumber(44 | HARDENED_BIT), - ChildNumber(60 | HARDENED_BIT), - ChildNumber(0 | HARDENED_BIT), - ChildNumber(0), - ], - }); - } + use super::*; + + #[test] + fn derive_path() { + let path: DerivationPath = "m/44'/60'/0'/0".parse().unwrap(); + + assert_eq!( + path, + DerivationPath { + path: vec![ + ChildNumber(44 | HARDENED_BIT), + ChildNumber(60 | HARDENED_BIT), + ChildNumber(0 | HARDENED_BIT), + ChildNumber(0), + ], + } + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 70e4fee..8b8cc27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,12 +13,12 @@ //! assert_eq!(&ext.secret(), b"\x98\x84\xbf\x56\x24\xfa\xdd\x7f\xb2\x80\x4c\xfb\x0c\xb6\xf7\x1f\x28\x9e\x21\x1f\xcf\x0d\xe8\x36\xa3\x84\x17\x57\xda\xd9\x70\xd0"); //! ``` -pub mod bip44; pub mod bip32; +pub mod bip44; #[derive(Clone, PartialEq, Eq, Debug)] pub enum Error { - Secp256k1(secp256k1::Error), + Secp256k1(libsecp256k1::Error), InvalidChildNumber, InvalidDerivationPath, InvalidExtendedPrivKey,