Skip to main content

r402_evm/
signature.rs

1//! Shared EOA / EIP-1271 / EIP-6492 signature parsing and payment time windows.
2
3use alloy_primitives::{Address, B256, Bytes, Signature, hex};
4use alloy_sol_types::SolType;
5use r402_core::error::VerificationError;
6use r402_core::wire::UnixTimestamp;
7#[cfg(feature = "telemetry")]
8use tracing::instrument;
9
10use crate::chain::contracts::Sig6492;
11
12/// The fixed 32-byte magic suffix defined by [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492).
13const EIP6492_MAGIC_SUFFIX: [u8; 32] =
14    hex!("6492649264926492649264926492649264926492649264926492649264926492");
15
16/// A structured representation of an Ethereum signature.
17///
18/// This enum normalizes two supported cases:
19/// - **EIP-6492 wrapped signatures**: used for counterfactual contract wallets.
20/// - **EIP-1271 signatures**: plain contract (or EOA-style) signatures.
21#[derive(Debug, Clone)]
22pub(crate) enum StructuredSignature {
23    /// An EIP-6492 wrapped signature.
24    EIP6492 {
25        factory: Address,
26        factory_calldata: Bytes,
27        inner: Bytes,
28        original: Bytes,
29    },
30    /// Normalized EOA signature.
31    Eoa(Signature),
32    /// A plain EIP-1271 or EOA signature (no 6492 wrappers).
33    EIP1271(Bytes),
34}
35
36/// Errors from parsing a structured signature.
37#[derive(Debug, thiserror::Error)]
38pub enum StructuredSignatureFormatError {
39    /// The EIP-6492 wrapper could not be decoded.
40    #[error(transparent)]
41    InvalidEIP6492Format(alloy_sol_types::Error),
42}
43
44/// Decodes the EIP-6492 wrapper from raw signature bytes.
45///
46/// Returns `Some(EIP6492 { .. })` if the bytes end with the 32-byte magic
47/// suffix, or `None` if no wrapper is present.
48#[allow(
49    clippy::indexing_slicing,
50    reason = "bounds checked by len() >= 32 guard"
51)]
52fn decode_eip6492(
53    bytes: Bytes,
54) -> Result<Option<StructuredSignature>, StructuredSignatureFormatError> {
55    let has_suffix = bytes.len() >= 32 && bytes[bytes.len() - 32..] == EIP6492_MAGIC_SUFFIX;
56    if !has_suffix {
57        return Ok(None);
58    }
59    let body = &bytes[..bytes.len() - 32];
60    let sig6492 = Sig6492::abi_decode_params(body)
61        .map_err(StructuredSignatureFormatError::InvalidEIP6492Format)?;
62    Ok(Some(StructuredSignature::EIP6492 {
63        factory: sig6492.factory,
64        factory_calldata: sig6492.factoryCalldata,
65        inner: sig6492.innerSig,
66        original: bytes,
67    }))
68}
69
70impl StructuredSignature {
71    pub(crate) fn try_from_bytes(
72        bytes: Bytes,
73        expected_signer: Address,
74        prehash: &B256,
75    ) -> Result<Self, StructuredSignatureFormatError> {
76        if let Some(eip6492) = decode_eip6492(bytes.clone())? {
77            return Ok(eip6492);
78        }
79        let eoa_signature = if bytes.len() == 65 {
80            Signature::from_raw(&bytes)
81                .ok()
82                .map(Signature::normalized_s)
83        } else if bytes.len() == 64 {
84            Some(Signature::from_erc2098(&bytes).normalized_s())
85        } else {
86            None
87        };
88        let signature = match eoa_signature {
89            None => Self::EIP1271(bytes),
90            Some(s) => {
91                let is_expected_signer = s
92                    .recover_address_from_prehash(prehash)
93                    .is_ok_and(|r| r == expected_signer);
94                if is_expected_signer {
95                    Self::Eoa(s)
96                } else {
97                    Self::EIP1271(bytes)
98                }
99            }
100        };
101        Ok(signature)
102    }
103}
104
105/// Checks that `now` sits inside `[valid_after, valid_before)` with skew.
106///
107/// Applies `clock_skew_tolerance` seconds of grace when checking both expiration
108/// and early-arrival to account for clock drift between nodes.
109///
110/// # Errors
111///
112/// Returns [`VerificationError::Expired`] or [`VerificationError::Early`].
113#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
114pub(crate) fn assert_time(
115    valid_after: UnixTimestamp,
116    valid_before: UnixTimestamp,
117    clock_skew_tolerance: u64,
118) -> Result<(), VerificationError> {
119    let now = UnixTimestamp::now();
120    if valid_before < now + clock_skew_tolerance {
121        return Err(VerificationError::Expired);
122    }
123    if valid_after > now + clock_skew_tolerance {
124        return Err(VerificationError::Early);
125    }
126    Ok(())
127}