diff --git a/examples/circuit-layout.rs b/examples/circuit-layout.rs index 045e50e..ccd9338 100644 --- a/examples/circuit-layout.rs +++ b/examples/circuit-layout.rs @@ -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()); diff --git a/src/dev.rs b/src/dev.rs index 02950bf..7bdbcee 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -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 { +pub struct MockProver { n: u32, cs: ConstraintSystem, @@ -296,6 +296,7 @@ impl MockProver { } 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 MockProver { // 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, 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, 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, diff --git a/src/plonk.rs b/src/plonk.rs index 80a05bc..bb9fd31 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -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, + ), + ), ], }, ], diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index bb66484..f9e38b2 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -224,6 +224,8 @@ pub trait Circuit { /// Low-degree expression representing an identity that must hold over the committed columns. #[derive(Clone, Debug)] pub enum Expression { + /// 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 Expression { /// operations. pub fn evaluate( &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 Expression { 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 Expression { scaled, ); let b = b.evaluate( + constant, fixed_column, advice_column, instance_column, @@ -275,6 +281,7 @@ impl Expression { } Expression::Product(a, b) => { let a = a.evaluate( + constant, fixed_column, advice_column, instance_column, @@ -283,6 +290,7 @@ impl Expression { scaled, ); let b = b.evaluate( + constant, fixed_column, advice_column, instance_column, @@ -294,6 +302,7 @@ impl Expression { } Expression::Scaled(a, f) => { let a = a.evaluate( + constant, fixed_column, advice_column, instance_column, @@ -309,6 +318,7 @@ impl Expression { /// 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 { +pub struct ConstraintSystem { 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 { pub(crate) permutations: Vec, // 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, + // input expressions and a sequence of table expressions involved in the lookup. + pub(crate) lookups: Vec>, } /// Represents the minimal parameters that determine a `ConstraintSystem`. @@ -391,7 +401,7 @@ pub struct PinnedConstraintSystem<'a, F: Field> { instance_queries: &'a Vec<(Column, Rotation)>, fixed_queries: &'a Vec<(Column, Rotation)>, permutations: &'a Vec, - lookups: &'a Vec, + lookups: &'a Vec>, } struct PinnedGates<'a, F: Field>(&'a Vec<(&'static str, Expression)>); @@ -451,26 +461,20 @@ impl ConstraintSystem { 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], - table_columns: &[Column], + input_expressions: &[Expression], + table_expressions: &[Expression], ) -> 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 } diff --git a/src/plonk/lookup.rs b/src/plonk/lookup.rs index acb76ff..c3a9f93 100644 --- a/src/plonk/lookup.rs +++ b/src/plonk/lookup.rs @@ -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>, - pub table_columns: Vec>, +pub(crate) struct Argument { + pub input_expressions: Vec>, + pub table_expressions: Vec>, } -impl Argument { - pub fn new(input_columns: &[Column], table_columns: &[Column]) -> Self { - assert_eq!(input_columns.len(), table_columns.len()); +impl Argument { + pub fn new(input_expressions: &[Expression], table_expressions: &[Expression]) -> 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 } } diff --git a/src/plonk/lookup/prover.rs b/src/plonk/lookup/prover.rs index 0078322..5abe772 100644 --- a/src/plonk/lookup/prover.rs +++ b/src/plonk/lookup/prover.rs @@ -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>, - unpermuted_input_cosets: Vec<&'a Polynomial>, - permuted_input_column: Polynomial, +pub(in crate::plonk) struct Permuted { + unpermuted_input_expressions: Vec>, + unpermuted_input_cosets: Vec>, + permuted_input_expression: Polynomial, permuted_input_poly: Polynomial, permuted_input_coset: Polynomial, permuted_input_inv_coset: Polynomial, permuted_input_blind: Blind, permuted_input_commitment: C, - unpermuted_table_columns: Vec<&'a Polynomial>, - unpermuted_table_cosets: Vec<&'a Polynomial>, - permuted_table_column: Polynomial, + unpermuted_table_expressions: Vec>, + unpermuted_table_cosets: Vec>, + permuted_table_expression: Polynomial, permuted_table_poly: Polynomial, permuted_table_coset: Polynomial, permuted_table_blind: Blind, @@ -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 { + permuted: Permuted, product_poly: Polynomial, product_coset: Polynomial, product_inv_coset: Polynomial, @@ -57,17 +61,17 @@ pub(in crate::plonk) struct Evaluated { constructed: Constructed, } -impl Argument { - /// Given a Lookup with input columns [A_0, A_1, ..., A_{m-1}] and table columns +impl Argument { + /// 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 struct using permuted_input_value = A', and - /// permuted_table_column = S'. + /// permuted_table_expression = S'. /// The Permuted struct is used to update the Lookup, and is then returned. - pub(in crate::plonk) fn commit_permuted<'a, C: CurveAffine, T: TranscriptWrite>( + pub(in crate::plonk) fn commit_permuted<'a, C, T: TranscriptWrite>( &self, pk: &ProvingKey, params: &Params, @@ -80,60 +84,115 @@ impl Argument { fixed_cosets: &'a [Polynomial], instance_cosets: &'a [Polynomial], transcript: &mut T, - ) -> Result, Error> { - // Closure to get values of columns and compress them - let compress_columns = |columns: &[Column]| { - // Values of input columns involved in the lookup - let (unpermuted_columns, unpermuted_cosets): (Vec<_>, Vec<_>) = columns + ) -> Result, Error> + where + C: CurveAffine, + C::Projective: Mul + MulAssign, + { + // Closure to get values of expressions and compress them + let compress_expressions = |expressions: &[Expression]| { + // 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| { - let poly = pk.vk.domain.lagrange_to_coeff(column.clone()); + // Closure to construct commitment to vector of values + let commit_values = |values: &Polynomial| { + 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::(domain, &compressed_input_column, &compressed_table_column)?; + // Permute compressed (InputExpression, TableExpression) pair + let (permuted_input_expression, permuted_table_expression) = permute_expression_pair::( + 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 Permuted { + /// 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 struct. The Product 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, gamma: ChallengeGamma, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, 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:: { 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 { + /// 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 Evaluated { } } -type ColumnPair = (Polynomial, Polynomial); +type ExpressionPair = (Polynomial, Polynomial); -/// 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( +fn permute_expression_pair( domain: &EvaluationDomain, - input_column: &Polynomial, - table_column: &Polynomial, -) -> Result, Error> { - let mut permuted_input_column = input_column.clone(); + input_expression: &Polynomial, + table_expression: &Polynomial, +) -> Result, 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 = - 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( } 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( }, ); - Ok((permuted_input_column, permuted_table_column)) + Ok((permuted_input_expression, permuted_table_expression)) } diff --git a/src/plonk/lookup/verifier.rs b/src/plonk/lookup/verifier.rs index 50581ee..e0c779b 100644 --- a/src/plonk/lookup/verifier.rs +++ b/src/plonk/lookup/verifier.rs @@ -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 { permuted_table_eval: C::Scalar, } -impl Argument { +impl Argument { pub(in crate::plonk) fn read_permuted_commitments>( &self, transcript: &mut T, @@ -99,9 +99,8 @@ impl Committed { impl Evaluated { pub(in crate::plonk) fn expressions<'a>( &'a self, - vk: &'a VerifyingKey, l_0: C::Scalar, - argument: &'a Argument, + argument: &'a Argument, theta: ChallengeTheta, beta: ChallengeBeta, gamma: ChallengeGamma, @@ -116,22 +115,25 @@ impl Evaluated { * &(self.permuted_input_eval + &*beta) * &(self.permuted_table_eval + &*gamma); - let compress_columns = |columns: &[Column]| { - columns + let compress_expressions = |expressions: &[Expression]| { + 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 }; diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 15ec08b..8d1c055 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -246,7 +246,7 @@ pub fn create_proof, ConcreteCircuit: Circ // Sample theta challenge for keeping lookup columns linearly independent let theta = ChallengeTheta::get(transcript); - let lookups: Vec>> = instance + let lookups: Vec>> = instance .iter() .zip(advice.iter()) .map(|(instance, advice)| -> Result, Error> { @@ -307,7 +307,7 @@ pub fn create_proof, ConcreteCircuit: Circ }) .collect::, _>>()?; - let lookups: Vec>> = lookups + let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { // Construct and commit to products for each lookup @@ -373,6 +373,7 @@ pub fn create_proof, 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(), diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index f1f09d2..38f7485 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -168,6 +168,7 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead>( // 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>( .zip(vk.cs.lookups.iter()) .flat_map(move |(p, argument)| { p.expressions( - vk, l_0, argument, theta, diff --git a/src/poly.rs b/src/poly.rs index 5ac1462..e39ba9d 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -187,6 +187,22 @@ impl<'a, F: Field> Mul<&'a Polynomial> } } +impl<'a, F: Field> Polynomial { + /// Rotates the values in a Lagrange basis polynomial by `Rotation` + pub fn rotate(&self, rotation: Rotation) -> Polynomial { + 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 for Polynomial { type Output = Polynomial; diff --git a/src/poly/domain.rs b/src/poly/domain.rs index c6bd2ce..338f1a6 100644 --- a/src/poly/domain.rs +++ b/src/poly/domain.rs @@ -177,6 +177,14 @@ impl EvaluationDomain { } } + /// Returns a constant polynomial in the Lagrange coefficient basis + pub fn constant_lagrange(&self, scalar: G) -> Polynomial { + 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 { @@ -186,6 +194,15 @@ impl EvaluationDomain { } } + /// Returns a constant polynomial in the extended Lagrange coefficient + /// basis + pub fn constant_extended(&self, scalar: G) -> Polynomial { + 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::::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) + ); +}