From ed2295b077fe98dec2881adbaf1628f3f427c293 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 20 Jul 2021 16:01:38 +0100 Subject: [PATCH 1/9] Add trait methods and general APIs for defining circuit constants During circuit configuration, fixed columns can be marked as suitable for containing arbitrary constant values (i.e. the columns aren't encumbered by constraints that preclude arbitrary values). During synthesis, circuits can now request that the layouter assign an advice cell within a region, to some specified constant value. It is the layouter's responsibility to find space within the previously marked fixed columns, in which to assign the constant. This commit intentionally does not compile. --- src/circuit.rs | 26 ++++++++++++++++++++++++++ src/circuit/layouter.rs | 14 ++++++++++++++ src/dev.rs | 3 ++- src/dev/graph.rs | 3 ++- src/dev/graph/layout.rs | 8 +++++++- src/plonk/circuit.rs | 23 +++++++++++++++++++++++ src/plonk/keygen.rs | 14 ++++++++++++-- src/plonk/prover.rs | 7 ++++++- 8 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 6ad86b25..54b94ace 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<'v, VR, A, AR>( + &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. /// diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index 21b48854..2c6a0e04 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. /// 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..d3682eb2 100644 --- a/src/dev/graph.rs +++ b/src/dev/graph.rs @@ -21,7 +21,8 @@ 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.clone()) + .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/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); From 9e2cba917fdddadc93a60caa0764e23912bc123c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 20 Jul 2021 16:07:47 +0100 Subject: [PATCH 2/9] Update SimpleFloorPlanner with constants support --- src/circuit/floor_planner/single_pass.rs | 60 ++++++++++++++++++++---- src/circuit/layouter.rs | 11 +++++ src/plonk.rs | 3 ++ tests/plonk_api.rs | 1 + 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/circuit/floor_planner/single_pass.rs b/src/circuit/floor_planner/single_pass.rs index 82aa8329..9bfad5eb 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,39 @@ 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 { + if self.layouter.constants.is_empty() { + return Err(Error::NotEnoughColumnsForConstants); + } + + let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?; + + // 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( + annotation, + 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 layed out in + // increasing row order). + self.next_constant_row + .saturating_sub(*self.layouter.regions[*self.region_index]), + &mut || Ok(constant), + )?; + + self.constrain_equal(advice, fixed)?; + + Ok(advice) + } + fn assign_advice_from_instance<'v>( &mut self, annotation: &'v (dyn Fn() -> String + 'v), @@ -233,12 +272,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, diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index 2c6a0e04..8a32e6e5 100644 --- a/src/circuit/layouter.rs +++ b/src/circuit/layouter.rs @@ -166,6 +166,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), diff --git a/src/plonk.rs b/src/plonk.rs index af0c7a9b..2a44e26a 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -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/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: [ From 7adf69529378aea748bc60623e787017af1751b8 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 20 Jul 2021 16:09:24 +0100 Subject: [PATCH 3/9] Add constants support to `floor_planner::V1` Co-authored-by: therealyingtong --- src/circuit/floor_planner/v1.rs | 77 ++++++++++++++++++++++-- src/circuit/floor_planner/v1/strategy.rs | 51 ++++++++++++---- 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/src/circuit/floor_planner/v1.rs b/src/circuit/floor_planner/v1.rs index 75658c47..c7b4e61a 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.plan.constants.push((constant, advice)); + + Ok(advice) + } + fn assign_advice_from_instance<'v>( &mut self, annotation: &'v (dyn Fn() -> String + 'v), diff --git a/src/circuit/floor_planner/v1/strategy.rs b/src/circuit/floor_planner/v1/strategy.rs index 753e86e5..ca9631c4 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, @@ -146,11 +162,16 @@ 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)>, + HashMap, Allocations>, +) { // Tracks the empty regions for each column. let mut column_allocations: HashMap, Allocations> = 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, HashMap, Allocations>) { 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>(), From 4c5a830e5ca8edf26b08e8d9482d0886d5e3eb93 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 20 Jul 2021 16:09:41 +0100 Subject: [PATCH 4/9] Use constants API in simple-example --- examples/simple-example.rs | 67 +++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 9 deletions(-) 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), }; From 629c13eae8ee60768bbf38f8d0b252d4aeb0bce2 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 20 Jul 2021 17:19:47 +0100 Subject: [PATCH 5/9] Add `Region::constrain_constant` API This is the non-assigning subset of `Region::assign_advice_from_constant`. --- src/circuit.rs | 10 ++++++ src/circuit/floor_planner/single_pass.rs | 45 +++++++++++++----------- src/circuit/floor_planner/v1.rs | 7 +++- src/circuit/layouter.rs | 10 ++++++ 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 54b94ace..ede0b9ef 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -225,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 9bfad5eb..c6829c79 100644 --- a/src/circuit/floor_planner/single_pass.rs +++ b/src/circuit/floor_planner/single_pass.rs @@ -215,28 +215,8 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter offset: usize, constant: Assigned, ) -> Result { - if self.layouter.constants.is_empty() { - return Err(Error::NotEnoughColumnsForConstants); - } - let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?; - - // 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( - annotation, - 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 layed out in - // increasing row order). - self.next_constant_row - .saturating_sub(*self.layouter.regions[*self.region_index]), - &mut || Ok(constant), - )?; - - self.constrain_equal(advice, fixed)?; + self.constrain_constant(advice, constant)?; Ok(advice) } @@ -291,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 layed 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, diff --git a/src/circuit/floor_planner/v1.rs b/src/circuit/floor_planner/v1.rs index c7b4e61a..6225b1e0 100644 --- a/src/circuit/floor_planner/v1.rs +++ b/src/circuit/floor_planner/v1.rs @@ -348,7 +348,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r constant: Assigned, ) -> Result { let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?; - self.plan.constants.push((constant, advice)); + self.constrain_constant(advice, constant)?; Ok(advice) } @@ -398,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, diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index 8a32e6e5..10fa84d2 100644 --- a/src/circuit/layouter.rs +++ b/src/circuit/layouter.rs @@ -94,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. @@ -215,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(()) From 62f088e2f249dfce2c3bcab7321cba0d99697af9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 21 Jul 2021 00:11:42 +0100 Subject: [PATCH 6/9] Fix clippy lints --- src/circuit.rs | 2 +- src/circuit/floor_planner/v1/strategy.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index ede0b9ef..f78a61f7 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -158,7 +158,7 @@ impl<'r, F: Field> Region<'r, F> { /// configured via `ConstraintSystem::enable_constant`. /// /// Returns the advice cell. - pub fn assign_advice_from_constant<'v, VR, A, AR>( + pub fn assign_advice_from_constant( &mut self, annotation: A, column: Column, diff --git a/src/circuit/floor_planner/v1/strategy.rs b/src/circuit/floor_planner/v1/strategy.rs index ca9631c4..090aceec 100644 --- a/src/circuit/floor_planner/v1/strategy.rs +++ b/src/circuit/floor_planner/v1/strategy.rs @@ -101,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, @@ -164,12 +167,9 @@ fn first_fit_region( /// in use, taking into account gaps between earlier regions. fn slot_in( region_shapes: Vec, -) -> ( - Vec<(RegionStart, RegionShape)>, - HashMap, Allocations>, -) { +) -> (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(); let regions = region_shapes .into_iter() @@ -200,7 +200,7 @@ fn slot_in( /// 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, HashMap, Allocations>) { +) -> (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 From cb9366d65687ecdc1175b292fe616d6f23e53cad Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 21 Jul 2021 12:00:25 +0100 Subject: [PATCH 7/9] Fix more clippy lints --- src/dev/graph.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dev/graph.rs b/src/dev/graph.rs index d3682eb2..0bc69a5e 100644 --- a/src/dev/graph.rs +++ b/src/dev/graph.rs @@ -21,8 +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, cs.constants.clone()) - .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. From 6420a25c8c3c1efd7739901b907e37c98f41b6a2 Mon Sep 17 00:00:00 2001 From: str4d Date: Wed, 21 Jul 2021 16:35:44 +0100 Subject: [PATCH 8/9] Typo fix Co-authored-by: Daira Hopwood --- src/circuit/floor_planner/single_pass.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/circuit/floor_planner/single_pass.rs b/src/circuit/floor_planner/single_pass.rs index c6829c79..cc1875e1 100644 --- a/src/circuit/floor_planner/single_pass.rs +++ b/src/circuit/floor_planner/single_pass.rs @@ -284,7 +284,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter 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 layed out in + // 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]), From 4e6c857854f0e19ed3edcc9faac5caf7a22871df Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 21 Jul 2021 16:43:18 +0100 Subject: [PATCH 9/9] Add test cases exercising Error::NotEnoughColumnsForConstants --- src/circuit/floor_planner/single_pass.rs | 55 ++++++++++++++++++++++++ src/circuit/floor_planner/v1.rs | 54 +++++++++++++++++++++++ src/plonk.rs | 2 +- 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/circuit/floor_planner/single_pass.rs b/src/circuit/floor_planner/single_pass.rs index cc1875e1..5b955290 100644 --- a/src/circuit/floor_planner/single_pass.rs +++ b/src/circuit/floor_planner/single_pass.rs @@ -305,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 6225b1e0..17adeedd 100644 --- a/src/circuit/floor_planner/v1.rs +++ b/src/circuit/floor_planner/v1.rs @@ -414,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/plonk.rs b/src/plonk.rs index 2a44e26a..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.