mirror of https://github.com/zcash/halo2.git
Merge pull request #347 from zcash/selector-optimization
Selector optimization
This commit is contained in:
commit
2e960317ae
|
@ -51,6 +51,7 @@ tabbycat = { version = "0.1", features = ["attributes"], optional = true }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
gumdrop = "0.8"
|
gumdrop = "0.8"
|
||||||
|
proptest = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dev-graph = ["plotters", "tabbycat"]
|
dev-graph = ["plotters", "tabbycat"]
|
||||||
|
|
|
@ -28,7 +28,6 @@ fn main() {
|
||||||
sc: Column<Fixed>,
|
sc: Column<Fixed>,
|
||||||
sm: Column<Fixed>,
|
sm: Column<Fixed>,
|
||||||
sl: Column<Fixed>,
|
sl: Column<Fixed>,
|
||||||
sl2: Column<Fixed>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait StandardCs<FF: FieldExt> {
|
trait StandardCs<FF: FieldExt> {
|
||||||
|
@ -46,13 +45,13 @@ fn main() {
|
||||||
fn lookup_table(
|
fn lookup_table(
|
||||||
&self,
|
&self,
|
||||||
layouter: &mut impl Layouter<FF>,
|
layouter: &mut impl Layouter<FF>,
|
||||||
values: &[Vec<FF>],
|
values: &[FF],
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MyCircuit<F: FieldExt> {
|
struct MyCircuit<F: FieldExt> {
|
||||||
a: Option<F>,
|
a: Option<F>,
|
||||||
lookup_tables: Vec<Vec<F>>,
|
lookup_table: Vec<F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StandardPlonk<F: FieldExt> {
|
struct StandardPlonk<F: FieldExt> {
|
||||||
|
@ -170,26 +169,13 @@ fn main() {
|
||||||
fn lookup_table(
|
fn lookup_table(
|
||||||
&self,
|
&self,
|
||||||
layouter: &mut impl Layouter<FF>,
|
layouter: &mut impl Layouter<FF>,
|
||||||
values: &[Vec<FF>],
|
values: &[FF],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
layouter.assign_region(
|
layouter.assign_region(
|
||||||
|| "",
|
|| "",
|
||||||
|mut region| {
|
|mut region| {
|
||||||
for (index, (&value_0, &value_1)) in
|
for (index, &value) in values.iter().enumerate() {
|
||||||
values[0].iter().zip(values[1].iter()).enumerate()
|
region.assign_fixed(|| "table col", self.config.sl, index, || Ok(value))?;
|
||||||
{
|
|
||||||
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),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
|
@ -205,7 +191,7 @@ fn main() {
|
||||||
fn without_witnesses(&self) -> Self {
|
fn without_witnesses(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
a: None,
|
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 sb = meta.fixed_column();
|
||||||
let sc = meta.fixed_column();
|
let sc = meta.fixed_column();
|
||||||
let sl = meta.fixed_column();
|
let sl = meta.fixed_column();
|
||||||
let sl2 = meta.fixed_column();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A B ... sl sl2
|
* A B ... sl
|
||||||
* [
|
* [
|
||||||
* instance 0 ... 0 0
|
* instance 0 ... 0
|
||||||
* a a ... 0 0
|
* a a ... 0
|
||||||
* a a^2 ... 0 0
|
* a a^2 ... 0
|
||||||
* a a ... 0 0
|
* a a ... 0
|
||||||
* a a^2 ... 0 0
|
* a a^2 ... 0
|
||||||
* ... ... ... ... ...
|
* ... ... ... ...
|
||||||
* ... ... ... instance 0
|
* ... ... ... instance
|
||||||
* ... ... ... a a
|
* ... ... ... a
|
||||||
* ... ... ... a a^2
|
* ... ... ... a
|
||||||
* ... ... ... 0 0
|
* ... ... ... 0
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
meta.lookup(|meta| {
|
meta.lookup(|meta| {
|
||||||
let a_ = meta.query_any(a.into(), Rotation::cur());
|
let a_ = meta.query_any(a.into(), Rotation::cur());
|
||||||
let sl_ = meta.query_any(sl.into(), Rotation::cur());
|
vec![(a_, sl)]
|
||||||
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_)]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
meta.create_gate("Combined add-mult", |meta| {
|
meta.create_gate("Combined add-mult", |meta| {
|
||||||
|
@ -289,7 +266,6 @@ fn main() {
|
||||||
sc,
|
sc,
|
||||||
sm,
|
sm,
|
||||||
sl,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let a = Fp::rand();
|
let a = Fp::rand();
|
||||||
let a_squared = a * a;
|
|
||||||
let instance = Fp::one() + Fp::one();
|
let instance = Fp::one() + Fp::one();
|
||||||
let lookup_table = vec![instance, a, a, Fp::zero()];
|
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 {
|
let circuit: MyCircuit<Fp> = MyCircuit {
|
||||||
a: None,
|
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();
|
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))
|
.titled("Example Circuit Layout", ("sans-serif", 60))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
CircuitLayout::default().render(&circuit, &root).unwrap();
|
CircuitLayout::default().render(5, &circuit, &root).unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
@ -7,7 +7,7 @@ use ff::Field;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
circuit::{
|
circuit::{
|
||||||
layouter::{RegionLayouter, RegionShape},
|
layouter::{RegionColumn, RegionLayouter, RegionShape},
|
||||||
Cell, Layouter, Region, RegionIndex, RegionStart,
|
Cell, Layouter, Region, RegionIndex, RegionStart,
|
||||||
},
|
},
|
||||||
plonk::{
|
plonk::{
|
||||||
|
@ -43,7 +43,7 @@ pub struct SingleChipLayouter<'a, F: Field, CS: Assignment<F> + 'a> {
|
||||||
/// Stores the starting row for each region.
|
/// Stores the starting row for each region.
|
||||||
regions: Vec<RegionStart>,
|
regions: Vec<RegionStart>,
|
||||||
/// Stores the first empty row for each column.
|
/// Stores the first empty row for each column.
|
||||||
columns: HashMap<Column<Any>, usize>,
|
columns: HashMap<RegionColumn, usize>,
|
||||||
_marker: PhantomData<F>,
|
_marker: PhantomData<F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +119,10 @@ impl<'a, F: Field, CS: Assignment<F> + 'a> Layouter<F> for SingleChipLayouter<'a
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let constants_column = self.constants[0];
|
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 {
|
for (constant, advice) in constants_to_assign {
|
||||||
self.cs.assign_fixed(
|
self.cs.assign_fixed(
|
||||||
|| format!("Constant({:?})", constant.evaluate()),
|
|| format!("Constant({:?})", constant.evaluate()),
|
||||||
|
|
|
@ -4,11 +4,11 @@ use ff::Field;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
circuit::{
|
circuit::{
|
||||||
layouter::{RegionLayouter, RegionShape},
|
layouter::{RegionColumn, RegionLayouter, RegionShape},
|
||||||
Cell, Layouter, Region, RegionIndex, RegionStart,
|
Cell, Layouter, Region, RegionIndex, RegionStart,
|
||||||
},
|
},
|
||||||
plonk::{
|
plonk::{
|
||||||
Advice, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance,
|
Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance,
|
||||||
Selector,
|
Selector,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -89,7 +89,7 @@ impl FloorPlanner for V1 {
|
||||||
(
|
(
|
||||||
c,
|
c,
|
||||||
column_allocations
|
column_allocations
|
||||||
.get(&c.into())
|
.get(&Column::<Any>::from(c).into())
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,11 +4,8 @@ use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RegionShape;
|
use super::{RegionColumn, RegionShape};
|
||||||
use crate::{
|
use crate::{circuit::RegionStart, plonk::Any};
|
||||||
circuit::RegionStart,
|
|
||||||
plonk::{Any, Column},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A region allocated within a column.
|
/// A region allocated within a column.
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||||
|
@ -102,14 +99,14 @@ impl Allocations {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocated rows within a circuit.
|
/// 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).
|
/// - `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
|
/// - `slack` is the maximum number of rows the start could be moved down, taking into
|
||||||
/// account prior columns.
|
/// account prior columns.
|
||||||
fn first_fit_region(
|
fn first_fit_region(
|
||||||
column_allocations: &mut CircuitAllocations,
|
column_allocations: &mut CircuitAllocations,
|
||||||
region_columns: &[Column<Any>],
|
region_columns: &[RegionColumn],
|
||||||
region_length: usize,
|
region_length: usize,
|
||||||
start: usize,
|
start: usize,
|
||||||
slack: Option<usize>,
|
slack: Option<usize>,
|
||||||
|
@ -207,7 +204,10 @@ pub fn slot_in_biggest_advice_first(
|
||||||
let advice_cols = shape
|
let advice_cols = shape
|
||||||
.columns()
|
.columns()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|c| matches!(c.column_type(), Any::Advice))
|
.filter(|c| match c {
|
||||||
|
RegionColumn::Column(c) => matches!(c.column_type(), Any::Advice),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
.count();
|
.count();
|
||||||
// Sort by advice area (since this has the most contention).
|
// Sort by advice area (since this has the most contention).
|
||||||
advice_cols * shape.row_count()
|
advice_cols * shape.row_count()
|
||||||
|
@ -226,23 +226,30 @@ pub fn slot_in_biggest_advice_first(
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slot_in() {
|
fn test_slot_in() {
|
||||||
|
use crate::plonk::Column;
|
||||||
|
|
||||||
let regions = vec![
|
let regions = vec![
|
||||||
RegionShape {
|
RegionShape {
|
||||||
region_index: 0.into(),
|
region_index: 0.into(),
|
||||||
columns: vec![Column::new(0, Any::Advice), Column::new(1, Any::Advice)]
|
columns: vec![Column::new(0, Any::Advice), Column::new(1, Any::Advice)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.map(|a| a.into())
|
||||||
.collect(),
|
.collect(),
|
||||||
row_count: 15,
|
row_count: 15,
|
||||||
},
|
},
|
||||||
RegionShape {
|
RegionShape {
|
||||||
region_index: 1.into(),
|
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,
|
row_count: 10,
|
||||||
},
|
},
|
||||||
RegionShape {
|
RegionShape {
|
||||||
region_index: 2.into(),
|
region_index: 2.into(),
|
||||||
columns: vec![Column::new(2, Any::Advice), Column::new(0, Any::Advice)]
|
columns: vec![Column::new(2, Any::Advice), Column::new(0, Any::Advice)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.map(|a| a.into())
|
||||||
.collect(),
|
.collect(),
|
||||||
row_count: 10,
|
row_count: 10,
|
||||||
},
|
},
|
||||||
|
|
|
@ -110,10 +110,49 @@ pub trait RegionLayouter<F: Field>: fmt::Debug {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RegionShape {
|
pub struct RegionShape {
|
||||||
pub(super) region_index: RegionIndex,
|
pub(super) region_index: RegionIndex,
|
||||||
pub(super) columns: HashSet<Column<Any>>,
|
pub(super) columns: HashSet<RegionColumn>,
|
||||||
pub(super) row_count: usize,
|
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 {
|
impl RegionShape {
|
||||||
/// Create a new `RegionShape` for a region at `region_index`.
|
/// Create a new `RegionShape` for a region at `region_index`.
|
||||||
pub fn new(region_index: RegionIndex) -> Self {
|
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`.
|
/// 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
|
&self.columns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,8 +187,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
|
||||||
offset: usize,
|
offset: usize,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Track the selector's fixed column as part of the region's shape.
|
// Track the selector's fixed column as part of the region's shape.
|
||||||
// TODO: Avoid exposing selector internals?
|
self.columns.insert((*selector).into());
|
||||||
self.columns.insert(selector.0.into());
|
|
||||||
self.row_count = cmp::max(self.row_count, offset + 1);
|
self.row_count = cmp::max(self.row_count, offset + 1);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -161,7 +199,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
|
_to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
|
||||||
) -> Result<Cell, Error> {
|
) -> 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);
|
self.row_count = cmp::max(self.row_count, offset + 1);
|
||||||
|
|
||||||
Ok(Cell {
|
Ok(Cell {
|
||||||
|
@ -190,7 +228,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
|
||||||
advice: Column<Advice>,
|
advice: Column<Advice>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
) -> Result<(Cell, Option<F>), Error> {
|
) -> 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);
|
self.row_count = cmp::max(self.row_count, offset + 1);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
@ -210,7 +248,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
|
_to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
|
||||||
) -> Result<Cell, Error> {
|
) -> 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);
|
self.row_count = cmp::max(self.row_count, offset + 1);
|
||||||
|
|
||||||
Ok(Cell {
|
Ok(Cell {
|
||||||
|
|
28
src/dev.rs
28
src/dev.rs
|
@ -327,6 +327,8 @@ pub struct MockProver<F: Group + Field> {
|
||||||
// The instance cells in the circuit, arranged as [column][row].
|
// The instance cells in the circuit, arranged as [column][row].
|
||||||
instance: Vec<Vec<F>>,
|
instance: Vec<Vec<F>>,
|
||||||
|
|
||||||
|
selectors: Vec<Vec<bool>>,
|
||||||
|
|
||||||
permutation: permutation::keygen::Assembly,
|
permutation: permutation::keygen::Assembly,
|
||||||
|
|
||||||
// A range of available rows for assignment and copies.
|
// 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());
|
self.regions.push(self.current_region.take().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_selector<A, AR>(
|
fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
|
||||||
&mut self,
|
|
||||||
annotation: A,
|
|
||||||
selector: &Selector,
|
|
||||||
row: usize,
|
|
||||||
) -> Result<(), Error>
|
|
||||||
where
|
where
|
||||||
A: FnOnce() -> AR,
|
A: FnOnce() -> AR,
|
||||||
AR: Into<String>,
|
AR: Into<String>,
|
||||||
|
@ -376,8 +373,9 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(row);
|
.push(row);
|
||||||
|
|
||||||
// Selectors are just fixed columns.
|
self.selectors[selector.0][row] = true;
|
||||||
self.assign_fixed(annotation, selector.0, row, || Ok(F::one()))
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_instance(&self, column: Column<Instance>, row: usize) -> Result<Option<F>, Error> {
|
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.
|
// Fixed columns contain no blinding factors.
|
||||||
let fixed = vec![vec![CellValue::Unassigned; n]; cs.num_fixed_columns];
|
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.
|
// Advice columns contain blinding factors.
|
||||||
let blinding_factors = cs.blinding_factors();
|
let blinding_factors = cs.blinding_factors();
|
||||||
let usable_rows = n - (blinding_factors + 1);
|
let usable_rows = n - (blinding_factors + 1);
|
||||||
|
@ -543,12 +542,23 @@ impl<F: FieldExt> MockProver<F> {
|
||||||
fixed,
|
fixed,
|
||||||
advice,
|
advice,
|
||||||
instance,
|
instance,
|
||||||
|
selectors,
|
||||||
permutation,
|
permutation,
|
||||||
usable_rows: 0..usable_rows,
|
usable_rows: 0..usable_rows,
|
||||||
};
|
};
|
||||||
|
|
||||||
ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config, constants)?;
|
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)
|
Ok(prover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,6 +650,7 @@ impl<F: FieldExt> MockProver<F> {
|
||||||
gate.polynomials().iter().enumerate().filter_map(
|
gate.polynomials().iter().enumerate().filter_map(
|
||||||
move |(poly_index, poly)| match poly.evaluate(
|
move |(poly_index, poly)| match poly.evaluate(
|
||||||
&|scalar| Value::Real(scalar),
|
&|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.fixed_queries, &self.fixed),
|
||||||
&load(n, row, &self.cs.advice_queries, &self.advice),
|
&load(n, row, &self.cs.advice_queries, &self.advice),
|
||||||
&load_instance(n, row, &self.cs.instance_queries, &self.instance),
|
&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| {
|
let load = |expression: &Expression<F>, row| {
|
||||||
expression.evaluate(
|
expression.evaluate(
|
||||||
&|scalar| Value::Real(scalar),
|
&|scalar| Value::Real(scalar),
|
||||||
|
&|_| panic!("virtual selectors are removed during optimization"),
|
||||||
&|index, _, _| {
|
&|index, _, _| {
|
||||||
let query = self.cs.fixed_queries[index];
|
let query = self.cs.fixed_queries[index];
|
||||||
let column_index = query.0.index();
|
let column_index = query.0.index();
|
||||||
|
|
103
src/dev/cost.rs
103
src/dev/cost.rs
|
@ -6,11 +6,14 @@ use std::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ff::PrimeField;
|
use ff::{Field, PrimeField};
|
||||||
use group::prime::PrimeGroup;
|
use group::prime::PrimeGroup;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
plonk::{Any, Circuit, Column, ConstraintSystem},
|
plonk::{
|
||||||
|
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
|
||||||
|
FloorPlanner, Instance, Selector,
|
||||||
|
},
|
||||||
poly::Rotation,
|
poly::Rotation,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,14 +40,106 @@ pub struct CircuitCost<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> {
|
||||||
_marker: PhantomData<(G, ConcreteCircuit)>,
|
_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> {
|
impl<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, ConcreteCircuit> {
|
||||||
/// Measures a circuit with parameter constant `k`.
|
/// Measures a circuit with parameter constant `k`.
|
||||||
///
|
///
|
||||||
/// Panics if `k` is not large enough for the circuit.
|
/// 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.
|
// Collect the layout details.
|
||||||
let mut cs = ConstraintSystem::default();
|
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());
|
assert!((1 << k) >= cs.minimum_rows());
|
||||||
|
|
||||||
// Figure out how many point sets we have due to queried cells.
|
// Figure out how many point sets we have due to queried cells.
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::cmp;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use crate::circuit::layouter::RegionColumn;
|
||||||
use crate::plonk::{
|
use crate::plonk::{
|
||||||
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
|
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
|
||||||
FloorPlanner, Instance, Selector,
|
FloorPlanner, Instance, Selector,
|
||||||
|
@ -83,16 +84,18 @@ impl CircuitLayout {
|
||||||
/// Renders the given circuit on the given drawing area.
|
/// Renders the given circuit on the given drawing area.
|
||||||
pub fn render<F: Field, ConcreteCircuit: Circuit<F>, DB: DrawingBackend>(
|
pub fn render<F: Field, ConcreteCircuit: Circuit<F>, DB: DrawingBackend>(
|
||||||
self,
|
self,
|
||||||
|
k: usize,
|
||||||
circuit: &ConcreteCircuit,
|
circuit: &ConcreteCircuit,
|
||||||
drawing_area: &DrawingArea<DB, Shift>,
|
drawing_area: &DrawingArea<DB, Shift>,
|
||||||
) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
|
) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
|
||||||
use plotters::coord::types::RangedCoordusize;
|
use plotters::coord::types::RangedCoordusize;
|
||||||
use plotters::prelude::*;
|
use plotters::prelude::*;
|
||||||
|
|
||||||
|
let n = 1 << k;
|
||||||
// Collect the layout details.
|
// Collect the layout details.
|
||||||
let mut cs = ConstraintSystem::default();
|
let mut cs = ConstraintSystem::default();
|
||||||
let config = ConcreteCircuit::configure(&mut cs);
|
let config = ConcreteCircuit::configure(&mut cs);
|
||||||
let mut layout = Layout::default();
|
let mut layout = Layout::new(n, cs.num_selectors);
|
||||||
ConcreteCircuit::FloorPlanner::synthesize(
|
ConcreteCircuit::FloorPlanner::synthesize(
|
||||||
&mut layout,
|
&mut layout,
|
||||||
circuit,
|
circuit,
|
||||||
|
@ -100,11 +103,17 @@ impl CircuitLayout {
|
||||||
cs.constants.clone(),
|
cs.constants.clone(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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.
|
// Figure out what order to render the columns in.
|
||||||
// TODO: For now, just render them in the order they were configured.
|
// 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 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()
|
column.index()
|
||||||
+ match column.column_type() {
|
+ match column.column_type() {
|
||||||
Any::Instance => 0,
|
Any::Instance => 0,
|
||||||
|
@ -143,15 +152,16 @@ impl CircuitLayout {
|
||||||
],
|
],
|
||||||
ShapeStyle::from(&BLUE.mix(0.2)).filled(),
|
ShapeStyle::from(&BLUE.mix(0.2)).filled(),
|
||||||
))?;
|
))?;
|
||||||
for selector in layout.selectors {
|
{
|
||||||
let index = selector.index();
|
|
||||||
root.draw(&Rectangle::new(
|
root.draw(&Rectangle::new(
|
||||||
[
|
[
|
||||||
(cs.num_instance_columns + cs.num_advice_columns + index, 0),
|
|
||||||
(
|
(
|
||||||
cs.num_instance_columns + cs.num_advice_columns + index + 1,
|
cs.num_instance_columns
|
||||||
view_bottom,
|
+ cs.num_advice_columns
|
||||||
|
+ non_selector_fixed_columns,
|
||||||
|
0,
|
||||||
),
|
),
|
||||||
|
(total_columns, view_bottom),
|
||||||
],
|
],
|
||||||
ShapeStyle::from(&BLUE.mix(0.1)).filled(),
|
ShapeStyle::from(&BLUE.mix(0.1)).filled(),
|
||||||
))?;
|
))?;
|
||||||
|
@ -191,12 +201,12 @@ impl CircuitLayout {
|
||||||
if let Some(offset) = region.offset {
|
if let Some(offset) = region.offset {
|
||||||
// Sort the region's columns according to the defined ordering.
|
// Sort the region's columns according to the defined ordering.
|
||||||
let mut columns: Vec<_> = region.columns.into_iter().collect();
|
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.
|
// Render contiguous parts of the same region as a single box.
|
||||||
let mut width = None;
|
let mut width = None;
|
||||||
for column in columns {
|
for column in columns {
|
||||||
let column = column_index(&column);
|
let column = column_index(&cs, column);
|
||||||
match width {
|
match width {
|
||||||
Some((start, end)) if end == column => width = Some((start, end + 1)),
|
Some((start, end)) if end == column => width = Some((start, end + 1)),
|
||||||
Some((start, end)) => {
|
Some((start, end)) => {
|
||||||
|
@ -220,22 +230,22 @@ impl CircuitLayout {
|
||||||
|
|
||||||
// Darken the cells of the region that have been assigned to.
|
// Darken the cells of the region that have been assigned to.
|
||||||
for (column, row) in region.cells {
|
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.
|
// Darken any loose cells that have been assigned to.
|
||||||
for (column, row) in layout.loose_cells {
|
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.
|
// Mark equality-constrained cells.
|
||||||
if self.mark_equality_cells {
|
if self.mark_equality_cells {
|
||||||
let mut cells = HashSet::new();
|
let mut cells = HashSet::new();
|
||||||
for (l_col, l_row, r_col, r_row) in &layout.equality {
|
for (l_col, l_row, r_col, r_row) in &layout.equality {
|
||||||
let l_col = column_index(l_col);
|
let l_col = column_index(&cs, (*l_col).into());
|
||||||
let r_col = column_index(r_col);
|
let r_col = column_index(&cs, (*r_col).into());
|
||||||
|
|
||||||
// Deduplicate cells.
|
// Deduplicate cells.
|
||||||
cells.insert((l_col, *l_row));
|
cells.insert((l_col, *l_row));
|
||||||
|
@ -253,8 +263,8 @@ impl CircuitLayout {
|
||||||
// Draw lines between equality-constrained cells.
|
// Draw lines between equality-constrained cells.
|
||||||
if self.show_equality_constraints {
|
if self.show_equality_constraints {
|
||||||
for (l_col, l_row, r_col, r_row) in &layout.equality {
|
for (l_col, l_row, r_col, r_row) in &layout.equality {
|
||||||
let l_col = column_index(l_col);
|
let l_col = column_index(&cs, (*l_col).into());
|
||||||
let r_col = column_index(r_col);
|
let r_col = column_index(&cs, (*r_col).into());
|
||||||
root.draw(&PathElement::new(
|
root.draw(&PathElement::new(
|
||||||
[(l_col, *l_row), (r_col, *r_row)],
|
[(l_col, *l_row), (r_col, *r_row)],
|
||||||
ShapeStyle::from(&RED),
|
ShapeStyle::from(&RED),
|
||||||
|
@ -280,14 +290,14 @@ struct Region {
|
||||||
/// The name of the region. Not required to be unique.
|
/// The name of the region. Not required to be unique.
|
||||||
name: String,
|
name: String,
|
||||||
/// The columns used by this region.
|
/// The columns used by this region.
|
||||||
columns: HashSet<Column<Any>>,
|
columns: HashSet<RegionColumn>,
|
||||||
/// The row that this region starts on, if known.
|
/// The row that this region starts on, if known.
|
||||||
offset: Option<usize>,
|
offset: Option<usize>,
|
||||||
/// The number of rows that this region takes up.
|
/// The number of rows that this region takes up.
|
||||||
rows: usize,
|
rows: usize,
|
||||||
/// The cells assigned in this region. We store this as a `Vec` so that if any cells
|
/// 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.
|
/// are double-assigned, they will be visibly darker.
|
||||||
cells: Vec<(Column<Any>, usize)>,
|
cells: Vec<(RegionColumn, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -297,15 +307,30 @@ struct Layout {
|
||||||
total_rows: usize,
|
total_rows: usize,
|
||||||
/// Any cells assigned outside of a region. We store this as a `Vec` so that if any
|
/// 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.
|
/// cells are double-assigned, they will be visibly darker.
|
||||||
loose_cells: Vec<(Column<Any>, usize)>,
|
loose_cells: Vec<(RegionColumn, usize)>,
|
||||||
/// Columns we have observed are actually Selectors.
|
|
||||||
selectors: HashSet<Column<Any>>,
|
|
||||||
/// Pairs of cells between which we have equality constraints.
|
/// Pairs of cells between which we have equality constraints.
|
||||||
equality: Vec<(Column<Any>, usize, Column<Any>, usize)>,
|
equality: Vec<(Column<Any>, usize, Column<Any>, usize)>,
|
||||||
|
/// Selector assignments used for optimization pass
|
||||||
|
selectors: Vec<Vec<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout {
|
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);
|
self.total_rows = cmp::max(self.total_rows, row + 1);
|
||||||
|
|
||||||
if let Some(region) = self.current_region {
|
if let Some(region) = self.current_region {
|
||||||
|
@ -353,20 +378,14 @@ impl<F: Field> Assignment<F> for Layout {
|
||||||
self.current_region = None;
|
self.current_region = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_selector<A, AR>(
|
fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
|
||||||
&mut self,
|
|
||||||
annotation: A,
|
|
||||||
selector: &Selector,
|
|
||||||
row: usize,
|
|
||||||
) -> Result<(), Error>
|
|
||||||
where
|
where
|
||||||
A: FnOnce() -> AR,
|
A: FnOnce() -> AR,
|
||||||
AR: Into<String>,
|
AR: Into<String>,
|
||||||
{
|
{
|
||||||
// Remember that this column is a selector.
|
self.selectors[selector.0][row] = true;
|
||||||
self.selectors.insert(selector.0.into());
|
self.update((*selector).into(), row);
|
||||||
// Selectors are just fixed columns.
|
Ok(())
|
||||||
self.assign_fixed(annotation, selector.0, row, || Ok(F::one()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Option<F>, Error> {
|
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,
|
A: FnOnce() -> AR,
|
||||||
AR: Into<String>,
|
AR: Into<String>,
|
||||||
{
|
{
|
||||||
self.update(column.into(), row);
|
self.update(Column::<Any>::from(column).into(), row);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,7 +422,7 @@ impl<F: Field> Assignment<F> for Layout {
|
||||||
A: FnOnce() -> AR,
|
A: FnOnce() -> AR,
|
||||||
AR: Into<String>,
|
AR: Into<String>,
|
||||||
{
|
{
|
||||||
self.update(column.into(), row);
|
self.update(Column::<Any>::from(column).into(), row);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,9 @@ use std::{
|
||||||
|
|
||||||
use super::{lookup, permutation, Error};
|
use super::{lookup, permutation, Error};
|
||||||
use crate::circuit::Layouter;
|
use crate::circuit::Layouter;
|
||||||
use crate::{arithmetic::FieldExt, circuit::Region, poly::Rotation};
|
use crate::{circuit::Region, poly::Rotation};
|
||||||
|
|
||||||
|
mod compress_selectors;
|
||||||
|
|
||||||
/// A column type
|
/// A column type
|
||||||
pub trait ColumnType:
|
pub trait ColumnType:
|
||||||
|
@ -228,13 +230,19 @@ impl TryFrom<Column<Any>> for Column<Instance> {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Selector(pub(crate) Column<Fixed>);
|
pub struct Selector(pub(crate) usize, bool);
|
||||||
|
|
||||||
impl Selector {
|
impl Selector {
|
||||||
/// Enable this selector at the given offset within the given region.
|
/// 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)
|
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.
|
/// A value assigned to a cell within a circuit.
|
||||||
|
@ -540,6 +548,8 @@ pub trait Circuit<F: Field> {
|
||||||
pub enum Expression<F> {
|
pub enum Expression<F> {
|
||||||
/// This is a constant polynomial
|
/// This is a constant polynomial
|
||||||
Constant(F),
|
Constant(F),
|
||||||
|
/// This is a virtual selector
|
||||||
|
Selector(Selector),
|
||||||
/// This is a fixed column queried at a certain relative location
|
/// This is a fixed column queried at a certain relative location
|
||||||
Fixed {
|
Fixed {
|
||||||
/// Query index
|
/// Query index
|
||||||
|
@ -581,6 +591,7 @@ impl<F: Field> Expression<F> {
|
||||||
pub fn evaluate<T>(
|
pub fn evaluate<T>(
|
||||||
&self,
|
&self,
|
||||||
constant: &impl Fn(F) -> T,
|
constant: &impl Fn(F) -> T,
|
||||||
|
selector_column: &impl Fn(Selector) -> T,
|
||||||
fixed_column: &impl Fn(usize, usize, Rotation) -> T,
|
fixed_column: &impl Fn(usize, usize, Rotation) -> T,
|
||||||
advice_column: &impl Fn(usize, usize, Rotation) -> T,
|
advice_column: &impl Fn(usize, usize, Rotation) -> T,
|
||||||
instance_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 {
|
) -> T {
|
||||||
match self {
|
match self {
|
||||||
Expression::Constant(scalar) => constant(*scalar),
|
Expression::Constant(scalar) => constant(*scalar),
|
||||||
|
Expression::Selector(selector) => selector_column(*selector),
|
||||||
Expression::Fixed {
|
Expression::Fixed {
|
||||||
query_index,
|
query_index,
|
||||||
column_index,
|
column_index,
|
||||||
|
@ -608,6 +620,7 @@ impl<F: Field> Expression<F> {
|
||||||
Expression::Sum(a, b) => {
|
Expression::Sum(a, b) => {
|
||||||
let a = a.evaluate(
|
let a = a.evaluate(
|
||||||
constant,
|
constant,
|
||||||
|
selector_column,
|
||||||
fixed_column,
|
fixed_column,
|
||||||
advice_column,
|
advice_column,
|
||||||
instance_column,
|
instance_column,
|
||||||
|
@ -617,6 +630,7 @@ impl<F: Field> Expression<F> {
|
||||||
);
|
);
|
||||||
let b = b.evaluate(
|
let b = b.evaluate(
|
||||||
constant,
|
constant,
|
||||||
|
selector_column,
|
||||||
fixed_column,
|
fixed_column,
|
||||||
advice_column,
|
advice_column,
|
||||||
instance_column,
|
instance_column,
|
||||||
|
@ -629,6 +643,7 @@ impl<F: Field> Expression<F> {
|
||||||
Expression::Product(a, b) => {
|
Expression::Product(a, b) => {
|
||||||
let a = a.evaluate(
|
let a = a.evaluate(
|
||||||
constant,
|
constant,
|
||||||
|
selector_column,
|
||||||
fixed_column,
|
fixed_column,
|
||||||
advice_column,
|
advice_column,
|
||||||
instance_column,
|
instance_column,
|
||||||
|
@ -638,6 +653,7 @@ impl<F: Field> Expression<F> {
|
||||||
);
|
);
|
||||||
let b = b.evaluate(
|
let b = b.evaluate(
|
||||||
constant,
|
constant,
|
||||||
|
selector_column,
|
||||||
fixed_column,
|
fixed_column,
|
||||||
advice_column,
|
advice_column,
|
||||||
instance_column,
|
instance_column,
|
||||||
|
@ -650,6 +666,7 @@ impl<F: Field> Expression<F> {
|
||||||
Expression::Scaled(a, f) => {
|
Expression::Scaled(a, f) => {
|
||||||
let a = a.evaluate(
|
let a = a.evaluate(
|
||||||
constant,
|
constant,
|
||||||
|
selector_column,
|
||||||
fixed_column,
|
fixed_column,
|
||||||
advice_column,
|
advice_column,
|
||||||
instance_column,
|
instance_column,
|
||||||
|
@ -666,6 +683,7 @@ impl<F: Field> Expression<F> {
|
||||||
pub fn degree(&self) -> usize {
|
pub fn degree(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Expression::Constant(_) => 0,
|
Expression::Constant(_) => 0,
|
||||||
|
Expression::Selector(_) => 1,
|
||||||
Expression::Fixed { .. } => 1,
|
Expression::Fixed { .. } => 1,
|
||||||
Expression::Advice { .. } => 1,
|
Expression::Advice { .. } => 1,
|
||||||
Expression::Instance { .. } => 1,
|
Expression::Instance { .. } => 1,
|
||||||
|
@ -679,6 +697,46 @@ impl<F: Field> Expression<F> {
|
||||||
pub fn square(self) -> Self {
|
pub fn square(self) -> Self {
|
||||||
self.clone() * 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> {
|
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>;
|
type Output = Expression<F>;
|
||||||
fn add(self, rhs: Expression<F>) -> 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))
|
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> {
|
impl<F: Field> Sub for Expression<F> {
|
||||||
type Output = Expression<F>;
|
type Output = Expression<F>;
|
||||||
fn sub(self, rhs: Expression<F>) -> 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))
|
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>;
|
type Output = Expression<F>;
|
||||||
fn mul(self, rhs: Expression<F>) -> 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))
|
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>;
|
type Output = Expression<F>;
|
||||||
fn mul(self, rhs: F) -> Expression<F> {
|
fn mul(self, rhs: F) -> Expression<F> {
|
||||||
Expression::Scaled(Box::new(self), rhs)
|
Expression::Scaled(Box::new(self), rhs)
|
||||||
|
@ -805,6 +872,8 @@ pub struct ConstraintSystem<F: Field> {
|
||||||
pub(crate) num_fixed_columns: usize,
|
pub(crate) num_fixed_columns: usize,
|
||||||
pub(crate) num_advice_columns: usize,
|
pub(crate) num_advice_columns: usize,
|
||||||
pub(crate) num_instance_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) gates: Vec<Gate<F>>,
|
||||||
pub(crate) advice_queries: Vec<(Column<Advice>, Rotation)>,
|
pub(crate) advice_queries: Vec<(Column<Advice>, Rotation)>,
|
||||||
// Contains an integer for each advice column
|
// Contains an integer for each advice column
|
||||||
|
@ -834,6 +903,8 @@ pub struct PinnedConstraintSystem<'a, F: Field> {
|
||||||
num_fixed_columns: &'a usize,
|
num_fixed_columns: &'a usize,
|
||||||
num_advice_columns: &'a usize,
|
num_advice_columns: &'a usize,
|
||||||
num_instance_columns: &'a usize,
|
num_instance_columns: &'a usize,
|
||||||
|
num_selectors: &'a usize,
|
||||||
|
selector_map: &'a [Column<Fixed>],
|
||||||
gates: PinnedGates<'a, F>,
|
gates: PinnedGates<'a, F>,
|
||||||
advice_queries: &'a Vec<(Column<Advice>, Rotation)>,
|
advice_queries: &'a Vec<(Column<Advice>, Rotation)>,
|
||||||
instance_queries: &'a Vec<(Column<Instance>, Rotation)>,
|
instance_queries: &'a Vec<(Column<Instance>, Rotation)>,
|
||||||
|
@ -860,6 +931,8 @@ impl<F: Field> Default for ConstraintSystem<F> {
|
||||||
num_fixed_columns: 0,
|
num_fixed_columns: 0,
|
||||||
num_advice_columns: 0,
|
num_advice_columns: 0,
|
||||||
num_instance_columns: 0,
|
num_instance_columns: 0,
|
||||||
|
num_selectors: 0,
|
||||||
|
selector_map: vec![],
|
||||||
gates: vec![],
|
gates: vec![],
|
||||||
fixed_queries: Vec::new(),
|
fixed_queries: Vec::new(),
|
||||||
advice_queries: Vec::new(),
|
advice_queries: Vec::new(),
|
||||||
|
@ -882,6 +955,8 @@ impl<F: Field> ConstraintSystem<F> {
|
||||||
num_fixed_columns: &self.num_fixed_columns,
|
num_fixed_columns: &self.num_fixed_columns,
|
||||||
num_advice_columns: &self.num_advice_columns,
|
num_advice_columns: &self.num_advice_columns,
|
||||||
num_instance_columns: &self.num_instance_columns,
|
num_instance_columns: &self.num_instance_columns,
|
||||||
|
num_selectors: &self.num_selectors,
|
||||||
|
selector_map: &self.selector_map,
|
||||||
gates: PinnedGates(&self.gates),
|
gates: PinnedGates(&self.gates),
|
||||||
fixed_queries: &self.fixed_queries,
|
fixed_queries: &self.fixed_queries,
|
||||||
advice_queries: &self.advice_queries,
|
advice_queries: &self.advice_queries,
|
||||||
|
@ -917,10 +992,21 @@ impl<F: Field> ConstraintSystem<F> {
|
||||||
/// they need to match.
|
/// they need to match.
|
||||||
pub fn lookup(
|
pub fn lookup(
|
||||||
&mut self,
|
&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 {
|
) -> usize {
|
||||||
let mut cells = VirtualCells::new(self);
|
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();
|
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 {
|
pub fn selector(&mut self) -> Selector {
|
||||||
// TODO: Track selectors separately, and combine selectors where possible.
|
let index = self.num_selectors;
|
||||||
// https://github.com/zcash/halo2/issues/116
|
self.num_selectors += 1;
|
||||||
Selector(self.fixed_column())
|
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
|
/// Allocate a new fixed column
|
||||||
|
@ -1204,13 +1427,8 @@ impl<'a, F: Field> VirtualCells<'a, F> {
|
||||||
|
|
||||||
/// Query a selector at the current position.
|
/// Query a selector at the current position.
|
||||||
pub fn query_selector(&mut self, selector: Selector) -> Expression<F> {
|
pub fn query_selector(&mut self, selector: Selector) -> Expression<F> {
|
||||||
// Selectors are always queried at the current row.
|
|
||||||
self.queried_selectors.push(selector);
|
self.queried_selectors.push(selector);
|
||||||
Expression::Fixed {
|
Expression::Selector(selector)
|
||||||
query_index: self.meta.query_fixed_index(selector.0, Rotation::cur()),
|
|
||||||
column_index: (selector.0).index,
|
|
||||||
rotation: Rotation::cur(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query a fixed column at a relative position
|
/// Query a fixed column at a relative position
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ where
|
||||||
struct Assembly<F: Field> {
|
struct Assembly<F: Field> {
|
||||||
fixed: Vec<Polynomial<Assigned<F>, LagrangeCoeff>>,
|
fixed: Vec<Polynomial<Assigned<F>, LagrangeCoeff>>,
|
||||||
permutation: permutation::keygen::Assembly,
|
permutation: permutation::keygen::Assembly,
|
||||||
|
selectors: Vec<Vec<bool>>,
|
||||||
// A range of available rows for assignment and copies.
|
// A range of available rows for assignment and copies.
|
||||||
usable_rows: RangeTo<usize>,
|
usable_rows: RangeTo<usize>,
|
||||||
_marker: std::marker::PhantomData<F>,
|
_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.
|
// Do nothing; we don't care about regions in this context.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_selector<A, AR>(
|
fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
|
||||||
&mut self,
|
|
||||||
annotation: A,
|
|
||||||
selector: &Selector,
|
|
||||||
row: usize,
|
|
||||||
) -> Result<(), Error>
|
|
||||||
where
|
where
|
||||||
A: FnOnce() -> AR,
|
A: FnOnce() -> AR,
|
||||||
AR: Into<String>,
|
AR: Into<String>,
|
||||||
|
@ -75,12 +71,10 @@ impl<F: Field> Assignment<F> for Assembly<F> {
|
||||||
if !self.usable_rows.contains(&row) {
|
if !self.usable_rows.contains(&row) {
|
||||||
return Err(Error::BoundsFailure);
|
return Err(Error::BoundsFailure);
|
||||||
}
|
}
|
||||||
// Selectors are just fixed columns.
|
|
||||||
// TODO: Ensure that the default for a selector's cells is always zero, if we
|
self.selectors[selector.0][row] = true;
|
||||||
// alter the proving system to change the global default.
|
|
||||||
// TODO: Implement selector combining optimization
|
Ok(())
|
||||||
// https://github.com/zcash/halo2/issues/116
|
|
||||||
self.assign_fixed(annotation, selector.0, row, || Ok(F::one()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_instance(&self, _: Column<Instance>, row: usize) -> Result<Option<F>, Error> {
|
fn query_instance(&self, _: Column<Instance>, row: usize) -> Result<Option<F>, Error> {
|
||||||
|
@ -181,6 +175,7 @@ where
|
||||||
let mut assembly: Assembly<C::Scalar> = Assembly {
|
let mut assembly: Assembly<C::Scalar> = Assembly {
|
||||||
fixed: vec![domain.empty_lagrange_assigned(); cs.num_fixed_columns],
|
fixed: vec![domain.empty_lagrange_assigned(); cs.num_fixed_columns],
|
||||||
permutation: permutation::keygen::Assembly::new(params.n as usize, &cs.permutation),
|
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),
|
usable_rows: ..params.n as usize - (cs.blinding_factors() + 1),
|
||||||
_marker: std::marker::PhantomData,
|
_marker: std::marker::PhantomData,
|
||||||
};
|
};
|
||||||
|
@ -193,7 +188,13 @@ where
|
||||||
cs.constants.clone(),
|
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
|
let permutation_vk = assembly
|
||||||
.permutation
|
.permutation
|
||||||
|
@ -232,9 +233,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut assembly: Assembly<C::Scalar> = Assembly {
|
let mut assembly: Assembly<C::Scalar> = Assembly {
|
||||||
fixed: vec![vk.domain.empty_lagrange_assigned(); vk.cs.num_fixed_columns],
|
fixed: vec![vk.domain.empty_lagrange_assigned(); cs.num_fixed_columns],
|
||||||
permutation: permutation::keygen::Assembly::new(params.n as usize, &vk.cs.permutation),
|
permutation: permutation::keygen::Assembly::new(params.n as usize, &cs.permutation),
|
||||||
usable_rows: ..params.n as usize - (vk.cs.blinding_factors() + 1),
|
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,
|
_marker: std::marker::PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -246,7 +248,13 @@ where
|
||||||
cs.constants.clone(),
|
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
|
let fixed_polys: Vec<_> = fixed
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -260,7 +268,7 @@ where
|
||||||
|
|
||||||
let permutation_pk = assembly
|
let permutation_pk = assembly
|
||||||
.permutation
|
.permutation
|
||||||
.build_pk(params, &vk.domain, &vk.cs.permutation);
|
.build_pk(params, &vk.domain, &cs.permutation);
|
||||||
|
|
||||||
// Compute l_0(X)
|
// Compute l_0(X)
|
||||||
// TODO: this can be done more efficiently
|
// TODO: this can be done more efficiently
|
||||||
|
|
|
@ -101,6 +101,7 @@ impl<F: FieldExt> Argument<F> {
|
||||||
.map(|expression| {
|
.map(|expression| {
|
||||||
expression.evaluate(
|
expression.evaluate(
|
||||||
&|scalar| pk.vk.domain.constant_lagrange(scalar),
|
&|scalar| pk.vk.domain.constant_lagrange(scalar),
|
||||||
|
&|_| panic!("virtual selectors are removed during optimization"),
|
||||||
&|_, column_index, rotation| {
|
&|_, column_index, rotation| {
|
||||||
fixed_values[column_index].clone().rotate(rotation)
|
fixed_values[column_index].clone().rotate(rotation)
|
||||||
},
|
},
|
||||||
|
@ -134,6 +135,7 @@ impl<F: FieldExt> Argument<F> {
|
||||||
.map(|expression| {
|
.map(|expression| {
|
||||||
expression.evaluate(
|
expression.evaluate(
|
||||||
&|scalar| pk.vk.domain.constant_extended(scalar),
|
&|scalar| pk.vk.domain.constant_extended(scalar),
|
||||||
|
&|_| panic!("virtual selectors are removed during optimization"),
|
||||||
&|_, column_index, rotation| {
|
&|_, column_index, rotation| {
|
||||||
pk.vk
|
pk.vk
|
||||||
.domain
|
.domain
|
||||||
|
|
|
@ -134,6 +134,7 @@ impl<C: CurveAffine> Evaluated<C> {
|
||||||
.map(|expression| {
|
.map(|expression| {
|
||||||
expression.evaluate(
|
expression.evaluate(
|
||||||
&|scalar| scalar,
|
&|scalar| scalar,
|
||||||
|
&|_| panic!("virtual selectors are removed during optimization"),
|
||||||
&|index, _, _| fixed_evals[index],
|
&|index, _, _| fixed_evals[index],
|
||||||
&|index, _, _| advice_evals[index],
|
&|index, _, _| advice_evals[index],
|
||||||
&|index, _, _| instance_evals[index],
|
&|index, _, _| instance_evals[index],
|
||||||
|
|
|
@ -56,6 +56,10 @@ pub fn create_proof<
|
||||||
let mut meta = ConstraintSystem::default();
|
let mut meta = ConstraintSystem::default();
|
||||||
let config = ConcreteCircuit::configure(&mut meta);
|
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> {
|
struct InstanceSingle<C: CurveAffine> {
|
||||||
pub instance_values: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
|
pub instance_values: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
|
||||||
pub instance_polys: Vec<Polynomial<C::Scalar, Coeff>>,
|
pub instance_polys: Vec<Polynomial<C::Scalar, Coeff>>,
|
||||||
|
@ -431,6 +435,7 @@ pub fn create_proof<
|
||||||
gate.polynomials().iter().map(move |poly| {
|
gate.polynomials().iter().map(move |poly| {
|
||||||
poly.evaluate(
|
poly.evaluate(
|
||||||
&|scalar| pk.vk.domain.constant_extended(scalar),
|
&|scalar| pk.vk.domain.constant_extended(scalar),
|
||||||
|
&|_| panic!("virtual selectors are removed during optimization"),
|
||||||
&|_, column_index, rotation| {
|
&|_, column_index, rotation| {
|
||||||
pk.vk
|
pk.vk
|
||||||
.domain
|
.domain
|
||||||
|
|
|
@ -184,6 +184,7 @@ pub fn verify_proof<'params, C: CurveAffine, E: EncodedChallenge<C>, T: Transcri
|
||||||
gate.polynomials().iter().map(move |poly| {
|
gate.polynomials().iter().map(move |poly| {
|
||||||
poly.evaluate(
|
poly.evaluate(
|
||||||
&|scalar| scalar,
|
&|scalar| scalar,
|
||||||
|
&|_| panic!("virtual selectors are removed during optimization"),
|
||||||
&|index, _, _| fixed_evals[index],
|
&|index, _, _| fixed_evals[index],
|
||||||
&|index, _, _| advice_evals[index],
|
&|index, _, _| advice_evals[index],
|
||||||
&|index, _, _| instance_evals[index],
|
&|index, _, _| instance_evals[index],
|
||||||
|
|
|
@ -38,7 +38,6 @@ fn plonk_api() {
|
||||||
sm: Column<Fixed>,
|
sm: Column<Fixed>,
|
||||||
sp: Column<Fixed>,
|
sp: Column<Fixed>,
|
||||||
sl: Column<Fixed>,
|
sl: Column<Fixed>,
|
||||||
sl2: Column<Fixed>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait StandardCs<FF: FieldExt> {
|
trait StandardCs<FF: FieldExt> {
|
||||||
|
@ -63,14 +62,14 @@ fn plonk_api() {
|
||||||
fn lookup_table(
|
fn lookup_table(
|
||||||
&self,
|
&self,
|
||||||
layouter: &mut impl Layouter<FF>,
|
layouter: &mut impl Layouter<FF>,
|
||||||
values: &[Vec<FF>],
|
values: &[FF],
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MyCircuit<F: FieldExt> {
|
struct MyCircuit<F: FieldExt> {
|
||||||
a: Option<F>,
|
a: Option<F>,
|
||||||
lookup_tables: Vec<Vec<F>>,
|
lookup_table: Vec<F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StandardPlonk<F: FieldExt> {
|
struct StandardPlonk<F: FieldExt> {
|
||||||
|
@ -227,26 +226,13 @@ fn plonk_api() {
|
||||||
fn lookup_table(
|
fn lookup_table(
|
||||||
&self,
|
&self,
|
||||||
layouter: &mut impl Layouter<FF>,
|
layouter: &mut impl Layouter<FF>,
|
||||||
values: &[Vec<FF>],
|
values: &[FF],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
layouter.assign_region(
|
layouter.assign_region(
|
||||||
|| "",
|
|| "",
|
||||||
|mut region| {
|
|mut region| {
|
||||||
for (index, (&value_0, &value_1)) in
|
for (index, &value) in values.iter().enumerate() {
|
||||||
values[0].iter().zip(values[1].iter()).enumerate()
|
region.assign_fixed(|| "table col", self.config.sl, index, || Ok(value))?;
|
||||||
{
|
|
||||||
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),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
|
@ -262,7 +248,7 @@ fn plonk_api() {
|
||||||
fn without_witnesses(&self) -> Self {
|
fn without_witnesses(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
a: None,
|
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 sc = meta.fixed_column();
|
||||||
let sp = meta.fixed_column();
|
let sp = meta.fixed_column();
|
||||||
let sl = meta.fixed_column();
|
let sl = meta.fixed_column();
|
||||||
let sl2 = meta.fixed_column();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A B ... sl sl2
|
* A B ... sl
|
||||||
* [
|
* [
|
||||||
* instance 0 ... 0 0
|
* instance 0 ... 0
|
||||||
* a a ... 0 0
|
* a a ... 0
|
||||||
* a a^2 ... 0 0
|
* a a^2 ... 0
|
||||||
* a a ... 0 0
|
* a a ... 0
|
||||||
* a a^2 ... 0 0
|
* a a^2 ... 0
|
||||||
* ... ... ... ... ...
|
* ... ... ... ...
|
||||||
* ... ... ... instance 0
|
* ... ... ... instance
|
||||||
* ... ... ... a a
|
* ... ... ... a
|
||||||
* ... ... ... a a^2
|
* ... ... ... a
|
||||||
* ... ... ... 0 0
|
* ... ... ... 0
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
meta.lookup(|meta| {
|
meta.lookup(|meta| {
|
||||||
let a_ = meta.query_any(a.into(), Rotation::cur());
|
let a_ = meta.query_any(a.into(), Rotation::cur());
|
||||||
let sl_ = meta.query_any(sl.into(), Rotation::cur());
|
vec![(a_, sl)]
|
||||||
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_)]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
meta.create_gate("Combined add-mult", |meta| {
|
meta.create_gate("Combined add-mult", |meta| {
|
||||||
|
@ -356,7 +333,6 @@ fn plonk_api() {
|
||||||
meta.enable_equality(sc.into());
|
meta.enable_equality(sc.into());
|
||||||
meta.enable_equality(sp.into());
|
meta.enable_equality(sp.into());
|
||||||
meta.enable_equality(sl.into());
|
meta.enable_equality(sl.into());
|
||||||
meta.enable_equality(sl2.into());
|
|
||||||
|
|
||||||
PlonkConfig {
|
PlonkConfig {
|
||||||
a,
|
a,
|
||||||
|
@ -370,7 +346,6 @@ fn plonk_api() {
|
||||||
sm,
|
sm,
|
||||||
sp,
|
sp,
|
||||||
sl,
|
sl,
|
||||||
sl2,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,26 +380,24 @@ fn plonk_api() {
|
||||||
cs.copy(&mut layouter, b1, c0)?;
|
cs.copy(&mut layouter, b1, c0)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.lookup_table(&mut layouter, &self.lookup_tables)?;
|
cs.lookup_table(&mut layouter, &self.lookup_table)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let a = Fp::from_u64(2834758237) * Fp::ZETA;
|
let a = Fp::from_u64(2834758237) * Fp::ZETA;
|
||||||
let a_squared = a * &a;
|
|
||||||
let instance = Fp::one() + Fp::one();
|
let instance = Fp::one() + Fp::one();
|
||||||
let lookup_table = vec![instance, a, a, Fp::zero()];
|
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 {
|
let empty_circuit: MyCircuit<Fp> = MyCircuit {
|
||||||
a: None,
|
a: None,
|
||||||
lookup_tables: vec![lookup_table.clone(), lookup_table_2.clone()],
|
lookup_table: lookup_table.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let circuit: MyCircuit<Fp> = MyCircuit {
|
let circuit: MyCircuit<Fp> = MyCircuit {
|
||||||
a: Some(a),
|
a: Some(a),
|
||||||
lookup_tables: vec![lookup_table, lookup_table_2],
|
lookup_table,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize the proving key
|
// Initialize the proving key
|
||||||
|
@ -454,7 +427,7 @@ fn plonk_api() {
|
||||||
let proof: Vec<u8> = transcript.finalize();
|
let proof: Vec<u8> = transcript.finalize();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
proof.len(),
|
proof.len(),
|
||||||
halo2::dev::CircuitCost::<Eq, MyCircuit<_>>::measure(K as usize)
|
halo2::dev::CircuitCost::<Eq, MyCircuit<_>>::measure(K as usize, &circuit)
|
||||||
.proof_size(2)
|
.proof_size(2)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
@ -514,13 +487,15 @@ fn plonk_api() {
|
||||||
scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001",
|
scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001",
|
||||||
domain: PinnedEvaluationDomain {
|
domain: PinnedEvaluationDomain {
|
||||||
k: 5,
|
k: 5,
|
||||||
extended_k: 8,
|
extended_k: 7,
|
||||||
omega: 0x0cc3380dc616f2e1daf29ad1560833ed3baea3393eceb7bc8fa36376929b78cc,
|
omega: 0x0cc3380dc616f2e1daf29ad1560833ed3baea3393eceb7bc8fa36376929b78cc,
|
||||||
},
|
},
|
||||||
cs: PinnedConstraintSystem {
|
cs: PinnedConstraintSystem {
|
||||||
num_fixed_columns: 8,
|
num_fixed_columns: 7,
|
||||||
num_advice_columns: 5,
|
num_advice_columns: 5,
|
||||||
num_instance_columns: 1,
|
num_instance_columns: 1,
|
||||||
|
num_selectors: 0,
|
||||||
|
selector_map: [],
|
||||||
gates: [
|
gates: [
|
||||||
Sum(
|
Sum(
|
||||||
Sum(
|
Sum(
|
||||||
|
@ -535,7 +510,7 @@ fn plonk_api() {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Fixed {
|
Fixed {
|
||||||
query_index: 3,
|
query_index: 2,
|
||||||
column_index: 2,
|
column_index: 2,
|
||||||
rotation: Rotation(
|
rotation: Rotation(
|
||||||
0,
|
0,
|
||||||
|
@ -551,7 +526,7 @@ fn plonk_api() {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Fixed {
|
Fixed {
|
||||||
query_index: 4,
|
query_index: 3,
|
||||||
column_index: 3,
|
column_index: 3,
|
||||||
rotation: Rotation(
|
rotation: Rotation(
|
||||||
0,
|
0,
|
||||||
|
@ -577,7 +552,7 @@ fn plonk_api() {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Fixed {
|
Fixed {
|
||||||
query_index: 6,
|
query_index: 5,
|
||||||
column_index: 1,
|
column_index: 1,
|
||||||
rotation: Rotation(
|
rotation: Rotation(
|
||||||
0,
|
0,
|
||||||
|
@ -595,7 +570,7 @@ fn plonk_api() {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Fixed {
|
Fixed {
|
||||||
query_index: 5,
|
query_index: 4,
|
||||||
column_index: 4,
|
column_index: 4,
|
||||||
rotation: Rotation(
|
rotation: Rotation(
|
||||||
0,
|
0,
|
||||||
|
@ -607,7 +582,7 @@ fn plonk_api() {
|
||||||
),
|
),
|
||||||
Product(
|
Product(
|
||||||
Fixed {
|
Fixed {
|
||||||
query_index: 2,
|
query_index: 1,
|
||||||
column_index: 0,
|
column_index: 0,
|
||||||
rotation: Rotation(
|
rotation: Rotation(
|
||||||
0,
|
0,
|
||||||
|
@ -633,7 +608,7 @@ fn plonk_api() {
|
||||||
),
|
),
|
||||||
Product(
|
Product(
|
||||||
Fixed {
|
Fixed {
|
||||||
query_index: 7,
|
query_index: 6,
|
||||||
column_index: 5,
|
column_index: 5,
|
||||||
rotation: Rotation(
|
rotation: Rotation(
|
||||||
0,
|
0,
|
||||||
|
@ -746,15 +721,6 @@ fn plonk_api() {
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
|
||||||
Column {
|
|
||||||
index: 7,
|
|
||||||
column_type: Fixed,
|
|
||||||
},
|
|
||||||
Rotation(
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
Column {
|
Column {
|
||||||
index: 0,
|
index: 0,
|
||||||
|
@ -864,10 +830,6 @@ fn plonk_api() {
|
||||||
index: 6,
|
index: 6,
|
||||||
column_type: Fixed,
|
column_type: Fixed,
|
||||||
},
|
},
|
||||||
Column {
|
|
||||||
index: 7,
|
|
||||||
column_type: Fixed,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
lookups: [
|
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: [],
|
constants: [],
|
||||||
minimum_degree: None,
|
minimum_degree: None,
|
||||||
|
@ -941,7 +865,6 @@ fn plonk_api() {
|
||||||
(0x02e62cd68370b13711139a08cbcdd889e800a272b9ea10acc90880fff9d89199, 0x1a96c468cb0ce77065d3a58f1e55fea9b72d15e44c01bba1e110bd0cbc6e9bc6),
|
(0x02e62cd68370b13711139a08cbcdd889e800a272b9ea10acc90880fff9d89199, 0x1a96c468cb0ce77065d3a58f1e55fea9b72d15e44c01bba1e110bd0cbc6e9bc6),
|
||||||
(0x224ef42758215157d3ee48fb8d769da5bddd35e5929a90a4a89736f5c4b5ae9b, 0x11bc3a1e08eb320cde764f1492ecef956d71e996e2165f7a9a30ad2febb511c1),
|
(0x224ef42758215157d3ee48fb8d769da5bddd35e5929a90a4a89736f5c4b5ae9b, 0x11bc3a1e08eb320cde764f1492ecef956d71e996e2165f7a9a30ad2febb511c1),
|
||||||
(0x3c145eb1e4f1e49d9eed351a4e2d9f3deed13bc5ba028d3b425084d606418cc8, 0x045d846e7df4e563ce57cd5483d17bad87f0345e18409bf15abc3d71953ae71c),
|
(0x3c145eb1e4f1e49d9eed351a4e2d9f3deed13bc5ba028d3b425084d606418cc8, 0x045d846e7df4e563ce57cd5483d17bad87f0345e18409bf15abc3d71953ae71c),
|
||||||
(0x27b1cd6c0408a2fe7a764e6ac7abda4f6c7e7a4b3f7375532fe11f3af579de64, 0x19dcda088f6c8ad67408650554cfdd5c8c2e5385cf59c662554c837cf3f42c2d),
|
|
||||||
],
|
],
|
||||||
permutation: VerifyingKey {
|
permutation: VerifyingKey {
|
||||||
commitments: [
|
commitments: [
|
||||||
|
@ -958,7 +881,6 @@ fn plonk_api() {
|
||||||
(0x394437571f9de32dccdc546fd4737772d8d92593c85438aa3473243997d5acc8, 0x14924ec6e3174f1fab7f0ce7070c22f04bbd0a0ecebdfc5c94be857f25493e95),
|
(0x394437571f9de32dccdc546fd4737772d8d92593c85438aa3473243997d5acc8, 0x14924ec6e3174f1fab7f0ce7070c22f04bbd0a0ecebdfc5c94be857f25493e95),
|
||||||
(0x3d907e0591343bd285c2c846f3e871a6ac70d80ec29e9500b8cb57f544e60202, 0x1034e48df35830244cabea076be8a16d67d7896e27c6ac22b285d017105da9c3),
|
(0x3d907e0591343bd285c2c846f3e871a6ac70d80ec29e9500b8cb57f544e60202, 0x1034e48df35830244cabea076be8a16d67d7896e27c6ac22b285d017105da9c3),
|
||||||
(0x21d210b41675a1eae44cbd0f3fd27d69e30716c71873f6089cee61acacd403ab, 0x2275e97c7e84f68bfaa528a9d8be4e059f7abefd80d03fbfca774e8414a9b7c1),
|
(0x21d210b41675a1eae44cbd0f3fd27d69e30716c71873f6089cee61acacd403ab, 0x2275e97c7e84f68bfaa528a9d8be4e059f7abefd80d03fbfca774e8414a9b7c1),
|
||||||
(0x0f9e7de28e0f650d99d99d95c0fcd39c9dac9db5aa1973319f66922d6eb9f7d5, 0x1ba644ecc18ad711ddd33af7f695f6834e9f35c93d47a6a5273dabbe800fc7e6),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}"#####
|
}"#####
|
||||||
|
|
Loading…
Reference in New Issue