alloy_consensus/
signed.rs

1use crate::transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignableTransaction};
2use alloy_eips::eip2718::Eip2718Result;
3use alloy_primitives::{PrimitiveSignature as Signature, B256};
4use alloy_rlp::BufMut;
5#[cfg(not(feature = "std"))]
6use once_cell::race::OnceBox as OnceLock;
7#[cfg(feature = "std")]
8use std::sync::OnceLock;
9
10/// A transaction with a signature and hash seal.
11#[derive(Debug)]
12pub struct Signed<T, Sig = Signature> {
13    #[doc(alias = "transaction")]
14    tx: T,
15    signature: Sig,
16    #[doc(alias = "tx_hash", alias = "transaction_hash")]
17    hash: OnceLock<B256>,
18}
19
20impl<T: Clone, Sig: Clone> Clone for Signed<T, Sig> {
21    fn clone(&self) -> Self {
22        self.hash.get().map_or_else(
23            || Self::new_unhashed(self.tx.clone(), self.signature.clone()),
24            |hash| Self::new_unchecked(self.tx.clone(), self.signature.clone(), *hash),
25        )
26    }
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 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> Signed<T>
173where
174    T: RlpEcdsaDecodableTx,
175{
176    /// RLP decode the signed transaction.
177    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
178        T::rlp_decode_signed(buf)
179    }
180
181    /// EIP-2718 decode the signed transaction with a specified type flag.
182    pub fn eip2718_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Self> {
183        T::eip2718_decode_with_type(buf, ty)
184    }
185
186    /// EIP-2718 decode the signed transaction.
187    pub fn eip2718_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
188        T::eip2718_decode(buf)
189    }
190
191    /// Network decode the signed transaction with a specified type flag.
192    pub fn network_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Self> {
193        T::network_decode_with_type(buf, ty)
194    }
195
196    /// Network decode the signed transaction.
197    pub fn network_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
198        T::network_decode(buf)
199    }
200}
201
202impl<T: RlpEcdsaEncodableTx + PartialEq> PartialEq for Signed<T> {
203    fn eq(&self, other: &Self) -> bool {
204        self.hash() == other.hash() && self.tx == other.tx && self.signature == other.signature
205    }
206}
207
208impl<T: RlpEcdsaEncodableTx + PartialEq> Eq for Signed<T> {}
209
210#[cfg(feature = "k256")]
211impl<T: SignableTransaction<Signature>> Signed<T, Signature> {
212    /// Recover the signer of the transaction
213    pub fn recover_signer(
214        &self,
215    ) -> Result<alloy_primitives::Address, alloy_primitives::SignatureError> {
216        let sighash = self.tx.signature_hash();
217        self.signature.recover_address_from_prehash(&sighash)
218    }
219
220    /// Attempts to recover signer and constructs a [`crate::transaction::Recovered`] object.
221    pub fn try_into_recovered(
222        self,
223    ) -> Result<crate::transaction::Recovered<T>, alloy_primitives::SignatureError> {
224        let signer = self.recover_signer()?;
225        Ok(crate::transaction::Recovered::new_unchecked(self.tx, signer))
226    }
227}
228
229#[cfg(all(any(test, feature = "arbitrary"), feature = "k256"))]
230impl<'a, T: SignableTransaction<Signature> + arbitrary::Arbitrary<'a>> arbitrary::Arbitrary<'a>
231    for Signed<T, Signature>
232{
233    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
234        use k256::{
235            ecdsa::{signature::hazmat::PrehashSigner, SigningKey},
236            NonZeroScalar,
237        };
238        use rand::{rngs::StdRng, SeedableRng};
239
240        let rng_seed = u.arbitrary::<[u8; 32]>()?;
241        let mut rand_gen = StdRng::from_seed(rng_seed);
242        let signing_key: SigningKey = NonZeroScalar::random(&mut rand_gen).into();
243
244        let tx = T::arbitrary(u)?;
245
246        let (recoverable_sig, recovery_id) =
247            signing_key.sign_prehash(tx.signature_hash().as_ref()).unwrap();
248        let signature: Signature = (recoverable_sig, recovery_id).into();
249
250        Ok(tx.into_signed(signature))
251    }
252}
253
254#[cfg(feature = "serde")]
255mod serde {
256    use crate::transaction::RlpEcdsaEncodableTx;
257    use alloc::borrow::Cow;
258    use alloy_primitives::B256;
259    use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
260
261    #[derive(Serialize, Deserialize)]
262    struct Signed<'a, T: Clone, Sig: Clone> {
263        #[serde(flatten)]
264        tx: Cow<'a, T>,
265        #[serde(flatten)]
266        signature: Cow<'a, Sig>,
267        hash: Cow<'a, B256>,
268    }
269
270    impl<T> Serialize for super::Signed<T>
271    where
272        T: Clone + RlpEcdsaEncodableTx + Serialize,
273    {
274        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
275        where
276            S: Serializer,
277        {
278            Signed {
279                tx: Cow::Borrowed(&self.tx),
280                signature: Cow::Borrowed(&self.signature),
281                hash: Cow::Borrowed(self.hash()),
282            }
283            .serialize(serializer)
284        }
285    }
286
287    impl<'de, T, Sig> Deserialize<'de> for super::Signed<T, Sig>
288    where
289        T: Clone + DeserializeOwned,
290        Sig: Clone + DeserializeOwned,
291    {
292        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
293        where
294            D: Deserializer<'de>,
295        {
296            Signed::<T, Sig>::deserialize(deserializer).map(|value| {
297                Self::new_unchecked(
298                    value.tx.into_owned(),
299                    value.signature.into_owned(),
300                    value.hash.into_owned(),
301                )
302            })
303        }
304    }
305}