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
22/// The prefix used for hashing messages according to EIP-191.
23pub const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n";
24
25/// The [Keccak-256](keccak256) hash of the empty string `""`.
26pub const KECCAK256_EMPTY: B256 =
27    b256!("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
28
29/// Tries to create a [`Vec`] containing the arguments.
30#[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/// Allocates memory on the heap then places `x` into it, returning an error if the allocation
47/// fails.
48///
49/// Stable version of `Box::try_new`.
50#[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/// Constructs a new box with uninitialized contents on the heap, returning an error if the
61/// allocation fails.
62///
63/// Stable version of `Box::try_new_uninit`.
64#[inline]
65pub fn box_try_new_uninit<T>() -> Result<Box<MaybeUninit<T>>, TryReserveError> {
66    let mut vec = Vec::<MaybeUninit<T>>::new();
67
68    // Reserve enough space for one `MaybeUninit<T>`.
69    vec.try_reserve_exact(1)?;
70
71    // `try_reserve_exact`'s docs note that the allocator might allocate more than requested anyway.
72    // Make sure we got exactly 1 element.
73    vec.shrink_to(1);
74
75    let mut vec = ManuallyDrop::new(vec);
76
77    // SAFETY: `vec` is exactly one element long and has not been deallocated.
78    Ok(unsafe { Box::from_raw(vec.as_mut_ptr()) })
79}
80
81/// Tries to collect the elements of an iterator into a `Vec`.
82pub 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/// Tries to create a `Vec` with the given capacity.
92#[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/// Tries to create a `Vec` of `n` elements, each initialized to `elem`.
99// Not public API. Use `try_vec!` instead.
100#[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
108/// Hash a message according to [EIP-191] (version `0x01`).
109///
110/// The final message is a UTF-8 string, encoded as follows:
111/// `"\x19Ethereum Signed Message:\n" + message.length + message`
112///
113/// This message is then hashed using [Keccak-256](keccak256).
114///
115/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
116pub fn eip191_hash_message<T: AsRef<[u8]>>(message: T) -> B256 {
117    keccak256(eip191_message(message))
118}
119
120/// Constructs a message according to [EIP-191] (version `0x01`).
121///
122/// The final message is a UTF-8 string, encoded as follows:
123/// `"\x19Ethereum Signed Message:\n" + message.length + message`
124///
125/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
126pub 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
142/// Simple interface to the [`Keccak-256`] hash function.
143///
144/// Uses the cache if the `keccak-cache-global` feature is enabled.
145///
146/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
147pub 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
154/// Simple interface to the [`Keccak-256`] hash function,
155/// with a thin cache layer.
156///
157/// Uses the cache if the `keccak-cache` feature is enabled.
158///
159/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
160pub 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/// Simple interface to the [`Keccak-256`] hash function.
168///
169/// This function always computes the hash directly without using the cache.
170///
171/// Does not use the cache even if the `keccak-cache` feature is enabled.
172///
173/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
174#[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                /// When targeting VMs with native keccak hooks, the `native-keccak` feature
188                /// can be enabled to import and use the host environment's implementation
189                /// of [`keccak256`] in place of [`sha3`] or [`tiny_keccak`]. This is overridden
190                /// when the `sha3-keccak` or `tiny-keccak` feature is enabled.
191                ///
192                /// # Safety
193                ///
194                /// The VM accepts the preimage by pointer and length, and writes the
195                /// 32-byte hash.
196                /// - `bytes` must point to an input buffer at least `len` long.
197                /// - `output` must point to a buffer that is at least 32-bytes long.
198                ///
199                /// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3
200                /// [`sha3`]: https://docs.rs/sha3/latest/sha3/
201                /// [`tiny_keccak`]: https://docs.rs/tiny-keccak/latest/tiny_keccak/
202                fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
203            }
204
205            // SAFETY: The output is 32-bytes, and the input comes from a slice.
206            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            // SAFETY: Never reads from `output`.
213            unsafe { hasher.finalize_into_raw(output.as_mut_ptr().cast()) };
214        }
215    }
216
217    // SAFETY: Initialized above.
218    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            /// Wraps `tiny_keccak::Keccak` to implement `Digest`-like API.
231            #[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/// Simple [`Keccak-256`] hasher.
261///
262/// Note that the "native-keccak" feature is not supported for this struct, and will default to the
263/// [`sha3`] implementation.
264///
265/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
266#[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    /// Creates a new [`Keccak256`] hasher.
287    #[inline]
288    pub fn new() -> Self {
289        Self { state: keccak256_state::State::new() }
290    }
291
292    /// Absorbs additional input. Can be called multiple times.
293    #[inline]
294    pub fn update(&mut self, bytes: impl AsRef<[u8]>) {
295        self.state.update(bytes.as_ref());
296    }
297
298    /// Pad and squeeze the state.
299    #[inline]
300    pub fn finalize(self) -> B256 {
301        let mut output = MaybeUninit::<B256>::uninit();
302        // SAFETY: The output is 32-bytes.
303        unsafe { self.finalize_into_raw(output.as_mut_ptr().cast()) };
304        // SAFETY: Initialized above.
305        unsafe { output.assume_init() }
306    }
307
308    /// Pad and squeeze the state into `output`.
309    ///
310    /// # Panics
311    ///
312    /// Panics if `output` is not 32 bytes long.
313    #[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    /// Pad and squeeze the state into `output`.
320    #[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    /// Pad and squeeze the state into `output`.
327    ///
328    /// # Safety
329    ///
330    /// `output` must point to a buffer that is at least 32-bytes long.
331    #[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 vector taken from:
343    // https://web3js.readthedocs.io/en/v1.10.0/web3-eth-accounts.html#hashmessage
344    #[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        // Test parameters (reduced for miri).
438        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        // Shared test vectors that will be hashed repeatedly to test cache hits.
448        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                // Use thread-local RNG with deterministic seed for reproducibility.
465                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                    // 70% chance to use a shared test vector (tests cache hits).
470                    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                        // 30% chance to use random data (tests cache misses).
487                        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        // Wait for all threads to complete.
505        for handle in handles {
506            handle.join().expect("Thread panicked");
507        }
508    }
509}