Complete create_proof refactor for fe-be split

This commit is contained in:
Eduard S. 2023-12-14 17:24:41 +01:00
parent 4510059ae7
commit 1b35fab453
6 changed files with 1511 additions and 63 deletions

View File

@ -1704,8 +1704,8 @@ impl<F: Field> ConstraintSystemV2Backend<F> {
let max_phase = self
.advice_column_phase
.iter()
.cloned()
.max()
.map(|phase| phase.0)
.unwrap_or_default();
(0..=max_phase).collect()
}

View File

@ -1,5 +1,5 @@
use crate::multicore;
use crate::plonk::{lookup, permutation, Any, ProvingKey};
use crate::plonk::{lookup, permutation, Any, ProvingKey, ProvingKeyV2};
use crate::poly::Basis;
use crate::{
arithmetic::{parallelize, CurveAffine},
@ -383,6 +383,317 @@ impl<C: CurveAffine> Evaluator<C> {
ev
}
/// Evaluate h poly
// NOTE: Copy of evaluate_h with ProvingKeyV2
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn evaluate_h_v2(
&self,
pk: &ProvingKeyV2<C>,
advice_polys: &[&[Polynomial<C::ScalarExt, Coeff>]],
instance_polys: &[&[Polynomial<C::ScalarExt, Coeff>]],
challenges: &[C::ScalarExt],
y: C::ScalarExt,
beta: C::ScalarExt,
gamma: C::ScalarExt,
theta: C::ScalarExt,
lookups: &[Vec<lookup::prover::Committed<C>>],
shuffles: &[Vec<shuffle::prover::Committed<C>>],
permutations: &[permutation::prover::Committed<C>],
) -> Polynomial<C::ScalarExt, ExtendedLagrangeCoeff> {
let domain = &pk.vk.domain;
let size = domain.extended_len();
let rot_scale = 1 << (domain.extended_k() - domain.k());
let fixed = &pk.fixed_cosets[..];
let extended_omega = domain.get_extended_omega();
let isize = size as i32;
let one = C::ScalarExt::ONE;
let l0 = &pk.l0;
let l_last = &pk.l_last;
let l_active_row = &pk.l_active_row;
let p = &pk.vk.cs.permutation;
// Calculate the advice and instance cosets
let advice: Vec<Vec<Polynomial<C::Scalar, ExtendedLagrangeCoeff>>> = advice_polys
.iter()
.map(|advice_polys| {
advice_polys
.iter()
.map(|poly| domain.coeff_to_extended(poly.clone()))
.collect()
})
.collect();
let instance: Vec<Vec<Polynomial<C::Scalar, ExtendedLagrangeCoeff>>> = instance_polys
.iter()
.map(|instance_polys| {
instance_polys
.iter()
.map(|poly| domain.coeff_to_extended(poly.clone()))
.collect()
})
.collect();
let mut values = domain.empty_extended();
// Core expression evaluations
let num_threads = multicore::current_num_threads();
for ((((advice, instance), lookups), shuffles), permutation) in advice
.iter()
.zip(instance.iter())
.zip(lookups.iter())
.zip(shuffles.iter())
.zip(permutations.iter())
{
// Custom gates
multicore::scope(|scope| {
let chunk_size = (size + num_threads - 1) / num_threads;
for (thread_idx, values) in values.chunks_mut(chunk_size).enumerate() {
let start = thread_idx * chunk_size;
scope.spawn(move |_| {
let mut eval_data = self.custom_gates.instance();
for (i, value) in values.iter_mut().enumerate() {
let idx = start + i;
*value = self.custom_gates.evaluate(
&mut eval_data,
fixed,
advice,
instance,
challenges,
&beta,
&gamma,
&theta,
&y,
value,
idx,
rot_scale,
isize,
);
}
});
}
});
// Permutations
let sets = &permutation.sets;
if !sets.is_empty() {
let blinding_factors = pk.vk.cs.blinding_factors();
let last_rotation = Rotation(-((blinding_factors + 1) as i32));
let chunk_len = pk.vk.cs.degree() - 2;
let delta_start = beta * &C::Scalar::ZETA;
let first_set = sets.first().unwrap();
let last_set = sets.last().unwrap();
// Permutation constraints
parallelize(&mut values, |values, start| {
let mut beta_term = extended_omega.pow_vartime([start as u64, 0, 0, 0]);
for (i, value) in values.iter_mut().enumerate() {
let idx = start + i;
let r_next = get_rotation_idx(idx, 1, rot_scale, isize);
let r_last = get_rotation_idx(idx, last_rotation.0, rot_scale, isize);
// Enforce only for the first set.
// l_0(X) * (1 - z_0(X)) = 0
*value = *value * y
+ ((one - first_set.permutation_product_coset[idx]) * l0[idx]);
// Enforce only for the last set.
// l_last(X) * (z_l(X)^2 - z_l(X)) = 0
*value = *value * y
+ ((last_set.permutation_product_coset[idx]
* last_set.permutation_product_coset[idx]
- last_set.permutation_product_coset[idx])
* l_last[idx]);
// Except for the first set, enforce.
// l_0(X) * (z_i(X) - z_{i-1}(\omega^(last) X)) = 0
for (set_idx, set) in sets.iter().enumerate() {
if set_idx != 0 {
*value = *value * y
+ ((set.permutation_product_coset[idx]
- permutation.sets[set_idx - 1].permutation_product_coset
[r_last])
* l0[idx]);
}
}
// And for all the sets we enforce:
// (1 - (l_last(X) + l_blind(X))) * (
// z_i(\omega X) \prod_j (p(X) + \beta s_j(X) + \gamma)
// - z_i(X) \prod_j (p(X) + \delta^j \beta X + \gamma)
// )
let mut current_delta = delta_start * beta_term;
for ((set, columns), cosets) in sets
.iter()
.zip(p.columns.chunks(chunk_len))
.zip(pk.permutation.cosets.chunks(chunk_len))
{
let mut left = set.permutation_product_coset[r_next];
for (values, permutation) in columns
.iter()
.map(|&column| match column.column_type() {
Any::Advice(_) => &advice[column.index()],
Any::Fixed => &fixed[column.index()],
Any::Instance => &instance[column.index()],
})
.zip(cosets.iter())
{
left *= values[idx] + beta * permutation[idx] + gamma;
}
let mut right = set.permutation_product_coset[idx];
for values in columns.iter().map(|&column| match column.column_type() {
Any::Advice(_) => &advice[column.index()],
Any::Fixed => &fixed[column.index()],
Any::Instance => &instance[column.index()],
}) {
right *= values[idx] + current_delta + gamma;
current_delta *= &C::Scalar::DELTA;
}
*value = *value * y + ((left - right) * l_active_row[idx]);
}
beta_term *= &extended_omega;
}
});
}
// Lookups
for (n, lookup) in lookups.iter().enumerate() {
// Polynomials required for this lookup.
// Calculated here so these only have to be kept in memory for the short time
// they are actually needed.
let product_coset = pk.vk.domain.coeff_to_extended(lookup.product_poly.clone());
let permuted_input_coset = pk
.vk
.domain
.coeff_to_extended(lookup.permuted_input_poly.clone());
let permuted_table_coset = pk
.vk
.domain
.coeff_to_extended(lookup.permuted_table_poly.clone());
// Lookup constraints
parallelize(&mut values, |values, start| {
let lookup_evaluator = &self.lookups[n];
let mut eval_data = lookup_evaluator.instance();
for (i, value) in values.iter_mut().enumerate() {
let idx = start + i;
let table_value = lookup_evaluator.evaluate(
&mut eval_data,
fixed,
advice,
instance,
challenges,
&beta,
&gamma,
&theta,
&y,
&C::ScalarExt::ZERO,
idx,
rot_scale,
isize,
);
let r_next = get_rotation_idx(idx, 1, rot_scale, isize);
let r_prev = get_rotation_idx(idx, -1, rot_scale, isize);
let a_minus_s = permuted_input_coset[idx] - permuted_table_coset[idx];
// l_0(X) * (1 - z(X)) = 0
*value = *value * y + ((one - product_coset[idx]) * l0[idx]);
// l_last(X) * (z(X)^2 - z(X)) = 0
*value = *value * y
+ ((product_coset[idx] * product_coset[idx] - product_coset[idx])
* l_last[idx]);
// (1 - (l_last(X) + l_blind(X))) * (
// z(\omega X) (a'(X) + \beta) (s'(X) + \gamma)
// - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta)
// (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma)
// ) = 0
*value = *value * y
+ ((product_coset[r_next]
* (permuted_input_coset[idx] + beta)
* (permuted_table_coset[idx] + gamma)
- product_coset[idx] * table_value)
* l_active_row[idx]);
// 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
*value = *value * y + (a_minus_s * l0[idx]);
// 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 expression.
// (1 - (l_last + l_blind)) * (a(X) s(X))⋅(a(X) a(\omega^{-1} X)) = 0
*value = *value * y
+ (a_minus_s
* (permuted_input_coset[idx] - permuted_input_coset[r_prev])
* l_active_row[idx]);
}
});
}
// Shuffle constraints
for (n, shuffle) in shuffles.iter().enumerate() {
let product_coset = pk.vk.domain.coeff_to_extended(shuffle.product_poly.clone());
// Shuffle constraints
parallelize(&mut values, |values, start| {
let input_evaluator = &self.shuffles[2 * n];
let shuffle_evaluator = &self.shuffles[2 * n + 1];
let mut eval_data_input = shuffle_evaluator.instance();
let mut eval_data_shuffle = shuffle_evaluator.instance();
for (i, value) in values.iter_mut().enumerate() {
let idx = start + i;
let input_value = input_evaluator.evaluate(
&mut eval_data_input,
fixed,
advice,
instance,
challenges,
&beta,
&gamma,
&theta,
&y,
&C::ScalarExt::ZERO,
idx,
rot_scale,
isize,
);
let shuffle_value = shuffle_evaluator.evaluate(
&mut eval_data_shuffle,
fixed,
advice,
instance,
challenges,
&beta,
&gamma,
&theta,
&y,
&C::ScalarExt::ZERO,
idx,
rot_scale,
isize,
);
let r_next = get_rotation_idx(idx, 1, rot_scale, isize);
// l_0(X) * (1 - z(X)) = 0
*value = *value * y + ((one - product_coset[idx]) * l0[idx]);
// l_last(X) * (z(X)^2 - z(X)) = 0
*value = *value * y
+ ((product_coset[idx] * product_coset[idx] - product_coset[idx])
* l_last[idx]);
// (1 - (l_last(X) + l_blind(X))) * (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) = 0
*value = *value * y
+ l_active_row[idx]
* (product_coset[r_next] * shuffle_value
- product_coset[idx] * input_value)
}
});
}
}
values
}
/// Evaluate h poly
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn evaluate_h(

View File

@ -1,6 +1,6 @@
use super::super::{
circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error,
ProvingKey,
ProvingKey, ProvingKeyV2,
};
use super::Argument;
use crate::plonk::evaluation::evaluate;
@ -51,6 +51,112 @@ pub(in crate::plonk) struct Evaluated<C: CurveAffine> {
}
impl<F: WithSmallOrderMulGroup<3>> 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_expression_pair() helper,
/// obtaining A' and S', and
/// - constructs Permuted<C> struct using permuted_input_value = A', and
/// permuted_table_expression = S'.
/// The Permuted<C> struct is used to update the Lookup, and is then returned.
// NOTE: Copy of commit_permuted that uses ProvingKeyV2
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn commit_permuted_v2<
'a,
'params: 'a,
C,
P: Params<'params, C>,
E: EncodedChallenge<C>,
R: RngCore,
T: TranscriptWrite<C, E>,
>(
&self,
pk: &ProvingKeyV2<C>,
params: &P,
domain: &EvaluationDomain<C::Scalar>,
theta: ChallengeTheta<C>,
advice_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
fixed_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
instance_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
challenges: &'a [C::Scalar],
mut rng: R,
transcript: &mut T,
) -> Result<Permuted<C>, Error>
where
C: CurveAffine<ScalarExt = F>,
C::Curve: Mul<F, Output = C::Curve> + MulAssign<F>,
{
// Closure to get values of expressions and compress them
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
let compressed_expression = expressions
.iter()
.map(|expression| {
pk.vk.domain.lagrange_from_vec(evaluate(
expression,
params.n() as usize,
1,
fixed_values,
advice_values,
instance_values,
challenges,
))
})
.fold(domain.empty_lagrange(), |acc, expression| {
acc * *theta + &expression
});
compressed_expression
};
// Get values of input expressions involved in the lookup and compress them
let compressed_input_expression = compress_expressions(&self.input_expressions);
// Get values of table expressions involved in the lookup and compress them
let compressed_table_expression = compress_expressions(&self.table_expressions);
// Permute compressed (InputExpression, TableExpression) pair
let (permuted_input_expression, permuted_table_expression) = permute_expression_pair_v2(
pk,
params,
domain,
&mut rng,
&compressed_input_expression,
&compressed_table_expression,
)?;
// Closure to construct commitment to vector of values
let mut commit_values = |values: &Polynomial<C::Scalar, LagrangeCoeff>| {
let poly = pk.vk.domain.lagrange_to_coeff(values.clone());
let blind = Blind(C::Scalar::random(&mut rng));
let commitment = params.commit_lagrange(values, blind).to_affine();
(poly, blind, commitment)
};
// Commit to permuted input expression
let (permuted_input_poly, permuted_input_blind, permuted_input_commitment) =
commit_values(&permuted_input_expression);
// Commit to permuted table expression
let (permuted_table_poly, permuted_table_blind, permuted_table_commitment) =
commit_values(&permuted_table_expression);
// Hash permuted input commitment
transcript.write_point(permuted_input_commitment)?;
// Hash permuted table commitment
transcript.write_point(permuted_table_commitment)?;
Ok(Permuted {
compressed_input_expression,
permuted_input_expression,
permuted_input_poly,
permuted_input_blind,
compressed_table_expression,
permuted_table_expression,
permuted_table_poly,
permuted_table_blind,
})
}
/// 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}
@ -159,6 +265,151 @@ impl<F: WithSmallOrderMulGroup<3>> Argument<F> {
}
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.
// NOTE: Copy of commit_permuted with ProvingKeyV2
pub(in crate::plonk) fn commit_product_v2<
'params,
P: Params<'params, C>,
E: EncodedChallenge<C>,
R: RngCore,
T: TranscriptWrite<C, E>,
>(
self,
pk: &ProvingKeyV2<C>,
params: &P,
beta: ChallengeBeta<C>,
gamma: ChallengeGamma<C>,
mut rng: R,
transcript: &mut T,
) -> Result<Committed<C>, Error> {
let blinding_factors = pk.vk.cs.blinding_factors();
// 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 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 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_expression[start..].iter())
.zip(self.permuted_table_expression[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;
*product *= &(self.compressed_input_expression[i] + &*beta);
*product *= &(self.compressed_table_expression[i] + &*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 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
let z = iter::once(C::Scalar::ONE)
.chain(lookup_product)
.scan(C::Scalar::ONE, |state, cur| {
*state *= &cur;
Some(*state)
})
// Take all rows including the "last" row which should
// be a boolean (and ideally 1, else soundness is broken)
.take(params.n() as usize - blinding_factors)
// Chain random blinding factors.
.chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng)))
.collect::<Vec<_>>();
assert_eq!(z.len(), params.n() as usize);
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 u = (params.n() as usize) - (blinding_factors + 1);
// l_0(X) * (1 - z(X)) = 0
assert_eq!(z[0], C::Scalar::ONE);
// z(\omega X) (a'(X) + \beta) (s'(X) + \gamma)
// - z(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..u {
let mut left = z[i + 1];
let permuted_input_value = &self.permuted_input_expression[i];
let permuted_table_value = &self.permuted_table_expression[i];
left *= &(*beta + permuted_input_value);
left *= &(*gamma + permuted_table_value);
let mut right = z[i];
let mut input_term = self.compressed_input_expression[i];
let mut table_term = self.compressed_table_expression[i];
input_term += &(*beta);
table_term += &(*gamma);
right *= &(input_term * &table_term);
assert_eq!(left, right);
}
// l_last(X) * (z(X)^2 - z(X)) = 0
// Assertion will fail only when soundness is broken, in which
// case this z[u] value will be zero. (bad!)
assert_eq!(z[u], C::Scalar::ONE);
}
let product_blind = Blind(C::Scalar::random(rng));
let product_commitment = params.commit_lagrange(&z, product_blind).to_affine();
let z = pk.vk.domain.lagrange_to_coeff(z);
// Hash product commitment
transcript.write_point(product_commitment)?;
Ok(Committed::<C> {
permuted_input_poly: self.permuted_input_poly,
permuted_input_blind: self.permuted_input_blind,
permuted_table_poly: self.permuted_table_poly,
permuted_table_blind: self.permuted_table_blind,
product_poly: z,
product_blind,
})
}
/// 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
@ -306,6 +557,36 @@ impl<C: CurveAffine> Permuted<C> {
}
impl<C: CurveAffine> Committed<C> {
pub(in crate::plonk) fn evaluate_v2<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
self,
pk: &ProvingKeyV2<C>,
x: ChallengeX<C>,
transcript: &mut T,
) -> Result<Evaluated<C>, Error> {
let domain = &pk.vk.domain;
let x_inv = domain.rotate_omega(*x, Rotation::prev());
let x_next = domain.rotate_omega(*x, Rotation::next());
let product_eval = eval_polynomial(&self.product_poly, *x);
let product_next_eval = eval_polynomial(&self.product_poly, x_next);
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_next_eval))
.chain(Some(permuted_input_eval))
.chain(Some(permuted_input_inv_eval))
.chain(Some(permuted_table_eval))
{
transcript.write_scalar(eval)?;
}
Ok(Evaluated { constructed: self })
}
pub(in crate::plonk) fn evaluate<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
self,
pk: &ProvingKey<C>,
@ -338,6 +619,48 @@ impl<C: CurveAffine> Committed<C> {
}
impl<C: CurveAffine> Evaluated<C> {
// NOTE: Copy of open with ProvingKeyV2
pub(in crate::plonk) fn open_v2<'a>(
&'a self,
pk: &'a ProvingKeyV2<C>,
x: ChallengeX<C>,
) -> impl Iterator<Item = ProverQuery<'a, C>> + Clone {
let x_inv = pk.vk.domain.rotate_omega(*x, Rotation::prev());
let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next());
iter::empty()
// Open lookup product commitments at x
.chain(Some(ProverQuery {
point: *x,
poly: &self.constructed.product_poly,
blind: self.constructed.product_blind,
}))
// Open lookup input commitments at x
.chain(Some(ProverQuery {
point: *x,
poly: &self.constructed.permuted_input_poly,
blind: self.constructed.permuted_input_blind,
}))
// Open lookup table commitments at x
.chain(Some(ProverQuery {
point: *x,
poly: &self.constructed.permuted_table_poly,
blind: self.constructed.permuted_table_blind,
}))
// 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,
}))
// Open lookup product commitments at x_next
.chain(Some(ProverQuery {
point: x_next,
poly: &self.constructed.product_poly,
blind: self.constructed.product_blind,
}))
}
pub(in crate::plonk) fn open<'a>(
&'a self,
pk: &'a ProvingKey<C>,
@ -382,6 +705,99 @@ impl<C: CurveAffine> Evaluated<C> {
type ExpressionPair<F> = (Polynomial<F, LagrangeCoeff>, Polynomial<F, LagrangeCoeff>);
/// 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.
// NOTE: Copy of permute_expression_pair that uses ProvingKeyV2
fn permute_expression_pair_v2<'params, C: CurveAffine, P: Params<'params, C>, R: RngCore>(
pk: &ProvingKeyV2<C>,
params: &P,
domain: &EvaluationDomain<C::Scalar>,
mut rng: R,
input_expression: &Polynomial<C::Scalar, LagrangeCoeff>,
table_expression: &Polynomial<C::Scalar, LagrangeCoeff>,
) -> Result<ExpressionPair<C::Scalar>, Error> {
let blinding_factors = pk.vk.cs.blinding_factors();
let usable_rows = params.n() as usize - (blinding_factors + 1);
let mut permuted_input_expression: Vec<C::Scalar> = input_expression.to_vec();
permuted_input_expression.truncate(usable_rows);
// Sort input lookup expression values
permuted_input_expression.sort();
// A BTreeMap of each unique element in the table expression and its count
let mut leftover_table_map: BTreeMap<C::Scalar, u32> = table_expression
.iter()
.take(usable_rows)
.fold(BTreeMap::new(), |mut acc, coeff| {
*acc.entry(*coeff).or_insert(0) += 1;
acc
});
let mut permuted_table_coeffs = vec![C::Scalar::ZERO; usable_rows];
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 occurrence 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) {
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()] = *coeff;
}
}
assert!(repeated_input_rows.is_empty());
permuted_input_expression
.extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng)));
permuted_table_coeffs.extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng)));
assert_eq!(permuted_input_expression.len(), params.n() as usize);
assert_eq!(permuted_table_coeffs.len(), params.n() as usize);
#[cfg(feature = "sanity-checks")]
{
let mut last = None;
for (a, b) in permuted_input_expression
.iter()
.zip(permuted_table_coeffs.iter())
.take(usable_rows)
{
if *a != *b {
assert_eq!(*a, last.unwrap());
}
last = Some(*a);
}
}
Ok((
domain.lagrange_from_vec(permuted_input_expression),
domain.lagrange_from_vec(permuted_table_coeffs),
))
}
/// 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

