diff --git a/Cargo.lock b/Cargo.lock index 56be0b40c0..8fc594c9d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3327,6 +3327,7 @@ dependencies = [ "arbitrary", "bytes", "crc", + "derive_more", "ethers-core", "hex", "hex-literal", @@ -3334,6 +3335,7 @@ dependencies = [ "parity-scale-codec", "reth-codecs", "reth-rlp", + "secp256k1", "serde", "serde_json", "sucds", diff --git a/crates/consensus/src/config.rs b/crates/consensus/src/config.rs index 69c52a10b7..880aa8bcdb 100644 --- a/crates/consensus/src/config.rs +++ b/crates/consensus/src/config.rs @@ -15,10 +15,12 @@ pub struct Config { pub london_hard_fork_block: BlockNumber, /// The Merge/Paris hard fork block number pub paris_hard_fork_block: BlockNumber, + /// Blockchain identifier introduced in EIP-155: Simple replay attack protection + pub chain_id: u64, } impl Default for Config { fn default() -> Self { - Self { london_hard_fork_block: 12965000, paris_hard_fork_block: 15537394 } + Self { london_hard_fork_block: 12965000, paris_hard_fork_block: 15537394, chain_id: 1 } } } diff --git a/crates/consensus/src/verification.rs b/crates/consensus/src/verification.rs index 97b217d176..f7bfdd7ed1 100644 --- a/crates/consensus/src/verification.rs +++ b/crates/consensus/src/verification.rs @@ -1,7 +1,9 @@ //! ALl functions for verification of block use crate::{config, Config}; use reth_interfaces::{consensus::Error, provider::HeaderProvider, Result as RethResult}; -use reth_primitives::{BlockLocked, SealedHeader, TransactionSigned}; +use reth_primitives::{ + Account, Address, BlockLocked, SealedHeader, Transaction, TransactionSigned, +}; use std::time::SystemTime; /// Validate header standalone @@ -34,10 +36,81 @@ pub fn validate_header_standalone( /// Validate transactions standlone pub fn validate_transactions_standalone( - _transactions: &[TransactionSigned], - _config: &Config, + transaction: &Transaction, + config: &Config, ) -> Result<(), Error> { - // TODO + let chain_id = match transaction { + Transaction::Legacy { chain_id, .. } => *chain_id, + Transaction::Eip2930 { chain_id, .. } => Some(*chain_id), + Transaction::Eip1559 { chain_id, max_fee_per_gas, max_priority_fee_per_gas, .. } => { + // EIP-1559: add more constraints to the tx validation + // https://github.com/ethereum/EIPs/pull/3594 + if max_priority_fee_per_gas > max_fee_per_gas { + return Err(Error::TransactionPriorityFeeMoreThenMaxFee) + } + Some(*chain_id) + } + }; + if let Some(chain_id) = chain_id { + if chain_id != config.chain_id { + return Err(Error::TransactionChainId) + } + } + + // signature validation? + + Ok(()) +} + +/// Validate transaction in regards to header +/// Only parametar from header that effects transaction is base_fee +pub fn validate_transaction_regarding_header( + transaction: &Transaction, + base_fee: Option, +) -> Result<(), Error> { + // check basefee and few checks that are related to that. + // https://github.com/ethereum/EIPs/pull/3594 + if let Some(base_fee_per_gas) = base_fee { + if transaction.max_fee_per_gas() < base_fee_per_gas { + return Err(Error::TransactionMaxFeeLessThenBaseFee) + } + } + + Ok(()) +} + +/// Account provider +pub trait AccountProvider { + /// Get basic account information. + fn basic_account(&self, address: Address) -> reth_interfaces::Result>; +} + +/// Validate transaction in regards of State +pub fn validate_transaction_regarding_state( + _transaction: &TransactionSigned, + _config: &Config, + _account_provider: &AP, +) -> Result<(), Error> { + // sanity check: if account has a bytecode. This is not allowed.s + // check nonce + // gas_price*gas_limit+value < account.balance + + // let max_gas_cost = U512::from(message.gas_limit()) + // * U512::from(ethereum_types::U256::from(message.max_fee_per_gas().to_be_bytes())); + // // See YP, Eq (57) in Section 6.2 "Execution" + // let v0 = max_gas_cost + + // U512::from(ethereum_types::U256::from(message.value().to_be_bytes())); + // let available_balance = + // ethereum_types::U256::from(self.state.get_balance(sender)?.to_be_bytes()).into(); + // if available_balance < v0 { + // return Err(TransactionValidationError::Validation( + // BadTransactionError::InsufficientFunds { + // account: sender, + // available: available_balance, + // required: v0, + // }, + // )); + // } Ok(()) } diff --git a/crates/executor/src/executor.rs b/crates/executor/src/executor.rs index afd23fc4ee..e019c1a400 100644 --- a/crates/executor/src/executor.rs +++ b/crates/executor/src/executor.rs @@ -49,6 +49,8 @@ impl Executor { // create receipt // bloom filter from logs + // Sum of the transaction’s gas limit and the gas utilized in this block prior + // Receipt outcome EIP-658: Embedding transaction status code in receipts // EIP-658 supperseeded EIP-98 in Byzantium fork } diff --git a/crates/interfaces/src/consensus.rs b/crates/interfaces/src/consensus.rs index 217b53f82d..c2ad8b1836 100644 --- a/crates/interfaces/src/consensus.rs +++ b/crates/interfaces/src/consensus.rs @@ -41,7 +41,6 @@ pub enum Error { TimestampIsInPast { parent_timestamp: u64, timestamp: u64 }, #[error("Block timestamp {timestamp:?} is in future in comparison of our clock time {present_timestamp:?}")] TimestampIsInFuture { timestamp: u64, present_timestamp: u64 }, - // TODO make better error msg :) #[error("Child gas_limit {child_gas_limit:?} max increase is {parent_gas_limit}/1024")] GasLimitInvalidIncrease { parent_gas_limit: u64, child_gas_limit: u64 }, #[error("Child gas_limit {child_gas_limit:?} max decrease is {parent_gas_limit}/1024")] @@ -50,4 +49,10 @@ pub enum Error { BaseFeeMissing, #[error("Block base fee ({got:?}) is different then expected: ({expected:?})")] BaseFeeDiff { expected: u64, got: u64 }, + #[error("Transaction eip1559 priority fee is more then max fee")] + TransactionPriorityFeeMoreThenMaxFee, + #[error("Transaction chain_id does not match")] + TransactionChainId, + #[error("Transation max fee is less them block base fee")] + TransactionMaxFeeLessThenBaseFee, } diff --git a/crates/net/eth-wire/src/types/transactions.rs b/crates/net/eth-wire/src/types/transactions.rs index d99b38ea02..14d65b7446 100644 --- a/crates/net/eth-wire/src/types/transactions.rs +++ b/crates/net/eth-wire/src/types/transactions.rs @@ -395,10 +395,10 @@ mod test { }; // checking tx by tx for easier debugging if there are any regressions - for (expected, decoded) in + for (decoded, expected) in decoded_transactions.message.0.iter().zip(expected_transactions.message.0.iter()) { - assert_eq!(expected, decoded); + assert_eq!(decoded, expected); } assert_eq!(decoded_transactions, expected_transactions); diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index f5549e4b64..4418823a36 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -17,6 +17,9 @@ ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = parity-scale-codec = { version = "3.2.1", features = ["derive", "bytes"] } tiny-keccak = { version = "2.0", features = ["keccak"] } +# crypto +secp256k1 = { version = "0.24.0", default-features = false, features = ["alloc", "recovery"] } + #used for forkid crc = "1" maplit = "1" @@ -29,6 +32,9 @@ sucds = "0.5.0" arbitrary = { version = "1.1.7", features = ["derive"], optional = true} hex = "0.4" hex-literal = "0.3" +derive_more = "0.99" + + [dev-dependencies] arbitrary = { version = "1.1.7", features = ["derive"]} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 0be56f0466..0cf932a414 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -31,7 +31,8 @@ pub use log::Log; pub use receipt::Receipt; pub use storage::StorageEntry; pub use transaction::{ - AccessList, AccessListItem, Signature, Transaction, TransactionKind, TransactionSigned, TxType, + AccessList, AccessListItem, Signature, Transaction, TransactionKind, TransactionSigned, + TransactionSignedEcRecovered, TxType, }; /// Block hash. @@ -46,6 +47,8 @@ pub type BlockID = H256; pub type TxHash = H256; /// TxNumber is sequence number of all existing transactions pub type TxNumber = u64; +/// Chain identifier type, introduced in EIP-155 +pub type ChainId = u64; /// Storage Key pub type StorageKey = H256; diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 71ebe505c6..13c022a96d 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1,15 +1,16 @@ mod access_list; mod signature; mod tx_type; +mod util; -use crate::{Address, Bytes, TxHash, U256}; +use crate::{Address, Bytes, ChainId, TxHash, H256, U256}; pub use access_list::{AccessList, AccessListItem}; -use bytes::Buf; +use bytes::{Buf, BytesMut}; +use derive_more::{AsRef, Deref}; use ethers_core::utils::keccak256; use reth_codecs::main_codec; use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_STRING_CODE}; pub use signature::Signature; -use std::ops::Deref; pub use tx_type::TxType; /// Raw Transaction. @@ -20,7 +21,7 @@ pub enum Transaction { /// Legacy transaciton. Legacy { /// Added as EIP-155: Simple replay attack protection - chain_id: Option, + chain_id: Option, /// A scalar value equal to the number of transactions sent by the sender; formally Tn. nonce: u64, /// A scalar value equal to the number of @@ -51,7 +52,7 @@ pub enum Transaction { /// Transaction with AccessList. https://eips.ethereum.org/EIPS/eip-2930 Eip2930 { /// Added as EIP-155: Simple replay attack protection - chain_id: u64, + chain_id: ChainId, /// A scalar value equal to the number of transactions sent by the sender; formally Tn. nonce: u64, /// A scalar value equal to the number of @@ -129,12 +130,12 @@ pub enum Transaction { } impl Transaction { - /// Heavy operation that return hash over rlp encoded transaction. - /// It is only used for signature signing. - pub fn signature_hash(&self) -> TxHash { - let mut encoded = Vec::with_capacity(self.length()); - self.encode(&mut encoded); - keccak256(encoded).into() + /// Heavy operation that return signature hash over rlp encoded transaction. + /// It is only for signature signing or signer recovery. + pub fn signature_hash(&self) -> H256 { + let mut buf = BytesMut::new(); + self.encode(&mut buf); + keccak256(&buf).into() } /// Sets the transaction's chain id to the provided value. @@ -174,6 +175,16 @@ impl Transaction { } } + /// Max fee per gas for eip1559 transaction, for legacy transactions this is gas_limit + pub fn max_fee_per_gas(&self) -> u64 { + match self { + Transaction::Legacy { gas_limit, .. } | Transaction::Eip2930 { gas_limit, .. } => { + *gas_limit + } + Transaction::Eip1559 { max_fee_per_gas, .. } => *max_fee_per_gas, + } + } + /// Get the transaction's input field. pub fn input(&self) -> &Bytes { match self { @@ -432,9 +443,11 @@ impl Decodable for TransactionKind { /// Signed transaction. #[main_codec] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref)] pub struct TransactionSigned { /// Raw transaction info + #[deref] + #[as_ref] pub transaction: Transaction, /// Transaction hash pub hash: TxHash, @@ -442,20 +455,6 @@ pub struct TransactionSigned { pub signature: Signature, } -impl AsRef for TransactionSigned { - fn as_ref(&self) -> &Transaction { - &self.transaction - } -} - -impl Deref for TransactionSigned { - type Target = Transaction; - - fn deref(&self) -> &Self::Target { - &self.transaction - } -} - impl Encodable for TransactionSigned { fn length(&self) -> usize { let len = self.payload_len(); @@ -465,41 +464,7 @@ impl Encodable for TransactionSigned { } fn encode(&self, out: &mut dyn bytes::BufMut) { - if let Transaction::Legacy { chain_id, .. } = self.transaction { - let header = Header { list: true, payload_length: self.payload_len() }; - header.encode(out); - self.transaction.encode_fields(out); - - if let Some(id) = chain_id { - self.signature.encode_eip155_inner(out, id); - } else { - // if the transaction has no chain id then it is a pre-EIP-155 transaction - self.signature.encode_inner_legacy(out); - } - } else { - let header = Header { list: false, payload_length: self.payload_len() }; - header.encode(out); - match self.transaction { - Transaction::Eip2930 { .. } => { - out.put_u8(1); - let list_header = Header { list: true, payload_length: self.inner_tx_len() }; - list_header.encode(out); - } - Transaction::Eip1559 { .. } => { - out.put_u8(2); - let list_header = Header { list: true, payload_length: self.inner_tx_len() }; - list_header.encode(out); - } - Transaction::Legacy { .. } => { - unreachable!("Legacy transaction should be handled above") - } - } - - self.transaction.encode_fields(out); - self.signature.odd_y_parity.encode(out); - self.signature.r.encode(out); - self.signature.s.encode(out); - } + self.encode_inner(out, true); } } @@ -512,6 +477,10 @@ impl Decodable for TransactionSigned { let first_header = Header::decode(buf)?; // if the transaction is encoded as a string then it is a typed transaction if !first_header.list { + // Bytes that are going to be used to create a hash of transaction. + // For eip2728 types transaction header is not used inside hash + let original_encoding = *buf; + let tx_type = *buf .first() .ok_or(DecodeError::Custom("typed tx cannot be decoded from an empty slice"))?; @@ -555,8 +524,7 @@ impl Decodable for TransactionSigned { }; let mut signed = TransactionSigned { transaction, hash: Default::default(), signature }; - let tx_length = first_header.payload_length + first_header.length(); - signed.hash = keccak256(&original_encoding[..tx_length]).into(); + signed.hash = keccak256(&original_encoding[..first_header.payload_length]).into(); Ok(signed) } else { let mut transaction = Transaction::Legacy { @@ -592,13 +560,79 @@ impl TransactionSigned { self.hash } + /// Recover signer from signature and hash. + pub fn recover_signer(&self) -> Option
{ + let signature_hash = self.signature_hash(); + self.signature.recover_signer(signature_hash) + } + + /// Devour Self, recover signer and return [`TransactionSignedEcRecovered`] + pub fn into_ecrecovered(self) -> Option { + let signer = self.recover_signer()?; + Some(TransactionSignedEcRecovered { signed_transaction: self, signer }) + } + + /// try to recover signer and return [`TransactionSignedEcRecovered`] + pub fn try_ecrecovered(&self) -> Option { + let signer = self.recover_signer()?; + Some(TransactionSignedEcRecovered { signed_transaction: self.clone(), signer }) + } + + /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating + /// hash that for eip2728 does not require rlp header + fn encode_inner(&self, out: &mut dyn bytes::BufMut, with_header: bool) { + if let Transaction::Legacy { chain_id, .. } = self.transaction { + let header = Header { list: true, payload_length: self.payload_len() }; + header.encode(out); + self.transaction.encode_fields(out); + + if let Some(id) = chain_id { + self.signature.encode_eip155_inner(out, id); + } else { + // if the transaction has no chain id then it is a pre-EIP-155 transaction + self.signature.encode_inner_legacy(out); + } + } else { + if with_header { + let header = Header { list: false, payload_length: self.payload_len() }; + header.encode(out); + } + match self.transaction { + Transaction::Eip2930 { .. } => { + out.put_u8(1); + let list_header = Header { list: true, payload_length: self.inner_tx_len() }; + list_header.encode(out); + } + Transaction::Eip1559 { .. } => { + out.put_u8(2); + let list_header = Header { list: true, payload_length: self.inner_tx_len() }; + list_header.encode(out); + } + Transaction::Legacy { .. } => { + unreachable!("Legacy transaction should be handled above") + } + } + + self.transaction.encode_fields(out); + self.signature.odd_y_parity.encode(out); + self.signature.r.encode(out); + self.signature.s.encode(out); + } + } + + /// Calculate transaction hash, eip2728 transaction does not contain rlp header and start with + /// tx type. + pub fn recalculate_hash(&self) -> H256 { + let mut buf = Vec::new(); + self.encode_inner(&mut buf, false); + keccak256(&buf).into() + } + /// Create a new signed transaction from a transaction and its signature. /// This will also calculate the transaction hash using its encoding. pub fn from_transaction_and_signature(transaction: Transaction, signature: Signature) -> Self { let mut initial_tx = Self { transaction, hash: Default::default(), signature }; - let mut buf = Vec::new(); - initial_tx.encode(&mut buf); - initial_tx.hash = keccak256(&buf).into(); + initial_tx.hash = initial_tx.recalculate_hash(); initial_tx } @@ -634,13 +668,42 @@ impl TransactionSigned { } } +/// Signed transaction with recovered signer. +#[main_codec] +#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref)] +pub struct TransactionSignedEcRecovered { + /// Signed transaction + #[deref] + #[as_ref] + signed_transaction: TransactionSigned, + /// Signer of the transaction + signer: Address, +} + +impl TransactionSignedEcRecovered { + /// Signer of transaction recovered from signature + pub fn signer(&self) -> Address { + self.signer + } + + /// Transform back to [`TransactionSigned`] + pub fn into_signed(self) -> TransactionSigned { + self.signed_transaction + } + + /// Create [`TransactionSignedEcRecovered`] from [`TransactionSigned`] and [`Address`]. + pub fn from_signed_transaction(signed_transaction: TransactionSigned, signer: Address) -> Self { + Self { signed_transaction, signer } + } +} + #[cfg(test)] mod tests { use std::str::FromStr; use crate::{ transaction::{signature::Signature, TransactionKind}, - Address, Bytes, Transaction, TransactionSigned, H256, U256, + AccessList, Address, Bytes, Transaction, TransactionSigned, H256, U256, }; use bytes::BytesMut; use ethers_core::utils::hex; @@ -648,7 +711,6 @@ mod tests { #[test] fn test_decode_create() { - // panic!("not implemented"); // tests that a contract creation tx encodes and decodes properly let request = Transaction::Eip2930 { chain_id: 1u64, @@ -844,4 +906,80 @@ mod tests { let expected = TransactionSigned::from_transaction_and_signature(expected, signature); assert_eq!(expected, TransactionSigned::decode(bytes_fifth).unwrap()); } + + #[test] + fn decode_raw_tx_and_recover_signer() { + use crate::hex_literal::hex; + // transaction is from ropsten + + let hash: H256 = + hex!("559fb34c4a7f115db26cbf8505389475caaab3df45f5c7a0faa4abfa3835306c").into(); + let signer: Address = hex!("641c5d790f862a58ec7abcfd644c0442e9c201b3").into(); + let raw =hex!("f88b8212b085028fa6ae00830f424094aad593da0c8116ef7d2d594dd6a63241bccfc26c80a48318b64b000000000000000000000000641c5d790f862a58ec7abcfd644c0442e9c201b32aa0a6ef9e170bca5ffb7ac05433b13b7043de667fbb0b4a5e45d3b54fb2d6efcc63a0037ec2c05c3d60c5f5f78244ce0a3859e3a18a36c61efb061b383507d3ce19d2"); + + let mut pointer = raw.as_ref(); + let tx = TransactionSigned::decode(&mut pointer).unwrap(); + assert_eq!(tx.hash(), hash, "Expected same hash"); + assert_eq!(tx.recover_signer(), Some(signer), "Recovering signer should pass."); + } + + #[test] + fn recover_signer_legacy() { + use crate::hex_literal::hex; + + let signer: Address = hex!("398137383b3d25c92898c656696e41950e47316b").into(); + let hash: H256 = + hex!("bb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0").into(); + + let tx = Transaction::Legacy { + chain_id: Some(1), + nonce: 0x18, + gas_price: 0xfa56ea00, + gas_limit: 119902, + to: TransactionKind::Call( hex!("06012c8cf97bead5deae237070f9587f8e7a266d").into()), + value: 0x1c6bf526340000u64.into(), + input: hex!("f7d8c88300000000000000000000000000000000000000000000000000000000000cee6100000000000000000000000000000000000000000000000000000000000ac3e1").into(), + }; + + let sig = Signature { + r: hex!("2a378831cf81d99a3f06a18ae1b6ca366817ab4d88a70053c41d7a8f0368e031").into(), + s: hex!("450d831a05b6e418724436c05c155e0a1b7b921015d0fbc2f667aed709ac4fb5").into(), + odd_y_parity: false, + }; + + let signed_tx = TransactionSigned::from_transaction_and_signature(tx, sig); + assert_eq!(signed_tx.hash(), hash, "Expected same hash"); + assert_eq!(signed_tx.recover_signer(), Some(signer), "Recovering signer should pass."); + } + + #[test] + fn recover_signer_eip1559() { + use crate::hex_literal::hex; + + let signer: Address = hex!("dd6b8b3dc6b7ad97db52f08a275ff4483e024cea").into(); + let hash: H256 = + hex!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0").into(); + + let tx = Transaction::Eip1559 { + chain_id: 1, + nonce: 0x42, + gas_limit: 44386, + to: TransactionKind::Call( hex!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into()), + value: 0.into(), + input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(), + max_fee_per_gas: 0x4a817c800, + max_priority_fee_per_gas: 0x3b9aca00, + access_list: AccessList::default(), + }; + + let sig = Signature { + r: hex!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565").into(), + s: hex!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1").into(), + odd_y_parity: false, + }; + + let signed_tx = TransactionSigned::from_transaction_and_signature(tx, sig); + assert_eq!(signed_tx.hash(), hash, "Expected same hash"); + assert_eq!(signed_tx.recover_signer(), Some(signer), "Recovering signer should pass."); + } } diff --git a/crates/primitives/src/transaction/signature.rs b/crates/primitives/src/transaction/signature.rs index fe8f2b035d..3116b93ccb 100644 --- a/crates/primitives/src/transaction/signature.rs +++ b/crates/primitives/src/transaction/signature.rs @@ -1,8 +1,7 @@ +use crate::{transaction::util::secp256k1, Address, H256, U256}; use reth_codecs::main_codec; use reth_rlp::{Decodable, DecodeError, Encodable}; -use crate::U256; - /// r, s: Values corresponding to the signature of the /// transaction and used to determine the sender of /// the transaction; formally Tr and Ts. This is expanded in Appendix F of yellow paper. @@ -68,4 +67,17 @@ impl Signature { Ok((Signature { r, s, odd_y_parity }, None)) } } + + /// Recover signature from hash. + pub(crate) fn recover_signer(&self, hash: H256) -> Option
{ + let mut sig: [u8; 65] = [0; 65]; + + self.r.to_big_endian(&mut sig[0..32]); + self.s.to_big_endian(&mut sig[32..64]); + sig[64] = self.odd_y_parity as u8; + + // NOTE: we are removing error from underlying crypto library as it will restrain primitive + // errors and we care only if recovery is passing or not. + secp256k1::recover(&sig, hash.as_fixed_bytes()).ok() + } } diff --git a/crates/primitives/src/transaction/util.rs b/crates/primitives/src/transaction/util.rs new file mode 100644 index 0000000000..e91e478f86 --- /dev/null +++ b/crates/primitives/src/transaction/util.rs @@ -0,0 +1,36 @@ +use crate::{keccak256, Address}; + +pub(crate) mod secp256k1 { + + use ::secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Error, Message, Secp256k1, + }; + + use super::*; + /// secp256k1 signer recovery + pub(crate) fn recover(sig: &[u8; 65], msg: &[u8; 32]) -> Result { + let sig = + RecoverableSignature::from_compact(&sig[0..64], RecoveryId::from_i32(sig[64] as i32)?)?; + + let secp = Secp256k1::new(); + let public = secp.recover_ecdsa(&Message::from_slice(&msg[..32])?, &sig)?; + let hash = keccak256(&public.serialize_uncompressed()[1..]); + Ok(Address::from_slice(&hash[12..])) + } +} +#[cfg(test)] +mod tests { + + use super::secp256k1; + use crate::{hex_literal::hex, Address}; + + #[test] + fn sanity_ecrecover_call() { + let sig = hex!("650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e0300"); + let hash = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"); + let out: Address = hex!("c08b5542d177ac6686946920409741463a15dddb").into(); + + assert_eq!(secp256k1::recover(&sig, &hash), Ok(out)); + } +}