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