Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat: convert signing to k256 #72

Merged
merged 7 commits into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 188 additions & 58 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions ethers-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ ethabi = { package = "ethabi-next", version = "12.0.0", default-features = false
arrayvec = { version = "0.5.1", default-features = false }

# crypto
secp256k1 = { package = "libsecp256k1", version = "0.3.5" }
ecdsa = { version = "0.8.0", features = ["std"] }
elliptic-curve = { version = "0.6.1", features = ["arithmetic"] }
generic-array = "0.14.4"
k256 = { version = "0.5.2", features = ["keccak256", "ecdsa"] }
rand = "0.7.2"
tiny-keccak = { version = "2.0.2", default-features = false }


# misc
serde = { version = "1.0.110", default-features = false, features = ["derive"] }
serde_json = { version = "1.0.53", default-features = false, features = ["alloc"] }
Expand Down
4 changes: 2 additions & 2 deletions ethers-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@ pub mod utils;
// re-export rand to avoid potential confusion when there's rand version mismatches
pub use rand;

// re-export libsecp
pub use secp256k1;
// re-export k256
pub use k256;
174 changes: 121 additions & 53 deletions ethers-core/src/types/crypto/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@ use crate::{
utils::{hash_message, keccak256},
};

use rand::Rng;
use rand::{CryptoRng, Rng};
use rustc_hex::FromHex;
use secp256k1::{
self as Secp256k1,
util::{COMPRESSED_PUBLIC_KEY_SIZE, SECRET_KEY_SIZE},
Error as SecpError, Message, PublicKey as PubKey, RecoveryId, SecretKey,
};
use serde::{
de::Error as DeserializeError,
de::{SeqAccess, Visitor},
Expand All @@ -18,18 +13,37 @@ use serde::{
};
use std::{fmt, ops::Deref, str::FromStr};

use k256::{
ecdsa::{
recoverable::{Id as RecoveryId, Signature as RecoverableSignature},
signature::Signer,
SigningKey,
},
elliptic_curve::{error::Error as EllipticCurveError, FieldBytes},
EncodedPoint as K256PublicKey, Secp256k1, SecretKey as K256SecretKey,
};

const SECRET_KEY_SIZE: usize = 32;
const COMPRESSED_PUBLIC_KEY_SIZE: usize = 33;

/// A private key on Secp256k1
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PrivateKey(pub(super) SecretKey);
#[derive(Clone, Debug)]
pub struct PrivateKey(pub(super) K256SecretKey);

impl PartialEq for PrivateKey {
fn eq(&self, other: &Self) -> bool {
self.0.to_bytes().eq(&other.0.to_bytes())
}
}

impl Serialize for PrivateKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_tuple(SECRET_KEY_SIZE)?;
for e in &self.0.serialize() {
seq.serialize_element(e)?;
for e in self.0.to_bytes() {
seq.serialize_element(&e)?;
}
seq.end()
}
Expand All @@ -42,26 +56,26 @@ impl<'de> Deserialize<'de> for PrivateKey {
{
let bytes = <[u8; SECRET_KEY_SIZE]>::deserialize(deserializer)?;
Ok(PrivateKey(
SecretKey::parse(&bytes).map_err(DeserializeError::custom)?,
K256SecretKey::from_bytes(&bytes).map_err(DeserializeError::custom)?,
))
}
}

impl FromStr for PrivateKey {
type Err = SecpError;
type Err = EllipticCurveError;

fn from_str(src: &str) -> Result<PrivateKey, Self::Err> {
let src = src
.from_hex::<Vec<u8>>()
.expect("invalid hex when reading PrivateKey");
let sk = SecretKey::parse_slice(&src)?;
let sk = K256SecretKey::from_bytes(&src)?;
Ok(PrivateKey(sk))
}
}

impl PrivateKey {
pub fn new<R: Rng>(rng: &mut R) -> Self {
PrivateKey(SecretKey::random(rng))
pub fn new<R: Rng + CryptoRng>(rng: &mut R) -> Self {
PrivateKey(K256SecretKey::random(rng))
}

/// Sign arbitrary string data.
Expand All @@ -78,9 +92,7 @@ impl PrivateKey {
let message = message.as_ref();
let message_hash = hash_message(message);

let sig_message =
Message::parse_slice(message_hash.as_bytes()).expect("hash is non-zero 32-bytes; qed");
self.sign_with_eip155(&sig_message, None)
self.sign_with_eip155(message_hash.as_bytes(), None)
}

/// RLP encodes and then signs the stransaction.
Expand All @@ -95,39 +107,40 @@ impl PrivateKey {
/// If `tx.to` is an ENS name. The caller MUST take care of name resolution before
/// calling this function.
pub fn sign_transaction(&self, tx: &TransactionRequest, chain_id: Option<u64>) -> Signature {
// Get the transaction's sighash
let sighash = tx.sighash(chain_id);
let message =
Message::parse_slice(sighash.as_bytes()).expect("hash is non-zero 32-bytes; qed");
// Sign it (with replay protection if applicable)
self.sign_with_eip155(&message, chain_id)
self.sign_with_eip155(sighash.as_bytes(), chain_id)
}

fn sign_with_eip155(&self, message: &Message, chain_id: Option<u64>) -> Signature {
let (signature, recovery_id) = Secp256k1::sign(message, &self.0);
fn sign_with_eip155(&self, message: &[u8], chain_id: Option<u64>) -> Signature {
let signing_key = SigningKey::new(&self.0.to_bytes()).expect("invalid secret key");

let recoverable_sig: RecoverableSignature = signing_key.sign(message);

let v = to_eip155_v(recovery_id, chain_id);
let r = H256::from_slice(&signature.r.b32());
let s = H256::from_slice(&signature.s.b32());
let v = to_eip155_v(recoverable_sig.recovery_id(), chain_id);

Signature { v, r, s }
let r_bytes: FieldBytes<Secp256k1> = recoverable_sig.r().into();
let s_bytes: FieldBytes<Secp256k1> = recoverable_sig.s().into();
let r = H256::from_slice(&r_bytes.as_slice());
let s = H256::from_slice(&s_bytes.as_slice());

Signature { r, s, v }
}
}

/// Applies [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
fn to_eip155_v(recovery_id: RecoveryId, chain_id: Option<u64>) -> u64 {
let standard_v = recovery_id.serialize() as u64;
let standard_v: u8 = recovery_id.into();
if let Some(chain_id) = chain_id {
// When signing with a chain ID, add chain replay protection.
standard_v + 35 + chain_id * 2
(standard_v as u64) + 35 + chain_id * 2
} else {
// Otherwise, convert to 'Electrum' notation.
standard_v + 27
(standard_v as u64) + 27
}
}

impl Deref for PrivateKey {
type Target = SecretKey;
type Target = K256SecretKey;

fn deref(&self) -> &Self::Target {
&self.0
Expand All @@ -136,19 +149,19 @@ impl Deref for PrivateKey {

/// A secp256k1 Public Key
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicKey(pub(super) PubKey);
pub struct PublicKey(pub(super) K256PublicKey);

impl From<PubKey> for PublicKey {
impl From<K256PublicKey> for PublicKey {
/// Gets the public address of a private key.
fn from(src: PubKey) -> PublicKey {
fn from(src: K256PublicKey) -> PublicKey {
PublicKey(src)
}
}

impl From<&PrivateKey> for PublicKey {
/// Gets the public address of a private key.
fn from(src: &PrivateKey) -> PublicKey {
let public_key = PubKey::from_secret_key(src);
let public_key = K256PublicKey::from_secret_key(src, false);
PublicKey(public_key)
}
}
Expand All @@ -162,7 +175,7 @@ impl From<&PrivateKey> for PublicKey {
/// computing the hash.
impl From<&PublicKey> for Address {
fn from(src: &PublicKey) -> Address {
let public_key = src.0.serialize();
let public_key = src.0.as_bytes();

debug_assert_eq!(public_key[0], 0x04);
let hash = keccak256(&public_key[1..]);
Expand Down Expand Up @@ -196,7 +209,8 @@ impl Serialize for PublicKey {
S: Serializer,
{
let mut seq = serializer.serialize_tuple(COMPRESSED_PUBLIC_KEY_SIZE)?;
for e in self.0.serialize_compressed().iter() {

for e in self.0.compress().as_bytes().iter() {
seq.serialize_element(e)?;
}
seq.end()
Expand Down Expand Up @@ -228,9 +242,17 @@ impl<'de> Deserialize<'de> for PublicKey {
.ok_or_else(|| DeserializeError::custom("could not read bytes"))?;
}

Ok(PublicKey(
PubKey::parse_compressed(&bytes).map_err(DeserializeError::custom)?,
))
let pub_key = K256PublicKey::from_bytes(&bytes[..]).map_or_else(
|_| Err(DeserializeError::custom("parse pub key")),
|v| Ok(v),
)?;

let uncompressed_pub_key = pub_key.decompress();
if uncompressed_pub_key.is_some().into() {
return Ok(PublicKey(uncompressed_pub_key.unwrap()));
} else {
return Err(DeserializeError::custom("parse pub key"));
}
}
}

Expand All @@ -241,40 +263,86 @@ impl<'de> Deserialize<'de> for PublicKey {
#[cfg(test)]
mod tests {
use super::*;
use rustc_hex::FromHex;

#[test]
fn serde() {
for _ in 0..10 {
let key = PrivateKey::new(&mut rand::thread_rng());
let serialized = bincode::serialize(&key).unwrap();
assert_eq!(serialized, &key.0.serialize());
assert_eq!(serialized.as_slice(), key.0.to_bytes().as_slice());
let de: PrivateKey = bincode::deserialize(&serialized).unwrap();
assert_eq!(key, de);

let public = PublicKey::from(&key);
println!("public = {:?}", public);

let serialized = bincode::serialize(&public).unwrap();
assert_eq!(&serialized[..], public.0.serialize_compressed().as_ref());
let de: PublicKey = bincode::deserialize(&serialized).unwrap();
assert_eq!(public, de);
}
}

#[test]
fn signs_data() {
// test vector taken from:
// https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#sign
#[cfg(not(feature = "celo"))]
fn signs_tx() {
use crate::types::Address;
// retrieved test vector from:
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
let tx = TransactionRequest {
from: None,
to: Some(
"F0109fC8DF283027b6285cc889F5aA624EaC1F55"
.parse::<Address>()
.unwrap()
.into(),
),
value: Some(1_000_000_000.into()),
gas: Some(2_000_000.into()),
nonce: Some(0.into()),
gas_price: Some(21_000_000_000u128.into()),
data: None,
};
let chain_id = 1;

let key: PrivateKey = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
.parse()
.unwrap();
let sign = key.sign("Some data");

let sig = key.sign_transaction(&tx, Some(chain_id));
let sighash = tx.sighash(Some(chain_id));
assert!(sig.verify(sighash, Address::from(key)).is_ok());
}

#[test]
fn key_to_address() {
let priv_key: PrivateKey =
"0000000000000000000000000000000000000000000000000000000000000001"
.parse()
.unwrap();
let addr: Address = priv_key.into();
assert_eq!(
sign.to_vec(),
"b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c"
.from_hex::<Vec<u8>>()
.unwrap()
addr,
Address::from_str("7E5F4552091A69125d5DfCb7b8C2659029395Bdf").expect("Decoding failed")
);

let priv_key: PrivateKey =
"0000000000000000000000000000000000000000000000000000000000000002"
.parse()
.unwrap();
let addr: Address = priv_key.into();
assert_eq!(
addr,
Address::from_str("2B5AD5c4795c026514f8317c7a215E218DcCD6cF").expect("Decoding failed")
);

let priv_key: PrivateKey =
"0000000000000000000000000000000000000000000000000000000000000003"
.parse()
.unwrap();
let addr: Address = priv_key.into();
assert_eq!(
addr,
Address::from_str("6813Eb9362372EEF6200f3b1dbC3f819671cBA69").expect("Decoding failed")
);
}
}
2 changes: 1 addition & 1 deletion ethers-core/src/types/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ mod keys;
pub use keys::{PrivateKey, PublicKey};

mod signature;
pub use signature::{Signature, SignatureError};
pub use signature::Signature;
Loading