halo2_proofs/dev/
failure.rs

1use std::collections::{BTreeMap, BTreeSet, HashSet};
2use std::fmt;
3use std::iter;
4
5use group::ff::Field;
6use pasta_curves::arithmetic::FieldExt;
7
8use super::{metadata, util, MockProver, Region};
9use crate::{
10    dev::Value,
11    plonk::{Any, Column, ConstraintSystem, Expression, Gate},
12    poly::Rotation,
13};
14
15mod emitter;
16
17/// The location within the circuit at which a particular [`VerifyFailure`] occurred.
18#[derive(Debug, PartialEq)]
19pub enum FailureLocation {
20    /// A location inside a region.
21    InRegion {
22        /// The region in which the failure occurred.
23        region: metadata::Region,
24        /// The offset (relative to the start of the region) at which the failure
25        /// occurred.
26        offset: usize,
27    },
28    /// A location outside of a region.
29    OutsideRegion {
30        /// The circuit row on which the failure occurred.
31        row: usize,
32    },
33}
34
35impl fmt::Display for FailureLocation {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            Self::InRegion { region, offset } => write!(f, "in {} at offset {}", region, offset),
39            Self::OutsideRegion { row } => {
40                write!(f, "outside any region, on row {}", row)
41            }
42        }
43    }
44}
45
46impl FailureLocation {
47    pub(super) fn find_expressions<'a, F: Field>(
48        cs: &ConstraintSystem<F>,
49        regions: &[Region],
50        failure_row: usize,
51        failure_expressions: impl Iterator<Item = &'a Expression<F>>,
52    ) -> Self {
53        let failure_columns: HashSet<Column<Any>> = failure_expressions
54            .flat_map(|expression| {
55                expression.evaluate(
56                    &|_| vec![],
57                    &|_| panic!("virtual selectors are removed during optimization"),
58                    &|index, _, _| vec![cs.fixed_queries[index].0.into()],
59                    &|index, _, _| vec![cs.advice_queries[index].0.into()],
60                    &|index, _, _| vec![cs.instance_queries[index].0.into()],
61                    &|a| a,
62                    &|mut a, mut b| {
63                        a.append(&mut b);
64                        a
65                    },
66                    &|mut a, mut b| {
67                        a.append(&mut b);
68                        a
69                    },
70                    &|a, _| a,
71                )
72            })
73            .collect();
74
75        Self::find(regions, failure_row, failure_columns)
76    }
77
78    /// Figures out whether the given row and columns overlap an assigned region.
79    pub(super) fn find(
80        regions: &[Region],
81        failure_row: usize,
82        failure_columns: HashSet<Column<Any>>,
83    ) -> Self {
84        regions
85            .iter()
86            .enumerate()
87            .find(|(_, r)| {
88                let (start, end) = r.rows.unwrap();
89                // We match the region if any input columns overlap, rather than all of
90                // them, because matching complex selector columns is hard. As long as
91                // regions are rectangles, and failures occur due to assignments entirely
92                // within single regions, "any" will be equivalent to "all". If these
93                // assumptions change, we'll start getting bug reports from users :)
94                (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns)
95            })
96            .map(|(r_i, r)| FailureLocation::InRegion {
97                region: (r_i, r.name.clone()).into(),
98                offset: failure_row as usize - r.rows.unwrap().0 as usize,
99            })
100            .unwrap_or_else(|| FailureLocation::OutsideRegion {
101                row: failure_row as usize,
102            })
103    }
104}
105
106/// The reasons why a particular circuit is not satisfied.
107#[derive(Debug, PartialEq)]
108pub enum VerifyFailure {
109    /// A cell used in an active gate was not assigned to.
110    CellNotAssigned {
111        /// The index of the active gate.
112        gate: metadata::Gate,
113        /// The region in which this cell should be assigned.
114        region: metadata::Region,
115        /// The offset (relative to the start of the region) at which the active gate
116        /// queries this cell.
117        gate_offset: usize,
118        /// The column in which this cell should be assigned.
119        column: Column<Any>,
120        /// The offset (relative to the start of the region) at which this cell should be
121        /// assigned. This may be negative (for example, if a selector enables a gate at
122        /// offset 0, but the gate uses `Rotation::prev()`).
123        offset: isize,
124    },
125    /// A constraint was not satisfied for a particular row.
126    ConstraintNotSatisfied {
127        /// The polynomial constraint that is not satisfied.
128        constraint: metadata::Constraint,
129        /// The location at which this constraint is not satisfied.
130        ///
131        /// `FailureLocation::OutsideRegion` is usually caused by a constraint that does
132        /// not contain a selector, and as a result is active on every row.
133        location: FailureLocation,
134        /// The values of the virtual cells used by this constraint.
135        cell_values: Vec<(metadata::VirtualCell, String)>,
136    },
137    /// A constraint was active on an unusable row, and is likely missing a selector.
138    ConstraintPoisoned {
139        /// The polynomial constraint that is not satisfied.
140        constraint: metadata::Constraint,
141    },
142    /// A lookup input did not exist in its corresponding table.
143    Lookup {
144        /// The index of the lookup that is not satisfied. These indices are assigned in
145        /// the order in which `ConstraintSystem::lookup` is called during
146        /// `Circuit::configure`.
147        lookup_index: usize,
148        /// The location at which the lookup is not satisfied.
149        ///
150        /// `FailureLocation::InRegion` is most common, and may be due to the intentional
151        /// use of a lookup (if its inputs are conditional on a complex selector), or an
152        /// unintentional lookup constraint that overlaps the region (indicating that the
153        /// lookup's inputs should be made conditional).
154        ///
155        /// `FailureLocation::OutsideRegion` is uncommon, and could mean that:
156        /// - The input expressions do not correctly constrain a default value that exists
157        ///   in the table when the lookup is not being used.
158        /// - The input expressions use a column queried at a non-zero `Rotation`, and the
159        ///   lookup is active on a row adjacent to an unrelated region.
160        location: FailureLocation,
161    },
162    /// A permutation did not preserve the original value of a cell.
163    Permutation {
164        /// The column in which this permutation is not satisfied.
165        column: metadata::Column,
166        /// The location at which the permutation is not satisfied.
167        location: FailureLocation,
168    },
169}
170
171impl fmt::Display for VerifyFailure {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        match self {
174            Self::CellNotAssigned {
175                gate,
176                region,
177                gate_offset,
178                column,
179                offset,
180            } => {
181                write!(
182                    f,
183                    "{} uses {} at offset {}, which requires cell in column {:?} at offset {} to be assigned.",
184                    region, gate, gate_offset, column, offset
185                )
186            }
187            Self::ConstraintNotSatisfied {
188                constraint,
189                location,
190                cell_values,
191            } => {
192                writeln!(f, "{} is not satisfied {}", constraint, location)?;
193                for (name, value) in cell_values {
194                    writeln!(f, "- {} = {}", name, value)?;
195                }
196                Ok(())
197            }
198            Self::ConstraintPoisoned { constraint } => {
199                write!(
200                    f,
201                    "{} is active on an unusable row - missing selector?",
202                    constraint
203                )
204            }
205            Self::Lookup {
206                lookup_index,
207                location,
208            } => write!(f, "Lookup {} is not satisfied {}", lookup_index, location),
209            Self::Permutation { column, location } => {
210                write!(
211                    f,
212                    "Equality constraint not satisfied by cell ({:?}, {})",
213                    column, location
214                )
215            }
216        }
217    }
218}
219
220/// Renders `VerifyFailure::CellNotAssigned`.
221///
222/// ```text
223/// error: cell not assigned
224///   Cell layout in region 'Faulty synthesis':
225///     | Offset | A0 | A1 |
226///     +--------+----+----+
227///     |    0   | x0 |    |
228///     |    1   |    |  X | <--{ X marks the spot! 🦜
229///
230///   Gate 'Equality check' (applied at offset 1) queries these cells.
231/// ```
232fn render_cell_not_assigned<F: Field>(
233    gates: &[Gate<F>],
234    gate: &metadata::Gate,
235    region: &metadata::Region,
236    gate_offset: usize,
237    column: Column<Any>,
238    offset: isize,
239) {
240    // Collect the necessary rendering information:
241    // - The columns involved in this gate.
242    // - How many cells are in each column.
243    // - The grid of cell values, indexed by rotation.
244    let mut columns = BTreeMap::<metadata::Column, usize>::default();
245    let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
246    for (i, cell) in gates[gate.index].queried_cells().iter().enumerate() {
247        let cell_column = cell.column.into();
248        *columns.entry(cell_column).or_default() += 1;
249        layout
250            .entry(cell.rotation.0)
251            .or_default()
252            .entry(cell_column)
253            .or_insert_with(|| {
254                if cell.column == column && gate_offset as i32 + cell.rotation.0 == offset as i32 {
255                    "X".to_string()
256                } else {
257                    format!("x{}", i)
258                }
259            });
260    }
261
262    eprintln!("error: cell not assigned");
263    emitter::render_cell_layout(
264        "  ",
265        &FailureLocation::InRegion {
266            region: region.clone(),
267            offset: gate_offset,
268        },
269        &columns,
270        &layout,
271        |row_offset, rotation| {
272            if (row_offset.unwrap() + rotation) as isize == offset {
273                eprint!(" <--{{ X marks the spot! 🦜");
274            }
275        },
276    );
277    eprintln!();
278    eprintln!(
279        "  Gate '{}' (applied at offset {}) queries these cells.",
280        gate.name, gate_offset
281    );
282}
283
284/// Renders `VerifyFailure::ConstraintNotSatisfied`.
285///
286/// ```text
287/// error: constraint not satisfied
288///   Cell layout in region 'somewhere':
289///     | Offset | A0 |
290///     +--------+----+
291///     |    0   | x0 | <--{ Gate 'foo' applied here
292///     |    1   | x1 |
293///
294///   Constraint 'bar':
295///     x1 + x1 * 0x100 + x1 * 0x10000 + x1 * 0x100_0000 - x0 = 0
296///
297///   Assigned cell values:
298///     x0 = 0x5
299///     x1 = 0x5
300/// ```
301fn render_constraint_not_satisfied<F: Field>(
302    gates: &[Gate<F>],
303    constraint: &metadata::Constraint,
304    location: &FailureLocation,
305    cell_values: &[(metadata::VirtualCell, String)],
306) {
307    // Collect the necessary rendering information:
308    // - The columns involved in this constraint.
309    // - How many cells are in each column.
310    // - The grid of cell values, indexed by rotation.
311    let mut columns = BTreeMap::<metadata::Column, usize>::default();
312    let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
313    for (i, (cell, _)) in cell_values.iter().enumerate() {
314        *columns.entry(cell.column).or_default() += 1;
315        layout
316            .entry(cell.rotation)
317            .or_default()
318            .entry(cell.column)
319            .or_insert(format!("x{}", i));
320    }
321
322    eprintln!("error: constraint not satisfied");
323    emitter::render_cell_layout("  ", location, &columns, &layout, |_, rotation| {
324        if rotation == 0 {
325            eprint!(" <--{{ Gate '{}' applied here", constraint.gate.name);
326        }
327    });
328
329    // Print the unsatisfied constraint, in terms of the local variables.
330    eprintln!();
331    eprintln!("  Constraint '{}':", constraint.name);
332    eprintln!(
333        "    {} = 0",
334        emitter::expression_to_string(
335            &gates[constraint.gate.index].polynomials()[constraint.index],
336            &layout
337        )
338    );
339
340    // Print the map from local variables to assigned values.
341    eprintln!();
342    eprintln!("  Assigned cell values:");
343    for (i, (_, value)) in cell_values.iter().enumerate() {
344        eprintln!("    x{} = {}", i, value);
345    }
346}
347
348/// Renders `VerifyFailure::Lookup`.
349///
350/// ```text
351/// error: lookup input does not exist in table
352///   (L0) ∉ (F0)
353///
354///   Lookup inputs:
355///     L0 = x1 * x0 + (1 - x1) * 0x2
356///     ^
357///     | Cell layout in region 'Faulty synthesis':
358///     |   | Offset | A0 | F1 |
359///     |   +--------+----+----+
360///     |   |    1   | x0 | x1 | <--{ Lookup inputs queried here
361///     |
362///     | Assigned cell values:
363///     |   x0 = 0x5
364///     |   x1 = 1
365/// ```
366fn render_lookup<F: FieldExt>(
367    prover: &MockProver<F>,
368    lookup_index: usize,
369    location: &FailureLocation,
370) {
371    let n = prover.n as i32;
372    let cs = &prover.cs;
373    let lookup = &cs.lookups[lookup_index];
374
375    // Get the absolute row on which the lookup's inputs are being queried, so we can
376    // fetch the input values.
377    let row = match location {
378        FailureLocation::InRegion { region, offset } => {
379            prover.regions[region.index].rows.unwrap().0 + offset
380        }
381        FailureLocation::OutsideRegion { row } => *row,
382    } as i32;
383
384    // Recover the fixed columns from the table expressions. We don't allow composite
385    // expressions for the table side of lookups.
386    let table_columns = lookup.table_expressions.iter().map(|expr| {
387        expr.evaluate(
388            &|_| panic!("no constants in table expressions"),
389            &|_| panic!("no selectors in table expressions"),
390            &|_, column, _| format!("F{}", column),
391            &|_, _, _| panic!("no advice columns in table expressions"),
392            &|_, _, _| panic!("no instance columns in table expressions"),
393            &|_| panic!("no negations in table expressions"),
394            &|_, _| panic!("no sums in table expressions"),
395            &|_, _| panic!("no products in table expressions"),
396            &|_, _| panic!("no scaling in table expressions"),
397        )
398    });
399
400    fn cell_value<'a, F: FieldExt>(
401        column_type: Any,
402        load: impl Fn(usize, usize, Rotation) -> Value<F> + 'a,
403    ) -> impl Fn(usize, usize, Rotation) -> BTreeMap<metadata::VirtualCell, String> + 'a {
404        move |query_index, column_index, rotation| {
405            Some((
406                ((column_type, column_index).into(), rotation.0).into(),
407                match load(query_index, column_index, rotation) {
408                    Value::Real(v) => util::format_value(v),
409                    Value::Poison => unreachable!(),
410                },
411            ))
412            .into_iter()
413            .collect()
414        }
415    }
416
417    eprintln!("error: lookup input does not exist in table");
418    eprint!("  (");
419    for i in 0..lookup.input_expressions.len() {
420        eprint!("{}L{}", if i == 0 { "" } else { ", " }, i);
421    }
422    eprint!(") ∉ (");
423    for (i, column) in table_columns.enumerate() {
424        eprint!("{}{}", if i == 0 { "" } else { ", " }, column);
425    }
426    eprintln!(")");
427
428    eprintln!();
429    eprintln!("  Lookup inputs:");
430    for (i, input) in lookup.input_expressions.iter().enumerate() {
431        // Fetch the cell values (since we don't store them in VerifyFailure::Lookup).
432        let cell_values = input.evaluate(
433            &|_| BTreeMap::default(),
434            &|_| panic!("virtual selectors are removed during optimization"),
435            &cell_value(
436                Any::Fixed,
437                &util::load(n, row, &cs.fixed_queries, &prover.fixed),
438            ),
439            &cell_value(
440                Any::Advice,
441                &util::load(n, row, &cs.advice_queries, &prover.advice),
442            ),
443            &cell_value(
444                Any::Instance,
445                &util::load_instance(n, row, &cs.instance_queries, &prover.instance),
446            ),
447            &|a| a,
448            &|mut a, mut b| {
449                a.append(&mut b);
450                a
451            },
452            &|mut a, mut b| {
453                a.append(&mut b);
454                a
455            },
456            &|a, _| a,
457        );
458
459        // Collect the necessary rendering information:
460        // - The columns involved in this constraint.
461        // - How many cells are in each column.
462        // - The grid of cell values, indexed by rotation.
463        let mut columns = BTreeMap::<metadata::Column, usize>::default();
464        let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
465        for (i, (cell, _)) in cell_values.iter().enumerate() {
466            *columns.entry(cell.column).or_default() += 1;
467            layout
468                .entry(cell.rotation)
469                .or_default()
470                .entry(cell.column)
471                .or_insert(format!("x{}", i));
472        }
473
474        if i != 0 {
475            eprintln!();
476        }
477        eprintln!(
478            "    L{} = {}",
479            i,
480            emitter::expression_to_string(input, &layout)
481        );
482        eprintln!("    ^");
483        emitter::render_cell_layout("    | ", location, &columns, &layout, |_, rotation| {
484            if rotation == 0 {
485                eprint!(" <--{{ Lookup inputs queried here");
486            }
487        });
488
489        // Print the map from local variables to assigned values.
490        eprintln!("    |");
491        eprintln!("    | Assigned cell values:");
492        for (i, (_, value)) in cell_values.iter().enumerate() {
493            eprintln!("    |   x{} = {}", i, value);
494        }
495    }
496}
497
498impl VerifyFailure {
499    /// Emits this failure in pretty-printed format to stderr.
500    pub(super) fn emit<F: FieldExt>(&self, prover: &MockProver<F>) {
501        match self {
502            Self::CellNotAssigned {
503                gate,
504                region,
505                gate_offset,
506                column,
507                offset,
508            } => render_cell_not_assigned(
509                &prover.cs.gates,
510                gate,
511                region,
512                *gate_offset,
513                *column,
514                *offset,
515            ),
516            Self::ConstraintNotSatisfied {
517                constraint,
518                location,
519                cell_values,
520            } => {
521                render_constraint_not_satisfied(&prover.cs.gates, constraint, location, cell_values)
522            }
523            Self::Lookup {
524                lookup_index,
525                location,
526            } => render_lookup(prover, *lookup_index, location),
527            _ => eprintln!("{}", self),
528        }
529    }
530}