Skip to main content

miden_stdlib_sys/stdlib/crypto/
hashes.rs

1//! Contains procedures for computing hashes using BLAKE3 and SHA256 hash
2//! functions. The input and output elements are assumed to contain one 32-bit
3//! value per element.
4
5#[cfg(all(target_family = "wasm", miden))]
6mod imp {
7    use alloc::vec::Vec;
8
9    use crate::{
10        felt,
11        intrinsics::{Digest, Felt, Word, assert_eq},
12    };
13
14    unsafe extern "C" {
15        /// Computes BLAKE3 1-to-1 hash.
16        ///
17        /// Input: 32-bytes stored in the first 8 elements of the stack (32 bits per element).
18        /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
19        /// The output is passed back to the caller via a pointer.
20        #[link_name = "miden::core::crypto::hashes::blake3::hash"]
21        fn extern_blake3_hash(
22            e1: u32,
23            e2: u32,
24            e3: u32,
25            e4: u32,
26            e5: u32,
27            e6: u32,
28            e7: u32,
29            e8: u32,
30            ptr: *mut u8,
31        );
32
33        /// Computes BLAKE3 2-to-1 hash.
34        ///
35        /// Input: 64-bytes stored in the first 16 elements of the stack (32 bits per element).
36        /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element)
37        /// The output is passed back to the caller via a pointer.
38        #[link_name = "miden::core::crypto::hashes::blake3::merge"]
39        fn extern_blake3_merge(
40            e1: u32,
41            e2: u32,
42            e3: u32,
43            e4: u32,
44            e5: u32,
45            e6: u32,
46            e7: u32,
47            e8: u32,
48            e9: u32,
49            e10: u32,
50            e11: u32,
51            e12: u32,
52            e13: u32,
53            e14: u32,
54            e15: u32,
55            e16: u32,
56            ptr: *mut u8,
57        );
58    }
59
60    unsafe extern "C" {
61        /// Computes SHA256 1-to-1 hash.
62        ///
63        /// Input: 32-bytes stored in the first 8 elements of the stack (32 bits per element).
64        /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
65        /// The output is passed back to the caller via a pointer.
66        #[link_name = "miden::core::crypto::hashes::sha256::hash"]
67        fn extern_sha256_hash(
68            e1: u32,
69            e2: u32,
70            e3: u32,
71            e4: u32,
72            e5: u32,
73            e6: u32,
74            e7: u32,
75            e8: u32,
76            ptr: *mut u8,
77        );
78
79        /// Computes SHA256 2-to-1 hash.
80        ///
81        /// Input: 64-bytes stored in the first 16 elements of the stack (32 bits per element).
82        /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
83        /// The output is passed back to the caller via a pointer.
84        #[link_name = "miden::core::crypto::hashes::sha256::merge"]
85        fn extern_sha256_merge(
86            e1: u32,
87            e2: u32,
88            e3: u32,
89            e4: u32,
90            e5: u32,
91            e6: u32,
92            e7: u32,
93            e8: u32,
94            e9: u32,
95            e10: u32,
96            e11: u32,
97            e12: u32,
98            e13: u32,
99            e14: u32,
100            e15: u32,
101            e16: u32,
102            ptr: *mut u8,
103        );
104    }
105
106    unsafe extern "C" {
107        /// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO)
108        /// hash function.
109        ///
110        /// This maps to the `miden::core::crypto::hashes::rpo256::hash_elements` procedure.
111        ///
112        /// Input: A pointer to the memory location and the number of elements to hash
113        /// Output: One digest (4 field elements)
114        /// The output is passed back to the caller via a pointer.
115        #[link_name = "miden::core::crypto::hashes::rpo256::hash_elements"]
116        pub fn extern_hash_elements(ptr: u32, num_elements: u32, result_ptr: *mut Felt);
117
118        /// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO) hash
119        /// function.
120        ///
121        /// This maps to the `miden::core::crypto::hashes::rpo256::hash_words` procedure.
122        ///
123        /// Input: The start and end addresses (in field elements) of the words to hash.
124        /// Output: One digest (4 field elements)
125        /// The output is passed back to the caller via a pointer.
126        #[link_name = "miden::core::crypto::hashes::rpo256::hash_words"]
127        pub fn extern_hash_words(start_addr: u32, end_addr: u32, result_ptr: *mut Felt);
128    }
129
130    /// Hashes a 32-byte input to a 32-byte output using the given hash function.
131    #[inline(always)]
132    fn hash(
133        input: [u8; 32],
134        extern_hash: unsafe extern "C" fn(u32, u32, u32, u32, u32, u32, u32, u32, *mut u8),
135    ) -> [u8; 32] {
136        use crate::intrinsics::WordAligned;
137        let input = unsafe { core::mem::transmute::<[u8; 32], [u32; 8]>(input) };
138        unsafe {
139            let mut ret_area = ::core::mem::MaybeUninit::<WordAligned<[u8; 32]>>::uninit();
140            let ptr = ret_area.as_mut_ptr() as *mut u8;
141            extern_hash(
142                input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], ptr,
143            );
144            ret_area.assume_init().into_inner()
145        }
146    }
147
148    /// Hashes a 64-byte input to a 32-byte output using the given hash function.
149    #[inline(always)]
150    fn merge(
151        input: [u8; 64],
152        extern_merge: unsafe extern "C" fn(
153            u32,
154            u32,
155            u32,
156            u32,
157            u32,
158            u32,
159            u32,
160            u32,
161            u32,
162            u32,
163            u32,
164            u32,
165            u32,
166            u32,
167            u32,
168            u32,
169            *mut u8,
170        ),
171    ) -> [u8; 32] {
172        let input = unsafe { core::mem::transmute::<[u8; 64], [u32; 16]>(input) };
173        unsafe {
174            let mut ret_area = ::core::mem::MaybeUninit::<[u8; 32]>::uninit();
175            let ptr = ret_area.as_mut_ptr() as *mut u8;
176            extern_merge(
177                input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
178                input[8], input[9], input[10], input[11], input[12], input[13], input[14],
179                input[15], ptr,
180            );
181            ret_area.assume_init()
182        }
183    }
184
185    /// Hashes a 32-byte input to a 32-byte output using the BLAKE3 hash function.
186    #[inline]
187    pub fn blake3_hash(input: [u8; 32]) -> [u8; 32] {
188        hash(input, extern_blake3_hash)
189    }
190
191    /// Hashes a 64-byte input to a 32-byte output using the BLAKE3 hash function.
192    #[inline]
193    pub fn blake3_merge(input: [u8; 64]) -> [u8; 32] {
194        merge(input, extern_blake3_merge)
195    }
196
197    /// Hashes a 32-byte input to a 32-byte output using the SHA256 hash function.
198    #[inline]
199    pub fn sha256_hash(input: [u8; 32]) -> [u8; 32] {
200        use crate::intrinsics::WordAligned;
201
202        let swapped_words = {
203            let mut be_bytes = input;
204            // The SHA-2 family is specified over big-endian 32-bit words. The Miden ABI mirrors that
205            // spec, so each lane we pass across the boundary must be encoded as a big-endian word.
206            // Our public Rust API uses `[u8; 32]` in native little-endian order, so we convert the bytes
207            // here before calling into the ABI.
208            for chunk in be_bytes.chunks_exact_mut(4) {
209                chunk.reverse();
210            }
211            unsafe { core::mem::transmute::<[u8; 32], [u32; 8]>(be_bytes) }
212        };
213
214        let [w0, w1, w2, w3, w4, w5, w6, w7] = swapped_words;
215
216        unsafe {
217            let mut ret_area = ::core::mem::MaybeUninit::<WordAligned<[u8; 32]>>::uninit();
218            let ptr = ret_area.as_mut_ptr() as *mut u8;
219            extern_sha256_hash(w0, w1, w2, w3, w4, w5, w6, w7, ptr);
220            let mut output = ret_area.assume_init().into_inner();
221            // The extern returns the digest as big-endian words as well; flip each lane so callers see
222            // the conventional Rust `[u8; 32]` ordering.
223            for chunk in output.chunks_exact_mut(4) {
224                chunk.reverse();
225            }
226            output
227        }
228    }
229
230    /// Hashes a 64-byte input to a 32-byte output using the SHA256 hash function.
231    #[inline]
232    pub fn sha256_merge(input: [u8; 64]) -> [u8; 32] {
233        use crate::intrinsics::WordAligned;
234
235        let swapped_words = {
236            let mut be_bytes = input;
237            // Same story as `sha256_hash`: adjust the byte layout so the ABI receives big-endian
238            // 32-bit words.
239            for chunk in be_bytes.chunks_exact_mut(4) {
240                chunk.reverse();
241            }
242            unsafe { core::mem::transmute::<[u8; 64], [u32; 16]>(be_bytes) }
243        };
244
245        let [w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15] = swapped_words;
246
247        unsafe {
248            let mut ret_area = ::core::mem::MaybeUninit::<WordAligned<[u8; 32]>>::uninit();
249            let ptr = ret_area.as_mut_ptr() as *mut u8;
250            extern_sha256_merge(
251                w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, ptr,
252            );
253            let mut output = ret_area.assume_init().into_inner();
254            // Restore the little-endian byte layout expected by Rust callers.
255            for chunk in output.chunks_exact_mut(4) {
256                chunk.reverse();
257            }
258            output
259        }
260    }
261
262    /// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO)
263    /// hash function.
264    ///
265    /// This maps to the `miden::core::crypto::hashes::rpo256::hash_elements` procedure and to the
266    /// `miden::core::crypto::hashes::rpo256::hash_words` word-optimized variant when the input
267    /// length is a multiple of 4.
268    ///
269    /// # Arguments
270    /// * `elements` - A Vec of field elements to be hashed
271    #[inline]
272    pub fn hash_elements(elements: Vec<Felt>) -> Digest {
273        let rust_ptr = elements.as_ptr().addr() as u32;
274        let element_count = elements.len();
275        let num_elements = element_count as u32;
276
277        unsafe {
278            let mut ret_area = core::mem::MaybeUninit::<Word>::uninit();
279            let result_ptr = ret_area.as_mut_ptr() as *mut Felt;
280            let miden_ptr = rust_ptr / 4;
281            // Since our BumpAlloc produces word-aligned allocations the pointer should be word-aligned
282            assert_eq(Felt::from_u32(miden_ptr % 4), felt!(0));
283
284            if element_count.is_multiple_of(4) {
285                let start_addr = miden_ptr;
286                let end_addr = start_addr + num_elements;
287                extern_hash_words(start_addr, end_addr, result_ptr);
288            } else {
289                extern_hash_elements(miden_ptr, num_elements, result_ptr);
290            }
291
292            Digest::from_word(ret_area.assume_init().reverse())
293        }
294    }
295
296    /// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO)
297    /// hash function.
298    ///
299    /// This maps to the `miden::core::crypto::hashes::rpo256::hash_words` procedure.
300    ///
301    /// # Arguments
302    /// * `words` - A slice of words to be hashed
303    #[inline]
304    pub fn hash_words(words: &[Word]) -> Digest {
305        let rust_ptr = words.as_ptr().addr() as u32;
306
307        let miden_ptr = rust_ptr / 4;
308        // It's safe to assume the `words` ptr is word-aligned.
309        assert_eq(Felt::from_u32(miden_ptr % 4), felt!(0));
310
311        unsafe {
312            let mut ret_area = core::mem::MaybeUninit::<Word>::uninit();
313            let result_ptr = ret_area.as_mut_ptr() as *mut Felt;
314            let start_addr = miden_ptr;
315            let end_addr = start_addr + (words.len() as u32 * 4);
316            extern_hash_words(start_addr, end_addr, result_ptr);
317
318            Digest::from_word(ret_area.assume_init().reverse())
319        }
320    }
321}
322
323#[cfg(not(all(target_family = "wasm", miden)))]
324mod imp {
325    use alloc::vec::Vec;
326
327    use crate::intrinsics::{Digest, Felt, Word};
328
329    /// Computes BLAKE3 1-to-1 hash.
330    #[inline]
331    pub fn blake3_hash(_input: [u8; 32]) -> [u8; 32] {
332        unimplemented!(
333            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
334        )
335    }
336
337    /// Computes BLAKE3 2-to-1 hash.
338    #[inline]
339    pub fn blake3_merge(_input: [u8; 64]) -> [u8; 32] {
340        unimplemented!(
341            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
342        )
343    }
344
345    /// Computes SHA256 1-to-1 hash.
346    #[inline]
347    pub fn sha256_hash(_input: [u8; 32]) -> [u8; 32] {
348        unimplemented!(
349            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
350        )
351    }
352
353    /// Computes SHA256 2-to-1 hash.
354    #[inline]
355    pub fn sha256_merge(_input: [u8; 64]) -> [u8; 32] {
356        unimplemented!(
357            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
358        )
359    }
360
361    /// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO)
362    /// hash function.
363    #[inline]
364    pub fn hash_elements(_elements: Vec<Felt>) -> Digest {
365        unimplemented!(
366            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
367        )
368    }
369
370    /// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO) hash
371    /// function.
372    #[inline]
373    pub fn hash_words(_words: &[Word]) -> Digest {
374        unimplemented!(
375            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
376        )
377    }
378
379    /// ABI helper for `miden::core::crypto::hashes::rpo256::hash_elements`.
380    #[inline]
381    pub fn extern_hash_elements(_ptr: u32, _num_elements: u32, _result_ptr: *mut Felt) {
382        unimplemented!(
383            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
384        )
385    }
386
387    /// ABI helper for `miden::core::crypto::hashes::rpo256::hash_words`.
388    #[inline]
389    pub fn extern_hash_words(_start_addr: u32, _end_addr: u32, _result_ptr: *mut Felt) {
390        unimplemented!(
391            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
392        )
393    }
394}
395
396pub use imp::*;