mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(primitive): Signer recovery (#179)
* feat(consensus): Signer recovery and tx validation * Signature hash and use seckp256k1 over k256 * use deref_more for transactions * cleanup and fix for eip1559 hash * fix hash calculation on decoding
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<u64>,
|
||||
) -> 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<Option<Account>>;
|
||||
}
|
||||
|
||||
/// Validate transaction in regards of State
|
||||
pub fn validate_transaction_regarding_state<AP: AccountProvider>(
|
||||
_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(())
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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"]}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<u64>,
|
||||
chain_id: Option<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
|
||||
@ -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<Transaction> 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<Address> {
|
||||
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<TransactionSignedEcRecovered> {
|
||||
let signer = self.recover_signer()?;
|
||||
Some(TransactionSignedEcRecovered { signed_transaction: self, signer })
|
||||
}
|
||||
|
||||
/// try to recover signer and return [`TransactionSignedEcRecovered`]
|
||||
pub fn try_ecrecovered(&self) -> Option<TransactionSignedEcRecovered> {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Address> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
36
crates/primitives/src/transaction/util.rs
Normal file
36
crates/primitives/src/transaction/util.rs
Normal file
@ -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<Address, Error> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user