Skip to content

Commit

Permalink
feat(primitives): improve utils (#432)
Browse files Browse the repository at this point in the history
* feat(primitives): split `eip191_message` to its own function

* feat: add `units` utilities

* add tests

* perf: don't zero keccak256 bytes

* chore: clippy

* test
  • Loading branch information
DaniPopes authored Nov 23, 2023
1 parent f2846ce commit 7062c0c
Show file tree
Hide file tree
Showing 5 changed files with 955 additions and 111 deletions.
24 changes: 9 additions & 15 deletions crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@
#[macro_use]
extern crate alloc;

// Used in Serde tests.
#[cfg(test)]
use {bincode as _, serde as _, serde_json as _};

pub mod aliases;
#[doc(no_inline)]
pub use aliases::{
Expand All @@ -48,23 +44,21 @@ pub use log::Log;
mod signed;
pub use signed::{BigIntConversionError, ParseSignedError, Sign, Signed};

mod utils;
pub mod utils;
pub use utils::{eip191_hash_message, keccak256};

#[doc(no_inline)]
pub use ::bytes;
#[doc(no_inline)]
pub use ::hex;
#[doc(no_inline)]
pub use hex_literal::{self, hex};
/// Re-export of [`ruint::uint`] for convenience. Note that users of this macro
pub use {
::hex,
hex_literal::{self, hex},
ruint::{self, Uint},
tiny_keccak::{self, Hasher, Keccak},
};

/// Re-export of [`ruint::uint!`] for convenience. Note that users of this macro
/// must also add [`ruint`] to their `Cargo.toml` as a dependency.
#[doc(inline)]
pub use ruint::uint;
#[doc(no_inline)]
pub use ruint::{self, Uint};
#[doc(no_inline)]
pub use tiny_keccak::{self, Hasher, Keccak};

#[cfg(feature = "serde")]
#[doc(no_inline)]
Expand Down
4 changes: 1 addition & 3 deletions crates/primitives/src/signed/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,8 @@ impl<const BITS: usize, const LIMBS: usize> fmt::Debug for Signed<BITS, LIMBS> {
impl<const BITS: usize, const LIMBS: usize> fmt::Display for Signed<BITS, LIMBS> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (sign, abs) = self.into_sign_and_abs();
// sign must be formatted directly, instead of with `write!` due to the
// `sign_positive` flag
sign.fmt(f)?;
write!(f, "{abs}")
abs.fmt(f)
}
}

Expand Down
93 changes: 0 additions & 93 deletions crates/primitives/src/utils.rs

This file was deleted.

122 changes: 122 additions & 0 deletions crates/primitives/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//! Common Ethereum utilities.

use crate::B256;
use alloc::vec::Vec;
use core::mem::MaybeUninit;

mod units;
pub use units::{
format_ether, format_units, parse_ether, parse_units, ParseUnits, Unit, UnitsError,
};

#[doc(hidden)]
#[deprecated(since = "0.5.0", note = "use `Unit::ETHER.wei()` instead")]
pub const WEI_IN_ETHER: crate::U256 = Unit::ETHER.wei_const();

#[doc(hidden)]
#[deprecated(since = "0.5.0", note = "use `Unit` instead")]
pub type Units = Unit;

/// The prefix used for hashing messages according to EIP-191.
pub const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n";

/// Hash a message according to [EIP-191] (version `0x01`).
///
/// The final message is a UTF-8 string, encoded as follows:
/// `"\x19Ethereum Signed Message:\n" + message.length + message`
///
/// This message is then hashed using [Keccak-256](keccak256).
///
/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
pub fn eip191_hash_message<T: AsRef<[u8]>>(message: T) -> B256 {
keccak256(eip191_message(message))
}

/// Constructs a message according to [EIP-191] (version `0x01`).
///
/// The final message is a UTF-8 string, encoded as follows:
/// `"\x19Ethereum Signed Message:\n" + message.length + message`
///
/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
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())
}

/// Simple interface to the [`Keccak-256`] hash function.
///
/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> B256 {
fn keccak256(bytes: &[u8]) -> B256 {
let mut output = MaybeUninit::<B256>::uninit();

cfg_if::cfg_if! {
if #[cfg(all(feature = "native-keccak", not(feature = "tiny-keccak")))] {
#[link(wasm_import_module = "vm_hooks")]
extern "C" {
/// When targeting VMs with native keccak hooks, the `native-keccak` feature
/// can be enabled to import and use the host environment's implementation
/// of [`keccak256`] in place of [`tiny_keccak`]. This is overridden when
/// the `tiny-keccak` feature is enabled.
///
/// # Safety
///
/// The VM accepts the preimage by pointer and length, and writes the
/// 32-byte hash.
/// - `bytes` must point to an input buffer at least `len` long.
/// - `output` must point to a buffer that is at least 32-bytes long.
///
/// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3
/// [`tiny_keccak`]: https://docs.rs/tiny-keccak/latest/tiny_keccak/
fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
}

// SAFETY: The output is 32-bytes, and the input comes from a slice.
unsafe { native_keccak256(bytes.as_ptr(), bytes.len(), output.as_mut_ptr().cast()) };
} else {
use tiny_keccak::{Hasher, Keccak};

let mut hasher = Keccak::v256();
hasher.update(bytes);
// SAFETY: Never reads from `output`.
hasher.finalize(unsafe { (*output.as_mut_ptr()).as_mut_slice() });
}
}

// SAFETY: Initialized above.
unsafe { output.assume_init() }
}

keccak256(bytes.as_ref())
}

#[cfg(test)]
mod tests {
use super::*;

// test vector taken from:
// https://web3js.readthedocs.io/en/v1.10.0/web3-eth-accounts.html#hashmessage
#[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!("a1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2"));
assert_eq!(eip191_hash_message(msg), hash);
}
}
Loading

0 comments on commit 7062c0c

Please sign in to comment.