alloy_primitives/utils/
mod.rs1use crate::B256;
4use alloc::{boxed::Box, collections::TryReserveError, vec::Vec};
5use cfg_if::cfg_if;
6use core::{
7 fmt,
8 mem::{ManuallyDrop, MaybeUninit},
9};
10
11mod units;
12pub use units::{
13 DecimalSeparator, ParseUnits, Unit, UnitsError, format_ether, format_units, format_units_with,
14 parse_ether, parse_units,
15};
16
17mod hint;
18
19#[cfg(feature = "keccak-cache")]
20mod keccak_cache;
21
22pub const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n";
24
25pub const KECCAK256_EMPTY: B256 =
27 b256!("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
28
29#[macro_export]
31macro_rules! try_vec {
32 () => {
33 $crate::private::Vec::new()
34 };
35 ($elem:expr; $n:expr) => {
36 $crate::utils::vec_try_from_elem($elem, $n)
37 };
38 ($($x:expr),+ $(,)?) => {
39 match $crate::utils::box_try_new([$($x),+]) {
40 ::core::result::Result::Ok(x) => ::core::result::Result::Ok(<[_]>::into_vec(x)),
41 ::core::result::Result::Err(e) => ::core::result::Result::Err(e),
42 }
43 };
44}
45
46#[inline]
51pub fn box_try_new<T>(value: T) -> Result<Box<T>, TryReserveError> {
52 let mut boxed = box_try_new_uninit::<T>()?;
53 unsafe {
54 boxed.as_mut_ptr().write(value);
55 let ptr = Box::into_raw(boxed);
56 Ok(Box::from_raw(ptr.cast()))
57 }
58}
59
60#[inline]
65pub fn box_try_new_uninit<T>() -> Result<Box<MaybeUninit<T>>, TryReserveError> {
66 let mut vec = Vec::<MaybeUninit<T>>::new();
67
68 vec.try_reserve_exact(1)?;
70
71 vec.shrink_to(1);
74
75 let mut vec = ManuallyDrop::new(vec);
76
77 Ok(unsafe { Box::from_raw(vec.as_mut_ptr()) })
79}
80
81pub fn try_collect_vec<I: Iterator<Item = T>, T>(iter: I) -> Result<Vec<T>, TryReserveError> {
83 let mut vec = Vec::new();
84 if let Some(size_hint) = iter.size_hint().1 {
85 vec.try_reserve(size_hint.max(4))?;
86 }
87 vec.extend(iter);
88 Ok(vec)
89}
90
91#[inline]
93pub fn vec_try_with_capacity<T>(capacity: usize) -> Result<Vec<T>, TryReserveError> {
94 let mut vec = Vec::new();
95 vec.try_reserve(capacity).map(|()| vec)
96}
97
98#[doc(hidden)]
101pub fn vec_try_from_elem<T: Clone>(elem: T, n: usize) -> Result<Vec<T>, TryReserveError> {
102 let mut vec = Vec::new();
103 vec.try_reserve(n)?;
104 vec.resize(n, elem);
105 Ok(vec)
106}
107
108pub fn eip191_hash_message<T: AsRef<[u8]>>(message: T) -> B256 {
117 keccak256(eip191_message(message))
118}
119
120pub fn eip191_message<T: AsRef<[u8]>>(message: T) -> Vec<u8> {
127 fn eip191_message(message: &[u8]) -> Vec<u8> {
128 let len = message.len();
129 let mut len_string_buffer = itoa::Buffer::new();
130 let len_string = len_string_buffer.format(len);
131
132 let mut eth_message = Vec::with_capacity(EIP191_PREFIX.len() + len_string.len() + len);
133 eth_message.extend_from_slice(EIP191_PREFIX.as_bytes());
134 eth_message.extend_from_slice(len_string.as_bytes());
135 eth_message.extend_from_slice(message);
136 eth_message
137 }
138
139 eip191_message(message.as_ref())
140}
141
142pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> B256 {
148 #[cfg(feature = "keccak-cache-global")]
149 return keccak_cache::compute(bytes.as_ref(), keccak256_impl);
150 #[cfg(not(feature = "keccak-cache-global"))]
151 return keccak256_impl(bytes.as_ref());
152}
153
154pub fn keccak256_cached<T: AsRef<[u8]>>(bytes: T) -> B256 {
161 #[cfg(feature = "keccak-cache")]
162 return keccak_cache::compute(bytes.as_ref(), keccak256_impl);
163 #[cfg(not(feature = "keccak-cache"))]
164 return keccak256_impl(bytes.as_ref());
165}
166
167#[inline]
175pub fn keccak256_uncached<T: AsRef<[u8]>>(bytes: T) -> B256 {
176 keccak256_impl(bytes.as_ref())
177}
178
179#[allow(unused)]
180fn keccak256_impl(bytes: &[u8]) -> B256 {
181 let mut output = MaybeUninit::<B256>::uninit();
182
183 cfg_if! {
184 if #[cfg(all(feature = "native-keccak", not(any(feature = "sha3-keccak", feature = "tiny-keccak", miri))))] {
185 #[link(wasm_import_module = "vm_hooks")]
186 unsafe extern "C" {
187 fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
203 }
204
205 unsafe { native_keccak256(bytes.as_ptr(), bytes.len(), output.as_mut_ptr().cast::<u8>()) };
207 } else if #[cfg(all(feature = "asm-keccak", not(miri)))] {
208 return B256::new(keccak_asm::Keccak256::digest(bytes).into());
209 } else {
210 let mut hasher = Keccak256::new();
211 hasher.update(bytes);
212 unsafe { hasher.finalize_into_raw(output.as_mut_ptr().cast()) };
214 }
215 }
216
217 unsafe { output.assume_init() }
219}
220
221mod keccak256_state {
222 cfg_if::cfg_if! {
223 if #[cfg(all(feature = "asm-keccak", not(miri)))] {
224 pub(super) use keccak_asm::Digest;
225
226 pub(super) type State = keccak_asm::Keccak256;
227 } else if #[cfg(feature = "tiny-keccak")] {
228 pub(super) use tiny_keccak::Hasher as Digest;
229
230 #[derive(Clone)]
232 pub(super) struct State(tiny_keccak::Keccak);
233
234 impl State {
235 #[inline]
236 pub(super) fn new() -> Self {
237 Self(tiny_keccak::Keccak::v256())
238 }
239
240 #[inline]
241 pub(super) fn finalize_into(self, output: &mut [u8; 32]) {
242 self.0.finalize(output);
243 }
244
245 #[inline]
246 pub(super) fn update(&mut self, bytes: &[u8]) {
247 self.0.update(bytes);
248 }
249 }
250 } else {
251 pub(super) use sha3::Digest;
252
253 pub(super) type State = sha3::Keccak256;
254 }
255 }
256}
257#[allow(unused_imports)]
258use keccak256_state::Digest;
259
260#[derive(Clone)]
267pub struct Keccak256 {
268 state: keccak256_state::State,
269}
270
271impl Default for Keccak256 {
272 #[inline]
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278impl fmt::Debug for Keccak256 {
279 #[inline]
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 f.debug_struct("Keccak256").finish_non_exhaustive()
282 }
283}
284
285impl Keccak256 {
286 #[inline]
288 pub fn new() -> Self {
289 Self { state: keccak256_state::State::new() }
290 }
291
292 #[inline]
294 pub fn update(&mut self, bytes: impl AsRef<[u8]>) {
295 self.state.update(bytes.as_ref());
296 }
297
298 #[inline]
300 pub fn finalize(self) -> B256 {
301 let mut output = MaybeUninit::<B256>::uninit();
302 unsafe { self.finalize_into_raw(output.as_mut_ptr().cast()) };
304 unsafe { output.assume_init() }
306 }
307
308 #[inline]
314 #[track_caller]
315 pub fn finalize_into(self, output: &mut [u8]) {
316 self.finalize_into_array(output.try_into().unwrap())
317 }
318
319 #[inline]
321 #[allow(clippy::useless_conversion)]
322 pub fn finalize_into_array(self, output: &mut [u8; 32]) {
323 self.state.finalize_into(output.into());
324 }
325
326 #[inline]
332 pub unsafe fn finalize_into_raw(self, output: *mut u8) {
333 self.finalize_into_array(unsafe { &mut *output.cast::<[u8; 32]>() })
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use alloc::string::ToString;
341
342 #[test]
345 fn test_hash_message() {
346 let msg = "Hello World";
347 let eip191_msg = eip191_message(msg);
348 let hash = keccak256(&eip191_msg);
349 assert_eq!(
350 eip191_msg,
351 [EIP191_PREFIX.as_bytes(), msg.len().to_string().as_bytes(), msg.as_bytes()].concat()
352 );
353 assert_eq!(
354 hash,
355 b256!("0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2")
356 );
357 assert_eq!(eip191_hash_message(msg), hash);
358 }
359
360 #[test]
361 fn keccak256_hasher() {
362 let expected = b256!("0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad");
363 assert_eq!(keccak256("hello world"), expected);
364
365 let mut hasher = Keccak256::new();
366 hasher.update(b"hello");
367 hasher.update(b" world");
368
369 assert_eq!(hasher.clone().finalize(), expected);
370
371 let mut hash = [0u8; 32];
372 hasher.clone().finalize_into(&mut hash);
373 assert_eq!(hash, expected);
374
375 let mut hash = [0u8; 32];
376 hasher.clone().finalize_into_array(&mut hash);
377 assert_eq!(hash, expected);
378
379 let mut hash = [0u8; 32];
380 unsafe { hasher.finalize_into_raw(hash.as_mut_ptr()) };
381 assert_eq!(hash, expected);
382 }
383
384 #[test]
385 fn test_try_boxing() {
386 let x = Box::new(42);
387 let y = box_try_new(42).unwrap();
388 assert_eq!(x, y);
389
390 let x = vec![1; 3];
391 let y = try_vec![1; 3].unwrap();
392 assert_eq!(x, y);
393
394 let x = vec![1, 2, 3];
395 let y = try_vec![1, 2, 3].unwrap();
396 assert_eq!(x, y);
397 }
398
399 #[test]
400 #[cfg(feature = "keccak-cache")]
401 fn test_keccak256_cache_edge_cases() {
402 use keccak256_cached as keccak256;
403 assert_eq!(keccak256([]), KECCAK256_EMPTY);
404 assert_eq!(keccak256([]), KECCAK256_EMPTY);
405
406 let max_cacheable = vec![0xAA; keccak_cache::MAX_INPUT_LEN];
407 let hash1 = keccak256(&max_cacheable);
408 let hash2 = keccak256_impl(&max_cacheable);
409 assert_eq!(hash1, hash2);
410
411 let over_max = vec![0xBB; keccak_cache::MAX_INPUT_LEN + 1];
412 let hash1 = keccak256(&over_max);
413 let hash2 = keccak256_impl(&over_max);
414 assert_eq!(hash1, hash2);
415
416 let long_input = vec![0xCC; 1000];
417 let hash1 = keccak256(&long_input);
418 let hash2 = keccak256_impl(&long_input);
419 assert_eq!(hash1, hash2);
420
421 let max = if cfg!(miri) { 10 } else { 255 };
422 for byte in 0..=max {
423 let data = &[byte];
424 let hash1 = keccak256(data);
425 let hash2 = keccak256_impl(data);
426 assert_eq!(hash1, hash2);
427 }
428 }
429
430 #[test]
431 #[cfg(all(feature = "keccak-cache", feature = "rand"))]
432 fn test_keccak256_cache_multithreaded() {
433 use keccak256_cached as keccak256;
434 use rand::{Rng, SeedableRng};
435 use std::{sync::Arc, thread};
436
437 let num_threads = if cfg!(miri) {
439 2
440 } else {
441 thread::available_parallelism().map(|n| n.get()).unwrap_or(8)
442 };
443 let iterations_per_thread = if cfg!(miri) { 10 } else { 1000 };
444 let num_test_vectors = if cfg!(miri) { 5 } else { 100 };
445 let max_data_length = keccak_cache::MAX_INPUT_LEN;
446
447 let test_vectors: Arc<Vec<Vec<u8>>> = Arc::new({
449 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
450 (0..num_test_vectors)
451 .map(|_| {
452 let len = rng.random_range(0..=max_data_length);
453 (0..len).map(|_| rng.random()).collect()
454 })
455 .collect()
456 });
457
458 let mut handles = vec![];
459
460 for thread_id in 0..num_threads {
461 let test_vectors = Arc::clone(&test_vectors);
462
463 let handle = thread::spawn(move || {
464 let mut rng = rand::rngs::StdRng::seed_from_u64(thread_id as u64);
466 let max_data_length = keccak_cache::MAX_INPUT_LEN;
467
468 for _ in 0..iterations_per_thread {
469 if rng.random_range(0..10) < 7 && !test_vectors.is_empty() {
471 let idx = rng.random_range(0..test_vectors.len());
472 let data = &test_vectors[idx];
473
474 let cached_hash = keccak256(data);
475 let direct_hash = keccak256_impl(data);
476
477 assert_eq!(
478 cached_hash,
479 direct_hash,
480 "Thread {}: Cached hash mismatch for shared vector {} (len {})",
481 thread_id,
482 idx,
483 data.len()
484 );
485 } else {
486 let len = rng.random_range(0..max_data_length + 20);
488 let data: Vec<u8> = (0..len).map(|_| rng.random()).collect();
489
490 let cached_hash = keccak256(&data);
491 let direct_hash = keccak256_impl(&data);
492
493 assert_eq!(
494 cached_hash, direct_hash,
495 "Thread {thread_id}: Cached hash mismatch for random data (len {len})"
496 );
497 }
498 }
499 });
500
501 handles.push(handle);
502 }
503
504 for handle in handles {
506 handle.join().expect("Thread panicked");
507 }
508 }
509}