halo2_proofs/dev.rs
1//! Tools for developing circuits.
2
3use std::collections::HashMap;
4use std::collections::HashSet;
5use std::fmt;
6use std::iter;
7use std::ops::{Add, Mul, Neg, Range};
8
9use ff::Field;
10
11use crate::plonk::Assigned;
12use crate::{
13 arithmetic::{FieldExt, Group},
14 plonk::{
15 permutation, Advice, Any, Assignment, Circuit, Column, ColumnType, ConstraintSystem, Error,
16 Expression, Fixed, FloorPlanner, Instance, Selector, VirtualCell,
17 },
18 poly::Rotation,
19};
20
21pub mod metadata;
22mod util;
23
24mod failure;
25pub use failure::{FailureLocation, VerifyFailure};
26
27pub mod cost;
28pub use cost::CircuitCost;
29
30mod gates;
31pub use gates::CircuitGates;
32
33#[cfg(feature = "dev-graph")]
34mod graph;
35
36#[cfg(feature = "dev-graph")]
37#[cfg_attr(docsrs, doc(cfg(feature = "dev-graph")))]
38pub use graph::{circuit_dot_graph, layout::CircuitLayout};
39
40#[derive(Debug)]
41struct Region {
42 /// The name of the region. Not required to be unique.
43 name: String,
44 /// The columns involved in this region.
45 columns: HashSet<Column<Any>>,
46 /// The rows that this region starts and ends on, if known.
47 rows: Option<(usize, usize)>,
48 /// The selectors that have been enabled in this region. All other selectors are by
49 /// construction not enabled.
50 enabled_selectors: HashMap<Selector, Vec<usize>>,
51 /// The cells assigned in this region. We store this as a `Vec` so that if any cells
52 /// are double-assigned, they will be visibly darker.
53 cells: Vec<(Column<Any>, usize)>,
54}
55
56impl Region {
57 fn update_extent(&mut self, column: Column<Any>, row: usize) {
58 self.columns.insert(column);
59
60 // The region start is the earliest row assigned to.
61 // The region end is the latest row assigned to.
62 let (mut start, mut end) = self.rows.unwrap_or((row, row));
63 if row < start {
64 // The first row assigned was not at start 0 within the region.
65 start = row;
66 }
67 if row > end {
68 end = row;
69 }
70 self.rows = Some((start, end));
71 }
72}
73
74/// The value of a particular cell within the circuit.
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76enum CellValue<F: Group + Field> {
77 // An unassigned cell.
78 Unassigned,
79 // A cell that has been assigned a value.
80 Assigned(F),
81 // A unique poisoned cell.
82 Poison(usize),
83}
84
85/// A value within an expression.
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
87enum Value<F: Group + Field> {
88 Real(F),
89 Poison,
90}
91
92impl<F: Group + Field> From<CellValue<F>> for Value<F> {
93 fn from(value: CellValue<F>) -> Self {
94 match value {
95 // Cells that haven't been explicitly assigned to, default to zero.
96 CellValue::Unassigned => Value::Real(F::zero()),
97 CellValue::Assigned(v) => Value::Real(v),
98 CellValue::Poison(_) => Value::Poison,
99 }
100 }
101}
102
103impl<F: Group + Field> Neg for Value<F> {
104 type Output = Self;
105
106 fn neg(self) -> Self::Output {
107 match self {
108 Value::Real(a) => Value::Real(-a),
109 _ => Value::Poison,
110 }
111 }
112}
113
114impl<F: Group + Field> Add for Value<F> {
115 type Output = Self;
116
117 fn add(self, rhs: Self) -> Self::Output {
118 match (self, rhs) {
119 (Value::Real(a), Value::Real(b)) => Value::Real(a + b),
120 _ => Value::Poison,
121 }
122 }
123}
124
125impl<F: Group + Field> Mul for Value<F> {
126 type Output = Self;
127
128 fn mul(self, rhs: Self) -> Self::Output {
129 match (self, rhs) {
130 (Value::Real(a), Value::Real(b)) => Value::Real(a * b),
131 // If poison is multiplied by zero, then we treat the poison as unconstrained
132 // and we don't propagate it.
133 (Value::Real(x), Value::Poison) | (Value::Poison, Value::Real(x))
134 if x.is_zero_vartime() =>
135 {
136 Value::Real(F::zero())
137 }
138 _ => Value::Poison,
139 }
140 }
141}
142
143impl<F: Group + Field> Mul<F> for Value<F> {
144 type Output = Self;
145
146 fn mul(self, rhs: F) -> Self::Output {
147 match self {
148 Value::Real(lhs) => Value::Real(lhs * rhs),
149 // If poison is multiplied by zero, then we treat the poison as unconstrained
150 // and we don't propagate it.
151 Value::Poison if rhs.is_zero_vartime() => Value::Real(F::zero()),
152 _ => Value::Poison,
153 }
154 }
155}
156
157/// A test prover for debugging circuits.
158///
159/// The normal proving process, when applied to a buggy circuit implementation, might
160/// return proofs that do not validate when they should, but it can't indicate anything
161/// other than "something is invalid". `MockProver` can be used to figure out _why_ these
162/// are invalid: it stores all the private inputs along with the circuit internals, and
163/// then checks every constraint manually.
164///
165/// # Examples
166///
167/// ```
168/// use halo2_proofs::{
169/// arithmetic::FieldExt,
170/// circuit::{Layouter, SimpleFloorPlanner},
171/// dev::{FailureLocation, MockProver, VerifyFailure},
172/// pasta::Fp,
173/// plonk::{Advice, Any, Circuit, Column, ConstraintSystem, Error, Selector},
174/// poly::Rotation,
175/// };
176/// const K: u32 = 5;
177///
178/// #[derive(Copy, Clone)]
179/// struct MyConfig {
180/// a: Column<Advice>,
181/// b: Column<Advice>,
182/// c: Column<Advice>,
183/// s: Selector,
184/// }
185///
186/// #[derive(Clone, Default)]
187/// struct MyCircuit {
188/// a: Option<u64>,
189/// b: Option<u64>,
190/// }
191///
192/// impl<F: FieldExt> Circuit<F> for MyCircuit {
193/// type Config = MyConfig;
194/// type FloorPlanner = SimpleFloorPlanner;
195///
196/// fn without_witnesses(&self) -> Self {
197/// Self::default()
198/// }
199///
200/// fn configure(meta: &mut ConstraintSystem<F>) -> MyConfig {
201/// let a = meta.advice_column();
202/// let b = meta.advice_column();
203/// let c = meta.advice_column();
204/// let s = meta.selector();
205///
206/// meta.create_gate("R1CS constraint", |meta| {
207/// let a = meta.query_advice(a, Rotation::cur());
208/// let b = meta.query_advice(b, Rotation::cur());
209/// let c = meta.query_advice(c, Rotation::cur());
210/// let s = meta.query_selector(s);
211///
212/// // BUG: Should be a * b - c
213/// Some(("buggy R1CS", s * (a * b + c)))
214/// });
215///
216/// MyConfig { a, b, c, s }
217/// }
218///
219/// fn synthesize(&self, config: MyConfig, mut layouter: impl Layouter<F>) -> Result<(), Error> {
220/// layouter.assign_region(|| "Example region", |mut region| {
221/// config.s.enable(&mut region, 0)?;
222/// region.assign_advice(|| "a", config.a, 0, || {
223/// self.a.map(|v| F::from(v)).ok_or(Error::Synthesis)
224/// })?;
225/// region.assign_advice(|| "b", config.b, 0, || {
226/// self.b.map(|v| F::from(v)).ok_or(Error::Synthesis)
227/// })?;
228/// region.assign_advice(|| "c", config.c, 0, || {
229/// self.a
230/// .and_then(|a| self.b.map(|b| F::from(a * b)))
231/// .ok_or(Error::Synthesis)
232/// })?;
233/// Ok(())
234/// })
235/// }
236/// }
237///
238/// // Assemble the private inputs to the circuit.
239/// let circuit = MyCircuit {
240/// a: Some(2),
241/// b: Some(4),
242/// };
243///
244/// // This circuit has no public inputs.
245/// let instance = vec![];
246///
247/// let prover = MockProver::<Fp>::run(K, &circuit, instance).unwrap();
248/// assert_eq!(
249/// prover.verify(),
250/// Err(vec![VerifyFailure::ConstraintNotSatisfied {
251/// constraint: ((0, "R1CS constraint").into(), 0, "buggy R1CS").into(),
252/// location: FailureLocation::InRegion {
253/// region: (0, "Example region").into(),
254/// offset: 0,
255/// },
256/// cell_values: vec![
257/// (((Any::Advice, 0).into(), 0).into(), "0x2".to_string()),
258/// (((Any::Advice, 1).into(), 0).into(), "0x4".to_string()),
259/// (((Any::Advice, 2).into(), 0).into(), "0x8".to_string()),
260/// ],
261/// }])
262/// );
263///
264/// // If we provide a too-small K, we get an error.
265/// assert!(matches!(
266/// MockProver::<Fp>::run(2, &circuit, vec![]).unwrap_err(),
267/// Error::NotEnoughRowsAvailable {
268/// current_k,
269/// } if current_k == 2,
270/// ));
271/// ```
272#[derive(Debug)]
273pub struct MockProver<F: Group + Field> {
274 k: u32,
275 n: u32,
276 cs: ConstraintSystem<F>,
277
278 /// The regions in the circuit.
279 regions: Vec<Region>,
280 /// The current region being assigned to. Will be `None` after the circuit has been
281 /// synthesized.
282 current_region: Option<Region>,
283
284 // The fixed cells in the circuit, arranged as [column][row].
285 fixed: Vec<Vec<CellValue<F>>>,
286 // The advice cells in the circuit, arranged as [column][row].
287 advice: Vec<Vec<CellValue<F>>>,
288 // The instance cells in the circuit, arranged as [column][row].
289 instance: Vec<Vec<F>>,
290
291 selectors: Vec<Vec<bool>>,
292
293 permutation: permutation::keygen::Assembly,
294
295 // A range of available rows for assignment and copies.
296 usable_rows: Range<usize>,
297}
298
299impl<F: Field + Group> Assignment<F> for MockProver<F> {
300 fn enter_region<NR, N>(&mut self, name: N)
301 where
302 NR: Into<String>,
303 N: FnOnce() -> NR,
304 {
305 assert!(self.current_region.is_none());
306 self.current_region = Some(Region {
307 name: name().into(),
308 columns: HashSet::default(),
309 rows: None,
310 enabled_selectors: HashMap::default(),
311 cells: vec![],
312 });
313 }
314
315 fn exit_region(&mut self) {
316 self.regions.push(self.current_region.take().unwrap());
317 }
318
319 fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
320 where
321 A: FnOnce() -> AR,
322 AR: Into<String>,
323 {
324 if !self.usable_rows.contains(&row) {
325 return Err(Error::not_enough_rows_available(self.k));
326 }
327
328 // Track that this selector was enabled. We require that all selectors are enabled
329 // inside some region (i.e. no floating selectors).
330 self.current_region
331 .as_mut()
332 .unwrap()
333 .enabled_selectors
334 .entry(*selector)
335 .or_default()
336 .push(row);
337
338 self.selectors[selector.0][row] = true;
339
340 Ok(())
341 }
342
343 fn query_instance(&self, column: Column<Instance>, row: usize) -> Result<Option<F>, Error> {
344 if !self.usable_rows.contains(&row) {
345 return Err(Error::not_enough_rows_available(self.k));
346 }
347
348 self.instance
349 .get(column.index())
350 .and_then(|column| column.get(row))
351 .map(|v| Some(*v))
352 .ok_or(Error::BoundsFailure)
353 }
354
355 fn assign_advice<V, VR, A, AR>(
356 &mut self,
357 _: A,
358 column: Column<Advice>,
359 row: usize,
360 to: V,
361 ) -> Result<(), Error>
362 where
363 V: FnOnce() -> Result<VR, Error>,
364 VR: Into<Assigned<F>>,
365 A: FnOnce() -> AR,
366 AR: Into<String>,
367 {
368 if !self.usable_rows.contains(&row) {
369 return Err(Error::not_enough_rows_available(self.k));
370 }
371
372 if let Some(region) = self.current_region.as_mut() {
373 region.update_extent(column.into(), row);
374 region.cells.push((column.into(), row));
375 }
376
377 *self
378 .advice
379 .get_mut(column.index())
380 .and_then(|v| v.get_mut(row))
381 .ok_or(Error::BoundsFailure)? = CellValue::Assigned(to()?.into().evaluate());
382
383 Ok(())
384 }
385
386 fn assign_fixed<V, VR, A, AR>(
387 &mut self,
388 _: A,
389 column: Column<Fixed>,
390 row: usize,
391 to: V,
392 ) -> Result<(), Error>
393 where
394 V: FnOnce() -> Result<VR, Error>,
395 VR: Into<Assigned<F>>,
396 A: FnOnce() -> AR,
397 AR: Into<String>,
398 {
399 if !self.usable_rows.contains(&row) {
400 return Err(Error::not_enough_rows_available(self.k));
401 }
402
403 if let Some(region) = self.current_region.as_mut() {
404 region.update_extent(column.into(), row);
405 region.cells.push((column.into(), row));
406 }
407
408 *self
409 .fixed
410 .get_mut(column.index())
411 .and_then(|v| v.get_mut(row))
412 .ok_or(Error::BoundsFailure)? = CellValue::Assigned(to()?.into().evaluate());
413
414 Ok(())
415 }
416
417 fn copy(
418 &mut self,
419 left_column: Column<Any>,
420 left_row: usize,
421 right_column: Column<Any>,
422 right_row: usize,
423 ) -> Result<(), crate::plonk::Error> {
424 if !self.usable_rows.contains(&left_row) || !self.usable_rows.contains(&right_row) {
425 return Err(Error::not_enough_rows_available(self.k));
426 }
427
428 self.permutation
429 .copy(left_column, left_row, right_column, right_row)
430 }
431
432 fn fill_from_row(
433 &mut self,
434 col: Column<Fixed>,
435 from_row: usize,
436 to: Option<Assigned<F>>,
437 ) -> Result<(), Error> {
438 if !self.usable_rows.contains(&from_row) {
439 return Err(Error::not_enough_rows_available(self.k));
440 }
441
442 for row in self.usable_rows.clone().skip(from_row) {
443 self.assign_fixed(|| "", col, row, || to.ok_or(Error::Synthesis))?;
444 }
445
446 Ok(())
447 }
448
449 fn push_namespace<NR, N>(&mut self, _: N)
450 where
451 NR: Into<String>,
452 N: FnOnce() -> NR,
453 {
454 // TODO: Do something with namespaces :)
455 }
456
457 fn pop_namespace(&mut self, _: Option<String>) {
458 // TODO: Do something with namespaces :)
459 }
460}
461
462impl<F: FieldExt> MockProver<F> {
463 /// Runs a synthetic keygen-and-prove operation on the given circuit, collecting data
464 /// about the constraints and their assignments.
465 pub fn run<ConcreteCircuit: Circuit<F>>(
466 k: u32,
467 circuit: &ConcreteCircuit,
468 instance: Vec<Vec<F>>,
469 ) -> Result<Self, Error> {
470 let n = 1 << k;
471
472 let mut cs = ConstraintSystem::default();
473 let config = ConcreteCircuit::configure(&mut cs);
474 let cs = cs;
475
476 if n < cs.minimum_rows() {
477 return Err(Error::not_enough_rows_available(k));
478 }
479
480 if instance.len() != cs.num_instance_columns {
481 return Err(Error::InvalidInstances);
482 }
483
484 let instance = instance
485 .into_iter()
486 .map(|mut instance| {
487 if instance.len() > n - (cs.blinding_factors() + 1) {
488 return Err(Error::InstanceTooLarge);
489 }
490
491 instance.resize(n, F::zero());
492 Ok(instance)
493 })
494 .collect::<Result<Vec<_>, _>>()?;
495
496 // Fixed columns contain no blinding factors.
497 let fixed = vec![vec![CellValue::Unassigned; n]; cs.num_fixed_columns];
498 let selectors = vec![vec![false; n]; cs.num_selectors];
499 // Advice columns contain blinding factors.
500 let blinding_factors = cs.blinding_factors();
501 let usable_rows = n - (blinding_factors + 1);
502 let advice = vec![
503 {
504 let mut column = vec![CellValue::Unassigned; n];
505 // Poison unusable rows.
506 for (i, cell) in column.iter_mut().enumerate().skip(usable_rows) {
507 *cell = CellValue::Poison(i);
508 }
509 column
510 };
511 cs.num_advice_columns
512 ];
513 let permutation = permutation::keygen::Assembly::new(n, &cs.permutation);
514 let constants = cs.constants.clone();
515
516 let mut prover = MockProver {
517 k,
518 n: n as u32,
519 cs,
520 regions: vec![],
521 current_region: None,
522 fixed,
523 advice,
524 instance,
525 selectors,
526 permutation,
527 usable_rows: 0..usable_rows,
528 };
529
530 ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config, constants)?;
531
532 let (cs, selector_polys) = prover.cs.compress_selectors(prover.selectors.clone());
533 prover.cs = cs;
534 prover.fixed.extend(selector_polys.into_iter().map(|poly| {
535 let mut v = vec![CellValue::Unassigned; n];
536 for (v, p) in v.iter_mut().zip(&poly[..]) {
537 *v = CellValue::Assigned(*p);
538 }
539 v
540 }));
541
542 Ok(prover)
543 }
544
545 /// Returns `Ok(())` if this `MockProver` is satisfied, or a list of errors indicating
546 /// the reasons that the circuit is not satisfied.
547 pub fn verify(&self) -> Result<(), Vec<VerifyFailure>> {
548 let n = self.n as i32;
549
550 // Check that within each region, all cells used in instantiated gates have been
551 // assigned to.
552 let selector_errors = self.regions.iter().enumerate().flat_map(|(r_i, r)| {
553 r.enabled_selectors.iter().flat_map(move |(selector, at)| {
554 // Find the gates enabled by this selector
555 self.cs
556 .gates
557 .iter()
558 // Assume that if a queried selector is enabled, the user wants to use the
559 // corresponding gate in some way.
560 //
561 // TODO: This will trip up on the reverse case, where leaving a selector
562 // un-enabled keeps a gate enabled. We could alternatively require that
563 // every selector is explicitly enabled or disabled on every row? But that
564 // seems messy and confusing.
565 .enumerate()
566 .filter(move |(_, g)| g.queried_selectors().contains(selector))
567 .flat_map(move |(gate_index, gate)| {
568 at.iter().flat_map(move |selector_row| {
569 // Selectors are queried with no rotation.
570 let gate_row = *selector_row as i32;
571
572 gate.queried_cells().iter().filter_map(move |cell| {
573 // Determine where this cell should have been assigned.
574 let cell_row = ((gate_row + n + cell.rotation.0) % n) as usize;
575
576 // Check that it was assigned!
577 if r.cells.contains(&(cell.column, cell_row)) {
578 None
579 } else {
580 Some(VerifyFailure::CellNotAssigned {
581 gate: (gate_index, gate.name()).into(),
582 region: (r_i, r.name.clone()).into(),
583 gate_offset: *selector_row,
584 column: cell.column,
585 offset: cell_row as isize - r.rows.unwrap().0 as isize,
586 })
587 }
588 })
589 })
590 })
591 })
592 });
593
594 // Check that all gates are satisfied for all rows.
595 let gate_errors =
596 self.cs
597 .gates
598 .iter()
599 .enumerate()
600 .flat_map(|(gate_index, gate)| {
601 // We iterate from n..2n so we can just reduce to handle wrapping.
602 (n..(2 * n)).flat_map(move |row| {
603 gate.polynomials().iter().enumerate().filter_map(
604 move |(poly_index, poly)| match poly.evaluate(
605 &|scalar| Value::Real(scalar),
606 &|_| panic!("virtual selectors are removed during optimization"),
607 &util::load(n, row, &self.cs.fixed_queries, &self.fixed),
608 &util::load(n, row, &self.cs.advice_queries, &self.advice),
609 &util::load_instance(
610 n,
611 row,
612 &self.cs.instance_queries,
613 &self.instance,
614 ),
615 &|a| -a,
616 &|a, b| a + b,
617 &|a, b| a * b,
618 &|a, scalar| a * scalar,
619 ) {
620 Value::Real(x) if x.is_zero_vartime() => None,
621 Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied {
622 constraint: (
623 (gate_index, gate.name()).into(),
624 poly_index,
625 gate.constraint_name(poly_index),
626 )
627 .into(),
628 location: FailureLocation::find_expressions(
629 &self.cs,
630 &self.regions,
631 (row - n) as usize,
632 Some(poly).into_iter(),
633 ),
634 cell_values: util::cell_values(
635 gate,
636 poly,
637 &util::load(n, row, &self.cs.fixed_queries, &self.fixed),
638 &util::load(n, row, &self.cs.advice_queries, &self.advice),
639 &util::load_instance(
640 n,
641 row,
642 &self.cs.instance_queries,
643 &self.instance,
644 ),
645 ),
646 }),
647 Value::Poison => Some(VerifyFailure::ConstraintPoisoned {
648 constraint: (
649 (gate_index, gate.name()).into(),
650 poly_index,
651 gate.constraint_name(poly_index),
652 )
653 .into(),
654 }),
655 },
656 )
657 })
658 });
659
660 // Check that all lookups exist in their respective tables.
661 let lookup_errors =
662 self.cs
663 .lookups
664 .iter()
665 .enumerate()
666 .flat_map(|(lookup_index, lookup)| {
667 let load = |expression: &Expression<F>, row| {
668 expression.evaluate(
669 &|scalar| Value::Real(scalar),
670 &|_| panic!("virtual selectors are removed during optimization"),
671 &|index, _, _| {
672 let query = self.cs.fixed_queries[index];
673 let column_index = query.0.index();
674 let rotation = query.1 .0;
675 self.fixed[column_index]
676 [(row as i32 + n + rotation) as usize % n as usize]
677 .into()
678 },
679 &|index, _, _| {
680 let query = self.cs.advice_queries[index];
681 let column_index = query.0.index();
682 let rotation = query.1 .0;
683 self.advice[column_index]
684 [(row as i32 + n + rotation) as usize % n as usize]
685 .into()
686 },
687 &|index, _, _| {
688 let query = self.cs.instance_queries[index];
689 let column_index = query.0.index();
690 let rotation = query.1 .0;
691 Value::Real(
692 self.instance[column_index]
693 [(row as i32 + n + rotation) as usize % n as usize],
694 )
695 },
696 &|a| -a,
697 &|a, b| a + b,
698 &|a, b| a * b,
699 &|a, scalar| a * scalar,
700 )
701 };
702
703 assert!(lookup.table_expressions.len() == lookup.input_expressions.len());
704 assert!(self.usable_rows.end > 0);
705
706 // We optimize on the basis that the table might have been filled so that the last
707 // usable row now has the fill contents (it doesn't matter if there was no filling).
708 // Note that this "fill row" necessarily exists in the table, and we use that fact to
709 // slightly simplify the optimization: we're only trying to check that all input rows
710 // are contained in the table, and so we can safely just drop input rows that
711 // match the fill row.
712 let fill_row: Vec<_> = lookup
713 .table_expressions
714 .iter()
715 .map(move |c| load(c, self.usable_rows.end - 1))
716 .collect();
717
718 // In the real prover, the lookup expressions are never enforced on
719 // unusable rows, due to the (1 - (l_last(X) + l_blind(X))) term.
720 let mut table: Vec<Vec<_>> = self
721 .usable_rows
722 .clone()
723 .filter_map(|table_row| {
724 let t = lookup
725 .table_expressions
726 .iter()
727 .map(move |c| load(c, table_row))
728 .collect();
729
730 if t != fill_row {
731 Some(t)
732 } else {
733 None
734 }
735 })
736 .collect();
737 table.sort_unstable();
738
739 let mut inputs: Vec<(Vec<_>, usize)> = self
740 .usable_rows
741 .clone()
742 .filter_map(|input_row| {
743 let t = lookup
744 .input_expressions
745 .iter()
746 .map(move |c| load(c, input_row))
747 .collect();
748
749 if t != fill_row {
750 // Also keep track of the original input row, since we're going to sort.
751 Some((t, input_row))
752 } else {
753 None
754 }
755 })
756 .collect();
757 inputs.sort_unstable();
758
759 let mut i = 0;
760 inputs
761 .iter()
762 .filter_map(move |(input, input_row)| {
763 while i < table.len() && &table[i] < input {
764 i += 1;
765 }
766 if i == table.len() || &table[i] > input {
767 assert!(table.binary_search(input).is_err());
768
769 Some(VerifyFailure::Lookup {
770 lookup_index,
771 location: FailureLocation::find_expressions(
772 &self.cs,
773 &self.regions,
774 *input_row,
775 lookup.input_expressions.iter(),
776 ),
777 })
778 } else {
779 None
780 }
781 })
782 .collect::<Vec<_>>()
783 });
784
785 // Check that permutations preserve the original values of the cells.
786 let perm_errors = {
787 // Original values of columns involved in the permutation.
788 let original = |column, row| {
789 self.cs
790 .permutation
791 .get_columns()
792 .get(column)
793 .map(|c: &Column<Any>| match c.column_type() {
794 Any::Advice => self.advice[c.index()][row],
795 Any::Fixed => self.fixed[c.index()][row],
796 Any::Instance => CellValue::Assigned(self.instance[c.index()][row]),
797 })
798 .unwrap()
799 };
800
801 // Iterate over each column of the permutation
802 self.permutation
803 .mapping
804 .iter()
805 .enumerate()
806 .flat_map(move |(column, values)| {
807 // Iterate over each row of the column to check that the cell's
808 // value is preserved by the mapping.
809 values.iter().enumerate().filter_map(move |(row, cell)| {
810 let original_cell = original(column, row);
811 let permuted_cell = original(cell.0, cell.1);
812 if original_cell == permuted_cell {
813 None
814 } else {
815 let columns = self.cs.permutation.get_columns();
816 let column = columns.get(column).unwrap();
817 Some(VerifyFailure::Permutation {
818 column: (*column).into(),
819 location: FailureLocation::find(
820 &self.regions,
821 row,
822 Some(column).into_iter().cloned().collect(),
823 ),
824 })
825 }
826 })
827 })
828 };
829
830 let mut errors: Vec<_> = iter::empty()
831 .chain(selector_errors)
832 .chain(gate_errors)
833 .chain(lookup_errors)
834 .chain(perm_errors)
835 .collect();
836 if errors.is_empty() {
837 Ok(())
838 } else {
839 // Remove any duplicate `ConstraintPoisoned` errors (we check all unavailable
840 // rows in case the trigger is row-specific, but the error message only points
841 // at the constraint).
842 errors.dedup_by(|a, b| match (a, b) {
843 (
844 a @ VerifyFailure::ConstraintPoisoned { .. },
845 b @ VerifyFailure::ConstraintPoisoned { .. },
846 ) => a == b,
847 _ => false,
848 });
849 Err(errors)
850 }
851 }
852
853 /// Panics if the circuit being checked by this `MockProver` is not satisfied.
854 ///
855 /// Any verification failures will be pretty-printed to stderr before the function
856 /// panics.
857 ///
858 /// Apart from the stderr output, this method is equivalent to:
859 /// ```ignore
860 /// assert_eq!(prover.verify(), Ok(()));
861 /// ```
862 pub fn assert_satisfied(&self) {
863 if let Err(errs) = self.verify() {
864 for err in errs {
865 err.emit(self);
866 eprintln!();
867 }
868 panic!("circuit was not satisfied");
869 }
870 }
871}
872
873#[cfg(test)]
874mod tests {
875 use pasta_curves::Fp;
876
877 use super::{FailureLocation, MockProver, VerifyFailure};
878 use crate::{
879 circuit::{Layouter, SimpleFloorPlanner},
880 plonk::{
881 Advice, Any, Circuit, Column, ConstraintSystem, Error, Expression, Selector,
882 TableColumn,
883 },
884 poly::Rotation,
885 };
886
887 #[test]
888 fn unassigned_cell() {
889 const K: u32 = 4;
890
891 #[derive(Clone)]
892 struct FaultyCircuitConfig {
893 a: Column<Advice>,
894 q: Selector,
895 }
896
897 struct FaultyCircuit {}
898
899 impl Circuit<Fp> for FaultyCircuit {
900 type Config = FaultyCircuitConfig;
901 type FloorPlanner = SimpleFloorPlanner;
902
903 fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
904 let a = meta.advice_column();
905 let b = meta.advice_column();
906 let q = meta.selector();
907
908 meta.create_gate("Equality check", |cells| {
909 let a = cells.query_advice(a, Rotation::prev());
910 let b = cells.query_advice(b, Rotation::cur());
911 let q = cells.query_selector(q);
912
913 // If q is enabled, a and b must be assigned to.
914 vec![q * (a - b)]
915 });
916
917 FaultyCircuitConfig { a, q }
918 }
919
920 fn without_witnesses(&self) -> Self {
921 Self {}
922 }
923
924 fn synthesize(
925 &self,
926 config: Self::Config,
927 mut layouter: impl Layouter<Fp>,
928 ) -> Result<(), Error> {
929 layouter.assign_region(
930 || "Faulty synthesis",
931 |mut region| {
932 // Enable the equality gate.
933 config.q.enable(&mut region, 1)?;
934
935 // Assign a = 0.
936 region.assign_advice(|| "a", config.a, 0, || Ok(Fp::zero()))?;
937
938 // BUG: Forget to assign b = 0! This could go unnoticed during
939 // development, because cell values default to zero, which in this
940 // case is fine, but for other assignments would be broken.
941 Ok(())
942 },
943 )
944 }
945 }
946
947 let prover = MockProver::run(K, &FaultyCircuit {}, vec![]).unwrap();
948 assert_eq!(
949 prover.verify(),
950 Err(vec![VerifyFailure::CellNotAssigned {
951 gate: (0, "Equality check").into(),
952 region: (0, "Faulty synthesis".to_owned()).into(),
953 gate_offset: 1,
954 column: Column::new(1, Any::Advice),
955 offset: 1,
956 }])
957 );
958 }
959
960 #[test]
961 fn bad_lookup() {
962 const K: u32 = 4;
963
964 #[derive(Clone)]
965 struct FaultyCircuitConfig {
966 a: Column<Advice>,
967 q: Selector,
968 table: TableColumn,
969 }
970
971 struct FaultyCircuit {}
972
973 impl Circuit<Fp> for FaultyCircuit {
974 type Config = FaultyCircuitConfig;
975 type FloorPlanner = SimpleFloorPlanner;
976
977 fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
978 let a = meta.advice_column();
979 let q = meta.complex_selector();
980 let table = meta.lookup_table_column();
981
982 meta.lookup(|cells| {
983 let a = cells.query_advice(a, Rotation::cur());
984 let q = cells.query_selector(q);
985
986 // If q is enabled, a must be in the table.
987 // When q is not enabled, lookup the default value instead.
988 let not_q = Expression::Constant(Fp::one()) - q.clone();
989 let default = Expression::Constant(Fp::from(2));
990 vec![(q * a + not_q * default, table)]
991 });
992
993 FaultyCircuitConfig { a, q, table }
994 }
995
996 fn without_witnesses(&self) -> Self {
997 Self {}
998 }
999
1000 fn synthesize(
1001 &self,
1002 config: Self::Config,
1003 mut layouter: impl Layouter<Fp>,
1004 ) -> Result<(), Error> {
1005 layouter.assign_table(
1006 || "Doubling table",
1007 |mut table| {
1008 (1..(1 << (K - 1)))
1009 .map(|i| {
1010 table.assign_cell(
1011 || format!("table[{}] = {}", i, 2 * i),
1012 config.table,
1013 i - 1,
1014 || Ok(Fp::from(2 * i as u64)),
1015 )
1016 })
1017 .fold(Ok(()), |acc, res| acc.and(res))
1018 },
1019 )?;
1020
1021 layouter.assign_region(
1022 || "Good synthesis",
1023 |mut region| {
1024 // Enable the lookup on rows 0 and 1.
1025 config.q.enable(&mut region, 0)?;
1026 config.q.enable(&mut region, 1)?;
1027
1028 // Assign a = 2 and a = 6.
1029 region.assign_advice(|| "a = 2", config.a, 0, || Ok(Fp::from(2)))?;
1030 region.assign_advice(|| "a = 6", config.a, 1, || Ok(Fp::from(6)))?;
1031
1032 Ok(())
1033 },
1034 )?;
1035
1036 layouter.assign_region(
1037 || "Faulty synthesis",
1038 |mut region| {
1039 // Enable the lookup on rows 0 and 1.
1040 config.q.enable(&mut region, 0)?;
1041 config.q.enable(&mut region, 1)?;
1042
1043 // Assign a = 4.
1044 region.assign_advice(|| "a = 4", config.a, 0, || Ok(Fp::from(4)))?;
1045
1046 // BUG: Assign a = 5, which doesn't exist in the table!
1047 region.assign_advice(|| "a = 5", config.a, 1, || Ok(Fp::from(5)))?;
1048
1049 Ok(())
1050 },
1051 )
1052 }
1053 }
1054
1055 let prover = MockProver::run(K, &FaultyCircuit {}, vec![]).unwrap();
1056 assert_eq!(
1057 prover.verify(),
1058 Err(vec![VerifyFailure::Lookup {
1059 lookup_index: 0,
1060 location: FailureLocation::InRegion {
1061 region: (2, "Faulty synthesis").into(),
1062 offset: 1,
1063 }
1064 }])
1065 );
1066 }
1067}