diff --git a/.cargo/config b/.cargo/config index 3a420e91..8b137891 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,4 +1 @@ -[build] -rustflags = [ - "-C", "target-cpu=native", -] \ No newline at end of file + diff --git a/.github/workflows/testudo.yml b/.github/workflows/testudo.yml index 781bd986..bce620a4 100644 --- a/.github/workflows/testudo.yml +++ b/.github/workflows/testudo.yml @@ -26,7 +26,7 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose + run: cargo test --release --all-features --verbose - name: Build examples run: cargo build --examples --verbose - name: Check Rustfmt Code Style diff --git a/README.md b/README.md index 0eed4a7e..b194ce2b 100644 --- a/README.md +++ b/README.md @@ -1,421 +1,3 @@ -# Spartan: High-speed zkSNARKs without trusted setup +# Testudo: Spartan + Groth16 -![Rust](https://github.com/microsoft/Spartan/workflows/Rust/badge.svg) -[![](https://img.shields.io/crates/v/spartan.svg)](<(https://crates.io/crates/spartan)>) - -Spartan is a high-speed zero-knowledge proof system, a cryptographic primitive that enables a prover to prove a mathematical statement to a verifier without revealing anything besides the validity of the statement. This repository provides `libspartan,` a Rust library that implements a zero-knowledge succinct non-interactive argument of knowledge (zkSNARK), which is a type of zero-knowledge proof system with short proofs and fast verification times. The details of the Spartan proof system are described in our [paper](https://eprint.iacr.org/2019/550) published at [CRYPTO 2020](https://crypto.iacr.org/2020/). The security of the Spartan variant implemented in this library is based on the discrete logarithm problem in the random oracle model. - -A simple example application is proving the knowledge of a secret s such that H(s) == d for a public d, where H is a cryptographic hash function (e.g., SHA-256, Keccak). A more complex application is a database-backed cloud service that produces proofs of correct state machine transitions for auditability. See this [paper](https://eprint.iacr.org/2020/758.pdf) for an overview and this [paper](https://eprint.iacr.org/2018/907.pdf) for details. - -Note that this library has _not_ received a security review or audit. - -## Highlights - -We now highlight Spartan's distinctive features. - -- **No "toxic" waste:** Spartan is a _transparent_ zkSNARK and does not require a trusted setup. So, it does not involve any trapdoors that must be kept secret or require a multi-party ceremony to produce public parameters. - -- **General-purpose:** Spartan produces proofs for arbitrary NP statements. `libspartan` supports NP statements expressed as rank-1 constraint satisfiability (R1CS) instances, a popular language for which there exists efficient transformations and compiler toolchains from high-level programs of interest. - -- **Sub-linear verification costs:** Spartan is the first transparent proof system with sub-linear verification costs for arbitrary NP statements (e.g., R1CS). - -- **Standardized security:** Spartan's security relies on the hardness of computing discrete logarithms (a standard cryptographic assumption) in the random oracle model. `libspartan` uses `ristretto255`, a prime-order group abstraction atop `curve25519` (a high-speed elliptic curve). We use [`curve25519-dalek`](https://docs.rs/curve25519-dalek) for arithmetic over `ristretto255`. - -- **State-of-the-art performance:** - Among transparent SNARKs, Spartan offers the fastest prover with speedups of 36–152× depending on the baseline, produces proofs that are shorter by 1.2–416×, and incurs the lowest verification times with speedups of 3.6–1326×. The only exception is proof sizes under Bulletproofs, but Bulletproofs incurs slower verification both asymptotically and concretely. When compared to the state-of-the-art zkSNARK with trusted setup, Spartan’s prover is 2× faster for arbitrary R1CS instances and 16× faster for data-parallel workloads. - -### Implementation details - -`libspartan` uses [`merlin`](https://docs.rs/merlin/) to automate the Fiat-Shamir transform. We also introduce a new type called `RandomTape` that extends a `Transcript` in `merlin` to allow the prover's internal methods to produce private randomness using its private transcript without having to create `OsRng` objects throughout the code. An object of type `RandomTape` is initialized with a new random seed from `OsRng` for each proof produced by the library. - -## Examples - -To import `libspartan` into your Rust project, add the following dependency to `Cargo.toml`: - -```text -spartan = "0.7.1" -``` - -The following example shows how to use `libspartan` to create and verify a SNARK proof. -Some of our public APIs' style is inspired by the underlying crates we use. - -```rust -# extern crate libspartan; -# extern crate merlin; -# use libspartan::{Instance, SNARKGens, SNARK}; -# use libspartan::poseidon_transcript::PoseidonTranscript; -# use libspartan::parameters::poseidon_params; -# fn main() { - // specify the size of an R1CS instance - let num_vars = 1024; - let num_cons = 1024; - let num_inputs = 10; - let num_non_zero_entries = 1024; - - // produce public parameters - let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_non_zero_entries); - - // ask the library to produce a synthentic R1CS instance - let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs); - - // create a commitment to the R1CS instance - let (comm, decomm) = SNARK::encode(&inst, &gens); - - let params = poseidon_params(); - - // produce a proof of satisfiability - let mut prover_transcript = PoseidonTranscript::new(¶ms); - let proof = SNARK::prove(&inst, &comm, &decomm, vars, &inputs, &gens, &mut prover_transcript); - - // verify the proof of satisfiability - let mut verifier_transcript = PoseidonTranscript::new(¶ms); - assert!(proof - .verify(&comm, &inputs, &mut verifier_transcript, &gens) - .is_ok()); - println!("proof verification successful!"); -# } -``` - -Here is another example to use the NIZK variant of the Spartan proof system: - -```rust -# extern crate libspartan; -# extern crate merlin; -# use libspartan::{Instance, NIZKGens, NIZK}; -# use libspartan::poseidon_transcript::PoseidonTranscript; -# use libspartan::parameters::poseidon_params; -# fn main() { - // specify the size of an R1CS instance - let num_vars = 1024; - let num_cons = 1024; - let num_inputs = 10; - - // produce public parameters - let gens = NIZKGens::new(num_cons, num_vars, num_inputs); - - // ask the library to produce a synthentic R1CS instance - let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(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, &gens, &mut prover_transcript); - - // verify the proof of satisfiability - let mut verifier_transcript = PoseidonTranscript::new(¶ms); - assert!(proof - .verify(&inst, &inputs, &mut verifier_transcript, &gens) - .is_ok()); - println!("proof verification successful!"); -# } -``` - -Finally, we provide an example that specifies a custom R1CS instance instead of using a synthetic instance - -```rust -#![allow(non_snake_case)] -# extern crate ark_std; -# extern crate libspartan; -# extern crate merlin; -# mod scalar; -# use scalar::Scalar; -# use libspartan::parameters::poseidon_params; -# use libspartan::{InputsAssignment, Instance, SNARKGens, VarsAssignment, SNARK}; -# use libspartan::poseidon_transcript::{AppendToPoseidon, PoseidonTranscript}; -# -# use ark_ff::{PrimeField, Field, BigInteger}; -# use ark_std::{One, Zero, UniformRand}; -# fn main() { - // produce a tiny instance - let ( - num_cons, - num_vars, - num_inputs, - num_non_zero_entries, - inst, - assignment_vars, - assignment_inputs, - ) = produce_tiny_r1cs(); - - // produce public parameters - let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_non_zero_entries); - - // create a commitment to the R1CS instance - let (comm, decomm) = SNARK::encode(&inst, &gens); - let params = poseidon_params(); - - // produce a proof of satisfiability - let mut prover_transcript = PoseidonTranscript::new(¶ms); - let proof = SNARK::prove( - &inst, - &comm, - &decomm, - assignment_vars, - &assignment_inputs, - &gens, - &mut prover_transcript, - ); - - // verify the proof of satisfiability - let mut verifier_transcript = PoseidonTranscript::new(¶ms); - assert!(proof - .verify(&comm, &assignment_inputs, &mut verifier_transcript, &gens) - .is_ok()); - println!("proof verification successful!"); -# } - -# fn produce_tiny_r1cs() -> ( -# usize, -# usize, -# usize, -# usize, -# Instance, -# VarsAssignment, -# InputsAssignment, -# ) { - // We will use the following example, but one could construct any R1CS instance. - // Our R1CS instance is three constraints over five variables and two public inputs - // (Z0 + Z1) * I0 - Z2 = 0 - // (Z0 + I1) * Z2 - Z3 = 0 - // Z4 * 1 - 0 = 0 - - // parameters of the R1CS instance rounded to the nearest power of two - let num_cons = 4; - let num_vars = 5; - let num_inputs = 2; - let num_non_zero_entries = 5; - - // We will encode the above constraints into three matrices, where - // the coefficients in the matrix are in the little-endian byte order - let mut A: Vec<(usize, usize, Vec)> = Vec::new(); - let mut B: Vec<(usize, usize, Vec)> = Vec::new(); - let mut C: Vec<(usize, usize, Vec)> = Vec::new(); - - // The constraint system is defined over a finite field, which in our case is - // the scalar field of ristreeto255/curve25519 i.e., p = 2^{252}+27742317777372353535851937790883648493 - // To construct these matrices, we will use `curve25519-dalek` but one can use any other method. - - // a variable that holds a byte representation of 1 - let one = Scalar::one().into_repr().to_bytes_le(); - - // R1CS is a set of three sparse matrices A B C, where is a row for every - // constraint and a column for every entry in z = (vars, 1, inputs) - // An R1CS instance is satisfiable iff: - // Az \circ Bz = Cz, where z = (vars, 1, inputs) - - // constraint 0 entries in (A,B,C) - // constraint 0 is (Z0 + Z1) * I0 - Z2 = 0. - // We set 1 in matrix A for columns that correspond to Z0 and Z1 - // We set 1 in matrix B for column that corresponds to I0 - // We set 1 in matrix C for column that corresponds to Z2 - A.push((0, 0, one.clone())); - A.push((0, 1, one.clone())); - B.push((0, num_vars + 1, one.clone())); - C.push((0, 2, one.clone())); - - // constraint 1 entries in (A,B,C) - A.push((1, 0, one.clone())); - A.push((1, num_vars + 2, one.clone())); - B.push((1, 2, one.clone())); - C.push((1, 3, one.clone())); - - // constraint 3 entries in (A,B,C) - A.push((2, 4, one.clone())); - B.push((2, num_vars, one.clone())); - - let inst = Instance::new(num_cons, num_vars, num_inputs, &A, &B, &C).unwrap(); - - // compute a satisfying assignment -let mut rng = ark_std::rand::thread_rng(); - let i0 = Scalar::rand(&mut rng); - let i1 = Scalar::rand(&mut rng); - let z0 = Scalar::rand(&mut rng); - let z1 = Scalar::rand(&mut rng); - let z2 = (z0 + z1) * i0; // constraint 0 - let z3 = (z0 + i1) * z2; // constraint 1 - let z4 = Scalar::zero(); //constraint 2 - - // create a VarsAssignment - let mut vars = vec![Scalar::zero().into_repr().to_bytes_le(); num_vars]; - vars[0] = z0.into_repr().to_bytes_le(); - vars[1] = z1.into_repr().to_bytes_le(); - vars[2] = z2.into_repr().to_bytes_le(); - vars[3] = z3.into_repr().to_bytes_le(); - vars[4] = z4.into_repr().to_bytes_le(); - let assignment_vars = VarsAssignment::new(&vars).unwrap(); - - // create an InputsAssignment - let mut inputs = vec![Scalar::zero().into_repr().to_bytes_le(); num_inputs]; - inputs[0] = i0.into_repr().to_bytes_le(); - inputs[1] = i1.into_repr().to_bytes_le(); - let assignment_inputs = InputsAssignment::new(&inputs).unwrap(); - - // check if the instance we created is satisfiable - let res = inst.is_sat(&assignment_vars, &assignment_inputs); - assert_eq!(res.unwrap(), true); - - ( - num_cons, - num_vars, - num_inputs, - num_non_zero_entries, - inst, - assignment_vars, - assignment_inputs, - ) -# } -``` - -For more examples, see [`examples/`](examples) directory in this repo. - -## Building `libspartan` - -Install [`rustup`](https://rustup.rs/) - -Switch to nightly Rust using `rustup`: - -```text -rustup default nightly -``` - -Clone the repository: - -```text -git clone https://github.com/Microsoft/Spartan -cd Spartan -``` - -To build docs for public APIs of `libspartan`: - -```text -cargo doc -``` - -To run tests: - -```text -RUSTFLAGS="-C target_cpu=native" cargo test -``` - -To build `libspartan`: - -```text -RUSTFLAGS="-C target_cpu=native" cargo build --release -``` - -> NOTE: We enable SIMD instructions in `curve25519-dalek` by default, so if it fails to build remove the "simd_backend" feature argument in `Cargo.toml`. - -### Supported features - -- `profile`: enables fine-grained profiling information (see below for its use) - -## Performance - -### End-to-end benchmarks - -`libspartan` includes two benches: `benches/nizk.rs` and `benches/snark.rs`. If you report the performance of Spartan in a research paper, we recommend using these benches for higher accuracy instead of fine-grained profiling (listed below). - -To run end-to-end benchmarks: - -```text -RUSTFLAGS="-C target_cpu=native" cargo bench -``` - -### Fine-grained profiling - -Build `libspartan` with `profile` feature enabled. It creates two profilers: `./target/release/snark` and `./target/release/nizk`. - -These profilers report performance as depicted below (for varying R1CS instance sizes). The reported -performance is from running the profilers on a Microsoft Surface Laptop 3 on a single CPU core of Intel Core i7-1065G7 running Ubuntu 20.04 (atop WSL2 on Windows 10). -See Section 9 in our [paper](https://eprint.iacr.org/2019/550) to see how this compares with other zkSNARKs in the literature. - -```text -$ ./target/release/snark -Profiler:: SNARK - * number_of_constraints 1048576 - * number_of_variables 1048576 - * number_of_inputs 10 - * number_non-zero_entries_A 1048576 - * number_non-zero_entries_B 1048576 - * number_non-zero_entries_C 1048576 - * SNARK::encode - * SNARK::encode 14.2644201s - * SNARK::prove - * R1CSProof::prove - * polycommit - * polycommit 2.7175848s - * prove_sc_phase_one - * prove_sc_phase_one 683.7481ms - * prove_sc_phase_two - * prove_sc_phase_two 846.1056ms - * polyeval - * polyeval 193.4216ms - * R1CSProof::prove 4.4416193s - * len_r1cs_sat_proof 47024 - * eval_sparse_polys - * eval_sparse_polys 377.357ms - * R1CSEvalProof::prove - * commit_nondet_witness - * commit_nondet_witness 14.4507331s - * build_layered_network - * build_layered_network 3.4360521s - * evalproof_layered_network - * len_product_layer_proof 64712 - * evalproof_layered_network 15.5708066s - * R1CSEvalProof::prove 34.2930559s - * len_r1cs_eval_proof 133720 - * SNARK::prove 39.1297568s - * SNARK::proof_compressed_len 141768 - * SNARK::verify - * verify_sat_proof - * verify_sat_proof 20.0828ms - * verify_eval_proof - * verify_polyeval_proof - * verify_prod_proof - * verify_prod_proof 1.1847ms - * verify_hash_proof - * verify_hash_proof 81.06ms - * verify_polyeval_proof 82.3583ms - * verify_eval_proof 82.8937ms - * SNARK::verify 103.0536ms -``` - -```text -$ ./target/release/nizk -Profiler:: NIZK - * number_of_constraints 1048576 - * number_of_variables 1048576 - * number_of_inputs 10 - * number_non-zero_entries_A 1048576 - * number_non-zero_entries_B 1048576 - * number_non-zero_entries_C 1048576 - * NIZK::prove - * R1CSProof::prove - * polycommit - * polycommit 2.7220635s - * prove_sc_phase_one - * prove_sc_phase_one 722.5487ms - * prove_sc_phase_two - * prove_sc_phase_two 862.6796ms - * polyeval - * polyeval 190.2233ms - * R1CSProof::prove 4.4982305s - * len_r1cs_sat_proof 47024 - * NIZK::prove 4.5139888s - * NIZK::proof_compressed_len 48134 - * NIZK::verify - * eval_sparse_polys - * eval_sparse_polys 395.0847ms - * verify_sat_proof - * verify_sat_proof 19.286ms - * NIZK::verify 414.5102ms -``` - -## LICENSE - -See [LICENSE](./LICENSE) - -## Contributing - -See [CONTRIBUTING](./CONTRIBUTING.md) +TODO documentation diff --git a/src/constraints.rs b/src/constraints.rs index edd54533..8ffb6fb3 100644 --- a/src/constraints.rs +++ b/src/constraints.rs @@ -7,7 +7,9 @@ use crate::{ sparse_mlpoly::{SparsePolyEntry, SparsePolynomial}, unipoly::UniPoly, }; + use ark_bls12_377::{constraints::PairingVar as IV, Bls12_377 as I, Fr}; + use ark_crypto_primitives::{ snark::BooleanInputVar, CircuitSpecificSetupSNARK, SNARKGadget, SNARK, }; @@ -18,10 +20,7 @@ use ark_groth16::{ Groth16, PreparedVerifyingKey, Proof as GrothProof, }; -use ark_poly_commit::multilinear_pc::{ - data_structures::{Commitment, Proof, VerifierKey}, - MultilinearPC, -}; +use ark_poly_commit::multilinear_pc::data_structures::{Commitment, Proof, VerifierKey}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, fields::fp::FpVar, @@ -409,6 +408,7 @@ impl ConstraintSynthesizer for R1CSVerificationCircuit { #[derive(Clone)] pub struct VerifierConfig { + pub comm: Commitment, pub num_vars: usize, pub num_cons: usize, pub input: Vec, diff --git a/src/dense_mlpoly.rs b/src/dense_mlpoly.rs index ae92f800..47517ebf 100644 --- a/src/dense_mlpoly.rs +++ b/src/dense_mlpoly.rs @@ -1,6 +1,7 @@ #![allow(clippy::too_many_arguments)] use crate::group::Fr; use crate::poseidon_transcript::{AppendToPoseidon, PoseidonTranscript}; +use crate::timer::Timer; use super::commitments::{Commitments, MultiCommitGens}; use super::errors::ProofVerifyError; @@ -13,18 +14,19 @@ use super::nizk::{DotProductProofGens, DotProductProofLog}; use super::random::RandomTape; use super::scalar::Scalar; use super::transcript::{AppendToTranscript, ProofTranscript}; -use ark_bls12_377::Bls12_377 as I; -use ark_ff::{One, UniformRand, Zero}; +use ark_bls12_377::{Bls12_377 as I, G1Affine}; +use ark_ec::msm::VariableBaseMSM; +use ark_ec::{PairingEngine, ProjectiveCurve}; +use ark_ff::{One, PrimeField, UniformRand, Zero}; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_poly_commit::multilinear_pc::data_structures::{ - CommitterKey, UniversalParams, VerifierKey, + Commitment, CommitterKey, Proof, 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::*; @@ -32,9 +34,9 @@ use rayon::prelude::*; // 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 + pub num_vars: usize, // the number of variables in the multilinear polynomial + pub len: usize, + pub Z: Vec, // evaluations of the polynomial in all the 2^num_vars Boolean inputs } impl MultilinearExtension for DensePolynomial { @@ -201,8 +203,8 @@ impl PolyCommitmentGens { // 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); + let pst_gens = MultilinearPC::::setup(num_vars / 2, &mut rng); + let (ck, vk) = MultilinearPC::::trim(&pst_gens, num_vars / 2); PolyCommitmentGens { gens, ck, vk } } @@ -591,6 +593,8 @@ impl PolyEvalProof { #[cfg(test)] mod tests { + use std::num; + use crate::parameters::poseidon_params; use super::*; diff --git a/src/lib.rs b/src/lib.rs index af4d4ff8..2dd548a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ mod r1csproof; mod random; mod scalar; mod sparse_mlpoly; +mod sqrt_pst; mod sumcheck; mod timer; mod transcript; diff --git a/src/poseidon_transcript.rs b/src/poseidon_transcript.rs index 577e2ded..f4440226 100644 --- a/src/poseidon_transcript.rs +++ b/src/poseidon_transcript.rs @@ -36,6 +36,8 @@ impl PoseidonTranscript { self.sponge.absorb(&x); } + // pub fn append_usize + pub fn append_bytes(&mut self, x: &Vec) { self.sponge.absorb(x); } diff --git a/src/r1csproof.rs b/src/r1csproof.rs index 5d9b139e..a2b3ede8 100644 --- a/src/r1csproof.rs +++ b/src/r1csproof.rs @@ -4,9 +4,11 @@ use crate::group::{Fq, Fr}; use crate::math::Math; use crate::parameters::poseidon_params; use crate::poseidon_transcript::{AppendToPoseidon, PoseidonTranscript}; +use crate::sqrt_pst::PolyList; use crate::sumcheck::SumcheckInstanceProof; use ark_bls12_377::Bls12_377 as I; use ark_bw6_761::BW6_761 as P; +use ark_ec::PairingEngine; use ark_poly::MultilinearExtension; use ark_poly_commit::multilinear_pc::data_structures::{Commitment, Proof}; use ark_poly_commit::multilinear_pc::MultilinearPC; @@ -42,6 +44,7 @@ pub struct R1CSProof { ry: Vec, // The transcript state after the satisfiability proof was computed. pub transcript_sat_state: Scalar, + pub t: ::Fqk, } #[derive(Clone)] pub struct R1CSSumcheckGens { @@ -142,12 +145,19 @@ impl R1CSProof { assert!(input.len() < vars.len()); // create the multilinear witness polynomial from the satisfying assiment - let poly_vars = DensePolynomial::new(vars.clone()); + // expressed as the list of sqrt-sized polynomials + let pl = PolyList::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); + + // commitment list to the satisfying witness polynomial list + let (comm_list, t) = PolyList::commit(&pl, &gens.gens_pc.ck); + + let mut bytes = Vec::new(); + t.serialize(&mut bytes).unwrap(); + transcript.append_bytes(&bytes); + + // comm.append_to_poseidon(transcript); timer_commit.stop(); let c = transcript.challenge_scalar(); @@ -234,15 +244,18 @@ impl R1CSProof { 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); + let q = pl.get_q(&dummy); + + let (comm, proof_eval_vars_at_ry) = PolyList::open_q(comm_list, &gens.gens_pc.ck, &q, &dummy); println!( "proof size (no of quotients): {:?}", proof_eval_vars_at_ry.proofs.len() ); + // comm.append_to_poseidon(transcript); timmer_opening.stop(); let timer_polyeval = Timer::new("polyeval"); - let eval_vars_at_ry = poly_vars.evaluate(&ry[1..]); + let eval_vars_at_ry = PolyList::eval_q(q.clone(), &dummy); timer_polyeval.stop(); timer_prove.stop(); @@ -260,6 +273,7 @@ impl R1CSProof { rx: rx.clone(), ry: ry.clone(), transcript_sat_state: c, + t: t, }, rx, ry, @@ -275,7 +289,10 @@ impl R1CSProof { transcript: &mut PoseidonTranscript, gens: &R1CSGens, ) -> Result<(u128, u128, u128), ProofVerifyError> { - self.comm.append_to_poseidon(transcript); + // serialise and add the IPP commitment to the transcript + let mut bytes = Vec::new(); + self.t.serialize(&mut bytes).unwrap(); + transcript.append_bytes(&bytes); let c = transcript.challenge_scalar(); @@ -303,7 +320,7 @@ 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(), + comm: self.comm.clone(), ry: self.ry.clone(), transcript_sat_state: self.transcript_sat_state, }; @@ -339,7 +356,7 @@ impl R1CSProof { // Verifies the proof of opening against the result of evaluating the // witness polynomial at point ry. - let res = MultilinearPC::::check( + let res = PolyList::verify_q( &gens.gens_pc.vk, &self.comm, &dummy, @@ -365,7 +382,7 @@ impl R1CSProof { transcript: &mut PoseidonTranscript, gens: &R1CSGens, ) -> Result { - self.comm.append_to_poseidon(transcript); + // self.comm.append_to_poseidon(transcript); let c = transcript.challenge_scalar(); @@ -393,8 +410,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(), + comm: self.comm.clone(), transcript_sat_state: self.transcript_sat_state, }; diff --git a/src/sqrt_pst.rs b/src/sqrt_pst.rs new file mode 100644 index 00000000..17be7997 --- /dev/null +++ b/src/sqrt_pst.rs @@ -0,0 +1,273 @@ +use ark_bls12_377::{Bls12_377 as I, G1Affine}; +use ark_ec::{msm::VariableBaseMSM, PairingEngine, ProjectiveCurve}; +use ark_ff::{One, PrimeField}; +use ark_poly_commit::multilinear_pc::{ + data_structures::{Commitment, CommitterKey, Proof, VerifierKey}, + MultilinearPC, +}; +use rayon::prelude::{ + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, +}; + +use super::scalar::Scalar; +use crate::{dense_mlpoly::DensePolynomial, math::Math, timer::Timer}; + +pub struct PolyList { + m: usize, + polys: Vec, +} + +impl PolyList { + // Given the evaluations over the boolean hypercube of a polynomial p of size + // 2*m compute the sqrt-sized polynomials p_i as + // p_i(Y) = \sum_{j \in \{0,1\}^m} p(j, i) * chi_j(Y) + // where p(X,Y) = \sum_{i \in \{0,\1}^m} chi_i(X) * p_i(Y) + pub fn new(Z: &[Scalar]) -> Self { + let pl_timer = Timer::new("poly_list_build"); + let m = Z.len().log_2() / 2; + let pow_m = 2_usize.pow(m as u32); + let polys: Vec = (0..pow_m) + .into_par_iter() + .map(|i| { + let z: Vec = (0..pow_m) + .into_par_iter() + .map(|j| Z[(j << m) | i]) + .collect(); + DensePolynomial::new(z) + }) + .collect(); + assert!(polys.len() == pow_m); + pl_timer.stop(); + Self { m, polys } + } + + // Given point = (\vec{a}, \vec{b}), compute the polynomial q as + // q(Y) = + // \sum_{j \in \{0,1\}^m}(\sum_{i \in \{0,1\}^m} p(j,i) * chi_i(a)) * chi_j(Y) + // and p(a,b) = q(b) where p is the initial polynomial + + pub fn get_q(&self, point: &[Scalar]) -> DensePolynomial { + let q_timer = Timer::new("build_q"); + assert!(point.len() == 2 * self.m); + let a = &point[0..self.m]; + let pow_m = 2_usize.pow(self.m as u32); + + let chis: Vec = (0..pow_m) + .into_par_iter() + .map(|i| Self::get_chi_i(a, i)) + .collect(); + + let z_q: Vec = (0..pow_m) + .into_par_iter() + .map(|j| (0..pow_m).map(|i| self.polys[i].Z[j] * chis[i]).sum()) + .collect(); + q_timer.stop(); + + DensePolynomial::new(z_q) + } + + // Given point = (\vec{a}, \vec{b}) used to construct q + // compute q(b) = p(a,b). + pub fn eval_q(q: DensePolynomial, point: &[Scalar]) -> Scalar { + let b = &point[point.len() / 2..point.len()]; + let prods = (0..q.Z.len()) + .into_par_iter() + .map(|j| q.Z[j] * PolyList::get_chi_i(b, j)); + + prods.sum() + } + + pub fn commit( + poly_list: &PolyList, + ck: &CommitterKey, + ) -> (Vec>, ::Fqk) { + let timer_commit = Timer::new("sqrt_commit"); + + let timer_list = Timer::new("comm_list"); + + // commit to each of the sqrt sized p_i + let comm_list: Vec> = poly_list + .polys + .par_iter() + .map(|p| MultilinearPC::::commit(&ck.clone(), p)) + .collect(); + timer_list.stop(); + + let h_vec = ck.powers_of_h[0].clone(); + assert!(comm_list.len() == h_vec.len()); + + let ipp_timer = Timer::new("ipp"); + let pairings: Vec<_> = comm_list + .clone() + .into_par_iter() + .map(|c| ::G1Prepared::from(c.g_product)) + .zip( + h_vec + .into_par_iter() + .map(|h| ::G2Prepared::from(h)), + ) + .collect(); + + // computer the IPP commitment + let t = I::product_of_pairings(pairings.iter()); + ipp_timer.stop(); + + timer_commit.stop(); + + (comm_list, t) + } + + pub fn get_chi_i(b: &[Scalar], i: usize) -> Scalar { + let m = b.len(); + let mut prod = Scalar::one(); + for j in 0..m { + let b_j = b[j]; + if i >> j & 1 == 1 { + prod = prod * b_j; + } else { + prod = prod * (Scalar::one() - b_j) + }; + } + prod + } + + pub fn open_q( + comm_list: Vec>, + ck: &CommitterKey, + q: &DensePolynomial, + point: &[Scalar], + ) -> (Commitment, Proof) { + let m = point.len() / 2; + let a = &point[0..m]; + let b = &point[m..2 * m]; + + let timer_open = Timer::new("sqrt_open"); + + // Compute the PST commitment to q obtained as the inner products of the + // commitments to the polynomials p_i and chi_i(a) for i ranging over the + // boolean hypercube of size m. + let m = a.len(); + let pow_m = 2_usize.pow(m as u32); + let timer_msm = Timer::new("msm"); + let chis: Vec<_> = (0..pow_m) + .into_par_iter() + .map(|i| Self::get_chi_i(a, i).into_repr()) + .collect(); + assert!(chis.len() == comm_list.len()); + + let c_u = VariableBaseMSM::multi_scalar_mul( + comm_list + .par_iter() + .map(|c| c.g_product) + .collect::>() + .as_slice(), + chis.as_slice(), + ) + .into_affine(); + + let U: Commitment = Commitment { + nv: q.num_vars, + g_product: c_u, + }; + timer_msm.stop(); + + let comm = MultilinearPC::::commit(ck, q); + assert!(c_u == comm.g_product); + + // TODO: MIPP proof that U is the inner product of the opening + // vector A to T and the vector y + + // PST proof for opening q at b + let timer_proof = Timer::new("open"); + let pst_proof = MultilinearPC::::open(ck, q, &b); + timer_proof.stop(); + + timer_open.stop(); + + // TODO: add MIPP proof as return value + (U, pst_proof) + } + + pub fn verify_q( + vk: &VerifierKey, + U: &Commitment, + point: &[Scalar], + v: Scalar, + pst_proof: &Proof, + // TODO: add MIPP proof as argument + ) -> bool { + // TODO: MIPP verification + + let len = point.len(); + let b = &point[len / 2..len]; + + let timer_verify = Timer::new("sqrt_verify"); + let res = MultilinearPC::::check(vk, U, b, v, pst_proof); + timer_verify.stop(); + res + } +} + +#[cfg(test)] +mod tests { + use std::clone; + + use super::*; + use ark_ff::Zero; + use ark_std::UniformRand; + #[test] + fn check_sqrt_poly_eval() { + let mut rng = ark_std::test_rng(); + let num_vars = 8; + let len = 2_usize.pow(num_vars); + let Z: Vec = (0..len) + .into_iter() + .map(|_| Scalar::rand(&mut rng)) + .collect(); + let r: Vec = (0..num_vars) + .into_iter() + .map(|_| Scalar::rand(&mut rng)) + .collect(); + + let p = DensePolynomial::new(Z.clone()); + let res1 = p.evaluate(&r); + + let mut r_new = r.to_vec(); + r_new.reverse(); + let pl = PolyList::new(&Z.clone()); + let q = pl.get_q(&r_new); + let res2 = PolyList::eval_q(q.clone(), &r_new); + + assert!(res1 == res2); + } + + #[test] + fn check_new_poly_commit() { + let mut rng = ark_std::test_rng(); + let num_vars = 26; + let len = 2_usize.pow(num_vars); + let Z: Vec = (0..len) + .into_iter() + .map(|_| Scalar::rand(&mut rng)) + .collect(); + let r: Vec = (0..num_vars) + .into_iter() + .map(|_| Scalar::rand(&mut rng)) + .collect(); + + let gens = MultilinearPC::::setup(13, &mut rng); + let (ck, vk) = MultilinearPC::::trim(&gens, 13); + + let pl = PolyList::new(&Z.clone()); + let q = pl.get_q(&r); + + let v = PolyList::eval_q(q.clone(), &r); + + let (comm_list, t) = PolyList::commit(&pl, &ck); + + let (u, pst_proof) = PolyList::open_q(comm_list, &ck, &q, &r); + + let res = PolyList::verify_q(&vk, &u, &r, v, &pst_proof); + assert!(res == true); + } +}