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