mirror of https://github.com/zcash/halo2.git
1152 lines
36 KiB
Rust
1152 lines
36 KiB
Rust
use crate::circuit::Region;
|
|
use crate::plonk::circuit::VirtualCells;
|
|
use crate::plonk::Error;
|
|
use core::cmp::max;
|
|
use core::ops::{Add, Mul};
|
|
use halo2_middleware::circuit::{
|
|
Advice, Any, ChallengeMid, ColumnMid, ColumnType, ExpressionMid, Fixed, Instance, QueryMid,
|
|
VarMid,
|
|
};
|
|
use halo2_middleware::ff::Field;
|
|
use halo2_middleware::metadata;
|
|
use halo2_middleware::poly::Rotation;
|
|
use sealed::SealedPhase;
|
|
use std::fmt::Debug;
|
|
use std::iter::{Product, Sum};
|
|
use std::{
|
|
convert::TryFrom,
|
|
ops::{Neg, Sub},
|
|
};
|
|
|
|
/// A column with an index and type
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
|
pub struct Column<C: ColumnType> {
|
|
pub index: usize,
|
|
pub column_type: C,
|
|
}
|
|
|
|
impl From<Column<Any>> for metadata::Column {
|
|
fn from(val: Column<Any>) -> Self {
|
|
metadata::Column {
|
|
index: val.index(),
|
|
column_type: *val.column_type(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<C: ColumnType> Column<C> {
|
|
pub fn new(index: usize, column_type: C) -> Self {
|
|
Column { index, column_type }
|
|
}
|
|
|
|
/// Index of this column.
|
|
pub fn index(&self) -> usize {
|
|
self.index
|
|
}
|
|
|
|
/// Type of this column.
|
|
pub fn column_type(&self) -> &C {
|
|
&self.column_type
|
|
}
|
|
|
|
/// Return expression from column at a relative position
|
|
pub fn query_cell<F: Field>(&self, at: Rotation) -> Expression<F> {
|
|
let expr_mid = self.column_type.query_cell::<F>(self.index, at);
|
|
match expr_mid {
|
|
ExpressionMid::Var(VarMid::Query(q)) => match q.column_type {
|
|
Any::Advice(Advice { phase }) => Expression::Advice(AdviceQuery {
|
|
index: None,
|
|
column_index: q.column_index,
|
|
rotation: q.rotation,
|
|
phase: sealed::Phase(phase),
|
|
}),
|
|
Any::Fixed => Expression::Fixed(FixedQuery {
|
|
index: None,
|
|
column_index: q.column_index,
|
|
rotation: q.rotation,
|
|
}),
|
|
Any::Instance => Expression::Instance(InstanceQuery {
|
|
index: None,
|
|
column_index: q.column_index,
|
|
rotation: q.rotation,
|
|
}),
|
|
},
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// Return expression from column at the current row
|
|
pub fn cur<F: Field>(&self) -> Expression<F> {
|
|
self.query_cell(Rotation::cur())
|
|
}
|
|
|
|
/// Return expression from column at the next row
|
|
pub fn next<F: Field>(&self) -> Expression<F> {
|
|
self.query_cell(Rotation::next())
|
|
}
|
|
|
|
/// Return expression from column at the previous row
|
|
pub fn prev<F: Field>(&self) -> Expression<F> {
|
|
self.query_cell(Rotation::prev())
|
|
}
|
|
|
|
/// Return expression from column at the specified rotation
|
|
pub fn rot<F: Field>(&self, rotation: i32) -> Expression<F> {
|
|
self.query_cell(Rotation(rotation))
|
|
}
|
|
}
|
|
|
|
impl<C: ColumnType> Ord for Column<C> {
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
// This ordering is consensus-critical! The layouters rely on deterministic column
|
|
// orderings.
|
|
match self.column_type.into().cmp(&other.column_type.into()) {
|
|
// Indices are assigned within column types.
|
|
std::cmp::Ordering::Equal => self.index.cmp(&other.index),
|
|
order => order,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<C: ColumnType> PartialOrd for Column<C> {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl From<ColumnMid> for Column<Any> {
|
|
fn from(column: ColumnMid) -> Column<Any> {
|
|
Column {
|
|
index: column.index,
|
|
column_type: column.column_type,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Column<Any>> for ColumnMid {
|
|
fn from(val: Column<Any>) -> Self {
|
|
ColumnMid {
|
|
index: val.index(),
|
|
column_type: *val.column_type(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Column<Advice>> for Column<Any> {
|
|
fn from(advice: Column<Advice>) -> Column<Any> {
|
|
Column {
|
|
index: advice.index(),
|
|
column_type: Any::Advice(advice.column_type),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Column<Fixed>> for Column<Any> {
|
|
fn from(advice: Column<Fixed>) -> Column<Any> {
|
|
Column {
|
|
index: advice.index(),
|
|
column_type: Any::Fixed,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Column<Instance>> for Column<Any> {
|
|
fn from(advice: Column<Instance>) -> Column<Any> {
|
|
Column {
|
|
index: advice.index(),
|
|
column_type: Any::Instance,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Column<Any>> for Column<Advice> {
|
|
type Error = &'static str;
|
|
|
|
fn try_from(any: Column<Any>) -> Result<Self, Self::Error> {
|
|
match any.column_type() {
|
|
Any::Advice(advice) => Ok(Column {
|
|
index: any.index(),
|
|
column_type: *advice,
|
|
}),
|
|
_ => Err("Cannot convert into Column<Advice>"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Column<Any>> for Column<Fixed> {
|
|
type Error = &'static str;
|
|
|
|
fn try_from(any: Column<Any>) -> Result<Self, Self::Error> {
|
|
match any.column_type() {
|
|
Any::Fixed => Ok(Column {
|
|
index: any.index(),
|
|
column_type: Fixed,
|
|
}),
|
|
_ => Err("Cannot convert into Column<Fixed>"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Column<Any>> for Column<Instance> {
|
|
type Error = &'static str;
|
|
|
|
fn try_from(any: Column<Any>) -> Result<Self, Self::Error> {
|
|
match any.column_type() {
|
|
Any::Instance => Ok(Column {
|
|
index: any.index(),
|
|
column_type: Instance,
|
|
}),
|
|
_ => Err("Cannot convert into Column<Instance>"),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod sealed {
|
|
/// Phase of advice column
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
pub struct Phase(pub u8);
|
|
|
|
impl Phase {
|
|
pub fn prev(&self) -> Option<Phase> {
|
|
self.0.checked_sub(1).map(Phase)
|
|
}
|
|
}
|
|
|
|
impl SealedPhase for Phase {
|
|
fn to_sealed(self) -> Phase {
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Sealed trait to help keep `Phase` private.
|
|
pub trait SealedPhase {
|
|
fn to_sealed(self) -> Phase;
|
|
}
|
|
}
|
|
|
|
/// Phase of advice column
|
|
pub trait Phase: SealedPhase {}
|
|
|
|
impl<P: SealedPhase> Phase for P {}
|
|
|
|
/// First phase
|
|
#[derive(Debug)]
|
|
pub struct FirstPhase;
|
|
|
|
impl SealedPhase for FirstPhase {
|
|
fn to_sealed(self) -> sealed::Phase {
|
|
sealed::Phase(0)
|
|
}
|
|
}
|
|
|
|
/// Second phase
|
|
#[derive(Debug)]
|
|
pub struct SecondPhase;
|
|
|
|
impl SealedPhase for SecondPhase {
|
|
fn to_sealed(self) -> sealed::Phase {
|
|
sealed::Phase(1)
|
|
}
|
|
}
|
|
|
|
/// Third phase
|
|
#[derive(Debug)]
|
|
pub struct ThirdPhase;
|
|
|
|
impl SealedPhase for ThirdPhase {
|
|
fn to_sealed(self) -> sealed::Phase {
|
|
sealed::Phase(2)
|
|
}
|
|
}
|
|
|
|
/// A selector, representing a fixed boolean value per row of the circuit.
|
|
///
|
|
/// Selectors can be used to conditionally enable (portions of) gates:
|
|
/// ```
|
|
/// use halo2_middleware::poly::Rotation;
|
|
/// # use halo2curves::pasta::Fp;
|
|
/// # use halo2_frontend::plonk::ConstraintSystem;
|
|
///
|
|
/// # let mut meta = ConstraintSystem::<Fp>::default();
|
|
/// let a = meta.advice_column();
|
|
/// let b = meta.advice_column();
|
|
/// let s = meta.selector();
|
|
///
|
|
/// meta.create_gate("foo", |meta| {
|
|
/// let a = meta.query_advice(a, Rotation::prev());
|
|
/// let b = meta.query_advice(b, Rotation::cur());
|
|
/// let s = meta.query_selector(s);
|
|
///
|
|
/// // On rows where the selector is enabled, a is constrained to equal b.
|
|
/// // On rows where the selector is disabled, a and b can take any value.
|
|
/// vec![s * (a - b)]
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// Selectors are disabled on all rows by default, and must be explicitly enabled on each
|
|
/// row when required:
|
|
/// ```
|
|
/// use halo2_middleware::circuit::Advice;
|
|
/// use halo2_frontend::circuit::{Chip, Layouter, Value};
|
|
/// use halo2_frontend::plonk::{Error, Column, Selector};
|
|
/// use halo2_middleware::ff::Field;
|
|
/// # use halo2_middleware::circuit::Fixed;
|
|
///
|
|
/// struct Config {
|
|
/// a: Column<Advice>,
|
|
/// b: Column<Advice>,
|
|
/// s: Selector,
|
|
/// }
|
|
///
|
|
/// fn circuit_logic<F: Field, C: Chip<F>>(chip: C, mut layouter: impl Layouter<F>) -> Result<(), Error> {
|
|
/// let config = chip.config();
|
|
/// # let config: Config = todo!();
|
|
/// layouter.assign_region(|| "bar", |mut region| {
|
|
/// region.assign_advice(|| "a", config.a, 0, || Value::known(F::ONE))?;
|
|
/// region.assign_advice(|| "a", config.b, 1, || Value::known(F::ONE))?;
|
|
/// config.s.enable(&mut region, 1)
|
|
/// })?;
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
pub struct Selector(pub usize, pub(crate) bool);
|
|
|
|
impl Selector {
|
|
/// Enable this selector at the given offset within the given region.
|
|
pub fn enable<F: Field>(&self, region: &mut Region<F>, offset: usize) -> Result<(), Error> {
|
|
region.enable_selector(|| "", self, offset)
|
|
}
|
|
|
|
/// Is this selector "simple"? Simple selectors can only be multiplied
|
|
/// by expressions that contain no other simple selectors.
|
|
pub fn is_simple(&self) -> bool {
|
|
self.1
|
|
}
|
|
|
|
/// Returns index of this selector
|
|
pub fn index(&self) -> usize {
|
|
self.0
|
|
}
|
|
|
|
/// Return expression from selector
|
|
pub fn expr<F: Field>(&self) -> Expression<F> {
|
|
Expression::Selector(*self)
|
|
}
|
|
}
|
|
|
|
/// Query of fixed column at a certain relative location
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub struct FixedQuery {
|
|
/// Query index
|
|
pub index: Option<usize>,
|
|
/// Column index
|
|
pub column_index: usize,
|
|
/// Rotation of this query
|
|
pub rotation: Rotation,
|
|
}
|
|
|
|
impl FixedQuery {
|
|
/// Column index
|
|
pub fn column_index(&self) -> usize {
|
|
self.column_index
|
|
}
|
|
|
|
/// Rotation of this query
|
|
pub fn rotation(&self) -> Rotation {
|
|
self.rotation
|
|
}
|
|
}
|
|
|
|
/// Query of advice column at a certain relative location
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub struct AdviceQuery {
|
|
/// Query index
|
|
pub index: Option<usize>,
|
|
/// Column index
|
|
pub column_index: usize,
|
|
/// Rotation of this query
|
|
pub rotation: Rotation,
|
|
/// Phase of this advice column
|
|
pub phase: sealed::Phase,
|
|
}
|
|
|
|
impl AdviceQuery {
|
|
/// Column index
|
|
pub fn column_index(&self) -> usize {
|
|
self.column_index
|
|
}
|
|
|
|
/// Rotation of this query
|
|
pub fn rotation(&self) -> Rotation {
|
|
self.rotation
|
|
}
|
|
|
|
/// Phase of this advice column
|
|
pub fn phase(&self) -> u8 {
|
|
self.phase.0
|
|
}
|
|
}
|
|
|
|
/// Query of instance column at a certain relative location
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub struct InstanceQuery {
|
|
/// Query index
|
|
pub index: Option<usize>,
|
|
/// Column index
|
|
pub column_index: usize,
|
|
/// Rotation of this query
|
|
pub rotation: Rotation,
|
|
}
|
|
|
|
impl InstanceQuery {
|
|
/// Column index
|
|
pub fn column_index(&self) -> usize {
|
|
self.column_index
|
|
}
|
|
|
|
/// Rotation of this query
|
|
pub fn rotation(&self) -> Rotation {
|
|
self.rotation
|
|
}
|
|
}
|
|
|
|
/// A fixed column of a lookup table.
|
|
///
|
|
/// A lookup table can be loaded into this column via [`Layouter::assign_table`]. Columns
|
|
/// can currently only contain a single table, but they may be used in multiple lookup
|
|
/// arguments via [`ConstraintSystem::lookup`].
|
|
///
|
|
/// Lookup table columns are always "encumbered" by the lookup arguments they are used in;
|
|
/// they cannot simultaneously be used as general fixed columns.
|
|
///
|
|
/// [`Layouter::assign_table`]: crate::circuit::Layouter::assign_table
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
|
pub struct TableColumn {
|
|
/// The fixed column that this table column is stored in.
|
|
///
|
|
/// # Security
|
|
///
|
|
/// This inner column MUST NOT be exposed in the public API, or else chip developers
|
|
/// can load lookup tables into their circuits without default-value-filling the
|
|
/// columns, which can cause soundness bugs.
|
|
pub(super) inner: Column<Fixed>,
|
|
}
|
|
|
|
impl TableColumn {
|
|
/// Returns inner column
|
|
pub fn inner(&self) -> Column<Fixed> {
|
|
self.inner
|
|
}
|
|
}
|
|
|
|
/// A challenge squeezed from transcript after advice columns at the phase have been committed.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
|
pub struct Challenge {
|
|
pub index: usize,
|
|
pub(crate) phase: u8,
|
|
}
|
|
|
|
impl Challenge {
|
|
/// Index of this challenge.
|
|
pub fn index(&self) -> usize {
|
|
self.index
|
|
}
|
|
|
|
/// Phase of this challenge.
|
|
pub fn phase(&self) -> u8 {
|
|
self.phase
|
|
}
|
|
|
|
/// Return Expression
|
|
pub fn expr<F: Field>(&self) -> Expression<F> {
|
|
Expression::Challenge(*self)
|
|
}
|
|
}
|
|
|
|
impl From<Challenge> for ChallengeMid {
|
|
fn from(val: Challenge) -> Self {
|
|
ChallengeMid {
|
|
index: val.index,
|
|
phase: val.phase,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ChallengeMid> for Challenge {
|
|
fn from(c: ChallengeMid) -> Self {
|
|
Self {
|
|
index: c.index,
|
|
phase: c.phase,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Low-degree expression representing an identity that must hold over the committed columns.
|
|
#[derive(Clone, PartialEq, Eq)]
|
|
pub enum Expression<F> {
|
|
/// This is a constant polynomial
|
|
Constant(F),
|
|
/// This is a virtual selector
|
|
Selector(Selector),
|
|
/// This is a fixed column queried at a certain relative location
|
|
Fixed(FixedQuery),
|
|
/// This is an advice (witness) column queried at a certain relative location
|
|
Advice(AdviceQuery),
|
|
/// This is an instance (external) column queried at a certain relative location
|
|
Instance(InstanceQuery),
|
|
/// This is a challenge
|
|
Challenge(Challenge),
|
|
/// This is a negated polynomial
|
|
Negated(Box<Expression<F>>),
|
|
/// This is the sum of two polynomials
|
|
Sum(Box<Expression<F>>, Box<Expression<F>>),
|
|
/// This is the product of two polynomials
|
|
Product(Box<Expression<F>>, Box<Expression<F>>),
|
|
/// This is a scaled polynomial
|
|
Scaled(Box<Expression<F>>, F),
|
|
}
|
|
|
|
impl<F> From<Expression<F>> for ExpressionMid<F> {
|
|
fn from(val: Expression<F>) -> Self {
|
|
match val {
|
|
Expression::Constant(c) => ExpressionMid::Constant(c),
|
|
Expression::Selector(_) => unreachable!(),
|
|
Expression::Fixed(FixedQuery {
|
|
column_index,
|
|
rotation,
|
|
..
|
|
}) => ExpressionMid::Var(VarMid::Query(QueryMid {
|
|
column_index,
|
|
column_type: Any::Fixed,
|
|
rotation,
|
|
})),
|
|
Expression::Advice(AdviceQuery {
|
|
column_index,
|
|
rotation,
|
|
phase,
|
|
..
|
|
}) => ExpressionMid::Var(VarMid::Query(QueryMid {
|
|
column_index,
|
|
column_type: Any::Advice(Advice { phase: phase.0 }),
|
|
rotation,
|
|
})),
|
|
Expression::Instance(InstanceQuery {
|
|
column_index,
|
|
rotation,
|
|
..
|
|
}) => ExpressionMid::Var(VarMid::Query(QueryMid {
|
|
column_index,
|
|
column_type: Any::Instance,
|
|
rotation,
|
|
})),
|
|
Expression::Challenge(c) => ExpressionMid::Var(VarMid::Challenge(c.into())),
|
|
Expression::Negated(e) => ExpressionMid::Negated(Box::new((*e).into())),
|
|
Expression::Sum(lhs, rhs) => {
|
|
ExpressionMid::Sum(Box::new((*lhs).into()), Box::new((*rhs).into()))
|
|
}
|
|
Expression::Product(lhs, rhs) => {
|
|
ExpressionMid::Product(Box::new((*lhs).into()), Box::new((*rhs).into()))
|
|
}
|
|
Expression::Scaled(e, c) => ExpressionMid::Scaled(Box::new((*e).into()), c),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<F: Field> Expression<F> {
|
|
/// Make side effects
|
|
pub fn query_cells(&mut self, cells: &mut VirtualCells<'_, F>) {
|
|
match self {
|
|
Expression::Constant(_) => (),
|
|
Expression::Selector(selector) => {
|
|
if !cells.queried_selectors.contains(selector) {
|
|
cells.queried_selectors.push(*selector);
|
|
}
|
|
}
|
|
Expression::Fixed(query) => {
|
|
if query.index.is_none() {
|
|
let col = Column {
|
|
index: query.column_index,
|
|
column_type: Fixed,
|
|
};
|
|
cells.queried_cells.push((col, query.rotation).into());
|
|
query.index = Some(cells.meta.query_fixed_index(col, query.rotation));
|
|
}
|
|
}
|
|
Expression::Advice(query) => {
|
|
if query.index.is_none() {
|
|
let col = Column {
|
|
index: query.column_index,
|
|
column_type: Advice {
|
|
phase: query.phase.0,
|
|
},
|
|
};
|
|
cells.queried_cells.push((col, query.rotation).into());
|
|
query.index = Some(cells.meta.query_advice_index(col, query.rotation));
|
|
}
|
|
}
|
|
Expression::Instance(query) => {
|
|
if query.index.is_none() {
|
|
let col = Column {
|
|
index: query.column_index,
|
|
column_type: Instance,
|
|
};
|
|
cells.queried_cells.push((col, query.rotation).into());
|
|
query.index = Some(cells.meta.query_instance_index(col, query.rotation));
|
|
}
|
|
}
|
|
Expression::Challenge(_) => (),
|
|
Expression::Negated(a) => a.query_cells(cells),
|
|
Expression::Sum(a, b) => {
|
|
a.query_cells(cells);
|
|
b.query_cells(cells);
|
|
}
|
|
Expression::Product(a, b) => {
|
|
a.query_cells(cells);
|
|
b.query_cells(cells);
|
|
}
|
|
Expression::Scaled(a, _) => a.query_cells(cells),
|
|
};
|
|
}
|
|
|
|
/// Evaluate the polynomial using the provided closures to perform the
|
|
/// operations.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn evaluate<T>(
|
|
&self,
|
|
constant: &impl Fn(F) -> T,
|
|
selector_column: &impl Fn(Selector) -> T,
|
|
fixed_column: &impl Fn(FixedQuery) -> T,
|
|
advice_column: &impl Fn(AdviceQuery) -> T,
|
|
instance_column: &impl Fn(InstanceQuery) -> T,
|
|
challenge: &impl Fn(Challenge) -> T,
|
|
negated: &impl Fn(T) -> T,
|
|
sum: &impl Fn(T, T) -> T,
|
|
product: &impl Fn(T, T) -> T,
|
|
scaled: &impl Fn(T, F) -> T,
|
|
) -> T {
|
|
match self {
|
|
Expression::Constant(scalar) => constant(*scalar),
|
|
Expression::Selector(selector) => selector_column(*selector),
|
|
Expression::Fixed(query) => fixed_column(*query),
|
|
Expression::Advice(query) => advice_column(*query),
|
|
Expression::Instance(query) => instance_column(*query),
|
|
Expression::Challenge(value) => challenge(*value),
|
|
Expression::Negated(a) => {
|
|
let a = a.evaluate(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
);
|
|
negated(a)
|
|
}
|
|
Expression::Sum(a, b) => {
|
|
let a = a.evaluate(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
);
|
|
let b = b.evaluate(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
);
|
|
sum(a, b)
|
|
}
|
|
Expression::Product(a, b) => {
|
|
let a = a.evaluate(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
);
|
|
let b = b.evaluate(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
);
|
|
product(a, b)
|
|
}
|
|
Expression::Scaled(a, f) => {
|
|
let a = a.evaluate(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
);
|
|
scaled(a, *f)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Evaluate the polynomial lazily using the provided closures to perform the
|
|
/// operations.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn evaluate_lazy<T: PartialEq>(
|
|
&self,
|
|
constant: &impl Fn(F) -> T,
|
|
selector_column: &impl Fn(Selector) -> T,
|
|
fixed_column: &impl Fn(FixedQuery) -> T,
|
|
advice_column: &impl Fn(AdviceQuery) -> T,
|
|
instance_column: &impl Fn(InstanceQuery) -> T,
|
|
challenge: &impl Fn(Challenge) -> T,
|
|
negated: &impl Fn(T) -> T,
|
|
sum: &impl Fn(T, T) -> T,
|
|
product: &impl Fn(T, T) -> T,
|
|
scaled: &impl Fn(T, F) -> T,
|
|
zero: &T,
|
|
) -> T {
|
|
match self {
|
|
Expression::Constant(scalar) => constant(*scalar),
|
|
Expression::Selector(selector) => selector_column(*selector),
|
|
Expression::Fixed(query) => fixed_column(*query),
|
|
Expression::Advice(query) => advice_column(*query),
|
|
Expression::Instance(query) => instance_column(*query),
|
|
Expression::Challenge(value) => challenge(*value),
|
|
Expression::Negated(a) => {
|
|
let a = a.evaluate_lazy(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
zero,
|
|
);
|
|
negated(a)
|
|
}
|
|
Expression::Sum(a, b) => {
|
|
let a = a.evaluate_lazy(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
zero,
|
|
);
|
|
let b = b.evaluate_lazy(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
zero,
|
|
);
|
|
sum(a, b)
|
|
}
|
|
Expression::Product(a, b) => {
|
|
let (a, b) = if a.complexity() <= b.complexity() {
|
|
(a, b)
|
|
} else {
|
|
(b, a)
|
|
};
|
|
let a = a.evaluate_lazy(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
zero,
|
|
);
|
|
|
|
if a == *zero {
|
|
a
|
|
} else {
|
|
let b = b.evaluate_lazy(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
zero,
|
|
);
|
|
product(a, b)
|
|
}
|
|
}
|
|
Expression::Scaled(a, f) => {
|
|
let a = a.evaluate_lazy(
|
|
constant,
|
|
selector_column,
|
|
fixed_column,
|
|
advice_column,
|
|
instance_column,
|
|
challenge,
|
|
negated,
|
|
sum,
|
|
product,
|
|
scaled,
|
|
zero,
|
|
);
|
|
scaled(a, *f)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_identifier<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
|
|
match self {
|
|
Expression::Constant(scalar) => write!(writer, "{scalar:?}"),
|
|
Expression::Selector(selector) => write!(writer, "selector[{}]", selector.0),
|
|
Expression::Fixed(query) => {
|
|
write!(
|
|
writer,
|
|
"fixed[{}][{}]",
|
|
query.column_index, query.rotation.0
|
|
)
|
|
}
|
|
Expression::Advice(query) => {
|
|
write!(
|
|
writer,
|
|
"advice[{}][{}]",
|
|
query.column_index, query.rotation.0
|
|
)
|
|
}
|
|
Expression::Instance(query) => {
|
|
write!(
|
|
writer,
|
|
"instance[{}][{}]",
|
|
query.column_index, query.rotation.0
|
|
)
|
|
}
|
|
Expression::Challenge(challenge) => {
|
|
write!(writer, "challenge[{}]", challenge.index())
|
|
}
|
|
Expression::Negated(a) => {
|
|
writer.write_all(b"(-")?;
|
|
a.write_identifier(writer)?;
|
|
writer.write_all(b")")
|
|
}
|
|
Expression::Sum(a, b) => {
|
|
writer.write_all(b"(")?;
|
|
a.write_identifier(writer)?;
|
|
writer.write_all(b"+")?;
|
|
b.write_identifier(writer)?;
|
|
writer.write_all(b")")
|
|
}
|
|
Expression::Product(a, b) => {
|
|
writer.write_all(b"(")?;
|
|
a.write_identifier(writer)?;
|
|
writer.write_all(b"*")?;
|
|
b.write_identifier(writer)?;
|
|
writer.write_all(b")")
|
|
}
|
|
Expression::Scaled(a, f) => {
|
|
a.write_identifier(writer)?;
|
|
write!(writer, "*{f:?}")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Identifier for this expression. Expressions with identical identifiers
|
|
/// do the same calculation (but the expressions don't need to be exactly equal
|
|
/// in how they are composed e.g. `1 + 2` and `2 + 1` can have the same identifier).
|
|
pub fn identifier(&self) -> String {
|
|
let mut cursor = std::io::Cursor::new(Vec::new());
|
|
self.write_identifier(&mut cursor).unwrap();
|
|
String::from_utf8(cursor.into_inner()).unwrap()
|
|
}
|
|
|
|
/// Compute the degree of this polynomial
|
|
pub fn degree(&self) -> usize {
|
|
match self {
|
|
Expression::Constant(_) => 0,
|
|
Expression::Selector(_) => 1,
|
|
Expression::Fixed(_) => 1,
|
|
Expression::Advice(_) => 1,
|
|
Expression::Instance(_) => 1,
|
|
Expression::Challenge(_) => 0,
|
|
Expression::Negated(poly) => poly.degree(),
|
|
Expression::Sum(a, b) => max(a.degree(), b.degree()),
|
|
Expression::Product(a, b) => a.degree() + b.degree(),
|
|
Expression::Scaled(poly, _) => poly.degree(),
|
|
}
|
|
}
|
|
|
|
/// Approximate the computational complexity of this expression.
|
|
pub fn complexity(&self) -> usize {
|
|
match self {
|
|
Expression::Constant(_) => 0,
|
|
Expression::Selector(_) => 1,
|
|
Expression::Fixed(_) => 1,
|
|
Expression::Advice(_) => 1,
|
|
Expression::Instance(_) => 1,
|
|
Expression::Challenge(_) => 0,
|
|
Expression::Negated(poly) => poly.complexity() + 5,
|
|
Expression::Sum(a, b) => a.complexity() + b.complexity() + 15,
|
|
Expression::Product(a, b) => a.complexity() + b.complexity() + 30,
|
|
Expression::Scaled(poly, _) => poly.complexity() + 30,
|
|
}
|
|
}
|
|
|
|
/// Square this expression.
|
|
pub fn square(self) -> Self {
|
|
self.clone() * self
|
|
}
|
|
|
|
/// Returns whether or not this expression contains a simple `Selector`.
|
|
pub(super) fn contains_simple_selector(&self) -> bool {
|
|
self.evaluate(
|
|
&|_| false,
|
|
&|selector| selector.is_simple(),
|
|
&|_| false,
|
|
&|_| false,
|
|
&|_| false,
|
|
&|_| false,
|
|
&|a| a,
|
|
&|a, b| a || b,
|
|
&|a, b| a || b,
|
|
&|a, _| a,
|
|
)
|
|
}
|
|
|
|
// TODO: Where is this used?
|
|
/// Extracts a simple selector from this gate, if present
|
|
pub(super) fn extract_simple_selector(&self) -> Option<Selector> {
|
|
let op = |a, b| match (a, b) {
|
|
(Some(a), None) | (None, Some(a)) => Some(a),
|
|
(Some(_), Some(_)) => panic!("two simple selectors cannot be in the same expression"),
|
|
_ => None,
|
|
};
|
|
|
|
self.evaluate(
|
|
&|_| None,
|
|
&|selector| {
|
|
if selector.is_simple() {
|
|
Some(selector)
|
|
} else {
|
|
None
|
|
}
|
|
},
|
|
&|_| None,
|
|
&|_| None,
|
|
&|_| None,
|
|
&|_| None,
|
|
&|a| a,
|
|
&op,
|
|
&op,
|
|
&|a, _| a,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<F: std::fmt::Debug> std::fmt::Debug for Expression<F> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Expression::Constant(scalar) => f.debug_tuple("Constant").field(scalar).finish(),
|
|
Expression::Selector(selector) => f.debug_tuple("Selector").field(selector).finish(),
|
|
// Skip enum variant and print query struct directly to maintain backwards compatibility.
|
|
Expression::Fixed(query) => {
|
|
let mut debug_struct = f.debug_struct("Fixed");
|
|
match query.index {
|
|
None => debug_struct.field("query_index", &query.index),
|
|
Some(idx) => debug_struct.field("query_index", &idx),
|
|
};
|
|
debug_struct
|
|
.field("column_index", &query.column_index)
|
|
.field("rotation", &query.rotation)
|
|
.finish()
|
|
}
|
|
Expression::Advice(query) => {
|
|
let mut debug_struct = f.debug_struct("Advice");
|
|
match query.index {
|
|
None => debug_struct.field("query_index", &query.index),
|
|
Some(idx) => debug_struct.field("query_index", &idx),
|
|
};
|
|
debug_struct
|
|
.field("column_index", &query.column_index)
|
|
.field("rotation", &query.rotation);
|
|
// Only show advice's phase if it's not in first phase.
|
|
if query.phase != FirstPhase.to_sealed() {
|
|
debug_struct.field("phase", &query.phase);
|
|
}
|
|
debug_struct.finish()
|
|
}
|
|
Expression::Instance(query) => {
|
|
let mut debug_struct = f.debug_struct("Instance");
|
|
match query.index {
|
|
None => debug_struct.field("query_index", &query.index),
|
|
Some(idx) => debug_struct.field("query_index", &idx),
|
|
};
|
|
debug_struct
|
|
.field("column_index", &query.column_index)
|
|
.field("rotation", &query.rotation)
|
|
.finish()
|
|
}
|
|
Expression::Challenge(challenge) => {
|
|
f.debug_tuple("Challenge").field(challenge).finish()
|
|
}
|
|
Expression::Negated(poly) => f.debug_tuple("Negated").field(poly).finish(),
|
|
Expression::Sum(a, b) => f.debug_tuple("Sum").field(a).field(b).finish(),
|
|
Expression::Product(a, b) => f.debug_tuple("Product").field(a).field(b).finish(),
|
|
Expression::Scaled(poly, scalar) => {
|
|
f.debug_tuple("Scaled").field(poly).field(scalar).finish()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<F: Field> Neg for Expression<F> {
|
|
type Output = Expression<F>;
|
|
fn neg(self) -> Self::Output {
|
|
Expression::Negated(Box::new(self))
|
|
}
|
|
}
|
|
|
|
impl<F: Field> Add for Expression<F> {
|
|
type Output = Expression<F>;
|
|
fn add(self, rhs: Expression<F>) -> Expression<F> {
|
|
if self.contains_simple_selector() || rhs.contains_simple_selector() {
|
|
panic!("attempted to use a simple selector in an addition");
|
|
}
|
|
Expression::Sum(Box::new(self), Box::new(rhs))
|
|
}
|
|
}
|
|
|
|
impl<F: Field> Sub for Expression<F> {
|
|
type Output = Expression<F>;
|
|
fn sub(self, rhs: Expression<F>) -> Expression<F> {
|
|
if self.contains_simple_selector() || rhs.contains_simple_selector() {
|
|
panic!("attempted to use a simple selector in a subtraction");
|
|
}
|
|
Expression::Sum(Box::new(self), Box::new(-rhs))
|
|
}
|
|
}
|
|
|
|
impl<F: Field> Mul for Expression<F> {
|
|
type Output = Expression<F>;
|
|
fn mul(self, rhs: Expression<F>) -> Expression<F> {
|
|
if self.contains_simple_selector() && rhs.contains_simple_selector() {
|
|
panic!("attempted to multiply two expressions containing simple selectors");
|
|
}
|
|
Expression::Product(Box::new(self), Box::new(rhs))
|
|
}
|
|
}
|
|
|
|
impl<F: Field> Mul<F> for Expression<F> {
|
|
type Output = Expression<F>;
|
|
fn mul(self, rhs: F) -> Expression<F> {
|
|
Expression::Scaled(Box::new(self), rhs)
|
|
}
|
|
}
|
|
|
|
impl<F: Field> Sum<Self> for Expression<F> {
|
|
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
|
iter.reduce(|acc, x| acc + x)
|
|
.unwrap_or(Expression::Constant(F::ZERO))
|
|
}
|
|
}
|
|
|
|
impl<F: Field> Product<Self> for Expression<F> {
|
|
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
|
|
iter.reduce(|acc, x| acc * x)
|
|
.unwrap_or(Expression::Constant(F::ONE))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::Expression;
|
|
use halo2curves::bn256::Fr;
|
|
|
|
#[test]
|
|
fn iter_sum() {
|
|
let exprs: Vec<Expression<Fr>> = vec![
|
|
Expression::Constant(1.into()),
|
|
Expression::Constant(2.into()),
|
|
Expression::Constant(3.into()),
|
|
];
|
|
let happened: Expression<Fr> = exprs.into_iter().sum();
|
|
let expected: Expression<Fr> = Expression::Sum(
|
|
Box::new(Expression::Sum(
|
|
Box::new(Expression::Constant(1.into())),
|
|
Box::new(Expression::Constant(2.into())),
|
|
)),
|
|
Box::new(Expression::Constant(3.into())),
|
|
);
|
|
|
|
assert_eq!(happened, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn iter_product() {
|
|
let exprs: Vec<Expression<Fr>> = vec![
|
|
Expression::Constant(1.into()),
|
|
Expression::Constant(2.into()),
|
|
Expression::Constant(3.into()),
|
|
];
|
|
let happened: Expression<Fr> = exprs.into_iter().product();
|
|
let expected: Expression<Fr> = Expression::Product(
|
|
Box::new(Expression::Product(
|
|
Box::new(Expression::Constant(1.into())),
|
|
Box::new(Expression::Constant(2.into())),
|
|
)),
|
|
Box::new(Expression::Constant(3.into())),
|
|
);
|
|
|
|
assert_eq!(happened, expected);
|
|
}
|
|
}
|