mirror of
https://github.com/arnaucube/Nova.git
synced 2026-01-11 08:31:29 +01:00
batch sum-checks (#161)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nova-snark"
|
name = "nova-snark"
|
||||||
version = "0.20.2"
|
version = "0.20.3"
|
||||||
authors = ["Srinath Setty <srinath@microsoft.com>"]
|
authors = ["Srinath Setty <srinath@microsoft.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Recursive zkSNARKs without trusted setup"
|
description = "Recursive zkSNARKs without trusted setup"
|
||||||
|
|||||||
@@ -20,6 +20,16 @@ use rayon::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sumcheck::SumcheckProof;
|
use sumcheck::SumcheckProof;
|
||||||
|
|
||||||
|
fn powers<G: Group>(s: &G::Scalar, n: usize) -> Vec<G::Scalar> {
|
||||||
|
assert!(n >= 1);
|
||||||
|
let mut powers = Vec::new();
|
||||||
|
powers.push(G::Scalar::one());
|
||||||
|
for i in 1..n {
|
||||||
|
powers.push(powers[i - 1] * s);
|
||||||
|
}
|
||||||
|
powers
|
||||||
|
}
|
||||||
|
|
||||||
/// A type that holds a witness to a polynomial evaluation instance
|
/// A type that holds a witness to a polynomial evaluation instance
|
||||||
pub struct PolyEvalWitness<G: Group> {
|
pub struct PolyEvalWitness<G: Group> {
|
||||||
p: Vec<G::Scalar>, // polynomial
|
p: Vec<G::Scalar>, // polynomial
|
||||||
@@ -51,6 +61,17 @@ impl<G: Group> PolyEvalWitness<G> {
|
|||||||
}
|
}
|
||||||
PolyEvalWitness { p }
|
PolyEvalWitness { p }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn batch(p_vec: &[&Vec<G::Scalar>], s: &G::Scalar) -> PolyEvalWitness<G> {
|
||||||
|
let powers_of_s = powers::<G>(s, p_vec.len());
|
||||||
|
let mut p = vec![G::Scalar::zero(); p_vec[0].len()];
|
||||||
|
for i in 0..p_vec.len() {
|
||||||
|
for (j, item) in p.iter_mut().enumerate().take(p_vec[i].len()) {
|
||||||
|
*item += p_vec[i][j] * powers_of_s[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PolyEvalWitness { p }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that holds a polynomial evaluation instance
|
/// A type that holds a polynomial evaluation instance
|
||||||
@@ -75,6 +96,31 @@ impl<G: Group> PolyEvalInstance<G> {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn batch(
|
||||||
|
c_vec: &[Commitment<G>],
|
||||||
|
x: &[G::Scalar],
|
||||||
|
e_vec: &[G::Scalar],
|
||||||
|
s: &G::Scalar,
|
||||||
|
) -> PolyEvalInstance<G> {
|
||||||
|
let powers_of_s = powers::<G>(s, c_vec.len());
|
||||||
|
let e = e_vec
|
||||||
|
.iter()
|
||||||
|
.zip(powers_of_s.iter())
|
||||||
|
.map(|(e, p)| *e * p)
|
||||||
|
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
||||||
|
let c = c_vec
|
||||||
|
.iter()
|
||||||
|
.zip(powers_of_s.iter())
|
||||||
|
.map(|(c, p)| *c * *p)
|
||||||
|
.fold(Commitment::<G>::default(), |acc, item| acc + item);
|
||||||
|
|
||||||
|
PolyEvalInstance {
|
||||||
|
c,
|
||||||
|
x: x.to_vec(),
|
||||||
|
e,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that represents the prover's key
|
/// A type that represents the prover's key
|
||||||
@@ -312,20 +358,10 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> RelaxedR1CSSNARKTrait<G
|
|||||||
let w_vec_padded = PolyEvalWitness::pad(&w_vec); // pad the polynomials to be of the same size
|
let w_vec_padded = PolyEvalWitness::pad(&w_vec); // pad the polynomials to be of the same size
|
||||||
let u_vec_padded = PolyEvalInstance::pad(&u_vec); // pad the evaluation points
|
let u_vec_padded = PolyEvalInstance::pad(&u_vec); // pad the evaluation points
|
||||||
|
|
||||||
let powers = |s: &G::Scalar, n: usize| -> Vec<G::Scalar> {
|
|
||||||
assert!(n >= 1);
|
|
||||||
let mut powers = Vec::new();
|
|
||||||
powers.push(G::Scalar::one());
|
|
||||||
for i in 1..n {
|
|
||||||
powers.push(powers[i - 1] * s);
|
|
||||||
}
|
|
||||||
powers
|
|
||||||
};
|
|
||||||
|
|
||||||
// generate a challenge
|
// generate a challenge
|
||||||
let rho = transcript.squeeze(b"r")?;
|
let rho = transcript.squeeze(b"r")?;
|
||||||
let num_claims = w_vec_padded.len();
|
let num_claims = w_vec_padded.len();
|
||||||
let powers_of_rho = powers(&rho, num_claims);
|
let powers_of_rho = powers::<G>(&rho, num_claims);
|
||||||
let claim_batch_joint = u_vec_padded
|
let claim_batch_joint = u_vec_padded
|
||||||
.iter()
|
.iter()
|
||||||
.zip(powers_of_rho.iter())
|
.zip(powers_of_rho.iter())
|
||||||
@@ -361,7 +397,7 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> RelaxedR1CSSNARKTrait<G
|
|||||||
|
|
||||||
// we now combine evaluation claims at the same point rz into one
|
// we now combine evaluation claims at the same point rz into one
|
||||||
let gamma = transcript.squeeze(b"g")?;
|
let gamma = transcript.squeeze(b"g")?;
|
||||||
let powers_of_gamma: Vec<G::Scalar> = powers(&gamma, num_claims);
|
let powers_of_gamma: Vec<G::Scalar> = powers::<G>(&gamma, num_claims);
|
||||||
let comm_joint = u_vec_padded
|
let comm_joint = u_vec_padded
|
||||||
.iter()
|
.iter()
|
||||||
.zip(powers_of_gamma.iter())
|
.zip(powers_of_gamma.iter())
|
||||||
@@ -517,20 +553,10 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> RelaxedR1CSSNARKTrait<G
|
|||||||
|
|
||||||
let u_vec_padded = PolyEvalInstance::pad(&u_vec); // pad the evaluation points
|
let u_vec_padded = PolyEvalInstance::pad(&u_vec); // pad the evaluation points
|
||||||
|
|
||||||
let powers = |s: &G::Scalar, n: usize| -> Vec<G::Scalar> {
|
|
||||||
assert!(n >= 1);
|
|
||||||
let mut powers = Vec::new();
|
|
||||||
powers.push(G::Scalar::one());
|
|
||||||
for i in 1..n {
|
|
||||||
powers.push(powers[i - 1] * s);
|
|
||||||
}
|
|
||||||
powers
|
|
||||||
};
|
|
||||||
|
|
||||||
// generate a challenge
|
// generate a challenge
|
||||||
let rho = transcript.squeeze(b"r")?;
|
let rho = transcript.squeeze(b"r")?;
|
||||||
let num_claims = u_vec.len();
|
let num_claims = u_vec.len();
|
||||||
let powers_of_rho = powers(&rho, num_claims);
|
let powers_of_rho = powers::<G>(&rho, num_claims);
|
||||||
let claim_batch_joint = u_vec
|
let claim_batch_joint = u_vec
|
||||||
.iter()
|
.iter()
|
||||||
.zip(powers_of_rho.iter())
|
.zip(powers_of_rho.iter())
|
||||||
@@ -566,7 +592,7 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> RelaxedR1CSSNARKTrait<G
|
|||||||
|
|
||||||
// we now combine evaluation claims at the same point rz into one
|
// we now combine evaluation claims at the same point rz into one
|
||||||
let gamma = transcript.squeeze(b"g")?;
|
let gamma = transcript.squeeze(b"g")?;
|
||||||
let powers_of_gamma: Vec<G::Scalar> = powers(&gamma, num_claims);
|
let powers_of_gamma: Vec<G::Scalar> = powers::<G>(&gamma, num_claims);
|
||||||
let comm_joint = u_vec_padded
|
let comm_joint = u_vec_padded
|
||||||
.iter()
|
.iter()
|
||||||
.zip(powers_of_gamma.iter())
|
.zip(powers_of_gamma.iter())
|
||||||
|
|||||||
2279
src/spartan/pp.rs
Normal file
2279
src/spartan/pp.rs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,629 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
errors::NovaError,
|
|
||||||
spartan::{
|
|
||||||
math::Math,
|
|
||||||
polynomial::{EqPolynomial, MultilinearPolynomial},
|
|
||||||
sumcheck::{CompressedUniPoly, SumcheckProof, UniPoly},
|
|
||||||
PolyEvalInstance, PolyEvalWitness,
|
|
||||||
},
|
|
||||||
traits::{commitment::CommitmentEngineTrait, Group, TranscriptEngineTrait},
|
|
||||||
Commitment, CommitmentKey,
|
|
||||||
};
|
|
||||||
use core::marker::PhantomData;
|
|
||||||
use ff::{Field, PrimeField};
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub(crate) struct IdentityPolynomial<Scalar: PrimeField> {
|
|
||||||
ell: usize,
|
|
||||||
_p: PhantomData<Scalar>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Scalar: PrimeField> IdentityPolynomial<Scalar> {
|
|
||||||
pub fn new(ell: usize) -> Self {
|
|
||||||
IdentityPolynomial {
|
|
||||||
ell,
|
|
||||||
_p: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn evaluate(&self, r: &[Scalar]) -> Scalar {
|
|
||||||
assert_eq!(self.ell, r.len());
|
|
||||||
(0..self.ell)
|
|
||||||
.map(|i| Scalar::from(2_usize.pow((self.ell - i - 1) as u32) as u64) * r[i])
|
|
||||||
.fold(Scalar::zero(), |acc, item| acc + item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<G: Group> SumcheckProof<G> {
|
|
||||||
pub fn prove_cubic_with_additive_term_batched<F>(
|
|
||||||
claim: &G::Scalar,
|
|
||||||
num_rounds: usize,
|
|
||||||
poly_vec: (
|
|
||||||
&mut MultilinearPolynomial<G::Scalar>,
|
|
||||||
&mut Vec<MultilinearPolynomial<G::Scalar>>,
|
|
||||||
&mut Vec<MultilinearPolynomial<G::Scalar>>,
|
|
||||||
&mut Vec<MultilinearPolynomial<G::Scalar>>,
|
|
||||||
),
|
|
||||||
coeffs: &[G::Scalar],
|
|
||||||
comb_func: F,
|
|
||||||
transcript: &mut G::TE,
|
|
||||||
) -> Result<
|
|
||||||
(
|
|
||||||
Self,
|
|
||||||
Vec<G::Scalar>,
|
|
||||||
(G::Scalar, Vec<G::Scalar>, Vec<G::Scalar>, Vec<G::Scalar>),
|
|
||||||
),
|
|
||||||
NovaError,
|
|
||||||
>
|
|
||||||
where
|
|
||||||
F: Fn(&G::Scalar, &G::Scalar, &G::Scalar, &G::Scalar) -> G::Scalar,
|
|
||||||
{
|
|
||||||
let (poly_A, poly_B_vec, poly_C_vec, poly_D_vec) = poly_vec;
|
|
||||||
|
|
||||||
let mut e = *claim;
|
|
||||||
let mut r: Vec<G::Scalar> = Vec::new();
|
|
||||||
let mut cubic_polys: Vec<CompressedUniPoly<G>> = Vec::new();
|
|
||||||
|
|
||||||
for _j in 0..num_rounds {
|
|
||||||
let mut evals: Vec<(G::Scalar, G::Scalar, G::Scalar)> = Vec::new();
|
|
||||||
|
|
||||||
for ((poly_B, poly_C), poly_D) in poly_B_vec
|
|
||||||
.iter()
|
|
||||||
.zip(poly_C_vec.iter())
|
|
||||||
.zip(poly_D_vec.iter())
|
|
||||||
{
|
|
||||||
let mut eval_point_0 = G::Scalar::zero();
|
|
||||||
let mut eval_point_2 = G::Scalar::zero();
|
|
||||||
let mut eval_point_3 = G::Scalar::zero();
|
|
||||||
|
|
||||||
let len = poly_A.len() / 2;
|
|
||||||
for i in 0..len {
|
|
||||||
// eval 0: bound_func is A(low)
|
|
||||||
eval_point_0 += comb_func(&poly_A[i], &poly_B[i], &poly_C[i], &poly_D[i]);
|
|
||||||
|
|
||||||
// eval 2: bound_func is -A(low) + 2*A(high)
|
|
||||||
let poly_A_bound_point = poly_A[len + i] + poly_A[len + i] - poly_A[i];
|
|
||||||
let poly_B_bound_point = poly_B[len + i] + poly_B[len + i] - poly_B[i];
|
|
||||||
let poly_C_bound_point = poly_C[len + i] + poly_C[len + i] - poly_C[i];
|
|
||||||
let poly_D_bound_point = poly_D[len + i] + poly_D[len + i] - poly_D[i];
|
|
||||||
|
|
||||||
eval_point_2 += comb_func(
|
|
||||||
&poly_A_bound_point,
|
|
||||||
&poly_B_bound_point,
|
|
||||||
&poly_C_bound_point,
|
|
||||||
&poly_D_bound_point,
|
|
||||||
);
|
|
||||||
|
|
||||||
// eval 3: bound_func is -2A(low) + 3A(high); computed incrementally with bound_func applied to eval(2)
|
|
||||||
let poly_A_bound_point = poly_A_bound_point + poly_A[len + i] - poly_A[i];
|
|
||||||
let poly_B_bound_point = poly_B_bound_point + poly_B[len + i] - poly_B[i];
|
|
||||||
let poly_C_bound_point = poly_C_bound_point + poly_C[len + i] - poly_C[i];
|
|
||||||
let poly_D_bound_point = poly_D_bound_point + poly_D[len + i] - poly_D[i];
|
|
||||||
|
|
||||||
eval_point_3 += comb_func(
|
|
||||||
&poly_A_bound_point,
|
|
||||||
&poly_B_bound_point,
|
|
||||||
&poly_C_bound_point,
|
|
||||||
&poly_D_bound_point,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
evals.push((eval_point_0, eval_point_2, eval_point_3));
|
|
||||||
}
|
|
||||||
|
|
||||||
let evals_combined_0 = (0..evals.len())
|
|
||||||
.map(|i| evals[i].0 * coeffs[i])
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
let evals_combined_2 = (0..evals.len())
|
|
||||||
.map(|i| evals[i].1 * coeffs[i])
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
let evals_combined_3 = (0..evals.len())
|
|
||||||
.map(|i| evals[i].2 * coeffs[i])
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let evals = vec![
|
|
||||||
evals_combined_0,
|
|
||||||
e - evals_combined_0,
|
|
||||||
evals_combined_2,
|
|
||||||
evals_combined_3,
|
|
||||||
];
|
|
||||||
let poly = UniPoly::from_evals(&evals);
|
|
||||||
|
|
||||||
// append the prover's message to the transcript
|
|
||||||
transcript.absorb(b"p", &poly);
|
|
||||||
|
|
||||||
// derive the verifier's challenge for the next round
|
|
||||||
let r_i = transcript.squeeze(b"c")?;
|
|
||||||
r.push(r_i);
|
|
||||||
|
|
||||||
// bound all tables to the verifier's challenege
|
|
||||||
poly_A.bound_poly_var_top(&r_i);
|
|
||||||
for ((poly_B, poly_C), poly_D) in poly_B_vec
|
|
||||||
.iter_mut()
|
|
||||||
.zip(poly_C_vec.iter_mut())
|
|
||||||
.zip(poly_D_vec.iter_mut())
|
|
||||||
{
|
|
||||||
poly_B.bound_poly_var_top(&r_i);
|
|
||||||
poly_C.bound_poly_var_top(&r_i);
|
|
||||||
poly_D.bound_poly_var_top(&r_i);
|
|
||||||
}
|
|
||||||
|
|
||||||
e = poly.evaluate(&r_i);
|
|
||||||
cubic_polys.push(poly.compress());
|
|
||||||
}
|
|
||||||
|
|
||||||
let poly_B_final = (0..poly_B_vec.len()).map(|i| poly_B_vec[i][0]).collect();
|
|
||||||
let poly_C_final = (0..poly_C_vec.len()).map(|i| poly_C_vec[i][0]).collect();
|
|
||||||
let poly_D_final = (0..poly_D_vec.len()).map(|i| poly_D_vec[i][0]).collect();
|
|
||||||
let claims_prod = (poly_A[0], poly_B_final, poly_C_final, poly_D_final);
|
|
||||||
|
|
||||||
Ok((SumcheckProof::new(cubic_polys), r, claims_prod))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides a product argument using the algorithm described by Setty-Lee, 2020
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(bound = "")]
|
|
||||||
pub struct ProductArgument<G: Group> {
|
|
||||||
comm_output_vec: Vec<Commitment<G>>,
|
|
||||||
sc_proof: SumcheckProof<G>,
|
|
||||||
eval_left_vec: Vec<G::Scalar>,
|
|
||||||
eval_right_vec: Vec<G::Scalar>,
|
|
||||||
eval_output_vec: Vec<G::Scalar>,
|
|
||||||
eval_input_vec: Vec<G::Scalar>,
|
|
||||||
eval_output2_vec: Vec<G::Scalar>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<G: Group> ProductArgument<G> {
|
|
||||||
pub fn prove(
|
|
||||||
ck: &CommitmentKey<G>,
|
|
||||||
input_vec: &[Vec<G::Scalar>], // a commitment to the input and the input vector to multiplied together
|
|
||||||
transcript: &mut G::TE,
|
|
||||||
) -> Result<
|
|
||||||
(
|
|
||||||
Self,
|
|
||||||
Vec<G::Scalar>,
|
|
||||||
Vec<G::Scalar>,
|
|
||||||
Vec<G::Scalar>,
|
|
||||||
Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>,
|
|
||||||
),
|
|
||||||
NovaError,
|
|
||||||
> {
|
|
||||||
let num_claims = input_vec.len();
|
|
||||||
|
|
||||||
let compute_layer = |input: &[G::Scalar]| -> (Vec<G::Scalar>, Vec<G::Scalar>, Vec<G::Scalar>) {
|
|
||||||
let left = (0..input.len() / 2)
|
|
||||||
.map(|i| input[2 * i])
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
|
|
||||||
let right = (0..input.len() / 2)
|
|
||||||
.map(|i| input[2 * i + 1])
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
|
|
||||||
assert_eq!(left.len(), right.len());
|
|
||||||
|
|
||||||
let output = (0..left.len())
|
|
||||||
.map(|i| left[i] * right[i])
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
|
|
||||||
(left, right, output)
|
|
||||||
};
|
|
||||||
|
|
||||||
// a closure that returns left, right, output, product
|
|
||||||
let prepare_inputs =
|
|
||||||
|input: &[G::Scalar]| -> (Vec<G::Scalar>, Vec<G::Scalar>, Vec<G::Scalar>, G::Scalar) {
|
|
||||||
let mut left: Vec<G::Scalar> = Vec::new();
|
|
||||||
let mut right: Vec<G::Scalar> = Vec::new();
|
|
||||||
let mut output: Vec<G::Scalar> = Vec::new();
|
|
||||||
|
|
||||||
let mut out = input.to_vec();
|
|
||||||
for _i in 0..input.len().log_2() {
|
|
||||||
let (l, r, o) = compute_layer(&out);
|
|
||||||
out = o.clone();
|
|
||||||
|
|
||||||
left.extend(l);
|
|
||||||
right.extend(r);
|
|
||||||
output.extend(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a dummy product operation to make the left.len() == right.len() == output.len() == input.len()
|
|
||||||
left.push(output[output.len() - 1]);
|
|
||||||
right.push(G::Scalar::zero());
|
|
||||||
output.push(G::Scalar::zero());
|
|
||||||
|
|
||||||
// output is stored at the last but one position
|
|
||||||
let product = output[output.len() - 2];
|
|
||||||
|
|
||||||
assert_eq!(left.len(), right.len());
|
|
||||||
assert_eq!(left.len(), output.len());
|
|
||||||
(left, right, output, product)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut left_vec = Vec::new();
|
|
||||||
let mut right_vec = Vec::new();
|
|
||||||
let mut output_vec = Vec::new();
|
|
||||||
let mut prod_vec = Vec::new();
|
|
||||||
for input in input_vec {
|
|
||||||
let (l, r, o, p) = prepare_inputs(input);
|
|
||||||
left_vec.push(l);
|
|
||||||
right_vec.push(r);
|
|
||||||
output_vec.push(o);
|
|
||||||
prod_vec.push(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit to the outputs
|
|
||||||
let comm_output_vec = (0..output_vec.len())
|
|
||||||
.into_par_iter()
|
|
||||||
.map(|i| G::CE::commit(ck, &output_vec[i]))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// absorb the output commitment and the claimed product
|
|
||||||
transcript.absorb(b"o", &comm_output_vec.as_slice());
|
|
||||||
transcript.absorb(b"r", &prod_vec.as_slice());
|
|
||||||
|
|
||||||
// this assumes all vectors passed have the same length
|
|
||||||
let num_rounds = output_vec[0].len().log_2();
|
|
||||||
|
|
||||||
// produce a fresh set of coeffs and a joint claim
|
|
||||||
let coeff_vec = {
|
|
||||||
let s = transcript.squeeze(b"r")?;
|
|
||||||
let mut s_vec = vec![s];
|
|
||||||
for i in 1..num_claims {
|
|
||||||
s_vec.push(s_vec[i - 1] * s);
|
|
||||||
}
|
|
||||||
s_vec
|
|
||||||
};
|
|
||||||
|
|
||||||
// generate randomness for the eq polynomial
|
|
||||||
let rand_eq = (0..num_rounds)
|
|
||||||
.map(|_i| transcript.squeeze(b"e"))
|
|
||||||
.collect::<Result<Vec<G::Scalar>, NovaError>>()?;
|
|
||||||
|
|
||||||
let mut poly_A = MultilinearPolynomial::new(EqPolynomial::new(rand_eq).evals());
|
|
||||||
let mut poly_B_vec = left_vec
|
|
||||||
.clone()
|
|
||||||
.into_par_iter()
|
|
||||||
.map(MultilinearPolynomial::new)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let mut poly_C_vec = right_vec
|
|
||||||
.clone()
|
|
||||||
.into_par_iter()
|
|
||||||
.map(MultilinearPolynomial::new)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let mut poly_D_vec = output_vec
|
|
||||||
.clone()
|
|
||||||
.into_par_iter()
|
|
||||||
.map(MultilinearPolynomial::new)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let comb_func =
|
|
||||||
|poly_A_comp: &G::Scalar,
|
|
||||||
poly_B_comp: &G::Scalar,
|
|
||||||
poly_C_comp: &G::Scalar,
|
|
||||||
poly_D_comp: &G::Scalar|
|
|
||||||
-> G::Scalar { *poly_A_comp * (*poly_B_comp * *poly_C_comp - *poly_D_comp) };
|
|
||||||
|
|
||||||
let (sc_proof, rand, _claims) = SumcheckProof::prove_cubic_with_additive_term_batched(
|
|
||||||
&G::Scalar::zero(),
|
|
||||||
num_rounds,
|
|
||||||
(
|
|
||||||
&mut poly_A,
|
|
||||||
&mut poly_B_vec,
|
|
||||||
&mut poly_C_vec,
|
|
||||||
&mut poly_D_vec,
|
|
||||||
),
|
|
||||||
&coeff_vec,
|
|
||||||
comb_func,
|
|
||||||
transcript,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// claims[0] is about the Eq polynomial, which the verifier computes directly
|
|
||||||
// claims[1] =? weighed sum of left(rand)
|
|
||||||
// claims[2] =? weighted sum of right(rand)
|
|
||||||
// claims[3] =? weighetd sum of output(rand), which is easy to verify by querying output
|
|
||||||
// we also need to prove that output(output.len()-2) = claimed_product
|
|
||||||
let eval_left_vec = (0..left_vec.len())
|
|
||||||
.into_par_iter()
|
|
||||||
.map(|i| MultilinearPolynomial::evaluate_with(&left_vec[i], &rand))
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
let eval_right_vec = (0..right_vec.len())
|
|
||||||
.into_par_iter()
|
|
||||||
.map(|i| MultilinearPolynomial::evaluate_with(&right_vec[i], &rand))
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
let eval_output_vec = (0..output_vec.len())
|
|
||||||
.into_par_iter()
|
|
||||||
.map(|i| MultilinearPolynomial::evaluate_with(&output_vec[i], &rand))
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
|
|
||||||
// we now combine eval_left = left(rand) and eval_right = right(rand)
|
|
||||||
// into claims about input and output
|
|
||||||
transcript.absorb(b"l", &eval_left_vec.as_slice());
|
|
||||||
transcript.absorb(b"r", &eval_right_vec.as_slice());
|
|
||||||
transcript.absorb(b"o", &eval_output_vec.as_slice());
|
|
||||||
|
|
||||||
let c = transcript.squeeze(b"c")?;
|
|
||||||
|
|
||||||
// eval = (G::Scalar::one() - c) * eval_left + c * eval_right
|
|
||||||
// eval is claimed evaluation of input||output(r, c), which can be proven by proving input(r[1..], c) and output(r[1..], c)
|
|
||||||
let rand_ext = {
|
|
||||||
let mut r = rand.clone();
|
|
||||||
r.extend(&[c]);
|
|
||||||
r
|
|
||||||
};
|
|
||||||
let eval_input_vec = (0..input_vec.len())
|
|
||||||
.into_par_iter()
|
|
||||||
.map(|i| MultilinearPolynomial::evaluate_with(&input_vec[i], &rand_ext[1..]))
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
|
|
||||||
let eval_output2_vec = (0..output_vec.len())
|
|
||||||
.into_par_iter()
|
|
||||||
.map(|i| MultilinearPolynomial::evaluate_with(&output_vec[i], &rand_ext[1..]))
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
|
|
||||||
// add claimed evaluations to the transcript
|
|
||||||
transcript.absorb(b"i", &eval_input_vec.as_slice());
|
|
||||||
transcript.absorb(b"o", &eval_output2_vec.as_slice());
|
|
||||||
|
|
||||||
// squeeze a challenge to combine multiple claims into one
|
|
||||||
let powers_of_rho = {
|
|
||||||
let s = transcript.squeeze(b"r")?;
|
|
||||||
let mut s_vec = vec![s];
|
|
||||||
for i in 1..num_claims {
|
|
||||||
s_vec.push(s_vec[i - 1] * s);
|
|
||||||
}
|
|
||||||
s_vec
|
|
||||||
};
|
|
||||||
|
|
||||||
// take weighted sum of input, output, and their commitments
|
|
||||||
let product = prod_vec
|
|
||||||
.iter()
|
|
||||||
.zip(powers_of_rho.iter())
|
|
||||||
.map(|(e, p)| *e * p)
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let eval_output = eval_output_vec
|
|
||||||
.iter()
|
|
||||||
.zip(powers_of_rho.iter())
|
|
||||||
.map(|(e, p)| *e * p)
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let comm_output = comm_output_vec
|
|
||||||
.iter()
|
|
||||||
.zip(powers_of_rho.iter())
|
|
||||||
.map(|(c, r_i)| *c * *r_i)
|
|
||||||
.fold(Commitment::<G>::default(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let weighted_sum = |W: &[Vec<G::Scalar>], s: &[G::Scalar]| -> Vec<G::Scalar> {
|
|
||||||
assert_eq!(W.len(), s.len());
|
|
||||||
let mut p = vec![G::Scalar::zero(); W[0].len()];
|
|
||||||
for i in 0..W.len() {
|
|
||||||
for (j, item) in W[i].iter().enumerate().take(W[i].len()) {
|
|
||||||
p[j] += *item * s[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p
|
|
||||||
};
|
|
||||||
|
|
||||||
let poly_output = weighted_sum(&output_vec, &powers_of_rho);
|
|
||||||
|
|
||||||
let eval_output2 = eval_output2_vec
|
|
||||||
.iter()
|
|
||||||
.zip(powers_of_rho.iter())
|
|
||||||
.map(|(e, p)| *e * p)
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let mut w_u_vec = Vec::new();
|
|
||||||
|
|
||||||
// eval_output = output(rand)
|
|
||||||
w_u_vec.push((
|
|
||||||
PolyEvalWitness {
|
|
||||||
p: poly_output.clone(),
|
|
||||||
},
|
|
||||||
PolyEvalInstance {
|
|
||||||
c: comm_output,
|
|
||||||
x: rand.clone(),
|
|
||||||
e: eval_output,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
// claimed_product = output(1, ..., 1, 0)
|
|
||||||
let x = {
|
|
||||||
let mut x = vec![G::Scalar::one(); rand.len()];
|
|
||||||
x[rand.len() - 1] = G::Scalar::zero();
|
|
||||||
x
|
|
||||||
};
|
|
||||||
w_u_vec.push((
|
|
||||||
PolyEvalWitness {
|
|
||||||
p: poly_output.clone(),
|
|
||||||
},
|
|
||||||
PolyEvalInstance {
|
|
||||||
c: comm_output,
|
|
||||||
x,
|
|
||||||
e: product,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
// eval_output2 = output(rand_ext[1..])
|
|
||||||
w_u_vec.push((
|
|
||||||
PolyEvalWitness { p: poly_output },
|
|
||||||
PolyEvalInstance {
|
|
||||||
c: comm_output,
|
|
||||||
x: rand_ext[1..].to_vec(),
|
|
||||||
e: eval_output2,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
let prod_arg = Self {
|
|
||||||
comm_output_vec,
|
|
||||||
sc_proof,
|
|
||||||
|
|
||||||
// claimed evaluations at rand
|
|
||||||
eval_left_vec,
|
|
||||||
eval_right_vec,
|
|
||||||
eval_output_vec,
|
|
||||||
|
|
||||||
// claimed evaluations at rand_ext[1..]
|
|
||||||
eval_input_vec: eval_input_vec.clone(),
|
|
||||||
eval_output2_vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
prod_arg,
|
|
||||||
prod_vec,
|
|
||||||
rand_ext[1..].to_vec(),
|
|
||||||
eval_input_vec,
|
|
||||||
w_u_vec,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify(
|
|
||||||
&self,
|
|
||||||
prod_vec: &[G::Scalar], // claimed products
|
|
||||||
len: usize,
|
|
||||||
transcript: &mut G::TE,
|
|
||||||
) -> Result<(Vec<G::Scalar>, Vec<G::Scalar>, Vec<PolyEvalInstance<G>>), NovaError> {
|
|
||||||
// absorb the provided commitment and claimed output
|
|
||||||
transcript.absorb(b"o", &self.comm_output_vec.as_slice());
|
|
||||||
transcript.absorb(b"r", &prod_vec.to_vec().as_slice());
|
|
||||||
|
|
||||||
let num_rounds = len.log_2();
|
|
||||||
let num_claims = prod_vec.len();
|
|
||||||
|
|
||||||
// produce a fresh set of coeffs and a joint claim
|
|
||||||
let coeff_vec = {
|
|
||||||
let s = transcript.squeeze(b"r")?;
|
|
||||||
let mut s_vec = vec![s];
|
|
||||||
for i in 1..num_claims {
|
|
||||||
s_vec.push(s_vec[i - 1] * s);
|
|
||||||
}
|
|
||||||
s_vec
|
|
||||||
};
|
|
||||||
|
|
||||||
// generate randomness for the eq polynomial
|
|
||||||
let rand_eq = (0..num_rounds)
|
|
||||||
.map(|_i| transcript.squeeze(b"e"))
|
|
||||||
.collect::<Result<Vec<G::Scalar>, NovaError>>()?;
|
|
||||||
|
|
||||||
let (final_claim, rand) = self.sc_proof.verify(
|
|
||||||
G::Scalar::zero(), // claim
|
|
||||||
num_rounds,
|
|
||||||
3, // degree bound
|
|
||||||
transcript,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// verify the final claim along with output[output.len() - 2 ] = claim
|
|
||||||
let eq = EqPolynomial::new(rand_eq).evaluate(&rand);
|
|
||||||
let final_claim_expected = (0..num_claims)
|
|
||||||
.map(|i| {
|
|
||||||
coeff_vec[i]
|
|
||||||
* eq
|
|
||||||
* (self.eval_left_vec[i] * self.eval_right_vec[i] - self.eval_output_vec[i])
|
|
||||||
})
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
if final_claim != final_claim_expected {
|
|
||||||
return Err(NovaError::InvalidSumcheckProof);
|
|
||||||
}
|
|
||||||
|
|
||||||
transcript.absorb(b"l", &self.eval_left_vec.as_slice());
|
|
||||||
transcript.absorb(b"r", &self.eval_right_vec.as_slice());
|
|
||||||
transcript.absorb(b"o", &self.eval_output_vec.as_slice());
|
|
||||||
|
|
||||||
let c = transcript.squeeze(b"c")?;
|
|
||||||
let eval_vec = self
|
|
||||||
.eval_left_vec
|
|
||||||
.iter()
|
|
||||||
.zip(self.eval_right_vec.iter())
|
|
||||||
.map(|(l, r)| (G::Scalar::one() - c) * l + c * r)
|
|
||||||
.collect::<Vec<G::Scalar>>();
|
|
||||||
|
|
||||||
// eval is claimed evaluation of input||output(r, c), which can be proven by proving input(r[1..], c) and output(r[1..], c)
|
|
||||||
let rand_ext = {
|
|
||||||
let mut r = rand.clone();
|
|
||||||
r.extend(&[c]);
|
|
||||||
r
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i, eval) in eval_vec.iter().enumerate() {
|
|
||||||
if *eval
|
|
||||||
!= (G::Scalar::one() - rand_ext[0]) * self.eval_input_vec[i]
|
|
||||||
+ rand_ext[0] * self.eval_output2_vec[i]
|
|
||||||
{
|
|
||||||
return Err(NovaError::InvalidSumcheckProof);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transcript.absorb(b"i", &self.eval_input_vec.as_slice());
|
|
||||||
transcript.absorb(b"o", &self.eval_output2_vec.as_slice());
|
|
||||||
|
|
||||||
// squeeze a challenge to combine multiple claims into one
|
|
||||||
let powers_of_rho = {
|
|
||||||
let s = transcript.squeeze(b"r")?;
|
|
||||||
let mut s_vec = vec![s];
|
|
||||||
for i in 1..num_claims {
|
|
||||||
s_vec.push(s_vec[i - 1] * s);
|
|
||||||
}
|
|
||||||
s_vec
|
|
||||||
};
|
|
||||||
|
|
||||||
// take weighted sum of input, output, and their commitments
|
|
||||||
let product = prod_vec
|
|
||||||
.iter()
|
|
||||||
.zip(powers_of_rho.iter())
|
|
||||||
.map(|(e, p)| *e * p)
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let eval_output = self
|
|
||||||
.eval_output_vec
|
|
||||||
.iter()
|
|
||||||
.zip(powers_of_rho.iter())
|
|
||||||
.map(|(e, p)| *e * p)
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let comm_output = self
|
|
||||||
.comm_output_vec
|
|
||||||
.iter()
|
|
||||||
.zip(powers_of_rho.iter())
|
|
||||||
.map(|(c, r_i)| *c * *r_i)
|
|
||||||
.fold(Commitment::<G>::default(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let eval_output2 = self
|
|
||||||
.eval_output2_vec
|
|
||||||
.iter()
|
|
||||||
.zip(powers_of_rho.iter())
|
|
||||||
.map(|(e, p)| *e * p)
|
|
||||||
.fold(G::Scalar::zero(), |acc, item| acc + item);
|
|
||||||
|
|
||||||
let mut u_vec = Vec::new();
|
|
||||||
|
|
||||||
// eval_output = output(rand)
|
|
||||||
u_vec.push(PolyEvalInstance {
|
|
||||||
c: comm_output,
|
|
||||||
x: rand.clone(),
|
|
||||||
e: eval_output,
|
|
||||||
});
|
|
||||||
|
|
||||||
// claimed_product = output(1, ..., 1, 0)
|
|
||||||
let x = {
|
|
||||||
let mut x = vec![G::Scalar::one(); rand.len()];
|
|
||||||
x[rand.len() - 1] = G::Scalar::zero();
|
|
||||||
x
|
|
||||||
};
|
|
||||||
u_vec.push(PolyEvalInstance {
|
|
||||||
c: comm_output,
|
|
||||||
x,
|
|
||||||
e: product,
|
|
||||||
});
|
|
||||||
|
|
||||||
// eval_output2 = output(rand_ext[1..])
|
|
||||||
u_vec.push(PolyEvalInstance {
|
|
||||||
c: comm_output,
|
|
||||||
x: rand_ext[1..].to_vec(),
|
|
||||||
e: eval_output2,
|
|
||||||
});
|
|
||||||
|
|
||||||
// input-related claims are checked by the caller
|
|
||||||
Ok((self.eval_input_vec.clone(), rand_ext[1..].to_vec(), u_vec))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -197,94 +197,6 @@ impl<G: Group> SumcheckProof<G> {
|
|||||||
Ok((SumcheckProof::new(quad_polys), r, claims_prod))
|
Ok((SumcheckProof::new(quad_polys), r, claims_prod))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prove_cubic<F>(
|
|
||||||
claim: &G::Scalar,
|
|
||||||
num_rounds: usize,
|
|
||||||
poly_A: &mut MultilinearPolynomial<G::Scalar>,
|
|
||||||
poly_B: &mut MultilinearPolynomial<G::Scalar>,
|
|
||||||
poly_C: &mut MultilinearPolynomial<G::Scalar>,
|
|
||||||
comb_func: F,
|
|
||||||
transcript: &mut G::TE,
|
|
||||||
) -> Result<(Self, Vec<G::Scalar>, Vec<G::Scalar>), NovaError>
|
|
||||||
where
|
|
||||||
F: Fn(&G::Scalar, &G::Scalar, &G::Scalar) -> G::Scalar + Sync,
|
|
||||||
{
|
|
||||||
let mut r: Vec<G::Scalar> = Vec::new();
|
|
||||||
let mut polys: Vec<CompressedUniPoly<G>> = Vec::new();
|
|
||||||
let mut claim_per_round = *claim;
|
|
||||||
|
|
||||||
for _ in 0..num_rounds {
|
|
||||||
let poly = {
|
|
||||||
let len = poly_A.len() / 2;
|
|
||||||
|
|
||||||
// Make an iterator returning the contributions to the evaluations
|
|
||||||
let (eval_point_0, eval_point_2, eval_point_3) = (0..len)
|
|
||||||
.into_par_iter()
|
|
||||||
.map(|i| {
|
|
||||||
// eval 0: bound_func is A(low)
|
|
||||||
let eval_point_0 = comb_func(&poly_A[i], &poly_B[i], &poly_C[i]);
|
|
||||||
|
|
||||||
// eval 2: bound_func is -A(low) + 2*A(high)
|
|
||||||
let poly_A_bound_point = poly_A[len + i] + poly_A[len + i] - poly_A[i];
|
|
||||||
let poly_B_bound_point = poly_B[len + i] + poly_B[len + i] - poly_B[i];
|
|
||||||
let poly_C_bound_point = poly_C[len + i] + poly_C[len + i] - poly_C[i];
|
|
||||||
let eval_point_2 = comb_func(
|
|
||||||
&poly_A_bound_point,
|
|
||||||
&poly_B_bound_point,
|
|
||||||
&poly_C_bound_point,
|
|
||||||
);
|
|
||||||
|
|
||||||
// eval 3: bound_func is -2A(low) + 3A(high); computed incrementally with bound_func applied to eval(2)
|
|
||||||
let poly_A_bound_point = poly_A_bound_point + poly_A[len + i] - poly_A[i];
|
|
||||||
let poly_B_bound_point = poly_B_bound_point + poly_B[len + i] - poly_B[i];
|
|
||||||
let poly_C_bound_point = poly_C_bound_point + poly_C[len + i] - poly_C[i];
|
|
||||||
let eval_point_3 = comb_func(
|
|
||||||
&poly_A_bound_point,
|
|
||||||
&poly_B_bound_point,
|
|
||||||
&poly_C_bound_point,
|
|
||||||
);
|
|
||||||
(eval_point_0, eval_point_2, eval_point_3)
|
|
||||||
})
|
|
||||||
.reduce(
|
|
||||||
|| (G::Scalar::zero(), G::Scalar::zero(), G::Scalar::zero()),
|
|
||||||
|a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2),
|
|
||||||
);
|
|
||||||
|
|
||||||
let evals = vec![
|
|
||||||
eval_point_0,
|
|
||||||
claim_per_round - eval_point_0,
|
|
||||||
eval_point_2,
|
|
||||||
eval_point_3,
|
|
||||||
];
|
|
||||||
UniPoly::from_evals(&evals)
|
|
||||||
};
|
|
||||||
|
|
||||||
// append the prover's message to the transcript
|
|
||||||
transcript.absorb(b"p", &poly);
|
|
||||||
|
|
||||||
//derive the verifier's challenge for the next round
|
|
||||||
let r_i = transcript.squeeze(b"c")?;
|
|
||||||
r.push(r_i);
|
|
||||||
polys.push(poly.compress());
|
|
||||||
|
|
||||||
// Set up next round
|
|
||||||
claim_per_round = poly.evaluate(&r_i);
|
|
||||||
|
|
||||||
// bound all tables to the verifier's challenege
|
|
||||||
poly_A.bound_poly_var_top(&r_i);
|
|
||||||
poly_B.bound_poly_var_top(&r_i);
|
|
||||||
poly_C.bound_poly_var_top(&r_i);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
SumcheckProof {
|
|
||||||
compressed_polys: polys,
|
|
||||||
},
|
|
||||||
r,
|
|
||||||
vec![poly_A[0], poly_B[0], poly_C[0]],
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prove_cubic_with_additive_term<F>(
|
pub fn prove_cubic_with_additive_term<F>(
|
||||||
claim: &G::Scalar,
|
claim: &G::Scalar,
|
||||||
num_rounds: usize,
|
num_rounds: usize,
|
||||||
|
|||||||
Reference in New Issue
Block a user