mirror of https://github.com/zcash/halo2.git
Merge pull request #125 from zcash/circuit-traits
Circuit component traits
This commit is contained in:
commit
1e4b449934
|
@ -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<Self>) -> 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<Any>,
|
||||
}
|
||||
|
||||
/// A permutation configured by a chip.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Permutation {
|
||||
index: usize,
|
||||
mapping: Vec<Column<Any>>,
|
||||
}
|
||||
|
||||
impl Permutation {
|
||||
/// Configures a new permutation for the given columns.
|
||||
pub fn new<F: FieldExt>(meta: &mut ConstraintSystem<F>, columns: &[Column<Advice>]) -> 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<C>,
|
||||
}
|
||||
|
||||
impl<'r, C: Chip> From<&'r mut dyn layouter::RegionLayouter<C>> for Region<'r, C> {
|
||||
fn from(region: &'r mut dyn layouter::RegionLayouter<C>) -> 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<Advice>,
|
||||
offset: usize,
|
||||
mut to: impl FnMut() -> Result<C::Field, Error> + 'v,
|
||||
) -> Result<Cell, Error> {
|
||||
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<Fixed>,
|
||||
offset: usize,
|
||||
mut to: impl FnMut() -> Result<C::Field, Error> + 'v,
|
||||
) -> Result<Cell, Error> {
|
||||
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<C: Chip> {
|
||||
/// 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>;
|
||||
}
|
|
@ -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<C::Field> + 'a> Layouter<C> 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<C> = &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<C: Chip>: fmt::Debug {
|
||||
/// Assign an advice column value (witness)
|
||||
fn assign_advice<'v>(
|
||||
&'v mut self,
|
||||
column: Column<Advice>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<C::Field, Error> + 'v),
|
||||
) -> Result<Cell, Error>;
|
||||
|
||||
/// Assign a fixed value
|
||||
fn assign_fixed<'v>(
|
||||
&'v mut self,
|
||||
column: Column<Fixed>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<C::Field, Error> + 'v),
|
||||
) -> Result<Cell, Error>;
|
||||
|
||||
/// 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<C::Field> + 'a> {
|
||||
cs: &'a mut CS,
|
||||
config: C::Config,
|
||||
regions: Vec<usize>,
|
||||
current_gate: usize,
|
||||
_marker: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<'a, C: Chip, CS: Assignment<C::Field> + '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<C::Field>> 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<C::Field> + 'a> Layouter<C> 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<C> = &mut region;
|
||||
assignment(region.into())?;
|
||||
}
|
||||
self.current_gate += region.row_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct SingleChipRegion<'r, 'a, C: Chip, CS: Assignment<C::Field> + 'a> {
|
||||
layouter: &'r mut SingleChip<'a, C, CS>,
|
||||
region_index: usize,
|
||||
row_count: usize,
|
||||
}
|
||||
|
||||
impl<'r, 'a, C: Chip, CS: Assignment<C::Field> + '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<C::Field> + '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<C::Field> + 'a> RegionLayouter<C>
|
||||
for SingleChipRegion<'r, 'a, C, CS>
|
||||
{
|
||||
fn assign_advice<'v>(
|
||||
&'v mut self,
|
||||
column: Column<Advice>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<C::Field, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
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<Fixed>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<C::Field, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
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(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
//! Self-contained circuit implementations of various primitives.
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue