Skip to main content

kimchi/circuits/
constraints.rs

1//! This module implements Plonk circuit constraint primitive.
2use super::lookup::runtime_tables::RuntimeTableCfg;
3use crate::{
4    circuits::{
5        domain_constant_evaluation::DomainConstantEvaluations,
6        domains::EvaluationDomains,
7        gate::{CircuitGate, GateType},
8        lookup::{
9            index::{LookupConstraintSystem, LookupError},
10            lookups::{LookupFeatures, LookupPatterns},
11            tables::{GateLookupTables, LookupTable},
12        },
13        polynomial::{WitnessEvals, WitnessOverDomains, WitnessShifts},
14        polynomials::permutation::Shifts,
15        wires::*,
16    },
17    curve::KimchiCurve,
18    error::{DomainCreationError, SetupError},
19    o1_utils::lazy_cache::LazyCache,
20    prover_index::ProverIndex,
21};
22use ark_ff::{PrimeField, Zero};
23use ark_poly::{
24    univariate::DensePolynomial as DP, EvaluationDomain, Evaluations as E,
25    Radix2EvaluationDomain as D,
26};
27use core::{array, default::Default};
28use o1_utils::ExtendedEvaluations;
29use poly_commitment::SRS;
30use rayon::prelude::*;
31use serde::{de::DeserializeOwned, Deserialize, Serialize};
32use serde_with::serde_as;
33use std::sync::Arc;
34
35//
36// ConstraintSystem
37//
38
39/// Flags indicating which optional gates are used in a circuit.
40///
41/// Kimchi circuits can use a variety of optional gates and lookup patterns.
42/// Rather than always including constraints for every possible gate type,
43/// these flags track which gates are actually present. This allows the prover
44/// and verifier to only compute and check relevant constraints.
45///
46/// Flags are typically computed automatically via [`Self::from_gates`], which
47/// scans the circuit gates and enables the corresponding flags.
48///
49/// When a flag is disabled, the following optimizations apply:
50///
51/// - The gate's selector polynomial is not computed
52/// - The gate's constraints are excluded from linearization
53/// - Associated lookup tables are not included
54#[cfg_attr(
55    feature = "ocaml_types",
56    derive(ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)
57)]
58#[cfg_attr(feature = "wasm_types", wasm_bindgen::prelude::wasm_bindgen)]
59#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
60pub struct FeatureFlags {
61    /// Enables [`GateType::RangeCheck0`], which partially constrains one
62    /// 88-bit value. Can also perform a standalone 64-bit range check by
63    /// constraining columns 1-2 to zero (removing the two highest 12-bit
64    /// limbs: 88 - 24 = 64 bits).
65    /// See [`crate::circuits::polynomials::range_check`] for details.
66    pub range_check0: bool,
67    /// Enables [`GateType::RangeCheck1`], which fully constrains the third
68    /// value in the multi-range-check gadget and triggers deferred lookups.
69    /// See [`crate::circuits::polynomials::range_check`] for details.
70    pub range_check1: bool,
71    /// Enables [`GateType::ForeignFieldAdd`] for addition over non-native fields.
72    pub foreign_field_add: bool,
73    /// Enables [`GateType::ForeignFieldMul`] for multiplication over non-native fields.
74    pub foreign_field_mul: bool,
75    /// Enables [`GateType::Xor16`] for 16-bit XOR operations.
76    pub xor: bool,
77    /// Enables [`GateType::Rot64`] for 64-bit rotation operations.
78    pub rot: bool,
79    /// Lookup feature configuration.
80    /// See [`LookupFeatures`] for details.
81    pub lookup_features: LookupFeatures,
82}
83
84impl Default for FeatureFlags {
85    /// Returns an instance with all features disabled.
86    fn default() -> FeatureFlags {
87        FeatureFlags {
88            range_check0: false,
89            range_check1: false,
90            lookup_features: LookupFeatures {
91                patterns: LookupPatterns {
92                    xor: false,
93                    lookup: false,
94                    range_check: false,
95                    foreign_field_mul: false,
96                },
97                joint_lookup_used: false,
98                uses_runtime_tables: false,
99            },
100            foreign_field_add: false,
101            foreign_field_mul: false,
102            xor: false,
103            rot: false,
104        }
105    }
106}
107
108/// The polynomials representing evaluated columns, in coefficient form.
109#[serde_as]
110#[derive(Clone, Serialize, Deserialize, Debug)]
111pub struct EvaluatedColumnCoefficients<F: PrimeField> {
112    /// permutation coefficients
113    #[serde_as(as = "[o1_utils::serialization::SerdeAs; PERMUTS]")]
114    pub permutation_coefficients: [DP<F>; PERMUTS],
115
116    /// gate coefficients
117    #[serde_as(as = "[o1_utils::serialization::SerdeAs; COLUMNS]")]
118    pub coefficients: [DP<F>; COLUMNS],
119
120    /// generic gate selector
121    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
122    pub generic_selector: DP<F>,
123
124    /// poseidon gate selector
125    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
126    pub poseidon_selector: DP<F>,
127}
128
129/// The polynomials representing columns, in evaluation form.
130/// The evaluations are expanded to the domain size required for their constraints.
131#[serde_as]
132#[derive(Clone, Serialize, Deserialize, Debug)]
133pub struct ColumnEvaluations<F: PrimeField> {
134    /// permutation coefficients over domain d8
135    #[serde_as(as = "[o1_utils::serialization::SerdeAs; PERMUTS]")]
136    pub permutation_coefficients8: [E<F, D<F>>; PERMUTS],
137
138    /// coefficients over domain d8
139    #[serde_as(as = "[o1_utils::serialization::SerdeAs; COLUMNS]")]
140    pub coefficients8: [E<F, D<F>>; COLUMNS],
141
142    /// generic selector over domain d4
143    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
144    pub generic_selector4: E<F, D<F>>,
145
146    /// poseidon selector over domain d8
147    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
148    pub poseidon_selector8: E<F, D<F>>,
149
150    /// EC point addition selector over domain d4
151    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
152    pub complete_add_selector4: E<F, D<F>>,
153
154    /// scalar multiplication selector over domain d8
155    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
156    pub mul_selector8: E<F, D<F>>,
157
158    /// endoscalar multiplication selector over domain d8
159    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
160    pub emul_selector8: E<F, D<F>>,
161
162    /// EC point addition selector over domain d8
163    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
164    pub endomul_scalar_selector8: E<F, D<F>>,
165
166    /// RangeCheck0 gate selector over domain d8
167    #[serde_as(as = "Option<o1_utils::serialization::SerdeAs>")]
168    pub range_check0_selector8: Option<E<F, D<F>>>,
169
170    /// RangeCheck1 gate selector over domain d8
171    #[serde_as(as = "Option<o1_utils::serialization::SerdeAs>")]
172    pub range_check1_selector8: Option<E<F, D<F>>>,
173
174    /// Foreign field addition gate selector over domain d8
175    #[serde_as(as = "Option<o1_utils::serialization::SerdeAs>")]
176    pub foreign_field_add_selector8: Option<E<F, D<F>>>,
177
178    /// Foreign field multiplication gate selector over domain d8
179    #[serde_as(as = "Option<o1_utils::serialization::SerdeAs>")]
180    pub foreign_field_mul_selector8: Option<E<F, D<F>>>,
181
182    /// Xor gate selector over domain d8
183    #[serde_as(as = "Option<o1_utils::serialization::SerdeAs>")]
184    pub xor_selector8: Option<E<F, D<F>>>,
185
186    /// Rot gate selector over domain d8
187    #[serde_as(as = "Option<o1_utils::serialization::SerdeAs>")]
188    pub rot_selector8: Option<E<F, D<F>>>,
189}
190
191#[serde_as]
192#[derive(Clone, Serialize, Debug)]
193pub struct ConstraintSystem<F: PrimeField> {
194    // Basics
195    // ------
196    /// number of public inputs
197    pub public: usize,
198    /// number of previous evaluation challenges, for recursive proving
199    pub prev_challenges: usize,
200    /// evaluation domains
201    #[serde(bound = "EvaluationDomains<F>: Serialize + DeserializeOwned")]
202    pub domain: EvaluationDomains<F>,
203    /// circuit gates
204    #[serde(bound = "CircuitGate<F>: Serialize + DeserializeOwned")]
205    pub gates: Arc<Vec<CircuitGate<F>>>,
206
207    pub zk_rows: u64,
208
209    /// flags for optional features
210    pub feature_flags: FeatureFlags,
211
212    /// SID polynomial
213    #[serde_as(as = "Vec<o1_utils::serialization::SerdeAs>")]
214    pub sid: Vec<F>,
215
216    /// wire coordinate shifts
217    #[serde_as(as = "[o1_utils::serialization::SerdeAs; PERMUTS]")]
218    pub shift: [F; PERMUTS],
219    /// coefficient for the group endomorphism
220    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
221    pub endo: F,
222    /// lookup constraint system
223    #[serde(bound = "LookupConstraintSystem<F>: Serialize + DeserializeOwned")]
224    pub lookup_constraint_system: Arc<LookupConstraintSystemCache<F>>,
225    /// precomputes
226    #[serde(skip)]
227    precomputations: Arc<LazyCache<Arc<DomainConstantEvaluations<F>>>>,
228
229    /// Disable gates checks (for testing; only enables with development builds)
230    pub disable_gates_checks: bool,
231}
232
233pub(crate) type LookupConstraintSystemCache<F> =
234    LazyCache<Result<Option<LookupConstraintSystem<F>>, LookupError>>;
235
236impl<'de, F> Deserialize<'de> for ConstraintSystem<F>
237where
238    F: PrimeField,
239    EvaluationDomains<F>: Serialize + DeserializeOwned,
240    CircuitGate<F>: Serialize + DeserializeOwned,
241    LookupConstraintSystem<F>: Serialize + DeserializeOwned,
242{
243    fn deserialize<D>(deserializer: D) -> Result<ConstraintSystem<F>, D::Error>
244    where
245        D: serde::Deserializer<'de>,
246    {
247        #[serde_as]
248        #[derive(Clone, Serialize, Deserialize, Debug)]
249        struct ConstraintSystemSerde<F: PrimeField> {
250            public: usize,
251            prev_challenges: usize,
252            #[serde(bound = "EvaluationDomains<F>: Serialize + DeserializeOwned")]
253            domain: EvaluationDomains<F>,
254            #[serde(bound = "CircuitGate<F>: Serialize + DeserializeOwned")]
255            gates: Arc<Vec<CircuitGate<F>>>,
256            zk_rows: u64,
257            feature_flags: FeatureFlags,
258            #[serde_as(as = "Vec<o1_utils::serialization::SerdeAs>")]
259            sid: Vec<F>,
260            #[serde_as(as = "[o1_utils::serialization::SerdeAs; PERMUTS]")]
261            shift: [F; PERMUTS],
262            #[serde_as(as = "o1_utils::serialization::SerdeAs")]
263            endo: F,
264            #[serde(bound = "LookupConstraintSystem<F>: Serialize + DeserializeOwned")]
265            lookup_constraint_system: Arc<LookupConstraintSystemCache<F>>,
266            disable_gates_checks: bool,
267        }
268
269        // This is to avoid implementing a default value for LazyCache
270        let cs = ConstraintSystemSerde::<F>::deserialize(deserializer)?;
271
272        let precomputations = Arc::new({
273            LazyCache::new(move || {
274                Arc::new(DomainConstantEvaluations::create(cs.domain, cs.zk_rows).unwrap())
275            })
276        });
277
278        Ok(ConstraintSystem {
279            public: cs.public,
280            prev_challenges: cs.prev_challenges,
281            domain: cs.domain,
282            gates: cs.gates,
283            zk_rows: cs.zk_rows,
284            feature_flags: cs.feature_flags,
285            sid: cs.sid,
286            shift: cs.shift,
287            endo: cs.endo,
288            lookup_constraint_system: cs.lookup_constraint_system,
289            disable_gates_checks: cs.disable_gates_checks,
290            precomputations,
291        })
292    }
293}
294
295/// Represents an error found when verifying a witness with a gate
296#[derive(Debug)]
297pub enum GateError {
298    /// Some connected wires have different values
299    DisconnectedWires(Wire, Wire),
300    /// A public gate was incorrectly connected
301    IncorrectPublic(usize),
302    /// A specific gate did not verify correctly
303    Custom { row: usize, err: String },
304}
305
306pub struct Builder<F: PrimeField> {
307    gates: Vec<CircuitGate<F>>,
308    public: usize,
309    prev_challenges: usize,
310    lookup_tables: Vec<LookupTable<F>>,
311    runtime_tables: Option<Vec<RuntimeTableCfg<F>>>,
312    precomputations: Option<Arc<DomainConstantEvaluations<F>>>,
313    disable_gates_checks: bool,
314    max_poly_size: Option<usize>,
315    lazy_mode: bool,
316}
317
318/// Create selector polynomial for a circuit gate
319pub fn selector_polynomial<F: PrimeField>(
320    gate_type: GateType,
321    gates: &[CircuitGate<F>],
322    domain: &EvaluationDomains<F>,
323    target_domain: &D<F>,
324    disable_gates_checks: bool,
325) -> E<F, D<F>> {
326    if cfg!(debug_assertions) && disable_gates_checks {
327        DP::<F>::zero().evaluate_over_domain_by_ref(*target_domain)
328    } else {
329        // Coefficient form
330        let coeff = E::<F, D<F>>::from_vec_and_domain(
331            gates
332                .iter()
333                .map(|gate| {
334                    if gate.typ == gate_type {
335                        F::one()
336                    } else {
337                        F::zero()
338                    }
339                })
340                .collect(),
341            domain.d1,
342        )
343        .interpolate();
344
345        coeff.evaluate_over_domain_by_ref(*target_domain)
346    }
347}
348
349impl<F: PrimeField> ConstraintSystem<F> {
350    /// Initializes the [`ConstraintSystem<F>`] on input `gates` and `fr_sponge_params`.
351    /// Returns a [`Builder<F>`]
352    /// It also defaults to the following values of the builder:
353    /// - `public: 0`
354    /// - `prev_challenges: 0`
355    /// - `lookup_tables: vec![]`,
356    /// - `runtime_tables: None`,
357    /// - `precomputations: None`,
358    /// - `disable_gates_checks: false`,
359    /// - `lazy_mode: false`,
360    ///
361    /// How to use it:
362    /// 1. Create your instance of your builder for the constraint system using `crate(gates, sponge params)`
363    /// 2. Iterativelly invoke any desired number of steps: `public(), lookup(), runtime(), precomputations(), lazy_mode()`
364    /// 3. Finally call the `build()` method and unwrap the `Result` to obtain your `ConstraintSystem`
365    pub fn create(gates: Vec<CircuitGate<F>>) -> Builder<F> {
366        Builder {
367            gates,
368            public: 0,
369            prev_challenges: 0,
370            lookup_tables: vec![],
371            runtime_tables: None,
372            precomputations: None,
373            disable_gates_checks: false,
374            max_poly_size: None,
375            lazy_mode: false,
376        }
377    }
378
379    pub fn precomputations(&self) -> Arc<DomainConstantEvaluations<F>> {
380        self.precomputations.get().clone()
381    }
382
383    /// test helpers
384    pub fn for_testing(gates: Vec<CircuitGate<F>>) -> Self {
385        let public = 0;
386        // not sure if theres a smarter way instead of the double unwrap, but should be fine in the test
387        ConstraintSystem::<F>::create(gates)
388            .public(public)
389            .build()
390            .unwrap()
391    }
392
393    pub fn fp_for_testing(gates: Vec<CircuitGate<F>>) -> Self {
394        Self::for_testing(gates)
395    }
396}
397
398impl<const FULL_ROUNDS: usize, F, G, Srs> ProverIndex<FULL_ROUNDS, G, Srs>
399where
400    F: PrimeField,
401    G: KimchiCurve<FULL_ROUNDS, ScalarField = F>,
402    Srs: SRS<G>,
403{
404    /// This function verifies the consistency of the wire
405    /// assignments (witness) against the constraints
406    ///     witness: wire assignment witness
407    ///     RETURN: verification status
408    pub fn verify(&self, witness: &[Vec<F>; COLUMNS], public: &[F]) -> Result<(), GateError> {
409        // pad the witness
410        let pad = vec![F::zero(); self.cs.domain.d1.size() - witness[0].len()];
411        let witness: [Vec<F>; COLUMNS] = array::from_fn(|i| {
412            let mut w = witness[i].to_vec();
413            w.extend_from_slice(&pad);
414            w
415        });
416
417        // check each rows' wiring
418        for (row, gate) in self.cs.gates.iter().enumerate() {
419            // check if wires are connected
420            for col in 0..PERMUTS {
421                let wire = gate.wires[col];
422
423                if wire.col >= PERMUTS {
424                    return Err(GateError::Custom {
425                        row,
426                        err: format!("a wire can only be connected to the first {PERMUTS} columns"),
427                    });
428                }
429
430                if witness[col][row] != witness[wire.col][wire.row] {
431                    return Err(GateError::DisconnectedWires(
432                        Wire { col, row },
433                        Wire {
434                            col: wire.col,
435                            row: wire.row,
436                        },
437                    ));
438                }
439            }
440
441            // for public gates, only the left wire is toggled
442            if row < self.cs.public && gate.coeffs.first() != Some(&F::one()) {
443                return Err(GateError::IncorrectPublic(row));
444            }
445
446            // check the gate's satisfiability
447            gate.verify(row, &witness, self, public)
448                .map_err(|err| GateError::Custom { row, err })?;
449        }
450
451        // all good!
452        Ok(())
453    }
454}
455
456impl<F: PrimeField> ConstraintSystem<F> {
457    /// evaluate witness polynomials over domains
458    pub fn evaluate(&self, w: &[DP<F>; COLUMNS], z: &DP<F>) -> WitnessOverDomains<F> {
459        // compute shifted witness polynomials and z8, all in parallel
460        let (w8, z8): ([E<F, D<F>>; COLUMNS], _) = {
461            let mut res = w
462                .par_iter()
463                .chain(rayon::iter::once(z))
464                .map(|elem| elem.evaluate_over_domain_by_ref(self.domain.d8))
465                .collect::<Vec<_>>();
466            let z8 = res[COLUMNS].clone();
467            res.truncate(COLUMNS);
468            (res.try_into().unwrap(), z8)
469        };
470
471        let w4: [E<F, D<F>>; COLUMNS] = (0..COLUMNS)
472            .into_par_iter()
473            .map(|i| {
474                E::<F, D<F>>::from_vec_and_domain(
475                    (0..self.domain.d4.size)
476                        .map(|j| w8[i].evals[2 * j as usize])
477                        .collect(),
478                    self.domain.d4,
479                )
480            })
481            .collect::<Vec<_>>()
482            .try_into()
483            .unwrap();
484
485        let z4 = DP::<F>::zero().evaluate_over_domain_by_ref(D::<F>::new(1).unwrap());
486        let z8_shift8 = z8.shift(8);
487
488        let d4_next_w: [_; COLUMNS] = w4
489            .par_iter()
490            .map(|w4_i| w4_i.shift(4))
491            .collect::<Vec<_>>()
492            .try_into()
493            .unwrap();
494
495        let d8_next_w: [_; COLUMNS] = w8
496            .par_iter()
497            .map(|w8_i| w8_i.shift(8))
498            .collect::<Vec<_>>()
499            .try_into()
500            .unwrap();
501
502        WitnessOverDomains {
503            d4: WitnessShifts {
504                next: WitnessEvals {
505                    w: d4_next_w,
506                    // TODO(mimoo): change z to an Option? Or maybe not, we might actually need this dummy evaluation in the aggregated evaluation proof
507                    z: z4.clone(), // dummy evaluation
508                },
509                this: WitnessEvals {
510                    w: w4,
511                    z: z4, // dummy evaluation
512                },
513            },
514            d8: WitnessShifts {
515                next: WitnessEvals {
516                    w: d8_next_w,
517                    z: z8_shift8,
518                },
519                this: WitnessEvals { w: w8, z: z8 },
520            },
521        }
522    }
523
524    pub(crate) fn evaluated_column_coefficients(&self) -> EvaluatedColumnCoefficients<F> {
525        // compute permutation polynomials
526        let shifts = Shifts::new(&self.domain.d1);
527
528        let n = self.domain.d1.size();
529
530        let mut sigmal1: [Vec<F>; PERMUTS] = array::from_fn(|_| vec![F::zero(); n]);
531
532        for (row, gate) in self.gates.iter().enumerate() {
533            for (cell, sigma) in gate.wires.iter().zip(sigmal1.iter_mut()) {
534                sigma[row] = shifts.cell_to_field(cell);
535            }
536        }
537
538        // Zero out the sigmas in the zk rows, to ensure that the permutation aggregation is
539        // quasi-random for those rows.
540        for row in n + 2 - (self.zk_rows as usize)..n - 1 {
541            for sigma in sigmal1.iter_mut() {
542                sigma[row] = F::zero();
543            }
544        }
545
546        let sigmal1: [_; PERMUTS] = {
547            let [s0, s1, s2, s3, s4, s5, s6] = sigmal1;
548            [
549                E::<F, D<F>>::from_vec_and_domain(s0, self.domain.d1),
550                E::<F, D<F>>::from_vec_and_domain(s1, self.domain.d1),
551                E::<F, D<F>>::from_vec_and_domain(s2, self.domain.d1),
552                E::<F, D<F>>::from_vec_and_domain(s3, self.domain.d1),
553                E::<F, D<F>>::from_vec_and_domain(s4, self.domain.d1),
554                E::<F, D<F>>::from_vec_and_domain(s5, self.domain.d1),
555                E::<F, D<F>>::from_vec_and_domain(s6, self.domain.d1),
556            ]
557        };
558
559        let permutation_coefficients: [DP<F>; PERMUTS] =
560            array::from_fn(|i| sigmal1[i].clone().interpolate());
561
562        // poseidon gate
563        let poseidon_selector = E::<F, D<F>>::from_vec_and_domain(
564            self.gates.iter().map(|gate| gate.ps()).collect(),
565            self.domain.d1,
566        )
567        .interpolate();
568
569        // double generic gate
570        let generic_selector = E::<F, D<F>>::from_vec_and_domain(
571            self.gates
572                .iter()
573                .map(|gate| {
574                    if matches!(gate.typ, GateType::Generic) {
575                        F::one()
576                    } else {
577                        F::zero()
578                    }
579                })
580                .collect(),
581            self.domain.d1,
582        )
583        .interpolate();
584
585        // coefficient polynomial
586        let coefficients: [_; COLUMNS] = array::from_fn(|i| {
587            let padded = self
588                .gates
589                .iter()
590                .map(|gate| gate.coeffs.get(i).cloned().unwrap_or_else(F::zero))
591                .collect();
592            let eval = E::from_vec_and_domain(padded, self.domain.d1);
593            eval.interpolate()
594        });
595
596        EvaluatedColumnCoefficients {
597            permutation_coefficients,
598            coefficients,
599            generic_selector,
600            poseidon_selector,
601        }
602    }
603
604    pub(crate) fn column_evaluations(
605        &self,
606        evaluated_column_coefficients: &EvaluatedColumnCoefficients<F>,
607    ) -> ColumnEvaluations<F> {
608        let permutation_coefficients8 = array::from_fn(|i| {
609            evaluated_column_coefficients.permutation_coefficients[i]
610                .evaluate_over_domain_by_ref(self.domain.d8)
611        });
612
613        let poseidon_selector8 = evaluated_column_coefficients
614            .poseidon_selector
615            .evaluate_over_domain_by_ref(self.domain.d8);
616
617        // ECC gates
618        let complete_add_selector4 = selector_polynomial(
619            GateType::CompleteAdd,
620            &self.gates,
621            &self.domain,
622            &self.domain.d4,
623            self.disable_gates_checks,
624        );
625
626        let mul_selector8 = selector_polynomial(
627            GateType::VarBaseMul,
628            &self.gates,
629            &self.domain,
630            &self.domain.d8,
631            self.disable_gates_checks,
632        );
633
634        let emul_selector8 = selector_polynomial(
635            GateType::EndoMul,
636            &self.gates,
637            &self.domain,
638            &self.domain.d8,
639            self.disable_gates_checks,
640        );
641
642        let endomul_scalar_selector8 = selector_polynomial(
643            GateType::EndoMulScalar,
644            &self.gates,
645            &self.domain,
646            &self.domain.d8,
647            self.disable_gates_checks,
648        );
649
650        let generic_selector4 = evaluated_column_coefficients
651            .generic_selector
652            .evaluate_over_domain_by_ref(self.domain.d4);
653
654        // RangeCheck0 constraint selector polynomials
655        let range_check0_selector8 = {
656            if !self.feature_flags.range_check0 {
657                None
658            } else {
659                Some(selector_polynomial(
660                    GateType::RangeCheck0,
661                    &self.gates,
662                    &self.domain,
663                    &self.domain.d8,
664                    self.disable_gates_checks,
665                ))
666            }
667        };
668
669        // RangeCheck1 constraint selector polynomials
670        let range_check1_selector8 = {
671            if !self.feature_flags.range_check1 {
672                None
673            } else {
674                Some(selector_polynomial(
675                    GateType::RangeCheck1,
676                    &self.gates,
677                    &self.domain,
678                    &self.domain.d8,
679                    self.disable_gates_checks,
680                ))
681            }
682        };
683
684        // Foreign field addition constraint selector polynomial
685        let foreign_field_add_selector8 = {
686            if !self.feature_flags.foreign_field_add {
687                None
688            } else {
689                Some(selector_polynomial(
690                    GateType::ForeignFieldAdd,
691                    &self.gates,
692                    &self.domain,
693                    &self.domain.d8,
694                    self.disable_gates_checks,
695                ))
696            }
697        };
698
699        // Foreign field multiplication constraint selector polynomial
700        let foreign_field_mul_selector8 = {
701            if !self.feature_flags.foreign_field_mul {
702                None
703            } else {
704                Some(selector_polynomial(
705                    GateType::ForeignFieldMul,
706                    &self.gates,
707                    &self.domain,
708                    &self.domain.d8,
709                    self.disable_gates_checks,
710                ))
711            }
712        };
713
714        let xor_selector8 = {
715            if !self.feature_flags.xor {
716                None
717            } else {
718                Some(selector_polynomial(
719                    GateType::Xor16,
720                    &self.gates,
721                    &self.domain,
722                    &self.domain.d8,
723                    self.disable_gates_checks,
724                ))
725            }
726        };
727
728        let rot_selector8 = {
729            if !self.feature_flags.rot {
730                None
731            } else {
732                Some(selector_polynomial(
733                    GateType::Rot64,
734                    &self.gates,
735                    &self.domain,
736                    &self.domain.d8,
737                    self.disable_gates_checks,
738                ))
739            }
740        };
741
742        // TODO: This doesn't need to be degree 8 but that would require some changes in expr
743        let coefficients8 = array::from_fn(|i| {
744            evaluated_column_coefficients.coefficients[i]
745                .evaluate_over_domain_by_ref(self.domain.d8)
746        });
747
748        ColumnEvaluations {
749            permutation_coefficients8,
750            coefficients8,
751            generic_selector4,
752            poseidon_selector8,
753            complete_add_selector4,
754            mul_selector8,
755            emul_selector8,
756            endomul_scalar_selector8,
757            range_check0_selector8,
758            range_check1_selector8,
759            foreign_field_add_selector8,
760            foreign_field_mul_selector8,
761            xor_selector8,
762            rot_selector8,
763        }
764    }
765}
766
767/// The default number of chunks in a circuit is one (< 2^16 rows)
768pub const NUM_CHUNKS_BY_DEFAULT: usize = 1;
769
770/// The number of rows required for zero knowledge in circuits with one single chunk
771pub const ZK_ROWS_BY_DEFAULT: u64 = 3;
772
773/// This function computes a strict lower bound in the number of rows required
774/// for zero knowledge in circuits with `num_chunks` chunks. This means that at
775/// least one needs 1 more row than the result of this function to achieve zero
776/// knowledge.
777/// Example:
778///   for 1 chunk, this function returns 2, but at least 3 rows are needed
779/// Note:
780///   the number of zero knowledge rows is usually computed across the codebase
781///   as the formula `(16 * num_chunks + 5) / 7`, which is precisely the formula
782///   in this function plus one.
783pub fn zk_rows_strict_lower_bound(num_chunks: usize) -> usize {
784    (2 * (PERMUTS + 1) * num_chunks - 2) / PERMUTS
785}
786
787impl FeatureFlags {
788    /// Creates feature flags by scanning gates, using pre-computed lookup features.
789    pub fn from_gates_and_lookup_features<F: PrimeField>(
790        gates: &[CircuitGate<F>],
791        lookup_features: LookupFeatures,
792    ) -> FeatureFlags {
793        let mut feature_flags = FeatureFlags {
794            range_check0: false,
795            range_check1: false,
796            lookup_features,
797            foreign_field_add: false,
798            foreign_field_mul: false,
799            xor: false,
800            rot: false,
801        };
802
803        for gate in gates {
804            match gate.typ {
805                GateType::RangeCheck0 => feature_flags.range_check0 = true,
806                GateType::RangeCheck1 => feature_flags.range_check1 = true,
807                GateType::ForeignFieldAdd => feature_flags.foreign_field_add = true,
808                GateType::ForeignFieldMul => feature_flags.foreign_field_mul = true,
809                GateType::Xor16 => feature_flags.xor = true,
810                GateType::Rot64 => feature_flags.rot = true,
811                _ => (),
812            }
813        }
814
815        feature_flags
816    }
817
818    /// Creates feature flags by scanning gates for optional gate types.
819    ///
820    /// This is the primary constructor. It detects both gate-level features
821    /// and lookup features from the circuit gates.
822    pub fn from_gates<F: PrimeField>(
823        gates: &[CircuitGate<F>],
824        uses_runtime_tables: bool,
825    ) -> FeatureFlags {
826        FeatureFlags::from_gates_and_lookup_features(
827            gates,
828            LookupFeatures::from_gates(gates, uses_runtime_tables),
829        )
830    }
831}
832
833impl<F: PrimeField> Builder<F> {
834    /// Set up the number of public inputs.
835    /// If not invoked, it equals `0` by default.
836    pub fn public(mut self, public: usize) -> Self {
837        self.public = public;
838        self
839    }
840
841    /// Set up the number of previous challenges, used for recusive proving.
842    /// If not invoked, it equals `0` by default.
843    pub fn prev_challenges(mut self, prev_challenges: usize) -> Self {
844        self.prev_challenges = prev_challenges;
845        self
846    }
847
848    /// Set up the lookup tables.
849    /// If not invoked, it is `vec![]` by default.
850    ///
851    /// **Warning:** you have to make sure that the IDs of the lookup tables,
852    /// are unique and not colliding with IDs of built-in lookup tables, otherwise
853    /// the error will be raised.
854    ///
855    /// (see [crate::circuits::lookup::tables]).
856    pub fn lookup(mut self, lookup_tables: Vec<LookupTable<F>>) -> Self {
857        self.lookup_tables = lookup_tables;
858        self
859    }
860
861    /// Set up the runtime tables.
862    /// If not invoked, it is `None` by default.
863    ///
864    /// **Warning:** you have to make sure that the IDs of the runtime
865    /// lookup tables, are unique, i.e. not colliding internaly (with other runtime tables),
866    /// otherwise error will be raised.
867    /// (see [crate::circuits::lookup::tables]).
868    pub fn runtime(mut self, runtime_tables: Option<Vec<RuntimeTableCfg<F>>>) -> Self {
869        self.runtime_tables = runtime_tables;
870        self
871    }
872
873    /// Set up the shared precomputations.
874    /// If not invoked, it is `None` by default.
875    pub fn shared_precomputations(
876        mut self,
877        shared_precomputations: Arc<DomainConstantEvaluations<F>>,
878    ) -> Self {
879        self.precomputations = Some(shared_precomputations);
880        self
881    }
882
883    /// Disable gates checks (for testing; only enables with development builds)
884    pub fn disable_gates_checks(mut self, disable_gates_checks: bool) -> Self {
885        self.disable_gates_checks = disable_gates_checks;
886        self
887    }
888
889    pub fn max_poly_size(mut self, max_poly_size: Option<usize>) -> Self {
890        self.max_poly_size = max_poly_size;
891        self
892    }
893
894    pub fn lazy_mode(mut self, lazy_mode: bool) -> Self {
895        self.lazy_mode = lazy_mode;
896        self
897    }
898
899    /// Build the [ConstraintSystem] from a [Builder].
900    pub fn build(self) -> Result<ConstraintSystem<F>, SetupError> {
901        let mut gates = self.gates;
902        let lookup_tables = self.lookup_tables.clone();
903        let runtime_tables = self.runtime_tables.clone();
904
905        //~ 1. If the circuit is less than 2 gates, abort.
906        // for some reason we need more than 1 gate for the circuit to work, see TODO below
907        assert!(gates.len() > 1);
908
909        let feature_flags = FeatureFlags::from_gates(&gates, runtime_tables.is_some());
910
911        let lookup_domain_size = {
912            // First we sum over the lookup table size
913            let mut has_table_with_id_0 = false;
914            let mut lookup_domain_size: usize = lookup_tables
915                .iter()
916                .map(|LookupTable { id, data }| {
917                    // See below for the reason
918                    if *id == 0_i32 {
919                        has_table_with_id_0 = true
920                    }
921                    if data.is_empty() {
922                        0
923                    } else {
924                        data[0].len()
925                    }
926                })
927                .sum();
928            // After that on the runtime tables
929            if let Some(runtime_tables) = &runtime_tables {
930                // FIXME: Check that a runtime table with ID 0 is enforced to
931                // contain a zero entry row.
932                for runtime_table in runtime_tables.iter() {
933                    lookup_domain_size += runtime_table.len();
934                }
935            }
936            // And we add the built-in tables, depending on the features.
937            let LookupFeatures { patterns, .. } = &feature_flags.lookup_features;
938            let mut gate_lookup_tables = GateLookupTables {
939                xor: false,
940                range_check: false,
941            };
942            for pattern in patterns.into_iter() {
943                if let Some(gate_table) = pattern.table() {
944                    gate_lookup_tables[gate_table] = true
945                }
946            }
947            for gate_table in gate_lookup_tables.into_iter() {
948                lookup_domain_size += gate_table.table_size();
949            }
950
951            // A dummy zero entry will be added if there is no table with ID
952            // zero. Therefore we must count this in the size.
953            if has_table_with_id_0 {
954                lookup_domain_size
955            } else {
956                lookup_domain_size + 1
957            }
958        };
959
960        //~ 1. Compute the number of zero-knowledge rows (`zk_rows`) that will be required to
961        //~    achieve zero-knowledge. The following constraints apply to `zk_rows`:
962        //~    * The number of chunks `c` results in an evaluation at `zeta` and `zeta * omega` in
963        //~      each column for `2*c` evaluations per column, so `zk_rows >= 2*c + 1`.
964        //~    * The permutation argument interacts with the `c` chunks in parallel, so it is
965        //~      possible to cross-correlate between them to compromise zero knowledge. We know
966        //~      that there is some `c >= 1` such that `zk_rows = 2*c + k` from the above. Thus,
967        //~      attempting to find the evaluation at a new point, we find that:
968        //~      * the evaluation of every witness column in the permutation contains `k` unknowns;
969        //~      * the evaluations of the permutation argument aggregation has `k-1` unknowns;
970        //~      * the permutation argument applies on all but `zk_rows - 3` rows;
971        //~      * and thus we form the equation `zk_rows - 3 < 7 * k + (k - 1)` to ensure that we
972        //~        can construct fewer equations than we have unknowns.
973        //~
974        //~    This simplifies to `k > (2 * c - 2) / 7`, giving `zk_rows > (16 * c - 2) / 7`.
975        //~    We can derive `c` from the `max_poly_size` supported by the URS, and thus we find
976        //~    `zk_rows` and `domain_size` satisfying the fixpoint
977        //~
978        //~    ```text
979        //~    zk_rows = (16 * (domain_size / max_poly_size) + 5) / 7
980        //~    domain_size = circuit_size + zk_rows
981        //~    ```
982        //~
983        let (zk_rows, domain_size_lower_bound) = {
984            // We add 1 to the lookup domain size because there is one element
985            // used to close the permutation argument (the polynomial Z is of
986            // degree n + 1 where n is the order of the subgroup H).
987            let circuit_lower_bound = core::cmp::max(gates.len(), lookup_domain_size + 1);
988            let get_domain_size_lower_bound = |zk_rows: u64| circuit_lower_bound + zk_rows as usize;
989
990            let mut zk_rows = 3;
991            let mut domain_size_lower_bound = get_domain_size_lower_bound(zk_rows);
992            if let Some(max_poly_size) = self.max_poly_size {
993                // Iterate to find a fixed-point where zk_rows is sufficient for the number of
994                // chunks that we use, and also does not cause us to overflow the domain size.
995                // NB: We use iteration here rather than hard-coding an assumption about
996                // `compute_size_of_domain`s internals. In practice, this will never be executed
997                // more than once.
998                while {
999                    let domain_size = D::<F>::compute_size_of_domain(domain_size_lower_bound)
1000                        .ok_or(SetupError::DomainCreation(
1001                            DomainCreationError::DomainSizeFailed(domain_size_lower_bound),
1002                        ))?;
1003                    let num_chunks = if domain_size < max_poly_size {
1004                        1
1005                    } else {
1006                        domain_size / max_poly_size
1007                    };
1008                    zk_rows = (zk_rows_strict_lower_bound(num_chunks) + 1) as u64;
1009                    domain_size_lower_bound = get_domain_size_lower_bound(zk_rows);
1010                    domain_size < domain_size_lower_bound
1011                } {}
1012            }
1013            (zk_rows, domain_size_lower_bound)
1014        };
1015
1016        //~ 1. Create a domain for the circuit. That is,
1017        //~    compute the smallest subgroup of the field that
1018        //~    has order greater or equal to `n + zk_rows` elements.
1019        let domain = EvaluationDomains::<F>::create(domain_size_lower_bound)
1020            .map_err(SetupError::DomainCreation)?;
1021
1022        assert!(domain.d1.size > zk_rows);
1023
1024        //~ 1. Pad the circuit: add zero gates to reach the domain size.
1025        let d1_size = domain.d1.size();
1026        let mut padding = (gates.len()..d1_size)
1027            .map(|i| {
1028                CircuitGate::<F>::zero(array::from_fn(|j| Wire {
1029                    col: WIRES[j],
1030                    row: i,
1031                }))
1032            })
1033            .collect();
1034        gates.append(&mut padding);
1035
1036        //~ 1. sample the `PERMUTS` shifts.
1037        let shifts = Shifts::new(&domain.d1);
1038
1039        //
1040        // Lookup
1041        // ------
1042        let gates = Arc::new(gates);
1043        let gates_clone = Arc::clone(&gates);
1044        let lookup_constraint_system = LazyCache::new(move || {
1045            LookupConstraintSystem::create(
1046                &gates_clone,
1047                self.lookup_tables,
1048                self.runtime_tables,
1049                &domain,
1050                zk_rows as usize,
1051            )
1052        });
1053        if !self.lazy_mode {
1054            // Precompute and map setup error
1055            lookup_constraint_system
1056                .try_get_or_err()
1057                .map_err(SetupError::from)?;
1058        }
1059
1060        let sid = shifts.map[0].clone();
1061
1062        // TODO: remove endo as a field
1063        let endo = F::zero();
1064
1065        let precomputations = if !self.lazy_mode {
1066            match self.precomputations {
1067                Some(t) => LazyCache::preinit(t),
1068                None => LazyCache::preinit(Arc::new(
1069                    DomainConstantEvaluations::create(domain, zk_rows).unwrap(),
1070                )),
1071            }
1072        } else {
1073            LazyCache::new(move || {
1074                Arc::new(DomainConstantEvaluations::create(domain, zk_rows).unwrap())
1075            })
1076        };
1077
1078        let constraints = ConstraintSystem {
1079            domain,
1080            public: self.public,
1081            prev_challenges: self.prev_challenges,
1082            sid,
1083            gates,
1084            shift: shifts.shifts,
1085            endo,
1086            zk_rows,
1087            //fr_sponge_params: self.sponge_params,
1088            lookup_constraint_system: Arc::new(lookup_constraint_system),
1089            feature_flags,
1090            precomputations: Arc::new(precomputations),
1091            disable_gates_checks: self.disable_gates_checks,
1092        };
1093
1094        Ok(constraints)
1095    }
1096}