Merge pull request #176 from zcash/general-lookup

Generalise lookup argument to work over expressions
This commit is contained in:
ebfull 2021-02-18 16:32:48 -07:00 committed by GitHub
commit fb6111df5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 385 additions and 210 deletions

View File

@ -283,8 +283,12 @@ fn main() {
* ... ... ... 0 0
* ]
*/
meta.lookup(&[a.into()], &[sl.into()]);
meta.lookup(&[a.into(), b.into()], &[sl.into(), sl2.into()]);
let a_ = meta.query_any(a.into(), Rotation::cur());
let b_ = meta.query_any(b.into(), Rotation::cur());
let sl_ = meta.query_any(sl.into(), Rotation::cur());
let sl2_ = meta.query_any(sl2.into(), Rotation::cur());
meta.lookup(&[a_.clone()], &[sl_.clone()]);
meta.lookup(&[a_, b_], &[sl_, sl2_]);
meta.create_gate("Combined add-mult", |meta| {
let d = meta.query_advice(d, Rotation::next());

View File

@ -5,8 +5,8 @@ use ff::Field;
use crate::{
arithmetic::{FieldExt, Group},
plonk::{
permutation, Advice, Any, Assignment, Circuit, Column, ColumnType, ConstraintSystem, Error,
Fixed,
permutation, Advice, Assignment, Circuit, Column, ColumnType, ConstraintSystem, Error,
Expression, Fixed,
},
poly::Rotation,
};
@ -143,7 +143,7 @@ pub enum VerifyFailure {
/// );
/// ```
#[derive(Debug)]
pub struct MockProver<F: Group> {
pub struct MockProver<F: Group + Field> {
n: u32,
cs: ConstraintSystem<F>,
@ -296,6 +296,7 @@ impl<F: FieldExt> MockProver<F> {
}
if gate.evaluate(
&|scalar| scalar,
&load(n, row, &self.cs.fixed_queries, &self.fixed),
&load(n, row, &self.cs.advice_queries, &self.advice),
&load(n, row, &self.cs.instance_queries, &self.instance),
@ -316,21 +317,41 @@ impl<F: FieldExt> MockProver<F> {
// Check that all lookups exist in their respective tables.
for (lookup_index, lookup) in self.cs.lookups.iter().enumerate() {
for input_row in 0..n {
let load = |column: &Column<Any>, row| match column.column_type() {
Any::Fixed => self.fixed[column.index()][row as usize],
Any::Advice => self.advice[column.index()][row as usize],
Any::Instance => self.instance[column.index()][row as usize],
let load = |expression: &Expression<F>, row| {
expression.evaluate(
&|scalar| scalar,
&|index| {
let column_index = self.cs.fixed_queries[index].0.index();
self.fixed[column_index][row as usize]
},
&|index| {
let column_index = self.cs.advice_queries[index].0.index();
self.advice[column_index][row as usize]
},
&|index| {
let column_index = self.cs.instance_queries[index].0.index();
self.instance[column_index][row as usize]
},
&|a, b| a + b,
&|a, b| a * b,
&|a, scalar| a * scalar,
)
};
let inputs: Vec<_> = lookup
.input_columns
.input_expressions
.iter()
.map(|c| load(c, input_row))
.collect();
if !(0..n)
.map(|table_row| lookup.table_columns.iter().map(move |c| load(c, table_row)))
.any(|table_row| table_row.eq(inputs.iter().cloned()))
{
let lookup_passes = (0..n)
.map(|table_row| {
lookup
.table_expressions
.iter()
.map(move |c| load(c, table_row))
})
.any(|table_row| table_row.eq(inputs.iter().cloned()));
if !lookup_passes {
return Err(VerifyFailure::Lookup {
lookup_index,
row: input_row as usize,

View File

@ -467,8 +467,12 @@ fn test_proving() {
* ... ... ... 0 0
* ]
*/
meta.lookup(&[a.into()], &[sl.into()]);
meta.lookup(&[a.into(), b.into()], &[sl.into(), sl2.into()]);
let a_ = meta.query_any(a.into(), Rotation::cur());
let b_ = meta.query_any(b.into(), Rotation::cur());
let sl_ = meta.query_any(sl.into(), Rotation::cur());
let sl2_ = meta.query_any(sl2.into(), Rotation::cur());
meta.lookup(&[a_.clone()], &[sl_.clone()]);
meta.lookup(&[a_ * b_], &[sl_ * sl2_]);
meta.create_gate("Combined add-mult", |meta| {
let d = meta.query_advice(d, Rotation::next());
@ -905,39 +909,37 @@ fn test_proving() {
],
lookups: [
Argument {
input_columns: [
Column {
index: 1,
column_type: Advice,
},
input_expressions: [
Advice(
0,
),
],
table_columns: [
Column {
index: 6,
column_type: Fixed,
},
table_expressions: [
Fixed(
0,
),
],
},
Argument {
input_columns: [
Column {
index: 1,
column_type: Advice,
},
Column {
index: 2,
column_type: Advice,
},
input_expressions: [
Product(
Advice(
0,
),
Advice(
1,
),
),
],
table_columns: [
Column {
index: 6,
column_type: Fixed,
},
Column {
index: 7,
column_type: Fixed,
},
table_expressions: [
Product(
Fixed(
0,
),
Fixed(
1,
),
),
],
},
],

View File

@ -224,6 +224,8 @@ pub trait Circuit<F: Field> {
/// Low-degree expression representing an identity that must hold over the committed columns.
#[derive(Clone, Debug)]
pub enum Expression<F> {
/// This is a constant polynomial
Constant(F),
/// This is a fixed column queried at a certain relative location
Fixed(usize),
/// This is an advice (witness) column queried at a certain relative location
@ -243,6 +245,7 @@ impl<F: Field> Expression<F> {
/// operations.
pub fn evaluate<T>(
&self,
constant: &impl Fn(F) -> T,
fixed_column: &impl Fn(usize) -> T,
advice_column: &impl Fn(usize) -> T,
instance_column: &impl Fn(usize) -> T,
@ -251,11 +254,13 @@ impl<F: Field> Expression<F> {
scaled: &impl Fn(T, F) -> T,
) -> T {
match self {
Expression::Constant(scalar) => constant(*scalar),
Expression::Fixed(index) => fixed_column(*index),
Expression::Advice(index) => advice_column(*index),
Expression::Instance(index) => instance_column(*index),
Expression::Sum(a, b) => {
let a = a.evaluate(
constant,
fixed_column,
advice_column,
instance_column,
@ -264,6 +269,7 @@ impl<F: Field> Expression<F> {
scaled,
);
let b = b.evaluate(
constant,
fixed_column,
advice_column,
instance_column,
@ -275,6 +281,7 @@ impl<F: Field> Expression<F> {
}
Expression::Product(a, b) => {
let a = a.evaluate(
constant,
fixed_column,
advice_column,
instance_column,
@ -283,6 +290,7 @@ impl<F: Field> Expression<F> {
scaled,
);
let b = b.evaluate(
constant,
fixed_column,
advice_column,
instance_column,
@ -294,6 +302,7 @@ impl<F: Field> Expression<F> {
}
Expression::Scaled(a, f) => {
let a = a.evaluate(
constant,
fixed_column,
advice_column,
instance_column,
@ -309,6 +318,7 @@ impl<F: Field> Expression<F> {
/// Compute the degree of this polynomial
pub fn degree(&self) -> usize {
match self {
Expression::Constant(_) => 0,
Expression::Fixed(_) => 1,
Expression::Advice(_) => 1,
Expression::Instance(_) => 1,
@ -362,7 +372,7 @@ pub(crate) struct PointIndex(pub usize);
/// This is a description of the circuit environment, such as the gate, column and
/// permutation arrangements.
#[derive(Debug, Clone)]
pub struct ConstraintSystem<F> {
pub struct ConstraintSystem<F: Field> {
pub(crate) num_fixed_columns: usize,
pub(crate) num_advice_columns: usize,
pub(crate) num_instance_columns: usize,
@ -376,8 +386,8 @@ pub struct ConstraintSystem<F> {
pub(crate) permutations: Vec<permutation::Argument>,
// Vector of lookup arguments, where each corresponds to a sequence of
// input columns and a sequence of table columns involved in the lookup.
pub(crate) lookups: Vec<lookup::Argument>,
// input expressions and a sequence of table expressions involved in the lookup.
pub(crate) lookups: Vec<lookup::Argument<F>>,
}
/// Represents the minimal parameters that determine a `ConstraintSystem`.
@ -391,7 +401,7 @@ pub struct PinnedConstraintSystem<'a, F: Field> {
instance_queries: &'a Vec<(Column<Instance>, Rotation)>,
fixed_queries: &'a Vec<(Column<Fixed>, Rotation)>,
permutations: &'a Vec<permutation::Argument>,
lookups: &'a Vec<lookup::Argument>,
lookups: &'a Vec<lookup::Argument<F>>,
}
struct PinnedGates<'a, F: Field>(&'a Vec<(&'static str, Expression<F>)>);
@ -451,26 +461,20 @@ impl<F: Field> ConstraintSystem<F> {
index
}
/// Add a lookup argument for some input columns and table columns.
/// The function will panic if the number of input columns and table
/// columns are not the same.
/// Add a lookup argument for some input expressions and table expressions.
/// The function will panic if the number of input expressions and table
/// expressions are not the same.
pub fn lookup(
&mut self,
input_columns: &[Column<Any>],
table_columns: &[Column<Any>],
input_expressions: &[Expression<F>],
table_expressions: &[Expression<F>],
) -> usize {
assert_eq!(input_columns.len(), table_columns.len());
assert_eq!(input_expressions.len(), table_expressions.len());
let index = self.lookups.len();
for input in input_columns {
self.query_any_index(*input, Rotation::cur());
}
for table in table_columns {
self.query_any_index(*table, Rotation::cur());
}
self.lookups
.push(lookup::Argument::new(input_columns, table_columns));
.push(lookup::Argument::new(input_expressions, table_expressions));
index
}

View File

@ -1,30 +1,31 @@
use super::circuit::{Any, Column};
use super::circuit::Expression;
use ff::Field;
pub(crate) mod prover;
pub(crate) mod verifier;
#[derive(Clone, Debug)]
pub(crate) struct Argument {
pub input_columns: Vec<Column<Any>>,
pub table_columns: Vec<Column<Any>>,
pub(crate) struct Argument<F: Field> {
pub input_expressions: Vec<Expression<F>>,
pub table_expressions: Vec<Expression<F>>,
}
impl Argument {
pub fn new(input_columns: &[Column<Any>], table_columns: &[Column<Any>]) -> Self {
assert_eq!(input_columns.len(), table_columns.len());
impl<F: Field> Argument<F> {
pub fn new(input_expressions: &[Expression<F>], table_expressions: &[Expression<F>]) -> Self {
assert_eq!(input_expressions.len(), table_expressions.len());
Argument {
input_columns: input_columns.to_vec(),
table_columns: table_columns.to_vec(),
input_expressions: input_expressions.to_vec(),
table_expressions: table_expressions.to_vec(),
}
}
pub(crate) fn required_degree(&self) -> usize {
assert_eq!(self.input_columns.len(), self.table_columns.len());
assert_eq!(self.input_expressions.len(), self.table_expressions.len());
// degree 2:
// l_0(X) * (1 - z'(X)) = 0
//
// degree 3:
// degree (1 + input_degree + table_degree):
// z'(X) (a'(X) + \beta) (s'(X) + \gamma)
// - z'(\omega^{-1} X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma)
//
@ -33,6 +34,15 @@ impl Argument {
//
// degree 2:
// (a(X)s(X))⋅(a(X)a(\omega{-1} X)) = 0
3
let mut input_degree = 1;
for expr in self.input_expressions.iter() {
input_degree = std::cmp::max(input_degree, expr.degree());
}
let mut table_degree = 1;
for expr in self.table_expressions.iter() {
table_degree = std::cmp::max(table_degree, expr.degree());
}
1 + input_degree + table_degree
}
}

View File

@ -1,6 +1,6 @@
use super::super::{
circuit::{Any, Column},
ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, ProvingKey,
circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error,
ProvingKey,
};
use super::Argument;
use crate::{
@ -13,21 +13,25 @@ use crate::{
transcript::TranscriptWrite,
};
use ff::Field;
use std::{collections::BTreeMap, iter};
use std::{
collections::BTreeMap,
iter,
ops::{Mul, MulAssign},
};
#[derive(Debug)]
pub(in crate::plonk) struct Permuted<'a, C: CurveAffine> {
unpermuted_input_columns: Vec<&'a Polynomial<C::Scalar, LagrangeCoeff>>,
unpermuted_input_cosets: Vec<&'a Polynomial<C::Scalar, ExtendedLagrangeCoeff>>,
permuted_input_column: Polynomial<C::Scalar, LagrangeCoeff>,
pub(in crate::plonk) struct Permuted<C: CurveAffine> {
unpermuted_input_expressions: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
unpermuted_input_cosets: Vec<Polynomial<C::Scalar, ExtendedLagrangeCoeff>>,
permuted_input_expression: Polynomial<C::Scalar, LagrangeCoeff>,
permuted_input_poly: Polynomial<C::Scalar, Coeff>,
permuted_input_coset: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
permuted_input_inv_coset: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
permuted_input_blind: Blind<C::Scalar>,
permuted_input_commitment: C,
unpermuted_table_columns: Vec<&'a Polynomial<C::Scalar, LagrangeCoeff>>,
unpermuted_table_cosets: Vec<&'a Polynomial<C::Scalar, ExtendedLagrangeCoeff>>,
permuted_table_column: Polynomial<C::Scalar, LagrangeCoeff>,
unpermuted_table_expressions: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
unpermuted_table_cosets: Vec<Polynomial<C::Scalar, ExtendedLagrangeCoeff>>,
permuted_table_expression: Polynomial<C::Scalar, LagrangeCoeff>,
permuted_table_poly: Polynomial<C::Scalar, Coeff>,
permuted_table_coset: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
permuted_table_blind: Blind<C::Scalar>,
@ -35,8 +39,8 @@ pub(in crate::plonk) struct Permuted<'a, C: CurveAffine> {
}
#[derive(Debug)]
pub(in crate::plonk) struct Committed<'a, C: CurveAffine> {
permuted: Permuted<'a, C>,
pub(in crate::plonk) struct Committed<C: CurveAffine> {
permuted: Permuted<C>,
product_poly: Polynomial<C::Scalar, Coeff>,
product_coset: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
product_inv_coset: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
@ -57,17 +61,17 @@ pub(in crate::plonk) struct Evaluated<C: CurveAffine> {
constructed: Constructed<C>,
}
impl Argument {
/// Given a Lookup with input columns [A_0, A_1, ..., A_{m-1}] and table columns
impl<F: FieldExt> Argument<F> {
/// Given a Lookup with input expressions [A_0, A_1, ..., A_{m-1}] and table expressions
/// [S_0, S_1, ..., S_{m-1}], this method
/// - constructs A_compressed = \theta^{m-1} A_0 + theta^{m-2} A_1 + ... + \theta A_{m-2} + A_{m-1}
/// and S_compressed = \theta^{m-1} S_0 + theta^{m-2} S_1 + ... + \theta S_{m-2} + S_{m-1},
/// - permutes A_compressed and S_compressed using permute_column_pair() helper,
/// - permutes A_compressed and S_compressed using permute_expression_pair() helper,
/// obtaining A' and S', and
/// - constructs Permuted<C> struct using permuted_input_value = A', and
/// permuted_table_column = S'.
/// permuted_table_expression = S'.
/// The Permuted<C> struct is used to update the Lookup, and is then returned.
pub(in crate::plonk) fn commit_permuted<'a, C: CurveAffine, T: TranscriptWrite<C>>(
pub(in crate::plonk) fn commit_permuted<'a, C, T: TranscriptWrite<C>>(
&self,
pk: &ProvingKey<C>,
params: &Params<C>,
@ -80,60 +84,115 @@ impl Argument {
fixed_cosets: &'a [Polynomial<C::Scalar, ExtendedLagrangeCoeff>],
instance_cosets: &'a [Polynomial<C::Scalar, ExtendedLagrangeCoeff>],
transcript: &mut T,
) -> Result<Permuted<'a, C>, Error> {
// Closure to get values of columns and compress them
let compress_columns = |columns: &[Column<Any>]| {
// Values of input columns involved in the lookup
let (unpermuted_columns, unpermuted_cosets): (Vec<_>, Vec<_>) = columns
) -> Result<Permuted<C>, Error>
where
C: CurveAffine<Scalar = F>,
C::Projective: Mul<F, Output = C::Projective> + MulAssign<F>,
{
// Closure to get values of expressions and compress them
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
// Values of input expressions involved in the lookup
let unpermuted_expressions: Vec<_> = expressions
.iter()
.map(|&column| {
let (values, cosets) = match column.column_type() {
Any::Advice => (advice_values, advice_cosets),
Any::Fixed => (fixed_values, fixed_cosets),
Any::Instance => (instance_values, instance_cosets),
};
(
&values[column.index()],
&cosets[pk.vk.cs.get_any_query_index(column, Rotation::cur())],
.map(|expression| {
expression.evaluate(
&|scalar| pk.vk.domain.constant_lagrange(scalar),
&|index| {
let query = pk.vk.cs.fixed_queries[index];
let column_index = query.0.index();
let rotation = query.1;
fixed_values[column_index].clone().rotate(rotation)
},
&|index| {
let query = pk.vk.cs.advice_queries[index];
let column_index = query.0.index();
let rotation = query.1;
advice_values[column_index].clone().rotate(rotation)
},
&|index| {
let query = pk.vk.cs.instance_queries[index];
let column_index = query.0.index();
let rotation = query.1;
instance_values[column_index].clone().rotate(rotation)
},
&|a, b| a + &b,
&|a, b| {
let mut modified_a = vec![C::Scalar::one(); params.n as usize];
parallelize(&mut modified_a, |modified_a, start| {
for ((modified_a, a), b) in modified_a
.iter_mut()
.zip(a[start..].iter())
.zip(b[start..].iter())
{
*modified_a *= *a * b;
}
});
pk.vk.domain.lagrange_from_vec(modified_a)
},
&|a, scalar| a * scalar,
)
})
.unzip();
.collect();
// Compressed version of columns
let compressed_column = unpermuted_columns
let unpermuted_cosets: Vec<_> = expressions
.iter()
.fold(domain.empty_lagrange(), |acc, column| acc * *theta + column);
.map(|expression| {
expression.evaluate(
&|scalar| pk.vk.domain.constant_extended(scalar),
&|index| fixed_cosets[index].clone(),
&|index| advice_cosets[index].clone(),
&|index| instance_cosets[index].clone(),
&|a, b| a + &b,
&|a, b| a * &b,
&|a, scalar| a * scalar,
)
})
.collect();
(unpermuted_columns, unpermuted_cosets, compressed_column)
// Compressed version of expressions
let compressed_expression = unpermuted_expressions
.iter()
.fold(domain.empty_lagrange(), |acc, expression| {
acc * *theta + expression
});
(
unpermuted_expressions,
unpermuted_cosets,
compressed_expression,
)
};
// Closure to construct commitment to column of values
let commit_column = |column: &Polynomial<C::Scalar, LagrangeCoeff>| {
let poly = pk.vk.domain.lagrange_to_coeff(column.clone());
// Closure to construct commitment to vector of values
let commit_values = |values: &Polynomial<C::Scalar, LagrangeCoeff>| {
let poly = pk.vk.domain.lagrange_to_coeff(values.clone());
let blind = Blind(C::Scalar::rand());
let commitment = params.commit_lagrange(&column, blind).to_affine();
let commitment = params.commit_lagrange(&values, blind).to_affine();
(poly, blind, commitment)
};
// Get values of input columns involved in the lookup and compress them
let (unpermuted_input_columns, unpermuted_input_cosets, compressed_input_column) =
compress_columns(&self.input_columns);
// Get values of input expressions involved in the lookup and compress them
let (unpermuted_input_expressions, unpermuted_input_cosets, compressed_input_expression) =
compress_expressions(&self.input_expressions);
// Get values of table columns involved in the lookup and compress them
let (unpermuted_table_columns, unpermuted_table_cosets, compressed_table_column) =
compress_columns(&self.table_columns);
// Get values of table expressions involved in the lookup and compress them
let (unpermuted_table_expressions, unpermuted_table_cosets, compressed_table_expression) =
compress_expressions(&self.table_expressions);
// Permute compressed (InputColumn, TableColumn) pair
let (permuted_input_column, permuted_table_column) =
permute_column_pair::<C>(domain, &compressed_input_column, &compressed_table_column)?;
// Permute compressed (InputExpression, TableExpression) pair
let (permuted_input_expression, permuted_table_expression) = permute_expression_pair::<C>(
domain,
&compressed_input_expression,
&compressed_table_expression,
)?;
// Commit to permuted input column
// Commit to permuted input expression
let (permuted_input_poly, permuted_input_blind, permuted_input_commitment) =
commit_column(&permuted_input_column);
commit_values(&permuted_input_expression);
// Commit to permuted table column
// Commit to permuted table expression
let (permuted_table_poly, permuted_table_blind, permuted_table_commitment) =
commit_column(&permuted_table_column);
commit_values(&permuted_table_expression);
// Hash permuted input commitment
transcript
@ -159,17 +218,17 @@ impl Argument {
.coeff_to_extended(permuted_table_poly.clone(), Rotation::cur());
Ok(Permuted {
unpermuted_input_columns,
unpermuted_input_expressions,
unpermuted_input_cosets,
permuted_input_column,
permuted_input_expression,
permuted_input_poly,
permuted_input_coset,
permuted_input_inv_coset,
permuted_input_blind,
permuted_input_commitment,
unpermuted_table_columns,
unpermuted_table_expressions,
unpermuted_table_cosets,
permuted_table_column,
permuted_table_expression,
permuted_table_poly,
permuted_table_coset,
permuted_table_blind,
@ -178,9 +237,9 @@ impl Argument {
}
}
impl<'a, C: CurveAffine> Permuted<'a, C> {
/// Given a Lookup with input columns, table columns, and the permuted
/// input column and permuted table column, this method constructs the
impl<C: CurveAffine> Permuted<C> {
/// Given a Lookup with input expressions, table expressions, and the permuted
/// input expression and permuted table expression, this method constructs the
/// grand product polynomial over the lookup. The grand product polynomial
/// is used to populate the Product<C> struct. The Product<C> struct is
/// added to the Lookup and finally returned by the method.
@ -192,25 +251,25 @@ impl<'a, C: CurveAffine> Permuted<'a, C> {
beta: ChallengeBeta<C>,
gamma: ChallengeGamma<C>,
transcript: &mut T,
) -> Result<Committed<'a, C>, Error> {
) -> Result<Committed<C>, Error> {
// Goal is to compute the products of fractions
//
// Numerator: (\theta^{m-1} a_0(\omega^i) + \theta^{m-2} a_1(\omega^i) + ... + \theta a_{m-2}(\omega^i) + a_{m-1}(\omega^i) + \beta)
// * (\theta^{m-1} s_0(\omega^i) + \theta^{m-2} s_1(\omega^i) + ... + \theta s_{m-2}(\omega^i) + s_{m-1}(\omega^i) + \gamma)
// Denominator: (a'(\omega^i) + \beta) (s'(\omega^i) + \gamma)
//
// where a_j(X) is the jth input column in this lookup,
// where a'(X) is the compression of the permuted input columns,
// s_j(X) is the jth table column in this lookup,
// s'(X) is the compression of the permuted table columns,
// and i is the ith row of the column.
// where a_j(X) is the jth input expression in this lookup,
// where a'(X) is the compression of the permuted input expressions,
// s_j(X) is the jth table expression in this lookup,
// s'(X) is the compression of the permuted table expressions,
// and i is the ith row of the expression.
let mut lookup_product = vec![C::Scalar::zero(); params.n as usize];
// Denominator uses the permuted input column and permuted table column
// Denominator uses the permuted input expression and permuted table expression
parallelize(&mut lookup_product, |lookup_product, start| {
for ((lookup_product, permuted_input_value), permuted_table_value) in lookup_product
.iter_mut()
.zip(self.permuted_input_column[start..].iter())
.zip(self.permuted_table_column[start..].iter())
.zip(self.permuted_input_expression[start..].iter())
.zip(self.permuted_table_expression[start..].iter())
{
*lookup_product = (*beta + permuted_input_value) * &(*gamma + permuted_table_value);
}
@ -227,18 +286,18 @@ impl<'a, C: CurveAffine> Permuted<'a, C> {
for (i, product) in product.iter_mut().enumerate() {
let i = i + start;
// Compress unpermuted input columns
// Compress unpermuted input expressions
let mut input_term = C::Scalar::zero();
for unpermuted_input_column in self.unpermuted_input_columns.iter() {
for unpermuted_input_expression in self.unpermuted_input_expressions.iter() {
input_term *= &*theta;
input_term += &unpermuted_input_column[i];
input_term += &unpermuted_input_expression[i];
}
// Compress unpermuted table columns
// Compress unpermuted table expressions
let mut table_term = C::Scalar::zero();
for unpermuted_table_column in self.unpermuted_table_columns.iter() {
for unpermuted_table_expression in self.unpermuted_table_expressions.iter() {
table_term *= &*theta;
table_term += &unpermuted_table_column[i];
table_term += &unpermuted_table_expression[i];
}
*product *= &(input_term + &*beta);
@ -252,12 +311,12 @@ impl<'a, C: CurveAffine> Permuted<'a, C> {
// * (\theta^{m-1} s_0(\omega^i) + \theta^{m-2} s_1(\omega^i) + ... + \theta s_{m-2}(\omega^i) + s_{m-1}(\omega^i) + \gamma)
// Denominator: (a'(\omega^i) + \beta) (s'(\omega^i) + \gamma)
//
// where there are m input columns and m table columns,
// a_j(\omega^i) is the jth input column in this lookup,
// a'j(\omega^i) is the permuted input column,
// s_j(\omega^i) is the jth table column in this lookup,
// s'(\omega^i) is the permuted table column,
// and i is the ith row of the column.
// where there are m input expressions and m table expressions,
// a_j(\omega^i) is the jth input expression in this lookup,
// a'j(\omega^i) is the permuted input expression,
// s_j(\omega^i) is the jth table expression in this lookup,
// s'(\omega^i) is the permuted table expression,
// and i is the ith row of the expression.
// Compute the evaluations of the lookup product polynomial
// over our domain, starting with z[0] = 1
@ -283,21 +342,21 @@ impl<'a, C: CurveAffine> Permuted<'a, C> {
let prev_idx = (n + i - 1) % n;
let mut left = z[i];
let permuted_input_value = &self.permuted_input_column[i];
let permuted_input_value = &self.permuted_input_expression[i];
let permuted_table_value = &self.permuted_table_column[i];
let permuted_table_value = &self.permuted_table_expression[i];
left *= &(*beta + permuted_input_value);
left *= &(*gamma + permuted_table_value);
let mut right = z[prev_idx];
let mut input_term = self
.unpermuted_input_columns
.unpermuted_input_expressions
.iter()
.fold(C::Scalar::zero(), |acc, input| acc * &*theta + &input[i]);
let mut table_term = self
.unpermuted_table_columns
.unpermuted_table_expressions
.iter()
.fold(C::Scalar::zero(), |acc, table| acc * &*theta + &table[i]);
@ -320,7 +379,7 @@ impl<'a, C: CurveAffine> Permuted<'a, C> {
.write_point(product_commitment)
.map_err(|_| Error::TranscriptError)?;
Ok(Committed::<'a, C> {
Ok(Committed::<C> {
permuted: self,
product_poly: z,
product_coset,
@ -331,9 +390,9 @@ impl<'a, C: CurveAffine> Permuted<'a, C> {
}
}
impl<'a, C: CurveAffine> Committed<'a, C> {
/// Given a Lookup with input columns, table columns, permuted input
/// column, permuted table column, and grand product polynomial, this
impl<'a, C: CurveAffine> Committed<C> {
/// Given a Lookup with input expressions, table expressions, permuted input
/// expression, permuted table expression, and grand product polynomial, this
/// method constructs constraints that must hold between these values.
/// This method returns the constraints as a vector of polynomials in
/// the extended evaluation domain.
@ -376,14 +435,14 @@ impl<'a, C: CurveAffine> Committed<'a, C> {
for (i, right) in right.iter_mut().enumerate() {
let i = i + start;
// Compress the unpermuted input columns
// Compress the unpermuted input expressions
let mut input_term = C::Scalar::zero();
for input in permuted.unpermuted_input_cosets.iter() {
input_term *= &*theta;
input_term += &input[i];
}
// Compress the unpermuted table columns
// Compress the unpermuted table expressions
let mut table_term = C::Scalar::zero();
for table in permuted.unpermuted_table_cosets.iter() {
table_term *= &*theta;
@ -398,15 +457,15 @@ impl<'a, C: CurveAffine> Committed<'a, C> {
Some(left - &right)
})
// Check that the first values in the permuted input column and permuted
// fixed column are the same.
// Check that the first values in the permuted input expression and permuted
// fixed expression are the same.
// l_0(X) * (a'(X) - s'(X)) = 0
.chain(Some(
(permuted.permuted_input_coset.clone() - &permuted.permuted_table_coset) * &pk.l0,
))
// Check that each value in the permuted lookup input column is either
// Check that each value in the permuted lookup input expression is either
// equal to the value above it, or the value at the same index in the
// permuted table column.
// permuted table expression.
// (a(X)s(X))⋅(a(X)a(\omega{-1} X)) = 0
.chain(Some(
(permuted.permuted_input_coset.clone() - &permuted.permuted_table_coset)
@ -502,39 +561,41 @@ impl<C: CurveAffine> Evaluated<C> {
}
}
type ColumnPair<F> = (Polynomial<F, LagrangeCoeff>, Polynomial<F, LagrangeCoeff>);
type ExpressionPair<F> = (Polynomial<F, LagrangeCoeff>, Polynomial<F, LagrangeCoeff>);
/// Given a column of input values A and a column of table values S,
/// Given a vector of input values A and a vector of table values S,
/// this method permutes A and S to produce A' and S', such that:
/// - like values in A' are vertically adjacent to each other; and
/// - the first row in a sequence of like values in A' is the row
/// that has the corresponding value in S'.
/// This method returns (A', S') if no errors are encountered.
fn permute_column_pair<C: CurveAffine>(
fn permute_expression_pair<C: CurveAffine>(
domain: &EvaluationDomain<C::Scalar>,
input_column: &Polynomial<C::Scalar, LagrangeCoeff>,
table_column: &Polynomial<C::Scalar, LagrangeCoeff>,
) -> Result<ColumnPair<C::Scalar>, Error> {
let mut permuted_input_column = input_column.clone();
input_expression: &Polynomial<C::Scalar, LagrangeCoeff>,
table_expression: &Polynomial<C::Scalar, LagrangeCoeff>,
) -> Result<ExpressionPair<C::Scalar>, Error> {
let mut permuted_input_expression = input_expression.clone();
// Sort input lookup column values
permuted_input_column.sort();
// Sort input lookup expression values
permuted_input_expression.sort();
// A BTreeMap of each unique element in the table column and its count
// A BTreeMap of each unique element in the table expression and its count
let mut leftover_table_map: BTreeMap<C::Scalar, u32> =
table_column.iter().fold(BTreeMap::new(), |mut acc, coeff| {
*acc.entry(*coeff).or_insert(0) += 1;
acc
});
let mut permuted_table_coeffs = vec![C::Scalar::zero(); table_column.len()];
table_expression
.iter()
.fold(BTreeMap::new(), |mut acc, coeff| {
*acc.entry(*coeff).or_insert(0) += 1;
acc
});
let mut permuted_table_coeffs = vec![C::Scalar::zero(); table_expression.len()];
let mut repeated_input_rows = permuted_input_column
let mut repeated_input_rows = permuted_input_expression
.iter()
.zip(permuted_table_coeffs.iter_mut())
.enumerate()
.filter_map(|(row, (input_value, table_value))| {
// If this is the first occurence of `input_value` in the input column
if row == 0 || *input_value != permuted_input_column[row - 1] {
// If this is the first occurence of `input_value` in the input expression
if row == 0 || *input_value != permuted_input_expression[row - 1] {
*table_value = *input_value;
// Remove one instance of input_value from leftover_table_map
if let Some(count) = leftover_table_map.get_mut(&input_value) {
@ -560,11 +621,11 @@ fn permute_column_pair<C: CurveAffine>(
}
assert!(repeated_input_rows.is_empty());
let mut permuted_table_column = domain.empty_lagrange();
let mut permuted_table_expression = domain.empty_lagrange();
parallelize(
&mut permuted_table_column,
|permuted_table_column, start| {
for (permuted_table_value, permuted_table_coeff) in permuted_table_column
&mut permuted_table_expression,
|permuted_table_expression, start| {
for (permuted_table_value, permuted_table_coeff) in permuted_table_expression
.iter_mut()
.zip(permuted_table_coeffs[start..].iter())
{
@ -573,5 +634,5 @@ fn permute_column_pair<C: CurveAffine>(
},
);
Ok((permuted_input_column, permuted_table_column))
Ok((permuted_input_expression, permuted_table_expression))
}

View File

@ -1,9 +1,9 @@
use std::iter;
use super::super::circuit::{Any, Column};
use super::super::circuit::Expression;
use super::Argument;
use crate::{
arithmetic::CurveAffine,
arithmetic::{CurveAffine, FieldExt},
plonk::{ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, VerifyingKey},
poly::{multiopen::VerifierQuery, Rotation},
transcript::TranscriptRead,
@ -29,7 +29,7 @@ pub struct Evaluated<C: CurveAffine> {
permuted_table_eval: C::Scalar,
}
impl Argument {
impl<F: FieldExt> Argument<F> {
pub(in crate::plonk) fn read_permuted_commitments<C: CurveAffine, T: TranscriptRead<C>>(
&self,
transcript: &mut T,
@ -99,9 +99,8 @@ impl<C: CurveAffine> Committed<C> {
impl<C: CurveAffine> Evaluated<C> {
pub(in crate::plonk) fn expressions<'a>(
&'a self,
vk: &'a VerifyingKey<C>,
l_0: C::Scalar,
argument: &'a Argument,
argument: &'a Argument<C::Scalar>,
theta: ChallengeTheta<C>,
beta: ChallengeBeta<C>,
gamma: ChallengeGamma<C>,
@ -116,22 +115,25 @@ impl<C: CurveAffine> Evaluated<C> {
* &(self.permuted_input_eval + &*beta)
* &(self.permuted_table_eval + &*gamma);
let compress_columns = |columns: &[Column<Any>]| {
columns
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
expressions
.iter()
.map(|column| {
let index = vk.cs.get_any_query_index(*column, Rotation::cur());
match column.column_type() {
Any::Advice => advice_evals[index],
Any::Fixed => fixed_evals[index],
Any::Instance => instance_evals[index],
}
.map(|expression| {
expression.evaluate(
&|scalar| scalar,
&|index| fixed_evals[index],
&|index| advice_evals[index],
&|index| instance_evals[index],
&|a, b| a + &b,
&|a, b| a * &b,
&|a, scalar| a * scalar,
)
})
.fold(C::Scalar::zero(), |acc, eval| acc * &*theta + &eval)
};
let right = self.product_inv_eval
* &(compress_columns(&argument.input_columns) + &*beta)
* &(compress_columns(&argument.table_columns) + &*gamma);
* &(compress_expressions(&argument.input_expressions) + &*beta)
* &(compress_expressions(&argument.table_expressions) + &*gamma);
left - &right
};

View File

@ -246,7 +246,7 @@ pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>, ConcreteCircuit: Circ
// Sample theta challenge for keeping lookup columns linearly independent
let theta = ChallengeTheta::get(transcript);
let lookups: Vec<Vec<lookup::prover::Permuted<'_, C>>> = instance
let lookups: Vec<Vec<lookup::prover::Permuted<C>>> = instance
.iter()
.zip(advice.iter())
.map(|(instance, advice)| -> Result<Vec<_>, Error> {
@ -307,7 +307,7 @@ pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>, ConcreteCircuit: Circ
})
.collect::<Result<Vec<_>, _>>()?;
let lookups: Vec<Vec<lookup::prover::Committed<'_, C>>> = lookups
let lookups: Vec<Vec<lookup::prover::Committed<C>>> = lookups
.into_iter()
.map(|lookups| -> Result<Vec<_>, _> {
// Construct and commit to products for each lookup
@ -373,6 +373,7 @@ pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>, ConcreteCircuit: Circ
// Custom constraints
.chain(meta.gates.iter().map(move |(_, poly)| {
poly.evaluate(
&|scalar| pk.vk.domain.constant_extended(scalar),
&|index| pk.fixed_cosets[index].clone(),
&|index| advice.advice_cosets[index].clone(),
&|index| instance.instance_cosets[index].clone(),

View File

@ -168,6 +168,7 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead<C>>(
// Evaluate the circuit using the custom gates provided
.chain(vk.cs.gates.iter().map(move |(_, poly)| {
poly.evaluate(
&|scalar| scalar,
&|index| fixed_evals[index],
&|index| advice_evals[index],
&|index| instance_evals[index],
@ -201,7 +202,6 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead<C>>(
.zip(vk.cs.lookups.iter())
.flat_map(move |(p, argument)| {
p.expressions(
vk,
l_0,
argument,
theta,

View File

@ -187,6 +187,22 @@ impl<'a, F: Field> Mul<&'a Polynomial<F, ExtendedLagrangeCoeff>>
}
}
impl<'a, F: Field> Polynomial<F, LagrangeCoeff> {
/// Rotates the values in a Lagrange basis polynomial by `Rotation`
pub fn rotate(&self, rotation: Rotation) -> Polynomial<F, LagrangeCoeff> {
let mut values = self.values.clone();
if rotation.0 < 0 {
values.rotate_right((-rotation.0) as usize);
} else {
values.rotate_left(rotation.0 as usize);
}
Polynomial {
values,
_marker: PhantomData,
}
}
}
impl<'a, F: Field, B: Basis> Mul<F> for Polynomial<F, B> {
type Output = Polynomial<F, B>;

View File

@ -177,6 +177,14 @@ impl<G: Group> EvaluationDomain<G> {
}
}
/// Returns a constant polynomial in the Lagrange coefficient basis
pub fn constant_lagrange(&self, scalar: G) -> Polynomial<G, LagrangeCoeff> {
Polynomial {
values: vec![scalar; self.n as usize],
_marker: PhantomData,
}
}
/// Returns an empty (zero) polynomial in the extended Lagrange coefficient
/// basis
pub fn empty_extended(&self) -> Polynomial<G, ExtendedLagrangeCoeff> {
@ -186,6 +194,15 @@ impl<G: Group> EvaluationDomain<G> {
}
}
/// Returns a constant polynomial in the extended Lagrange coefficient
/// basis
pub fn constant_extended(&self, scalar: G) -> Polynomial<G, ExtendedLagrangeCoeff> {
Polynomial {
values: vec![scalar; self.extended_len()],
_marker: PhantomData,
}
}
/// This takes us from an n-length vector into the coefficient form.
///
/// This function will panic if the provided vector is not the correct
@ -396,3 +413,40 @@ pub struct PinnedEvaluationDomain<'a, G: Group> {
extended_k: &'a u32,
omega: &'a G::Scalar,
}
#[test]
fn test_rotate() {
use crate::arithmetic::eval_polynomial;
use crate::pasta::pallas::Scalar;
let domain = EvaluationDomain::<Scalar>::new(1, 3);
let mut poly = domain.empty_lagrange();
assert_eq!(poly.len(), 8);
for value in poly.iter_mut() {
*value = Scalar::rand();
}
let poly_rotated_cur = poly.rotate(Rotation::cur());
let poly_rotated_next = poly.rotate(Rotation::next());
let poly_rotated_prev = poly.rotate(Rotation::prev());
let poly = domain.lagrange_to_coeff(poly);
let poly_rotated_cur = domain.lagrange_to_coeff(poly_rotated_cur);
let poly_rotated_next = domain.lagrange_to_coeff(poly_rotated_next);
let poly_rotated_prev = domain.lagrange_to_coeff(poly_rotated_prev);
let x = Scalar::rand();
assert_eq!(
eval_polynomial(&poly[..], x),
eval_polynomial(&poly_rotated_cur[..], x)
);
assert_eq!(
eval_polynomial(&poly[..], x * domain.omega),
eval_polynomial(&poly_rotated_next[..], x)
);
assert_eq!(
eval_polynomial(&poly[..], x * domain.omega_inv),
eval_polynomial(&poly_rotated_prev[..], x)
);
}