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
5use alloc::vec::Vec;
6
7use crate::{
8    felt,
9    intrinsics::{Digest, Felt, Word, assert_eq},
10};
11
12unsafe extern "C" {
13    /// Computes BLAKE3 1-to-1 hash.
14    ///
15    /// Input: 32-bytes stored in the first 8 elements of the stack (32 bits per element).
16    /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
17    /// The output is passed back to the caller via a pointer.
18    #[link_name = "std::crypto::hashes::blake3::hash_1to1"]
19    fn extern_blake3_hash_1to1(
20        e1: u32,
21        e2: u32,
22        e3: u32,
23        e4: u32,
24        e5: u32,
25        e6: u32,
26        e7: u32,
27        e8: u32,
28        ptr: *mut u8,
29    );
30
31    /// Computes BLAKE3 2-to-1 hash.
32    ///
33    /// Input: 64-bytes stored in the first 16 elements of the stack (32 bits per element).
34    /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element)
35    /// The output is passed back to the caller via a pointer.
36    #[link_name = "std::crypto::hashes::blake3::hash_2to1"]
37    fn extern_blake3_hash_2to1(
38        e1: u32,
39        e2: u32,
40        e3: u32,
41        e4: u32,
42        e5: u32,
43        e6: u32,
44        e7: u32,
45        e8: u32,
46        e9: u32,
47        e10: u32,
48        e11: u32,
49        e12: u32,
50        e13: u32,
51        e14: u32,
52        e15: u32,
53        e16: u32,
54        ptr: *mut u8,
55    );
56}
57
58unsafe extern "C" {
59    /// Computes SHA256 1-to-1 hash.
60    ///
61    /// Input: 32-bytes stored in the first 8 elements of the stack (32 bits per element).
62    /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
63    /// The output is passed back to the caller via a pointer.
64    #[link_name = "std::crypto::hashes::sha256::hash_1to1"]
65    fn extern_sha256_hash_1to1(
66        e1: u32,
67        e2: u32,
68        e3: u32,
69        e4: u32,
70        e5: u32,
71        e6: u32,
72        e7: u32,
73        e8: u32,
74        ptr: *mut u8,
75    );
76
77    /// Computes SHA256 2-to-1 hash.
78    ///
79    /// Input: 64-bytes stored in the first 16 elements of the stack (32 bits per element).
80    /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
81    /// The output is passed back to the caller via a pointer.
82    #[link_name = "std::crypto::hashes::sha256::hash_2to1"]
83    fn extern_sha256_hash_2to1(
84        e1: u32,
85        e2: u32,
86        e3: u32,
87        e4: u32,
88        e5: u32,
89        e6: u32,
90        e7: u32,
91        e8: u32,
92        e9: u32,
93        e10: u32,
94        e11: u32,
95        e12: u32,
96        e13: u32,
97        e14: u32,
98        e15: u32,
99        e16: u32,
100        ptr: *mut u8,
101    );
102}
103
104unsafe extern "C" {
105    /// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO)
106    /// hash function.
107    ///
108    /// This maps to the `std::crypto::rpo::hash_memory` procedure in the Miden stdlib.
109    ///
110    /// Input: A pointer to the memory location and the number of elements to hash
111    /// Output: One digest (4 field elements)
112    /// The output is passed back to the caller via a pointer.
113    #[link_name = "std::crypto::hashes::rpo::hash_memory"]
114    pub fn extern_hash_memory(ptr: u32, num_elements: u32, result_ptr: *mut Felt);
115
116    /// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO) hash
117    /// function.
118    ///
119    /// This maps to the `std::crypto::hashes::rpo::hash_memory_words` procedure in the Miden
120    /// stdlib.
121    ///
122    /// Input: The start and end addresses (in field elements) of the words to hash.
123    /// Output: One digest (4 field elements)
124    /// The output is passed back to the caller via a pointer.
125    #[link_name = "std::crypto::hashes::rpo::hash_memory_words"]
126    pub fn extern_hash_memory_words(start_addr: u32, end_addr: u32, result_ptr: *mut Felt);
127}
128
129/// Hashes a 32-byte input to a 32-byte output using the given hash function.
130#[inline(always)]
131fn hash_1to1(
132    input: [u8; 32],
133    extern_hash_1to1: unsafe extern "C" fn(u32, u32, u32, u32, u32, u32, u32, u32, *mut u8),
134) -> [u8; 32] {
135    use crate::intrinsics::WordAligned;
136    let input = unsafe { core::mem::transmute::<[u8; 32], [u32; 8]>(input) };
137    unsafe {
138        let mut ret_area = ::core::mem::MaybeUninit::<WordAligned<[u8; 32]>>::uninit();
139        let ptr = ret_area.as_mut_ptr() as *mut u8;
140        extern_hash_1to1(
141            input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], ptr,
142        );
143        ret_area.assume_init().into_inner()
144    }
145}
146
147/// Hashes a 64-byte input to a 32-byte output using the given hash function.
148#[inline(always)]
149fn hash_2to1(
150    input: [u8; 64],
151    extern_hash_2to1: unsafe extern "C" fn(
152        u32,
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        *mut u8,
169    ),
170) -> [u8; 32] {
171    let input = unsafe { core::mem::transmute::<[u8; 64], [u32; 16]>(input) };
172    unsafe {
173        let mut ret_area = ::core::mem::MaybeUninit::<[u8; 32]>::uninit();
174        let ptr = ret_area.as_mut_ptr() as *mut u8;
175        extern_hash_2to1(
176            input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
177            input[8], input[9], input[10], input[11], input[12], input[13], input[14], input[15],
178            ptr,
179        );
180        ret_area.assume_init()
181    }
182}
183
184/// Hashes a 32-byte input to a 32-byte output using the BLAKE3 hash function.
185#[inline]
186pub fn blake3_hash_1to1(input: [u8; 32]) -> [u8; 32] {
187    hash_1to1(input, extern_blake3_hash_1to1)
188}
189
190/// Hashes a 64-byte input to a 32-byte output using the BLAKE3 hash function.
191#[inline]
192pub fn blake3_hash_2to1(input: [u8; 64]) -> [u8; 32] {
193    hash_2to1(input, extern_blake3_hash_2to1)
194}
195
196/// Hashes a 32-byte input to a 32-byte output using the SHA256 hash function.
197#[inline]
198pub fn sha256_hash_1to1(input: [u8; 32]) -> [u8; 32] {
199    use crate::intrinsics::WordAligned;
200
201    let swapped_words = {
202        let mut be_bytes = input;
203        // The SHA-2 family is specified over big-endian 32-bit words. The Miden ABI mirrors that
204        // spec, so each lane we pass across the boundary must be encoded as a big-endian word.
205        // Our public Rust API uses `[u8; 32]` in native little-endian order, so we convert the bytes
206        // here before calling into the ABI.
207        for chunk in be_bytes.chunks_exact_mut(4) {
208            chunk.reverse();
209        }
210        unsafe { core::mem::transmute::<[u8; 32], [u32; 8]>(be_bytes) }
211    };
212
213    let [w0, w1, w2, w3, w4, w5, w6, w7] = swapped_words;
214
215    unsafe {
216        let mut ret_area = ::core::mem::MaybeUninit::<WordAligned<[u8; 32]>>::uninit();
217        let ptr = ret_area.as_mut_ptr() as *mut u8;
218        extern_sha256_hash_1to1(w0, w1, w2, w3, w4, w5, w6, w7, ptr);
219        let mut output = ret_area.assume_init().into_inner();
220        // The extern returns the digest as big-endian words as well; flip each lane so callers see
221        // the conventional Rust `[u8; 32]` ordering.
222        for chunk in output.chunks_exact_mut(4) {
223            chunk.reverse();
224        }
225        output
226    }
227}
228
229/// Hashes a 64-byte input to a 32-byte output using the SHA256 hash function.
230#[inline]
231pub fn sha256_hash_2to1(input: [u8; 64]) -> [u8; 32] {
232    use crate::intrinsics::WordAligned;
233
234    let swapped_words = {
235        let mut be_bytes = input;
236        // Same story as `sha256_hash_1to1`: adjust the byte layout so the ABI receives big-endian
237        // 32-bit words.
238        for chunk in be_bytes.chunks_exact_mut(4) {
239            chunk.reverse();
240        }
241        unsafe { core::mem::transmute::<[u8; 64], [u32; 16]>(be_bytes) }
242    };
243
244    let [w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15] = swapped_words;
245
246    unsafe {
247        let mut ret_area = ::core::mem::MaybeUninit::<WordAligned<[u8; 32]>>::uninit();
248        let ptr = ret_area.as_mut_ptr() as *mut u8;
249        extern_sha256_hash_2to1(
250            w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, ptr,
251        );
252        let mut output = ret_area.assume_init().into_inner();
253        // Restore the little-endian byte layout expected by Rust callers.
254        for chunk in output.chunks_exact_mut(4) {
255            chunk.reverse();
256        }
257        output
258    }
259}
260
261/// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO)
262/// hash function.
263///
264/// This maps to the `std::crypto::rpo::hash_memory` procedure in the Miden stdlib and to the
265/// `std::crypto::hashes::rpo::hash_memory_words` word-optimized variant when the input length is a
266/// multiple of 4.
267///
268/// # Arguments
269/// * `elements` - A Vec of field elements to be hashed
270#[inline]
271pub fn hash_elements(elements: Vec<Felt>) -> Digest {
272    let rust_ptr = elements.as_ptr().addr() as u32;
273    let element_count = elements.len();
274    let num_elements = element_count as u32;
275
276    unsafe {
277        let mut ret_area = core::mem::MaybeUninit::<Word>::uninit();
278        let result_ptr = ret_area.as_mut_ptr() as *mut Felt;
279        let miden_ptr = rust_ptr / 4;
280        // Since our BumpAlloc produces word-aligned allocations the pointer should be word-aligned
281        assert_eq(Felt::from_u32(miden_ptr % 4), felt!(0));
282
283        if element_count.is_multiple_of(4) {
284            let start_addr = miden_ptr;
285            let end_addr = start_addr + num_elements;
286            extern_hash_memory_words(start_addr, end_addr, result_ptr);
287        } else {
288            extern_hash_memory(miden_ptr, num_elements, result_ptr);
289        }
290
291        Digest::from_word(ret_area.assume_init().reverse())
292    }
293}
294
295/// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO)
296/// hash function.
297///
298/// This maps to the `std::crypto::hashes::rpo::hash_memory_words` procedure in the Miden stdlib.
299///
300/// # Arguments
301/// * `words` - A slice of words to be hashed
302#[inline]
303pub fn hash_words(words: &[Word]) -> Digest {
304    let rust_ptr = words.as_ptr().addr() as u32;
305
306    unsafe {
307        let mut ret_area = core::mem::MaybeUninit::<Word>::uninit();
308        let result_ptr = ret_area.as_mut_ptr() as *mut Felt;
309        let miden_ptr = rust_ptr / 4;
310        // It's safe to assume the `words` ptr is word-aligned.
311        assert_eq(Felt::from_u32(miden_ptr % 4), felt!(0));
312
313        let start_addr = miden_ptr;
314        let end_addr = start_addr + (words.len() as u32 * 4);
315        extern_hash_memory_words(start_addr, end_addr, result_ptr);
316
317        Digest::from_word(ret_area.assume_init().reverse())
318    }
319}