use crate::B256;
use alloc::{boxed::Box, collections::TryReserveError, vec::Vec};
use cfg_if::cfg_if;
use core::{
fmt,
mem::{ManuallyDrop, MaybeUninit},
};
mod units;
pub use units::{
DecimalSeparator, ParseUnits, Unit, UnitsError, format_ether, format_units, format_units_with,
parse_ether, parse_units,
};
mod hint;
#[cfg(feature = "keccak-cache")]
mod keccak_cache;
pub const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n";
pub const KECCAK256_EMPTY: B256 =
b256!("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
#[macro_export]
macro_rules! try_vec {
() => {
$crate::private::Vec::new()
};
($elem:expr; $n:expr) => {
$crate::utils::vec_try_from_elem($elem, $n)
};
($($x:expr),+ $(,)?) => {
match $crate::utils::box_try_new([$($x),+]) {
::core::result::Result::Ok(x) => ::core::result::Result::Ok(<[_]>::into_vec(x)),
::core::result::Result::Err(e) => ::core::result::Result::Err(e),
}
};
}
#[inline]
pub fn box_try_new<T>(value: T) -> Result<Box<T>, TryReserveError> {
let mut boxed = box_try_new_uninit::<T>()?;
unsafe {
boxed.as_mut_ptr().write(value);
let ptr = Box::into_raw(boxed);
Ok(Box::from_raw(ptr.cast()))
}
}
#[inline]
pub fn box_try_new_uninit<T>() -> Result<Box<MaybeUninit<T>>, TryReserveError> {
let mut vec = Vec::<MaybeUninit<T>>::new();
vec.try_reserve_exact(1)?;
vec.shrink_to(1);
let mut vec = ManuallyDrop::new(vec);
Ok(unsafe { Box::from_raw(vec.as_mut_ptr()) })
}
pub fn try_collect_vec<I: Iterator<Item = T>, T>(iter: I) -> Result<Vec<T>, TryReserveError> {
let mut vec = Vec::new();
if let Some(size_hint) = iter.size_hint().1 {
vec.try_reserve(size_hint.max(4))?;
}
vec.extend(iter);
Ok(vec)
}
#[inline]
pub fn vec_try_with_capacity<T>(capacity: usize) -> Result<Vec<T>, TryReserveError> {
let mut vec = Vec::new();
vec.try_reserve(capacity).map(|()| vec)
}
#[doc(hidden)]
pub fn vec_try_from_elem<T: Clone>(elem: T, n: usize) -> Result<Vec<T>, TryReserveError> {
let mut vec = Vec::new();
vec.try_reserve(n)?;
vec.resize(n, elem);
Ok(vec)
}
pub fn eip191_hash_message<T: AsRef<[u8]>>(message: T) -> B256 {
keccak256(eip191_message(message))
}
pub fn eip191_message<T: AsRef<[u8]>>(message: T) -> Vec<u8> {
fn eip191_message(message: &[u8]) -> Vec<u8> {
let len = message.len();
let mut len_string_buffer = itoa::Buffer::new();
let len_string = len_string_buffer.format(len);
let mut eth_message = Vec::with_capacity(EIP191_PREFIX.len() + len_string.len() + len);
eth_message.extend_from_slice(EIP191_PREFIX.as_bytes());
eth_message.extend_from_slice(len_string.as_bytes());
eth_message.extend_from_slice(message);
eth_message
}
eip191_message(message.as_ref())
}
pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> B256 {
#[cfg(feature = "keccak-cache-global")]
return keccak_cache::compute(bytes.as_ref(), keccak256_impl);
#[cfg(not(feature = "keccak-cache-global"))]
return keccak256_impl(bytes.as_ref());
}
pub fn keccak256_cached<T: AsRef<[u8]>>(bytes: T) -> B256 {
#[cfg(feature = "keccak-cache")]
return keccak_cache::compute(bytes.as_ref(), keccak256_impl);
#[cfg(not(feature = "keccak-cache"))]
return keccak256_impl(bytes.as_ref());
}
#[inline]
pub fn keccak256_uncached<T: AsRef<[u8]>>(bytes: T) -> B256 {
keccak256_impl(bytes.as_ref())
}
#[allow(unused)]
fn keccak256_impl(bytes: &[u8]) -> B256 {
let mut output = MaybeUninit::<B256>::uninit();
cfg_if! {
if #[cfg(all(feature = "native-keccak", not(any(feature = "sha3-keccak", feature = "tiny-keccak", miri))))] {
#[link(wasm_import_module = "vm_hooks")]
unsafe extern "C" {
fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
}
unsafe { native_keccak256(bytes.as_ptr(), bytes.len(), output.as_mut_ptr().cast::<u8>()) };
} else if #[cfg(all(feature = "asm-keccak", not(miri)))] {
return B256::new(keccak_asm::Keccak256::digest(bytes).into());
} else {
let mut hasher = Keccak256::new();
hasher.update(bytes);
unsafe { hasher.finalize_into_raw(output.as_mut_ptr().cast()) };
}
}
unsafe { output.assume_init() }
}
mod keccak256_state {
cfg_if::cfg_if! {
if #[cfg(all(feature = "asm-keccak", not(miri)))] {
pub(super) use keccak_asm::Digest;
pub(super) type State = keccak_asm::Keccak256;
} else if #[cfg(feature = "tiny-keccak")] {
pub(super) use tiny_keccak::Hasher as Digest;
#[derive(Clone)]
pub(super) struct State(tiny_keccak::Keccak);
impl State {
#[inline]
pub(super) fn new() -> Self {
Self(tiny_keccak::Keccak::v256())
}
#[inline]
pub(super) fn finalize_into(self, output: &mut [u8; 32]) {
self.0.finalize(output);
}
#[inline]
pub(super) fn update(&mut self, bytes: &[u8]) {
self.0.update(bytes);
}
}
} else {
pub(super) use sha3::Digest;
pub(super) type State = sha3::Keccak256;
}
}
}
#[allow(unused_imports)]
use keccak256_state::Digest;
#[derive(Clone)]
pub struct Keccak256 {
state: keccak256_state::State,
}
impl Default for Keccak256 {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Keccak256 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Keccak256").finish_non_exhaustive()
}
}
impl Keccak256 {
#[inline]
pub fn new() -> Self {
Self { state: keccak256_state::State::new() }
}
#[inline]
pub fn update(&mut self, bytes: impl AsRef<[u8]>) {
self.state.update(bytes.as_ref());
}
#[inline]
pub fn finalize(self) -> B256 {
let mut output = MaybeUninit::<B256>::uninit();
unsafe { self.finalize_into_raw(output.as_mut_ptr().cast()) };
unsafe { output.assume_init() }
}
#[inline]
#[track_caller]
pub fn finalize_into(self, output: &mut [u8]) {
self.finalize_into_array(output.try_into().unwrap())
}
#[inline]
#[allow(clippy::useless_conversion)]
pub fn finalize_into_array(self, output: &mut [u8; 32]) {
self.state.finalize_into(output.into());
}
#[inline]
pub unsafe fn finalize_into_raw(self, output: *mut u8) {
self.finalize_into_array(unsafe { &mut *output.cast::<[u8; 32]>() })
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn test_hash_message() {
let msg = "Hello World";
let eip191_msg = eip191_message(msg);
let hash = keccak256(&eip191_msg);
assert_eq!(
eip191_msg,
[EIP191_PREFIX.as_bytes(), msg.len().to_string().as_bytes(), msg.as_bytes()].concat()
);
assert_eq!(
hash,
b256!("0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2")
);
assert_eq!(eip191_hash_message(msg), hash);
}
#[test]
fn keccak256_hasher() {
let expected = b256!("0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad");
assert_eq!(keccak256("hello world"), expected);
let mut hasher = Keccak256::new();
hasher.update(b"hello");
hasher.update(b" world");
assert_eq!(hasher.clone().finalize(), expected);
let mut hash = [0u8; 32];
hasher.clone().finalize_into(&mut hash);
assert_eq!(hash, expected);
let mut hash = [0u8; 32];
hasher.clone().finalize_into_array(&mut hash);
assert_eq!(hash, expected);
let mut hash = [0u8; 32];
unsafe { hasher.finalize_into_raw(hash.as_mut_ptr()) };
assert_eq!(hash, expected);
}
#[test]
fn test_try_boxing() {
let x = Box::new(42);
let y = box_try_new(42).unwrap();
assert_eq!(x, y);
let x = vec![1; 3];
let y = try_vec![1; 3].unwrap();
assert_eq!(x, y);
let x = vec![1, 2, 3];
let y = try_vec![1, 2, 3].unwrap();
assert_eq!(x, y);
}
#[test]
#[cfg(feature = "keccak-cache")]
fn test_keccak256_cache_edge_cases() {
use keccak256_cached as keccak256;
assert_eq!(keccak256([]), KECCAK256_EMPTY);
assert_eq!(keccak256([]), KECCAK256_EMPTY);
let max_cacheable = vec![0xAA; keccak_cache::MAX_INPUT_LEN];
let hash1 = keccak256(&max_cacheable);
let hash2 = keccak256_impl(&max_cacheable);
assert_eq!(hash1, hash2);
let over_max = vec![0xBB; keccak_cache::MAX_INPUT_LEN + 1];
let hash1 = keccak256(&over_max);
let hash2 = keccak256_impl(&over_max);
assert_eq!(hash1, hash2);
let long_input = vec![0xCC; 1000];
let hash1 = keccak256(&long_input);
let hash2 = keccak256_impl(&long_input);
assert_eq!(hash1, hash2);
let max = if cfg!(miri) { 10 } else { 255 };
for byte in 0..=max {
let data = &[byte];
let hash1 = keccak256(data);
let hash2 = keccak256_impl(data);
assert_eq!(hash1, hash2);
}
}
#[test]
#[cfg(all(feature = "keccak-cache", feature = "rand"))]
fn test_keccak256_cache_multithreaded() {
use keccak256_cached as keccak256;
use rand::{Rng, SeedableRng};
use std::{sync::Arc, thread};
let num_threads = if cfg!(miri) {
2
} else {
thread::available_parallelism().map(|n| n.get()).unwrap_or(8)
};
let iterations_per_thread = if cfg!(miri) { 10 } else { 1000 };
let num_test_vectors = if cfg!(miri) { 5 } else { 100 };
let max_data_length = keccak_cache::MAX_INPUT_LEN;
let test_vectors: Arc<Vec<Vec<u8>>> = Arc::new({
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
(0..num_test_vectors)
.map(|_| {
let len = rng.random_range(0..=max_data_length);
(0..len).map(|_| rng.random()).collect()
})
.collect()
});
let mut handles = vec![];
for thread_id in 0..num_threads {
let test_vectors = Arc::clone(&test_vectors);
let handle = thread::spawn(move || {
let mut rng = rand::rngs::StdRng::seed_from_u64(thread_id as u64);
let max_data_length = keccak_cache::MAX_INPUT_LEN;
for _ in 0..iterations_per_thread {
if rng.random_range(0..10) < 7 && !test_vectors.is_empty() {
let idx = rng.random_range(0..test_vectors.len());
let data = &test_vectors[idx];
let cached_hash = keccak256(data);
let direct_hash = keccak256_impl(data);
assert_eq!(
cached_hash,
direct_hash,
"Thread {}: Cached hash mismatch for shared vector {} (len {})",
thread_id,
idx,
data.len()
);
} else {
let len = rng.random_range(0..max_data_length + 20);
let data: Vec<u8> = (0..len).map(|_| rng.random()).collect();
let cached_hash = keccak256(&data);
let direct_hash = keccak256_impl(&data);
assert_eq!(
cached_hash, direct_hash,
"Thread {thread_id}: Cached hash mismatch for random data (len {len})"
);
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread panicked");
}
}
}