diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 8d0ffb023615..fd973ff74191 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -92,10 +92,10 @@ pub use transaction::{ util::secp256k1::{public_key_to_address, recover_signer, sign_message}, AccessList, AccessListItem, AccessListWithGasUsed, BlobTransaction, BlobTransactionSidecar, FromRecoveredTransaction, IntoRecoveredTransaction, InvalidTransactionError, - PooledTransactionsElement, Signature, Transaction, TransactionKind, TransactionMeta, - TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxEip1559, TxEip2930, - TxEip4844, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, - LEGACY_TX_TYPE_ID, + PooledTransactionsElement, PooledTransactionsElementEcRecovered, Signature, Transaction, + TransactionKind, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, + TransactionSignedNoHash, TxEip1559, TxEip2930, TxEip4844, TxLegacy, TxType, EIP1559_TX_TYPE_ID, + EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, }; pub use withdrawal::Withdrawal; diff --git a/crates/primitives/src/transaction/eip1559.rs b/crates/primitives/src/transaction/eip1559.rs index c446da2e1607..4bc5cc4754b8 100644 --- a/crates/primitives/src/transaction/eip1559.rs +++ b/crates/primitives/src/transaction/eip1559.rs @@ -1,5 +1,6 @@ use super::access_list::AccessList; -use crate::{Bytes, ChainId, Signature, TransactionKind, TxType}; +use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, H256}; +use bytes::BytesMut; use reth_codecs::{main_codec, Compact}; use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header}; use std::mem; @@ -188,6 +189,28 @@ impl TxEip1559 { self.access_list.size() + // access_list self.input.len() // input } + + /// Encodes the legacy transaction in RLP for signing. + pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) { + out.put_u8(self.tx_type() as u8); + Header { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } + + /// Outputs the length of the signature RLP encoding for the transaction. + pub(crate) fn payload_len_for_signature(&self) -> usize { + let payload_length = self.fields_len(); + // 'transaction type byte length' + 'header length' + 'payload length' + 1 + length_of_length(payload_length) + payload_length + } + + /// Outputs the signature hash of the transaction by first encoding without a signature, then + /// hashing. + pub(crate) fn signature_hash(&self) -> H256 { + let mut buf = BytesMut::with_capacity(self.payload_len_for_signature()); + self.encode_for_signing(&mut buf); + keccak256(&buf) + } } #[cfg(test)] diff --git a/crates/primitives/src/transaction/eip2930.rs b/crates/primitives/src/transaction/eip2930.rs index 3f2a8f8fa6b7..78e187889105 100644 --- a/crates/primitives/src/transaction/eip2930.rs +++ b/crates/primitives/src/transaction/eip2930.rs @@ -1,5 +1,6 @@ use super::access_list::AccessList; -use crate::{Bytes, ChainId, Signature, TransactionKind, TxType}; +use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, H256}; +use bytes::BytesMut; use reth_codecs::{main_codec, Compact}; use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header}; use std::mem; @@ -153,6 +154,28 @@ impl TxEip2930 { pub(crate) fn tx_type(&self) -> TxType { TxType::EIP2930 } + + /// Encodes the legacy transaction in RLP for signing. + pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) { + out.put_u8(self.tx_type() as u8); + Header { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } + + /// Outputs the length of the signature RLP encoding for the transaction. + pub(crate) fn payload_len_for_signature(&self) -> usize { + let payload_length = self.fields_len(); + // 'transaction type byte length' + 'header length' + 'payload length' + 1 + length_of_length(payload_length) + payload_length + } + + /// Outputs the signature hash of the transaction by first encoding without a signature, then + /// hashing. + pub(crate) fn signature_hash(&self) -> H256 { + let mut buf = BytesMut::with_capacity(self.payload_len_for_signature()); + self.encode_for_signing(&mut buf); + keccak256(&buf) + } } #[cfg(test)] diff --git a/crates/primitives/src/transaction/eip4844.rs b/crates/primitives/src/transaction/eip4844.rs index 90246031e500..3092f284f171 100644 --- a/crates/primitives/src/transaction/eip4844.rs +++ b/crates/primitives/src/transaction/eip4844.rs @@ -9,6 +9,7 @@ use crate::{ kzg_to_versioned_hash, Bytes, ChainId, Signature, Transaction, TransactionKind, TransactionSigned, TxHash, TxType, EIP4844_TX_TYPE_ID, H256, }; +use bytes::BytesMut; use reth_codecs::{main_codec, Compact}; use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header}; use serde::{Deserialize, Serialize}; @@ -226,6 +227,28 @@ impl TxEip4844 { pub(crate) fn tx_type(&self) -> TxType { TxType::EIP4844 } + + /// Encodes the legacy transaction in RLP for signing. + pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) { + out.put_u8(self.tx_type() as u8); + Header { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } + + /// Outputs the length of the signature RLP encoding for the transaction. + pub(crate) fn payload_len_for_signature(&self) -> usize { + let payload_length = self.fields_len(); + // 'transaction type byte length' + 'header length' + 'payload length' + 1 + length_of_length(payload_length) + payload_length + } + + /// Outputs the signature hash of the transaction by first encoding without a signature, then + /// hashing. + pub(crate) fn signature_hash(&self) -> H256 { + let mut buf = BytesMut::with_capacity(self.payload_len_for_signature()); + self.encode_for_signing(&mut buf); + keccak256(&buf) + } } /// An error that can occur when validating a [BlobTransaction]. diff --git a/crates/primitives/src/transaction/legacy.rs b/crates/primitives/src/transaction/legacy.rs index fcbb627268b4..ec61490a54c5 100644 --- a/crates/primitives/src/transaction/legacy.rs +++ b/crates/primitives/src/transaction/legacy.rs @@ -1,4 +1,5 @@ -use crate::{Bytes, ChainId, Signature, TransactionKind, TxType}; +use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, H256}; +use bytes::BytesMut; use reth_codecs::{main_codec, Compact}; use reth_rlp::{length_of_length, Encodable, Header}; use std::mem; @@ -105,6 +106,59 @@ impl TxLegacy { pub(crate) fn tx_type(&self) -> TxType { TxType::Legacy } + + /// Encodes EIP-155 arguments into the desired buffer. Only encodes values for legacy + /// transactions. + pub(crate) fn encode_eip155_fields(&self, out: &mut dyn bytes::BufMut) { + // if this is a legacy transaction without a chain ID, it must be pre-EIP-155 + // and does not need to encode the chain ID for the signature hash encoding + if let Some(id) = self.chain_id { + // EIP-155 encodes the chain ID and two zeroes + id.encode(out); + 0x00u8.encode(out); + 0x00u8.encode(out); + } + } + + /// Outputs the length of EIP-155 fields. Only outputs a non-zero value for EIP-155 legacy + /// transactions. + pub(crate) fn eip155_fields_len(&self) -> usize { + if let Some(id) = self.chain_id { + // EIP-155 encodes the chain ID and two zeroes, so we add 2 to the length of the chain + // ID to get the length of all 3 fields + // len(chain_id) + (0x00) + (0x00) + id.length() + 2 + } else { + // this is either a pre-EIP-155 legacy transaction or a typed transaction + 0 + } + } + + /// Encodes the legacy transaction in RLP for signing, including the EIP-155 fields if possible. + pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) { + Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() } + .encode(out); + self.encode_fields(out); + self.encode_eip155_fields(out); + } + + /// Outputs the length of the signature RLP encoding for the transaction, including the length + /// of the EIP-155 fields if possible. + pub(crate) fn payload_len_for_signature(&self) -> usize { + let payload_length = self.fields_len() + self.eip155_fields_len(); + // 'header length' + 'payload length' + length_of_length(payload_length) + payload_length + } + + /// Outputs the signature hash of the transaction by first encoding without a signature, then + /// hashing. + /// + /// See [Self::encode_for_signing] for more information on the encoding format. + pub(crate) fn signature_hash(&self) -> H256 { + let mut buf = BytesMut::with_capacity(self.payload_len_for_signature()); + self.encode_for_signing(&mut buf); + keccak256(&buf) + } } #[cfg(test)] diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 962bd7e5bcdc..115bc83aea1f 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -10,9 +10,7 @@ pub use meta::TransactionMeta; use once_cell::sync::Lazy; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use reth_codecs::{add_arbitrary_tests, derive_arbitrary, Compact}; -use reth_rlp::{ - length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE, -}; +use reth_rlp::{Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE}; use serde::{Deserialize, Serialize}; pub use signature::Signature; use std::mem; @@ -24,7 +22,7 @@ pub use eip1559::TxEip1559; pub use eip2930::TxEip2930; pub use eip4844::{BlobTransaction, BlobTransactionSidecar, TxEip4844}; pub use legacy::TxLegacy; -pub use pooled::PooledTransactionsElement; +pub use pooled::{PooledTransactionsElement, PooledTransactionsElementEcRecovered}; mod access_list; mod eip1559; @@ -100,9 +98,12 @@ impl Transaction { /// 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) + match self { + Transaction::Legacy(tx) => tx.signature_hash(), + Transaction::Eip2930(tx) => tx.signature_hash(), + Transaction::Eip1559(tx) => tx.signature_hash(), + Transaction::Eip4844(tx) => tx.signature_hash(), + } } /// Get chain_id. @@ -316,54 +317,6 @@ impl Transaction { } } - /// Encodes EIP-155 arguments into the desired buffer. Only encodes values for legacy - /// transactions. - pub(crate) fn encode_eip155_fields(&self, out: &mut dyn bytes::BufMut) { - // if this is a legacy transaction without a chain ID, it must be pre-EIP-155 - // and does not need to encode the chain ID for the signature hash encoding - if let Transaction::Legacy(TxLegacy { chain_id: Some(id), .. }) = self { - // EIP-155 encodes the chain ID and two zeroes - id.encode(out); - 0x00u8.encode(out); - 0x00u8.encode(out); - } - } - - /// Outputs the length of EIP-155 fields. Only outputs a non-zero value for EIP-155 legacy - /// transactions. - pub(crate) fn eip155_fields_len(&self) -> usize { - if let Transaction::Legacy(TxLegacy { chain_id: Some(id), .. }) = self { - // EIP-155 encodes the chain ID and two zeroes, so we add 2 to the length of the chain - // ID to get the length of all 3 fields - // len(chain_id) + (0x00) + (0x00) - id.length() + 2 - } else { - // this is either a pre-EIP-155 legacy transaction or a typed transaction - 0 - } - } - - /// Outputs the length of the transaction's fields, without a RLP header or length of the - /// eip155 fields. - pub(crate) fn fields_len(&self) -> usize { - match self { - Transaction::Legacy(legacy_tx) => legacy_tx.fields_len(), - Transaction::Eip2930(access_list_tx) => access_list_tx.fields_len(), - Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.fields_len(), - Transaction::Eip4844(blob_tx) => blob_tx.fields_len(), - } - } - - /// Encodes only the transaction's fields into the desired buffer, without a RLP header. - pub(crate) fn encode_fields(&self, out: &mut dyn bytes::BufMut) { - match self { - Transaction::Legacy(legacy_tx) => legacy_tx.encode_fields(out), - Transaction::Eip2930(access_list_tx) => access_list_tx.encode_fields(out), - Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.encode_fields(out), - Transaction::Eip4844(blob_tx) => blob_tx.encode_fields(out), - } - } - /// This encodes the transaction _without_ the signature, and is only suitable for creating a /// hash intended for signing. pub fn encode_without_signature(&self, out: &mut dyn bytes::BufMut) { @@ -496,32 +449,27 @@ impl Default for Transaction { impl Encodable for Transaction { fn encode(&self, out: &mut dyn bytes::BufMut) { match self { - Transaction::Legacy { .. } => { - Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() } - .encode(out); - self.encode_fields(out); - self.encode_eip155_fields(out); + Transaction::Legacy(legacy_tx) => { + legacy_tx.encode_for_signing(out); } - _ => { - out.put_u8(self.tx_type() as u8); - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); + Transaction::Eip2930(access_list_tx) => { + access_list_tx.encode_for_signing(out); + } + Transaction::Eip1559(dynamic_fee_tx) => { + dynamic_fee_tx.encode_for_signing(out); + } + Transaction::Eip4844(blob_tx) => { + blob_tx.encode_for_signing(out); } } } fn length(&self) -> usize { match self { - Transaction::Legacy { .. } => { - let payload_length = self.fields_len() + self.eip155_fields_len(); - // 'header length' + 'payload length' - length_of_length(payload_length) + payload_length - } - _ => { - let payload_length = self.fields_len(); - // 'transaction type byte length' + 'header length' + 'payload length' - 1 + length_of_length(payload_length) + payload_length - } + Transaction::Legacy(legacy_tx) => legacy_tx.payload_len_for_signature(), + Transaction::Eip2930(access_list_tx) => access_list_tx.payload_len_for_signature(), + Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.payload_len_for_signature(), + Transaction::Eip4844(blob_tx) => blob_tx.payload_len_for_signature(), } } } diff --git a/crates/primitives/src/transaction/pooled.rs b/crates/primitives/src/transaction/pooled.rs index ebaf5ce144de..bcc870a2c0f2 100644 --- a/crates/primitives/src/transaction/pooled.rs +++ b/crates/primitives/src/transaction/pooled.rs @@ -1,10 +1,11 @@ //! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a //! response to `GetPooledTransactions`. use crate::{ - BlobTransaction, Bytes, Signature, Transaction, TransactionSigned, TxEip1559, TxEip2930, - TxHash, TxLegacy, EIP4844_TX_TYPE_ID, + Address, BlobTransaction, Bytes, Signature, Transaction, TransactionSigned, + TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxHash, TxLegacy, EIP4844_TX_TYPE_ID, H256, }; use bytes::Buf; +use derive_more::{AsRef, Deref}; use reth_rlp::{Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE}; use serde::{Deserialize, Serialize}; @@ -45,6 +46,46 @@ pub enum PooledTransactionsElement { } impl PooledTransactionsElement { + /// 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 { + match self { + Self::Legacy { transaction, .. } => transaction.signature_hash(), + Self::Eip2930 { transaction, .. } => transaction.signature_hash(), + Self::Eip1559 { transaction, .. } => transaction.signature_hash(), + Self::BlobTransaction(blob_tx) => blob_tx.transaction.signature_hash(), + } + } + + /// Returns the signature of the transaction. + pub fn signature(&self) -> &Signature { + match self { + Self::Legacy { signature, .. } => signature, + Self::Eip2930 { signature, .. } => signature, + Self::Eip1559 { signature, .. } => signature, + Self::BlobTransaction(blob_tx) => &blob_tx.signature, + } + } + + /// Recover signer from signature and hash. + /// + /// Returns `None` if the transaction's signature is invalid, see also [Self::recover_signer]. + pub fn recover_signer(&self) -> Option
{ + let signature_hash = self.signature_hash(); + self.signature().recover_signer(signature_hash) + } + + /// Tries to recover signer and return [`PooledTransactionsElementEcRecovered`]. + /// + /// Returns `Err(Self)` if the transaction's signature is invalid, see also + /// [Self::recover_signer]. + pub fn try_into_ecrecovered(self) -> Result