miden_core_lib/handlers/keccak256.rs
1//! Keccak256 precompile for the Miden VM.
2//!
3//! This module provides both execution-time and verification-time support for Keccak256 hashing.
4//!
5//! ## Architecture
6//!
7//! ### Event Handler (Execution-Time)
8//! When the VM emits a Keccak event requesting non-deterministic hash results, the processor calls
9//! [`KeccakPrecompile`] which reads input data from memory, computes the hash, provides the digest
10//! via the advice stack, and logs the raw preimage bytes as a precompile request.
11//!
12//! ### Precompile Verifier (Verification-Time)
13//! During verification, the [`PrecompileVerifier`] receives the stored preimage bytes, recomputes
14//! the hash, and generates a commitment `P2(P2(input) || P2(digest))`, where P2 stands
15//! for Poseidon2, that validates the computation was performed correctly.
16//!
17//! ### Commitment Tag Format
18//! Each request is tagged as `[event_id, len_bytes, 0, 0]`. The `len_bytes` field prevents
19//! collisions: since bytes are packed into 32-bit limbs, we must distinguish actual data bytes
20//! from padding in the final limb.
21//!
22//! ## Digest Representation
23//! A Keccak256 digest (256 bits) is represented as 8 field elements `[h0, ..., h7]`,
24//! each containing a u32 value where `hi = u32::from_le_bytes([b_{4i}, ..., b_{4i+3}])`.
25
26use alloc::{format, vec, vec::Vec};
27use core::array;
28
29use miden_core::{
30 Felt, Word, ZERO,
31 crypto::hash::{Keccak256, Poseidon2},
32 events::EventName,
33 precompile::{PrecompileCommitment, PrecompileError, PrecompileRequest, PrecompileVerifier},
34 utils::bytes_to_packed_u32_elements,
35};
36use miden_processor::{
37 ProcessorState,
38 advice::AdviceMutation,
39 event::{EventError, EventHandler},
40};
41
42use crate::handlers::{BYTES_PER_U32, read_memory_packed_u32};
43
44/// Event name for the keccak256 hash_bytes operation.
45pub const KECCAK_HASH_BYTES_EVENT_NAME: EventName =
46 EventName::new("miden::core::hash::keccak256::hash_bytes");
47
48pub struct KeccakPrecompile;
49
50impl EventHandler for KeccakPrecompile {
51 /// Keccak256 event handler called by the processor when the VM emits a hash request event.
52 ///
53 /// Reads packed input data from memory, computes the Keccak256 hash, provides the digest via
54 /// the advice stack, and stores the raw preimage for verification (see [`PrecompileVerifier`]).
55 ///
56 /// ## Input Format
57 /// - **Stack**: `[event_id, ptr, len_bytes, ...]` where `ptr` is word-aligned (divisible by 4)
58 /// - **Memory**: Input bytes packed as u32 field elements (4 bytes per element, little-endian)
59 /// from `ptr` to `ptr+ceil(len_bytes/4)`, with unused bytes in the final u32 set to zero
60 ///
61 /// ## Output Format
62 /// - **Advice Stack**: Extended with digest `[h_0, ..., h_7]` (least significant u32 on top)
63 /// - **Precompile Request**: Stores tag `[event_id, len_bytes, 0, 0]` and raw preimage bytes
64 /// for verification time
65 fn on_event(&self, process: &ProcessorState) -> Result<Vec<AdviceMutation>, EventError> {
66 // Stack: [event_id, ptr, len_bytes, ...]
67 let ptr = process.get_stack_item(1).as_canonical_u64();
68 let len_bytes = process.get_stack_item(2).as_canonical_u64();
69
70 // Enforce the maximum precompile input size to prevent host-side resource exhaustion. Make
71 // sure to compare in u64 to avoid truncation on 32-bit targets.
72 let max = process.execution_options().max_hash_len_bytes();
73 if len_bytes > max as u64 {
74 return Err(format!(
75 "keccak256 input length {len_bytes} bytes exceeds maximum of {max} bytes"
76 )
77 .into());
78 }
79 let len_bytes = usize::try_from(len_bytes).map_err(|_| {
80 EventError::from(format!(
81 "keccak256 input length {len_bytes} exceeds addressable range"
82 ))
83 })?;
84
85 // Read input bytes from memory using the shared helper (u32-packed, LE, zero-padded)
86 let input_bytes = read_memory_packed_u32(process, ptr, len_bytes)?;
87
88 // Build preimage from bytes and compute digest
89 let preimage = KeccakPreimage::new(input_bytes);
90 let digest = preimage.digest();
91
92 // Extend the stack with the digest [h_0, ..., h_7] for consumption via adv_pipe
93 let advice_stack_extension = AdviceMutation::extend_stack(digest.0);
94
95 // Store the precompile data for deferred verification.
96 let precompile_request_extension =
97 AdviceMutation::extend_precompile_requests([preimage.into()]);
98
99 Ok(vec![advice_stack_extension, precompile_request_extension])
100 }
101}
102
103// KECCAK VERIFIER
104// ================================================================================================
105
106impl PrecompileVerifier for KeccakPrecompile {
107 /// Verifier for Keccak256 precompile computations at verification time.
108 ///
109 /// Receives the raw preimage bytes stored during execution (see [`EventHandler::on_event`]),
110 /// recomputes the Keccak256 hash, and generates a commitment `P2(P2(input) || P2(digest))`
111 /// with tag `[event_id, len_bytes, 0, 0]` that validates against the execution trace.
112 fn verify(&self, calldata: &[u8]) -> Result<PrecompileCommitment, PrecompileError> {
113 let preimage = KeccakPreimage::new(calldata.to_vec());
114 Ok(preimage.precompile_commitment())
115 }
116}
117
118// KECCAK DIGEST
119// ================================================================================================
120
121/// Keccak256 digest representation in the Miden VM.
122///
123/// Represents a 256-bit Keccak digest as 8 field elements, each containing a u32 value
124/// packed in little-endian order: `[d_0, ..., d_7]` where
125/// `d_0 = u32::from_le_bytes([b_0, b_1, b_2, b_3])` and so on.
126#[derive(Debug, Copy, Clone, Eq, PartialEq)]
127pub struct KeccakFeltDigest([Felt; 8]);
128
129impl KeccakFeltDigest {
130 /// Creates a digest from a 32-byte Keccak256 hash output.
131 pub fn from_bytes(bytes: &[u8]) -> Self {
132 assert_eq!(bytes.len(), 32, "input must be 32 bytes");
133 let packed: [u32; 8] = array::from_fn(|i| {
134 let limbs = array::from_fn(|j| bytes[BYTES_PER_U32 * i + j]);
135 u32::from_le_bytes(limbs)
136 });
137 Self(packed.map(Felt::from_u32))
138 }
139
140 /// Creates a commitment of the digest using Poseidon2 over `[d_0, ..., d_7]`.
141 pub fn to_commitment(&self) -> Word {
142 Poseidon2::hash_elements(&self.0)
143 }
144}
145
146// KECCAK PREIMAGE
147// ================================================================================================
148
149/// Keccak256 preimage structure representing the raw input data to be hashed.
150///
151/// This structure encapsulates the raw bytes that will be passed to the Keccak256
152/// hash function, providing utilities for:
153/// - Converting between bytes and field element representations
154/// - Computing the Keccak256 digest
155/// - Generating precompile commitments for verification
156/// - Handling the data packing format used by the VM
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct KeccakPreimage(Vec<u8>);
159
160impl KeccakPreimage {
161 /// Creates a new `KeccakPreimage` from a vector of bytes.
162 pub fn new(bytes: Vec<u8>) -> Self {
163 Self(bytes)
164 }
165
166 /// Consumes the preimage and returns the inner byte vector.
167 pub fn into_inner(self) -> Vec<u8> {
168 self.0
169 }
170
171 /// Converts the preimage bytes to field elements using u32 packing.
172 ///
173 /// Each field element contains a u32 value representing 4 bytes in little-endian format.
174 /// The last chunk is padded with zeros if the byte length is not a multiple of 4.
175 ///
176 /// Produces the same u32‑packed format expected by the MASM wrappers.
177 pub fn as_felts(&self) -> Vec<Felt> {
178 bytes_to_packed_u32_elements(self.as_ref())
179 }
180
181 /// Computes the Poseidon2 hash of the input data in field element format.
182 ///
183 /// This creates a cryptographic commitment to the input data that can be
184 /// used for verification purposes. The input is first converted to field
185 /// elements using the same packing format as the VM.
186 pub fn input_commitment(&self) -> Word {
187 Poseidon2::hash_elements(&self.as_felts())
188 }
189
190 /// Computes the Keccak256 hash of the preimage bytes.
191 ///
192 /// Returns the digest formatted as 8 field elements, each containing a u32 value
193 /// in little-endian byte order. This matches the format expected by the VM
194 /// and can be directly used on the operand stack.
195 pub fn digest(&self) -> KeccakFeltDigest {
196 let hash_u8 = Keccak256::hash(self.as_ref());
197 KeccakFeltDigest::from_bytes(&hash_u8)
198 }
199
200 /// Computes the precompile commitment: `P2(P2(input) || P2(keccak_hash))` with tag
201 /// `[event_id, len_bytes, 0, 0]`.
202 ///
203 /// Generated by the [`PrecompileVerifier`] at verification time and validated against
204 /// commitments tracked during execution by the [`EventHandler`]. The double hash binds
205 /// input and output together, preventing tampering.
206 pub fn precompile_commitment(&self) -> PrecompileCommitment {
207 let tag = self.precompile_tag();
208 let comm = Poseidon2::merge(&[self.input_commitment(), self.digest().to_commitment()]);
209 PrecompileCommitment::new(tag, comm)
210 }
211
212 /// Returns the tag used to identify the commitment to the precompile. defined as
213 /// `[event_id, preimage_u8.len(), 0, 0]` where event_id is computed from the event name.
214 fn precompile_tag(&self) -> Word {
215 [
216 KECCAK_HASH_BYTES_EVENT_NAME.to_event_id().as_felt(),
217 Felt::new(self.as_ref().len() as u64),
218 ZERO,
219 ZERO,
220 ]
221 .into()
222 }
223}
224
225impl From<KeccakPreimage> for PrecompileRequest {
226 fn from(preimage: KeccakPreimage) -> Self {
227 let event_id = KECCAK_HASH_BYTES_EVENT_NAME.to_event_id();
228 PrecompileRequest::new(event_id, preimage.into_inner())
229 }
230}
231
232impl AsRef<[u8]> for KeccakPreimage {
233 fn as_ref(&self) -> &[u8] {
234 &self.0
235 }
236}
237
238impl From<Vec<u8>> for KeccakPreimage {
239 fn from(bytes: Vec<u8>) -> Self {
240 Self::new(bytes)
241 }
242}
243
244impl AsRef<[Felt]> for KeccakFeltDigest {
245 fn as_ref(&self) -> &[Felt] {
246 &self.0
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 // KECCAK FELT DIGEST TESTS
255 // ============================================================================================
256
257 #[test]
258 fn test_keccak_felt_digest_from_bytes() {
259 // Test with a known 32-byte sequence
260 let bytes: Vec<u8> = (1..=32).collect();
261 let digest = KeccakFeltDigest::from_bytes(&bytes);
262
263 // Verify each u32 is packed correctly in little-endian order
264 // Each u32 is constructed from 4 consecutive bytes: byte[i] + byte[i+1]<<8 + byte[i+2]<<16
265 // + byte[i+3]<<24
266 let expected = [
267 u32::from_le_bytes([1, 2, 3, 4]),
268 u32::from_le_bytes([5, 6, 7, 8]),
269 u32::from_le_bytes([9, 10, 11, 12]),
270 u32::from_le_bytes([13, 14, 15, 16]),
271 u32::from_le_bytes([17, 18, 19, 20]),
272 u32::from_le_bytes([21, 22, 23, 24]),
273 u32::from_le_bytes([25, 26, 27, 28]),
274 u32::from_le_bytes([29, 30, 31, 32]),
275 ]
276 .map(Felt::from_u32);
277
278 assert_eq!(digest.0, expected);
279 }
280
281 // KECCAK PREIMAGE TESTS
282 // ============================================================================================
283
284 #[test]
285 fn test_keccak_preimage_packing_cases() {
286 // Table of inputs and expected u32-packed felts (little-endian)
287 let cases: &[(&[u8], &[u32])] = &[
288 (&[], &[]),
289 (&[0x42], &[0x0000_0042]),
290 (&[1, 2, 3, 4], &[0x0403_0201]),
291 (&[1, 2, 3, 4, 5], &[0x0403_0201, 0x0000_0005]),
292 ];
293
294 for (input, expected_u32) in cases {
295 let preimage = KeccakPreimage::new((*input).to_vec());
296 let felts = preimage.as_felts();
297 assert_eq!(felts.len(), expected_u32.len());
298 for (felt, &u) in felts.iter().zip((*expected_u32).iter()) {
299 assert_eq!(*felt, Felt::from_u32(u));
300 }
301
302 if input.is_empty() {
303 assert_eq!(preimage.input_commitment(), Word::empty());
304 }
305 }
306
307 // 32-byte boundary sanity check
308 let input: Vec<u8> = (1..=32).collect();
309 let preimage = KeccakPreimage::new(input);
310 let felts = preimage.as_felts();
311 assert_eq!(felts.len(), 8);
312 assert_eq!(felts[0], Felt::from_u32(u32::from_le_bytes([1, 2, 3, 4])));
313 assert_eq!(felts[7], Felt::from_u32(u32::from_le_bytes([29, 30, 31, 32])));
314 }
315
316 #[test]
317 fn test_keccak_preimage_digest_consistency() {
318 // Test that digest computation is consistent with direct Keccak256
319 let input = b"hello world";
320 let preimage = KeccakPreimage::new(input.to_vec());
321
322 // Compute digest using preimage
323 let preimage_digest = preimage.digest();
324
325 // Compute digest directly using Keccak256
326 let direct_hash = Keccak256::hash(input);
327 let direct_digest = KeccakFeltDigest::from_bytes(&direct_hash);
328
329 assert_eq!(preimage_digest, direct_digest);
330 }
331
332 #[test]
333 fn test_keccak_preimage_commitments() {
334 let input = b"test input for commitments";
335 let preimage = KeccakPreimage::new(input.to_vec());
336
337 // Test input commitment
338 let felts = preimage.as_felts();
339 let expected_input_commitment = Poseidon2::hash_elements(&felts);
340 assert_eq!(preimage.input_commitment(), expected_input_commitment);
341
342 // Test digest commitment
343 let digest = preimage.digest();
344 let expected_digest_commitment = Poseidon2::hash_elements(digest.as_ref());
345 assert_eq!(digest.to_commitment(), expected_digest_commitment);
346
347 // Test precompile commitment (double hash)
348 let expected_precompile_commitment = PrecompileCommitment::new(
349 preimage.precompile_tag(),
350 Poseidon2::merge(&[preimage.input_commitment(), digest.to_commitment()]),
351 );
352
353 assert_eq!(preimage.precompile_commitment(), expected_precompile_commitment);
354 }
355
356 #[test]
357 fn test_keccak_verifier() {
358 let input = b"test verifier input";
359 let preimage = KeccakPreimage::new(input.to_vec());
360 let expected_commitment = preimage.precompile_commitment();
361
362 let commitment = KeccakPrecompile.verify(input).unwrap();
363 assert_eq!(commitment, expected_commitment);
364 }
365}