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