Skip to main content

starkom_plonk/
plonk.rs

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