Browse Source

batch sum-checks (#161)

main
Srinath Setty 1 year ago
committed by GitHub
parent
commit
b76d7aa7ea
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 2330 additions and 2318 deletions
  1. +1
    -1
      Cargo.toml
  2. +50
    -24
      src/spartan/mod.rs
  3. +2279
    -0
      src/spartan/pp.rs
  4. +0
    -1576
      src/spartan/pp/mod.rs
  5. +0
    -629
      src/spartan/pp/product.rs
  6. +0
    -88
      src/spartan/sumcheck.rs

+ 1
- 1
Cargo.toml

@ -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"

+ 50
- 24
src/spartan/mod.rs

@ -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 PolyEvalWitness {
} }
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 PolyEvalInstance {
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> RelaxedR1CSSNARKTrait
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> RelaxedR1CSSNARKTrait
// 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> RelaxedR1CSSNARKTrait
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> RelaxedR1CSSNARKTrait
// 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
- 0
src/spartan/pp.rs
File diff suppressed because it is too large
View File


+ 0
- 1576
src/spartan/pp/mod.rs
File diff suppressed because it is too large
View File


+ 0
- 629
src/spartan/pp/product.rs

@ -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))
}
}

+ 0
- 88
src/spartan/sumcheck.rs

@ -197,94 +197,6 @@ impl SumcheckProof {
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,

Loading…
Cancel
Save