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    format_ether, format_units, format_units_with, parse_ether, parse_units, DecimalSeparator,
14    ParseUnits, Unit, UnitsError,
15};
16
17#[doc(hidden)]
18#[deprecated(since = "0.5.0", note = "use `Unit::ETHER.wei()` instead")]
19pub const WEI_IN_ETHER: crate::U256 = Unit::ETHER.wei_const();
20
21#[doc(hidden)]
22#[deprecated(since = "0.5.0", note = "use `Unit` instead")]
23pub type Units = Unit;
24
25/// The prefix used for hashing messages according to EIP-191.
26pub const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n";
27
28/// The [Keccak-256](keccak256) hash of the empty string `""`.
29pub const KECCAK256_EMPTY: B256 =
30    b256!("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
31
32/// Tries to create a [`Vec`] containing the arguments.
33#[macro_export]
34macro_rules! try_vec {
35    () => {
36        $crate::private::Vec::new()
37    };
38    ($elem:expr; $n:expr) => {
39        $crate::utils::vec_try_from_elem($elem, $n)
40    };
41    ($($x:expr),+ $(,)?) => {
42        match $crate::utils::box_try_new([$($x),+]) {
43            ::core::result::Result::Ok(x) => ::core::result::Result::Ok(<[_]>::into_vec(x)),
44            ::core::result::Result::Err(e) => ::core::result::Result::Err(e),
45        }
46    };
47}
48
49/// Allocates memory on the heap then places `x` into it, returning an error if the allocation
50/// fails.
51///
52/// Stable version of `Box::try_new`.
53#[inline]
54pub fn box_try_new<T>(value: T) -> Result<Box<T>, TryReserveError> {
55    let mut boxed = box_try_new_uninit::<T>()?;
56    unsafe {
57        boxed.as_mut_ptr().write(value);
58        let ptr = Box::into_raw(boxed);
59        Ok(Box::from_raw(ptr.cast()))
60    }
61}
62
63/// Constructs a new box with uninitialized contents on the heap, returning an error if the
64/// allocation fails.
65///
66/// Stable version of `Box::try_new_uninit`.
67#[inline]
68pub fn box_try_new_uninit<T>() -> Result<Box<MaybeUninit<T>>, TryReserveError> {
69    let mut vec = Vec::<MaybeUninit<T>>::new();
70
71    // Reserve enough space for one `MaybeUninit<T>`.
72    vec.try_reserve_exact(1)?;
73
74    // `try_reserve_exact`'s docs note that the allocator might allocate more than requested anyway.
75    // Make sure we got exactly 1 element.
76    vec.shrink_to(1);
77
78    let mut vec = ManuallyDrop::new(vec);
79
80    // SAFETY: `vec` is exactly one element long and has not been deallocated.
81    Ok(unsafe { Box::from_raw(vec.as_mut_ptr()) })
82}
83
84/// Tries to collect the elements of an iterator into a `Vec`.
85pub fn try_collect_vec<I: Iterator<Item = T>, T>(iter: I) -> Result<Vec<T>, TryReserveError> {
86    let mut vec = Vec::new();
87    if let Some(size_hint) = iter.size_hint().1 {
88        vec.try_reserve(size_hint.max(4))?;
89    }
90    vec.extend(iter);
91    Ok(vec)
92}
93
94/// Tries to create a `Vec` with the given capacity.
95#[inline]
96pub fn vec_try_with_capacity<T>(capacity: usize) -> Result<Vec<T>, TryReserveError> {
97    let mut vec = Vec::new();
98    vec.try_reserve(capacity).map(|()| vec)
99}
100
101/// Tries to create a `Vec` of `n` elements, each initialized to `elem`.
102// Not public API. Use `try_vec!` instead.
103#[doc(hidden)]
104pub fn vec_try_from_elem<T: Clone>(elem: T, n: usize) -> Result<Vec<T>, TryReserveError> {
105    let mut vec = Vec::new();
106    vec.try_reserve(n)?;
107    vec.resize(n, elem);
108    Ok(vec)
109}
110
111/// Hash a message according to [EIP-191] (version `0x01`).
112///
113/// The final message is a UTF-8 string, encoded as follows:
114/// `"\x19Ethereum Signed Message:\n" + message.length + message`
115///
116/// This message is then hashed using [Keccak-256](keccak256).
117///
118/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
119pub fn eip191_hash_message<T: AsRef<[u8]>>(message: T) -> B256 {
120    keccak256(eip191_message(message))
121}
122
123/// Constructs a message according to [EIP-191] (version `0x01`).
124///
125/// The final message is a UTF-8 string, encoded as follows:
126/// `"\x19Ethereum Signed Message:\n" + message.length + message`
127///
128/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
129pub fn eip191_message<T: AsRef<[u8]>>(message: T) -> Vec<u8> {
130    fn eip191_message(message: &[u8]) -> Vec<u8> {
131        let len = message.len();
132        let mut len_string_buffer = itoa::Buffer::new();
133        let len_string = len_string_buffer.format(len);
134
135        let mut eth_message = Vec::with_capacity(EIP191_PREFIX.len() + len_string.len() + len);
136        eth_message.extend_from_slice(EIP191_PREFIX.as_bytes());
137        eth_message.extend_from_slice(len_string.as_bytes());
138        eth_message.extend_from_slice(message);
139        eth_message
140    }
141
142    eip191_message(message.as_ref())
143}
144
145/// Simple interface to the [`Keccak-256`] hash function.
146///
147/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
148pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> B256 {
149    fn keccak256(bytes: &[u8]) -> B256 {
150        let mut output = MaybeUninit::<B256>::uninit();
151
152        cfg_if! {
153            if #[cfg(all(feature = "native-keccak", not(any(feature = "sha3-keccak", feature = "tiny-keccak", miri))))] {
154                #[link(wasm_import_module = "vm_hooks")]
155                extern "C" {
156                    /// When targeting VMs with native keccak hooks, the `native-keccak` feature
157                    /// can be enabled to import and use the host environment's implementation
158                    /// of [`keccak256`] in place of [`sha3`] or [`tiny_keccak`]. This is overridden
159                    /// when the `sha3-keccak` or `tiny-keccak` feature is enabled.
160                    ///
161                    /// # Safety
162                    ///
163                    /// The VM accepts the preimage by pointer and length, and writes the
164                    /// 32-byte hash.
165                    /// - `bytes` must point to an input buffer at least `len` long.
166                    /// - `output` must point to a buffer that is at least 32-bytes long.
167                    ///
168                    /// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3
169                    /// [`sha3`]: https://docs.rs/sha3/latest/sha3/
170                    /// [`tiny_keccak`]: https://docs.rs/tiny-keccak/latest/tiny_keccak/
171                    fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
172                }
173
174                // SAFETY: The output is 32-bytes, and the input comes from a slice.
175                unsafe { native_keccak256(bytes.as_ptr(), bytes.len(), output.as_mut_ptr().cast::<u8>()) };
176            } else {
177                let mut hasher = Keccak256::new();
178                hasher.update(bytes);
179                // SAFETY: Never reads from `output`.
180                unsafe { hasher.finalize_into_raw(output.as_mut_ptr().cast()) };
181            }
182        }
183
184        // SAFETY: Initialized above.
185        unsafe { output.assume_init() }
186    }
187
188    keccak256(bytes.as_ref())
189}
190
191mod keccak256_state {
192    cfg_if::cfg_if! {
193        if #[cfg(all(feature = "asm-keccak", not(miri)))] {
194            pub(super) use keccak_asm::Digest;
195
196            pub(super) type State = keccak_asm::Keccak256;
197        } else if #[cfg(feature = "sha3-keccak")] {
198            pub(super) use sha3::Digest;
199
200            pub(super) type State = sha3::Keccak256;
201        } else {
202            pub(super) use tiny_keccak::Hasher as Digest;
203
204            /// Wraps `tiny_keccak::Keccak` to implement `Digest`-like API.
205            #[derive(Clone)]
206            pub(super) struct State(tiny_keccak::Keccak);
207
208            impl State {
209                #[inline]
210                pub(super) fn new() -> Self {
211                    Self(tiny_keccak::Keccak::v256())
212                }
213
214                #[inline]
215                pub(super) fn finalize_into(self, output: &mut [u8; 32]) {
216                    self.0.finalize(output);
217                }
218
219                #[inline]
220                pub(super) fn update(&mut self, bytes: &[u8]) {
221                    self.0.update(bytes);
222                }
223            }
224        }
225    }
226}
227#[allow(unused_imports)]
228use keccak256_state::Digest;
229
230/// Simple [`Keccak-256`] hasher.
231///
232/// Note that the "native-keccak" feature is not supported for this struct, and will default to the
233/// [`tiny_keccak`] implementation.
234///
235/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
236#[derive(Clone)]
237pub struct Keccak256 {
238    state: keccak256_state::State,
239}
240
241impl Default for Keccak256 {
242    #[inline]
243    fn default() -> Self {
244        Self::new()
245    }
246}
247
248impl fmt::Debug for Keccak256 {
249    #[inline]
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        f.debug_struct("Keccak256").finish_non_exhaustive()
252    }
253}
254
255impl Keccak256 {
256    /// Creates a new [`Keccak256`] hasher.
257    #[inline]
258    pub fn new() -> Self {
259        Self { state: keccak256_state::State::new() }
260    }
261
262    /// Absorbs additional input. Can be called multiple times.
263    #[inline]
264    pub fn update(&mut self, bytes: impl AsRef<[u8]>) {
265        self.state.update(bytes.as_ref());
266    }
267
268    /// Pad and squeeze the state.
269    #[inline]
270    pub fn finalize(self) -> B256 {
271        let mut output = MaybeUninit::<B256>::uninit();
272        // SAFETY: The output is 32-bytes.
273        unsafe { self.finalize_into_raw(output.as_mut_ptr().cast()) };
274        // SAFETY: Initialized above.
275        unsafe { output.assume_init() }
276    }
277
278    /// Pad and squeeze the state into `output`.
279    ///
280    /// # Panics
281    ///
282    /// Panics if `output` is not 32 bytes long.
283    #[inline]
284    #[track_caller]
285    pub fn finalize_into(self, output: &mut [u8]) {
286        self.finalize_into_array(output.try_into().unwrap())
287    }
288
289    /// Pad and squeeze the state into `output`.
290    #[inline]
291    #[allow(clippy::useless_conversion)]
292    pub fn finalize_into_array(self, output: &mut [u8; 32]) {
293        self.state.finalize_into(output.into());
294    }
295
296    /// Pad and squeeze the state into `output`.
297    ///
298    /// # Safety
299    ///
300    /// `output` must point to a buffer that is at least 32-bytes long.
301    #[inline]
302    pub unsafe fn finalize_into_raw(self, output: *mut u8) {
303        self.finalize_into_array(&mut *output.cast::<[u8; 32]>())
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use alloc::string::ToString;
311
312    // test vector taken from:
313    // https://web3js.readthedocs.io/en/v1.10.0/web3-eth-accounts.html#hashmessage
314    #[test]
315    fn test_hash_message() {
316        let msg = "Hello World";
317        let eip191_msg = eip191_message(msg);
318        let hash = keccak256(&eip191_msg);
319        assert_eq!(
320            eip191_msg,
321            [EIP191_PREFIX.as_bytes(), msg.len().to_string().as_bytes(), msg.as_bytes()].concat()
322        );
323        assert_eq!(
324            hash,
325            b256!("0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2")
326        );
327        assert_eq!(eip191_hash_message(msg), hash);
328    }
329
330    #[test]
331    fn keccak256_hasher() {
332        let expected = b256!("0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad");
333        assert_eq!(keccak256("hello world"), expected);
334
335        let mut hasher = Keccak256::new();
336        hasher.update(b"hello");
337        hasher.update(b" world");
338
339        assert_eq!(hasher.clone().finalize(), expected);
340
341        let mut hash = [0u8; 32];
342        hasher.clone().finalize_into(&mut hash);
343        assert_eq!(hash, expected);
344
345        let mut hash = [0u8; 32];
346        hasher.clone().finalize_into_array(&mut hash);
347        assert_eq!(hash, expected);
348
349        let mut hash = [0u8; 32];
350        unsafe { hasher.finalize_into_raw(hash.as_mut_ptr()) };
351        assert_eq!(hash, expected);
352    }
353
354    #[test]
355    fn test_try_boxing() {
356        let x = Box::new(42);
357        let y = box_try_new(42).unwrap();
358        assert_eq!(x, y);
359
360        let x = vec![1; 3];
361        let y = try_vec![1; 3].unwrap();
362        assert_eq!(x, y);
363
364        let x = vec![1, 2, 3];
365        let y = try_vec![1, 2, 3].unwrap();
366        assert_eq!(x, y);
367    }
368}