Skip to main content

starkom_plonk/
plonk.rs

1use crate::expr::{Constraint, Variable};
2use crate::utils::{hash_to_scalar, padded_circuit_size};
3use crate::witness::{Cell, CellOffset, Partitioner, Witness, WitnessView};
4use anyhow::{Result, anyhow};
5use primitive_types::H256;
6use starkom_bluesky::Scalar;
7use starkom_ff::{Field, PrimeField};
8use starkom_pcs::{self as pcs, hash::HashBackend};
9use starkom_poly;
10use std::collections::{BTreeMap, BTreeSet};
11use std::marker::PhantomData;
12use std::sync::LazyLock;
13
14type Polynomial = starkom_poly::Polynomial<Scalar>;
15
16/// Default blowup factor (16) in logarithmic form.
17///
18/// Used with the underlying PCS to compute low-degree extensions.
19pub const OPTIONS_DEFAULT_BLOWUP_LOG2: usize = 4;
20
21const COMMIT_INDEX_CIRCUIT: usize = 0;
22const COMMIT_INDEX_WITNESS: usize = 1;
23const COMMIT_INDEX_PERMUTATION_ARGUMENT: usize = 2;
24const COMMIT_INDEX_QUOTIENT: usize = 3;
25const NUM_COMMIT_INDICES: usize = 4;
26
27// Domain separator tags used for various Fiat-Shamir challenges.
28static DST_ALPHA: LazyLock<Scalar> =
29    LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/alpha"));
30static DST_BETA: LazyLock<Scalar> =
31    LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/beta"));
32static DST_GAMMA: LazyLock<Scalar> =
33    LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/gamma"));
34static DST_DELTA: LazyLock<Scalar> =
35    LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/delta"));
36static DST_XI: LazyLock<Scalar> = LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/xi"));
37
38/// Builds the set of all rotations used in a circuit.
39///
40/// NOTE: we're always including 0 and +1 because we need to open xi and xi*omega regardless;
41/// they're needed for the final algebraic check and for the shifted permutation argument,
42/// respectively.
43fn get_rotation_set<'a, I: IntoIterator<Item = &'a Constraint>>(gates: I) -> BTreeSet<isize> {
44    gates
45        .into_iter()
46        .map(|constraint| {
47            constraint
48                .get_free_variables()
49                .iter()
50                .map(Variable::rotation)
51                .collect::<BTreeSet<isize>>()
52                .into_iter()
53        })
54        .flatten()
55        .chain([0, 1])
56        .collect::<BTreeSet<isize>>()
57}
58
59/// Calculates the degree bound of the PLONK quotient, typically much higher than the circuit's
60/// general [degree bound](`Circuit::degree_bound`) `N` because the constraint equations involve
61/// several polynomial multiplications, such as the gate selectors multiplied by the gate
62/// constraints combined with the witness columns.
63///
64/// The algorithm uses the formula `(N - 1) * E`, where `E = max(max_gate_degree, num_columns)`.
65/// The rationale behind it is:
66///
67/// * each column has degree less than or equal to `N - 1`;
68/// * the grand gate constraint has degree less than or equal to
69///   `(N - 1) * (1 + max_gate_degree)` (the selector contributes one factor, degree composition
70///   with the constraint columns contributes `max_gate_degree` more);
71/// * the recurrence constraint of the permutation argument has degree less than or equal to
72///   `(N - 1) * (1 + num_columns)` (the accumulator/shifted term contributes one factor, one more
73///   per column);
74/// * the grand PLONK constraint (grand gate constraint + permutation argument fixpoint constraint
75///   + permutation argument recurrence constraint) has degree less than or equal to
76///   `(N - 1) * (1 + E)`;
77/// * dividing that by the zero polynomial (`x^N - 1`, degree-N) yields a quotient with degree
78///   `(N - 1) * (1 + E) - N`;
79/// * so the degree bound of the quotient is `(N - 1) * (1 + E) - N + 1`
80/// * ... which simplifies to `(N - 1) * E`.
81fn quotient_degree_bound<'a, I: Iterator<Item = &'a Constraint>>(
82    degree_bound: usize,
83    num_columns: usize,
84    gate_constraints: I,
85) -> usize {
86    let max_gate_degree = gate_constraints
87        .map(|constraint| constraint.get_degree())
88        .max()
89        .unwrap_or(0);
90    (degree_bound - 1) * std::cmp::max(max_gate_degree, num_columns)
91}
92
93/// Circuit compilation & proving options.
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct CompilationOptions {
96    /// Converts all constraints to canonical form using [`Constraint::canonicalize`].
97    ///
98    /// When disabled, proving errors out rather than attempting canonicalization if there are
99    /// negative exponents.
100    ///
101    /// Canonicalization is carried out inside [`CircuitBuilder::build`].
102    ///
103    /// WARNING: canonicalized constraints may be more permissive than their original form because a
104    /// negative exponent requires the variable to be different from zero. Starkom does not allow
105    /// proving with negative exponents, so enable this flag only if your circuit is correctly
106    /// constrained even when those variables are zero.
107    pub canonicalize_constraints: bool,
108}
109
110impl Default for CompilationOptions {
111    fn default() -> Self {
112        Self {
113            canonicalize_constraints: false,
114        }
115    }
116}
117
118/// Circuit compilation & proving options.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct ProvingOptions {
121    /// Log2 of the blowup factor used to compute the low-degree extensions for the underlying PCS.
122    pub blowup_log2: usize,
123}
124
125impl Default for ProvingOptions {
126    fn default() -> Self {
127        Self {
128            blowup_log2: OPTIONS_DEFAULT_BLOWUP_LOG2,
129        }
130    }
131}
132
133mod internal {
134    use super::*;
135
136    /// Provides access to the internal state of a circuit view.
137    ///
138    /// This is used to implement the provided methods of the [`CircuitView`] trait.
139    pub trait CircuitViewState {
140        /// Returns a reference to the [`CircuitBuilder`].
141        fn builder(&self) -> &CircuitBuilder;
142
143        /// Returns a mutable reference to the [`CircuitBuilder`].
144        fn builder_mut(&mut self) -> &mut CircuitBuilder;
145
146        /// Returns the row offset of the view (0 for the [`CircuitBuilder`] itself).
147        ///
148        /// This is always an absolute value even for transitive sub-views. It is not relative to
149        /// the parent view.
150        fn row_offset(&self) -> usize;
151
152        /// Returns the column offset of the view (0 for the [`CircuitBuilder`] itself).
153        ///
154        /// This is always an absolute value even for transitive sub-views. It is not relative to
155        /// the parent view.
156        fn column_offset(&self) -> usize;
157
158        /// Returns [`Self::row_offset()`] and [`Self::column_offset()`] as a [`Cell`].
159        fn root_cell(&self) -> Cell {
160            Cell::new(self.row_offset(), self.column_offset())
161        }
162
163        /// Returns the next root cell where an [auto gate](`CircuitView::auto_gate`) can be placed,
164        /// advancing the internal state to the next row.
165        fn step_row(&mut self) -> Cell;
166
167        /// Advances the internal row counter by `n`.
168        fn skip_rows(&mut self, n: usize);
169    }
170}
171
172pub trait CircuitView: internal::CircuitViewState {
173    /// Returns the number of columns included in the view.
174    ///
175    /// If this is the root view, that is the raw [`CircuitBuilder`] instance, the width is
176    /// unbounded and `None` is returned.
177    fn width(&self) -> Option<usize>;
178
179    /// Creates a [`Cell`] relative to this view.
180    ///
181    /// For example, if this view is at row offset 3 and column offset 5, then `cell(6, 2)` will
182    /// return the cell at row 9 and column 7.
183    fn cell(&self, row_offset: impl CellOffset, column_offset: impl CellOffset) -> Cell {
184        let row = self.row_offset() as isize + row_offset.into_offset();
185        let column = self.column_offset() as isize + column_offset.into_offset();
186        debug_assert!(row >= 0);
187        debug_assert!(column >= 0);
188        Cell::new(row as usize, column as usize)
189    }
190
191    /// Adds a gate to the circuit.
192    fn add_gate(&mut self, row: usize, constraint: Constraint) {
193        let root_cell = self.cell(row, 0);
194        self.builder_mut().add_gate_internal(root_cell, constraint);
195    }
196
197    /// "Connects" two circuit [`Cell`]s, meaning they will be constrained to have the same value.
198    fn connect(&mut self, cell1: Option<Cell>, cell2: Option<Cell>) {
199        self.builder_mut().connect_internal(cell1, cell2);
200    }
201
202    /// Skips `n` rows, advancing the internal row counter by `n`.
203    ///
204    /// This is useful between [`Self::auto_gate`] / [`Self::auto_constraint`] calls because it
205    /// allows the caller to place auto-gates at the correct position and satisfy assumptions about
206    /// rotated variables accessed by the gate. For example:
207    ///
208    /// ```ignore
209    /// let [sum] = view.auto_gate(rvar(0, 0) + rvar(0, 1) - rvar(0, 2), [x, y]);
210    /// view.skip_rows(2);
211    /// let [result] = view.auto_constraint(var(0) - 42, [sum.into()]);
212    /// ```
213    ///
214    /// The above circuit proves knowledge of two numbers whose sum is 42, and the `skip_rows` call
215    /// is required because the second gate must be placed three rows after the first.
216    fn skip_rows(&mut self, n: usize) {
217        internal::CircuitViewState::skip_rows(self, n);
218    }
219
220    /// Adds a gate with `N` inputs and `M` outputs.
221    ///
222    /// The provided `constraint` must use exactly `N+M` variables, or the function will panic. The
223    /// provided input cells are wrapped in `Option`s because `None` means the corresponding input
224    /// is unconstrained.
225    ///
226    /// The first `N` variables used in the constraint (those with the lowest column numbers) will
227    /// be automatically connected to the specified `inputs` unless they're unconstrained / None,
228    /// while the last `M` variables (those with the highest column numbers) will be returned as
229    /// outputs.
230    ///
231    /// NOTE: variables with the same column number but different rotations are considered different
232    /// variables for the purpose of counting against `N` and `M`. For example, the constraint
233    /// `var(0,+1) == var(0,-1) + var(0)` uses three variables, not one. Variables with the same
234    /// column number are ordered by rotation, so for example if you set `N=2` and `M=1` the above
235    /// constraint would associate the 2 inputs to `var(0,-1)` and `var(0)`, and the output to
236    /// `var(0,+1)`.
237    fn auto_gate<const N: usize, const M: usize>(
238        &mut self,
239        constraint: Constraint,
240        inputs: [Option<Cell>; N],
241    ) -> [Cell; M] {
242        let variables: Vec<Variable> = constraint.get_free_variables().into_iter().collect();
243        assert_eq!(variables.len(), N + M);
244
245        let root_cell = self.step_row();
246
247        for i in 0..N {
248            if let Some(input) = inputs[i] {
249                self.builder_mut()
250                    .connect_internal(Some(input), Some(variables[i].map_to_cell(root_cell)));
251            }
252        }
253
254        self.builder_mut().add_gate_internal(root_cell, constraint);
255
256        std::array::from_fn(|i| variables[N + i].map_to_cell(root_cell))
257    }
258
259    /// Adds a gate with `N` terminations.
260    ///
261    /// Unlike [`Self::auto_gate`] this method doesn't make a distinction between input and output
262    /// terminations; it simply enforces a polynomial relation among `N` terminations.
263    ///
264    /// The provided `constraint` must use exactly `N` variables, or the function will panic. The
265    /// provided input cells are wrapped in `Option`s because `None` means the corresponding input
266    /// is unconstrained.
267    ///
268    /// The `N` variables used in the constraint will be automatically connected to the specified
269    /// `inputs` unless they're unconstrained / None.
270    ///
271    /// NOTE: variables with the same column number but different rotations are considered different
272    /// variables for the purpose of counting against `N`. For example, the constraint
273    /// `var(0,+1) == var(0,-1) + var(0)` uses three variables, not one. Variables with the same
274    /// column number are ordered by rotation, so for example the above constraint would associate
275    /// the 3 inputs to `var(0,-1)`, `var(0)`, and `var(0,+1)`, respectively.
276    fn auto_constraint<const N: usize>(
277        &mut self,
278        constraint: Constraint,
279        inputs: [Option<Cell>; N],
280    ) -> [Cell; N] {
281        let variables: Vec<Variable> = constraint.get_free_variables().into_iter().collect();
282        assert_eq!(variables.len(), N);
283
284        let root_cell = self.step_row();
285
286        for i in 0..N {
287            if let Some(input) = inputs[i] {
288                self.connect(Some(input), Some(variables[i].map_to_cell(root_cell)));
289            }
290        }
291
292        self.builder_mut().add_gate_internal(root_cell, constraint);
293
294        std::array::from_fn(|i| variables[i].map_to_cell(root_cell))
295    }
296
297    /// Adds a NOP gate with `N` terminations; the constraint of the gate is `0 == 0`.
298    ///
299    /// This is conceptually equivalent to calling:
300    ///
301    /// ```ignore
302    /// witness.auto_constraint::<N>(Constraint::nop(), inputs);
303    /// ```
304    ///
305    /// except that the above call wouldn't work because the nop constraint uses 0 variables, so it
306    /// would panic because `0 != N`.
307    fn add_nop_gate<const N: usize>(&mut self, inputs: [Option<Cell>; N]) -> [Cell; N] {
308        let root_cell = self.step_row();
309        let outputs = std::array::from_fn(|i| Cell::new(0, i).remap(root_cell));
310
311        for i in 0..N {
312            if let Some(input) = inputs[i] {
313                self.connect(Some(input), Some(outputs[i]));
314            }
315        }
316
317        self.builder_mut()
318            .add_gate_internal(root_cell, Constraint::nop());
319
320        outputs
321    }
322
323    /// Spawns a child `CircuitView` at the given coordinates.
324    fn sub(&mut self, row_offset: usize, column_offset: usize, width: usize) -> impl CircuitView {
325        let row_offset = self.row_offset() + row_offset;
326        let column_offset = self.column_offset() + column_offset;
327        CircuitSectionBuilder::new(self.builder_mut(), row_offset, column_offset, width)
328    }
329
330    /// Spawns a child `CircuitView` at the given coordinates and runs the provided `callback` on
331    /// it.
332    ///
333    /// `sub_fn` returns `self`, not the child view. The child view is only valid for the duration
334    /// of the callback, while `self` is returned to make `sub_fn` chainable.
335    fn sub_fn(
336        &mut self,
337        row_offset: usize,
338        column_offset: usize,
339        width: usize,
340        callback: impl FnOnce(&mut CircuitSectionBuilder),
341    ) -> &mut Self {
342        let row_offset = self.row_offset() + row_offset;
343        let column_offset = self.column_offset() + column_offset;
344        callback(&mut CircuitSectionBuilder::new(
345            self.builder_mut(),
346            row_offset,
347            column_offset,
348            width,
349        ));
350        self
351    }
352
353    fn auto_sub<'a>(&'a mut self, width: usize, count: usize) -> CircuitViewGenerator<'a>;
354}
355
356#[derive(Debug)]
357pub struct CircuitViewGenerator<'a> {
358    /// Reference to the [`CircuitBuilder`].
359    builder: &'a mut CircuitBuilder,
360
361    /// Row offset where all sub-sections are rooted.
362    row_offset: usize,
363
364    /// Width of each sub-section.
365    width: usize,
366
367    /// Number of sub-sections to generate.
368    count: usize,
369}
370
371impl<'a> CircuitViewGenerator<'a> {
372    pub fn get(&'a mut self, index: usize) -> CircuitSectionBuilder<'a> {
373        assert!(index < self.count);
374        CircuitSectionBuilder::new(
375            self.builder,
376            self.row_offset,
377            self.width * index,
378            self.width,
379        )
380    }
381}
382
383/// Describes an instance of a gate.
384///
385/// NOTE: this struct doesn't specify the row of the root cell where the gate was placed because
386/// activating a gate at the correct rows is the gate selector's job. The column of the root cell,
387/// on the other hand, is specified by [`Self::column_index`].
388#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
389struct GateInstance {
390    /// Column of the root cell where the gate was placed.
391    column_index: usize,
392
393    /// Index of the gate selector polynomial within the [selector pool](`Circuit::selectors`).
394    selector_index: usize,
395}
396
397/// Allows building PLONK [`Circuit`]s.
398#[derive(Debug, Default, Clone)]
399pub struct CircuitBuilder {
400    /// Current number of rows in the circuit.
401    num_rows: usize,
402
403    /// Current number of columns in the circuit.
404    num_columns: usize,
405
406    /// Used by [`Self::auto_gate`] to keep track of the current row;
407    row_counter: usize,
408
409    /// The gates of the circuit, indexed by constraint.
410    ///
411    /// For every gate type (that is, for every unique gate constraint) this map associates the list
412    /// of places where the gate has been instantiated. Such list is represented as an array of
413    /// "root cells", each root cell being the reference cell for the gate instance: the row of the
414    /// root cell corresponds to rotation 0 of all variables referenced by the gate, and the column
415    /// corresponds to the column offset of the [`CircuitView`] used to instantiate the gate.
416    ///
417    /// NOTE: in order to minimize the number of different gate types stored in a circuit, the
418    /// constraints stored in this map are _not_ [remapped](`Constraint::remap_variables`). This map
419    /// basically keeps "raw gate types".
420    gates: BTreeMap<Constraint, Vec<Cell>>,
421
422    /// Cell partitioning inferred from the connections made with [`Self::connect`].
423    partitioner: Partitioner,
424
425    /// List of rows that are revealed in the proofs.
426    public_rows: BTreeSet<usize>,
427}
428
429impl CircuitBuilder {
430    fn add_gate_internal(&mut self, root_cell: Cell, constraint: Constraint) {
431        let row = root_cell.row();
432        let column = root_cell.column();
433        {
434            self.num_rows = std::cmp::max(self.num_rows, row + 1);
435            self.num_columns = std::cmp::max(self.num_columns, column + 1);
436            for variable in constraint.get_free_variables() {
437                let rotation = variable.rotation();
438                self.num_rows = std::cmp::max(
439                    self.num_rows,
440                    if rotation < 0 {
441                        assert!(rotation.unsigned_abs() <= row);
442                        row - rotation.unsigned_abs()
443                    } else {
444                        row + rotation.unsigned_abs()
445                    } + 1,
446                );
447                self.num_columns =
448                    std::cmp::max(self.num_columns, column + variable.column_index() + 1);
449            }
450        }
451        self.gates.entry(constraint).or_default().push(root_cell);
452    }
453
454    fn connect_internal(&mut self, cell1: Option<Cell>, cell2: Option<Cell>) {
455        match (cell1, cell2) {
456            (Some(cell1), Some(cell2)) => {
457                self.partitioner.connect(cell1, cell2);
458            }
459            _ => {}
460        }
461    }
462
463    /// Updates the list of witness rows that are revealed.
464    ///
465    /// This method drops any previously provided lists, so if it's called multiple times only the
466    /// list provided in the last call is used.
467    ///
468    /// Ideally you should call this method only once after adding all gates, right before
469    /// [`Self::build`].
470    pub fn declare_public_rows<I: IntoIterator<Item = usize>>(&mut self, gates: I) {
471        self.public_rows = BTreeSet::from_iter(gates);
472    }
473
474    fn make_selector(degree_bound: usize, activation_row_set: BTreeSet<usize>) -> Polynomial {
475        let mut selector_values = vec![Scalar::ZERO; degree_bound];
476        for row in activation_row_set {
477            selector_values[row] = Scalar::ONE;
478        }
479        Polynomial::encode2(selector_values)
480    }
481
482    fn build_gates_and_selectors(
483        &self,
484        degree_bound: usize,
485    ) -> (
486        BTreeMap<Constraint, BTreeSet<GateInstance>>,
487        Vec<Polynomial>,
488    ) {
489        // Keys are (constraint, column_index) pairs; values are activation row sets.
490        let mut row_set_map: BTreeMap<(Constraint, usize), BTreeSet<usize>> = BTreeMap::default();
491        for (constraint, root_cells) in &self.gates {
492            for root_cell in root_cells.as_slice() {
493                let key = (constraint.clone(), root_cell.column());
494                let row = root_cell.row();
495                row_set_map.entry(key).or_default().insert(row);
496            }
497        }
498
499        // Roughly the inverse of `row_set_map`: keys are activation row sets, values are the list
500        // of (constraint, column_index) instances that activate at those rows.
501        let mut gates_by_row_set: BTreeMap<BTreeSet<usize>, Vec<(Constraint, usize)>> =
502            BTreeMap::default();
503        for ((constraint, column_index), row_set) in row_set_map {
504            gates_by_row_set
505                .entry(row_set)
506                .or_default()
507                .push((constraint, column_index));
508        }
509
510        let mut gates: BTreeMap<Constraint, BTreeSet<GateInstance>> = BTreeMap::default();
511        let mut selectors: Vec<Polynomial> = vec![];
512
513        for (selector_index, (activation_row_set, gate_instances)) in
514            gates_by_row_set.into_iter().enumerate()
515        {
516            selectors.push(Self::make_selector(degree_bound, activation_row_set));
517            for (constraint, column_index) in gate_instances {
518                gates.entry(constraint).or_default().insert(GateInstance {
519                    column_index,
520                    selector_index,
521                });
522            }
523        }
524
525        (gates, selectors)
526    }
527
528    /// Compiles the circuit built so far into a [`Circuit`] object.
529    pub fn build(mut self, options: CompilationOptions) -> Result<Circuit> {
530        if options.canonicalize_constraints {
531            let mut old_gates: BTreeMap<Constraint, Vec<Cell>> = BTreeMap::default();
532            std::mem::swap(&mut self.gates, &mut old_gates);
533            for (constraint, mut root_cells) in old_gates {
534                self.gates
535                    .entry(constraint.canonicalize())
536                    .or_default()
537                    .append(&mut root_cells);
538            }
539        } else {
540            for (constraint, _) in &self.gates {
541                if !constraint.is_canonical() {
542                    return Err(anyhow!("constraint `{}` is not canonical", constraint));
543                }
544            }
545        }
546
547        let (degree_bound, num_blinding_rows) = padded_circuit_size(
548            self.num_rows,
549            self.gates.iter().flat_map(|(constraint, _)| {
550                constraint
551                    .get_free_variables()
552                    .iter()
553                    .map(Variable::rotation)
554                    .collect::<BTreeSet<isize>>()
555            }),
556        );
557
558        let (gates, selectors) = Self::build_gates_and_selectors(&self, degree_bound);
559
560        let sigma_values: Vec<Vec<Scalar>> = {
561            let mut sigma = vec![Scalar::ZERO; degree_bound * self.num_columns];
562            let omega = Polynomial::domain_element2(1, degree_bound);
563            let mut k = Scalar::ONE;
564            for i in 0..self.num_columns {
565                let offset = i * degree_bound;
566                sigma[offset] = k;
567                for j in 1..degree_bound {
568                    sigma[offset + j] = sigma[offset + j - 1] * omega;
569                }
570                k *= Scalar::MULTIPLICATIVE_GENERATOR;
571            }
572            for node in self.partitioner.iter_nodes() {
573                let indices: Vec<usize> = node
574                    .iter()
575                    .map(|cell| cell.column() * degree_bound + cell.row())
576                    .collect();
577                let mut permuted: Vec<Scalar> = indices.iter().map(|&i| sigma[i]).collect();
578                permuted.rotate_left(1);
579                for i in 0..indices.len() {
580                    sigma[indices[i]] = permuted[i];
581                }
582            }
583            sigma
584                .chunks_exact(degree_bound)
585                .map(|chunk| chunk.to_vec())
586                .collect()
587        };
588
589        let sigma = sigma_values
590            .iter()
591            .map(|chunk| Polynomial::encode2(chunk.to_vec()))
592            .collect();
593
594        Ok(Circuit {
595            num_rows: self.num_rows,
596            num_blinding_rows,
597            degree_bound,
598            num_columns: self.num_columns,
599            selectors,
600            gates,
601            sigma,
602            sigma_values,
603            public_rows: self.public_rows,
604        })
605    }
606}
607
608impl internal::CircuitViewState for CircuitBuilder {
609    fn builder(&self) -> &CircuitBuilder {
610        self
611    }
612
613    fn builder_mut(&mut self) -> &mut CircuitBuilder {
614        self
615    }
616
617    fn row_offset(&self) -> usize {
618        0
619    }
620
621    fn column_offset(&self) -> usize {
622        0
623    }
624
625    fn step_row(&mut self) -> Cell {
626        let root_cell = self.cell(self.row_counter, 0);
627        self.row_counter += 1;
628        root_cell
629    }
630
631    fn skip_rows(&mut self, n: usize) {
632        self.row_counter += n;
633    }
634}
635
636impl CircuitView for CircuitBuilder {
637    fn width(&self) -> Option<usize> {
638        None
639    }
640
641    fn auto_sub<'a>(&'a mut self, width: usize, count: usize) -> CircuitViewGenerator<'a> {
642        let row_offset = self.row_counter;
643        CircuitViewGenerator {
644            builder: self,
645            row_offset,
646            width,
647            count,
648        }
649    }
650}
651
652/// Implements [`CircuitView`] for a sub-section of the circuit.
653#[derive(Debug)]
654pub struct CircuitSectionBuilder<'a> {
655    /// Reference to the parent [`CircuitBuilder`].
656    builder: &'a mut CircuitBuilder,
657
658    /// Row offset of the sub-section.
659    row_offset: usize,
660
661    /// Column offset of the sub-section.
662    column_offset: usize,
663
664    /// Width (number of columns) of the sub-section.
665    width: usize,
666
667    /// Row counter for [`Self::auto_gate`].
668    row_counter: usize,
669}
670
671impl<'a> CircuitSectionBuilder<'a> {
672    fn new(
673        builder: &'a mut CircuitBuilder,
674        row_offset: usize,
675        column_offset: usize,
676        width: usize,
677    ) -> Self {
678        Self {
679            builder,
680            row_offset,
681            column_offset,
682            width,
683            row_counter: 0,
684        }
685    }
686}
687
688impl<'a> internal::CircuitViewState for CircuitSectionBuilder<'a> {
689    fn builder(&self) -> &CircuitBuilder {
690        self.builder
691    }
692
693    fn builder_mut(&mut self) -> &mut CircuitBuilder {
694        self.builder
695    }
696
697    fn row_offset(&self) -> usize {
698        self.row_offset
699    }
700
701    fn column_offset(&self) -> usize {
702        self.column_offset
703    }
704
705    fn step_row(&mut self) -> Cell {
706        let root_cell = self.cell(self.row_counter, 0);
707        self.row_counter += 1;
708        root_cell
709    }
710
711    fn skip_rows(&mut self, n: usize) {
712        self.row_counter += n;
713    }
714}
715
716impl<'a> CircuitView for CircuitSectionBuilder<'a> {
717    fn width(&self) -> Option<usize> {
718        Some(self.width)
719    }
720
721    fn auto_sub<'b>(&'b mut self, width: usize, count: usize) -> CircuitViewGenerator<'b> {
722        let row_offset = self.row_offset + self.row_counter;
723        CircuitViewGenerator {
724            builder: self.builder,
725            row_offset,
726            width,
727            count,
728        }
729    }
730}
731
732impl<'a> Drop for CircuitSectionBuilder<'a> {
733    fn drop(&mut self) {
734        self.builder.row_counter =
735            std::cmp::max(self.builder.row_counter, self.row_offset + self.row_counter);
736    }
737}
738
739/// A PLONK proof.
740///
741/// The API in the implementation mostly mirrors that of the underlying PCS proof.
742#[derive(Debug, Clone)]
743pub struct Proof<H: HashBackend<Scalar>> {
744    commitment: pcs::Commitment<H>,
745    inner_proof: pcs::Proof<H>,
746}
747
748impl<H: HashBackend<Scalar>> Proof<H> {
749    /// Returns the proven degree bound.
750    pub fn degree_bound(&self) -> usize {
751        self.inner_proof.degree_bound()
752    }
753
754    /// Returns the base-2 logarithm of the blowup factor used in the proof.
755    pub fn blowup_log2(&self) -> usize {
756        self.inner_proof.blowup_log2()
757    }
758
759    /// Returns the size of the extended evaluation domain.
760    pub fn extended_domain_size(&self) -> usize {
761        self.inner_proof.extended_domain_size()
762    }
763
764    /// Returns the number of committed polynomials.
765    ///
766    /// These include the circuit selectors and sigma polynomials, the witness columns, and the
767    /// chunks of the grand quotient.
768    pub fn num_polys(&self) -> usize {
769        self.inner_proof.num_polys()
770    }
771}
772
773/// A PLONK circuit.
774#[derive(Debug, Clone)]
775pub struct Circuit {
776    /// The raw number of rows of the circuit.
777    ///
778    /// Unlike [`Self::degree_bound`], this count doesn't include the blinding rows and is not
779    /// padded to the next power of 2.
780    num_rows: usize,
781
782    /// Number of blinding rows used in the circuit.
783    ///
784    /// This is calculated by [`padded_circuit_size`] and depends on how many different variable
785    /// rotations were used across all constraints.
786    num_blinding_rows: usize,
787
788    /// Number of witness rows (including the blinding rows) rounded up to the next power of 2.
789    ///
790    /// This is the direct result of [`padded_circuit_size`].
791    degree_bound: usize,
792
793    /// Number of witness columns.
794    num_columns: usize,
795
796    /// Gate selectors.
797    ///
798    /// This is a pool of Lagrange bases that the grand gate constraint uses to selectively activate
799    /// gates at the rows where they're used.
800    ///
801    /// The size of this pool is not (necessarily) the number of rows: [`CircuitBuilder`]'s
802    /// algorithm tries to reuse selectors as much as possible.
803    selectors: Vec<Polynomial>,
804
805    /// Gates used in the circuit: the first component of each pair is the gate constraint and the
806    /// second component is the set of instances of that gate across the circuit.
807    gates: BTreeMap<Constraint, BTreeSet<GateInstance>>,
808
809    /// Sigma polynomials of the permutation argument, one for every witness column.
810    sigma: Vec<Polynomial>,
811
812    /// The [sigma polynomials](`Self::sigma`) expressed on the value domain.
813    ///
814    /// The layout is analogous to [`Self::sigma`] itself: the values are indexed column-first.
815    sigma_values: Vec<Vec<Scalar>>,
816
817    /// List of gates that are revealed in the proofs. Each element is a row index.
818    public_rows: BTreeSet<usize>,
819}
820
821impl Circuit {
822    pub fn num_rows(&self) -> usize {
823        self.num_rows
824    }
825
826    pub fn degree_bound(&self) -> usize {
827        self.degree_bound
828    }
829
830    pub fn num_columns(&self) -> usize {
831        self.num_columns
832    }
833
834    pub fn public_rows(&self) -> &BTreeSet<usize> {
835        &self.public_rows
836    }
837
838    /// Makes an empty [`Witness`] objects suitable for use with this circuit.
839    pub fn make_witness(&self) -> Witness {
840        Witness::new(
841            self.num_rows,
842            self.num_columns,
843            self.gates.iter().flat_map(|(constraint, _)| {
844                constraint
845                    .get_free_variables()
846                    .iter()
847                    .map(Variable::rotation)
848                    .collect::<BTreeSet<isize>>()
849            }),
850        )
851    }
852
853    /// Performs various checks to verify that the provided `witness` is compatible with this
854    /// circuit and doesn't violate any constraints.
855    pub fn check_witness(&self, witness: &Witness) -> Result<()> {
856        if witness.num_rows() != self.num_rows {
857            return Err(anyhow!(
858                "wrong number of rows: got {}, want {}",
859                witness.num_rows(),
860                self.num_rows
861            ));
862        }
863        if witness.num_columns() != self.num_columns {
864            return Err(anyhow!(
865                "wrong number of columns: got {}, want {}",
866                witness.num_columns(),
867                self.num_columns
868            ));
869        }
870        if witness.degree_bound() != self.degree_bound {
871            return Err(anyhow!(
872                "incorrect degree bound: got {}, want {}",
873                witness.degree_bound(),
874                self.degree_bound
875            ));
876        }
877
878        let active_row_set: Vec<BTreeSet<usize>> = self
879            .selectors
880            .iter()
881            .map(|selector| {
882                selector
883                    .clone()
884                    .decode2()
885                    .into_iter()
886                    .enumerate()
887                    .filter(|(_, value)| *value != Scalar::ZERO)
888                    .map(|(index, _)| index)
889                    .collect()
890            })
891            .collect();
892        for (constraint, gate_instances) in &self.gates {
893            for gate_instance in gate_instances {
894                let variables = constraint.get_free_variables();
895                for &row in &active_row_set[gate_instance.selector_index] {
896                    let root_cell = Cell::new(row, gate_instance.column_index);
897                    let substitution: BTreeMap<Variable, Scalar> = variables
898                        .iter()
899                        .map(|variable| {
900                            (
901                                variable.clone(),
902                                witness.get(variable.map_to_cell(root_cell)),
903                            )
904                        })
905                        .collect();
906                    if constraint.evaluate(&substitution) != Scalar::ZERO {
907                        return Err(anyhow!(
908                            "gate constraint `{}` violated at row {}, column {}",
909                            constraint,
910                            row,
911                            gate_instance.column_index
912                        ));
913                    }
914                }
915            }
916        }
917
918        let cell_by_identity_value: BTreeMap<Scalar, Cell> = {
919            let mut cell_by_identity_value = BTreeMap::default();
920            let omega = Polynomial::domain_element2(1, self.degree_bound);
921            let mut generator_power = Scalar::ONE;
922            for column in 0..self.num_columns {
923                let mut omega_power = Scalar::ONE;
924                for row in 0..self.degree_bound {
925                    cell_by_identity_value
926                        .insert(generator_power * omega_power, Cell::new(row, column));
927                    omega_power *= omega;
928                }
929                generator_power *= Scalar::MULTIPLICATIVE_GENERATOR;
930            }
931            cell_by_identity_value
932        };
933        for column in 0..self.num_columns {
934            for row in 0..self.num_rows {
935                let source_cell = Cell::new(row, column);
936                let target_cell = *cell_by_identity_value
937                    .get(&self.sigma_values[column][row])
938                    .unwrap();
939                let source_value = witness.get(source_cell);
940                let target_value = witness.get(target_cell);
941                if source_value != target_value {
942                    return Err(anyhow!(
943                        "wire constraint violated: cell({}, {}) = {}, cell({}, {}) = {}",
944                        source_cell.row(),
945                        source_cell.column(),
946                        source_value,
947                        target_cell.row(),
948                        target_cell.column(),
949                        target_value,
950                    ));
951                }
952            }
953        }
954
955        Ok(())
956    }
957
958    /// Builds the three polynomials used in the permutation argument. The components of the
959    /// returned tuple are, respectively: the coordinate pair accumulator, the fixpoint constraint,
960    /// and the recurrence constraint.
961    fn build_permutation_argument(
962        &self,
963        witness: &Witness,
964        columns: &[Polynomial],
965        beta: Scalar,
966        gamma: Scalar,
967    ) -> Result<(Polynomial, Polynomial, Polynomial)> {
968        let omega = Polynomial::domain_element2(1, self.degree_bound);
969
970        let accumulator = {
971            let mut accumulator = vec![Scalar::ZERO; self.degree_bound + 1];
972
973            accumulator[0] = Scalar::ONE;
974            let mut omega_power = Scalar::ONE;
975            for i in 0..self.degree_bound {
976                let mut generator_power = Scalar::ONE;
977                accumulator[i + 1] = accumulator[i];
978                for j in 0..self.num_columns {
979                    let witness_value = witness.get(Cell::new(i, j));
980                    accumulator[i + 1] *=
981                        witness_value + beta * generator_power * omega_power + gamma;
982                    accumulator[i + 1] *=
983                        (witness_value + beta * self.sigma_values[j][i] + gamma).invert_unwrap();
984                    generator_power *= Scalar::MULTIPLICATIVE_GENERATOR;
985                }
986                omega_power *= omega;
987            }
988
989            if accumulator.pop().unwrap() != Scalar::ONE {
990                return Err(anyhow!("permutation accumulator wraparound check failed"));
991            }
992
993            Polynomial::encode2(accumulator)
994        };
995
996        let shifted = accumulator.clone().shift_domain_by(omega);
997
998        let recurrence_constraint = {
999            let mut lhs = shifted;
1000            for (column, sigma) in columns.iter().zip(self.sigma.iter()) {
1001                lhs *= column.clone() + sigma.clone() * beta + gamma;
1002            }
1003            let mut rhs = accumulator.clone();
1004            let mut power = Scalar::ONE;
1005            for column in columns {
1006                rhs *= column.clone() + Polynomial::with_coefficients(vec![gamma, beta * power]);
1007                power *= Scalar::MULTIPLICATIVE_GENERATOR;
1008            }
1009            lhs - rhs
1010        };
1011
1012        let fixpoint_constraint =
1013            (accumulator.clone() - Scalar::ONE) * Polynomial::lagrange0(self.degree_bound).clone();
1014
1015        Ok((accumulator, fixpoint_constraint, recurrence_constraint))
1016    }
1017
1018    /// Splits the quotient polynomial in chunks so that it can be batch-committed even though its
1019    /// degree is much higher than the bound configured in the underlying PCS.
1020    fn split_quotient(&self, quotient: Polynomial) -> Vec<Polynomial> {
1021        let degree_bound = quotient_degree_bound(
1022            self.degree_bound,
1023            self.num_columns,
1024            self.gates.iter().map(|(constraint, _)| constraint),
1025        );
1026        let mut coefficients = quotient.take();
1027        assert!(coefficients.len() <= degree_bound);
1028        coefficients.resize(degree_bound, Scalar::ZERO);
1029        coefficients
1030            .chunks(self.degree_bound)
1031            .map(|coefficients| Polynomial::with_coefficients(coefficients.to_vec()))
1032            .collect()
1033    }
1034
1035    /// Proves correctness of the given witness, or returns an error in case of a constraint
1036    /// violation.
1037    pub fn prove<H: HashBackend<Scalar>>(
1038        &self,
1039        mut witness: Witness,
1040        options: ProvingOptions,
1041    ) -> Result<Proof<H>> {
1042        witness.blind();
1043        if witness.num_rows() != self.num_rows {
1044            return Err(anyhow!(
1045                "incorrect witness size (got {} rows, want {})",
1046                witness.num_rows(),
1047                self.num_rows
1048            ));
1049        }
1050        if witness.degree_bound() != self.degree_bound {
1051            return Err(anyhow!(
1052                "incorrect witness degree bound (got {}, want {})",
1053                witness.degree_bound(),
1054                self.degree_bound
1055            ));
1056        }
1057        if witness.num_columns() != self.num_columns {
1058            return Err(anyhow!(
1059                "incorrect witness size (got {} columns, want {})",
1060                witness.num_columns(),
1061                self.num_columns
1062            ));
1063        }
1064
1065        let circuit_polynomials = self
1066            .selectors
1067            .iter()
1068            .cloned()
1069            .chain(self.sigma.iter().cloned())
1070            .collect();
1071
1072        let mut committer =
1073            pcs::Committer::<H>::new(self.degree_bound, options.blowup_log2, circuit_polynomials);
1074
1075        let columns = witness.clone().encode();
1076        committer.add_batch(columns.clone());
1077
1078        let omega = Polynomial::domain_element2(1, self.degree_bound);
1079
1080        let gate_constraint = {
1081            let delta = H::challenge(*DST_DELTA, [committer.transcript_hash()]);
1082            let mut gate_constraint = Polynomial::default();
1083            let mut power = Scalar::ONE;
1084            for (constraint, instances) in &self.gates {
1085                for instance in instances {
1086                    let constraint = constraint.clone().remap_variables(instance.column_index);
1087                    let selector = self.selectors[instance.selector_index].clone();
1088                    gate_constraint +=
1089                        selector * constraint.compose(omega, columns.as_slice()) * power;
1090                    power *= delta;
1091                }
1092            }
1093            gate_constraint
1094        };
1095
1096        let (
1097            permutation_accumulator,
1098            permutation_fixpoint_constraint,
1099            permutation_recurrence_constraint,
1100        ) = {
1101            let beta = H::challenge(*DST_BETA, [committer.transcript_hash()]);
1102            let gamma = H::challenge(*DST_GAMMA, [committer.transcript_hash()]);
1103            self.build_permutation_argument(&witness, columns.as_slice(), beta, gamma)?
1104        };
1105        committer.add_batch(vec![permutation_accumulator]);
1106
1107        let alpha = H::challenge(*DST_ALPHA, [committer.transcript_hash()]);
1108
1109        let quotient = (gate_constraint
1110            + permutation_fixpoint_constraint * alpha
1111            + permutation_recurrence_constraint * alpha.square())
1112        .divide_by_zero(self.degree_bound)?;
1113        committer.add_batch(self.split_quotient(quotient));
1114
1115        let xi = H::challenge(*DST_XI, [committer.transcript_hash()]);
1116
1117        let omega_inv = omega.invert_unwrap();
1118        let (commitment, prover) = committer.commit(BTreeSet::from_iter(
1119            get_rotation_set(self.gates.iter().map(|(constraint, _)| constraint))
1120                .into_iter()
1121                .map(|rotation| {
1122                    xi * if rotation < 0 { omega_inv } else { omega }
1123                        .pow_small(rotation.unsigned_abs())
1124                })
1125                .chain(self.public_rows.iter().map(|&row| omega.pow_small(row))),
1126        ));
1127        let inner_proof = prover.prove(&commitment);
1128
1129        Ok(Proof {
1130            commitment,
1131            inner_proof,
1132        })
1133    }
1134
1135    pub fn to_compressed<H: HashBackend<Scalar>>(
1136        self,
1137        options: ProvingOptions,
1138    ) -> CompressedCircuit<H> {
1139        let committer = pcs::Committer::<H>::new(
1140            self.degree_bound,
1141            options.blowup_log2,
1142            self.selectors
1143                .into_iter()
1144                .chain(self.sigma.into_iter())
1145                .collect(),
1146        );
1147        CompressedCircuit {
1148            num_rows: self.num_rows,
1149            num_blinding_rows: self.num_blinding_rows,
1150            degree_bound: self.degree_bound,
1151            num_columns: self.num_columns,
1152            options,
1153            gates: self
1154                .gates
1155                .into_iter()
1156                .map(|(constraint, instances)| (constraint, instances.into_iter().collect()))
1157                .collect(),
1158            public_rows: self.public_rows,
1159            circuit_commitment: committer.root_hash(COMMIT_INDEX_CIRCUIT),
1160            _data: Default::default(),
1161        }
1162    }
1163
1164    pub fn as_compressed<H: HashBackend<Scalar>>(
1165        &self,
1166        options: ProvingOptions,
1167    ) -> CompressedCircuit<H> {
1168        let committer = pcs::Committer::<H>::new(
1169            self.degree_bound,
1170            options.blowup_log2,
1171            self.selectors
1172                .iter()
1173                .cloned()
1174                .chain(self.sigma.iter().cloned())
1175                .collect(),
1176        );
1177        CompressedCircuit {
1178            num_rows: self.num_rows,
1179            num_blinding_rows: self.num_blinding_rows,
1180            degree_bound: self.degree_bound,
1181            num_columns: self.num_columns,
1182            options,
1183            gates: self
1184                .gates
1185                .iter()
1186                .map(|(constraint, instances)| {
1187                    (constraint.clone(), instances.iter().cloned().collect())
1188                })
1189                .collect(),
1190            public_rows: self.public_rows.clone(),
1191            circuit_commitment: committer.root_hash(COMMIT_INDEX_CIRCUIT),
1192            _data: Default::default(),
1193        }
1194    }
1195
1196    pub fn verify<H: HashBackend<Scalar>>(
1197        &self,
1198        proof: &Proof<H>,
1199        options: ProvingOptions,
1200    ) -> Result<BTreeMap<Cell, Scalar>> {
1201        self.as_compressed::<H>(options).verify(proof)
1202    }
1203}
1204
1205/// A PLONK circuit in committed form.
1206///
1207/// This struct is much smaller than the original circuit but still allows full verification of a
1208/// proof for the circuit.
1209#[derive(Debug, Clone, PartialEq, Eq)]
1210pub struct CompressedCircuit<H: HashBackend<Scalar>> {
1211    /// The raw number of rows of the circuit.
1212    ///
1213    /// Unlike [`Self::degree_bound`], this count doesn't include the blinding rows and is not
1214    /// padded to the next power of 2.
1215    num_rows: usize,
1216
1217    /// Number of blinding rows used in the circuit.
1218    num_blinding_rows: usize,
1219
1220    /// Number of witness rows (including the blinding rows) rounded up to the next power of 2.
1221    degree_bound: usize,
1222
1223    /// Number of witness columns.
1224    num_columns: usize,
1225
1226    /// Proving options used to commit to this circuit (in [`Circuit::as_compressed`] or
1227    /// [`Circuit::to_compressed`]).
1228    options: ProvingOptions,
1229
1230    /// Gates used in the circuit: the first component of each pair is the gate constraint and the
1231    /// second component is the set of instances of that gate across the circuit.
1232    gates: Vec<(Constraint, Vec<GateInstance>)>,
1233
1234    /// List of rows that are revealed in the proofs.
1235    public_rows: BTreeSet<usize>,
1236
1237    /// Merkle root of the circuit selectors.
1238    circuit_commitment: H256,
1239
1240    _data: PhantomData<H>,
1241}
1242
1243impl<H: HashBackend<Scalar>> CompressedCircuit<H> {
1244    pub fn num_rows(&self) -> usize {
1245        self.num_rows
1246    }
1247
1248    pub fn degree_bound(&self) -> usize {
1249        self.degree_bound
1250    }
1251
1252    pub fn num_columns(&self) -> usize {
1253        self.num_columns
1254    }
1255
1256    pub fn public_rows(&self) -> &BTreeSet<usize> {
1257        &self.public_rows
1258    }
1259
1260    /// Calculates the number of chunks the quotient was split into.
1261    ///
1262    /// See [`quotient_degree_bound`] for details.
1263    fn get_num_quotient_chunks(&self) -> usize {
1264        quotient_degree_bound(
1265            self.degree_bound,
1266            self.num_columns,
1267            self.gates.iter().map(|(constraint, _)| constraint),
1268        )
1269        .div_ceil(self.degree_bound)
1270    }
1271
1272    fn lagrange0(x: Scalar, n: usize) -> Scalar {
1273        (x.pow_small(n) - Scalar::ONE)
1274            * (Scalar::from(n as u64) * (x - Scalar::ONE)).invert_unwrap()
1275    }
1276
1277    /// Verifies a [`Proof`], returning the map of proven public values if successful or an error
1278    /// otherwise.
1279    ///
1280    /// The returned map will have exactly `N` entries for every row in [`Self::public_rows`], with
1281    /// `N` being the [number of columns](`Self::num_columns`).
1282    pub fn verify(&self, proof: &Proof<H>) -> Result<BTreeMap<Cell, Scalar>> {
1283        let commitment = &proof.commitment;
1284        let inner_proof = &proof.inner_proof;
1285
1286        if commitment.tree_roots().len() != NUM_COMMIT_INDICES {
1287            return Err(anyhow!(
1288                "wrong number of Merkle roots (got {}, want {})",
1289                commitment.tree_roots().len(),
1290                NUM_COMMIT_INDICES
1291            ));
1292        }
1293        if commitment.tree_roots()[COMMIT_INDEX_CIRCUIT] != self.circuit_commitment {
1294            return Err(anyhow!(
1295                "wrong circuit commitment (got {}, want {})",
1296                commitment.tree_roots()[COMMIT_INDEX_CIRCUIT],
1297                self.circuit_commitment
1298            ));
1299        }
1300
1301        if inner_proof.degree_bound() != self.degree_bound {
1302            return Err(anyhow!(
1303                "wrong degree bound (got {}, want {})",
1304                inner_proof.degree_bound(),
1305                self.degree_bound
1306            ));
1307        }
1308        if inner_proof.blowup_log2() != self.options.blowup_log2 {
1309            return Err(anyhow!(
1310                "blowup factor mismatch (got {}, want {})",
1311                1usize << inner_proof.blowup_log2(),
1312                1usize << self.options.blowup_log2
1313            ));
1314        }
1315
1316        let num_gate_selectors = self
1317            .gates
1318            .iter()
1319            .map(|(_, gate_instances)| {
1320                gate_instances
1321                    .iter()
1322                    .map(|instance| instance.selector_index)
1323            })
1324            .flatten()
1325            .collect::<BTreeSet<usize>>()
1326            .len();
1327        let num_sigma_polynomials = self.num_columns;
1328        let num_witness_columns = self.num_columns;
1329        let num_permutation_accumulator_polynomial = 1usize;
1330        let num_quotient_chunks = self.get_num_quotient_chunks();
1331        let expected_polynomials = num_gate_selectors
1332            + num_sigma_polynomials
1333            + num_witness_columns
1334            + num_permutation_accumulator_polynomial
1335            + num_quotient_chunks;
1336
1337        if inner_proof.num_polys() != expected_polynomials {
1338            return Err(anyhow!(
1339                "incorrect number of committed polynomials (got {}, want {})",
1340                inner_proof.num_polys(),
1341                expected_polynomials,
1342            ));
1343        }
1344
1345        let omega = Polynomial::domain_element2(1, self.degree_bound);
1346        let omega_inv = omega.invert_vartime().unwrap();
1347
1348        let xi = H::challenge(
1349            *DST_XI,
1350            [commitment.transcript_hash(COMMIT_INDEX_QUOTIENT + 1)],
1351        );
1352
1353        let points = inner_proof.points();
1354        for rotation in get_rotation_set(self.gates.iter().map(|(constraint, _)| constraint)) {
1355            let challenge = xi
1356                * if rotation < 0 { omega_inv } else { omega }
1357                    .pow_small_vartime(rotation.unsigned_abs());
1358            if !points.contains_key(&challenge) {
1359                return Err(anyhow!(
1360                    "the proof doesn't have an opening for the required rotation {}",
1361                    rotation
1362                ));
1363            }
1364        }
1365        for &row in &self.public_rows {
1366            let z = omega.pow_small(row);
1367            if !points.contains_key(&z) {
1368                return Err(anyhow!(
1369                    "the proof doesn't have an opening for public row {row}"
1370                ));
1371            }
1372        }
1373
1374        inner_proof.verify(&commitment)?;
1375
1376        let sigma: Vec<Scalar> = {
1377            let offset = num_gate_selectors;
1378            (0..self.num_columns)
1379                .map(|i| points[&xi][offset + i])
1380                .collect()
1381        };
1382
1383        let gate_constraint: Scalar = {
1384            let selectors: Vec<Scalar> = (0..num_gate_selectors).map(|i| points[&xi][i]).collect();
1385            let delta = H::challenge(
1386                *DST_DELTA,
1387                [commitment.transcript_hash(COMMIT_INDEX_WITNESS + 1)],
1388            );
1389            let mut result = Scalar::ZERO;
1390            let mut power = Scalar::ONE;
1391            for (constraint, gate_instances) in &self.gates {
1392                for instance in gate_instances {
1393                    let constraint = constraint.clone().remap_variables(instance.column_index);
1394                    let substitution: BTreeMap<Variable, Scalar> = constraint
1395                        .get_free_variables()
1396                        .into_iter()
1397                        .map(|variable| {
1398                            let offset = num_gate_selectors + num_sigma_polynomials;
1399                            let rotation = variable.rotation();
1400                            let challenge = xi
1401                                * if rotation < 0 { omega_inv } else { omega }
1402                                    .pow_small_vartime(rotation.unsigned_abs());
1403                            (
1404                                variable,
1405                                points[&challenge][offset + variable.column_index()],
1406                            )
1407                        })
1408                        .collect();
1409                    result += selectors[instance.selector_index]
1410                        * constraint.evaluate(&substitution)
1411                        * power;
1412                    power *= delta;
1413                }
1414            }
1415            result
1416        };
1417
1418        let (permutation_accumulator, shifted_permutation_accumulator) = {
1419            let offset = num_gate_selectors + num_sigma_polynomials + num_witness_columns;
1420            (points[&xi][offset], points[&(xi * omega)][offset])
1421        };
1422
1423        let beta = H::challenge(
1424            *DST_BETA,
1425            [commitment.transcript_hash(COMMIT_INDEX_WITNESS + 1)],
1426        );
1427        let gamma = H::challenge(
1428            *DST_GAMMA,
1429            [commitment.transcript_hash(COMMIT_INDEX_WITNESS + 1)],
1430        );
1431
1432        let (permutation_numerator, permutation_denominator) = {
1433            let mut numerator = Scalar::ONE;
1434            let mut denominator = Scalar::ONE;
1435            let mut generator_power = Scalar::ONE;
1436            let offset = num_gate_selectors + num_sigma_polynomials;
1437            for column_index in 0..self.num_columns {
1438                let variable = points[&xi][offset + column_index];
1439                let sigma = sigma[column_index];
1440                numerator *= variable + beta * generator_power * xi + gamma;
1441                denominator *= variable + beta * sigma + gamma;
1442                generator_power *= Scalar::MULTIPLICATIVE_GENERATOR;
1443            }
1444            (numerator, denominator)
1445        };
1446
1447        let quotient: Scalar = {
1448            let offset = num_gate_selectors
1449                + num_sigma_polynomials
1450                + num_witness_columns
1451                + num_permutation_accumulator_polynomial;
1452            (0..num_quotient_chunks)
1453                .map(|i| points[&xi][offset + i] * xi.pow_small(i * self.degree_bound))
1454                .sum()
1455        };
1456        let zero = xi.pow_small(self.degree_bound) - Scalar::ONE;
1457
1458        let alpha = H::challenge(
1459            *DST_ALPHA,
1460            [commitment.transcript_hash(COMMIT_INDEX_PERMUTATION_ARGUMENT + 1)],
1461        );
1462
1463        let permutation_recurrence_constraint = shifted_permutation_accumulator
1464            * permutation_denominator
1465            - permutation_accumulator * permutation_numerator;
1466        let permutation_fixpoint_constraint = (permutation_accumulator - Scalar::from_const(1))
1467            * Self::lagrange0(xi, self.degree_bound);
1468
1469        let full_constraint = gate_constraint
1470            + alpha * permutation_fixpoint_constraint
1471            + alpha.square() * permutation_recurrence_constraint;
1472        if full_constraint != quotient * zero {
1473            return Err(anyhow!("constraint violation"));
1474        }
1475
1476        Ok(self
1477            .public_rows
1478            .iter()
1479            .map(|&row| {
1480                let offset = num_gate_selectors + num_sigma_polynomials;
1481                (0..self.num_columns).into_iter().map(move |column| {
1482                    let x = omega.pow_small_vartime(row);
1483                    (Cell::new(row, column), points[&x][offset + column])
1484                })
1485            })
1486            .flatten()
1487            .collect())
1488    }
1489}
1490
1491#[cfg(test)]
1492mod tests {
1493    use super::*;
1494    use crate::expr::var;
1495    use crate::witness::WitnessView;
1496    use starkom_bluesky::from_const;
1497    use starkom_pcs::hash::{Poseidon1Hash, Poseidon2Hash, Sha2Hash};
1498
1499    #[inline]
1500    fn cell(row: usize, column: usize) -> Cell {
1501        Cell::new(row, column)
1502    }
1503
1504    // This function tests the circuit from Vitalik's PLONK tutorial,
1505    // https://vitalik.eth.limo/general/2019/09/22/plonk.html#how-plonk-works.
1506    fn test_vitalik_circuit_impl<H: HashBackend<Scalar>>(
1507        canonicalize_constraints: bool,
1508        blowup_log2: usize,
1509    ) -> Result<()> {
1510        let mut builder = CircuitBuilder::default();
1511        builder.add_gate(0, (var(0) ^ 2) - var(1));
1512        builder.connect(cell(0, 0).into(), cell(1, 0).into());
1513        builder.connect(cell(0, 1).into(), cell(1, 1).into());
1514        builder.add_gate(1, var(0) * var(1) + var(0) + 5 - var(2));
1515        builder.connect(cell(0, 0).into(), cell(2, 0).into());
1516        builder.connect(cell(1, 2).into(), cell(2, 1).into());
1517        builder.add_gate(2, Constraint::nop());
1518        builder.declare_public_rows([2]);
1519        let circuit = builder.build(CompilationOptions {
1520            canonicalize_constraints,
1521        })?;
1522        assert_eq!(circuit.num_rows(), 3);
1523        assert_eq!(circuit.degree_bound(), 8);
1524        assert_eq!(circuit.num_columns(), 3);
1525        let mut witness = circuit.make_witness();
1526        assert_eq!(witness.num_rows(), 3);
1527        assert_eq!(witness.degree_bound(), 8);
1528        assert_eq!(witness.num_columns(), 3);
1529        witness.set(cell(0, 0), from_const(3));
1530        witness.set(cell(0, 1), from_const(9));
1531        witness.set(cell(1, 0), from_const(3));
1532        witness.set(cell(1, 1), from_const(9));
1533        witness.set(cell(1, 2), from_const(35));
1534        witness.set(cell(2, 0), from_const(3));
1535        witness.set(cell(2, 1), from_const(35));
1536        assert!(circuit.check_witness(&witness).is_ok());
1537        let options = ProvingOptions { blowup_log2 };
1538        let proof = circuit.prove::<H>(witness, options.clone())?;
1539        assert_eq!(proof.degree_bound(), 8);
1540        assert_eq!(proof.blowup_log2(), blowup_log2);
1541        assert_eq!(proof.extended_domain_size(), 8 << blowup_log2);
1542        assert_eq!(proof.num_polys(), 13);
1543        let public_inputs = circuit.verify(&proof, options)?;
1544        assert_eq!(public_inputs[&cell(2, 0)], from_const(3));
1545        assert_eq!(public_inputs[&cell(2, 1)], from_const(35));
1546        Ok(())
1547    }
1548
1549    #[test]
1550    fn test_vitalik_circuit_sha2_blowup_2() {
1551        assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(false, 1).is_ok());
1552        assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(true, 1).is_ok());
1553    }
1554
1555    #[test]
1556    fn test_vitalik_circuit_poseidon1_blowup_2() {
1557        assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(false, 1).is_ok());
1558        assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(true, 1).is_ok());
1559    }
1560
1561    #[test]
1562    fn test_vitalik_circuit_poseidon2_blowup_2() {
1563        assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(false, 1).is_ok());
1564        assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(true, 1).is_ok());
1565    }
1566
1567    #[test]
1568    fn test_vitalik_circuit_sha2_blowup_4() {
1569        assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(false, 2).is_ok());
1570        assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(true, 2).is_ok());
1571    }
1572
1573    #[test]
1574    fn test_vitalik_circuit_poseidon1_blowup_4() {
1575        assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(false, 2).is_ok());
1576        assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(true, 2).is_ok());
1577    }
1578
1579    #[test]
1580    fn test_vitalik_circuit_poseidon2_blowup_4() {
1581        assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(false, 2).is_ok());
1582        assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(true, 2).is_ok());
1583    }
1584
1585    #[test]
1586    fn test_vitalik_circuit_sha2_blowup_8() {
1587        assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(false, 3).is_ok());
1588        assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(true, 3).is_ok());
1589    }
1590
1591    #[test]
1592    fn test_vitalik_circuit_poseidon1_blowup_8() {
1593        assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(false, 3).is_ok());
1594        assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(true, 3).is_ok());
1595    }
1596
1597    #[test]
1598    fn test_vitalik_circuit_poseidon2_blowup_8() {
1599        assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(false, 3).is_ok());
1600        assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(true, 3).is_ok());
1601    }
1602
1603    fn test_vitalik_circuit_with_auto_gates_impl<H: HashBackend<Scalar>>(
1604        canonicalize_constraints: bool,
1605        blowup_log2: usize,
1606    ) -> Result<()> {
1607        let mut builder = CircuitBuilder::default();
1608        let [x, square] = builder.auto_gate((var(0) ^ 2) - var(1), []);
1609        let [result] = builder.auto_gate(
1610            var(0) * var(1) + var(0) + 5 - var(2),
1611            [x.into(), square.into()],
1612        );
1613        let [_, result] = builder.add_nop_gate([x.into(), result.into()]);
1614        builder.declare_public_rows([result.row()]);
1615        let circuit = builder.build(CompilationOptions {
1616            canonicalize_constraints,
1617        })?;
1618        assert_eq!(circuit.num_rows(), 3);
1619        assert_eq!(circuit.degree_bound(), 8);
1620        assert_eq!(circuit.num_columns(), 3);
1621        let mut witness = circuit.make_witness();
1622        assert_eq!(witness.num_rows(), 3);
1623        assert_eq!(witness.degree_bound(), 8);
1624        assert_eq!(witness.num_columns(), 3);
1625        let square = witness.auto_set_one(var(1), var(0) ^ 2, [from_const(3).into()]);
1626        let result = witness.auto_set_one(
1627            var(2),
1628            var(0) * var(1) + var(0) + 5,
1629            [x.into(), square.into()],
1630        );
1631        let [x, result] = witness.nop([x.into(), result.into()]);
1632        assert!(circuit.check_witness(&witness).is_ok());
1633        let options = ProvingOptions { blowup_log2 };
1634        let proof = circuit.prove::<H>(witness, options.clone())?;
1635        assert_eq!(proof.degree_bound(), 8);
1636        assert_eq!(proof.blowup_log2(), blowup_log2);
1637        assert_eq!(proof.extended_domain_size(), 8 << blowup_log2);
1638        assert_eq!(proof.num_polys(), 13);
1639        let public_inputs = circuit.verify(&proof, options)?;
1640        assert_eq!(public_inputs[&x], from_const(3));
1641        assert_eq!(public_inputs[&result], from_const(35));
1642        Ok(())
1643    }
1644
1645    #[test]
1646    fn test_vitalik_circuit_with_auto_gates_blowup_2() {
1647        assert!(test_vitalik_circuit_with_auto_gates_impl::<Sha2Hash<Scalar>>(false, 1).is_ok());
1648        assert!(test_vitalik_circuit_with_auto_gates_impl::<Sha2Hash<Scalar>>(true, 1).is_ok());
1649    }
1650
1651    #[test]
1652    fn test_vitalik_circuit_with_auto_gates_blowup_4() {
1653        assert!(test_vitalik_circuit_with_auto_gates_impl::<Sha2Hash<Scalar>>(false, 2).is_ok());
1654        assert!(test_vitalik_circuit_with_auto_gates_impl::<Sha2Hash<Scalar>>(true, 2).is_ok());
1655    }
1656
1657    /// A slight variation of Vitalik's circuit. This one proves knowledge of three numbers x, y,
1658    /// and z such that x^3 + xy + 5 = z. Valid combinations are (3, 4, 44) and (4, 3, 81).
1659    fn test_vitalik_circuit_variation_impl<H: HashBackend<Scalar>>(
1660        canonicalize_constraints: bool,
1661        blowup_log2: usize,
1662    ) -> Result<()> {
1663        let mut builder = CircuitBuilder::default();
1664        let [x, square] = builder.auto_gate((var(0) ^ 2) - var(1), []);
1665        let [y, mul] = builder.auto_gate(var(0) * var(1) - var(2), [x.into()]);
1666        let [result] = builder.auto_gate(
1667            var(0) * var(1) + var(2) + 5 - var(3),
1668            [x.into(), square.into(), mul.into()],
1669        );
1670        let [_, _, result] = builder.add_nop_gate([x.into(), y.into(), result.into()]);
1671        builder.declare_public_rows([result.row()]);
1672        let circuit = builder.build(CompilationOptions {
1673            canonicalize_constraints,
1674        })?;
1675        assert_eq!(circuit.num_rows(), 4);
1676        assert_eq!(circuit.degree_bound(), 8);
1677        assert_eq!(circuit.num_columns(), 4);
1678        let mut witness = circuit.make_witness();
1679        assert_eq!(witness.num_rows(), 4);
1680        assert_eq!(witness.degree_bound(), 8);
1681        assert_eq!(witness.num_columns(), 4);
1682        let square = witness.auto_set_one(var(1), var(0) ^ 2, [from_const(3).into()]);
1683        let mul = witness.auto_set_one(
1684            var(2),
1685            var(0) * var(1),
1686            [from_const(3).into(), from_const(4).into()],
1687        );
1688        let result = witness.auto_set_one(
1689            var(3),
1690            var(0) * var(1) + var(2) + 5,
1691            [x.into(), square.into(), mul.into()],
1692        );
1693        let [x, y, result] = witness.nop([x.into(), y.into(), result.into()]);
1694        assert!(circuit.check_witness(&witness).is_ok());
1695        let options = ProvingOptions { blowup_log2 };
1696        let proof = circuit.prove::<H>(witness, options.clone())?;
1697        assert_eq!(proof.degree_bound(), 8);
1698        assert_eq!(proof.blowup_log2(), blowup_log2);
1699        assert_eq!(proof.extended_domain_size(), 8 << blowup_log2);
1700        assert_eq!(proof.num_polys(), 17);
1701        let public_inputs = circuit.verify(&proof, options)?;
1702        assert_eq!(public_inputs[&x], from_const(3));
1703        assert_eq!(public_inputs[&y], from_const(4));
1704        assert_eq!(public_inputs[&result], from_const(44));
1705        Ok(())
1706    }
1707
1708    #[test]
1709    fn test_vitalik_circuit_variation_blowup_2() {
1710        test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(true, 1).unwrap();
1711        assert!(test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(false, 1).is_ok());
1712        assert!(test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(true, 1).is_ok());
1713    }
1714
1715    #[test]
1716    fn test_vitalik_circuit_variation_blowup_4() {
1717        assert!(test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(false, 2).is_ok());
1718        assert!(test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(true, 2).is_ok());
1719    }
1720
1721    fn build_vitalik_circuit() -> Circuit {
1722        let mut builder = CircuitBuilder::default();
1723        builder.add_gate(0, (var(0) ^ 2) - var(1));
1724        builder.connect(cell(0, 0).into(), cell(1, 0).into());
1725        builder.connect(cell(0, 1).into(), cell(1, 1).into());
1726        builder.add_gate(1, var(0) * var(1) + var(0) + 5 - var(2));
1727        builder.connect(cell(0, 0).into(), cell(2, 0).into());
1728        builder.connect(cell(1, 2).into(), cell(2, 1).into());
1729        builder.add_gate(2, Constraint::nop());
1730        builder.declare_public_rows([2]);
1731        builder
1732            .build(CompilationOptions {
1733                canonicalize_constraints: false,
1734            })
1735            .unwrap()
1736    }
1737
1738    #[test]
1739    fn test_check_witness_detects_wrong_number_of_rows() {
1740        let circuit = build_vitalik_circuit();
1741        let witness = Witness::new(4, 3, [0, 1]);
1742        let error = circuit.check_witness(&witness).unwrap_err();
1743        assert!(error.to_string().contains("wrong number of rows"));
1744    }
1745
1746    #[test]
1747    fn test_check_witness_detects_wrong_number_of_columns() {
1748        let circuit = build_vitalik_circuit();
1749        let witness = Witness::new(3, 4, [0, 1]);
1750        let error = circuit.check_witness(&witness).unwrap_err();
1751        assert!(error.to_string().contains("wrong number of columns"));
1752    }
1753
1754    #[test]
1755    fn test_check_witness_detects_wrong_degree_bound() {
1756        let circuit = build_vitalik_circuit();
1757        let witness = Witness::new(3, 3, [-2, -1, 0, 1, 2]);
1758        let error = circuit.check_witness(&witness).unwrap_err();
1759        assert!(error.to_string().contains("incorrect degree bound"));
1760    }
1761
1762    #[test]
1763    fn test_check_witness_detects_gate_constraint_violation() {
1764        let circuit = build_vitalik_circuit();
1765        let mut witness = circuit.make_witness();
1766        witness.set(cell(0, 0), from_const(3));
1767        witness.set(cell(0, 1), from_const(10));
1768        witness.set(cell(1, 0), from_const(3));
1769        witness.set(cell(1, 1), from_const(9));
1770        witness.set(cell(1, 2), from_const(35));
1771        witness.set(cell(2, 0), from_const(3));
1772        witness.set(cell(2, 1), from_const(35));
1773        let error = circuit.check_witness(&witness).unwrap_err();
1774        assert!(error.to_string().contains("gate constraint"));
1775    }
1776
1777    #[test]
1778    fn test_check_witness_detects_direct_wire_constraint_violation() {
1779        let circuit = build_vitalik_circuit();
1780        let mut witness = circuit.make_witness();
1781        witness.set(cell(0, 0), from_const(3));
1782        witness.set(cell(0, 1), from_const(9));
1783        witness.set(cell(1, 0), from_const(4));
1784        witness.set(cell(1, 1), from_const(9));
1785        witness.set(cell(1, 2), from_const(45));
1786        witness.set(cell(2, 0), from_const(3));
1787        witness.set(cell(2, 1), from_const(45));
1788        let error = circuit.check_witness(&witness).unwrap_err();
1789        assert!(error.to_string().contains("wire constraint violated"));
1790    }
1791
1792    #[test]
1793    fn test_check_witness_detects_transitive_wire_constraint_violation() {
1794        let circuit = build_vitalik_circuit();
1795        let mut witness = circuit.make_witness();
1796        witness.set(cell(0, 0), from_const(3));
1797        witness.set(cell(0, 1), from_const(9));
1798        witness.set(cell(1, 0), from_const(3));
1799        witness.set(cell(1, 1), from_const(9));
1800        witness.set(cell(1, 2), from_const(35));
1801        witness.set(cell(2, 0), from_const(99));
1802        witness.set(cell(2, 1), from_const(35));
1803        let error = circuit.check_witness(&witness).unwrap_err();
1804        assert!(error.to_string().contains("wire constraint violated"));
1805    }
1806}