1use super::rlp;
33use super::EthereumSigner;
34use crate::error::SignerError;
35use sha3::{Digest, Keccak256};
36
37#[derive(Debug, Clone)]
41pub struct SignedTransaction {
42 raw: Vec<u8>,
44}
45
46impl SignedTransaction {
47 #[must_use]
51 pub fn raw_tx(&self) -> &[u8] {
52 &self.raw
53 }
54
55 #[must_use]
57 pub fn tx_hash(&self) -> [u8; 32] {
58 let mut out = [0u8; 32];
59 out.copy_from_slice(&Keccak256::digest(&self.raw));
60 out
61 }
62
63 #[must_use]
65 pub fn raw_tx_hex(&self) -> String {
66 format!("0x{}", hex::encode(&self.raw))
67 }
68}
69
70#[derive(Debug, Clone)]
76pub struct LegacyTransaction {
77 pub nonce: u64,
79 pub gas_price: u128,
81 pub gas_limit: u64,
83 pub to: Option<[u8; 20]>,
85 pub value: u128,
87 pub data: Vec<u8>,
89 pub chain_id: u64,
91}
92
93impl LegacyTransaction {
94 fn signing_payload(&self) -> Vec<u8> {
98 let mut items = Vec::new();
99 items.extend_from_slice(&rlp::encode_u64(self.nonce));
100 items.extend_from_slice(&rlp::encode_u128(self.gas_price));
101 items.extend_from_slice(&rlp::encode_u64(self.gas_limit));
102 items.extend_from_slice(&encode_address(&self.to));
103 items.extend_from_slice(&rlp::encode_u128(self.value));
104 items.extend_from_slice(&rlp::encode_bytes(&self.data));
105 items.extend_from_slice(&rlp::encode_u64(self.chain_id));
107 items.extend_from_slice(&rlp::encode_u64(0));
108 items.extend_from_slice(&rlp::encode_u64(0));
109 rlp::encode_list(&items)
110 }
111
112 pub fn sign(&self, signer: &EthereumSigner) -> Result<SignedTransaction, SignerError> {
114 let payload = self.signing_payload();
115 let hash = keccak256(&payload);
116 let sig = signer.sign_digest(&hash)?;
117
118 let v = (sig.v as u64 - 27) + self.chain_id * 2 + 35;
120
121 let mut items = Vec::new();
122 items.extend_from_slice(&rlp::encode_u64(self.nonce));
123 items.extend_from_slice(&rlp::encode_u128(self.gas_price));
124 items.extend_from_slice(&rlp::encode_u64(self.gas_limit));
125 items.extend_from_slice(&encode_address(&self.to));
126 items.extend_from_slice(&rlp::encode_u128(self.value));
127 items.extend_from_slice(&rlp::encode_bytes(&self.data));
128 items.extend_from_slice(&rlp::encode_u64(v));
129 items.extend_from_slice(&rlp::encode_bytes(&strip_leading_zeros(&sig.r)));
130 items.extend_from_slice(&rlp::encode_bytes(&strip_leading_zeros(&sig.s)));
131
132 Ok(SignedTransaction {
133 raw: rlp::encode_list(&items),
134 })
135 }
136}
137
138#[derive(Debug, Clone)]
144pub struct EIP2930Transaction {
145 pub chain_id: u64,
147 pub nonce: u64,
149 pub gas_price: u128,
151 pub gas_limit: u64,
153 pub to: Option<[u8; 20]>,
155 pub value: u128,
157 pub data: Vec<u8>,
159 pub access_list: Vec<([u8; 20], Vec<[u8; 32]>)>,
161}
162
163impl EIP2930Transaction {
164 fn signing_hash(&self) -> [u8; 32] {
166 let mut items = Vec::new();
167 items.extend_from_slice(&rlp::encode_u64(self.chain_id));
168 items.extend_from_slice(&rlp::encode_u64(self.nonce));
169 items.extend_from_slice(&rlp::encode_u128(self.gas_price));
170 items.extend_from_slice(&rlp::encode_u64(self.gas_limit));
171 items.extend_from_slice(&encode_address(&self.to));
172 items.extend_from_slice(&rlp::encode_u128(self.value));
173 items.extend_from_slice(&rlp::encode_bytes(&self.data));
174 items.extend_from_slice(&rlp::encode_access_list(&self.access_list));
175
176 let mut payload = vec![0x01]; payload.extend_from_slice(&rlp::encode_list(&items));
178 keccak256(&payload)
179 }
180
181 pub fn sign(&self, signer: &EthereumSigner) -> Result<SignedTransaction, SignerError> {
183 let hash = self.signing_hash();
184 let sig = signer.sign_digest(&hash)?;
185 let y_parity = sig.v - 27; let mut items = Vec::new();
188 items.extend_from_slice(&rlp::encode_u64(self.chain_id));
189 items.extend_from_slice(&rlp::encode_u64(self.nonce));
190 items.extend_from_slice(&rlp::encode_u128(self.gas_price));
191 items.extend_from_slice(&rlp::encode_u64(self.gas_limit));
192 items.extend_from_slice(&encode_address(&self.to));
193 items.extend_from_slice(&rlp::encode_u128(self.value));
194 items.extend_from_slice(&rlp::encode_bytes(&self.data));
195 items.extend_from_slice(&rlp::encode_access_list(&self.access_list));
196 items.extend_from_slice(&rlp::encode_u64(y_parity));
197 items.extend_from_slice(&rlp::encode_bytes(&strip_leading_zeros(&sig.r)));
198 items.extend_from_slice(&rlp::encode_bytes(&strip_leading_zeros(&sig.s)));
199
200 let mut raw = vec![0x01]; raw.extend_from_slice(&rlp::encode_list(&items));
202
203 Ok(SignedTransaction { raw })
204 }
205}
206
207#[derive(Debug, Clone)]
214pub struct EIP1559Transaction {
215 pub chain_id: u64,
217 pub nonce: u64,
219 pub max_priority_fee_per_gas: u128,
221 pub max_fee_per_gas: u128,
223 pub gas_limit: u64,
225 pub to: Option<[u8; 20]>,
227 pub value: u128,
229 pub data: Vec<u8>,
231 pub access_list: Vec<([u8; 20], Vec<[u8; 32]>)>,
233}
234
235impl EIP1559Transaction {
236 fn signing_hash(&self) -> [u8; 32] {
238 let mut items = Vec::new();
239 items.extend_from_slice(&rlp::encode_u64(self.chain_id));
240 items.extend_from_slice(&rlp::encode_u64(self.nonce));
241 items.extend_from_slice(&rlp::encode_u128(self.max_priority_fee_per_gas));
242 items.extend_from_slice(&rlp::encode_u128(self.max_fee_per_gas));
243 items.extend_from_slice(&rlp::encode_u64(self.gas_limit));
244 items.extend_from_slice(&encode_address(&self.to));
245 items.extend_from_slice(&rlp::encode_u128(self.value));
246 items.extend_from_slice(&rlp::encode_bytes(&self.data));
247 items.extend_from_slice(&rlp::encode_access_list(&self.access_list));
248
249 let mut payload = vec![0x02]; payload.extend_from_slice(&rlp::encode_list(&items));
251 keccak256(&payload)
252 }
253
254 pub fn sign(&self, signer: &EthereumSigner) -> Result<SignedTransaction, SignerError> {
256 let hash = self.signing_hash();
257 let sig = signer.sign_digest(&hash)?;
258 let y_parity = sig.v - 27; let mut items = Vec::new();
261 items.extend_from_slice(&rlp::encode_u64(self.chain_id));
262 items.extend_from_slice(&rlp::encode_u64(self.nonce));
263 items.extend_from_slice(&rlp::encode_u128(self.max_priority_fee_per_gas));
264 items.extend_from_slice(&rlp::encode_u128(self.max_fee_per_gas));
265 items.extend_from_slice(&rlp::encode_u64(self.gas_limit));
266 items.extend_from_slice(&encode_address(&self.to));
267 items.extend_from_slice(&rlp::encode_u128(self.value));
268 items.extend_from_slice(&rlp::encode_bytes(&self.data));
269 items.extend_from_slice(&rlp::encode_access_list(&self.access_list));
270 items.extend_from_slice(&rlp::encode_u64(y_parity));
271 items.extend_from_slice(&rlp::encode_bytes(&strip_leading_zeros(&sig.r)));
272 items.extend_from_slice(&rlp::encode_bytes(&strip_leading_zeros(&sig.s)));
273
274 let mut raw = vec![0x02]; raw.extend_from_slice(&rlp::encode_list(&items));
276
277 Ok(SignedTransaction { raw })
278 }
279}
280
281#[derive(Debug, Clone)]
289pub struct EIP4844Transaction {
290 pub chain_id: u64,
292 pub nonce: u64,
294 pub max_priority_fee_per_gas: u128,
296 pub max_fee_per_gas: u128,
298 pub gas_limit: u64,
300 pub to: [u8; 20],
302 pub value: u128,
304 pub data: Vec<u8>,
306 pub access_list: Vec<([u8; 20], Vec<[u8; 32]>)>,
308 pub max_fee_per_blob_gas: u128,
310 pub blob_versioned_hashes: Vec<[u8; 32]>,
312}
313
314impl EIP4844Transaction {
315 fn signing_hash(&self) -> [u8; 32] {
317 let mut items = Vec::new();
318 items.extend_from_slice(&rlp::encode_u64(self.chain_id));
319 items.extend_from_slice(&rlp::encode_u64(self.nonce));
320 items.extend_from_slice(&rlp::encode_u128(self.max_priority_fee_per_gas));
321 items.extend_from_slice(&rlp::encode_u128(self.max_fee_per_gas));
322 items.extend_from_slice(&rlp::encode_u64(self.gas_limit));
323 items.extend_from_slice(&rlp::encode_bytes(&self.to));
324 items.extend_from_slice(&rlp::encode_u128(self.value));
325 items.extend_from_slice(&rlp::encode_bytes(&self.data));
326 items.extend_from_slice(&rlp::encode_access_list(&self.access_list));
327 items.extend_from_slice(&rlp::encode_u128(self.max_fee_per_blob_gas));
328 let mut hash_items = Vec::new();
330 for h in &self.blob_versioned_hashes {
331 hash_items.extend_from_slice(&rlp::encode_bytes(h));
332 }
333 items.extend_from_slice(&rlp::encode_list(&hash_items));
334
335 let mut payload = vec![0x03]; payload.extend_from_slice(&rlp::encode_list(&items));
337 keccak256(&payload)
338 }
339
340 pub fn sign(&self, signer: &EthereumSigner) -> Result<SignedTransaction, SignerError> {
342 let hash = self.signing_hash();
343 let sig = signer.sign_digest(&hash)?;
344 let y_parity = sig.v - 27;
345
346 let mut items = Vec::new();
347 items.extend_from_slice(&rlp::encode_u64(self.chain_id));
348 items.extend_from_slice(&rlp::encode_u64(self.nonce));
349 items.extend_from_slice(&rlp::encode_u128(self.max_priority_fee_per_gas));
350 items.extend_from_slice(&rlp::encode_u128(self.max_fee_per_gas));
351 items.extend_from_slice(&rlp::encode_u64(self.gas_limit));
352 items.extend_from_slice(&rlp::encode_bytes(&self.to));
353 items.extend_from_slice(&rlp::encode_u128(self.value));
354 items.extend_from_slice(&rlp::encode_bytes(&self.data));
355 items.extend_from_slice(&rlp::encode_access_list(&self.access_list));
356 items.extend_from_slice(&rlp::encode_u128(self.max_fee_per_blob_gas));
357 let mut hash_items = Vec::new();
358 for h in &self.blob_versioned_hashes {
359 hash_items.extend_from_slice(&rlp::encode_bytes(h));
360 }
361 items.extend_from_slice(&rlp::encode_list(&hash_items));
362 items.extend_from_slice(&rlp::encode_u64(y_parity));
363 items.extend_from_slice(&rlp::encode_bytes(&strip_leading_zeros(&sig.r)));
364 items.extend_from_slice(&rlp::encode_bytes(&strip_leading_zeros(&sig.s)));
365
366 let mut raw = vec![0x03];
367 raw.extend_from_slice(&rlp::encode_list(&items));
368
369 Ok(SignedTransaction { raw })
370 }
371}
372
373pub fn create_address(sender: &[u8; 20], nonce: u64) -> [u8; 20] {
379 let mut items = Vec::new();
380 items.extend_from_slice(&rlp::encode_bytes(sender));
381 items.extend_from_slice(&rlp::encode_u64(nonce));
382 let rlp_data = rlp::encode_list(&items);
383 let hash = keccak256(&rlp_data);
384 let mut addr = [0u8; 20];
385 addr.copy_from_slice(&hash[12..]);
386 addr
387}
388
389pub fn create2_address(sender: &[u8; 20], salt: &[u8; 32], init_code: &[u8]) -> [u8; 20] {
393 let code_hash = keccak256(init_code);
394 let mut buf = Vec::with_capacity(1 + 20 + 32 + 32);
395 buf.push(0xFF);
396 buf.extend_from_slice(sender);
397 buf.extend_from_slice(salt);
398 buf.extend_from_slice(&code_hash);
399 let hash = keccak256(&buf);
400 let mut addr = [0u8; 20];
401 addr.copy_from_slice(&hash[12..]);
402 addr
403}
404
405pub const EIP1271_MAGIC: [u8; 4] = [0x16, 0x26, 0xBA, 0x7E];
409
410pub fn encode_is_valid_signature(hash: &[u8; 32], signature: &[u8]) -> Vec<u8> {
414 let selector = &keccak256(b"isValidSignature(bytes32,bytes)")[..4];
416
417 let mut calldata = Vec::new();
418 calldata.extend_from_slice(selector);
419 calldata.extend_from_slice(hash);
421 let mut offset = [0u8; 32];
423 offset[31] = 64;
424 calldata.extend_from_slice(&offset);
425 let mut len_buf = [0u8; 32];
427 len_buf[28..32].copy_from_slice(&(signature.len() as u32).to_be_bytes());
428 calldata.extend_from_slice(&len_buf);
429 calldata.extend_from_slice(signature);
431 let padding = (32 - (signature.len() % 32)) % 32;
432 calldata.extend_from_slice(&vec![0u8; padding]);
433
434 calldata
435}
436
437fn keccak256(data: &[u8]) -> [u8; 32] {
440 let mut out = [0u8; 32];
441 out.copy_from_slice(&Keccak256::digest(data));
442 out
443}
444
445fn encode_address(to: &Option<[u8; 20]>) -> Vec<u8> {
446 match to {
447 Some(addr) => rlp::encode_bytes(addr),
448 None => rlp::encode_bytes(&[]),
449 }
450}
451
452fn strip_leading_zeros(data: &[u8; 32]) -> Vec<u8> {
453 let start = data.iter().position(|b| *b != 0).unwrap_or(31);
454 data[start..].to_vec()
455}
456
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461pub enum TxType {
462 Legacy,
464 Type1AccessList,
466 Type2DynamicFee,
468 Type3Blob,
470}
471
472#[derive(Debug, Clone)]
474pub struct DecodedTransaction {
475 pub tx_type: TxType,
477 pub chain_id: u64,
479 pub nonce: u64,
481 pub to: Option<[u8; 20]>,
483 pub value: Vec<u8>,
485 pub data: Vec<u8>,
487 pub gas_limit: u64,
489 pub gas_price_or_max_fee: Vec<u8>,
491 pub max_priority_fee: Vec<u8>,
493 pub v: u64,
495 pub r: [u8; 32],
497 pub s: [u8; 32],
499 pub from: [u8; 20],
501 pub tx_hash: [u8; 32],
503}
504
505pub fn decode_signed_tx(raw: &[u8]) -> Result<DecodedTransaction, SignerError> {
521 if raw.is_empty() {
522 return Err(SignerError::ParseError("empty transaction".into()));
523 }
524
525 let tx_hash = keccak256(raw);
526
527 match raw[0] {
528 0x01 => decode_type1_tx(raw, tx_hash),
530 0x02 => decode_type2_tx(raw, tx_hash),
531 0x03 => decode_type3_tx(raw, tx_hash),
532 0xC0..=0xFF => decode_legacy_tx(raw, tx_hash),
534 b => Err(SignerError::ParseError(format!(
535 "unknown tx type byte: 0x{b:02x}"
536 ))),
537 }
538}
539
540fn decode_legacy_tx(raw: &[u8], tx_hash: [u8; 32]) -> Result<DecodedTransaction, SignerError> {
541 let items = rlp::decode_list_items(raw)?;
542 if items.len() != 9 {
543 return Err(SignerError::ParseError(format!(
544 "legacy tx: expected 9 RLP items, got {}",
545 items.len()
546 )));
547 }
548
549 let nonce = items[0].as_u64()?;
550 let gas_price = items[1].as_bytes()?.to_vec();
551 let gas_limit = items[2].as_u64()?;
552 let to_bytes = items[3].as_bytes()?;
553 let to = if to_bytes.len() == 20 {
554 let mut addr = [0u8; 20];
555 addr.copy_from_slice(to_bytes);
556 Some(addr)
557 } else {
558 None
559 };
560 let value = items[4].as_bytes()?.to_vec();
561 let data = items[5].as_bytes()?.to_vec();
562 let v = items[6].as_u64()?;
563 let r = pad_to_32(items[7].as_bytes()?);
564 let s = pad_to_32(items[8].as_bytes()?);
565
566 let chain_id = if v >= 35 { (v - 35) / 2 } else { 0 };
568 let recovery_id = if v >= 35 { (v - 35) % 2 } else { v - 27 };
569
570 let mut sign_items = Vec::new();
572 sign_items.extend_from_slice(&rlp::encode_u64(nonce));
573 sign_items.extend_from_slice(&rlp::encode_bytes(&gas_price));
574 sign_items.extend_from_slice(&rlp::encode_u64(gas_limit));
575 sign_items.extend_from_slice(&encode_address(&to));
576 sign_items.extend_from_slice(&rlp::encode_bytes(&value));
577 sign_items.extend_from_slice(&rlp::encode_bytes(&data));
578 if chain_id > 0 {
579 sign_items.extend_from_slice(&rlp::encode_u64(chain_id));
580 sign_items.extend_from_slice(&rlp::encode_u64(0));
581 sign_items.extend_from_slice(&rlp::encode_u64(0));
582 }
583 let signing_hash = keccak256(&rlp::encode_list(&sign_items));
584
585 let from = recover_signer(&signing_hash, &r, &s, recovery_id as u8)?;
586
587 Ok(DecodedTransaction {
588 tx_type: TxType::Legacy,
589 chain_id,
590 nonce,
591 to,
592 value,
593 data,
594 gas_limit,
595 gas_price_or_max_fee: gas_price,
596 max_priority_fee: vec![],
597 v,
598 r,
599 s,
600 from,
601 tx_hash,
602 })
603}
604
605fn decode_type1_tx(raw: &[u8], tx_hash: [u8; 32]) -> Result<DecodedTransaction, SignerError> {
606 let items = rlp::decode_list_items(&raw[1..])?;
607 if items.len() != 11 {
608 return Err(SignerError::ParseError(format!(
609 "type1 tx: expected 11 items, got {}",
610 items.len()
611 )));
612 }
613
614 let chain_id = items[0].as_u64()?;
615 let nonce = items[1].as_u64()?;
616 let gas_price = items[2].as_bytes()?.to_vec();
617 let gas_limit = items[3].as_u64()?;
618 let to_bytes = items[4].as_bytes()?;
619 let to = if to_bytes.len() == 20 {
620 let mut a = [0u8; 20];
621 a.copy_from_slice(to_bytes);
622 Some(a)
623 } else {
624 None
625 };
626 let value = items[5].as_bytes()?.to_vec();
627 let data = items[6].as_bytes()?.to_vec();
628 let y_parity = items[8].as_u64()?;
630 let r = pad_to_32(items[9].as_bytes()?);
631 let s = pad_to_32(items[10].as_bytes()?);
632
633 let mut sign_items = Vec::new();
635 sign_items.extend_from_slice(&rlp::encode_u64(chain_id));
636 sign_items.extend_from_slice(&rlp::encode_u64(nonce));
637 sign_items.extend_from_slice(&rlp::encode_bytes(&gas_price));
638 sign_items.extend_from_slice(&rlp::encode_u64(gas_limit));
639 sign_items.extend_from_slice(&encode_address(&to));
640 sign_items.extend_from_slice(&rlp::encode_bytes(&value));
641 sign_items.extend_from_slice(&rlp::encode_bytes(&data));
642 sign_items.extend_from_slice(&re_encode_rlp_item(&items[7]));
644 let mut payload = vec![0x01];
645 payload.extend_from_slice(&rlp::encode_list(&sign_items));
646 let signing_hash = keccak256(&payload);
647
648 let from = recover_signer(&signing_hash, &r, &s, y_parity as u8)?;
649
650 Ok(DecodedTransaction {
651 tx_type: TxType::Type1AccessList,
652 chain_id,
653 nonce,
654 to,
655 value,
656 data,
657 gas_limit,
658 gas_price_or_max_fee: gas_price,
659 max_priority_fee: vec![],
660 v: y_parity,
661 r,
662 s,
663 from,
664 tx_hash,
665 })
666}
667
668fn decode_type2_tx(raw: &[u8], tx_hash: [u8; 32]) -> Result<DecodedTransaction, SignerError> {
669 let items = rlp::decode_list_items(&raw[1..])?;
670 if items.len() != 12 {
671 return Err(SignerError::ParseError(format!(
672 "type2 tx: expected 12 items, got {}",
673 items.len()
674 )));
675 }
676
677 let chain_id = items[0].as_u64()?;
678 let nonce = items[1].as_u64()?;
679 let max_priority_fee = items[2].as_bytes()?.to_vec();
680 let max_fee = items[3].as_bytes()?.to_vec();
681 let gas_limit = items[4].as_u64()?;
682 let to_bytes = items[5].as_bytes()?;
683 let to = if to_bytes.len() == 20 {
684 let mut a = [0u8; 20];
685 a.copy_from_slice(to_bytes);
686 Some(a)
687 } else {
688 None
689 };
690 let value = items[6].as_bytes()?.to_vec();
691 let data = items[7].as_bytes()?.to_vec();
692 let y_parity = items[9].as_u64()?;
694 let r = pad_to_32(items[10].as_bytes()?);
695 let s = pad_to_32(items[11].as_bytes()?);
696
697 let mut sign_items = Vec::new();
699 sign_items.extend_from_slice(&rlp::encode_u64(chain_id));
700 sign_items.extend_from_slice(&rlp::encode_u64(nonce));
701 sign_items.extend_from_slice(&rlp::encode_bytes(&max_priority_fee));
702 sign_items.extend_from_slice(&rlp::encode_bytes(&max_fee));
703 sign_items.extend_from_slice(&rlp::encode_u64(gas_limit));
704 sign_items.extend_from_slice(&encode_address(&to));
705 sign_items.extend_from_slice(&rlp::encode_bytes(&value));
706 sign_items.extend_from_slice(&rlp::encode_bytes(&data));
707 sign_items.extend_from_slice(&re_encode_rlp_item(&items[8]));
708 let mut payload = vec![0x02];
709 payload.extend_from_slice(&rlp::encode_list(&sign_items));
710 let signing_hash = keccak256(&payload);
711
712 let from = recover_signer(&signing_hash, &r, &s, y_parity as u8)?;
713
714 Ok(DecodedTransaction {
715 tx_type: TxType::Type2DynamicFee,
716 chain_id,
717 nonce,
718 to,
719 value,
720 data,
721 gas_limit,
722 gas_price_or_max_fee: max_fee,
723 max_priority_fee,
724 v: y_parity,
725 r,
726 s,
727 from,
728 tx_hash,
729 })
730}
731
732fn decode_type3_tx(raw: &[u8], tx_hash: [u8; 32]) -> Result<DecodedTransaction, SignerError> {
733 let items = rlp::decode_list_items(&raw[1..])?;
734 if items.len() != 14 {
735 return Err(SignerError::ParseError(format!(
736 "type3 tx: expected 14 items, got {}",
737 items.len()
738 )));
739 }
740
741 let chain_id = items[0].as_u64()?;
742 let nonce = items[1].as_u64()?;
743 let max_priority_fee = items[2].as_bytes()?.to_vec();
744 let max_fee = items[3].as_bytes()?.to_vec();
745 let gas_limit = items[4].as_u64()?;
746 let to_bytes = items[5].as_bytes()?;
747 let to = if to_bytes.len() == 20 {
748 let mut a = [0u8; 20];
749 a.copy_from_slice(to_bytes);
750 Some(a)
751 } else {
752 None
753 };
754 let value = items[6].as_bytes()?.to_vec();
755 let data = items[7].as_bytes()?.to_vec();
756 let y_parity = items[11].as_u64()?;
758 let r = pad_to_32(items[12].as_bytes()?);
759 let s = pad_to_32(items[13].as_bytes()?);
760
761 let mut sign_items = Vec::new();
763 sign_items.extend_from_slice(&rlp::encode_u64(chain_id));
764 sign_items.extend_from_slice(&rlp::encode_u64(nonce));
765 sign_items.extend_from_slice(&rlp::encode_bytes(&max_priority_fee));
766 sign_items.extend_from_slice(&rlp::encode_bytes(&max_fee));
767 sign_items.extend_from_slice(&rlp::encode_u64(gas_limit));
768 sign_items.extend_from_slice(&encode_address(&to));
769 sign_items.extend_from_slice(&rlp::encode_bytes(&value));
770 sign_items.extend_from_slice(&rlp::encode_bytes(&data));
771 sign_items.extend_from_slice(&re_encode_rlp_item(&items[8]));
772 sign_items.extend_from_slice(&re_encode_rlp_item(&items[9]));
773 sign_items.extend_from_slice(&re_encode_rlp_item(&items[10]));
774 let mut payload = vec![0x03];
775 payload.extend_from_slice(&rlp::encode_list(&sign_items));
776 let signing_hash = keccak256(&payload);
777
778 let from = recover_signer(&signing_hash, &r, &s, y_parity as u8)?;
779
780 Ok(DecodedTransaction {
781 tx_type: TxType::Type3Blob,
782 chain_id,
783 nonce,
784 to,
785 value,
786 data,
787 gas_limit,
788 gas_price_or_max_fee: max_fee,
789 max_priority_fee,
790 v: y_parity,
791 r,
792 s,
793 from,
794 tx_hash,
795 })
796}
797
798fn re_encode_rlp_item(item: &rlp::RlpItem) -> Vec<u8> {
800 match item {
801 rlp::RlpItem::Bytes(b) => rlp::encode_bytes(b),
802 rlp::RlpItem::List(items) => {
803 let mut inner = Vec::new();
804 for i in items {
805 inner.extend_from_slice(&re_encode_rlp_item(i));
806 }
807 rlp::encode_list(&inner)
808 }
809 }
810}
811
812fn recover_signer(
814 hash: &[u8; 32],
815 r: &[u8; 32],
816 s: &[u8; 32],
817 recovery_id: u8,
818) -> Result<[u8; 20], SignerError> {
819 use k256::ecdsa::{RecoveryId, Signature as K256Signature, VerifyingKey};
820
821 let mut sig_bytes = [0u8; 64];
822 sig_bytes[..32].copy_from_slice(r);
823 sig_bytes[32..].copy_from_slice(s);
824 let sig = K256Signature::from_bytes((&sig_bytes).into())
825 .map_err(|e| SignerError::InvalidSignature(format!("invalid sig: {e}")))?;
826 let rid = RecoveryId::new(recovery_id & 1 != 0, false);
827 let key = VerifyingKey::recover_from_prehash(hash, &sig, rid)
828 .map_err(|e| SignerError::InvalidSignature(format!("ecrecover: {e}")))?;
829
830 let uncompressed = key.to_encoded_point(false);
831 let pub_bytes = &uncompressed.as_bytes()[1..]; let addr_hash = keccak256(pub_bytes);
833 let mut addr = [0u8; 20];
834 addr.copy_from_slice(&addr_hash[12..]);
835 Ok(addr)
836}
837
838fn pad_to_32(data: &[u8]) -> [u8; 32] {
839 let mut buf = [0u8; 32];
840 if data.len() <= 32 {
841 buf[32 - data.len()..].copy_from_slice(data);
842 }
843 buf
844}
845
846#[cfg(test)]
847#[allow(clippy::unwrap_used, clippy::expect_used)]
848mod tests {
849 use super::*;
850 use crate::traits::KeyPair;
851
852 #[test]
853 fn test_legacy_tx_sign_recoverable() {
854 let signer = EthereumSigner::generate().unwrap();
855 let tx = LegacyTransaction {
856 nonce: 0,
857 gas_price: 20_000_000_000, gas_limit: 21_000,
859 to: Some([0xBB; 20]),
860 value: 1_000_000_000_000_000_000, data: vec![],
862 chain_id: 1,
863 };
864 let signed = tx.sign(&signer).unwrap();
865 let raw = signed.raw_tx();
866 assert!(!raw.is_empty());
867 let decoded = rlp::decode(raw).unwrap();
869 let items = decoded.as_list().unwrap();
870 assert_eq!(items.len(), 9); }
872
873 #[test]
874 fn test_legacy_tx_hash_deterministic() {
875 let signer = EthereumSigner::from_bytes(&[0x42; 32]).unwrap();
876 let tx = LegacyTransaction {
877 nonce: 5,
878 gas_price: 30_000_000_000,
879 gas_limit: 21_000,
880 to: Some([0xCC; 20]),
881 value: 0,
882 data: vec![0xDE, 0xAD],
883 chain_id: 1,
884 };
885 let signed1 = tx.sign(&signer).unwrap();
886 let signed2 = tx.sign(&signer).unwrap();
887 assert_eq!(signed1.tx_hash(), signed2.tx_hash());
889 }
890
891 #[test]
892 fn test_legacy_contract_creation() {
893 let signer = EthereumSigner::generate().unwrap();
894 let tx = LegacyTransaction {
895 nonce: 0,
896 gas_price: 20_000_000_000,
897 gas_limit: 1_000_000,
898 to: None, value: 0,
900 data: vec![0x60, 0x00], chain_id: 1,
902 };
903 let signed = tx.sign(&signer).unwrap();
904 assert!(!signed.raw_tx().is_empty());
905 }
906
907 #[test]
908 fn test_eip2930_tx_type1_prefix() {
909 let signer = EthereumSigner::generate().unwrap();
910 let tx = EIP2930Transaction {
911 chain_id: 1,
912 nonce: 0,
913 gas_price: 20_000_000_000,
914 gas_limit: 21_000,
915 to: Some([0xAA; 20]),
916 value: 1_000_000_000_000_000_000,
917 data: vec![],
918 access_list: vec![([0xDD; 20], vec![[0xEE; 32]])],
919 };
920 let signed = tx.sign(&signer).unwrap();
921 assert_eq!(signed.raw_tx()[0], 0x01, "Type 1 prefix");
922 }
923
924 #[test]
925 fn test_eip1559_tx_type2_prefix() {
926 let signer = EthereumSigner::generate().unwrap();
927 let tx = EIP1559Transaction {
928 chain_id: 1,
929 nonce: 0,
930 max_priority_fee_per_gas: 2_000_000_000,
931 max_fee_per_gas: 100_000_000_000,
932 gas_limit: 21_000,
933 to: Some([0xAA; 20]),
934 value: 1_000_000_000_000_000_000,
935 data: vec![],
936 access_list: vec![],
937 };
938 let signed = tx.sign(&signer).unwrap();
939 assert_eq!(signed.raw_tx()[0], 0x02, "Type 2 prefix");
940 }
941
942 #[test]
943 fn test_eip1559_different_nonces_different_hashes() {
944 let signer = EthereumSigner::from_bytes(&[0x42; 32]).unwrap();
945 let base = EIP1559Transaction {
946 chain_id: 1,
947 nonce: 0,
948 max_priority_fee_per_gas: 2_000_000_000,
949 max_fee_per_gas: 100_000_000_000,
950 gas_limit: 21_000,
951 to: Some([0xAA; 20]),
952 value: 0,
953 data: vec![],
954 access_list: vec![],
955 };
956 let mut tx2 = base.clone();
957 tx2.nonce = 1;
958 let h1 = base.sign(&signer).unwrap().tx_hash();
959 let h2 = tx2.sign(&signer).unwrap().tx_hash();
960 assert_ne!(h1, h2);
961 }
962
963 #[test]
964 fn test_eip4844_tx_type3_prefix() {
965 let signer = EthereumSigner::generate().unwrap();
966 let tx = EIP4844Transaction {
967 chain_id: 1,
968 nonce: 0,
969 max_priority_fee_per_gas: 2_000_000_000,
970 max_fee_per_gas: 100_000_000_000,
971 gas_limit: 21_000,
972 to: [0xAA; 20],
973 value: 0,
974 data: vec![],
975 access_list: vec![],
976 max_fee_per_blob_gas: 1_000_000_000,
977 blob_versioned_hashes: vec![[0x01; 32]],
978 };
979 let signed = tx.sign(&signer).unwrap();
980 assert_eq!(signed.raw_tx()[0], 0x03, "Type 3 prefix");
981 }
982
983 #[test]
984 fn test_create_address_known_vector() {
985 let sender = [0u8; 20];
987 let addr = create_address(&sender, 0);
988 assert_eq!(addr.len(), 20);
989 assert_eq!(addr, create_address(&sender, 0));
991 assert_ne!(addr, create_address(&sender, 1));
993 }
994
995 #[test]
996 fn test_create2_address_eip1014_vector() {
997 let sender = [0u8; 20];
1003 let salt = [0u8; 32];
1004 let addr = create2_address(&sender, &salt, &[0x00]);
1005 assert_eq!(addr, create2_address(&sender, &salt, &[0x00]));
1007 assert_ne!(addr, create2_address(&sender, &salt, &[0x01]));
1009 }
1010
1011 #[test]
1012 fn test_eip1271_encode() {
1013 let hash = [0xAA; 32];
1014 let sig = vec![0xBB; 65];
1015 let calldata = encode_is_valid_signature(&hash, &sig);
1016 assert_eq!(
1018 &calldata[..4],
1019 &keccak256(b"isValidSignature(bytes32,bytes)")[..4]
1020 );
1021 assert_eq!(&calldata[4..36], &hash);
1023 }
1024
1025 #[test]
1026 fn test_raw_tx_hex_format() {
1027 let signer = EthereumSigner::generate().unwrap();
1028 let tx = EIP1559Transaction {
1029 chain_id: 1,
1030 nonce: 0,
1031 max_priority_fee_per_gas: 0,
1032 max_fee_per_gas: 0,
1033 gas_limit: 21_000,
1034 to: Some([0; 20]),
1035 value: 0,
1036 data: vec![],
1037 access_list: vec![],
1038 };
1039 let hex = tx.sign(&signer).unwrap().raw_tx_hex();
1040 assert!(hex.starts_with("0x02"), "should start with 0x02");
1041 }
1042
1043 #[test]
1044 fn test_signed_tx_hash_is_keccak_of_raw() {
1045 let signer = EthereumSigner::generate().unwrap();
1046 let tx = EIP1559Transaction {
1047 chain_id: 1,
1048 nonce: 42,
1049 max_priority_fee_per_gas: 1_000_000,
1050 max_fee_per_gas: 50_000_000_000,
1051 gas_limit: 100_000,
1052 to: Some([0xFF; 20]),
1053 value: 500_000_000_000_000,
1054 data: vec![0x01, 0x02, 0x03],
1055 access_list: vec![],
1056 };
1057 let signed = tx.sign(&signer).unwrap();
1058 let expected = keccak256(signed.raw_tx());
1059 assert_eq!(signed.tx_hash(), expected);
1060 }
1061
1062 #[test]
1065 fn test_decode_legacy_roundtrip() {
1066 let signer = EthereumSigner::from_bytes(&[0x42; 32]).unwrap();
1067 let tx = LegacyTransaction {
1068 nonce: 7,
1069 gas_price: 20_000_000_000,
1070 gas_limit: 21_000,
1071 to: Some([0xBB; 20]),
1072 value: 1_000_000_000_000_000_000,
1073 data: vec![0xAB, 0xCD],
1074 chain_id: 1,
1075 };
1076 let signed = tx.sign(&signer).unwrap();
1077 let decoded = decode_signed_tx(signed.raw_tx()).unwrap();
1078
1079 assert_eq!(decoded.tx_type, TxType::Legacy);
1080 assert_eq!(decoded.chain_id, 1);
1081 assert_eq!(decoded.nonce, 7);
1082 assert_eq!(decoded.gas_limit, 21_000);
1083 assert_eq!(decoded.to, Some([0xBB; 20]));
1084 assert_eq!(decoded.data, vec![0xAB, 0xCD]);
1085 assert_eq!(decoded.from, signer.address());
1086 assert_eq!(decoded.tx_hash, signed.tx_hash());
1087 }
1088
1089 #[test]
1090 fn test_decode_type1_roundtrip() {
1091 let signer = EthereumSigner::from_bytes(&[0x42; 32]).unwrap();
1092 let tx = EIP2930Transaction {
1093 chain_id: 1,
1094 nonce: 3,
1095 gas_price: 30_000_000_000,
1096 gas_limit: 50_000,
1097 to: Some([0xCC; 20]),
1098 value: 0,
1099 data: vec![0x01],
1100 access_list: vec![([0xDD; 20], vec![[0xEE; 32]])],
1101 };
1102 let signed = tx.sign(&signer).unwrap();
1103 let decoded = decode_signed_tx(signed.raw_tx()).unwrap();
1104
1105 assert_eq!(decoded.tx_type, TxType::Type1AccessList);
1106 assert_eq!(decoded.chain_id, 1);
1107 assert_eq!(decoded.nonce, 3);
1108 assert_eq!(decoded.from, signer.address());
1109 }
1110
1111 #[test]
1112 fn test_decode_type2_roundtrip() {
1113 let signer = EthereumSigner::from_bytes(&[0x42; 32]).unwrap();
1114 let tx = EIP1559Transaction {
1115 chain_id: 1,
1116 nonce: 42,
1117 max_priority_fee_per_gas: 2_000_000_000,
1118 max_fee_per_gas: 100_000_000_000,
1119 gas_limit: 21_000,
1120 to: Some([0xAA; 20]),
1121 value: 500_000_000_000_000,
1122 data: vec![],
1123 access_list: vec![],
1124 };
1125 let signed = tx.sign(&signer).unwrap();
1126 let decoded = decode_signed_tx(signed.raw_tx()).unwrap();
1127
1128 assert_eq!(decoded.tx_type, TxType::Type2DynamicFee);
1129 assert_eq!(decoded.chain_id, 1);
1130 assert_eq!(decoded.nonce, 42);
1131 assert_eq!(decoded.gas_limit, 21_000);
1132 assert_eq!(decoded.to, Some([0xAA; 20]));
1133 assert_eq!(decoded.from, signer.address());
1134 assert_eq!(decoded.tx_hash, signed.tx_hash());
1135 }
1136
1137 #[test]
1138 fn test_decode_type3_roundtrip() {
1139 let signer = EthereumSigner::from_bytes(&[0x42; 32]).unwrap();
1140 let tx = EIP4844Transaction {
1141 chain_id: 1,
1142 nonce: 0,
1143 max_priority_fee_per_gas: 1_000_000_000,
1144 max_fee_per_gas: 50_000_000_000,
1145 gas_limit: 100_000,
1146 to: [0xFF; 20],
1147 value: 0,
1148 data: vec![],
1149 access_list: vec![],
1150 max_fee_per_blob_gas: 1_000_000_000,
1151 blob_versioned_hashes: vec![[0x01; 32]],
1152 };
1153 let signed = tx.sign(&signer).unwrap();
1154 let decoded = decode_signed_tx(signed.raw_tx()).unwrap();
1155
1156 assert_eq!(decoded.tx_type, TxType::Type3Blob);
1157 assert_eq!(decoded.from, signer.address());
1158 assert_eq!(decoded.chain_id, 1);
1159 }
1160
1161 #[test]
1162 fn test_decode_contract_creation() {
1163 let signer = EthereumSigner::from_bytes(&[0x42; 32]).unwrap();
1164 let tx = LegacyTransaction {
1165 nonce: 0,
1166 gas_price: 20_000_000_000,
1167 gas_limit: 1_000_000,
1168 to: None,
1169 value: 0,
1170 data: vec![0x60, 0x00],
1171 chain_id: 1,
1172 };
1173 let signed = tx.sign(&signer).unwrap();
1174 let decoded = decode_signed_tx(signed.raw_tx()).unwrap();
1175
1176 assert_eq!(decoded.to, None, "contract creation has no 'to'");
1177 assert_eq!(decoded.from, signer.address());
1178 }
1179
1180 #[test]
1181 fn test_decode_empty_tx_rejected() {
1182 assert!(decode_signed_tx(&[]).is_err());
1183 }
1184
1185 #[test]
1186 fn test_decode_unknown_type_rejected() {
1187 assert!(decode_signed_tx(&[0x04, 0x00]).is_err());
1188 }
1189
1190 #[test]
1191 fn test_decode_signer_matches_across_types() {
1192 let signer = EthereumSigner::from_bytes(&[0x42; 32]).unwrap();
1194 let expected_addr = signer.address();
1195
1196 let legacy = LegacyTransaction {
1197 nonce: 0,
1198 gas_price: 1,
1199 gas_limit: 21000,
1200 to: Some([0xAA; 20]),
1201 value: 0,
1202 data: vec![],
1203 chain_id: 1,
1204 }
1205 .sign(&signer)
1206 .unwrap();
1207
1208 let type2 = EIP1559Transaction {
1209 chain_id: 1,
1210 nonce: 0,
1211 max_priority_fee_per_gas: 1,
1212 max_fee_per_gas: 1,
1213 gas_limit: 21000,
1214 to: Some([0xAA; 20]),
1215 value: 0,
1216 data: vec![],
1217 access_list: vec![],
1218 }
1219 .sign(&signer)
1220 .unwrap();
1221
1222 assert_eq!(
1223 decode_signed_tx(legacy.raw_tx()).unwrap().from,
1224 expected_addr
1225 );
1226 assert_eq!(
1227 decode_signed_tx(type2.raw_tx()).unwrap().from,
1228 expected_addr
1229 );
1230 }
1231}