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