Skip to main content

miden_crypto/hash/blake/
mod.rs

1use alloc::string::String;
2use core::{mem::size_of, ops::Deref, slice};
3
4use p3_field::{BasedVectorSpace, PrimeField64};
5use p3_goldilocks::Goldilocks as Felt;
6
7use super::HasherExt;
8use crate::utils::{
9    ByteReader, ByteWriter, Deserializable, DeserializationError, HexParseError, Serializable,
10    bytes_to_hex_string, hex_to_bytes,
11};
12
13#[cfg(test)]
14mod tests;
15
16// CONSTANTS
17// ================================================================================================
18
19const DIGEST32_BYTES: usize = 32;
20const DIGEST24_BYTES: usize = 24;
21
22// BLAKE3 N-BIT OUTPUT
23// ================================================================================================
24
25/// N-bytes output of a blake3 function.
26///
27/// Note: `N` can't be greater than `32` because [`Blake3Digest::as_bytes`] currently supports only
28/// 32 bytes.
29#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
30#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
31#[cfg_attr(feature = "serde", serde(into = "String", try_from = "&str"))]
32#[repr(transparent)]
33pub struct Blake3Digest<const N: usize>([u8; N]);
34
35impl<const N: usize> Blake3Digest<N> {
36    pub fn as_bytes(&self) -> [u8; 32] {
37        // compile-time assertion
38        assert!(N <= 32, "digest currently supports only 32 bytes!");
39        expand_bytes(&self.0)
40    }
41
42    pub fn digests_as_bytes(digests: &[Blake3Digest<N>]) -> &[u8] {
43        let p = digests.as_ptr();
44        let len = digests.len() * N;
45        unsafe { slice::from_raw_parts(p as *const u8, len) }
46    }
47}
48
49impl<const N: usize> Default for Blake3Digest<N> {
50    fn default() -> Self {
51        Self([0; N])
52    }
53}
54
55impl<const N: usize> Deref for Blake3Digest<N> {
56    type Target = [u8];
57
58    fn deref(&self) -> &Self::Target {
59        &self.0
60    }
61}
62
63impl<const N: usize> From<Blake3Digest<N>> for [u8; N] {
64    fn from(value: Blake3Digest<N>) -> Self {
65        value.0
66    }
67}
68
69impl<const N: usize> From<[u8; N]> for Blake3Digest<N> {
70    fn from(value: [u8; N]) -> Self {
71        Self(value)
72    }
73}
74
75impl<const N: usize> From<Blake3Digest<N>> for String {
76    fn from(value: Blake3Digest<N>) -> Self {
77        bytes_to_hex_string(value.as_bytes())
78    }
79}
80
81impl<const N: usize> TryFrom<&str> for Blake3Digest<N> {
82    type Error = HexParseError;
83
84    fn try_from(value: &str) -> Result<Self, Self::Error> {
85        hex_to_bytes(value).map(|v| v.into())
86    }
87}
88
89impl<const N: usize> Serializable for Blake3Digest<N> {
90    fn write_into<W: ByteWriter>(&self, target: &mut W) {
91        target.write_bytes(&self.0);
92    }
93}
94
95impl<const N: usize> Deserializable for Blake3Digest<N> {
96    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
97        source.read_array().map(Self)
98    }
99}
100
101// BLAKE3 256-BIT OUTPUT
102// ================================================================================================
103
104/// 256-bit output blake3 hasher.
105#[derive(Debug, Copy, Clone, Eq, PartialEq)]
106pub struct Blake3_256;
107
108impl HasherExt for Blake3_256 {
109    type Digest = Blake3Digest<32>;
110
111    fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Self::Digest {
112        let mut hasher = blake3::Hasher::new();
113        for slice in slices {
114            hasher.update(slice);
115        }
116        Blake3Digest(hasher.finalize().into())
117    }
118}
119
120impl Blake3_256 {
121    /// Blake3 collision resistance is 128-bits for 32-bytes output.
122    pub const COLLISION_RESISTANCE: u32 = 128;
123
124    pub fn hash(bytes: &[u8]) -> Blake3Digest<32> {
125        Blake3Digest(blake3::hash(bytes).into())
126    }
127
128    // Note: merge/merge_many/merge_with_int methods were previously trait delegations
129    // (<Self as Hasher>::merge). They're now direct implementations as part of removing
130    // the Winterfell Hasher trait dependency. These are public API used in benchmarks.
131    pub fn merge(values: &[Blake3Digest<32>; 2]) -> Blake3Digest<32> {
132        Self::hash(Blake3Digest::digests_as_bytes(values))
133    }
134
135    pub fn merge_many(values: &[Blake3Digest<32>]) -> Blake3Digest<32> {
136        Blake3Digest(blake3::hash(Blake3Digest::digests_as_bytes(values)).into())
137    }
138
139    pub fn merge_with_int(seed: Blake3Digest<32>, value: u64) -> Blake3Digest<32> {
140        let mut hasher = blake3::Hasher::new();
141        hasher.update(&seed.0);
142        hasher.update(&value.to_le_bytes());
143        Blake3Digest(hasher.finalize().into())
144    }
145
146    /// Returns a hash of the provided field elements.
147    #[inline(always)]
148    pub fn hash_elements<E: BasedVectorSpace<Felt>>(elements: &[E]) -> Blake3Digest<32> {
149        Blake3Digest(hash_elements(elements))
150    }
151
152    /// Hashes an iterator of byte slices.
153    #[inline(always)]
154    pub fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Blake3Digest<DIGEST32_BYTES> {
155        <Self as HasherExt>::hash_iter(slices)
156    }
157}
158
159// BLAKE3 192-BIT OUTPUT
160// ================================================================================================
161
162/// 192-bit output blake3 hasher.
163#[derive(Debug, Copy, Clone, Eq, PartialEq)]
164pub struct Blake3_192;
165
166impl HasherExt for Blake3_192 {
167    type Digest = Blake3Digest<24>;
168
169    fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Self::Digest {
170        let mut hasher = blake3::Hasher::new();
171        for slice in slices {
172            hasher.update(slice);
173        }
174        Blake3Digest(shrink_array(hasher.finalize().into()))
175    }
176}
177
178impl Blake3_192 {
179    /// Blake3 collision resistance is 96-bits for 24-bytes output.
180    pub const COLLISION_RESISTANCE: u32 = 96;
181
182    pub fn hash(bytes: &[u8]) -> Blake3Digest<24> {
183        Blake3Digest(shrink_array(blake3::hash(bytes).into()))
184    }
185
186    // Note: Same as Blake3_256 - these methods replaced trait delegations to remove Winterfell.
187    pub fn merge_many(values: &[Blake3Digest<24>]) -> Blake3Digest<24> {
188        let bytes = Blake3Digest::digests_as_bytes(values);
189        Blake3Digest(shrink_array(blake3::hash(bytes).into()))
190    }
191
192    pub fn merge(values: &[Blake3Digest<24>; 2]) -> Blake3Digest<24> {
193        Self::hash(Blake3Digest::digests_as_bytes(values))
194    }
195
196    pub fn merge_with_int(seed: Blake3Digest<24>, value: u64) -> Blake3Digest<24> {
197        let mut hasher = blake3::Hasher::new();
198        hasher.update(&seed.0);
199        hasher.update(&value.to_le_bytes());
200        Blake3Digest(shrink_array(hasher.finalize().into()))
201    }
202
203    /// Returns a hash of the provided field elements.
204    #[inline(always)]
205    pub fn hash_elements<E: BasedVectorSpace<Felt>>(elements: &[E]) -> Blake3Digest<32> {
206        Blake3Digest(hash_elements(elements))
207    }
208
209    /// Hashes an iterator of byte slices.
210    #[inline(always)]
211    pub fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Blake3Digest<DIGEST24_BYTES> {
212        <Self as HasherExt>::hash_iter(slices)
213    }
214}
215
216// HELPER FUNCTIONS
217// ================================================================================================
218
219/// Hash the elements into bytes and shrink the output.
220fn hash_elements<const N: usize, E>(elements: &[E]) -> [u8; N]
221where
222    E: BasedVectorSpace<Felt>,
223{
224    let digest = {
225        const FELT_BYTES: usize = size_of::<u64>();
226        const { assert!(FELT_BYTES == 8, "buffer arithmetic assumes 8-byte field elements") };
227
228        let mut hasher = blake3::Hasher::new();
229        // BLAKE3 block size: 64 bytes
230        let mut buf = [0_u8; 64];
231        let mut buf_offset = 0;
232
233        for elem in elements.iter() {
234            for &felt in E::as_basis_coefficients_slice(elem) {
235                buf[buf_offset..buf_offset + FELT_BYTES]
236                    .copy_from_slice(&felt.as_canonical_u64().to_le_bytes());
237                buf_offset += FELT_BYTES;
238
239                if buf_offset == 64 {
240                    hasher.update(&buf);
241                    buf_offset = 0;
242                }
243            }
244        }
245
246        if buf_offset > 0 {
247            hasher.update(&buf[..buf_offset]);
248        }
249
250        hasher.finalize()
251    };
252
253    shrink_array(digest.into())
254}
255
256/// Shrinks an array.
257///
258/// Due to compiler optimizations, this function is zero-copy.
259fn shrink_array<const M: usize, const N: usize>(source: [u8; M]) -> [u8; N] {
260    const {
261        assert!(M >= N, "size of destination should be smaller or equal than source");
262    }
263    core::array::from_fn(|i| source[i])
264}
265
266/// Owned bytes expansion.
267fn expand_bytes<const M: usize, const N: usize>(bytes: &[u8; M]) -> [u8; N] {
268    // compile-time assertion
269    assert!(M <= N, "M should fit in N so M can be expanded!");
270    let mut expanded = [0u8; N];
271    expanded[..M].copy_from_slice(bytes);
272    expanded
273}