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
17pub 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
28static 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
39fn 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
60fn 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#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct CompilationOptions {
97 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#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct ProvingOptions {
122 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 pub trait CircuitViewState {
141 fn builder(&self) -> &CircuitBuilder;
143
144 fn builder_mut(&mut self) -> &mut CircuitBuilder;
146
147 fn row_offset(&self) -> usize;
152
153 fn column_offset(&self) -> usize;
158
159 fn root_cell(&self) -> Cell {
161 Cell::new(self.row_offset(), self.column_offset())
162 }
163
164 fn step_row(&mut self) -> Cell;
167
168 fn skip_rows(&mut self, n: usize);
170 }
171}
172
173pub trait CircuitView: internal::CircuitViewState {
174 fn width(&self) -> Option<usize>;
179
180 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 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 fn connect(&mut self, cell1: Option<Cell>, cell2: Option<Cell>) {
200 self.builder_mut().connect_internal(cell1, cell2);
201 }
202
203 fn skip_rows(&mut self, n: usize) {
218 internal::CircuitViewState::skip_rows(self, n);
219 }
220
221 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 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 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 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 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 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 builder: &'a mut CircuitBuilder,
376
377 row_offset: usize,
379
380 width: usize,
382
383 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#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
405struct GateInstance {
406 column_index: usize,
408
409 selector_index: usize,
411}
412
413#[derive(Debug, Default, Clone)]
415pub struct CircuitBuilder {
416 num_rows: usize,
418
419 num_columns: usize,
421
422 row_counter: usize,
424
425 gates: BTreeMap<Constraint, Vec<Cell>>,
437
438 partitioner: Partitioner,
440
441 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 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 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 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 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#[derive(Debug)]
670pub struct CircuitSectionBuilder<'a> {
671 builder: &'a mut CircuitBuilder,
673
674 row_offset: usize,
676
677 column_offset: usize,
679
680 width: usize,
682
683 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#[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 pub fn degree_bound(&self) -> usize {
767 self.inner_proof.degree_bound()
768 }
769
770 pub fn blowup_log2(&self) -> usize {
772 self.inner_proof.blowup_log2()
773 }
774
775 pub fn extended_domain_size(&self) -> usize {
777 self.inner_proof.extended_domain_size()
778 }
779
780 pub fn num_polys(&self) -> usize {
785 self.inner_proof.num_polys()
786 }
787}
788
789#[derive(Debug, Clone)]
791pub struct Circuit {
792 num_rows: usize,
797
798 num_blinding_rows: usize,
803
804 degree_bound: usize,
808
809 num_columns: usize,
811
812 selectors: Vec<Polynomial>,
820
821 gates: BTreeMap<Constraint, BTreeSet<GateInstance>>,
824
825 sigma: Vec<Polynomial>,
827
828 sigma_values: Vec<Vec<Scalar>>,
832
833 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 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 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
1226pub struct CompressedCircuit<H: HashBackend<Scalar>> {
1227 num_rows: usize,
1232
1233 num_blinding_rows: usize,
1235
1236 degree_bound: usize,
1238
1239 num_columns: usize,
1241
1242 options: ProvingOptions,
1245
1246 gates: Vec<(Constraint, Vec<GateInstance>)>,
1249
1250 public_rows: BTreeSet<usize>,
1252
1253 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 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 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 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 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}