Skip to main content

om_primitives_types/transaction/
mod.rs

1pub mod hashing;
2
3use alloy_primitives::{TxHash, keccak256};
4use alloy_rlp::Encodable;
5pub use hashing::*;
6pub mod openapi;
7pub use openapi::*;
8
9pub mod envelope;
10pub mod payload;
11
12pub mod validate;
13pub use validate::*;
14
15mod signed;
16pub use signed::{B264, MultiSigSignatureEntry, SigError, Signature, SignatureType, Signed, SignerRecoverable};
17
18pub trait TxHashable: Encodable {
19    /// Compute transaction hash for a single signature (standard Ethereum
20    /// format)
21    fn tx_hash(&self, signature: &Signature) -> TxHash {
22        let mut buf = Vec::new();
23        let out = &mut buf;
24
25        let payload_length = self.length() + signature.rlp_rs_len() + signature.v().length();
26        let header = alloy_rlp::Header {
27            list: true,
28            payload_length,
29        };
30        header.encode(out);
31
32        self.encode(out);
33        signature.write_rlp_vrs(out, signature.v());
34
35        keccak256(&buf)
36    }
37
38    /// Compute transaction hash for any signature type (single or multi-sig)
39    ///
40    /// Note: multi-sig intentionally hashes the RLP-encoded payload bytes
41    /// concatenated with raw signature bytes (no list header) to ensure a
42    /// distinct TxHash from single-sig. Empty multi-sig signature sets are
43    /// rejected during multi-sig verification (MultiSigError::NoSignatures).
44    fn tx_hash_with_signature_type(&self, signature_type: &SignatureType) -> TxHash {
45        match signature_type {
46            SignatureType::Single(sig) => self.tx_hash(sig),
47            SignatureType::Multi { signatures, .. } => {
48                // For multi-sig: hash(payload + all signatures)
49                let mut buf = Vec::new();
50                self.encode(&mut buf);
51                for sig_entry in signatures {
52                    buf.extend_from_slice(&sig_entry.signature.as_bytes());
53                }
54                keccak256(&buf)
55            }
56        }
57    }
58}
59
60/// Auto implement the `TxHashable` trait for all types that implement
61/// `Encodable`.
62impl<T: Encodable> TxHashable for T {}
63
64#[cfg(test)]
65mod tests {
66    use alloy_primitives::{Address, U256};
67    use alloy_rlp::Encodable;
68
69    use super::*;
70    use crate::transaction::payload::PaymentPayload;
71
72    fn sample_payload() -> PaymentPayload {
73        PaymentPayload {
74            chain_id: 1,
75            nonce: 7,
76            recipient: Address::from([0x11u8; 20]),
77            value: U256::from(42u64),
78            token: Address::from([0x22u8; 20]),
79        }
80    }
81
82    fn make_signature(v: u8) -> Signature {
83        let mut bytes = [0u8; 65];
84        bytes[0] = 1;
85        bytes[32] = 2;
86        bytes[64] = v;
87        Signature::try_from(bytes.as_slice()).expect("signature bytes should parse")
88    }
89
90    #[test]
91    fn test_tx_hash_single_vs_multi() {
92        let payload = sample_payload();
93        let single_sig = make_signature(27);
94        let single_hash = payload.tx_hash(&single_sig);
95
96        let signatures = vec![
97            MultiSigSignatureEntry::new(B264::repeat_byte(0x02), make_signature(27)),
98            MultiSigSignatureEntry::new(B264::repeat_byte(0x03), make_signature(28)),
99        ];
100        let multi_sig = SignatureType::Multi {
101            account: Address::from([0x33u8; 20]),
102            signatures: signatures.clone(),
103        };
104        let multi_hash = payload.tx_hash_with_signature_type(&multi_sig);
105
106        let mut buf = Vec::new();
107        payload.encode(&mut buf);
108        for entry in &signatures {
109            buf.extend_from_slice(&entry.signature.as_bytes());
110        }
111        let expected_multi_hash = keccak256(&buf);
112
113        assert_eq!(multi_hash, expected_multi_hash);
114        assert_ne!(single_hash, multi_hash);
115    }
116
117    #[test]
118    fn test_tx_hash_multi_no_signatures() {
119        let payload = sample_payload();
120        let multi_sig = SignatureType::Multi {
121            account: Address::from([0x44u8; 20]),
122            signatures: Vec::new(),
123        };
124        let hash = payload.tx_hash_with_signature_type(&multi_sig);
125
126        let mut buf = Vec::new();
127        payload.encode(&mut buf);
128        let expected = keccak256(&buf);
129
130        assert_eq!(hash, expected);
131    }
132}