use std::{
collections::{HashMap, HashSet},
iter,
ops::{Add, Mul, Neg, Range},
};
use blake2b_simd::blake2b;
use ff::{Field, FromUniformBytes};
use rayon::{
iter::{
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator,
},
slice::ParallelSliceMut,
};
use crate::{
circuit,
plonk::{
permutation,
permutation::keygen::Assembly,
sealed::{self, SealedPhase},
Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, Expression,
FirstPhase, Fixed, FloorPlanner, Instance, Phase, Selector,
},
};
pub mod metadata;
use metadata::Column as ColumnMetadata;
mod util;
mod failure;
pub use failure::{FailureLocation, VerifyFailure};
pub mod cost_model;
mod gates;
pub use gates::CircuitGates;
mod tfp;
pub use tfp::TracingFloorPlanner;
use crate::{plonk::VirtualCell, poly::Rotation, utils::rational::Rational};
#[derive(Debug)]
struct Region {
name: String,
columns: HashSet<Column<Any>>,
rows: Option<(usize, usize)>,
enabled_selectors: HashMap<Selector, Vec<usize>>,
annotations: HashMap<ColumnMetadata, String>,
cells: HashMap<(Column<Any>, usize), usize>,
}
impl Region {
fn update_extent(&mut self, column: Column<Any>, row: usize) {
self.columns.insert(column);
let (mut start, mut end) = self.rows.unwrap_or((row, row));
if row < start {
start = row;
}
if row > end {
end = row;
}
self.rows = Some((start, end));
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CellValue<F: Field> {
Unassigned,
Assigned(F),
Poison(usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
enum Value<F: Field> {
Real(F),
Poison,
}
impl<F: Field> From<CellValue<F>> for Value<F> {
fn from(value: CellValue<F>) -> Self {
match value {
CellValue::Unassigned => Value::Real(F::ZERO),
CellValue::Assigned(v) => Value::Real(v),
CellValue::Poison(_) => Value::Poison,
}
}
}
impl<F: Field> Neg for Value<F> {
type Output = Self;
fn neg(self) -> Self::Output {
match self {
Value::Real(a) => Value::Real(-a),
_ => Value::Poison,
}
}
}
impl<F: Field> Add for Value<F> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Value::Real(a), Value::Real(b)) => Value::Real(a + b),
_ => Value::Poison,
}
}
}
impl<F: Field> Mul for Value<F> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Value::Real(a), Value::Real(b)) => Value::Real(a * b),
(Value::Real(x), Value::Poison) | (Value::Poison, Value::Real(x))
if x.is_zero_vartime() =>
{
Value::Real(F::ZERO)
}
_ => Value::Poison,
}
}
}
impl<F: Field> Mul<F> for Value<F> {
type Output = Self;
fn mul(self, rhs: F) -> Self::Output {
match self {
Value::Real(lhs) => Value::Real(lhs * rhs),
Value::Poison if rhs.is_zero_vartime() => Value::Real(F::ZERO),
_ => Value::Poison,
}
}
}
#[derive(Debug)]
pub struct MockProver<F: Field> {
k: u32,
n: u32,
cs: ConstraintSystem<F>,
regions: Vec<Region>,
current_region: Option<Region>,
fixed: Vec<Vec<CellValue<F>>>,
advice: Vec<Vec<CellValue<F>>>,
instance: Vec<Vec<InstanceValue<F>>>,
selectors: Vec<Vec<bool>>,
challenges: Vec<F>,
permutation: permutation::keygen::Assembly,
usable_rows: Range<usize>,
current_phase: sealed::Phase,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InstanceValue<F: Field> {
Assigned(F),
Padding,
}
impl<F: Field> InstanceValue<F> {
fn value(&self) -> F {
match self {
InstanceValue::Assigned(v) => *v,
InstanceValue::Padding => F::ZERO,
}
}
}
impl<F: Field> MockProver<F> {
fn in_phase<P: Phase>(&self, phase: P) -> bool {
self.current_phase == phase.to_sealed()
}
}
impl<F: Field> Assignment<F> for MockProver<F> {
fn enter_region<NR, N>(&mut self, name: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
if !self.in_phase(FirstPhase) {
return;
}
assert!(self.current_region.is_none());
self.current_region = Some(Region {
name: name().into(),
columns: HashSet::default(),
rows: None,
annotations: HashMap::default(),
enabled_selectors: HashMap::default(),
cells: HashMap::default(),
});
}
fn exit_region(&mut self) {
if !self.in_phase(FirstPhase) {
return;
}
self.regions.push(self.current_region.take().unwrap());
}
fn annotate_column<A, AR>(&mut self, annotation: A, column: Column<Any>)
where
A: FnOnce() -> AR,
AR: Into<String>,
{
if !self.in_phase(FirstPhase) {
return;
}
if let Some(region) = self.current_region.as_mut() {
region.annotations.insert(ColumnMetadata::from(column), annotation().into());
}
}
fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
where
A: FnOnce() -> AR,
AR: Into<String>,
{
if !self.in_phase(FirstPhase) {
return Ok(());
}
assert!(
self.usable_rows.contains(&row),
"row={} not in usable_rows={:?}, k={}",
row,
self.usable_rows,
self.k,
);
self.current_region
.as_mut()
.unwrap()
.enabled_selectors
.entry(*selector)
.or_default()
.push(row);
self.selectors[selector.0][row] = true;
Ok(())
}
fn query_instance(
&self,
column: Column<Instance>,
row: usize,
) -> Result<circuit::Value<F>, Error> {
assert!(
self.usable_rows.contains(&row),
"row={}, usable_rows={:?}, k={}",
row,
self.usable_rows,
self.k,
);
Ok(self
.instance
.get(column.index())
.and_then(|column| column.get(row))
.map(|v| circuit::Value::known(v.value()))
.expect("bound failure"))
}
fn assign_advice<V, VR, A, AR>(
&mut self,
_: A,
column: Column<Advice>,
row: usize,
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> circuit::Value<VR>,
VR: Into<Rational<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
{
if self.in_phase(FirstPhase) {
assert!(
self.usable_rows.contains(&row),
"row={}, usable_rows={:?}, k={}",
row,
self.usable_rows,
self.k,
);
if let Some(region) = self.current_region.as_mut() {
region.update_extent(column.into(), row);
region
.cells
.entry((column.into(), row))
.and_modify(|count| *count += 1)
.or_default();
}
}
match to().into_field().evaluate().assign() {
Ok(to) => {
let value = self
.advice
.get_mut(column.index())
.and_then(|v| v.get_mut(row))
.expect("bounds failure");
*value = CellValue::Assigned(to);
}
Err(err) => {
if self.in_phase(column.column_type().phase) {
return Err(err);
}
}
}
Ok(())
}
fn assign_fixed<V, VR, A, AR>(
&mut self,
_: A,
column: Column<Fixed>,
row: usize,
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> circuit::Value<VR>,
VR: Into<Rational<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
{
if !self.in_phase(FirstPhase) {
return Ok(());
}
assert!(
self.usable_rows.contains(&row),
"row={}, usable_rows={:?}, k={}",
row,
self.usable_rows,
self.k,
);
if let Some(region) = self.current_region.as_mut() {
region.update_extent(column.into(), row);
region
.cells
.entry((column.into(), row))
.and_modify(|count| *count += 1)
.or_default();
}
*self
.fixed
.get_mut(column.index())
.and_then(|v| v.get_mut(row))
.expect("bounds failure") = CellValue::Assigned(to().into_field().evaluate().assign()?);
Ok(())
}
fn copy(
&mut self,
left_column: Column<Any>,
left_row: usize,
right_column: Column<Any>,
right_row: usize,
) -> Result<(), crate::plonk::Error> {
if !self.in_phase(FirstPhase) {
return Ok(());
}
assert!(
self.usable_rows.contains(&left_row) && self.usable_rows.contains(&right_row),
"left_row={}, right_row={}, usable_rows={:?}, k={}",
left_row,
right_row,
self.usable_rows,
self.k,
);
self.permutation.copy(left_column, left_row, right_column, right_row)
}
fn fill_from_row(
&mut self,
col: Column<Fixed>,
from_row: usize,
to: circuit::Value<Rational<F>>,
) -> Result<(), Error> {
if !self.in_phase(FirstPhase) {
return Ok(());
}
assert!(
self.usable_rows.contains(&from_row),
"row={}, usable_rows={:?}, k={}",
from_row,
self.usable_rows,
self.k,
);
for row in self.usable_rows.clone().skip(from_row) {
self.assign_fixed(|| "", col, row, || to)?;
}
Ok(())
}
fn get_challenge(&self, challenge: Challenge) -> circuit::Value<F> {
if self.current_phase <= challenge.phase {
return circuit::Value::unknown();
}
circuit::Value::known(self.challenges[challenge.index()])
}
fn push_namespace<NR, N>(&mut self, _: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
}
fn pop_namespace(&mut self, _: Option<String>) {
}
}
impl<F: FromUniformBytes<64> + Ord> MockProver<F> {
pub fn run<ConcreteCircuit: Circuit<F>>(
k: u32,
circuit: &ConcreteCircuit,
instance: Vec<Vec<F>>,
) -> Result<Self, Error> {
let n = 1 << k;
let mut cs = ConstraintSystem::default();
#[cfg(feature = "circuit-params")]
let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params());
#[cfg(not(feature = "circuit-params"))]
let config = ConcreteCircuit::configure(&mut cs);
let cs = cs;
assert!(
n >= cs.minimum_rows(),
"n={}, minimum_rows={}, k={}",
n,
cs.minimum_rows(),
k,
);
assert_eq!(instance.len(), cs.num_instance_columns);
let instance = instance
.into_iter()
.map(|instance| {
assert!(
instance.len() <= n - (cs.blinding_factors() + 1),
"instance.len={}, n={}, cs.blinding_factors={}",
instance.len(),
n,
cs.blinding_factors()
);
let mut instance_values = vec![InstanceValue::Padding; n];
for (idx, value) in instance.into_iter().enumerate() {
instance_values[idx] = InstanceValue::Assigned(value);
}
instance_values
})
.collect::<Vec<_>>();
let fixed = vec![vec![CellValue::Unassigned; n]; cs.num_fixed_columns];
let selectors = vec![vec![false; n]; cs.num_selectors];
let blinding_factors = cs.blinding_factors();
let usable_rows = n - (blinding_factors + 1);
let advice = vec![
{
let mut column = vec![CellValue::Unassigned; n];
for (i, cell) in column.iter_mut().enumerate().skip(usable_rows) {
*cell = CellValue::Poison(i);
}
column
};
cs.num_advice_columns
];
let permutation = permutation::keygen::Assembly::new(n, &cs.permutation);
let constants = cs.constants.clone();
let challenges = {
let mut hash: [u8; 64] = blake2b(b"Halo2-MockProver").as_bytes().try_into().unwrap();
iter::repeat_with(|| {
hash = blake2b(&hash).as_bytes().try_into().unwrap();
F::from_uniform_bytes(&hash)
})
.take(cs.num_challenges)
.collect()
};
let mut prover = MockProver {
k,
n: n as u32,
cs,
regions: vec![],
current_region: None,
fixed,
advice,
instance,
selectors,
challenges,
permutation,
usable_rows: 0..usable_rows,
current_phase: FirstPhase.to_sealed(),
};
for current_phase in prover.cs.phases() {
prover.current_phase = current_phase;
ConcreteCircuit::FloorPlanner::synthesize(
&mut prover,
circuit,
config.clone(),
constants.clone(),
)?;
}
let (cs, selector_polys) =
prover.cs.directly_convert_selectors_to_fixed(prover.selectors.clone());
prover.cs = cs;
prover.fixed.extend(selector_polys.into_iter().map(|poly| {
let mut v = vec![CellValue::Unassigned; n];
for (v, p) in v.iter_mut().zip(&poly[..]) {
*v = CellValue::Assigned(*p);
}
v
}));
Ok(prover)
}
pub fn advice_values(&self, column: Column<Advice>) -> &[CellValue<F>] {
&self.advice[column.index()]
}
pub fn fixed_values(&self, column: Column<Fixed>) -> &[CellValue<F>] {
&self.fixed[column.index()]
}
pub fn verify(&self) -> Result<(), Vec<VerifyFailure>> {
self.verify_at_rows(self.usable_rows.clone(), self.usable_rows.clone())
}
pub fn verify_at_rows<I: Clone + Iterator<Item = usize>>(
&self,
gate_row_ids: I,
lookup_input_row_ids: I,
) -> Result<(), Vec<VerifyFailure>> {
let n = self.n as i32;
let gate_row_ids = gate_row_ids.collect::<Vec<_>>();
let lookup_input_row_ids = lookup_input_row_ids.collect::<Vec<_>>();
gate_row_ids.par_iter().for_each(|row_id| {
if !self.usable_rows.contains(row_id) {
panic!("invalid gate row id {row_id}");
}
});
lookup_input_row_ids.par_iter().for_each(|row_id| {
if !self.usable_rows.contains(row_id) {
panic!("invalid gate row id {row_id}");
}
});
let selector_errors = self.regions.iter().enumerate().flat_map(|(r_i, r)| {
r.enabled_selectors.iter().flat_map(move |(selector, at)| {
self.cs
.gates
.iter()
.enumerate()
.filter(move |(_, g)| g.queried_selectors().contains(selector))
.flat_map(move |(gate_index, gate)| {
at.par_iter()
.flat_map(move |selector_row| {
let gate_row = *selector_row as i32;
gate.queried_cells()
.iter()
.filter_map(move |cell| {
let cell_row =
((gate_row + n + cell.rotation.0) % n) as usize;
match cell.column.column_type() {
Any::Instance => {
let instance_value =
&self.instance[cell.column.index()][cell_row];
match instance_value {
InstanceValue::Assigned(_) => None,
_ => Some(
VerifyFailure::InstanceCellNotAssigned {
gate: (gate_index, gate.name()).into(),
region: (r_i, r.name.clone()).into(),
gate_offset: *selector_row,
column: cell.column.try_into().unwrap(),
row: cell_row,
},
),
}
}
_ => {
if r.cells.contains_key(&(cell.column, cell_row))
|| gate.polynomials().par_iter().all(|expr| {
self.cell_is_irrelevant(
cell,
expr,
gate_row as usize,
)
})
{
None
} else {
Some(VerifyFailure::CellNotAssigned {
gate: (gate_index, gate.name()).into(),
region: (
r_i,
r.name.clone(),
r.annotations.clone(),
)
.into(),
gate_offset: *selector_row,
column: cell.column,
offset: cell_row as isize
- r.rows.unwrap().0 as isize,
})
}
}
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
})
})
});
let gate_errors = self.cs.gates.iter().enumerate().flat_map(|(gate_index, gate)| {
let blinding_rows =
(self.n as usize - (self.cs.blinding_factors() + 1))..(self.n as usize);
(gate_row_ids.clone().into_par_iter().chain(blinding_rows.into_par_iter()))
.flat_map(move |row| {
let row = row as i32 + n;
gate.polynomials()
.iter()
.enumerate()
.filter_map(move |(poly_index, poly)| {
match poly.evaluate_lazy(
&|scalar| Value::Real(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&util::load(n, row, &self.cs.fixed_queries, &self.fixed),
&util::load(n, row, &self.cs.advice_queries, &self.advice),
&util::load_instance(
n,
row,
&self.cs.instance_queries,
&self.instance,
),
&|challenge| Value::Real(self.challenges[challenge.index()]),
&|a| -a,
&|a, b| a + b,
&|a, b| a * b,
&|a, scalar| a * scalar,
&Value::Real(F::ZERO),
) {
Value::Real(x) if x.is_zero_vartime() => None,
Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied {
constraint: (
(gate_index, gate.name()).into(),
poly_index,
gate.constraint_name(poly_index),
)
.into(),
location: FailureLocation::find_expressions(
&self.cs,
&self.regions,
(row - n) as usize,
Some(poly).into_iter(),
),
cell_values: util::cell_values(
gate,
poly,
util::load(n, row, &self.cs.fixed_queries, &self.fixed),
util::load(n, row, &self.cs.advice_queries, &self.advice),
util::load_instance(
n,
row,
&self.cs.instance_queries,
&self.instance,
),
),
}),
Value::Poison => Some(VerifyFailure::ConstraintPoisoned {
constraint: (
(gate_index, gate.name()).into(),
poly_index,
gate.constraint_name(poly_index),
)
.into(),
}),
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
});
let load = |expression: &Expression<F>, row| {
expression.evaluate_lazy(
&|scalar| Value::Real(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&|query| {
self.fixed[query.column_index]
[(row as i32 + n + query.rotation.0) as usize % n as usize]
.into()
},
&|query| {
self.advice[query.column_index]
[(row as i32 + n + query.rotation.0) as usize % n as usize]
.into()
},
&|query| {
Value::Real(
self.instance[query.column_index]
[(row as i32 + n + query.rotation.0) as usize % n as usize]
.value(),
)
},
&|challenge| Value::Real(self.challenges[challenge.index()]),
&|a| -a,
&|a, b| a + b,
&|a, b| a * b,
&|a, scalar| a * scalar,
&Value::Real(F::ZERO),
)
};
let mut cached_table = Vec::new();
let mut cached_table_identifier = Vec::new();
let lookup_errors =
self.cs.lookups.iter().enumerate().flat_map(|(lookup_index, lookup)| {
assert!(lookup.table_expressions.len() == lookup.input_expressions.len());
assert!(self.usable_rows.end > 0);
let fill_row: Vec<_> = lookup
.table_expressions
.iter()
.map(move |c| load(c, self.usable_rows.end - 1))
.collect();
let table_identifier =
lookup.table_expressions.iter().map(Expression::identifier).collect::<Vec<_>>();
if table_identifier != cached_table_identifier {
cached_table_identifier = table_identifier;
cached_table = self
.usable_rows
.clone()
.into_par_iter()
.filter_map(|table_row| {
let t = lookup
.table_expressions
.iter()
.map(move |c| load(c, table_row))
.collect();
if t != fill_row {
Some(t)
} else {
None
}
})
.collect();
cached_table.par_sort_unstable();
}
let table = &cached_table;
let mut inputs: Vec<(Vec<_>, usize)> = lookup_input_row_ids
.clone()
.into_par_iter()
.filter_map(|input_row| {
let t = lookup
.input_expressions
.iter()
.map(move |c| load(c, input_row))
.collect();
if t != fill_row {
Some((t, input_row))
} else {
None
}
})
.collect();
inputs.par_sort_unstable();
inputs
.par_iter()
.filter_map(move |(input, input_row)| {
if table.binary_search(input).is_err() {
Some(VerifyFailure::Lookup {
name: lookup.name.clone(),
lookup_index,
location: FailureLocation::find_expressions(
&self.cs,
&self.regions,
*input_row,
lookup.input_expressions.iter(),
),
})
} else {
None
}
})
.collect::<Vec<_>>()
});
let mapping = self.permutation.mapping();
let perm_errors = {
let original = |column, row| {
self.cs
.permutation
.get_columns()
.get(column)
.map(|c: &Column<Any>| match c.column_type() {
Any::Advice(_) => self.advice[c.index()][row],
Any::Fixed => self.fixed[c.index()][row],
Any::Instance => {
let cell: &InstanceValue<F> = &self.instance[c.index()][row];
CellValue::Assigned(cell.value())
}
})
.unwrap()
};
mapping.enumerate().flat_map(move |(column, values)| {
values
.enumerate()
.filter_map(move |(row, cell)| {
let original_cell = original(column, row);
let permuted_cell = original(cell.0, cell.1);
if original_cell == permuted_cell {
None
} else {
let columns = self.cs.permutation.get_columns();
let column = columns.get(column).unwrap();
Some(VerifyFailure::Permutation {
column: (*column).into(),
location: FailureLocation::find(
&self.regions,
row,
Some(column).into_iter().cloned().collect(),
),
})
}
})
.collect::<Vec<_>>()
})
};
let mut errors: Vec<_> = iter::empty()
.chain(selector_errors)
.chain(gate_errors)
.chain(lookup_errors)
.chain(perm_errors)
.collect();
if errors.is_empty() {
Ok(())
} else {
errors.dedup_by(|a, b| match (a, b) {
(
a @ VerifyFailure::ConstraintPoisoned { .. },
b @ VerifyFailure::ConstraintPoisoned { .. },
) => a == b,
_ => false,
});
Err(errors)
}
}
fn expr_is_constantly_zero(&self, expr: &Expression<F>, offset: usize) -> bool {
match expr {
Expression::Constant(constant) => constant.is_zero().into(),
Expression::Selector(selector) => !self.selectors[selector.0][offset],
Expression::Fixed(query) => match self.fixed[query.column_index][offset] {
CellValue::Assigned(value) => value.is_zero().into(),
_ => false,
},
Expression::Scaled(e, factor) => {
factor.is_zero().into() || self.expr_is_constantly_zero(e, offset)
}
Expression::Sum(e1, e2) => {
self.expr_is_constantly_zero(e1, offset) && self.expr_is_constantly_zero(e2, offset)
}
Expression::Product(e1, e2) => {
self.expr_is_constantly_zero(e1, offset) || self.expr_is_constantly_zero(e2, offset)
}
_ => false,
}
}
fn cell_is_irrelevant(&self, cell: &VirtualCell, expr: &Expression<F>, offset: usize) -> bool {
let eq_query = |query_column: usize, query_rotation: Rotation, col_type: Any| {
cell.column.index() == query_column
&& cell.column.column_type() == &col_type
&& query_rotation == cell.rotation
};
match expr {
Expression::Constant(_) | Expression::Selector(_) => true,
Expression::Fixed(query) => !eq_query(query.column_index, query.rotation(), Any::Fixed),
Expression::Advice(query) => !eq_query(
query.column_index,
query.rotation(),
Any::Advice(Advice::new(query.phase)),
),
Expression::Instance(query) => {
!eq_query(query.column_index, query.rotation(), Any::Instance)
}
Expression::Challenge(_) => true,
Expression::Negated(e) => self.cell_is_irrelevant(cell, e, offset),
Expression::Sum(e1, e2) => {
self.cell_is_irrelevant(cell, e1, offset)
&& self.cell_is_irrelevant(cell, e2, offset)
}
Expression::Product(e1, e2) => {
(self.expr_is_constantly_zero(e1, offset)
|| self.expr_is_constantly_zero(e2, offset))
|| (self.cell_is_irrelevant(cell, e1, offset)
&& self.cell_is_irrelevant(cell, e2, offset))
}
Expression::Scaled(e, factor) => {
factor.is_zero().into() || self.cell_is_irrelevant(cell, e, offset)
}
}
}
pub fn assert_satisfied(&self) {
if let Err(errs) = self.verify() {
for err in errs {
err.emit(self);
eprintln!();
}
panic!("circuit was not satisfied");
}
}
pub fn assert_satisfied_at_rows<I: Clone + Iterator<Item = usize>>(
&self,
gate_row_ids: I,
lookup_input_row_ids: I,
) {
if let Err(errs) = self.verify_at_rows(gate_row_ids, lookup_input_row_ids) {
for err in errs {
err.emit(self);
eprintln!();
}
panic!("circuit was not satisfied");
}
}
pub fn cs(&self) -> &ConstraintSystem<F> {
&self.cs
}
pub fn usable_rows(&self) -> &Range<usize> {
&self.usable_rows
}
pub fn advice(&self) -> &Vec<Vec<CellValue<F>>> {
&self.advice
}
pub fn fixed(&self) -> &Vec<Vec<CellValue<F>>> {
&self.fixed
}
pub fn selectors(&self) -> &Vec<Vec<bool>> {
&self.selectors
}
pub fn instance(&self) -> &Vec<Vec<InstanceValue<F>>> {
&self.instance
}
pub fn permutation(&self) -> &Assembly {
&self.permutation
}
}
#[cfg(test)]
mod tests {
use ff::Field;
use midnight_curves::Fq as Scalar;
use super::{FailureLocation, MockProver, VerifyFailure};
use crate::{
circuit::{Layouter, SimpleFloorPlanner, Value},
plonk::{
sealed::SealedPhase, Advice, Any, Circuit, Column, ConstraintSystem, Constraints,
Error, Expression, FirstPhase, Fixed, Instance, Selector, TableColumn,
},
poly::Rotation,
};
#[test]
fn unassigned_cell() {
const K: u32 = 4;
#[derive(Clone)]
struct FaultyCircuitConfig {
a: Column<Advice>,
b: Column<Advice>,
q: Selector,
}
struct FaultyCircuit {}
impl Circuit<Scalar> for FaultyCircuit {
type Config = FaultyCircuitConfig;
type FloorPlanner = SimpleFloorPlanner;
#[cfg(feature = "circuit-params")]
type Params = ();
fn configure(meta: &mut ConstraintSystem<Scalar>) -> Self::Config {
let a = meta.advice_column();
let b = meta.advice_column();
let q = meta.selector();
meta.create_gate("Equality check", |cells| {
let a = cells.query_advice(a, Rotation::prev());
let b = cells.query_advice(b, Rotation::cur());
Constraints::with_selector(q, vec![a - b])
});
FaultyCircuitConfig { a, b, q }
}
fn without_witnesses(&self) -> Self {
Self {}
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<Scalar>,
) -> Result<(), Error> {
layouter.assign_region(
|| "Faulty synthesis",
|mut region| {
config.q.enable(&mut region, 1)?;
region.assign_advice(|| "a", config.a, 0, || Value::known(Scalar::ZERO))?;
region.name_column(|| "This is annotated!", config.a);
region.name_column(|| "This is also annotated!", config.b);
Ok(())
},
)
}
}
let prover = MockProver::run(K, &FaultyCircuit {}, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![VerifyFailure::CellNotAssigned {
gate: (0, "Equality check").into(),
region: (0, "Faulty synthesis".to_owned()).into(),
gate_offset: 1,
column: Column::new(
1,
Any::Advice(Advice {
phase: FirstPhase.to_sealed()
})
),
offset: 1,
}])
);
}
#[test]
fn bad_lookup_any() {
const K: u32 = 4;
#[derive(Clone)]
struct FaultyCircuitConfig {
a: Column<Advice>,
table: Column<Instance>,
advice_table: Column<Advice>,
q: Selector,
}
struct FaultyCircuit {}
impl Circuit<Scalar> for FaultyCircuit {
type Config = FaultyCircuitConfig;
type FloorPlanner = SimpleFloorPlanner;
#[cfg(feature = "circuit-params")]
type Params = ();
fn configure(meta: &mut ConstraintSystem<Scalar>) -> Self::Config {
let a = meta.advice_column();
let q = meta.complex_selector();
let table = meta.instance_column();
let advice_table = meta.advice_column();
meta.annotate_lookup_any_column(table, || "Inst-Table");
meta.enable_equality(table);
meta.annotate_lookup_any_column(advice_table, || "Adv-Table");
meta.enable_equality(advice_table);
meta.lookup_any("lookup", |cells| {
let a = cells.query_advice(a, Rotation::cur());
let q = cells.query_selector(q);
let advice_table = cells.query_advice(advice_table, Rotation::cur());
let table = cells.query_instance(table, Rotation::cur());
let not_q = Expression::from(1) - q.clone();
let default = Expression::from(2);
vec![
(
q.clone() * a.clone() + not_q.clone() * default.clone(),
table,
),
(q * a + not_q * default, advice_table),
]
});
FaultyCircuitConfig {
a,
q,
table,
advice_table,
}
}
fn without_witnesses(&self) -> Self {
Self {}
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<Scalar>,
) -> Result<(), Error> {
layouter.assign_region(
|| "Good synthesis",
|mut region| {
config.q.enable(&mut region, 0)?;
config.q.enable(&mut region, 1)?;
for i in 0..4 {
region.assign_advice_from_instance(
|| "Advice from instance tables",
config.table,
i,
config.advice_table,
i,
)?;
}
region.assign_advice(
|| "a = 2",
config.a,
0,
|| Value::known(Scalar::from(2)),
)?;
region.assign_advice(
|| "a = 6",
config.a,
1,
|| Value::known(Scalar::from(6)),
)?;
Ok(())
},
)?;
layouter.assign_region(
|| "Faulty synthesis",
|mut region| {
config.q.enable(&mut region, 0)?;
config.q.enable(&mut region, 1)?;
for i in 0..4 {
region.assign_advice_from_instance(
|| "Advice from instance tables",
config.table,
i,
config.advice_table,
i,
)?;
}
region.assign_advice(
|| "a = 4",
config.a,
0,
|| Value::known(Scalar::from(4)),
)?;
region.assign_advice(
|| "a = 5",
config.a,
1,
|| Value::known(Scalar::from(5)),
)?;
region.name_column(|| "Witness example", config.a);
Ok(())
},
)
}
}
let prover = MockProver::run(
K,
&FaultyCircuit {},
vec![vec![
Scalar::from(1u64),
Scalar::from(2u64),
Scalar::from(4u64),
Scalar::from(6u64),
]],
)
.unwrap();
assert_eq!(
prover.verify(),
Err(vec![VerifyFailure::Lookup {
name: "lookup".to_string(),
lookup_index: 0,
location: FailureLocation::InRegion {
region: (1, "Faulty synthesis").into(),
offset: 1,
}
}])
);
}
#[test]
fn bad_fixed_lookup() {
const K: u32 = 4;
#[derive(Clone)]
struct FaultyCircuitConfig {
a: Column<Advice>,
q: Selector,
table: TableColumn,
}
struct FaultyCircuit {}
impl Circuit<Scalar> for FaultyCircuit {
type Config = FaultyCircuitConfig;
type FloorPlanner = SimpleFloorPlanner;
#[cfg(feature = "circuit-params")]
type Params = ();
fn configure(meta: &mut ConstraintSystem<Scalar>) -> Self::Config {
let a = meta.advice_column();
let q = meta.complex_selector();
let table = meta.lookup_table_column();
meta.annotate_lookup_column(table, || "Table1");
meta.lookup("lookup", |cells| {
let a = cells.query_advice(a, Rotation::cur());
let q = cells.query_selector(q);
let not_q = Expression::from(1) - q.clone();
let default = Expression::from(2);
vec![(q * a + not_q * default, table)]
});
FaultyCircuitConfig { a, q, table }
}
fn without_witnesses(&self) -> Self {
Self {}
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<Scalar>,
) -> Result<(), Error> {
layouter.assign_table(
|| "Doubling table",
|mut table| {
(1..(1 << (K - 1)))
.map(|i| {
table.assign_cell(
|| format!("table[{}] = {}", i, 2 * i),
config.table,
i - 1,
|| Value::known(Scalar::from(2 * i as u64)),
)
})
.try_fold((), |_, res| res)
},
)?;
layouter.assign_region(
|| "Good synthesis",
|mut region| {
config.q.enable(&mut region, 0)?;
config.q.enable(&mut region, 1)?;
region.assign_advice(
|| "a = 2",
config.a,
0,
|| Value::known(Scalar::from(2)),
)?;
region.assign_advice(
|| "a = 6",
config.a,
1,
|| Value::known(Scalar::from(6)),
)?;
Ok(())
},
)?;
layouter.assign_region(
|| "Faulty synthesis",
|mut region| {
config.q.enable(&mut region, 0)?;
config.q.enable(&mut region, 1)?;
region.assign_advice(
|| "a = 4",
config.a,
0,
|| Value::known(Scalar::from(4)),
)?;
region.assign_advice(
|| "a = 5",
config.a,
1,
|| Value::known(Scalar::from(5)),
)?;
region.name_column(|| "Witness example", config.a);
Ok(())
},
)
}
}
let prover = MockProver::run(K, &FaultyCircuit {}, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![VerifyFailure::Lookup {
name: "lookup".to_string(),
lookup_index: 0,
location: FailureLocation::InRegion {
region: (2, "Faulty synthesis").into(),
offset: 1,
}
}])
);
}
#[test]
fn contraint_unsatisfied() {
const K: u32 = 4;
#[derive(Clone)]
struct FaultyCircuitConfig {
a: Column<Advice>,
b: Column<Advice>,
c: Column<Advice>,
d: Column<Fixed>,
q: Selector,
}
struct FaultyCircuit {}
impl Circuit<Scalar> for FaultyCircuit {
type Config = FaultyCircuitConfig;
type FloorPlanner = SimpleFloorPlanner;
#[cfg(feature = "circuit-params")]
type Params = ();
fn configure(meta: &mut ConstraintSystem<Scalar>) -> 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, Rotation::cur());
Constraints::with_selector(q, vec![(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<Scalar>,
) -> Result<(), Error> {
layouter.assign_region(
|| "Correct synthesis",
|mut region| {
config.q.enable(&mut region, 0)?;
region.assign_advice(|| "a", config.a, 0, || Value::known(Scalar::ONE))?;
region.assign_advice(|| "b", config.b, 0, || Value::known(Scalar::ONE))?;
region.assign_advice(
|| "c",
config.c,
0,
|| Value::known(Scalar::from(5u64)),
)?;
region.assign_fixed(
|| "d",
config.d,
0,
|| Value::known(Scalar::from(7u64)),
)?;
Ok(())
},
)?;
layouter.assign_region(
|| "Wrong synthesis",
|mut region| {
config.q.enable(&mut region, 0)?;
region.assign_advice(|| "a", config.a, 0, || Value::known(Scalar::ONE))?;
region.assign_advice(|| "b", config.b, 0, || Value::known(Scalar::ZERO))?;
region.name_column(|| "This is Advice!", config.a);
region.name_column(|| "This is Advice too!", config.b);
region.assign_advice(
|| "c",
config.c,
0,
|| Value::known(Scalar::from(5u64)),
)?;
region.assign_fixed(
|| "d",
config.d,
0,
|| Value::known(Scalar::from(7u64)),
)?;
region.name_column(|| "Another one!", config.c);
region.name_column(|| "This is a Fixed!", config.d);
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(Advice {
phase: FirstPhase.to_sealed()
}),
0
)
.into(),
0
)
.into(),
"1".to_string()
),
(
(
(
Any::Advice(Advice {
phase: FirstPhase.to_sealed()
}),
1
)
.into(),
0
)
.into(),
"0".to_string()
),
(
(
(
Any::Advice(Advice {
phase: FirstPhase.to_sealed()
}),
2
)
.into(),
0
)
.into(),
"0x5".to_string()
),
(((Any::Fixed, 0).into(), 0).into(), "0x7".to_string()),
],
},])
)
}
}