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
|
||||
let params = vec![];
|
||||
let token_stream = expand_inputs_call_arg(¶ms);
|
||||
assert_eq!(token_stream.to_string(), "( )");
|
||||
assert_eq!(token_stream.to_string(), "()");
|
||||
|
||||
// single input
|
||||
let params = vec![Param {
|
||||
|
@ -137,7 +137,7 @@ mod tests {
|
|||
},
|
||||
];
|
||||
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
|
||||
let params = vec![
|
||||
|
@ -155,7 +155,7 @@ mod tests {
|
|||
},
|
||||
];
|
||||
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]
|
||||
|
|
|
@ -17,10 +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 = { git = "https://github.com/RustCrypto/elliptic-curves", version = "0.5.2", features = ["keccak256", "ecdsa"] }
|
||||
rand = "0.7.2"
|
||||
tiny-keccak = { version = "2.0.2", default-features = false }
|
||||
|
||||
sha2 = { version = "0.9.1" }
|
||||
|
||||
# misc
|
||||
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
|
||||
pub use rand;
|
||||
|
||||
// re-export libsecp
|
||||
pub use secp256k1;
|
||||
// re-export k256
|
||||
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::{
|
||||
types::{Address, Signature, TransactionRequest, H256},
|
||||
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},
|
||||
|
@ -18,9 +14,28 @@ use serde::{
|
|||
};
|
||||
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
|
||||
#[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>
|
||||
|
@ -28,8 +43,8 @@ impl Serialize for PrivateKey {
|
|||
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()
|
||||
}
|
||||
|
@ -42,26 +57,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.
|
||||
|
@ -78,9 +93,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_hash_with_eip155(message_hash, None)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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_hash_with_eip155(sighash, 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_hash_with_eip155(&self, hash: H256, chain_id: Option<u64>) -> Signature {
|
||||
let signing_key = SigningKey::new(&self.0.to_bytes()).expect("invalid secret key");
|
||||
|
||||
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 recoverable_sig: RecoverableSignature =
|
||||
signing_key.sign_digest(Sha256Proxy::from(hash));
|
||||
|
||||
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)
|
||||
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
|
||||
|
@ -136,11 +151,11 @@ 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)
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +163,7 @@ impl From<PubKey> for PublicKey {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +177,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..]);
|
||||
|
@ -196,7 +211,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()
|
||||
|
@ -228,9 +244,15 @@ 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_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)]
|
||||
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,4 +2,6 @@ mod keys;
|
|||
pub use keys::{PrivateKey, PublicKey};
|
||||
|
||||
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 secp256k1::{
|
||||
self as Secp256k1, Error as Secp256k1Error, Message, RecoveryId,
|
||||
Signature as RecoverableSignature,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{convert::TryFrom, fmt, str::FromStr};
|
||||
|
||||
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.
|
||||
#[derive(Clone, Debug, Error)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SignatureError {
|
||||
/// Internal error inside the recovery
|
||||
#[error(transparent)]
|
||||
Secp256k1Error(#[from] Secp256k1Error),
|
||||
/// Invalid length, secp256k1 signatures are 65 bytes
|
||||
#[error("invalid signature length, got {0}, expected 65")]
|
||||
InvalidLength(usize),
|
||||
|
@ -30,6 +31,12 @@ pub enum SignatureError {
|
|||
/// produced the signature did not match the expected address)
|
||||
#[error("Signature verification failed. Expected {0}, got {0}")]
|
||||
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.
|
||||
|
@ -92,22 +99,27 @@ impl Signature {
|
|||
RecoveryMessage::Data(ref message) => hash_message(message),
|
||||
RecoveryMessage::Hash(hash) => hash,
|
||||
};
|
||||
let message = Message::parse_slice(message_hash.as_bytes())?;
|
||||
|
||||
let (signature, recovery_id) = self.as_signature()?;
|
||||
let public_key = Secp256k1::recover(&message, &signature, &recovery_id)?;
|
||||
let (recoverable_sig, _recovery_id) = self.as_signature()?;
|
||||
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.
|
||||
fn as_signature(&self) -> Result<(RecoverableSignature, RecoveryId), SignatureError> {
|
||||
let recovery_id = self.recovery_id()?;
|
||||
let signature = {
|
||||
let mut sig = [0u8; 64];
|
||||
sig[..32].copy_from_slice(self.r.as_bytes());
|
||||
sig[32..].copy_from_slice(self.s.as_bytes());
|
||||
RecoverableSignature::parse(&sig)
|
||||
let gar: &GenericArray<u8, U32> = GenericArray::from_slice(self.r.as_bytes());
|
||||
let gas: &GenericArray<u8, U32> = GenericArray::from_slice(self.s.as_bytes());
|
||||
let sig = K256Signature::from_scalars(*gar, *gas)?;
|
||||
RecoverableSignature::new(&sig, recovery_id)?
|
||||
};
|
||||
|
||||
Ok((signature, recovery_id))
|
||||
|
@ -116,7 +128,7 @@ impl Signature {
|
|||
/// Retrieve the recovery ID.
|
||||
fn recovery_id(&self) -> Result<RecoveryId, SignatureError> {
|
||||
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
|
||||
|
@ -258,6 +270,23 @@ mod tests {
|
|||
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]
|
||||
fn to_vec() {
|
||||
let message = "Some data";
|
||||
|
|
|
@ -33,9 +33,6 @@ pub enum LedgerError {
|
|||
|
||||
#[error("Error when decoding UTF8 Response: {0}")]
|
||||
Utf8Error(#[from] std::str::Utf8Error),
|
||||
|
||||
#[error(transparent)]
|
||||
SignatureError(#[from] ethers_core::types::SignatureError),
|
||||
}
|
||||
|
||||
pub const P1_FIRST: u8 = 0x00;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::Signer;
|
||||
|
||||
use ethers_core::{
|
||||
rand::Rng,
|
||||
secp256k1,
|
||||
k256::elliptic_curve::error::Error as K256Error,
|
||||
rand::{CryptoRng, Rng},
|
||||
types::{Address, PrivateKey, PublicKey, Signature, TransactionRequest},
|
||||
};
|
||||
|
||||
|
@ -77,7 +77,7 @@ impl Wallet {
|
|||
// TODO: Add support for mnemonic and encrypted JSON
|
||||
|
||||
/// 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 public_key = PublicKey::from(&private_key);
|
||||
let address = Address::from(&private_key);
|
||||
|
@ -133,7 +133,7 @@ impl From<PrivateKey> for Wallet {
|
|||
}
|
||||
|
||||
impl FromStr for Wallet {
|
||||
type Err = secp256k1::Error;
|
||||
type Err = K256Error;
|
||||
|
||||
fn from_str(src: &str) -> Result<Self, Self::Err> {
|
||||
Ok(PrivateKey::from_str(src)?.into())
|
||||
|
|
Loading…
Reference in New Issue