1use crate::expr::{Constraint, Variable};
2use crate::utils::{hash_to_scalar, padded_circuit_size};
3use crate::witness::{Cell, CellOffset, Partitioner, Witness, WitnessView};
4use anyhow::{Result, anyhow};
5use primitive_types::H256;
6use starkom_bluesky::Scalar;
7use starkom_ff::{Field, PrimeField};
8use starkom_pcs::{self as pcs, hash::HashBackend};
9use starkom_poly;
10use std::collections::{BTreeMap, BTreeSet};
11use std::marker::PhantomData;
12use std::sync::LazyLock;
13
14type Polynomial = starkom_poly::Polynomial<Scalar>;
15
16pub const OPTIONS_DEFAULT_BLOWUP_LOG2: usize = 4;
20
21const COMMIT_INDEX_CIRCUIT: usize = 0;
22const COMMIT_INDEX_WITNESS: usize = 1;
23const COMMIT_INDEX_PERMUTATION_ARGUMENT: usize = 2;
24const COMMIT_INDEX_QUOTIENT: usize = 3;
25const NUM_COMMIT_INDICES: usize = 4;
26
27static DST_ALPHA: LazyLock<Scalar> =
29 LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/alpha"));
30static DST_BETA: LazyLock<Scalar> =
31 LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/beta"));
32static DST_GAMMA: LazyLock<Scalar> =
33 LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/gamma"));
34static DST_DELTA: LazyLock<Scalar> =
35 LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/delta"));
36static DST_XI: LazyLock<Scalar> = LazyLock::new(|| hash_to_scalar(b"starkom/plonk/challenge/xi"));
37
38fn get_rotation_set<'a, I: IntoIterator<Item = &'a Constraint>>(gates: I) -> BTreeSet<isize> {
44 gates
45 .into_iter()
46 .map(|constraint| {
47 constraint
48 .get_free_variables()
49 .iter()
50 .map(Variable::rotation)
51 .collect::<BTreeSet<isize>>()
52 .into_iter()
53 })
54 .flatten()
55 .chain([0, 1])
56 .collect::<BTreeSet<isize>>()
57}
58
59fn quotient_degree_bound<'a, I: Iterator<Item = &'a Constraint>>(
82 degree_bound: usize,
83 num_columns: usize,
84 gate_constraints: I,
85) -> usize {
86 let max_gate_degree = gate_constraints
87 .map(|constraint| constraint.get_degree())
88 .max()
89 .unwrap_or(0);
90 (degree_bound - 1) * std::cmp::max(max_gate_degree, num_columns)
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct CompilationOptions {
96 pub canonicalize_constraints: bool,
108}
109
110impl Default for CompilationOptions {
111 fn default() -> Self {
112 Self {
113 canonicalize_constraints: false,
114 }
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct ProvingOptions {
121 pub blowup_log2: usize,
123}
124
125impl Default for ProvingOptions {
126 fn default() -> Self {
127 Self {
128 blowup_log2: OPTIONS_DEFAULT_BLOWUP_LOG2,
129 }
130 }
131}
132
133mod internal {
134 use super::*;
135
136 pub trait CircuitViewState {
140 fn builder(&self) -> &CircuitBuilder;
142
143 fn builder_mut(&mut self) -> &mut CircuitBuilder;
145
146 fn row_offset(&self) -> usize;
151
152 fn column_offset(&self) -> usize;
157
158 fn root_cell(&self) -> Cell {
160 Cell::new(self.row_offset(), self.column_offset())
161 }
162
163 fn step_row(&mut self) -> Cell;
166
167 fn skip_rows(&mut self, n: usize);
169 }
170}
171
172pub trait CircuitView: internal::CircuitViewState {
173 fn width(&self) -> Option<usize>;
178
179 fn cell(&self, row_offset: impl CellOffset, column_offset: impl CellOffset) -> Cell {
184 let row = self.row_offset() as isize + row_offset.into_offset();
185 let column = self.column_offset() as isize + column_offset.into_offset();
186 debug_assert!(row >= 0);
187 debug_assert!(column >= 0);
188 Cell::new(row as usize, column as usize)
189 }
190
191 fn add_gate(&mut self, row: usize, constraint: Constraint) {
193 let root_cell = self.cell(row, 0);
194 self.builder_mut().add_gate_internal(root_cell, constraint);
195 }
196
197 fn connect(&mut self, cell1: Option<Cell>, cell2: Option<Cell>) {
199 self.builder_mut().connect_internal(cell1, cell2);
200 }
201
202 fn skip_rows(&mut self, n: usize) {
217 internal::CircuitViewState::skip_rows(self, n);
218 }
219
220 fn auto_gate<const N: usize, const M: usize>(
238 &mut self,
239 constraint: Constraint,
240 inputs: [Option<Cell>; N],
241 ) -> [Cell; M] {
242 let variables: Vec<Variable> = constraint.get_free_variables().into_iter().collect();
243 assert_eq!(variables.len(), N + M);
244
245 let root_cell = self.step_row();
246
247 for i in 0..N {
248 if let Some(input) = inputs[i] {
249 self.builder_mut()
250 .connect_internal(Some(input), Some(variables[i].map_to_cell(root_cell)));
251 }
252 }
253
254 self.builder_mut().add_gate_internal(root_cell, constraint);
255
256 std::array::from_fn(|i| variables[N + i].map_to_cell(root_cell))
257 }
258
259 fn auto_constraint<const N: usize>(
277 &mut self,
278 constraint: Constraint,
279 inputs: [Option<Cell>; N],
280 ) -> [Cell; N] {
281 let variables: Vec<Variable> = constraint.get_free_variables().into_iter().collect();
282 assert_eq!(variables.len(), N);
283
284 let root_cell = self.step_row();
285
286 for i in 0..N {
287 if let Some(input) = inputs[i] {
288 self.connect(Some(input), Some(variables[i].map_to_cell(root_cell)));
289 }
290 }
291
292 self.builder_mut().add_gate_internal(root_cell, constraint);
293
294 std::array::from_fn(|i| variables[i].map_to_cell(root_cell))
295 }
296
297 fn add_nop_gate<const N: usize>(&mut self, inputs: [Option<Cell>; N]) -> [Cell; N] {
308 let root_cell = self.step_row();
309 let outputs = std::array::from_fn(|i| Cell::new(0, i).remap(root_cell));
310
311 for i in 0..N {
312 if let Some(input) = inputs[i] {
313 self.connect(Some(input), Some(outputs[i]));
314 }
315 }
316
317 self.builder_mut()
318 .add_gate_internal(root_cell, Constraint::nop());
319
320 outputs
321 }
322
323 fn sub(&mut self, row_offset: usize, column_offset: usize, width: usize) -> impl CircuitView {
325 let row_offset = self.row_offset() + row_offset;
326 let column_offset = self.column_offset() + column_offset;
327 CircuitSectionBuilder::new(self.builder_mut(), row_offset, column_offset, width)
328 }
329
330 fn sub_fn(
336 &mut self,
337 row_offset: usize,
338 column_offset: usize,
339 width: usize,
340 callback: impl FnOnce(&mut CircuitSectionBuilder),
341 ) -> &mut Self {
342 let row_offset = self.row_offset() + row_offset;
343 let column_offset = self.column_offset() + column_offset;
344 callback(&mut CircuitSectionBuilder::new(
345 self.builder_mut(),
346 row_offset,
347 column_offset,
348 width,
349 ));
350 self
351 }
352
353 fn auto_sub<'a>(&'a mut self, width: usize, count: usize) -> CircuitViewGenerator<'a>;
354}
355
356#[derive(Debug)]
357pub struct CircuitViewGenerator<'a> {
358 builder: &'a mut CircuitBuilder,
360
361 row_offset: usize,
363
364 width: usize,
366
367 count: usize,
369}
370
371impl<'a> CircuitViewGenerator<'a> {
372 pub fn get(&'a mut self, index: usize) -> CircuitSectionBuilder<'a> {
373 assert!(index < self.count);
374 CircuitSectionBuilder::new(
375 self.builder,
376 self.row_offset,
377 self.width * index,
378 self.width,
379 )
380 }
381}
382
383#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
389struct GateInstance {
390 column_index: usize,
392
393 selector_index: usize,
395}
396
397#[derive(Debug, Default, Clone)]
399pub struct CircuitBuilder {
400 num_rows: usize,
402
403 num_columns: usize,
405
406 row_counter: usize,
408
409 gates: BTreeMap<Constraint, Vec<Cell>>,
421
422 partitioner: Partitioner,
424
425 public_rows: BTreeSet<usize>,
427}
428
429impl CircuitBuilder {
430 fn add_gate_internal(&mut self, root_cell: Cell, constraint: Constraint) {
431 let row = root_cell.row();
432 let column = root_cell.column();
433 {
434 self.num_rows = std::cmp::max(self.num_rows, row + 1);
435 self.num_columns = std::cmp::max(self.num_columns, column + 1);
436 for variable in constraint.get_free_variables() {
437 let rotation = variable.rotation();
438 self.num_rows = std::cmp::max(
439 self.num_rows,
440 if rotation < 0 {
441 assert!(rotation.unsigned_abs() <= row);
442 row - rotation.unsigned_abs()
443 } else {
444 row + rotation.unsigned_abs()
445 } + 1,
446 );
447 self.num_columns =
448 std::cmp::max(self.num_columns, column + variable.column_index() + 1);
449 }
450 }
451 self.gates.entry(constraint).or_default().push(root_cell);
452 }
453
454 fn connect_internal(&mut self, cell1: Option<Cell>, cell2: Option<Cell>) {
455 match (cell1, cell2) {
456 (Some(cell1), Some(cell2)) => {
457 self.partitioner.connect(cell1, cell2);
458 }
459 _ => {}
460 }
461 }
462
463 pub fn declare_public_rows<I: IntoIterator<Item = usize>>(&mut self, gates: I) {
471 self.public_rows = BTreeSet::from_iter(gates);
472 }
473
474 fn make_selector(degree_bound: usize, activation_row_set: BTreeSet<usize>) -> Polynomial {
475 let mut selector_values = vec![Scalar::ZERO; degree_bound];
476 for row in activation_row_set {
477 selector_values[row] = Scalar::ONE;
478 }
479 Polynomial::encode2(selector_values)
480 }
481
482 fn build_gates_and_selectors(
483 &self,
484 degree_bound: usize,
485 ) -> (
486 BTreeMap<Constraint, BTreeSet<GateInstance>>,
487 Vec<Polynomial>,
488 ) {
489 let mut row_set_map: BTreeMap<(Constraint, usize), BTreeSet<usize>> = BTreeMap::default();
491 for (constraint, root_cells) in &self.gates {
492 for root_cell in root_cells.as_slice() {
493 let key = (constraint.clone(), root_cell.column());
494 let row = root_cell.row();
495 row_set_map.entry(key).or_default().insert(row);
496 }
497 }
498
499 let mut gates_by_row_set: BTreeMap<BTreeSet<usize>, Vec<(Constraint, usize)>> =
502 BTreeMap::default();
503 for ((constraint, column_index), row_set) in row_set_map {
504 gates_by_row_set
505 .entry(row_set)
506 .or_default()
507 .push((constraint, column_index));
508 }
509
510 let mut gates: BTreeMap<Constraint, BTreeSet<GateInstance>> = BTreeMap::default();
511 let mut selectors: Vec<Polynomial> = vec![];
512
513 for (selector_index, (activation_row_set, gate_instances)) in
514 gates_by_row_set.into_iter().enumerate()
515 {
516 selectors.push(Self::make_selector(degree_bound, activation_row_set));
517 for (constraint, column_index) in gate_instances {
518 gates.entry(constraint).or_default().insert(GateInstance {
519 column_index,
520 selector_index,
521 });
522 }
523 }
524
525 (gates, selectors)
526 }
527
528 pub fn build(mut self, options: CompilationOptions) -> Result<Circuit> {
530 if options.canonicalize_constraints {
531 let mut old_gates: BTreeMap<Constraint, Vec<Cell>> = BTreeMap::default();
532 std::mem::swap(&mut self.gates, &mut old_gates);
533 for (constraint, mut root_cells) in old_gates {
534 self.gates
535 .entry(constraint.canonicalize())
536 .or_default()
537 .append(&mut root_cells);
538 }
539 } else {
540 for (constraint, _) in &self.gates {
541 if !constraint.is_canonical() {
542 return Err(anyhow!("constraint `{}` is not canonical", constraint));
543 }
544 }
545 }
546
547 let (degree_bound, num_blinding_rows) = padded_circuit_size(
548 self.num_rows,
549 self.gates.iter().flat_map(|(constraint, _)| {
550 constraint
551 .get_free_variables()
552 .iter()
553 .map(Variable::rotation)
554 .collect::<BTreeSet<isize>>()
555 }),
556 );
557
558 let (gates, selectors) = Self::build_gates_and_selectors(&self, degree_bound);
559
560 let sigma_values: Vec<Vec<Scalar>> = {
561 let mut sigma = vec![Scalar::ZERO; degree_bound * self.num_columns];
562 let omega = Polynomial::domain_element2(1, degree_bound);
563 let mut k = Scalar::ONE;
564 for i in 0..self.num_columns {
565 let offset = i * degree_bound;
566 sigma[offset] = k;
567 for j in 1..degree_bound {
568 sigma[offset + j] = sigma[offset + j - 1] * omega;
569 }
570 k *= Scalar::MULTIPLICATIVE_GENERATOR;
571 }
572 for node in self.partitioner.iter_nodes() {
573 let indices: Vec<usize> = node
574 .iter()
575 .map(|cell| cell.column() * degree_bound + cell.row())
576 .collect();
577 let mut permuted: Vec<Scalar> = indices.iter().map(|&i| sigma[i]).collect();
578 permuted.rotate_left(1);
579 for i in 0..indices.len() {
580 sigma[indices[i]] = permuted[i];
581 }
582 }
583 sigma
584 .chunks_exact(degree_bound)
585 .map(|chunk| chunk.to_vec())
586 .collect()
587 };
588
589 let sigma = sigma_values
590 .iter()
591 .map(|chunk| Polynomial::encode2(chunk.to_vec()))
592 .collect();
593
594 Ok(Circuit {
595 num_rows: self.num_rows,
596 num_blinding_rows,
597 degree_bound,
598 num_columns: self.num_columns,
599 selectors,
600 gates,
601 sigma,
602 sigma_values,
603 public_rows: self.public_rows,
604 })
605 }
606}
607
608impl internal::CircuitViewState for CircuitBuilder {
609 fn builder(&self) -> &CircuitBuilder {
610 self
611 }
612
613 fn builder_mut(&mut self) -> &mut CircuitBuilder {
614 self
615 }
616
617 fn row_offset(&self) -> usize {
618 0
619 }
620
621 fn column_offset(&self) -> usize {
622 0
623 }
624
625 fn step_row(&mut self) -> Cell {
626 let root_cell = self.cell(self.row_counter, 0);
627 self.row_counter += 1;
628 root_cell
629 }
630
631 fn skip_rows(&mut self, n: usize) {
632 self.row_counter += n;
633 }
634}
635
636impl CircuitView for CircuitBuilder {
637 fn width(&self) -> Option<usize> {
638 None
639 }
640
641 fn auto_sub<'a>(&'a mut self, width: usize, count: usize) -> CircuitViewGenerator<'a> {
642 let row_offset = self.row_counter;
643 CircuitViewGenerator {
644 builder: self,
645 row_offset,
646 width,
647 count,
648 }
649 }
650}
651
652#[derive(Debug)]
654pub struct CircuitSectionBuilder<'a> {
655 builder: &'a mut CircuitBuilder,
657
658 row_offset: usize,
660
661 column_offset: usize,
663
664 width: usize,
666
667 row_counter: usize,
669}
670
671impl<'a> CircuitSectionBuilder<'a> {
672 fn new(
673 builder: &'a mut CircuitBuilder,
674 row_offset: usize,
675 column_offset: usize,
676 width: usize,
677 ) -> Self {
678 Self {
679 builder,
680 row_offset,
681 column_offset,
682 width,
683 row_counter: 0,
684 }
685 }
686}
687
688impl<'a> internal::CircuitViewState for CircuitSectionBuilder<'a> {
689 fn builder(&self) -> &CircuitBuilder {
690 self.builder
691 }
692
693 fn builder_mut(&mut self) -> &mut CircuitBuilder {
694 self.builder
695 }
696
697 fn row_offset(&self) -> usize {
698 self.row_offset
699 }
700
701 fn column_offset(&self) -> usize {
702 self.column_offset
703 }
704
705 fn step_row(&mut self) -> Cell {
706 let root_cell = self.cell(self.row_counter, 0);
707 self.row_counter += 1;
708 root_cell
709 }
710
711 fn skip_rows(&mut self, n: usize) {
712 self.row_counter += n;
713 }
714}
715
716impl<'a> CircuitView for CircuitSectionBuilder<'a> {
717 fn width(&self) -> Option<usize> {
718 Some(self.width)
719 }
720
721 fn auto_sub<'b>(&'b mut self, width: usize, count: usize) -> CircuitViewGenerator<'b> {
722 let row_offset = self.row_offset + self.row_counter;
723 CircuitViewGenerator {
724 builder: self.builder,
725 row_offset,
726 width,
727 count,
728 }
729 }
730}
731
732impl<'a> Drop for CircuitSectionBuilder<'a> {
733 fn drop(&mut self) {
734 self.builder.row_counter =
735 std::cmp::max(self.builder.row_counter, self.row_offset + self.row_counter);
736 }
737}
738
739#[derive(Debug, Clone)]
743pub struct Proof<H: HashBackend<Scalar>> {
744 commitment: pcs::Commitment<H>,
745 inner_proof: pcs::Proof<H>,
746}
747
748impl<H: HashBackend<Scalar>> Proof<H> {
749 pub fn degree_bound(&self) -> usize {
751 self.inner_proof.degree_bound()
752 }
753
754 pub fn blowup_log2(&self) -> usize {
756 self.inner_proof.blowup_log2()
757 }
758
759 pub fn extended_domain_size(&self) -> usize {
761 self.inner_proof.extended_domain_size()
762 }
763
764 pub fn num_polys(&self) -> usize {
769 self.inner_proof.num_polys()
770 }
771}
772
773#[derive(Debug, Clone)]
775pub struct Circuit {
776 num_rows: usize,
781
782 num_blinding_rows: usize,
787
788 degree_bound: usize,
792
793 num_columns: usize,
795
796 selectors: Vec<Polynomial>,
804
805 gates: BTreeMap<Constraint, BTreeSet<GateInstance>>,
808
809 sigma: Vec<Polynomial>,
811
812 sigma_values: Vec<Vec<Scalar>>,
816
817 public_rows: BTreeSet<usize>,
819}
820
821impl Circuit {
822 pub fn num_rows(&self) -> usize {
823 self.num_rows
824 }
825
826 pub fn degree_bound(&self) -> usize {
827 self.degree_bound
828 }
829
830 pub fn num_columns(&self) -> usize {
831 self.num_columns
832 }
833
834 pub fn public_rows(&self) -> &BTreeSet<usize> {
835 &self.public_rows
836 }
837
838 pub fn make_witness(&self) -> Witness {
840 Witness::new(
841 self.num_rows,
842 self.num_columns,
843 self.gates.iter().flat_map(|(constraint, _)| {
844 constraint
845 .get_free_variables()
846 .iter()
847 .map(Variable::rotation)
848 .collect::<BTreeSet<isize>>()
849 }),
850 )
851 }
852
853 pub fn check_witness(&self, witness: &Witness) -> Result<()> {
856 if witness.num_rows() != self.num_rows {
857 return Err(anyhow!(
858 "wrong number of rows: got {}, want {}",
859 witness.num_rows(),
860 self.num_rows
861 ));
862 }
863 if witness.num_columns() != self.num_columns {
864 return Err(anyhow!(
865 "wrong number of columns: got {}, want {}",
866 witness.num_columns(),
867 self.num_columns
868 ));
869 }
870 if witness.degree_bound() != self.degree_bound {
871 return Err(anyhow!(
872 "incorrect degree bound: got {}, want {}",
873 witness.degree_bound(),
874 self.degree_bound
875 ));
876 }
877
878 let active_row_set: Vec<BTreeSet<usize>> = self
879 .selectors
880 .iter()
881 .map(|selector| {
882 selector
883 .clone()
884 .decode2()
885 .into_iter()
886 .enumerate()
887 .filter(|(_, value)| *value != Scalar::ZERO)
888 .map(|(index, _)| index)
889 .collect()
890 })
891 .collect();
892 for (constraint, gate_instances) in &self.gates {
893 for gate_instance in gate_instances {
894 let variables = constraint.get_free_variables();
895 for &row in &active_row_set[gate_instance.selector_index] {
896 let root_cell = Cell::new(row, gate_instance.column_index);
897 let substitution: BTreeMap<Variable, Scalar> = variables
898 .iter()
899 .map(|variable| {
900 (
901 variable.clone(),
902 witness.get(variable.map_to_cell(root_cell)),
903 )
904 })
905 .collect();
906 if constraint.evaluate(&substitution) != Scalar::ZERO {
907 return Err(anyhow!(
908 "gate constraint `{}` violated at row {}, column {}",
909 constraint,
910 row,
911 gate_instance.column_index
912 ));
913 }
914 }
915 }
916 }
917
918 let cell_by_identity_value: BTreeMap<Scalar, Cell> = {
919 let mut cell_by_identity_value = BTreeMap::default();
920 let omega = Polynomial::domain_element2(1, self.degree_bound);
921 let mut generator_power = Scalar::ONE;
922 for column in 0..self.num_columns {
923 let mut omega_power = Scalar::ONE;
924 for row in 0..self.degree_bound {
925 cell_by_identity_value
926 .insert(generator_power * omega_power, Cell::new(row, column));
927 omega_power *= omega;
928 }
929 generator_power *= Scalar::MULTIPLICATIVE_GENERATOR;
930 }
931 cell_by_identity_value
932 };
933 for column in 0..self.num_columns {
934 for row in 0..self.num_rows {
935 let source_cell = Cell::new(row, column);
936 let target_cell = *cell_by_identity_value
937 .get(&self.sigma_values[column][row])
938 .unwrap();
939 let source_value = witness.get(source_cell);
940 let target_value = witness.get(target_cell);
941 if source_value != target_value {
942 return Err(anyhow!(
943 "wire constraint violated: cell({}, {}) = {}, cell({}, {}) = {}",
944 source_cell.row(),
945 source_cell.column(),
946 source_value,
947 target_cell.row(),
948 target_cell.column(),
949 target_value,
950 ));
951 }
952 }
953 }
954
955 Ok(())
956 }
957
958 fn build_permutation_argument(
962 &self,
963 witness: &Witness,
964 columns: &[Polynomial],
965 beta: Scalar,
966 gamma: Scalar,
967 ) -> Result<(Polynomial, Polynomial, Polynomial)> {
968 let omega = Polynomial::domain_element2(1, self.degree_bound);
969
970 let accumulator = {
971 let mut accumulator = vec![Scalar::ZERO; self.degree_bound + 1];
972
973 accumulator[0] = Scalar::ONE;
974 let mut omega_power = Scalar::ONE;
975 for i in 0..self.degree_bound {
976 let mut generator_power = Scalar::ONE;
977 accumulator[i + 1] = accumulator[i];
978 for j in 0..self.num_columns {
979 let witness_value = witness.get(Cell::new(i, j));
980 accumulator[i + 1] *=
981 witness_value + beta * generator_power * omega_power + gamma;
982 accumulator[i + 1] *=
983 (witness_value + beta * self.sigma_values[j][i] + gamma).invert_unwrap();
984 generator_power *= Scalar::MULTIPLICATIVE_GENERATOR;
985 }
986 omega_power *= omega;
987 }
988
989 if accumulator.pop().unwrap() != Scalar::ONE {
990 return Err(anyhow!("permutation accumulator wraparound check failed"));
991 }
992
993 Polynomial::encode2(accumulator)
994 };
995
996 let shifted = accumulator.clone().shift_domain_by(omega);
997
998 let recurrence_constraint = {
999 let mut lhs = shifted;
1000 for (column, sigma) in columns.iter().zip(self.sigma.iter()) {
1001 lhs *= column.clone() + sigma.clone() * beta + gamma;
1002 }
1003 let mut rhs = accumulator.clone();
1004 let mut power = Scalar::ONE;
1005 for column in columns {
1006 rhs *= column.clone() + Polynomial::with_coefficients(vec![gamma, beta * power]);
1007 power *= Scalar::MULTIPLICATIVE_GENERATOR;
1008 }
1009 lhs - rhs
1010 };
1011
1012 let fixpoint_constraint =
1013 (accumulator.clone() - Scalar::ONE) * Polynomial::lagrange0(self.degree_bound).clone();
1014
1015 Ok((accumulator, fixpoint_constraint, recurrence_constraint))
1016 }
1017
1018 fn split_quotient(&self, quotient: Polynomial) -> Vec<Polynomial> {
1021 let degree_bound = quotient_degree_bound(
1022 self.degree_bound,
1023 self.num_columns,
1024 self.gates.iter().map(|(constraint, _)| constraint),
1025 );
1026 let mut coefficients = quotient.take();
1027 assert!(coefficients.len() <= degree_bound);
1028 coefficients.resize(degree_bound, Scalar::ZERO);
1029 coefficients
1030 .chunks(self.degree_bound)
1031 .map(|coefficients| Polynomial::with_coefficients(coefficients.to_vec()))
1032 .collect()
1033 }
1034
1035 pub fn prove<H: HashBackend<Scalar>>(
1038 &self,
1039 mut witness: Witness,
1040 options: ProvingOptions,
1041 ) -> Result<Proof<H>> {
1042 witness.blind();
1043 if witness.num_rows() != self.num_rows {
1044 return Err(anyhow!(
1045 "incorrect witness size (got {} rows, want {})",
1046 witness.num_rows(),
1047 self.num_rows
1048 ));
1049 }
1050 if witness.degree_bound() != self.degree_bound {
1051 return Err(anyhow!(
1052 "incorrect witness degree bound (got {}, want {})",
1053 witness.degree_bound(),
1054 self.degree_bound
1055 ));
1056 }
1057 if witness.num_columns() != self.num_columns {
1058 return Err(anyhow!(
1059 "incorrect witness size (got {} columns, want {})",
1060 witness.num_columns(),
1061 self.num_columns
1062 ));
1063 }
1064
1065 let circuit_polynomials = self
1066 .selectors
1067 .iter()
1068 .cloned()
1069 .chain(self.sigma.iter().cloned())
1070 .collect();
1071
1072 let mut committer =
1073 pcs::Committer::<H>::new(self.degree_bound, options.blowup_log2, circuit_polynomials);
1074
1075 let columns = witness.clone().encode();
1076 committer.add_batch(columns.clone());
1077
1078 let omega = Polynomial::domain_element2(1, self.degree_bound);
1079
1080 let gate_constraint = {
1081 let delta = H::challenge(*DST_DELTA, [committer.transcript_hash()]);
1082 let mut gate_constraint = Polynomial::default();
1083 let mut power = Scalar::ONE;
1084 for (constraint, instances) in &self.gates {
1085 for instance in instances {
1086 let constraint = constraint.clone().remap_variables(instance.column_index);
1087 let selector = self.selectors[instance.selector_index].clone();
1088 gate_constraint +=
1089 selector * constraint.compose(omega, columns.as_slice()) * power;
1090 power *= delta;
1091 }
1092 }
1093 gate_constraint
1094 };
1095
1096 let (
1097 permutation_accumulator,
1098 permutation_fixpoint_constraint,
1099 permutation_recurrence_constraint,
1100 ) = {
1101 let beta = H::challenge(*DST_BETA, [committer.transcript_hash()]);
1102 let gamma = H::challenge(*DST_GAMMA, [committer.transcript_hash()]);
1103 self.build_permutation_argument(&witness, columns.as_slice(), beta, gamma)?
1104 };
1105 committer.add_batch(vec![permutation_accumulator]);
1106
1107 let alpha = H::challenge(*DST_ALPHA, [committer.transcript_hash()]);
1108
1109 let quotient = (gate_constraint
1110 + permutation_fixpoint_constraint * alpha
1111 + permutation_recurrence_constraint * alpha.square())
1112 .divide_by_zero(self.degree_bound)?;
1113 committer.add_batch(self.split_quotient(quotient));
1114
1115 let xi = H::challenge(*DST_XI, [committer.transcript_hash()]);
1116
1117 let omega_inv = omega.invert_unwrap();
1118 let (commitment, prover) = committer.commit(BTreeSet::from_iter(
1119 get_rotation_set(self.gates.iter().map(|(constraint, _)| constraint))
1120 .into_iter()
1121 .map(|rotation| {
1122 xi * if rotation < 0 { omega_inv } else { omega }
1123 .pow_small(rotation.unsigned_abs())
1124 })
1125 .chain(self.public_rows.iter().map(|&row| omega.pow_small(row))),
1126 ));
1127 let inner_proof = prover.prove(&commitment);
1128
1129 Ok(Proof {
1130 commitment,
1131 inner_proof,
1132 })
1133 }
1134
1135 pub fn to_compressed<H: HashBackend<Scalar>>(
1136 self,
1137 options: ProvingOptions,
1138 ) -> CompressedCircuit<H> {
1139 let committer = pcs::Committer::<H>::new(
1140 self.degree_bound,
1141 options.blowup_log2,
1142 self.selectors
1143 .into_iter()
1144 .chain(self.sigma.into_iter())
1145 .collect(),
1146 );
1147 CompressedCircuit {
1148 num_rows: self.num_rows,
1149 num_blinding_rows: self.num_blinding_rows,
1150 degree_bound: self.degree_bound,
1151 num_columns: self.num_columns,
1152 options,
1153 gates: self
1154 .gates
1155 .into_iter()
1156 .map(|(constraint, instances)| (constraint, instances.into_iter().collect()))
1157 .collect(),
1158 public_rows: self.public_rows,
1159 circuit_commitment: committer.root_hash(COMMIT_INDEX_CIRCUIT),
1160 _data: Default::default(),
1161 }
1162 }
1163
1164 pub fn as_compressed<H: HashBackend<Scalar>>(
1165 &self,
1166 options: ProvingOptions,
1167 ) -> CompressedCircuit<H> {
1168 let committer = pcs::Committer::<H>::new(
1169 self.degree_bound,
1170 options.blowup_log2,
1171 self.selectors
1172 .iter()
1173 .cloned()
1174 .chain(self.sigma.iter().cloned())
1175 .collect(),
1176 );
1177 CompressedCircuit {
1178 num_rows: self.num_rows,
1179 num_blinding_rows: self.num_blinding_rows,
1180 degree_bound: self.degree_bound,
1181 num_columns: self.num_columns,
1182 options,
1183 gates: self
1184 .gates
1185 .iter()
1186 .map(|(constraint, instances)| {
1187 (constraint.clone(), instances.iter().cloned().collect())
1188 })
1189 .collect(),
1190 public_rows: self.public_rows.clone(),
1191 circuit_commitment: committer.root_hash(COMMIT_INDEX_CIRCUIT),
1192 _data: Default::default(),
1193 }
1194 }
1195
1196 pub fn verify<H: HashBackend<Scalar>>(
1197 &self,
1198 proof: &Proof<H>,
1199 options: ProvingOptions,
1200 ) -> Result<BTreeMap<Cell, Scalar>> {
1201 self.as_compressed::<H>(options).verify(proof)
1202 }
1203}
1204
1205#[derive(Debug, Clone, PartialEq, Eq)]
1210pub struct CompressedCircuit<H: HashBackend<Scalar>> {
1211 num_rows: usize,
1216
1217 num_blinding_rows: usize,
1219
1220 degree_bound: usize,
1222
1223 num_columns: usize,
1225
1226 options: ProvingOptions,
1229
1230 gates: Vec<(Constraint, Vec<GateInstance>)>,
1233
1234 public_rows: BTreeSet<usize>,
1236
1237 circuit_commitment: H256,
1239
1240 _data: PhantomData<H>,
1241}
1242
1243impl<H: HashBackend<Scalar>> CompressedCircuit<H> {
1244 pub fn num_rows(&self) -> usize {
1245 self.num_rows
1246 }
1247
1248 pub fn degree_bound(&self) -> usize {
1249 self.degree_bound
1250 }
1251
1252 pub fn num_columns(&self) -> usize {
1253 self.num_columns
1254 }
1255
1256 pub fn public_rows(&self) -> &BTreeSet<usize> {
1257 &self.public_rows
1258 }
1259
1260 fn get_num_quotient_chunks(&self) -> usize {
1264 quotient_degree_bound(
1265 self.degree_bound,
1266 self.num_columns,
1267 self.gates.iter().map(|(constraint, _)| constraint),
1268 )
1269 .div_ceil(self.degree_bound)
1270 }
1271
1272 fn lagrange0(x: Scalar, n: usize) -> Scalar {
1273 (x.pow_small(n) - Scalar::ONE)
1274 * (Scalar::from(n as u64) * (x - Scalar::ONE)).invert_unwrap()
1275 }
1276
1277 pub fn verify(&self, proof: &Proof<H>) -> Result<BTreeMap<Cell, Scalar>> {
1283 let commitment = &proof.commitment;
1284 let inner_proof = &proof.inner_proof;
1285
1286 if commitment.tree_roots().len() != NUM_COMMIT_INDICES {
1287 return Err(anyhow!(
1288 "wrong number of Merkle roots (got {}, want {})",
1289 commitment.tree_roots().len(),
1290 NUM_COMMIT_INDICES
1291 ));
1292 }
1293 if commitment.tree_roots()[COMMIT_INDEX_CIRCUIT] != self.circuit_commitment {
1294 return Err(anyhow!(
1295 "wrong circuit commitment (got {}, want {})",
1296 commitment.tree_roots()[COMMIT_INDEX_CIRCUIT],
1297 self.circuit_commitment
1298 ));
1299 }
1300
1301 if inner_proof.degree_bound() != self.degree_bound {
1302 return Err(anyhow!(
1303 "wrong degree bound (got {}, want {})",
1304 inner_proof.degree_bound(),
1305 self.degree_bound
1306 ));
1307 }
1308 if inner_proof.blowup_log2() != self.options.blowup_log2 {
1309 return Err(anyhow!(
1310 "blowup factor mismatch (got {}, want {})",
1311 1usize << inner_proof.blowup_log2(),
1312 1usize << self.options.blowup_log2
1313 ));
1314 }
1315
1316 let num_gate_selectors = self
1317 .gates
1318 .iter()
1319 .map(|(_, gate_instances)| {
1320 gate_instances
1321 .iter()
1322 .map(|instance| instance.selector_index)
1323 })
1324 .flatten()
1325 .collect::<BTreeSet<usize>>()
1326 .len();
1327 let num_sigma_polynomials = self.num_columns;
1328 let num_witness_columns = self.num_columns;
1329 let num_permutation_accumulator_polynomial = 1usize;
1330 let num_quotient_chunks = self.get_num_quotient_chunks();
1331 let expected_polynomials = num_gate_selectors
1332 + num_sigma_polynomials
1333 + num_witness_columns
1334 + num_permutation_accumulator_polynomial
1335 + num_quotient_chunks;
1336
1337 if inner_proof.num_polys() != expected_polynomials {
1338 return Err(anyhow!(
1339 "incorrect number of committed polynomials (got {}, want {})",
1340 inner_proof.num_polys(),
1341 expected_polynomials,
1342 ));
1343 }
1344
1345 let omega = Polynomial::domain_element2(1, self.degree_bound);
1346 let omega_inv = omega.invert_vartime().unwrap();
1347
1348 let xi = H::challenge(
1349 *DST_XI,
1350 [commitment.transcript_hash(COMMIT_INDEX_QUOTIENT + 1)],
1351 );
1352
1353 let points = inner_proof.points();
1354 for rotation in get_rotation_set(self.gates.iter().map(|(constraint, _)| constraint)) {
1355 let challenge = xi
1356 * if rotation < 0 { omega_inv } else { omega }
1357 .pow_small_vartime(rotation.unsigned_abs());
1358 if !points.contains_key(&challenge) {
1359 return Err(anyhow!(
1360 "the proof doesn't have an opening for the required rotation {}",
1361 rotation
1362 ));
1363 }
1364 }
1365 for &row in &self.public_rows {
1366 let z = omega.pow_small(row);
1367 if !points.contains_key(&z) {
1368 return Err(anyhow!(
1369 "the proof doesn't have an opening for public row {row}"
1370 ));
1371 }
1372 }
1373
1374 inner_proof.verify(&commitment)?;
1375
1376 let sigma: Vec<Scalar> = {
1377 let offset = num_gate_selectors;
1378 (0..self.num_columns)
1379 .map(|i| points[&xi][offset + i])
1380 .collect()
1381 };
1382
1383 let gate_constraint: Scalar = {
1384 let selectors: Vec<Scalar> = (0..num_gate_selectors).map(|i| points[&xi][i]).collect();
1385 let delta = H::challenge(
1386 *DST_DELTA,
1387 [commitment.transcript_hash(COMMIT_INDEX_WITNESS + 1)],
1388 );
1389 let mut result = Scalar::ZERO;
1390 let mut power = Scalar::ONE;
1391 for (constraint, gate_instances) in &self.gates {
1392 for instance in gate_instances {
1393 let constraint = constraint.clone().remap_variables(instance.column_index);
1394 let substitution: BTreeMap<Variable, Scalar> = constraint
1395 .get_free_variables()
1396 .into_iter()
1397 .map(|variable| {
1398 let offset = num_gate_selectors + num_sigma_polynomials;
1399 let rotation = variable.rotation();
1400 let challenge = xi
1401 * if rotation < 0 { omega_inv } else { omega }
1402 .pow_small_vartime(rotation.unsigned_abs());
1403 (
1404 variable,
1405 points[&challenge][offset + variable.column_index()],
1406 )
1407 })
1408 .collect();
1409 result += selectors[instance.selector_index]
1410 * constraint.evaluate(&substitution)
1411 * power;
1412 power *= delta;
1413 }
1414 }
1415 result
1416 };
1417
1418 let (permutation_accumulator, shifted_permutation_accumulator) = {
1419 let offset = num_gate_selectors + num_sigma_polynomials + num_witness_columns;
1420 (points[&xi][offset], points[&(xi * omega)][offset])
1421 };
1422
1423 let beta = H::challenge(
1424 *DST_BETA,
1425 [commitment.transcript_hash(COMMIT_INDEX_WITNESS + 1)],
1426 );
1427 let gamma = H::challenge(
1428 *DST_GAMMA,
1429 [commitment.transcript_hash(COMMIT_INDEX_WITNESS + 1)],
1430 );
1431
1432 let (permutation_numerator, permutation_denominator) = {
1433 let mut numerator = Scalar::ONE;
1434 let mut denominator = Scalar::ONE;
1435 let mut generator_power = Scalar::ONE;
1436 let offset = num_gate_selectors + num_sigma_polynomials;
1437 for column_index in 0..self.num_columns {
1438 let variable = points[&xi][offset + column_index];
1439 let sigma = sigma[column_index];
1440 numerator *= variable + beta * generator_power * xi + gamma;
1441 denominator *= variable + beta * sigma + gamma;
1442 generator_power *= Scalar::MULTIPLICATIVE_GENERATOR;
1443 }
1444 (numerator, denominator)
1445 };
1446
1447 let quotient: Scalar = {
1448 let offset = num_gate_selectors
1449 + num_sigma_polynomials
1450 + num_witness_columns
1451 + num_permutation_accumulator_polynomial;
1452 (0..num_quotient_chunks)
1453 .map(|i| points[&xi][offset + i] * xi.pow_small(i * self.degree_bound))
1454 .sum()
1455 };
1456 let zero = xi.pow_small(self.degree_bound) - Scalar::ONE;
1457
1458 let alpha = H::challenge(
1459 *DST_ALPHA,
1460 [commitment.transcript_hash(COMMIT_INDEX_PERMUTATION_ARGUMENT + 1)],
1461 );
1462
1463 let permutation_recurrence_constraint = shifted_permutation_accumulator
1464 * permutation_denominator
1465 - permutation_accumulator * permutation_numerator;
1466 let permutation_fixpoint_constraint = (permutation_accumulator - Scalar::from_const(1))
1467 * Self::lagrange0(xi, self.degree_bound);
1468
1469 let full_constraint = gate_constraint
1470 + alpha * permutation_fixpoint_constraint
1471 + alpha.square() * permutation_recurrence_constraint;
1472 if full_constraint != quotient * zero {
1473 return Err(anyhow!("constraint violation"));
1474 }
1475
1476 Ok(self
1477 .public_rows
1478 .iter()
1479 .map(|&row| {
1480 let offset = num_gate_selectors + num_sigma_polynomials;
1481 (0..self.num_columns).into_iter().map(move |column| {
1482 let x = omega.pow_small_vartime(row);
1483 (Cell::new(row, column), points[&x][offset + column])
1484 })
1485 })
1486 .flatten()
1487 .collect())
1488 }
1489}
1490
1491#[cfg(test)]
1492mod tests {
1493 use super::*;
1494 use crate::expr::var;
1495 use crate::witness::WitnessView;
1496 use starkom_bluesky::from_const;
1497 use starkom_pcs::hash::{Poseidon1Hash, Poseidon2Hash, Sha2Hash};
1498
1499 #[inline]
1500 fn cell(row: usize, column: usize) -> Cell {
1501 Cell::new(row, column)
1502 }
1503
1504 fn test_vitalik_circuit_impl<H: HashBackend<Scalar>>(
1507 canonicalize_constraints: bool,
1508 blowup_log2: usize,
1509 ) -> Result<()> {
1510 let mut builder = CircuitBuilder::default();
1511 builder.add_gate(0, (var(0) ^ 2) - var(1));
1512 builder.connect(cell(0, 0).into(), cell(1, 0).into());
1513 builder.connect(cell(0, 1).into(), cell(1, 1).into());
1514 builder.add_gate(1, var(0) * var(1) + var(0) + 5 - var(2));
1515 builder.connect(cell(0, 0).into(), cell(2, 0).into());
1516 builder.connect(cell(1, 2).into(), cell(2, 1).into());
1517 builder.add_gate(2, Constraint::nop());
1518 builder.declare_public_rows([2]);
1519 let circuit = builder.build(CompilationOptions {
1520 canonicalize_constraints,
1521 })?;
1522 assert_eq!(circuit.num_rows(), 3);
1523 assert_eq!(circuit.degree_bound(), 8);
1524 assert_eq!(circuit.num_columns(), 3);
1525 let mut witness = circuit.make_witness();
1526 assert_eq!(witness.num_rows(), 3);
1527 assert_eq!(witness.degree_bound(), 8);
1528 assert_eq!(witness.num_columns(), 3);
1529 witness.set(cell(0, 0), from_const(3));
1530 witness.set(cell(0, 1), from_const(9));
1531 witness.set(cell(1, 0), from_const(3));
1532 witness.set(cell(1, 1), from_const(9));
1533 witness.set(cell(1, 2), from_const(35));
1534 witness.set(cell(2, 0), from_const(3));
1535 witness.set(cell(2, 1), from_const(35));
1536 assert!(circuit.check_witness(&witness).is_ok());
1537 let options = ProvingOptions { blowup_log2 };
1538 let proof = circuit.prove::<H>(witness, options.clone())?;
1539 assert_eq!(proof.degree_bound(), 8);
1540 assert_eq!(proof.blowup_log2(), blowup_log2);
1541 assert_eq!(proof.extended_domain_size(), 8 << blowup_log2);
1542 assert_eq!(proof.num_polys(), 13);
1543 let public_inputs = circuit.verify(&proof, options)?;
1544 assert_eq!(public_inputs[&cell(2, 0)], from_const(3));
1545 assert_eq!(public_inputs[&cell(2, 1)], from_const(35));
1546 Ok(())
1547 }
1548
1549 #[test]
1550 fn test_vitalik_circuit_sha2_blowup_2() {
1551 assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(false, 1).is_ok());
1552 assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(true, 1).is_ok());
1553 }
1554
1555 #[test]
1556 fn test_vitalik_circuit_poseidon1_blowup_2() {
1557 assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(false, 1).is_ok());
1558 assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(true, 1).is_ok());
1559 }
1560
1561 #[test]
1562 fn test_vitalik_circuit_poseidon2_blowup_2() {
1563 assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(false, 1).is_ok());
1564 assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(true, 1).is_ok());
1565 }
1566
1567 #[test]
1568 fn test_vitalik_circuit_sha2_blowup_4() {
1569 assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(false, 2).is_ok());
1570 assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(true, 2).is_ok());
1571 }
1572
1573 #[test]
1574 fn test_vitalik_circuit_poseidon1_blowup_4() {
1575 assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(false, 2).is_ok());
1576 assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(true, 2).is_ok());
1577 }
1578
1579 #[test]
1580 fn test_vitalik_circuit_poseidon2_blowup_4() {
1581 assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(false, 2).is_ok());
1582 assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(true, 2).is_ok());
1583 }
1584
1585 #[test]
1586 fn test_vitalik_circuit_sha2_blowup_8() {
1587 assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(false, 3).is_ok());
1588 assert!(test_vitalik_circuit_impl::<Sha2Hash<Scalar>>(true, 3).is_ok());
1589 }
1590
1591 #[test]
1592 fn test_vitalik_circuit_poseidon1_blowup_8() {
1593 assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(false, 3).is_ok());
1594 assert!(test_vitalik_circuit_impl::<Poseidon1Hash<Scalar>>(true, 3).is_ok());
1595 }
1596
1597 #[test]
1598 fn test_vitalik_circuit_poseidon2_blowup_8() {
1599 assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(false, 3).is_ok());
1600 assert!(test_vitalik_circuit_impl::<Poseidon2Hash<Scalar>>(true, 3).is_ok());
1601 }
1602
1603 fn test_vitalik_circuit_with_auto_gates_impl<H: HashBackend<Scalar>>(
1604 canonicalize_constraints: bool,
1605 blowup_log2: usize,
1606 ) -> Result<()> {
1607 let mut builder = CircuitBuilder::default();
1608 let [x, square] = builder.auto_gate((var(0) ^ 2) - var(1), []);
1609 let [result] = builder.auto_gate(
1610 var(0) * var(1) + var(0) + 5 - var(2),
1611 [x.into(), square.into()],
1612 );
1613 let [_, result] = builder.add_nop_gate([x.into(), result.into()]);
1614 builder.declare_public_rows([result.row()]);
1615 let circuit = builder.build(CompilationOptions {
1616 canonicalize_constraints,
1617 })?;
1618 assert_eq!(circuit.num_rows(), 3);
1619 assert_eq!(circuit.degree_bound(), 8);
1620 assert_eq!(circuit.num_columns(), 3);
1621 let mut witness = circuit.make_witness();
1622 assert_eq!(witness.num_rows(), 3);
1623 assert_eq!(witness.degree_bound(), 8);
1624 assert_eq!(witness.num_columns(), 3);
1625 let square = witness.auto_set_one(var(1), var(0) ^ 2, [from_const(3).into()]);
1626 let result = witness.auto_set_one(
1627 var(2),
1628 var(0) * var(1) + var(0) + 5,
1629 [x.into(), square.into()],
1630 );
1631 let [x, result] = witness.nop([x.into(), result.into()]);
1632 assert!(circuit.check_witness(&witness).is_ok());
1633 let options = ProvingOptions { blowup_log2 };
1634 let proof = circuit.prove::<H>(witness, options.clone())?;
1635 assert_eq!(proof.degree_bound(), 8);
1636 assert_eq!(proof.blowup_log2(), blowup_log2);
1637 assert_eq!(proof.extended_domain_size(), 8 << blowup_log2);
1638 assert_eq!(proof.num_polys(), 13);
1639 let public_inputs = circuit.verify(&proof, options)?;
1640 assert_eq!(public_inputs[&x], from_const(3));
1641 assert_eq!(public_inputs[&result], from_const(35));
1642 Ok(())
1643 }
1644
1645 #[test]
1646 fn test_vitalik_circuit_with_auto_gates_blowup_2() {
1647 assert!(test_vitalik_circuit_with_auto_gates_impl::<Sha2Hash<Scalar>>(false, 1).is_ok());
1648 assert!(test_vitalik_circuit_with_auto_gates_impl::<Sha2Hash<Scalar>>(true, 1).is_ok());
1649 }
1650
1651 #[test]
1652 fn test_vitalik_circuit_with_auto_gates_blowup_4() {
1653 assert!(test_vitalik_circuit_with_auto_gates_impl::<Sha2Hash<Scalar>>(false, 2).is_ok());
1654 assert!(test_vitalik_circuit_with_auto_gates_impl::<Sha2Hash<Scalar>>(true, 2).is_ok());
1655 }
1656
1657 fn test_vitalik_circuit_variation_impl<H: HashBackend<Scalar>>(
1660 canonicalize_constraints: bool,
1661 blowup_log2: usize,
1662 ) -> Result<()> {
1663 let mut builder = CircuitBuilder::default();
1664 let [x, square] = builder.auto_gate((var(0) ^ 2) - var(1), []);
1665 let [y, mul] = builder.auto_gate(var(0) * var(1) - var(2), [x.into()]);
1666 let [result] = builder.auto_gate(
1667 var(0) * var(1) + var(2) + 5 - var(3),
1668 [x.into(), square.into(), mul.into()],
1669 );
1670 let [_, _, result] = builder.add_nop_gate([x.into(), y.into(), result.into()]);
1671 builder.declare_public_rows([result.row()]);
1672 let circuit = builder.build(CompilationOptions {
1673 canonicalize_constraints,
1674 })?;
1675 assert_eq!(circuit.num_rows(), 4);
1676 assert_eq!(circuit.degree_bound(), 8);
1677 assert_eq!(circuit.num_columns(), 4);
1678 let mut witness = circuit.make_witness();
1679 assert_eq!(witness.num_rows(), 4);
1680 assert_eq!(witness.degree_bound(), 8);
1681 assert_eq!(witness.num_columns(), 4);
1682 let square = witness.auto_set_one(var(1), var(0) ^ 2, [from_const(3).into()]);
1683 let mul = witness.auto_set_one(
1684 var(2),
1685 var(0) * var(1),
1686 [from_const(3).into(), from_const(4).into()],
1687 );
1688 let result = witness.auto_set_one(
1689 var(3),
1690 var(0) * var(1) + var(2) + 5,
1691 [x.into(), square.into(), mul.into()],
1692 );
1693 let [x, y, result] = witness.nop([x.into(), y.into(), result.into()]);
1694 assert!(circuit.check_witness(&witness).is_ok());
1695 let options = ProvingOptions { blowup_log2 };
1696 let proof = circuit.prove::<H>(witness, options.clone())?;
1697 assert_eq!(proof.degree_bound(), 8);
1698 assert_eq!(proof.blowup_log2(), blowup_log2);
1699 assert_eq!(proof.extended_domain_size(), 8 << blowup_log2);
1700 assert_eq!(proof.num_polys(), 17);
1701 let public_inputs = circuit.verify(&proof, options)?;
1702 assert_eq!(public_inputs[&x], from_const(3));
1703 assert_eq!(public_inputs[&y], from_const(4));
1704 assert_eq!(public_inputs[&result], from_const(44));
1705 Ok(())
1706 }
1707
1708 #[test]
1709 fn test_vitalik_circuit_variation_blowup_2() {
1710 test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(true, 1).unwrap();
1711 assert!(test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(false, 1).is_ok());
1712 assert!(test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(true, 1).is_ok());
1713 }
1714
1715 #[test]
1716 fn test_vitalik_circuit_variation_blowup_4() {
1717 assert!(test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(false, 2).is_ok());
1718 assert!(test_vitalik_circuit_variation_impl::<Sha2Hash<Scalar>>(true, 2).is_ok());
1719 }
1720
1721 fn build_vitalik_circuit() -> Circuit {
1722 let mut builder = CircuitBuilder::default();
1723 builder.add_gate(0, (var(0) ^ 2) - var(1));
1724 builder.connect(cell(0, 0).into(), cell(1, 0).into());
1725 builder.connect(cell(0, 1).into(), cell(1, 1).into());
1726 builder.add_gate(1, var(0) * var(1) + var(0) + 5 - var(2));
1727 builder.connect(cell(0, 0).into(), cell(2, 0).into());
1728 builder.connect(cell(1, 2).into(), cell(2, 1).into());
1729 builder.add_gate(2, Constraint::nop());
1730 builder.declare_public_rows([2]);
1731 builder
1732 .build(CompilationOptions {
1733 canonicalize_constraints: false,
1734 })
1735 .unwrap()
1736 }
1737
1738 #[test]
1739 fn test_check_witness_detects_wrong_number_of_rows() {
1740 let circuit = build_vitalik_circuit();
1741 let witness = Witness::new(4, 3, [0, 1]);
1742 let error = circuit.check_witness(&witness).unwrap_err();
1743 assert!(error.to_string().contains("wrong number of rows"));
1744 }
1745
1746 #[test]
1747 fn test_check_witness_detects_wrong_number_of_columns() {
1748 let circuit = build_vitalik_circuit();
1749 let witness = Witness::new(3, 4, [0, 1]);
1750 let error = circuit.check_witness(&witness).unwrap_err();
1751 assert!(error.to_string().contains("wrong number of columns"));
1752 }
1753
1754 #[test]
1755 fn test_check_witness_detects_wrong_degree_bound() {
1756 let circuit = build_vitalik_circuit();
1757 let witness = Witness::new(3, 3, [-2, -1, 0, 1, 2]);
1758 let error = circuit.check_witness(&witness).unwrap_err();
1759 assert!(error.to_string().contains("incorrect degree bound"));
1760 }
1761
1762 #[test]
1763 fn test_check_witness_detects_gate_constraint_violation() {
1764 let circuit = build_vitalik_circuit();
1765 let mut witness = circuit.make_witness();
1766 witness.set(cell(0, 0), from_const(3));
1767 witness.set(cell(0, 1), from_const(10));
1768 witness.set(cell(1, 0), from_const(3));
1769 witness.set(cell(1, 1), from_const(9));
1770 witness.set(cell(1, 2), from_const(35));
1771 witness.set(cell(2, 0), from_const(3));
1772 witness.set(cell(2, 1), from_const(35));
1773 let error = circuit.check_witness(&witness).unwrap_err();
1774 assert!(error.to_string().contains("gate constraint"));
1775 }
1776
1777 #[test]
1778 fn test_check_witness_detects_direct_wire_constraint_violation() {
1779 let circuit = build_vitalik_circuit();
1780 let mut witness = circuit.make_witness();
1781 witness.set(cell(0, 0), from_const(3));
1782 witness.set(cell(0, 1), from_const(9));
1783 witness.set(cell(1, 0), from_const(4));
1784 witness.set(cell(1, 1), from_const(9));
1785 witness.set(cell(1, 2), from_const(45));
1786 witness.set(cell(2, 0), from_const(3));
1787 witness.set(cell(2, 1), from_const(45));
1788 let error = circuit.check_witness(&witness).unwrap_err();
1789 assert!(error.to_string().contains("wire constraint violated"));
1790 }
1791
1792 #[test]
1793 fn test_check_witness_detects_transitive_wire_constraint_violation() {
1794 let circuit = build_vitalik_circuit();
1795 let mut witness = circuit.make_witness();
1796 witness.set(cell(0, 0), from_const(3));
1797 witness.set(cell(0, 1), from_const(9));
1798 witness.set(cell(1, 0), from_const(3));
1799 witness.set(cell(1, 1), from_const(9));
1800 witness.set(cell(1, 2), from_const(35));
1801 witness.set(cell(2, 0), from_const(99));
1802 witness.set(cell(2, 1), from_const(35));
1803 let error = circuit.check_witness(&witness).unwrap_err();
1804 assert!(error.to_string().contains("wire constraint violated"));
1805 }
1806}