Skip to main content

binius_examples/circuits/
blake3.rs

1// Copyright 2026 The Binius Developers
2use std::array;
3
4use anyhow::{Result, ensure};
5use binius_circuits::blake3::{CHUNK_BYTES, blake3_fixed};
6use binius_core::word::Word;
7use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
8
9use super::utils::{self, HasherInstance, HasherMode, HasherParams};
10use crate::ExampleCircuit;
11
12/// BLAKE3 circuit example using the fixed-length (single-chunk) hasher gadget.
13pub struct Blake3Example {
14	message: Vec<Wire>,
15	digest: [Wire; 8],
16	mode: HasherMode,
17}
18
19impl ExampleCircuit for Blake3Example {
20	type Params = HasherParams;
21	type Instance = HasherInstance;
22
23	fn build(params: HasherParams, builder: &mut CircuitBuilder) -> Result<Self> {
24		// TODO: pass `supports_variable = true` and wire up a variable-length BLAKE3 gadget here
25		// once one exists in binius-circuits. Until then `--max-message-len` is rejected.
26		let mode = utils::resolve_hasher_mode(&params, "BLAKE3", false)?;
27		let HasherMode::Fixed { len_bytes } = mode else {
28			unreachable!("BLAKE3 only supports the fixed-length gadget")
29		};
30
31		// blake3_fixed is restricted to single-chunk inputs. Multi-chunk hashing needs BLAKE3's
32		// tree construction, which the gadget does not yet support.
33		ensure!(
34			len_bytes <= CHUNK_BYTES,
35			"BLAKE3 example is limited to single-chunk messages (<= {CHUNK_BYTES} bytes), got {len_bytes}"
36		);
37
38		let n_words = len_bytes.div_ceil(4);
39		let message: Vec<Wire> = (0..n_words).map(|_| builder.add_inout()).collect();
40		let computed_digest = blake3_fixed(builder, &message, len_bytes);
41		let digest: [Wire; 8] = array::from_fn(|_| builder.add_inout());
42		for i in 0..8 {
43			builder.assert_eq(format!("digest[{i}]"), computed_digest[i], digest[i]);
44		}
45
46		Ok(Self {
47			message,
48			digest,
49			mode,
50		})
51	}
52
53	fn populate_witness(&self, instance: HasherInstance, w: &mut WitnessFiller<'_>) -> Result<()> {
54		let message_bytes = utils::resolve_hasher_message(&self.mode, &instance)?;
55
56		// Message: 32-bit little-endian words, 4 bytes per wire, high 32 bits zero.
57		for (wire, word) in self
58			.message
59			.iter()
60			.zip(utils::pack_bytes_u32words(&message_bytes, false))
61		{
62			w[*wire] = word;
63		}
64
65		// Digest: 8 x 32-bit little-endian words.
66		let expected = blake3::hash(&message_bytes);
67		for (i, chunk) in expected.as_bytes().chunks(4).enumerate() {
68			w[self.digest[i]] = Word(u32::from_le_bytes(chunk.try_into().unwrap()) as u64);
69		}
70
71		Ok(())
72	}
73
74	fn param_summary(params: &Self::Params) -> Option<String> {
75		utils::hasher_param_summary(params)
76	}
77}