reth_primitives_traits/transaction/
signed.rs

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