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 #[doc(hidden)]
60 pub const fn tx_mut(&mut self) -> &mut T {
61 &mut self.tx
62 }
63
64 pub const fn signature(&self) -> &Sig {
66 &self.signature
67 }
68
69 pub fn strip_signature(self) -> T {
71 self.tx
72 }
73
74 pub fn convert<U>(self) -> Signed<U, Sig>
79 where
80 U: From<T>,
81 {
82 self.map(U::from)
83 }
84
85 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 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 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 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 #[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 pub fn into_parts(self) -> (T, Signature, B256) {
142 let hash = *self.hash();
143 (self.tx, self.signature, hash)
144 }
145
146 pub fn rlp_encoded_length(&self) -> usize {
148 self.tx.rlp_encoded_length_with_signature(&self.signature)
149 }
150
151 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
153 self.tx.rlp_encode_signed(&self.signature, out);
154 }
155
156 pub fn eip2718_encoded_length(&self) -> usize {
158 self.tx.eip2718_encoded_length(&self.signature)
159 }
160
161 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 pub fn eip2718_encode(&self, out: &mut dyn BufMut) {
168 self.tx.eip2718_encode(&self.signature, out);
169 }
170
171 pub fn network_encoded_length(&self) -> usize {
173 self.tx.network_encoded_length(&self.signature)
174 }
175
176 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 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 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
202 T::rlp_decode_signed(buf)
203 }
204
205 pub fn eip2718_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Self> {
207 T::eip2718_decode_with_type(buf, ty)
208 }
209
210 pub fn eip2718_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
212 T::eip2718_decode(buf)
213 }
214
215 pub fn network_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Self> {
217 T::network_decode_with_type(buf, ty)
218 }
219
220 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 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 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 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}