diff --git a/Cargo.toml b/Cargo.toml index 7355665..905f629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ homepage = "https://github.com/tomusdrw/ethsign" license = "GPL-3.0" name = "ethsign" repository = "https://github.com/tomusdrw/ethsign" -version = "0.4.0" +version = "0.5.0" [dependencies] libsecp256k1 = { package = "libsecp256k1", version = "0.2.2", optional = true } diff --git a/res/scrypt-wallet.json b/res/scrypt-wallet.json new file mode 100644 index 0000000..2edf114 --- /dev/null +++ b/res/scrypt-wallet.json @@ -0,0 +1,21 @@ +{ + "address": "8e049da484e853d92d118be16377ff616275d470", + "crypto": { + "cipher": "aes-128-ctr", + "ciphertext": "7912715bd7c0754f393c83fb76e381e7390cf0d31bdf5b6cfb1e360b765b6afe", + "cipherparams": { + "iv": "49e5a2fc31dae4aa0277b8c498c546fd" + }, + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "5ae79ae9e57fd62902695637cfe134d14a7120fa6dc7bf1e9cf97e636eaccae1" + }, + "mac": "0a5194f97f6ffae80aa15d23f0a11a001b276fe7b76f2f3762b09df77fc81256" + }, + "id": "afb46908-5019-41d6-bc60-e369d3b5e1a7", + "version": 3 +} \ No newline at end of file diff --git a/src/ec.rs b/src/ec.rs index 4ba2727..884a59e 100644 --- a/src/ec.rs +++ b/src/ec.rs @@ -29,7 +29,11 @@ mod secp256k1 { Ok((rec_id.to_i32() as u8, data)) } - fn to_recoverable_signature(v: u8, r: &[u8; 32], s: &[u8; 32]) -> Result { + fn to_recoverable_signature( + v: u8, + r: &[u8; 32], + s: &[u8; 32], + ) -> Result { let rec_id = secp256k1::RecoveryId::from_i32(v as i32)?; let mut data = [0u8; 64]; @@ -56,14 +60,20 @@ mod secp256k1 { /// Checks ECDSA validity of `signature(v ,r ,s)` for `message` with `public` key. /// Returns `Ok(true)` on success. - pub fn verify(public: &[u8], v: u8, r: &[u8; 32], s: &[u8; 32], message: &[u8]) -> Result { + pub fn verify( + public: &[u8], + v: u8, + r: &[u8; 32], + s: &[u8; 32], + message: &[u8], + ) -> Result { let sig = to_recoverable_signature(v, r, s)?.to_standard(); let msg = secp256k1::Message::from_slice(message)?; match secp256k1::Secp256k1::new().verify(&msg, &sig, &to_pubkey(public)?) { Ok(_) => Ok(true), Err(Error::IncorrectSignature) => Ok(false), - Err(e) => Err(e) + Err(e) => Err(e), } } } @@ -82,7 +92,7 @@ mod secp256k1 { pub fn secret_to_public(secret: &[u8]) -> Result<[u8; 65], Error> { let sec = libsecp256k1::SecretKey::parse_slice(secret)?; let pubkey = libsecp256k1::PublicKey::from_secret_key(&sec); - + Ok(pubkey.serialize()) } @@ -96,7 +106,6 @@ mod secp256k1 { Ok((rec_id.serialize(), sig.serialize())) } - fn to_signature(r: &[u8; 32], s: &[u8; 32]) -> libsecp256k1::Signature { let mut data = [0u8; 64]; data[0..32].copy_from_slice(r); @@ -107,11 +116,11 @@ mod secp256k1 { /// Recover the signer of the message. pub fn recover(v: u8, r: &[u8; 32], s: &[u8; 32], message: &[u8]) -> Result<[u8; 65], Error> { - let rec_id= libsecp256k1::RecoveryId::parse(v)?; - let sig= to_signature(r, s); + let rec_id = libsecp256k1::RecoveryId::parse(v)?; + let sig = to_signature(r, s); let msg = libsecp256k1::Message::parse_slice(message)?; let pubkey = libsecp256k1::recover(&msg, &sig, &rec_id)?; - + Ok(pubkey.serialize()) } @@ -123,8 +132,14 @@ mod secp256k1 { /// Checks ECDSA validity of `signature(r, s)` for `message` with `public` key. /// Returns `Ok(true)` on success. - pub fn verify(public: &[u8], _v: u8, r: &[u8; 32], s: &[u8; 32], message: &[u8]) -> Result { - let sig= to_signature(r, s); + pub fn verify( + public: &[u8], + _v: u8, + r: &[u8; 32], + s: &[u8; 32], + message: &[u8], + ) -> Result { + let sig = to_signature(r, s); let msg = libsecp256k1::Message::parse_slice(message)?; Ok(libsecp256k1::verify(&msg, &sig, &to_pubkey(public)?)) diff --git a/src/error.rs b/src/error.rs index a864390..1694051 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,8 @@ pub enum Error { InvalidPassword, /// Crypto error Crypto(parity_crypto::Error), + /// Scrypt error + ScryptError(parity_crypto::error::ScryptError), /// Secp256k1 error Secp256k1(crate::ec::Error), } @@ -18,6 +20,7 @@ impl std::fmt::Display for Error { match *self { Error::InvalidPassword => write!(fmt, "Invalid Password"), Error::Crypto(ref e) => write!(fmt, "Crypto: {}", e), + Error::ScryptError(ref e) => write!(fmt, "ScryptError: {}", e), Error::Secp256k1(ref e) => write!(fmt, "Secp256k1: {:?}", e), } } @@ -26,7 +29,7 @@ impl std::fmt::Display for Error { impl std::error::Error for Error {} impl From for Error { - fn from(e: ec::Error) -> Error { - Error::Secp256k1(e) - } + fn from(e: ec::Error) -> Error { + Error::Secp256k1(e) + } } diff --git a/src/key.rs b/src/key.rs index 0563f26..119a660 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,12 +1,9 @@ -use std::{ - fmt, - num::NonZeroU32, -}; +use std::{fmt, num::NonZeroU32}; use crate::ec; +use crate::error::Error; use crate::keyfile::Crypto; use crate::protected::Protected; -use crate::error::Error; use rustc_hex::ToHex; /// Message signature @@ -85,7 +82,13 @@ impl PublicKey { /// Checks ECDSA validity of `signature` for `message` with this public key. /// Returns `Ok(true)` on success. pub fn verify(&self, signature: &Signature, message: &[u8]) -> Result { - ec::verify(&self.public, signature.v, &signature.r, &signature.s, message) + ec::verify( + &self.public, + signature.v, + &signature.r, + &signature.s, + message, + ) } } @@ -124,8 +127,7 @@ impl SecretKey { let uncompressed = ec::secret_to_public(self.secret.as_ref()) .expect("The key is validated in the constructor; qed"); - PublicKey::from_slice(&uncompressed[1..]) - .expect("The length of the key is correct; qed") + PublicKey::from_slice(&uncompressed[1..]).expect("The length of the key is correct; qed") } /// Sign given 32-byte message with the key. @@ -148,35 +150,65 @@ mod tests { use rustc_hex::{FromHex, ToHex}; #[test] - fn should_read_keyfile() { + fn should_read_pbkdf_keyfile() { let keyfile: KeyFile = serde_json::from_str(include_str!("../res/wallet.json")).unwrap(); let password = b""; - let key = SecretKey::from_crypto(&keyfile.crypto, &Protected::new(password.to_vec())).unwrap(); + let key = + SecretKey::from_crypto(&keyfile.crypto, &Protected::new(password.to_vec())).unwrap(); let pub_key = key.public(); - assert_eq!(pub_key.address().to_hex::(), "005b3bcf82085eededd551f50de7892471ffb272"); + assert_eq!( + pub_key.address().to_hex::(), + "005b3bcf82085eededd551f50de7892471ffb272" + ); assert_eq!(&pub_key.bytes().to_hex::(), "782cc7dd72426893ae0d71477e41c41b03249a2b72e78eefcfe0baa9df604a8f979ab94cd23d872dac7bfa8d07d8b76b26efcbede7079f1c5cacd88fe9858f6e"); } + #[test] + fn should_read_scrypt_keyfile() { + let keyfile: KeyFile = + serde_json::from_str(include_str!("../res/scrypt-wallet.json")).unwrap(); + let password = b"geth"; + let key = + SecretKey::from_crypto(&keyfile.crypto, &Protected::new(password.to_vec())).unwrap(); + let pub_key = key.public(); + + assert_eq!( + pub_key.address().to_hex::(), + "8e049da484e853d92d118be16377ff616275d470" + ); + assert_eq!(&pub_key.bytes().to_hex::(), "e54553168b429c0407c5e4338f0a61fa7a515ff382ada9f323e313353c1904b0d8039f99e213778ba479196ef24c838e41dc77215c41895fe15e4de018d7d1dd"); + } + #[test] fn should_derive_public_and_address_correctly() { - let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap(); + let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7" + .from_hex() + .unwrap(); let key = SecretKey::from_raw(&secret).unwrap(); let pub_key = key.public(); assert_eq!(&pub_key.bytes().to_hex::(), "3fa8c08c65a83f6b4ea3e04e1cc70cbe3cd391499e3e05ab7dedf28aff9afc538200ff93e3f2b2cb5029f03c7ebee820d63a4c5a9541c83acebe293f54cacf0e"); - assert_eq!(pub_key.address().to_hex::(), "00a329c0648769a73afac7f9381e08fb43dbea72"); + assert_eq!( + pub_key.address().to_hex::(), + "00a329c0648769a73afac7f9381e08fb43dbea72" + ); } #[test] fn should_have_debug_impl() { - let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap(); + let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7" + .from_hex() + .unwrap(); let key = SecretKey::from_raw(&secret).unwrap(); let pub_key = key.public(); let signature = key.sign(&secret).unwrap(); - assert_eq!(format!("{:?}", key), "SecretKey { secret: Protected(77..183) }"); + assert_eq!( + format!("{:?}", key), + "SecretKey { secret: Protected(77..183) }" + ); assert_eq!(format!("{:?}", pub_key), "PublicKey { address: \"00a329c0648769a73afac7f9381e08fb43dbea72\", public: \"3fa8c08c65a83f6b4ea3e04e1cc70cbe3cd391499e3e05ab7dedf28aff9afc538200ff93e3f2b2cb5029f03c7ebee820d63a4c5a9541c83acebe293f54cacf0e\" }"); assert_eq!(format!("{:?}", signature), "Signature { v: 0, r: \"8a4f2d73a2cc80cdfe27c6e3ab68de7913865a5968298731bee7b4673752fd76\", s: \"8a4f2d73a2cc80cdfe27c6e3ab68de7913865a5968298731bee7b4673752fd76\" }"); } @@ -184,15 +216,21 @@ mod tests { #[test] fn should_recover_succesfuly() { let v = 0u8; - let r2: Vec = "319a63079d7cdd4e1ec99996f840253c1b0e41a4caf474602c43e83b5a8de183".from_hex().unwrap(); - let s2: Vec = "2e9424ac2ba94abc12a79349888545f26958c2fccc28d91f6dee72ab9c069738".from_hex().unwrap(); + let r2: Vec = "319a63079d7cdd4e1ec99996f840253c1b0e41a4caf474602c43e83b5a8de183" + .from_hex() + .unwrap(); + let s2: Vec = "2e9424ac2ba94abc12a79349888545f26958c2fccc28d91f6dee72ab9c069738" + .from_hex() + .unwrap(); let mut s = [0u8; 32]; s.copy_from_slice(&s2); let mut r = [0u8; 32]; r.copy_from_slice(&r2); let signature = Signature { v, s, r }; - let message: Vec = "044a19199dc40e61210715bea94bcb0fff4c8dfa1c20988ab7783fc82c802a9f".from_hex().unwrap(); + let message: Vec = "044a19199dc40e61210715bea94bcb0fff4c8dfa1c20988ab7783fc82c802a9f" + .from_hex() + .unwrap(); let pub_key = signature.recover(&message).unwrap(); assert_eq!(format!("{:?}", pub_key), "PublicKey { address: \"00af8b5cc1f8d0e862b4f303c0fa59b3709c2bb3\", public: \"929acaa0a4a4246225162496cc18e50719bb057519a150a94cfef77ae5e0dd50786c54cfe05f564d2ef09aae0b587bf73b83f45636def775bbf9010dded0e235\" }"); @@ -200,23 +238,33 @@ mod tests { #[test] fn should_convert_to_crypto_and_back() { - let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap(); + let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7" + .from_hex() + .unwrap(); let key = SecretKey::from_raw(&secret).unwrap(); let pass = "hunter2".into(); - let crypto = key.to_crypto(&pass, NonZeroU32::new(4096).unwrap()).unwrap(); + let crypto = key + .to_crypto(&pass, NonZeroU32::new(4096).unwrap()) + .unwrap(); let key2 = SecretKey::from_crypto(&crypto, &pass).unwrap(); - - assert_eq!(key.public().bytes().as_ref(), key2.public().bytes().as_ref()); + assert_eq!( + key.public().bytes().as_ref(), + key2.public().bytes().as_ref() + ); } #[test] fn test_sign_verify() { // given - let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap(); + let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7" + .from_hex() + .unwrap(); let key = SecretKey::from_raw(&secret).unwrap(); - let message: Vec = "12da94d92a71f7692013002513e5bc4a3180344cfe3292e2b54c15f9d4421965".from_hex().unwrap(); + let message: Vec = "12da94d92a71f7692013002513e5bc4a3180344cfe3292e2b54c15f9d4421965" + .from_hex() + .unwrap(); // when let sig = key.sign(&message).unwrap(); @@ -228,11 +276,18 @@ mod tests { #[test] fn test_sign_verify_fail_for_other_key() { // given - let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap(); + let secret: Vec = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7" + .from_hex() + .unwrap(); let key = SecretKey::from_raw(&secret).unwrap(); - let other_secret: Vec = "2222222222222222222222222222222222222222222222222222222222222222".from_hex().unwrap(); + let other_secret: Vec = + "2222222222222222222222222222222222222222222222222222222222222222" + .from_hex() + .unwrap(); let other_key = SecretKey::from_raw(&other_secret).unwrap(); - let message: Vec = "12da94d92a71f7692013002513e5bc4a3180344cfe3292e2b54c15f9d4421965".from_hex().unwrap(); + let message: Vec = "12da94d92a71f7692013002513e5bc4a3180344cfe3292e2b54c15f9d4421965" + .from_hex() + .unwrap(); // when let sig = key.sign(&message).unwrap(); diff --git a/src/keyfile.rs b/src/keyfile.rs index 84e8cf8..47a7930 100644 --- a/src/keyfile.rs +++ b/src/keyfile.rs @@ -1,46 +1,45 @@ //! JSON keyfile representation. -use std::num::NonZeroU32; -use parity_crypto::Keccak256; -use crate::Protected; use crate::error::Error; +use crate::Protected; +use parity_crypto::Keccak256; +use std::num::NonZeroU32; -use serde::{Serialize, Deserialize}; use rand::{thread_rng, RngCore}; +use serde::{Deserialize, Serialize}; /// A set of bytes. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Bytes(#[serde(with="bytes")] pub Vec); +pub struct Bytes(#[serde(with = "bytes")] pub Vec); /// Key file #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct KeyFile { /// Keyfile UUID - pub id: String, + pub id: String, /// Keyfile version - pub version: u64, + pub version: u64, /// Keyfile crypto - pub crypto: Crypto, + pub crypto: Crypto, /// Optional address - pub address: Option, + pub address: Option, } /// Encrypted secret #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Crypto { /// Cipher definition - pub cipher: Cipher, + pub cipher: Cipher, /// Cipher parameters pub cipherparams: Aes128Ctr, /// Cipher bytes - pub ciphertext: Bytes, - /// Key-derivation function - pub kdf: Kdf, - /// KDF params - pub kdfparams: Pbkdf2, + pub ciphertext: Bytes, + /// KDF + #[serde(flatten)] + pub kdf: Kdf, /// MAC - pub mac: Bytes, + pub mac: Bytes, } /// Cipher kind @@ -48,7 +47,7 @@ pub struct Crypto { pub enum Cipher { /// AES 128 CTR #[serde(rename = "aes-128-ctr")] - Aes128Ctr, + Aes128Ctr, } /// AES 128 CTR params @@ -60,23 +59,40 @@ pub struct Aes128Ctr { /// Key-Derivation function #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "camelCase", tag = "kdf", content = "kdfparams")] pub enum Kdf { /// Password-based KDF 2 - Pbkdf2, + Pbkdf2(Pbkdf2), + /// Scrypt + Scrypt(Scrypt), } /// PBKDF2 params #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Pbkdf2 { /// C - pub c: NonZeroU32, + pub c: NonZeroU32, /// DKLen - pub dklen: u32, + pub dklen: u32, /// Prf - pub prf: Prf, + pub prf: Prf, /// Salt - pub salt: Bytes, + pub salt: Bytes, +} + +/// Scrypt params +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Scrypt { + /// DKLen + pub dklen: u32, + /// P + pub p: u32, + /// N + pub n: u32, + /// R + pub r: u32, + /// Salt + pub salt: Bytes, } /// PRF @@ -89,7 +105,11 @@ pub enum Prf { impl Crypto { /// Encrypt plain data with password - pub fn encrypt(plain: &[u8], password: &Protected, iterations: NonZeroU32) -> Result { + pub fn encrypt( + plain: &[u8], + password: &Protected, + iterations: NonZeroU32, + ) -> Result { let mut rng = thread_rng(); let mut salt = [0u8; 32]; @@ -110,7 +130,8 @@ impl Crypto { // aes-128-ctr with initial vector of iv parity_crypto::aes::encrypt_128_ctr(&derived_left_bits, &iv, plain, &mut *ciphertext.0) - .map_err(parity_crypto::Error::from).map_err(Error::Crypto)?; + .map_err(parity_crypto::Error::from) + .map_err(Error::Crypto)?; // KECCAK(DK[16..31] ++ ), where DK[16..31] - derived_right_bits let mac = parity_crypto::derive_mac(&derived_right_bits, &*ciphertext.0).keccak256(); @@ -121,24 +142,31 @@ impl Crypto { iv: Bytes(iv.to_vec()), }, ciphertext, - kdf: Kdf::Pbkdf2, - kdfparams: Pbkdf2 { + kdf: Kdf::Pbkdf2(Pbkdf2 { c: iterations, dklen: parity_crypto::KEY_LENGTH as u32, prf: Prf::HmacSha256, salt: Bytes(salt.to_vec()), - }, + }), mac: Bytes(mac.to_vec()), }) } /// Decrypt into plain data pub fn decrypt(&self, password: &Protected) -> Result, Error> { - let (left_bits, right_bits) = parity_crypto::derive_key_iterations( - password.as_ref(), - &self.kdfparams.salt.0, - self.kdfparams.c, - ); + let (left_bits, right_bits) = match self.kdf { + Kdf::Pbkdf2(ref params) => { + parity_crypto::derive_key_iterations(password.as_ref(), ¶ms.salt.0, params.c) + } + Kdf::Scrypt(ref params) => parity_crypto::scrypt::derive_key( + password.as_ref(), + ¶ms.salt.0, + params.n, + params.p, + params.r, + ) + .map_err(Error::ScryptError)?, + }; let mac = parity_crypto::derive_mac(&right_bits, &self.ciphertext.0).keccak256(); @@ -153,7 +181,9 @@ impl Crypto { &self.cipherparams.iv.0, &self.ciphertext.0, &mut plain, - ).map_err(parity_crypto::Error::from).map_err(Error::Crypto)?; + ) + .map_err(parity_crypto::Error::from) + .map_err(Error::Crypto)?; Ok(plain) } @@ -162,10 +192,11 @@ impl Crypto { mod bytes { use std::fmt; - use serde::{de, Serializer, Deserializer}; + use serde::{de, Deserializer, Serializer}; /// Serializes a slice of bytes. - pub fn serialize(bytes: &[u8], serializer: S) -> Result where + pub fn serialize(bytes: &[u8], serializer: S) -> Result + where S: Serializer, { let it: String = rustc_hex::ToHex::to_hex(bytes); @@ -173,7 +204,8 @@ mod bytes { } /// Deserialize into vector of bytes. - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de>, { struct Visitor; @@ -187,11 +219,10 @@ mod bytes { fn visit_str(self, v: &str) -> Result { if v.len() % 2 != 0 { - return Err(E::invalid_length(v.len(), &self)) + return Err(E::invalid_length(v.len(), &self)); } - ::rustc_hex::FromHex::from_hex(&v) - .map_err(|e| E::custom(e.to_string())) + ::rustc_hex::FromHex::from_hex(&v).map_err(|e| E::custom(e.to_string())) } fn visit_string(self, v: String) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 7403090..6456a7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,8 @@ #![warn(missing_docs)] -mod error; mod ec; +mod error; mod key; mod protected; diff --git a/src/protected.rs b/src/protected.rs index 733a6df..c227664 100644 --- a/src/protected.rs +++ b/src/protected.rs @@ -3,7 +3,6 @@ use memzero::Memzero; /// A protected set of bytes. pub struct Protected(Memzero>); - impl>> From for Protected { fn from(x: T) -> Self { Protected::new(x.into())