feat: use alloy Signature type (#10758)

Co-authored-by: Emilia Hane <elsaemiliaevahane@gmail.com>
This commit is contained in:
Aurélien
2024-09-23 15:29:48 +02:00
committed by GitHub
parent fba837468c
commit 15aee9b144
30 changed files with 537 additions and 709 deletions

View File

@ -6,7 +6,7 @@ use crate::{
TxEip4844, TxLegacy, TxType,
};
use alloc::{string::ToString, vec::Vec};
use alloy_primitives::TxKind;
use alloy_primitives::{Parity, TxKind};
use alloy_rlp::Error as RlpError;
use alloy_serde::WithOtherFields;
use op_alloy_rpc_types as _;
@ -219,27 +219,32 @@ impl TryFrom<WithOtherFields<alloy_rpc_types::Transaction>> for TransactionSigne
let signature = tx.signature.ok_or(ConversionError::MissingSignature)?;
let transaction: Transaction = tx.try_into()?;
let y_parity = if let Some(y_parity) = signature.y_parity {
y_parity.0
} else {
match transaction.tx_type() {
// If the transaction type is Legacy, adjust the v component of the
// signature according to the Ethereum specification
TxType::Legacy => {
extract_chain_id(signature.v.to())
.map_err(|_| ConversionError::InvalidSignature)?
.0
}
_ => !signature.v.is_zero(),
}
};
let mut parity = Parity::Parity(y_parity);
if matches!(transaction.tx_type(), TxType::Legacy) {
if let Some(chain_id) = transaction.chain_id() {
parity = parity.with_chain_id(chain_id)
}
}
Ok(Self::from_transaction_and_signature(
transaction.clone(),
Signature {
r: signature.r,
s: signature.s,
odd_y_parity: if let Some(y_parity) = signature.y_parity {
y_parity.0
} else {
match transaction.tx_type() {
// If the transaction type is Legacy, adjust the v component of the
// signature according to the Ethereum specification
TxType::Legacy => {
extract_chain_id(signature.v.to())
.map_err(|_| ConversionError::InvalidSignature)?
.0
}
_ => !signature.v.is_zero(),
}
},
},
transaction,
Signature::new(signature.r, signature.s, parity),
))
}
}
@ -256,22 +261,6 @@ impl TryFrom<WithOtherFields<alloy_rpc_types::Transaction>> for TransactionSigne
}
}
impl TryFrom<alloy_rpc_types::Signature> for Signature {
type Error = alloy_rpc_types::ConversionError;
fn try_from(signature: alloy_rpc_types::Signature) -> Result<Self, Self::Error> {
use alloy_rpc_types::ConversionError;
let odd_y_parity = if let Some(y_parity) = signature.y_parity {
y_parity.0
} else {
extract_chain_id(signature.v.to()).map_err(|_| ConversionError::InvalidSignature)?.0
};
Ok(Self { r: signature.r, s: signature.s, odd_y_parity })
}
}
impl TryFrom<WithOtherFields<alloy_rpc_types::Transaction>> for TransactionSignedNoHash {
type Error = alloy_rpc_types::ConversionError;

View File

@ -112,7 +112,7 @@ pub use c_kzg as kzg;
/// Optimism specific re-exports
#[cfg(feature = "optimism")]
mod optimism {
pub use crate::transaction::{TxDeposit, DEPOSIT_TX_TYPE_ID};
pub use crate::transaction::{optimism_deposit_tx_signature, TxDeposit, DEPOSIT_TX_TYPE_ID};
pub use reth_optimism_chainspec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA};
}

View File

