Skip to content

Commit

Permalink
SCrypt support (#15)
Browse files Browse the repository at this point in the history
* support for SCrypt kdf

* fix identation

* cargo fmt

* version bump
  • Loading branch information
tworec authored and tomusdrw committed Mar 7, 2019
1 parent 4dca737 commit 7b281a8
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 83 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
21 changes: 21 additions & 0 deletions res/scrypt-wallet.json
Original file line number Diff line number Diff line change
@@ -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
}
35 changes: 25 additions & 10 deletions src/ec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<secp256k1::RecoverableSignature, Error> {
fn to_recoverable_signature(
v: u8,
r: &[u8; 32],
s: &[u8; 32],
) -> Result<secp256k1::RecoverableSignature, Error> {
let rec_id = secp256k1::RecoveryId::from_i32(v as i32)?;

let mut data = [0u8; 64];
Expand All @@ -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<bool, Error> {
pub fn verify(
public: &[u8],
v: u8,
r: &[u8; 32],
s: &[u8; 32],
message: &[u8],
) -> Result<bool, Error> {
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),
}
}
}
Expand All @@ -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())
}

Expand All @@ -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);
Expand All @@ -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())
}

Expand All @@ -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<bool, Error> {
let sig= to_signature(r, s);
pub fn verify(
public: &[u8],
_v: u8,
r: &[u8; 32],
s: &[u8; 32],
message: &[u8],
) -> Result<bool, Error> {
let sig = to_signature(r, s);
let msg = libsecp256k1::Message::parse_slice(message)?;

Ok(libsecp256k1::verify(&msg, &sig, &to_pubkey(public)?))
Expand Down
9 changes: 6 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand All @@ -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),
}
}
Expand All @@ -26,7 +29,7 @@ impl std::fmt::Display for Error {
impl std::error::Error for Error {}

impl From<ec::Error> for Error {
fn from(e: ec::Error) -> Error {
Error::Secp256k1(e)
}
fn from(e: ec::Error) -> Error {
Error::Secp256k1(e)
}
}
109 changes: 82 additions & 27 deletions src/key.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<bool, ec::Error> {
ec::verify(&self.public, signature.v, &signature.r, &signature.s, message)
ec::verify(
&self.public,
signature.v,
&signature.r,
&signature.s,
message,
)
}
}

Expand Down Expand Up @@ -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.
Expand All @@ -148,75 +150,121 @@ 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::<String>(), "005b3bcf82085eededd551f50de7892471ffb272");
assert_eq!(
pub_key.address().to_hex::<String>(),
"005b3bcf82085eededd551f50de7892471ffb272"
);
assert_eq!(&pub_key.bytes().to_hex::<String>(), "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::<String>(),
"8e049da484e853d92d118be16377ff616275d470"
);
assert_eq!(&pub_key.bytes().to_hex::<String>(), "e54553168b429c0407c5e4338f0a61fa7a515ff382ada9f323e313353c1904b0d8039f99e213778ba479196ef24c838e41dc77215c41895fe15e4de018d7d1dd");
}

#[test]
fn should_derive_public_and_address_correctly() {
let secret: Vec<u8> = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap();
let secret: Vec<u8> = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"
.from_hex()
.unwrap();
let key = SecretKey::from_raw(&secret).unwrap();

let pub_key = key.public();

assert_eq!(&pub_key.bytes().to_hex::<String>(), "3fa8c08c65a83f6b4ea3e04e1cc70cbe3cd391499e3e05ab7dedf28aff9afc538200ff93e3f2b2cb5029f03c7ebee820d63a4c5a9541c83acebe293f54cacf0e");
assert_eq!(pub_key.address().to_hex::<String>(), "00a329c0648769a73afac7f9381e08fb43dbea72");
assert_eq!(
pub_key.address().to_hex::<String>(),
"00a329c0648769a73afac7f9381e08fb43dbea72"
);
}

#[test]
fn should_have_debug_impl() {
let secret: Vec<u8> = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap();
let secret: Vec<u8> = "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\" }");
}

#[test]
fn should_recover_succesfuly() {
let v = 0u8;
let r2: Vec<u8> = "319a63079d7cdd4e1ec99996f840253c1b0e41a4caf474602c43e83b5a8de183".from_hex().unwrap();
let s2: Vec<u8> = "2e9424ac2ba94abc12a79349888545f26958c2fccc28d91f6dee72ab9c069738".from_hex().unwrap();
let r2: Vec<u8> = "319a63079d7cdd4e1ec99996f840253c1b0e41a4caf474602c43e83b5a8de183"
.from_hex()
.unwrap();
let s2: Vec<u8> = "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<u8> = "044a19199dc40e61210715bea94bcb0fff4c8dfa1c20988ab7783fc82c802a9f".from_hex().unwrap();
let message: Vec<u8> = "044a19199dc40e61210715bea94bcb0fff4c8dfa1c20988ab7783fc82c802a9f"
.from_hex()
.unwrap();

let pub_key = signature.recover(&message).unwrap();
assert_eq!(format!("{:?}", pub_key), "PublicKey { address: \"00af8b5cc1f8d0e862b4f303c0fa59b3709c2bb3\", public: \"929acaa0a4a4246225162496cc18e50719bb057519a150a94cfef77ae5e0dd50786c54cfe05f564d2ef09aae0b587bf73b83f45636def775bbf9010dded0e235\" }");
}

#[test]
fn should_convert_to_crypto_and_back() {
let secret: Vec<u8> = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap();
let secret: Vec<u8> = "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<u8> = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap();
let secret: Vec<u8> = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"
.from_hex()
.unwrap();
let key = SecretKey::from_raw(&secret).unwrap();
let message: Vec<u8> = "12da94d92a71f7692013002513e5bc4a3180344cfe3292e2b54c15f9d4421965".from_hex().unwrap();
let message: Vec<u8> = "12da94d92a71f7692013002513e5bc4a3180344cfe3292e2b54c15f9d4421965"
.from_hex()
.unwrap();

// when
let sig = key.sign(&message).unwrap();
Expand All @@ -228,11 +276,18 @@ mod tests {
#[test]
fn test_sign_verify_fail_for_other_key() {
// given
let secret: Vec<u8> = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".from_hex().unwrap();
let secret: Vec<u8> = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"
.from_hex()
.unwrap();
let key = SecretKey::from_raw(&secret).unwrap();
let other_secret: Vec<u8> = "2222222222222222222222222222222222222222222222222222222222222222".from_hex().unwrap();
let other_secret: Vec<u8> =
"2222222222222222222222222222222222222222222222222222222222222222"
.from_hex()
.unwrap();
let other_key = SecretKey::from_raw(&other_secret).unwrap();
let message: Vec<u8> = "12da94d92a71f7692013002513e5bc4a3180344cfe3292e2b54c15f9d4421965".from_hex().unwrap();
let message: Vec<u8> = "12da94d92a71f7692013002513e5bc4a3180344cfe3292e2b54c15f9d4421965"
.from_hex()
.unwrap();

// when
let sig = key.sign(&message).unwrap();
Expand Down
Loading

0 comments on commit 7b281a8

Please sign in to comment.