Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spark-based commitments to R1CS matrices #152

Merged
merged 2 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.18.2"
version = "0.19.0"
authors = ["Srinath Setty <[email protected]>"]
edition = "2021"
description = "Recursive zkSNARKs without trusted setup"
Expand Down
8 changes: 5 additions & 3 deletions benches/compressed-snark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ 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 S1 = nova_snark::spartan::RelaxedR1CSSNARK<G1, EE1>;
type S2 = nova_snark::spartan::RelaxedR1CSSNARK<G2, EE2>;
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 C1 = NonTrivialTestCircuit<<G1 as Group>::Scalar>;
type C2 = TrivialTestCircuit<<G2 as Group>::Scalar>;

Expand Down Expand Up @@ -50,7 +52,7 @@ fn bench_compressed_snark(c: &mut Criterion) {
);

// Produce prover and verifier keys for CompressedSNARK
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp);
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap();

// produce a recursive SNARK
let num_steps = 3;
Expand Down
8 changes: 5 additions & 3 deletions examples/minroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,15 @@ fn main() {

// produce a compressed SNARK
println!("Generating a CompressedSNARK using Spartan with IPA-PC...");
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp);
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap();

let start = Instant::now();
type EE1 = nova_snark::provider::ipa_pc::EvaluationEngine<G1>;
type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine<G2>;
type S1 = nova_snark::spartan::RelaxedR1CSSNARK<G1, EE1>;
type S2 = nova_snark::spartan::RelaxedR1CSSNARK<G2, EE2>;
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>;

