From f88d29fb0c0cdf081017319dcec4165c8c6733e3 Mon Sep 17 00:00:00 2001 From: Mara Mihali Date: Tue, 22 Nov 2022 18:09:27 +0000 Subject: [PATCH] first version of PST --- .github/workflows/testudo.yml | 12 ++- Cargo.toml | 13 ++- README.md | 4 +- benches/nizk.rs | 13 ++- benches/r1cs.rs | 30 +++--- profiler/nizk.rs | 8 +- src/constraints.rs | 43 ++++++-- src/dense_mlpoly.rs | 178 +++++++++++++++++++++++++++++++++- src/lib.rs | 56 ++++++----- src/poseidon_transcript.rs | 12 ++- src/r1csproof.rs | 88 ++++++++++++++--- 11 files changed, 380 insertions(+), 77 deletions(-) diff --git a/.github/workflows/testudo.yml b/.github/workflows/testudo.yml index acd6a67..3d2063b 100644 --- a/.github/workflows/testudo.yml +++ b/.github/workflows/testudo.yml @@ -5,6 +5,12 @@ on: branches: [master] pull_request: branches: [master] +# The crate ark-ff uses the macro llvm_asm! when emitting asm which returns an +# error because it was deprecated in favour of asm!. We temporarily overcome +# this problem by setting the environment variable below (until the crate +# is updated). +env: + RUSTFLAGS: "--emit asm -C llvm-args=-x86-asm-syntax=intel" jobs: build_nightly: @@ -25,5 +31,7 @@ jobs: run: cargo build --examples --verbose - name: Check Rustfmt Code Style run: cargo fmt --all -- --check - - name: Check clippy warnings - run: cargo clippy --all-targets --all-features + # cargo clippy uses cargo check which returns an error when asm is emitted + # we want to emit asm for ff operations so we avoid using clippy for now + # - name: Check clippy warnings + # run: cargo clippy --all-targets --all-features diff --git a/Cargo.toml b/Cargo.toml index 4088fb8..da7c835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,10 +36,12 @@ ark-sponge = { version = "^0.3.0" , features = ["r1cs"] } ark-crypto-primitives = { version = "^0.3.0", default-features = true } ark-r1cs-std = { version = "^0.3.0", default-features = false } ark-nonnative-field = { version = "0.3.0", default-features = false } -ark-relations = { version = "^0.3.0", default-features = false } +ark-relations = { version = "^0.3.0", default-features = false, optional = true } ark-snark = { version = "^0.3.0", default-features = false } ark-groth16 = { version = "^0.3.0", features = ["r1cs"] } ark-bw6-761 = { version = "^0.3.0" } +ark-poly-commit = { version = "^0.3.0" } +ark-poly = {version = "^0.3.0"} lazy_static = "1.4.0" rand = { version = "0.8", features = [ "std", "std_rng" ] } @@ -82,9 +84,12 @@ debug = true [features] multicore = ["rayon"] profile = [] -default = ["parallel", "std"] -parallel = [ "std", "ark-ff/parallel", "ark-std/parallel", "ark-ec/parallel"] +default = ["asm","parallel", "std", "multicore"] +asm = ["ark-ff/asm"] +parallel = [ "std", "ark-ff/parallel", "ark-std/parallel", "ark-ec/parallel", "ark-poly/parallel", "rayon"] std = ["ark-ff/std", "ark-ec/std", "ark-std/std", "ark-relations/std", "ark-serialize/std"] [patch.crates-io] -ark-r1cs-std = { git = "https://github.com/arkworks-rs/r1cs-std/", rev = "a2a5ac491ae005ba2afd03fd21b7d3160d794a83"} \ No newline at end of file +ark-r1cs-std = { git = "https://github.com/arkworks-rs/r1cs-std/", rev = "a2a5ac491ae005ba2afd03fd21b7d3160d794a83"} +ark-poly-commit = {git = "https://github.com/maramihali/poly-commit"} + diff --git a/README.md b/README.md index 1003957..0eed4a7 100644 --- a/README.md +++ b/README.md @@ -100,12 +100,12 @@ Here is another example to use the NIZK variant of the Spartan proof system: // produce a proof of satisfiability let mut prover_transcript = PoseidonTranscript::new(¶ms); - let proof = NIZK::prove(&inst, vars, &inputs, &mut prover_transcript); + let proof = NIZK::prove(&inst, vars, &inputs, &gens, &mut prover_transcript); // verify the proof of satisfiability let mut verifier_transcript = PoseidonTranscript::new(¶ms); assert!(proof - .verify(&inst, &inputs, &mut verifier_transcript) + .verify(&inst, &inputs, &mut verifier_transcript, &gens) .is_ok()); println!("proof verification successful!"); # } diff --git a/benches/nizk.rs b/benches/nizk.rs index e2f6bea..57490ae 100644 --- a/benches/nizk.rs +++ b/benches/nizk.rs @@ -9,7 +9,8 @@ extern crate sha3; use std::time::{Duration, SystemTime}; use libspartan::{ - parameters::POSEIDON_PARAMETERS_FR_377, poseidon_transcript::PoseidonTranscript, Instance, NIZK, + parameters::POSEIDON_PARAMETERS_FR_377, poseidon_transcript::PoseidonTranscript, Instance, + NIZKGens, NIZK, }; use criterion::*; @@ -30,6 +31,7 @@ fn nizk_prove_benchmark(c: &mut Criterion) { num_cons, duration.as_millis() ); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); let name = format!("R1CS_prove_{}", num_vars); group @@ -41,6 +43,7 @@ fn nizk_prove_benchmark(c: &mut Criterion) { black_box(&inst), black_box(vars.clone()), black_box(&inputs), + black_box(&gens), black_box(&mut prover_transcript), ); }); @@ -66,9 +69,10 @@ fn nizk_verify_benchmark(c: &mut Criterion) { num_cons, duration.as_millis() ); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); // produce a proof of satisfiability let mut prover_transcript = PoseidonTranscript::new(&POSEIDON_PARAMETERS_FR_377); - let proof = NIZK::prove(&inst, vars, &inputs, &mut prover_transcript); + let proof = NIZK::prove(&inst, vars, &inputs, &gens, &mut prover_transcript); let name = format!("R1CS_verify_{}", num_cons); group @@ -81,6 +85,7 @@ fn nizk_verify_benchmark(c: &mut Criterion) { black_box(&inst), black_box(&inputs), black_box(&mut verifier_transcript), + black_box(&gens), ) .is_ok()); }); @@ -108,7 +113,8 @@ fn nizk_verify_groth16_benchmark(c: &mut Criterion) { ); // produce a proof of satisfiability let mut prover_transcript = PoseidonTranscript::new(&POSEIDON_PARAMETERS_FR_377); - let proof = NIZK::prove(&inst, vars, &inputs, &mut prover_transcript); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); + let proof = NIZK::prove(&inst, vars, &inputs, &gens, &mut prover_transcript); let name = format!("R1CS_verify_groth16_{}", num_cons); group @@ -121,6 +127,7 @@ fn nizk_verify_groth16_benchmark(c: &mut Criterion) { black_box(&inst), black_box(&inputs), black_box(&mut verifier_transcript), + black_box(&gens) ) .is_ok()); }); diff --git a/benches/r1cs.rs b/benches/r1cs.rs index 79daa68..5852c3f 100644 --- a/benches/r1cs.rs +++ b/benches/r1cs.rs @@ -1,7 +1,8 @@ use std::time::Instant; use libspartan::{ - parameters::POSEIDON_PARAMETERS_FR_377, poseidon_transcript::PoseidonTranscript, Instance, NIZK, + parameters::POSEIDON_PARAMETERS_FR_377, poseidon_transcript::PoseidonTranscript, Instance, + NIZKGens, NIZK, }; use serde::Serialize; @@ -14,17 +15,19 @@ struct BenchmarkResults { spartan_proving_time: u128, groth16_setup_time: u128, groth16_proving_time: u128, - groth16_verification_time: u128, + testudo_verification_time: u128, testudo_proving_time: u128, } fn main() { let mut writer = csv::Writer::from_path("testudo.csv").expect("unable to open csv writer"); - for &s in [ - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, - ] - .iter() - { + // for &s in [ + // 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + // ] + // .iter() + // For testing purposes we currently bench on very small instance to ensure + // correctness and then on biggest one for timings. + for &s in [4, 26].iter() { println!("Running for {} inputs", s); let mut br = BenchmarkResults::default(); let num_vars = (2_usize).pow(s as u32); @@ -38,28 +41,29 @@ fn main() { let duration = start.elapsed().as_millis(); br.r1cs_instance_generation_time = duration; let mut prover_transcript = PoseidonTranscript::new(&POSEIDON_PARAMETERS_FR_377); + + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); + let start = Instant::now(); - let proof = NIZK::prove(&inst, vars, &inputs, &mut prover_transcript); + let proof = NIZK::prove(&inst, vars, &inputs, &gens, &mut prover_transcript); let duration = start.elapsed().as_millis(); - println!("{:?}", duration); br.spartan_proving_time = duration; let mut verifier_transcript = PoseidonTranscript::new(&POSEIDON_PARAMETERS_FR_377); - let res = proof.verify(&inst, &inputs, &mut verifier_transcript); + let res = proof.verify(&inst, &inputs, &mut verifier_transcript, &gens); assert!(res.is_ok()); br.spartan_verifier_circuit_constraints = res.unwrap(); let mut verifier_transcript = PoseidonTranscript::new(&POSEIDON_PARAMETERS_FR_377); - let res = proof.verify_groth16(&inst, &inputs, &mut verifier_transcript); + let res = proof.verify_groth16(&inst, &inputs, &mut verifier_transcript, &gens); assert!(res.is_ok()); let (ds, dp, dv) = res.unwrap(); br.groth16_setup_time = ds; br.groth16_proving_time = dp; - br.groth16_verification_time = dv; br.testudo_proving_time = br.spartan_proving_time + br.groth16_proving_time; - + br.testudo_verification_time = dv; writer .serialize(br) .expect("unable to write results to csv"); diff --git a/profiler/nizk.rs b/profiler/nizk.rs index 335e9d7..941124b 100644 --- a/profiler/nizk.rs +++ b/profiler/nizk.rs @@ -9,7 +9,7 @@ extern crate rand; use ark_serialize::*; use libspartan::parameters::poseidon_params; use libspartan::poseidon_transcript::PoseidonTranscript; -use libspartan::{Instance, NIZK}; +use libspartan::{Instance, NIZKGens, NIZK}; fn print(msg: &str) { let star = "* "; @@ -30,12 +30,12 @@ pub fn main() { let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs); // produce public generators - // let gens = NIZKGens::new(num_cons, num_vars, num_inputs); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); let params = poseidon_params(); // produce a proof of satisfiability let mut prover_transcript = PoseidonTranscript::new(¶ms); - let proof = NIZK::prove(&inst, vars, &inputs, &mut prover_transcript); + let proof = NIZK::prove(&inst, vars, &inputs, &gens, &mut prover_transcript); let mut proof_encoded = Vec::new(); proof.serialize(&mut proof_encoded).unwrap(); @@ -45,7 +45,7 @@ pub fn main() { // verify the proof of satisfiability let mut verifier_transcript = PoseidonTranscript::new(¶ms); assert!(proof - .verify(&inst, &inputs, &mut verifier_transcript) + .verify(&inst, &inputs, &mut verifier_transcript, &gens) .is_ok()); println!(); diff --git a/src/constraints.rs b/src/constraints.rs index 4b44a3f..85d1921 100644 --- a/src/constraints.rs +++ b/src/constraints.rs @@ -18,10 +18,15 @@ use ark_groth16::{ Groth16, PreparedVerifyingKey, Proof as GrothProof, }; +use ark_poly_commit::multilinear_pc::{ + data_structures::{Commitment, Proof, VerifierKey}, + MultilinearPC, +}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, fields::fp::FpVar, prelude::{Boolean, EqGadget, FieldVar}, + R1CSVar, }; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; use ark_sponge::{ @@ -248,6 +253,8 @@ pub struct R1CSVerificationCircuit { pub eval_vars_at_ry: Fr, pub sc_phase1: SumcheckVerificationCircuit, pub sc_phase2: SumcheckVerificationCircuit, + // The point on which the polynomial was evaluated by the prover. + pub claimed_ry: Vec, } impl R1CSVerificationCircuit { @@ -268,6 +275,7 @@ impl R1CSVerificationCircuit { sc_phase2: SumcheckVerificationCircuit { polys: config.polys_sc2.clone(), }, + claimed_ry: config.ry.clone(), } } } @@ -294,7 +302,13 @@ impl ConstraintSynthesizer for R1CSVerificationCircuit { let input_vars = self .input .iter() - .map(|i| FpVar::::new_input(cs.clone(), || Ok(i)).unwrap()) + .map(|i| FpVar::::new_variable(cs.clone(), || Ok(i), AllocationMode::Witness).unwrap()) + .collect::>>(); + + let claimed_ry_vars = self + .claimed_ry + .iter() + .map(|r| FpVar::::new_variable(cs.clone(), || Ok(r), AllocationMode::Input).unwrap()) .collect::>>(); transcript_var.append_vector(&input_vars)?; @@ -344,6 +358,17 @@ impl ConstraintSynthesizer for R1CSVerificationCircuit { .sc_phase2 .verifiy_sumcheck(&poly_sc2_vars, &claim_phase2_var, &mut transcript_var)?; + // Because the verifier checks the commitment opening on point ry outside + // the circuit, the prover needs to send ry to the verifier (making the + // proof size O(log n)). As this point is normally obtained by the verifier + // from the second round of sumcheck, the circuit needs to ensure the + // claimed point, coming from the prover, is actually the point derived + // inside the circuit. These additional checks will be removed + // when the commitment verification is done inside the circuit. + for (i, r) in claimed_ry_vars.iter().enumerate() { + ry_var[i].enforce_equal(r)?; + } + let input_as_sparse_poly_var = SparsePolynomialVar::new_variable( cs.clone(), || Ok(&self.input_as_sparse_poly), @@ -366,7 +391,6 @@ impl ConstraintSynthesizer for R1CSVerificationCircuit { let scalar_var = &r_A_var * &eval_A_r_var + &r_B_var * &eval_B_r_var + &r_C_var * &eval_C_r_var; let expected_claim_post_phase2_var = eval_Z_at_ry_var * scalar_var; - claim_post_phase2_var.enforce_equal(&expected_claim_post_phase2_var)?; Ok(()) @@ -386,15 +410,16 @@ pub struct VerifierConfig { pub eval_vars_at_ry: Fr, pub polys_sc1: Vec, pub polys_sc2: Vec, + pub ry: Vec, } #[derive(Clone)] pub struct VerifierCircuit { pub inner_circuit: R1CSVerificationCircuit, pub inner_proof: GrothProof, pub inner_vk: PreparedVerifyingKey, - pub evals_var_at_ry: Fr, + pub eval_vars_at_ry: Fr, pub claims_phase2: (Fr, Fr, Fr, Fr), - pub input: Vec, + pub ry: Vec, } impl VerifierCircuit { @@ -410,9 +435,9 @@ impl VerifierCircuit { inner_circuit, inner_proof: proof, inner_vk: pvk, - evals_var_at_ry: config.eval_vars_at_ry, + eval_vars_at_ry: config.eval_vars_at_ry, claims_phase2: config.claims_phase2, - input: config.input.clone(), + ry: config.ry.clone(), }) } } @@ -421,8 +446,10 @@ impl ConstraintSynthesizer for VerifierCircuit { fn generate_constraints(self, cs: ConstraintSystemRef) -> ark_relations::r1cs::Result<()> { let proof_var = ProofVar::::new_witness(cs.clone(), || Ok(self.inner_proof.clone()))?; let (v_A, v_B, v_C, v_AB) = self.claims_phase2; - let mut pubs = self.input.clone(); - pubs.extend(vec![v_A, v_B, v_C, v_AB, self.evals_var_at_ry]); + let mut pubs = vec![]; + pubs.extend(self.ry); + pubs.extend(vec![v_A, v_B, v_C, v_AB]); + pubs.extend(vec![self.eval_vars_at_ry]); let bits = pubs .iter() .map(|c| { diff --git a/src/dense_mlpoly.rs b/src/dense_mlpoly.rs index 4b05fdd..ae92f80 100644 --- a/src/dense_mlpoly.rs +++ b/src/dense_mlpoly.rs @@ -1,4 +1,5 @@ #![allow(clippy::too_many_arguments)] +use crate::group::Fr; use crate::poseidon_transcript::{AppendToPoseidon, PoseidonTranscript}; use super::commitments::{Commitments, MultiCommitGens}; @@ -12,32 +13,198 @@ use super::nizk::{DotProductProofGens, DotProductProofLog}; use super::random::RandomTape; use super::scalar::Scalar; use super::transcript::{AppendToTranscript, ProofTranscript}; -use ark_ff::{One, Zero}; +use ark_bls12_377::Bls12_377 as I; +use ark_ff::{One, UniformRand, Zero}; +use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; +use ark_poly_commit::multilinear_pc::data_structures::{ + CommitterKey, UniversalParams, VerifierKey, +}; +use ark_poly_commit::multilinear_pc::MultilinearPC; use ark_serialize::*; use core::ops::Index; use merlin::Transcript; +use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; +use std::process::abort; #[cfg(feature = "multicore")] use rayon::prelude::*; -#[derive(Debug)] +// TODO: integrate the DenseMultilinearExtension(and Sparse) https://github.com/arkworks-rs/algebra/tree/master/poly/src/evaluations/multivariate/multilinear from arkworks into Spartan. This requires moving the specific Spartan functionalities in separate traits. +#[derive(Debug, Clone, Eq, PartialEq, Hash, CanonicalDeserialize, CanonicalSerialize)] pub struct DensePolynomial { num_vars: usize, // the number of variables in the multilinear polynomial len: usize, Z: Vec, // evaluations of the polynomial in all the 2^num_vars Boolean inputs } +impl MultilinearExtension for DensePolynomial { + fn num_vars(&self) -> usize { + self.get_num_vars() + } + + fn evaluate(&self, point: &[Scalar]) -> Option { + if point.len() == self.num_vars { + Some(self.evaluate(&point)) + } else { + None + } + } + + fn rand(num_vars: usize, rng: &mut R) -> Self { + let evals = (0..(1 << num_vars)).map(|_| Scalar::rand(rng)).collect(); + Self { + num_vars: num_vars, + len: 1 << num_vars, + Z: evals, + } + } + + fn relabel(&self, a: usize, b: usize, k: usize) -> Self { + unimplemented!() + } + + fn fix_variables(&self, partial_point: &[Scalar]) -> Self { + unimplemented!() + } + + fn to_evaluations(&self) -> Vec { + self.Z.to_vec() + } +} + +impl Zero for DensePolynomial { + fn zero() -> Self { + Self { + num_vars: 0, + len: 1, + Z: vec![Scalar::zero()], + } + } + + fn is_zero(&self) -> bool { + self.num_vars == 0 && self.len == 1 && self.Z[0].is_zero() + } +} + +impl Add for DensePolynomial { + type Output = DensePolynomial; + fn add(self, other: Self) -> Self { + &self + &other + } +} + +// function needed because the result might have a different lifetime than the +// operands +impl<'a, 'b> Add<&'a DensePolynomial> for &'b DensePolynomial { + type Output = DensePolynomial; + + fn add(self, other: &'a DensePolynomial) -> Self::Output { + if other.is_zero() { + return self.clone(); + } + if self.is_zero() { + return other.clone(); + } + assert_eq!(self.num_vars, other.num_vars); + + let res: Vec = self + .Z + .iter() + .zip(other.Z.iter()) + .map(|(a, b)| *a + *b) + .collect(); + Self::Output { + num_vars: self.num_vars, + len: self.len, + Z: res, + } + } +} + +impl AddAssign for DensePolynomial { + fn add_assign(&mut self, other: Self) { + *self = &*self + &other; + } +} + +impl<'a, 'b> AddAssign<&'a DensePolynomial> for DensePolynomial { + fn add_assign(&mut self, other: &'a DensePolynomial) { + *self = &*self + other; + } +} + +impl<'a, 'b> AddAssign<(Scalar, &'a DensePolynomial)> for DensePolynomial { + fn add_assign(&mut self, (scalar, other): (Scalar, &'a DensePolynomial)) { + let other = Self { + num_vars: other.num_vars, + len: 1 << other.num_vars, + Z: other.Z.iter().map(|x| scalar * x).collect(), + }; + *self = &*self + &other; + } +} + +impl Neg for DensePolynomial { + type Output = DensePolynomial; + + fn neg(self) -> Self::Output { + Self::Output { + num_vars: self.num_vars, + len: self.len, + Z: self.Z.iter().map(|x| -*x).collect(), + } + } +} + +impl Sub for DensePolynomial { + type Output = DensePolynomial; + + fn sub(self, other: Self) -> Self::Output { + &self - &other + } +} + +impl<'a, 'b> Sub<&'a DensePolynomial> for &'b DensePolynomial { + type Output = DensePolynomial; + + fn sub(self, other: &'a DensePolynomial) -> Self::Output { + self + &other.clone().neg() + } +} + +impl SubAssign for DensePolynomial { + fn sub_assign(&mut self, other: Self) { + *self = &*self - &other; + } +} + +impl<'a, 'b> SubAssign<&'a DensePolynomial> for DensePolynomial { + fn sub_assign(&mut self, other: &'a DensePolynomial) { + *self = &*self - other; + } +} + #[derive(Clone)] pub struct PolyCommitmentGens { pub gens: DotProductProofGens, + pub ck: CommitterKey, + pub vk: VerifierKey, } impl PolyCommitmentGens { - // the number of variables in the multilinear polynomial + // num vars is the number of variables in the multilinear polynomial + // this gives the maximum degree bound pub fn new(num_vars: usize, label: &'static [u8]) -> PolyCommitmentGens { let (_left, right) = EqPolynomial::compute_factored_lens(num_vars); let gens = DotProductProofGens::new(right.pow2(), label); - PolyCommitmentGens { gens } + + // Generates the SRS and trims it based on the number of variables in the + // multilinear polynomial. + let mut rng = ark_std::test_rng(); + let pst_gens = MultilinearPC::::setup(num_vars, &mut rng); + let (ck, vk) = MultilinearPC::::trim(&pst_gens, num_vars); + + PolyCommitmentGens { gens, ck, vk } } } @@ -79,6 +246,9 @@ impl EqPolynomial { for j in 0..ell { // in each iteration, we double the size of chis size *= 2; + // TODO: this reverse causes inconsistent evaluation in comparison to the + //evaluation function in ark-poly-commit, we should look into this to + // avoid the extra constraints in the circuit for i in (0..size).rev().step_by(2) { // copy each element from the prior iteration twice let scalar = evals[i / 2]; diff --git a/src/lib.rs b/src/lib.rs index 58776d0..9e5286f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -328,6 +328,8 @@ pub struct SNARK { r1cs_sat_proof: R1CSProof, inst_evals: (Scalar, Scalar, Scalar), r1cs_eval_proof: R1CSEvalProof, + rx: Vec, + ry: Vec, } impl SNARK { @@ -385,7 +387,7 @@ impl SNARK { &inst.inst, padded_vars.assignment, &inputs.assignment, - // &gens.gens_r1cs_sat, + &gens.gens_r1cs_sat, transcript, // &mut random_tape, ) @@ -432,6 +434,8 @@ impl SNARK { r1cs_sat_proof, inst_evals, r1cs_eval_proof, + rx, + ry, } } @@ -441,9 +445,9 @@ impl SNARK { comm: &ComputationCommitment, input: &InputsAssignment, transcript: &mut PoseidonTranscript, - _gens: &SNARKGens, - ) -> Result<(), ProofVerifyError> { - let _timer_verify = Timer::new("SNARK::verify"); + gens: &SNARKGens, + ) -> Result<(u128, u128, u128), ProofVerifyError> { + let timer_verify = Timer::new("SNARK::verify"); // transcript.append_protocol_name(SNARK::protocol_name()); // append a commitment to the computation to the transcript @@ -452,35 +456,36 @@ impl SNARK { let timer_sat_proof = Timer::new("verify_sat_proof"); assert_eq!(input.assignment.len(), comm.comm.get_num_inputs()); // let (rx, ry) = - self.r1cs_sat_proof.circuit_size( + let res = self.r1cs_sat_proof.verify_groth16( comm.comm.get_num_vars(), comm.comm.get_num_cons(), &input.assignment, &self.inst_evals, transcript, - // &gens.gens_r1cs_sat, + &gens.gens_r1cs_sat, )?; timer_sat_proof.stop(); // let timer_eval_proof = Timer::new("verify_eval_proof"); - // let (Ar, Br, Cr) = &self.inst_evals; - // // Ar.append_to_transcript(b"Ar_claim", transcript); - // // Br.append_to_transcript(b"Br_claim", transcript); - // // Cr.append_to_transcript(b"Cr_claim", transcript); - // transcript.append_scalar(&Ar); - // transcript.append_scalar(&Br); - // transcript.append_scalar(&Cr); + + let (Ar, Br, Cr) = &self.inst_evals; + transcript.append_scalar(&Ar); + transcript.append_scalar(&Br); + transcript.append_scalar(&Cr); + + // TODO: debug this + // https://github.com/maramihali/Spartan/issues/6 // self.r1cs_eval_proof.verify( // &comm.comm, - // &rx, - // &ry, + // &self.rx, + // &self.ry, // &self.inst_evals, // &gens.gens_r1cs_eval, // transcript, // )?; // timer_eval_proof.stop(); - // timer_verify.stop(); - Ok(()) + timer_verify.stop(); + Ok(res) } } @@ -523,7 +528,7 @@ impl NIZK { inst: &Instance, vars: VarsAssignment, input: &InputsAssignment, - // gens: &NIZKGens, + gens: &NIZKGens, transcript: &mut PoseidonTranscript, ) -> Self { let timer_prove = Timer::new("NIZK::prove"); @@ -550,7 +555,7 @@ impl NIZK { &inst.inst, padded_vars.assignment, &input.assignment, - // &gens.gens_r1cs_sat, + &gens.gens_r1cs_sat, transcript, // &mut random_tape, ); @@ -573,7 +578,7 @@ impl NIZK { inst: &Instance, input: &InputsAssignment, transcript: &mut PoseidonTranscript, - // gens: &NIZKGens, + gens: &NIZKGens, ) -> Result { let timer_verify = Timer::new("NIZK::verify"); @@ -595,7 +600,7 @@ impl NIZK { &input.assignment, &inst_evals, transcript, - // &gens.gens_r1cs_sat, + &gens.gens_r1cs_sat, )?; // verify if claimed rx and ry are correct @@ -613,6 +618,7 @@ impl NIZK { inst: &Instance, input: &InputsAssignment, transcript: &mut PoseidonTranscript, + gens: &NIZKGens, ) -> Result<(u128, u128, u128), ProofVerifyError> { let timer_verify = Timer::new("NIZK::verify"); @@ -635,7 +641,7 @@ impl NIZK { &input.assignment, &inst_evals, transcript, - // &gens.gens_r1cs_sat, + &gens.gens_r1cs_sat, )?; // verify if claimed rx and ry are correct @@ -805,7 +811,7 @@ mod tests { .is_ok()); // NIZK public params - let _gens = NIZKGens::new(num_cons, num_vars, num_inputs); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); let params = poseidon_params(); @@ -815,14 +821,14 @@ mod tests { &inst, assignment_vars, &assignment_inputs, - // &gens, + &gens, &mut prover_transcript, ); // verify the NIZK let mut verifier_transcript = PoseidonTranscript::new(¶ms); assert!(proof - .verify(&inst, &assignment_inputs, &mut verifier_transcript) + .verify_groth16(&inst, &assignment_inputs, &mut verifier_transcript, &gens) .is_ok()); } } diff --git a/src/poseidon_transcript.rs b/src/poseidon_transcript.rs index 3ada903..577e2de 100644 --- a/src/poseidon_transcript.rs +++ b/src/poseidon_transcript.rs @@ -1,7 +1,9 @@ use crate::group::{CompressedGroup, Fr}; use super::scalar::Scalar; - +use ark_bls12_377::Bls12_377 as I; +use ark_poly_commit::multilinear_pc::data_structures::Commitment; +use ark_serialize::CanonicalSerialize; // use ark_r1cs_std::prelude::*; use ark_sponge::{ poseidon::{PoseidonParameters, PoseidonSponge}, @@ -70,3 +72,11 @@ impl AppendToPoseidon for CompressedGroup { transcript.append_point(self); } } + +impl AppendToPoseidon for Commitment { + fn append_to_poseidon(&self, transcript: &mut PoseidonTranscript) { + let mut bytes = Vec::new(); + self.serialize(&mut bytes).unwrap(); + transcript.append_bytes(&bytes); + } +} diff --git a/src/r1csproof.rs b/src/r1csproof.rs index 02d3645..6282550 100644 --- a/src/r1csproof.rs +++ b/src/r1csproof.rs @@ -3,10 +3,13 @@ use crate::constraints::{VerifierCircuit, VerifierConfig}; use crate::group::{Fq, Fr}; use crate::math::Math; use crate::parameters::poseidon_params; -use crate::poseidon_transcript::PoseidonTranscript; +use crate::poseidon_transcript::{AppendToPoseidon, PoseidonTranscript}; use crate::sumcheck::SumcheckInstanceProof; - +use ark_bls12_377::Bls12_377 as I; use ark_bw6_761::BW6_761 as P; +use ark_poly::MultilinearExtension; +use ark_poly_commit::multilinear_pc::data_structures::{Commitment, Proof}; +use ark_poly_commit::multilinear_pc::MultilinearPC; use super::commitments::MultiCommitGens; use super::dense_mlpoly::{DensePolynomial, EqPolynomial, PolyCommitmentGens}; @@ -28,14 +31,15 @@ use std::time::Instant; #[derive(CanonicalSerialize, CanonicalDeserialize, Debug)] pub struct R1CSProof { + // The PST commitment to the multilinear extension of the witness. + comm: Commitment, sc_proof_phase1: SumcheckInstanceProof, claims_phase2: (Scalar, Scalar, Scalar, Scalar), - // pok_claims_phase2: (KnowledgeProof, ProductProof), - // proof_eq_sc_phase1: EqualityProof, sc_proof_phase2: SumcheckInstanceProof, eval_vars_at_ry: Scalar, - // proof_eval_vars_at_ry: PolyEvalProof, - // proof_eq_sc_phase2: EqualityProof, + proof_eval_vars_at_ry: Proof, + rx: Vec, + ry: Vec, } #[derive(Clone)] pub struct R1CSSumcheckGens { @@ -128,19 +132,27 @@ impl R1CSProof { inst: &R1CSInstance, vars: Vec, input: &[Scalar], + gens: &R1CSGens, transcript: &mut PoseidonTranscript, ) -> (R1CSProof, Vec, Vec) { let timer_prove = Timer::new("R1CSProof::prove"); // we currently require the number of |inputs| + 1 to be at most number of vars assert!(input.len() < vars.len()); + // create the multilinear witness polynomial from the satisfying assiment + let poly_vars = DensePolynomial::new(vars.clone()); + + let timer_commit = Timer::new("polycommit"); + // commitment to the satisfying witness polynomial + let comm = MultilinearPC::::commit(&gens.gens_pc.ck, &poly_vars); + comm.append_to_poseidon(transcript); + timer_commit.stop(); + let c = transcript.challenge_scalar(); transcript.new_from_state(&c); transcript.append_scalar_vector(input); - let poly_vars = DensePolynomial::new(vars.clone()); - let timer_sc_proof_phase1 = Timer::new("prove_sc_phase_one"); // append input to variables to create a single vector z @@ -214,6 +226,19 @@ impl R1CSProof { ); timer_sc_proof_phase2.stop(); + // TODO: modify the polynomial evaluation in Spartan to be consistent + // with the evaluation in ark-poly-commit so that reversing is not needed + // anymore + let timmer_opening = Timer::new("polyopening"); + let mut dummy = ry[1..].to_vec().clone(); + dummy.reverse(); + let proof_eval_vars_at_ry = MultilinearPC::::open(&gens.gens_pc.ck, &poly_vars, &dummy); + println!( + "proof size (no of quotients): {:?}", + proof_eval_vars_at_ry.proofs.len() + ); + timmer_opening.stop(); + let timer_polyeval = Timer::new("polyeval"); let eval_vars_at_ry = poly_vars.evaluate(&ry[1..]); timer_polyeval.stop(); @@ -222,10 +247,14 @@ impl R1CSProof { ( R1CSProof { + comm, sc_proof_phase1, claims_phase2: (*Az_claim, *Bz_claim, *Cz_claim, prod_Az_Bz_claims), sc_proof_phase2, eval_vars_at_ry, + proof_eval_vars_at_ry, + rx: rx.clone(), + ry: ry.clone(), }, rx, ry, @@ -239,7 +268,10 @@ impl R1CSProof { input: &[Scalar], evals: &(Scalar, Scalar, Scalar), transcript: &mut PoseidonTranscript, + gens: &R1CSGens, ) -> Result<(u128, u128, u128), ProofVerifyError> { + self.comm.append_to_poseidon(transcript); + let c = transcript.challenge_scalar(); let mut input_as_sparse_poly_entries = vec![SparsePolyEntry::new(0, Scalar::one())]; @@ -266,30 +298,58 @@ impl R1CSProof { polys_sc2: self.sc_proof_phase2.polys.clone(), eval_vars_at_ry: self.eval_vars_at_ry, input_as_sparse_poly, + // rx: self.rx.clone(), + ry: self.ry.clone(), }; let mut rng = ark_std::test_rng(); + let prove_inner = Timer::new("proveinnercircuit"); let start = Instant::now(); let circuit = VerifierCircuit::new(&config, &mut rng).unwrap(); let dp1 = start.elapsed().as_millis(); + prove_inner.stop(); let start = Instant::now(); let (pk, vk) = Groth16::

