Skip to main content

kimchi/circuits/
constraints.rs

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