Skip to main content

miden_core_lib/handlers/
sha512.rs

1//! SHA2-512 precompile for the Miden VM.
2//!
3//! This mirrors the Keccak256 precompile flow but targets SHA2-512. Execution-time handlers read
4//! packed bytes from memory, compute the digest, extend the advice stack with the 512-bit hash, and
5//! record calldata for deferred verification. Verification-time logic recomputes the digest and
6//! commits to both input and output using Poseidon2 hashing.
7
8use alloc::{format, vec, vec::Vec};
9use core::array;
10
11use miden_core::{
12    Felt, Word, ZERO,
13    crypto::hash::{Poseidon2, Sha512},
14    events::EventName,
15    precompile::{PrecompileCommitment, PrecompileError, PrecompileRequest, PrecompileVerifier},
16    utils::bytes_to_packed_u32_elements,
17};
18use miden_processor::{
19    ProcessorState,
20    advice::AdviceMutation,
21    event::{EventError, EventHandler},
22};
23
24use crate::handlers::{BYTES_PER_U32, read_memory_packed_u32};
25
26/// Event name for the SHA512 hash_bytes operation.
27pub const SHA512_HASH_BYTES_EVENT_NAME: EventName =
28    EventName::new("miden::core::hash::sha512::hash_bytes");
29
30pub struct Sha512Precompile;
31
32impl EventHandler for Sha512Precompile {
33    /// SHA2-512 event handler invoked when the VM emits a hash request.
34    ///
35    /// Reads packed bytes from memory, computes the SHA2-512 digest, extends the advice stack with
36    /// the 16 u32 limbs of the digest, and stores the raw preimage for verification.
37    ///
38    /// ## Input Format
39    /// - **Stack**: `[event_id, ptr, len_bytes, ...]`
40    /// - **Memory**: bytes packed into u32 field elements starting at `ptr`
41    fn on_event(&self, process: &ProcessorState) -> Result<Vec<AdviceMutation>, EventError> {
42        // Stack: [event_id, ptr, len_bytes, ...]
43        let ptr = process.get_stack_item(1).as_canonical_u64();
44        let len_bytes = process.get_stack_item(2).as_canonical_u64();
45
46        // Enforce the maximum precompile input size to prevent host-side resource exhaustion. Make
47        // sure to compare in u64 to avoid truncation on 32-bit targets.
48        let max = process.execution_options().max_hash_len_bytes();
49        if len_bytes > max as u64 {
50            return Err(format!(
51                "sha512 input length {len_bytes} bytes exceeds maximum of {max} bytes"
52            )
53            .into());
54        }
55        let len_bytes = usize::try_from(len_bytes).map_err(|_| {
56            EventError::from(format!("sha512 input length {len_bytes} exceeds addressable range"))
57        })?;
58
59        // Read input bytes (u32-packed) from memory.
60        let input_bytes = read_memory_packed_u32(process, ptr, len_bytes)?;
61        let preimage = Sha512Preimage::new(input_bytes);
62        let digest = preimage.digest();
63
64        Ok(vec![
65            AdviceMutation::extend_stack(digest.0),
66            AdviceMutation::extend_precompile_requests([preimage.into()]),
67        ])
68    }
69}
70
71impl PrecompileVerifier for Sha512Precompile {
72    fn verify(&self, calldata: &[u8]) -> Result<PrecompileCommitment, PrecompileError> {
73        let preimage = Sha512Preimage::new(calldata.to_vec());
74        Ok(preimage.precompile_commitment())
75    }
76}
77
78// SHA2-512 DIGEST
79// ================================================================================================
80
81/// SHA2-512 digest represented as 16 field elements (u32-packed).
82#[derive(Debug, Copy, Clone, Eq, PartialEq)]
83pub struct Sha512FeltDigest(pub [Felt; 16]);
84
85impl Sha512FeltDigest {
86    pub fn from_bytes(bytes: &[u8]) -> Self {
87        assert_eq!(bytes.len(), 64, "digest must be 64 bytes");
88        let packed: [u32; 16] = array::from_fn(|i| {
89            let limbs = array::from_fn(|j| bytes[BYTES_PER_U32 * i + j]);
90            u32::from_le_bytes(limbs)
91        });
92        Self(packed.map(Felt::from_u32))
93    }
94
95    pub fn to_commitment(&self) -> Word {
96        Poseidon2::hash_elements(&self.0)
97    }
98}
99
100impl AsRef<[Felt]> for Sha512FeltDigest {
101    fn as_ref(&self) -> &[Felt] {
102        &self.0
103    }
104}
105
106// SHA2-512 PREIMAGE
107// ================================================================================================
108
109/// Wrapped preimage bytes along with helpers for commitments and digest computation.
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct Sha512Preimage(Vec<u8>);
112
113impl Sha512Preimage {
114    pub fn new(bytes: Vec<u8>) -> Self {
115        Self(bytes)
116    }
117
118    pub fn into_inner(self) -> Vec<u8> {
119        self.0
120    }
121
122    pub fn as_felts(&self) -> Vec<Felt> {
123        bytes_to_packed_u32_elements(self.as_ref())
124    }
125
126    pub fn input_commitment(&self) -> Word {
127        Poseidon2::hash_elements(&self.as_felts())
128    }
129
130    pub fn digest(&self) -> Sha512FeltDigest {
131        let hash = Sha512::hash(self.as_ref());
132        Sha512FeltDigest::from_bytes(&hash)
133    }
134
135    pub fn precompile_commitment(&self) -> PrecompileCommitment {
136        let tag = self.precompile_tag();
137        let comm = Poseidon2::merge(&[self.input_commitment(), self.digest().to_commitment()]);
138        PrecompileCommitment::new(tag, comm)
139    }
140
141    fn precompile_tag(&self) -> Word {
142        [
143            SHA512_HASH_BYTES_EVENT_NAME.to_event_id().as_felt(),
144            Felt::new(self.as_ref().len() as u64),
145            ZERO,
146            ZERO,
147        ]
148        .into()
149    }
150}
151
152impl AsRef<[u8]> for Sha512Preimage {
153    fn as_ref(&self) -> &[u8] {
154        &self.0
155    }
156}
157
158impl From<Sha512Preimage> for PrecompileRequest {
159    fn from(preimage: Sha512Preimage) -> Self {
160        PrecompileRequest::new(SHA512_HASH_BYTES_EVENT_NAME.to_event_id(), preimage.into_inner())
161    }
162}