Merge pull request #347 from zcash/selector-optimization

Selector optimization
This commit is contained in:
str4d 2021-07-26 01:50:28 +01:00 committed by GitHub
commit 2e960317ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 928 additions and 260 deletions

View File

@ -51,6 +51,7 @@ tabbycat = { version = "0.1", features = ["attributes"], optional = true }
[dev-dependencies]
criterion = "0.3"
gumdrop = "0.8"
proptest = "1"
[features]
dev-graph = ["plotters", "tabbycat"]

View File

@ -28,7 +28,6 @@ fn main() {
sc: Column<Fixed>,
sm: Column<Fixed>,
sl: Column<Fixed>,
sl2: Column<Fixed>,
}
trait StandardCs<FF: FieldExt> {
@ -46,13 +45,13 @@ fn main() {
fn lookup_table(
&self,
layouter: &mut impl Layouter<FF>,
values: &[Vec<FF>],
values: &[FF],
) -> Result<(), Error>;
}
struct MyCircuit<F: FieldExt> {
a: Option<F>,
lookup_tables: Vec<Vec<F>>,
lookup_table: Vec<F>,
}
struct StandardPlonk<F: FieldExt> {
@ -170,26 +169,13 @@ fn main() {
fn lookup_table(
&self,
layouter: &mut impl Layouter<FF>,
values: &[Vec<FF>],
values: &[FF],
) -> Result<(), Error> {
layouter.assign_region(
|| "",
|mut region| {
for (index, (&value_0, &value_1)) in
values[0].iter().zip(values[1].iter()).enumerate()
{
region.assign_fixed(
|| "table col 1",
self.config.sl,
index,
|| Ok(value_0),
)?;
region.assign_fixed(
|| "table col 2",
self.config.sl2,
index,
|| Ok(value_1),
)?;
for (index, &value) in values.iter().enumerate() {
region.assign_fixed(|| "table col", self.config.sl, index, || Ok(value))?;
}
Ok(())
},
@ -205,7 +191,7 @@ fn main() {
fn without_witnesses(&self) -> Self {
Self {
a: None,
lookup_tables: self.lookup_tables.clone(),
lookup_table: self.lookup_table.clone(),
}
}
@ -226,34 +212,25 @@ fn main() {
let sb = meta.fixed_column();
let sc = meta.fixed_column();
let sl = meta.fixed_column();
let sl2 = meta.fixed_column();
/*
* A B ... sl sl2
* A B ... sl
* [
* instance 0 ... 0 0
* a a ... 0 0
* a a^2 ... 0 0
* a a ... 0 0
* a a^2 ... 0 0
* ... ... ... ... ...
* ... ... ... instance 0
* ... ... ... a a
* ... ... ... a a^2
* ... ... ... 0 0
* instance 0 ... 0
* a a ... 0
* a a^2 ... 0
* a a ... 0
* a a^2 ... 0
* ... ... ... ...
* ... ... ... instance
* ... ... ... a
* ... ... ... a
* ... ... ... 0
* ]
*/
meta.lookup(|meta| {
let a_ = meta.query_any(a.into(), Rotation::cur());
let sl_ = meta.query_any(sl.into(), Rotation::cur());
vec![(a_, sl_)]
});
meta.lookup(|meta| {
let a_ = meta.query_any(a.into(), Rotation::cur());
let b_ = meta.query_any(b.into(), Rotation::cur());
let sl_ = meta.query_any(sl.into(), Rotation::cur());
let sl2_ = meta.query_any(sl2.into(), Rotation::cur());
vec![(a_, sl_), (b_, sl2_)]
vec![(a_, sl)]
});
meta.create_gate("Combined add-mult", |meta| {
@ -289,7 +266,6 @@ fn main() {
sc,
sm,
sl,
sl2,
}
}
@ -327,21 +303,19 @@ fn main() {
)?;
}
cs.lookup_table(&mut layouter, &self.lookup_tables)?;
cs.lookup_table(&mut layouter, &self.lookup_table)?;
Ok(())
}
}
let a = Fp::rand();
let a_squared = a * a;
let instance = Fp::one() + Fp::one();
let lookup_table = vec![instance, a, a, Fp::zero()];
let lookup_table_2 = vec![Fp::zero(), a, a_squared, Fp::zero()];
let circuit: MyCircuit<Fp> = MyCircuit {
a: None,
lookup_tables: vec![lookup_table, lookup_table_2],
lookup_table,
};
let root = BitMapBackend::new("example-circuit-layout.png", (1024, 768)).into_drawing_area();
@ -350,5 +324,5 @@ fn main() {
.titled("Example Circuit Layout", ("sans-serif", 60))
.unwrap();
CircuitLayout::default().render(&circuit, &root).unwrap();
CircuitLayout::default().render(5, &circuit, &root).unwrap();
}

View File

@ -0,0 +1,9 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 782948e336b9fcaaf993d40cd290eff20399d34766a93793fc3a4516274c1ea7 # shrinks to (selectors, max_degree) = ([SelectorDescription { selector: 0, activations: [false], max_degree: 0 }, SelectorDescription { selector: 1, activations: [false], max_degree: 0 }], 1)
cc 656e5446792c4f5fe22fd10bcd2dbadc70e84ac1ddb1a7ec8f622f64a15ff260 # shrinks to (selectors, max_degree) = ([SelectorDescription { selector: 0, activations: [false], max_degree: 1 }, SelectorDescription { selector: 1, activations: [false], max_degree: 1 }, SelectorDescription { selector: 2, activations: [false], max_degree: 1 }], 2)
cc b7b81ca8745931e4dd8b4f896f7bde78f85f4d88857c5fdf9dc4bbf0f172db5e # shrinks to (selectors, max_degree) = ([SelectorDescription { selector: 0, activations: [false], max_degree: 1 }, SelectorDescription { selector: 1, activations: [false], max_degree: 1 }, SelectorDescription { selector: 2, activations: [false], max_degree: 1 }], 2)

View File

@ -7,7 +7,7 @@ use ff::Field;
use crate::{
circuit::{
layouter::{RegionLayouter, RegionShape},
layouter::{RegionColumn, RegionLayouter, RegionShape},
Cell, Layouter, Region, RegionIndex, RegionStart,
},
plonk::{
@ -43,7 +43,7 @@ pub struct SingleChipLayouter<'a, F: Field, CS: Assignment<F> + 'a> {
/// Stores the starting row for each region.
regions: Vec<RegionStart>,
/// Stores the first empty row for each column.
columns: HashMap<Column<Any>, usize>,
columns: HashMap<RegionColumn, usize>,
_marker: PhantomData<F>,
}
@ -119,7 +119,10 @@ impl<'a, F: Field, CS: Assignment<F> + 'a> Layouter<F> for SingleChipLayouter<'a
}
} else {
let constants_column = self.constants[0];
let next_constant_row = self.columns.entry(constants_column.into()).or_default();
let next_constant_row = self
.columns
.entry(Column::<Any>::from(constants_column).into())
.or_default();
for (constant, advice) in constants_to_assign {
self.cs.assign_fixed(
|| format!("Constant({:?})", constant.evaluate()),

View File

@ -4,11 +4,11 @@ use ff::Field;
use crate::{
circuit::{
layouter::{RegionLayouter, RegionShape},
layouter::{RegionColumn, RegionLayouter, RegionShape},
Cell, Layouter, Region, RegionIndex, RegionStart,
},
plonk::{
Advice, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance,
Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance,
Selector,
},
};
@ -89,7 +89,7 @@ impl FloorPlanner for V1 {
(
c,
column_allocations
.get(&c.into())
.get(&Column::<Any>::from(c).into())
.cloned()
.unwrap_or_default(),
)

View File

@ -4,11 +4,8 @@ use std::{
ops::Range,
};
use super::RegionShape;
use crate::{
circuit::RegionStart,
plonk::{Any, Column},
};
use super::{RegionColumn, RegionShape};
use crate::{circuit::RegionStart, plonk::Any};
/// A region allocated within a column.
#[derive(Clone, Default, Debug, PartialEq, Eq)]
@ -102,14 +99,14 @@ impl Allocations {
}
/// Allocated rows within a circuit.
pub type CircuitAllocations = HashMap<Column<Any>, Allocations>;
pub type CircuitAllocations = HashMap<RegionColumn, Allocations>;
/// - `start` is the current start row of the region (not of this column).
/// - `slack` is the maximum number of rows the start could be moved down, taking into
/// account prior columns.
fn first_fit_region(
column_allocations: &mut CircuitAllocations,
region_columns: &[Column<Any>],
region_columns: &[RegionColumn],
region_length: usize,
start: usize,
slack: Option<usize>,
@ -207,7 +204,10 @@ pub fn slot_in_biggest_advice_first(
let advice_cols = shape
.columns()
.iter()
.filter(|c| matches!(c.column_type(), Any::Advice))
.filter(|c| match c {
RegionColumn::Column(c) => matches!(c.column_type(), Any::Advice),
_ => false,
})
.count();
// Sort by advice area (since this has the most contention).
advice_cols * shape.row_count()
@ -226,23 +226,30 @@ pub fn slot_in_biggest_advice_first(
#[test]
fn test_slot_in() {
use crate::plonk::Column;
let regions = vec![
RegionShape {
region_index: 0.into(),
columns: vec![Column::new(0, Any::Advice), Column::new(1, Any::Advice)]
.into_iter()
.map(|a| a.into())
.collect(),
row_count: 15,
},
RegionShape {
region_index: 1.into(),
columns: vec![Column::new(2, Any::Advice)].into_iter().collect(),
columns: vec![Column::new(2, Any::Advice)]
.into_iter()
.map(|a| a.into())
.collect(),
row_count: 10,
},
RegionShape {
region_index: 2.into(),
columns: vec![Column::new(2, Any::Advice), Column::new(0, Any::Advice)]
.into_iter()
.map(|a| a.into())
.collect(),
row_count: 10,
},

View File

@ -110,10 +110,49 @@ pub trait RegionLayouter<F: Field>: fmt::Debug {
#[derive(Clone, Debug)]
pub struct RegionShape {
pub(super) region_index: RegionIndex,
pub(super) columns: HashSet<Column<Any>>,
pub(super) columns: HashSet<RegionColumn>,
pub(super) row_count: usize,
}
/// The virtual column involved in a region. This includes concrete columns,
/// as well as selectors that are not concrete columns at this stage.
#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)]
pub enum RegionColumn {
/// Concrete column
Column(Column<Any>),
/// Virtual column representing a (boolean) selector
Selector(Selector),
}
impl From<Column<Any>> for RegionColumn {
fn from(column: Column<Any>) -> RegionColumn {
RegionColumn::Column(column)
}
}
impl From<Selector> for RegionColumn {
fn from(selector: Selector) -> RegionColumn {
RegionColumn::Selector(selector)
}
}
impl Ord for RegionColumn {
fn cmp(&self, other: &Self) -> cmp::Ordering {
match (self, other) {
(Self::Column(ref a), Self::Column(ref b)) => a.cmp(b),
(Self::Selector(ref a), Self::Selector(ref b)) => a.0.cmp(&b.0),
(Self::Column(_), Self::Selector(_)) => cmp::Ordering::Less,
(Self::Selector(_), Self::Column(_)) => cmp::Ordering::Greater,
}
}
}
impl PartialOrd for RegionColumn {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl RegionShape {
/// Create a new `RegionShape` for a region at `region_index`.
pub fn new(region_index: RegionIndex) -> Self {
@ -130,7 +169,7 @@ impl RegionShape {
}
/// Get a reference to the set of `columns` used in a `RegionShape`.
pub fn columns(&self) -> &HashSet<Column<Any>> {
pub fn columns(&self) -> &HashSet<RegionColumn> {
&self.columns
}
@ -148,8 +187,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
offset: usize,
) -> Result<(), Error> {
// Track the selector's fixed column as part of the region's shape.
// TODO: Avoid exposing selector internals?
self.columns.insert(selector.0.into());
self.columns.insert((*selector).into());
self.row_count = cmp::max(self.row_count, offset + 1);
Ok(())
}
@ -161,7 +199,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
offset: usize,
_to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
) -> Result<Cell, Error> {
self.columns.insert(column.into());
self.columns.insert(Column::<Any>::from(column).into());
self.row_count = cmp::max(self.row_count, offset + 1);
Ok(Cell {
@ -190,7 +228,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Option<F>), Error> {
self.columns.insert(advice.into());
self.columns.insert(Column::<Any>::from(advice).into());
self.row_count = cmp::max(self.row_count, offset + 1);
Ok((
@ -210,7 +248,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
offset: usize,
_to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
) -> Result<Cell, Error> {
self.columns.insert(column.into());
self.columns.insert(Column::<Any>::from(column).into());
self.row_count = cmp::max(self.row_count, offset + 1);
Ok(Cell {

View File

@ -327,6 +327,8 @@ pub struct MockProver<F: Group + Field> {
// The instance cells in the circuit, arranged as [column][row].
instance: Vec<Vec<F>>,
selectors: Vec<Vec<bool>>,
permutation: permutation::keygen::Assembly,
// A range of available rows for assignment and copies.
@ -352,12 +354,7 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
self.regions.push(self.current_region.take().unwrap());
}
fn enable_selector<A, AR>(
&mut self,
annotation: A,
selector: &Selector,
row: usize,
) -> Result<(), Error>
fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
where
A: FnOnce() -> AR,
AR: Into<String>,
@ -376,8 +373,9 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
.or_default()
.push(row);
// Selectors are just fixed columns.
self.assign_fixed(annotation, selector.0, row, || Ok(F::one()))
self.selectors[selector.0][row] = true;
Ok(())
}
fn query_instance(&self, column: Column<Instance>, row: usize) -> Result<Option<F>, Error> {
@ -518,6 +516,7 @@ impl<F: FieldExt> MockProver<F> {
// Fixed columns contain no blinding factors.
let fixed = vec![vec![CellValue::Unassigned; n]; cs.num_fixed_columns];
let selectors = vec![vec![false; n]; cs.num_selectors];
// Advice columns contain blinding factors.
let blinding_factors = cs.blinding_factors();
let usable_rows = n - (blinding_factors + 1);
@ -543,12 +542,23 @@ impl<F: FieldExt> MockProver<F> {
fixed,
advice,
instance,
selectors,
permutation,
usable_rows: 0..usable_rows,
};
ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config, constants)?;
let (cs, selector_polys) = prover.cs.compress_selectors(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)
}
@ -640,6 +650,7 @@ impl<F: FieldExt> MockProver<F> {
gate.polynomials().iter().enumerate().filter_map(
move |(poly_index, poly)| match poly.evaluate(
&|scalar| Value::Real(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&load(n, row, &self.cs.fixed_queries, &self.fixed),
&load(n, row, &self.cs.advice_queries, &self.advice),
&load_instance(n, row, &self.cs.instance_queries, &self.instance),
@ -680,6 +691,7 @@ impl<F: FieldExt> MockProver<F> {
let load = |expression: &Expression<F>, row| {
expression.evaluate(
&|scalar| Value::Real(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&|index, _, _| {
let query = self.cs.fixed_queries[index];
let column_index = query.0.index();

View File

@ -6,11 +6,14 @@ use std::{
marker::PhantomData,
};
use ff::PrimeField;
use ff::{Field, PrimeField};
use group::prime::PrimeGroup;
use crate::{
plonk::{Any, Circuit, Column, ConstraintSystem},
plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
FloorPlanner, Instance, Selector,
},
poly::Rotation,
};
@ -37,14 +40,106 @@ pub struct CircuitCost<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> {
_marker: PhantomData<(G, ConcreteCircuit)>,
}
struct Assembly {
selectors: Vec<Vec<bool>>,
}
impl<F: Field> Assignment<F> for Assembly {
fn enter_region<NR, N>(&mut self, _: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
// Do nothing; we don't care about regions in this context.
}
fn exit_region(&mut self) {
// Do nothing; we don't care about regions in this context.
}
fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
where
A: FnOnce() -> AR,
AR: Into<String>,
{
self.selectors[selector.0][row] = true;
Ok(())
}
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Option<F>, Error> {
Ok(None)
}
fn assign_advice<V, VR, A, AR>(
&mut self,
_: A,
_: Column<Advice>,
_: usize,
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
{
Ok(())
}
fn assign_fixed<V, VR, A, AR>(
&mut self,
_: A,
_: Column<Fixed>,
_: usize,
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
{
Ok(())
}
fn copy(&mut self, _: Column<Any>, _: usize, _: Column<Any>, _: usize) -> Result<(), Error> {
Ok(())
}
fn push_namespace<NR, N>(&mut self, _: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
// Do nothing; we don't care about namespaces in this context.
}
fn pop_namespace(&mut self, _: Option<String>) {
// Do nothing; we don't care about namespaces in this context.
}
}
impl<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, ConcreteCircuit> {
/// Measures a circuit with parameter constant `k`.
///
/// Panics if `k` is not large enough for the circuit.
pub fn measure(k: usize) -> Self {
pub fn measure(k: usize, circuit: &ConcreteCircuit) -> Self {
// Collect the layout details.
let mut cs = ConstraintSystem::default();
let _ = ConcreteCircuit::configure(&mut cs);
let config = ConcreteCircuit::configure(&mut cs);
let mut assembly = Assembly {
selectors: vec![vec![false; 1 << k]; cs.num_selectors],
};
ConcreteCircuit::FloorPlanner::synthesize(
&mut assembly,
circuit,
config,
cs.constants.clone(),
)
.unwrap();
let (cs, _) = cs.compress_selectors(assembly.selectors);
assert!((1 << k) >= cs.minimum_rows());
// Figure out how many point sets we have due to queried cells.

View File

@ -7,6 +7,7 @@ use std::cmp;
use std::collections::HashSet;
use std::ops::Range;
use crate::circuit::layouter::RegionColumn;
use crate::plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
FloorPlanner, Instance, Selector,
@ -83,16 +84,18 @@ impl CircuitLayout {
/// Renders the given circuit on the given drawing area.
pub fn render<F: Field, ConcreteCircuit: Circuit<F>, DB: DrawingBackend>(
self,
k: usize,
circuit: &ConcreteCircuit,
drawing_area: &DrawingArea<DB, Shift>,
) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
use plotters::coord::types::RangedCoordusize;
use plotters::prelude::*;
let n = 1 << k;
// Collect the layout details.
let mut cs = ConstraintSystem::default();
let config = ConcreteCircuit::configure(&mut cs);
let mut layout = Layout::default();
let mut layout = Layout::new(n, cs.num_selectors);
ConcreteCircuit::FloorPlanner::synthesize(
&mut layout,
circuit,
@ -100,11 +103,17 @@ impl CircuitLayout {
cs.constants.clone(),
)
.unwrap();
let (cs, selector_polys) = cs.compress_selectors(layout.selectors);
let non_selector_fixed_columns = cs.num_fixed_columns - selector_polys.len();
// Figure out what order to render the columns in.
// TODO: For now, just render them in the order they were configured.
let total_columns = cs.num_instance_columns + cs.num_advice_columns + cs.num_fixed_columns;
let column_index = |column: &Column<Any>| {
let column_index = |cs: &ConstraintSystem<F>, column: RegionColumn| {
let column: Column<Any> = match column {
RegionColumn::Column(col) => col,
RegionColumn::Selector(selector) => cs.selector_map[selector.0].into(),
};
column.index()
+ match column.column_type() {
Any::Instance => 0,
@ -143,15 +152,16 @@ impl CircuitLayout {
],
ShapeStyle::from(&BLUE.mix(0.2)).filled(),
))?;
for selector in layout.selectors {
let index = selector.index();
{
root.draw(&Rectangle::new(
[
(cs.num_instance_columns + cs.num_advice_columns + index, 0),
(
cs.num_instance_columns + cs.num_advice_columns + index + 1,
view_bottom,
cs.num_instance_columns
+ cs.num_advice_columns
+ non_selector_fixed_columns,
0,
),
(total_columns, view_bottom),
],
ShapeStyle::from(&BLUE.mix(0.1)).filled(),
))?;
@ -191,12 +201,12 @@ impl CircuitLayout {
if let Some(offset) = region.offset {
// Sort the region's columns according to the defined ordering.
let mut columns: Vec<_> = region.columns.into_iter().collect();
columns.sort_unstable_by_key(|a| column_index(a));
columns.sort_unstable_by_key(|a| column_index(&cs, *a));
// Render contiguous parts of the same region as a single box.
let mut width = None;
for column in columns {
let column = column_index(&column);
let column = column_index(&cs, column);
match width {
Some((start, end)) if end == column => width = Some((start, end + 1)),
Some((start, end)) => {
@ -220,22 +230,22 @@ impl CircuitLayout {
// Darken the cells of the region that have been assigned to.
for (column, row) in region.cells {
draw_cell(&root, column_index(&column), row)?;
draw_cell(&root, column_index(&cs, column), row)?;
}
}
}
// Darken any loose cells that have been assigned to.
for (column, row) in layout.loose_cells {
draw_cell(&root, column_index(&column), row)?;
draw_cell(&root, column_index(&cs, column), row)?;
}
// Mark equality-constrained cells.
if self.mark_equality_cells {
let mut cells = HashSet::new();
for (l_col, l_row, r_col, r_row) in &layout.equality {
let l_col = column_index(l_col);
let r_col = column_index(r_col);
let l_col = column_index(&cs, (*l_col).into());
let r_col = column_index(&cs, (*r_col).into());
// Deduplicate cells.
cells.insert((l_col, *l_row));
@ -253,8 +263,8 @@ impl CircuitLayout {
// Draw lines between equality-constrained cells.
if self.show_equality_constraints {
for (l_col, l_row, r_col, r_row) in &layout.equality {
let l_col = column_index(l_col);
let r_col = column_index(r_col);
let l_col = column_index(&cs, (*l_col).into());
let r_col = column_index(&cs, (*r_col).into());
root.draw(&PathElement::new(
[(l_col, *l_row), (r_col, *r_row)],
ShapeStyle::from(&RED),
@ -280,14 +290,14 @@ struct Region {
/// The name of the region. Not required to be unique.
name: String,
/// The columns used by this region.
columns: HashSet<Column<Any>>,
columns: HashSet<RegionColumn>,
/// The row that this region starts on, if known.
offset: Option<usize>,
/// The number of rows that this region takes up.
rows: usize,
/// 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<Any>, usize)>,
cells: Vec<(RegionColumn, usize)>,
}
#[derive(Default)]
@ -297,15 +307,30 @@ struct Layout {
total_rows: usize,
/// Any cells assigned outside of a region. We store this as a `Vec` so that if any
/// cells are double-assigned, they will be visibly darker.
loose_cells: Vec<(Column<Any>, usize)>,
/// Columns we have observed are actually Selectors.
selectors: HashSet<Column<Any>>,
loose_cells: Vec<(RegionColumn, usize)>,
/// Pairs of cells between which we have equality constraints.
equality: Vec<(Column<Any>, usize, Column<Any>, usize)>,
/// Selector assignments used for optimization pass
selectors: Vec<Vec<bool>>,
}
impl Layout {
fn update(&mut self, column: Column<Any>, row: usize) {
fn new(n: usize, num_selectors: usize) -> Self {
Layout {
regions: vec![],
current_region: None,
total_rows: 0,
/// Any cells assigned outside of a region. We store this as a `Vec` so that if any
/// cells are double-assigned, they will be visibly darker.
loose_cells: vec![],
/// Pairs of cells between which we have equality constraints.
equality: vec![],
/// Selector assignments used for optimization pass
selectors: vec![vec![false; n]; num_selectors],
}
}
fn update(&mut self, column: RegionColumn, row: usize) {
self.total_rows = cmp::max(self.total_rows, row + 1);
if let Some(region) = self.current_region {
@ -353,20 +378,14 @@ impl<F: Field> Assignment<F> for Layout {
self.current_region = None;
}
fn enable_selector<A, AR>(
&mut self,
annotation: A,
selector: &Selector,
row: usize,
) -> Result<(), Error>
fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
where
A: FnOnce() -> AR,
AR: Into<String>,
{
// Remember that this column is a selector.
self.selectors.insert(selector.0.into());
// Selectors are just fixed columns.
self.assign_fixed(annotation, selector.0, row, || Ok(F::one()))
self.selectors[selector.0][row] = true;
self.update((*selector).into(), row);
Ok(())
}
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Option<F>, Error> {
@ -386,7 +405,7 @@ impl<F: Field> Assignment<F> for Layout {
A: FnOnce() -> AR,
AR: Into<String>,
{
self.update(column.into(), row);
self.update(Column::<Any>::from(column).into(), row);
Ok(())
}
@ -403,7 +422,7 @@ impl<F: Field> Assignment<F> for Layout {
A: FnOnce() -> AR,
AR: Into<String>,
{
self.update(column.into(), row);
self.update(Column::<Any>::from(column).into(), row);
Ok(())
}

View File

@ -8,7 +8,9 @@ use std::{
use super::{lookup, permutation, Error};
use crate::circuit::Layouter;
use crate::{arithmetic::FieldExt, circuit::Region, poly::Rotation};
use crate::{circuit::Region, poly::Rotation};
mod compress_selectors;
/// A column type
pub trait ColumnType:
@ -228,13 +230,19 @@ impl TryFrom<Column<Any>> for Column<Instance> {
/// }
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Selector(pub(crate) Column<Fixed>);
pub struct Selector(pub(crate) usize, bool);
impl Selector {
/// Enable this selector at the given offset within the given region.
pub fn enable<F: FieldExt>(&self, region: &mut Region<F>, offset: usize) -> Result<(), Error> {
pub fn enable<F: Field>(&self, region: &mut Region<F>, offset: usize) -> Result<(), Error> {
region.enable_selector(|| "", self, offset)
}
/// Is this selector "simple"? Simple selectors can only be multiplied
/// by expressions that contain no other simple selectors.
pub fn is_simple(&self) -> bool {
self.1
}
}
/// A value assigned to a cell within a circuit.
@ -540,6 +548,8 @@ pub trait Circuit<F: Field> {
pub enum Expression<F> {
/// This is a constant polynomial
Constant(F),
/// This is a virtual selector
Selector(Selector),
/// This is a fixed column queried at a certain relative location
Fixed {
/// Query index
@ -581,6 +591,7 @@ impl<F: Field> Expression<F> {
pub fn evaluate<T>(
&self,
constant: &impl Fn(F) -> T,
selector_column: &impl Fn(Selector) -> T,
fixed_column: &impl Fn(usize, usize, Rotation) -> T,
advice_column: &impl Fn(usize, usize, Rotation) -> T,
instance_column: &impl Fn(usize, usize, Rotation) -> T,
@ -590,6 +601,7 @@ impl<F: Field> Expression<F> {
) -> T {
match self {
Expression::Constant(scalar) => constant(*scalar),
Expression::Selector(selector) => selector_column(*selector),
Expression::Fixed {
query_index,
column_index,
@ -608,6 +620,7 @@ impl<F: Field> Expression<F> {
Expression::Sum(a, b) => {
let a = a.evaluate(
constant,
selector_column,
fixed_column,
advice_column,
instance_column,
@ -617,6 +630,7 @@ impl<F: Field> Expression<F> {
);
let b = b.evaluate(
constant,
selector_column,
fixed_column,
advice_column,
instance_column,
@ -629,6 +643,7 @@ impl<F: Field> Expression<F> {
Expression::Product(a, b) => {
let a = a.evaluate(
constant,
selector_column,
fixed_column,
advice_column,
instance_column,
@ -638,6 +653,7 @@ impl<F: Field> Expression<F> {
);
let b = b.evaluate(
constant,
selector_column,
fixed_column,
advice_column,
instance_column,
@ -650,6 +666,7 @@ impl<F: Field> Expression<F> {
Expression::Scaled(a, f) => {
let a = a.evaluate(
constant,
selector_column,
fixed_column,
advice_column,
instance_column,
@ -666,6 +683,7 @@ impl<F: Field> Expression<F> {
pub fn degree(&self) -> usize {
match self {
Expression::Constant(_) => 0,
Expression::Selector(_) => 1,
Expression::Fixed { .. } => 1,
Expression::Advice { .. } => 1,
Expression::Instance { .. } => 1,
@ -679,6 +697,46 @@ impl<F: Field> Expression<F> {
pub fn square(self) -> Self {
self.clone() * self
}
/// Returns whether or not this expression contains a simple `Selector`.
fn contains_simple_selector(&self) -> bool {
self.evaluate(
&|_| false,
&|selector| selector.is_simple(),
&|_, _, _| false,
&|_, _, _| false,
&|_, _, _| false,
&|a, b| a || b,
&|a, b| a || b,
&|a, _| a,
)
}
/// Extracts a simple selector from this gate, if present
fn extract_simple_selector(&self) -> Option<Selector> {
let op = |a, b| match (a, b) {
(Some(a), None) | (None, Some(a)) => Some(a),
(Some(_), Some(_)) => panic!("two simple selectors cannot be in the same expression"),
_ => None,
};
self.evaluate(
&|_| None,
&|selector| {
if selector.is_simple() {
Some(selector)
} else {
None
}
},
&|_, _, _| None,
&|_, _, _| None,
&|_, _, _| None,
&op,
&op,
&|a, _| a,
)
}
}
impl<F: Field> Neg for Expression<F> {
@ -688,9 +746,12 @@ impl<F: Field> Neg for Expression<F> {
}
}
impl<F> Add for Expression<F> {
impl<F: Field> Add for Expression<F> {
type Output = Expression<F>;
fn add(self, rhs: Expression<F>) -> Expression<F> {
if self.contains_simple_selector() || rhs.contains_simple_selector() {
panic!("attempted to use a simple selector in an addition");
}
Expression::Sum(Box::new(self), Box::new(rhs))
}
}
@ -698,18 +759,24 @@ impl<F> Add for Expression<F> {
impl<F: Field> Sub for Expression<F> {
type Output = Expression<F>;
fn sub(self, rhs: Expression<F>) -> Expression<F> {
if self.contains_simple_selector() || rhs.contains_simple_selector() {
panic!("attempted to use a simple selector in a subtraction");
}
Expression::Sum(Box::new(self), Box::new(-rhs))
}
}
impl<F> Mul for Expression<F> {
impl<F: Field> Mul for Expression<F> {
type Output = Expression<F>;
fn mul(self, rhs: Expression<F>) -> Expression<F> {
if self.contains_simple_selector() && rhs.contains_simple_selector() {
panic!("attempted to multiply two expressions containing simple selectors");
}
Expression::Product(Box::new(self), Box::new(rhs))
}
}
impl<F> Mul<F> for Expression<F> {
impl<F: Field> Mul<F> for Expression<F> {
type Output = Expression<F>;
fn mul(self, rhs: F) -> Expression<F> {
Expression::Scaled(Box::new(self), rhs)
@ -805,6 +872,8 @@ pub struct ConstraintSystem<F: Field> {
pub(crate) num_fixed_columns: usize,
pub(crate) num_advice_columns: usize,
pub(crate) num_instance_columns: usize,
pub(crate) num_selectors: usize,
pub(crate) selector_map: Vec<Column<Fixed>>,
pub(crate) gates: Vec<Gate<F>>,
pub(crate) advice_queries: Vec<(Column<Advice>, Rotation)>,
// Contains an integer for each advice column
@ -834,6 +903,8 @@ pub struct PinnedConstraintSystem<'a, F: Field> {
num_fixed_columns: &'a usize,
num_advice_columns: &'a usize,
num_instance_columns: &'a usize,
num_selectors: &'a usize,
selector_map: &'a [Column<Fixed>],
gates: PinnedGates<'a, F>,
advice_queries: &'a Vec<(Column<Advice>, Rotation)>,
instance_queries: &'a Vec<(Column<Instance>, Rotation)>,
@ -860,6 +931,8 @@ impl<F: Field> Default for ConstraintSystem<F> {
num_fixed_columns: 0,
num_advice_columns: 0,
num_instance_columns: 0,
num_selectors: 0,
selector_map: vec![],
gates: vec![],
fixed_queries: Vec::new(),
advice_queries: Vec::new(),
@ -882,6 +955,8 @@ impl<F: Field> ConstraintSystem<F> {
num_fixed_columns: &self.num_fixed_columns,
num_advice_columns: &self.num_advice_columns,
num_instance_columns: &self.num_instance_columns,
num_selectors: &self.num_selectors,
selector_map: &self.selector_map,
gates: PinnedGates(&self.gates),
fixed_queries: &self.fixed_queries,
advice_queries: &self.advice_queries,
@ -917,10 +992,21 @@ impl<F: Field> ConstraintSystem<F> {
/// they need to match.
pub fn lookup(
&mut self,
table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression<F>, Expression<F>)>,
table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression<F>, Column<Fixed>)>,
) -> usize {
let mut cells = VirtualCells::new(self);
let table_map = table_map(&mut cells);
let table_map = table_map(&mut cells)
.into_iter()
.map(|(input, table)| {
if input.contains_simple_selector() {
panic!("expression containing simple selector supplied to lookup argument");
}
let table = cells.query_fixed(table, Rotation::cur());
(input, table)
})
.collect();
let index = self.lookups.len();
@ -1072,11 +1158,148 @@ impl<F: Field> ConstraintSystem<F> {
});
}
/// Allocate a new selector.
/// This will compress selectors together depending on their provided
/// assignments. This `ConstraintSystem` will then be modified to add new
/// fixed columns (representing the actual selectors) and will return the
/// polynomials for those columns. Finally, an internal map is updated to
/// find which fixed column corresponds with a given `Selector`.
///
/// Do not call this twice. Yes, this should be a builder pattern instead.
pub(crate) fn compress_selectors(mut self, selectors: Vec<Vec<bool>>) -> (Self, Vec<Vec<F>>) {
// The number of provided selector assignments must be the number we
// counted for this constraint system.
assert_eq!(selectors.len(), self.num_selectors);
// Compute the maximal degree of every selector. We only consider the
// expressions in gates, as lookup arguments cannot support simple
// selectors. Selectors that are complex or do not appear in any gates
// will have degree zero.
let mut degrees = vec![0; selectors.len()];
for expr in self.gates.iter().flat_map(|gate| gate.polys.iter()) {
if let Some(selector) = expr.extract_simple_selector() {
degrees[selector.0] = max(degrees[selector.0], expr.degree());
}
}
// We will not increase the degree of the constraint system, so we limit
// ourselves to the largest existing degree constraint.
let max_degree = self.degree();
let mut new_columns = vec![];
let (polys, selector_assignment) = compress_selectors::process(
selectors
.into_iter()
.zip(degrees.into_iter())
.enumerate()
.map(
|(i, (activations, max_degree))| compress_selectors::SelectorDescription {
selector: i,
activations,
max_degree,
},
)
.collect(),
max_degree,
|| {
let column = self.fixed_column();
new_columns.push(column);
Expression::Fixed {
query_index: self.query_fixed_index(column, Rotation::cur()),
column_index: column.index,
rotation: Rotation::cur(),
}
},
);
let mut selector_map = vec![None; selector_assignment.len()];
let mut selector_replacements = vec![None; selector_assignment.len()];
for assignment in selector_assignment {
selector_replacements[assignment.selector] = Some(assignment.expression);
selector_map[assignment.selector] = Some(new_columns[assignment.combination_index]);
}
self.selector_map = selector_map
.into_iter()
.map(|a| a.unwrap())
.collect::<Vec<_>>();
let selector_replacements = selector_replacements
.into_iter()
.map(|a| a.unwrap())
.collect::<Vec<_>>();
fn replace_selectors<F: Field>(
expr: &mut Expression<F>,
selector_replacements: &[Expression<F>],
must_be_nonsimple: bool,
) {
*expr = expr.evaluate(
&|constant| Expression::Constant(constant),
&|selector| {
if must_be_nonsimple {
// Simple selectors are prohibited from appearing in
// expressions in the lookup argument by
// `ConstraintSystem`.
assert!(!selector.is_simple());
}
selector_replacements[selector.0].clone()
},
&|query_index, column_index, rotation| Expression::Fixed {
query_index,
column_index,
rotation,
},
&|query_index, column_index, rotation| Expression::Advice {
query_index,
column_index,
rotation,
},
&|query_index, column_index, rotation| Expression::Instance {
query_index,
column_index,
rotation,
},
&|a, b| a + b,
&|a, b| a * b,
&|a, f| a * f,
);
}
// Substitute selectors for the real fixed columns in all gates
for expr in self.gates.iter_mut().flat_map(|gate| gate.polys.iter_mut()) {
replace_selectors(expr, &selector_replacements, false);
}
// Substitute non-simple selectors for the real fixed columns in all
// lookup expressions
for expr in self.lookups.iter_mut().flat_map(|lookup| {
lookup
.input_expressions
.iter_mut()
.chain(lookup.table_expressions.iter_mut())
}) {
replace_selectors(expr, &selector_replacements, true);
}
(self, polys)
}
/// Allocate a new (simple) selector. Simple selectors cannot be added to
/// expressions nor multiplied by other expressions containing simple
/// selectors. Also, simple selectors may not appear in lookup argument
/// inputs.
pub fn selector(&mut self) -> Selector {
// TODO: Track selectors separately, and combine selectors where possible.
// https://github.com/zcash/halo2/issues/116
Selector(self.fixed_column())
let index = self.num_selectors;
self.num_selectors += 1;
Selector(index, true)
}
/// Allocate a new complex selector that can appear anywhere
/// within expressions.
pub fn complex_selector(&mut self) -> Selector {
let index = self.num_selectors;
self.num_selectors += 1;
Selector(index, false)
}
/// Allocate a new fixed column
@ -1204,13 +1427,8 @@ impl<'a, F: Field> VirtualCells<'a, F> {
/// Query a selector at the current position.
pub fn query_selector(&mut self, selector: Selector) -> Expression<F> {
// Selectors are always queried at the current row.
self.queried_selectors.push(selector);
Expression::Fixed {
query_index: self.meta.query_fixed_index(selector.0, Rotation::cur()),
column_index: (selector.0).index,
rotation: Rotation::cur(),
}
Expression::Selector(selector)
}
/// Query a fixed column at a relative position

View File

@ -0,0 +1,353 @@
use super::Expression;
use ff::Field;
/// This describes a selector and where it is activated.
#[derive(Debug, Clone)]
pub struct SelectorDescription {
/// The selector that this description references, by index.
pub selector: usize,
/// The vector of booleans defining which rows are active for this selector.
pub activations: Vec<bool>,
/// The maximum degree of a gate involving this selector, including the
/// virtual selector itself. This means this will be at least 1 for any
/// expression containing a simple selector, even if that selector is not
/// multiplied by anything.
pub max_degree: usize,
}
/// This describes the assigned combination of a particular selector as well as
/// the expression it should be substituted with.
#[derive(Debug, Clone)]
pub struct SelectorAssignment<F> {
/// The selector that this structure references, by index.
pub selector: usize,
/// The combination this selector was assigned to
pub combination_index: usize,
/// The expression we wish to substitute with
pub expression: Expression<F>,
}
/// This function takes a vector that defines each selector as well as a closure
/// used to allocate new fixed columns, and returns the assignment of each
/// combination as well as details about each selector assignment.
///
/// This function takes
/// * `selectors`, a vector of `SelectorDescription`s that describe each
/// selector
/// * `max_degree`, the maximum allowed degree of any gate
/// * `allocate_fixed_columns`, a closure that constructs a new fixed column and
/// queries it at Rotation::cur(), returning the expression
///
/// and returns `Vec<Vec<F>>` containing the assignment of each new fixed column
/// (which each correspond to a combination) as well as a vector of
/// `SelectorAssignment` that the caller can use to perform the necessary
/// substitutions to the constraint system.
///
/// This function is completely deterministic.
pub fn process<F: Field, E>(
mut selectors: Vec<SelectorDescription>,
max_degree: usize,
mut allocate_fixed_column: E,
) -> (Vec<Vec<F>>, Vec<SelectorAssignment<F>>)
where
E: FnMut() -> Expression<F>,
{
if selectors.is_empty() {
// There is nothing to optimize.
return (vec![], vec![]);
}
// The length of all provided selectors must be the same.
let n = selectors[0].activations.len();
assert!(selectors.iter().all(|a| a.activations.len() == n));
let mut combination_assignments = vec![];
let mut selector_assignments = vec![];
// All provided selectors of degree 0 are assumed to be either concrete
// selectors or do not appear in a gate. Let's address these first.
selectors = selectors
.into_iter()
.filter(|selector| {
if selector.max_degree == 0 {
// This is a complex selector, or a selector that does not appear in any
// gate constraint.
let expression = allocate_fixed_column();
let combination_assignment = selector
.activations
.iter()
.map(|b| if *b { F::one() } else { F::zero() })
.collect::<Vec<_>>();
let combination_index = combination_assignments.len();
combination_assignments.push(combination_assignment);
selector_assignments.push(SelectorAssignment {
selector: selector.selector,
combination_index,
expression,
});
false
} else {
true
}
})
.collect();
// All of the remaining `selectors` are simple. Let's try to combine them.
// First, we compute the exclusion matrix that has (j, k) = true if selector
// j and selector k conflict -- that is, they are both enabled on the same
// row. This matrix is symmetric and the diagonal entries are false, so we
// only need to store the lower triangular entries.
let mut exclusion_matrix = (0..selectors.len())
.map(|i| vec![false; i])
.collect::<Vec<_>>();
for (i, rows) in selectors
.iter()
.map(|selector| &selector.activations)
.enumerate()
{
// Loop over the selectors previous to this one
for (j, other_selector) in selectors.iter().enumerate().take(i) {
// Look at what selectors are active at the same row
if rows
.iter()
.zip(other_selector.activations.iter())
.any(|(l, r)| l & r)
{
// Mark them as incompatible
exclusion_matrix[i][j] = true;
}
}
}
// Simple selectors that we've added to combinations already.
let mut added = vec![false; selectors.len()];
for (i, selector) in selectors.iter().enumerate() {
if added[i] {
continue;
}
added[i] = true;
assert!(selector.max_degree <= max_degree);
// This is used to keep track of the largest degree gate involved in the
// combination so far. We subtract by one to omit the virtual selector
// which will be substituted by the caller with the expression we give
// them.
let mut d = selector.max_degree - 1;
let mut combination = vec![selector];
let mut combination_added = vec![i];
// Try to find other selectors that can join this one.
'try_selectors: for (j, selector) in selectors.iter().enumerate().skip(i + 1) {
if d + combination.len() == max_degree {
// Short circuit; nothing can be added to this
// combination.
break 'try_selectors;
}
// Skip selectors that have been added to previous combinations
if added[j] {
continue 'try_selectors;
}
// Is this selector excluded from co-existing in the same
// combination with any of the other selectors so far?
for &i in combination_added.iter() {
if exclusion_matrix[j][i] {
continue 'try_selectors;
}
}
// Can the new selector join the combination? Reminder: we use
// selector.max_degree - 1 to omit the influence of the virtual
// selector on the degree, as it will be substituted.
let new_d = std::cmp::max(d, selector.max_degree - 1);
if new_d + combination.len() + 1 > max_degree {
// Guess not.
continue 'try_selectors;
}
d = new_d;
combination.push(selector);
combination_added.push(j);
added[j] = true;
}
// Now, compute the selector and combination assignments.
let mut combination_assignment = vec![F::zero(); n];
let combination_len = combination.len();
let combination_index = combination_assignments.len();
let query = allocate_fixed_column();
let mut assigned_root = F::one();
selector_assignments.extend(combination.into_iter().map(|selector| {
// Compute the expression for substitution. This produces an expression of the
// form
// q * Prod[i = 1..=combination_len, i != assigned_root](i - q)
//
// which is non-zero only on rows where `combination_assignment` is set to
// `assigned_root`. In particular, rows set to 0 correspond to all selectors
// being disabled.
let mut expression = query.clone();
let mut root = F::one();
for _ in 0..combination_len {
if root != assigned_root {
expression = expression * (Expression::Constant(root) - query.clone());
}
root += F::one();
}
// Update the combination assignment
for (combination, selector) in combination_assignment
.iter_mut()
.zip(selector.activations.iter())
{
// This will not overwrite another selector's activations because
// we have ensured that selectors are disjoint.
if *selector {
*combination = assigned_root;
}
}
assigned_root += F::one();
SelectorAssignment {
selector: selector.selector,
combination_index,
expression,
}
}));
combination_assignments.push(combination_assignment);
}
(combination_assignments, selector_assignments)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::poly::Rotation;
use pasta_curves::Fp;
use proptest::collection::{vec, SizeRange};
use proptest::prelude::*;
prop_compose! {
fn arb_selector(assignment_size: usize, max_degree: usize)
(degree in 0..max_degree,
assignment in vec(any::<bool>(), assignment_size))
-> (usize, Vec<bool>) {
(degree, assignment)
}
}
prop_compose! {
fn arb_selector_list(assignment_size: usize, max_degree: usize, num_selectors: impl Into<SizeRange>)
(list in vec(arb_selector(assignment_size, max_degree), num_selectors))
-> Vec<SelectorDescription>
{
list.into_iter().enumerate().map(|(i, (max_degree, activations))| {
SelectorDescription {
selector: i,
activations,
max_degree,
}
}).collect()
}
}
prop_compose! {
fn arb_instance(max_assignment_size: usize,
max_degree: usize,
max_selectors: usize)
(assignment_size in 1..max_assignment_size,
degree in 1..max_degree,
num_selectors in 1..max_selectors)
(list in arb_selector_list(assignment_size, degree, num_selectors),
degree in Just(degree))
-> (Vec<SelectorDescription>, usize)
{
(list, degree)
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10000))]
#[test]
fn test_selector_combination((selectors, max_degree) in arb_instance(10, 10, 15)) {
let mut query = 0;
let (combination_assignments, selector_assignments) =
process::<Fp, _>(selectors.clone(), max_degree, || {
let tmp = Expression::Fixed {
query_index: query,
column_index: query,
rotation: Rotation::cur(),
};
query += 1;
tmp
});
{
let mut selectors_seen = vec![];
assert_eq!(selectors.len(), selector_assignments.len());
for selector in &selector_assignments {
// Every selector should be assigned to a combination
assert!(selector.combination_index < combination_assignments.len());
assert!(!selectors_seen.contains(&selector.selector));
selectors_seen.push(selector.selector);
}
}
// Test that, for each selector, the provided expression
// 1. evaluates to zero on rows where the selector's activation is off
// 2. evaluates to nonzero on rows where the selector's activation is on
// 3. is of degree d such that d + (selector.max_degree - 1) <= max_degree
// OR selector.max_degree is zero
for selector in selector_assignments {
assert_eq!(
selectors[selector.selector].activations.len(),
combination_assignments[selector.combination_index].len()
);
for (&activation, &assignment) in selectors[selector.selector]
.activations
.iter()
.zip(combination_assignments[selector.combination_index].iter())
{
let eval = selector.expression.evaluate(
&|c| c,
&|_| panic!("should not occur in returned expressions"),
&|query_index, _, _| {
// Should be the correct combination in the expression
assert_eq!(selector.combination_index, query_index);
assignment
},
&|_, _, _| panic!("should not occur in returned expressions"),
&|_, _, _| panic!("should not occur in returned expressions"),
&|a, b| a + b,
&|a, b| a * b,
&|a, f| a * f,
);
if activation {
assert!(!eval.is_zero());
} else {
assert!(eval.is_zero());
}
}
let expr_degree = selector.expression.degree();
assert!(expr_degree <= max_degree);
if selectors[selector.selector].max_degree > 0 {
assert!(
(selectors[selector.selector].max_degree - 1) + expr_degree <= max_degree
);
}
}
}
}
}

View File

@ -44,6 +44,7 @@ where
struct Assembly<F: Field> {
fixed: Vec<Polynomial<Assigned<F>, LagrangeCoeff>>,
permutation: permutation::keygen::Assembly,
selectors: Vec<Vec<bool>>,
// A range of available rows for assignment and copies.
usable_rows: RangeTo<usize>,
_marker: std::marker::PhantomData<F>,
@ -62,12 +63,7 @@ impl<F: Field> Assignment<F> for Assembly<F> {
// Do nothing; we don't care about regions in this context.
}
fn enable_selector<A, AR>(
&mut self,
annotation: A,
selector: &Selector,
row: usize,
) -> Result<(), Error>
fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
where
A: FnOnce() -> AR,
AR: Into<String>,
@ -75,12 +71,10 @@ impl<F: Field> Assignment<F> for Assembly<F> {
if !self.usable_rows.contains(&row) {
return Err(Error::BoundsFailure);
}
// Selectors are just fixed columns.
// TODO: Ensure that the default for a selector's cells is always zero, if we
// alter the proving system to change the global default.
// TODO: Implement selector combining optimization
// https://github.com/zcash/halo2/issues/116
self.assign_fixed(annotation, selector.0, row, || Ok(F::one()))
self.selectors[selector.0][row] = true;
Ok(())
}
fn query_instance(&self, _: Column<Instance>, row: usize) -> Result<Option<F>, Error> {
@ -181,6 +175,7 @@ where
let mut assembly: Assembly<C::Scalar> = Assembly {
fixed: vec![domain.empty_lagrange_assigned(); cs.num_fixed_columns],
permutation: permutation::keygen::Assembly::new(params.n as usize, &cs.permutation),
selectors: vec![vec![false; params.n as usize]; cs.num_selectors],
usable_rows: ..params.n as usize - (cs.blinding_factors() + 1),
_marker: std::marker::PhantomData,
};
@ -193,7 +188,13 @@ where
cs.constants.clone(),
)?;
let fixed = batch_invert_assigned(assembly.fixed);
let mut fixed = batch_invert_assigned(assembly.fixed);
let (cs, selector_polys) = cs.compress_selectors(assembly.selectors);
fixed.extend(
selector_polys
.into_iter()
.map(|poly| domain.lagrange_from_vec(poly)),
);
let permutation_vk = assembly
.permutation
@ -232,9 +233,10 @@ where
}
let mut assembly: Assembly<C::Scalar> = Assembly {
fixed: vec![vk.domain.empty_lagrange_assigned(); vk.cs.num_fixed_columns],
permutation: permutation::keygen::Assembly::new(params.n as usize, &vk.cs.permutation),
usable_rows: ..params.n as usize - (vk.cs.blinding_factors() + 1),
fixed: vec![vk.domain.empty_lagrange_assigned(); cs.num_fixed_columns],
permutation: permutation::keygen::Assembly::new(params.n as usize, &cs.permutation),
selectors: vec![vec![false; params.n as usize]; cs.num_selectors],
usable_rows: ..params.n as usize - (cs.blinding_factors() + 1),
_marker: std::marker::PhantomData,
};
@ -246,7 +248,13 @@ where
cs.constants.clone(),
)?;
let fixed = batch_invert_assigned(assembly.fixed);
let mut fixed = batch_invert_assigned(assembly.fixed);
let (cs, selector_polys) = cs.compress_selectors(assembly.selectors);
fixed.extend(
selector_polys
.into_iter()
.map(|poly| vk.domain.lagrange_from_vec(poly)),
);
let fixed_polys: Vec<_> = fixed
.iter()
@ -260,7 +268,7 @@ where
let permutation_pk = assembly
.permutation
.build_pk(params, &vk.domain, &vk.cs.permutation);
.build_pk(params, &vk.domain, &cs.permutation);
// Compute l_0(X)
// TODO: this can be done more efficiently

View File

@ -101,6 +101,7 @@ impl<F: FieldExt> Argument<F> {
.map(|expression| {
expression.evaluate(
&|scalar| pk.vk.domain.constant_lagrange(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&|_, column_index, rotation| {
fixed_values[column_index].clone().rotate(rotation)
},
@ -134,6 +135,7 @@ impl<F: FieldExt> Argument<F> {
.map(|expression| {
expression.evaluate(
&|scalar| pk.vk.domain.constant_extended(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&|_, column_index, rotation| {
pk.vk
.domain

View File

@ -134,6 +134,7 @@ impl<C: CurveAffine> Evaluated<C> {
.map(|expression| {
expression.evaluate(
&|scalar| scalar,
&|_| panic!("virtual selectors are removed during optimization"),
&|index, _, _| fixed_evals[index],
&|index, _, _| advice_evals[index],
&|index, _, _| instance_evals[index],

View File

@ -56,6 +56,10 @@ pub fn create_proof<
let mut meta = ConstraintSystem::default();
let config = ConcreteCircuit::configure(&mut meta);
// Selector optimizations cannot be applied here; use the ConstraintSystem
// from the verification key.
let meta = &pk.vk.cs;
struct InstanceSingle<C: CurveAffine> {
pub instance_values: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
pub instance_polys: Vec<Polynomial<C::Scalar, Coeff>>,
@ -431,6 +435,7 @@ pub fn create_proof<
gate.polynomials().iter().map(move |poly| {
poly.evaluate(
&|scalar| pk.vk.domain.constant_extended(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&|_, column_index, rotation| {
pk.vk
.domain

View File

@ -184,6 +184,7 @@ pub fn verify_proof<'params, C: CurveAffine, E: EncodedChallenge<C>, T: Transcri
gate.polynomials().iter().map(move |poly| {
poly.evaluate(
&|scalar| scalar,
&|_| panic!("virtual selectors are removed during optimization"),
&|index, _, _| fixed_evals[index],
&|index, _, _| advice_evals[index],
&|index, _, _| instance_evals[index],

View File

@ -38,7 +38,6 @@ fn plonk_api() {
sm: Column<Fixed>,
sp: Column<Fixed>,
sl: Column<Fixed>,
sl2: Column<Fixed>,
}
trait StandardCs<FF: FieldExt> {
@ -63,14 +62,14 @@ fn plonk_api() {
fn lookup_table(
&self,
layouter: &mut impl Layouter<FF>,
values: &[Vec<FF>],
values: &[FF],
) -> Result<(), Error>;
}
#[derive(Clone)]
struct MyCircuit<F: FieldExt> {
a: Option<F>,
lookup_tables: Vec<Vec<F>>,
lookup_table: Vec<F>,
}
struct StandardPlonk<F: FieldExt> {
@ -227,26 +226,13 @@ fn plonk_api() {
fn lookup_table(
&self,
layouter: &mut impl Layouter<FF>,
values: &[Vec<FF>],
values: &[FF],
) -> Result<(), Error> {
layouter.assign_region(
|| "",
|mut region| {
for (index, (&value_0, &value_1)) in
values[0].iter().zip(values[1].iter()).enumerate()
{
region.assign_fixed(
|| "table col 1",
self.config.sl,
index,
|| Ok(value_0),
)?;
region.assign_fixed(
|| "table col 2",
self.config.sl2,
index,
|| Ok(value_1),
)?;
for (index, &value) in values.iter().enumerate() {
region.assign_fixed(|| "table col", self.config.sl, index, || Ok(value))?;
}
Ok(())
},
@ -262,7 +248,7 @@ fn plonk_api() {
fn without_witnesses(&self) -> Self {
Self {
a: None,
lookup_tables: self.lookup_tables.clone(),
lookup_table: self.lookup_table.clone(),
}
}
@ -285,35 +271,26 @@ fn plonk_api() {
let sc = meta.fixed_column();
let sp = meta.fixed_column();
let sl = meta.fixed_column();
let sl2 = meta.fixed_column();
/*
* A B ... sl sl2
* A B ... sl
* [
* instance 0 ... 0 0
* a a ... 0 0
* a a^2 ... 0 0
* a a ... 0 0
* a a^2 ... 0 0
* ... ... ... ... ...
* ... ... ... instance 0
* ... ... ... a a
* ... ... ... a a^2
* ... ... ... 0 0
* instance 0 ... 0
* a a ... 0
* a a^2 ... 0
* a a ... 0
* a a^2 ... 0
* ... ... ... ...
* ... ... ... instance
* ... ... ... a
* ... ... ... a
* ... ... ... 0
* ]
*/
meta.lookup(|meta| {
let a_ = meta.query_any(a.into(), Rotation::cur());
let sl_ = meta.query_any(sl.into(), Rotation::cur());
vec![(a_, sl_)]
});
meta.lookup(|meta| {
let a_ = meta.query_any(a.into(), Rotation::cur());
let b_ = meta.query_any(b.into(), Rotation::cur());
let sl_ = meta.query_any(sl.into(), Rotation::cur());
let sl2_ = meta.query_any(sl2.into(), Rotation::cur());
vec![(a_ * b_, sl_ * sl2_)]
vec![(a_, sl)]
});
meta.create_gate("Combined add-mult", |meta| {
@ -356,7 +333,6 @@ fn plonk_api() {
meta.enable_equality(sc.into());
meta.enable_equality(sp.into());
meta.enable_equality(sl.into());
meta.enable_equality(sl2.into());
PlonkConfig {
a,
@ -370,7 +346,6 @@ fn plonk_api() {
sm,
sp,
sl,
sl2,
}
}
@ -405,26 +380,24 @@ fn plonk_api() {
cs.copy(&mut layouter, b1, c0)?;
}
cs.lookup_table(&mut layouter, &self.lookup_tables)?;
cs.lookup_table(&mut layouter, &self.lookup_table)?;
Ok(())
}
}
let a = Fp::from_u64(2834758237) * Fp::ZETA;
let a_squared = a * &a;
let instance = Fp::one() + Fp::one();
let lookup_table = vec![instance, a, a, Fp::zero()];
let lookup_table_2 = vec![Fp::zero(), a, a_squared, Fp::zero()];
let empty_circuit: MyCircuit<Fp> = MyCircuit {
a: None,
lookup_tables: vec![lookup_table.clone(), lookup_table_2.clone()],
lookup_table: lookup_table.clone(),
};
let circuit: MyCircuit<Fp> = MyCircuit {
a: Some(a),
lookup_tables: vec![lookup_table, lookup_table_2],
lookup_table,
};
// Initialize the proving key
@ -454,7 +427,7 @@ fn plonk_api() {
let proof: Vec<u8> = transcript.finalize();
assert_eq!(
proof.len(),
halo2::dev::CircuitCost::<Eq, MyCircuit<_>>::measure(K as usize)
halo2::dev::CircuitCost::<Eq, MyCircuit<_>>::measure(K as usize, &circuit)
.proof_size(2)
.into(),
);
@ -514,13 +487,15 @@ fn plonk_api() {
scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001",
domain: PinnedEvaluationDomain {
k: 5,
extended_k: 8,
extended_k: 7,
omega: 0x0cc3380dc616f2e1daf29ad1560833ed3baea3393eceb7bc8fa36376929b78cc,
},
cs: PinnedConstraintSystem {
num_fixed_columns: 8,
num_fixed_columns: 7,
num_advice_columns: 5,
num_instance_columns: 1,
num_selectors: 0,
selector_map: [],
gates: [
Sum(
Sum(
@ -535,7 +510,7 @@ fn plonk_api() {
),
},
Fixed {
query_index: 3,
query_index: 2,
column_index: 2,
rotation: Rotation(
0,
@ -551,7 +526,7 @@ fn plonk_api() {
),
},
Fixed {
query_index: 4,
query_index: 3,
column_index: 3,
rotation: Rotation(
0,
@ -577,7 +552,7 @@ fn plonk_api() {
},
),
Fixed {
query_index: 6,
query_index: 5,
column_index: 1,
rotation: Rotation(
0,
@ -595,7 +570,7 @@ fn plonk_api() {
),
},
Fixed {
query_index: 5,
query_index: 4,
column_index: 4,
rotation: Rotation(
0,
@ -607,7 +582,7 @@ fn plonk_api() {
),
Product(
Fixed {
query_index: 2,
query_index: 1,
column_index: 0,
rotation: Rotation(
0,
@ -633,7 +608,7 @@ fn plonk_api() {
),
Product(
Fixed {
query_index: 7,
query_index: 6,
column_index: 5,
rotation: Rotation(
0,
@ -746,15 +721,6 @@ fn plonk_api() {
0,
),
),
(
Column {
index: 7,
column_type: Fixed,
},
Rotation(
0,
),
),
(
Column {
index: 0,
@ -864,10 +830,6 @@ fn plonk_api() {
index: 6,
column_type: Fixed,
},
Column {
index: 7,
column_type: Fixed,
},
],
},
lookups: [
@ -891,44 +853,6 @@ fn plonk_api() {
},
],
},
Argument {
input_expressions: [
Product(
Advice {
query_index: 0,
column_index: 1,
rotation: Rotation(
0,
),
},
Advice {
query_index: 1,
column_index: 2,
rotation: Rotation(
0,
),
},
),
],
table_expressions: [
Product(
Fixed {
query_index: 0,
column_index: 6,
rotation: Rotation(
0,
),
},
Fixed {
query_index: 1,
column_index: 7,
rotation: Rotation(
0,
),
},
),
],
},
],
constants: [],
minimum_degree: None,
@ -941,7 +865,6 @@ fn plonk_api() {
(0x02e62cd68370b13711139a08cbcdd889e800a272b9ea10acc90880fff9d89199, 0x1a96c468cb0ce77065d3a58f1e55fea9b72d15e44c01bba1e110bd0cbc6e9bc6),
(0x224ef42758215157d3ee48fb8d769da5bddd35e5929a90a4a89736f5c4b5ae9b, 0x11bc3a1e08eb320cde764f1492ecef956d71e996e2165f7a9a30ad2febb511c1),
(0x3c145eb1e4f1e49d9eed351a4e2d9f3deed13bc5ba028d3b425084d606418cc8, 0x045d846e7df4e563ce57cd5483d17bad87f0345e18409bf15abc3d71953ae71c),
(0x27b1cd6c0408a2fe7a764e6ac7abda4f6c7e7a4b3f7375532fe11f3af579de64, 0x19dcda088f6c8ad67408650554cfdd5c8c2e5385cf59c662554c837cf3f42c2d),
],
permutation: VerifyingKey {
commitments: [
@ -958,7 +881,6 @@ fn plonk_api() {
(0x394437571f9de32dccdc546fd4737772d8d92593c85438aa3473243997d5acc8, 0x14924ec6e3174f1fab7f0ce7070c22f04bbd0a0ecebdfc5c94be857f25493e95),
(0x3d907e0591343bd285c2c846f3e871a6ac70d80ec29e9500b8cb57f544e60202, 0x1034e48df35830244cabea076be8a16d67d7896e27c6ac22b285d017105da9c3),
(0x21d210b41675a1eae44cbd0f3fd27d69e30716c71873f6089cee61acacd403ab, 0x2275e97c7e84f68bfaa528a9d8be4e059f7abefd80d03fbfca774e8414a9b7c1),
(0x0f9e7de28e0f650d99d99d95c0fcd39c9dac9db5aa1973319f66922d6eb9f7d5, 0x1ba644ecc18ad711ddd33af7f695f6834e9f35c93d47a6a5273dabbe800fc7e6),
],
},
}"#####