From ffdda2ac51379d019ac460217e1d8d71f4f76294 Mon Sep 17 00:00:00 2001 From: silverpill Date: Sat, 16 Apr 2022 19:33:47 +0000 Subject: [PATCH] Add recover_address() function It can be used to recover ethereum address from signature. --- Cargo.lock | 3 ++ Cargo.toml | 2 +- src/ethereum/signatures.rs | 80 +++++++++++++++++++++++++++++++------- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5af02d8..a55ba97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1134,6 +1134,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hmac" diff --git a/Cargo.toml b/Cargo.toml index d59ded4..c9882db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ deadpool-postgres = { version = "0.10.2", default-features = false } # Used to read .env files dotenv = "0.15.0" # Used to work with hexadecimal strings -hex = "0.4.3" +hex = { version = "0.4.3", features = ["serde"] } # Used for logging log = { version = "0.4.14", features = ["serde"] } env_logger = { version = "0.9.0", default-features = false } diff --git a/src/ethereum/signatures.rs b/src/ethereum/signatures.rs index f0a89d6..64cac25 100644 --- a/src/ethereum/signatures.rs +++ b/src/ethereum/signatures.rs @@ -3,8 +3,15 @@ use std::str::FromStr; use secp256k1::{Error as KeyError, SecretKey, rand::rngs::OsRng}; use serde::Serialize; use web3::ethabi::{token::Token, encode}; -use web3::signing::{keccak256, Key, SigningError}; -use web3::types::{Address, U256}; +use web3::signing::{ + keccak256, + recover, + Key, + RecoveryError, + SecretKeyRef, + SigningError, +}; +use web3::types::{Address, H256, Recovery, U256}; /// Generates signing key pub fn generate_ecdsa_key() -> SecretKey { @@ -15,8 +22,10 @@ pub fn generate_ecdsa_key() -> SecretKey { #[derive(Serialize)] pub struct SignatureData { pub v: u64, - pub r: String, - pub s: String, + #[serde(serialize_with = "hex::serde::serialize")] + pub r: [u8; 32], + #[serde(serialize_with = "hex::serde::serialize")] + pub s: [u8; 32], } #[derive(thiserror::Error, Debug)] @@ -29,28 +38,65 @@ pub enum SignatureError { #[error("signing error")] SigningError(#[from] SigningError), + + #[error("invalid signature")] + InvalidSignature, + + #[error("recovery error")] + RecoveryError(#[from] RecoveryError), } -pub fn sign_message( - signing_key: &str, - message: &[u8], -) -> Result { - let key = SecretKey::from_str(signing_key)?; +fn prepare_message(message: &[u8]) -> [u8; 32] { let message_hash = keccak256(message); let eip_191_message = [ "\x19Ethereum Signed Message:\n32".as_bytes(), &message_hash, ].concat(); let eip_191_message_hash = keccak256(&eip_191_message); - let signature = Box::new(key).sign(&eip_191_message_hash, None)?; + eip_191_message_hash +} + +/// Create EIP-191 signature +/// https://eips.ethereum.org/EIPS/eip-191 +pub fn sign_message( + signing_key: &str, + message: &[u8], +) -> Result { + let key = SecretKey::from_str(signing_key)?; + let key_ref = SecretKeyRef::new(&key); + let eip_191_message_hash = prepare_message(message); + // Create signature without replay protection (chain ID is None) + let signature = key_ref.sign(&eip_191_message_hash, None)?; let signature_data = SignatureData { v: signature.v, - r: hex::encode(signature.r.as_bytes()), - s: hex::encode(signature.s.as_bytes()), + r: signature.r.to_fixed_bytes(), + s: signature.s.to_fixed_bytes(), }; Ok(signature_data) } +/// Verify EIP-191 signature +pub fn recover_address( + message: &[u8], + signature: &SignatureData, +) -> Result { + let eip_191_message_hash = prepare_message(message); + let recovery = Recovery::new( + "", // this message is not used + signature.v, + H256(signature.r), + H256(signature.s), + ); + let (signature_raw, recovery_id) = recovery.as_signature() + .ok_or(SignatureError::InvalidSignature)?; + let address = recover( + &eip_191_message_hash, + &signature_raw, + recovery_id, + )?; + Ok(address) +} + pub type CallArgs = Vec>>; pub fn sign_contract_call( @@ -83,10 +129,16 @@ mod tests { #[test] fn test_sign_message() { - let signing_key = generate_ecdsa_key().to_string(); + let signing_key = generate_ecdsa_key(); let message = "test_message"; - let result = sign_message(&signing_key, message.as_bytes()).unwrap(); + let result = sign_message( + &signing_key.to_string(), + message.as_bytes(), + ).unwrap(); assert!(result.v == 27 || result.v == 28); + + let recovered = recover_address(message.as_bytes(), &result).unwrap(); + assert_eq!(recovered, SecretKeyRef::new(&signing_key).address()); } #[test]