diff --git a/src/circuit.rs b/src/circuit.rs new file mode 100644 index 0000000..fb74fad --- /dev/null +++ b/src/circuit.rs @@ -0,0 +1,143 @@ +//! Traits and structs for implementing circuit components. + +use std::fmt; + +use crate::{ + arithmetic::FieldExt, + plonk::{Advice, Any, Column, ConstraintSystem, Error, Fixed}, +}; + +pub mod layouter; + +/// A chip implements a set of instructions that can be used by gadgets. +/// +/// The chip itself should not store any state; instead, state that is required at circuit +/// synthesis time should be stored in [`Chip::Config`], which can then be fetched via +/// [`Layouter::config`]. +pub trait Chip: Sized { + /// A type that holds the configuration for this chip, and any other state it may need + /// during circuit synthesis. + type Config: fmt::Debug; + + /// The field that the chip is defined over. + /// + /// This provides a type that the chip's configuration can reference if necessary. + type Field: FieldExt; + + /// Load any fixed configuration for this chip into the circuit. + fn load(layouter: &mut impl Layouter) -> Result<(), Error>; +} + +/// A pointer to a cell within a circuit. +#[derive(Clone, Copy, Debug)] +pub struct Cell { + /// Identifies the region in which this cell resides. + region_index: usize, + row_offset: usize, + column: Column, +} + +/// A permutation configured by a chip. +#[derive(Clone, Debug)] +pub struct Permutation { + index: usize, + mapping: Vec>, +} + +impl Permutation { + /// Configures a new permutation for the given columns. + pub fn new(meta: &mut ConstraintSystem, columns: &[Column]) -> Self { + let index = meta.permutation(columns); + Permutation { + index, + mapping: columns.iter().map(|c| (*c).into()).collect(), + } + } +} + +/// A region of the circuit in which a [`Chip`] can assign cells. +/// +/// Inside a region, the chip may freely use relative offsets; the [`Layouter`] will +/// treat these assignments as a single "region" within the circuit. +/// +/// The [`Layouter`] is allowed to optimise between regions as it sees fit. Chips must use +/// [`Region::constrain_equal`] to copy in variables assigned in other regions. +/// +/// TODO: It would be great if we could constrain the columns in these types to be +/// "logical" columns that are guaranteed to correspond to the chip (and have come from +/// `Chip::Config`). +#[derive(Debug)] +pub struct Region<'r, C: Chip> { + region: &'r mut dyn layouter::RegionLayouter, +} + +impl<'r, C: Chip> From<&'r mut dyn layouter::RegionLayouter> for Region<'r, C> { + fn from(region: &'r mut dyn layouter::RegionLayouter) -> Self { + Region { region } + } +} + +impl<'r, C: Chip> Region<'r, C> { + /// Assign an advice column value (witness). + /// + /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. + pub fn assign_advice<'v>( + &'v mut self, + column: Column, + offset: usize, + mut to: impl FnMut() -> Result + 'v, + ) -> Result { + self.region.assign_advice(column, offset, &mut to) + } + + /// Assign a fixed value. + /// + /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. + pub fn assign_fixed<'v>( + &'v mut self, + column: Column, + offset: usize, + mut to: impl FnMut() -> Result + 'v, + ) -> Result { + self.region.assign_fixed(column, offset, &mut to) + } + + /// Constraint two cells to have the same value. + /// + /// Returns an error if either of the cells is not within the given permutation. + pub fn constrain_equal( + &mut self, + permutation: &Permutation, + left: Cell, + right: Cell, + ) -> Result<(), Error> { + self.region.constrain_equal(permutation, left, right) + } +} + +/// A layout strategy for a specific chip within a circuit. +/// +/// This abstracts over the circuit assignments, handling row indices etc. +/// +/// A particular concrete layout strategy will implement this trait for each chip it +/// supports. +pub trait Layouter { + /// Provides access to the chip configuration. + fn config(&self) -> &C::Config; + + /// Assign a region of gates to an absolute row number. + /// + /// Inside the closure, the chip may freely use relative offsets; the `Layouter` will + /// treat these assignments as a single "region" within the circuit. Outside this + /// closure, the `Layouter` is allowed to optimise as it sees fit. + /// + /// ```ignore + /// fn assign_region(&mut self, |region| { + /// region.assign_advice(self.config.a, offset, || { Some(value)}); + /// }); + /// ``` + fn assign_region( + &mut self, + assignment: impl FnOnce(Region<'_, C>) -> Result<(), Error>, + ) -> Result<(), Error>; +} diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs new file mode 100644 index 0000000..fa329f5 --- /dev/null +++ b/src/circuit/layouter.rs @@ -0,0 +1,218 @@ +//! Implementations of common circuit layouters. + +use std::cmp; +use std::fmt; +use std::marker::PhantomData; + +use super::{Cell, Chip, Layouter, Permutation, Region}; +use crate::plonk::{Advice, Assignment, Column, Error, Fixed}; + +/// Helper trait for implementing a custom [`Layouter`]. +/// +/// This trait is used for implementing region assignments: +/// +/// ```ignore +/// impl<'a, C: Chip, CS: Assignment + 'a> Layouter for MyLayouter<'a, C, CS> { +/// fn assign_region( +/// &mut self, +/// assignment: impl FnOnce(Region<'_, C>) -> Result<(), Error>, +/// ) -> Result<(), Error> { +/// let region_index = self.regions.len(); +/// self.regions.push(self.current_gate); +/// +/// let mut region = MyRegion::new(self, region_index); +/// { +/// let region: &mut dyn RegionLayouter = &mut region; +/// assignment(region.into())?; +/// } +/// self.current_gate += region.row_count; +/// +/// Ok(()) +/// } +/// } +/// ``` +/// +/// TODO: It would be great if we could constrain the columns in these types to be +/// "logical" columns that are guaranteed to correspond to the chip (and have come from +/// `Chip::Config`). +pub trait RegionLayouter: fmt::Debug { + /// Assign an advice column value (witness) + fn assign_advice<'v>( + &'v mut self, + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Result + 'v), + ) -> Result; + + /// Assign a fixed value + fn assign_fixed<'v>( + &'v mut self, + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Result + 'v), + ) -> Result; + + /// Constraint two cells to have the same value. + /// + /// Returns an error if either of the cells is not within the given permutation. + fn constrain_equal( + &mut self, + permutation: &Permutation, + left: Cell, + right: Cell, + ) -> Result<(), Error>; +} + +/// A [`Layouter`] for a single-chip circuit. +pub struct SingleChip<'a, C: Chip, CS: Assignment + 'a> { + cs: &'a mut CS, + config: C::Config, + regions: Vec, + current_gate: usize, + _marker: PhantomData, +} + +impl<'a, C: Chip, CS: Assignment + 'a> fmt::Debug for SingleChip<'a, C, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SingleChip") + .field("config", &self.config) + .field("regions", &self.regions) + .field("current_gate", &self.current_gate) + .finish() + } +} + +impl<'a, C: Chip, CS: Assignment> SingleChip<'a, C, CS> { + /// Creates a new single-chip layouter. + pub fn new(cs: &'a mut CS, config: C::Config) -> Self { + SingleChip { + cs, + config, + regions: vec![], + current_gate: 0, + _marker: PhantomData, + } + } +} + +impl<'a, C: Chip, CS: Assignment + 'a> Layouter for SingleChip<'a, C, CS> { + fn config(&self) -> &C::Config { + &self.config + } + + fn assign_region( + &mut self, + assignment: impl FnOnce(Region<'_, C>) -> Result<(), Error>, + ) -> Result<(), Error> { + let region_index = self.regions.len(); + self.regions.push(self.current_gate); + + let mut region = SingleChipRegion::new(self, region_index); + { + let region: &mut dyn RegionLayouter = &mut region; + assignment(region.into())?; + } + self.current_gate += region.row_count; + + Ok(()) + } +} + +struct SingleChipRegion<'r, 'a, C: Chip, CS: Assignment + 'a> { + layouter: &'r mut SingleChip<'a, C, CS>, + region_index: usize, + row_count: usize, +} + +impl<'r, 'a, C: Chip, CS: Assignment + 'a> fmt::Debug + for SingleChipRegion<'r, 'a, C, CS> +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SingleChipRegion") + .field("layouter", &self.layouter) + .field("region_index", &self.region_index) + .field("row_count", &self.row_count) + .finish() + } +} + +impl<'r, 'a, C: Chip, CS: Assignment + 'a> SingleChipRegion<'r, 'a, C, CS> { + fn new(layouter: &'r mut SingleChip<'a, C, CS>, region_index: usize) -> Self { + SingleChipRegion { + layouter, + region_index, + row_count: 0, + } + } +} + +impl<'r, 'a, C: Chip, CS: Assignment + 'a> RegionLayouter + for SingleChipRegion<'r, 'a, C, CS> +{ + fn assign_advice<'v>( + &'v mut self, + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Result + 'v), + ) -> Result { + self.layouter.cs.assign_advice( + column, + self.layouter.regions[self.region_index] + offset, + to, + )?; + self.row_count = cmp::max(self.row_count, offset); + + Ok(Cell { + region_index: self.region_index, + row_offset: offset, + column: column.into(), + }) + } + + fn assign_fixed<'v>( + &'v mut self, + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Result + 'v), + ) -> Result { + self.layouter.cs.assign_fixed( + column, + self.layouter.regions[self.region_index] + offset, + to, + )?; + self.row_count = cmp::max(self.row_count, offset); + Ok(Cell { + region_index: self.region_index, + row_offset: offset, + column: column.into(), + }) + } + + fn constrain_equal( + &mut self, + permutation: &Permutation, + left: Cell, + right: Cell, + ) -> Result<(), Error> { + let left_column = permutation + .mapping + .iter() + .position(|c| c == &left.column) + .ok_or(Error::SynthesisError)?; + let right_column = permutation + .mapping + .iter() + .position(|c| c == &right.column) + .ok_or(Error::SynthesisError)?; + + self.layouter.cs.copy( + permutation.index, + left_column, + self.layouter.regions[left.region_index] + left.row_offset, + right_column, + self.layouter.regions[right.region_index] + right.row_offset, + )?; + + Ok(()) + } +} diff --git a/src/gadget.rs b/src/gadget.rs new file mode 100644 index 0000000..a876ca7 --- /dev/null +++ b/src/gadget.rs @@ -0,0 +1 @@ +//! Self-contained circuit implementations of various primitives. diff --git a/src/lib.rs b/src/lib.rs index ac043c4..2accaaa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ #![deny(unsafe_code)] pub mod arithmetic; +pub mod circuit; +pub mod gadget; pub mod pasta; pub mod plonk; pub mod poly;