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