Browse Source

Batch polynomial evaluations (#154)

* Ability to collect evaluation claims

* defer polynomial evaluation claims

* address cargo clippy
main
Srinath Setty 1 year ago
committed by GitHub
parent
commit
4aab459050
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 436 additions and 265 deletions
  1. +1
    -1
      Cargo.toml
  2. +2
    -2
      src/lib.rs
  3. +232
    -73
      src/spartan/mod.rs
  4. +66
    -41
      src/spartan/spark/mod.rs
  5. +88
    -96
      src/spartan/spark/sparse.rs
  6. +47
    -52
      src/spartan/sumcheck.rs

+ 1
- 1
Cargo.toml

@ -1,6 +1,6 @@
[package] [package]
name = "nova-snark" name = "nova-snark"
version = "0.19.0"
version = "0.19.1"
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"

+ 2
- 2
src/lib.rs

@ -1095,8 +1095,8 @@ mod tests {
assert_eq!(zn_secondary, vec![<G2 as Group>::Scalar::from(2460515u64)]); assert_eq!(zn_secondary, vec![<G2 as Group>::Scalar::from(2460515u64)]);
// run the compressed snark with Spark compiler // run the compressed snark with Spark compiler
type CC1Prime = spartan::spark::SparkEngine<G1, EE1>;
type CC2Prime = spartan::spark::SparkEngine<G2, EE2>;
type CC1Prime = spartan::spark::SparkEngine<G1>;
type CC2Prime = spartan::spark::SparkEngine<G2>;
type S1Prime = spartan::RelaxedR1CSSNARK<G1, EE1, CC1Prime>; type S1Prime = spartan::RelaxedR1CSSNARK<G1, EE1, CC1Prime>;
type S2Prime = spartan::RelaxedR1CSSNARK<G2, EE2, CC2Prime>; type S2Prime = spartan::RelaxedR1CSSNARK<G2, EE2, CC2Prime>;

+ 232
- 73
src/spartan/mod.rs

@ -12,7 +12,7 @@ use crate::{
evaluation::EvaluationEngineTrait, snark::RelaxedR1CSSNARKTrait, Group, TranscriptEngineTrait, evaluation::EvaluationEngineTrait, snark::RelaxedR1CSSNARKTrait, Group, TranscriptEngineTrait,
TranscriptReprTrait, TranscriptReprTrait,
}, },
CommitmentKey,
Commitment, CommitmentKey,
}; };
use ff::Field; use ff::Field;
use itertools::concat; use itertools::concat;
@ -21,8 +21,67 @@ use rayon::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sumcheck::SumcheckProof; use sumcheck::SumcheckProof;
/// A type that holds a witness to a polynomial evaluation instance
#[allow(dead_code)]
pub struct PolyEvalWitness<G: Group> {
p: Vec<G::Scalar>, // polynomial
}
impl<G: Group> PolyEvalWitness<G> {
fn pad(W: &[PolyEvalWitness<G>]) -> Vec<PolyEvalWitness<G>> {
// determine the maximum size
if let Some(n) = W.iter().map(|w| w.p.len()).max() {
W.iter()
.map(|w| {
let mut p = w.p.clone();
p.resize(n, G::Scalar::zero());
PolyEvalWitness { p }
})
.collect()
} else {
Vec::new()
}
}
fn weighted_sum(W: &[PolyEvalWitness<G>], s: &[G::Scalar]) -> PolyEvalWitness<G> {
assert_eq!(W.len(), s.len());
let mut p = vec![G::Scalar::zero(); W[0].p.len()];
for i in 0..W.len() {
for j in 0..W[i].p.len() {
p[j] += W[i].p[j] * s[i]
}
}
PolyEvalWitness { p }
}
}
/// A type that holds a polynomial evaluation instance
#[allow(dead_code)]
pub struct PolyEvalInstance<G: Group> {
c: Commitment<G>, // commitment to the polynomial
x: Vec<G::Scalar>, // evaluation point
e: G::Scalar, // claimed evaluation
}
impl<G: Group> PolyEvalInstance<G> {
fn pad(U: &[PolyEvalInstance<G>]) -> Vec<PolyEvalInstance<G>> {
// determine the maximum size
if let Some(ell) = U.iter().map(|u| u.x.len()).max() {
U.iter()
.map(|u| {
let mut x = vec![G::Scalar::zero(); ell - u.x.len()];
x.extend(u.x.clone());
PolyEvalInstance { c: u.c, x, e: u.e }
})
.collect()
} else {
Vec::new()
}
}
}
/// A trait that defines the behavior of a computation commitment engine /// A trait that defines the behavior of a computation commitment engine
pub trait CompCommitmentEngineTrait<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> {
pub trait CompCommitmentEngineTrait<G: Group> {
/// A type that holds opening hint /// A type that holds opening hint
type Decommitment: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>; type Decommitment: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>;
@ -46,22 +105,26 @@ pub trait CompCommitmentEngineTrait
/// proves an evaluation of R1CS matrices viewed as polynomials /// proves an evaluation of R1CS matrices viewed as polynomials
fn prove( fn prove(
ck: &CommitmentKey<G>, ck: &CommitmentKey<G>,
ek: &EE::ProverKey,
S: &R1CSShape<G>, S: &R1CSShape<G>,
decomm: &Self::Decommitment, decomm: &Self::Decommitment,
comm: &Self::Commitment, comm: &Self::Commitment,
r: &(&[G::Scalar], &[G::Scalar]), r: &(&[G::Scalar], &[G::Scalar]),
transcript: &mut G::TE, transcript: &mut G::TE,
) -> Result<Self::EvaluationArgument, NovaError>;
) -> Result<
(
Self::EvaluationArgument,
Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>,
),
NovaError,
>;
/// verifies an evaluation of R1CS matrices viewed as polynomials and returns verified evaluations /// verifies an evaluation of R1CS matrices viewed as polynomials and returns verified evaluations
fn verify( fn verify(
vk: &EE::VerifierKey,
comm: &Self::Commitment, comm: &Self::Commitment,
r: &(&[G::Scalar], &[G::Scalar]), r: &(&[G::Scalar], &[G::Scalar]),
arg: &Self::EvaluationArgument, arg: &Self::EvaluationArgument,
transcript: &mut G::TE, transcript: &mut G::TE,
) -> Result<(G::Scalar, G::Scalar, G::Scalar), NovaError>;
) -> Result<(G::Scalar, G::Scalar, G::Scalar, Vec<PolyEvalInstance<G>>), NovaError>;
} }
/// A type that represents the prover's key /// A type that represents the prover's key
@ -70,7 +133,7 @@ pub trait CompCommitmentEngineTrait
pub struct ProverKey< pub struct ProverKey<
G: Group, G: Group,
EE: EvaluationEngineTrait<G, CE = G::CE>, EE: EvaluationEngineTrait<G, CE = G::CE>,
CC: CompCommitmentEngineTrait<G, EE>,
CC: CompCommitmentEngineTrait<G>,
> { > {
pk_ee: EE::ProverKey, pk_ee: EE::ProverKey,
S: R1CSShape<G>, S: R1CSShape<G>,
@ -84,7 +147,7 @@ pub struct ProverKey<
pub struct VerifierKey< pub struct VerifierKey<
G: Group, G: Group,
EE: EvaluationEngineTrait<G, CE = G::CE>, EE: EvaluationEngineTrait<G, CE = G::CE>,
CC: CompCommitmentEngineTrait<G, EE>,
CC: CompCommitmentEngineTrait<G>,
> { > {
num_cons: usize, num_cons: usize,
num_vars: usize, num_vars: usize,
@ -100,21 +163,20 @@ pub struct VerifierKey<
pub struct RelaxedR1CSSNARK< pub struct RelaxedR1CSSNARK<
G: Group, G: Group,
EE: EvaluationEngineTrait<G, CE = G::CE>, EE: EvaluationEngineTrait<G, CE = G::CE>,
CC: CompCommitmentEngineTrait<G, EE>,
CC: CompCommitmentEngineTrait<G>,
> { > {
sc_proof_outer: SumcheckProof<G>, sc_proof_outer: SumcheckProof<G>,
claims_outer: (G::Scalar, G::Scalar, G::Scalar), claims_outer: (G::Scalar, G::Scalar, G::Scalar),
eval_E: G::Scalar, eval_E: G::Scalar,
sc_proof_inner: SumcheckProof<G>, sc_proof_inner: SumcheckProof<G>,
eval_W: G::Scalar, eval_W: G::Scalar,
eval_arg_cc: CC::EvaluationArgument,
sc_proof_batch: SumcheckProof<G>, sc_proof_batch: SumcheckProof<G>,
eval_E_prime: G::Scalar,
eval_W_prime: G::Scalar,
evals_batch: Vec<G::Scalar>,
eval_arg: EE::EvaluationArgument, eval_arg: EE::EvaluationArgument,
eval_arg_cc: CC::EvaluationArgument,
} }
impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngineTrait<G, EE>>
impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngineTrait<G>>
RelaxedR1CSSNARKTrait<G> for RelaxedR1CSSNARK<G, EE, CC> RelaxedR1CSSNARKTrait<G> for RelaxedR1CSSNARK<G, EE, CC>
{ {
type ProverKey = ProverKey<G, EE, CC>; type ProverKey = ProverKey<G, EE, CC>;
@ -292,9 +354,8 @@ impl, CC: CompCommitmentEngin
)?; )?;
// we now prove evaluations of R1CS matrices at (r_x, r_y) // we now prove evaluations of R1CS matrices at (r_x, r_y)
let eval_arg_cc = CC::prove(
let (eval_arg_cc, mut w_u_vec) = CC::prove(
ck, ck,
&pk.pk_ee,
&pk.S, &pk.S,
&pk.decomm, &pk.decomm,
&pk.comm, &pk.comm,
@ -302,52 +363,111 @@ impl, CC: CompCommitmentEngin
&mut transcript, &mut transcript,
)?; )?;
let eval_W = MultilinearPolynomial::new(W.W.clone()).evaluate(&r_y[1..]);
transcript.absorb(b"eval_W", &eval_W);
// We will now reduce eval_W =? W(r_y[1..]) and eval_W =? E(r_x) into
// add additional claims about W and E polynomials to the list from CC
let eval_W = MultilinearPolynomial::evaluate_with(&W.W, &r_y[1..]);
w_u_vec.push((
PolyEvalWitness { p: W.W.clone() },
PolyEvalInstance {
c: U.comm_W,
x: r_y[1..].to_vec(),
e: eval_W,
},
));
w_u_vec.push((
PolyEvalWitness { p: W.E },
PolyEvalInstance {
c: U.comm_E,
x: r_x,
e: eval_E,
},
));
// We will now reduce a vector of claims of evaluations at different points into claims about them at the same point.
// For example, eval_W =? W(r_y[1..]) and eval_W =? E(r_x) into
// two claims: eval_W_prime =? W(rz) and eval_E_prime =? E(rz) // two claims: eval_W_prime =? W(rz) and eval_E_prime =? E(rz)
// We can them combine the two into one: eval_W_prime + gamma * eval_E_prime =? (W + gamma*E)(rz), // We can them combine the two into one: eval_W_prime + gamma * eval_E_prime =? (W + gamma*E)(rz),
// where gamma is a public challenge // where gamma is a public challenge
// Since commitments to W and E are homomorphic, the verifier can compute a commitment // Since commitments to W and E are homomorphic, the verifier can compute a commitment
// to the batched polynomial. // to the batched polynomial.
let rho = transcript.squeeze(b"rho")?;
assert!(w_u_vec.len() >= 2);
let (w_vec, u_vec): (Vec<PolyEvalWitness<G>>, Vec<PolyEvalInstance<G>>) =
w_u_vec.into_iter().unzip();
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 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
};
let claim_batch_joint = eval_E + rho * eval_W;
let num_rounds_z = num_rounds_x;
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 + rho * *poly_C_comp * *poly_D_comp };
let (sc_proof_batch, r_z, claims_batch) = SumcheckProof::prove_quad_sum(
// generate a challenge
let rho = transcript.squeeze(b"r")?;
let num_claims = w_vec_padded.len();
let powers_of_rho = powers(&rho, num_claims);
let claim_batch_joint = u_vec_padded
.iter()
.zip(powers_of_rho.iter())
.map(|(u, p)| u.e * p)
.fold(G::Scalar::zero(), |acc, item| acc + item);
let mut polys_left: Vec<MultilinearPolynomial<G::Scalar>> = w_vec_padded
.iter()
.map(|w| MultilinearPolynomial::new(w.p.clone()))
.collect();
let mut polys_right: Vec<MultilinearPolynomial<G::Scalar>> = u_vec_padded
.iter()
.map(|u| MultilinearPolynomial::new(EqPolynomial::new(u.x.clone()).evals()))
.collect();
let num_rounds_z = u_vec_padded[0].x.len();
let comb_func = |poly_A_comp: &G::Scalar, poly_B_comp: &G::Scalar| -> G::Scalar {
*poly_A_comp * *poly_B_comp
};
let (sc_proof_batch, r_z, claims_batch) = SumcheckProof::prove_quad_batch(
&claim_batch_joint, &claim_batch_joint,
num_rounds_z, num_rounds_z,
&mut MultilinearPolynomial::new(EqPolynomial::new(r_x.clone()).evals()),
&mut MultilinearPolynomial::new(W.E.clone()),
&mut MultilinearPolynomial::new(EqPolynomial::new(r_y[1..].to_vec()).evals()),
&mut MultilinearPolynomial::new(W.W.clone()),
&mut polys_left,
&mut polys_right,
&powers_of_rho,
comb_func, comb_func,
&mut transcript, &mut transcript,
)?; )?;
let eval_E_prime = claims_batch[1];
let eval_W_prime = claims_batch[3];
transcript.absorb(b"claims_batch", &[eval_E_prime, eval_W_prime].as_slice());
let (claims_batch_left, _): (Vec<G::Scalar>, Vec<G::Scalar>) = claims_batch;
transcript.absorb(b"l", &claims_batch_left.as_slice());
// 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"gamma")?;
let comm = U.comm_E + U.comm_W * gamma;
let poly = W
.E
let gamma = transcript.squeeze(b"g")?;
let powers_of_gamma: Vec<G::Scalar> = powers(&gamma, num_claims);
let comm_joint = u_vec_padded
.iter()
.zip(powers_of_gamma.iter())
.map(|(u, g_i)| u.c * *g_i)
.fold(Commitment::<G>::default(), |acc, item| acc + item);
let poly_joint = PolyEvalWitness::weighted_sum(&w_vec_padded, &powers_of_gamma);
let eval_joint = claims_batch_left
.iter() .iter()
.zip(W.W.iter())
.map(|(e, w)| *e + gamma * w)
.collect::<Vec<G::Scalar>>();
let eval = eval_E_prime + gamma * eval_W_prime;
.zip(powers_of_gamma.iter())
.map(|(e, g_i)| *e * *g_i)
.fold(G::Scalar::zero(), |acc, item| acc + item);
let eval_arg = EE::prove(ck, &pk.pk_ee, &mut transcript, &comm, &poly, &r_z, &eval)?;
let eval_arg = EE::prove(
ck,
&pk.pk_ee,
&mut transcript,
&comm_joint,
&poly_joint.p,
&r_z,
&eval_joint,
)?;
Ok(RelaxedR1CSSNARK { Ok(RelaxedR1CSSNARK {
sc_proof_outer, sc_proof_outer,
@ -355,11 +475,10 @@ impl, CC: CompCommitmentEngin
eval_E, eval_E,
sc_proof_inner, sc_proof_inner,
eval_W, eval_W,
eval_arg_cc,
sc_proof_batch, sc_proof_batch,
eval_E_prime,
eval_W_prime,
evals_batch: claims_batch_left,
eval_arg, eval_arg,
eval_arg_cc,
}) })
} }
@ -433,25 +552,50 @@ impl, CC: CompCommitmentEngin
}; };
// verify evaluation argument to retrieve evaluations of R1CS matrices // verify evaluation argument to retrieve evaluations of R1CS matrices
let (eval_A, eval_B, eval_C) = CC::verify(
&vk.vk_ee,
&vk.comm,
&(&r_x, &r_y),
&self.eval_arg_cc,
&mut transcript,
)?;
let (eval_A, eval_B, eval_C, mut u_vec) =
CC::verify(&vk.comm, &(&r_x, &r_y), &self.eval_arg_cc, &mut transcript)?;
let claim_inner_final_expected = (eval_A + r * eval_B + r * r * eval_C) * eval_Z; let claim_inner_final_expected = (eval_A + r * eval_B + r * r * eval_C) * eval_Z;
if claim_inner_final != claim_inner_final_expected { if claim_inner_final != claim_inner_final_expected {
return Err(NovaError::InvalidSumcheckProof); return Err(NovaError::InvalidSumcheckProof);
} }
// batch sum-check
transcript.absorb(b"eval_W", &self.eval_W);
// add additional claims about W and E polynomials to the list from CC
u_vec.push(PolyEvalInstance {
c: U.comm_W,
x: r_y[1..].to_vec(),
e: self.eval_W,
});
u_vec.push(PolyEvalInstance {
c: U.comm_E,
x: r_x,
e: self.eval_E,
});
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
let rho = transcript.squeeze(b"r")?;
let num_claims = u_vec.len();
let powers_of_rho = powers(&rho, num_claims);
let claim_batch_joint = u_vec
.iter()
.zip(powers_of_rho.iter())
.map(|(u, p)| u.e * p)
.fold(G::Scalar::zero(), |acc, item| acc + item);
let rho = transcript.squeeze(b"rho")?;
let claim_batch_joint = self.eval_E + rho * self.eval_W;
let num_rounds_z = num_rounds_x;
let num_rounds_z = u_vec_padded[0].x.len();
let (claim_batch_final, r_z) = let (claim_batch_final, r_z) =
self self
.sc_proof_batch .sc_proof_batch
@ -459,32 +603,47 @@ impl, CC: CompCommitmentEngin
let claim_batch_final_expected = { let claim_batch_final_expected = {
let poly_rz = EqPolynomial::new(r_z.clone()); let poly_rz = EqPolynomial::new(r_z.clone());
let rz_rx = poly_rz.evaluate(&r_x);
let rz_ry = poly_rz.evaluate(&r_y[1..]);
rz_rx * self.eval_E_prime + rho * rz_ry * self.eval_W_prime
let evals = u_vec_padded
.iter()
.map(|u| poly_rz.evaluate(&u.x))
.collect::<Vec<G::Scalar>>();
evals
.iter()
.zip(self.evals_batch.iter())
.zip(powers_of_rho.iter())
.map(|((e_i, p_i), rho_i)| *e_i * *p_i * rho_i)
.fold(G::Scalar::zero(), |acc, item| acc + item)
}; };
if claim_batch_final != claim_batch_final_expected { if claim_batch_final != claim_batch_final_expected {
return Err(NovaError::InvalidSumcheckProof); return Err(NovaError::InvalidSumcheckProof);
} }
transcript.absorb(
b"claims_batch",
&[self.eval_E_prime, self.eval_W_prime].as_slice(),
);
transcript.absorb(b"l", &self.evals_batch.as_slice());
// 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"gamma")?;
let comm = U.comm_E + U.comm_W * gamma;
let eval = self.eval_E_prime + gamma * self.eval_W_prime;
let gamma = transcript.squeeze(b"g")?;
let powers_of_gamma: Vec<G::Scalar> = powers(&gamma, num_claims);
let comm_joint = u_vec_padded
.iter()
.zip(powers_of_gamma.iter())
.map(|(u, g_i)| u.c * *g_i)
.fold(Commitment::<G>::default(), |acc, item| acc + item);
let eval_joint = self
.evals_batch
.iter()
.zip(powers_of_gamma.iter())
.map(|(e, g_i)| *e * *g_i)
.fold(G::Scalar::zero(), |acc, item| acc + item);
// verify eval_W and eval_E
// verify
EE::verify( EE::verify(
&vk.vk_ee, &vk.vk_ee,
&mut transcript, &mut transcript,
&comm,
&comm_joint,
&r_z, &r_z,
&eval,
&eval_joint,
&self.eval_arg, &self.eval_arg,
)?; )?;

+ 66
- 41
src/spartan/spark/mod.rs

@ -3,7 +3,7 @@
use crate::{ use crate::{
errors::NovaError, errors::NovaError,
r1cs::R1CSShape, r1cs::R1CSShape,
spartan::{math::Math, CompCommitmentEngineTrait},
spartan::{math::Math, CompCommitmentEngineTrait, PolyEvalInstance, PolyEvalWitness},
traits::{evaluation::EvaluationEngineTrait, Group, TranscriptReprTrait}, traits::{evaluation::EvaluationEngineTrait, Group, TranscriptReprTrait},
CommitmentKey, CommitmentKey,
}; };
@ -43,7 +43,7 @@ impl TranscriptReprTrait for TrivialCommitment {
} }
} }
impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> CompCommitmentEngineTrait<G, EE>
impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> CompCommitmentEngineTrait<G>
for TrivialCompComputationEngine<G, EE> for TrivialCompComputationEngine<G, EE>
{ {
type Decommitment = TrivialDecommitment<G>; type Decommitment = TrivialDecommitment<G>;
@ -66,29 +66,36 @@ impl> CompCommitmentEngineTra
/// proves an evaluation of R1CS matrices viewed as polynomials /// proves an evaluation of R1CS matrices viewed as polynomials
fn prove( fn prove(
_ck: &CommitmentKey<G>, _ck: &CommitmentKey<G>,
_ek: &EE::ProverKey,
_S: &R1CSShape<G>, _S: &R1CSShape<G>,
_decomm: &Self::Decommitment, _decomm: &Self::Decommitment,
_comm: &Self::Commitment, _comm: &Self::Commitment,
_r: &(&[G::Scalar], &[G::Scalar]), _r: &(&[G::Scalar], &[G::Scalar]),
_transcript: &mut G::TE, _transcript: &mut G::TE,
) -> Result<Self::EvaluationArgument, NovaError> {
Ok(TrivialEvaluationArgument {
_p: Default::default(),
})
) -> Result<
(
Self::EvaluationArgument,
Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>,
),
NovaError,
> {
Ok((
TrivialEvaluationArgument {
_p: Default::default(),
},
Vec::new(),
))
} }
/// verifies an evaluation of R1CS matrices viewed as polynomials /// verifies an evaluation of R1CS matrices viewed as polynomials
fn verify( fn verify(
_vk: &EE::VerifierKey,
comm: &Self::Commitment, comm: &Self::Commitment,
r: &(&[G::Scalar], &[G::Scalar]), r: &(&[G::Scalar], &[G::Scalar]),
_arg: &Self::EvaluationArgument, _arg: &Self::EvaluationArgument,
_transcript: &mut G::TE, _transcript: &mut G::TE,
) -> Result<(G::Scalar, G::Scalar, G::Scalar), NovaError> {
) -> Result<(G::Scalar, G::Scalar, G::Scalar, Vec<PolyEvalInstance<G>>), NovaError> {
let (r_x, r_y) = r; let (r_x, r_y) = r;
let evals = SparsePolynomial::<G>::multi_evaluate(&[&comm.S.A, &comm.S.B, &comm.S.C], r_x, r_y); let evals = SparsePolynomial::<G>::multi_evaluate(&[&comm.S.A, &comm.S.B, &comm.S.C], r_x, r_y);
Ok((evals[0], evals[1], evals[2]))
Ok((evals[0], evals[1], evals[2], Vec::new()))
} }
} }
@ -98,9 +105,8 @@ mod sparse;
use sparse::{SparseEvaluationArgument, SparsePolynomial, SparsePolynomialCommitment}; use sparse::{SparseEvaluationArgument, SparsePolynomial, SparsePolynomialCommitment};
/// A non-trivial implementation of `CompCommitmentEngineTrait` using Spartan's SPARK compiler /// A non-trivial implementation of `CompCommitmentEngineTrait` using Spartan's SPARK compiler
pub struct SparkEngine<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> {
pub struct SparkEngine<G: Group> {
_p: PhantomData<G>, _p: PhantomData<G>,
_p2: PhantomData<EE>,
} }
/// An implementation of Spark decommitment /// An implementation of Spark decommitment
@ -156,18 +162,16 @@ impl TranscriptReprTrait for SparkCommitment {
/// Provides an implementation of a trivial evaluation argument /// Provides an implementation of a trivial evaluation argument
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
#[serde(bound = "")] #[serde(bound = "")]
pub struct SparkEvaluationArgument<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> {
arg_A: SparseEvaluationArgument<G, EE>,
arg_B: SparseEvaluationArgument<G, EE>,
arg_C: SparseEvaluationArgument<G, EE>,
pub struct SparkEvaluationArgument<G: Group> {
arg_A: SparseEvaluationArgument<G>,
arg_B: SparseEvaluationArgument<G>,
arg_C: SparseEvaluationArgument<G>,
} }
impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> CompCommitmentEngineTrait<G, EE>
for SparkEngine<G, EE>
{
impl<G: Group> CompCommitmentEngineTrait<G> for SparkEngine<G> {
type Decommitment = SparkDecommitment<G>; type Decommitment = SparkDecommitment<G>;
type Commitment = SparkCommitment<G>; type Commitment = SparkCommitment<G>;
type EvaluationArgument = SparkEvaluationArgument<G, EE>;
type EvaluationArgument = SparkEvaluationArgument<G>;
/// commits to R1CS matrices /// commits to R1CS matrices
fn commit( fn commit(
@ -182,39 +186,60 @@ impl> CompCommitmentEngineTra
/// proves an evaluation of R1CS matrices viewed as polynomials /// proves an evaluation of R1CS matrices viewed as polynomials
fn prove( fn prove(
ck: &CommitmentKey<G>, ck: &CommitmentKey<G>,
pk_ee: &EE::ProverKey,
S: &R1CSShape<G>, S: &R1CSShape<G>,
decomm: &Self::Decommitment, decomm: &Self::Decommitment,
comm: &Self::Commitment, comm: &Self::Commitment,
r: &(&[G::Scalar], &[G::Scalar]), r: &(&[G::Scalar], &[G::Scalar]),
transcript: &mut G::TE, transcript: &mut G::TE,
) -> Result<Self::EvaluationArgument, NovaError> {
let arg_A =
SparseEvaluationArgument::prove(ck, pk_ee, &decomm.A, &S.A, &comm.comm_A, r, transcript)?;
let arg_B =
SparseEvaluationArgument::prove(ck, pk_ee, &decomm.B, &S.B, &comm.comm_B, r, transcript)?;
let arg_C =
SparseEvaluationArgument::prove(ck, pk_ee, &decomm.C, &S.C, &comm.comm_C, r, transcript)?;
Ok(SparkEvaluationArgument {
arg_A,
arg_B,
arg_C,
})
) -> Result<
(
Self::EvaluationArgument,
Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>,
),
NovaError,
> {
let (arg_A, u_w_vec_A) =
SparseEvaluationArgument::prove(ck, &decomm.A, &S.A, &comm.comm_A, r, transcript)?;
let (arg_B, u_w_vec_B) =
SparseEvaluationArgument::prove(ck, &decomm.B, &S.B, &comm.comm_B, r, transcript)?;
let (arg_C, u_w_vec_C) =
SparseEvaluationArgument::prove(ck, &decomm.C, &S.C, &comm.comm_C, r, transcript)?;
let u_w_vec = {
let mut u_w_vec = u_w_vec_A;
u_w_vec.extend(u_w_vec_B);
u_w_vec.extend(u_w_vec_C);
u_w_vec
};
Ok((
SparkEvaluationArgument {
arg_A,
arg_B,
arg_C,
},
u_w_vec,
))
} }
/// verifies an evaluation of R1CS matrices viewed as polynomials /// verifies an evaluation of R1CS matrices viewed as polynomials
fn verify( fn verify(
vk_ee: &EE::VerifierKey,
comm: &Self::Commitment, comm: &Self::Commitment,
r: &(&[G::Scalar], &[G::Scalar]), r: &(&[G::Scalar], &[G::Scalar]),
arg: &Self::EvaluationArgument, arg: &Self::EvaluationArgument,
transcript: &mut G::TE, transcript: &mut G::TE,
) -> Result<(G::Scalar, G::Scalar, G::Scalar), NovaError> {
let eval_A = arg.arg_A.verify(vk_ee, &comm.comm_A, r, transcript)?;
let eval_B = arg.arg_B.verify(vk_ee, &comm.comm_B, r, transcript)?;
let eval_C = arg.arg_C.verify(vk_ee, &comm.comm_C, r, transcript)?;
Ok((eval_A, eval_B, eval_C))
) -> Result<(G::Scalar, G::Scalar, G::Scalar, Vec<PolyEvalInstance<G>>), NovaError> {
let (eval_A, u_vec_A) = arg.arg_A.verify(&comm.comm_A, r, transcript)?;
let (eval_B, u_vec_B) = arg.arg_B.verify(&comm.comm_B, r, transcript)?;
let (eval_C, u_vec_C) = arg.arg_C.verify(&comm.comm_C, r, transcript)?;
let u_vec = {
let mut u_vec = u_vec_A;
u_vec.extend(u_vec_B);
u_vec.extend(u_vec_C);
u_vec
};
Ok((eval_A, eval_B, eval_C, u_vec))
} }
} }

+ 88
- 96
src/spartan/spark/sparse.rs

@ -7,12 +7,9 @@ use crate::{
math::Math, math::Math,
polynomial::{EqPolynomial, MultilinearPolynomial}, polynomial::{EqPolynomial, MultilinearPolynomial},
spark::product::{IdentityPolynomial, ProductArgumentBatched}, spark::product::{IdentityPolynomial, ProductArgumentBatched},
SumcheckProof,
},
traits::{
commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait, Group,
TranscriptEngineTrait, TranscriptReprTrait,
PolyEvalInstance, PolyEvalWitness, SumcheckProof,
}, },
traits::{commitment::CommitmentEngineTrait, Group, TranscriptEngineTrait, TranscriptReprTrait},
Commitment, CommitmentKey, Commitment, CommitmentKey,
}; };
use ff::Field; use ff::Field;
@ -227,7 +224,7 @@ impl SparsePolynomial {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")] #[serde(bound = "")]
pub struct SparseEvaluationArgument<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> {
pub struct SparseEvaluationArgument<G: Group> {
// claimed evaluation // claimed evaluation
eval: G::Scalar, eval: G::Scalar,
@ -240,7 +237,6 @@ pub struct SparseEvaluationArgument
eval_E_row: G::Scalar, eval_E_row: G::Scalar,
eval_E_col: G::Scalar, eval_E_col: G::Scalar,
eval_val: G::Scalar, eval_val: G::Scalar,
arg_eval: EE::EvaluationArgument,
// proof that E_row is well-formed // proof that E_row is well-formed
eval_init_row: G::Scalar, eval_init_row: G::Scalar,
@ -262,24 +258,23 @@ pub struct SparseEvaluationArgument
eval_col_read_ts: G::Scalar, eval_col_read_ts: G::Scalar,
eval_E_col2: G::Scalar, eval_E_col2: G::Scalar,
eval_col_audit_ts: G::Scalar, eval_col_audit_ts: G::Scalar,
arg_row_col_joint: EE::EvaluationArgument,
arg_row_audit_ts: EE::EvaluationArgument,
arg_col_audit_ts: EE::EvaluationArgument,
} }
impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> SparseEvaluationArgument<G, EE> {
impl<G: Group> SparseEvaluationArgument<G> {
pub fn prove( pub fn prove(
ck: &CommitmentKey<G>, ck: &CommitmentKey<G>,
pk_ee: &EE::ProverKey,
poly: &SparsePolynomial<G>, poly: &SparsePolynomial<G>,
sparse: &[(usize, usize, G::Scalar)], sparse: &[(usize, usize, G::Scalar)],
comm: &SparsePolynomialCommitment<G>, comm: &SparsePolynomialCommitment<G>,
r: &(&[G::Scalar], &[G::Scalar]), r: &(&[G::Scalar], &[G::Scalar]),
transcript: &mut G::TE, transcript: &mut G::TE,
) -> Result<Self, NovaError> {
) -> Result<(Self, Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>), NovaError> {
let (r_x, r_y) = r; let (r_x, r_y) = r;
let eval = SparsePolynomial::<G>::multi_evaluate(&[sparse], r_x, r_y)[0]; let eval = SparsePolynomial::<G>::multi_evaluate(&[sparse], r_x, r_y)[0];
// keep track of evaluation claims
let mut w_u_vec: Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)> = Vec::new();
// compute oracles to prove the correctness of `eval` // compute oracles to prove the correctness of `eval`
let (E_row, E_col, T_x, T_y) = SparsePolynomial::<G>::evaluation_oracles(sparse, r_x, r_y); let (E_row, E_col, T_x, T_y) = SparsePolynomial::<G>::evaluation_oracles(sparse, r_x, r_y);
let val = poly.val.clone(); let val = poly.val.clone();
@ -316,15 +311,16 @@ impl> SparseEvaluationArgumen
.zip(val.iter()) .zip(val.iter())
.map(|((a, b), c)| *a + rho * *b + rho * rho * *c) .map(|((a, b), c)| *a + rho * *b + rho * rho * *c)
.collect::<Vec<G::Scalar>>(); .collect::<Vec<G::Scalar>>();
let arg_eval = EE::prove(
ck,
pk_ee,
transcript,
&comm_joint,
&poly_eval,
&r_eval,
&eval_joint,
)?;
// add the claim to prove for later
w_u_vec.push((
PolyEvalWitness { p: poly_eval },
PolyEvalInstance {
c: comm_joint,
x: r_eval,
e: eval_joint,
},
));
// we now need to prove that E_row and E_col are well-formed // we now need to prove that E_row and E_col are well-formed
// we use memory checking: H(INIT) * H(WS) =? H(RS) * H(FINAL) // we use memory checking: H(INIT) * H(WS) =? H(RS) * H(FINAL)
@ -462,37 +458,41 @@ impl> SparseEvaluationArgumen
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let arg_row_col_joint = EE::prove(
ck,
pk_ee,
transcript,
&comm_joint,
&poly_joint,
&r_read_write_row_col,
&eval_joint,
)?;
let arg_row_audit_ts = EE::prove(
ck,
pk_ee,
transcript,
&comm.comm_row_audit_ts,
&poly.row_audit_ts,
&r_init_audit_row,
&eval_row_audit_ts,
)?;
let arg_col_audit_ts = EE::prove(
ck,
pk_ee,
transcript,
&comm.comm_col_audit_ts,
&poly.col_audit_ts,
&r_init_audit_col,
&eval_col_audit_ts,
)?;
Ok(Self {
// add the claim to prove for later
w_u_vec.push((
PolyEvalWitness { p: poly_joint },
PolyEvalInstance {
c: comm_joint,
x: r_read_write_row_col,
e: eval_joint,
},
));
transcript.absorb(b"a", &eval_row_audit_ts); // add evaluation to transcript, commitment is already in
w_u_vec.push((
PolyEvalWitness {
p: poly.row_audit_ts.clone(),
},
PolyEvalInstance {
c: comm.comm_row_audit_ts,
x: r_init_audit_row,
e: eval_row_audit_ts,
},
));
transcript.absorb(b"a", &eval_col_audit_ts); // add evaluation to transcript, commitment is already in
w_u_vec.push((
PolyEvalWitness {
p: poly.col_audit_ts.clone(),
},
PolyEvalInstance {
c: comm.comm_col_audit_ts,
x: r_init_audit_col,
e: eval_col_audit_ts,
},
));
let eval_arg = Self {
// claimed evaluation // claimed evaluation
eval, eval,
@ -505,7 +505,6 @@ impl> SparseEvaluationArgumen
eval_E_row: claims_eval[0], eval_E_row: claims_eval[0],
eval_E_col: claims_eval[1], eval_E_col: claims_eval[1],
eval_val: claims_eval[2], eval_val: claims_eval[2],
arg_eval,
// proof that E_row and E_row are well-formed // proof that E_row and E_row are well-formed
eval_init_row: eval_init_audit_row[0], eval_init_row: eval_init_audit_row[0],
@ -527,21 +526,22 @@ impl> SparseEvaluationArgumen
eval_col_read_ts, eval_col_read_ts,
eval_E_col2, eval_E_col2,
eval_col_audit_ts, eval_col_audit_ts,
arg_row_col_joint,
arg_row_audit_ts,
arg_col_audit_ts,
})
};
Ok((eval_arg, w_u_vec))
} }
pub fn verify( pub fn verify(
&self, &self,
vk_ee: &EE::VerifierKey,
comm: &SparsePolynomialCommitment<G>, comm: &SparsePolynomialCommitment<G>,
r: &(&[G::Scalar], &[G::Scalar]), r: &(&[G::Scalar], &[G::Scalar]),
transcript: &mut G::TE, transcript: &mut G::TE,
) -> Result<G::Scalar, NovaError> {
) -> Result<(G::Scalar, Vec<PolyEvalInstance<G>>), NovaError> {
let (r_x, r_y) = r; let (r_x, r_y) = r;
// keep track of evaluation claims
let mut u_vec: Vec<PolyEvalInstance<G>> = Vec::new();
// append the transcript and scalar // append the transcript and scalar
transcript.absorb(b"E", &vec![self.comm_E_row, self.comm_E_col].as_slice()); transcript.absorb(b"E", &vec![self.comm_E_row, self.comm_E_col].as_slice());
transcript.absorb(b"e", &self.eval); transcript.absorb(b"e", &self.eval);
@ -562,14 +562,13 @@ impl> SparseEvaluationArgumen
let rho = transcript.squeeze(b"r")?; let rho = transcript.squeeze(b"r")?;
let comm_joint = self.comm_E_row + self.comm_E_col * rho + comm.comm_val * rho * rho; let comm_joint = self.comm_E_row + self.comm_E_col * rho + comm.comm_val * rho * rho;
let eval_joint = self.eval_E_row + rho * self.eval_E_col + rho * rho * self.eval_val; let eval_joint = self.eval_E_row + rho * self.eval_E_col + rho * rho * self.eval_val;
EE::verify(
vk_ee,
transcript,
&comm_joint,
&r_eval,
&eval_joint,
&self.arg_eval,
)?;
// add the claim to prove for later
u_vec.push(PolyEvalInstance {
c: comm_joint,
x: r_eval,
e: eval_joint,
});
// (2) verify if E_row and E_col are well formed // (2) verify if E_row and E_col are well formed
let gamma_1 = transcript.squeeze(b"g1")?; let gamma_1 = transcript.squeeze(b"g1")?;
@ -700,33 +699,26 @@ impl> SparseEvaluationArgumen
+ comm.comm_col_read_ts * c * c * c * c + comm.comm_col_read_ts * c * c * c * c
+ self.comm_E_col * c * c * c * c * c; + self.comm_E_col * c * c * c * c * c;
EE::verify(
vk_ee,
transcript,
&comm_joint,
&r_read_write_row_col,
&eval_joint,
&self.arg_row_col_joint,
)?;
EE::verify(
vk_ee,
transcript,
&comm.comm_row_audit_ts,
&r_init_audit_row,
&self.eval_row_audit_ts,
&self.arg_row_audit_ts,
)?;
EE::verify(
vk_ee,
transcript,
&comm.comm_col_audit_ts,
&r_init_audit_col,
&self.eval_col_audit_ts,
&self.arg_col_audit_ts,
)?;
Ok(self.eval)
u_vec.push(PolyEvalInstance {
c: comm_joint,
x: r_read_write_row_col,
e: eval_joint,
});
transcript.absorb(b"a", &self.eval_row_audit_ts); // add evaluation to transcript, commitment is already in
u_vec.push(PolyEvalInstance {
c: comm.comm_row_audit_ts,
x: r_init_audit_row,
e: self.eval_row_audit_ts,
});
transcript.absorb(b"a", &self.eval_col_audit_ts); // add evaluation to transcript, commitment is already in
u_vec.push(PolyEvalInstance {
c: comm.comm_col_audit_ts,
x: r_init_audit_col,
e: self.eval_col_audit_ts,
});
Ok((self.eval, u_vec))
} }
} }

