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::poseidon2::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::poseidon2::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::poseidon2::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::poseidon2::hash_words"]
127        pub fn extern_hash_words(start_addr: u32, end_addr: u32, result_ptr: *mut Felt);
128    }
129
130    /// Encodes 32 bytes as 8 little-endian u32 lanes.
131    #[inline(always)]
132    fn bytes_to_u32_le_8(input: [u8; 32]) -> [u32; 8] {
133        core::array::from_fn(|i| {
134            let off = i * 4;
135            u32::from_le_bytes([input[off], input[off + 1], input[off + 2], input[off + 3]])
136        })
137    }
138
139    /// Encodes 64 bytes as 16 little-endian u32 lanes.
140    #[inline(always)]
141    fn bytes_to_u32_le_16(input: [u8; 64]) -> [u32; 16] {
142        core::array::from_fn(|i| {
143            let off = i * 4;
144            u32::from_le_bytes([input[off], input[off + 1], input[off + 2], input[off + 3]])
145        })
146    }
147
148    /// Encodes 32 bytes as 8 big-endian u32 lanes.
149    #[inline(always)]
150    fn bytes_to_u32_be_8(input: [u8; 32]) -> [u32; 8] {
151        core::array::from_fn(|i| {
152            let off = i * 4;
153            u32::from_be_bytes([input[off], input[off + 1], input[off + 2], input[off + 3]])
154        })
155    }
156
157    /// Encodes 64 bytes as 16 big-endian u32 lanes.
158    #[inline(always)]
159    fn bytes_to_u32_be_16(input: [u8; 64]) -> [u32; 16] {
160        core::array::from_fn(|i| {
161            let off = i * 4;
162            u32::from_be_bytes([input[off], input[off + 1], input[off + 2], input[off + 3]])
163        })
164    }
165
166    #[inline(always)]
167    fn decode_be_lanes_in_place(bytes: &mut [u8]) {
168        for chunk in bytes.chunks_exact_mut(4) {
169            chunk.reverse();
170        }
171    }
172
173    /// Hashes a 32-byte input to a 32-byte output using the BLAKE3 hash function.
174    #[inline]
175    pub fn blake3_hash(input: [u8; 32]) -> [u8; 32] {
176        use crate::intrinsics::WordAligned;
177
178        let lanes = bytes_to_u32_le_8(input);
179        unsafe {
180            let mut ret_area = ::core::mem::MaybeUninit::<WordAligned<[u8; 32]>>::uninit();
181            let ptr = ret_area.as_mut_ptr() as *mut u8;
182            extern_blake3_hash(
183                lanes[0], lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], lanes[7], ptr,
184            );
185            ret_area.assume_init().into_inner()
186        }
187    }
188
189    /// Hashes a 64-byte input to a 32-byte output using the BLAKE3 hash function.
190    #[inline]
191    pub fn blake3_merge(input: [u8; 64]) -> [u8; 32] {
192        let lanes = bytes_to_u32_le_16(input);
193        unsafe {
194            let mut ret_area = ::core::mem::MaybeUninit::<[u8; 32]>::uninit();
195            let ptr = ret_area.as_mut_ptr() as *mut u8;
196            extern_blake3_merge(
197                lanes[0], lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], lanes[7],
198                lanes[8], lanes[9], lanes[10], lanes[11], lanes[12], lanes[13], lanes[14],
199                lanes[15], ptr,
200            );
201            ret_area.assume_init()
202        }
203    }
204
205    /// Hashes a 32-byte input to a 32-byte output using the SHA256 hash function.
206    #[inline]
207    pub fn sha256_hash(input: [u8; 32]) -> [u8; 32] {
208        use crate::intrinsics::WordAligned;
209
210        let lanes = bytes_to_u32_be_8(input);
211        unsafe {
212            let mut ret_area = ::core::mem::MaybeUninit::<WordAligned<[u8; 32]>>::uninit();
213            let ptr = ret_area.as_mut_ptr() as *mut u8;
214            extern_sha256_hash(
215                lanes[0], lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], lanes[7], ptr,
216            );
217            let mut output = ret_area.assume_init().into_inner();
218            decode_be_lanes_in_place(&mut output);
219            output
220        }
221    }
222
223    /// Hashes a 64-byte input to a 32-byte output using the SHA256 hash function.
224    #[inline]
225    pub fn sha256_merge(input: [u8; 64]) -> [u8; 32] {
226        let lanes = bytes_to_u32_be_16(input);
227        unsafe {
228            let mut ret_area = ::core::mem::MaybeUninit::<[u8; 32]>::uninit();
229            let ptr = ret_area.as_mut_ptr() as *mut u8;
230            extern_sha256_merge(
231                lanes[0], lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], lanes[7],
232                lanes[8], lanes[9], lanes[10], lanes[11], lanes[12], lanes[13], lanes[14],
233                lanes[15], ptr,
234            );
235            let mut output = ret_area.assume_init();
236            decode_be_lanes_in_place(&mut output);
237            output
238        }
239    }
240
241    /// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO)
242    /// hash function.
243    ///
244    /// This maps to the `miden::core::crypto::hashes::poseidon2::hash_elements` procedure and to the
245    /// `miden::core::crypto::hashes::poseidon2::hash_words` word-optimized variant when the input
246    /// length is a multiple of 4.
247    ///
248    /// # Arguments
249    /// * `elements` - A Vec of field elements to be hashed
250    #[inline]
251    pub fn hash_elements(elements: Vec<Felt>) -> Digest {
252        let rust_ptr = elements.as_ptr().addr() as u32;
253        let element_count = elements.len();
254        let num_elements = element_count as u32;
255
256        unsafe {
257            let mut ret_area = core::mem::MaybeUninit::<Word>::uninit();
258            let result_ptr = ret_area.as_mut_ptr() as *mut Felt;
259            let miden_ptr = rust_ptr / 4;
260            // Since our BumpAlloc produces word-aligned allocations the pointer should be word-aligned
261            assert_eq(Felt::new((miden_ptr % 4) as u64), felt!(0));
262
263            if element_count.is_multiple_of(4) {
264                let start_addr = miden_ptr;
265                let end_addr = start_addr + num_elements;
266                extern_hash_words(start_addr, end_addr, result_ptr);
267            } else {
268                extern_hash_elements(miden_ptr, num_elements, result_ptr);
269            }
270
271            Digest::from_word(ret_area.assume_init())
272        }
273    }
274
275    /// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO)
276    /// hash function.
277    ///
278    /// This maps to the `miden::core::crypto::hashes::poseidon2::hash_words` procedure.
279    ///
280    /// # Arguments
281    /// * `words` - A slice of words to be hashed
282    #[inline]
283    pub fn hash_words(words: &[Word]) -> Digest {
284        let rust_ptr = words.as_ptr().addr() as u32;
285
286        let miden_ptr = rust_ptr / 4;
287        // It's safe to assume the `words` ptr is word-aligned.
288        assert_eq(Felt::new((miden_ptr % 4) as u64), felt!(0));
289
290        unsafe {
291            let mut ret_area = core::mem::MaybeUninit::<Word>::uninit();
292            let result_ptr = ret_area.as_mut_ptr() as *mut Felt;
293            let start_addr = miden_ptr;
294            let end_addr = start_addr + (words.len() as u32 * 4);
295            extern_hash_words(start_addr, end_addr, result_ptr);
296
297            Digest::from_word(ret_area.assume_init())
298        }
299    }
300}
301
302#[cfg(not(all(target_family = "wasm", miden)))]
303mod imp {
304    use alloc::vec::Vec;
305
306    use crate::intrinsics::{Digest, Felt, Word};
307
308    /// Computes BLAKE3 1-to-1 hash.
309    #[inline]
310    pub fn blake3_hash(_input: [u8; 32]) -> [u8; 32] {
311        unimplemented!(
312            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
313        )
314    }
315
316    /// Computes BLAKE3 2-to-1 hash.
317    #[inline]
318    pub fn blake3_merge(_input: [u8; 64]) -> [u8; 32] {
319        unimplemented!(
320            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
321        )
322    }
323
324    /// Computes SHA256 1-to-1 hash.
325    #[inline]
326    pub fn sha256_hash(_input: [u8; 32]) -> [u8; 32] {
327        unimplemented!(
328            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
329        )
330    }
331
332    /// Computes SHA256 2-to-1 hash.
333    #[inline]
334    pub fn sha256_merge(_input: [u8; 64]) -> [u8; 32] {
335        unimplemented!(
336            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
337        )
338    }
339
340    /// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO)
341    /// hash function.
342    #[inline]
343    pub fn hash_elements(_elements: Vec<Felt>) -> Digest {
344        unimplemented!(
345            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
346        )
347    }
348
349    /// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO) hash
350    /// function.
351    #[inline]
352    pub fn hash_words(_words: &[Word]) -> Digest {
353        unimplemented!(
354            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
355        )
356    }
357
358    /// ABI helper for `miden::core::crypto::hashes::poseidon2::hash_elements`.
359    #[inline]
360    pub fn extern_hash_elements(_ptr: u32, _num_elements: u32, _result_ptr: *mut Felt) {
361        unimplemented!(
362            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
363        )
364    }
365
366    /// ABI helper for `miden::core::crypto::hashes::poseidon2::hash_words`.
367    #[inline]
368    pub fn extern_hash_words(_start_addr: u32, _end_addr: u32, _result_ptr: *mut Felt) {
369        unimplemented!(
370            "miden::core::crypto::hashes bindings are only available when targeting the Miden VM"
371        )
372    }
373}
374
375pub use imp::*;