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