binius_examples/circuits/
independent_hashes.rs1use anyhow::{Result, ensure};
13use binius_circuits::{
14 blake3::{blake3_compress, ref_compress},
15 keccak::permutation::keccak_f1600,
16 sha256::{State as Sha256State, populate_message_block, sha256_compress},
17 util::clear_high_bits,
18};
19use binius_core::word::Word;
20use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
21use clap::Args;
22use rand::prelude::*;
23
24use super::utils::DEFAULT_RANDOM_SEED;
25use crate::ExampleCircuit;
26
27pub const DEFAULT_NUM_PRIMITIVES: usize = 16;
31
32const SHA256_IV: [u32; 8] = [
34 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
35];
36
37#[derive(Debug, Clone, Args)]
38pub struct PrimitiveParams {
39 #[arg(short = 'n', long, default_value_t = DEFAULT_NUM_PRIMITIVES)]
41 pub num_primitives: usize,
42}
43
44#[derive(Debug, Clone, Args)]
45pub struct Instance {
46 #[arg(long)]
48 pub seed: Option<u64>,
49}
50
51pub struct IndependentSha256Compressions {
56 compressions: Vec<Sha256Compression>,
57}
58
59struct Sha256Compression {
60 block: [Wire; 16],
61 digest: [Wire; 8],
62}
63
64impl ExampleCircuit for IndependentSha256Compressions {
65 type Params = PrimitiveParams;
66 type Instance = Instance;
67
68 fn build(params: PrimitiveParams, builder: &mut CircuitBuilder) -> Result<Self> {
69 ensure!(params.num_primitives > 0, "num_primitives must be positive");
70
71 let compressions = (0..params.num_primitives)
72 .map(|i| {
73 let block = std::array::from_fn(|_| builder.add_witness());
74 let out = sha256_compress(builder, Sha256State::iv(builder), block);
75 let digest = std::array::from_fn(|_| builder.add_inout());
76 builder.assert_eq_v(format!("sha256_compression_out[{i}]"), out.0, digest);
80 Sha256Compression { block, digest }
81 })
82 .collect();
83
84 Ok(Self { compressions })
85 }
86
87 fn populate_witness(&self, instance: Instance, w: &mut WitnessFiller<'_>) -> Result<()> {
88 let mut rng = StdRng::seed_from_u64(instance.seed.unwrap_or(DEFAULT_RANDOM_SEED));
89 for compression in &self.compressions {
90 let block_bytes = next_block(&mut rng);
91 populate_message_block(w, &compression.block, block_bytes);
92
93 let mut state = SHA256_IV;
94 sha2::block_api::compress256(&mut state, &[block_bytes]);
95 for (wire, value) in compression.digest.iter().zip(state) {
96 w[*wire] = Word(value as u64);
97 }
98 }
99 Ok(())
100 }
101
102 fn param_summary(params: &Self::Params) -> Option<String> {
103 Some(format!("{}c", params.num_primitives))
104 }
105}
106
107pub struct IndependentBlake3Compressions {
112 compressions: Vec<Blake3Compression>,
113}
114
115struct Blake3Compression {
116 cv: [Wire; 8],
117 block: [Wire; 16],
118 counter: Wire,
119 block_len: Wire,
120 flags: Wire,
121 out_cv: [Wire; 8],
122}
123
124impl ExampleCircuit for IndependentBlake3Compressions {
125 type Params = PrimitiveParams;
126 type Instance = Instance;
127
128 fn build(params: PrimitiveParams, builder: &mut CircuitBuilder) -> Result<Self> {
129 ensure!(params.num_primitives > 0, "num_primitives must be positive");
130
131 let compressions = (0..params.num_primitives)
132 .map(|i| {
133 let cv = std::array::from_fn(|_| builder.add_witness());
134 let block = std::array::from_fn(|_| builder.add_witness());
135 let counter = builder.add_witness();
136 let block_len = builder.add_witness();
137 let flags = builder.add_witness();
138 let out = blake3_compress(builder, cv, block, counter, block_len, flags);
139 let out = out.map(|word| clear_high_bits(builder, word, 32));
142 let out_cv = std::array::from_fn(|_| builder.add_inout());
143 builder.assert_eq_v(format!("blake3_compression_out[{i}]"), out, out_cv);
144 Blake3Compression {
145 cv,
146 block,
147 counter,
148 block_len,
149 flags,
150 out_cv,
151 }
152 })
153 .collect();
154
155 Ok(Self { compressions })
156 }
157
158 fn populate_witness(&self, instance: Instance, w: &mut WitnessFiller<'_>) -> Result<()> {
159 let mut rng = StdRng::seed_from_u64(instance.seed.unwrap_or(DEFAULT_RANDOM_SEED));
160 for compression in &self.compressions {
161 let cv: [u32; 8] = std::array::from_fn(|_| rng.next_u32());
162 let block: [u32; 16] = std::array::from_fn(|_| rng.next_u32());
163 let counter = rng.next_u64();
164 let block_len = rng.next_u32() % 65;
165 let flags = rng.next_u32();
166
167 for (wire, value) in compression.cv.iter().zip(cv) {
168 w[*wire] = Word(value as u64);
169 }
170 for (wire, value) in compression.block.iter().zip(block) {
171 w[*wire] = Word(value as u64);
172 }
173 w[compression.counter] = Word(counter);
174 w[compression.block_len] = Word(block_len as u64);
175 w[compression.flags] = Word(flags as u64);
176
177 let expected = ref_compress(&cv, &block, counter, block_len, flags);
178 for (wire, value) in compression.out_cv.iter().zip(expected) {
179 w[*wire] = Word(value as u64);
180 }
181 }
182 Ok(())
183 }
184
185 fn param_summary(params: &Self::Params) -> Option<String> {
186 Some(format!("{}c", params.num_primitives))
187 }
188}
189
190pub struct IndependentKeccakPermutations {
195 permutations: Vec<KeccakPermutation>,
196}
197
198struct KeccakPermutation {
199 input: [Wire; 25],
200 output: [Wire; 25],
201}
202
203impl ExampleCircuit for IndependentKeccakPermutations {
204 type Params = PrimitiveParams;
205 type Instance = Instance;
206
207 fn build(params: PrimitiveParams, builder: &mut CircuitBuilder) -> Result<Self> {
208 ensure!(params.num_primitives > 0, "num_primitives must be positive");
209
210 let permutations = (0..params.num_primitives)
211 .map(|i| {
212 let input: [Wire; 25] = std::array::from_fn(|_| builder.add_witness());
213 let mut output = input;
214 keccak_f1600(builder, &mut output);
215 let expected = std::array::from_fn(|_| builder.add_inout());
216 builder.assert_eq_v(format!("keccak_permutation_out[{i}]"), output, expected);
217 KeccakPermutation {
218 input,
219 output: expected,
220 }
221 })
222 .collect();
223
224 Ok(Self { permutations })
225 }
226
227 fn populate_witness(&self, instance: Instance, w: &mut WitnessFiller<'_>) -> Result<()> {
228 let mut rng = StdRng::seed_from_u64(instance.seed.unwrap_or(DEFAULT_RANDOM_SEED));
229 for permutation in &self.permutations {
230 let state: [u64; 25] = std::array::from_fn(|_| rng.next_u64());
231 for (wire, value) in permutation.input.iter().zip(state) {
232 w[*wire] = Word(value);
233 }
234
235 let mut expected = state;
236 keccak::Keccak::new().with_f1600(|f1600| f1600(&mut expected));
237 for (wire, value) in permutation.output.iter().zip(expected) {
238 w[*wire] = Word(value);
239 }
240 }
241 Ok(())
242 }
243
244 fn param_summary(params: &Self::Params) -> Option<String> {
245 Some(format!("{}p", params.num_primitives))
246 }
247}
248
249fn next_block(rng: &mut StdRng) -> [u8; 64] {
250 let mut block = [0; 64];
251 for chunk in block.chunks_exact_mut(8) {
252 chunk.copy_from_slice(&rng.next_u64().to_le_bytes());
253 }
254 block
255}
256
257#[cfg(test)]
258mod tests {
259 use binius_frontend::CircuitBuilder;
260
261 use super::*;
262
263 fn assert_builds_and_populates<E>()
264 where
265 E: ExampleCircuit<Params = PrimitiveParams, Instance = Instance>,
266 {
267 let mut builder = CircuitBuilder::new();
268 let example = E::build(PrimitiveParams { num_primitives: 2 }, &mut builder).unwrap();
269 let circuit = builder.build();
270 let mut filler = circuit.new_witness_filler();
271
272 example
273 .populate_witness(Instance { seed: Some(7) }, &mut filler)
274 .unwrap();
275 circuit.populate_wire_witness(&mut filler).unwrap();
276 circuit
277 .constraint_system()
278 .verify(&filler.into_value_vec())
279 .unwrap();
280 }
281
282 #[test]
283 fn independent_sha256_compressions_populate_valid_witness() {
284 assert_builds_and_populates::<IndependentSha256Compressions>();
285 }
286
287 #[test]
288 fn independent_blake3_compressions_populate_valid_witness() {
289 assert_builds_and_populates::<IndependentBlake3Compressions>();
290 }
291
292 #[test]
293 fn independent_keccak_permutations_populate_valid_witness() {
294 assert_builds_and_populates::<IndependentKeccakPermutations>();
295 }
296
297 #[test]
298 fn compression_benchmarks_accept_odd_counts() {
299 let mut builder = CircuitBuilder::new();
300 IndependentSha256Compressions::build(PrimitiveParams { num_primitives: 1 }, &mut builder)
301 .unwrap();
302
303 let mut builder = CircuitBuilder::new();
304 IndependentBlake3Compressions::build(PrimitiveParams { num_primitives: 1 }, &mut builder)
305 .unwrap();
306 }
307
308 #[test]
309 fn compression_benchmarks_reject_zero_counts() {
310 let mut builder = CircuitBuilder::new();
311 assert!(
312 IndependentSha256Compressions::build(
313 PrimitiveParams { num_primitives: 0 },
314 &mut builder
315 )
316 .is_err()
317 );
318
319 let mut builder = CircuitBuilder::new();
320 assert!(
321 IndependentBlake3Compressions::build(
322 PrimitiveParams { num_primitives: 0 },
323 &mut builder
324 )
325 .is_err()
326 );
327 }
328
329 #[test]
332 fn circuits_emit_hash_constraints() {
333 fn and_count<E>() -> usize
334 where
335 E: ExampleCircuit<Params = PrimitiveParams, Instance = Instance>,
336 {
337 let mut builder = CircuitBuilder::new();
338 E::build(PrimitiveParams { num_primitives: 1 }, &mut builder).unwrap();
339 builder.build().constraint_system().and_constraints.len()
340 }
341
342 assert!(and_count::<IndependentSha256Compressions>() > 100);
343 assert!(and_count::<IndependentBlake3Compressions>() > 100);
344 assert!(and_count::<IndependentKeccakPermutations>() > 100);
345 }
346
347 #[test]
350 fn tampered_digest_fails_output_assertion() {
351 let mut builder = CircuitBuilder::new();
352 let example = IndependentSha256Compressions::build(
353 PrimitiveParams { num_primitives: 1 },
354 &mut builder,
355 )
356 .unwrap();
357 let circuit = builder.build();
358 let mut filler = circuit.new_witness_filler();
359 example
360 .populate_witness(Instance { seed: Some(7) }, &mut filler)
361 .unwrap();
362
363 let digest_wire = example.compressions[0].digest[0];
364 filler[digest_wire] = Word(filler[digest_wire].0 ^ 1);
365 assert!(circuit.populate_wire_witness(&mut filler).is_err());
366 }
367}