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#[cfg(feature = "keccak-cache")]
22pub use keccak_cache::init_keccak_cache;
23#[cfg(feature = "keccak-cache-stats")]
24pub use keccak_cache::{KECCAK_CACHE_STATS, KeccakCacheStats};
25
26pub const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n";
28
29pub const KECCAK256_EMPTY: B256 =
31 b256!("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
32
33#[macro_export]
35macro_rules! try_vec {
36 () => {
37 $crate::private::Vec::new()
38 };
39 ($elem:expr; $n:expr) => {
40 $crate::utils::vec_try_from_elem($elem, $n)
41 };
42 ($($x:expr),+ $(,)?) => {
43 match $crate::utils::box_try_new([$($x),+]) {
44 ::core::result::Result::Ok(x) => ::core::result::Result::Ok(<[_]>::into_vec(x)),
45 ::core::result::Result::Err(e) => ::core::result::Result::Err(e),
46 }
47 };
48}
49
50#[inline]
55pub fn box_try_new<T>(value: T) -> Result<Box<T>, TryReserveError> {
56 let mut boxed = box_try_new_uninit::<T>()?;
57 unsafe {
58 boxed.as_mut_ptr().write(value);
59 let ptr = Box::into_raw(boxed);
60 Ok(Box::from_raw(ptr.cast()))
61 }
62}
63
64#[inline]
69pub fn box_try_new_uninit<T>() -> Result<Box<MaybeUninit<T>>, TryReserveError> {
70 let mut vec = Vec::<MaybeUninit<T>>::new();
71
72 vec.try_reserve_exact(1)?;
74
75 vec.shrink_to(1);
78
79 let mut vec = ManuallyDrop::new(vec);
80
81 Ok(unsafe { Box::from_raw(vec.as_mut_ptr()) })
83}
84
85pub fn try_collect_vec<I: Iterator<Item = T>, T>(iter: I) -> Result<Vec<T>, TryReserveError> {
87 let mut vec = Vec::new();
88 if let Some(size_hint) = iter.size_hint().1 {
89 vec.try_reserve(size_hint.max(4))?;
90 }
91 vec.extend(iter);
92 Ok(vec)
93}
94
95#[inline]
97pub fn vec_try_with_capacity<T>(capacity: usize) -> Result<Vec<T>, TryReserveError> {
98 let mut vec = Vec::new();
99 vec.try_reserve(capacity).map(|()| vec)
100}
101
102#[doc(hidden)]
105pub fn vec_try_from_elem<T: Clone>(elem: T, n: usize) -> Result<Vec<T>, TryReserveError> {
106 let mut vec = Vec::new();
107 vec.try_reserve(n)?;
108 vec.resize(n, elem);
109 Ok(vec)
110}
111
112pub fn eip191_hash_message<T: AsRef<[u8]>>(message: T) -> B256 {
121 keccak256(eip191_message(message))
122}
123
124pub fn eip191_message<T: AsRef<[u8]>>(message: T) -> Vec<u8> {
131 fn eip191_message(message: &[u8]) -> Vec<u8> {
132 let len = message.len();
133 let mut len_string_buffer = itoa::Buffer::new();
134 let len_string = len_string_buffer.format(len);
135
136 let mut eth_message = Vec::with_capacity(EIP191_PREFIX.len() + len_string.len() + len);
137 eth_message.extend_from_slice(EIP191_PREFIX.as_bytes());
138 eth_message.extend_from_slice(len_string.as_bytes());
139 eth_message.extend_from_slice(message);
140 eth_message
141 }
142
143 eip191_message(message.as_ref())
144}
145
146pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> B256 {
152 #[cfg(feature = "keccak-cache-global")]
153 return keccak_cache::compute(bytes.as_ref(), keccak256_impl);
154 #[cfg(not(feature = "keccak-cache-global"))]
155 return keccak256_impl(bytes.as_ref());
156}
157
158pub fn keccak256_cached<T: AsRef<[u8]>>(bytes: T) -> B256 {
165 #[cfg(feature = "keccak-cache")]
166 return keccak_cache::compute(bytes.as_ref(), keccak256_impl);
167 #[cfg(not(feature = "keccak-cache"))]
168 return keccak256_impl(bytes.as_ref());
169}
170
171#[inline]
179pub fn keccak256_uncached<T: AsRef<[u8]>>(bytes: T) -> B256 {
180 keccak256_impl(bytes.as_ref())
181}
182
183#[allow(unused)]
184fn keccak256_impl(bytes: &[u8]) -> B256 {
185 let mut output = MaybeUninit::<B256>::uninit();
186
187 cfg_if! {
188 if #[cfg(all(feature = "native-keccak", not(any(feature = "sha3-keccak", feature = "tiny-keccak", miri))))] {
189 #[link(wasm_import_module = "vm_hooks")]
190 unsafe extern "C" {
191 fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
207 }
208
209 unsafe { native_keccak256(bytes.as_ptr(), bytes.len(), output.as_mut_ptr().cast::<u8>()) };
211 } else if #[cfg(all(feature = "asm-keccak", not(miri)))] {
212 return B256::new(keccak_asm::Keccak256::digest(bytes).into());
213 } else {
214 let mut hasher = Keccak256::new();
215 hasher.update(bytes);
216 unsafe { hasher.finalize_into_raw(output.as_mut_ptr().cast()) };
218 }
219 }
220
221 unsafe { output.assume_init() }
223}
224
225mod keccak256_state {
226 cfg_if::cfg_if! {
227 if #[cfg(all(feature = "asm-keccak", not(miri)))] {
228 pub(super) use keccak_asm::Digest;
229
230 pub(super) type State = keccak_asm::Keccak256;
231 } else if #[cfg(feature = "tiny-keccak")] {
232 pub(super) use tiny_keccak::Hasher as Digest;
233
234 #[derive(Clone)]
236 pub(super) struct State(tiny_keccak::Keccak);
237
238 impl State {
239 #[inline]
240 pub(super) fn new() -> Self {
241 Self(tiny_keccak::Keccak::v256())
242 }
243
244 #[inline]
245 pub(super) fn finalize_into(self, output: &mut [u8; 32]) {
246 self.0.finalize(output);
247 }
248
249 #[inline]
250 pub(super) fn update(&mut self, bytes: &[u8]) {
251 self.0.update(bytes);
252 }
253 }
254 } else {
255 pub(super) use sha3::Digest;
256
257 pub(super) type State = sha3::Keccak256;
258 }
259 }
260}
261#[allow(unused_imports)]
262use keccak256_state::Digest;
263
264#[derive(Clone)]
271pub struct Keccak256 {
272 state: keccak256_state::State,
273}
274
275impl Default for Keccak256 {
276 #[inline]
277 fn default() -> Self {
278 Self::new()
279 }
280}
281
282impl fmt::Debug for Keccak256 {
283 #[inline]
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 f.debug_struct("Keccak256").finish_non_exhaustive()
286 }
287}
288
289impl Keccak256 {
290 #[inline]
292 pub fn new() -> Self {
293 Self { state: keccak256_state::State::new() }
294 }
295
296 #[inline]
298 pub fn update(&mut self, bytes: impl AsRef<[u8]>) {
299 self.state.update(bytes.as_ref());
300 }
301
302 #[inline]
304 pub fn finalize(self) -> B256 {
305 let mut output = MaybeUninit::<B256>::uninit();
306 unsafe { self.finalize_into_raw(output.as_mut_ptr().cast()) };
308 unsafe { output.assume_init() }
310 }
311
312 #[inline]
318 #[track_caller]
319 pub fn finalize_into(self, output: &mut [u8]) {
320 self.finalize_into_array(output.try_into().unwrap())
321 }
322
323 #[inline]
325 #[allow(clippy::useless_conversion)]
326 pub fn finalize_into_array(self, output: &mut [u8; 32]) {
327 self.state.finalize_into(output.into());
328 }
329
330 #[inline]
336 pub unsafe fn finalize_into_raw(self, output: *mut u8) {
337 self.finalize_into_array(unsafe { &mut *output.cast::<[u8; 32]>() })
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use alloc::string::ToString;
345
346 #[test]
349 fn test_hash_message() {
350 let msg = "Hello World";
351 let eip191_msg = eip191_message(msg);
352 let hash = keccak256(&eip191_msg);
353 assert_eq!(
354 eip191_msg,
355 [EIP191_PREFIX.as_bytes(), msg.len().to_string().as_bytes(), msg.as_bytes()].concat()
356 );
357 assert_eq!(
358 hash,
359 b256!("0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2")
360 );
361 assert_eq!(eip191_hash_message(msg), hash);
362 }
363
364 #[test]
365 fn keccak256_hasher() {
366 let expected = b256!("0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad");
367 assert_eq!(keccak256("hello world"), expected);
368
369 let mut hasher = Keccak256::new();
370 hasher.update(b"hello");
371 hasher.update(b" world");
372
373 assert_eq!(hasher.clone().finalize(), expected);
374
375 let mut hash = [0u8; 32];
376 hasher.clone().finalize_into(&mut hash);
377 assert_eq!(hash, expected);
378
379 let mut hash = [0u8; 32];
380 hasher.clone().finalize_into_array(&mut hash);
381 assert_eq!(hash, expected);
382
383 let mut hash = [0u8; 32];
384 unsafe { hasher.finalize_into_raw(hash.as_mut_ptr()) };
385 assert_eq!(hash, expected);
386 }
387
388 #[test]
389 fn test_try_boxing() {
390 let x = Box::new(42);
391 let y = box_try_new(42).unwrap();
392 assert_eq!(x, y);
393
394 let x = vec![1; 3];
395 let y = try_vec![1; 3].unwrap();
396 assert_eq!(x, y);
397
398 let x = vec![1, 2, 3];
399 let y = try_vec![1, 2, 3].unwrap();
400 assert_eq!(x, y);
401 }
402
403 #[test]
404 #[cfg(feature = "keccak-cache")]
405 fn test_keccak256_cache_edge_cases() {
406 use keccak256_cached as keccak256;
407 assert_eq!(keccak256([]), KECCAK256_EMPTY);
408 assert_eq!(keccak256([]), KECCAK256_EMPTY);
409
410 let max_cacheable = vec![0xAA; keccak_cache::MAX_INPUT_LEN];
411 let hash1 = keccak256(&max_cacheable);
412 let hash2 = keccak256_impl(&max_cacheable);
413 assert_eq!(hash1, hash2);
414
415 let over_max = vec![0xBB; keccak_cache::MAX_INPUT_LEN + 1];
416 let hash1 = keccak256(&over_max);
417 let hash2 = keccak256_impl(&over_max);
418 assert_eq!(hash1, hash2);
419
420 let long_input = vec![0xCC; 1000];
421 let hash1 = keccak256(&long_input);
422 let hash2 = keccak256_impl(&long_input);
423 assert_eq!(hash1, hash2);
424
425 let max = if cfg!(miri) { 10 } else { 255 };
426 for byte in 0..=max {
427 let data = &[byte];
428 let hash1 = keccak256(data);
429 let hash2 = keccak256_impl(data);
430 assert_eq!(hash1, hash2);
431 }
432 }
433
434 #[test]
435 #[cfg(all(feature = "keccak-cache", feature = "rand"))]
436 fn test_keccak256_cache_multithreaded() {
437 use keccak256_cached as keccak256;
438 use rand::{Rng, SeedableRng};
439 use std::{sync::Arc, thread};
440
441 let num_threads = if cfg!(miri) {
443 2
444 } else {
445 thread::available_parallelism().map(|n| n.get()).unwrap_or(8)
446 };
447 let iterations_per_thread = if cfg!(miri) { 10 } else { 1000 };
448 let num_test_vectors = if cfg!(miri) { 5 } else { 100 };
449 let max_data_length = keccak_cache::MAX_INPUT_LEN;
450
451 let test_vectors: Arc<Vec<Vec<u8>>> = Arc::new({
453 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
454 (0..num_test_vectors)
455 .map(|_| {
456 let len = rng.random_range(0..=max_data_length);
457 (0..len).map(|_| rng.random()).collect()
458 })
459 .collect()
460 });
461
462 let mut handles = vec![];
463
464 for thread_id in 0..num_threads {
465 let test_vectors = Arc::clone(&test_vectors);
466
467 let handle = thread::spawn(move || {
468 let mut rng = rand::rngs::StdRng::seed_from_u64(thread_id as u64);
470 let max_data_length = keccak_cache::MAX_INPUT_LEN;
471
472 for _ in 0..iterations_per_thread {
473 if rng.random_range(0..10) < 7 && !test_vectors.is_empty() {
475 let idx = rng.random_range(0..test_vectors.len());
476 let data = &test_vectors[idx];
477
478 let cached_hash = keccak256(data);
479 let direct_hash = keccak256_impl(data);
480
481 assert_eq!(
482 cached_hash,
483 direct_hash,
484 "Thread {}: Cached hash mismatch for shared vector {} (len {})",
485 thread_id,
486 idx,
487 data.len()
488 );
489 } else {
490 let len = rng.random_range(0..max_data_length + 20);
492 let data: Vec<u8> = (0..len).map(|_| rng.random()).collect();
493
494 let cached_hash = keccak256(&data);
495 let direct_hash = keccak256_impl(&data);
496
497 assert_eq!(
498 cached_hash, direct_hash,
499 "Thread {thread_id}: Cached hash mismatch for random data (len {len})"
500 );
501 }
502 }
503 });
504
505 handles.push(handle);
506 }
507
508 for handle in handles {
510 handle.join().expect("Thread panicked");
511 }
512 }
513}