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, WitnessShifts},
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    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        let w4: [E<F, D<F>>; COLUMNS] = (0..COLUMNS)
502            .into_par_iter()
503            .map(|i| {
504                E::<F, D<F>>::from_vec_and_domain(
505                    (0..self.domain.d4.size)
506                        .map(|j| w8[i].evals[2 * j as usize])
507                        .collect(),
508                    self.domain.d4,
509                )
510            })
511            .collect::<Vec<_>>()
512            .try_into()
513            .unwrap();
514
515        let z4 = DP::<F>::zero().evaluate_over_domain_by_ref(D::<F>::new(1).unwrap());
516        let z8_shift8 = z8.shift(8);
517
518        let d4_next_w: [_; COLUMNS] = w4
519            .par_iter()
520            .map(|w4_i| w4_i.shift(4))
521            .collect::<Vec<_>>()
522            .try_into()
523            .unwrap();
524
525        let d8_next_w: [_; COLUMNS] = w8
526            .par_iter()
527            .map(|w8_i| w8_i.shift(8))
528            .collect::<Vec<_>>()
529            .try_into()
530            .unwrap();
531
532        WitnessOverDomains {
533            d4: WitnessShifts {
534                next: WitnessEvals {
535                    w: d4_next_w,
536                    // TODO(mimoo): change z to an Option? Or maybe not, we might actually need this dummy evaluation in the aggregated evaluation proof
537                    z: z4.clone(), // dummy evaluation
538                },
539                this: WitnessEvals {
540                    w: w4,
541                    z: z4, // dummy evaluation
542                },
543            },
544            d8: WitnessShifts {
545                next: WitnessEvals {
546                    w: d8_next_w,
547                    z: z8_shift8,
548                },
549                this: WitnessEvals { w: w8, z: z8 },
550            },
551        }
552    }
553
554    pub(crate) fn evaluated_column_coefficients(&self) -> EvaluatedColumnCoefficients<F> {
555        // compute permutation polynomials
556        let shifts = Shifts::new(&self.domain.d1);
557
558        let n = self.domain.d1.size();
559
560        let mut sigmal1: [Vec<F>; PERMUTS] = array::from_fn(|_| vec![F::zero(); n]);
561
562        for (row, gate) in self.gates.iter().enumerate() {
563            for (cell, sigma) in gate.wires.iter().zip(sigmal1.iter_mut()) {
564                sigma[row] = shifts.cell_to_field(cell);
565            }
566        }
567
568        // Zero out the sigmas in the zk rows, to ensure that the permutation aggregation is
569        // quasi-random for those rows.
570        for row in n + 2 - (self.zk_rows as usize)..n - 1 {
571            for sigma in sigmal1.iter_mut() {
572                sigma[row] = F::zero();
573            }
574        }
575
576        let sigmal1: [_; PERMUTS] = {
577            let [s0, s1, s2, s3, s4, s5, s6] = sigmal1;
578            [
579                E::<F, D<F>>::from_vec_and_domain(s0, self.domain.d1),
580                E::<F, D<F>>::from_vec_and_domain(s1, self.domain.d1),
581                E::<F, D<F>>::from_vec_and_domain(s2, self.domain.d1),
582                E::<F, D<F>>::from_vec_and_domain(s3, self.domain.d1),
583                E::<F, D<F>>::from_vec_and_domain(s4, self.domain.d1),
584                E::<F, D<F>>::from_vec_and_domain(s5, self.domain.d1),
585                E::<F, D<F>>::from_vec_and_domain(s6, self.domain.d1),
586            ]
587        };
588
589        let permutation_coefficients: [DP<F>; PERMUTS] =
590            array::from_fn(|i| sigmal1[i].clone().interpolate());
591
592        // poseidon gate
593        let poseidon_selector = E::<F, D<F>>::from_vec_and_domain(
594            self.gates.iter().map(|gate| gate.ps()).collect(),
595            self.domain.d1,
596        )
597        .interpolate();
598
599        // double generic gate
600        let generic_selector = E::<F, D<F>>::from_vec_and_domain(
601            self.gates
602                .iter()
603                .map(|gate| {
604                    if matches!(gate.typ, GateType::Generic) {
605                        F::one()
606                    } else {
607                        F::zero()
608                    }
609                })
610                .collect(),
611            self.domain.d1,
612        )
613        .interpolate();
614
615        // coefficient polynomial
616        let coefficients: [_; COLUMNS] = array::from_fn(|i| {
617            let padded = self
618                .gates
619                .iter()
620                .map(|gate| gate.coeffs.get(i).cloned().unwrap_or_else(F::zero))
621                .collect();
622            let eval = E::from_vec_and_domain(padded, self.domain.d1);
623            eval.interpolate()
624        });
625
626        EvaluatedColumnCoefficients {
627            permutation_coefficients,
628            coefficients,
629            generic_selector,
630            poseidon_selector,
631        }
632    }
633
634    pub(crate) fn column_evaluations(
635        &self,
636        evaluated_column_coefficients: &EvaluatedColumnCoefficients<F>,
637    ) -> ColumnEvaluations<F> {
638        let permutation_coefficients8 = array::from_fn(|i| {
639            evaluated_column_coefficients.permutation_coefficients[i]
640                .evaluate_over_domain_by_ref(self.domain.d8)
641        });
642
643        let poseidon_selector8 = evaluated_column_coefficients
644            .poseidon_selector
645            .evaluate_over_domain_by_ref(self.domain.d8);
646
647        // ECC gates
648        let complete_add_selector4 = selector_polynomial(
649            GateType::CompleteAdd,
650            &self.gates,
651            &self.domain,
652            &self.domain.d4,
653            self.disable_gates_checks,
654        );
655
656        let mul_selector8 = selector_polynomial(
657            GateType::VarBaseMul,
658            &self.gates,
659            &self.domain,
660            &self.domain.d8,
661            self.disable_gates_checks,
662        );
663
664        let emul_selector8 = selector_polynomial(
665            GateType::EndoMul,
666            &self.gates,
667            &self.domain,
668            &self.domain.d8,
669            self.disable_gates_checks,
670        );
671
672        let endomul_scalar_selector8 = selector_polynomial(
673            GateType::EndoMulScalar,
674            &self.gates,
675            &self.domain,
676            &self.domain.d8,
677            self.disable_gates_checks,
678        );
679
680        let generic_selector4 = evaluated_column_coefficients
681            .generic_selector
682            .evaluate_over_domain_by_ref(self.domain.d4);
683
684        // RangeCheck0 constraint selector polynomials
685        let range_check0_selector8 = {
686            if !self.feature_flags.range_check0 {
687                None
688            } else {
689                Some(selector_polynomial(
690                    GateType::RangeCheck0,
691                    &self.gates,
692                    &self.domain,
693                    &self.domain.d8,
694                    self.disable_gates_checks,
695                ))
696            }
697        };
698
699        // RangeCheck1 constraint selector polynomials
700        let range_check1_selector8 = {
701            if !self.feature_flags.range_check1 {
702                None
703            } else {
704                Some(selector_polynomial(
705                    GateType::RangeCheck1,
706                    &self.gates,
707                    &self.domain,
708                    &self.domain.d8,
709                    self.disable_gates_checks,
710                ))
711            }
712        };
713
714        // Foreign field addition constraint selector polynomial
715        let foreign_field_add_selector8 = {
716            if !self.feature_flags.foreign_field_add {
717                None
718            } else {
719                Some(selector_polynomial(
720                    GateType::ForeignFieldAdd,
721                    &self.gates,
722                    &self.domain,
723                    &self.domain.d8,
724                    self.disable_gates_checks,
725                ))
726            }
727        };
728
729        // Foreign field multiplication constraint selector polynomial
730        let foreign_field_mul_selector8 = {
731            if !self.feature_flags.foreign_field_mul {
732                None
733            } else {
734                Some(selector_polynomial(
735                    GateType::ForeignFieldMul,
736                    &self.gates,
737                    &self.domain,
738                    &self.domain.d8,
739                    self.disable_gates_checks,
740                ))
741            }
742        };
743
744        let xor_selector8 = {
745            if !self.feature_flags.xor {
746                None
747            } else {
748                Some(selector_polynomial(
749                    GateType::Xor16,
750                    &self.gates,
751                    &self.domain,
752                    &self.domain.d8,
753                    self.disable_gates_checks,
754                ))
755            }
756        };
757
758        let rot_selector8 = {
759            if !self.feature_flags.rot {
760                None
761            } else {
762                Some(selector_polynomial(
763                    GateType::Rot64,
764                    &self.gates,
765                    &self.domain,
766                    &self.domain.d8,
767                    self.disable_gates_checks,
768                ))
769            }
770        };
771
772        // TODO: This doesn't need to be degree 8 but that would require some changes in expr
773        let coefficients8 = array::from_fn(|i| {
774            evaluated_column_coefficients.coefficients[i]
775                .evaluate_over_domain_by_ref(self.domain.d8)
776        });
777
778        ColumnEvaluations {
779            permutation_coefficients8,
780            coefficients8,
781            generic_selector4,
782            poseidon_selector8,
783            complete_add_selector4,
784            mul_selector8,
785            emul_selector8,
786            endomul_scalar_selector8,
787            range_check0_selector8,
788            range_check1_selector8,
789            foreign_field_add_selector8,
790            foreign_field_mul_selector8,
791            xor_selector8,
792            rot_selector8,
793        }
794    }
795}
796
797/// The default number of chunks in a circuit is one (< 2^16 rows)
798pub const NUM_CHUNKS_BY_DEFAULT: usize = 1;
799
800/// The number of rows required for zero knowledge in circuits with one single chunk
801pub const ZK_ROWS_BY_DEFAULT: u64 = 3;
802
803/// This function computes a strict lower bound in the number of rows required
804/// for zero knowledge in circuits with `num_chunks` chunks. This means that at
805/// least one needs 1 more row than the result of this function to achieve zero
806/// knowledge.
807/// Example:
808///   for 1 chunk, this function returns 2, but at least 3 rows are needed
809/// Note:
810///   the number of zero knowledge rows is usually computed across the codebase
811///   as the formula `(16 * num_chunks + 5) / 7`, which is precisely the formula
812///   in this function plus one.
813pub fn zk_rows_strict_lower_bound(num_chunks: usize) -> usize {
814    (2 * (PERMUTS + 1) * num_chunks - 2) / PERMUTS
815}
816
817impl FeatureFlags {
818    /// Creates feature flags by scanning gates, using pre-computed lookup features.
819    pub fn from_gates_and_lookup_features<F: PrimeField>(
820        gates: &[CircuitGate<F>],
821        lookup_features: LookupFeatures,
822    ) -> FeatureFlags {
823        let mut feature_flags = FeatureFlags {
824            range_check0: false,
825            range_check1: false,
826            lookup_features,
827            foreign_field_add: false,
828            foreign_field_mul: false,
829            xor: false,
830            rot: false,
831        };
832
833        for gate in gates {
834            match gate.typ {
835                GateType::RangeCheck0 => feature_flags.range_check0 = true,
836                GateType::RangeCheck1 => feature_flags.range_check1 = true,
837                GateType::ForeignFieldAdd => feature_flags.foreign_field_add = true,
838                GateType::ForeignFieldMul => feature_flags.foreign_field_mul = true,
839                GateType::Xor16 => feature_flags.xor = true,
840                GateType::Rot64 => feature_flags.rot = true,
841                _ => (),
842            }
843        }
844
845        feature_flags
846    }
847
848    /// Creates feature flags by scanning gates for optional gate types.
849    ///
850    /// This is the primary constructor. It detects both gate-level features
851    /// and lookup features from the circuit gates.
852    pub fn from_gates<F: PrimeField>(
853        gates: &[CircuitGate<F>],
854        uses_runtime_tables: bool,
855    ) -> FeatureFlags {
856        FeatureFlags::from_gates_and_lookup_features(
857            gates,
858            LookupFeatures::from_gates(gates, uses_runtime_tables),
859        )
860    }
861}
862
863impl<F: PrimeField> Builder<F> {
864    /// Set up the number of public inputs.
865    /// If not invoked, it equals `0` by default.
866    pub fn public(mut self, public: usize) -> Self {
867        self.public = public;
868        self
869    }
870
871    /// Set up the number of previous challenges, used for recusive proving.
872    /// If not invoked, it equals `0` by default.
873    pub fn prev_challenges(mut self, prev_challenges: usize) -> Self {
874        self.prev_challenges = prev_challenges;
875        self
876    }
877
878    /// Set up the lookup tables.
879    /// If not invoked, it is `vec![]` by default.
880    ///
881    /// **Warning:** you have to make sure that the IDs of the lookup tables,
882    /// are unique and not colliding with IDs of built-in lookup tables, otherwise
883    /// the error will be raised.
884    ///
885    /// (see [crate::circuits::lookup::tables]).
886    pub fn lookup(mut self, lookup_tables: Vec<LookupTable<F>>) -> Self {
887        self.lookup_tables = lookup_tables;
888        self
889    }
890
891    /// Set up the runtime tables.
892    /// If not invoked, it is `None` by default.
893    ///
894    /// **Warning:** you have to make sure that the IDs of the runtime
895    /// lookup tables, are unique, i.e. not colliding internaly (with other runtime tables),
896    /// otherwise error will be raised.
897    /// (see [crate::circuits::lookup::tables]).
898    pub fn runtime(mut self, runtime_tables: Option<Vec<RuntimeTableCfg<F>>>) -> Self {
899        self.runtime_tables = runtime_tables;
900        self
901    }
902
903    /// Set up the shared precomputations.
904    /// If not invoked, it is `None` by default.
905    pub fn shared_precomputations(
906        mut self,
907        shared_precomputations: Arc<DomainConstantEvaluations<F>>,
908    ) -> Self {
909        self.precomputations = Some(shared_precomputations);
910        self
911    }
912
913    /// Disable gates checks (for testing; only enables with development builds)
914    pub fn disable_gates_checks(mut self, disable_gates_checks: bool) -> Self {
915        self.disable_gates_checks = disable_gates_checks;
916        self
917    }
918
919    pub fn max_poly_size(mut self, max_poly_size: Option<usize>) -> Self {
920        self.max_poly_size = max_poly_size;
921        self
922    }
923
924    pub fn lazy_mode(mut self, lazy_mode: bool) -> Self {
925        self.lazy_mode = lazy_mode;
926        self
927    }
928
929    /// Build the [ConstraintSystem] from a [Builder].
930    pub fn build(self) -> Result<ConstraintSystem<F>, SetupError> {
931        let mut gates = self.gates;
932        let lookup_tables = self.lookup_tables.clone();
933        let runtime_tables = self.runtime_tables.clone();
934
935        //~ 1. If the circuit is less than 2 gates, abort.
936        // for some reason we need more than 1 gate for the circuit to work, see TODO below
937        assert!(gates.len() > 1);
938
939        let feature_flags = FeatureFlags::from_gates(&gates, runtime_tables.is_some());
940
941        let lookup_domain_size = {
942            // First we sum over the lookup table size
943            let mut has_table_with_id_0 = false;
944            let mut lookup_domain_size: usize = lookup_tables
945                .iter()
946                .map(|LookupTable { id, data }| {
947                    // See below for the reason
948                    if *id == 0_i32 {
949                        has_table_with_id_0 = true
950                    }
951                    if data.is_empty() {
952                        0
953                    } else {
954                        data[0].len()
955                    }
956                })
957                .sum();
958            // After that on the runtime tables
959            if let Some(runtime_tables) = &runtime_tables {
960                // FIXME: Check that a runtime table with ID 0 is enforced to
961                // contain a zero entry row.
962                for runtime_table in runtime_tables.iter() {
963                    lookup_domain_size += runtime_table.len();
964                }
965            }
966            // And we add the built-in tables, depending on the features.
967            let LookupFeatures { patterns, .. } = &feature_flags.lookup_features;
968            let mut gate_lookup_tables = GateLookupTables {
969                xor: false,
970                range_check: false,
971            };
972            for pattern in patterns.into_iter() {
973                if let Some(gate_table) = pattern.table() {
974                    gate_lookup_tables[gate_table] = true
975                }
976            }
977            for gate_table in gate_lookup_tables.into_iter() {
978                lookup_domain_size += gate_table.table_size();
979            }
980
981            // A dummy zero entry will be added if there is no table with ID
982            // zero. Therefore we must count this in the size.
983            if has_table_with_id_0 {
984                lookup_domain_size
985            } else {
986                lookup_domain_size + 1
987            }
988        };
989
990        //~ 1. Compute the number of zero-knowledge rows (`zk_rows`) that will be required to
991        //~    achieve zero-knowledge. The following constraints apply to `zk_rows`:
992        //~    * The number of chunks `c` results in an evaluation at `zeta` and `zeta * omega` in
993        //~      each column for `2*c` evaluations per column, so `zk_rows >= 2*c + 1`.
994        //~    * The permutation argument interacts with the `c` chunks in parallel, so it is
995        //~      possible to cross-correlate between them to compromise zero knowledge. We know
996        //~      that there is some `c >= 1` such that `zk_rows = 2*c + k` from the above. Thus,
997        //~      attempting to find the evaluation at a new point, we find that:
998        //~      * the evaluation of every witness column in the permutation contains `k` unknowns;
999        //~      * the evaluations of the permutation argument aggregation has `k-1` unknowns;
1000        //~      * the permutation argument applies on all but `zk_rows - 3` rows;
1001        //~      * and thus we form the equation `zk_rows - 3 < 7 * k + (k - 1)` to ensure that we
1002        //~        can construct fewer equations than we have unknowns.
1003        //~
1004        //~    This simplifies to `k > (2 * c - 2) / 7`, giving `zk_rows > (16 * c - 2) / 7`.
1005        //~    We can derive `c` from the `max_poly_size` supported by the URS, and thus we find
1006        //~    `zk_rows` and `domain_size` satisfying the fixpoint
1007        //~
1008        //~    ```text
1009        //~    zk_rows = (16 * (domain_size / max_poly_size) + 5) / 7
1010        //~    domain_size = circuit_size + zk_rows
1011        //~    ```
1012        //~
1013        let (zk_rows, domain_size_lower_bound) = {
1014            // We add 1 to the lookup domain size because there is one element
1015            // used to close the permutation argument (the polynomial Z is of
1016            // degree n + 1 where n is the order of the subgroup H).
1017            let circuit_lower_bound = core::cmp::max(gates.len(), lookup_domain_size + 1);
1018            let get_domain_size_lower_bound = |zk_rows: u64| circuit_lower_bound + zk_rows as usize;
1019
1020            let mut zk_rows = 3;
1021            let mut domain_size_lower_bound = get_domain_size_lower_bound(zk_rows);
1022            if let Some(max_poly_size) = self.max_poly_size {
1023                // Iterate to find a fixed-point where zk_rows is sufficient for the number of
1024                // chunks that we use, and also does not cause us to overflow the domain size.
1025                // NB: We use iteration here rather than hard-coding an assumption about
1026                // `compute_size_of_domain`s internals. In practice, this will never be executed
1027                // more than once.
1028                while {
1029                    let domain_size = D::<F>::compute_size_of_domain(domain_size_lower_bound)
1030                        .ok_or(SetupError::DomainCreation(
1031                            DomainCreationError::DomainSizeFailed(domain_size_lower_bound),
1032                        ))?;
1033                    let num_chunks = if domain_size < max_poly_size {
1034                        1
1035                    } else {
1036                        domain_size / max_poly_size
1037                    };
1038                    zk_rows = (zk_rows_strict_lower_bound(num_chunks) + 1) as u64;
1039                    domain_size_lower_bound = get_domain_size_lower_bound(zk_rows);
1040                    domain_size < domain_size_lower_bound
1041                } {}
1042            }
1043            (zk_rows, domain_size_lower_bound)
1044        };
1045
1046        //~ 1. Create a domain for the circuit. That is,
1047        //~    compute the smallest subgroup of the field that
1048        //~    has order greater or equal to `n + zk_rows` elements.
1049        let domain = EvaluationDomains::<F>::create(domain_size_lower_bound)
1050            .map_err(SetupError::DomainCreation)?;
1051
1052        assert!(domain.d1.size > zk_rows);
1053
1054        //~ 1. Pad the circuit: add zero gates to reach the domain size.
1055        let d1_size = domain.d1.size();
1056        let mut padding = (gates.len()..d1_size)
1057            .map(|i| {
1058                CircuitGate::<F>::zero(array::from_fn(|j| Wire {
1059                    col: WIRES[j],
1060                    row: i,
1061                }))
1062            })
1063            .collect();
1064        gates.append(&mut padding);
1065
1066        //~ 1. sample the `PERMUTS` shifts.
1067        let shifts = Shifts::new(&domain.d1);
1068
1069        //
1070        // Lookup
1071        // ------
1072        let gates = Arc::new(gates);
1073        let gates_clone = Arc::clone(&gates);
1074        let lookup_constraint_system = {
1075            #[cfg(feature = "std")]
1076            {
1077                LazyCache::new(move || {
1078                    LookupConstraintSystem::create(
1079                        &gates_clone,
1080                        self.lookup_tables,
1081                        self.runtime_tables,
1082                        &domain,
1083                        zk_rows as usize,
1084                    )
1085                })
1086            }
1087            #[cfg(not(feature = "std"))]
1088            {
1089                LazyCache::preinit(LookupConstraintSystem::create(
1090                    &gates_clone,
1091                    self.lookup_tables,
1092                    self.runtime_tables,
1093                    &domain,
1094                    zk_rows as usize,
1095                ))
1096            }
1097        };
1098        if !self.lazy_mode {
1099            // Precompute and map setup error
1100            lookup_constraint_system
1101                .try_get_or_err()
1102                .map_err(SetupError::from)?;
1103        }
1104
1105        let sid = shifts.map[0].clone();
1106
1107        // TODO: remove endo as a field
1108        let endo = F::zero();
1109
1110        let precomputations = if !self.lazy_mode {
1111            match self.precomputations {
1112                Some(t) => LazyCache::preinit(t),
1113                None => LazyCache::preinit(Arc::new(
1114                    DomainConstantEvaluations::create(domain, zk_rows).unwrap(),
1115                )),
1116            }
1117        } else {
1118            #[cfg(feature = "std")]
1119            {
1120                LazyCache::new(move || {
1121                    Arc::new(DomainConstantEvaluations::create(domain, zk_rows).unwrap())
1122                })
1123            }
1124            #[cfg(not(feature = "std"))]
1125            {
1126                LazyCache::preinit(Arc::new(
1127                    DomainConstantEvaluations::create(domain, zk_rows).unwrap(),
1128                ))
1129            }
1130        };
1131
1132        let constraints = ConstraintSystem {
1133            domain,
1134            public: self.public,
1135            prev_challenges: self.prev_challenges,
1136            sid,
1137            gates,
1138            shift: shifts.shifts,
1139            endo,
1140            zk_rows,
1141            //fr_sponge_params: self.sponge_params,
1142            lookup_constraint_system: Arc::new(lookup_constraint_system),
1143            feature_flags,
1144            precomputations: Arc::new(precomputations),
1145            disable_gates_checks: self.disable_gates_checks,
1146        };
1147
1148        Ok(constraints)
1149    }
1150}
1151
1152// TODO: "testing" modules should be cleaned up and removed. They only exist
1153// because bench.rs does not use #[cfg(test)] and needs access to test helpers.
1154pub(crate) mod testing {
1155    use super::ConstraintSystem;
1156    use crate::circuits::{
1157        gate::CircuitGate,
1158        lookup::{runtime_tables::RuntimeTableCfg, tables::LookupTable},
1159    };
1160    use alloc::vec::Vec;
1161    use ark_ff::PrimeField;
1162
1163    #[allow(clippy::too_many_arguments)]
1164    #[allow(dead_code)]
1165    pub(crate) fn create_constraint_system<F: PrimeField>(
1166        gates: Vec<CircuitGate<F>>,
1167        public: usize,
1168        prev_challenges: usize,
1169        lookup_tables: Vec<LookupTable<F>>,
1170        runtime_tables: Option<Vec<RuntimeTableCfg<F>>>,
1171        disable_gates_checks: bool,
1172        override_srs_size: Option<usize>,
1173        lazy_mode: bool,
1174    ) -> ConstraintSystem<F> {
1175        ConstraintSystem::<F>::create(gates)
1176            .lookup(lookup_tables)
1177            .runtime(runtime_tables)
1178            .public(public)
1179            .prev_challenges(prev_challenges)
1180            .disable_gates_checks(disable_gates_checks)
1181            .max_poly_size(override_srs_size)
1182            .lazy_mode(lazy_mode)
1183            .build()
1184            .unwrap()
1185    }
1186}