Merge pull request #342 from zcash/335-assign-advice-from-constant

Add `Region::assign_advice_from_constant` API
This commit is contained in:
str4d 2021-07-21 16:44:33 +01:00 committed by GitHub
commit 2b1baaf693
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 468 additions and 42 deletions

View File

@ -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
// asq = a*a
// bsq = b*b
// absq = asq*bsq
// c = constant*asq*bsq
//
// but it's more efficient to implement it as:
// ab = a*b
// c = ab^2
// ab = a*b
// 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),
};

View File

@ -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

View File

@ -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,
);
}
}

View File

@ -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,
);
}
}

View File

@ -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<_>>(),

View File

@ -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(())

View File

@ -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)
}

View File

@ -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.

View File

@ -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.

View File

@ -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> {

View File

@ -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());

View File

@ -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);

View File

@ -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);

View File

@ -924,6 +924,7 @@ fn plonk_api() {
],
},
],
constants: [],
minimum_degree: None,
},
fixed_commitments: [