reth_primitives/transaction/
signature.rs

1use crate::transaction::util::secp256k1;
2use alloy_consensus::transaction::from_eip155_value;
3use alloy_primitives::{Address, PrimitiveSignature as Signature, B256, U256};
4use alloy_rlp::Decodable;
5use reth_primitives_traits::crypto::SECP256K1N_HALF;
6
7pub(crate) fn decode_with_eip155_chain_id(
8    buf: &mut &[u8],
9) -> alloy_rlp::Result<(Signature, Option<u64>)> {
10    let v = Decodable::decode(buf)?;
11    let r: U256 = Decodable::decode(buf)?;
12    let s: U256 = Decodable::decode(buf)?;
13
14    let Some((parity, chain_id)) = from_eip155_value(v) else {
15        // pre bedrock system transactions were sent from the zero address as legacy
16        // transactions with an empty signature
17        //
18        // NOTE: this is very hacky and only relevant for op-mainnet pre bedrock
19        #[cfg(feature = "optimism")]
20        if v == 0 && r.is_zero() && s.is_zero() {
21            return Ok((Signature::new(r, s, false), None))
22        }
23        return Err(alloy_rlp::Error::Custom("invalid parity for legacy transaction"))
24    };
25
26    Ok((Signature::new(r, s, parity), chain_id))
27}
28
29/// Recover signer from message hash, _without ensuring that the signature has a low `s`
30/// value_.
31///
32/// Using this for signature validation will succeed, even if the signature is malleable or not
33/// compliant with EIP-2. This is provided for compatibility with old signatures which have
34/// large `s` values.
35pub fn recover_signer_unchecked(signature: &Signature, hash: B256) -> Option<Address> {
36    let mut sig: [u8; 65] = [0; 65];
37
38    sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
39    sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
40    sig[64] = signature.v() as u8;
41
42    // NOTE: we are removing error from underlying crypto library as it will restrain primitive
43    // errors and we care only if recovery is passing or not.
44    secp256k1::recover_signer_unchecked(&sig, &hash.0).ok()
45}
46
47/// Recover signer address from message hash. This ensures that the signature S value is
48/// greater than `secp256k1n / 2`, as specified in
49/// [EIP-2](https://eips.ethereum.org/EIPS/eip-2).
50///
51/// If the S value is too large, then this will return `None`
52pub fn recover_signer(signature: &Signature, hash: B256) -> Option<Address> {
53    if signature.s() > SECP256K1N_HALF {
54        return None
55    }
56
57    recover_signer_unchecked(signature, hash)
58}
59
60#[cfg(test)]
61mod tests {
62    use crate::transaction::signature::{
63        recover_signer, recover_signer_unchecked, SECP256K1N_HALF,
64    };
65    use alloy_eips::eip2718::Decodable2718;
66    use alloy_primitives::{hex, Address, PrimitiveSignature as Signature, B256, U256};
67    use reth_primitives_traits::SignedTransaction;
68    use std::str::FromStr;
69
70    #[test]
71    fn test_recover_signer() {
72        let signature = Signature::new(
73            U256::from_str(
74                "18515461264373351373200002665853028612451056578545711640558177340181847433846",
75            )
76            .unwrap(),
77            U256::from_str(
78                "46948507304638947509940763649030358759909902576025900602547168820602576006531",
79            )
80            .unwrap(),
81            false,
82        );
83        let hash =
84            B256::from_str("daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53")
85                .unwrap();
86        let signer = recover_signer(&signature, hash).unwrap();
87        let expected = Address::from_str("0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f").unwrap();
88        assert_eq!(expected, signer);
89    }
90
91    #[test]
92    fn eip_2_reject_high_s_value() {
93        // This pre-homestead transaction has a high `s` value and should be rejected by the
94        // `recover_signer` method:
95        // https://etherscan.io/getRawTx?tx=0x9e6e19637bb625a8ff3d052b7c2fe57dc78c55a15d258d77c43d5a9c160b0384
96        //
97        // Block number: 46170
98        let raw_tx = hex!("f86d8085746a52880082520894c93f2250589a6563f5359051c1ea25746549f0d889208686e75e903bc000801ba034b6fdc33ea520e8123cf5ac4a9ff476f639cab68980cd9366ccae7aef437ea0a0e517caa5f50e27ca0d1e9a92c503b4ccb039680c6d9d0c71203ed611ea4feb33");
99        let tx = crate::transaction::TransactionSigned::decode_2718(&mut &raw_tx[..]).unwrap();
100        let signature = tx.signature();
101
102        // make sure we know it's greater than SECP256K1N_HALF
103        assert!(signature.s() > SECP256K1N_HALF);
104
105        // recover signer, expect failure
106        let hash = tx.hash();
107        assert!(recover_signer(signature, hash).is_none());
108
109        // use unchecked, ensure it succeeds (the signature is valid if not for EIP-2)
110        assert!(recover_signer_unchecked(signature, hash).is_some());
111    }
112}