mirror of https://github.com/zcash/halo2.git
Merge pull request #342 from zcash/335-assign-advice-from-constant
Add `Region::assign_advice_from_constant` API
This commit is contained in:
commit
2b1baaf693
|
@ -5,7 +5,7 @@ use std::marker::PhantomData;
|
|||
use halo2::{
|
||||
arithmetic::FieldExt,
|
||||
circuit::{Cell, Chip, Layouter, Region, SimpleFloorPlanner},
|
||||
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector},
|
||||
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector},
|
||||
poly::Rotation,
|
||||
};
|
||||
|
||||
|
@ -17,6 +17,9 @@ trait NumericInstructions<F: FieldExt>: Chip<F> {
|
|||
/// Loads a number into the circuit as a private input.
|
||||
fn load_private(&self, layouter: impl Layouter<F>, a: Option<F>) -> Result<Self::Num, Error>;
|
||||
|
||||
/// Loads a number into the circuit as a fixed constant.
|
||||
fn load_constant(&self, layouter: impl Layouter<F>, constant: F) -> Result<Self::Num, Error>;
|
||||
|
||||
/// Returns `c = a * b`.
|
||||
fn mul(
|
||||
&self,
|
||||
|
@ -62,6 +65,9 @@ struct FieldConfig {
|
|||
// This is important when building larger circuits, where columns are used by
|
||||
// multiple sets of instructions.
|
||||
s_mul: Selector,
|
||||
|
||||
/// The fixed column used to load constants.
|
||||
constant: Column<Fixed>,
|
||||
}
|
||||
|
||||
impl<F: FieldExt> FieldChip<F> {
|
||||
|
@ -76,8 +82,10 @@ impl<F: FieldExt> FieldChip<F> {
|
|||
meta: &mut ConstraintSystem<F>,
|
||||
advice: [Column<Advice>; 2],
|
||||
instance: Column<Instance>,
|
||||
constant: Column<Fixed>,
|
||||
) -> <Self as Chip<F>>::Config {
|
||||
meta.enable_equality(instance.into());
|
||||
meta.enable_constant(constant);
|
||||
for column in &advice {
|
||||
meta.enable_equality((*column).into());
|
||||
}
|
||||
|
@ -117,6 +125,7 @@ impl<F: FieldExt> FieldChip<F> {
|
|||
advice,
|
||||
instance,
|
||||
s_mul,
|
||||
constant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -172,6 +181,33 @@ impl<F: FieldExt> NumericInstructions<F> for FieldChip<F> {
|
|||
Ok(num.unwrap())
|
||||
}
|
||||
|
||||
fn load_constant(
|
||||
&self,
|
||||
mut layouter: impl Layouter<F>,
|
||||
constant: F,
|
||||
) -> Result<Self::Num, Error> {
|
||||
let config = self.config();
|
||||
|
||||
let mut num = None;
|
||||
layouter.assign_region(
|
||||
|| "load constant",
|
||||
|mut region| {
|
||||
let cell = region.assign_advice_from_constant(
|
||||
|| "constant value",
|
||||
config.advice[0],
|
||||
0,
|
||||
constant,
|
||||
)?;
|
||||
num = Some(Number {
|
||||
cell,
|
||||
value: Some(constant),
|
||||
});
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
Ok(num.unwrap())
|
||||
}
|
||||
|
||||
fn mul(
|
||||
&self,
|
||||
mut layouter: impl Layouter<F>,
|
||||
|
@ -248,6 +284,7 @@ impl<F: FieldExt> NumericInstructions<F> for FieldChip<F> {
|
|||
/// were `None` we would get an error.
|
||||
#[derive(Default)]
|
||||
struct MyCircuit<F: FieldExt> {
|
||||
constant: F,
|
||||
a: Option<F>,
|
||||
b: Option<F>,
|
||||
}
|
||||
|
@ -268,7 +305,10 @@ impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
|
|||
// We also need an instance column to store public inputs.
|
||||
let instance = meta.instance_column();
|
||||
|
||||
FieldChip::configure(meta, advice, instance)
|
||||
// Create a fixed column to load constants.
|
||||
let constant = meta.fixed_column();
|
||||
|
||||
FieldChip::configure(meta, advice, instance, constant)
|
||||
}
|
||||
|
||||
fn synthesize(
|
||||
|
@ -282,17 +322,24 @@ impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
|
|||
let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?;
|
||||
let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?;
|
||||
|
||||
// Load the constant factor into the circuit.
|
||||
let constant =
|
||||
field_chip.load_constant(layouter.namespace(|| "load constant"), self.constant)?;
|
||||
|
||||
// We only have access to plain multiplication.
|
||||
// We could implement our circuit as:
|
||||
// asq = a*a
|
||||
// bsq = b*b
|
||||
// c = asq*bsq
|
||||
// absq = asq*bsq
|
||||
// c = constant*asq*bsq
|
||||
//
|
||||
// but it's more efficient to implement it as:
|
||||
// ab = a*b
|
||||
// c = ab^2
|
||||
// absq = ab^2
|
||||
// c = constant*absq
|
||||
let ab = field_chip.mul(layouter.namespace(|| "a * b"), a, b)?;
|
||||
let c = field_chip.mul(layouter.namespace(|| "ab * ab"), ab.clone(), ab)?;
|
||||
let absq = field_chip.mul(layouter.namespace(|| "ab * ab"), ab.clone(), ab)?;
|
||||
let c = field_chip.mul(layouter.namespace(|| "constant * absq"), constant, absq)?;
|
||||
|
||||
// Expose the result as a public input to the circuit.
|
||||
field_chip.expose_public(layouter.namespace(|| "expose c"), c, 0)
|
||||
|
@ -309,12 +356,14 @@ fn main() {
|
|||
let k = 4;
|
||||
|
||||
// Prepare the private and public inputs to the circuit!
|
||||
let constant = Fp::from(7);
|
||||
let a = Fp::from(2);
|
||||
let b = Fp::from(3);
|
||||
let c = a.square() * b.square();
|
||||
let c = constant * a.square() * b.square();
|
||||
|
||||
// Instantiate the circuit with the private inputs.
|
||||
let circuit = MyCircuit {
|
||||
constant,
|
||||
a: Some(a),
|
||||
b: Some(b),
|
||||
};
|
||||
|
|
|
@ -152,6 +152,32 @@ impl<'r, F: Field> Region<'r, F> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Assigns a constant value to the column `advice` at `offset` within this region.
|
||||
///
|
||||
/// The constant value will be assigned to a cell within one of the fixed columns
|
||||
/// configured via `ConstraintSystem::enable_constant`.
|
||||
///
|
||||
/// Returns the advice cell.
|
||||
pub fn assign_advice_from_constant<VR, A, AR>(
|
||||
&mut self,
|
||||
annotation: A,
|
||||
column: Column<Advice>,
|
||||
offset: usize,
|
||||
constant: VR,
|
||||
) -> Result<Cell, Error>
|
||||
where
|
||||
VR: Into<Assigned<F>>,
|
||||
A: Fn() -> AR,
|
||||
AR: Into<String>,
|
||||
{
|
||||
self.region.assign_advice_from_constant(
|
||||
&|| annotation().into(),
|
||||
column,
|
||||
offset,
|
||||
constant.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Assign the value of the instance column's cell at absolute location
|
||||
/// `row` to the column `advice` at `offset` within this region.
|
||||
///
|
||||
|
@ -199,6 +225,16 @@ impl<'r, F: Field> Region<'r, F> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Constrains a cell to have a constant value.
|
||||
///
|
||||
/// Returns an error if the cell is in a column where equality has not been enabled.
|
||||
pub fn constrain_constant<VR>(&mut self, cell: Cell, constant: VR) -> Result<(), Error>
|
||||
where
|
||||
VR: Into<Assigned<F>>,
|
||||
{
|
||||
self.region.constrain_constant(cell, constant.into())
|
||||
}
|
||||
|
||||
/// Constrains two cells to have the same value.
|
||||
///
|
||||
/// Returns an error if either of the cells are in columns where equality
|
||||
|
|
|
@ -29,8 +29,9 @@ impl FloorPlanner for SimpleFloorPlanner {
|
|||
cs: &mut CS,
|
||||
circuit: &C,
|
||||
config: C::Config,
|
||||
constants: Vec<Column<Fixed>>,
|
||||
) -> Result<(), Error> {
|
||||
let layouter = SingleChipLayouter::new(cs)?;
|
||||
let layouter = SingleChipLayouter::new(cs, constants)?;
|
||||
circuit.synthesize(config, layouter)
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +39,7 @@ impl FloorPlanner for SimpleFloorPlanner {
|
|||
/// A [`Layouter`] for a single-chip circuit.
|
||||
pub struct SingleChipLayouter<'a, F: Field, CS: Assignment<F> + 'a> {
|
||||
cs: &'a mut CS,
|
||||
constants: Vec<Column<Fixed>>,
|
||||
/// Stores the starting row for each region.
|
||||
regions: Vec<RegionStart>,
|
||||
/// Stores the first empty row for each column.
|
||||
|
@ -56,9 +58,10 @@ impl<'a, F: Field, CS: Assignment<F> + 'a> fmt::Debug for SingleChipLayouter<'a,
|
|||
|
||||
impl<'a, F: Field, CS: Assignment<F>> SingleChipLayouter<'a, F, CS> {
|
||||
/// Creates a new single-chip layouter.
|
||||
pub fn new(cs: &'a mut CS) -> Result<Self, Error> {
|
||||
pub fn new(cs: &'a mut CS, constants: Vec<Column<Fixed>>) -> Result<Self, Error> {
|
||||
let ret = SingleChipLayouter {
|
||||
cs,
|
||||
constants,
|
||||
regions: vec![],
|
||||
columns: HashMap::default(),
|
||||
_marker: PhantomData,
|
||||
|
@ -143,6 +146,8 @@ impl<'a, F: Field, CS: Assignment<F> + 'a> Layouter<F> for SingleChipLayouter<'a
|
|||
struct SingleChipLayouterRegion<'r, 'a, F: Field, CS: Assignment<F> + 'a> {
|
||||
layouter: &'r mut SingleChipLayouter<'a, F, CS>,
|
||||
region_index: RegionIndex,
|
||||
// The index of the next available row in the first constants column.
|
||||
next_constant_row: usize,
|
||||
}
|
||||
|
||||
impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> fmt::Debug
|
||||
|
@ -161,6 +166,7 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> SingleChipLayouterRegion<'r, 'a,
|
|||
SingleChipLayouterRegion {
|
||||
layouter,
|
||||
region_index,
|
||||
next_constant_row: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,6 +208,19 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
|
|||
})
|
||||
}
|
||||
|
||||
fn assign_advice_from_constant<'v>(
|
||||
&'v mut self,
|
||||
annotation: &'v (dyn Fn() -> String + 'v),
|
||||
column: Column<Advice>,
|
||||
offset: usize,
|
||||
constant: Assigned<F>,
|
||||
) -> Result<Cell, Error> {
|
||||
let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?;
|
||||
self.constrain_constant(advice, constant)?;
|
||||
|
||||
Ok(advice)
|
||||
}
|
||||
|
||||
fn assign_advice_from_instance<'v>(
|
||||
&mut self,
|
||||
annotation: &'v (dyn Fn() -> String + 'v),
|
||||
|
@ -233,12 +252,17 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
|
|||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
self.layouter.cs.assign_fixed(
|
||||
annotation,
|
||||
column,
|
||||
*self.layouter.regions[*self.region_index] + offset,
|
||||
to,
|
||||
)?;
|
||||
let row = *self.layouter.regions[*self.region_index] + offset;
|
||||
|
||||
self.layouter.cs.assign_fixed(annotation, column, row, to)?;
|
||||
|
||||
if let Some(c) = self.layouter.constants.first() {
|
||||
if c == &column {
|
||||
// Ensure that the next row we will assign a constant to is always after any
|
||||
// prior assignments (for simplicity).
|
||||
self.next_constant_row = cmp::max(self.next_constant_row, row + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Cell {
|
||||
region_index: self.region_index,
|
||||
|
@ -247,6 +271,29 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
|
|||
})
|
||||
}
|
||||
|
||||
fn constrain_constant(&mut self, cell: Cell, constant: Assigned<F>) -> Result<(), Error> {
|
||||
if self.layouter.constants.is_empty() {
|
||||
return Err(Error::NotEnoughColumnsForConstants);
|
||||
}
|
||||
|
||||
// For the simple layouter, just assign the fixed constants inside the region
|
||||
// using the first constants column. self.next_constant_row is updated by this
|
||||
// function call.
|
||||
let fixed = self.assign_fixed(
|
||||
&|| "constant".into(),
|
||||
self.layouter.constants[0],
|
||||
// Convert the absolute row into a relative offset within this region, but
|
||||
// always at an offset that is not before this region (taking advantage of the
|
||||
// fact that for this single-pass layouter, regions are always laid out in
|
||||
// increasing row order).
|
||||
self.next_constant_row
|
||||
.saturating_sub(*self.layouter.regions[*self.region_index]),
|
||||
&mut || Ok(constant),
|
||||
)?;
|
||||
|
||||
self.constrain_equal(cell, fixed)
|
||||
}
|
||||
|
||||
fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> {
|
||||
self.layouter.cs.copy(
|
||||
left.column,
|
||||
|
@ -258,3 +305,58 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pasta_curves::vesta;
|
||||
|
||||
use super::SimpleFloorPlanner;
|
||||
use crate::{
|
||||
dev::MockProver,
|
||||
plonk::{Advice, Circuit, Column, Error},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn not_enough_columns_for_constants() {
|
||||
struct MyCircuit {}
|
||||
|
||||
impl Circuit<vesta::Scalar> for MyCircuit {
|
||||
type Config = Column<Advice>;
|
||||
type FloorPlanner = SimpleFloorPlanner;
|
||||
|
||||
fn without_witnesses(&self) -> Self {
|
||||
MyCircuit {}
|
||||
}
|
||||
|
||||
fn configure(meta: &mut crate::plonk::ConstraintSystem<vesta::Scalar>) -> Self::Config {
|
||||
meta.advice_column()
|
||||
}
|
||||
|
||||
fn synthesize(
|
||||
&self,
|
||||
config: Self::Config,
|
||||
mut layouter: impl crate::circuit::Layouter<vesta::Scalar>,
|
||||
) -> Result<(), crate::plonk::Error> {
|
||||
layouter.assign_region(
|
||||
|| "assign constant",
|
||||
|mut region| {
|
||||
region.assign_advice_from_constant(
|
||||
|| "one",
|
||||
config,
|
||||
0,
|
||||
vesta::Scalar::one(),
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let circuit = MyCircuit {};
|
||||
assert_eq!(
|
||||
MockProver::run(3, &circuit, vec![]).unwrap_err(),
|
||||
Error::NotEnoughColumnsForConstants,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use ff::Field;
|
||||
|
||||
|
@ -31,7 +30,8 @@ struct V1Plan<'a, F: Field, CS: Assignment<F> + 'a> {
|
|||
cs: &'a mut CS,
|
||||
/// Stores the starting row for each region.
|
||||
regions: Vec<RegionStart>,
|
||||
_marker: PhantomData<F>,
|
||||
/// Stores the constants to be assigned, and the cells to which they are copied.
|
||||
constants: Vec<(Assigned<F>, Cell)>,
|
||||
}
|
||||
|
||||
impl<'a, F: Field, CS: Assignment<F> + 'a> fmt::Debug for V1Plan<'a, F, CS> {
|
||||
|
@ -46,7 +46,7 @@ impl<'a, F: Field, CS: Assignment<F>> V1Plan<'a, F, CS> {
|
|||
let ret = V1Plan {
|
||||
cs,
|
||||
regions: vec![],
|
||||
_marker: PhantomData,
|
||||
constants: vec![],
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ impl FloorPlanner for V1 {
|
|||
cs: &mut CS,
|
||||
circuit: &C,
|
||||
config: C::Config,
|
||||
constants: Vec<Column<Fixed>>,
|
||||
) -> Result<(), Error> {
|
||||
let mut plan = V1Plan::new(cs)?;
|
||||
|
||||
|
@ -69,15 +70,68 @@ impl FloorPlanner for V1 {
|
|||
.synthesize(config.clone(), V1Pass::<_, CS>::measure(pass))?;
|
||||
}
|
||||
|
||||
plan.regions = strategy::slot_in_biggest_advice_first(measure.regions);
|
||||
// Planning:
|
||||
// - Position the regions.
|
||||
let (regions, column_allocations) = strategy::slot_in_biggest_advice_first(measure.regions);
|
||||
plan.regions = regions;
|
||||
|
||||
// Second pass: assign the regions.
|
||||
// - Determine how many rows our planned circuit will require.
|
||||
let first_unassigned_row = column_allocations
|
||||
.iter()
|
||||
.map(|(_, a)| a.unbounded_interval_start())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
// - Position the constants within those rows.
|
||||
let fixed_allocations: Vec<_> = constants
|
||||
.into_iter()
|
||||
.map(|c| {
|
||||
(
|
||||
c,
|
||||
column_allocations
|
||||
.get(&c.into())
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let constant_positions = || {
|
||||
fixed_allocations.iter().flat_map(|(c, a)| {
|
||||
let c = *c;
|
||||
a.free_intervals(0, Some(first_unassigned_row))
|
||||
.flat_map(move |e| e.range().unwrap().map(move |i| (c, i)))
|
||||
})
|
||||
};
|
||||
|
||||
// Second pass:
|
||||
// - Assign the regions.
|
||||
let mut assign = AssignmentPass::new(&mut plan);
|
||||
{
|
||||
let pass = &mut assign;
|
||||
circuit.synthesize(config, V1Pass::assign(pass))?;
|
||||
}
|
||||
|
||||
// - Assign the constants.
|
||||
if constant_positions().count() < plan.constants.len() {
|
||||
return Err(Error::NotEnoughColumnsForConstants);
|
||||
}
|
||||
for ((fixed_column, fixed_row), (value, advice)) in
|
||||
constant_positions().zip(plan.constants.into_iter())
|
||||
{
|
||||
plan.cs.assign_fixed(
|
||||
|| format!("Constant({:?})", value.evaluate()),
|
||||
fixed_column,
|
||||
fixed_row,
|
||||
|| Ok(value),
|
||||
)?;
|
||||
plan.cs.copy(
|
||||
fixed_column.into(),
|
||||
fixed_row,
|
||||
advice.column,
|
||||
*plan.regions[*advice.region_index] + advice.row_offset,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -286,6 +340,19 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F> for V1Region<'r
|
|||
})
|
||||
}
|
||||
|
||||
fn assign_advice_from_constant<'v>(
|
||||
&'v mut self,
|
||||
annotation: &'v (dyn Fn() -> String + 'v),
|
||||
column: Column<Advice>,
|
||||
offset: usize,
|
||||
constant: Assigned<F>,
|
||||
) -> Result<Cell, Error> {
|
||||
let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?;
|
||||
self.constrain_constant(advice, constant)?;
|
||||
|
||||
Ok(advice)
|
||||
}
|
||||
|
||||
fn assign_advice_from_instance<'v>(
|
||||
&mut self,
|
||||
annotation: &'v (dyn Fn() -> String + 'v),
|
||||
|
@ -331,6 +398,11 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F> for V1Region<'r
|
|||
})
|
||||
}
|
||||
|
||||
fn constrain_constant(&mut self, cell: Cell, constant: Assigned<F>) -> Result<(), Error> {
|
||||
self.plan.constants.push((constant, cell));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> {
|
||||
self.plan.cs.copy(
|
||||
left.column,
|
||||
|
@ -342,3 +414,57 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F> for V1Region<'r
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pasta_curves::vesta;
|
||||
|
||||
use crate::{
|
||||
dev::MockProver,
|
||||
plonk::{Advice, Circuit, Column, Error},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn not_enough_columns_for_constants() {
|
||||
struct MyCircuit {}
|
||||
|
||||
impl Circuit<vesta::Scalar> for MyCircuit {
|
||||
type Config = Column<Advice>;
|
||||
type FloorPlanner = super::V1;
|
||||
|
||||
fn without_witnesses(&self) -> Self {
|
||||
MyCircuit {}
|
||||
}
|
||||
|
||||
fn configure(meta: &mut crate::plonk::ConstraintSystem<vesta::Scalar>) -> Self::Config {
|
||||
meta.advice_column()
|
||||
}
|
||||
|
||||
fn synthesize(
|
||||
&self,
|
||||
config: Self::Config,
|
||||
mut layouter: impl crate::circuit::Layouter<vesta::Scalar>,
|
||||
) -> Result<(), crate::plonk::Error> {
|
||||
layouter.assign_region(
|
||||
|| "assign constant",
|
||||
|mut region| {
|
||||
region.assign_advice_from_constant(
|
||||
|| "one",
|
||||
config,
|
||||
0,
|
||||
vesta::Scalar::one(),
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let circuit = MyCircuit {};
|
||||
assert_eq!(
|
||||
MockProver::run(3, &circuit, vec![]).unwrap_err(),
|
||||
Error::NotEnoughColumnsForConstants,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
cmp,
|
||||
collections::{BTreeSet, HashMap},
|
||||
ops::Range,
|
||||
};
|
||||
|
||||
use super::RegionShape;
|
||||
|
@ -31,24 +32,39 @@ impl PartialOrd for AllocatedRegion {
|
|||
}
|
||||
|
||||
/// An area of empty space within a column.
|
||||
struct EmptySpace {
|
||||
// The starting position of the empty space.
|
||||
pub(crate) struct EmptySpace {
|
||||
// The starting position (inclusive) of the empty space.
|
||||
start: usize,
|
||||
// The number of rows of empty space, or `None` if unbounded.
|
||||
// The ending position (exclusive) of the empty space, or `None` if unbounded.
|
||||
end: Option<usize>,
|
||||
}
|
||||
|
||||
impl EmptySpace {
|
||||
pub(crate) fn range(&self) -> Option<Range<usize>> {
|
||||
self.end.map(|end| self.start..end)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocated rows within a column.
|
||||
///
|
||||
/// This is a set of [a_start, a_end) pairs representing disjoint allocated intervals.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
struct Allocations(BTreeSet<AllocatedRegion>);
|
||||
pub struct Allocations(BTreeSet<AllocatedRegion>);
|
||||
|
||||
impl Allocations {
|
||||
/// Returns the row that forms the unbounded unallocated interval [row, None).
|
||||
pub(crate) fn unbounded_interval_start(&self) -> usize {
|
||||
self.0
|
||||
.iter()
|
||||
.last()
|
||||
.map(|r| r.start + r.length)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Return all the *unallocated* nonempty intervals intersecting [start, end).
|
||||
///
|
||||
/// `end = None` represents an unbounded end.
|
||||
fn free_intervals(
|
||||
pub(crate) fn free_intervals(
|
||||
&self,
|
||||
start: usize,
|
||||
end: Option<usize>,
|
||||
|
@ -85,11 +101,14 @@ impl Allocations {
|
|||
}
|
||||
}
|
||||
|
||||
/// Allocated rows within a circuit.
|
||||
pub type CircuitAllocations = HashMap<Column<Any>, Allocations>;
|
||||
|
||||
/// - `start` is the current start row of the region (not of this column).
|
||||
/// - `slack` is the maximum number of rows the start could be moved down, taking into
|
||||
/// account prior columns.
|
||||
fn first_fit_region(
|
||||
column_allocations: &mut HashMap<Column<Any>, Allocations>,
|
||||
column_allocations: &mut CircuitAllocations,
|
||||
region_columns: &[Column<Any>],
|
||||
region_length: usize,
|
||||
start: usize,
|
||||
|
@ -146,11 +165,13 @@ fn first_fit_region(
|
|||
|
||||
/// Positions the regions starting at the earliest row for which none of the columns are
|
||||
/// in use, taking into account gaps between earlier regions.
|
||||
fn slot_in(region_shapes: Vec<RegionShape>) -> Vec<(RegionStart, RegionShape)> {
|
||||
fn slot_in(
|
||||
region_shapes: Vec<RegionShape>,
|
||||
) -> (Vec<(RegionStart, RegionShape)>, CircuitAllocations) {
|
||||
// Tracks the empty regions for each column.
|
||||
let mut column_allocations: HashMap<Column<Any>, Allocations> = Default::default();
|
||||
let mut column_allocations: CircuitAllocations = Default::default();
|
||||
|
||||
region_shapes
|
||||
let regions = region_shapes
|
||||
.into_iter()
|
||||
.map(|region| {
|
||||
// Sort the region's columns to ensure determinism.
|
||||
|
@ -170,11 +191,16 @@ fn slot_in(region_shapes: Vec<RegionShape>) -> Vec<(RegionStart, RegionShape)> {
|
|||
|
||||
(region_start.into(), region)
|
||||
})
|
||||
.collect()
|
||||
.collect();
|
||||
|
||||
// Return the column allocations for potential further processing.
|
||||
(regions, column_allocations)
|
||||
}
|
||||
|
||||
/// Sorts the regions by advice area and then lays them out with the [`slot_in`] strategy.
|
||||
pub fn slot_in_biggest_advice_first(region_shapes: Vec<RegionShape>) -> Vec<RegionStart> {
|
||||
pub fn slot_in_biggest_advice_first(
|
||||
region_shapes: Vec<RegionShape>,
|
||||
) -> (Vec<RegionStart>, CircuitAllocations) {
|
||||
let mut sorted_regions: Vec<_> = region_shapes.into_iter().collect();
|
||||
sorted_regions.sort_unstable_by_key(|shape| {
|
||||
// Count the number of advice columns
|
||||
|
@ -189,11 +215,13 @@ pub fn slot_in_biggest_advice_first(region_shapes: Vec<RegionShape>) -> Vec<Regi
|
|||
sorted_regions.reverse();
|
||||
|
||||
// Lay out the sorted regions.
|
||||
let mut regions = slot_in(sorted_regions);
|
||||
let (mut regions, column_allocations) = slot_in(sorted_regions);
|
||||
|
||||
// Un-sort the regions so they match the original indexing.
|
||||
regions.sort_unstable_by_key(|(_, region)| region.region_index().0);
|
||||
regions.into_iter().map(|(start, _)| start).collect()
|
||||
let regions = regions.into_iter().map(|(start, _)| start).collect();
|
||||
|
||||
(regions, column_allocations)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -221,6 +249,7 @@ fn test_slot_in() {
|
|||
];
|
||||
assert_eq!(
|
||||
slot_in(regions)
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|(i, _)| i)
|
||||
.collect::<Vec<_>>(),
|
||||
|
|
|
@ -58,6 +58,20 @@ pub trait RegionLayouter<F: Field>: fmt::Debug {
|
|||
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
|
||||
) -> Result<Cell, Error>;
|
||||
|
||||
/// Assigns a constant value to the column `advice` at `offset` within this region.
|
||||
///
|
||||
/// The constant value will be assigned to a cell within one of the fixed columns
|
||||
/// configured via `ConstraintSystem::enable_constant`.
|
||||
///
|
||||
/// Returns the advice cell that has been equality-constrained to the constant.
|
||||
fn assign_advice_from_constant<'v>(
|
||||
&'v mut self,
|
||||
annotation: &'v (dyn Fn() -> String + 'v),
|
||||
column: Column<Advice>,
|
||||
offset: usize,
|
||||
constant: Assigned<F>,
|
||||
) -> Result<Cell, Error>;
|
||||
|
||||
/// Assign the value of the instance column's cell at absolute location
|
||||
/// `row` to the column `advice` at `offset` within this region.
|
||||
///
|
||||
|
@ -80,6 +94,11 @@ pub trait RegionLayouter<F: Field>: fmt::Debug {
|
|||
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
|
||||
) -> Result<Cell, Error>;
|
||||
|
||||
/// Constrains a cell to have a constant value.
|
||||
///
|
||||
/// Returns an error if the cell is in a column where equality has not been enabled.
|
||||
fn constrain_constant(&mut self, cell: Cell, constant: Assigned<F>) -> Result<(), Error>;
|
||||
|
||||
/// Constraint two cells to have the same value.
|
||||
///
|
||||
/// Returns an error if either of the cells is not within the given permutation.
|
||||
|
@ -152,6 +171,17 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
|
|||
})
|
||||
}
|
||||
|
||||
fn assign_advice_from_constant<'v>(
|
||||
&'v mut self,
|
||||
annotation: &'v (dyn Fn() -> String + 'v),
|
||||
column: Column<Advice>,
|
||||
offset: usize,
|
||||
constant: Assigned<F>,
|
||||
) -> Result<Cell, Error> {
|
||||
// The rest is identical to witnessing an advice cell.
|
||||
self.assign_advice(annotation, column, offset, &mut || Ok(constant))
|
||||
}
|
||||
|
||||
fn assign_advice_from_instance<'v>(
|
||||
&mut self,
|
||||
_: &'v (dyn Fn() -> String + 'v),
|
||||
|
@ -190,6 +220,11 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
|
|||
})
|
||||
}
|
||||
|
||||
fn constrain_constant(&mut self, _cell: Cell, _constant: Assigned<F>) -> Result<(), Error> {
|
||||
// Global constants don't affect the region shape.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn constrain_equal(&mut self, _left: Cell, _right: Cell) -> Result<(), Error> {
|
||||
// Equality constraints don't affect the region shape.
|
||||
Ok(())
|
||||
|
|
|
@ -530,6 +530,7 @@ impl<F: FieldExt> MockProver<F> {
|
|||
cs.num_advice_columns
|
||||
];
|
||||
let permutation = permutation::keygen::Assembly::new(n, &cs.permutation);
|
||||
let constants = cs.constants.clone();
|
||||
|
||||
let mut prover = MockProver {
|
||||
n: n as u32,
|
||||
|
@ -543,7 +544,7 @@ impl<F: FieldExt> MockProver<F> {
|
|||
usable_rows: 0..usable_rows,
|
||||
};
|
||||
|
||||
ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config)?;
|
||||
ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config, constants)?;
|
||||
|
||||
Ok(prover)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ pub fn circuit_dot_graph<F: Field, ConcreteCircuit: Circuit<F>>(
|
|||
let mut cs = ConstraintSystem::default();
|
||||
let config = ConcreteCircuit::configure(&mut cs);
|
||||
let mut graph = Graph::default();
|
||||
ConcreteCircuit::FloorPlanner::synthesize(&mut graph, circuit, config).unwrap();
|
||||
ConcreteCircuit::FloorPlanner::synthesize(&mut graph, circuit, config, cs.constants).unwrap();
|
||||
|
||||
// Construct the node labels. We need to store these, because tabbycat operates on
|
||||
// string references, and we need those references to live long enough.
|
||||
|
|
|
@ -93,7 +93,13 @@ impl CircuitLayout {
|
|||
let mut cs = ConstraintSystem::default();
|
||||
let config = ConcreteCircuit::configure(&mut cs);
|
||||
let mut layout = Layout::default();
|
||||
ConcreteCircuit::FloorPlanner::synthesize(&mut layout, circuit, config).unwrap();
|
||||
ConcreteCircuit::FloorPlanner::synthesize(
|
||||
&mut layout,
|
||||
circuit,
|
||||
config,
|
||||
cs.constants.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Figure out what order to render the columns in.
|
||||
// TODO: For now, just render them in the order they were configured.
|
||||
|
|
|
@ -134,7 +134,7 @@ pub struct ProvingKey<C: CurveAffine> {
|
|||
|
||||
/// This is an error that could occur during proving or circuit synthesis.
|
||||
// TODO: these errors need to be cleaned up
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// This is an error that can occur during synthesis of the circuit, for
|
||||
/// example, when the witness is not present.
|
||||
|
@ -154,6 +154,9 @@ pub enum Error {
|
|||
NotEnoughRowsAvailable,
|
||||
/// Instance provided exceeds number of available rows
|
||||
InstanceTooLarge,
|
||||
/// Circuit synthesis requires global constants, but circuit configuration did not
|
||||
/// call [`ConstraintSystem::enable_constant`] on fixed columns with sufficient space.
|
||||
NotEnoughColumnsForConstants,
|
||||
}
|
||||
|
||||
impl<C: CurveAffine> ProvingKey<C> {
|
||||
|
|
|
@ -495,6 +495,9 @@ pub trait Assignment<F: Field> {
|
|||
pub trait FloorPlanner {
|
||||
/// Given the provided `cs`, synthesize the given circuit.
|
||||
///
|
||||
/// `constants` is the list of fixed columns that the layouter may use to assign
|
||||
/// global constant values. These columns will all have been equality-enabled.
|
||||
///
|
||||
/// Internally, a floor planner will perform the following operations:
|
||||
/// - Instantiate a [`Layouter`] for this floor planner.
|
||||
/// - Perform any necessary setup or measurement tasks, which may involve one or more
|
||||
|
@ -504,6 +507,7 @@ pub trait FloorPlanner {
|
|||
cs: &mut CS,
|
||||
circuit: &C,
|
||||
config: C::Config,
|
||||
constants: Vec<Column<Fixed>>,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
|
@ -817,6 +821,10 @@ pub struct ConstraintSystem<F: Field> {
|
|||
// input expressions and a sequence of table expressions involved in the lookup.
|
||||
pub(crate) lookups: Vec<lookup::Argument<F>>,
|
||||
|
||||
// Vector of fixed columns, which can be used to store constant values
|
||||
// that are copied into advice columns.
|
||||
pub(crate) constants: Vec<Column<Fixed>>,
|
||||
|
||||
pub(crate) minimum_degree: Option<usize>,
|
||||
}
|
||||
|
||||
|
@ -832,6 +840,7 @@ pub struct PinnedConstraintSystem<'a, F: Field> {
|
|||
fixed_queries: &'a Vec<(Column<Fixed>, Rotation)>,
|
||||
permutation: &'a permutation::Argument,
|
||||
lookups: &'a Vec<lookup::Argument<F>>,
|
||||
constants: &'a Vec<Column<Fixed>>,
|
||||
minimum_degree: &'a Option<usize>,
|
||||
}
|
||||
|
||||
|
@ -858,6 +867,7 @@ impl<F: Field> Default for ConstraintSystem<F> {
|
|||
instance_queries: Vec::new(),
|
||||
permutation: permutation::Argument::new(),
|
||||
lookups: Vec::new(),
|
||||
constants: vec![],
|
||||
minimum_degree: None,
|
||||
}
|
||||
}
|
||||
|
@ -878,10 +888,23 @@ impl<F: Field> ConstraintSystem<F> {
|
|||
instance_queries: &self.instance_queries,
|
||||
permutation: &self.permutation,
|
||||
lookups: &self.lookups,
|
||||
constants: &self.constants,
|
||||
minimum_degree: &self.minimum_degree,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables this fixed column to be used for global constant assignments.
|
||||
///
|
||||
/// # Side-effects
|
||||
///
|
||||
/// The column will be equality-enabled.
|
||||
pub fn enable_constant(&mut self, column: Column<Fixed>) {
|
||||
if !self.constants.contains(&column) {
|
||||
self.constants.push(column);
|
||||
self.enable_equality(column.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable the ability to enforce equality over cells in this column
|
||||
pub fn enable_equality(&mut self, column: Column<Any>) {
|
||||
self.query_any_index(column, Rotation::cur());
|
||||
|
|
|
@ -186,7 +186,12 @@ where
|
|||
};
|
||||
|
||||
// Synthesize the circuit to obtain URS
|
||||
ConcreteCircuit::FloorPlanner::synthesize(&mut assembly, circuit, config)?;
|
||||
ConcreteCircuit::FloorPlanner::synthesize(
|
||||
&mut assembly,
|
||||
circuit,
|
||||
config,
|
||||
cs.constants.clone(),
|
||||
)?;
|
||||
|
||||
let fixed = batch_invert_assigned(assembly.fixed);
|
||||
|
||||
|
@ -234,7 +239,12 @@ where
|
|||
};
|
||||
|
||||
// Synthesize the circuit to obtain URS
|
||||
ConcreteCircuit::FloorPlanner::synthesize(&mut assembly, circuit, config)?;
|
||||
ConcreteCircuit::FloorPlanner::synthesize(
|
||||
&mut assembly,
|
||||
circuit,
|
||||
config,
|
||||
cs.constants.clone(),
|
||||
)?;
|
||||
|
||||
let fixed = batch_invert_assigned(assembly.fixed);
|
||||
|
||||
|
|
|
@ -261,7 +261,12 @@ pub fn create_proof<
|
|||
};
|
||||
|
||||
// Synthesize the circuit to obtain the witness and other information.
|
||||
ConcreteCircuit::FloorPlanner::synthesize(&mut witness, circuit, config.clone())?;
|
||||
ConcreteCircuit::FloorPlanner::synthesize(
|
||||
&mut witness,
|
||||
circuit,
|
||||
config.clone(),
|
||||
meta.constants.clone(),
|
||||
)?;
|
||||
|
||||
let mut advice = batch_invert_assigned(witness.advice);
|
||||
|
||||
|
|
|
@ -924,6 +924,7 @@ fn plonk_api() {
|
|||
],
|
||||
},
|
||||
],
|
||||
constants: [],
|
||||
minimum_degree: None,
|
||||
},
|
||||
fixed_commitments: [
|
||||
|
|
Loading…
Reference in New Issue