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}