Skip to main content

binius_spartan_frontend/
circuit_builder.rs

1// Copyright 2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4use std::{array, backtrace::Backtrace, collections::HashMap, mem, ops::Deref, sync::Arc};
5
6use binius_field::Field;
7use bytemuck::zeroed_vec;
8use smallvec::{SmallVec, smallvec};
9
10use crate::constraint_system::{
11	ConstraintSystem, ConstraintWire, MulConstraint, Operand, WireKind, Witness, WitnessIndex,
12	WitnessLayout, WitnessSegment,
13};
14
15/// Common interface for circuit construction and witness generation.
16///
17/// This trait enables writing circuit logic once that works for both abstract circuit
18/// expression ([`ConstraintBuilder`]) and concrete witness generation ([`WitnessGenerator`]).
19/// The same function can build constraints symbolically or evaluate them with concrete values.
20pub trait CircuitBuilder {
21	type Wire: Copy;
22	type Field: Field;
23
24	fn assert_zero(&mut self, wire: Self::Wire);
25
26	fn assert_eq(&mut self, lhs: Self::Wire, rhs: Self::Wire) {
27		let diff = self.add(lhs, rhs);
28		self.assert_zero(diff);
29	}
30
31	fn constant(&mut self, val: Self::Field) -> Self::Wire;
32
33	fn add(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire;
34
35	// Addition is subtraction in characteristic 2.
36	fn sub(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire {
37		self.add(lhs, rhs)
38	}
39
40	fn mul(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire;
41
42	fn hint<H: Fn([Self::Field; IN]) -> [Self::Field; OUT], const IN: usize, const OUT: usize>(
43		&mut self,
44		inputs: [Self::Wire; IN],
45		f: H,
46	) -> [Self::Wire; OUT];
47
48	/// Runtime-arity variant of [`hint`](Self::hint): takes a slice of input wires and an
49	/// `out_len`, with a closure mapping the input values to `out_len` output values. Emits no
50	/// constraints; the outputs are public-derivable (allocated in the derived segment) iff every
51	/// input is, matching [`hint`](Self::hint). The closure is invoked at most once —
52	/// value-generating builders run it, the symbolic builder skips it.
53	fn hint_varsize<H: FnOnce(&[Self::Field]) -> Vec<Self::Field>>(
54		&mut self,
55		inputs: &[Self::Wire],
56		out_len: usize,
57		f: H,
58	) -> Vec<Self::Wire>;
59}
60
61#[derive(Debug)]
62pub struct WireAllocator {
63	n_wires: u32,
64	kind: WireKind,
65}
66
67impl WireAllocator {
68	pub const fn new(kind: WireKind) -> Self {
69		WireAllocator { n_wires: 0, kind }
70	}
71
72	pub const fn alloc(&mut self) -> ConstraintWire {
73		let wire = ConstraintWire {
74			kind: self.kind,
75			id: self.n_wires,
76		};
77		self.n_wires += 1;
78		wire
79	}
80}
81
82// TODO: Add string labels for constraints to make validation easier.
83
84// Witness values are a permuted subset of the wire values.
85// Need a way to fingerprint a constraint system.
86
87/// Status of a private wire during optimization.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum WireStatus {
90	/// Wire is unknown - not yet analyzed for elimination
91	Unknown,
92	/// Wire is pinned - explicitly kept alive
93	Pinned,
94	/// Wire has been pruned (eliminated)
95	Pruned,
96}
97
98/// Intermediate representation of a constraint system that can be manipulated and optimized.
99///
100/// This IR is used during circuit construction and optimization passes like wire elimination.
101/// It tracks wire allocators, constants, and constraints, along with metadata about which
102/// private wires are still alive (not eliminated by optimization).
103#[derive(Debug)]
104pub struct ConstraintSystemIR<F: Field> {
105	pub(crate) constant_alloc: WireAllocator,
106	pub(crate) public_alloc: WireAllocator,
107	pub(crate) derived_alloc: WireAllocator,
108	pub(crate) precommit_alloc: WireAllocator,
109	pub(crate) private_alloc: WireAllocator,
110	pub(crate) constants: HashMap<F, u32>,
111	pub(crate) zero_constraints: Vec<Operand<ConstraintWire>>,
112	pub(crate) mul_constraints: Vec<MulConstraint<ConstraintWire>>,
113	/// Tracks the status of private wires (Unknown, Pinned, or Pruned).
114	/// Index corresponds to private wire ID. Initially all wires are Unknown.
115	pub(crate) private_wires_status: Vec<WireStatus>,
116}
117
118impl<F: Field> ConstraintSystemIR<F> {
119	/// Finalize the IR into a ConstraintSystem and WitnessLayout by converting remaining
120	/// zero constraints to MulConstraints, computing the final witness layout, and mapping all
121	/// ConstraintWires to WitnessIndices.
122	///
123	/// Internally looks up or allocates a constant wire with value 1, used to convert
124	/// zero constraints of the form `A = 0` into MulConstraints `A * 1 = 0`.
125	pub fn finalize(mut self) -> (ConstraintSystem<F>, WitnessLayout<F>) {
126		// Look up or allocate a constant wire for ONE
127		let one_id = self
128			.constants
129			.entry(F::ONE)
130			.or_insert_with(|| self.constant_alloc.alloc().id);
131		let one_wire = ConstraintWire {
132			kind: WireKind::Constant,
133			id: *one_id,
134		};
135
136		// Capture the zero constant wire id before consuming self.constants
137		let zero_const_id: Option<u32> = self.constants.get(&F::ZERO).copied();
138
139		// Convert constants HashMap to Vec
140		let mut constants = zeroed_vec(self.constant_alloc.n_wires as usize);
141		for (val, id) in self.constants {
142			constants[id as usize] = val;
143		}
144
145		// Replace all remaining zero constraints with mul constraints
146		let one_operand = Operand::from(one_wire);
147		let zero_operand = Operand::default();
148		for operand in mem::take(&mut self.zero_constraints) {
149			if !operand.is_empty() {
150				self.mul_constraints.push(MulConstraint {
151					a: operand,
152					b: one_operand.clone(),
153					c: zero_operand.clone(),
154				});
155			}
156		}
157
158		// Create private_alive array from wire status (invert pruned logic)
159		let private_alive: Vec<bool> = self
160			.private_wires_status
161			.iter()
162			.map(|&status| !matches!(status, WireStatus::Pruned))
163			.collect();
164
165		// A derived wire needs a public slot iff it is referenced by a surviving constraint. Scan
166		// the (now fully folded) mul constraints for derived ids; intermediate derived wires that
167		// feed only other derived wires are referenced nowhere and get no slot — the generators
168		// compute them inline and `layout.get` returns `None`.
169		let mut derived_alive = vec![false; self.derived_alloc.n_wires as usize];
170		for MulConstraint { a, b, c } in &self.mul_constraints {
171			for operand in [a, b, c] {
172				for wire in operand.wires() {
173					if wire.kind == WireKind::Derived {
174						derived_alive[wire.id as usize] = true;
175					}
176				}
177			}
178		}
179
180		// Create WitnessLayout
181		let layout = WitnessLayout::sparse(
182			constants.clone(),
183			self.public_alloc.n_wires,
184			self.precommit_alloc.n_wires,
185			&derived_alive,
186			&private_alive,
187		);
188
189		// Map all ConstraintWire to WitnessIndex, filtering out zero constant wires.
190		let map_operand = |operand: &Operand<ConstraintWire>| -> Operand<WitnessIndex> {
191			let indices: SmallVec<[WitnessIndex; 4]> = operand
192				.wires()
193				.iter()
194				.filter(|wire| !(wire.kind == WireKind::Constant && zero_const_id == Some(wire.id)))
195				.filter_map(|wire| layout.get(wire))
196				.collect();
197			Operand::new(indices)
198		};
199
200		let mul_constraints = self
201			.mul_constraints
202			.iter()
203			.map(|constraint| MulConstraint {
204				a: map_operand(&constraint.a),
205				b: map_operand(&constraint.b),
206				c: map_operand(&constraint.c),
207			})
208			.collect();
209
210		// Map one_wire to WitnessIndex
211		let one_wire_index = layout
212			.get(&one_wire)
213			.expect("one_wire constant should exist in layout")
214			.index;
215
216		let cs = ConstraintSystem::new(
217			constants,
218			layout.n_inout() as u32,
219			layout.n_precommit() as u32,
220			layout.n_private() as u32,
221			layout.log_public(),
222			mul_constraints,
223			one_wire_index,
224		);
225
226		(cs, layout)
227	}
228}
229
230/// Builds constraint systems symbolically by recording operations as constraints.
231///
232/// Implements [`CircuitBuilder`] with [`ConstraintWire`] as the wire type. Operations like
233/// `add` and `mul` allocate new wires and record constraints without evaluating values.
234#[derive(Debug)]
235pub struct ConstraintBuilder<F: Field> {
236	ir: ConstraintSystemIR<F>,
237}
238
239impl<F: Field> ConstraintBuilder<F> {
240	#[allow(clippy::new_without_default)]
241	pub fn new() -> Self {
242		ConstraintBuilder {
243			ir: ConstraintSystemIR {
244				constant_alloc: WireAllocator::new(WireKind::Constant),
245				public_alloc: WireAllocator::new(WireKind::InOut),
246				derived_alloc: WireAllocator::new(WireKind::Derived),
247				precommit_alloc: WireAllocator::new(WireKind::Precommit),
248				private_alloc: WireAllocator::new(WireKind::Private),
249				constants: HashMap::new(),
250				zero_constraints: Vec::new(),
251				mul_constraints: Vec::new(),
252				private_wires_status: Vec::new(),
253			},
254		}
255	}
256
257	pub const fn alloc_inout(&mut self) -> ConstraintWire {
258		self.ir.public_alloc.alloc()
259	}
260
261	pub const fn alloc_precommit(&mut self) -> ConstraintWire {
262		self.ir.precommit_alloc.alloc()
263	}
264
265	pub fn build(self) -> ConstraintSystemIR<F> {
266		self.ir
267	}
268}
269
270impl<F: Field> CircuitBuilder for ConstraintBuilder<F> {
271	type Wire = ConstraintWire;
272	type Field = F;
273
274	fn assert_zero(&mut self, wire: Self::Wire) {
275		self.ir.zero_constraints.push(wire.into());
276	}
277
278	fn assert_eq(&mut self, lhs: Self::Wire, rhs: Self::Wire) {
279		self.ir
280			.zero_constraints
281			.push(Operand::new(smallvec![lhs, rhs]));
282	}
283
284	fn constant(&mut self, val: F) -> Self::Wire {
285		let id = self
286			.ir
287			.constants
288			.entry(val)
289			.or_insert_with(|| self.ir.constant_alloc.alloc().id);
290		ConstraintWire {
291			kind: WireKind::Constant,
292			id: *id,
293		}
294	}
295
296	fn add(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire {
297		// If both inputs are public-derivable, the sum is too: allocate a derived wire in the
298		// public segment and emit no constraint. The verifier recomputes it itself.
299		if lhs.kind.is_public() && rhs.kind.is_public() {
300			return self.ir.derived_alloc.alloc();
301		}
302		let out = self.ir.private_alloc.alloc();
303		self.ir.private_wires_status.push(WireStatus::Unknown);
304		self.ir
305			.zero_constraints
306			.push(Operand::new(smallvec![lhs, rhs, out]));
307		out
308	}
309
310	fn mul(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire {
311		// If both inputs are public-derivable, the product is too: allocate a derived wire and emit
312		// no multiplication constraint.
313		if lhs.kind.is_public() && rhs.kind.is_public() {
314			return self.ir.derived_alloc.alloc();
315		}
316		let out = self.ir.private_alloc.alloc();
317		self.ir.private_wires_status.push(WireStatus::Unknown);
318		self.ir.mul_constraints.push(MulConstraint {
319			a: lhs.into(),
320			b: rhs.into(),
321			c: out.into(),
322		});
323		out
324	}
325
326	fn hint<H: Fn([F; IN]) -> [F; OUT], const IN: usize, const OUT: usize>(
327		&mut self,
328		inputs: [Self::Wire; IN],
329		_f: H,
330	) -> [Self::Wire; OUT] {
331		// A hint over only public-derivable inputs is itself public-derivable. (Hints emit no
332		// constraints regardless; only the allocator choice changes.)
333		let derived = inputs.iter().all(|wire| wire.kind.is_public());
334		array::from_fn(|_| {
335			if derived {
336				self.ir.derived_alloc.alloc()
337			} else {
338				let wire = self.ir.private_alloc.alloc();
339				self.ir.private_wires_status.push(WireStatus::Unknown);
340				wire
341			}
342		})
343	}
344
345	fn hint_varsize<H: FnOnce(&[F]) -> Vec<F>>(
346		&mut self,
347		inputs: &[Self::Wire],
348		out_len: usize,
349		_f: H,
350	) -> Vec<Self::Wire> {
351		// A hint over only public-derivable inputs is itself public-derivable. (Hints emit no
352		// constraints regardless; only the allocator choice changes.)
353		let derived = inputs.iter().all(|wire| wire.kind.is_public());
354		(0..out_len)
355			.map(|_| {
356				if derived {
357					self.ir.derived_alloc.alloc()
358				} else {
359					let wire = self.ir.private_alloc.alloc();
360					self.ir.private_wires_status.push(WireStatus::Unknown);
361					wire
362				}
363			})
364			.collect()
365	}
366}
367
368#[derive(Debug)]
369pub struct WitnessError {
370	pub backtrace: Backtrace,
371}
372
373#[derive(Debug, Clone, Copy, PartialEq, Eq)]
374pub struct WitnessWire<F: Field> {
375	val: F,
376	segment: WitnessSegment,
377}
378
379impl<F: Field> WitnessWire<F> {
380	#[inline]
381	pub const fn val(self) -> F {
382		self.val
383	}
384
385	/// Whether this wire's value lives in the public segment, i.e. is public-derivable. Drives the
386	/// derived-vs-private allocation choice, matching [`ConstraintBuilder`].
387	#[inline]
388	fn is_public(self) -> bool {
389		self.segment == WitnessSegment::Public
390	}
391}
392
393/// Generates witness values by evaluating circuit operations with concrete field elements.
394///
395/// Implements [`CircuitBuilder`] with [`WitnessWire`] as the wire type. Operations like
396/// `add` and `mul` compute actual field values and populate the witness array. Captures
397/// the first constraint violation as an error for debugging.
398///
399/// `LayoutRef` is any pointer to a [`WitnessLayout`], chosen by the caller:
400/// - `&WitnessLayout<F>` — a zero-cost borrow, tied to the borrow's lifetime.
401/// - `Arc<WitnessLayout<F>>` — shared and `'static` (the default).
402///
403/// A `'static` generator is what lets its `CircuitElem`s live in a `'static` transparent closure.
404/// Sharing an `Arc` gives that without cloning the layout, so the wrapper channels use it.
405#[derive(Debug)]
406pub struct WitnessGenerator<F: Field, LayoutRef = Arc<WitnessLayout<F>>> {
407	derived_alloc: WireAllocator,
408	private_alloc: WireAllocator,
409	public: Vec<F>,
410	precommit: Vec<F>,
411	private: Vec<F>,
412	layout: LayoutRef,
413	first_error: Option<Backtrace>,
414}
415
416impl<F: Field, LayoutRef: Deref<Target = WitnessLayout<F>>> WitnessGenerator<F, LayoutRef> {
417	pub fn new(layout: LayoutRef) -> Self {
418		let mut public = zeroed_vec(layout.public_size());
419		public[..layout.constants.len()].copy_from_slice(&layout.constants);
420
421		let precommit = zeroed_vec(layout.precommit_size());
422		let private = zeroed_vec(layout.private_size());
423
424		Self {
425			derived_alloc: WireAllocator::new(WireKind::Derived),
426			private_alloc: WireAllocator::new(WireKind::Private),
427			public,
428			precommit,
429			private,
430			layout,
431			first_error: None,
432		}
433	}
434
435	/// Allocates the output wire of an op, choosing the derived or private allocator from the input
436	/// kinds (matching [`ConstraintBuilder`]), and writes its value.
437	fn alloc_op_value(&mut self, all_derivable: bool, value: F) -> WitnessWire<F> {
438		let wire = if all_derivable {
439			self.derived_alloc.alloc()
440		} else {
441			self.private_alloc.alloc()
442		};
443		self.write_value(wire, value)
444	}
445
446	fn write_value(&mut self, wire: ConstraintWire, value: F) -> WitnessWire<F> {
447		if let Some(index) = self.layout.get(&wire) {
448			match index.segment {
449				WitnessSegment::Public => self.public[index.index as usize] = value,
450				WitnessSegment::Precommit => self.precommit[index.index as usize] = value,
451				WitnessSegment::Private => self.private[index.index as usize] = value,
452			}
453		}
454		WitnessWire {
455			val: value,
456			segment: wire.kind.segment(),
457		}
458	}
459
460	pub fn write_inout(&mut self, wire: ConstraintWire, value: F) -> WitnessWire<F> {
461		assert_eq!(wire.kind, WireKind::InOut);
462		self.write_value(wire, value)
463	}
464
465	pub fn write_precommit(&mut self, wire: ConstraintWire, value: F) -> WitnessWire<F> {
466		assert_eq!(wire.kind, WireKind::Precommit);
467		self.write_value(wire, value)
468	}
469
470	pub fn build(self) -> Result<Witness<F>, WitnessError> {
471		if let Some(backtrace) = self.first_error {
472			Err(WitnessError { backtrace })
473		} else {
474			Ok(Witness::new(self.public, self.precommit, self.private))
475		}
476	}
477
478	pub const fn error(&self) -> Option<&Backtrace> {
479		self.first_error.as_ref()
480	}
481
482	fn record_error(&mut self) {
483		if self.first_error.is_none() {
484			self.first_error = Some(Backtrace::capture());
485		}
486	}
487}
488
489impl<F: Field, LayoutRef: Deref<Target = WitnessLayout<F>>> CircuitBuilder
490	for WitnessGenerator<F, LayoutRef>
491{
492	type Wire = WitnessWire<F>;
493	type Field = F;
494
495	fn assert_zero(&mut self, wire: Self::Wire) {
496		if wire.val() != F::ZERO {
497			self.record_error();
498		}
499	}
500
501	fn assert_eq(&mut self, lhs: Self::Wire, rhs: Self::Wire) {
502		if lhs.val() != rhs.val() {
503			self.record_error();
504		}
505	}
506
507	fn constant(&mut self, val: F) -> Self::Wire {
508		WitnessWire {
509			val,
510			segment: WitnessSegment::Public,
511		}
512	}
513
514	fn add(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire {
515		let all_derivable = lhs.is_public() && rhs.is_public();
516		self.alloc_op_value(all_derivable, lhs.val() + rhs.val())
517	}
518
519	fn mul(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire {
520		let all_derivable = lhs.is_public() && rhs.is_public();
521		self.alloc_op_value(all_derivable, lhs.val() * rhs.val())
522	}
523
524	fn hint<H: Fn([F; IN]) -> [F; OUT], const IN: usize, const OUT: usize>(
525		&mut self,
526		inputs: [Self::Wire; IN],
527		f: H,
528	) -> [Self::Wire; OUT] {
529		let all_derivable = inputs.iter().all(|wire| wire.is_public());
530		f(inputs.map(WitnessWire::val)).map(|value| self.alloc_op_value(all_derivable, value))
531	}
532
533	fn hint_varsize<H: FnOnce(&[F]) -> Vec<F>>(
534		&mut self,
535		inputs: &[Self::Wire],
536		out_len: usize,
537		f: H,
538	) -> Vec<Self::Wire> {
539		let all_derivable = inputs.iter().all(|wire| wire.is_public());
540		let input_vals: Vec<F> = inputs.iter().map(|wire| wire.val()).collect();
541		let outputs = f(&input_vals);
542		debug_assert_eq!(
543			outputs.len(),
544			out_len,
545			"hint_varsize closure returned wrong output count"
546		);
547		outputs
548			.into_iter()
549			.map(|value| self.alloc_op_value(all_derivable, value))
550			.collect()
551	}
552}
553
554/// Wire type for [`InstanceGenerator`]: holds the field value of a public wire (a constant, inout,
555/// or derived value the verifier can compute) and is `None` for private/precommit wires, whose
556/// values the verifier does not know. A wire being `Some` mirrors [`WireKind::is_public`], so the
557/// derived-vs-private branching stays aligned with the other builders.
558#[derive(Debug, Clone, Copy, PartialEq, Eq)]
559pub struct PublicWire<F: Field>(Option<F>);
560
561impl<F: Field> PublicWire<F> {
562	/// The wire's value if it is public-derivable (constant, inout, or derived), else `None` for a
563	/// private/precommit wire whose value the verifier does not know.
564	#[inline]
565	pub const fn value(self) -> Option<F> {
566		self.0
567	}
568}
569
570/// Reconstructs the public input vector `[constants | inout | derived]` on the verifier side by
571/// re-running the circuit function.
572///
573/// Implements [`CircuitBuilder`] with [`PublicWire`] as the wire type, structurally mirroring
574/// [`WitnessGenerator`], but it only computes the *public* segment. The verifier lacks the secret
575/// (precommit) values, so private wires carry no value; by the soundness invariant a derived wire
576/// never reads a secret, so every derived value is computable from the real public inputs alone.
577/// Derived wires are allocated in the same order as [`ConstraintBuilder`], keeping their ids
578/// aligned so [`WitnessLayout::get`] resolves each to the correct public slot.
579///
580/// `LayoutRef` is any pointer to a [`WitnessLayout`], chosen by the caller:
581/// - `&WitnessLayout<F>` — a zero-cost borrow, tied to the borrow's lifetime.
582/// - `Arc<WitnessLayout<F>>` — shared and `'static` (the default).
583///
584/// A `'static` generator is what lets a wrapper channel hand its transparent closures to an opener.
585/// Sharing an `Arc` gives that without cloning the layout, so the wrapper channel uses it.
586#[derive(Debug)]
587pub struct InstanceGenerator<F: Field, LayoutRef = Arc<WitnessLayout<F>>> {
588	derived_alloc: WireAllocator,
589	public: Vec<F>,
590	layout: LayoutRef,
591}
592
593impl<F: Field, LayoutRef: Deref<Target = WitnessLayout<F>>> InstanceGenerator<F, LayoutRef> {
594	pub fn new(layout: LayoutRef) -> Self {
595		let mut public = zeroed_vec(layout.public_size());
596		public[..layout.constants.len()].copy_from_slice(&layout.constants);
597
598		Self {
599			derived_alloc: WireAllocator::new(WireKind::Derived),
600			public,
601			layout,
602		}
603	}
604
605	/// Writes a real inout value into its public slot.
606	pub fn write_inout(&mut self, wire: ConstraintWire, value: F) -> PublicWire<F> {
607		assert_eq!(wire.kind, WireKind::InOut);
608		self.write_public(wire, value)
609	}
610
611	/// Records a precommit wire as private (value unknown to the verifier). Per the soundness
612	/// invariant a secret never feeds a derived value, so the missing value is inert.
613	pub fn placeholder_precommit(&mut self, wire: ConstraintWire) -> PublicWire<F> {
614		assert_eq!(wire.kind, WireKind::Precommit);
615		PublicWire(None)
616	}
617
618	/// Writes `value` to the wire's public slot if it has one (i.e. it is alive), then returns the
619	/// wire carrying its value. Wires with no slot (pruned intermediates) are computed inline only.
620	fn write_public(&mut self, wire: ConstraintWire, value: F) -> PublicWire<F> {
621		if let Some(index) = self.layout.get(&wire) {
622			debug_assert_eq!(index.segment, WitnessSegment::Public);
623			self.public[index.index as usize] = value;
624		}
625		PublicWire(Some(value))
626	}
627
628	/// The reconstructed public vector `[constants | inout | derived]` so far, of length
629	/// `1 << layout.log_public()`. Once every inout and alive-derived wire has been written, this
630	/// is the final public vector; reading it by reference (rather than consuming via
631	/// [`Self::build`]) lets callers keep the generator alive for wires that still reference it.
632	pub fn public(&self) -> &[F] {
633		&self.public
634	}
635
636	/// Returns the reconstructed public vector `[constants | inout | derived]`, of length
637	/// `1 << layout.log_public()`, ready to pass to `Verifier::verify`.
638	pub fn build(self) -> Vec<F> {
639		self.public
640	}
641}
642
643impl<F: Field, LayoutRef: Deref<Target = WitnessLayout<F>>> CircuitBuilder
644	for InstanceGenerator<F, LayoutRef>
645{
646	type Wire = PublicWire<F>;
647	type Field = F;
648
649	// Assertions are enforced as ordinary public-segment constraints by the verifier, so they are
650	// no-ops here. They must not allocate, to stay wire-id-aligned with the other builders.
651	fn assert_zero(&mut self, _wire: Self::Wire) {}
652
653	fn assert_eq(&mut self, _lhs: Self::Wire, _rhs: Self::Wire) {}
654
655	fn constant(&mut self, val: F) -> Self::Wire {
656		PublicWire(Some(val))
657	}
658
659	fn add(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire {
660		match (lhs.0, rhs.0) {
661			(Some(lhs), Some(rhs)) => {
662				let wire = self.derived_alloc.alloc();
663				self.write_public(wire, lhs + rhs)
664			}
665			_ => PublicWire(None),
666		}
667	}
668
669	fn mul(&mut self, lhs: Self::Wire, rhs: Self::Wire) -> Self::Wire {
670		match (lhs.0, rhs.0) {
671			(Some(lhs), Some(rhs)) => {
672				let wire = self.derived_alloc.alloc();
673				self.write_public(wire, lhs * rhs)
674			}
675			_ => PublicWire(None),
676		}
677	}
678
679	fn hint<H: Fn([F; IN]) -> [F; OUT], const IN: usize, const OUT: usize>(
680		&mut self,
681		inputs: [Self::Wire; IN],
682		f: H,
683	) -> [Self::Wire; OUT] {
684		// Only invoke `f` when every input is public (its value is known); a non-derived hint would
685		// receive value-less inputs, so `f` is skipped to avoid panics on missing values.
686		if inputs.iter().all(|wire| wire.0.is_some()) {
687			let values = inputs.map(|wire| wire.0.expect("every input checked public above"));
688			f(values).map(|value| {
689				let wire = self.derived_alloc.alloc();
690				self.write_public(wire, value)
691			})
692		} else {
693			[PublicWire(None); OUT]
694		}
695	}
696
697	fn hint_varsize<H: FnOnce(&[F]) -> Vec<F>>(
698		&mut self,
699		inputs: &[Self::Wire],
700		out_len: usize,
701		f: H,
702	) -> Vec<Self::Wire> {
703		// Only invoke `f` when every input is public (its value is known); otherwise the outputs
704		// are private (value-less to the verifier) and allocate no derived wires, matching the
705		// other builders' private branch.
706		if inputs.iter().all(|wire| wire.0.is_some()) {
707			let input_vals: Vec<F> = inputs
708				.iter()
709				.map(|wire| wire.0.expect("every input checked public above"))
710				.collect();
711			let outputs = f(&input_vals);
712			debug_assert_eq!(
713				outputs.len(),
714				out_len,
715				"hint_varsize closure returned wrong output count"
716			);
717			outputs
718				.into_iter()
719				.map(|value| {
720					let wire = self.derived_alloc.alloc();
721					self.write_public(wire, value)
722				})
723				.collect()
724		} else {
725			vec![PublicWire(None); out_len]
726		}
727	}
728}
729
730#[cfg(test)]
731mod tests {
732	use std::iter::successors;
733
734	use binius_field::{Field, Ghash128b as B128, PackedField};
735
736	use super::*;
737
738	fn fibonacci<Builder: CircuitBuilder>(
739		builder: &mut Builder,
740		x0: Builder::Wire,
741		x1: Builder::Wire,
742		n: usize,
743	) -> Builder::Wire {
744		if n == 0 {
745			return x0;
746		}
747
748		let (_xnsub1, xn) = successors(Some((x0, x1)), |&(a, b)| {
749			let next = builder.mul(a, b);
750			Some((b, next))
751		})
752		.nth(n - 1)
753		.expect("closure always returns Some");
754
755		xn
756	}
757
758	#[test]
759	fn test_fibonacci() {
760		let mut constraint_builder = ConstraintBuilder::new();
761		let x0 = constraint_builder.alloc_inout();
762		let x1 = constraint_builder.alloc_inout();
763		let xn = constraint_builder.alloc_inout();
764		let out = fibonacci(&mut constraint_builder, x0, x1, 20);
765		constraint_builder.assert_eq(out, xn);
766		let ir = constraint_builder.build();
767		let (constraint_system, layout) = ir.finalize();
768
769		let mut witness_generator = WitnessGenerator::new(&layout);
770		let x0 = witness_generator.write_inout(x0, B128::ONE);
771		let x1 = witness_generator.write_inout(x1, B128::MULTIPLICATIVE_GENERATOR);
772		let xn = witness_generator.write_inout(xn, B128::MULTIPLICATIVE_GENERATOR.pow(6765));
773		let out = fibonacci(&mut witness_generator, x0, x1, 20);
774		witness_generator.assert_eq(out, xn);
775		let witness = witness_generator.build().unwrap();
776
777		constraint_system.validate(&witness);
778	}
779
780	#[test]
781	fn test_fibonacci_with_precommit() {
782		let mut constraint_builder = ConstraintBuilder::new();
783		let x0 = constraint_builder.alloc_inout();
784		let x1 = constraint_builder.alloc_inout();
785		let xn = constraint_builder.alloc_precommit();
786		let out = fibonacci(&mut constraint_builder, x0, x1, 20);
787		constraint_builder.assert_eq(out, xn);
788		let ir = constraint_builder.build();
789		let (constraint_system, layout) = ir.finalize();
790
791		let mut witness_generator = WitnessGenerator::new(&layout);
792		let x0 = witness_generator.write_inout(x0, B128::ONE);
793		let x1 = witness_generator.write_inout(x1, B128::MULTIPLICATIVE_GENERATOR);
794		let xn = witness_generator.write_precommit(xn, B128::MULTIPLICATIVE_GENERATOR.pow(6765));
795		let out = fibonacci(&mut witness_generator, x0, x1, 20);
796		witness_generator.assert_eq(out, xn);
797		let witness = witness_generator.build().unwrap();
798
799		constraint_system.validate(&witness);
800	}
801
802	#[test]
803	fn test_assertion_failure_captured() {
804		let mut constraint_builder = ConstraintBuilder::new();
805		let x = constraint_builder.alloc_inout();
806		let y = constraint_builder.alloc_inout();
807		let sum = constraint_builder.add(x, y);
808		let expected = constraint_builder.alloc_inout();
809		constraint_builder.assert_eq(sum, expected);
810		let ir = constraint_builder.build();
811		let (_constraint_system, layout) = ir.finalize();
812
813		let mut witness_generator = WitnessGenerator::new(&layout);
814		let x_val = B128::new(5);
815		let y_val = B128::new(7);
816		let wrong_expected = B128::new(99); // Incorrect: should be 5 + 7 = 2 in binary field
817
818		let x_wire = witness_generator.write_inout(x, x_val);
819		let y_wire = witness_generator.write_inout(y, y_val);
820		let sum_wire = witness_generator.add(x_wire, y_wire);
821		let expected_wire = witness_generator.write_inout(expected, wrong_expected);
822		witness_generator.assert_eq(sum_wire, expected_wire);
823
824		assert!(witness_generator.build().is_err());
825	}
826
827	#[test]
828	fn test_zero_constant_not_in_final_operands() {
829		use crate::{compiler::compile, constraint_system::WitnessSegment};
830
831		// Build a circuit where a zero constant is added to a wire; after compilation the
832		// zero constant term must be absent from all mul-constraint operands.
833		let mut builder = ConstraintBuilder::new();
834		let x = builder.alloc_inout();
835		let zero = builder.constant(B128::ZERO);
836		let sum = builder.add(x, zero);
837		let y = builder.alloc_inout();
838		builder.assert_eq(sum, y);
839
840		let (cs, _layout) = compile(builder);
841
842		let zero_constant_indices: std::collections::HashSet<u32> = cs
843			.constants()
844			.iter()
845			.enumerate()
846			.filter(|&(_, c)| *c == B128::ZERO)
847			.map(|(i, _)| i as u32)
848			.collect();
849
850		for constraint in cs.mul_constraints() {
851			for operand in [&constraint.a, &constraint.b, &constraint.c] {
852				for wire_idx in operand.wires() {
853					assert!(
854						!(wire_idx.segment == WitnessSegment::Public
855							&& zero_constant_indices.contains(&wire_idx.index)),
856						"zero constant found in compiled operand at WitnessIndex::public({})",
857						wire_idx.index
858					);
859				}
860			}
861		}
862	}
863
864	/// A circuit mixing derived wires (a derived mul, a derived hint, a derived add) with a private
865	/// wire (a mul against a precommit input). Used by the sync test below.
866	///
867	/// `expected` must equal `((a * b) * (a * b).invert_or_zero() + b) * s` (see
868	/// [`mixed_expected`]), so that the witness is satisfiable.
869	fn mixed_circuit<Builder: CircuitBuilder>(
870		builder: &mut Builder,
871		a: Builder::Wire,
872		b: Builder::Wire,
873		s: Builder::Wire,
874		expected: Builder::Wire,
875	) {
876		use binius_field::arithmetic_traits::InvertOrZero;
877
878		let d = builder.mul(a, b); // derived (a, b public-derivable)
879		let [d_inv] = builder.hint([d], |[x]| [x.invert_or_zero()]); // derived hint
880		let one_check = builder.mul(d, d_inv); // derived (== 1 when d != 0)
881		let e = builder.add(one_check, b); // derived
882		let p = builder.mul(e, s); // private (s is precommit)
883		builder.assert_eq(p, expected);
884	}
885
886	// Computes the `expected` inout value for `mixed_circuit` from concrete inputs.
887	fn mixed_expected(a: B128, b: B128, s: B128) -> B128 {
888		use binius_field::arithmetic_traits::InvertOrZero;
889		let d = a * b;
890		let one_check = d * d.invert_or_zero();
891		(one_check + b) * s
892	}
893
894	#[test]
895	fn test_instance_generator_syncs_with_witness() {
896		use crate::compiler::compile;
897
898		let a_val = B128::new(3);
899		let b_val = B128::new(5);
900		let s_val = B128::new(7);
901		let expected_val = mixed_expected(a_val, b_val, s_val);
902
903		let mut cb = ConstraintBuilder::new();
904		let a = cb.alloc_inout();
905		let b = cb.alloc_inout();
906		let s = cb.alloc_precommit();
907		let expected = cb.alloc_inout();
908		mixed_circuit(&mut cb, a, b, s, expected);
909		let (cs, layout) = compile(cb);
910
911		// Witness generation (prover side) fills the full witness including the derived publics.
912		let mut wg = WitnessGenerator::new(&layout);
913		let a_w = wg.write_inout(a, a_val);
914		let b_w = wg.write_inout(b, b_val);
915		let s_w = wg.write_precommit(s, s_val);
916		let expected_w = wg.write_inout(expected, expected_val);
917		mixed_circuit(&mut wg, a_w, b_w, s_w, expected_w);
918		let witness = wg.build().expect("witness generation should succeed");
919		cs.validate(&witness);
920
921		// Instance generation (verifier side) reconstructs only the public segment, without the
922		// secret `s`. It must match the witness's public segment exactly.
923		let mut ig = InstanceGenerator::new(&layout);
924		let a_i = ig.write_inout(a, a_val);
925		let b_i = ig.write_inout(b, b_val);
926		let s_i = ig.placeholder_precommit(s);
927		let expected_i = ig.write_inout(expected, expected_val);
928		mixed_circuit(&mut ig, a_i, b_i, s_i, expected_i);
929		let public = ig.build();
930
931		assert_eq!(public, witness.public());
932	}
933
934	#[test]
935	fn test_derived_elision_no_private_wires() {
936		use crate::compiler::compile;
937
938		// A circuit that is a pure function of inout: x -> x^2 -> x^3, asserting x^3 == y.
939		let mut cb = ConstraintBuilder::new();
940		let x = cb.alloc_inout();
941		let y = cb.alloc_inout();
942		let x2 = cb.mul(x, x); // derived (intermediate, feeds only x3)
943		let x3 = cb.mul(x2, x); // derived (referenced by the assert)
944		cb.assert_eq(x3, y);
945		assert_eq!(x2.kind, WireKind::Derived);
946		assert_eq!(x3.kind, WireKind::Derived);
947		let (cs, layout) = compile(cb);
948
949		// No private wires or mul constraints from the chain; x2 is a pruned intermediate, so only
950		// x3 occupies a derived public slot.
951		assert_eq!(cs.n_private(), 0);
952		assert_eq!(layout.n_derived(), 1);
953
954		let x_val = B128::new(9);
955		let y_val = x_val * x_val * x_val;
956
957		let mut wg = WitnessGenerator::new(&layout);
958		let x_w = wg.write_inout(x, x_val);
959		let y_w = wg.write_inout(y, y_val);
960		let x2_w = wg.mul(x_w, x_w);
961		let x3_w = wg.mul(x2_w, x_w);
962		wg.assert_eq(x3_w, y_w);
963		let witness = wg.build().expect("witness generation should succeed");
964		cs.validate(&witness);
965	}
966
967	#[test]
968	fn test_derived_wire_in_constraint_maps_to_public() {
969		use crate::{compiler::compile, constraint_system::WitnessSegment};
970
971		// `e` is derived but consumed by a mul constraint that also references the private `p`.
972		let mut cb = ConstraintBuilder::new();
973		let a = cb.alloc_inout();
974		let b = cb.alloc_inout();
975		let s = cb.alloc_precommit();
976		let e = cb.add(a, b); // derived
977		let p = cb.mul(e, s); // private; mul constraint references derived `e`
978		cb.assert_zero(p);
979		assert_eq!(e.kind, WireKind::Derived);
980		let (cs, layout) = compile(cb);
981
982		// The derived wire resolves to a slot in the public segment.
983		let e_index = layout.get(&e).expect("alive derived wire must have a slot");
984		assert_eq!(e_index.segment, WitnessSegment::Public);
985
986		// Some surviving mul constraint references that public index.
987		let references_e = cs.mul_constraints().iter().any(|c| {
988			[&c.a, &c.b, &c.c]
989				.iter()
990				.any(|operand| operand.wires().contains(&e_index))
991		});
992		assert!(references_e, "derived wire's public index should appear in a mul constraint");
993
994		// And a consistent witness validates.
995		let a_val = B128::new(3);
996		let b_val = B128::new(5);
997		let s_val = B128::ZERO; // makes p = e * 0 = 0 so assert_zero(p) holds
998		let mut wg = WitnessGenerator::new(&layout);
999		let a_w = wg.write_inout(a, a_val);
1000		let b_w = wg.write_inout(b, b_val);
1001		let s_w = wg.write_precommit(s, s_val);
1002		let e_w = wg.add(a_w, b_w);
1003		let p_w = wg.mul(e_w, s_w);
1004		wg.assert_zero(p_w);
1005		let witness = wg.build().expect("witness generation should succeed");
1006		cs.validate(&witness);
1007	}
1008
1009	#[test]
1010	fn test_pruned_derived_intermediate_has_no_slot() {
1011		use crate::compiler::compile;
1012
1013		// x2 feeds only the derived x3; only x3 is referenced by a surviving constraint.
1014		let mut cb = ConstraintBuilder::<B128>::new();
1015		let x = cb.alloc_inout();
1016		let y = cb.alloc_inout();
1017		let x2 = cb.mul(x, x); // derived intermediate
1018		let x3 = cb.mul(x2, x); // derived, referenced by assert
1019		cb.assert_eq(x3, y);
1020		let (_cs, layout) = compile(cb);
1021
1022		// The intermediate has no public slot; the alive one does.
1023		assert!(layout.get(&x2).is_none(), "pruned derived intermediate must have no slot");
1024		assert!(layout.get(&x3).is_some(), "referenced derived wire must have a slot");
1025	}
1026
1027	#[test]
1028	fn test_hint_varsize_syncs_with_witness() {
1029		use crate::compiler::compile;
1030
1031		// From two public inouts `a`, `b`, a runtime-arity hint yields `[a + b, a * b]`; the first
1032		// output (a derived wire) is asserted equal to an inout `expected`. The second output feeds
1033		// nothing and is pruned. The instance generator must reproduce the witness public segment.
1034		fn circuit<Builder: CircuitBuilder>(
1035			builder: &mut Builder,
1036			a: Builder::Wire,
1037			b: Builder::Wire,
1038			expected: Builder::Wire,
1039		) {
1040			let outs =
1041				builder.hint_varsize(&[a, b], 2, |vals| vec![vals[0] + vals[1], vals[0] * vals[1]]);
1042			builder.assert_eq(outs[0], expected);
1043		}
1044
1045		let a_val = B128::new(3);
1046		let b_val = B128::new(5);
1047		let expected_val = a_val + b_val;
1048
1049		let mut cb = ConstraintBuilder::new();
1050		let a = cb.alloc_inout();
1051		let b = cb.alloc_inout();
1052		let expected = cb.alloc_inout();
1053		circuit(&mut cb, a, b, expected);
1054		let (cs, layout) = compile(cb);
1055
1056		let mut wg = WitnessGenerator::new(&layout);
1057		let a_w = wg.write_inout(a, a_val);
1058		let b_w = wg.write_inout(b, b_val);
1059		let expected_w = wg.write_inout(expected, expected_val);
1060		circuit(&mut wg, a_w, b_w, expected_w);
1061		let witness = wg.build().expect("witness generation should succeed");
1062		cs.validate(&witness);
1063
1064		let mut ig = InstanceGenerator::new(&layout);
1065		let a_i = ig.write_inout(a, a_val);
1066		let b_i = ig.write_inout(b, b_val);
1067		let expected_i = ig.write_inout(expected, expected_val);
1068		circuit(&mut ig, a_i, b_i, expected_i);
1069		let public = ig.build();
1070
1071		assert_eq!(public, witness.public());
1072	}
1073}