View File

@ -42,6 +42,155 @@ pub(crate) struct Evaluated<C: CurveAffine> {
}
impl Argument {
// NOTE: Copy of commit with ProvingKeyV2
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn commit_v2<
'params,
C: CurveAffine,
P: Params<'params, C>,
E: EncodedChallenge<C>,
R: RngCore,
T: TranscriptWrite<C, E>,
>(
&self,
params: &P,
pk: &plonk::ProvingKeyV2<C>,
pkey: &ProvingKey<C>,
advice: &[Polynomial<C::Scalar, LagrangeCoeff>],
fixed: &[Polynomial<C::Scalar, LagrangeCoeff>],
instance: &[Polynomial<C::Scalar, LagrangeCoeff>],
beta: ChallengeBeta<C>,
gamma: ChallengeGamma<C>,
mut rng: R,
transcript: &mut T,
) -> Result<Committed<C>, Error> {
let domain = &pk.vk.domain;
// How many columns can be included in a single permutation polynomial?
// We need to multiply by z(X) and (1 - (l_last(X) + l_blind(X))). This
// will never underflow because of the requirement of at least a degree
// 3 circuit for the permutation argument.
assert!(pk.vk.cs_degree >= 3);
let chunk_len = pk.vk.cs_degree - 2;
let blinding_factors = pk.vk.cs.blinding_factors();
// Each column gets its own delta power.
let mut deltaomega = C::Scalar::ONE;
// Track the "last" value from the previous column set
let mut last_z = C::Scalar::ONE;
let mut sets = vec![];
for (columns, permutations) in self
.columns
.chunks(chunk_len)
.zip(pkey.permutations.chunks(chunk_len))
{
// Goal is to compute the products of fractions
//
// (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) /
// (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma)
//
// where p_j(X) is the jth column in this permutation,
// and i is the ith row of the column.
let mut modified_values = vec![C::Scalar::ONE; params.n() as usize];
// Iterate over each column of the permutation
for (&column, permuted_column_values) in columns.iter().zip(permutations.iter()) {
let values = match column.column_type() {
Any::Advice(_) => advice,
Any::Fixed => fixed,
Any::Instance => instance,
};
parallelize(&mut modified_values, |modified_values, start| {
for ((modified_values, value), permuted_value) in modified_values
.iter_mut()
.zip(values[column.index()][start..].iter())
.zip(permuted_column_values[start..].iter())
{
*modified_values *= &(*beta * permuted_value + &*gamma + value);
}
});
}
// Invert to obtain the denominator for the permutation product polynomial
modified_values.batch_invert();
// Iterate over each column again, this time finishing the computation
// of the entire fraction by computing the numerators
for &column in columns.iter() {
let omega = domain.get_omega();
let values = match column.column_type() {
Any::Advice(_) => advice,
Any::Fixed => fixed,
Any::Instance => instance,
};
parallelize(&mut modified_values, |modified_values, start| {
let mut deltaomega = deltaomega * &omega.pow_vartime([start as u64, 0, 0, 0]);
for (modified_values, value) in modified_values
.iter_mut()
.zip(values[column.index()][start..].iter())
{
// Multiply by p_j(\omega^i) + \delta^j \omega^i \beta
*modified_values *= &(deltaomega * &*beta + &*gamma + value);
deltaomega *= &omega;
}
});
deltaomega *= &<C::Scalar as PrimeField>::DELTA;
}
// The modified_values vector is a vector of products of fractions
// of the form
//
// (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) /
// (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma)
//
// where i is the index into modified_values, for the jth column in
// the permutation
// Compute the evaluations of the permutation product polynomial
// over our domain, starting with z[0] = 1
let mut z = vec![last_z];
for row in 1..(params.n() as usize) {
let mut tmp = z[row - 1];
tmp *= &modified_values[row - 1];
z.push(tmp);
}
let mut z = domain.lagrange_from_vec(z);
// Set blinding factors
for z in &mut z[params.n() as usize - blinding_factors..] {
*z = C::Scalar::random(&mut rng);
}
// Set new last_z
last_z = z[params.n() as usize - (blinding_factors + 1)];
let blind = Blind(C::Scalar::random(&mut rng));
let permutation_product_commitment_projective = params.commit_lagrange(&z, blind);
let permutation_product_blind = blind;
let z = domain.lagrange_to_coeff(z);
let permutation_product_poly = z.clone();
let permutation_product_coset = domain.coeff_to_extended(z.clone());
let permutation_product_commitment =
permutation_product_commitment_projective.to_affine();
// Hash the permutation product commitment
transcript.write_point(permutation_product_commitment)?;
sets.push(CommittedSet {
permutation_product_poly,
permutation_product_coset,
permutation_product_blind,
});
}
Ok(Committed { sets })
}
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn commit<
'params,
@ -234,6 +383,51 @@ impl<C: CurveAffine> super::ProvingKey<C> {
}
impl<C: CurveAffine> Constructed<C> {
// NOTE: Copy of evaluate with ProvingKeyV2
pub(in crate::plonk) fn evaluate_v2<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
self,
pk: &plonk::ProvingKeyV2<C>,
x: ChallengeX<C>,
transcript: &mut T,
) -> Result<Evaluated<C>, Error> {
let domain = &pk.vk.domain;
let blinding_factors = pk.vk.cs.blinding_factors();
{
let mut sets = self.sets.iter();
while let Some(set) = sets.next() {
let permutation_product_eval = eval_polynomial(&set.permutation_product_poly, *x);
let permutation_product_next_eval = eval_polynomial(
&set.permutation_product_poly,
domain.rotate_omega(*x, Rotation::next()),
);
// Hash permutation product evals
for eval in iter::empty()
.chain(Some(&permutation_product_eval))
.chain(Some(&permutation_product_next_eval))
{
transcript.write_scalar(*eval)?;
}
// If we have any remaining sets to process, evaluate this set at omega^u
// so we can constrain the last value of its running product to equal the
// first value of the next set's running product, chaining them together.
if sets.len() > 0 {
let permutation_product_last_eval = eval_polynomial(
&set.permutation_product_poly,
domain.rotate_omega(*x, Rotation(-((blinding_factors + 1) as i32))),
);
transcript.write_scalar(permutation_product_last_eval)?;
}
}
}
Ok(Evaluated { constructed: self })
}
pub(in crate::plonk) fn evaluate<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
self,
pk: &plonk::ProvingKey<C>,
@ -281,6 +475,52 @@ impl<C: CurveAffine> Constructed<C> {
}
impl<C: CurveAffine> Evaluated<C> {
// NOTE: Copy of open with ProvingKeyV2
pub(in crate::plonk) fn open_v2<'a>(
&'a self,
pk: &'a plonk::ProvingKeyV2<C>,
x: ChallengeX<C>,
) -> impl Iterator<Item = ProverQuery<'a, C>> + Clone {
let blinding_factors = pk.vk.cs.blinding_factors();
let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next());
let x_last = pk
.vk
.domain
.rotate_omega(*x, Rotation(-((blinding_factors + 1) as i32)));
iter::empty()
.chain(self.constructed.sets.iter().flat_map(move |set| {
iter::empty()
// Open permutation product commitments at x and \omega x
.chain(Some(ProverQuery {
point: *x,
poly: &set.permutation_product_poly,
blind: set.permutation_product_blind,
}))
.chain(Some(ProverQuery {
point: x_next,
poly: &set.permutation_product_poly,
blind: set.permutation_product_blind,
}))
}))
// Open it at \omega^{last} x for all but the last set. This rotation is only
// sensical for the first row, but we only use this rotation in a constraint
// that is gated on l_0.
.chain(
self.constructed
.sets
.iter()
.rev()
.skip(1)
.flat_map(move |set| {
Some(ProverQuery {
point: x_last,
poly: &set.permutation_product_poly,
blind: set.permutation_product_blind,
})
}),
)
}
pub(in crate::plonk) fn open<'a>(
&'a self,
pk: &'a plonk::ProvingKey<C>,

