alloy_consensus/transaction/
rlp.rs

1use crate::Signed;
2use alloc::vec::Vec;
3use alloy_eips::eip2718::{Eip2718Error, Eip2718Result};
4use alloy_primitives::{keccak256, PrimitiveSignature as Signature, TxHash};
5use alloy_rlp::{Buf, BufMut, Decodable, Encodable, Header};
6
7/// Helper trait for managing RLP encoding of transactions inside 2718
8#[doc(hidden)]
9#[doc(alias = "RlpEncodableTx", alias = "RlpTxEncoding")]
10#[auto_impl::auto_impl(&, Arc)]
11pub trait RlpEcdsaEncodableTx: Sized {
12    /// The default transaction type for this transaction.
13    const DEFAULT_TX_TYPE: u8;
14
15    /// Calculate the encoded length of the transaction's fields, without a RLP
16    /// header.
17    fn rlp_encoded_fields_length(&self) -> usize;
18
19    /// Encodes only the transaction's fields into the desired buffer, without
20    /// a RLP header.
21    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut);
22
23    /// Create an list rlp header for the unsigned transaction.
24    fn rlp_header(&self) -> Header {
25        Header { list: true, payload_length: self.rlp_encoded_fields_length() }
26    }
27
28    /// Get the length of the transaction when RLP encoded.
29    fn rlp_encoded_length(&self) -> usize {
30        self.rlp_header().length_with_payload()
31    }
32
33    /// RLP encodes the transaction.
34    fn rlp_encode(&self, out: &mut dyn BufMut) {
35        self.rlp_header().encode(out);
36        self.rlp_encode_fields(out);
37    }
38
39    /// Create an rlp list header for the signed transaction.
40    fn rlp_header_signed(&self, signature: &Signature) -> Header {
41        let payload_length =
42            self.rlp_encoded_fields_length() + signature.rlp_rs_len() + signature.v().length();
43        Header { list: true, payload_length }
44    }
45
46    /// Get the length of the transaction when RLP encoded with the given
47    /// signature.
48    fn rlp_encoded_length_with_signature(&self, signature: &Signature) -> usize {
49        self.rlp_header_signed(signature).length_with_payload()
50    }
51
52    /// RLP encodes the transaction with the given signature.
53    fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
54        self.rlp_header_signed(signature).encode(out);
55        self.rlp_encode_fields(out);
56        signature.write_rlp_vrs(out, signature.v());
57    }
58
59    /// Get the length of the transaction when EIP-2718 encoded. This is the
60    /// 1 byte type flag + the length of the RLP encoded transaction.
61    fn eip2718_encoded_length(&self, signature: &Signature) -> usize {
62        self.rlp_encoded_length_with_signature(signature) + 1
63    }
64
65    /// EIP-2718 encode the transaction with the given signature and type flag.
66    fn eip2718_encode_with_type(&self, signature: &Signature, ty: u8, out: &mut dyn BufMut) {
67        out.put_u8(ty);
68        self.rlp_encode_signed(signature, out);
69    }
70
71    /// EIP-2718 encode the transaction with the given signature and the default
72    /// type flag.
73    fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
74        self.eip2718_encode_with_type(signature, Self::DEFAULT_TX_TYPE, out);
75    }
76
77    /// Create an rlp header for the network encoded transaction. This will
78    /// usually be a string header, however, legacy transactions' network
79    /// encoding is a list.
80    fn network_header(&self, signature: &Signature) -> Header {
81        let payload_length = self.eip2718_encoded_length(signature);
82        Header { list: false, payload_length }
83    }
84
85    /// Get the length of the transaction when network encoded. This is the
86    /// EIP-2718 encoded length with an outer RLP header.
87    fn network_encoded_length(&self, signature: &Signature) -> usize {
88        self.network_header(signature).length_with_payload()
89    }
90
91    /// Network encode the transaction with the given signature.
92    fn network_encode_with_type(&self, signature: &Signature, ty: u8, out: &mut dyn BufMut) {
93        self.network_header(signature).encode(out);
94        self.eip2718_encode_with_type(signature, ty, out);
95    }
96
97    /// Network encode the transaction with the given signature and the default
98    /// type flag.
99    fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
100        self.network_encode_with_type(signature, Self::DEFAULT_TX_TYPE, out);
101    }
102
103    /// Calculate the transaction hash for the given signature and type.
104    fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> TxHash {
105        let mut buf = Vec::with_capacity(self.eip2718_encoded_length(signature));
106        self.eip2718_encode_with_type(signature, ty, &mut buf);
107        keccak256(&buf)
108    }
109
110    /// Calculate the transaction hash for the given signature.
111    fn tx_hash(&self, signature: &Signature) -> TxHash {
112        self.tx_hash_with_type(signature, Self::DEFAULT_TX_TYPE)
113    }
114}
115
116/// Helper trait for managing RLP decoding of transactions inside 2718 envelopes.
117#[doc(hidden)]
118#[doc(alias = "RlpDecodableTx", alias = "RlpTxDecoding")]
119pub trait RlpEcdsaDecodableTx: RlpEcdsaEncodableTx {
120    /// Decodes the fields of the transaction from RLP bytes. Do not decode a
121    /// header. You may assume the buffer is long enough to contain the
122    /// transaction.
123    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self>;
124
125    /// Decodes the transaction from RLP bytes.
126    fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
127        let header = Header::decode(buf)?;
128        if !header.list {
129            return Err(alloy_rlp::Error::UnexpectedString);
130        }
131        let remaining = buf.len();
132
133        if header.payload_length > remaining {
134            return Err(alloy_rlp::Error::InputTooShort);
135        }
136
137        let this = Self::rlp_decode_fields(buf)?;
138
139        if buf.len() + header.payload_length != remaining {
140            return Err(alloy_rlp::Error::UnexpectedLength);
141        }
142
143        Ok(this)
144    }
145
146    /// Decodes the transaction from RLP bytes, including the signature.
147    fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
148        let header = Header::decode(buf)?;
149        if !header.list {
150            return Err(alloy_rlp::Error::UnexpectedString);
151        }
152
153        let remaining = buf.len();
154        let tx = Self::rlp_decode_fields(buf)?;
155        let signature = Signature::decode_rlp_vrs(buf, bool::decode)?;
156
157        if buf.len() + header.payload_length != remaining {
158            return Err(alloy_rlp::Error::ListLengthMismatch {
159                expected: header.payload_length,
160                got: remaining - buf.len(),
161            });
162        }
163
164        Ok((tx, signature))
165    }
166
167    /// Decodes the transaction from RLP bytes, including the signature
168    /// Produces a [`Signed`].
169    fn rlp_decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result<Signed<Self>> {
170        Self::rlp_decode_with_signature(buf)
171            .map(|(tx, signature)| Signed::new_unhashed(tx, signature))
172    }
173
174    /// Decodes the transaction from eip2718 bytes, expecting the given type
175    /// flag.
176    fn eip2718_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Signed<Self>> {
177        let original_buf = *buf;
178
179        if buf.remaining() < 1 {
180            return Err(alloy_rlp::Error::InputTooShort.into());
181        }
182        let actual = buf.get_u8();
183        if actual != ty {
184            return Err(Eip2718Error::UnexpectedType(actual));
185        }
186
187        // OPT: We avoid re-serializing by calculating the hash directly
188        // from the original buffer contents.
189        let (tx, signature) = Self::rlp_decode_with_signature(buf)?;
190        let total_len = tx.eip2718_encoded_length(&signature);
191        let hash = keccak256(&original_buf[..total_len]);
192
193        Ok(Signed::new_unchecked(tx, signature, hash))
194    }
195
196    /// Decodes the transaction from eip2718 bytes, expecting the default type
197    /// flag.
198    fn eip2718_decode(buf: &mut &[u8]) -> Eip2718Result<Signed<Self>> {
199        Self::eip2718_decode_with_type(buf, Self::DEFAULT_TX_TYPE)
200    }
201
202    /// Decodes the transaction from network bytes.
203    fn network_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Signed<Self>> {
204        let header = Header::decode(buf)?;
205        if header.list {
206            return Err(alloy_rlp::Error::UnexpectedList.into());
207        }
208
209        let remaining = buf.len();
210        let res = Self::eip2718_decode_with_type(buf, ty)?;
211
212        if buf.len() + header.payload_length != remaining {
213            return Err(alloy_rlp::Error::UnexpectedLength.into());
214        }
215
216        Ok(res)
217    }
218
219    /// Decodes the transaction from network bytes, expecting the default type
220    /// flag.
221    fn network_decode(buf: &mut &[u8]) -> Eip2718Result<Signed<Self>> {
222        Self::network_decode_with_type(buf, Self::DEFAULT_TX_TYPE)
223    }
224}
225
226/// Helper trait for managing RLP encoding and decoding of transactions inside 2718
227/// envelopes.
228#[doc(hidden)]
229pub trait RlpEcdsaTx: RlpEcdsaEncodableTx + RlpEcdsaDecodableTx {}
230
231// Implement RlpEcdsaTx for all types that implement both required traits
232impl<T> RlpEcdsaTx for T where T: RlpEcdsaEncodableTx + RlpEcdsaDecodableTx {}