Skip to main content

alloy_primitives/utils/
mod.rs

1//! Common Ethereum utilities.
2
3use 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
26/// The prefix used for hashing messages according to EIP-191.
27pub const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n";
28
29/// The [Keccak-256](keccak256) hash of the empty string `""`.
30pub const KECCAK256_EMPTY: B256 =
31    b256!("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
32
33/// Tries to create a [`Vec`] containing the arguments.
34#[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/// Allocates memory on the heap then places `x` into it, returning an error if the allocation
51/// fails.
52///
53/// Stable version of `Box::try_new`.
54#[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/// Constructs a new box with uninitialized contents on the heap, returning an error if the
65/// allocation fails.
66///
67/// Stable version of `Box::try_new_uninit`.
68#[inline]
69pub fn box_try_new_uninit<T>() -> Result<Box<MaybeUninit<T>>, TryReserveError> {
70    let mut vec = Vec::<MaybeUninit<T>>::new();
71
72    // Reserve enough space for one `MaybeUninit<T>`.
73    vec.try_reserve_exact(1)?;
74
75    // `try_reserve_exact`'s docs note that the allocator might allocate more than requested anyway.
76    // Make sure we got exactly 1 element.
77    vec.shrink_to(1);
78
79    let mut vec = ManuallyDrop::new(vec);
80
81    // SAFETY: `vec` is exactly one element long and has not been deallocated.
82    Ok(unsafe { Box::from_raw(vec.as_mut_ptr()) })
83}
84
85/// Tries to collect the elements of an iterator into a `Vec`.
86pub 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/// Tries to create a `Vec` with the given capacity.
96#[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/// Tries to create a `Vec` of `n` elements, each initialized to `elem`.
103// Not public API. Use `try_vec!` instead.
104#[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
112/// Hash a message according to [EIP-191] (version `0x01`).
113///
114/// The final message is a UTF-8 string, encoded as follows:
115/// `"\x19Ethereum Signed Message:\n" + message.length + message`
116///
117/// This message is then hashed using [Keccak-256](keccak256).
118///
119/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
120pub fn eip191_hash_message<T: AsRef<[u8]>>(message: T) -> B256 {
121    keccak256(eip191_message(message))
122}
123
124/// Constructs a message according to [EIP-191] (version `0x01`).
125///
126/// The final message is a UTF-8 string, encoded as follows:
127/// `"\x19Ethereum Signed Message:\n" + message.length + message`
128///
129/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
130pub 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
146/// Simple interface to the [`Keccak-256`] hash function.
147///
148/// Uses the cache if the `keccak-cache-global` feature is enabled.
149///
150/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
151pub 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
158/// Simple interface to the [`Keccak-256`] hash function,
159/// with a thin cache layer.
160///
161/// Uses the cache if the `keccak-cache` feature is enabled.
162///
163/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
164pub 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/// Simple interface to the [`Keccak-256`] hash function.
172///
173/// This function always computes the hash directly without using the cache.
174///
175/// Does not use the cache even if the `keccak-cache` feature is enabled.
176///
177/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
178#[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                /// When targeting VMs with native keccak hooks, the `native-keccak` feature
192                /// can be enabled to import and use the host environment's implementation
193                /// of [`keccak256`] in place of [`sha3`] or [`tiny_keccak`]. This is overridden
194                /// when the `sha3-keccak` or `tiny-keccak` feature is enabled.
195                ///
196                /// # Safety
197                ///
198                /// The VM accepts the preimage by pointer and length, and writes the
199                /// 32-byte hash.
200                /// - `bytes` must point to an input buffer at least `len` long.
201                /// - `output` must point to a buffer that is at least 32-bytes long.
202                ///
203                /// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3
204                /// [`sha3`]: https://docs.rs/sha3/latest/sha3/
205                /// [`tiny_keccak`]: https://docs.rs/tiny-keccak/latest/tiny_keccak/
206                fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
207            }
208
209            // SAFETY: The output is 32-bytes, and the input comes from a slice.
210            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            // SAFETY: Never reads from `output`.
217            unsafe { hasher.finalize_into_raw(output.as_mut_ptr().cast()) };
218        }
219    }
220
221    // SAFETY: Initialized above.
222    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            /// Wraps `tiny_keccak::Keccak` to implement `Digest`-like API.
235            #[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/// Simple [`Keccak-256`] hasher.
265///
266/// Note that the "native-keccak" feature is not supported for this struct, and will default to the
267/// [`sha3`] implementation.
268///
269/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
270#[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    /// Creates a new [`Keccak256`] hasher.
291    #[inline]
292    pub fn new() -> Self {
293        Self { state: keccak256_state::State::new() }
294    }
295
296    /// Absorbs additional input. Can be called multiple times.
297    #[inline]
298    pub fn update(&mut self, bytes: impl AsRef<[u8]>) {
299        self.state.update(bytes.as_ref());
300    }
301
302    /// Pad and squeeze the state.
303    #[inline]
304    pub fn finalize(self) -> B256 {
305        let mut output = MaybeUninit::<B256>::uninit();
306        // SAFETY: The output is 32-bytes.
307        unsafe { self.finalize_into_raw(output.as_mut_ptr().cast()) };
308        // SAFETY: Initialized above.
309        unsafe { output.assume_init() }
310    }
311
312    /// Pad and squeeze the state into `output`.
313    ///
314    /// # Panics
315    ///
316    /// Panics if `output` is not 32 bytes long.
317    #[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    /// Pad and squeeze the state into `output`.
324    #[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    /// Pad and squeeze the state into `output`.
331    ///
332    /// # Safety
333    ///
334    /// `output` must point to a buffer that is at least 32-bytes long.
335    #[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 vector taken from:
347    // https://web3js.readthedocs.io/en/v1.10.0/web3-eth-accounts.html#hashmessage
348    #[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        // Test parameters (reduced for miri).
442        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        // Shared test vectors that will be hashed repeatedly to test cache hits.
452        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                // Use thread-local RNG with deterministic seed for reproducibility.
469                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                    // 70% chance to use a shared test vector (tests cache hits).
474                    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                        // 30% chance to use random data (tests cache misses).
491                        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        // Wait for all threads to complete.
509        for handle in handles {
510            handle.join().expect("Thread panicked");
511        }
512    }
513}