View File

@ -42,6 +42,7 @@ struct AdviceSingle<C: CurveAffine, B: Basis> {
pub advice_blinds: Vec<Blind<C::Scalar>>,
}
// TODO: Rewrite as multi-instance prover, and make a wraper for signle-instance case.
/// The prover object used to create proofs interactively by passing the witnesses to commit at
/// each phase.
#[derive(Debug)]
@ -61,7 +62,7 @@ pub struct ProverV2<
instance_queries: Vec<(Column<Instance>, Rotation)>,
fixed_queries: Vec<(Column<Fixed>, Rotation)>,
phases: Vec<u8>,
instance: Vec<InstanceSingle<Scheme::Curve>>,
instance: InstanceSingle<Scheme::Curve>,
rng: R,
transcript: T,
advice: AdviceSingle<Scheme::Curve, LagrangeCoeff>,
@ -89,9 +90,12 @@ impl<
rng: R,
mut transcript: T,
) -> Result<Self, Error>
// TODO: Can I move this `where` to the struct definition?
where
Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>,
{
// TODO: We have cs duplicated in circuit.cs and pk.vk.cs. Can we dedup them?
if instance.len() != pk.vk.cs.num_instance_columns {
return Err(Error::InvalidInstances);
}
@ -107,59 +111,57 @@ impl<
let domain = &pk.vk.domain;
let instance: Vec<InstanceSingle<Scheme::Curve>> = iter::once(instance)
.map(|instance| -> Result<InstanceSingle<Scheme::Curve>, Error> {
let instance_values = instance
.iter()
.map(|values| {
let mut poly = domain.empty_lagrange();
assert_eq!(poly.len(), params.n() as usize);
if values.len() > (poly.len() - (meta.blinding_factors() + 1)) {
return Err(Error::InstanceTooLarge);
}
for (poly, value) in poly.iter_mut().zip(values.iter()) {
if !P::QUERY_INSTANCE {
transcript.common_scalar(*value)?;
}
*poly = *value;
}
Ok(poly)
})
.collect::<Result<Vec<_>, _>>()?;
if P::QUERY_INSTANCE {
let instance_commitments_projective: Vec<_> = instance_values
.iter()
.map(|poly| params.commit_lagrange(poly, Blind::default()))
.collect();
let mut instance_commitments =
vec![Scheme::Curve::identity(); instance_commitments_projective.len()];
<Scheme::Curve as CurveAffine>::CurveExt::batch_normalize(
&instance_commitments_projective,
&mut instance_commitments,
);
let instance_commitments = instance_commitments;
drop(instance_commitments_projective);
for commitment in &instance_commitments {
transcript.common_point(*commitment)?;
let instance: InstanceSingle<Scheme::Curve> = {
let instance_values = instance
.iter()
.map(|values| {
let mut poly = domain.empty_lagrange();
assert_eq!(poly.len(), params.n() as usize);
if values.len() > (poly.len() - (meta.blinding_factors() + 1)) {
return Err(Error::InstanceTooLarge);
}
}
let instance_polys: Vec<_> = instance_values
.iter()
.map(|poly| {
let lagrange_vec = domain.lagrange_from_vec(poly.to_vec());
domain.lagrange_to_coeff(lagrange_vec)
})
.collect();
Ok(InstanceSingle {
instance_values,
instance_polys,
for (poly, value) in poly.iter_mut().zip(values.iter()) {
if !P::QUERY_INSTANCE {
transcript.common_scalar(*value)?;
}
*poly = *value;
}
Ok(poly)
})
})
.collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<_>, _>>()?;
if P::QUERY_INSTANCE {
let instance_commitments_projective: Vec<_> = instance_values
.iter()
.map(|poly| params.commit_lagrange(poly, Blind::default()))
.collect();
let mut instance_commitments =
vec![Scheme::Curve::identity(); instance_commitments_projective.len()];
<Scheme::Curve as CurveAffine>::CurveExt::batch_normalize(
&instance_commitments_projective,
&mut instance_commitments,
);
let instance_commitments = instance_commitments;
drop(instance_commitments_projective);
for commitment in &instance_commitments {
transcript.common_point(*commitment)?;
}
}
let instance_polys: Vec<_> = instance_values
.iter()
.map(|poly| {
let lagrange_vec = domain.lagrange_from_vec(poly.to_vec());
domain.lagrange_to_coeff(lagrange_vec)
})
.collect();
InstanceSingle {
instance_values,
instance_polys,
}
};
let advice = AdviceSingle::<Scheme::Curve, LagrangeCoeff> {
advice_polys: vec![domain.empty_lagrange(); meta.num_advice_columns],
@ -208,13 +210,12 @@ impl<
let params = self.params;
let meta = self.cs;
let domain = self.pk.vk.domain;
let mut transcript = self.transcript;
let mut rng = self.rng;
let transcript = &mut self.transcript;
let mut rng = &mut self.rng;
let mut advice = self.advice;
let mut challenges = self.challenges;
let advice = &mut self.advice;
let challenges = &mut self.challenges;
let column_indices = meta
.advice_column_phase
@ -242,7 +243,8 @@ impl<
}
let mut advice_values =
batch_invert_assigned::<Scheme::Scalar>(witness.into_iter().flatten().collect());
let unblinded_advice = HashSet::from_iter(meta.unblinded_advice_columns.clone());
let unblinded_advice: HashSet<usize> =
HashSet::from_iter(meta.unblinded_advice_columns.clone());
let unusable_rows_start = params.n() as usize - (meta.blinding_factors() + 1);
// Add blinding factors to advice columns
@ -306,8 +308,274 @@ impl<
Ok(challenges.clone())
}
pub fn create_proof(self) -> Result<T, Error> {
todo!()
/// Finalizes the proof creation.
pub fn create_proof(mut self) -> Result<T, Error>
where
Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>,
{
let params = self.params;
let meta = self.cs;
let pk = self.pk;
let domain = &self.pk.vk.domain;
let mut transcript = self.transcript;
let mut rng = self.rng;
let instance = std::mem::replace(
&mut self.instance,
InstanceSingle {
instance_values: Vec::new(),
instance_polys: Vec::new(),
},
);
let advice = std::mem::replace(
&mut self.advice,
AdviceSingle {
advice_polys: Vec::new(),
advice_blinds: Vec::new(),
},
);
let mut challenges = self.challenges;
assert_eq!(challenges.len(), meta.num_challenges);
let challenges = (0..meta.num_challenges)
.map(|index| challenges.remove(&index).unwrap())
.collect::<Vec<_>>();
// Sample theta challenge for keeping lookup columns linearly independent
let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar();
// Construct and commit to permuted values for each lookup
let lookups: Vec<lookup::prover::Permuted<Scheme::Curve>> = pk
.vk
.cs
.lookups
.iter()
.map(|lookup| {
lookup.commit_permuted_v2(
pk,
params,
&domain,
theta,
&advice.advice_polys,
&pk.fixed_values,
&instance.instance_values,
&challenges,
&mut rng,
&mut transcript,
)
})
.collect::<Result<Vec<_>, _>>()?;
// Sample beta challenge
let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar();
// Sample gamma challenge
let gamma: ChallengeGamma<_> = transcript.squeeze_challenge_scalar();
// Commit to permutation.
let permutation = [pk.vk.cs.permutation.commit_v2(
params,
pk,
&pk.permutation,
&advice.advice_polys,
&pk.fixed_values,
&instance.instance_values,
beta,
gamma,
&mut rng,
&mut transcript,
)?];
// Construct and commit to products for each lookup
let lookups: [Vec<lookup::prover::Committed<Scheme::Curve>>; 1] = [lookups
.into_iter()
.map(|lookup| {
lookup.commit_product_v2(pk, params, beta, gamma, &mut rng, &mut transcript)
})
.collect::<Result<Vec<_>, _>>()?];
// Compress expressions for each shuffle
let shuffles: [Vec<shuffle::prover::Committed<Scheme::Curve>>; 1] = [pk
.vk
.cs
.shuffles
.iter()
.map(|shuffle| {
shuffle.commit_product_v2(
pk,
params,
domain,
theta,
gamma,
&advice.advice_polys,
&pk.fixed_values,
&instance.instance_values,
&challenges,
&mut rng,
&mut transcript,
)
})
.collect::<Result<Vec<_>, _>>()?];
// Commit to the vanishing argument's random polynomial for blinding h(x_3)
let vanishing = vanishing::Argument::commit(params, domain, &mut rng, &mut transcript)?;
// Obtain challenge for keeping all separate gates linearly independent
let y: ChallengeY<_> = transcript.squeeze_challenge_scalar();
// Calculate the advice polys
let advice: AdviceSingle<Scheme::Curve, Coeff> = AdviceSingle {
advice_polys: advice
.advice_polys
.into_iter()
.map(|poly| domain.lagrange_to_coeff(poly))
.collect::<Vec<_>>(),
advice_blinds: advice.advice_blinds,
};
// Evaluate the h(X) polynomial
let h_poly = pk.ev.evaluate_h_v2(
pk,
&[advice.advice_polys.as_slice()],
&[instance.instance_polys.as_slice()],
&challenges,
*y,
*beta,
*gamma,
*theta,
&lookups,
&shuffles,
&permutation,
);
// Construct the vanishing argument's h(X) commitments
let vanishing = vanishing.construct(params, domain, h_poly, &mut rng, &mut transcript)?;
let x: ChallengeX<_> = transcript.squeeze_challenge_scalar();
let xn = x.pow([params.n()]);
if P::QUERY_INSTANCE {
// Compute and hash instance evals for the circuit instance
// Evaluate polynomials at omega^i x
let instance_evals: Vec<_> = self
.instance_queries
.iter()
.map(|&(column, at)| {
eval_polynomial(
&instance.instance_polys[column.index()],
domain.rotate_omega(*x, at),
)
})
.collect();
// Hash each instance column evaluation
for eval in instance_evals.iter() {
transcript.write_scalar(*eval)?;
}
}
// Compute and hash advice evals for the circuit instance
// Evaluate polynomials at omega^i x
let advice_evals: Vec<_> = self
.advice_queries
.iter()
.map(|&(column, at)| {
eval_polynomial(
&advice.advice_polys[column.index()],
domain.rotate_omega(*x, at),
)
})
.collect();
// Hash each advice column evaluation
for eval in advice_evals.iter() {
transcript.write_scalar(*eval)?;
}
// Compute and hash fixed evals
let fixed_evals: Vec<_> = self
.fixed_queries
.iter()
.map(|&(column, at)| {
eval_polynomial(&pk.fixed_polys[column.index()], domain.rotate_omega(*x, at))
})
.collect();
// Hash each fixed column evaluation
for eval in fixed_evals.iter() {
transcript.write_scalar(*eval)?;
}
let vanishing = vanishing.evaluate(x, xn, domain, &mut transcript)?;
// Evaluate common permutation data
pk.permutation.evaluate(x, &mut transcript)?;
let [permutation] = permutation;
let [lookups] = lookups;
let [shuffles] = shuffles;
// Evaluate the permutations, if any, at omega^i x.
let permutation = permutation
.construct()
.evaluate_v2(pk, x, &mut transcript)?;
// Evaluate the lookups, if any, at omega^i x.
let lookups: Vec<lookup::prover::Evaluated<Scheme::Curve>> = lookups
.into_iter()
.map(|p| p.evaluate_v2(pk, x, &mut transcript))
.collect::<Result<Vec<_>, _>>()?;
// Evaluate the shuffles, if any, at omega^i x.
let shuffles: Vec<shuffle::prover::Evaluated<Scheme::Curve>> = shuffles
.into_iter()
.map(|p| p.evaluate_v2(pk, x, &mut transcript))
.collect::<Result<Vec<_>, _>>()?;
let instance_ref = &instance;
let advice_ref = &advice;
let instances =
iter::empty()
.chain(
P::QUERY_INSTANCE
.then_some(self.instance_queries.iter().map(move |&(column, at)| {
ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &instance_ref.instance_polys[column.index()],
blind: Blind::default(),
}
}))
.into_iter()
.flatten(),
)
.chain(
self.advice_queries
.iter()
.map(move |&(column, at)| ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &advice_ref.advice_polys[column.index()],
blind: advice_ref.advice_blinds[column.index()],
}),
)
.chain(permutation.open_v2(pk, x))
.chain(lookups.iter().flat_map(move |p| p.open_v2(pk, x)))
.chain(shuffles.iter().flat_map(move |p| p.open_v2(pk, x)))
.chain(self.fixed_queries.iter().map(|&(column, at)| ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &pk.fixed_polys[column.index()],
blind: Blind::default(),
}))
.chain(pk.permutation.open(x))
// We query the h(X) polynomial at x
.chain(vanishing.open(x));
let prover = P::new(params);
prover
.create_proof(rng, &mut transcript, instances)
.map_err(|_| Error::ConstraintSystemFailure)?;
Ok(transcript)
}
}

