Skip to main content

ic_hash/
blake2.rs

1//! RFC 7693 BLAKE2b.
2//!
3//! **Not FIPS-approved.** BLAKE2b is here because Argon2 is defined in terms of
4//! it: `ic_kdf::argon2` needs both the plain hash and the variable-length
5//! `H'` construction built on top of it. It is a perfectly good general-purpose
6//! hash โ€” faster than SHA-512 on 64-bit hardware, and keyed without needing
7//! HMAC โ€” but the ontology marks it `not-approved`, so `ic-fips` blocks it in
8//! approved mode.
9//!
10//! Unlike the SHA-2 and SHA-3 types, BLAKE2b has a *variable* output length
11//! chosen at construction, which does not fit the fixed-size
12//! [`Digest`][ic_core::traits::Digest] contract. It therefore exposes its own
13//! API rather than pretending to be a fixed-width digest.
14
15use ic_core::{ensure, Result, Zeroize};
16
17/// Block size in bytes.
18pub const BLOCK_LEN: usize = 128;
19
20/// Largest digest this produces.
21pub const MAX_OUTPUT_LEN: usize = 64;
22
23/// Largest key accepted in keyed mode.
24pub const MAX_KEY_LEN: usize = 64;
25
26/// The BLAKE2b initialization vector, identical to SHA-512's.
27const IV: [u64; 8] = [
28    0x6a09e667f3bcc908,
29    0xbb67ae8584caa73b,
30    0x3c6ef372fe94f82b,
31    0xa54ff53a5f1d36f1,
32    0x510e527fade682d1,
33    0x9b05688c2b3e6c1f,
34    0x1f83d9abfb41bd6b,
35    0x5be0cd19137e2179,
36];
37
38/// The message-word permutation, ten rows of sixteen.
39const SIGMA: [[usize; 16]; 10] = [
40    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
41    [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
42    [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
43    [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
44    [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
45    [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
46    [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
47    [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
48    [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
49    [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
50];
51
52/// The BLAKE2b mixing function.
53#[inline(always)]
54#[allow(clippy::too_many_arguments)]
55fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) {
56    v[a] = v[a].wrapping_add(v[b]).wrapping_add(x);
57    v[d] = (v[d] ^ v[a]).rotate_right(32);
58    v[c] = v[c].wrapping_add(v[d]);
59    v[b] = (v[b] ^ v[c]).rotate_right(24);
60    v[a] = v[a].wrapping_add(v[b]).wrapping_add(y);
61    v[d] = (v[d] ^ v[a]).rotate_right(16);
62    v[c] = v[c].wrapping_add(v[d]);
63    v[b] = (v[b] ^ v[c]).rotate_right(63);
64}
65
66/// A BLAKE2b hasher with a caller-chosen output length.
67#[derive(Clone)]
68pub struct Blake2b {
69    h: [u64; 8],
70    buf: [u8; BLOCK_LEN],
71    buffered: usize,
72    counter: u128,
73    output_len: usize,
74}
75
76impl Drop for Blake2b {
77    fn drop(&mut self) {
78        self.h.zeroize();
79        self.buf.zeroize();
80    }
81}
82
83impl Blake2b {
84    /// Create a hasher producing `output_len` bytes, between 1 and 64.
85    pub fn new(output_len: usize) -> Result<Self> {
86        Self::with_key(output_len, &[])
87    }
88
89    /// Create a keyed hasher, the BLAKE2b equivalent of an HMAC.
90    ///
91    /// The key is absorbed as a zero-padded first block, exactly as RFC 7693
92    /// specifies, so a keyed hash of an empty message is still one compression.
93    pub fn with_key(output_len: usize, key: &[u8]) -> Result<Self> {
94        ensure!(
95            (1..=MAX_OUTPUT_LEN).contains(&output_len),
96            InvalidLength,
97            "blake2b output must be 1..=64 bytes"
98        );
99        ensure!(
100            key.len() <= MAX_KEY_LEN,
101            InvalidLength,
102            "blake2b key must be at most 64 bytes"
103        );
104
105        let mut h = IV;
106        // Parameter block word 0: digest length, key length, fanout, depth.
107        h[0] ^= 0x0101_0000 ^ ((key.len() as u64) << 8) ^ (output_len as u64);
108
109        let mut state = Self {
110            h,
111            buf: [0u8; BLOCK_LEN],
112            buffered: 0,
113            counter: 0,
114            output_len,
115        };
116
117        if !key.is_empty() {
118            let mut block = [0u8; BLOCK_LEN];
119            block[..key.len()].copy_from_slice(key);
120            state.update(&block);
121            block.zeroize();
122        }
123        Ok(state)
124    }
125
126    /// The compression function.
127    fn compress(&mut self, block: &[u8; BLOCK_LEN], last: bool) {
128        let mut m = [0u64; 16];
129        for (i, word) in m.iter_mut().enumerate() {
130            let mut b = [0u8; 8];
131            b.copy_from_slice(&block[i * 8..i * 8 + 8]);
132            *word = u64::from_le_bytes(b);
133        }
134
135        let mut v = [0u64; 16];
136        v[..8].copy_from_slice(&self.h);
137        v[8..].copy_from_slice(&IV);
138        v[12] ^= self.counter as u64;
139        v[13] ^= (self.counter >> 64) as u64;
140        if last {
141            v[14] = !v[14];
142        }
143
144        for round in 0..12 {
145            let s = &SIGMA[round % 10];
146            g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]);
147            g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]);
148            g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]);
149            g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]);
150            g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]);
151            g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]);
152            g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]);
153            g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]);
154        }
155
156        for i in 0..8 {
157            self.h[i] ^= v[i] ^ v[i + 8];
158        }
159        m.zeroize();
160        v.zeroize();
161    }
162
163    /// Absorb more input.
164    ///
165    /// BLAKE2b marks the *last* block specially, so a full buffer is only
166    /// compressed once more input is known to follow.
167    pub fn update(&mut self, mut data: &[u8]) {
168        if self.buffered > 0 {
169            let take = core::cmp::min(BLOCK_LEN - self.buffered, data.len());
170            self.buf[self.buffered..self.buffered + take].copy_from_slice(&data[..take]);
171            self.buffered += take;
172            data = &data[take..];
173            if self.buffered < BLOCK_LEN || data.is_empty() {
174                return;
175            }
176            let block = self.buf;
177            self.counter = self.counter.wrapping_add(BLOCK_LEN as u128);
178            self.compress(&block, false);
179            self.buffered = 0;
180        }
181
182        while data.len() > BLOCK_LEN {
183            let mut block = [0u8; BLOCK_LEN];
184            block.copy_from_slice(&data[..BLOCK_LEN]);
185            self.counter = self.counter.wrapping_add(BLOCK_LEN as u128);
186            self.compress(&block, false);
187            data = &data[BLOCK_LEN..];
188        }
189
190        self.buf[..data.len()].copy_from_slice(data);
191        self.buffered = data.len();
192    }
193
194    /// Finish and write the digest into `out`, which must be `output_len` long.
195    pub fn finalize_into(mut self, out: &mut [u8]) -> Result<()> {
196        ensure!(
197            out.len() == self.output_len,
198            InvalidLength,
199            "blake2b output buffer"
200        );
201
202        self.counter = self.counter.wrapping_add(self.buffered as u128);
203        let mut block = [0u8; BLOCK_LEN];
204        block[..self.buffered].copy_from_slice(&self.buf[..self.buffered]);
205        self.compress(&block, true);
206        block.zeroize();
207
208        let mut full = [0u8; MAX_OUTPUT_LEN];
209        for (i, word) in self.h.iter().enumerate() {
210            full[i * 8..i * 8 + 8].copy_from_slice(&word.to_le_bytes());
211        }
212        out.copy_from_slice(&full[..self.output_len]);
213        full.zeroize();
214        Ok(())
215    }
216
217    /// One-shot hash.
218    pub fn hash(data: &[u8], out: &mut [u8]) -> Result<()> {
219        let mut h = Self::new(out.len())?;
220        h.update(data);
221        h.finalize_into(out)
222    }
223
224    /// One-shot keyed hash.
225    pub fn keyed_hash(key: &[u8], data: &[u8], out: &mut [u8]) -> Result<()> {
226        let mut h = Self::with_key(out.len(), key)?;
227        h.update(data);
228        h.finalize_into(out)
229    }
230}
231
232/// The Argon2 variable-length hash `H'`.
233///
234/// For outputs up to 64 bytes this is just BLAKE2b with the length prefixed.
235/// Beyond that, RFC 9106 chains 64-byte hashes and takes the first 32 bytes of
236/// each, which is why Argon2 can fill a 1024-byte block from a 64-byte
237/// primitive. Lives here because it is a property of BLAKE2b's use rather than
238/// of Argon2's structure.
239pub fn blake2b_long(input: &[&[u8]], out: &mut [u8]) -> Result<()> {
240    ensure!(!out.is_empty(), InvalidLength, "blake2b_long output");
241    let len_prefix = (out.len() as u32).to_le_bytes();
242
243    if out.len() <= MAX_OUTPUT_LEN {
244        let mut h = Blake2b::new(out.len())?;
245        h.update(&len_prefix);
246        for part in input {
247            h.update(part);
248        }
249        return h.finalize_into(out);
250    }
251
252    // RFC 9106 ยง3.3, followed literally:
253    //
254    //   r      = ceil(T/32) - 2
255    //   V_1    = H^64(LE32(T) || A)
256    //   V_i    = H^64(V_{i-1})           for 2 <= i <= r
257    //   V_{r+1} = H^(T - 32r)(V_r)
258    //   output = A_1 || ... || A_r || V_{r+1},  A_i = first 32 bytes of V_i
259    //
260    // The final hash is taken from V_r, so the chain must *not* be advanced
261    // after emitting the last 32-byte piece.
262    let r = out.len().div_ceil(32) - 2;
263
264    let mut v = [0u8; MAX_OUTPUT_LEN];
265    let mut h = Blake2b::new(MAX_OUTPUT_LEN)?;
266    h.update(&len_prefix);
267    for part in input {
268        h.update(part);
269    }
270    h.finalize_into(&mut v)?;
271
272    for (i, piece) in out.chunks_exact_mut(32).take(r).enumerate() {
273        piece.copy_from_slice(&v[..32]);
274        if i + 1 < r {
275            let previous = v;
276            Blake2b::hash(&previous, &mut v)?;
277        }
278    }
279
280    // `v` is now V_r; the tail is a single hash of it at the remaining length.
281    let tail_len = out.len() - 32 * r;
282    let mut tail = [0u8; MAX_OUTPUT_LEN];
283    Blake2b::hash(&v, &mut tail[..tail_len])?;
284    out[32 * r..].copy_from_slice(&tail[..tail_len]);
285
286    v.zeroize();
287    tail.zeroize();
288    Ok(())
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294    use ic_core::codec::hex;
295
296    /// RFC 7693 Appendix A: BLAKE2b-512 of "abc".
297    #[test]
298    fn rfc7693_abc_vector() {
299        let mut out = [0u8; 64];
300        Blake2b::hash(b"abc", &mut out).unwrap();
301        assert_eq!(
302            hex(&out),
303            "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1\
304             7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"
305                .replace(char::is_whitespace, "")
306        );
307    }
308
309    /// The empty message, the other widely published BLAKE2b-512 value.
310    #[test]
311    fn empty_message_vector() {
312        let mut out = [0u8; 64];
313        Blake2b::hash(b"", &mut out).unwrap();
314        assert_eq!(
315            hex(&out),
316            "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419\
317             d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"
318                .replace(char::is_whitespace, "")
319        );
320    }
321
322    #[test]
323    fn output_length_changes_the_digest() {
324        let mut a = [0u8; 32];
325        let mut b = [0u8; 64];
326        Blake2b::hash(b"same input", &mut a).unwrap();
327        Blake2b::hash(b"same input", &mut b).unwrap();
328        // The length is bound into the parameter block, so the short digest is
329        // not a prefix of the long one.
330        assert_ne!(&b[..32], &a[..]);
331    }
332
333    #[test]
334    fn keying_changes_the_digest() {
335        let mut unkeyed = [0u8; 32];
336        let mut keyed = [0u8; 32];
337        Blake2b::hash(b"message", &mut unkeyed).unwrap();
338        Blake2b::keyed_hash(b"key", b"message", &mut keyed).unwrap();
339        assert_ne!(unkeyed, keyed);
340
341        let mut other = [0u8; 32];
342        Blake2b::keyed_hash(b"kez", b"message", &mut other).unwrap();
343        assert_ne!(keyed, other);
344    }
345
346    /// Streaming must match one-shot at every block boundary โ€” BLAKE2b flags
347    /// the final block, so an off-by-one in the buffering changes the result.
348    #[test]
349    fn streaming_matches_one_shot() {
350        let data: Vec<u8> = (0..400u32).map(|i| (i * 7) as u8).collect();
351        let mut expected = [0u8; 64];
352        Blake2b::hash(&data, &mut expected).unwrap();
353
354        for split in [0usize, 1, 127, 128, 129, 200, 255, 256, 257, 400] {
355            let mut h = Blake2b::new(64).unwrap();
356            h.update(&data[..split]);
357            h.update(&data[split..]);
358            let mut got = [0u8; 64];
359            h.finalize_into(&mut got).unwrap();
360            assert_eq!(got, expected, "split at {split}");
361        }
362    }
363
364    /// An input of exactly one block must not be compressed as a non-final
365    /// block; this is the classic BLAKE2 implementation bug.
366    #[test]
367    fn exactly_one_block_is_handled() {
368        let data = [0x61u8; BLOCK_LEN];
369        let mut a = [0u8; 64];
370        Blake2b::hash(&data, &mut a).unwrap();
371
372        let mut h = Blake2b::new(64).unwrap();
373        for byte in data.iter() {
374            h.update(&[*byte]);
375        }
376        let mut b = [0u8; 64];
377        h.finalize_into(&mut b).unwrap();
378        assert_eq!(a, b);
379    }
380
381    #[test]
382    fn rejects_invalid_parameters() {
383        assert!(Blake2b::new(0).is_err());
384        assert!(Blake2b::new(65).is_err());
385        assert!(Blake2b::with_key(32, &[0u8; 65]).is_err());
386        let h = Blake2b::new(32).unwrap();
387        assert!(h.finalize_into(&mut [0u8; 31]).is_err());
388    }
389
390    /// `blake2b_long` must agree with plain BLAKE2b for short outputs, and
391    /// produce distinct, deterministic output beyond 64 bytes.
392    #[test]
393    fn long_hash_matches_short_path_and_extends() {
394        for len in [1usize, 32, 64] {
395            let mut via_long = vec![0u8; len];
396            blake2b_long(&[b"input"], &mut via_long).unwrap();
397
398            let mut direct = vec![0u8; len];
399            let mut h = Blake2b::new(len).unwrap();
400            h.update(&(len as u32).to_le_bytes());
401            h.update(b"input");
402            h.finalize_into(&mut direct).unwrap();
403
404            assert_eq!(via_long, direct, "len {len}");
405        }
406
407        let mut a = [0u8; 1024];
408        let mut b = [0u8; 1024];
409        blake2b_long(&[b"input"], &mut a).unwrap();
410        blake2b_long(&[b"input"], &mut b).unwrap();
411        assert_eq!(a, b, "must be deterministic");
412        assert_ne!(&a[..64], &a[64..128], "must not repeat");
413
414        // The output length is bound in, so a short request is not a prefix.
415        let mut short = [0u8; 128];
416        blake2b_long(&[b"input"], &mut short).unwrap();
417        assert_ne!(&a[..128], &short[..]);
418    }
419
420    #[test]
421    fn long_hash_concatenates_its_inputs() {
422        let mut joined = [0u8; 100];
423        let mut split = [0u8; 100];
424        blake2b_long(&[b"abcdef"], &mut joined).unwrap();
425        blake2b_long(&[b"abc", b"def"], &mut split).unwrap();
426        assert_eq!(joined, split);
427    }
428}