@ -6,6 +6,7 @@ use crate::{
};
use alloy_consensus::SignableTransaction;
use alloy_primitives::Parity;
use alloy_rlp::{
Decodable, Encodable, Error as RlpError, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE,
};
@ -15,6 +16,7 @@ use derive_more::{AsRef, Deref};
use once_cell::sync::Lazy;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use serde::{Deserialize, Serialize};
use signature::{decode_with_eip155_chain_id, with_eip155_parity};
pub use access_list::{AccessList, AccessListItem, AccessListResult};
@ -32,7 +34,9 @@ pub use sidecar::BlobTransactionValidationError;
pub use sidecar::{BlobTransaction, BlobTransactionSidecar};
pub use compat::FillTxEnv;
pub use signature::{extract_chain_id, Signature};
pub use signature::{
extract_chain_id, legacy_parity, recover_signer, recover_signer_unchecked, Signature,
};
pub use tx_type::{
TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
LEGACY_TX_TYPE_ID,
@ -53,6 +57,8 @@ mod variant;
#[cfg(feature = "optimism")]
pub use op_alloy_consensus::TxDeposit;
#[cfg(feature = "optimism")]
pub use reth_optimism_chainspec::optimism_deposit_tx_signature;
#[cfg(feature = "optimism")]
pub use tx_type::DEPOSIT_TX_TYPE_ID;
#[cfg(any(test, feature = "reth-codec"))]
use tx_type::{
@ -136,7 +142,7 @@ pub enum Transaction {
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for Transaction {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(match TxType::arbitrary(u)? {
let mut tx = match TxType::arbitrary(u)? {
TxType::Legacy => {
let mut tx = TxLegacy::arbitrary(u)?;
tx.gas_limit = (tx.gas_limit as u64).into();
@ -169,7 +175,14 @@ impl<'a> arbitrary::Arbitrary<'a> for Transaction {
tx.gas_limit = (tx.gas_limit as u64).into();
Self::Deposit(tx)
}
})
};
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
if let Some(chain_id) = tx.chain_id() {
tx.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
Ok(tx)
}
}
@ -529,30 +542,20 @@ impl Transaction {
Self::Legacy(legacy_tx) => {
// do nothing w/ with_header
legacy_tx.encode_with_signature_fields(
&signature.as_signature_with_eip155_parity(legacy_tx.chain_id),
&with_eip155_parity(signature, legacy_tx.chain_id),
out,
)
}
Self::Eip2930(access_list_tx) => access_list_tx.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
with_header,
),
Self::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
with_header,
),
Self::Eip4844(blob_tx) => blob_tx.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
with_header,
),
Self::Eip7702(set_code_tx) => set_code_tx.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
with_header,
),
Self::Eip2930(access_list_tx) => {
access_list_tx.encode_with_signature(signature, out, with_header)
}
Self::Eip1559(dynamic_fee_tx) => {
dynamic_fee_tx.encode_with_signature(signature, out, with_header)
}
Self::Eip4844(blob_tx) => blob_tx.encode_with_signature(signature, out, with_header),
Self::Eip7702(set_code_tx) => {
set_code_tx.encode_with_signature(signature, out, with_header)
}
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => deposit_tx.encode_inner(out, with_header),
}
@ -828,8 +831,7 @@ impl Encodable for Transaction {
/// Signed transaction without its Hash. Used type for inserting into the DB.
///
/// This can by converted to [`TransactionSigned`] by calling [`TransactionSignedNoHash::hash`].
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Default, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
pub struct TransactionSignedNoHash {
/// The transaction signature values
@ -862,7 +864,7 @@ impl TransactionSignedNoHash {
}
let signature_hash = self.signature_hash();
self.signature.recover_signer(signature_hash)
recover_signer(&self.signature, signature_hash)
}
/// Recover signer from signature and hash _without ensuring that the signature has a low `s`
@ -872,7 +874,7 @@ impl TransactionSignedNoHash {
/// buffer before use.**
///
/// Returns `None` if the transaction's signature is invalid, see also
/// [`Signature::recover_signer_unchecked`].
/// [`recover_signer_unchecked`].
///
/// # Optimism
///
@ -894,12 +896,12 @@ impl TransactionSignedNoHash {
// transactions with an empty signature
//
// NOTE: this is very hacky and only relevant for op-mainnet pre bedrock
if self.is_legacy() && self.signature == Signature::optimism_deposit_tx_signature() {
if self.is_legacy() && self.signature == optimism_deposit_tx_signature() {
return Some(Address::ZERO)
}
}
self.signature.recover_signer_unchecked(keccak256(buffer))
recover_signer_unchecked(&self.signature, keccak256(buffer))
}
/// Converts into a transaction type with its hash: [`TransactionSigned`].
@ -927,6 +929,21 @@ impl TransactionSignedNoHash {
}
}
impl Default for TransactionSignedNoHash {
fn default() -> Self {
Self { signature: Signature::test_signature(), transaction: Default::default() }
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TransactionSignedNoHash {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let tx_signed = TransactionSigned::arbitrary(u)?;
Ok(Self { signature: tx_signed.signature, transaction: tx_signed.transaction })
}
}
#[cfg(any(test, feature = "reth-codec"))]
impl reth_codecs::Compact for TransactionSignedNoHash {
fn to_compact<B>(&self, buf: &mut B) -> usize
@ -967,7 +984,7 @@ impl reth_codecs::Compact for TransactionSignedNoHash {
let bitflags = buf.get_u8() as usize;
let sig_bit = bitflags & 1;
let (signature, buf) = Signature::from_compact(buf, sig_bit);
let (mut signature, buf) = Signature::from_compact(buf, sig_bit);
let zstd_bit = bitflags >> 3;
let (transaction, buf) = if zstd_bit != 0 {
@ -987,6 +1004,10 @@ impl reth_codecs::Compact for TransactionSignedNoHash {
Transaction::from_compact(buf, transaction_type)
};
if matches!(transaction, Transaction::Legacy(_)) {
signature = signature.with_parity(legacy_parity(&signature, transaction.chain_id()))
}
(Self { signature, transaction }, buf)
}
}
@ -1005,7 +1026,7 @@ impl From<TransactionSigned> for TransactionSignedNoHash {
/// Signed transaction.
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Serialize, Deserialize)]
pub struct TransactionSigned {
/// Transaction hash
pub hash: TxHash,
@ -1017,6 +1038,16 @@ pub struct TransactionSigned {
pub transaction: Transaction,
}
impl Default for TransactionSigned {
fn default() -> Self {
Self {
hash: Default::default(),
signature: Signature::test_signature(),
transaction: Default::default(),
}
}
}
impl AsRef<Self> for TransactionSigned {
fn as_ref(&self) -> &Self {
self
@ -1043,7 +1074,7 @@ impl TransactionSigned {
/// Recover signer from signature and hash.
///
/// Returns `None` if the transaction's signature is invalid following [EIP-2](https://eips.ethereum.org/EIPS/eip-2), see also [`Signature::recover_signer`].
/// Returns `None` if the transaction's signature is invalid following [EIP-2](https://eips.ethereum.org/EIPS/eip-2), see also [`recover_signer`].
///
/// Note:
///
@ -1058,14 +1089,14 @@ impl TransactionSigned {
return Some(from)
}
let signature_hash = self.signature_hash();
self.signature.recover_signer(signature_hash)
recover_signer(&self.signature, signature_hash)
}
/// Recover signer from signature and hash _without ensuring that the signature has a low `s`
/// value_.
///
/// Returns `None` if the transaction's signature is invalid, see also
/// [`Signature::recover_signer_unchecked`].
/// [`recover_signer_unchecked`].
pub fn recover_signer_unchecked(&self) -> Option<Address> {
// Optimism's Deposit transaction does not have a signature. Directly return the
// `from` address.
@ -1074,7 +1105,7 @@ impl TransactionSigned {
return Some(from)
}
let signature_hash = self.signature_hash();
self.signature.recover_signer_unchecked(signature_hash)
recover_signer_unchecked(&self.signature, signature_hash)
}
/// Recovers a list of signers from a transaction list iterator.
@ -1192,24 +1223,20 @@ impl TransactionSigned {
pub(crate) fn payload_len_inner(&self) -> usize {
match &self.transaction {
Transaction::Legacy(legacy_tx) => legacy_tx.encoded_len_with_signature(
&self.signature.as_signature_with_eip155_parity(legacy_tx.chain_id),
),
Transaction::Eip2930(access_list_tx) => access_list_tx.encoded_len_with_signature(
&self.signature.as_signature_with_boolean_parity(),
true,
),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.encoded_len_with_signature(
&self.signature.as_signature_with_boolean_parity(),
true,
),
Transaction::Eip4844(blob_tx) => blob_tx.encoded_len_with_signature(
&self.signature.as_signature_with_boolean_parity(),
true,
),
Transaction::Eip7702(set_code_tx) => set_code_tx.encoded_len_with_signature(
&self.signature.as_signature_with_boolean_parity(),
true,
&with_eip155_parity(&self.signature, legacy_tx.chain_id),
),
Transaction::Eip2930(access_list_tx) => {
access_list_tx.encoded_len_with_signature(&self.signature, true)
}
Transaction::Eip1559(dynamic_fee_tx) => {
dynamic_fee_tx.encoded_len_with_signature(&self.signature, true)
}
Transaction::Eip4844(blob_tx) => {
blob_tx.encoded_len_with_signature(&self.signature, true)
}
Transaction::Eip7702(set_code_tx) => {
set_code_tx.encoded_len_with_signature(&self.signature, true)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.encoded_len(true),
}
@ -1235,7 +1262,7 @@ impl TransactionSigned {
/// Calculate a heuristic for the in-memory size of the [`TransactionSigned`].
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<TxHash>() + self.transaction.size() + self.signature.size()
mem::size_of::<TxHash>() + self.transaction.size() + mem::size_of::<Signature>()
}
/// Decodes legacy transaction from the data buffer into a tuple.
@ -1268,7 +1295,7 @@ impl TransactionSigned {
input: Decodable::decode(data)?,
chain_id: None,
};
let (signature, extracted_id) = Signature::decode_with_eip155_chain_id(data)?;
let (signature, extracted_id) = decode_with_eip155_chain_id(data)?;
transaction.chain_id = extracted_id;
// check the new length, compared to the original length and the header length
@ -1343,15 +1370,19 @@ impl TransactionSigned {
};
#[cfg(not(feature = "optimism"))]
let signature = Signature::decode(data)?;
let signature = Signature::decode_rlp_vrs(data)?;
#[cfg(feature = "optimism")]
let signature = if tx_type == TxType::Deposit {
Signature::optimism_deposit_tx_signature()
optimism_deposit_tx_signature()
} else {
Signature::decode(data)?
Signature::decode_rlp_vrs(data)?
};
if !matches!(signature.v(), Parity::Parity(_)) {
return Err(alloy_rlp::Error::Custom("invalid parity for typed transaction"));
}
let bytes_consumed = remaining_len - data.len();
if bytes_consumed != header.payload_length {
return Err(RlpError::UnexpectedLength)
@ -1404,24 +1435,20 @@ impl TransactionSigned {
// method computes the payload len without a RLP header
match &self.transaction {
Transaction::Legacy(legacy_tx) => legacy_tx.encoded_len_with_signature(
&self.signature.as_signature_with_eip155_parity(legacy_tx.chain_id),
),
Transaction::Eip2930(access_list_tx) => access_list_tx.encoded_len_with_signature(
&self.signature.as_signature_with_boolean_parity(),
false,
),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.encoded_len_with_signature(
&self.signature.as_signature_with_boolean_parity(),
false,
),
Transaction::Eip4844(blob_tx) => blob_tx.encoded_len_with_signature(
&self.signature.as_signature_with_boolean_parity(),
false,
),
Transaction::Eip7702(set_code_tx) => set_code_tx.encoded_len_with_signature(
&self.signature.as_signature_with_boolean_parity(),
false,
&with_eip155_parity(&self.signature, legacy_tx.chain_id),
),
Transaction::Eip2930(access_list_tx) => {
access_list_tx.encoded_len_with_signature(&self.signature, false)
}
Transaction::Eip1559(dynamic_fee_tx) => {
dynamic_fee_tx.encoded_len_with_signature(&self.signature, false)
}
Transaction::Eip4844(blob_tx) => {
blob_tx.encoded_len_with_signature(&self.signature, false)
}
Transaction::Eip7702(set_code_tx) => {
set_code_tx.encoded_len_with_signature(&self.signature, false)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.encoded_len(false),
}
@ -1516,11 +1543,19 @@ impl Decodable for TransactionSigned {
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[allow(unused_mut)]
let mut transaction = Transaction::arbitrary(u)?;
if let Some(chain_id) = transaction.chain_id() {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut signature = Signature::arbitrary(u)?;
signature = if matches!(transaction, Transaction::Legacy(_)) {
if let Some(chain_id) = transaction.chain_id() {
signature.with_chain_id(chain_id)
} else {
signature.with_parity(alloy_primitives::Parity::NonEip155(bool::arbitrary(u)?))
}
} else {
signature.with_parity_bool()
};
#[cfg(feature = "optimism")]
// Both `Some(0)` and `None` values are encoded as empty string byte. This introduces
@ -1532,21 +1567,16 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
}
}
let signature = Signature::arbitrary(u)?;
#[cfg(feature = "optimism")]
let signature = if transaction.is_deposit() {
Signature::optimism_deposit_tx_signature()
} else {
signature
};
let signature =
if transaction.is_deposit() { optimism_deposit_tx_signature() } else { signature };
Ok(Self::from_transaction_and_signature(transaction, signature))
}
}
/// Signed transaction with recovered signer.
#[derive(Debug, Clone, PartialEq, Hash, Eq, AsRef, Deref, Default)]
#[derive(Debug, Clone, PartialEq, Hash, Eq, AsRef, Deref)]
pub struct TransactionSignedEcRecovered {
/// Signer of the transaction
signer: Address,
@ -1679,7 +1709,7 @@ mod tests {
Address, Bytes, Transaction, TransactionSigned, TransactionSignedEcRecovered,
TransactionSignedNoHash, B256, U256,
};
use alloy_primitives::{address, b256, bytes};
use alloy_primitives::{address, b256, bytes, Parity};
use alloy_rlp::{Decodable, Encodable, Error as RlpError};
use reth_chainspec::MIN_TRANSACTION_GAS;
use reth_codecs::Compact;
@ -1782,13 +1812,13 @@ mod tests {
value: U256::from(1000000000000000u64),
input: Bytes::default(),
});
let signature = Signature {
odd_y_parity: false,
r: U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae")
let signature = Signature::new(
U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae")
.unwrap(),
s: U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18")
U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18")
.unwrap(),
};
Parity::Eip155(43),
);
let hash = b256!("a517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34");
test_decode_and_encode(&bytes, transaction, signature, Some(hash));
@ -1802,13 +1832,13 @@ mod tests {
value: U256::from(693361000000000u64),
input: Default::default(),
});
let signature = Signature {
odd_y_parity: false,
r: U256::from_str("0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a")
let signature = Signature::new(
U256::from_str("0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a")
.unwrap(),
s: U256::from_str("0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da")
U256::from_str("0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da")
.unwrap(),
};
Parity::Eip155(43),
);
test_decode_and_encode(&bytes, transaction, signature, None);
let bytes = hex!("f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88");
@ -1821,13 +1851,13 @@ mod tests {
value: U256::from(1000000000000000u64),
input: Bytes::default(),
});
let signature = Signature {
odd_y_parity: false,
r: U256::from_str("0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071")
let signature = Signature::new(
U256::from_str("0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071")
.unwrap(),
s: U256::from_str("0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88")
U256::from_str("0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88")
.unwrap(),
};
Parity::Eip155(43),
);
test_decode_and_encode(&bytes, transaction, signature, None);
let bytes = hex!("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469");
@ -1842,13 +1872,13 @@ mod tests {
input: Default::default(),
access_list: Default::default(),
});
let signature = Signature {
odd_y_parity: true,
r: U256::from_str("0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd")
let signature = Signature::new(
U256::from_str("0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd")
.unwrap(),
s: U256::from_str("0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469")
U256::from_str("0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469")
.unwrap(),
};
Parity::Parity(true),
);
test_decode_and_encode(&bytes, transaction, signature, None);
let bytes = hex!("f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860");
@ -1861,13 +1891,13 @@ mod tests {
value: U256::from(1234),
input: Bytes::default(),
});
let signature = Signature {
odd_y_parity: true,
r: U256::from_str("0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981")
let signature = Signature::new(
U256::from_str("0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981")
.unwrap(),
s: U256::from_str("0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860")
U256::from_str("0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860")
.unwrap(),
};
Parity::Eip155(44),
);
test_decode_and_encode(&bytes, transaction, signature, None);
}
@ -2025,13 +2055,13 @@ mod tests {
fn transaction_signed_no_hash_zstd_codec() {
// will use same signature everywhere.
// We don't need signature to match tx, just decoded to the same signature
let signature = Signature {
odd_y_parity: false,
r: U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae")
let signature = Signature::new(
U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae")
.unwrap(),
s: U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18")
U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18")
.unwrap(),
};
Parity::Eip155(43),
);
let inputs: Vec<Vec<u8>> = vec![
vec![],

View File

@ -1,7 +1,11 @@
//! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a
//! response to `GetPooledTransactions`.
use super::{error::TransactionConversionError, TxEip7702};
use super::{
error::TransactionConversionError,
signature::{recover_signer, with_eip155_parity},
TxEip7702,
};
use crate::{
Address, BlobTransaction, BlobTransactionSidecar, Bytes, Signature, Transaction,
TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxHash,
@ -161,7 +165,7 @@ impl PooledTransactionsElement {
///
/// Returns `None` if the transaction's signature is invalid, see also [`Self::recover_signer`].
pub fn recover_signer(&self) -> Option<Address> {
self.signature().recover_signer(self.signature_hash())
recover_signer(self.signature(), self.signature_hash())
}
/// Tries to recover signer and return [`PooledTransactionsElementEcRecovered`].
@ -304,30 +308,22 @@ impl PooledTransactionsElement {
match self {
Self::Legacy { transaction, signature, .. } => {
// method computes the payload len with a RLP header
transaction.encoded_len_with_signature(
&signature.as_signature_with_eip155_parity(transaction.chain_id),
)
transaction.encoded_len_with_signature(&with_eip155_parity(
signature,
transaction.chain_id,
))
}
Self::Eip2930 { transaction, signature, .. } => {
// method computes the payload len without a RLP header
transaction.encoded_len_with_signature(
&signature.as_signature_with_boolean_parity(),
false,
)
transaction.encoded_len_with_signature(signature, false)
}
Self::Eip1559 { transaction, signature, .. } => {
// method computes the payload len without a RLP header
transaction.encoded_len_with_signature(
&signature.as_signature_with_boolean_parity(),
false,
)
transaction.encoded_len_with_signature(signature, false)
}
Self::Eip7702 { transaction, signature, .. } => {
// method computes the payload len without a RLP header
transaction.encoded_len_with_signature(
&signature.as_signature_with_boolean_parity(),
false,
)
transaction.encoded_len_with_signature(signature, false)
}
Self::BlobTransaction(blob_tx) => {
// the encoding does not use a header, so we set `with_header` to false
@ -363,24 +359,18 @@ impl PooledTransactionsElement {
match self {
Self::Legacy { transaction, signature, .. } => transaction
.encode_with_signature_fields(
&signature.as_signature_with_eip155_parity(transaction.chain_id),
&with_eip155_parity(signature, transaction.chain_id),
out,
),
Self::Eip2930 { transaction, signature, .. } => transaction.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
false,
),
Self::Eip1559 { transaction, signature, .. } => transaction.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
false,
),
Self::Eip7702 { transaction, signature, .. } => transaction.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
false,
),
Self::Eip2930 { transaction, signature, .. } => {
transaction.encode_with_signature(signature, out, false)
}
Self::Eip1559 { transaction, signature, .. } => {
transaction.encode_with_signature(signature, out, false)
}
Self::Eip7702 { transaction, signature, .. } => {
transaction.encode_with_signature(signature, out, false)
}
Self::BlobTransaction(blob_tx) => {
// The inner encoding is used with `with_header` set to true, making the final
// encoding:
@ -502,32 +492,20 @@ impl Encodable for PooledTransactionsElement {
match self {
Self::Legacy { transaction, signature, .. } => transaction
.encode_with_signature_fields(
&signature.as_signature_with_eip155_parity(transaction.chain_id),
&with_eip155_parity(signature, transaction.chain_id),
out,
),
Self::Eip2930 { transaction, signature, .. } => {
// encodes with string header
transaction.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
true,
)
transaction.encode_with_signature(signature, out, true)
}
Self::Eip1559 { transaction, signature, .. } => {
// encodes with string header
transaction.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
true,
)
transaction.encode_with_signature(signature, out, true)
}
Self::Eip7702 { transaction, signature, .. } => {
// encodes with string header
transaction.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
out,
true,
)
transaction.encode_with_signature(signature, out, true)
}
Self::BlobTransaction(blob_tx) => {
// The inner encoding is used with `with_header` set to true, making the final
@ -542,24 +520,22 @@ impl Encodable for PooledTransactionsElement {
match self {
Self::Legacy { transaction, signature, .. } => {
// method computes the payload len with a RLP header
transaction.encoded_len_with_signature(
&signature.as_signature_with_eip155_parity(transaction.chain_id),
)
transaction.encoded_len_with_signature(&with_eip155_parity(
signature,
transaction.chain_id,
))
}
Self::Eip2930 { transaction, signature, .. } => {
// method computes the payload len with a RLP header
transaction
.encoded_len_with_signature(&signature.as_signature_with_boolean_parity(), true)
transaction.encoded_len_with_signature(signature, true)
}
Self::Eip1559 { transaction, signature, .. } => {
// method computes the payload len with a RLP header
transaction
.encoded_len_with_signature(&signature.as_signature_with_boolean_parity(), true)
transaction.encoded_len_with_signature(signature, true)
}
Self::Eip7702 { transaction, signature, .. } => {
// method computes the payload len with a RLP header
transaction
.encoded_len_with_signature(&signature.as_signature_with_boolean_parity(), true)
transaction.encoded_len_with_signature(signature, true)
}
Self::BlobTransaction(blob_tx) => {
// the encoding uses a header, so we set `with_header` to true

View File

@ -20,7 +20,7 @@ use alloc::vec::Vec;
///
/// This is defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#networking) as an element
/// of a `PooledTransactions` response.
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlobTransaction {
/// The transaction hash.
pub hash: TxHash,
@ -114,8 +114,7 @@ impl BlobTransaction {
/// Note: this should be used only when implementing other RLP encoding methods, and does not
/// represent the full RLP encoding of the blob transaction.
pub(crate) fn encode_inner(&self, out: &mut dyn bytes::BufMut) {
self.transaction
.encode_with_signature_fields(&self.signature.as_signature_with_boolean_parity(), out);
self.transaction.encode_with_signature_fields(&self.signature, out);
}
/// Outputs the length of the RLP encoding of the blob transaction, including the tx type byte,
@ -156,7 +155,7 @@ impl BlobTransaction {
// its list header.
let tx_header = Header {
list: true,
payload_length: self.transaction.tx.fields_len() + self.signature.payload_len(),
payload_length: self.transaction.tx.fields_len() + self.signature.rlp_vrs_len(),
};
let tx_length = tx_header.length() + tx_header.payload_length;
@ -212,7 +211,7 @@ impl BlobTransaction {
let transaction = TxEip4844::decode_fields(data)?;
// signature
let signature = Signature::decode(data)?;
let signature = Signature::decode_rlp_vrs(data)?;
// the inner header only decodes the transaction and signature, so we check the length here
let inner_consumed = inner_remaining_len - data.len();
@ -240,11 +239,7 @@ impl BlobTransaction {
// Instead, we use `encode_with_signature`, which RLP encodes the transaction with a
// signature for hashing without a header. We then hash the result.
let mut buf = Vec::new();
transaction.encode_with_signature(
&signature.as_signature_with_boolean_parity(),
&mut buf,
false,
);
transaction.encode_with_signature(&signature, &mut buf, false);
let hash = keccak256(&buf);
// the outer header is for the entire transaction, so we check the length here

View File

@ -1,13 +1,12 @@
use core::fmt::Debug;
use crate::{transaction::util::secp256k1, Address, B256, U256};
use alloy_consensus::EncodableSignature;
use alloy_primitives::{Bytes, Parity};
use alloy_rlp::{Decodable, Encodable, Error as RlpError};
use serde::{Deserialize, Serialize};
#[cfg(test)]
use reth_codecs::Compact;
use alloy_primitives::Parity;
use alloy_rlp::{Decodable, Error as RlpError};
pub use alloy_primitives::Signature;
#[cfg(feature = "optimism")]
use reth_optimism_chainspec::optimism_deposit_tx_signature;
/// The order of the secp256k1 curve, divided by two. Signatures that should be checked according
/// to EIP-2 should have an S value less than or equal to this.
@ -18,194 +17,82 @@ const SECP256K1N_HALF: U256 = U256::from_be_bytes([
0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46, 0x68, 0x1B, 0x20, 0xA0,
]);
/// 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.
///
/// This type is unaware of the chain id, and thus shouldn't be used when encoding or decoding
/// legacy transactions. Use `SignatureWithParity` instead.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
pub struct Signature {
/// The R field of the signature; the point on the curve.
pub r: U256,
/// The S field of the signature; the point on the curve.
pub s: U256,
/// yParity: Signature Y parity; formally Ty
///
/// WARNING: if it's deprecated in favor of `alloy_primitives::Signature` be sure that parity
/// storage deser matches.
pub odd_y_parity: bool,
}
pub(crate) fn decode_with_eip155_chain_id(
buf: &mut &[u8],
) -> alloy_rlp::Result<(Signature, Option<u64>)> {
let v: Parity = Decodable::decode(buf)?;
let r: U256 = Decodable::decode(buf)?;
let s: U256 = Decodable::decode(buf)?;
#[cfg(any(test, feature = "reth-codec"))]
impl reth_codecs::Compact for Signature {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
buf.put_slice(&self.r.as_le_bytes());
buf.put_slice(&self.s.as_le_bytes());
self.odd_y_parity as usize
#[cfg(not(feature = "optimism"))]
if matches!(v, Parity::Parity(_)) {
return Err(alloy_rlp::Error::Custom("invalid parity for legacy transaction"));
}
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
use bytes::Buf;
assert!(buf.len() >= 64);
let r = U256::from_le_slice(&buf[0..32]);
let s = U256::from_le_slice(&buf[32..64]);
buf.advance(64);
(Self { r, s, odd_y_parity: identifier != 0 }, buf)
}
}
impl Signature {
/// Decodes the `v`, `r`, `s` values without a RLP header.
/// This will return a chain ID if the `v` value is [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) compatible.
pub(crate) fn decode_with_eip155_chain_id(
buf: &mut &[u8],
) -> alloy_rlp::Result<(Self, Option<u64>)> {
let v = u64::decode(buf)?;
let r: U256 = Decodable::decode(buf)?;
let s: U256 = Decodable::decode(buf)?;
if v < 35 {
// non-EIP-155 legacy scheme, v = 27 for even y-parity, v = 28 for odd y-parity
if v != 27 && v != 28 {
#[cfg(feature = "optimism")]
// pre bedrock system transactions were sent from the zero address as legacy
// transactions with an empty signature
//
// NOTE: this is very hacky and only relevant for op-mainnet pre bedrock
if v == 0 && r.is_zero() && s.is_zero() {
return Ok((Self { r, s, odd_y_parity: false }, None))
}
}
}
let (odd_y_parity, chain_id) = extract_chain_id(v)?;
Ok((Self { r, s, odd_y_parity }, chain_id))
}
/// Output the length of the signature without the length of the RLP header
pub fn payload_len(&self) -> usize {
self.odd_y_parity.length() + self.r.length() + self.s.length()
}
/// Encode the `odd_y_parity`, `r`, `s` values without a RLP header.
pub fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.odd_y_parity.encode(out);
self.r.encode(out);
self.s.encode(out);
}
/// Decodes the `odd_y_parity`, `r`, `s` values without a RLP header.
pub fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self {
odd_y_parity: Decodable::decode(buf)?,
r: Decodable::decode(buf)?,
s: Decodable::decode(buf)?,
})
}
/// Recover signer from message hash, _without ensuring that the signature has a low `s`
/// value_.
///
/// Using this for signature validation will succeed, even if the signature is malleable or not
/// compliant with EIP-2. This is provided for compatibility with old signatures which have
/// large `s` values.
pub fn recover_signer_unchecked(&self, hash: B256) -> Option<Address> {
let mut sig: [u8; 65] = [0; 65];
sig[0..32].copy_from_slice(&self.r.to_be_bytes::<32>());
sig[32..64].copy_from_slice(&self.s.to_be_bytes::<32>());
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_signer_unchecked(&sig, &hash.0).ok()
}
/// Recover signer address from message hash. This ensures that the signature S value is
/// greater than `secp256k1n / 2`, as specified in
/// [EIP-2](https://eips.ethereum.org/EIPS/eip-2).
///
/// If the S value is too large, then this will return `None`
pub fn recover_signer(&self, hash: B256) -> Option<Address> {
if self.s > SECP256K1N_HALF {
return None
}
self.recover_signer_unchecked(hash)
}
/// Turn this signature into its byte
/// (hex) representation.
pub fn to_bytes(&self) -> [u8; 65] {
let mut sig = [0u8; 65];
sig[..32].copy_from_slice(&self.r.to_be_bytes::<32>());
sig[32..64].copy_from_slice(&self.s.to_be_bytes::<32>());
let v = u8::from(self.odd_y_parity) + 27;
sig[64] = v;
sig
}
/// Turn this signature into its hex-encoded representation.
pub fn to_hex_bytes(&self) -> Bytes {
self.to_bytes().into()
}
/// Calculates a heuristic for the in-memory size of the [Signature].
#[inline]
pub const fn size(&self) -> usize {
core::mem::size_of::<Self>()
}
/// Returns [Parity] value based on `chain_id` for legacy transaction signature.
#[allow(clippy::missing_const_for_fn)]
pub fn legacy_parity(&self, chain_id: Option<u64>) -> Parity {
if let Some(chain_id) = chain_id {
Parity::Parity(self.odd_y_parity).with_chain_id(chain_id)
} else {
#[cfg(feature = "optimism")]
// pre bedrock system transactions were sent from the zero address as legacy
// transactions with an empty signature
//
// NOTE: this is very hacky and only relevant for op-mainnet pre bedrock
if *self == Self::optimism_deposit_tx_signature() {
return Parity::Parity(false)
}
Parity::NonEip155(self.odd_y_parity)
}
}
/// Returns a signature with the given chain ID applied to the `v` value.
pub(crate) fn as_signature_with_eip155_parity(
&self,
chain_id: Option<u64>,
) -> SignatureWithParity {
SignatureWithParity::new(self.r, self.s, self.legacy_parity(chain_id))
}
/// Returns a signature with a boolean parity flag. This is useful when we want to encode
/// the `v` value as 0 or 1.
pub(crate) const fn as_signature_with_boolean_parity(&self) -> SignatureWithParity {
SignatureWithParity::new(self.r, self.s, Parity::Parity(self.odd_y_parity))
}
/// Returns the signature for the optimism deposit transactions, which don't include a
/// signature.
#[cfg(feature = "optimism")]
pub const fn optimism_deposit_tx_signature() -> Self {
Self { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false }
// pre bedrock system transactions were sent from the zero address as legacy
// transactions with an empty signature
//
// NOTE: this is very hacky and only relevant for op-mainnet pre bedrock
if matches!(v, Parity::Parity(false)) && r.is_zero() && s.is_zero() {
return Ok((Signature::new(r, s, Parity::Parity(false)), None))
}
Ok((Signature::new(r, s, v), v.chain_id()))
}
/// Recover signer from message hash, _without ensuring that the signature has a low `s`
/// value_.
///
/// Using this for signature validation will succeed, even if the signature is malleable or not
/// compliant with EIP-2. This is provided for compatibility with old signatures which have
/// large `s` values.
pub fn recover_signer_unchecked(signature: &Signature, hash: B256) -> Option<Address> {
let mut sig: [u8; 65] = [0; 65];
sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
sig[64] = signature.v().y_parity_byte();
// 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_signer_unchecked(&sig, &hash.0).ok()
}
/// Recover signer address from message hash. This ensures that the signature S value is
/// greater than `secp256k1n / 2`, as specified in
/// [EIP-2](https://eips.ethereum.org/EIPS/eip-2).
///
/// If the S value is too large, then this will return `None`
pub fn recover_signer(signature: &Signature, hash: B256) -> Option<Address> {
if signature.s() > SECP256K1N_HALF {
return None
}
recover_signer_unchecked(signature, hash)
}
/// Returns [Parity] value based on `chain_id` for legacy transaction signature.
#[allow(clippy::missing_const_for_fn)]
pub fn legacy_parity(signature: &Signature, chain_id: Option<u64>) -> Parity {
if let Some(chain_id) = chain_id {
Parity::Parity(signature.v().y_parity()).with_chain_id(chain_id)
} else {
#[cfg(feature = "optimism")]
// pre bedrock system transactions were sent from the zero address as legacy
// transactions with an empty signature
//
// NOTE: this is very hacky and only relevant for op-mainnet pre bedrock
if *signature == optimism_deposit_tx_signature() {
return Parity::Parity(false)
}
Parity::NonEip155(signature.v().y_parity())
}
}
impl From<alloy_primitives::Signature> for Signature {
fn from(value: alloy_primitives::Signature) -> Self {
Self { r: value.r(), s: value.s(), odd_y_parity: value.v().y_parity() }
}
/// Returns a signature with the given chain ID applied to the `v` value.
pub(crate) fn with_eip155_parity(signature: &Signature, chain_id: Option<u64>) -> Signature {
Signature::new(signature.r(), signature.s(), legacy_parity(signature, chain_id))
}
/// Outputs (`odd_y_parity`, `chain_id`) from the `v` value.
@ -226,144 +113,51 @@ pub const fn extract_chain_id(v: u64) -> alloy_rlp::Result<(bool, Option<u64>)>
}
}
/// A signature with full parity included.
// TODO: replace by alloy Signature when there will be an easy way to instantiate them.
pub(crate) struct SignatureWithParity {
/// The R field of the signature; the point on the curve.
r: U256,
/// The S field of the signature; the point on the curve.
s: U256,
/// Signature parity
parity: Parity,
}
impl SignatureWithParity {
/// Creates a new [`SignatureWithParity`].
pub(crate) const fn new(r: U256, s: U256, parity: Parity) -> Self {
Self { r, s, parity }
}
}
impl EncodableSignature for SignatureWithParity {
fn from_rs_and_parity<
P: TryInto<Parity, Error = E>,
E: Into<alloy_primitives::SignatureError>,
>(
r: U256,
s: U256,
parity: P,
) -> Result<Self, alloy_primitives::SignatureError> {
Ok(Self { r, s, parity: parity.try_into().map_err(Into::into)? })
}
fn r(&self) -> U256 {
self.r
}
fn s(&self) -> U256 {
self.s
}
fn v(&self) -> Parity {
self.parity
}
fn with_parity<T: Into<Parity>>(self, parity: T) -> Self {
Self { r: self.r, s: self.s, parity: parity.into() }
}
}
#[cfg(test)]
mod tests {
use crate::{hex, transaction::signature::SECP256K1N_HALF, Address, Signature, B256, U256};
use alloy_primitives::{hex::FromHex, Bytes, Parity};
use crate::{
hex,
transaction::signature::{
legacy_parity, recover_signer, recover_signer_unchecked, SECP256K1N_HALF,
},
Address, Signature, B256, U256,
};
use alloy_primitives::Parity;
use std::str::FromStr;
#[test]
fn test_legacy_parity() {
// Select 1 as an arbitrary nonzero value for R and S, as v() always returns 0 for (0, 0).
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
assert_eq!(Parity::NonEip155(false), signature.legacy_parity(None));
assert_eq!(Parity::Eip155(37), signature.legacy_parity(Some(1)));
let signature = Signature::new(U256::from(1), U256::from(1), Parity::Parity(false));
assert_eq!(Parity::NonEip155(false), legacy_parity(&signature, None));
assert_eq!(Parity::Eip155(37), legacy_parity(&signature, Some(1)));
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: true };
assert_eq!(Parity::NonEip155(true), signature.legacy_parity(None));
assert_eq!(Parity::Eip155(38), signature.legacy_parity(Some(1)));
}
#[test]
fn test_payload_len() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
assert_eq!(3, signature.payload_len());
}
#[test]
fn test_encode_and_decode() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
let mut encoded = Vec::new();
signature.encode(&mut encoded);
assert_eq!(encoded.len(), signature.payload_len());
let decoded = Signature::decode(&mut &*encoded).unwrap();
assert_eq!(signature, decoded);
let signature = Signature::new(U256::from(1), U256::from(1), Parity::Parity(true));
assert_eq!(Parity::NonEip155(true), legacy_parity(&signature, None));
assert_eq!(Parity::Eip155(38), legacy_parity(&signature, Some(1)));
}
#[test]
fn test_recover_signer() {
let signature = Signature {
r: U256::from_str(
let signature = Signature::new(
U256::from_str(
"18515461264373351373200002665853028612451056578545711640558177340181847433846",
)
.unwrap(),
s: U256::from_str(
U256::from_str(
"46948507304638947509940763649030358759909902576025900602547168820602576006531",
)
.unwrap(),
odd_y_parity: false,
};
Parity::Parity(false),
);
let hash =
B256::from_str("daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53")
.unwrap();
let signer = signature.recover_signer(hash).unwrap();
let signer = recover_signer(&signature, hash).unwrap();
let expected = Address::from_str("0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f").unwrap();
assert_eq!(expected, signer);
}
#[test]
fn ensure_size_equals_sum_of_fields() {
let signature = Signature {
r: U256::from_str(
"18515461264373351373200002665853028612451056578545711640558177340181847433846",
)
.unwrap(),
s: U256::from_str(
"46948507304638947509940763649030358759909902576025900602547168820602576006531",
)
.unwrap(),
odd_y_parity: false,
};
assert!(signature.size() >= 65);
}
#[test]
fn test_to_hex_bytes() {
let signature = Signature {
r: U256::from_str(
"18515461264373351373200002665853028612451056578545711640558177340181847433846",
)
.unwrap(),
s: U256::from_str(
"46948507304638947509940763649030358759909902576025900602547168820602576006531",
)
.unwrap(),
odd_y_parity: false,
};
let expected = Bytes::from_hex("0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa63627667cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d831b").unwrap();
assert_eq!(signature.to_hex_bytes(), expected);
}
#[test]
fn eip_2_reject_high_s_value() {
// This pre-homestead transaction has a high `s` value and should be rejected by the
@ -376,13 +170,13 @@ mod tests {
let signature = tx.signature();
// make sure we know it's greater than SECP256K1N_HALF
assert!(signature.s > SECP256K1N_HALF);
assert!(signature.s() > SECP256K1N_HALF);
// recover signer, expect failure
let hash = tx.hash();
assert!(signature.recover_signer(hash).is_none());
assert!(recover_signer(signature, hash).is_none());
// use unchecked, ensure it succeeds (the signature is valid if not for EIP-2)
assert!(signature.recover_signer_unchecked(hash).is_some());
assert!(recover_signer_unchecked(signature, hash).is_some());
}
}

View File

@ -20,6 +20,7 @@ mod impl_secp256k1 {
ecdsa::{RecoverableSignature, RecoveryId},
Message, PublicKey, SecretKey, SECP256K1,
};
use alloy_primitives::Parity;
use revm_primitives::U256;
/// Recovers the address of the sender using secp256k1 pubkey recovery.
@ -43,11 +44,11 @@ mod impl_secp256k1 {
let s = SECP256K1.sign_ecdsa_recoverable(&Message::from_digest(message.0), &sec);
let (rec_id, data) = s.serialize_compact();
let signature = Signature {
r: U256::try_from_be_slice(&data[..32]).expect("The slice has at most 32 bytes"),
s: U256::try_from_be_slice(&data[32..64]).expect("The slice has at most 32 bytes"),
odd_y_parity: rec_id.to_i32() != 0,
};
let signature = Signature::new(
U256::try_from_be_slice(&data[..32]).expect("The slice has at most 32 bytes"),
U256::try_from_be_slice(&data[32..64]).expect("The slice has at most 32 bytes"),
Parity::Parity(rec_id.to_i32() != 0),
);
Ok(signature)
}
@ -65,6 +66,7 @@ mod impl_secp256k1 {
mod impl_k256 {
use super::*;
use crate::keccak256;
use alloy_primitives::Parity;
pub(crate) use k256::ecdsa::Error;
use k256::ecdsa::{RecoveryId, SigningKey, VerifyingKey};
use revm_primitives::U256;
@ -98,11 +100,11 @@ mod impl_k256 {
let (sig, rec_id) = sec.sign_prehash_recoverable(&message.0)?;
let (r, s) = sig.split_bytes();
let signature = Signature {
r: U256::try_from_be_slice(&r).expect("The slice has at most 32 bytes"),
s: U256::try_from_be_slice(&s).expect("The slice has at most 32 bytes"),
odd_y_parity: rec_id.is_y_odd(),
};
let signature = Signature::new(
U256::try_from_be_slice(&r).expect("The slice has at most 32 bytes"),
U256::try_from_be_slice(&s).expect("The slice has at most 32 bytes"),
Parity::Parity(rec_id.is_y_odd()),
);
Ok(signature)
}
@ -131,9 +133,9 @@ mod tests {
sign_message(B256::from_slice(&secret.secret_bytes()[..]), hash).expect("sign message");
let mut sig: [u8; 65] = [0; 65];
sig[0..32].copy_from_slice(&signature.r.to_be_bytes::<32>());
sig[32..64].copy_from_slice(&signature.s.to_be_bytes::<32>());
sig[64] = signature.odd_y_parity as u8;
sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
sig[64] = signature.v().y_parity_byte();
assert_eq!(recover_signer_unchecked(&sig, &hash), Ok(signer));
}
@ -191,16 +193,16 @@ mod tests {
let mut sig: [u8; 65] = [0; 65];
sig[0..32].copy_from_slice(&secp256k1_signature.r.to_be_bytes::<32>());
sig[32..64].copy_from_slice(&secp256k1_signature.s.to_be_bytes::<32>());
sig[64] = secp256k1_signature.odd_y_parity as u8;
sig[0..32].copy_from_slice(&secp256k1_signature.r().to_be_bytes::<32>());
sig[32..64].copy_from_slice(&secp256k1_signature.s().to_be_bytes::<32>());
sig[64] = secp256k1_signature.v().y_parity_byte();
let secp256k1_recovered =
impl_secp256k1::recover_signer_unchecked(&sig, &hash).expect("secp256k1 recover");
assert_eq!(secp256k1_recovered, secp256k1_signer);
sig[0..32].copy_from_slice(&k256_signature.r.to_be_bytes::<32>());
sig[32..64].copy_from_slice(&k256_signature.s.to_be_bytes::<32>());
sig[64] = k256_signature.odd_y_parity as u8;
sig[0..32].copy_from_slice(&k256_signature.r().to_be_bytes::<32>());
sig[32..64].copy_from_slice(&k256_signature.s().to_be_bytes::<32>());
sig[64] = k256_signature.v().y_parity_byte();
let k256_recovered =
impl_k256::recover_signer_unchecked(&sig, &hash).expect("k256 recover");
assert_eq!(k256_recovered, k256_signer);