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