diff --git a/crates/common/types/transaction.rs b/crates/common/types/transaction.rs index 0857755357e..e71ff361ad4 100644 --- a/crates/common/types/transaction.rs +++ b/crates/common/types/transaction.rs @@ -1891,23 +1891,27 @@ mod serde_impl { chain_id: value.chain_id, address: value.address, nonce: value.nonce, - y_parity: value.y_parity, + y_parity: U256::from(value.y_parity), r: value.r_signature, s: value.s_signature, } } } - impl From for AuthorizationTuple { - fn from(entry: AuthorizationTupleEntry) -> AuthorizationTuple { - AuthorizationTuple { + impl TryFrom for AuthorizationTuple { + type Error = String; + + // EIP-7702 bounds y_parity to < 2**8; reject anything that doesn't fit a u8. + fn try_from(entry: AuthorizationTupleEntry) -> Result { + Ok(AuthorizationTuple { chain_id: entry.chain_id, address: entry.address, nonce: entry.nonce, - y_parity: entry.y_parity, + y_parity: TryInto::::try_into(entry.y_parity) + .map_err(|_| "authorization tuple y_parity exceeds 2**8".to_string())?, r_signature: entry.r, s_signature: entry.s, - } + }) } } @@ -2448,8 +2452,9 @@ mod serde_impl { "authorizationList", )? .into_iter() - .map(AuthorizationTuple::from) - .collect::>(), + .map(AuthorizationTuple::try_from) + .collect::, _>>() + .map_err(serde::de::Error::custom)?, signature_y_parity: u8::from_str_radix( deserialize_field::(&mut map, "yParity")?.trim_start_matches("0x"), 16, @@ -2833,8 +2838,9 @@ mod serde_impl { .authorization_list .unwrap_or_default() .into_iter() - .map(AuthorizationTuple::from) - .collect(), + .map(AuthorizationTuple::try_from) + .collect::, _>>() + .map_err(GenericTransactionError::InvalidField)?, ..Default::default() }) } @@ -3546,7 +3552,7 @@ mod tests { chain_id: U256::from(65536999), address: H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B1").unwrap(), nonce: 2, - y_parity: U256::one(), + y_parity: 1, r_signature: U256::from(22), s_signature: U256::from(37), }], diff --git a/crates/common/types/tx_fields.rs b/crates/common/types/tx_fields.rs index a73840e4090..582d52ebe85 100644 --- a/crates/common/types/tx_fields.rs +++ b/crates/common/types/tx_fields.rs @@ -41,8 +41,9 @@ pub struct AuthorizationTuple { deserialize_with = "crate::serde_utils::u64::deser_hex_or_dec_str" )] pub nonce: u64, - #[rkyv(with = crate::rkyv_utils::U256Wrapper)] - pub y_parity: U256, + // EIP-7702 bounds y_parity to < 2**8; a u8 makes that a type invariant and lets + // RLP decoding reject any out-of-range value, matching geth's `uint8`. + pub y_parity: u8, #[serde(rename = "r")] #[rkyv(with = crate::rkyv_utils::U256Wrapper)] pub r_signature: U256, @@ -87,3 +88,36 @@ impl RLPDecode for AuthorizationTuple { )) } } + +#[cfg(test)] +mod eip7702_y_parity_tests { + //! Regression test for the `eip7702-auth-y-parity-bound` finding. EIP-7702 bounds + //! an authorization tuple's `y_parity` to `< 2**8`; geth models it as a `u8` and + //! rejects an out-of-range value at RLP decode. ethrex must do the same, otherwise + //! an L1 block carrying a type-4 transaction whose `y_parity >= 256` is accepted + //! here but rejected by other clients (consensus split). The authorization-tuple + //! decode is the chokepoint: a type-4 transaction decodes its `authorization_list` + //! through it. + use super::*; + + #[test] + fn authorization_tuple_rejects_y_parity_at_or_above_256() { + // Hand-build the RLP of an authorization tuple whose `y_parity` field encodes + // 256 (two bytes, 0x01 0x00), independent of the field's Rust type. + let mut buf = Vec::new(); + Encoder::new(&mut buf) + .encode_field(&U256::zero()) // chain_id + .encode_field(&Address::zero()) // address + .encode_field(&0u64) // nonce + .encode_field(&U256::from(256u64)) // y_parity = 256, out of the < 2**8 bound + .encode_field(&U256::one()) // r_signature + .encode_field(&U256::one()) // s_signature + .finish(); + + let result = AuthorizationTuple::decode(&buf); + assert!( + result.is_err(), + "authorization tuple with y_parity >= 2**8 must be rejected at decode, got: {result:?}" + ); + } +} diff --git a/crates/vm/backends/levm/mod.rs b/crates/vm/backends/levm/mod.rs index 6fc2a7079a4..38f43ca80e7 100644 --- a/crates/vm/backends/levm/mod.rs +++ b/crates/vm/backends/levm/mod.rs @@ -2791,8 +2791,9 @@ fn vm_from_generic<'a>( .collect(), authorization_list: authorization_list .iter() - .map(|auth| Into::::into(auth.clone())) - .collect(), + .map(|auth| AuthorizationTuple::try_from(auth.clone())) + .collect::, _>>() + .map_err(|_| InternalError::TypeConversion)?, ..Default::default() }), None => Transaction::EIP1559Transaction(EIP1559Transaction { diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index ce531f23b3b..4b93f4134e0 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -215,7 +215,7 @@ pub fn eip7702_recover_address( if auth_tuple.r_signature > *SECP256K1_ORDER || U256::zero() >= auth_tuple.r_signature { return Ok(None); } - if auth_tuple.y_parity != U256::one() && auth_tuple.y_parity != U256::zero() { + if auth_tuple.y_parity != 1 && auth_tuple.y_parity != 0 { return Ok(None); } @@ -224,8 +224,7 @@ pub fn eip7702_recover_address( (auth_tuple.chain_id, auth_tuple.address, auth_tuple.nonce).encode(&mut rlp_buf); let msg = crypto.keccak256(&rlp_buf); - let y_parity: u8 = - TryInto::::try_into(auth_tuple.y_parity).map_err(|_| InternalError::TypeConversion)?; + let y_parity: u8 = auth_tuple.y_parity; let mut sig = [0u8; 65]; sig[..32].copy_from_slice(&auth_tuple.r_signature.to_big_endian()); diff --git a/test/tests/blockchain/eip7702_revert_authority_tests.rs b/test/tests/blockchain/eip7702_revert_authority_tests.rs index db83ba619b7..7f026d0cf5e 100644 --- a/test/tests/blockchain/eip7702_revert_authority_tests.rs +++ b/test/tests/blockchain/eip7702_revert_authority_tests.rs @@ -126,7 +126,7 @@ fn sign_auth_tuple( let r = U256::from_big_endian(&sig[..32]); let s = U256::from_big_endian(&sig[32..64]); - let y_parity = U256::from(Into::::into(recovery_id) as u64); + let y_parity = Into::::into(recovery_id) as u8; AuthorizationTuple { chain_id: U256::from(chain_id), diff --git a/test/tests/blockchain/eip7702_zero_transfer_tests.rs b/test/tests/blockchain/eip7702_zero_transfer_tests.rs index fcd3bb34064..86e6881d819 100644 --- a/test/tests/blockchain/eip7702_zero_transfer_tests.rs +++ b/test/tests/blockchain/eip7702_zero_transfer_tests.rs @@ -95,7 +95,7 @@ fn sign_auth_tuple( let r = U256::from_big_endian(&sig[..32]); let s = U256::from_big_endian(&sig[32..64]); - let y_parity = U256::from(Into::::into(recovery_id) as u64); + let y_parity = Into::::into(recovery_id) as u8; AuthorizationTuple { chain_id: U256::from(chain_id), diff --git a/test/tests/levm/eip8037_tests.rs b/test/tests/levm/eip8037_tests.rs index 39d34277310..0af96dcee3f 100644 --- a/test/tests/levm/eip8037_tests.rs +++ b/test/tests/levm/eip8037_tests.rs @@ -199,7 +199,7 @@ fn test_intrinsic_parity_eip7702_auth_list() { chain_id: U256::from(1), address: Address::from_low_u64_be(0xAA), nonce: 0, - y_parity: U256::zero(), + y_parity: 0, r_signature: U256::from(1), s_signature: U256::from(1), }; diff --git a/tooling/ef_tests/blockchain/types.rs b/tooling/ef_tests/blockchain/types.rs index eacf14c0db9..437f876424b 100644 --- a/tooling/ef_tests/blockchain/types.rs +++ b/tooling/ef_tests/blockchain/types.rs @@ -553,7 +553,7 @@ impl From for EIP7702Transaction { chain_id: a.chain_id, address: a.address, nonce: a.nonce.try_into().unwrap(), - y_parity: a.v, + y_parity: a.v.try_into().unwrap_or(u8::MAX), r_signature: a.r, s_signature: a.s, }) diff --git a/tooling/ef_tests/state/runner/levm_runner.rs b/tooling/ef_tests/state/runner/levm_runner.rs index 20c4d153c40..a9b6b038b75 100644 --- a/tooling/ef_tests/state/runner/levm_runner.rs +++ b/tooling/ef_tests/state/runner/levm_runner.rs @@ -159,7 +159,7 @@ pub fn prepare_vm_for_tx<'a>( chain_id: auth_tuple.chain_id, address: auth_tuple.address, nonce: auth_tuple.nonce, - y_parity: auth_tuple.v, + y_parity: auth_tuple.v.try_into().unwrap_or(u8::MAX), r_signature: auth_tuple.r, s_signature: auth_tuple.s, }) diff --git a/tooling/ef_tests/state_v2/src/modules/types.rs b/tooling/ef_tests/state_v2/src/modules/types.rs index c9f7b217840..b5ece9b6ecd 100644 --- a/tooling/ef_tests/state_v2/src/modules/types.rs +++ b/tooling/ef_tests/state_v2/src/modules/types.rs @@ -509,7 +509,7 @@ impl AuthorizationListTuple { chain_id: self.chain_id, address: self.address, nonce: self.nonce, - y_parity: self.v, + y_parity: self.v.try_into().unwrap_or(u8::MAX), r_signature: self.r, s_signature: self.s, } diff --git a/tooling/migrations/src/utils.rs b/tooling/migrations/src/utils.rs index 389a49412ae..158b683e2d3 100644 --- a/tooling/migrations/src/utils.rs +++ b/tooling/migrations/src/utils.rs @@ -161,7 +161,7 @@ pub fn migrate_transaction(tx: LibmdbxTransaction) -> Transaction { chain_id: auth.chain_id, address: auth.address, nonce: auth.nonce, - y_parity: auth.y_parity, + y_parity: auth.y_parity.try_into().unwrap_or(u8::MAX), r_signature: auth.r_signature, s_signature: auth.s_signature, })