Skip to main content

binius_circuits/blake2b/
reference.rs

1// Copyright 2025 Irreducible Inc.
2//! BLAKE2b reference implementation
3//!
4//! This module provides a pure Rust implementation of BLAKE2b following RFC 7693.
5//! It serves as a reference for the circuit implementation and testing.
6
7use super::constants::{BLOCK_BYTES, IV, MAX_OUTPUT_BYTES, R1, R2, R3, R4, ROUNDS, SIGMA};
8
9/// Rotate right for 64-bit words
10#[inline(always)]
11const fn rotr64(x: u64, n: u32) -> u64 {
12	x.rotate_right(n)
13}
14
15/// G mixing function - the core primitive of BLAKE2b
16///
17/// Performs 8 operations mixing two input words with the state
18#[inline(always)]
19pub const fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) {
20	v[a] = v[a].wrapping_add(v[b]).wrapping_add(x);
21	v[d] = rotr64(v[d] ^ v[a], R1);
22	v[c] = v[c].wrapping_add(v[d]);
23	v[b] = rotr64(v[b] ^ v[c], R2);
24	v[a] = v[a].wrapping_add(v[b]).wrapping_add(y);
25	v[d] = rotr64(v[d] ^ v[a], R3);
26	v[c] = v[c].wrapping_add(v[d]);
27	v[b] = rotr64(v[b] ^ v[c], R4);
28}
29
30/// Convert bytes to 64-bit words (little-endian)
31pub fn bytes_to_words(bytes: &[u8]) -> [u64; 16] {
32	let mut words = [0u64; 16];
33	for (i, chunk) in bytes.chunks_exact(8).enumerate() {
34		words[i] = u64::from_le_bytes(chunk.try_into().unwrap());
35	}
36	words
37}
38
39/// BLAKE2b compression function F
40///
41/// Compresses a 128-byte block into the state using 12 rounds of mixing
42pub fn compress(h: &mut [u64; 8], block: &[u8; 128], t: u128, last: bool) {
43	// Initialize working vector
44	let mut v = [0u64; 16];
45
46	// First half from state
47	v[0..8].copy_from_slice(h);
48
49	// Second half from IV
50	v[8..16].copy_from_slice(&IV);
51
52	// Mix in counter (128-bit counter split into two 64-bit words)
53	v[12] ^= t as u64; // Low word
54	v[13] ^= (t >> 64) as u64; // High word
55
56	// Invert v[14] for last block flag
57	if last {
58		v[14] = !v[14];
59	}
60
61	// Convert block to 16 words
62	let m = bytes_to_words(block);
63
64	// 12 rounds of mixing
65	for round in 0..ROUNDS {
66		let s = &SIGMA[round];
67
68		// Column step (mix columns of the 4x4 matrix)
69		g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]);
70		g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]);
71		g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]);
72		g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]);
73
74		// Diagonal step (mix diagonals of the 4x4 matrix)
75		g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]);
76		g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]);
77		g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]);
78		g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]);
79	}
80
81	// Finalization: XOR the two halves back into state
82	for i in 0..8 {
83		h[i] ^= v[i] ^ v[i + 8];
84	}
85}
86
87/// BLAKE2b hash function with variable output length
88///
89/// Computes BLAKE2b hash of input data with specified output length (1-64 bytes)
90pub fn blake2b(data: &[u8], outlen: usize) -> Vec<u8> {
91	assert!(outlen > 0 && outlen <= MAX_OUTPUT_BYTES, "Output length must be 1-64 bytes");
92
93	// Initialize state with IV XORed with parameter block
94	let mut h = IV;
95
96	// Parameter block: Set output length in first byte, rest are zeros for basic version
97	// Format: 0x0101kknn where nn=outlen, kk=keylen (0 for us), fanout=depth=1
98	h[0] ^= 0x01010000 | (outlen as u64);
99
100	// Process message blocks
101	let mut t = 0u128; // Total bytes counter
102	let mut offset = 0;
103
104	// Process all complete blocks except the last one
105	while offset + BLOCK_BYTES < data.len() {
106		let mut block = [0u8; BLOCK_BYTES];
107		block.copy_from_slice(&data[offset..offset + BLOCK_BYTES]);
108
109		t += BLOCK_BYTES as u128;
110		compress(&mut h, &block, t, false);
111
112		offset += BLOCK_BYTES;
113	}
114
115	// Process final block (always exists, may be partial or full)
116	let mut final_block = [0u8; BLOCK_BYTES];
117	let remaining = data.len() - offset;
118	if remaining > 0 {
119		final_block[..remaining].copy_from_slice(&data[offset..]);
120	}
121
122	t += remaining as u128;
123	compress(&mut h, &final_block, t, true); // Set last block flag
124
125	// Convert state to bytes and return requested length
126	let mut output = Vec::with_capacity(outlen);
127	for word in h.iter() {
128		let bytes = word.to_le_bytes();
129		for byte in bytes {
130			if output.len() < outlen {
131				output.push(byte);
132			}
133		}
134	}
135	output.truncate(outlen);
136	output
137}
138
139/// BLAKE2b-256: Fixed 256-bit (32-byte) output variant
140///
141/// This is a convenience function for the common 256-bit output case
142pub fn blake2b_256(data: &[u8]) -> [u8; 32] {
143	let hash = blake2b(data, 32);
144	let mut result = [0u8; 32];
145	result.copy_from_slice(&hash);
146	result
147}
148
149#[cfg(test)]
150mod tests {
151	use blake2::{
152		Blake2b, Blake2b256, Digest,
153		digest::{
154			array::ArraySize,
155			consts::U64,
156			typenum::{IsLessOrEqual, True},
157		},
158	};
159
160	use super::*;
161
162	/// Compares [`blake2b`] against the reference hasher at the output length `N` names.
163	///
164	/// The length is a type rather than a value, since the hasher fixes its output size at
165	/// compile time.
166	fn check_output_length<N>(msg: &[u8])
167	where
168		N: ArraySize + IsLessOrEqual<U64, Output = True>,
169	{
170		let expected = Blake2b::<N>::digest(msg);
171		assert_eq!(blake2b(msg, N::USIZE), expected.as_slice(), "output length {}", N::USIZE);
172	}
173
174	/// Calls [`check_output_length`] once per named length.
175	macro_rules! check_output_lengths {
176		($msg:expr, $($len:ident),+ $(,)?) => {
177			$(check_output_length::<blake2::digest::consts::$len>($msg);)+
178		};
179	}
180
181	/// Test variable output lengths
182	#[test]
183	fn test_variable_output_lengths() {
184		let msg = b"test message for variable output lengths";
185
186		// BLAKE2b mixes its output length into the parameter block.
187		// So every length is its own computation, and none of them stands in for the rest.
188		check_output_lengths!(
189			msg, U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13, U14, U15, U16, U17, U18,
190			U19, U20, U21, U22, U23, U24, U25, U26, U27, U28, U29, U30, U31, U32, U33, U34, U35,
191			U36, U37, U38, U39, U40, U41, U42, U43, U44, U45, U46, U47, U48, U49, U50, U51, U52,
192			U53, U54, U55, U56, U57, U58, U59, U60, U61, U62, U63, U64
193		);
194	}
195
196	/// Test incremental hashing verification
197	#[test]
198	fn test_incremental_hashing() {
199		let data = vec![0x55u8; 300];
200
201		// Hash all at once with our reference
202		let expected = blake2b_256(&data);
203
204		// Incremental hashing using standard crate
205		let mut hasher = Blake2b256::new();
206		hasher.update(&data[0..100]);
207		hasher.update(&data[100..200]);
208		hasher.update(&data[200..300]);
209		let incremental = hasher.finalize();
210
211		// Our implementation should match incrementally computed hash
212		assert_eq!(&expected[..], &incremental[..], "Incremental hashing verification failed");
213	}
214}