alloy_consensus/
signed.rs

1use crate::{
2    transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignableTransaction, TxHashRef},
3    Transaction,
4};
5use alloy_eips::{
6    eip2718::{Eip2718Error, Eip2718Result},
7    eip2930::AccessList,
8    eip7702::SignedAuthorization,
9    Decodable2718, Encodable2718, Typed2718,
10};
11use alloy_primitives::{Bytes, Sealed, Signature, TxKind, B256, U256};
12use alloy_rlp::BufMut;
13use core::hash::{Hash, Hasher};
14#[cfg(not(feature = "std"))]
15use once_cell::race::OnceBox as OnceLock;
16#[cfg(feature = "std")]
17use std::sync::OnceLock;
18
19/// A transaction with a signature and hash seal.
20#[derive(Debug, Clone)]
21pub struct Signed<T, Sig = Signature> {
22    #[doc(alias = "transaction")]
23    tx: T,
24    signature: Sig,
25    #[doc(alias = "tx_hash", alias = "transaction_hash")]
26    hash: OnceLock<B256>,
27}
28
29impl<T, Sig> Signed<T, Sig> {
30    /// Instantiate from a transaction and signature. Does not verify the signature.
31    pub fn new_unchecked(tx: T, signature: Sig, hash: B256) -> Self {
32        let value = OnceLock::new();
33        #[allow(clippy::useless_conversion)]
34        value.get_or_init(|| hash.into());
35        Self { tx, signature, hash: value }
36    }
37
38    /// Instantiate from a transaction and signature. Does not verify the signature.
39    pub const fn new_unhashed(tx: T, signature: Sig) -> Self {
40        Self { tx, signature, hash: OnceLock::new() }
41    }
42
43    /// Returns a reference to the transaction.
44    #[doc(alias = "transaction")]
45    pub const fn tx(&self) -> &T {
46        &self.tx
47    }
48
49    /// Returns a mutable reference to the transaction.
50    pub const fn tx_mut(&mut self) -> &mut T {
51        &mut self.tx
52    }
53
54    /// Returns a reference to the signature.
55    pub const fn signature(&self) -> &Sig {
56        &self.signature
57    }
58
59    /// Returns the transaction without signature.
60    pub fn strip_signature(self) -> T {
61        self.tx
62    }
63
64    /// Converts the transaction type to the given alternative that is `From<T>`
65    ///
66    /// Caution: This is only intended for converting transaction types that are structurally
67    /// equivalent (produce the same hash).
68    pub fn convert<U>(self) -> Signed<U, Sig>
69    where
70        U: From<T>,
71    {
72        self.map(U::from)
73    }
74
75    /// Converts the transaction to the given alternative that is `TryFrom<T>`
76    ///
77    /// Returns the transaction with the new transaction type if all conversions were successful.
78    ///
79    /// Caution: This is only intended for converting transaction types that are structurally
80    /// equivalent (produce the same hash).
81    pub fn try_convert<U>(self) -> Result<Signed<U, Sig>, U::Error>
82    where
83        U: TryFrom<T>,
84    {
85        self.try_map(U::try_from)
86    }
87
88    /// Applies the given closure to the inner transaction type.
89    ///
90    /// Caution: This is only intended for converting transaction types that are structurally
91    /// equivalent (produce the same hash).
92    pub fn map<Tx>(self, f: impl FnOnce(T) -> Tx) -> Signed<Tx, Sig> {
93        let Self { tx, signature, hash } = self;
94        Signed { tx: f(tx), signature, hash }
95    }
96
97    /// Applies the given fallible closure to the inner transactions.
98    ///
99    /// Caution: This is only intended for converting transaction types that are structurally
100    /// equivalent (produce the same hash).
101    pub fn try_map<Tx, E>(self, f: impl FnOnce(T) -> Result<Tx, E>) -> Result<Signed<Tx, Sig>, E> {
102        let Self { tx, signature, hash } = self;
103        Ok(Signed { tx: f(tx)?, signature, hash })
104    }
105}
106
107impl<T: SignableTransaction<Sig>, Sig> Signed<T, Sig> {
108    /// Calculate the signing hash for the transaction.
109    pub fn signature_hash(&self) -> B256 {
110        self.tx.signature_hash()
111    }
112}
113
114impl<T> Signed<T>
115where
116    T: RlpEcdsaEncodableTx,
117{
118    /// Returns a reference to the transaction hash.
119    #[doc(alias = "tx_hash", alias = "transaction_hash")]
120    pub fn hash(&self) -> &B256 {
121        #[allow(clippy::useless_conversion)]
122        self.hash.get_or_init(|| self.tx.tx_hash(&self.signature).into())
123    }
124
125    /// Splits the transaction into parts.
126    pub fn into_parts(self) -> (T, Signature, B256) {
127        let hash = *self.hash();
128        (self.tx, self.signature, hash)
129    }
130
131    /// Get the length of the transaction when RLP encoded.
132    pub fn rlp_encoded_length(&self) -> usize {
133        self.tx.rlp_encoded_length_with_signature(&self.signature)
134    }
135
136    /// RLP encode the signed transaction.
137    pub fn rlp_encode(&self, out: &mut dyn BufMut) {
138        self.tx.rlp_encode_signed(&self.signature, out);
139    }
140
141    /// Get the length of the transaction when EIP-2718 encoded.
142    pub fn eip2718_encoded_length(&self) -> usize {
143        self.tx.eip2718_encoded_length(&self.signature)
144    }
145
146    /// EIP-2718 encode the signed transaction with a specified type flag.
147    pub fn eip2718_encode_with_type(&self, ty: u8, out: &mut dyn BufMut) {
148        self.tx.eip2718_encode_with_type(&self.signature, ty, out);
149    }
150
151    /// EIP-2718 encode the signed transaction.
152    pub fn eip2718_encode(&self, out: &mut dyn BufMut) {
153        self.tx.eip2718_encode(&self.signature, out);
154    }
155
156    /// Get the length of the transaction when network encoded.
157    pub fn network_encoded_length(&self) -> usize {
158        self.tx.network_encoded_length(&self.signature)
159    }
160
161    /// Network encode the signed transaction with a specified type flag.
162    pub fn network_encode_with_type(&self, ty: u8, out: &mut dyn BufMut) {
163        self.tx.network_encode_with_type(&self.signature, ty, out);
164    }
165
166    /// Network encode the signed transaction.
167    pub fn network_encode(&self, out: &mut dyn BufMut) {
168        self.tx.network_encode(&self.signature, out);
169    }
170}
171
172impl<T> TxHashRef for Signed<T>
173where
174    T: RlpEcdsaEncodableTx,
175{
176    fn tx_hash(&self) -> &B256 {
177        self.hash()
178    }
179}
180
181impl<T> Signed<T>
182where
183    T: RlpEcdsaDecodableTx,
184{
185    /// RLP decode the signed transaction.
186    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
187        T::rlp_decode_signed(buf)
188    }
189
190    /// EIP-2718 decode the signed transaction with a specified type flag.
191    pub fn eip2718_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Self> {
192        T::eip2718_decode_with_type(buf, ty)
193    }
194
195    /// EIP-2718 decode the signed transaction.
196    pub fn eip2718_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
197        T::eip2718_decode(buf)
198    }
199
200    /// Network decode the signed transaction with a specified type flag.
201    pub fn network_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Self> {
202        T::network_decode_with_type(buf, ty)
203    }
204
205    /// Network decode the signed transaction.
206    pub fn network_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
207        T::network_decode(buf)
208    }
209}
210
211impl<T> Hash for Signed<T>
212where
213    T: RlpEcdsaDecodableTx + Hash,
214{
215    fn hash<H: Hasher>(&self, state: &mut H) {
216        self.hash().hash(state);
217        self.tx.hash(state);
218        self.signature.hash(state);
219    }
220}
221
222impl<T: RlpEcdsaEncodableTx + PartialEq> PartialEq for Signed<T> {
223    fn eq(&self, other: &Self) -> bool {
224        self.hash() == other.hash() && self.tx == other.tx && self.signature == other.signature
225    }
226}
227
228impl<T: RlpEcdsaEncodableTx + PartialEq> Eq for Signed<T> {}
229
230#[cfg(feature = "k256")]
231impl<T: SignableTransaction<Signature>> Signed<T, Signature> {
232    /// Recover the signer of the transaction
233    pub fn recover_signer(
234        &self,
235    ) -> Result<alloy_primitives::Address, alloy_primitives::SignatureError> {
236        let sighash = self.tx.signature_hash();
237        self.signature.recover_address_from_prehash(&sighash)
238    }
239
240    /// Attempts to recover signer and constructs a [`crate::transaction::Recovered`] object.
241    pub fn try_into_recovered(
242        self,
243    ) -> Result<crate::transaction::Recovered<T>, alloy_primitives::SignatureError> {
244        let signer = self.recover_signer()?;
245        Ok(crate::transaction::Recovered::new_unchecked(self.tx, signer))
246    }
247
248    /// Attempts to recover signer and constructs a [`crate::transaction::Recovered`] with a
249    /// reference to the transaction `Recovered<&T>`
250    pub fn try_to_recovered_ref(
251        &self,
252    ) -> Result<crate::transaction::Recovered<&T>, alloy_primitives::SignatureError> {
253        let signer = self.recover_signer()?;
254        Ok(crate::transaction::Recovered::new_unchecked(&self.tx, signer))
255    }
256}
257
258#[cfg(all(any(test, feature = "arbitrary"), feature = "k256"))]
259impl<'a, T: SignableTransaction<Signature> + arbitrary::Arbitrary<'a>> arbitrary::Arbitrary<'a>
260    for Signed<T, Signature>
261{
262    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
263        use k256::{
264            ecdsa::{signature::hazmat::PrehashSigner, SigningKey},
265            NonZeroScalar,
266        };
267        use rand::{rngs::StdRng, SeedableRng};
268
269        let rng_seed = u.arbitrary::<[u8; 32]>()?;
270        let mut rand_gen = StdRng::from_seed(rng_seed);
271        let signing_key: SigningKey = NonZeroScalar::random(&mut rand_gen).into();
272
273        let tx = T::arbitrary(u)?;
274
275        let (recoverable_sig, recovery_id) =
276            signing_key.sign_prehash(tx.signature_hash().as_ref()).unwrap();
277        let signature: Signature = (recoverable_sig, recovery_id).into();
278
279        Ok(tx.into_signed(signature))
280    }
281}
282
283impl<T> Typed2718 for Signed<T>
284where
285    T: Typed2718,
286{
287    fn ty(&self) -> u8 {
288        self.tx().ty()
289    }
290}
291
292impl<T: Transaction> Transaction for Signed<T> {
293    #[inline]
294    fn chain_id(&self) -> Option<u64> {
295        self.tx.chain_id()
296    }
297
298    #[inline]
299    fn nonce(&self) -> u64 {
300        self.tx.nonce()
301    }
302
303    #[inline]
304    fn gas_limit(&self) -> u64 {
305        self.tx.gas_limit()
306    }
307
308    #[inline]
309    fn gas_price(&self) -> Option<u128> {
310        self.tx.gas_price()
311    }
312
313    #[inline]
314    fn max_fee_per_gas(&self) -> u128 {
315        self.tx.max_fee_per_gas()
316    }
317
318    #[inline]
319    fn max_priority_fee_per_gas(&self) -> Option<u128> {
320        self.tx.max_priority_fee_per_gas()
321    }
322
323    #[inline]
324    fn max_fee_per_blob_gas(&self) -> Option<u128> {
325        self.tx.max_fee_per_blob_gas()
326    }
327
328    #[inline]
329    fn priority_fee_or_price(&self) -> u128 {
330        self.tx.priority_fee_or_price()
331    }
332
333    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
334        self.tx.effective_gas_price(base_fee)
335    }
336
337    #[inline]
338    fn is_dynamic_fee(&self) -> bool {
339        self.tx.is_dynamic_fee()
340    }
341
342    #[inline]
343    fn kind(&self) -> TxKind {
344        self.tx.kind()
345    }
346
347    #[inline]
348    fn is_create(&self) -> bool {
349        self.tx.is_create()
350    }
351
352    #[inline]
353    fn value(&self) -> U256 {
354        self.tx.value()
355    }
356
357    #[inline]
358    fn input(&self) -> &Bytes {
359        self.tx.input()
360    }
361
362    #[inline]
363    fn access_list(&self) -> Option<&AccessList> {
364        self.tx.access_list()
365    }
366
367    #[inline]
368    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
369        self.tx.blob_versioned_hashes()
370    }
371
372    #[inline]
373    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
374        self.tx.authorization_list()
375    }
376}
377
378impl<T: Transaction> Transaction for Sealed<T> {
379    #[inline]
380    fn chain_id(&self) -> Option<u64> {
381        self.inner().chain_id()
382    }
383
384    #[inline]
385    fn nonce(&self) -> u64 {
386        self.inner().nonce()
387    }
388
389    #[inline]
390    fn gas_limit(&self) -> u64 {
391        self.inner().gas_limit()
392    }
393
394    #[inline]
395    fn gas_price(&self) -> Option<u128> {
396        self.inner().gas_price()
397    }
398
399    #[inline]
400    fn max_fee_per_gas(&self) -> u128 {
401        self.inner().max_fee_per_gas()
402    }
403
404    #[inline]
405    fn max_priority_fee_per_gas(&self) -> Option<u128> {
406        self.inner().max_priority_fee_per_gas()
407    }
408
409    #[inline]
410    fn max_fee_per_blob_gas(&self) -> Option<u128> {
411        self.inner().max_fee_per_blob_gas()
412    }
413
414    #[inline]
415    fn priority_fee_or_price(&self) -> u128 {
416        self.inner().priority_fee_or_price()
417    }
418
419    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
420        self.inner().effective_gas_price(base_fee)
421    }
422
423    #[inline]
424    fn is_dynamic_fee(&self) -> bool {
425        self.inner().is_dynamic_fee()
426    }
427
428    #[inline]
429    fn kind(&self) -> TxKind {
430        self.inner().kind()
431    }
432
433    #[inline]
434    fn is_create(&self) -> bool {
435        self.inner().is_create()
436    }
437
438    #[inline]
439    fn value(&self) -> U256 {
440        self.inner().value()
441    }
442
443    #[inline]
444    fn input(&self) -> &Bytes {
445        self.inner().input()
446    }
447
448    #[inline]
449    fn access_list(&self) -> Option<&AccessList> {
450        self.inner().access_list()
451    }
452
453    #[inline]
454    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
455        self.inner().blob_versioned_hashes()
456    }
457
458    #[inline]
459    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
460        self.inner().authorization_list()
461    }
462}
463
464#[cfg(any(feature = "secp256k1", feature = "k256"))]
465impl<T> crate::transaction::SignerRecoverable for Signed<T>
466where
467    T: SignableTransaction<Signature>,
468{
469    fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
470        let signature_hash = self.signature_hash();
471        crate::crypto::secp256k1::recover_signer(self.signature(), signature_hash)
472    }
473
474    fn recover_signer_unchecked(
475        &self,
476    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
477        let signature_hash = self.signature_hash();
478        crate::crypto::secp256k1::recover_signer_unchecked(self.signature(), signature_hash)
479    }
480
481    fn recover_unchecked_with_buf(
482        &self,
483        buf: &mut alloc::vec::Vec<u8>,
484    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
485        buf.clear();
486        self.tx.encode_for_signing(buf);
487        let signature_hash = alloy_primitives::keccak256(buf);
488        crate::crypto::secp256k1::recover_signer_unchecked(self.signature(), signature_hash)
489    }
490}
491
492impl<T> Encodable2718 for Signed<T>
493where
494    T: RlpEcdsaEncodableTx + Typed2718 + Send + Sync,
495{
496    fn encode_2718_len(&self) -> usize {
497        self.eip2718_encoded_length()
498    }
499
500    fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
501        self.eip2718_encode(out)
502    }
503
504    fn trie_hash(&self) -> B256 {
505        *self.hash()
506    }
507}
508
509impl<T> Decodable2718 for Signed<T>
510where
511    T: RlpEcdsaDecodableTx + Typed2718 + Send + Sync,
512{
513    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
514        let decoded = T::rlp_decode_signed(buf)?;
515
516        if decoded.ty() != ty {
517            return Err(Eip2718Error::UnexpectedType(ty));
518        }
519
520        Ok(decoded)
521    }
522
523    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
524        T::rlp_decode_signed(buf).map_err(Into::into)
525    }
526}
527
528#[cfg(feature = "serde")]
529mod serde {
530    use crate::transaction::RlpEcdsaEncodableTx;
531    use alloc::borrow::Cow;
532    use alloy_primitives::B256;
533    use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
534
535    #[derive(Serialize, Deserialize)]
536    struct Signed<'a, T: Clone, Sig: Clone> {
537        #[serde(flatten)]
538        tx: Cow<'a, T>,
539        #[serde(flatten)]
540        signature: Cow<'a, Sig>,
541        hash: Cow<'a, B256>,
542    }
543
544    impl<T> Serialize for super::Signed<T>
545    where
546        T: Clone + RlpEcdsaEncodableTx + Serialize,
547    {
548        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
549        where
550            S: Serializer,
551        {
552            Signed {
553                tx: Cow::Borrowed(&self.tx),
554                signature: Cow::Borrowed(&self.signature),
555                hash: Cow::Borrowed(self.hash()),
556            }
557            .serialize(serializer)
558        }
559    }
560
561    impl<'de, T, Sig> Deserialize<'de> for super::Signed<T, Sig>
562    where
563        T: Clone + DeserializeOwned,
564        Sig: Clone + DeserializeOwned,
565    {
566        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
567        where
568            D: Deserializer<'de>,
569        {
570            Signed::<T, Sig>::deserialize(deserializer).map(|value| {
571                Self::new_unchecked(
572                    value.tx.into_owned(),
573                    value.signature.into_owned(),
574                    value.hash.into_owned(),
575                )
576            })
577        }
578    }
579}