binius_circuits/
lib.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3//! The Binius frontend library, along with useful gadgets and examples.
4//!
5//! The frontend library provides high-level interfaces for constructing constraint systems in the
6//! [`crate::builder`] module. Most other modules contain circuit gadgets that can be used to build
7//! more complex constraint systems.
8
9#![allow(clippy::module_inception)]
10
11pub mod arithmetic;
12pub mod bitwise;
13pub mod blake3;
14pub mod builder;
15pub mod collatz;
16pub mod keccakf;
17pub mod lasso;
18mod pack;
19pub mod plain_lookup;
20pub mod sha256;
21pub mod transparent;
22pub mod u32fib;
23pub mod unconstrained;
24pub mod vision;
25
26#[cfg(test)]
27mod tests {
28	use binius_core::{
29		constraint_system::{
30			self,
31			channel::{validate_witness, Boundary, FlushDirection, OracleOrConst},
32		},
33		fiat_shamir::HasherChallenger,
34		oracle::ShiftVariant,
35		polynomial::ArithCircuitPoly,
36		tower::CanonicalTowerFamily,
37	};
38	use binius_field::{
39		arch::OptimalUnderlier, as_packed_field::PackedType, underlier::WithUnderlier,
40		BinaryField128b, BinaryField1b, BinaryField64b, BinaryField8b, Field, TowerField,
41	};
42	use binius_hal::make_portable_backend;
43	use binius_hash::groestl::{Groestl256, Groestl256ByteCompression};
44	use binius_macros::arith_expr;
45	use binius_math::CompositionPoly;
46	use rand::{seq::SliceRandom, thread_rng};
47
48	type B128 = BinaryField128b;
49	type B64 = BinaryField64b;
50
51	use crate::{
52		builder::{
53			test_utils::test_circuit,
54			types::{F, U},
55			ConstraintSystemBuilder,
56		},
57		unconstrained::unconstrained,
58	};
59
60	#[test]
61	fn test_boundaries() {
62		// Proving Collatz Orbits
63		let allocator = bumpalo::Bump::new();
64		let mut builder = ConstraintSystemBuilder::new_with_witness(&allocator);
65
66		let log_size = PackedType::<U, BinaryField8b>::LOG_WIDTH + 2;
67
68		let channel_id = builder.add_channel();
69
70		let push_boundaries = Boundary {
71			values: vec![F::from_underlier(6)],
72			channel_id,
73			direction: FlushDirection::Push,
74			multiplicity: 1,
75		};
76
77		let pull_boundaries = Boundary {
78			values: vec![F::ONE],
79			channel_id,
80			direction: FlushDirection::Pull,
81			multiplicity: 1,
82		};
83
84		let boundaries = vec![pull_boundaries, push_boundaries];
85
86		let even = builder.add_committed("even", log_size, 3);
87
88		let half = builder.add_committed("half", log_size, 3);
89
90		let odd = builder.add_committed("odd", log_size, 3);
91
92		let output = builder.add_committed("output", log_size, 3);
93
94		let mut even_counter = 0;
95
96		let mut odd_counter = 0;
97
98		if let Some(witness) = builder.witness() {
99			let mut current = 6;
100
101			let mut even = witness.new_column::<BinaryField8b>(even);
102
103			let even_u8 = even.as_mut_slice::<u8>();
104
105			let mut half = witness.new_column::<BinaryField8b>(half);
106
107			let half_u8 = half.as_mut_slice::<u8>();
108
109			let mut odd = witness.new_column::<BinaryField8b>(odd);
110
111			let odd_u8 = odd.as_mut_slice::<u8>();
112
113			let mut output = witness.new_column::<BinaryField8b>(output);
114
115			let output_u8 = output.as_mut_slice::<u8>();
116
117			while current != 1 {
118				if current & 1 == 0 {
119					even_u8[even_counter] = current;
120					half_u8[even_counter] = current / 2;
121					current = half_u8[even_counter];
122					even_counter += 1;
123				} else {
124					odd_u8[odd_counter] = current;
125					output_u8[odd_counter] = 3 * current + 1;
126					current = output_u8[odd_counter];
127					odd_counter += 1;
128				}
129			}
130		}
131
132		builder
133			.flush(FlushDirection::Pull, channel_id, even_counter, [OracleOrConst::Oracle(even)])
134			.unwrap();
135		builder
136			.flush(FlushDirection::Push, channel_id, even_counter, [OracleOrConst::Oracle(half)])
137			.unwrap();
138		builder
139			.flush(FlushDirection::Pull, channel_id, odd_counter, [OracleOrConst::Oracle(odd)])
140			.unwrap();
141		builder
142			.flush(FlushDirection::Push, channel_id, odd_counter, [OracleOrConst::Oracle(output)])
143			.unwrap();
144
145		let witness = builder
146			.take_witness()
147			.expect("builder created with witness");
148
149		let constraint_system = builder.build().unwrap();
150
151		let backend = make_portable_backend();
152
153		let proof = constraint_system::prove::<
154			U,
155			CanonicalTowerFamily,
156			Groestl256,
157			Groestl256ByteCompression,
158			HasherChallenger<Groestl256>,
159			_,
160		>(&constraint_system, 1, 10, &boundaries, witness, &backend)
161		.unwrap();
162
163		constraint_system::verify::<
164			U,
165			CanonicalTowerFamily,
166			Groestl256,
167			Groestl256ByteCompression,
168			HasherChallenger<Groestl256>,
169		>(&constraint_system, 1, 10, &boundaries, proof)
170		.unwrap();
171	}
172
173	#[test]
174	#[ignore]
175	fn test_composite_circuit() {
176		let backend = make_portable_backend();
177		let allocator = bumpalo::Bump::new();
178		let mut builder = ConstraintSystemBuilder::new_with_witness(&allocator);
179		let n_vars = 8;
180		let log_inv_rate = 1;
181		let security_bits = 30;
182		let comp_1 = arith_expr!(B128[x, y] = x*y*y*0x85 +x*x*y*0x9 + y + 0x123);
183		let comp_2 =
184			arith_expr!(B128[x, y, z] = x*z*y*0x81115 +x*y*0x98888 + y*z + z*z*z*z*z*z + 0x155523);
185		let comp_3 = arith_expr!(B128[a, b, c, d, e, f] = e*f*f + a*b*c*2 + d*0x999 + 0x123);
186		let comp_4 = arith_expr!(B128[a, b] = a*(b+a));
187
188		let column_x = builder.add_committed("x", n_vars, 7);
189		let column_y = builder.add_committed("y", n_vars, 7);
190		let column_comp_1 = builder
191			.add_composite_mle("comp1", n_vars, [column_x, column_y], comp_1.clone())
192			.unwrap();
193
194		let column_shift = builder
195			.add_shifted(
196				"shift",
197				column_comp_1,
198				(1 << n_vars) - 1,
199				n_vars,
200				ShiftVariant::CircularLeft,
201			)
202			.unwrap();
203
204		let column_comp_2 = builder
205			.add_composite_mle(
206				"comp2",
207				n_vars,
208				[column_y, column_comp_1, column_shift],
209				comp_2.clone(),
210			)
211			.unwrap();
212
213		let column_z = builder.add_committed("z", n_vars + 1, 6);
214		let column_packed = builder.add_packed("packed", column_z, 1).unwrap();
215
216		let column_comp_3 = builder
217			.add_composite_mle(
218				"comp3",
219				n_vars,
220				[
221					column_x,
222					column_x,
223					column_comp_1,
224					column_shift,
225					column_comp_2,
226					column_packed,
227				],
228				comp_3.clone(),
229			)
230			.unwrap();
231
232		let column_comp_4 = builder
233			.add_composite_mle(
234				"comp4",
235				n_vars,
236				[
237					column_comp_2,
238					column_comp_3,
239					column_x,
240					column_shift,
241					column_y,
242				],
243				comp_4.clone(),
244			)
245			.unwrap();
246
247		// dummy channel
248		let channel = builder.add_channel();
249		builder
250			.send(
251				channel,
252				1 << n_vars,
253				vec![
254					OracleOrConst::Oracle(column_y),
255					OracleOrConst::Oracle(column_x),
256					OracleOrConst::Oracle(column_comp_1),
257					OracleOrConst::Oracle(column_shift),
258					OracleOrConst::Oracle(column_comp_2),
259					OracleOrConst::Oracle(column_packed),
260					OracleOrConst::Oracle(column_comp_3),
261				],
262			)
263			.unwrap();
264		builder
265			.receive(
266				channel,
267				1 << n_vars,
268				vec![
269					OracleOrConst::Oracle(column_x),
270					OracleOrConst::Oracle(column_y),
271					OracleOrConst::Oracle(column_comp_1),
272					OracleOrConst::Oracle(column_shift),
273					OracleOrConst::Oracle(column_comp_2),
274					OracleOrConst::Oracle(column_packed),
275					OracleOrConst::Oracle(column_comp_3),
276				],
277			)
278			.unwrap();
279
280		let values_x = (0..(1 << n_vars))
281			.map(|i| B128::from(i as u128))
282			.collect::<Vec<_>>();
283		let values_y = (0..(1 << n_vars))
284			.map(|i| B128::from(i * i))
285			.collect::<Vec<_>>();
286
287		let arith_poly_1 = ArithCircuitPoly::new(comp_1);
288		let values_comp_1 = (0..(1 << n_vars))
289			.map(|i| arith_poly_1.evaluate(&[values_x[i], values_y[i]]).unwrap())
290			.collect::<Vec<_>>();
291
292		let mut values_shift = values_comp_1.clone();
293		let first = values_shift.remove(0);
294		values_shift.push(first);
295
296		let arith_poly_2 = ArithCircuitPoly::new(comp_2);
297		let values_comp_2 = (0..(1 << n_vars))
298			.map(|i| {
299				arith_poly_2
300					.evaluate(&[values_y[i], values_comp_1[i], values_shift[i]])
301					.unwrap()
302			})
303			.collect::<Vec<_>>();
304
305		let values_z = (0..(1 << (n_vars + 1)))
306			.map(|i| B64::from(i * i / 8 + i % 10_u64))
307			.collect::<Vec<_>>();
308		let values_packed = (0..(1 << n_vars))
309			.map(|i| {
310				B128::from(
311					((values_z[2 * i + 1].val() as u128) << 64) + values_z[2 * i].val() as u128,
312				)
313			})
314			.collect::<Vec<_>>();
315
316		let arith_poly_3 = ArithCircuitPoly::new(comp_3);
317		let values_comp_3 = (0..(1 << n_vars))
318			.map(|i| {
319				arith_poly_3
320					.evaluate(&[
321						values_x[i],
322						values_x[i],
323						values_comp_1[i],
324						values_shift[i],
325						values_comp_2[i],
326						values_packed[i],
327					])
328					.unwrap()
329			})
330			.collect::<Vec<_>>();
331
332		let arith_poly_4 = ArithCircuitPoly::new(comp_4);
333		let values_comp_4 = (0..(1 << n_vars))
334			.map(|i| {
335				arith_poly_4
336					.evaluate(&[values_comp_2[i], values_comp_3[i]])
337					.unwrap()
338			})
339			.collect::<Vec<_>>();
340
341		let mut add_witness_col_b128 = |oracle_id: usize, values: &[B128]| {
342			builder
343				.witness()
344				.unwrap()
345				.new_column::<B128>(oracle_id)
346				.as_mut_slice()
347				.copy_from_slice(values);
348		};
349		add_witness_col_b128(column_x, &values_x);
350		add_witness_col_b128(column_y, &values_y);
351		add_witness_col_b128(column_comp_1, &values_comp_1);
352		add_witness_col_b128(column_shift, &values_shift);
353		add_witness_col_b128(column_comp_2, &values_comp_2);
354		add_witness_col_b128(column_packed, &values_packed);
355		add_witness_col_b128(column_comp_3, &values_comp_3);
356		add_witness_col_b128(column_comp_4, &values_comp_4);
357		builder
358			.witness()
359			.unwrap()
360			.new_column::<B64>(column_z)
361			.as_mut_slice()
362			.copy_from_slice(&values_z);
363
364		let witness = builder.take_witness().unwrap();
365		let constraint_system = builder.build().unwrap();
366
367		validate_witness(&witness, &[], &[], 1).unwrap();
368
369		let proof = binius_core::constraint_system::prove::<
370			OptimalUnderlier,
371			CanonicalTowerFamily,
372			Groestl256,
373			Groestl256ByteCompression,
374			HasherChallenger<Groestl256>,
375			_,
376		>(&constraint_system, log_inv_rate, security_bits, &[], witness, &backend)
377		.unwrap();
378
379		binius_core::constraint_system::verify::<
380			OptimalUnderlier,
381			CanonicalTowerFamily,
382			Groestl256,
383			Groestl256ByteCompression,
384			HasherChallenger<Groestl256>,
385		>(&constraint_system, log_inv_rate, security_bits, &[], proof)
386		.unwrap();
387	}
388
389	#[test]
390	fn test_flush_with_const() {
391		test_circuit(|builder| {
392			let channel_id = builder.add_channel();
393			let oracle = unconstrained::<BinaryField1b>(builder, "oracle", 1)?;
394			builder
395				.flush(
396					FlushDirection::Push,
397					channel_id,
398					1,
399					vec![
400						OracleOrConst::Oracle(oracle),
401						OracleOrConst::Const {
402							base: F::ONE,
403							tower_level: BinaryField1b::TOWER_LEVEL,
404						},
405					],
406				)
407				.unwrap();
408
409			builder
410				.flush(
411					FlushDirection::Pull,
412					channel_id,
413					1,
414					vec![
415						OracleOrConst::Oracle(oracle),
416						OracleOrConst::Const {
417							base: F::ONE,
418							tower_level: BinaryField1b::TOWER_LEVEL,
419						},
420					],
421				)
422				.unwrap();
423
424			Ok(vec![])
425		})
426		.unwrap()
427	}
428
429	//Testing with larger oracles, and random constants, in a random order. To see if given appropriate flushes with constants the channel balances.
430	#[test]
431	fn test_flush_with_const_large() {
432		test_circuit(|builder| {
433			let channel_id = builder.add_channel();
434			let mut rng = thread_rng();
435			let oracles = (0..5)
436				.map(|i| unconstrained::<BinaryField128b>(builder, format!("oracle {i}"), 5))
437				.collect::<Result<Vec<_>, _>>()?;
438			let random_consts = (0..5).map(|_| OracleOrConst::Const {
439				base: BinaryField128b::random(&mut rng),
440				tower_level: BinaryField128b::TOWER_LEVEL,
441			});
442			//Places the oracles and consts in a random order
443			//This is not a cryptographic random order, but it is good enough for testing
444			let mut random_order = oracles
445				.iter()
446				.copied()
447				.map(OracleOrConst::Oracle)
448				.chain(random_consts)
449				.collect::<Vec<_>>();
450			random_order.shuffle(&mut rng);
451
452			let random_order_iterator = random_order.iter().copied();
453			for i in 0..1 << 5 {
454				builder
455					.flush(FlushDirection::Push, channel_id, i, random_order_iterator.clone())
456					.unwrap();
457
458				builder
459					.flush(FlushDirection::Pull, channel_id, i, random_order_iterator.clone())
460					.unwrap();
461			}
462
463			Ok(vec![])
464		})
465		.unwrap()
466	}
467}