View File

@ -1,5 +1,6 @@
use super::super::{
circuit::Expression, ChallengeGamma, ChallengeTheta, ChallengeX, Error, ProvingKey,
ProvingKeyV2,
};
use super::Argument;
use crate::plonk::evaluation::evaluate;
@ -36,6 +37,60 @@ pub(in crate::plonk) struct Evaluated<C: CurveAffine> {
}
impl<F: WithSmallOrderMulGroup<3>> Argument<F> {
/// Given a Shuffle 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},
// NOTE: Copy of compress with ProvingKeyV2
#[allow(clippy::too_many_arguments)]
fn compress_v2<'a, 'params: 'a, C, P: Params<'params, C>>(
&self,
pk: &ProvingKeyV2<C>,
params: &P,
domain: &EvaluationDomain<C::Scalar>,
theta: ChallengeTheta<C>,
advice_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
fixed_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
instance_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
challenges: &'a [C::Scalar],
) -> Compressed<C>
where
C: CurveAffine<ScalarExt = F>,
C::Curve: Mul<F, Output = C::Curve> + MulAssign<F>,
{
// Closure to get values of expressions and compress them
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
let compressed_expression = expressions
.iter()
.map(|expression| {
pk.vk.domain.lagrange_from_vec(evaluate(
expression,
params.n() as usize,
1,
fixed_values,
advice_values,
instance_values,
challenges,
))
})
.fold(domain.empty_lagrange(), |acc, expression| {
acc * *theta + &expression
});
compressed_expression
};
// Get values of input expressions involved in the shuffle and compress them
let input_expression = compress_expressions(&self.input_expressions);
// Get values of table expressions involved in the shuffle and compress them
let shuffle_expression = compress_expressions(&self.shuffle_expressions);
Compressed {
input_expression,
shuffle_expression,
}
}
/// Given a Shuffle 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}
@ -89,6 +144,117 @@ impl<F: WithSmallOrderMulGroup<3>> Argument<F> {
}
}
/// Given a Shuffle with input expressions and table expressions this method
/// constructs the grand product polynomial over the shuffle.
/// The grand product polynomial is used to populate the Product<C> struct.
/// The Product<C> struct is added to the Shuffle and finally returned by the method.
// NOTE: Copy of commit_product with ProvingKeyV2
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn commit_product_v2<
'a,
'params: 'a,
C,
P: Params<'params, C>,
E: EncodedChallenge<C>,
R: RngCore,
T: TranscriptWrite<C, E>,
>(
&self,
pk: &ProvingKeyV2<C>,
params: &P,
domain: &EvaluationDomain<C::Scalar>,
theta: ChallengeTheta<C>,
gamma: ChallengeGamma<C>,
advice_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
fixed_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
instance_values: &'a [Polynomial<C::Scalar, LagrangeCoeff>],
challenges: &'a [C::Scalar],
mut rng: R,
transcript: &mut T,
) -> Result<Committed<C>, Error>
where
C: CurveAffine<ScalarExt = F>,
C::Curve: Mul<F, Output = C::Curve> + MulAssign<F>,
{
let compressed = self.compress_v2(
pk,
params,
domain,
theta,
advice_values,
fixed_values,
instance_values,
challenges,
);
let blinding_factors = pk.vk.cs.blinding_factors();
let mut shuffle_product = vec![C::Scalar::ZERO; params.n() as usize];
parallelize(&mut shuffle_product, |shuffle_product, start| {
for (shuffle_product, shuffle_value) in shuffle_product
.iter_mut()
.zip(compressed.shuffle_expression[start..].iter())
{
*shuffle_product = *gamma + shuffle_value;
}
});
shuffle_product.iter_mut().batch_invert();
parallelize(&mut shuffle_product, |product, start| {
for (i, product) in product.iter_mut().enumerate() {
let i = i + start;
*product *= &(*gamma + compressed.input_expression[i]);
}
});
// Compute the evaluations of the shuffle product polynomial
// over our domain, starting with z[0] = 1
let z = iter::once(C::Scalar::ONE)
.chain(shuffle_product)
.scan(C::Scalar::ONE, |state, cur| {
*state *= &cur;
Some(*state)
})
// Take all rows including the "last" row which should
// be a boolean (and ideally 1, else soundness is broken)
.take(params.n() as usize - blinding_factors)
// Chain random blinding factors.
.chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng)))
.collect::<Vec<_>>();
assert_eq!(z.len(), params.n() as usize);
let z = pk.vk.domain.lagrange_from_vec(z);
#[cfg(feature = "sanity-checks")]
{
// While in Lagrange basis, check that product is correctly constructed
let u = (params.n() as usize) - (blinding_factors + 1);
assert_eq!(z[0], C::Scalar::ONE);
for i in 0..u {
let mut left = z[i + 1];
let input_value = &compressed.input_expression[i];
let shuffle_value = &compressed.shuffle_expression[i];
left *= &(*gamma + shuffle_value);
let mut right = z[i];
right *= &(*gamma + input_value);
assert_eq!(left, right);
}
assert_eq!(z[u], C::Scalar::ONE);
}
let product_blind = Blind(C::Scalar::random(rng));
let product_commitment = params.commit_lagrange(&z, product_blind).to_affine();
let z = pk.vk.domain.lagrange_to_coeff(z);
// Hash product commitment
transcript.write_point(product_commitment)?;
Ok(Committed::<C> {
product_poly: z,
product_blind,
})
}
/// Given a Shuffle with input expressions and table expressions this method
/// constructs the grand product polynomial over the shuffle.
/// The grand product polynomial is used to populate the Product<C> struct.
@ -201,6 +367,30 @@ impl<F: WithSmallOrderMulGroup<3>> Argument<F> {
}
impl<C: CurveAffine> Committed<C> {
// NOTE: Copy of evaluate with ProvingKeyV2
pub(in crate::plonk) fn evaluate_v2<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
self,
pk: &ProvingKeyV2<C>,
x: ChallengeX<C>,
transcript: &mut T,
) -> Result<Evaluated<C>, Error> {
let domain = &pk.vk.domain;
let x_next = domain.rotate_omega(*x, Rotation::next());
let product_eval = eval_polynomial(&self.product_poly, *x);
let product_next_eval = eval_polynomial(&self.product_poly, x_next);
// Hash each advice evaluation
for eval in iter::empty()
.chain(Some(product_eval))
.chain(Some(product_next_eval))
{
transcript.write_scalar(eval)?;
}
Ok(Evaluated { constructed: self })
}
pub(in crate::plonk) fn evaluate<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
self,
pk: &ProvingKey<C>,
@ -226,6 +416,29 @@ impl<C: CurveAffine> Committed<C> {
}
impl<C: CurveAffine> Evaluated<C> {
// NOTE: Copy of open with ProvingKeyV2
pub(in crate::plonk) fn open_v2<'a>(
&'a self,
pk: &'a ProvingKeyV2<C>,
x: ChallengeX<C>,
) -> impl Iterator<Item = ProverQuery<'a, C>> + Clone {
let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next());
iter::empty()
// Open shuffle product commitments at x
.chain(Some(ProverQuery {
point: *x,
poly: &self.constructed.product_poly,
blind: self.constructed.product_blind,
}))
// Open shuffle product commitments at x_next
.chain(Some(ProverQuery {
point: x_next,
poly: &self.constructed.product_poly,
blind: self.constructed.product_blind,
}))
}
pub(in crate::plonk) fn open<'a>(
&'a self,
pk: &'a ProvingKey<C>,