om_primitives_types/transaction/
hashing.rs1use alloy_primitives::{Address, B256, keccak256};
4use alloy_rlp::Encodable;
5
6pub trait Signable {
8 fn signature_hash(&self) -> B256;
10}
11
12pub fn multisig_signature_hash<T: Encodable>(payload: &T, multisig_account: Address) -> B256 {
15 let mut encoded = Vec::new();
16 payload.encode(&mut encoded);
17 encoded.extend_from_slice(multisig_account.as_slice());
18 keccak256(&encoded)
19}
20
21#[cfg(test)]
22mod tests {
23 use std::{str::FromStr, time::Instant};
24
25 use alloy::primitives::{Address, U256};
26
27 use super::*;
28 use crate::transaction::payload::{PaymentPayload, TokenBurnPayload, TokenMintPayload};
29
30 #[test]
31 fn test_signable_trait_consistency() {
32 let token_address = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Valid address");
34 let recipient = Address::from_str("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd").expect("Valid address");
35
36 let payload = TokenMintPayload {
37 chain_id: 1,
38 nonce: 1,
39 token: token_address,
40 recipient,
41 value: U256::from(1000000000000000000u64),
42 };
43
44 let hash1 = payload.signature_hash();
46 let hash2 = payload.signature_hash();
47 let hash3 = payload.signature_hash();
48
49 assert_eq!(hash1, hash2, "Hash should be consistent across calls");
50 assert_eq!(hash2, hash3, "Hash should be consistent across calls");
51 assert_eq!(hash1, hash3, "Hash should be consistent across calls");
52 }
53
54 #[test]
55 fn test_signable_trait_determinism() {
56 let token_address = Address::from_str("0x1111111111111111111111111111111111111111").expect("Valid address");
58 let recipient = Address::from_str("0x2222222222222222222222222222222222222222").expect("Valid address");
59
60 let payload1 = TokenMintPayload {
61 chain_id: 42,
62 nonce: 5,
63 token: token_address,
64 recipient,
65 value: U256::from(2000000000000000000u64),
66 };
67
68 let payload2 = TokenMintPayload {
70 chain_id: 42,
71 nonce: 5,
72 token: token_address,
73 recipient,
74 value: U256::from(2000000000000000000u64),
75 };
76
77 let hash1 = payload1.signature_hash();
78 let hash2 = payload2.signature_hash();
79
80 assert_eq!(hash1, hash2, "Identical payloads should produce identical hashes");
81 }
82
83 #[test]
84 fn test_different_payloads_different_hashes() {
85 let token_address = Address::from_str("0x3333333333333333333333333333333333333333").expect("Valid address");
87 let recipient = Address::from_str("0x4444444444444444444444444444444444444444").expect("Valid address");
88
89 let base_payload = TokenMintPayload {
90 chain_id: 1,
91 nonce: 1,
92 token: token_address,
93 recipient,
94 value: U256::from(1000000000000000000u64),
95 };
96
97 let mut different_nonce = base_payload.clone();
99 different_nonce.nonce = 2;
100
101 let mut different_value = base_payload.clone();
102 different_value.value = U256::from(2000000000000000000u64);
103
104 let mut different_chain = base_payload.clone();
105 different_chain.chain_id = 2;
106
107 let hashes = [
108 base_payload.signature_hash(),
109 different_nonce.signature_hash(),
110 different_value.signature_hash(),
111 different_chain.signature_hash(),
112 ];
113
114 for i in 0..hashes.len() {
116 for j in (i + 1)..hashes.len() {
117 assert_ne!(
118 hashes[i], hashes[j],
119 "Different payloads should produce different hashes (indices {} and {})",
120 i, j
121 );
122 }
123 }
124 }
125
126 #[test]
127 fn test_signature_hash_length() {
128 let token_address = Address::ZERO;
130 let recipient = Address::from([0xFF; 20]);
131
132 let payloads: Vec<Box<dyn Signable>> = vec![
133 Box::new(TokenMintPayload {
134 chain_id: 1,
135 nonce: 1,
136 token: token_address,
137 recipient,
138 value: U256::from(1u64),
139 }),
140 Box::new(TokenBurnPayload {
141 chain_id: 1,
142 nonce: 1,
143 token: token_address,
144 value: U256::from(1u64),
145 }),
146 Box::new(PaymentPayload {
147 chain_id: 1,
148 nonce: 1,
149 recipient,
150 value: U256::from(1u64),
151 token: token_address,
152 }),
153 ];
154
155 for (i, payload) in payloads.iter().enumerate() {
156 let hash = payload.signature_hash();
157 assert_eq!(
158 hash.len(),
159 32,
160 "Signature hash should be exactly 32 bytes for payload type {}",
161 i
162 );
163 }
164 }
165
166 #[test]
167 fn test_signature_hash_performance() {
168 let token_address = Address::from_str("0x5555555555555555555555555555555555555555").expect("Valid address");
170 let recipient = Address::from_str("0x6666666666666666666666666666666666666666").expect("Valid address");
171
172 let payload = TokenMintPayload {
173 chain_id: 1,
174 nonce: 1,
175 token: token_address,
176 recipient,
177 value: U256::from(1000000000000000000u64),
178 };
179
180 let iterations = 1000;
181 let start = Instant::now();
182
183 for _ in 0..iterations {
184 let _hash = payload.signature_hash();
185 }
186
187 let duration = start.elapsed();
188 let avg_time = duration / iterations;
189
190 assert!(
192 avg_time.as_millis() < 1,
193 "Hash calculation too slow: {:?} per operation",
194 avg_time
195 );
196
197 println!(
198 "Performance test: {} hashes in {:?} (avg: {:?})",
199 iterations, duration, avg_time
200 );
201 }
202
203 #[test]
204 fn test_signable_trait_different_payload_types() {
205 let token_address = Address::from_str("0x7777777777777777777777777777777777777777").expect("Valid address");
207 let recipient = Address::from_str("0x8888888888888888888888888888888888888888").expect("Valid address");
208
209 let mint_payload = TokenMintPayload {
210 chain_id: 1,
211 nonce: 1,
212 token: token_address,
213 recipient,
214 value: U256::from(1000u64),
215 };
216
217 let burn_payload = TokenBurnPayload {
218 chain_id: 1,
219 nonce: 1,
220 token: token_address,
221 value: U256::from(500u64), };
223
224 let payment_payload = PaymentPayload {
225 chain_id: 1,
226 nonce: 2, recipient,
228 value: U256::from(1000u64),
229 token: token_address,
230 };
231
232 let mint_hash = mint_payload.signature_hash();
234 let burn_hash = burn_payload.signature_hash();
235 let payment_hash = payment_payload.signature_hash();
236
237 assert_eq!(mint_hash.len(), 32);
239 assert_eq!(burn_hash.len(), 32);
240 assert_eq!(payment_hash.len(), 32);
241
242 assert_ne!(mint_hash, burn_hash);
246 assert_ne!(mint_hash, payment_hash);
247
248 if burn_hash == payment_hash {
251 println!("Warning: TokenBurnPayload and PaymentPayload produce identical hashes");
252 println!("This indicates identical RLP encoding despite different types");
253 } else {
254 assert_ne!(burn_hash, payment_hash);
255 }
256 }
257
258 #[test]
259 fn test_signable_extreme_values() {
260 let token_address = Address::from([0xFF; 20]);
262 let recipient = Address::ZERO;
263
264 let extreme_payloads = [
265 TokenMintPayload {
267 chain_id: u64::MAX,
268 nonce: u64::MAX,
269 token: token_address,
270 recipient,
271 value: U256::MAX,
272 },
273 TokenMintPayload {
275 chain_id: 0,
276 nonce: 0,
277 token: Address::ZERO,
278 recipient: Address::ZERO,
279 value: U256::ZERO,
280 },
281 ];
282
283 for (i, payload) in extreme_payloads.iter().enumerate() {
284 let hash = payload.signature_hash();
285 assert_eq!(
286 hash.len(),
287 32,
288 "Extreme values should produce valid 32-byte hash for payload {}",
289 i
290 );
291 }
292
293 let hash1 = extreme_payloads[0].signature_hash();
295 let hash2 = extreme_payloads[1].signature_hash();
296 assert_ne!(hash1, hash2, "Extreme payloads should produce different hashes");
297 }
298
299 #[test]
300 fn test_signable_field_sensitivity() {
301 let base_payload = TokenMintPayload {
303 chain_id: 1,
304 nonce: 1,
305 token: Address::from_str("0x1111111111111111111111111111111111111111").unwrap(),
306 recipient: Address::from_str("0x2222222222222222222222222222222222222222").unwrap(),
307 value: U256::from(1000u64),
308 };
309
310 let base_hash = base_payload.signature_hash();
311
312 let field_variants = [
314 TokenMintPayload {
315 chain_id: 2,
316 ..base_payload
317 },
318 TokenMintPayload {
319 nonce: 2,
320 ..base_payload
321 },
322 TokenMintPayload {
323 token: Address::from_str("0x3333333333333333333333333333333333333333").unwrap(),
324 ..base_payload
325 },
326 TokenMintPayload {
327 recipient: Address::from_str("0x4444444444444444444444444444444444444444").unwrap(),
328 ..base_payload
329 },
330 TokenMintPayload {
331 value: U256::from(2000u64),
332 ..base_payload
333 },
334 ];
335
336 for (i, variant) in field_variants.iter().enumerate() {
337 let variant_hash = variant.signature_hash();
338 assert_ne!(base_hash, variant_hash, "Field {} change should affect hash", i);
339 }
340 }
341}