::setup(circuit.clone(), &mut rng).unwrap(); let ds = start.elapsed().as_millis(); + let prove_outer = Timer::new("proveoutercircuit"); let start = Instant::now(); let proof = Groth16::

::prove(&pk, circuit, &mut rng).unwrap(); let dp2 = start.elapsed().as_millis(); + prove_outer.stop(); let start = Instant::now(); let is_verified = Groth16::

::verify(&vk, &[], &proof).unwrap(); - let dv = start.elapsed().as_millis(); assert!(is_verified); + let timer_verification = Timer::new("commitverification"); + let mut dummy = self.ry[1..].to_vec(); + // TODO: ensure ark-poly-commit and Spartan produce consistent results + // when evaluating a polynomial at a given point so this reverse is not + // needed. + dummy.reverse(); + + // Verifies the proof of opening against the result of evaluating the + // witness polynomial at point ry. + let res = MultilinearPC::::check( + &gens.gens_pc.vk, + &self.comm, + &dummy, + self.eval_vars_at_ry, + &self.proof_eval_vars_at_ry, + ); + + timer_verification.stop(); + assert!(res == true); + let dv = start.elapsed().as_millis(); + Ok((ds, dp1 + dp2, dv)) } + // Helper function to find the number of constraint in the circuit which + // requires executing it. pub fn circuit_size( &self, num_vars: usize, @@ -297,7 +357,10 @@ impl R1CSProof { input: &[Scalar], evals: &(Scalar, Scalar, Scalar), transcript: &mut PoseidonTranscript, + gens: &R1CSGens, ) -> Result { + self.comm.append_to_poseidon(transcript); + let c = transcript.challenge_scalar(); let mut input_as_sparse_poly_entries = vec![SparsePolyEntry::new(0, Scalar::one())]; @@ -324,6 +387,8 @@ impl R1CSProof { polys_sc2: self.sc_proof_phase2.polys.clone(), eval_vars_at_ry: self.eval_vars_at_ry, input_as_sparse_poly, + // rx: self.rx.clone(), + ry: self.ry.clone(), }; let mut rng = ark_std::test_rng(); @@ -439,13 +504,13 @@ mod tests { let num_inputs = 10; let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs); - // let gens = R1CSGens::new(b"test-m", num_cons, num_vars); + let gens = R1CSGens::new(b"test-m", num_cons, num_vars); let params = poseidon_params(); // let mut random_tape = RandomTape::new(b"proof"); let mut prover_transcript = PoseidonTranscript::new(¶ms); - let (proof, rx, ry) = R1CSProof::prove(&inst, vars, &input, &mut prover_transcript); + let (proof, rx, ry) = R1CSProof::prove(&inst, vars, &input, &gens, &mut prover_transcript); let inst_evals = inst.evaluate(&rx, &ry); @@ -461,6 +526,7 @@ mod tests { &input, &inst_evals, &mut verifier_transcript, + &gens, ) .is_ok()); }