Skip to main content

miden_crypto/hash/blake/
mod.rs

1use core::mem::size_of;
2
3use super::{
4    HasherExt,
5    digest::{Digest, Digest192, Digest256},
6};
7use crate::{Felt, field::BasedVectorSpace};
8
9#[cfg(test)]
10mod tests;
11
12// RE-EXPORTS
13// ================================================================================================
14
15/// Re-export of the Blake3 hasher from Plonky3 for use in the prover config downstream.
16pub use p3_blake3::Blake3 as Blake3Hasher;
17
18// TYPE ALIASES
19// ================================================================================================
20
21/// Alias for the generic `Digest` type, for consistency with other hash modules.
22pub type Blake3Digest<const N: usize> = Digest<N>;
23
24// BLAKE3 256-BIT OUTPUT
25// ================================================================================================
26
27/// 256-bit output blake3 hasher.
28#[derive(Debug, Copy, Clone, Eq, PartialEq)]
29pub struct Blake3_256;
30
31impl HasherExt for Blake3_256 {
32    type Digest = Digest256;
33
34    fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Self::Digest {
35        let mut hasher = blake3::Hasher::new();
36        for slice in slices {
37            hasher.update(slice);
38        }
39        Digest::new(hasher.finalize().into())
40    }
41}
42
43impl Blake3_256 {
44    /// Blake3 collision resistance is 128-bits for 32-bytes output.
45    pub const COLLISION_RESISTANCE: u32 = 128;
46
47    pub fn hash(bytes: &[u8]) -> Digest256 {
48        Digest::new(blake3::hash(bytes).into())
49    }
50
51    // Note: merge/merge_many/merge_with_int methods were previously trait delegations
52    // (<Self as Hasher>::merge). They're now direct implementations as part of removing
53    // the Winterfell Hasher trait dependency. These are public API used in benchmarks.
54    pub fn merge(values: &[Digest256; 2]) -> Digest256 {
55        Self::hash(Digest::digests_as_bytes(values))
56    }
57
58    pub fn merge_many(values: &[Digest256]) -> Digest256 {
59        Digest::new(blake3::hash(Digest::digests_as_bytes(values)).into())
60    }
61
62    pub fn merge_with_int(seed: Digest256, value: u64) -> Digest256 {
63        let mut hasher = blake3::Hasher::new();
64        hasher.update(seed.as_bytes());
65        hasher.update(&value.to_le_bytes());
66        Digest::new(hasher.finalize().into())
67    }
68
69    /// Returns a hash of the provided field elements.
70    #[inline(always)]
71    pub fn hash_elements<E: BasedVectorSpace<Felt>>(elements: &[E]) -> Digest256 {
72        Digest::new(hash_elements(elements))
73    }
74
75    /// Hashes an iterator of byte slices.
76    #[inline(always)]
77    pub fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Digest256 {
78        <Self as HasherExt>::hash_iter(slices)
79    }
80}
81
82// BLAKE3 192-BIT OUTPUT
83// ================================================================================================
84
85/// 192-bit output blake3 hasher.
86#[derive(Debug, Copy, Clone, Eq, PartialEq)]
87pub struct Blake3_192;
88
89impl HasherExt for Blake3_192 {
90    type Digest = Digest192;
91
92    fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Self::Digest {
93        let mut hasher = blake3::Hasher::new();
94        for slice in slices {
95            hasher.update(slice);
96        }
97        Digest::new(shrink_array(hasher.finalize().into()))
98    }
99}
100
101impl Blake3_192 {
102    /// Blake3 collision resistance is 96-bits for 24-bytes output.
103    pub const COLLISION_RESISTANCE: u32 = 96;
104
105    pub fn hash(bytes: &[u8]) -> Digest192 {
106        Digest::new(shrink_array(blake3::hash(bytes).into()))
107    }
108
109    // Note: Same as Blake3_256 - these methods replaced trait delegations to remove Winterfell.
110    pub fn merge_many(values: &[Digest192]) -> Digest192 {
111        let bytes = Digest::digests_as_bytes(values);
112        Digest::new(shrink_array(blake3::hash(bytes).into()))
113    }
114
115    pub fn merge(values: &[Digest192; 2]) -> Digest192 {
116        Self::hash(Digest::digests_as_bytes(values))
117    }
118
119    pub fn merge_with_int(seed: Digest192, value: u64) -> Digest192 {
120        let mut hasher = blake3::Hasher::new();
121        hasher.update(seed.as_bytes());
122        hasher.update(&value.to_le_bytes());
123        Digest::new(shrink_array(hasher.finalize().into()))
124    }
125
126    /// Returns a hash of the provided field elements.
127    #[inline(always)]
128    pub fn hash_elements<E: BasedVectorSpace<Felt>>(elements: &[E]) -> Digest192 {
129        Digest::new(hash_elements(elements))
130    }
131
132    /// Hashes an iterator of byte slices.
133    #[inline(always)]
134    pub fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Digest192 {
135        <Self as HasherExt>::hash_iter(slices)
136    }
137}
138
139// HELPER FUNCTIONS
140// ================================================================================================
141
142/// Hash the elements into bytes and shrink the output.
143fn hash_elements<const N: usize, E>(elements: &[E]) -> [u8; N]
144where
145    E: BasedVectorSpace<Felt>,
146{
147    let digest = {
148        const FELT_BYTES: usize = size_of::<u64>();
149        const { assert!(FELT_BYTES == 8, "buffer arithmetic assumes 8-byte field elements") };
150
151        let mut hasher = blake3::Hasher::new();
152        // BLAKE3 block size: 64 bytes
153        let mut buf = [0_u8; 64];
154        let mut buf_offset = 0;
155
156        for elem in elements.iter() {
157            for &felt in E::as_basis_coefficients_slice(elem) {
158                buf[buf_offset..buf_offset + FELT_BYTES]
159                    .copy_from_slice(&felt.as_canonical_u64().to_le_bytes());
160                buf_offset += FELT_BYTES;
161
162                if buf_offset == 64 {
163                    hasher.update(&buf);
164                    buf_offset = 0;
165                }
166            }
167        }
168
169        if buf_offset > 0 {
170            hasher.update(&buf[..buf_offset]);
171        }
172
173        hasher.finalize()
174    };
175
176    shrink_array(digest.into())
177}
178
179/// Shrinks an array.
180///
181/// Due to compiler optimizations, this function is zero-copy.
182fn shrink_array<const M: usize, const N: usize>(source: [u8; M]) -> [u8; N] {
183    const {
184        assert!(M >= N, "size of destination should be smaller or equal than source");
185    }
186    core::array::from_fn(|i| source[i])
187}