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