feat: convert signing to k256 (#72)
* feat: convert signing to k256 * fix: pass pre-hashed message to sig verification * feat: wrap the hash to a Digest implementation * refactor: cleanup and move digest impl to separate file * chore: adjust abigen tests due to rust update * test: add byte equality test between ethers-rs / web3.js signatures * fix(keys): use 512 blocks for sha256 Co-authored-by: Rohit Narurkar <rohit.narurkar@protonmail.com> Co-authored-by: Alex Vlasov <alex.m.vlasov@gmail.com>
This commit is contained in:
parent
2d51c523ba
commit
c65497543e
File diff suppressed because it is too large
Load Diff
|
@ -115,7 +115,7 @@ mod tests {
|
||||||
// no inputs
|
// no inputs
|
||||||
let params = vec![];
|
let params = vec![];
|
||||||
let token_stream = expand_inputs_call_arg(¶ms);
|
let token_stream = expand_inputs_call_arg(¶ms);
|
||||||
assert_eq!(token_stream.to_string(), "( )");
|
assert_eq!(token_stream.to_string(), "()");
|
||||||
|
|
||||||
// single input
|
// single input
|
||||||
let params = vec![Param {
|
let params = vec![Param {
|
||||||
|
@ -137,7 +137,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let token_stream = expand_inputs_call_arg(¶ms);
|
let token_stream = expand_inputs_call_arg(¶ms);
|
||||||
assert_eq!(token_stream.to_string(), "( arg_a , arg_b , )");
|
assert_eq!(token_stream.to_string(), "(arg_a , arg_b ,)");
|
||||||
|
|
||||||
// three inputs
|
// three inputs
|
||||||
let params = vec![
|
let params = vec![
|
||||||
|
@ -155,7 +155,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let token_stream = expand_inputs_call_arg(¶ms);
|
let token_stream = expand_inputs_call_arg(¶ms);
|
||||||
assert_eq!(token_stream.to_string(), "( arg_a , arg_b , arg_c , )");
|
assert_eq!(token_stream.to_string(), "(arg_a , arg_b , arg_c ,)");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -17,10 +17,13 @@ ethabi = { package = "ethabi-next", version = "12.0.0", default-features = false
|
||||||
arrayvec = { version = "0.5.1", default-features = false }
|
arrayvec = { version = "0.5.1", default-features = false }
|
||||||
|
|
||||||
# crypto
|
# 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 = { git = "https://github.com/RustCrypto/elliptic-curves", version = "0.5.2", features = ["keccak256", "ecdsa"] }
|
||||||
rand = "0.7.2"
|
rand = "0.7.2"
|
||||||
tiny-keccak = { version = "2.0.2", default-features = false }
|
tiny-keccak = { version = "2.0.2", default-features = false }
|
||||||
|
sha2 = { version = "0.9.1" }
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
serde = { version = "1.0.110", default-features = false, features = ["derive"] }
|
serde = { version = "1.0.110", default-features = false, features = ["derive"] }
|
||||||
|
|
|
@ -49,5 +49,5 @@ pub mod utils;
|
||||||
// re-export rand to avoid potential confusion when there's rand version mismatches
|
// re-export rand to avoid potential confusion when there's rand version mismatches
|
||||||
pub use rand;
|
pub use rand;
|
||||||
|
|
||||||
// re-export libsecp
|
// re-export k256
|
||||||
pub use secp256k1;
|
pub use k256;
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
//! This is a helper module used to pass the pre-hashed message for signing to the
|
||||||
|
//! `sign_digest` methods of K256.
|
||||||
|
use crate::types::H256;
|
||||||
|
use elliptic_curve::consts::U64;
|
||||||
|
use k256::ecdsa::signature::digest::{
|
||||||
|
generic_array::GenericArray, BlockInput, Digest, FixedOutput, Output, Reset, Update,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type Sha256Proxy = ProxyDigest<sha2::Sha256>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ProxyDigest<D: Digest> {
|
||||||
|
Proxy(Output<D>),
|
||||||
|
Digest(D),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Digest + Clone> From<H256> for ProxyDigest<D>
|
||||||
|
where
|
||||||
|
GenericArray<u8, <D as Digest>::OutputSize>: Copy,
|
||||||
|
{
|
||||||
|
fn from(src: H256) -> Self {
|
||||||
|
ProxyDigest::Proxy(*GenericArray::from_slice(src.as_bytes()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Digest> Default for ProxyDigest<D> {
|
||||||
|
fn default() -> Self {
|
||||||
|
ProxyDigest::Digest(D::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Digest> Update for ProxyDigest<D> {
|
||||||
|
// we update only if we are digest
|
||||||
|
fn update(&mut self, data: impl AsRef<[u8]>) {
|
||||||
|
match self {
|
||||||
|
ProxyDigest::Digest(ref mut d) => {
|
||||||
|
d.update(data);
|
||||||
|
}
|
||||||
|
ProxyDigest::Proxy(..) => {
|
||||||
|
unreachable!("can not update if we are proxy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we chain only if we are digest
|
||||||
|
fn chain(self, data: impl AsRef<[u8]>) -> Self {
|
||||||
|
match self {
|
||||||
|
ProxyDigest::Digest(d) => ProxyDigest::Digest(d.chain(data)),
|
||||||
|
ProxyDigest::Proxy(..) => {
|
||||||
|
unreachable!("can not update if we are proxy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Digest> Reset for ProxyDigest<D> {
|
||||||
|
// make new one
|
||||||
|
fn reset(&mut self) {
|
||||||
|
*self = Self::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Sha256 with 512 bit blocks
|
||||||
|
impl<D: Digest> BlockInput for ProxyDigest<D> {
|
||||||
|
type BlockSize = U64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Digest> FixedOutput for ProxyDigest<D> {
|
||||||
|
// we default to the output of the original digest
|
||||||
|
type OutputSize = D::OutputSize;
|
||||||
|
|
||||||
|
fn finalize_into(self, out: &mut GenericArray<u8, Self::OutputSize>) {
|
||||||
|
match self {
|
||||||
|
ProxyDigest::Digest(d) => {
|
||||||
|
*out = d.finalize();
|
||||||
|
}
|
||||||
|
ProxyDigest::Proxy(p) => {
|
||||||
|
*out = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize_into_reset(&mut self, out: &mut GenericArray<u8, Self::OutputSize>) {
|
||||||
|
let s = std::mem::take(self);
|
||||||
|
s.finalize_into(out);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,11 @@
|
||||||
|
use super::hash::Sha256Proxy;
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{Address, Signature, TransactionRequest, H256},
|
types::{Address, Signature, TransactionRequest, H256},
|
||||||
utils::{hash_message, keccak256},
|
utils::{hash_message, keccak256},
|
||||||
};
|
};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::{CryptoRng, Rng};
|
||||||
use rustc_hex::FromHex;
|
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::{
|
use serde::{
|
||||||
de::Error as DeserializeError,
|
de::Error as DeserializeError,
|
||||||
de::{SeqAccess, Visitor},
|
de::{SeqAccess, Visitor},
|
||||||
|
@ -18,9 +14,28 @@ use serde::{
|
||||||
};
|
};
|
||||||
use std::{fmt, ops::Deref, str::FromStr};
|
use std::{fmt, ops::Deref, str::FromStr};
|
||||||
|
|
||||||
|
use k256::{
|
||||||
|
ecdsa::{
|
||||||
|
recoverable::{Id as RecoveryId, Signature as RecoverableSignature},
|
||||||
|
signature::DigestSigner,
|
||||||
|
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
|
/// A private key on Secp256k1
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PrivateKey(pub(super) SecretKey);
|
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 {
|
impl Serialize for PrivateKey {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
@ -28,8 +43,8 @@ impl Serialize for PrivateKey {
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
let mut seq = serializer.serialize_tuple(SECRET_KEY_SIZE)?;
|
let mut seq = serializer.serialize_tuple(SECRET_KEY_SIZE)?;
|
||||||
for e in &self.0.serialize() {
|
for e in self.0.to_bytes() {
|
||||||
seq.serialize_element(e)?;
|
seq.serialize_element(&e)?;
|
||||||
}
|
}
|
||||||
seq.end()
|
seq.end()
|
||||||
}
|
}
|
||||||
|
@ -42,26 +57,26 @@ impl<'de> Deserialize<'de> for PrivateKey {
|
||||||
{
|
{
|
||||||
let bytes = <[u8; SECRET_KEY_SIZE]>::deserialize(deserializer)?;
|
let bytes = <[u8; SECRET_KEY_SIZE]>::deserialize(deserializer)?;
|
||||||
Ok(PrivateKey(
|
Ok(PrivateKey(
|
||||||
SecretKey::parse(&bytes).map_err(DeserializeError::custom)?,
|
K256SecretKey::from_bytes(&bytes).map_err(DeserializeError::custom)?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for PrivateKey {
|
impl FromStr for PrivateKey {
|
||||||
type Err = SecpError;
|
type Err = EllipticCurveError;
|
||||||
|
|
||||||
fn from_str(src: &str) -> Result<PrivateKey, Self::Err> {
|
fn from_str(src: &str) -> Result<PrivateKey, Self::Err> {
|
||||||
let src = src
|
let src = src
|
||||||
.from_hex::<Vec<u8>>()
|
.from_hex::<Vec<u8>>()
|
||||||
.expect("invalid hex when reading PrivateKey");
|
.expect("invalid hex when reading PrivateKey");
|
||||||
let sk = SecretKey::parse_slice(&src)?;
|
let sk = K256SecretKey::from_bytes(&src)?;
|
||||||
Ok(PrivateKey(sk))
|
Ok(PrivateKey(sk))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrivateKey {
|
impl PrivateKey {
|
||||||
pub fn new<R: Rng>(rng: &mut R) -> Self {
|
pub fn new<R: Rng + CryptoRng>(rng: &mut R) -> Self {
|
||||||
PrivateKey(SecretKey::random(rng))
|
PrivateKey(K256SecretKey::random(rng))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign arbitrary string data.
|
/// Sign arbitrary string data.
|
||||||
|
@ -78,9 +93,7 @@ impl PrivateKey {
|
||||||
let message = message.as_ref();
|
let message = message.as_ref();
|
||||||
let message_hash = hash_message(message);
|
let message_hash = hash_message(message);
|
||||||
|
|
||||||
let sig_message =
|
self.sign_hash_with_eip155(message_hash, None)
|
||||||
Message::parse_slice(message_hash.as_bytes()).expect("hash is non-zero 32-bytes; qed");
|
|
||||||
self.sign_with_eip155(&sig_message, None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RLP encodes and then signs the stransaction.
|
/// RLP encodes and then signs the stransaction.
|
||||||
|
@ -95,39 +108,41 @@ impl PrivateKey {
|
||||||
/// If `tx.to` is an ENS name. The caller MUST take care of name resolution before
|
/// If `tx.to` is an ENS name. The caller MUST take care of name resolution before
|
||||||
/// calling this function.
|
/// calling this function.
|
||||||
pub fn sign_transaction(&self, tx: &TransactionRequest, chain_id: Option<u64>) -> Signature {
|
pub fn sign_transaction(&self, tx: &TransactionRequest, chain_id: Option<u64>) -> Signature {
|
||||||
// Get the transaction's sighash
|
|
||||||
let sighash = tx.sighash(chain_id);
|
let sighash = tx.sighash(chain_id);
|
||||||
let message =
|
self.sign_hash_with_eip155(sighash, chain_id)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_with_eip155(&self, message: &Message, chain_id: Option<u64>) -> Signature {
|
fn sign_hash_with_eip155(&self, hash: H256, chain_id: Option<u64>) -> Signature {
|
||||||
let (signature, recovery_id) = Secp256k1::sign(message, &self.0);
|
let signing_key = SigningKey::new(&self.0.to_bytes()).expect("invalid secret key");
|
||||||
|
|
||||||
let v = to_eip155_v(recovery_id, chain_id);
|
let recoverable_sig: RecoverableSignature =
|
||||||
let r = H256::from_slice(&signature.r.b32());
|
signing_key.sign_digest(Sha256Proxy::from(hash));
|
||||||
let s = H256::from_slice(&signature.s.b32());
|
|
||||||
|
|
||||||
Signature { v, r, s }
|
let v = to_eip155_v(recoverable_sig.recovery_id(), chain_id);
|
||||||
|
|
||||||
|
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)
|
/// 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 {
|
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 {
|
if let Some(chain_id) = chain_id {
|
||||||
// When signing with a chain ID, add chain replay protection.
|
// 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 {
|
} else {
|
||||||
// Otherwise, convert to 'Electrum' notation.
|
// Otherwise, convert to 'Electrum' notation.
|
||||||
standard_v + 27
|
(standard_v as u64) + 27
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for PrivateKey {
|
impl Deref for PrivateKey {
|
||||||
type Target = SecretKey;
|
type Target = K256SecretKey;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
|
@ -136,11 +151,11 @@ impl Deref for PrivateKey {
|
||||||
|
|
||||||
/// A secp256k1 Public Key
|
/// A secp256k1 Public Key
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[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.
|
/// Gets the public address of a private key.
|
||||||
fn from(src: PubKey) -> PublicKey {
|
fn from(src: K256PublicKey) -> PublicKey {
|
||||||
PublicKey(src)
|
PublicKey(src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +163,7 @@ impl From<PubKey> for PublicKey {
|
||||||
impl From<&PrivateKey> for PublicKey {
|
impl From<&PrivateKey> for PublicKey {
|
||||||
/// Gets the public address of a private key.
|
/// Gets the public address of a private key.
|
||||||
fn from(src: &PrivateKey) -> PublicKey {
|
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)
|
PublicKey(public_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +177,7 @@ impl From<&PrivateKey> for PublicKey {
|
||||||
/// computing the hash.
|
/// computing the hash.
|
||||||
impl From<&PublicKey> for Address {
|
impl From<&PublicKey> for Address {
|
||||||
fn from(src: &PublicKey) -> 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);
|
debug_assert_eq!(public_key[0], 0x04);
|
||||||
let hash = keccak256(&public_key[1..]);
|
let hash = keccak256(&public_key[1..]);
|
||||||
|
@ -196,7 +211,8 @@ impl Serialize for PublicKey {
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
let mut seq = serializer.serialize_tuple(COMPRESSED_PUBLIC_KEY_SIZE)?;
|
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.serialize_element(e)?;
|
||||||
}
|
}
|
||||||
seq.end()
|
seq.end()
|
||||||
|
@ -228,9 +244,15 @@ impl<'de> Deserialize<'de> for PublicKey {
|
||||||
.ok_or_else(|| DeserializeError::custom("could not read bytes"))?;
|
.ok_or_else(|| DeserializeError::custom("could not read bytes"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PublicKey(
|
let pub_key = K256PublicKey::from_bytes(&bytes[..])
|
||||||
PubKey::parse_compressed(&bytes).map_err(DeserializeError::custom)?,
|
.map_err(|_| DeserializeError::custom("parse pub key"))?;
|
||||||
))
|
|
||||||
|
let uncompressed_pub_key = pub_key.decompress();
|
||||||
|
if uncompressed_pub_key.is_some().into() {
|
||||||
|
Ok(PublicKey(uncompressed_pub_key.unwrap()))
|
||||||
|
} else {
|
||||||
|
Err(DeserializeError::custom("parse pub key"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,40 +263,86 @@ impl<'de> Deserialize<'de> for PublicKey {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rustc_hex::FromHex;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serde() {
|
fn serde() {
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
let key = PrivateKey::new(&mut rand::thread_rng());
|
let key = PrivateKey::new(&mut rand::thread_rng());
|
||||||
let serialized = bincode::serialize(&key).unwrap();
|
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();
|
let de: PrivateKey = bincode::deserialize(&serialized).unwrap();
|
||||||
assert_eq!(key, de);
|
assert_eq!(key, de);
|
||||||
|
|
||||||
let public = PublicKey::from(&key);
|
let public = PublicKey::from(&key);
|
||||||
|
println!("public = {:?}", public);
|
||||||
|
|
||||||
let serialized = bincode::serialize(&public).unwrap();
|
let serialized = bincode::serialize(&public).unwrap();
|
||||||
assert_eq!(&serialized[..], public.0.serialize_compressed().as_ref());
|
|
||||||
let de: PublicKey = bincode::deserialize(&serialized).unwrap();
|
let de: PublicKey = bincode::deserialize(&serialized).unwrap();
|
||||||
assert_eq!(public, de);
|
assert_eq!(public, de);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn signs_data() {
|
#[cfg(not(feature = "celo"))]
|
||||||
// test vector taken from:
|
fn signs_tx() {
|
||||||
// https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#sign
|
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"
|
let key: PrivateKey = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.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!(
|
assert_eq!(
|
||||||
sign.to_vec(),
|
addr,
|
||||||
"b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c"
|
Address::from_str("7E5F4552091A69125d5DfCb7b8C2659029395Bdf").expect("Decoding failed")
|
||||||
.from_hex::<Vec<u8>>()
|
);
|
||||||
.unwrap()
|
|
||||||
|
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,4 +2,6 @@ mod keys;
|
||||||
pub use keys::{PrivateKey, PublicKey};
|
pub use keys::{PrivateKey, PublicKey};
|
||||||
|
|
||||||
mod signature;
|
mod signature;
|
||||||
pub use signature::{Signature, SignatureError};
|
pub use signature::Signature;
|
||||||
|
|
||||||
|
mod hash;
|
||||||
|
|
|
@ -5,21 +5,22 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use rustc_hex::{FromHex, ToHex};
|
use rustc_hex::{FromHex, ToHex};
|
||||||
use secp256k1::{
|
|
||||||
self as Secp256k1, Error as Secp256k1Error, Message, RecoveryId,
|
|
||||||
Signature as RecoverableSignature,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{convert::TryFrom, fmt, str::FromStr};
|
use std::{convert::TryFrom, fmt, str::FromStr};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use elliptic_curve::consts::U32;
|
||||||
|
use generic_array::GenericArray;
|
||||||
|
use k256::ecdsa::{
|
||||||
|
recoverable::{Id as RecoveryId, Signature as RecoverableSignature},
|
||||||
|
Error as K256SignatureError, Signature as K256Signature,
|
||||||
|
};
|
||||||
|
use k256::EncodedPoint as K256PublicKey;
|
||||||
|
|
||||||
/// An error involving a signature.
|
/// An error involving a signature.
|
||||||
#[derive(Clone, Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum SignatureError {
|
pub enum SignatureError {
|
||||||
/// Internal error inside the recovery
|
|
||||||
#[error(transparent)]
|
|
||||||
Secp256k1Error(#[from] Secp256k1Error),
|
|
||||||
/// Invalid length, secp256k1 signatures are 65 bytes
|
/// Invalid length, secp256k1 signatures are 65 bytes
|
||||||
#[error("invalid signature length, got {0}, expected 65")]
|
#[error("invalid signature length, got {0}, expected 65")]
|
||||||
InvalidLength(usize),
|
InvalidLength(usize),
|
||||||
|
@ -30,6 +31,12 @@ pub enum SignatureError {
|
||||||
/// produced the signature did not match the expected address)
|
/// produced the signature did not match the expected address)
|
||||||
#[error("Signature verification failed. Expected {0}, got {0}")]
|
#[error("Signature verification failed. Expected {0}, got {0}")]
|
||||||
VerificationError(Address, Address),
|
VerificationError(Address, Address),
|
||||||
|
/// Internal error during signature recovery
|
||||||
|
#[error(transparent)]
|
||||||
|
K256Error(#[from] K256SignatureError),
|
||||||
|
/// Error in recovering public key from signature
|
||||||
|
#[error("Public key recovery error")]
|
||||||
|
RecoveryError,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recovery message data.
|
/// Recovery message data.
|
||||||
|
@ -92,22 +99,27 @@ impl Signature {
|
||||||
RecoveryMessage::Data(ref message) => hash_message(message),
|
RecoveryMessage::Data(ref message) => hash_message(message),
|
||||||
RecoveryMessage::Hash(hash) => hash,
|
RecoveryMessage::Hash(hash) => hash,
|
||||||
};
|
};
|
||||||
let message = Message::parse_slice(message_hash.as_bytes())?;
|
|
||||||
|
|
||||||
let (signature, recovery_id) = self.as_signature()?;
|
let (recoverable_sig, _recovery_id) = self.as_signature()?;
|
||||||
let public_key = Secp256k1::recover(&message, &signature, &recovery_id)?;
|
let verify_key =
|
||||||
|
recoverable_sig.recover_verify_key_from_digest_bytes(message_hash.as_ref().into())?;
|
||||||
|
|
||||||
Ok(PublicKey::from(public_key).into())
|
let uncompressed_pub_key = K256PublicKey::from(&verify_key).decompress();
|
||||||
|
if uncompressed_pub_key.is_some().into() {
|
||||||
|
Ok(PublicKey::from(uncompressed_pub_key.unwrap()).into())
|
||||||
|
} else {
|
||||||
|
Err(SignatureError::RecoveryError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the recovery signature.
|
/// Retrieves the recovery signature.
|
||||||
fn as_signature(&self) -> Result<(RecoverableSignature, RecoveryId), SignatureError> {
|
fn as_signature(&self) -> Result<(RecoverableSignature, RecoveryId), SignatureError> {
|
||||||
let recovery_id = self.recovery_id()?;
|
let recovery_id = self.recovery_id()?;
|
||||||
let signature = {
|
let signature = {
|
||||||
let mut sig = [0u8; 64];
|
let gar: &GenericArray<u8, U32> = GenericArray::from_slice(self.r.as_bytes());
|
||||||
sig[..32].copy_from_slice(self.r.as_bytes());
|
let gas: &GenericArray<u8, U32> = GenericArray::from_slice(self.s.as_bytes());
|
||||||
sig[32..].copy_from_slice(self.s.as_bytes());
|
let sig = K256Signature::from_scalars(*gar, *gas)?;
|
||||||
RecoverableSignature::parse(&sig)
|
RecoverableSignature::new(&sig, recovery_id)?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((signature, recovery_id))
|
Ok((signature, recovery_id))
|
||||||
|
@ -116,7 +128,7 @@ impl Signature {
|
||||||
/// Retrieve the recovery ID.
|
/// Retrieve the recovery ID.
|
||||||
fn recovery_id(&self) -> Result<RecoveryId, SignatureError> {
|
fn recovery_id(&self) -> Result<RecoveryId, SignatureError> {
|
||||||
let standard_v = normalize_recovery_id(self.v);
|
let standard_v = normalize_recovery_id(self.v);
|
||||||
Ok(RecoveryId::parse(standard_v)?)
|
Ok(RecoveryId::new(standard_v)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies and serializes `self` into a new `Vec` with the recovery id included
|
/// Copies and serializes `self` into a new `Vec` with the recovery id included
|
||||||
|
@ -258,6 +270,23 @@ mod tests {
|
||||||
assert_eq!(recovered2, address);
|
assert_eq!(recovered2, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recover_web3_signature() {
|
||||||
|
// test vector taken from:
|
||||||
|
// https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#sign
|
||||||
|
let key: PrivateKey = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
let address = Address::from(&key);
|
||||||
|
let our_signature = key.sign("Some data");
|
||||||
|
let signature = Signature::from_str(
|
||||||
|
"b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c"
|
||||||
|
).expect("could not parse signature");
|
||||||
|
assert_eq!(our_signature.recover("Some data").unwrap(), address,);
|
||||||
|
assert_eq!(signature.recover("Some data").unwrap(), address);
|
||||||
|
assert_eq!(our_signature, signature);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_vec() {
|
fn to_vec() {
|
||||||
let message = "Some data";
|
let message = "Some data";
|
||||||
|
|
|
@ -33,9 +33,6 @@ pub enum LedgerError {
|
||||||
|
|
||||||
#[error("Error when decoding UTF8 Response: {0}")]
|
#[error("Error when decoding UTF8 Response: {0}")]
|
||||||
Utf8Error(#[from] std::str::Utf8Error),
|
Utf8Error(#[from] std::str::Utf8Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
SignatureError(#[from] ethers_core::types::SignatureError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const P1_FIRST: u8 = 0x00;
|
pub const P1_FIRST: u8 = 0x00;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::Signer;
|
use crate::Signer;
|
||||||
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
rand::Rng,
|
k256::elliptic_curve::error::Error as K256Error,
|
||||||
secp256k1,
|
rand::{CryptoRng, Rng},
|
||||||
types::{Address, PrivateKey, PublicKey, Signature, TransactionRequest},
|
types::{Address, PrivateKey, PublicKey, Signature, TransactionRequest},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ impl Wallet {
|
||||||
// TODO: Add support for mnemonic and encrypted JSON
|
// TODO: Add support for mnemonic and encrypted JSON
|
||||||
|
|
||||||
/// Creates a new random keypair seeded with the provided RNG
|
/// Creates a new random keypair seeded with the provided RNG
|
||||||
pub fn new<R: Rng>(rng: &mut R) -> Self {
|
pub fn new<R: Rng + CryptoRng>(rng: &mut R) -> Self {
|
||||||
let private_key = PrivateKey::new(rng);
|
let private_key = PrivateKey::new(rng);
|
||||||
let public_key = PublicKey::from(&private_key);
|
let public_key = PublicKey::from(&private_key);
|
||||||
let address = Address::from(&private_key);
|
let address = Address::from(&private_key);
|
||||||
|
@ -133,7 +133,7 @@ impl From<PrivateKey> for Wallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Wallet {
|
impl FromStr for Wallet {
|
||||||
type Err = secp256k1::Error;
|
type Err = K256Error;
|
||||||
|
|
||||||
fn from_str(src: &str) -> Result<Self, Self::Err> {
|
fn from_str(src: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(PrivateKey::from_str(src)?.into())
|
Ok(PrivateKey::from_str(src)?.into())
|
||||||
|
|
Loading…
Reference in New Issue