diff --git a/examples/simple-example.rs b/examples/simple-example.rs index 15e9463e..f001fdc2 100644 --- a/examples/simple-example.rs +++ b/examples/simple-example.rs @@ -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: Chip { /// Loads a number into the circuit as a private input. fn load_private(&self, layouter: impl Layouter, a: Option) -> Result; + /// Loads a number into the circuit as a fixed constant. + fn load_constant(&self, layouter: impl Layouter, constant: F) -> Result; + /// 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, } impl FieldChip { @@ -76,8 +82,10 @@ impl FieldChip { meta: &mut ConstraintSystem, advice: [Column; 2], instance: Column, + constant: Column, ) -> >::Config { meta.enable_equality(instance.into()); + meta.enable_constant(constant); for column in &advice { meta.enable_equality((*column).into()); } @@ -117,6 +125,7 @@ impl FieldChip { advice, instance, s_mul, + constant, } } } @@ -172,6 +181,33 @@ impl NumericInstructions for FieldChip { Ok(num.unwrap()) } + fn load_constant( + &self, + mut layouter: impl Layouter, + constant: F, + ) -> Result { + 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, @@ -248,6 +284,7 @@ impl NumericInstructions for FieldChip { /// were `None` we would get an error. #[derive(Default)] struct MyCircuit { + constant: F, a: Option, b: Option, } @@ -268,7 +305,10 @@ impl Circuit for MyCircuit { // 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 Circuit for MyCircuit { 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), }; diff --git a/src/circuit.rs b/src/circuit.rs index 6ad86b25..f78a61f7 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -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( + &mut self, + annotation: A, + column: Column, + offset: usize, + constant: VR, + ) -> Result + where + VR: Into>, + A: Fn() -> AR, + AR: Into, + { + 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(&mut self, cell: Cell, constant: VR) -> Result<(), Error> + where + VR: Into>, + { + 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 diff --git a/src/circuit/floor_planner/single_pass.rs b/src/circuit/floor_planner/single_pass.rs index 82aa8329..5b955290 100644 --- a/src/circuit/floor_planner/single_pass.rs +++ b/src/circuit/floor_planner/single_pass.rs @@ -29,8 +29,9 @@ impl FloorPlanner for SimpleFloorPlanner { cs: &mut CS, circuit: &C, config: C::Config, + constants: Vec>, ) -> 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 + 'a> { cs: &'a mut CS, + constants: Vec>, /// Stores the starting row for each region. regions: Vec, /// Stores the first empty row for each column. @@ -56,9 +58,10 @@ impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for SingleChipLayouter<'a, impl<'a, F: Field, CS: Assignment> SingleChipLayouter<'a, F, CS> { /// Creates a new single-chip layouter. - pub fn new(cs: &'a mut CS) -> Result { + pub fn new(cs: &'a mut CS, constants: Vec>) -> Result { let ret = SingleChipLayouter { cs, + constants, regions: vec![], columns: HashMap::default(), _marker: PhantomData, @@ -143,6 +146,8 @@ impl<'a, F: Field, CS: Assignment + 'a> Layouter for SingleChipLayouter<'a struct SingleChipLayouterRegion<'r, 'a, F: Field, CS: Assignment + '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 + 'a> fmt::Debug @@ -161,6 +166,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> SingleChipLayouterRegion<'r, 'a, SingleChipLayouterRegion { layouter, region_index, + next_constant_row: 0, } } } @@ -202,6 +208,19 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter }) } + fn assign_advice_from_constant<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + constant: Assigned, + ) -> Result { + 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 + 'a> RegionLayouter offset: usize, to: &'v mut (dyn FnMut() -> Result, Error> + 'v), ) -> Result { - 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 + 'a> RegionLayouter }) } + fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> 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 + 'a> RegionLayouter 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 for MyCircuit { + type Config = Column; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit {} + } + + fn configure(meta: &mut crate::plonk::ConstraintSystem) -> Self::Config { + meta.advice_column() + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl crate::circuit::Layouter, + ) -> 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, + ); + } +} diff --git a/src/circuit/floor_planner/v1.rs b/src/circuit/floor_planner/v1.rs index 75658c47..17adeedd 100644 --- a/src/circuit/floor_planner/v1.rs +++ b/src/circuit/floor_planner/v1.rs @@ -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 + 'a> { cs: &'a mut CS, /// Stores the starting row for each region. regions: Vec, - _marker: PhantomData, + /// Stores the constants to be assigned, and the cells to which they are copied. + constants: Vec<(Assigned, Cell)>, } impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for V1Plan<'a, F, CS> { @@ -46,7 +46,7 @@ impl<'a, F: Field, CS: Assignment> 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>, ) -> 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 + 'a> RegionLayouter for V1Region<'r }) } + fn assign_advice_from_constant<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + constant: Assigned, + ) -> Result { + 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 + 'a> RegionLayouter for V1Region<'r }) } + fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> 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 + 'a> RegionLayouter 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 for MyCircuit { + type Config = Column; + type FloorPlanner = super::V1; + + fn without_witnesses(&self) -> Self { + MyCircuit {} + } + + fn configure(meta: &mut crate::plonk::ConstraintSystem) -> Self::Config { + meta.advice_column() + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl crate::circuit::Layouter, + ) -> 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, + ); + } +} diff --git a/src/circuit/floor_planner/v1/strategy.rs b/src/circuit/floor_planner/v1/strategy.rs index 753e86e5..090aceec 100644 --- a/src/circuit/floor_planner/v1/strategy.rs +++ b/src/circuit/floor_planner/v1/strategy.rs @@ -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, } +impl EmptySpace { + pub(crate) fn range(&self) -> Option> { + 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); +pub struct Allocations(BTreeSet); 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, @@ -85,11 +101,14 @@ impl Allocations { } } +/// Allocated rows within a circuit. +pub type CircuitAllocations = HashMap, 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, Allocations>, + column_allocations: &mut CircuitAllocations, region_columns: &[Column], 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) -> Vec<(RegionStart, RegionShape)> { +fn slot_in( + region_shapes: Vec, +) -> (Vec<(RegionStart, RegionShape)>, CircuitAllocations) { // Tracks the empty regions for each column. - let mut column_allocations: HashMap, 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) -> 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) -> Vec { +pub fn slot_in_biggest_advice_first( + region_shapes: Vec, +) -> (Vec, 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) -> Vec>(), diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index 21b48854..10fa84d2 100644 --- a/src/circuit/layouter.rs +++ b/src/circuit/layouter.rs @@ -58,6 +58,20 @@ pub trait RegionLayouter: fmt::Debug { to: &'v mut (dyn FnMut() -> Result, Error> + 'v), ) -> Result; + /// 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, + offset: usize, + constant: Assigned, + ) -> Result; + /// 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: fmt::Debug { to: &'v mut (dyn FnMut() -> Result, Error> + 'v), ) -> Result; + /// 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) -> 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 RegionLayouter for RegionShape { }) } + fn assign_advice_from_constant<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + constant: Assigned, + ) -> Result { + // 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 RegionLayouter for RegionShape { }) } + fn constrain_constant(&mut self, _cell: Cell, _constant: Assigned) -> 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(()) diff --git a/src/dev.rs b/src/dev.rs index 0f7a4f83..98ac4a50 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -530,6 +530,7 @@ impl MockProver { 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 MockProver { usable_rows: 0..usable_rows, }; - ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config)?; + ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config, constants)?; Ok(prover) } diff --git a/src/dev/graph.rs b/src/dev/graph.rs index 8edb67ac..0bc69a5e 100644 --- a/src/dev/graph.rs +++ b/src/dev/graph.rs @@ -21,7 +21,7 @@ pub fn circuit_dot_graph>( 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. diff --git a/src/dev/graph/layout.rs b/src/dev/graph/layout.rs index 53136699..f63513b8 100644 --- a/src/dev/graph/layout.rs +++ b/src/dev/graph/layout.rs @@ -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. diff --git a/src/plonk.rs b/src/plonk.rs index af0c7a9b..830dce77 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -134,7 +134,7 @@ pub struct ProvingKey { /// 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 ProvingKey { diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 95d51174..a04c78a8 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -495,6 +495,9 @@ pub trait Assignment { 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>, ) -> Result<(), Error>; } @@ -817,6 +821,10 @@ pub struct ConstraintSystem { // input expressions and a sequence of table expressions involved in the lookup. pub(crate) lookups: Vec>, + // Vector of fixed columns, which can be used to store constant values + // that are copied into advice columns. + pub(crate) constants: Vec>, + pub(crate) minimum_degree: Option, } @@ -832,6 +840,7 @@ pub struct PinnedConstraintSystem<'a, F: Field> { fixed_queries: &'a Vec<(Column, Rotation)>, permutation: &'a permutation::Argument, lookups: &'a Vec>, + constants: &'a Vec>, minimum_degree: &'a Option, } @@ -858,6 +867,7 @@ impl Default for ConstraintSystem { instance_queries: Vec::new(), permutation: permutation::Argument::new(), lookups: Vec::new(), + constants: vec![], minimum_degree: None, } } @@ -878,10 +888,23 @@ impl ConstraintSystem { 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) { + 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) { self.query_any_index(column, Rotation::cur()); diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index c7fc1b58..a1c7be23 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -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); diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index f3ad0168..90b633d9 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -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); diff --git a/tests/plonk_api.rs b/tests/plonk_api.rs index ef68a429..3f239bc3 100644 --- a/tests/plonk_api.rs +++ b/tests/plonk_api.rs @@ -924,6 +924,7 @@ fn plonk_api() { ], }, ], + constants: [], minimum_degree: None, }, fixed_commitments: [