reth_primitives_traits/transaction/
signed.rs

1//! API of a signed transaction.
2
3use crate::{
4    crypto::secp256k1::{recover_signer, recover_signer_unchecked},
5    InMemorySize, MaybeCompact, MaybeSerde, MaybeSerdeBincodeCompat,
6};
7use alloc::{fmt, vec::Vec};
8use alloy_consensus::{
9    transaction::{Recovered, RlpEcdsaEncodableTx},
10    EthereumTxEnvelope, SignableTransaction,
11};
12use alloy_eips::eip2718::{Decodable2718, Encodable2718};
13use alloy_primitives::{keccak256, Address, Signature, TxHash, B256};
14use alloy_rlp::{Decodable, Encodable};
15use core::hash::Hash;
16
17/// Helper trait that unifies all behaviour required by block to support full node operations.
18pub trait FullSignedTx: SignedTransaction + MaybeCompact + MaybeSerdeBincodeCompat {}
19impl<T> FullSignedTx for T where T: SignedTransaction + MaybeCompact + MaybeSerdeBincodeCompat {}
20
21/// A signed transaction.
22#[auto_impl::auto_impl(&, Arc)]
23pub trait SignedTransaction:
24    Send
25    + Sync
26    + Unpin
27    + Clone
28    + fmt::Debug
29    + PartialEq
30    + Eq
31    + Hash
32    + Encodable
33    + Decodable
34    + Encodable2718
35    + Decodable2718
36    + alloy_consensus::Transaction
37    + MaybeSerde
38    + InMemorySize
39{
40    /// Returns reference to transaction hash.
41    fn tx_hash(&self) -> &TxHash;
42
43    /// Returns whether this transaction type can be __broadcasted__ as full transaction over the
44    /// network.
45    ///
46    /// Some transactions are not broadcastable as objects and only allowed to be broadcasted as
47    /// hashes, e.g. because they missing context (e.g. blob sidecar).
48    fn is_broadcastable_in_full(&self) -> bool {
49        // EIP-4844 transactions are not broadcastable in full, only hashes are allowed.
50        !self.is_eip4844()
51    }
52
53    /// Recover signer from signature and hash.
54    ///
55    /// Returns `RecoveryError` if the transaction's signature is invalid following [EIP-2](https://eips.ethereum.org/EIPS/eip-2), see also `reth_primitive_traits::crypto::secp256k1::recover_signer`.
56    ///
57    /// Note:
58    ///
59    /// This can fail for some early ethereum mainnet transactions pre EIP-2, use
60    /// [`Self::recover_signer_unchecked`] if you want to recover the signer without ensuring that
61    /// the signature has a low `s` value.
62    fn recover_signer(&self) -> Result<Address, RecoveryError>;
63
64    /// Recover signer from signature and hash.
65    ///
66    /// Returns an error if the transaction's signature is invalid.
67    fn try_recover(&self) -> Result<Address, RecoveryError> {
68        self.recover_signer()
69    }
70
71    /// Recover signer from signature and hash _without ensuring that the signature has a low `s`
72    /// value_.
73    ///
74    /// Returns `RecoveryError` if the transaction's signature is invalid, see also
75    /// `reth_primitive_traits::crypto::secp256k1::recover_signer_unchecked`.
76    fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
77        self.recover_signer_unchecked_with_buf(&mut Vec::new())
78    }
79
80    /// Recover signer from signature and hash _without ensuring that the signature has a low `s`
81    /// value_.
82    ///
83    /// Returns an error if the transaction's signature is invalid.
84    fn try_recover_unchecked(&self) -> Result<Address, RecoveryError> {
85        self.recover_signer_unchecked()
86    }
87
88    /// Same as [`Self::recover_signer_unchecked`] but receives a buffer to operate on. This is used
89    /// during batch recovery to avoid allocating a new buffer for each transaction.
90    fn recover_signer_unchecked_with_buf(
91        &self,
92        buf: &mut Vec<u8>,
93    ) -> Result<Address, RecoveryError>;
94
95    /// Calculate transaction hash, eip2728 transaction does not contain rlp header and start with
96    /// tx type.
97    fn recalculate_hash(&self) -> B256 {
98        keccak256(self.encoded_2718())
99    }
100
101    /// Tries to recover signer and return [`Recovered`] by cloning the type.
102    #[auto_impl(keep_default_for(&, Arc))]
103    fn try_clone_into_recovered(&self) -> Result<Recovered<Self>, RecoveryError> {
104        self.recover_signer().map(|signer| Recovered::new_unchecked(self.clone(), signer))
105    }
106
107    /// Tries to recover signer and return [`Recovered`].
108    ///
109    /// Returns `Err(Self)` if the transaction's signature is invalid, see also
110    /// [`SignedTransaction::recover_signer`].
111    #[auto_impl(keep_default_for(&, Arc))]
112    fn try_into_recovered(self) -> Result<Recovered<Self>, Self> {
113        match self.recover_signer() {
114            Ok(signer) => Ok(Recovered::new_unchecked(self, signer)),
115            Err(_) => Err(self),
116        }
117    }
118
119    /// Consumes the type, recover signer and return [`Recovered`] _without
120    /// ensuring that the signature has a low `s` value_ (EIP-2).
121    ///
122    /// Returns `RecoveryError` if the transaction's signature is invalid.
123    #[auto_impl(keep_default_for(&, Arc))]
124    fn into_recovered_unchecked(self) -> Result<Recovered<Self>, RecoveryError> {
125        self.recover_signer_unchecked().map(|signer| Recovered::new_unchecked(self, signer))
126    }
127
128    /// Returns the [`Recovered`] transaction with the given sender.
129    ///
130    /// Note: assumes the given signer is the signer of this transaction.
131    #[auto_impl(keep_default_for(&, Arc))]
132    fn with_signer(self, signer: Address) -> Recovered<Self> {
133        Recovered::new_unchecked(self, signer)
134    }
135}
136
137impl<T> SignedTransaction for EthereumTxEnvelope<T>
138where
139    T: RlpEcdsaEncodableTx + SignableTransaction<Signature> + Unpin,
140    Self: Clone + PartialEq + Eq + Decodable + Decodable2718 + MaybeSerde + InMemorySize,
141{
142    fn tx_hash(&self) -> &TxHash {
143        match self {
144            Self::Legacy(tx) => tx.hash(),
145            Self::Eip2930(tx) => tx.hash(),
146            Self::Eip1559(tx) => tx.hash(),
147            Self::Eip7702(tx) => tx.hash(),
148            Self::Eip4844(tx) => tx.hash(),
149        }
150    }
151
152    fn recover_signer(&self) -> Result<Address, RecoveryError> {
153        let signature_hash = self.signature_hash();
154        recover_signer(self.signature(), signature_hash)
155    }
156
157    fn recover_signer_unchecked_with_buf(
158        &self,
159        buf: &mut Vec<u8>,
160    ) -> Result<Address, RecoveryError> {
161        match self {
162            Self::Legacy(tx) => tx.tx().encode_for_signing(buf),
163            Self::Eip2930(tx) => tx.tx().encode_for_signing(buf),
164            Self::Eip1559(tx) => tx.tx().encode_for_signing(buf),
165            Self::Eip7702(tx) => tx.tx().encode_for_signing(buf),
166            Self::Eip4844(tx) => tx.tx().encode_for_signing(buf),
167        }
168        let signature_hash = keccak256(buf);
169        recover_signer_unchecked(self.signature(), signature_hash)
170    }
171}
172
173#[cfg(feature = "op")]
174mod op {
175    use super::*;
176    use op_alloy_consensus::{OpPooledTransaction, OpTxEnvelope};
177
178    impl SignedTransaction for OpPooledTransaction {
179        fn tx_hash(&self) -> &TxHash {
180            match self {
181                Self::Legacy(tx) => tx.hash(),
182                Self::Eip2930(tx) => tx.hash(),
183                Self::Eip1559(tx) => tx.hash(),
184                Self::Eip7702(tx) => tx.hash(),
185            }
186        }
187
188        fn recover_signer(&self) -> Result<Address, RecoveryError> {
189            recover_signer(self.signature(), self.signature_hash())
190        }
191
192        fn recover_signer_unchecked_with_buf(
193            &self,
194            buf: &mut Vec<u8>,
195        ) -> Result<Address, RecoveryError> {
196            match self {
197                Self::Legacy(tx) => tx.tx().encode_for_signing(buf),
198                Self::Eip2930(tx) => tx.tx().encode_for_signing(buf),
199                Self::Eip1559(tx) => tx.tx().encode_for_signing(buf),
200                Self::Eip7702(tx) => tx.tx().encode_for_signing(buf),
201            }
202            let signature_hash = keccak256(buf);
203            recover_signer_unchecked(self.signature(), signature_hash)
204        }
205    }
206
207    impl SignedTransaction for OpTxEnvelope {
208        fn tx_hash(&self) -> &TxHash {
209            match self {
210                Self::Legacy(tx) => tx.hash(),
211                Self::Eip2930(tx) => tx.hash(),
212                Self::Eip1559(tx) => tx.hash(),
213                Self::Eip7702(tx) => tx.hash(),
214                Self::Deposit(tx) => tx.hash_ref(),
215            }
216        }
217
218        fn recover_signer(&self) -> Result<Address, RecoveryError> {
219            let signature_hash = match self {
220                Self::Legacy(tx) => tx.signature_hash(),
221                Self::Eip2930(tx) => tx.signature_hash(),
222                Self::Eip1559(tx) => tx.signature_hash(),
223                Self::Eip7702(tx) => tx.signature_hash(),
224                // Optimism's Deposit transaction does not have a signature. Directly return the
225                // `from` address.
226                Self::Deposit(tx) => return Ok(tx.from),
227            };
228            let signature = match self {
229                Self::Legacy(tx) => tx.signature(),
230                Self::Eip2930(tx) => tx.signature(),
231                Self::Eip1559(tx) => tx.signature(),
232                Self::Eip7702(tx) => tx.signature(),
233                Self::Deposit(_) => unreachable!("Deposit transactions should not be handled here"),
234            };
235            recover_signer(signature, signature_hash)
236        }
237
238        fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
239            let signature_hash = match self {
240                Self::Legacy(tx) => tx.signature_hash(),
241                Self::Eip2930(tx) => tx.signature_hash(),
242                Self::Eip1559(tx) => tx.signature_hash(),
243                Self::Eip7702(tx) => tx.signature_hash(),
244                // Optimism's Deposit transaction does not have a signature. Directly return the
245                // `from` address.
246                Self::Deposit(tx) => return Ok(tx.from),
247            };
248            let signature = match self {
249                Self::Legacy(tx) => tx.signature(),
250                Self::Eip2930(tx) => tx.signature(),
251                Self::Eip1559(tx) => tx.signature(),
252                Self::Eip7702(tx) => tx.signature(),
253                Self::Deposit(_) => unreachable!("Deposit transactions should not be handled here"),
254            };
255            recover_signer_unchecked(signature, signature_hash)
256        }
257
258        fn recover_signer_unchecked_with_buf(
259            &self,
260            buf: &mut Vec<u8>,
261        ) -> Result<Address, RecoveryError> {
262            match self {
263                Self::Deposit(tx) => return Ok(tx.from),
264                Self::Legacy(tx) => tx.tx().encode_for_signing(buf),
265                Self::Eip2930(tx) => tx.tx().encode_for_signing(buf),
266                Self::Eip1559(tx) => tx.tx().encode_for_signing(buf),
267                Self::Eip7702(tx) => tx.tx().encode_for_signing(buf),
268            }
269            let signature_hash = keccak256(buf);
270            let signature = match self {
271                Self::Legacy(tx) => tx.signature(),
272                Self::Eip2930(tx) => tx.signature(),
273                Self::Eip1559(tx) => tx.signature(),
274                Self::Eip7702(tx) => tx.signature(),
275                Self::Deposit(_) => unreachable!("Deposit transactions should not be handled here"),
276            };
277            recover_signer_unchecked(signature, signature_hash)
278        }
279    }
280}
281/// Opaque error type for sender recovery.
282#[derive(Debug, Default, thiserror::Error)]
283#[error("Failed to recover the signer")]
284pub struct RecoveryError;