Skip to main content

alloy_consensus/
signed.rs

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