use super::super::{
circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error,
ProvingKey,
};
use super::Argument;
use crate::multicore::{self, IntoParallelIterator};
#[cfg(feature = "multicore")]
use crate::multicore::{
IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator,
ParallelSliceMut,
};
use crate::plonk::evaluation::evaluate;
use crate::{
arithmetic::{eval_polynomial, parallelize, CurveAffine},
poly::{
commitment::{Blind, Params},
Coeff, EvaluationDomain, LagrangeCoeff, Polynomial, ProverQuery, Rotation,
},
transcript::{EncodedChallenge, TranscriptWrite},
};
#[cfg(feature = "profile")]
use ark_std::{end_timer, start_timer};
use ff::WithSmallOrderMulGroup;
use group::{
ff::{BatchInvert, Field},
Curve,
};
use rand_core::RngCore;
use std::collections::HashMap;
use std::hash::Hash;
use std::{
collections::BTreeMap,
iter,
ops::{Mul, MulAssign},
};
#[derive(Debug)]
pub(in crate::plonk) struct Permuted<C: CurveAffine> {
compressed_input_expression: Polynomial<C::Scalar, LagrangeCoeff>,
permuted_input_expression: Polynomial<C::Scalar, LagrangeCoeff>,
permuted_input_poly: Polynomial<C::Scalar, Coeff>,
permuted_input_blind: Blind<C::Scalar>,
compressed_table_expression: Polynomial<C::Scalar, LagrangeCoeff>,
permuted_table_expression: Polynomial<C::Scalar, LagrangeCoeff>,
permuted_table_poly: Polynomial<C::Scalar, Coeff>,
permuted_table_blind: Blind<C::Scalar>,
}
#[derive(Debug)]
pub(in crate::plonk) struct Committed<C: CurveAffine> {
pub(in crate::plonk) permuted_input_poly: Polynomial<C::Scalar, Coeff>,
permuted_input_blind: Blind<C::Scalar>,
pub(in crate::plonk) permuted_table_poly: Polynomial<C::Scalar, Coeff>,
permuted_table_blind: Blind<C::Scalar>,
pub(in crate::plonk) product_poly: Polynomial<C::Scalar, Coeff>,
product_blind: Blind<C::Scalar>,
}
pub(in crate::plonk) struct Evaluated<C: CurveAffine> {
constructed: Committed<C>,
}
impl<F: WithSmallOrderMulGroup<3>> Argument<F> {
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn commit_permuted<
'a,
'params: 'a,
C,
P: Params<'params, C>,
E: EncodedChallenge<C>,
R: RngCore,
T: TranscriptWrite<C, E>,
>(
&self,
pk: &ProvingKey<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
F: Hash,
C: CurveAffine<ScalarExt = F>,
C::Curve: Mul<F, Output = C::Curve> + MulAssign<F>,
{
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
};
let compressed_input_expression = compress_expressions(&self.input_expressions);
let compressed_table_expression = compress_expressions(&self.table_expressions);
let (permuted_input_expression, permuted_table_expression) = permute_expression_pair(
pk,
params,
domain,
&mut rng,
&compressed_input_expression,
&compressed_table_expression,
)?;
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)
};
let (permuted_input_poly, permuted_input_blind, permuted_input_commitment) =
commit_values(&permuted_input_expression);
let (permuted_table_poly, permuted_table_blind, permuted_table_commitment) =
commit_values(&permuted_table_expression);
transcript.write_point(permuted_input_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,
})
}
}
impl<C: CurveAffine> Permuted<C> {
pub(in crate::plonk) fn commit_product<
'params,
P: Params<'params, C>,
E: EncodedChallenge<C>,
R: RngCore,
T: TranscriptWrite<C, E>,
>(
self,
pk: &ProvingKey<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();
let mut lookup_product = vec![C::Scalar::ZERO; params.n() as usize];
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);
}
});
lookup_product.iter_mut().batch_invert();
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);
}
});
let z = iter::once(C::Scalar::ONE)
.chain(lookup_product)
.scan(C::Scalar::ONE, |state, cur| {
*state *= &cur;
Some(*state)
})
.take(params.n() as usize - 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")]
{
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 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);
}
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);
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,
})
}
}
impl<C: CurveAffine> Committed<C> {
pub(in crate::plonk) fn evaluate<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
self,
pk: &ProvingKey<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);
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 })
}
}
impl<C: CurveAffine> Evaluated<C> {
pub(in crate::plonk) fn open<'a>(
&'a self,
pk: &'a ProvingKey<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()
.chain(Some(ProverQuery {
point: *x,
poly: &self.constructed.product_poly,
blind: self.constructed.product_blind,
}))
.chain(Some(ProverQuery {
point: *x,
poly: &self.constructed.permuted_input_poly,
blind: self.constructed.permuted_input_blind,
}))
.chain(Some(ProverQuery {
point: *x,
poly: &self.constructed.permuted_table_poly,
blind: self.constructed.permuted_table_blind,
}))
.chain(Some(ProverQuery {
point: x_inv,
poly: &self.constructed.permuted_input_poly,
blind: self.constructed.permuted_input_blind,
}))
.chain(Some(ProverQuery {
point: x_next,
poly: &self.constructed.product_poly,
blind: self.constructed.product_blind,
}))
}
}
type ExpressionPair<F> = (Polynomial<F, LagrangeCoeff>, Polynomial<F, LagrangeCoeff>);
fn permute_expression_pair<'params, C: CurveAffine, P: Params<'params, C>, R: RngCore>(
pk: &ProvingKey<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>
where
C::Scalar: Hash,
{
let num_threads = multicore::current_num_threads();
let usable_rows = params.n() as usize - (pk.vk.cs.blinding_factors() + 1);
let input_expression = &input_expression[0..usable_rows];
#[cfg(feature = "profile")]
let input_time = start_timer!(|| "permute_par input hashmap (cpu par)");
let capacity = usable_rows / num_threads + 1;
#[cfg(feature = "multicore")]
let input_uniques: HashMap<C::Scalar, usize> = input_expression
.par_iter()
.fold(
|| HashMap::with_capacity(capacity),
|mut acc, coeff| {
*acc.entry(*coeff).or_insert(0) += 1;
acc
},
)
.reduce_with(|mut m1, m2| {
m2.into_iter().for_each(|(k, v)| {
*m1.entry(k).or_insert(0) += v;
});
m1
})
.unwrap();
#[cfg(not(feature = "multicore"))]
let input_uniques: HashMap<C::Scalar, usize> =
input_expression
.iter()
.fold(HashMap::with_capacity(capacity), |mut acc, coeff| {
*acc.entry(*coeff).or_insert(0) += 1;
acc
});
#[cfg(feature = "profile")]
end_timer!(input_time);
#[cfg(feature = "profile")]
let timer = start_timer!(|| "permute_par input unique ranges (cpu par)");
#[cfg(feature = "multicore")]
let input_unique_ranges = input_uniques
.par_iter()
.fold(
|| Vec::with_capacity(capacity),
|mut input_ranges, (&coeff, &count)| {
if input_ranges.is_empty() {
input_ranges.push((coeff, 0..count));
} else {
let prev_end = input_ranges.last().unwrap().1.end;
input_ranges.push((coeff, prev_end..prev_end + count));
}
input_ranges
},
)
.reduce_with(|r1, mut r2| {
let r1_end = r1.last().unwrap().1.end;
r2.par_iter_mut().for_each(|r2| {
r2.1.start += r1_end;
r2.1.end += r1_end;
});
[r1, r2].concat()
})
.unwrap();
#[cfg(not(feature = "multicore"))]
let input_unique_ranges = input_uniques.iter().fold(
Vec::with_capacity(capacity),
|mut input_ranges, (&coeff, &count)| {
if input_ranges.is_empty() {
input_ranges.push((coeff, 0..count));
} else {
let prev_end = input_ranges.last().unwrap().1.end;
input_ranges.push((coeff, prev_end..prev_end + count));
}
input_ranges
},
);
#[cfg(feature = "profile")]
end_timer!(timer);
#[cfg(feature = "profile")]
let to_vec_time = start_timer!(|| "to_vec");
let mut sorted_table_coeffs = table_expression[0..usable_rows].to_vec();
#[cfg(feature = "profile")]
end_timer!(to_vec_time);
#[cfg(feature = "profile")]
let sort_table_time = start_timer!(|| "permute_par sort table");
#[cfg(feature = "multicore")]
sorted_table_coeffs.par_sort();
#[cfg(not(feature = "multicore"))]
sorted_table_coeffs.sort();
#[cfg(feature = "profile")]
end_timer!(sort_table_time);
#[cfg(feature = "profile")]
let timer = start_timer!(|| "leftover table coeffs (cpu par)");
let leftover_table_coeffs: Vec<C::Scalar> = sorted_table_coeffs
.as_slice()
.into_par_iter()
.enumerate()
.filter_map(|(i, coeff)| {
((i != 0 && coeff == &sorted_table_coeffs[i - 1]) || !input_uniques.contains_key(coeff))
.then_some(*coeff)
})
.collect();
#[cfg(feature = "profile")]
end_timer!(timer);
let (mut permuted_input_expression, mut permuted_table_coeffs): (Vec<_>, Vec<_>) =
input_unique_ranges
.into_par_iter()
.enumerate()
.flat_map(|(i, (coeff, range))| {
let leftover_range_start = range.start - i;
let leftover_range_end = range.end - i - 1;
[(coeff, coeff)].into_par_iter().chain(
leftover_table_coeffs[leftover_range_start..leftover_range_end]
.into_par_iter()
.map(move |leftover_table_coeff| (coeff, *leftover_table_coeff)),
)
})
.unzip();
permuted_input_expression.resize_with(params.n() as usize, || C::Scalar::random(&mut rng));
permuted_table_coeffs.resize_with(params.n() as usize, || C::Scalar::random(&mut rng));
Ok((
domain.lagrange_from_vec(permuted_input_expression),
domain.lagrange_from_vec(permuted_table_coeffs),
))
}
#[allow(dead_code)]
fn permute_expression_pair_seq<'params, C: CurveAffine, P: Params<'params, C>, R: RngCore>(
pk: &ProvingKey<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);
#[cfg(feature = "multicore")]
permuted_input_expression.par_sort();
#[cfg(not(feature = "multicore"))]
permuted_input_expression.sort();
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 row == 0 || *input_value != permuted_input_expression[row - 1] {
*table_value = *input_value;
if let Some(count) = leftover_table_map.get_mut(input_value) {
assert!(*count > 0);
*count -= 1;
None
} else {
panic!("{:?}", Error::ConstraintSystemFailure);
}
} else {
Some(row)
}
})
.collect::<Vec<_>>();
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),
))
}