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