mirror of
https://github.com/saymrwulf/pasta_curves-source.git
synced 2026-09-04 20:03:39 +00:00
Merge pull request #53 from zcash/lookup
Lookup argument implementation
This commit is contained in:
commit
cf734f7875
9 changed files with 1042 additions and 45 deletions
|
|
@ -41,3 +41,6 @@ ff = "0.8"
|
|||
metrics = "=0.13.0-alpha.11"
|
||||
num_cpus = "1.13"
|
||||
rand = "0.7"
|
||||
|
||||
[features]
|
||||
sanity-checks = []
|
||||
|
|
|
|||
71
src/plonk.rs
71
src/plonk.rs
|
|
@ -6,11 +6,14 @@
|
|||
//! [plonk]: https://eprint.iacr.org/2019/953
|
||||
|
||||
use crate::arithmetic::CurveAffine;
|
||||
use crate::poly::{multiopen, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial};
|
||||
use crate::poly::{
|
||||
multiopen, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial,
|
||||
};
|
||||
use crate::transcript::ChallengeScalar;
|
||||
|
||||
mod circuit;
|
||||
mod keygen;
|
||||
mod lookup;
|
||||
mod permutation;
|
||||
mod prover;
|
||||
mod verifier;
|
||||
|
|
@ -37,6 +40,7 @@ pub struct ProvingKey<C: CurveAffine> {
|
|||
vk: VerifyingKey<C>,
|
||||
// TODO: get rid of this?
|
||||
l0: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
|
||||
fixed_values: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
|
||||
fixed_polys: Vec<Polynomial<C::Scalar, Coeff>>,
|
||||
fixed_cosets: Vec<Polynomial<C::Scalar, ExtendedLagrangeCoeff>>,
|
||||
permutations: Vec<permutation::ProvingKey<C>>,
|
||||
|
|
@ -49,6 +53,7 @@ pub struct Proof<C: CurveAffine> {
|
|||
advice_commitments: Vec<C>,
|
||||
h_commitments: Vec<C>,
|
||||
permutations: Option<permutation::Proof<C>>,
|
||||
lookups: Vec<lookup::Proof<C>>,
|
||||
advice_evals: Vec<C::Scalar>,
|
||||
aux_evals: Vec<C::Scalar>,
|
||||
fixed_evals: Vec<C::Scalar>,
|
||||
|
|
@ -90,6 +95,10 @@ impl<C: CurveAffine> VerifyingKey<C> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Theta;
|
||||
type ChallengeTheta<F> = ChallengeScalar<F, Theta>;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Beta;
|
||||
type ChallengeBeta<F> = ChallengeScalar<F, Beta>;
|
||||
|
|
@ -135,6 +144,8 @@ fn test_proving() {
|
|||
sc: Column<Fixed>,
|
||||
sm: Column<Fixed>,
|
||||
sp: Column<Fixed>,
|
||||
sl: Column<Fixed>,
|
||||
sl2: Column<Fixed>,
|
||||
|
||||
perm: usize,
|
||||
perm2: usize,
|
||||
|
|
@ -151,10 +162,12 @@ fn test_proving() {
|
|||
fn public_input<F>(&mut self, f: F) -> Result<Variable, Error>
|
||||
where
|
||||
F: FnOnce() -> Result<FF, Error>;
|
||||
fn lookup_table(&mut self, values: &[Vec<FF>]) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
struct MyCircuit<F: FieldExt> {
|
||||
a: Option<F>,
|
||||
lookup_tables: Vec<Vec<F>>,
|
||||
}
|
||||
|
||||
struct StandardPLONK<'a, F: FieldExt, CS: Assignment<F> + 'a> {
|
||||
|
|
@ -288,6 +301,18 @@ fn test_proving() {
|
|||
|
||||
Ok(Variable(self.config.a, index))
|
||||
}
|
||||
fn lookup_table(&mut self, values: &[Vec<FF>]) -> Result<(), Error> {
|
||||
for (&value_0, &value_1) in values[0].iter().zip(values[1].iter()) {
|
||||
let index = self.current_gate;
|
||||
|
||||
self.current_gate += 1;
|
||||
self.cs
|
||||
.assign_fixed(self.config.sl, index, || Ok(value_0))?;
|
||||
self.cs
|
||||
.assign_fixed(self.config.sl2, index, || Ok(value_1))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
|
||||
|
|
@ -310,6 +335,27 @@ fn test_proving() {
|
|||
let sb = meta.fixed_column();
|
||||
let sc = meta.fixed_column();
|
||||
let sp = meta.fixed_column();
|
||||
let sl = meta.fixed_column();
|
||||
let sl2 = meta.fixed_column();
|
||||
|
||||
/*
|
||||
* A B ... sl sl2
|
||||
* [
|
||||
* aux 0 ... 0 0
|
||||
* a a ... 0 0
|
||||
* a a^2 ... 0 0
|
||||
* a a ... 0 0
|
||||
* a a^2 ... 0 0
|
||||
* ... ... ... ... ...
|
||||
* ... ... ... aux 0
|
||||
* ... ... ... a a
|
||||
* ... ... ... a a^2
|
||||
* ... ... ... 0 0
|
||||
*
|
||||
* ]
|
||||
*/
|
||||
meta.lookup(&[a.into()], &[sl.into()]);
|
||||
meta.lookup(&[a.into(), b.into()], &[sl.into(), sl2.into()]);
|
||||
|
||||
meta.create_gate(|meta| {
|
||||
let d = meta.query_advice(d, 1);
|
||||
|
|
@ -346,6 +392,8 @@ fn test_proving() {
|
|||
sc,
|
||||
sm,
|
||||
sp,
|
||||
sl,
|
||||
sl2,
|
||||
perm,
|
||||
perm2,
|
||||
}
|
||||
|
|
@ -382,22 +430,33 @@ fn test_proving() {
|
|||
cs.copy(b1, c0)?;
|
||||
}
|
||||
|
||||
cs.lookup_table(&self.lookup_tables)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let circuit: MyCircuit<Fp> = MyCircuit {
|
||||
a: Some(Fp::rand()),
|
||||
let a = Fp::rand();
|
||||
let a_squared = a * &a;
|
||||
let aux = Fp::one() + Fp::one();
|
||||
let lookup_table = vec![aux, a, a, Fp::zero()];
|
||||
let lookup_table_2 = vec![Fp::zero(), a, a_squared, Fp::zero()];
|
||||
|
||||
let empty_circuit: MyCircuit<Fp> = MyCircuit {
|
||||
a: None,
|
||||
lookup_tables: vec![lookup_table.clone(), lookup_table_2.clone()],
|
||||
};
|
||||
|
||||
let empty_circuit: MyCircuit<Fp> = MyCircuit { a: None };
|
||||
let circuit: MyCircuit<Fp> = MyCircuit {
|
||||
a: Some(a),
|
||||
lookup_tables: vec![lookup_table, lookup_table_2],
|
||||
};
|
||||
|
||||
// Initialize the proving key
|
||||
let pk = keygen(¶ms, &empty_circuit).expect("keygen should not fail");
|
||||
|
||||
let mut pubinputs = pk.get_vk().get_domain().empty_lagrange();
|
||||
pubinputs[0] = Fp::one();
|
||||
pubinputs[0] += Fp::one();
|
||||
pubinputs[0] = aux;
|
||||
let pubinput = params
|
||||
.commit_lagrange(&pubinputs, Blind::default())
|
||||
.to_affine();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use ff::Field;
|
|||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use super::{permutation, Error};
|
||||
use super::{lookup, permutation, Error};
|
||||
use crate::poly::Rotation;
|
||||
|
||||
/// A column type
|
||||
|
|
@ -313,6 +313,10 @@ pub struct ConstraintSystem<F> {
|
|||
// Vector of permutation arguments, where each corresponds to a sequence of columns
|
||||
// that are involved in a permutation argument.
|
||||
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>,
|
||||
}
|
||||
|
||||
impl<F: Field> Default for ConstraintSystem<F> {
|
||||
|
|
@ -330,6 +334,7 @@ impl<F: Field> Default for ConstraintSystem<F> {
|
|||
aux_queries: Vec::new(),
|
||||
rotations,
|
||||
permutations: Vec::new(),
|
||||
lookups: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -339,9 +344,7 @@ impl<F: Field> ConstraintSystem<F> {
|
|||
pub fn permutation(&mut self, columns: &[Column<Advice>]) -> usize {
|
||||
let index = self.permutations.len();
|
||||
if self.permutations.is_empty() {
|
||||
let at = Rotation(-1);
|
||||
let len = self.rotations.len();
|
||||
self.rotations.entry(at).or_insert(PointIndex(len));
|
||||
self.add_rotation(Rotation(-1));
|
||||
}
|
||||
|
||||
for column in columns {
|
||||
|
|
@ -353,12 +356,36 @@ 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.
|
||||
pub fn lookup(
|
||||
&mut self,
|
||||
input_columns: &[Column<Any>],
|
||||
table_columns: &[Column<Any>],
|
||||
) -> usize {
|
||||
assert_eq!(input_columns.len(), table_columns.len());
|
||||
|
||||
let index = self.lookups.len();
|
||||
if self.lookups.is_empty() {
|
||||
self.add_rotation(Rotation(-1));
|
||||
}
|
||||
|
||||
for input in input_columns {
|
||||
self.query_any_index(*input, 0);
|
||||
}
|
||||
for table in table_columns {
|
||||
self.query_any_index(*table, 0);
|
||||
}
|
||||
self.lookups
|
||||
.push(lookup::Argument::new(input_columns, table_columns));
|
||||
|
||||
index
|
||||
}
|
||||
|
||||
fn query_fixed_index(&mut self, column: Column<Fixed>, at: i32) -> usize {
|
||||
let at = Rotation(at);
|
||||
{
|
||||
let len = self.rotations.len();
|
||||
self.rotations.entry(at).or_insert(PointIndex(len));
|
||||
}
|
||||
self.add_rotation(at);
|
||||
|
||||
// Return existing query, if it exists
|
||||
for (index, fixed_query) in self.fixed_queries.iter().enumerate() {
|
||||
|
|
@ -381,10 +408,7 @@ impl<F: Field> ConstraintSystem<F> {
|
|||
|
||||
pub(crate) fn query_advice_index(&mut self, column: Column<Advice>, at: i32) -> usize {
|
||||
let at = Rotation(at);
|
||||
{
|
||||
let len = self.rotations.len();
|
||||
self.rotations.entry(at).or_insert(PointIndex(len));
|
||||
}
|
||||
self.add_rotation(at);
|
||||
|
||||
// Return existing query, if it exists
|
||||
for (index, advice_query) in self.advice_queries.iter().enumerate() {
|
||||
|
|
@ -407,10 +431,7 @@ impl<F: Field> ConstraintSystem<F> {
|
|||
|
||||
fn query_aux_index(&mut self, column: Column<Aux>, at: i32) -> usize {
|
||||
let at = Rotation(at);
|
||||
{
|
||||
let len = self.rotations.len();
|
||||
self.rotations.entry(at).or_insert(PointIndex(len));
|
||||
}
|
||||
self.add_rotation(at);
|
||||
|
||||
// Return existing query, if it exists
|
||||
for (index, aux_query) in self.aux_queries.iter().enumerate() {
|
||||
|
|
@ -432,13 +453,11 @@ impl<F: Field> ConstraintSystem<F> {
|
|||
}
|
||||
|
||||
fn query_any_index(&mut self, column: Column<Any>, at: i32) -> usize {
|
||||
let index = match column.column_type() {
|
||||
match column.column_type() {
|
||||
Any::Advice => self.query_advice_index(Column::<Advice>::try_from(column).unwrap(), at),
|
||||
Any::Fixed => self.query_fixed_index(Column::<Fixed>::try_from(column).unwrap(), at),
|
||||
Any::Aux => self.query_aux_index(Column::<Aux>::try_from(column).unwrap(), at),
|
||||
};
|
||||
|
||||
index
|
||||
}
|
||||
}
|
||||
|
||||
/// Query an Any column at a relative position
|
||||
|
|
@ -536,4 +555,9 @@ impl<F: Field> ConstraintSystem<F> {
|
|||
self.num_aux_columns += 1;
|
||||
tmp
|
||||
}
|
||||
|
||||
fn add_rotation(&mut self, at: Rotation) {
|
||||
let len = self.rotations.len();
|
||||
self.rotations.entry(at).or_insert(PointIndex(len));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ where
|
|||
|
||||
let fixed_polys: Vec<_> = assembly
|
||||
.fixed
|
||||
.into_iter()
|
||||
.map(|poly| domain.lagrange_to_coeff(poly))
|
||||
.iter()
|
||||
.map(|poly| domain.lagrange_to_coeff(poly.clone()))
|
||||
.collect();
|
||||
|
||||
let fixed_cosets = cs
|
||||
|
|
@ -131,6 +131,7 @@ where
|
|||
cs,
|
||||
},
|
||||
l0,
|
||||
fixed_values: assembly.fixed,
|
||||
fixed_polys,
|
||||
fixed_cosets,
|
||||
permutations: permutation_pks,
|
||||
|
|
|
|||
33
src/plonk/lookup.rs
Normal file
33
src/plonk/lookup.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use super::circuit::{Any, Column};
|
||||
use crate::arithmetic::CurveAffine;
|
||||
|
||||
mod prover;
|
||||
mod verifier;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Argument {
|
||||
pub input_columns: Vec<Column<Any>>,
|
||||
pub table_columns: Vec<Column<Any>>,
|
||||
}
|
||||
|
||||
impl Argument {
|
||||
pub fn new(input_columns: &[Column<Any>], table_columns: &[Column<Any>]) -> Self {
|
||||
assert_eq!(input_columns.len(), table_columns.len());
|
||||
Argument {
|
||||
input_columns: input_columns.to_vec(),
|
||||
table_columns: table_columns.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Proof<C: CurveAffine> {
|
||||
product_commitment: C,
|
||||
product_eval: C::Scalar,
|
||||
product_inv_eval: C::Scalar,
|
||||
permuted_input_commitment: C,
|
||||
permuted_table_commitment: C,
|
||||
permuted_input_eval: C::Scalar,
|
||||
permuted_input_inv_eval: C::Scalar,
|
||||
permuted_table_eval: C::Scalar,
|
||||
}
|
||||
624
src/plonk/lookup/prover.rs
Normal file
624
src/plonk/lookup/prover.rs
Normal file
|
|
@ -0,0 +1,624 @@
|
|||
use super::super::{
|
||||
circuit::{Any, Column},
|
||||
ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, ProvingKey,
|
||||
};
|
||||
use super::{Argument, Proof};
|
||||
use crate::{
|
||||
arithmetic::{eval_polynomial, parallelize, BatchInvert, Curve, CurveAffine, FieldExt},
|
||||
poly::{
|
||||
commitment::{Blind, Params},
|
||||
multiopen::ProverQuery,
|
||||
Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation,
|
||||
},
|
||||
transcript::{Hasher, Transcript},
|
||||
};
|
||||
use ff::Field;
|
||||
use std::{collections::BTreeMap, iter};
|
||||
|
||||
#[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>,
|
||||
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>,
|
||||
permuted_table_poly: Polynomial<C::Scalar, Coeff>,
|
||||
permuted_table_coset: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
|
||||
permuted_table_blind: Blind<C::Scalar>,
|
||||
permuted_table_commitment: C,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::plonk) struct Committed<'a, C: CurveAffine> {
|
||||
permuted: Permuted<'a, C>,
|
||||
product_poly: Polynomial<C::Scalar, Coeff>,
|
||||
product_coset: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
|
||||
product_inv_coset: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
|
||||
product_blind: Blind<C::Scalar>,
|
||||
product_commitment: C,
|
||||
}
|
||||
|
||||
pub(in crate::plonk) struct Constructed<C: CurveAffine> {
|
||||
permuted_input_poly: Polynomial<C::Scalar, Coeff>,
|
||||
permuted_input_blind: Blind<C::Scalar>,
|
||||
permuted_input_commitment: C,
|
||||
permuted_table_poly: Polynomial<C::Scalar, Coeff>,
|
||||
permuted_table_blind: Blind<C::Scalar>,
|
||||
permuted_table_commitment: C,
|
||||
product_poly: Polynomial<C::Scalar, Coeff>,
|
||||
product_blind: Blind<C::Scalar>,
|
||||
product_commitment: C,
|
||||
}
|
||||
|
||||
pub(in crate::plonk) struct Evaluated<C: CurveAffine> {
|
||||
constructed: Constructed<C>,
|
||||
product_eval: C::Scalar,
|
||||
product_inv_eval: C::Scalar,
|
||||
permuted_input_eval: C::Scalar,
|
||||
permuted_input_inv_eval: C::Scalar,
|
||||
permuted_table_eval: C::Scalar,
|
||||
}
|
||||
|
||||
impl Argument {
|
||||
/// Given a Lookup with input columns [A_0, A_1, ..., A_{m-1}] and table columns
|
||||
/// [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,
|
||||
/// obtaining A' and S', and
|
||||
/// - constructs Permuted<C> struct using permuted_input_value = A', and
|
||||
/// permuted_table_column = 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,
|
||||
HBase: Hasher<C::Base>,
|
||||
HScalar: Hasher<C::Scalar>,
|
||||
>(
|
||||
&self,
|
||||
pk: &ProvingKey<C>,
|
||||
params: &Params<C>,
|
||||
domain: &EvaluationDomain<C::Scalar>,
|
||||
theta: ChallengeTheta<C::Scalar>,
|
||||
advice_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
|
||||
fixed_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
|
||||
aux_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
|
||||
advice_cosets: &'a [Polynomial<C::Scalar, ExtendedLagrangeCoeff>],
|
||||
fixed_cosets: &'a [Polynomial<C::Scalar, ExtendedLagrangeCoeff>],
|
||||
aux_cosets: &'a [Polynomial<C::Scalar, ExtendedLagrangeCoeff>],
|
||||
transcript: &mut Transcript<C, HBase, HScalar>,
|
||||
) -> 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
|
||||
.iter()
|
||||
.map(|&column| {
|
||||
let (values, cosets) = match column.column_type() {
|
||||
Any::Advice => (advice_values, advice_cosets),
|
||||
Any::Fixed => (fixed_values, fixed_cosets),
|
||||
Any::Aux => (aux_values, aux_cosets),
|
||||
};
|
||||
(
|
||||
&values[column.index()],
|
||||
&cosets[pk.vk.cs.get_any_query_index(column, 0)],
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
// Compressed version of columns
|
||||
let compressed_column = unpermuted_columns
|
||||
.iter()
|
||||
.fold(domain.empty_lagrange(), |acc, column| acc * *theta + column);
|
||||
|
||||
(unpermuted_columns, unpermuted_cosets, compressed_column)
|
||||
};
|
||||
|
||||
// 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());
|
||||
let blind = Blind(C::Scalar::rand());
|
||||
let commitment = params.commit_lagrange(&column, 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 table columns involved in the lookup and compress them
|
||||
let (unpermuted_table_columns, unpermuted_table_cosets, compressed_table_column) =
|
||||
compress_columns(&self.table_columns);
|
||||
|
||||
// Permute compressed (InputColumn, TableColumn) pair
|
||||
let (permuted_input_column, permuted_table_column) =
|
||||
permute_column_pair::<C>(domain, &compressed_input_column, &compressed_table_column)?;
|
||||
|
||||
// Commit to permuted input column
|
||||
let (permuted_input_poly, permuted_input_blind, permuted_input_commitment) =
|
||||
commit_column(&permuted_input_column);
|
||||
|
||||
// Commit to permuted table column
|
||||
let (permuted_table_poly, permuted_table_blind, permuted_table_commitment) =
|
||||
commit_column(&permuted_table_column);
|
||||
|
||||
// Hash permuted input commitment
|
||||
transcript
|
||||
.absorb_point(&permuted_input_commitment)
|
||||
.map_err(|_| Error::TranscriptError)?;
|
||||
|
||||
// Hash permuted table commitment
|
||||
transcript
|
||||
.absorb_point(&permuted_table_commitment)
|
||||
.map_err(|_| Error::TranscriptError)?;
|
||||
|
||||
let permuted_input_coset = pk
|
||||
.vk
|
||||
.domain
|
||||
.coeff_to_extended(permuted_input_poly.clone(), Rotation::default());
|
||||
let permuted_input_inv_coset = pk
|
||||
.vk
|
||||
.domain
|
||||
.coeff_to_extended(permuted_input_poly.clone(), Rotation(-1));
|
||||
let permuted_table_coset = pk
|
||||
.vk
|
||||
.domain
|
||||
.coeff_to_extended(permuted_table_poly.clone(), Rotation::default());
|
||||
|
||||
Ok(Permuted {
|
||||
unpermuted_input_columns,
|
||||
unpermuted_input_cosets,
|
||||
permuted_input_column,
|
||||
permuted_input_poly,
|
||||
permuted_input_coset,
|
||||
permuted_input_inv_coset,
|
||||
permuted_input_blind,
|
||||
permuted_input_commitment,
|
||||
unpermuted_table_columns,
|
||||
unpermuted_table_cosets,
|
||||
permuted_table_column,
|
||||
permuted_table_poly,
|
||||
permuted_table_coset,
|
||||
permuted_table_blind,
|
||||
permuted_table_commitment,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// 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.
|
||||
pub(in crate::plonk) fn commit_product<HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
|
||||
self,
|
||||
pk: &ProvingKey<C>,
|
||||
params: &Params<C>,
|
||||
theta: ChallengeTheta<C::Scalar>,
|
||||
beta: ChallengeBeta<C::Scalar>,
|
||||
gamma: ChallengeGamma<C::Scalar>,
|
||||
transcript: &mut Transcript<C, HBase, HScalar>,
|
||||
) -> Result<Committed<'a, 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.
|
||||
let mut lookup_product = vec![C::Scalar::zero(); params.n as usize];
|
||||
// Denominator uses the permuted input column and permuted table column
|
||||
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())
|
||||
{
|
||||
*lookup_product = (*beta + permuted_input_value) * &(*gamma + permuted_table_value);
|
||||
}
|
||||
});
|
||||
|
||||
// Batch invert to obtain the denominators for the lookup product
|
||||
// polynomials
|
||||
lookup_product.iter_mut().batch_invert();
|
||||
|
||||
// Finish the computation of the entire fraction by computing the numerators
|
||||
// (\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)
|
||||
parallelize(&mut lookup_product, |product, start| {
|
||||
for (i, product) in product.iter_mut().enumerate() {
|
||||
let i = i + start;
|
||||
|
||||
// Compress unpermuted input columns
|
||||
let mut input_term = C::Scalar::zero();
|
||||
for unpermuted_input_column in self.unpermuted_input_columns.iter() {
|
||||
input_term *= θ
|
||||
input_term += &unpermuted_input_column[i];
|
||||
}
|
||||
|
||||
// Compress unpermuted table columns
|
||||
let mut table_term = C::Scalar::zero();
|
||||
for unpermuted_table_column in self.unpermuted_table_columns.iter() {
|
||||
table_term *= θ
|
||||
table_term += &unpermuted_table_column[i];
|
||||
}
|
||||
|
||||
*product *= &(input_term + &beta);
|
||||
*product *= &(table_term + &gamma);
|
||||
}
|
||||
});
|
||||
|
||||
// The product vector is a vector of products of fractions of the form
|
||||
//
|
||||
// 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 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.
|
||||
|
||||
// Compute the evaluations of the lookup product polynomial
|
||||
// over our domain, starting with z[0] = 1
|
||||
let z = iter::once(C::Scalar::one())
|
||||
.chain(lookup_product.into_iter().skip(1))
|
||||
.scan(C::Scalar::one(), |state, cur| {
|
||||
*state *= &cur;
|
||||
Some(*state)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let z = pk.vk.domain.lagrange_from_vec(z);
|
||||
|
||||
#[cfg(feature = "sanity-checks")]
|
||||
// This test works only with intermediate representations in this method.
|
||||
// It can be used for debugging purposes.
|
||||
{
|
||||
// While in Lagrange basis, check that product is correctly constructed
|
||||
let n = params.n as usize;
|
||||
|
||||
// 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)
|
||||
for i in 0..n {
|
||||
let prev_idx = (n + i - 1) % n;
|
||||
|
||||
let mut left = z[i];
|
||||
let permuted_input_value = &self.permuted_input_column[i];
|
||||
|
||||
let permuted_table_value = &self.permuted_table_column[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
|
||||
.iter()
|
||||
.fold(C::Scalar::zero(), |acc, input| acc * &theta + &input[i]);
|
||||
|
||||
let mut table_term = self.unpermuted_table_columns
|
||||
.iter()
|
||||
.fold(C::Scalar::zero(), |acc, table| acc * &theta + &table[i]);
|
||||
|
||||
input_term += &(*beta);
|
||||
table_term += &(*gamma);
|
||||
right *= &(input_term * &table_term);
|
||||
|
||||
assert_eq!(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
let product_blind = Blind(C::Scalar::rand());
|
||||
let product_commitment = params.commit_lagrange(&z, product_blind).to_affine();
|
||||
let z = pk.vk.domain.lagrange_to_coeff(z);
|
||||
let product_coset = pk
|
||||
.vk
|
||||
.domain
|
||||
.coeff_to_extended(z.clone(), Rotation::default());
|
||||
let product_inv_coset = pk.vk.domain.coeff_to_extended(z.clone(), Rotation(-1));
|
||||
|
||||
// Hash product commitment
|
||||
transcript
|
||||
.absorb_point(&product_commitment)
|
||||
.map_err(|_| Error::TranscriptError)?;
|
||||
|
||||
Ok(Committed::<'a, C> {
|
||||
permuted: self,
|
||||
product_poly: z,
|
||||
product_coset,
|
||||
product_inv_coset,
|
||||
product_commitment,
|
||||
product_blind,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// method constructs constraints that must hold between these values.
|
||||
/// This method returns the constraints as a vector of polynomials in
|
||||
/// the extended evaluation domain.
|
||||
pub(in crate::plonk) fn construct(
|
||||
self,
|
||||
pk: &'a ProvingKey<C>,
|
||||
theta: ChallengeTheta<C::Scalar>,
|
||||
beta: ChallengeBeta<C::Scalar>,
|
||||
gamma: ChallengeGamma<C::Scalar>,
|
||||
) -> Result<
|
||||
(
|
||||
Constructed<C>,
|
||||
impl Iterator<Item = Polynomial<C::Scalar, ExtendedLagrangeCoeff>> + 'a,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let permuted = self.permuted;
|
||||
|
||||
let expressions = iter::empty()
|
||||
// l_0(X) * (1 - z'(X)) = 0
|
||||
.chain(Some(
|
||||
Polynomial::one_minus(self.product_coset.clone()) * &pk.l0,
|
||||
))
|
||||
// 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)
|
||||
.chain({
|
||||
// z'(X) (a'(X) + \beta) (s'(X) + \gamma)
|
||||
let mut left = self.product_coset.clone();
|
||||
parallelize(&mut left, |left, start| {
|
||||
for ((left, permuted_input), permuted_table) in left
|
||||
.iter_mut()
|
||||
.zip(permuted.permuted_input_coset[start..].iter())
|
||||
.zip(permuted.permuted_table_coset[start..].iter())
|
||||
{
|
||||
*left *= &(*permuted_input + &(*beta));
|
||||
*left *= &(*permuted_table + &(*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)
|
||||
let mut right = self.product_inv_coset;
|
||||
parallelize(&mut right, |right, start| {
|
||||
for (i, right) in right.iter_mut().enumerate() {
|
||||
let i = i + start;
|
||||
|
||||
// Compress the unpermuted input columns
|
||||
let mut input_term = C::Scalar::zero();
|
||||
for input in permuted.unpermuted_input_cosets.iter() {
|
||||
input_term *= θ
|
||||
input_term += &input[i];
|
||||
}
|
||||
|
||||
// Compress the unpermuted table columns
|
||||
let mut table_term = C::Scalar::zero();
|
||||
for table in permuted.unpermuted_table_cosets.iter() {
|
||||
table_term *= θ
|
||||
table_term += &table[i];
|
||||
}
|
||||
|
||||
// Add \beta and \gamma offsets
|
||||
*right *= &(input_term + &beta);
|
||||
*right *= &(table_term + &gamma);
|
||||
}
|
||||
});
|
||||
|
||||
Some(left - &right)
|
||||
})
|
||||
// Check that the first values in the permuted input column and permuted
|
||||
// fixed column 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
|
||||
// equal to the value above it, or the value at the same index in the
|
||||
// permuted table column.
|
||||
// (a′(X)−s′(X))⋅(a′(X)−a′(\omega{-1} X)) = 0
|
||||
.chain(Some(
|
||||
(permuted.permuted_input_coset.clone() - &permuted.permuted_table_coset)
|
||||
* &(permuted.permuted_input_coset.clone() - &permuted.permuted_input_inv_coset),
|
||||
));
|
||||
|
||||
Ok((
|
||||
Constructed {
|
||||
permuted_input_poly: permuted.permuted_input_poly,
|
||||
permuted_input_blind: permuted.permuted_input_blind,
|
||||
permuted_input_commitment: permuted.permuted_input_commitment,
|
||||
permuted_table_poly: permuted.permuted_table_poly,
|
||||
permuted_table_blind: permuted.permuted_table_blind,
|
||||
permuted_table_commitment: permuted.permuted_table_commitment,
|
||||
product_poly: self.product_poly,
|
||||
product_blind: self.product_blind,
|
||||
product_commitment: self.product_commitment,
|
||||
},
|
||||
expressions,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveAffine> Constructed<C> {
|
||||
pub(in crate::plonk) fn evaluate<HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
|
||||
self,
|
||||
pk: &ProvingKey<C>,
|
||||
x: ChallengeX<C::Scalar>,
|
||||
transcript: &mut Transcript<C, HBase, HScalar>,
|
||||
) -> Evaluated<C> {
|
||||
let domain = &pk.vk.domain;
|
||||
let x_inv = domain.rotate_omega(*x, Rotation(-1));
|
||||
|
||||
let product_eval = eval_polynomial(&self.product_poly, *x);
|
||||
let product_inv_eval = eval_polynomial(&self.product_poly, x_inv);
|
||||
let permuted_input_eval = eval_polynomial(&self.permuted_input_poly, *x);
|
||||
let permuted_input_inv_eval = eval_polynomial(&self.permuted_input_poly, x_inv);
|
||||
let permuted_table_eval = eval_polynomial(&self.permuted_table_poly, *x);
|
||||
|
||||
// Hash each advice evaluation
|
||||
for eval in iter::empty()
|
||||
.chain(Some(product_eval))
|
||||
.chain(Some(product_inv_eval))
|
||||
.chain(Some(permuted_input_eval))
|
||||
.chain(Some(permuted_input_inv_eval))
|
||||
.chain(Some(permuted_table_eval))
|
||||
{
|
||||
transcript.absorb_scalar(eval);
|
||||
}
|
||||
|
||||
Evaluated {
|
||||
constructed: self,
|
||||
product_eval,
|
||||
product_inv_eval,
|
||||
permuted_input_eval,
|
||||
permuted_input_inv_eval,
|
||||
permuted_table_eval,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveAffine> Evaluated<C> {
|
||||
pub(in crate::plonk) fn open<'a>(
|
||||
&'a self,
|
||||
pk: &'a ProvingKey<C>,
|
||||
x: ChallengeX<C::Scalar>,
|
||||
) -> impl Iterator<Item = ProverQuery<'a, C>> + Clone {
|
||||
let x_inv = pk.vk.domain.rotate_omega(*x, Rotation(-1));
|
||||
|
||||
iter::empty()
|
||||
// Open lookup product commitments at x
|
||||
.chain(Some(ProverQuery {
|
||||
point: *x,
|
||||
poly: &self.constructed.product_poly,
|
||||
blind: self.constructed.product_blind,
|
||||
eval: self.product_eval,
|
||||
}))
|
||||
// Open lookup input commitments at x
|
||||
.chain(Some(ProverQuery {
|
||||
point: *x,
|
||||
poly: &self.constructed.permuted_input_poly,
|
||||
blind: self.constructed.permuted_input_blind,
|
||||
eval: self.permuted_input_eval,
|
||||
}))
|
||||
// Open lookup table commitments at x
|
||||
.chain(Some(ProverQuery {
|
||||
point: *x,
|
||||
poly: &self.constructed.permuted_table_poly,
|
||||
blind: self.constructed.permuted_table_blind,
|
||||
eval: self.permuted_table_eval,
|
||||
}))
|
||||
// Open lookup input commitments at x_inv
|
||||
.chain(Some(ProverQuery {
|
||||
point: x_inv,
|
||||
poly: &self.constructed.permuted_input_poly,
|
||||
blind: self.constructed.permuted_input_blind,
|
||||
eval: self.permuted_input_eval,
|
||||
}))
|
||||
// Open lookup product commitments at x_inv
|
||||
.chain(Some(ProverQuery {
|
||||
point: x_inv,
|
||||
poly: &self.constructed.product_poly,
|
||||
blind: self.constructed.product_blind,
|
||||
eval: self.product_eval,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Proof<C> {
|
||||
Proof {
|
||||
product_commitment: self.constructed.product_commitment,
|
||||
product_eval: self.product_eval,
|
||||
product_inv_eval: self.product_inv_eval,
|
||||
permuted_input_commitment: self.constructed.permuted_input_commitment,
|
||||
permuted_table_commitment: self.constructed.permuted_table_commitment,
|
||||
permuted_input_eval: self.permuted_input_eval,
|
||||
permuted_input_inv_eval: self.permuted_input_inv_eval,
|
||||
permuted_table_eval: self.permuted_table_eval,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a column of input values A and a column 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>(
|
||||
domain: &EvaluationDomain<C::Scalar>,
|
||||
input_column: &Polynomial<C::Scalar, LagrangeCoeff>,
|
||||
table_column: &Polynomial<C::Scalar, LagrangeCoeff>,
|
||||
) -> Result<
|
||||
(
|
||||
Polynomial<C::Scalar, LagrangeCoeff>,
|
||||
Polynomial<C::Scalar, LagrangeCoeff>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let mut permuted_input_column = input_column.clone();
|
||||
|
||||
// Sort input lookup column values
|
||||
permuted_input_column.sort();
|
||||
|
||||
// A BTreeMap of each unique element in the table column 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()];
|
||||
|
||||
let mut repeated_input_rows = permuted_input_column
|
||||
.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] {
|
||||
*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) {
|
||||
assert!(*count > 0);
|
||||
*count -= 1;
|
||||
None
|
||||
} else {
|
||||
// Return error if input_value not found
|
||||
Some(Err(Error::ConstraintSystemFailure))
|
||||
}
|
||||
// If input value is repeated
|
||||
} else {
|
||||
Some(Ok(row))
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Populate permuted table at unfilled rows with leftover table elements
|
||||
for (coeff, count) in leftover_table_map.iter() {
|
||||
for _ in 0..*count {
|
||||
permuted_table_coeffs[repeated_input_rows.pop().unwrap() as usize] = *coeff;
|
||||
}
|
||||
}
|
||||
assert!(repeated_input_rows.is_empty());
|
||||
|
||||
let mut permuted_table_column = domain.empty_lagrange();
|
||||
parallelize(
|
||||
&mut permuted_table_column,
|
||||
|permuted_table_column, start| {
|
||||
for (permuted_table_value, permuted_table_coeff) in permuted_table_column
|
||||
.iter_mut()
|
||||
.zip(permuted_table_coeffs[start..].iter())
|
||||
{
|
||||
*permuted_table_value += permuted_table_coeff;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Ok((permuted_input_column, permuted_table_column))
|
||||
}
|
||||
149
src/plonk/lookup/verifier.rs
Normal file
149
src/plonk/lookup/verifier.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
use std::iter;
|
||||
|
||||
use super::super::circuit::{Any, Column};
|
||||
use super::{Argument, Proof};
|
||||
use crate::{
|
||||
arithmetic::CurveAffine,
|
||||
plonk::{ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, VerifyingKey},
|
||||
poly::{multiopen::VerifierQuery, Rotation},
|
||||
transcript::{Hasher, Transcript},
|
||||
};
|
||||
use ff::Field;
|
||||
|
||||
impl<C: CurveAffine> Proof<C> {
|
||||
pub(in crate::plonk) fn absorb_permuted_commitments<
|
||||
HBase: Hasher<C::Base>,
|
||||
HScalar: Hasher<C::Scalar>,
|
||||
>(
|
||||
&self,
|
||||
transcript: &mut Transcript<C, HBase, HScalar>,
|
||||
) -> Result<(), Error> {
|
||||
transcript
|
||||
.absorb_point(&self.permuted_input_commitment)
|
||||
.map_err(|_| Error::TranscriptError)?;
|
||||
transcript
|
||||
.absorb_point(&self.permuted_table_commitment)
|
||||
.map_err(|_| Error::TranscriptError)
|
||||
}
|
||||
|
||||
pub(in crate::plonk) fn absorb_product_commitment<
|
||||
HBase: Hasher<C::Base>,
|
||||
HScalar: Hasher<C::Scalar>,
|
||||
>(
|
||||
&self,
|
||||
transcript: &mut Transcript<C, HBase, HScalar>,
|
||||
) -> Result<(), Error> {
|
||||
transcript
|
||||
.absorb_point(&self.product_commitment)
|
||||
.map_err(|_| Error::TranscriptError)
|
||||
}
|
||||
|
||||
pub(in crate::plonk) fn expressions<'a>(
|
||||
&'a self,
|
||||
vk: &'a VerifyingKey<C>,
|
||||
l_0: C::Scalar,
|
||||
argument: &'a Argument,
|
||||
theta: ChallengeTheta<C::Scalar>,
|
||||
beta: ChallengeBeta<C::Scalar>,
|
||||
gamma: ChallengeGamma<C::Scalar>,
|
||||
advice_evals: &[C::Scalar],
|
||||
fixed_evals: &[C::Scalar],
|
||||
aux_evals: &[C::Scalar],
|
||||
) -> impl Iterator<Item = C::Scalar> + 'a {
|
||||
let product_expression = || {
|
||||
// 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)
|
||||
let left = self.product_eval
|
||||
* &(self.permuted_input_eval + &beta)
|
||||
* &(self.permuted_table_eval + &gamma);
|
||||
|
||||
let compress_columns = |columns: &[Column<Any>]| {
|
||||
columns
|
||||
.iter()
|
||||
.map(|column| {
|
||||
let index = vk.cs.get_any_query_index(*column, 0);
|
||||
match column.column_type() {
|
||||
Any::Advice => advice_evals[index],
|
||||
Any::Fixed => fixed_evals[index],
|
||||
Any::Aux => aux_evals[index],
|
||||
}
|
||||
})
|
||||
.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);
|
||||
|
||||
left - &right
|
||||
};
|
||||
|
||||
std::iter::empty()
|
||||
.chain(
|
||||
// l_0(X) * (1 - z'(X)) = 0
|
||||
Some(l_0 * &(C::Scalar::one() - &self.product_eval)),
|
||||
)
|
||||
.chain(
|
||||
// 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)
|
||||
Some(product_expression()),
|
||||
)
|
||||
.chain(Some(
|
||||
// l_0(X) * (a'(X) - s'(X)) = 0
|
||||
l_0 * &(self.permuted_input_eval - &self.permuted_table_eval),
|
||||
))
|
||||
.chain(Some(
|
||||
// (a′(X)−s′(X))⋅(a′(X)−a′(\omega{-1} X)) = 0
|
||||
(self.permuted_input_eval - &self.permuted_table_eval)
|
||||
* &(self.permuted_input_eval - &self.permuted_input_inv_eval),
|
||||
))
|
||||
}
|
||||
|
||||
pub(in crate::plonk) fn evals(&self) -> impl Iterator<Item = &C::Scalar> {
|
||||
iter::empty()
|
||||
.chain(Some(&self.product_eval))
|
||||
.chain(Some(&self.product_inv_eval))
|
||||
.chain(Some(&self.permuted_input_eval))
|
||||
.chain(Some(&self.permuted_input_inv_eval))
|
||||
.chain(Some(&self.permuted_table_eval))
|
||||
}
|
||||
|
||||
pub(in crate::plonk) fn queries<'a>(
|
||||
&'a self,
|
||||
vk: &'a VerifyingKey<C>,
|
||||
x: ChallengeX<C::Scalar>,
|
||||
) -> impl Iterator<Item = VerifierQuery<'a, C>> + Clone {
|
||||
let x_inv = vk.domain.rotate_omega(*x, Rotation(-1));
|
||||
|
||||
iter::empty()
|
||||
// Open lookup product commitments at x
|
||||
.chain(Some(VerifierQuery {
|
||||
point: *x,
|
||||
commitment: &self.product_commitment,
|
||||
eval: self.product_eval,
|
||||
}))
|
||||
// Open lookup input commitments at x
|
||||
.chain(Some(VerifierQuery {
|
||||
point: *x,
|
||||
commitment: &self.permuted_input_commitment,
|
||||
eval: self.permuted_input_eval,
|
||||
}))
|
||||
// Open lookup table commitments at x
|
||||
.chain(Some(VerifierQuery {
|
||||
point: *x,
|
||||
commitment: &self.permuted_table_commitment,
|
||||
eval: self.permuted_table_eval,
|
||||
}))
|
||||
// Open lookup input commitments at \omega^{-1} x
|
||||
.chain(Some(VerifierQuery {
|
||||
point: x_inv,
|
||||
commitment: &self.permuted_input_commitment,
|
||||
eval: self.permuted_input_inv_eval,
|
||||
}))
|
||||
// Open lookup product commitments at \omega^{-1} x
|
||||
.chain(Some(VerifierQuery {
|
||||
point: x_inv,
|
||||
commitment: &self.product_commitment,
|
||||
eval: self.product_inv_eval,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,8 @@ use std::iter;
|
|||
|
||||
use super::{
|
||||
circuit::{Advice, Assignment, Circuit, Column, ConstraintSystem, Fixed},
|
||||
permutation, ChallengeBeta, ChallengeGamma, ChallengeX, ChallengeY, Error, Proof, ProvingKey,
|
||||
permutation, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error,
|
||||
Proof, ProvingKey,
|
||||
};
|
||||
use crate::arithmetic::{eval_polynomial, Curve, CurveAffine, FieldExt};
|
||||
use crate::poly::{
|
||||
|
|
@ -168,6 +169,32 @@ impl<C: CurveAffine> Proof<C> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
// Sample theta challenge for keeping lookup columns linearly independent
|
||||
let theta = ChallengeTheta::get(&mut transcript);
|
||||
|
||||
// Construct and commit to permuted values for each lookup
|
||||
let lookups = pk
|
||||
.vk
|
||||
.cs
|
||||
.lookups
|
||||
.iter()
|
||||
.map(|lookup| {
|
||||
lookup.commit_permuted(
|
||||
&pk,
|
||||
¶ms,
|
||||
&domain,
|
||||
theta,
|
||||
&witness.advice,
|
||||
&pk.fixed_values,
|
||||
&aux,
|
||||
&advice_cosets,
|
||||
&pk.fixed_cosets,
|
||||
&aux_cosets,
|
||||
&mut transcript,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Sample beta challenge
|
||||
let beta = ChallengeBeta::get(&mut transcript);
|
||||
|
||||
|
|
@ -188,6 +215,12 @@ impl<C: CurveAffine> Proof<C> {
|
|||
None
|
||||
};
|
||||
|
||||
// Construct and commit to products for each lookup
|
||||
let lookups = lookups
|
||||
.into_iter()
|
||||
.map(|lookup| lookup.commit_product(&pk, ¶ms, theta, beta, gamma, &mut transcript))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Obtain challenge for keeping all separate gates linearly independent
|
||||
let y = ChallengeY::<C::Scalar>::get(&mut transcript);
|
||||
|
||||
|
|
@ -198,6 +231,16 @@ impl<C: CurveAffine> Proof<C> {
|
|||
.map(|(p, expressions)| (Some(p), Some(expressions)))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Evaluate the h(X) polynomial's constraint system expressions for the lookup constraints, if any.
|
||||
let (lookups, lookup_expressions): (Vec<_>, Vec<_>) = {
|
||||
let tmp = lookups
|
||||
.into_iter()
|
||||
.map(|p| p.construct(pk, theta, beta, gamma))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
tmp.into_iter().unzip()
|
||||
};
|
||||
|
||||
// Evaluate the h(X) polynomial's constraint system expressions for the constraints provided
|
||||
let h_poly = iter::empty()
|
||||
// Custom constraints
|
||||
|
|
@ -213,6 +256,8 @@ impl<C: CurveAffine> Proof<C> {
|
|||
}))
|
||||
// Permutation constraints, if any.
|
||||
.chain(permutation_expressions.into_iter().flatten())
|
||||
// Lookup constraints, if any.
|
||||
.chain(lookup_expressions.into_iter().flatten())
|
||||
.fold(domain.empty_extended(), |h_poly, v| h_poly * *y + &v);
|
||||
|
||||
// Divide by t(X) = X^{params.n} - 1.
|
||||
|
|
@ -292,6 +337,12 @@ impl<C: CurveAffine> Proof<C> {
|
|||
// Evaluate the permutations, if any, at omega^i x.
|
||||
let permutations = permutations.map(|p| p.evaluate(pk, x, &mut transcript));
|
||||
|
||||
// Evaluate the lookups, if any, at omega^i x.
|
||||
let lookups = lookups
|
||||
.into_iter()
|
||||
.map(|p| p.evaluate(pk, x, &mut transcript))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instances =
|
||||
iter::empty()
|
||||
.chain(pk.vk.cs.advice_queries.iter().enumerate().map(
|
||||
|
|
@ -335,13 +386,15 @@ impl<C: CurveAffine> Proof<C> {
|
|||
let multiopening = multiopen::Proof::create(
|
||||
params,
|
||||
&mut transcript,
|
||||
instances.chain(
|
||||
permutations
|
||||
.as_ref()
|
||||
.map(|p| p.open(pk, x))
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
),
|
||||
instances
|
||||
.chain(
|
||||
permutations
|
||||
.as_ref()
|
||||
.map(|p| p.open(pk, x))
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.chain(lookups.iter().map(|p| p.open(pk, x)).into_iter().flatten()),
|
||||
)
|
||||
.map_err(|_| Error::OpeningError)?;
|
||||
|
||||
|
|
@ -349,6 +402,7 @@ impl<C: CurveAffine> Proof<C> {
|
|||
advice_commitments,
|
||||
h_commitments,
|
||||
permutations: permutations.map(|p| p.build()),
|
||||
lookups: lookups.into_iter().map(|p| p.build()).collect(),
|
||||
advice_evals,
|
||||
fixed_evals,
|
||||
aux_evals,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use ff::Field;
|
||||
use std::iter;
|
||||
|
||||
use super::{ChallengeBeta, ChallengeGamma, ChallengeX, ChallengeY, Error, Proof, VerifyingKey};
|
||||
use super::{
|
||||
ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, Proof,
|
||||
VerifyingKey,
|
||||
};
|
||||
use crate::arithmetic::{CurveAffine, FieldExt};
|
||||
use crate::poly::{
|
||||
commitment::{Guard, Params, MSM},
|
||||
|
|
@ -45,6 +48,14 @@ impl<'a, C: CurveAffine> Proof<C> {
|
|||
.map_err(|_| Error::TranscriptError)?;
|
||||
}
|
||||
|
||||
// Sample theta challenge for keeping lookup columns linearly independent
|
||||
let theta = ChallengeTheta::get(&mut transcript);
|
||||
|
||||
// Hash each lookup permuted commitment
|
||||
for lookup in &self.lookups {
|
||||
lookup.absorb_permuted_commitments(&mut transcript)?;
|
||||
}
|
||||
|
||||
// Sample beta challenge
|
||||
let beta = ChallengeBeta::get(&mut transcript);
|
||||
|
||||
|
|
@ -56,6 +67,11 @@ impl<'a, C: CurveAffine> Proof<C> {
|
|||
p.absorb_commitments(&mut transcript)?;
|
||||
}
|
||||
|
||||
// Hash each lookup product commitment
|
||||
for lookup in &self.lookups {
|
||||
lookup.absorb_product_commitment(&mut transcript)?;
|
||||
}
|
||||
|
||||
// Sample y challenge, which keeps the gates linearly independent.
|
||||
let y = ChallengeY::get(&mut transcript);
|
||||
|
||||
|
|
@ -72,7 +88,7 @@ impl<'a, C: CurveAffine> Proof<C> {
|
|||
|
||||
// This check ensures the circuit is satisfied so long as the polynomial
|
||||
// commitments open to the correct values.
|
||||
self.check_hx(params, vk, beta, gamma, y, x)?;
|
||||
self.check_hx(params, vk, theta, beta, gamma, y, x)?;
|
||||
|
||||
for eval in self
|
||||
.advice_evals
|
||||
|
|
@ -87,6 +103,7 @@ impl<'a, C: CurveAffine> Proof<C> {
|
|||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.chain(self.lookups.iter().map(|p| p.evals()).into_iter().flatten())
|
||||
{
|
||||
transcript.absorb_scalar(*eval);
|
||||
}
|
||||
|
|
@ -136,13 +153,21 @@ impl<'a, C: CurveAffine> Proof<C> {
|
|||
.verify(
|
||||
params,
|
||||
&mut transcript,
|
||||
queries.chain(
|
||||
self.permutations
|
||||
.as_ref()
|
||||
.map(|p| p.queries(vk, x))
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
),
|
||||
queries
|
||||
.chain(
|
||||
self.permutations
|
||||
.as_ref()
|
||||
.map(|p| p.queries(vk, x))
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.chain(
|
||||
self.lookups
|
||||
.iter()
|
||||
.map(|p| p.queries(vk, x))
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
),
|
||||
msm,
|
||||
)
|
||||
.map_err(|_| Error::OpeningError)
|
||||
|
|
@ -174,6 +199,10 @@ impl<'a, C: CurveAffine> Proof<C> {
|
|||
.map(|p| p.check_lengths(vk))
|
||||
.transpose()?;
|
||||
|
||||
if self.lookups.len() != vk.cs.lookups.len() {
|
||||
return Err(Error::IncompatibleParams);
|
||||
}
|
||||
|
||||
// TODO: check h_commitments
|
||||
|
||||
if self.advice_commitments.len() != vk.cs.num_advice_columns {
|
||||
|
|
@ -189,6 +218,7 @@ impl<'a, C: CurveAffine> Proof<C> {
|
|||
&self,
|
||||
params: &'a Params<C>,
|
||||
vk: &VerifyingKey<C>,
|
||||
theta: ChallengeTheta<C::Scalar>,
|
||||
beta: ChallengeBeta<C::Scalar>,
|
||||
gamma: ChallengeGamma<C::Scalar>,
|
||||
y: ChallengeY<C::Scalar>,
|
||||
|
|
@ -223,6 +253,26 @@ impl<'a, C: CurveAffine> Proof<C> {
|
|||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.chain(
|
||||
self.lookups
|
||||
.iter()
|
||||
.zip(vk.cs.lookups.iter())
|
||||
.map(|(p, argument)| {
|
||||
p.expressions(
|
||||
vk,
|
||||
l_0,
|
||||
argument,
|
||||
theta,
|
||||
beta,
|
||||
gamma,
|
||||
&self.advice_evals,
|
||||
&self.fixed_evals,
|
||||
&self.aux_evals,
|
||||
)
|
||||
})
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.fold(C::Scalar::zero(), |h_eval, v| h_eval * &y + &v);
|
||||
|
||||
// Compute h(x) from the prover
|
||||
|
|
|
|||
Loading…
Reference in a new issue