diff --git a/halo2_proofs/CHANGELOG.md b/halo2_proofs/CHANGELOG.md index 680b1dc458..7816f5a01e 100644 --- a/halo2_proofs/CHANGELOG.md +++ b/halo2_proofs/CHANGELOG.md @@ -19,6 +19,11 @@ and this project adheres to Rust's notion of - `halo2_proofs::circuit::layouter`: - `RegionLayouter::instance_value` method added to provide access to instance values within a region. + - `RegionLayouter::annotate_column` method added to provide annotation capabilities + to the circuit Columns. +- `halo2_proofs::plonk::circuit`: + - `ConstraintSystem::annotate_lookup_column` method added to allow for + `TableColumn` annotation capabilities. ### Changed - Migrated to `ff 0.13`, `group 0.13`, `pasta_curves 0.5`. diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index 7f16651670..b13e51f4de 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -201,6 +201,20 @@ impl<'r, F: Field> Region<'r, F> { .enable_selector(&|| annotation().into(), selector, offset) } + /// Allows the circuit implementor to name/annotate a Column within a Region context. + /// + /// This is useful in order to improve the amount of information that `prover.verify()` + /// and `prover.assert_satisfied()` can provide. + pub fn annotate_column(&mut self, annotation: A, column: T) + where + A: Fn() -> AR, + AR: Into, + T: Into>, + { + self.region + .annotate_column(&|| annotation().into(), column.into()); + } + /// Assign an advice column value (witness). /// /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. diff --git a/halo2_proofs/src/circuit/floor_planner/single_pass.rs b/halo2_proofs/src/circuit/floor_planner/single_pass.rs index 349373759c..4e9d15c4e3 100644 --- a/halo2_proofs/src/circuit/floor_planner/single_pass.rs +++ b/halo2_proofs/src/circuit/floor_planner/single_pass.rs @@ -275,6 +275,14 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter ) } + fn annotate_column<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + ) { + self.layouter.cs.annotate_column(annotation, column); + } + fn assign_advice<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), diff --git a/halo2_proofs/src/circuit/floor_planner/v1.rs b/halo2_proofs/src/circuit/floor_planner/v1.rs index fe24d10eda..4d6f3f9508 100644 --- a/halo2_proofs/src/circuit/floor_planner/v1.rs +++ b/halo2_proofs/src/circuit/floor_planner/v1.rs @@ -482,6 +482,14 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r Ok(()) } + fn annotate_column<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + ) { + self.plan.cs.annotate_column(annotation, column) + } + fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { self.plan.cs.copy( left.column, diff --git a/halo2_proofs/src/circuit/layouter.rs b/halo2_proofs/src/circuit/layouter.rs index 695c450432..79ebe38d45 100644 --- a/halo2_proofs/src/circuit/layouter.rs +++ b/halo2_proofs/src/circuit/layouter.rs @@ -48,6 +48,15 @@ pub trait RegionLayouter: fmt::Debug { offset: usize, ) -> Result<(), Error>; + /// Allows the circuit implementor to name/annotate a Column within a Region context. + /// + /// This is useful in order to improve the amount of information that [`crate::dev::MockProver::assert_satisfied()`] can provide. + fn annotate_column<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + ); + /// Assign an advice column value (witness) fn assign_advice<'v>( &'v mut self, @@ -288,6 +297,14 @@ impl RegionLayouter for RegionShape { }) } + fn annotate_column<'v>( + &'v mut self, + _annotation: &'v (dyn Fn() -> String + 'v), + _column: Column, + ) { + // Do nothing + } + fn constrain_constant(&mut self, _cell: Cell, _constant: Assigned) -> Result<(), Error> { // Global constants don't affect the region shape. Ok(()) diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index 41e6d92fdd..8710d67ca5 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -17,6 +17,7 @@ use crate::{ }; pub mod metadata; +use metadata::Column as ColumnMetadata; mod util; mod failure; @@ -46,6 +47,8 @@ struct Region { /// The selectors that have been enabled in this region. All other selectors are by /// construction not enabled. enabled_selectors: HashMap>, + /// Annotations given to Advice, Fixed or Instance columns within a region context. + annotations: HashMap, /// The cells assigned in this region. We store this as a `Vec` so that if any cells /// are double-assigned, they will be visibly darker. cells: Vec<(Column, usize)>, @@ -318,6 +321,7 @@ impl Assignment for MockProver { name: name().into(), columns: HashSet::default(), rows: None, + annotations: HashMap::default(), enabled_selectors: HashMap::default(), cells: vec![], }); @@ -327,6 +331,18 @@ impl Assignment for MockProver { self.regions.push(self.current_region.take().unwrap()); } + fn annotate_column(&mut self, annotation: A, column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + if let Some(region) = self.current_region.as_mut() { + region + .annotations + .insert(ColumnMetadata::from(column), annotation().into()); + } + } + fn enable_selector(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error> where A: FnOnce() -> AR, @@ -565,6 +581,10 @@ impl MockProver { /// Returns `Ok(())` if this `MockProver` is satisfied, or a list of errors indicating /// the reasons that the circuit is not satisfied. + /// + /// ### Note + /// This method will not print any annotations (in case they've been placed). If annotation printing is desired + /// please use [`MockProver::assert_satisfied`]. pub fn verify(&self) -> Result<(), Vec> { let n = self.n as i32; @@ -603,7 +623,12 @@ impl MockProver { InstanceValue::Assigned(_) => None, _ => Some(VerifyFailure::InstanceCellNotAssigned { gate: (gate_index, gate.name()).into(), - region: (r_i, r.name.clone()).into(), + region: ( + r_i, + r.name.clone(), + r.annotations.clone(), + ) + .into(), gate_offset: *selector_row, column: cell.column.try_into().unwrap(), row: cell_row, @@ -617,7 +642,12 @@ impl MockProver { } else { Some(VerifyFailure::CellNotAssigned { gate: (gate_index, gate.name()).into(), - region: (r_i, r.name.clone()).into(), + region: ( + r_i, + r.name.clone(), + r.annotations.clone(), + ) + .into(), gate_offset: *selector_row, column: cell.column, offset: cell_row as isize @@ -898,7 +928,7 @@ impl MockProver { /// Panics if the circuit being checked by this `MockProver` is not satisfied. /// /// Any verification failures will be pretty-printed to stderr before the function - /// panics. + /// panics. These will also include annotations (in case they've been placed). /// /// Apart from the stderr output, this method is equivalent to: /// ```ignore @@ -924,7 +954,7 @@ mod tests { use crate::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{ - Advice, Any, Circuit, Column, ConstraintSystem, Error, Expression, Selector, + Advice, Any, Circuit, Column, ConstraintSystem, Error, Expression, Fixed, Selector, TableColumn, }, poly::Rotation, @@ -937,6 +967,7 @@ mod tests { #[derive(Clone)] struct FaultyCircuitConfig { a: Column, + b: Column, q: Selector, } @@ -960,7 +991,7 @@ mod tests { vec![q * (a - b)] }); - FaultyCircuitConfig { a, q } + FaultyCircuitConfig { a, b, q } } fn without_witnesses(&self) -> Self { @@ -981,6 +1012,12 @@ mod tests { // Assign a = 0. region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::ZERO))?; + // Name Column a + region.annotate_column(|| "This is annotated!", config.a); + + // Name Column b + region.annotate_column(|| "This is also annotated!", config.b); + // BUG: Forget to assign b = 0! This could go unnoticed during // development, because cell values default to zero, which in this // case is fine, but for other assignments would be broken. @@ -1024,6 +1061,7 @@ mod tests { let a = meta.advice_column(); let q = meta.complex_selector(); let table = meta.lookup_table_column(); + meta.annotate_lookup_column(|| "Table1", table); meta.lookup(|cells| { let a = cells.query_advice(a, Rotation::cur()); @@ -1112,6 +1150,8 @@ mod tests { || Value::known(Fp::from(5)), )?; + region.annotate_column(|| "Witness example", config.a); + Ok(()) }, ) @@ -1130,4 +1170,146 @@ mod tests { }]) ); } + + #[test] + fn contraint_unsatisfied() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + a: Column, + b: Column, + c: Column, + d: Column, + q: Selector, + } + + struct FaultyCircuit {} + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + let d = meta.fixed_column(); + let q = meta.selector(); + + meta.create_gate("Equality check", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + let b = cells.query_advice(b, Rotation::cur()); + let c = cells.query_advice(c, Rotation::cur()); + let d = cells.query_fixed(d); + let q = cells.query_selector(q); + + // If q is enabled, a and b must be assigned to. + vec![q * (a - b) * (c - d)] + }); + + FaultyCircuitConfig { a, b, c, d, q } + } + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "Correct synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 1. + region.assign_advice(|| "b", config.b, 0, || Value::known(Fp::one()))?; + + // Assign c = 5. + region.assign_advice( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(Fp::from(7u64)), + )?; + Ok(()) + }, + )?; + layouter.assign_region( + || "Wrong synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 0. + region.assign_advice(|| "b", config.b, 0, || Value::known(Fp::zero()))?; + + // Name Column a + region.annotate_column(|| "This is Advice!", config.a); + // Name Column b + region.annotate_column(|| "This is Advice too!", config.b); + + // Assign c = 5. + region.assign_advice( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(Fp::from(7u64)), + )?; + + // Name Column c + region.annotate_column(|| "Another one!", config.c); + // Name Column d + region.annotate_column(|| "This is a Fixed!", config.d); + + // Note that none of the terms cancel each other. Therefore we will have a constraint that is not satisfied for + // the `Equality check` gate. + Ok(()) + }, + ) + } + } + + let prover = MockProver::run(K, &FaultyCircuit {}, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![VerifyFailure::ConstraintNotSatisfied { + constraint: ((0, "Equality check").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (1, "Wrong synthesis").into(), + offset: 0, + }, + cell_values: vec![ + (((Any::Advice, 0).into(), 0).into(), "1".to_string()), + (((Any::Advice, 1).into(), 0).into(), "0".to_string()), + (((Any::Advice, 2).into(), 0).into(), "0x5".to_string()), + (((Any::Fixed, 0).into(), 0).into(), "0x7".to_string()), + ], + },]) + ) + } } diff --git a/halo2_proofs/src/dev/cost.rs b/halo2_proofs/src/dev/cost.rs index 7f82f1b2a8..30b60fd2e8 100644 --- a/halo2_proofs/src/dev/cost.rs +++ b/halo2_proofs/src/dev/cost.rs @@ -241,6 +241,14 @@ impl Assignment for Layout { Ok(()) } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn push_namespace(&mut self, _: N) where NR: Into, diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index c6c5aecfff..a42a8e3778 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -1,12 +1,13 @@ use std::collections::{BTreeMap, HashSet}; -use std::fmt; +use std::fmt::{self, Debug}; use group::ff::Field; +use super::MockProver; use super::{ metadata, util::{self, AnyQuery}, - MockProver, Region, + Region, }; use crate::{ dev::Value, @@ -16,7 +17,7 @@ use crate::{ mod emitter; /// The location within the circuit at which a particular [`VerifyFailure`] occurred. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum FailureLocation { /// A location inside a region. InRegion { @@ -99,7 +100,7 @@ impl FailureLocation { } }) .map(|(r_i, r)| FailureLocation::InRegion { - region: (r_i, r.name.clone()).into(), + region: (r_i, r.name.clone(), r.annotations.clone()).into(), offset: failure_row - r.rows.unwrap().0, }) .unwrap_or_else(|| FailureLocation::OutsideRegion { row: failure_row }) @@ -223,6 +224,7 @@ impl fmt::Display for VerifyFailure { for (name, value) in cell_values { writeln!(f, "- {} = {}", name, value)?; } + Ok(()) } Self::ConstraintPoisoned { constraint } => { @@ -417,7 +419,7 @@ fn render_lookup( expr.evaluate( &|_| panic!("no constants in table expressions"), &|_| panic!("no selectors in table expressions"), - &|query| format!("F{}", query.column_index), + &|query| metadata::Column::from((Any::Fixed, query.column_index)), &|_| panic!("no advice columns in table expressions"), &|_| panic!("no instance columns in table expressions"), &|_| panic!("no negations in table expressions"), @@ -454,9 +456,19 @@ fn render_lookup( for i in 0..lookup.input_expressions.len() { eprint!("{}L{}", if i == 0 { "" } else { ", " }, i); } + eprint!(") ∉ ("); for (i, column) in table_columns.enumerate() { - eprint!("{}{}", if i == 0 { "" } else { ", " }, column); + eprint!( + "{}{}", + if i == 0 { "" } else { ", " }, + prover + .cs + .lookup_annotations + .get(&column) + .cloned() + .unwrap_or_else(|| format!("{}", column)) + ); } eprintln!(")"); @@ -515,6 +527,7 @@ fn render_lookup( emitter::expression_to_string(input, &layout) ); eprintln!(" ^"); + emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| { if rotation == 0 { eprint!(" <--{{ Lookup inputs queried here"); diff --git a/halo2_proofs/src/dev/failure/emitter.rs b/halo2_proofs/src/dev/failure/emitter.rs index 071dca6f16..ee3fee3789 100644 --- a/halo2_proofs/src/dev/failure/emitter.rs +++ b/halo2_proofs/src/dev/failure/emitter.rs @@ -11,6 +11,7 @@ use crate::{ fn padded(p: char, width: usize, text: &str) -> String { let pad = width - text.len(); + format!( "{}{}{}", iter::repeat(p).take(pad - pad / 2).collect::(), @@ -19,6 +20,18 @@ fn padded(p: char, width: usize, text: &str) -> String { ) } +fn column_type_and_idx(column: &metadata::Column) -> String { + format!( + "{}{}", + match column.column_type { + Any::Advice => "A", + Any::Fixed => "F", + Any::Instance => "I", + }, + column.index + ) +} + /// Renders a cell layout around a given failure location. /// /// `highlight_row` is called at the end of each row, with the offset of the active row @@ -32,46 +45,67 @@ pub(super) fn render_cell_layout( highlight_row: impl Fn(Option, i32), ) { let col_width = |cells: usize| cells.to_string().len() + 3; + let mut col_headers = String::new(); // If we are in a region, show rows at offsets relative to it. Otherwise, just show // the rotations directly. let offset = match location { FailureLocation::InRegion { region, offset } => { - eprintln!("{}Cell layout in region '{}':", prefix, region.name); - eprint!("{} | Offset |", prefix); + col_headers + .push_str(format!("{}Cell layout in region '{}':\n", prefix, region.name).as_str()); + col_headers.push_str(format!("{} | Offset |", prefix).as_str()); Some(*offset as i32) } FailureLocation::OutsideRegion { row } => { - eprintln!("{}Cell layout at row {}:", prefix, row); - eprint!("{} |Rotation|", prefix); + col_headers.push_str(format!("{}Cell layout at row {}:\n", prefix, row).as_str()); + col_headers.push_str(format!("{} |Rotation|", prefix).as_str()); None } }; + eprint!("\n{}", col_headers); + + let widths: Vec = columns + .iter() + .map(|(col, _)| { + let mut size = col_width(column_type_and_idx(col).as_str().len()); + if let FailureLocation::InRegion { region, offset: _ } = location { + region + .column_annotations + .as_ref() + .map(|column_ann| column_ann.get(col).map(|ann| size = ann.len())); + }; + size + }) + .collect(); - // Print the assigned cells, and their region offset or rotation. - for (column, cells) in columns { - let width = col_width(*cells); + // Print the assigned cells, and their region offset or rotation + the column name at which they're assigned to. + for ((column, _), &width) in columns.iter().zip(widths.iter()) { eprint!( "{}|", padded( ' ', width, - &format!( - "{}{}", - match column.column_type { - Any::Advice => "A", - Any::Fixed => "F", - Any::Instance => "I", - }, - column.index, - ) + &match location { + FailureLocation::InRegion { region, offset: _ } => { + region + .column_annotations + .as_ref() + .and_then(|column_ann| column_ann.get(column).cloned()) + .unwrap_or_else(|| column_type_and_idx(column)) + } + FailureLocation::OutsideRegion { row: _ } => { + column_type_and_idx(column) + } + } + .to_string() ) ); } + eprintln!(); eprint!("{} +--------+", prefix); - for cells in columns.values() { - eprint!("{}+", padded('-', col_width(*cells), "")); + for &width in widths.iter() { + eprint!("{}+", padded('-', width, "")); } eprintln!(); for (rotation, row) in layout { @@ -80,8 +114,7 @@ pub(super) fn render_cell_layout( prefix, padded(' ', 8, &(offset.unwrap_or(0) + rotation).to_string()) ); - for (col, cells) in columns { - let width = col_width(*cells); + for ((col, _), &width) in columns.iter().zip(widths.iter()) { eprint!( "{}|", padded( diff --git a/halo2_proofs/src/dev/graph.rs b/halo2_proofs/src/dev/graph.rs index 18ef8154df..3315b8fcc8 100644 --- a/halo2_proofs/src/dev/graph.rs +++ b/halo2_proofs/src/dev/graph.rs @@ -99,6 +99,14 @@ impl Assignment for Graph { Ok(()) } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn query_instance(&self, _: Column, _: usize) -> Result, Error> { Ok(Value::unknown()) } diff --git a/halo2_proofs/src/dev/metadata.rs b/halo2_proofs/src/dev/metadata.rs index 9a47e3cb35..d92f3b0c07 100644 --- a/halo2_proofs/src/dev/metadata.rs +++ b/halo2_proofs/src/dev/metadata.rs @@ -1,10 +1,13 @@ //! Metadata about circuits. +use super::metadata::Column as ColumnMetadata; use crate::plonk::{self, Any}; -use std::fmt; - +use std::{ + collections::HashMap, + fmt::{self, Debug}, +}; /// Metadata about a column within a circuit. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Column { /// The type of the column. pub(super) column_type: Any, @@ -33,6 +36,40 @@ impl From> for Column { } } +/// A helper structure that allows to print a Column with it's annotation as a single structure. +#[derive(Debug, Clone)] +struct DebugColumn { + /// The type of the column. + column_type: Any, + /// The index of the column. + index: usize, + /// Annotation of the column + annotation: String, +} + +impl From<(Column, Option<&HashMap>)> for DebugColumn { + fn from(info: (Column, Option<&HashMap>)) -> Self { + DebugColumn { + column_type: info.0.column_type, + index: info.0.index, + annotation: info + .1 + .and_then(|map| map.get(&info.0).cloned()) + .unwrap_or_default(), + } + } +} + +impl fmt::Display for DebugColumn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Column('{:?}', {} - {})", + self.column_type, self.index, self.annotation + ) + } +} + /// A "virtual cell" is a PLONK cell that has been queried at a particular relative offset /// within a custom gate. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -83,7 +120,7 @@ impl fmt::Display for VirtualCell { } /// Metadata about a configured gate within a circuit. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Gate { /// The index of the active gate. These indices are assigned in the order in which /// `ConstraintSystem::create_gate` is called during `Circuit::configure`. @@ -106,7 +143,7 @@ impl From<(usize, &'static str)> for Gate { } /// Metadata about a configured constraint within a circuit. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Constraint { /// The gate containing the constraint. pub(super) gate: Gate, @@ -143,7 +180,7 @@ impl From<(Gate, usize, &'static str)> for Constraint { } /// Metadata about an assigned region within a circuit. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone)] pub struct Region { /// The index of the region. These indices are assigned in the order in which /// `Layouter::assign_region` is called during `Circuit::synthesize`. @@ -151,6 +188,22 @@ pub struct Region { /// The name of the region. This is specified by the region creator (such as a chip /// implementation), and is not enforced to be unique. pub(super) name: String, + /// A reference to the annotations of the Columns that exist within this `Region`. + pub(super) column_annotations: Option>, +} + +impl PartialEq for Region { + fn eq(&self, other: &Self) -> bool { + self.index == other.index && self.name == other.name + } +} + +impl Eq for Region {} + +impl Debug for Region { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Region {} ('{}')", self.index, self.name) + } } impl fmt::Display for Region { @@ -161,7 +214,11 @@ impl fmt::Display for Region { impl From<(usize, String)> for Region { fn from((index, name): (usize, String)) -> Self { - Region { index, name } + Region { + index, + name, + column_annotations: None, + } } } @@ -170,6 +227,27 @@ impl From<(usize, &str)> for Region { Region { index, name: name.to_owned(), + column_annotations: None, + } + } +} + +impl From<(usize, String, HashMap)> for Region { + fn from((index, name, annotations): (usize, String, HashMap)) -> Self { + Region { + index, + name, + column_annotations: Some(annotations), + } + } +} + +impl From<(usize, &str, HashMap)> for Region { + fn from((index, name, annotations): (usize, &str, HashMap)) -> Self { + Region { + index, + name: name.to_owned(), + column_annotations: Some(annotations), } } } diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 334755dded..585937c981 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -1,12 +1,14 @@ use core::cmp::max; use core::ops::{Add, Mul}; use ff::Field; +use std::collections::HashMap; use std::{ convert::TryFrom, ops::{Neg, Sub}, }; use super::{lookup, permutation, Assigned, Error}; +use crate::dev::metadata; use crate::{ circuit::{Layouter, Region, Value}, poly::Rotation, @@ -349,6 +351,12 @@ pub trait Assignment { NR: Into, N: FnOnce() -> NR; + /// Annotates a column within the current region. + fn annotate_column(&mut self, annotation: A, column: Column) + where + A: FnOnce() -> AR, + AR: Into; + /// Exits the current region. /// /// Panics if we are not currently in a region (if `enter_region` was not called). @@ -958,6 +966,9 @@ pub struct ConstraintSystem { // input expressions and a sequence of table expressions involved in the lookup. pub(crate) lookups: Vec>, + // List of indexes of Fixed columns which are associated to a `TableColumn` tied to their annotation. + pub(crate) lookup_annotations: HashMap, + // Vector of fixed columns, which can be used to store constant values // that are copied into advice columns. pub(crate) constants: Vec>, @@ -1008,6 +1019,7 @@ impl Default for ConstraintSystem { instance_queries: Vec::new(), permutation: permutation::Argument::new(), lookups: Vec::new(), + lookup_annotations: HashMap::new(), constants: vec![], minimum_degree: None, } @@ -1367,6 +1379,19 @@ impl ConstraintSystem { } } + /// Annotate a Lookup column. + pub fn annotate_lookup_column(&mut self, annotation: A, column: TableColumn) + where + A: Fn() -> AR, + AR: Into, + { + // We don't care if the table has already an annotation. If it's the case we keep the original one. + self.lookup_annotations.insert( + metadata::Column::from((Any::Fixed, column.inner().index)), + annotation().into(), + ); + } + /// Allocate a new fixed column pub fn fixed_column(&mut self) -> Column { let tmp = Column { diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index bb26d5a6fb..e13dfb8ac2 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -172,6 +172,14 @@ impl Assignment for Assembly { Ok(()) } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn push_namespace(&mut self, _: N) where NR: Into, diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index e54f2129f0..35215bea54 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -253,6 +253,14 @@ pub fn create_proof< Ok(()) } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn push_namespace(&mut self, _: N) where NR: Into,