Skip to content

Commit

Permalink
A more optimal preprocessing SNARK (#158)
Browse files Browse the repository at this point in the history
* a more optimal preprocessing SNARK

* update version

* cleanup; address clippy
  • Loading branch information
srinathsetty authored Apr 1, 2023
1 parent 4aab459 commit 3b3ae70
Show file tree
Hide file tree
Showing 14 changed files with 2,112 additions and 1,595 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nova-snark"
version = "0.19.1"
version = "0.20.0"
authors = ["Srinath Setty <srinath@microsoft.com>"]
edition = "2021"
description = "Recursive zkSNARKs without trusted setup"
Expand Down
6 changes: 2 additions & 4 deletions benches/compressed-snark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ type G1 = pasta_curves::pallas::Point;
type G2 = pasta_curves::vesta::Point;
type EE1 = nova_snark::provider::ipa_pc::EvaluationEngine<G1>;
type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine<G2>;
type CC1 = nova_snark::spartan::spark::TrivialCompComputationEngine<G1, EE1>;
type CC2 = nova_snark::spartan::spark::TrivialCompComputationEngine<G2, EE2>;
type S1 = nova_snark::spartan::RelaxedR1CSSNARK<G1, EE1, CC1>;
type S2 = nova_snark::spartan::RelaxedR1CSSNARK<G2, EE2, CC2>;
type S1 = nova_snark::spartan::RelaxedR1CSSNARK<G1, EE1>;
type S2 = nova_snark::spartan::RelaxedR1CSSNARK<G2, EE2>;
type C1 = NonTrivialTestCircuit<<G1 as Group>::Scalar>;
type C2 = TrivialTestCircuit<<G2 as Group>::Scalar>;

Expand Down
6 changes: 2 additions & 4 deletions examples/minroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,8 @@ fn main() {
let start = Instant::now();
type EE1 = nova_snark::provider::ipa_pc::EvaluationEngine<G1>;
type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine<G2>;
type CC1 = nova_snark::spartan::spark::TrivialCompComputationEngine<G1, EE1>;
type CC2 = nova_snark::spartan::spark::TrivialCompComputationEngine<G2, EE2>;
type S1 = nova_snark::spartan::RelaxedR1CSSNARK<G1, EE1, CC1>;
type S2 = nova_snark::spartan::RelaxedR1CSSNARK<G2, EE2, CC2>;
type S1 = nova_snark::spartan::RelaxedR1CSSNARK<G1, EE1>;
type S2 = nova_snark::spartan::RelaxedR1CSSNARK<G2, EE2>;

let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark);
println!(
Expand Down
3 changes: 3 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,7 @@ pub enum NovaError {
/// returned when the product proof check fails
#[error("InvalidProductProof")]
InvalidProductProof,
/// returned when the consistency with public IO and assignment used fails
#[error("IncorrectWitness")]
IncorrectWitness,
}
12 changes: 4 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -788,10 +788,8 @@ mod tests {
type G2 = pasta_curves::vesta::Point;
type EE1 = provider::ipa_pc::EvaluationEngine<G1>;
type EE2 = provider::ipa_pc::EvaluationEngine<G2>;
type CC1 = spartan::spark::TrivialCompComputationEngine<G1, EE1>;
type CC2 = spartan::spark::TrivialCompComputationEngine<G2, EE2>;
type S1 = spartan::RelaxedR1CSSNARK<G1, EE1, CC1>;
type S2 = spartan::RelaxedR1CSSNARK<G2, EE2, CC2>;
type S1 = spartan::RelaxedR1CSSNARK<G1, EE1>;
type S2 = spartan::RelaxedR1CSSNARK<G2, EE2>;
use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
use core::marker::PhantomData;
use ff::PrimeField;
Expand Down Expand Up @@ -1095,10 +1093,8 @@ mod tests {
assert_eq!(zn_secondary, vec![<G2 as Group>::Scalar::from(2460515u64)]);

// run the compressed snark with Spark compiler
type CC1Prime = spartan::spark::SparkEngine<G1>;
type CC2Prime = spartan::spark::SparkEngine<G2>;
type S1Prime = spartan::RelaxedR1CSSNARK<G1, EE1, CC1Prime>;
type S2Prime = spartan::RelaxedR1CSSNARK<G2, EE2, CC2Prime>;
type S1Prime = spartan::pp::RelaxedR1CSSNARK<G1, EE1>;
type S2Prime = spartan::pp::RelaxedR1CSSNARK<G2, EE2>;

// produce the prover and verifier keys for compressed snark
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1Prime, S2Prime>::setup(&pp).unwrap();
Expand Down
4 changes: 2 additions & 2 deletions src/r1cs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ impl<G: Group> R1CS<G> {
pub fn commitment_key(S: &R1CSShape<G>) -> CommitmentKey<G> {
let num_cons = S.num_cons;
let num_vars = S.num_vars;
let num_nz = max(max(S.A.len(), S.B.len()), S.C.len());
G::CE::setup(b"ck", max(max(num_cons, num_vars), num_nz))
let total_nz = S.A.len() + S.B.len() + S.C.len();
G::CE::setup(b"ck", max(max(num_cons, num_vars), total_nz))
}
}

Expand Down
184 changes: 63 additions & 121 deletions src/spartan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
//! over the polynomial commitment and evaluation argument (i.e., a PCS)
mod math;
pub(crate) mod polynomial;
pub mod spark;
pub mod pp;
mod sumcheck;

use crate::{
errors::NovaError,
r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness},
traits::{
evaluation::EvaluationEngineTrait, snark::RelaxedR1CSSNARKTrait, Group, TranscriptEngineTrait,
TranscriptReprTrait,
},
Commitment, CommitmentKey,
};
Expand All @@ -22,7 +21,6 @@ use serde::{Deserialize, Serialize};
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
}
Expand Down Expand Up @@ -56,7 +54,6 @@ impl<G: Group> PolyEvalWitness<G> {
}

/// 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
Expand All @@ -80,107 +77,43 @@ impl<G: Group> PolyEvalInstance<G> {
}
}

/// A trait that defines the behavior of a computation commitment engine
pub trait CompCommitmentEngineTrait<G: Group> {
/// A type that holds opening hint
type Decommitment: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>;

/// A type that holds a commitment
type Commitment: Clone
+ Send
+ Sync
+ TranscriptReprTrait<G>
+ Serialize
+ for<'de> Deserialize<'de>;

/// A type that holds an evaluation argument
type EvaluationArgument: Send + Sync + Serialize + for<'de> Deserialize<'de>;

/// commits to R1CS matrices
fn commit(
ck: &CommitmentKey<G>,
S: &R1CSShape<G>,
) -> Result<(Self::Commitment, Self::Decommitment), NovaError>;

/// proves an evaluation of R1CS matrices viewed as polynomials
fn prove(
ck: &CommitmentKey<G>,
S: &R1CSShape<G>,
decomm: &Self::Decommitment,
comm: &Self::Commitment,
r: &(&[G::Scalar], &[G::Scalar]),
transcript: &mut G::TE,
) -> Result<
(
Self::EvaluationArgument,
Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>,
),
NovaError,
>;

/// verifies an evaluation of R1CS matrices viewed as polynomials and returns verified evaluations
fn verify(
comm: &Self::Commitment,
r: &(&[G::Scalar], &[G::Scalar]),
arg: &Self::EvaluationArgument,
transcript: &mut G::TE,
) -> Result<(G::Scalar, G::Scalar, G::Scalar, Vec<PolyEvalInstance<G>>), NovaError>;
}

/// A type that represents the prover's key
#[derive(Serialize, Deserialize)]
#[serde(bound = "")]
pub struct ProverKey<
G: Group,
EE: EvaluationEngineTrait<G, CE = G::CE>,
CC: CompCommitmentEngineTrait<G>,
> {
pub struct ProverKey<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> {
pk_ee: EE::ProverKey,
S: R1CSShape<G>,
decomm: CC::Decommitment,
comm: CC::Commitment,
}

/// A type that represents the verifier's key
#[derive(Serialize, Deserialize)]
#[serde(bound = "")]
pub struct VerifierKey<
G: Group,
EE: EvaluationEngineTrait<G, CE = G::CE>,
CC: CompCommitmentEngineTrait<G>,
> {
num_cons: usize,
num_vars: usize,
pub struct VerifierKey<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> {
vk_ee: EE::VerifierKey,
comm: CC::Commitment,
S: R1CSShape<G>,
}

/// A succinct proof of knowledge of a witness to a relaxed R1CS instance
/// The proof is produced using Spartan's combination of the sum-check and
/// the commitment to a vector viewed as a polynomial commitment
#[derive(Serialize, Deserialize)]
#[serde(bound = "")]
pub struct RelaxedR1CSSNARK<
G: Group,
EE: EvaluationEngineTrait<G, CE = G::CE>,
CC: CompCommitmentEngineTrait<G>,
> {
pub struct RelaxedR1CSSNARK<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> {
sc_proof_outer: SumcheckProof<G>,
claims_outer: (G::Scalar, G::Scalar, G::Scalar),
eval_E: G::Scalar,
sc_proof_inner: SumcheckProof<G>,
eval_W: G::Scalar,
eval_arg_cc: CC::EvaluationArgument,
sc_proof_batch: SumcheckProof<G>,
evals_batch: Vec<G::Scalar>,
eval_arg: EE::EvaluationArgument,
}

impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngineTrait<G>>
RelaxedR1CSSNARKTrait<G> for RelaxedR1CSSNARK<G, EE, CC>
impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> RelaxedR1CSSNARKTrait<G>
for RelaxedR1CSSNARK<G, EE>
{
type ProverKey = ProverKey<G, EE, CC>;
type VerifierKey = VerifierKey<G, EE, CC>;
type ProverKey = ProverKey<G, EE>;
type VerifierKey = VerifierKey<G, EE>;

fn setup(
ck: &CommitmentKey<G>,
Expand All @@ -190,21 +123,12 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngin

let S = S.pad();

let (comm, decomm) = CC::commit(ck, &S)?;

let vk = VerifierKey {
num_cons: S.num_cons,
num_vars: S.num_vars,
vk_ee,
comm: comm.clone(),
S: S.clone(),
};

let pk = ProverKey {
pk_ee,
S,
comm,
decomm,
};
let pk = ProverKey { pk_ee, S };

Ok((pk, vk))
}
Expand All @@ -225,8 +149,8 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngin
assert_eq!(pk.S.num_io.next_power_of_two(), pk.S.num_io);
assert!(pk.S.num_io < pk.S.num_vars);

// append the commitment to R1CS matrices and the RelaxedR1CSInstance to the transcript
transcript.absorb(b"C", &pk.comm);
// append the digest of R1CS matrices and the RelaxedR1CSInstance to the transcript
transcript.absorb(b"S", &pk.S);
transcript.absorb(b"U", U);

// compute the full satisfying assignment by concatenating W.W, U.u, and U.X
Expand Down Expand Up @@ -353,17 +277,8 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngin
&mut transcript,
)?;

// we now prove evaluations of R1CS matrices at (r_x, r_y)
let (eval_arg_cc, mut w_u_vec) = CC::prove(
ck,
&pk.S,
&pk.decomm,
&pk.comm,
&(&r_x, &r_y),
&mut transcript,
)?;

// add additional claims about W and E polynomials to the list from CC
let mut w_u_vec = Vec::new();
let eval_W = MultilinearPolynomial::evaluate_with(&W.W, &r_y[1..]);
w_u_vec.push((
PolyEvalWitness { p: W.W.clone() },
Expand Down Expand Up @@ -475,7 +390,6 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngin
eval_E,
sc_proof_inner,
eval_W,
eval_arg_cc,
sc_proof_batch,
evals_batch: claims_batch_left,
eval_arg,
Expand All @@ -486,13 +400,13 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngin
fn verify(&self, vk: &Self::VerifierKey, U: &RelaxedR1CSInstance<G>) -> Result<(), NovaError> {
let mut transcript = G::TE::new(b"RelaxedR1CSSNARK");

// append the commitment to R1CS matrices and the RelaxedR1CSInstance to the transcript
transcript.absorb(b"C", &vk.comm);
// append the digest of R1CS matrices and the RelaxedR1CSInstance to the transcript
transcript.absorb(b"S", &vk.S);
transcript.absorb(b"U", U);

let (num_rounds_x, num_rounds_y) = (
(vk.num_cons as f64).log2() as usize,
((vk.num_vars as f64).log2() as usize + 1),
(vk.S.num_cons as f64).log2() as usize,
((vk.S.num_vars as f64).log2() as usize + 1),
);

// outer sum-check
Expand Down Expand Up @@ -546,32 +460,60 @@ impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngin
.map(|i| (i + 1, U.X[i]))
.collect::<Vec<(usize, G::Scalar)>>(),
);
SparsePolynomial::new((vk.num_vars as f64).log2() as usize, poly_X).evaluate(&r_y[1..])
SparsePolynomial::new((vk.S.num_vars as f64).log2() as usize, poly_X).evaluate(&r_y[1..])
};
(G::Scalar::one() - r_y[0]) * self.eval_W + r_y[0] * eval_X
};

// verify evaluation argument to retrieve evaluations of R1CS matrices
let (eval_A, eval_B, eval_C, mut u_vec) =
CC::verify(&vk.comm, &(&r_x, &r_y), &self.eval_arg_cc, &mut transcript)?;
// compute evaluations of R1CS matrices
let multi_evaluate = |M_vec: &[&[(usize, usize, G::Scalar)]],
r_x: &[G::Scalar],
r_y: &[G::Scalar]|
-> Vec<G::Scalar> {
let evaluate_with_table =
|M: &[(usize, usize, G::Scalar)], T_x: &[G::Scalar], T_y: &[G::Scalar]| -> G::Scalar {
(0..M.len())
.collect::<Vec<usize>>()
.par_iter()
.map(|&i| {
let (row, col, val) = M[i];
T_x[row] * T_y[col] * val
})
.reduce(G::Scalar::zero, |acc, x| acc + x)
};

let (T_x, T_y) = rayon::join(
|| EqPolynomial::new(r_x.to_vec()).evals(),
|| EqPolynomial::new(r_y.to_vec()).evals(),
);

(0..M_vec.len())
.collect::<Vec<usize>>()
.par_iter()
.map(|&i| evaluate_with_table(M_vec[i], &T_x, &T_y))
.collect()
};

let evals = multi_evaluate(&[&vk.S.A, &vk.S.B, &vk.S.C], &r_x, &r_y);

let claim_inner_final_expected = (eval_A + r * eval_B + r * r * eval_C) * eval_Z;
let claim_inner_final_expected = (evals[0] + r * evals[1] + r * r * evals[2]) * eval_Z;
if claim_inner_final != claim_inner_final_expected {
return Err(NovaError::InvalidSumcheckProof);
}

// 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,
});
// add claims about W and E polynomials
let u_vec: Vec<PolyEvalInstance<G>> = vec![
PolyEvalInstance {
c: U.comm_W,
x: r_y[1..].to_vec(),
e: self.eval_W,
},
PolyEvalInstance {
c: U.comm_E,
x: r_x,
e: self.eval_E,
},
];

let u_vec_padded = PolyEvalInstance::pad(&u_vec); // pad the evaluation points

Expand Down
Loading

0 comments on commit 3b3ae70

Please sign in to comment.