halo2_base/gates/circuit/
mod.rs

1use serde::{Deserialize, Serialize};
2
3use crate::utils::ScalarField;
4use crate::{
5    halo2_proofs::{
6        circuit::{Layouter, SimpleFloorPlanner},
7        plonk::{Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector},
8    },
9    virtual_region::manager::VirtualRegionManager,
10};
11
12use self::builder::BaseCircuitBuilder;
13
14use super::flex_gate::{FlexGateConfig, FlexGateConfigParams};
15use super::range::RangeConfig;
16
17/// Module that helps auto-build circuits
18pub mod builder;
19
20/// A struct defining the configuration parameters for a halo2-base circuit
21/// - this is used to configure [BaseConfig].
22#[derive(Clone, Default, Debug, Hash, Serialize, Deserialize)]
23pub struct BaseCircuitParams {
24    // Keeping FlexGateConfigParams expanded for backwards compatibility
25    /// Specifies the number of rows in the circuit to be 2<sup>k</sup>
26    pub k: usize,
27    /// The number of advice columns per phase
28    pub num_advice_per_phase: Vec<usize>,
29    /// The number of fixed columns
30    pub num_fixed: usize,
31    /// The number of bits that can be ranged checked using a special lookup table with values [0, 2<sup>lookup_bits</sup>), if using.
32    /// The number of special advice columns that have range lookup enabled per phase
33    pub num_lookup_advice_per_phase: Vec<usize>,
34    /// This is `None` if no lookup table is used.
35    pub lookup_bits: Option<usize>,
36    /// Number of public instance columns
37    #[serde(default)]
38    pub num_instance_columns: usize,
39}
40
41impl BaseCircuitParams {
42    fn gate_params(&self) -> FlexGateConfigParams {
43        FlexGateConfigParams {
44            k: self.k,
45            num_advice_per_phase: self.num_advice_per_phase.clone(),
46            num_fixed: self.num_fixed,
47        }
48    }
49}
50
51/// Configuration with [`BaseConfig`] with `NI` public instance columns.
52#[derive(Clone, Debug)]
53pub struct BaseConfig<F: ScalarField> {
54    /// The underlying private gate/range configuration
55    pub base: MaybeRangeConfig<F>,
56    /// The public instance column
57    pub instance: Vec<Column<Instance>>,
58}
59
60/// Smart Halo2 circuit config that has different variants depending on whether you need range checks or not.
61/// The difference is that to enable range checks, the Halo2 config needs to add a lookup table.
62#[derive(Clone, Debug)]
63pub enum MaybeRangeConfig<F: ScalarField> {
64    /// Config for a circuit that does not use range checks
65    WithoutRange(FlexGateConfig<F>),
66    /// Config for a circuit that does use range checks
67    WithRange(RangeConfig<F>),
68}
69
70impl<F: ScalarField> BaseConfig<F> {
71    /// Generates a new `BaseConfig` depending on `params`.
72    /// - It will generate a `RangeConfig` is `params` has `lookup_bits` not None **and** `num_lookup_advice_per_phase` are not all empty or zero (i.e., if `params` indicates that the circuit actually requires a lookup table).
73    /// - Otherwise it will generate a `FlexGateConfig`.
74    pub fn configure(meta: &mut ConstraintSystem<F>, params: BaseCircuitParams) -> Self {
75        let total_lookup_advice_cols = params.num_lookup_advice_per_phase.iter().sum::<usize>();
76        let base = if params.lookup_bits.is_some() && total_lookup_advice_cols != 0 {
77            // We only add a lookup table if lookup bits is not None
78            MaybeRangeConfig::WithRange(RangeConfig::configure(
79                meta,
80                params.gate_params(),
81                &params.num_lookup_advice_per_phase,
82                params.lookup_bits.unwrap(),
83            ))
84        } else {
85            MaybeRangeConfig::WithoutRange(FlexGateConfig::configure(meta, params.gate_params()))
86        };
87        let instance = (0..params.num_instance_columns)
88            .map(|_| {
89                let inst = meta.instance_column();
90                meta.enable_equality(inst);
91                inst
92            })
93            .collect();
94        Self { base, instance }
95    }
96
97    /// Returns the inner [`FlexGateConfig`]
98    pub fn gate(&self) -> &FlexGateConfig<F> {
99        match &self.base {
100            MaybeRangeConfig::WithoutRange(config) => config,
101            MaybeRangeConfig::WithRange(config) => &config.gate,
102        }
103    }
104
105    /// Returns the fixed columns for constants
106    pub fn constants(&self) -> &Vec<Column<Fixed>> {
107        match &self.base {
108            MaybeRangeConfig::WithoutRange(config) => &config.constants,
109            MaybeRangeConfig::WithRange(config) => &config.gate.constants,
110        }
111    }
112
113    /// Returns a slice of the selector column to enable lookup -- this is only in the situation where there is a single advice column of any kind -- per phase
114    /// Returns empty slice if there are no lookups enabled.
115    pub fn q_lookup(&self) -> &[Option<Selector>] {
116        match &self.base {
117            MaybeRangeConfig::WithoutRange(_) => &[],
118            MaybeRangeConfig::WithRange(config) => &config.q_lookup,
119        }
120    }
121
122    /// Updates the number of usable rows in the circuit. Used if you mutate [ConstraintSystem] after `BaseConfig::configure` is called.
123    pub fn set_usable_rows(&mut self, usable_rows: usize) {
124        match &mut self.base {
125            MaybeRangeConfig::WithoutRange(config) => config.max_rows = usable_rows,
126            MaybeRangeConfig::WithRange(config) => config.gate.max_rows = usable_rows,
127        }
128    }
129
130    /// Initialization of config at very beginning of `synthesize`.
131    /// Loads fixed lookup table, if using.
132    pub fn initialize(&self, layouter: &mut impl Layouter<F>) {
133        // only load lookup table if we are actually doing lookups
134        if let MaybeRangeConfig::WithRange(config) = &self.base {
135            config.load_lookup_table(layouter).expect("load lookup table should not fail");
136        }
137    }
138}
139
140impl<F: ScalarField> Circuit<F> for BaseCircuitBuilder<F> {
141    type Config = BaseConfig<F>;
142    type FloorPlanner = SimpleFloorPlanner;
143    type Params = BaseCircuitParams;
144
145    fn params(&self) -> Self::Params {
146        self.config_params.clone()
147    }
148
149    /// Creates a new instance of the [BaseCircuitBuilder] without witnesses by setting the witness_gen_only flag to false
150    fn without_witnesses(&self) -> Self {
151        unimplemented!()
152    }
153
154    /// Configures a new circuit using [`BaseCircuitParams`]
155    fn configure_with_params(meta: &mut ConstraintSystem<F>, params: Self::Params) -> Self::Config {
156        BaseConfig::configure(meta, params)
157    }
158
159    fn configure(_: &mut ConstraintSystem<F>) -> Self::Config {
160        unreachable!("You must use configure_with_params");
161    }
162
163    /// Performs the actual computation on the circuit (e.g., witness generation), populating the lookup table and filling in all the advice values for a particular proof.
164    fn synthesize(
165        &self,
166        config: Self::Config,
167        mut layouter: impl Layouter<F>,
168    ) -> Result<(), Error> {
169        // only load lookup table if we are actually doing lookups
170        if let MaybeRangeConfig::WithRange(config) = &config.base {
171            config.load_lookup_table(&mut layouter).expect("load lookup table should not fail");
172        }
173        // Only FirstPhase (phase 0)
174        layouter
175            .assign_region(
176                || "BaseCircuitBuilder generated circuit",
177                |mut region| {
178                    let usable_rows = config.gate().max_rows;
179                    self.core.phase_manager[0].assign_raw(
180                        &(config.gate().basic_gates[0].clone(), usable_rows),
181                        &mut region,
182                    );
183                    // Only assign cells to lookup if we're sure we're doing range lookups
184                    if let MaybeRangeConfig::WithRange(config) = &config.base {
185                        self.assign_lookups_in_phase(config, &mut region, 0);
186                    }
187                    // Impose equality constraints
188                    if !self.core.witness_gen_only() {
189                        self.core.copy_manager.assign_raw(config.constants(), &mut region);
190                    }
191                    Ok(())
192                },
193            )
194            .unwrap();
195
196        self.assign_instances(&config.instance, layouter.namespace(|| "expose"));
197        Ok(())
198    }
199}
200
201/// Defines stage of circuit building.
202#[derive(Clone, Copy, Debug, PartialEq, Eq)]
203pub enum CircuitBuilderStage {
204    /// Keygen phase
205    Keygen,
206    /// Prover Circuit
207    Prover,
208    /// Mock Circuit
209    Mock,
210}
211
212impl CircuitBuilderStage {
213    /// Returns true if the circuit is used for witness generation only.
214    pub fn witness_gen_only(&self) -> bool {
215        matches!(self, CircuitBuilderStage::Prover)
216    }
217}
218
219impl<F: ScalarField> AsRef<BaseConfig<F>> for BaseConfig<F> {
220    fn as_ref(&self) -> &BaseConfig<F> {
221        self
222    }
223}
224
225impl<F: ScalarField> AsMut<BaseConfig<F>> for BaseConfig<F> {
226    fn as_mut(&mut self) -> &mut BaseConfig<F> {
227        self
228    }
229}