+ 47
- 52
src/spartan/sumcheck.rs

@ -126,54 +126,52 @@ impl SumcheckProof {
)) ))
} }
pub fn prove_quad_sum<F>(
pub fn prove_quad_batch<F>(
claim: &G::Scalar, claim: &G::Scalar,
num_rounds: usize, num_rounds: usize,
poly_A: &mut MultilinearPolynomial<G::Scalar>,
poly_B: &mut MultilinearPolynomial<G::Scalar>,
poly_C: &mut MultilinearPolynomial<G::Scalar>,
poly_D: &mut MultilinearPolynomial<G::Scalar>,
poly_A_vec: &mut Vec<MultilinearPolynomial<G::Scalar>>,
poly_B_vec: &mut Vec<MultilinearPolynomial<G::Scalar>>,
coeffs: &[G::Scalar],
comb_func: F, comb_func: F,
transcript: &mut G::TE, transcript: &mut G::TE,
) -> Result<(Self, Vec<G::Scalar>, Vec<G::Scalar>), NovaError>
) -> Result<(Self, Vec<G::Scalar>, (Vec<G::Scalar>, Vec<G::Scalar>)), NovaError>
where where
F: Fn(&G::Scalar, &G::Scalar, &G::Scalar, &G::Scalar) -> G::Scalar + Sync,
F: Fn(&G::Scalar, &G::Scalar) -> G::Scalar,
{ {
let mut e = *claim;
let mut r: Vec<G::Scalar> = Vec::new(); 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 mut quad_polys: Vec<CompressedUniPoly<G>> = Vec::new();
for _j in 0..num_rounds {
let mut evals: Vec<(G::Scalar, G::Scalar)> = Vec::new();
for (poly_A, poly_B) in poly_A_vec.iter().zip(poly_B_vec.iter()) {
let mut eval_point_0 = G::Scalar::zero();
let mut eval_point_2 = G::Scalar::zero();
let len = poly_A.len() / 2; 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]);
// Make an iterator returning the contributions to the evaluations
let (eval_point_0, eval_point_2) = (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], &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];
eval_point_2 += comb_func(&poly_A_bound_point, &poly_B_bound_point);
}
// 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];
let eval_point_2 = comb_func(
&poly_A_bound_point,
&poly_B_bound_point,
&poly_C_bound_point,
&poly_D_bound_point,
);
(eval_point_0, eval_point_2)
})
.reduce(
|| (G::Scalar::zero(), G::Scalar::zero()),
|a, b| (a.0 + b.0, a.1 + b.1),
);
evals.push((eval_point_0, eval_point_2));
}
let evals = vec![eval_point_0, claim_per_round - eval_point_0, eval_point_2];
UniPoly::from_evals(&evals)
};
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 = vec![evals_combined_0, e - evals_combined_0, evals_combined_2];
let poly = UniPoly::from_evals(&evals);
// append the prover's message to the transcript // append the prover's message to the transcript
transcript.absorb(b"p", &poly); transcript.absorb(b"p", &poly);
@ -181,25 +179,22 @@ impl SumcheckProof {
// derive the verifier's challenge for the next round // derive the verifier's challenge for the next round
let r_i = transcript.squeeze(b"c")?; let r_i = transcript.squeeze(b"c")?;
r.push(r_i); 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 // 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);
poly_D.bound_poly_var_top(&r_i);
for (poly_A, poly_B) in poly_A_vec.iter_mut().zip(poly_B_vec.iter_mut()) {
poly_A.bound_poly_var_top(&r_i);
poly_B.bound_poly_var_top(&r_i);
}
e = poly.evaluate(&r_i);
quad_polys.push(poly.compress());
} }
Ok((
SumcheckProof {
compressed_polys: polys,
},
r,
vec![poly_A[0], poly_B[0], poly_C[0], poly_D[0]],
))
let poly_A_final = (0..poly_A_vec.len()).map(|i| poly_A_vec[i][0]).collect();
let poly_B_final = (0..poly_B_vec.len()).map(|i| poly_B_vec[i][0]).collect();
let claims_prod = (poly_A_final, poly_B_final);
Ok((SumcheckProof::new(quad_polys), r, claims_prod))
} }
pub fn prove_cubic_with_additive_term<F>( pub fn prove_cubic_with_additive_term<F>(

Loading…
Cancel
Save