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