//! 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, A, AR>( &'v mut self, annotation: A, column: Column, offset: usize, mut to: V, ) -> Result where V: FnMut() -> Result + 'v, A: Fn() -> AR, AR: Into, { self.region .assign_advice(&|| annotation().into(), 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, A, AR>( &'v mut self, annotation: A, column: Column, offset: usize, mut to: V, ) -> Result where V: FnMut() -> Result + 'v, A: Fn() -> AR, AR: Into, { self.region .assign_fixed(&|| annotation().into(), 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 name", |region| { /// region.assign_advice(self.config.a, offset, || { Some(value)}); /// }); /// ``` fn assign_region(&mut self, name: N, assignment: A) -> Result<(), Error> where A: FnMut(Region<'_, C>) -> Result<(), Error>, N: Fn() -> NR, NR: Into; }