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