1use std::sync::{Arc, Mutex};
23use getset::{Getters, MutGetters, Setters};
4use itertools::Itertools;
56use crate::{
7 gates::{
8 circuit::CircuitBuilderStage,
9 flex_gate::{
10 threads::{GateStatistics, MultiPhaseCoreManager, SinglePhaseCoreManager},
11 MultiPhaseThreadBreakPoints, MAX_PHASE,
12 },
13 range::RangeConfig,
14 RangeChip,
15 },
16 halo2_proofs::{
17 circuit::{Layouter, Region},
18 plonk::{Column, Instance},
19 },
20 utils::ScalarField,
21 virtual_region::{
22 copy_constraints::{CopyConstraintManager, SharedCopyConstraintManager},
23 lookups::LookupAnyManager,
24 manager::VirtualRegionManager,
25 },
26 AssignedValue, Context,
27};
2829use super::BaseCircuitParams;
3031/// Keeping the naming `RangeCircuitBuilder` for backwards compatibility.
32pub type RangeCircuitBuilder<F> = BaseCircuitBuilder<F>;
3334/// A circuit builder is a collection of virtual region managers that together assign virtual
35/// regions into a single physical circuit.
36///
37/// [BaseCircuitBuilder] is a circuit builder to create a circuit where the columns correspond to [super::BaseConfig].
38/// This builder can hold multiple threads, but the `Circuit` implementation only evaluates the first phase.
39/// The user will have to implement a separate `Circuit` with multi-phase witness generation logic.
40///
41/// This is used to manage the virtual region corresponding to [super::FlexGateConfig] and (optionally) [RangeConfig].
42/// This can be used even if only using [`GateChip`](crate::gates::flex_gate::GateChip) without [RangeChip].
43///
44/// The circuit will have `NI` public instance (aka public inputs+outputs) columns.
45#[derive(Clone, Debug, Getters, MutGetters, Setters)]
46pub struct BaseCircuitBuilder<F: ScalarField> {
47/// Virtual region for each challenge phase. These cannot be shared across threads while keeping circuit deterministic.
48#[getset(get = "pub", get_mut = "pub", set = "pub")]
49pub(super) core: MultiPhaseCoreManager<F>,
50/// The range lookup manager
51#[getset(get = "pub", get_mut = "pub", set = "pub")]
52pub(super) lookup_manager: [LookupAnyManager<F, 1>; MAX_PHASE],
53/// Configuration parameters for the circuit shape
54pub config_params: BaseCircuitParams,
55/// The assigned instances to expose publicly at the end of circuit synthesis
56pub assigned_instances: Vec<Vec<AssignedValue<F>>>,
57}
5859impl<F: ScalarField> Default for BaseCircuitBuilder<F> {
60/// Quick start default circuit builder which can be used for MockProver, Keygen, and real prover.
61 /// For best performance during real proof generation, we recommend using [BaseCircuitBuilder::prover] instead.
62fn default() -> Self {
63Self::new(false)
64 }
65}
6667impl<F: ScalarField> BaseCircuitBuilder<F> {
68/// Creates a new [BaseCircuitBuilder] with all default managers.
69 /// * `witness_gen_only`:
70 /// * If true, the builder only does witness asignments and does not store constraint information -- this should only be used for the real prover.
71 /// * If false, the builder also imposes constraints (selectors, fixed columns, copy constraints). Primarily used for keygen and mock prover (but can also be used for real prover).
72 ///
73 /// By default, **no** circuit configuration parameters have been set.
74 /// These should be set separately using `use_params`, or `use_k`, `use_lookup_bits`, and `calculate_params`.
75 ///
76 /// Upon construction, there are no public instances (aka all witnesses are private).
77 /// The intended usage is that _before_ calling `synthesize`, witness generation can be done to populate
78 /// assigned instances, which are supplied as `assigned_instances` to this struct.
79 /// The `Circuit` implementation for this struct will then expose these instances and constrain
80 /// them using the Halo2 API.
81pub fn new(witness_gen_only: bool) -> Self {
82let core = MultiPhaseCoreManager::new(witness_gen_only);
83let lookup_manager = [(); MAX_PHASE]
84 .map(|_| LookupAnyManager::new(witness_gen_only, core.copy_manager.clone()));
85Self { core, lookup_manager, config_params: Default::default(), assigned_instances: vec![] }
86 }
8788/// Creates a new [MultiPhaseCoreManager] depending on the stage of circuit building. If the stage is [CircuitBuilderStage::Prover], the [MultiPhaseCoreManager] is used for witness generation only.
89pub fn from_stage(stage: CircuitBuilderStage) -> Self {
90Self::new(stage.witness_gen_only()).unknown(stage == CircuitBuilderStage::Keygen)
91 }
9293/// Creates a new [BaseCircuitBuilder] with a pinned circuit configuration given by `config_params` and `break_points`.
94pub fn prover(
95 config_params: BaseCircuitParams,
96 break_points: MultiPhaseThreadBreakPoints,
97 ) -> Self {
98Self::new(true).use_params(config_params).use_break_points(break_points)
99 }
100101/// Sets the copy manager to the given one in all shared references.
102pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager<F>) {
103for lm in &mut self.lookup_manager {
104 lm.set_copy_manager(copy_manager.clone());
105 }
106self.core.set_copy_manager(copy_manager);
107 }
108109/// Returns `self` with a given copy manager
110pub fn use_copy_manager(mut self, copy_manager: SharedCopyConstraintManager<F>) -> Self {
111self.set_copy_manager(copy_manager);
112self
113}
114115/// Deep clone of `self`, where the underlying object of shared references in [SharedCopyConstraintManager] and [LookupAnyManager] are cloned.
116pub fn deep_clone(&self) -> Self {
117let cm: CopyConstraintManager<F> = self.core.copy_manager.lock().unwrap().clone();
118let cm_ref = Arc::new(Mutex::new(cm));
119let mut clone = self.clone().use_copy_manager(cm_ref.clone());
120for lm in &mut clone.lookup_manager {
121*lm = lm.deep_clone(cm_ref.clone());
122 }
123 clone
124 }
125126/// The log_2 size of the lookup table, if using.
127pub fn lookup_bits(&self) -> Option<usize> {
128self.config_params.lookup_bits
129 }
130131/// Set lookup bits
132pub fn set_lookup_bits(&mut self, lookup_bits: usize) {
133self.config_params.lookup_bits = Some(lookup_bits);
134 }
135136/// Returns new with lookup bits
137pub fn use_lookup_bits(mut self, lookup_bits: usize) -> Self {
138self.set_lookup_bits(lookup_bits);
139self
140}
141142/// Sets new `k` = log2 of domain
143pub fn set_k(&mut self, k: usize) {
144self.config_params.k = k;
145 }
146147/// Returns new with `k` set
148pub fn use_k(mut self, k: usize) -> Self {
149self.set_k(k);
150self
151}
152153/// Set the number of instance columns. This resizes `self.assigned_instances`.
154pub fn set_instance_columns(&mut self, num_instance_columns: usize) {
155self.config_params.num_instance_columns = num_instance_columns;
156while self.assigned_instances.len() < num_instance_columns {
157self.assigned_instances.push(vec![]);
158 }
159assert_eq!(self.assigned_instances.len(), num_instance_columns);
160 }
161162/// Returns new with `self.assigned_instances` resized to specified number of instance columns.
163pub fn use_instance_columns(mut self, num_instance_columns: usize) -> Self {
164self.set_instance_columns(num_instance_columns);
165self
166}
167168/// Set config params
169pub fn set_params(&mut self, params: BaseCircuitParams) {
170self.set_instance_columns(params.num_instance_columns);
171self.config_params = params;
172 }
173174/// Returns new with config params
175pub fn use_params(mut self, params: BaseCircuitParams) -> Self {
176self.set_params(params);
177self
178}
179180/// The break points of the circuit.
181pub fn break_points(&self) -> MultiPhaseThreadBreakPoints {
182self.core
183 .phase_manager
184 .iter()
185 .map(|pm| pm.break_points.borrow().as_ref().expect("break points not set").clone())
186 .collect()
187 }
188189/// Sets the break points of the circuit.
190pub fn set_break_points(&mut self, break_points: MultiPhaseThreadBreakPoints) {
191if break_points.is_empty() {
192return;
193 }
194self.core.touch(break_points.len() - 1);
195for (pm, bp) in self.core.phase_manager.iter().zip_eq(break_points) {
196*pm.break_points.borrow_mut() = Some(bp);
197 }
198 }
199200/// Returns new with break points
201pub fn use_break_points(mut self, break_points: MultiPhaseThreadBreakPoints) -> Self {
202self.set_break_points(break_points);
203self
204}
205206/// Returns if the circuit is only used for witness generation.
207pub fn witness_gen_only(&self) -> bool {
208self.core.witness_gen_only()
209 }
210211/// Creates a new [MultiPhaseCoreManager] with `use_unknown` flag set.
212 /// * `use_unknown`: If true, during key generation witness `Value`s are replaced with `Value::unknown()` for safety.
213pub fn unknown(mut self, use_unknown: bool) -> Self {
214self.core = self.core.unknown(use_unknown);
215self
216}
217218/// Clears state and copies, effectively resetting the circuit builder.
219pub fn clear(&mut self) {
220self.core.clear();
221for lm in &mut self.lookup_manager {
222 lm.clear();
223 }
224self.assigned_instances.iter_mut().for_each(|c| c.clear());
225 }
226227/// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists.
228 /// * `phase`: The challenge phase (as an index) of the gate thread.
229pub fn main(&mut self, phase: usize) -> &mut Context<F> {
230self.core.main(phase)
231 }
232233/// Returns [SinglePhaseCoreManager] with the virtual region with all core threads in the given phase.
234pub fn pool(&mut self, phase: usize) -> &mut SinglePhaseCoreManager<F> {
235self.core.phase_manager.get_mut(phase).unwrap()
236 }
237238/// Spawns a new thread for a new given `phase`. Returns a mutable reference to the [Context] of the new thread.
239 /// * `phase`: The phase (index) of the gate thread.
240pub fn new_thread(&mut self, phase: usize) -> &mut Context<F> {
241self.core.new_thread(phase)
242 }
243244/// Returns some statistics about the virtual region.
245pub fn statistics(&self) -> RangeStatistics {
246let gate = self.core.statistics();
247let total_lookup_advice_per_phase = self.total_lookup_advice_per_phase();
248 RangeStatistics { gate, total_lookup_advice_per_phase }
249 }
250251fn total_lookup_advice_per_phase(&self) -> Vec<usize> {
252self.lookup_manager.iter().map(|lm| lm.total_rows()).collect()
253 }
254255/// Auto-calculates configuration parameters for the circuit and sets them.
256 ///
257 /// * `k`: The number of in the circuit (i.e. numeber of rows = 2<sup>k</sup>)
258 /// * `minimum_rows`: The minimum number of rows in the circuit that cannot be used for witness assignments and contain random `blinding factors` to ensure zk property, defaults to 0.
259 /// * `lookup_bits`: The fixed lookup table will consist of [0, 2<sup>lookup_bits</sup>)
260pub fn calculate_params(&mut self, minimum_rows: Option<usize>) -> BaseCircuitParams {
261let k = self.config_params.k;
262let ni = self.config_params.num_instance_columns;
263assert_ne!(k, 0, "k must be set");
264let max_rows = (1 << k) - minimum_rows.unwrap_or(0);
265let gate_params = self.core.calculate_params(k, minimum_rows);
266let total_lookup_advice_per_phase = self.total_lookup_advice_per_phase();
267let num_lookup_advice_per_phase = total_lookup_advice_per_phase
268 .iter()
269 .map(|count| count.div_ceil(max_rows))
270 .collect::<Vec<_>>();
271272let params = BaseCircuitParams {
273 k: gate_params.k,
274 num_advice_per_phase: gate_params.num_advice_per_phase,
275 num_fixed: gate_params.num_fixed,
276 num_lookup_advice_per_phase,
277 lookup_bits: self.lookup_bits(),
278 num_instance_columns: ni,
279 };
280self.config_params = params.clone();
281#[cfg(feature = "display")]
282{
283println!("Total range check advice cells to lookup per phase: {total_lookup_advice_per_phase:?}");
284log::info!("Auto-calculated config params:\n {params:#?}");
285 }
286 params
287 }
288289/// Copies `assigned_instances` to the instance columns. Should only be called at the very end of
290 /// `synthesize` after virtual `assigned_instances` have been assigned to physical circuit.
291pub fn assign_instances(
292&self,
293 instance_columns: &[Column<Instance>],
294mut layouter: impl Layouter<F>,
295 ) {
296if !self.core.witness_gen_only() {
297// expose public instances
298for (instances, instance_col) in self.assigned_instances.iter().zip_eq(instance_columns)
299 {
300for (i, instance) in instances.iter().enumerate() {
301let cell = instance.cell.unwrap();
302let copy_manager = self.core.copy_manager.lock().unwrap();
303let cell =
304 copy_manager.assigned_advices.get(&cell).expect("instance not assigned");
305 layouter.constrain_instance(*cell, *instance_col, i);
306 }
307 }
308 }
309 }
310311/// Creates a new [RangeChip] sharing the same [LookupAnyManager]s as `self`.
312pub fn range_chip(&self) -> RangeChip<F> {
313 RangeChip::new(
314self.config_params.lookup_bits.expect("lookup bits not set"),
315self.lookup_manager.clone(),
316 )
317 }
318319/// Copies the queued cells to be range looked up in phase `phase` to special advice lookup columns
320 /// using [LookupAnyManager].
321 ///
322 /// ## Special case
323 /// Just for [RangeConfig], we have special handling for the case where there is a single (physical)
324 /// advice column in [super::FlexGateConfig]. In this case, `RangeConfig` does not create extra lookup advice columns,
325 /// the single advice column has lookup enabled, and there is a selector to toggle when lookup should
326 /// be turned on.
327pub fn assign_lookups_in_phase(
328&self,
329 config: &RangeConfig<F>,
330 region: &mut Region<F>,
331 phase: usize,
332 ) {
333let lookup_manager = self.lookup_manager.get(phase).expect("too many phases");
334if lookup_manager.total_rows() == 0 {
335return;
336 }
337if let Some(q_lookup) = config.q_lookup.get(phase).and_then(|q| *q) {
338// if q_lookup is Some, that means there should be a single advice column and it has lookup enabled
339assert_eq!(config.gate.basic_gates[phase].len(), 1);
340if !self.witness_gen_only() {
341let cells_to_lookup = lookup_manager.cells_to_lookup.lock().unwrap();
342for advice in cells_to_lookup.iter().flat_map(|(_, advices)| advices) {
343let cell = advice[0].cell.as_ref().unwrap();
344let copy_manager = self.core.copy_manager.lock().unwrap();
345let acell = copy_manager.assigned_advices[cell];
346assert_eq!(
347 acell.column,
348 config.gate.basic_gates[phase][0].value.into(),
349"lookup column does not match"
350);
351 q_lookup.enable(region, acell.row_offset).unwrap();
352 }
353 }
354 } else {
355let lookup_cols = config
356 .lookup_advice
357 .get(phase)
358 .expect("No special lookup advice columns")
359 .iter()
360 .map(|c| [*c])
361 .collect_vec();
362 lookup_manager.assign_raw(&lookup_cols, region);
363 }
364let _ = lookup_manager.assigned.set(());
365 }
366}
367368/// Basic statistics
369pub struct RangeStatistics {
370/// Number of advice cells for the basic gate and total constants used
371pub gate: GateStatistics,
372/// Total special advice cells that need to be looked up, per phase
373pub total_lookup_advice_per_phase: Vec<usize>,
374}
375376impl<F: ScalarField> AsRef<BaseCircuitBuilder<F>> for BaseCircuitBuilder<F> {
377fn as_ref(&self) -> &BaseCircuitBuilder<F> {
378self
379}
380}
381382impl<F: ScalarField> AsMut<BaseCircuitBuilder<F>> for BaseCircuitBuilder<F> {
383fn as_mut(&mut self) -> &mut BaseCircuitBuilder<F> {
384self
385}
386}