use alloc::{vec, vec::Vec};
use core::array;
use miden_core::{
EventName, Felt, Word, ZERO,
precompile::{PrecompileCommitment, PrecompileError, PrecompileRequest, PrecompileVerifier},
};
use miden_crypto::hash::{keccak::Keccak256, rpo::Rpo256};
use miden_processor::{AdviceMutation, EventError, EventHandler, ProcessState};
use crate::handlers::{BYTES_PER_U32, bytes_to_packed_u32_felts, read_memory_packed_u32};
pub const KECCAK_HASH_MEMORY_EVENT_NAME: EventName =
EventName::new("stdlib::hash::keccak256::hash_memory");
pub struct KeccakPrecompile;
impl EventHandler for KeccakPrecompile {
fn on_event(&self, process: &ProcessState) -> Result<Vec<AdviceMutation>, EventError> {
let ptr = process.get_stack_item(1).as_int();
let len_bytes = process.get_stack_item(2).as_int();
let input_bytes = read_memory_packed_u32(process, ptr, len_bytes as usize)?;
let preimage = KeccakPreimage::new(input_bytes);
let digest = preimage.digest();
let advice_stack_extension = AdviceMutation::extend_stack(digest.0);
let precompile_request_extension =
AdviceMutation::extend_precompile_requests([preimage.into()]);
Ok(vec![advice_stack_extension, precompile_request_extension])
}
}
impl PrecompileVerifier for KeccakPrecompile {
fn verify(&self, calldata: &[u8]) -> Result<PrecompileCommitment, PrecompileError> {
let preimage = KeccakPreimage::new(calldata.to_vec());
Ok(preimage.precompile_commitment())
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct KeccakFeltDigest([Felt; 8]);
impl KeccakFeltDigest {
pub fn from_bytes(bytes: &[u8]) -> Self {
assert_eq!(bytes.len(), 32, "input must be 32 bytes");
let packed: [u32; 8] = array::from_fn(|i| {
let limbs = array::from_fn(|j| bytes[BYTES_PER_U32 * i + j]);
u32::from_le_bytes(limbs)
});
Self(packed.map(Felt::from))
}
pub fn to_commitment(&self) -> Word {
Rpo256::hash_elements(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeccakPreimage(Vec<u8>);
impl KeccakPreimage {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn into_inner(self) -> Vec<u8> {
self.0
}
pub fn as_felts(&self) -> Vec<Felt> {
bytes_to_packed_u32_felts(self.as_ref())
}
pub fn input_commitment(&self) -> Word {
Rpo256::hash_elements(&self.as_felts())
}
pub fn digest(&self) -> KeccakFeltDigest {
let hash_u8 = Keccak256::hash(self.as_ref());
KeccakFeltDigest::from_bytes(&hash_u8)
}
pub fn precompile_commitment(&self) -> PrecompileCommitment {
let tag = self.precompile_tag();
let comm = Rpo256::merge(&[self.input_commitment(), self.digest().to_commitment()]);
PrecompileCommitment::new(tag, comm)
}
fn precompile_tag(&self) -> Word {
[
KECCAK_HASH_MEMORY_EVENT_NAME.to_event_id().as_felt(),
Felt::new(self.as_ref().len() as u64),
ZERO,
ZERO,
]
.into()
}
}
impl From<KeccakPreimage> for PrecompileRequest {
fn from(preimage: KeccakPreimage) -> Self {
let event_id = KECCAK_HASH_MEMORY_EVENT_NAME.to_event_id();
PrecompileRequest::new(event_id, preimage.into_inner())
}
}
impl AsRef<[u8]> for KeccakPreimage {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for KeccakPreimage {
fn from(bytes: Vec<u8>) -> Self {
Self::new(bytes)
}
}
impl AsRef<[Felt]> for KeccakFeltDigest {
fn as_ref(&self) -> &[Felt] {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keccak_felt_digest_from_bytes() {
let bytes: Vec<u8> = (1..=32).collect();
let digest = KeccakFeltDigest::from_bytes(&bytes);
let expected = [
u32::from_le_bytes([1, 2, 3, 4]),
u32::from_le_bytes([5, 6, 7, 8]),
u32::from_le_bytes([9, 10, 11, 12]),
u32::from_le_bytes([13, 14, 15, 16]),
u32::from_le_bytes([17, 18, 19, 20]),
u32::from_le_bytes([21, 22, 23, 24]),
u32::from_le_bytes([25, 26, 27, 28]),
u32::from_le_bytes([29, 30, 31, 32]),
]
.map(Felt::from);
assert_eq!(digest.0, expected);
}
#[test]
fn test_keccak_preimage_packing_cases() {
let cases: &[(&[u8], &[u32])] = &[
(&[], &[]),
(&[0x42], &[0x0000_0042]),
(&[1, 2, 3, 4], &[0x0403_0201]),
(&[1, 2, 3, 4, 5], &[0x0403_0201, 0x0000_0005]),
];
for (input, expected_u32) in cases {
let preimage = KeccakPreimage::new((*input).to_vec());
let felts = preimage.as_felts();
assert_eq!(felts.len(), expected_u32.len());
for (felt, &u) in felts.iter().zip((*expected_u32).iter()) {
assert_eq!(*felt, Felt::from(u));
}
if input.is_empty() {
assert_eq!(preimage.input_commitment(), Word::empty());
}
}
let input: Vec<u8> = (1..=32).collect();
let preimage = KeccakPreimage::new(input);
let felts = preimage.as_felts();
assert_eq!(felts.len(), 8);
assert_eq!(felts[0], Felt::from(u32::from_le_bytes([1, 2, 3, 4])));
assert_eq!(felts[7], Felt::from(u32::from_le_bytes([29, 30, 31, 32])));
}
#[test]
fn test_keccak_preimage_digest_consistency() {
let input = b"hello world";
let preimage = KeccakPreimage::new(input.to_vec());
let preimage_digest = preimage.digest();
let direct_hash = Keccak256::hash(input);
let direct_digest = KeccakFeltDigest::from_bytes(&direct_hash);
assert_eq!(preimage_digest, direct_digest);
}
#[test]
fn test_keccak_preimage_commitments() {
let input = b"test input for commitments";
let preimage = KeccakPreimage::new(input.to_vec());
let felts = preimage.as_felts();
let expected_input_commitment = Rpo256::hash_elements(&felts);
assert_eq!(preimage.input_commitment(), expected_input_commitment);
let digest = preimage.digest();
let expected_digest_commitment = Rpo256::hash_elements(digest.as_ref());
assert_eq!(digest.to_commitment(), expected_digest_commitment);
let expected_precompile_commitment = PrecompileCommitment::new(
preimage.precompile_tag(),
Rpo256::merge(&[preimage.input_commitment(), digest.to_commitment()]),
);
assert_eq!(preimage.precompile_commitment(), expected_precompile_commitment);
}
#[test]
fn test_keccak_verifier() {
let input = b"test verifier input";
let preimage = KeccakPreimage::new(input.to_vec());
let expected_commitment = preimage.precompile_commitment();
let commitment = KeccakPrecompile.verify(input).unwrap();
assert_eq!(commitment, expected_commitment);
}
}