let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark);
println!(
Expand Down
3 changes: 1 addition & 2 deletions src/bellperson/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ mod tests {
// First create the shape
let mut cs: ShapeCS<G> = ShapeCS::new();
let _ = synthesize_alloc_bit(&mut cs);
let shape = cs.r1cs_shape();
let ck = cs.commitment_key();
let (shape, ck) = cs.r1cs_shape();

// Now get the assignment
let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
Expand Down
16 changes: 6 additions & 10 deletions src/bellperson/r1cs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@ pub trait NovaWitness<G: Group> {
) -> Result<(R1CSInstance<G>, R1CSWitness<G>), NovaError>;
}

/// `NovaShape` provides methods for acquiring `R1CSShape` and `R1CSGens` from implementers.
/// `NovaShape` provides methods for acquiring `R1CSShape` and `CommitmentKey` from implementers.
pub trait NovaShape<G: Group> {
/// Return an appropriate `R1CSShape` struct.
fn r1cs_shape(&self) -> R1CSShape<G>;
/// Return an appropriate `CommitmentKey` struct.
fn commitment_key(&self) -> CommitmentKey<G>;
/// Return an appropriate `R1CSShape` and `CommitmentKey` structs.
fn r1cs_shape(&self) -> (R1CSShape<G>, CommitmentKey<G>);
}

impl<G: Group> NovaWitness<G> for SatisfyingAssignment<G>
Expand All @@ -54,7 +52,7 @@ impl<G: Group> NovaShape<G> for ShapeCS<G>
where
G::Scalar: PrimeField,
{
fn r1cs_shape(&self) -> R1CSShape<G> {
fn r1cs_shape(&self) -> (R1CSShape<G>, CommitmentKey<G>) {
let mut A: Vec<(usize, usize, G::Scalar)> = Vec::new();
let mut B: Vec<(usize, usize, G::Scalar)> = Vec::new();
let mut C: Vec<(usize, usize, G::Scalar)> = Vec::new();
Expand Down Expand Up @@ -84,11 +82,9 @@ where
res.unwrap()
};

S
}
let ck = R1CS::<G>::commitment_key(&S);

fn commitment_key(&self) -> CommitmentKey<G> {
R1CS::<G>::commitment_key(self.num_constraints(), self.num_aux())
(S, ck)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ mod tests {
);
let mut cs: ShapeCS<G1> = ShapeCS::new();
let _ = circuit1.synthesize(&mut cs);
let (shape1, ck1) = (cs.r1cs_shape(), cs.commitment_key());
let (shape1, ck1) = cs.r1cs_shape();
assert_eq!(cs.num_constraints(), 9815);

// Initialize the shape and ck for the secondary
Expand All @@ -413,7 +413,7 @@ mod tests {
);
let mut cs: ShapeCS<G2> = ShapeCS::new();
let _ = circuit2.synthesize(&mut cs);
let (shape2, ck2) = (cs.r1cs_shape(), cs.commitment_key());
let (shape2, ck2) = cs.r1cs_shape();
assert_eq!(cs.num_constraints(), 10347);

// Execute the base case for the primary
Expand Down
6 changes: 6 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,10 @@ pub enum NovaError {
/// returned when the transcript engine encounters an overflow of the round number
#[error("InternalTranscriptError")]
InternalTranscriptError,
/// returned when the multiset check fails
#[error("InvalidMultisetProof")]
InvalidMultisetProof,
/// returned when the product proof check fails
#[error("InvalidProductProof")]
InvalidProductProof,
}
9 changes: 3 additions & 6 deletions src/gadgets/ecc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -975,8 +975,7 @@ mod tests {
let mut cs: ShapeCS<G2> = ShapeCS::new();
let _ = synthesize_smul::<G1, _>(cs.namespace(|| "synthesize"));
println!("Number of constraints: {}", cs.num_constraints());
let shape = cs.r1cs_shape();
let ck = cs.commitment_key();
let (shape, ck) = cs.r1cs_shape();

// Then the satisfying assignment
let mut cs: SatisfyingAssignment<G2> = SatisfyingAssignment::new();
Expand Down Expand Up @@ -1017,8 +1016,7 @@ mod tests {
let mut cs: ShapeCS<G2> = ShapeCS::new();
let _ = synthesize_add_equal::<G1, _>(cs.namespace(|| "synthesize add equal"));
println!("Number of constraints: {}", cs.num_constraints());
let shape = cs.r1cs_shape();
let ck = cs.commitment_key();
let (shape, ck) = cs.r1cs_shape();

// Then the satisfying assignment
let mut cs: SatisfyingAssignment<G2> = SatisfyingAssignment::new();
Expand Down Expand Up @@ -1063,8 +1061,7 @@ mod tests {
let mut cs: ShapeCS<G2> = ShapeCS::new();
let _ = synthesize_add_negation::<G1, _>(cs.namespace(|| "synthesize add equal"));
println!("Number of constraints: {}", cs.num_constraints());
let shape = cs.r1cs_shape();
let ck = cs.commitment_key();
let (shape, ck) = cs.r1cs_shape();

// Then the satisfying assignment
let mut cs: SatisfyingAssignment<G2> = SatisfyingAssignment::new();
Expand Down
116 changes: 103 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ where
);
let mut cs: ShapeCS<G1> = ShapeCS::new();
let _ = circuit_primary.synthesize(&mut cs);
let (r1cs_shape_primary, ck_primary) = (cs.r1cs_shape(), cs.commitment_key());
let (r1cs_shape_primary, ck_primary) = cs.r1cs_shape();

// Initialize ck for the secondary
let circuit_secondary: NovaAugmentedCircuit<G1, C2> = NovaAugmentedCircuit::new(
Expand All @@ -117,7 +117,7 @@ where
);
let mut cs: ShapeCS<G2> = ShapeCS::new();
let _ = circuit_secondary.synthesize(&mut cs);
let (r1cs_shape_secondary, ck_secondary) = (cs.r1cs_shape(), cs.commitment_key());
let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape();

Self {
F_arity_primary,
Expand Down Expand Up @@ -580,12 +580,15 @@ where
/// Creates prover and verifier keys for `CompressedSNARK`
pub fn setup(
pp: &PublicParams<G1, G2, C1, C2>,
) -> (
ProverKey<G1, G2, C1, C2, S1, S2>,
VerifierKey<G1, G2, C1, C2, S1, S2>,
) {
let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary);
let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary);
) -> Result<
(
ProverKey<G1, G2, C1, C2, S1, S2>,
VerifierKey<G1, G2, C1, C2, S1, S2>,
),
NovaError,
> {
let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?;
let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?;

let pk = ProverKey {
pk_primary,
Expand All @@ -607,7 +610,7 @@ where
_p_c2: Default::default(),
};

(pk, vk)
Ok((pk, vk))
}

/// Create a new `CompressedSNARK`
Expand Down Expand Up @@ -785,8 +788,10 @@ mod tests {
type G2 = pasta_curves::vesta::Point;
type EE1 = provider::ipa_pc::EvaluationEngine<G1>;
type EE2 = provider::ipa_pc::EvaluationEngine<G2>;
type S1 = spartan::RelaxedR1CSSNARK<G1, EE1>;
type S2 = spartan::RelaxedR1CSSNARK<G2, EE2>;
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>;
use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
use core::marker::PhantomData;
use ff::PrimeField;
Expand Down Expand Up @@ -1011,7 +1016,7 @@ mod tests {
assert_eq!(zn_secondary, vec![<G2 as Group>::Scalar::from(2460515u64)]);

// produce the prover and verifier keys for compressed snark
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp);
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap();

// produce a compressed SNARK
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark);
Expand All @@ -1028,6 +1033,91 @@ mod tests {
assert!(res.is_ok());
}

#[test]
fn test_ivc_nontrivial_with_spark_compression() {
let circuit_primary = TrivialTestCircuit::default();
let circuit_secondary = CubicCircuit::default();

// produce public parameters
let pp = PublicParams::<
G1,
G2,
TrivialTestCircuit<<G1 as Group>::Scalar>,
CubicCircuit<<G2 as Group>::Scalar>,
>::setup(circuit_primary.clone(), circuit_secondary.clone());

let num_steps = 3;

// produce a recursive SNARK
let mut recursive_snark: Option<
RecursiveSNARK<
G1,
G2,
TrivialTestCircuit<<G1 as Group>::Scalar>,
CubicCircuit<<G2 as Group>::Scalar>,
>,
> = None;

for _i in 0..num_steps {
let res = RecursiveSNARK::prove_step(
&pp,
recursive_snark,
circuit_primary.clone(),
circuit_secondary.clone(),
vec![<G1 as Group>::Scalar::one()],
vec![<G2 as Group>::Scalar::zero()],
);
assert!(res.is_ok());
recursive_snark = Some(res.unwrap());
}

assert!(recursive_snark.is_some());
let recursive_snark = recursive_snark.unwrap();

// verify the recursive SNARK
let res = recursive_snark.verify(
&pp,
num_steps,
vec![<G1 as Group>::Scalar::one()],
vec![<G2 as Group>::Scalar::zero()],
);
assert!(res.is_ok());

let (zn_primary, zn_secondary) = res.unwrap();

// sanity: check the claimed output with a direct computation of the same
assert_eq!(zn_primary, vec![<G1 as Group>::Scalar::one()]);
let mut zn_secondary_direct = vec![<G2 as Group>::Scalar::zero()];
for _i in 0..num_steps {
zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct);
}
assert_eq!(zn_secondary, zn_secondary_direct);
assert_eq!(zn_secondary, vec![<G2 as Group>::Scalar::from(2460515u64)]);

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

// produce the prover and verifier keys for compressed snark
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1Prime, S2Prime>::setup(&pp).unwrap();

// produce a compressed SNARK
let res = CompressedSNARK::<_, _, _, _, S1Prime, S2Prime>::prove(&pp, &pk, &recursive_snark);
assert!(res.is_ok());
let compressed_snark = res.unwrap();

// verify the compressed SNARK
let res = compressed_snark.verify(
&vk,
num_steps,
vec![<G1 as Group>::Scalar::one()],
vec![<G2 as Group>::Scalar::zero()],
);
assert!(res.is_ok());
}

#[test]
fn test_ivc_nondet_with_compression() {
// y is a non-deterministic advice representing the fifth root of the input at a step.
Expand Down Expand Up @@ -1162,7 +1252,7 @@ mod tests {
assert!(res.is_ok());

// produce the prover and verifier keys for compressed snark
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp);
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap();

// produce a compressed SNARK
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark);
Expand Down
5 changes: 2 additions & 3 deletions src/nifs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,7 @@ mod tests {
// First create the shape
let mut cs: ShapeCS<G> = ShapeCS::new();
let _ = synthesize_tiny_r1cs_bellperson(&mut cs, None);
let shape = cs.r1cs_shape();
let ck = cs.commitment_key();
let (shape, ck) = cs.r1cs_shape();
let ro_consts =
<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants::new();

Expand Down Expand Up @@ -305,7 +304,7 @@ mod tests {
};

// generate generators and ro constants
let ck = R1CS::<G>::commitment_key(num_cons, num_vars);
let ck = R1CS::<G>::commitment_key(&S);
let ro_consts =
<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants::new();

Expand Down
8 changes: 6 additions & 2 deletions src/provider/ipa_pc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ where
) -> Result<Self, NovaError> {
transcript.dom_sep(Self::protocol_name());

let (ck, _) = ck.split_at(U.b_vec.len());

if U.b_vec.len() != W.a_vec.len() {
return Err(NovaError::InvalidInputLength);
}
Expand Down Expand Up @@ -272,7 +274,7 @@ where
// we create mutable copies of vectors and generators
let mut a_vec = W.a_vec.to_vec();
let mut b_vec = U.b_vec.to_vec();
let mut ck = ck.clone();
let mut ck = ck;
for _i in 0..(U.b_vec.len() as f64).log2() as usize {
let (L, R, a_vec_folded, b_vec_folded, ck_folded) =
prove_inner(&a_vec, &b_vec, &ck, transcript)?;
Expand Down Expand Up @@ -300,6 +302,8 @@ where
U: &InnerProductInstance<G>,
transcript: &mut G::TE,
) -> Result<(), NovaError> {
let (ck, _) = ck.split_at(U.b_vec.len());

transcript.dom_sep(Self::protocol_name());
if U.b_vec.len() != n
|| n != (1 << self.L_vec.len())
Expand Down Expand Up @@ -383,7 +387,7 @@ where
};

let ck_hat = {
let c = CE::<G>::commit(ck, &s).compress();
let c = CE::<G>::commit(&ck, &s).compress();
CommitmentKey::<G>::reinterpret_commitments_as_ck(&[c])?
};

Expand Down
7 changes: 5 additions & 2 deletions src/r1cs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ pub struct RelaxedR1CSInstance<G: Group> {

impl<G: Group> R1CS<G> {
/// Samples public parameters for the specified number of constraints and variables in an R1CS
pub fn commitment_key(num_cons: usize, num_vars: usize) -> CommitmentKey<G> {
G::CE::setup(b"ck", max(num_vars, num_cons))
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))
}
}

Expand Down
Loading