From 17cff52765ce005d5a08a6a0f7572bc1b9ce7920 Mon Sep 17 00:00:00 2001 From: zhenfei Date: Thu, 21 Jul 2022 13:01:49 -0400 Subject: [PATCH] a generic Trait for both ml and uni KZG (#43) --- pcs/benches/bench.rs | 29 +- pcs/src/commit.rs | 620 -------------------------- pcs/src/lib.rs | 112 +++-- pcs/src/multilinear_kzg/batching.rs | 433 ++++++++++++++++++ pcs/src/multilinear_kzg/mod.rs | 441 ++++++++++++++++++ pcs/src/multilinear_kzg/srs.rs | 258 +++++++++++ pcs/src/{ => multilinear_kzg}/util.rs | 133 ++++-- pcs/src/param.rs | 253 +---------- pcs/src/prelude.rs | 13 + pcs/src/structs.rs | 9 + pcs/src/univariate_kzg/mod.rs | 384 ++++++++++++++++ pcs/src/univariate_kzg/srs.rs | 127 ++++++ poly-iop/src/transcript.rs | 10 + 13 files changed, 1872 insertions(+), 950 deletions(-) delete mode 100644 pcs/src/commit.rs create mode 100644 pcs/src/multilinear_kzg/batching.rs create mode 100644 pcs/src/multilinear_kzg/mod.rs create mode 100644 pcs/src/multilinear_kzg/srs.rs rename pcs/src/{ => multilinear_kzg}/util.rs (84%) create mode 100644 pcs/src/prelude.rs create mode 100644 pcs/src/structs.rs create mode 100644 pcs/src/univariate_kzg/mod.rs create mode 100644 pcs/src/univariate_kzg/srs.rs diff --git a/pcs/benches/bench.rs b/pcs/benches/bench.rs index eb2f274..418ac94 100644 --- a/pcs/benches/bench.rs +++ b/pcs/benches/bench.rs @@ -2,7 +2,10 @@ use ark_bls12_381::{Bls12_381, Fr}; use ark_ff::UniformRand; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_std::test_rng; -use pcs::{KZGMultilinearPC, MultilinearCommitmentScheme, PCSErrors}; +use pcs::{ + prelude::{KZGMultilinearPCS, PCSErrors, PolynomialCommitmentScheme}, + StructuredReferenceString, +}; use std::time::Instant; fn main() -> Result<(), PCSErrors> { @@ -13,7 +16,7 @@ fn bench_pcs() -> Result<(), PCSErrors> { let mut rng = test_rng(); // normal polynomials - let uni_params = KZGMultilinearPC::::setup(&mut rng, 18)?; + let uni_params = KZGMultilinearPCS::::gen_srs_for_testing(&mut rng, 18)?; for nv in 4..19 { let repetition = if nv < 10 { @@ -25,14 +28,18 @@ fn bench_pcs() -> Result<(), PCSErrors> { }; let poly = DenseMultilinearExtension::rand(nv, &mut rng); - let (ck, vk) = uni_params.trim(nv)?; + let (ml_ck, ml_vk) = uni_params.0.trim(nv)?; + let (uni_ck, uni_vk) = uni_params.1.trim(nv)?; + let ck = (ml_ck, uni_ck); + let vk = (ml_vk, uni_vk); + let point: Vec<_> = (0..nv).map(|_| Fr::rand(&mut rng)).collect(); // commit let com = { let start = Instant::now(); for _ in 0..repetition { - let _commit = KZGMultilinearPC::commit(&ck, &poly)?; + let _commit = KZGMultilinearPCS::commit(&ck, &poly)?; } println!( @@ -41,14 +48,14 @@ fn bench_pcs() -> Result<(), PCSErrors> { start.elapsed().as_nanos() / repetition as u128 ); - KZGMultilinearPC::commit(&ck, &poly)? + KZGMultilinearPCS::commit(&ck, &poly)? }; // open - let proof = { + let (proof, value) = { let start = Instant::now(); for _ in 0..repetition { - let _open = KZGMultilinearPC::open(&ck, &poly, &point)?; + let _open = KZGMultilinearPCS::open(&ck, &poly, &point)?; } println!( @@ -56,16 +63,16 @@ fn bench_pcs() -> Result<(), PCSErrors> { nv, start.elapsed().as_nanos() / repetition as u128 ); - KZGMultilinearPC::open(&ck, &poly, &point)? + KZGMultilinearPCS::open(&ck, &poly, &point)? }; - let value = poly.evaluate(&point).unwrap(); // verify - { let start = Instant::now(); for _ in 0..repetition { - assert!(KZGMultilinearPC::verify(&vk, &com, &point, &value, &proof)?); + assert!(KZGMultilinearPCS::verify( + &vk, &com, &point, &value, &proof + )?); } println!( "KZG verify for {} variables: {} ns", diff --git a/pcs/src/commit.rs b/pcs/src/commit.rs deleted file mode 100644 index 29e6c8a..0000000 --- a/pcs/src/commit.rs +++ /dev/null @@ -1,620 +0,0 @@ -use crate::{ - util::{build_l, compute_w_circ_l, merge_polynomials}, - KZGMultilinearPC, MultilinearCommitmentScheme, PCSErrors, ProverParam, UniversalParams, - VerifierParam, -}; -use ark_ec::{ - msm::{FixedBaseMSM, VariableBaseMSM}, - AffineCurve, PairingEngine, ProjectiveCurve, -}; -use ark_ff::PrimeField; -use ark_poly::{univariate::DensePolynomial, MultilinearExtension, Polynomial, UVPolynomial}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; -use ark_std::{end_timer, log2, rand::RngCore, start_timer, vec::Vec, One, Zero}; -use poly_iop::IOPTranscript; - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -/// commitment -pub struct Commitment { - /// number of variables - pub nv: usize, - /// product of g as described by the vRAM paper - pub g_product: E::G1Affine, -} - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -/// proof of opening -pub struct Proof { - /// Evaluation of quotients - pub proofs: Vec, -} - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -/// proof of batch opening -pub struct BatchProof { - /// The actual proof - pub proof: Proof, - /// The value which is `w` evaluated at `p:= l(r)`, where - /// - `w` is the merged MLE - /// - `l` is the list of univariate polys that goes through all points - /// - `r` is sampled from the transcript. - pub value: E::Fr, - /// Commitment to q(x) - // This is currently set to the entire coefficient list of q(x) - // TODO: replace me with a KZG commit - pub q_x_com: Vec, -} - -impl MultilinearCommitmentScheme for KZGMultilinearPC { - type ProverParam = ProverParam; - type VerifierParam = VerifierParam; - type SRS = UniversalParams; - type Commitment = Commitment; - type Proof = Proof; - type Transcript = IOPTranscript; - type BatchProof = BatchProof; - - /// Generate SRS from RNG. - /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. - /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. - fn setup(rng: &mut R, num_vars: usize) -> Result { - let setup_timer = start_timer!(|| format!("SRS setup for dim {}", num_vars)); - let res = Self::SRS::gen_srs_for_testing(rng, num_vars); - end_timer!(setup_timer); - res - } - - /// Generate a commitment for a polynomial. - /// - /// This function takes `2^num_vars` number of scalar multiplications over - /// G1. - fn commit( - prover_param: &Self::ProverParam, - poly: &impl MultilinearExtension, - ) -> Result { - let commit_timer = start_timer!(|| "commit"); - - let nv = poly.num_vars(); - let scalars: Vec<_> = poly - .to_evaluations() - .into_iter() - .map(|x| x.into_repr()) - .collect(); - let g_product = VariableBaseMSM::multi_scalar_mul( - &prover_param.powers_of_g[0].evals, - scalars.as_slice(), - ) - .into_affine(); - - end_timer!(commit_timer); - Ok(Commitment { nv, g_product }) - } - - /// Generate a commitment for a list of polynomials. - /// - /// This function takes `2^(num_vars + log(polys.len())` number of scalar - /// multiplications over G1. - fn multi_commit( - prover_param: &Self::ProverParam, - polys: &[impl MultilinearExtension], - ) -> Result { - let commit_timer = start_timer!(|| "multi commit"); - let poly = merge_polynomials(polys)?; - - let scalars: Vec<_> = poly - .to_evaluations() - .iter() - .map(|x| x.into_repr()) - .collect(); - - let g_product = VariableBaseMSM::multi_scalar_mul( - &prover_param.powers_of_g[0].evals, - scalars.as_slice(), - ) - .into_affine(); - - end_timer!(commit_timer); - Ok(Commitment { - nv: poly.num_vars, - g_product, - }) - } - - /// On input a polynomial `p` and a point `point`, outputs a proof for the - /// same. This function does not need to take the evaluation value as an - /// input. - /// - /// This function takes 2^{num_var +1} number of scalar multiplications over - /// G1: - /// - it proceeds with `num_var` number of rounds, - /// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2 - /// elements. - fn open( - prover_param: &Self::ProverParam, - polynomial: &impl MultilinearExtension, - point: &[E::Fr], - ) -> Result { - let open_timer = start_timer!(|| "open"); - - assert_eq!( - polynomial.num_vars(), - prover_param.num_vars, - "Invalid size of polynomial" - ); - let nv = polynomial.num_vars(); - let mut r: Vec> = (0..nv + 1).map(|_| Vec::new()).collect(); - let mut q: Vec> = (0..nv + 1).map(|_| Vec::new()).collect(); - - r[nv] = polynomial.to_evaluations(); - - let mut proofs = Vec::new(); - - for (i, (&point_at_k, gi)) in point - .iter() - .zip(prover_param.powers_of_g.iter()) - .take(nv) - .enumerate() - { - let ith_round = start_timer!(|| format!("{}-th round", i)); - - let k = nv - i; - let cur_dim = 1 << (k - 1); - let mut cur_q = vec![E::Fr::zero(); cur_dim]; - let mut cur_r = vec![E::Fr::zero(); cur_dim]; - - for b in 0..(1 << (k - 1)) { - // q_b = pre_r [2^b + 1] - pre_r [2^b] - cur_q[b] = r[k][(b << 1) + 1] - r[k][b << 1]; - - // r_b = pre_r [2^b]*(1-p) + pre_r [2^b + 1] * p - cur_r[b] = - r[k][b << 1] * (E::Fr::one() - point_at_k) + (r[k][(b << 1) + 1] * point_at_k); - } - - let scalars: Vec<_> = (0..(1 << k)).map(|x| cur_q[x >> 1].into_repr()).collect(); - - q[k] = cur_q; - r[k - 1] = cur_r; - - // this is a MSM over G1 and is likely to be the bottleneck - proofs.push(VariableBaseMSM::multi_scalar_mul(&gi.evals, &scalars).into_affine()); - end_timer!(ith_round); - } - - end_timer!(open_timer); - Ok(Proof { proofs }) - } - - /// Input - /// - the prover parameters, - /// - a list of MLEs, - /// - and a same number of points, - /// - and a transcript, - /// compute a multi-opening for all the polynomials. - /// - /// For simplicity, this API requires each MLE to have only one point. If - /// the caller wish to use more than one points per MLE, it should be - /// handled at the caller layer. - /// - /// Returns an error if the lengths do not match. - /// - /// Returns: - /// - the proof, - /// - q(x), which is a univariate polynomial `w circ l` where `w` is the - /// merged MLE, and `l` is a list of polynomials that go through all the - /// points. TODO: change this field to a commitment to `q(x)` - /// - and a value which is `w` evaluated at `p:= l(r)` from some `r` from - /// the transcript. - /// - /// Steps: - /// 1. build `l(points)` which is a list of univariate polynomials that goes - /// through the points - /// 2. build MLE `w` which is the merge of all MLEs. - /// 3. build `q(x)` which is a univariate polynomial `W circ l` - /// 4. output `q(x)`' and put it into transcript - /// 5. sample `r` from transcript - /// 6. get a point `p := l(r)` - /// 7. output an opening of `w` over point `p` - /// 8. output `w(p)` - fn multi_open( - prover_param: &Self::ProverParam, - polynomials: &[impl MultilinearExtension], - points: &[&[E::Fr]], - transcript: &mut IOPTranscript, - ) -> Result { - let open_timer = start_timer!(|| "multi open"); - - if points.len() != polynomials.len() { - return Err(PCSErrors::InvalidParameters( - "polynomial length does not match point length".to_string(), - )); - } - - let num_var = polynomials[0].num_vars(); - for poly in polynomials.iter().skip(1) { - if poly.num_vars() != num_var { - return Err(PCSErrors::InvalidParameters( - "polynomials do not have same num_vars".to_string(), - )); - } - } - for &point in points.iter() { - if point.len() != num_var { - return Err(PCSErrors::InvalidParameters( - "points do not have same num_vars".to_string(), - )); - } - } - - // 1. build `l(points)` which is a list of univariate polynomials that goes - // through the points - let uni_polys = build_l(num_var, points)?; - - // 2. build MLE `w` which is the merge of all MLEs. - let merge_poly = merge_polynomials(polynomials)?; - - // 3. build `q(x)` which is a univariate polynomial `W circ l` - let q_x = compute_w_circ_l(&merge_poly, &uni_polys)?; - - // 4. output `q(x)`' and put it into transcript - // - // TODO: use KZG commit for q(x) - // TODO: unwrap - q_x.coeffs - .iter() - .for_each(|x| transcript.append_field_element(b"q(x)", x).unwrap()); - - // 5. sample `r` from transcript - let r = transcript.get_and_append_challenge(b"r")?; - - // 6. get a point `p := l(r)` - let point: Vec = uni_polys - .iter() - .rev() - .map(|poly| poly.evaluate(&r)) - .collect(); - - // 7. output an opening of `w` over point `p` - let opening = Self::open(prover_param, &merge_poly, &point)?; - - // 8. output value that is `w` evaluated at `p` (which should match `q(r)`) - let value = merge_poly.evaluate(&point).unwrap(); - let value2 = q_x.evaluate(&r); - - if value != value2 { - return Err(PCSErrors::InvalidProver( - "Q(r) does not match W(l(r))".to_string(), - )); - } - - end_timer!(open_timer); - - Ok(Self::BatchProof { - proof: opening, - q_x_com: q_x.coeffs, - value, - }) - } - - /// Verifies that `value` is the evaluation at `x` of the polynomial - /// committed inside `comm`. - /// - /// This function takes - /// - num_var number of pairing product. - /// - num_var number of MSM - fn verify( - verifier_param: &Self::VerifierParam, - commitment: &Self::Commitment, - point: &[E::Fr], - value: &E::Fr, - proof: &Self::Proof, - ) -> Result { - let verify_timer = start_timer!(|| "verify"); - let prepare_inputs_timer = start_timer!(|| "prepare pairing inputs"); - - let scalar_size = E::Fr::size_in_bits(); - let window_size = FixedBaseMSM::get_mul_window_size(verifier_param.num_vars); - - let h_table = FixedBaseMSM::get_window_table( - scalar_size, - window_size, - verifier_param.h.into_projective(), - ); - let h_mul: Vec = - FixedBaseMSM::multi_scalar_mul(scalar_size, window_size, &h_table, point); - - let h_vec: Vec<_> = (0..verifier_param.num_vars) - .map(|i| verifier_param.h_mask[i].into_projective() - h_mul[i]) - .collect(); - let h_vec: Vec = E::G2Projective::batch_normalization_into_affine(&h_vec); - end_timer!(prepare_inputs_timer); - - let pairing_product_timer = start_timer!(|| "pairing product"); - - let mut pairings: Vec<_> = proof - .proofs - .iter() - .map(|&x| E::G1Prepared::from(x)) - .zip( - h_vec - .into_iter() - .take(verifier_param.num_vars) - .map(E::G2Prepared::from), - ) - .collect(); - - pairings.push(( - E::G1Prepared::from( - (verifier_param.g.mul(*value) - commitment.g_product.into_projective()) - .into_affine(), - ), - E::G2Prepared::from(verifier_param.h), - )); - - let res = E::product_of_pairings(pairings.iter()) == E::Fqk::one(); - - end_timer!(pairing_product_timer); - end_timer!(verify_timer); - Ok(res) - } - - /// Verifies that `value` is the evaluation at `x_i` of the polynomial - /// `poly_i` committed inside `comm`. - /// steps: - /// - /// 1. put `q(x)`'s evaluations over `(1, omega,...)` into transcript - /// 2. sample `r` from transcript - /// 3. check `q(r) == value` - /// 4. build `l(points)` which is a list of univariate polynomials that goes - /// through the points - /// 5. get a point `p := l(r)` - /// 6. verifies `p` is verifies against proof - fn batch_verify( - verifier_param: &Self::VerifierParam, - multi_commitment: &Self::Commitment, - points: &[&[E::Fr]], - batch_proof: &Self::BatchProof, - transcript: &mut IOPTranscript, - ) -> Result { - let verify_timer = start_timer!(|| "batch verify"); - let num_var = points[0].len(); - - for &point in points.iter().skip(1) { - if point.len() != num_var { - return Err(PCSErrors::InvalidParameters(format!( - "points do not have same num_vars ({} vs {})", - point.len(), - num_var, - ))); - } - } - if num_var + log2(points.len()) as usize != multi_commitment.nv { - return Err(PCSErrors::InvalidParameters(format!( - "points and multi_commitment do not have same num_vars ({} vs {})", - num_var + log2(points.len()) as usize, - num_var, - ))); - } - - // TODO: verify commitment of `q(x)` instead of receiving full `q(x)` - - // 1. put `q(x)`'s evaluations over `(1, omega,...)` into transcript - // TODO: unwrap - batch_proof - .q_x_com - .iter() - .for_each(|x| transcript.append_field_element(b"q(x)", x).unwrap()); - - // 2. sample `r` from transcript - let r = transcript.get_and_append_challenge(b"r")?; - - // 3. check `q(r) == value` - let q_x = DensePolynomial::from_coefficients_slice(&batch_proof.q_x_com); - let q_r = q_x.evaluate(&r); - if q_r != batch_proof.value { - return Ok(false); - } - - // 4. build `l(points)` which is a list of univariate polynomials that goes - // through the points - let uni_polys = build_l(num_var, points)?; - - // 5. get a point `p := l(r)` - let point: Vec = uni_polys.iter().rev().map(|x| x.evaluate(&r)).collect(); - - // 6. verifies `p` is verifies against proof - let res = Self::verify( - verifier_param, - multi_commitment, - &point, - &batch_proof.value, - &batch_proof.proof, - ); - end_timer!(verify_timer); - - res - } -} - -#[cfg(test)] -mod tests { - use crate::util::get_batched_nv; - - use super::*; - use ark_bls12_381::Bls12_381; - use ark_ec::PairingEngine; - use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; - use ark_std::{rand::RngCore, test_rng, vec::Vec, UniformRand}; - type E = Bls12_381; - type Fr = ::Fr; - - fn test_single_helper( - uni_params: &UniversalParams, - poly: &impl MultilinearExtension, - rng: &mut R, - ) -> Result<(), PCSErrors> { - let nv = poly.num_vars(); - assert_ne!(nv, 0); - let (ck, vk) = uni_params.trim(nv)?; - let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect(); - let com = KZGMultilinearPC::commit(&ck, poly)?; - let proof = KZGMultilinearPC::open(&ck, poly, &point)?; - - let value = poly.evaluate(&point).unwrap(); - assert!(KZGMultilinearPC::verify(&vk, &com, &point, &value, &proof)?); - - let value = Fr::rand(rng); - assert!(!KZGMultilinearPC::verify( - &vk, &com, &point, &value, &proof - )?); - - Ok(()) - } - - #[test] - fn test_single_commit() -> Result<(), PCSErrors> { - let mut rng = test_rng(); - - let uni_params = KZGMultilinearPC::::setup(&mut rng, 10)?; - - // normal polynomials - let poly1 = DenseMultilinearExtension::rand(8, &mut rng); - test_single_helper(&uni_params, &poly1, &mut rng)?; - - // single-variate polynomials - let poly2 = DenseMultilinearExtension::rand(1, &mut rng); - test_single_helper(&uni_params, &poly2, &mut rng)?; - - Ok(()) - } - - fn test_multi_commit_helper( - uni_params: &UniversalParams, - polys: &[impl MultilinearExtension], - rng: &mut R, - ) -> Result<(), PCSErrors> { - let mut transcript = IOPTranscript::new(b"test"); - - let nv = get_batched_nv(polys[0].num_vars(), polys.len()); - let (ck, vk) = uni_params.trim(nv)?; - let mut points = Vec::new(); - - for poly in polys.iter() { - let point = (0..poly.num_vars()) - .map(|_| Fr::rand(rng)) - .collect::>(); - points.push(point); - } - let points_ref: Vec<&[Fr]> = points.iter().map(|x| x.as_ref()).collect(); - - let com = KZGMultilinearPC::multi_commit(&ck, polys)?; - let batch_proof = KZGMultilinearPC::multi_open(&ck, polys, &points_ref, &mut transcript)?; - - // good path - let mut transcript = IOPTranscript::new(b"test"); - assert!(KZGMultilinearPC::batch_verify( - &vk, - &com, - &points_ref, - &batch_proof, - &mut transcript - )?); - - // bad commitment - assert!(KZGMultilinearPC::batch_verify( - &vk, - &Commitment { - nv: 0, - g_product: ::G1Affine::default() - }, - &points_ref, - &batch_proof, - &mut transcript - ) - .is_err()); - - // bad points - let points_ref: Vec<&[Fr]> = points.iter().skip(1).map(|x| x.as_ref()).collect(); - assert!(KZGMultilinearPC::batch_verify( - &vk, - &com, - &points_ref, - &batch_proof, - &mut transcript - ) - .is_err()); - - // bad proof - assert!(KZGMultilinearPC::batch_verify( - &vk, - &com, - &points_ref, - &BatchProof { - proof: Proof { proofs: Vec::new() }, - value: batch_proof.value, - q_x_com: batch_proof.q_x_com.clone() - }, - &mut transcript - ) - .is_err()); - - // bad value - assert!(KZGMultilinearPC::batch_verify( - &vk, - &com, - &points_ref, - &BatchProof { - proof: batch_proof.proof.clone(), - value: Fr::one(), - q_x_com: batch_proof.q_x_com - }, - &mut transcript - ) - .is_err()); - - // bad q(x) commit - assert!(KZGMultilinearPC::batch_verify( - &vk, - &com, - &points_ref, - &BatchProof { - proof: batch_proof.proof, - value: batch_proof.value, - q_x_com: Vec::new() - }, - &mut transcript - ) - .is_err()); - - Ok(()) - } - - #[test] - fn test_multi_commit() -> Result<(), PCSErrors> { - let mut rng = test_rng(); - - let uni_params = KZGMultilinearPC::::setup(&mut rng, 15)?; - - // normal polynomials - let polys1: Vec<_> = (0..2) - .map(|_| DenseMultilinearExtension::rand(4, &mut rng)) - .collect(); - test_multi_commit_helper(&uni_params, &polys1, &mut rng)?; - - // single-variate polynomials - let polys1: Vec<_> = (0..5) - .map(|_| DenseMultilinearExtension::rand(1, &mut rng)) - .collect(); - test_multi_commit_helper(&uni_params, &polys1, &mut rng)?; - - Ok(()) - } - - #[test] - fn setup_commit_verify_constant_polynomial() { - let mut rng = test_rng(); - - // normal polynomials - assert!(KZGMultilinearPC::::setup(&mut rng, 0).is_err()); - } -} diff --git a/pcs/src/lib.rs b/pcs/src/lib.rs index 2663490..88bdd35 100644 --- a/pcs/src/lib.rs +++ b/pcs/src/lib.rs @@ -1,85 +1,125 @@ -mod commit; mod errors; -mod param; -mod util; +mod multilinear_kzg; +mod structs; +mod univariate_kzg; + +pub mod prelude; use ark_ec::PairingEngine; -use ark_poly::MultilinearExtension; use ark_std::rand::RngCore; -use poly_iop::IOPTranscript; -use std::marker::PhantomData; - -pub use errors::PCSErrors; -pub use param::{ProverParam, UniversalParams, VerifierParam}; - -/// KZG Polynomial Commitment Scheme on multilinear extensions. -pub struct KZGMultilinearPC { - #[doc(hidden)] - phantom: PhantomData, -} +use errors::PCSErrors; -pub trait MultilinearCommitmentScheme { +/// This trait defines APIs for polynomial commitment schemes. +/// Note that for our usage of PCS, we do not require the hiding property. +pub trait PolynomialCommitmentScheme { + // Parameters type ProverParam; type VerifierParam; type SRS; + // Polynomial and its associated types + type Polynomial; + type Point; + type Evaluation; + // Commitments and proofs type Commitment; type Proof; type BatchProof; - type Transcript; + type BatchCommitment; - /// Generate SRS from RNG. + /// Build SRS for testing. + /// + /// - For univariate polynomials, `log_size` is the log of maximum degree. + /// - For multilinear polynomials, `log_size` is the number of variables. + /// /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. - fn setup(rng: &mut R, num_vars: usize) -> Result; + fn gen_srs_for_testing( + rng: &mut R, + log_size: usize, + ) -> Result; /// Generate a commitment for a polynomial fn commit( prover_param: &Self::ProverParam, - poly: &impl MultilinearExtension, + poly: &Self::Polynomial, ) -> Result; /// Generate a commitment for a list of polynomials fn multi_commit( prover_param: &Self::ProverParam, - polys: &[impl MultilinearExtension], - ) -> Result; + polys: &[Self::Polynomial], + ) -> Result; /// On input a polynomial `p` and a point `point`, outputs a proof for the /// same. fn open( prover_param: &Self::ProverParam, - polynomial: &impl MultilinearExtension, - point: &[E::Fr], - ) -> Result; + polynomial: &Self::Polynomial, + point: &Self::Point, + ) -> Result<(Self::Proof, Self::Evaluation), PCSErrors>; /// Input a list of MLEs, and a same number of points, and a transcript, /// compute a multi-opening for all the polynomials. - #[allow(clippy::type_complexity)] - // TODO: remove after we KZG-commit q(x) fn multi_open( prover_param: &Self::ProverParam, - polynomials: &[impl MultilinearExtension], - point: &[&[E::Fr]], - transcript: &mut Self::Transcript, - ) -> Result; + multi_commitment: &Self::Commitment, + polynomials: &[Self::Polynomial], + points: &[Self::Point], + ) -> Result<(Self::BatchProof, Vec), PCSErrors>; /// Verifies that `value` is the evaluation at `x` of the polynomial /// committed inside `comm`. fn verify( verifier_param: &Self::VerifierParam, commitment: &Self::Commitment, - point: &[E::Fr], + point: &Self::Point, value: &E::Fr, proof: &Self::Proof, ) -> Result; /// Verifies that `value_i` is the evaluation at `x_i` of the polynomial /// `poly_i` committed inside `comm`. - fn batch_verify( + fn batch_verify( verifier_param: &Self::VerifierParam, - multi_commitment: &Self::Commitment, - points: &[&[E::Fr]], + multi_commitment: &Self::BatchCommitment, + points: &[Self::Point], + values: &[E::Fr], batch_proof: &Self::BatchProof, - transcript: &mut IOPTranscript, + rng: &mut R, ) -> Result; } + +/// API definitions for structured reference string +pub trait StructuredReferenceString: Sized { + type ProverParam; + type VerifierParam; + + /// Extract the prover parameters from the public parameters. + fn extract_prover_param(&self, supported_log_size: usize) -> Self::ProverParam; + /// Extract the verifier parameters from the public parameters. + fn extract_verifier_param(&self, supported_log_size: usize) -> Self::VerifierParam; + + /// Trim the universal parameters to specialize the public parameters + /// for polynomials to the given `supported_log_size`, and + /// returns committer key and verifier key. + /// + /// - For univariate polynomials, `supported_log_size` is the log of maximum + /// degree. + /// - For multilinear polynomials, `supported_log_size` is the number of + /// variables. + /// + /// `supported_log_size` should be in range `1..=params.log_size` + fn trim( + &self, + supported_log_size: usize, + ) -> Result<(Self::ProverParam, Self::VerifierParam), PCSErrors>; + + /// Build SRS for testing. + /// + /// - For univariate polynomials, `log_size` is the log of maximum degree. + /// - For multilinear polynomials, `log_size` is the number of variables. + /// + /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. + /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. + fn gen_srs_for_testing(rng: &mut R, log_size: usize) -> Result; +} diff --git a/pcs/src/multilinear_kzg/batching.rs b/pcs/src/multilinear_kzg/batching.rs new file mode 100644 index 0000000..875eb89 --- /dev/null +++ b/pcs/src/multilinear_kzg/batching.rs @@ -0,0 +1,433 @@ +use super::{ + open_internal, + srs::{MultilinearProverParam, MultilinearVerifierParam}, + util::{build_l, compute_w_circ_l, merge_polynomials}, + verify_internal, BatchProof, +}; +use crate::{ + multilinear_kzg::util::get_uni_domain, + prelude::{Commitment, UnivariateProverParam, UnivariateVerifierParam}, + univariate_kzg::KZGUnivariatePCS, + PCSErrors, PolynomialCommitmentScheme, +}; +use ark_ec::PairingEngine; +use ark_poly::{DenseMultilinearExtension, EvaluationDomain, MultilinearExtension, Polynomial}; +use ark_std::{end_timer, start_timer, vec::Vec}; +use poly_iop::IOPTranscript; + +/// Input +/// - the prover parameters for univariate KZG, +/// - the prover parameters for multilinear KZG, +/// - a list of MLEs, +/// - a commitment to all MLEs +/// - and a same number of points, +/// compute a multi-opening for all the polynomials. +/// +/// For simplicity, this API requires each MLE to have only one point. If +/// the caller wish to use more than one points per MLE, it should be +/// handled at the caller layer. +/// +/// Returns an error if the lengths do not match. +/// +/// Returns the proof, consists of +/// - the multilinear KZG opening +/// - the univariate KZG commitment to q(x) +/// - the openings and evaluations of q(x) at omega^i and r +/// +/// Steps: +/// 1. build `l(points)` which is a list of univariate polynomials that goes +/// through the points +/// 2. build MLE `w` which is the merge of all MLEs. +/// 3. build `q(x)` which is a univariate polynomial `W circ l` +/// 4. commit to q(x) and sample r from transcript +/// transcript contains: w commitment, points, q(x)'s commitment +/// 5. build q(omega^i) and their openings +/// 6. build q(r) and its opening +/// 7. get a point `p := l(r)` +/// 8. output an opening of `w` over point `p` +/// 9. output `w(p)` +pub(super) fn multi_open_internal( + uni_prover_param: &UnivariateProverParam, + ml_prover_param: &MultilinearProverParam, + polynomials: &[DenseMultilinearExtension], + multi_commitment: &Commitment, + points: &[Vec], +) -> Result<(BatchProof, Vec), PCSErrors> { + let open_timer = start_timer!(|| "multi open"); + + // =================================== + // Sanity checks on inputs + // =================================== + let points_len = points.len(); + if points_len == 0 { + return Err(PCSErrors::InvalidParameters("points is empty".to_string())); + } + + if points_len != polynomials.len() { + return Err(PCSErrors::InvalidParameters( + "polynomial length does not match point length".to_string(), + )); + } + + let num_var = polynomials[0].num_vars(); + for poly in polynomials.iter().skip(1) { + if poly.num_vars() != num_var { + return Err(PCSErrors::InvalidParameters( + "polynomials do not have same num_vars".to_string(), + )); + } + } + for point in points.iter() { + if point.len() != num_var { + return Err(PCSErrors::InvalidParameters( + "points do not have same num_vars".to_string(), + )); + } + } + + let domain = get_uni_domain::(points_len)?; + + // 1. build `l(points)` which is a list of univariate polynomials that goes + // through the points + let uni_polys = build_l(num_var, points, &domain)?; + + // 2. build MLE `w` which is the merge of all MLEs. + let merge_poly = merge_polynomials(polynomials)?; + + // 3. build `q(x)` which is a univariate polynomial `W circ l` + let q_x = compute_w_circ_l(&merge_poly, &uni_polys)?; + + // 4. commit to q(x) and sample r from transcript + // transcript contains: w commitment, points, q(x)'s commitment + let mut transcript = IOPTranscript::new(b"ml kzg"); + transcript.append_serializable_element(b"w", multi_commitment)?; + for point in points { + transcript.append_serializable_element(b"w", point)?; + } + + let q_x_commit = KZGUnivariatePCS::::commit(uni_prover_param, &q_x)?; + transcript.append_serializable_element(b"q(x)", &q_x_commit)?; + let r = transcript.get_and_append_challenge(b"r")?; + + // 5. build q(omega^i) and their openings + let mut q_x_opens = vec![]; + let mut q_x_evals = vec![]; + for i in 0..points_len { + let (q_x_open, q_x_eval) = + KZGUnivariatePCS::::open(uni_prover_param, &q_x, &domain.element(i))?; + q_x_opens.push(q_x_open); + q_x_evals.push(q_x_eval); + + // sanity check + let point: Vec = uni_polys + .iter() + .rev() + .map(|poly| poly.evaluate(&domain.element(i))) + .collect(); + let mle_eval = merge_poly.evaluate(&point).unwrap(); + if mle_eval != q_x_eval { + return Err(PCSErrors::InvalidProver( + "Q(omega) does not match W(l(omega))".to_string(), + )); + } + } + + // 6. build q(r) and its opening + let (q_x_open, q_r_value) = KZGUnivariatePCS::::open(uni_prover_param, &q_x, &r)?; + q_x_opens.push(q_x_open); + q_x_evals.push(q_r_value); + + // 7. get a point `p := l(r)` + let point: Vec = uni_polys + .iter() + .rev() + .map(|poly| poly.evaluate(&r)) + .collect(); + + // 8. output an opening of `w` over point `p` + let (mle_opening, mle_eval) = open_internal(ml_prover_param, &merge_poly, &point)?; + + // 9. output value that is `w` evaluated at `p` (which should match `q(r)`) + if mle_eval != q_r_value { + return Err(PCSErrors::InvalidProver( + "Q(r) does not match W(l(r))".to_string(), + )); + } + end_timer!(open_timer); + + Ok(( + BatchProof { + proof: mle_opening, + q_x_commit, + q_x_opens, + }, + q_x_evals, + )) +} + +/// Verifies that the `multi_commitment` is a valid commitment +/// to a list of MLEs for the given openings and evaluations in +/// the batch_proof. +/// +/// steps: +/// +/// 1. push w, points and q_com into transcript +/// 2. sample `r` from transcript +/// 3. check `q(r) == batch_proof.q_x_value.last` and +/// `q(omega^i) == batch_proof.q_x_value[i]` +/// 4. build `l(points)` which is a list of univariate +/// polynomials that goes through the points +/// 5. get a point `p := l(r)` +/// 6. verifies `p` is valid against multilinear KZG proof +pub(super) fn batch_verify_internal( + uni_verifier_param: &UnivariateVerifierParam, + ml_verifier_param: &MultilinearVerifierParam, + multi_commitment: &Commitment, + points: &[Vec], + values: &[E::Fr], + batch_proof: &BatchProof, +) -> Result { + let verify_timer = start_timer!(|| "batch verify"); + + // =================================== + // Sanity checks on inputs + // =================================== + let points_len = points.len(); + if points_len == 0 { + return Err(PCSErrors::InvalidParameters("points is empty".to_string())); + } + + // add one here because we also have q(r) and its opening + if points_len + 1 != batch_proof.q_x_opens.len() { + return Err(PCSErrors::InvalidParameters( + "openings length does not match point length".to_string(), + )); + } + + if points_len + 1 != values.len() { + return Err(PCSErrors::InvalidParameters( + "values length does not match point length".to_string(), + )); + } + + let num_var = points[0].len(); + for point in points.iter().skip(1) { + if point.len() != num_var { + return Err(PCSErrors::InvalidParameters(format!( + "points do not have same num_vars ({} vs {})", + point.len(), + num_var, + ))); + } + } + + let domain = get_uni_domain::(points_len)?; + + // 1. push w, points and q_com into transcript + let mut transcript = IOPTranscript::new(b"ml kzg"); + transcript.append_serializable_element(b"w", multi_commitment)?; + for point in points { + transcript.append_serializable_element(b"w", point)?; + } + + transcript.append_serializable_element(b"q(x)", &batch_proof.q_x_commit)?; + + // 2. sample `r` from transcript + let r = transcript.get_and_append_challenge(b"r")?; + + // 3. check `q(r) == batch_proof.q_x_value.last` and `q(omega^i) = + // batch_proof.q_x_value[i]` + for i in 0..points_len { + if !KZGUnivariatePCS::verify( + uni_verifier_param, + &batch_proof.q_x_commit, + &domain.element(i), + &values[i], + &batch_proof.q_x_opens[i], + )? { + #[cfg(debug_assertion)] + println!("q(omega^{}) verification failed", i); + return Ok(false); + } + } + + if !KZGUnivariatePCS::verify( + uni_verifier_param, + &batch_proof.q_x_commit, + &r, + &values[points_len], + &batch_proof.q_x_opens[points_len], + )? { + #[cfg(debug_assertion)] + println!("q(r) verification failed"); + return Ok(false); + } + + // 4. build `l(points)` which is a list of univariate polynomials that goes + // through the points + let uni_polys = build_l(num_var, points, &domain)?; + + // 5. get a point `p := l(r)` + let point: Vec = uni_polys.iter().rev().map(|x| x.evaluate(&r)).collect(); + + // 6. verifies `p` is valid against multilinear KZG proof + let res = verify_internal( + ml_verifier_param, + multi_commitment, + &point, + &values[points_len], + &batch_proof.proof, + )?; + + #[cfg(debug_assertion)] + if !res { + println!("multilinear KZG verification failed"); + } + + end_timer!(verify_timer); + + Ok(res) +} + +#[cfg(test)] +mod tests { + use super::{ + super::{util::get_batched_nv, *}, + *, + }; + use crate::{ + multilinear_kzg::util::{compute_qx_degree, generate_evaluations}, + prelude::UnivariateUniversalParams, + StructuredReferenceString, + }; + use ark_bls12_381::Bls12_381 as E; + use ark_ec::PairingEngine; + use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; + use ark_std::{log2, rand::RngCore, test_rng, vec::Vec, UniformRand}; + type Fr = ::Fr; + + fn test_multi_commit_helper( + uni_params: &UnivariateUniversalParams, + ml_params: &MultilinearUniversalParams, + polys: &[DenseMultilinearExtension], + rng: &mut R, + ) -> Result<(), PCSErrors> { + let merged_nv = get_batched_nv(polys[0].num_vars(), polys.len()); + let qx_degree = compute_qx_degree(merged_nv, polys.len()); + let log_qx_degree = log2(qx_degree) as usize; + + let (uni_ck, uni_vk) = uni_params.trim(log_qx_degree)?; + let (ml_ck, ml_vk) = ml_params.trim(merged_nv)?; + + let mut points = Vec::new(); + for poly in polys.iter() { + let point = (0..poly.num_vars()) + .map(|_| Fr::rand(rng)) + .collect::>(); + points.push(point); + } + + let evals = generate_evaluations(polys, &points)?; + + let com = KZGMultilinearPCS::multi_commit(&(ml_ck.clone(), uni_ck.clone()), polys)?; + let (batch_proof, evaluations) = + multi_open_internal(&uni_ck, &ml_ck, polys, &com, &points)?; + + for (a, b) in evals.iter().zip(evaluations.iter()) { + assert_eq!(a, b) + } + + // good path + assert!(batch_verify_internal( + &uni_vk, + &ml_vk, + &com, + &points, + &evaluations, + &batch_proof, + )?); + + // bad commitment + assert!(!batch_verify_internal( + &uni_vk, + &ml_vk, + &Commitment { + commitment: ::G1Affine::default() + }, + &points, + &evaluations, + &batch_proof, + )?); + + // bad points + assert!( + batch_verify_internal(&uni_vk, &ml_vk, &com, &points[1..], &[], &batch_proof,).is_err() + ); + + // bad proof + assert!(batch_verify_internal( + &uni_vk, + &ml_vk, + &com, + &points, + &evaluations, + &BatchProof { + proof: Proof { proofs: Vec::new() }, + q_x_commit: Commitment { + commitment: ::G1Affine::default() + }, + q_x_opens: vec![], + }, + ) + .is_err()); + + // bad value + let mut wrong_evals = evaluations.clone(); + wrong_evals[0] = Fr::default(); + assert!(!batch_verify_internal( + &uni_vk, + &ml_vk, + &com, + &points, + &wrong_evals, + &batch_proof + )?); + + // bad q(x) commit + let mut wrong_proof = batch_proof.clone(); + wrong_proof.q_x_commit = Commitment { + commitment: ::G1Affine::default(), + }; + assert!(!batch_verify_internal( + &uni_vk, + &ml_vk, + &com, + &points, + &evaluations, + &wrong_proof, + )?); + Ok(()) + } + + #[test] + fn test_multi_commit_internal() -> Result<(), PCSErrors> { + let mut rng = test_rng(); + + let uni_params = UnivariateUniversalParams::::gen_srs_for_testing(&mut rng, 15)?; + let ml_params = MultilinearUniversalParams::::gen_srs_for_testing(&mut rng, 15)?; + + // normal polynomials + let polys1: Vec<_> = (0..5) + .map(|_| DenseMultilinearExtension::rand(4, &mut rng)) + .collect(); + test_multi_commit_helper(&uni_params, &ml_params, &polys1, &mut rng)?; + + // single-variate polynomials + let polys1: Vec<_> = (0..5) + .map(|_| DenseMultilinearExtension::rand(1, &mut rng)) + .collect(); + test_multi_commit_helper(&uni_params, &ml_params, &polys1, &mut rng)?; + + Ok(()) + } +} diff --git a/pcs/src/multilinear_kzg/mod.rs b/pcs/src/multilinear_kzg/mod.rs new file mode 100644 index 0000000..8a3d6ae --- /dev/null +++ b/pcs/src/multilinear_kzg/mod.rs @@ -0,0 +1,441 @@ +//! Main module for multilinear KZG commitment scheme + +mod batching; +pub(crate) mod srs; +pub(crate) mod util; + +use crate::{ + prelude::{ + Commitment, UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam, + }, + univariate_kzg::KZGUnivariateOpening, + PCSErrors, PolynomialCommitmentScheme, StructuredReferenceString, +}; +use ark_ec::{ + msm::{FixedBaseMSM, VariableBaseMSM}, + AffineCurve, PairingEngine, ProjectiveCurve, +}; +use ark_ff::PrimeField; +use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; +use ark_std::{end_timer, rand::RngCore, start_timer, vec::Vec, One, Zero}; +use batching::{batch_verify_internal, multi_open_internal}; +use srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam}; +use std::marker::PhantomData; +use util::merge_polynomials; + +/// KZG Polynomial Commitment Scheme on multilinear polynomials. +pub struct KZGMultilinearPCS { + #[doc(hidden)] + phantom: PhantomData, +} + +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +/// proof of opening +pub struct Proof { + /// Evaluation of quotients + pub proofs: Vec, +} + +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +/// proof of batch opening +pub struct BatchProof { + /// The actual proof + pub proof: Proof, + /// Commitment to q(x):= w(l(x)) where + /// - `w` is the merged MLE + /// - `l` is the list of univariate polys that goes through all points + pub q_x_commit: Commitment, + /// openings of q(x) at 1, omega, ..., and r + pub q_x_opens: Vec>, +} + +impl PolynomialCommitmentScheme for KZGMultilinearPCS { + // Parameters + type ProverParam = ( + MultilinearProverParam, + UnivariateProverParam, + ); + type VerifierParam = (MultilinearVerifierParam, UnivariateVerifierParam); + type SRS = (MultilinearUniversalParams, UnivariateUniversalParams); + // Polynomial and its associated types + type Polynomial = DenseMultilinearExtension; + type Point = Vec; + type Evaluation = E::Fr; + // Commitments and proofs + type Commitment = Commitment; + type Proof = Proof; + type BatchProof = BatchProof; + type BatchCommitment = Commitment; + + /// Build SRS for testing. + /// + /// - For univariate polynomials, `log_size` is the log of maximum degree. + /// - For multilinear polynomials, `log_size` is the number of variables. + /// + /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. + /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. + fn gen_srs_for_testing( + rng: &mut R, + log_size: usize, + ) -> Result { + Ok(( + MultilinearUniversalParams::::gen_srs_for_testing(rng, log_size)?, + UnivariateUniversalParams::::gen_srs_for_testing(rng, log_size)?, + )) + } + + /// Generate a commitment for a polynomial. + /// + /// This function takes `2^num_vars` number of scalar multiplications over + /// G1. + fn commit( + prover_param: &Self::ProverParam, + poly: &Self::Polynomial, + ) -> Result { + let commit_timer = start_timer!(|| "commit"); + + let scalars: Vec<_> = poly + .to_evaluations() + .into_iter() + .map(|x| x.into_repr()) + .collect(); + let commitment = VariableBaseMSM::multi_scalar_mul( + &prover_param.0.powers_of_g[0].evals, + scalars.as_slice(), + ) + .into_affine(); + + end_timer!(commit_timer); + Ok(Commitment { commitment }) + } + + /// Generate a commitment for a list of polynomials. + /// + /// This function takes `2^(num_vars + log(polys.len())` number of scalar + /// multiplications over G1. + fn multi_commit( + prover_param: &Self::ProverParam, + polys: &[Self::Polynomial], + ) -> Result { + let commit_timer = start_timer!(|| "multi commit"); + let poly = merge_polynomials(polys)?; + + let scalars: Vec<_> = poly + .to_evaluations() + .iter() + .map(|x| x.into_repr()) + .collect(); + + let commitment = VariableBaseMSM::multi_scalar_mul( + &prover_param.0.powers_of_g[0].evals, + scalars.as_slice(), + ) + .into_affine(); + + end_timer!(commit_timer); + Ok(Commitment { commitment }) + } + + /// On input a polynomial `p` and a point `point`, outputs a proof for the + /// same. This function does not need to take the evaluation value as an + /// input. + /// + /// This function takes 2^{num_var +1} number of scalar multiplications over + /// G1: + /// - it prodceeds with `num_var` number of rounds, + /// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2 + /// elements. + fn open( + prover_param: &Self::ProverParam, + polynomial: &Self::Polynomial, + point: &Self::Point, + ) -> Result<(Self::Proof, Self::Evaluation), PCSErrors> { + open_internal(&prover_param.0, polynomial, point) + } + + /// Input + /// - the prover parameters for univariate KZG, + /// - the prover parameters for multilinear KZG, + /// - a list of MLEs, + /// - a commitment to all MLEs + /// - and a same number of points, + /// compute a multi-opening for all the polynomials. + /// + /// For simplicity, this API requires each MLE to have only one point. If + /// the caller wish to use more than one points per MLE, it should be + /// handled at the caller layer. + /// + /// Returns an error if the lengths do not match. + /// + /// Returns the proof, consists of + /// - the multilinear KZG opening + /// - the univariate KZG commitment to q(x) + /// - the openings and evaluations of q(x) at omega^i and r + /// + /// Steps: + /// 1. build `l(points)` which is a list of univariate polynomials that goes + /// through the points + /// 2. build MLE `w` which is the merge of all MLEs. + /// 3. build `q(x)` which is a univariate polynomial `W circ l` + /// 4. commit to q(x) and sample r from transcript + /// transcript contains: w commitment, points, q(x)'s commitment + /// 5. build q(omega^i) and their openings + /// 6. build q(r) and its opening + /// 7. get a point `p := l(r)` + /// 8. output an opening of `w` over point `p` + /// 9. output `w(p)` + fn multi_open( + prover_param: &Self::ProverParam, + multi_commitment: &Self::Commitment, + polynomials: &[Self::Polynomial], + points: &[Self::Point], + ) -> Result<(Self::BatchProof, Vec), PCSErrors> { + multi_open_internal::( + &prover_param.1, + &prover_param.0, + polynomials, + multi_commitment, + points, + ) + } + + /// Verifies that `value` is the evaluation at `x` of the polynomial + /// committed inside `comm`. + /// + /// This function takes + /// - num_var number of pairing product. + /// - num_var number of MSM + fn verify( + verifier_param: &Self::VerifierParam, + commitment: &Self::Commitment, + point: &Self::Point, + value: &E::Fr, + proof: &Self::Proof, + ) -> Result { + verify_internal(&verifier_param.0, commitment, point, value, proof) + } + + /// Verifies that `value` is the evaluation at `x_i` of the polynomial + /// `poly_i` committed inside `comm`. + /// steps: + /// + /// 1. put `q(x)`'s evaluations over `(1, omega,...)` into transcript + /// 2. sample `r` from transcript + /// 3. check `q(r) == value` + /// 4. build `l(points)` which is a list of univariate polynomials that goes + /// through the points + /// 5. get a point `p := l(r)` + /// 6. verifies `p` is verifies against proof + fn batch_verify( + verifier_param: &Self::VerifierParam, + multi_commitment: &Self::BatchCommitment, + points: &[Self::Point], + values: &[E::Fr], + batch_proof: &Self::BatchProof, + _rng: &mut R, + ) -> Result { + batch_verify_internal( + &verifier_param.1, + &verifier_param.0, + multi_commitment, + points, + values, + batch_proof, + ) + } +} + +/// On input a polynomial `p` and a point `point`, outputs a proof for the +/// same. This function does not need to take the evaluation value as an +/// input. +/// +/// This function takes 2^{num_var +1} number of scalar multiplications over +/// G1: +/// - it proceeds with `num_var` number of rounds, +/// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2 +/// elements. +fn open_internal( + prover_param: &MultilinearProverParam, + polynomial: &DenseMultilinearExtension, + point: &[E::Fr], +) -> Result<(Proof, E::Fr), PCSErrors> { + let open_timer = start_timer!(|| format!("open mle with {} variable", polynomial.num_vars)); + + assert_eq!( + polynomial.num_vars(), + prover_param.num_vars, + "Invalid size of polynomial" + ); + let nv = polynomial.num_vars(); + let mut r: Vec> = (0..nv + 1).map(|_| Vec::new()).collect(); + let mut q: Vec> = (0..nv + 1).map(|_| Vec::new()).collect(); + + r[nv] = polynomial.to_evaluations(); + + let mut proofs = Vec::new(); + + for (i, (&point_at_k, gi)) in point + .iter() + .zip(prover_param.powers_of_g.iter()) + .take(nv) + .enumerate() + { + let ith_round = start_timer!(|| format!("{}-th round", i)); + + let k = nv - i; + let cur_dim = 1 << (k - 1); + let mut cur_q = vec![E::Fr::zero(); cur_dim]; + let mut cur_r = vec![E::Fr::zero(); cur_dim]; + + for b in 0..(1 << (k - 1)) { + // q_b = pre_r [2^b + 1] - pre_r [2^b] + cur_q[b] = r[k][(b << 1) + 1] - r[k][b << 1]; + + // r_b = pre_r [2^b]*(1-p) + pre_r [2^b + 1] * p + cur_r[b] = + r[k][b << 1] * (E::Fr::one() - point_at_k) + (r[k][(b << 1) + 1] * point_at_k); + } + + let scalars: Vec<_> = (0..(1 << k)).map(|x| cur_q[x >> 1].into_repr()).collect(); + + q[k] = cur_q; + r[k - 1] = cur_r; + + // this is a MSM over G1 and is likely to be the bottleneck + proofs.push(VariableBaseMSM::multi_scalar_mul(&gi.evals, &scalars).into_affine()); + end_timer!(ith_round); + } + let eval = polynomial.evaluate(point).ok_or_else(|| { + PCSErrors::InvalidParameters("fail to evaluate the polynomial".to_string()) + })?; + end_timer!(open_timer); + Ok((Proof { proofs }, eval)) +} + +/// Verifies that `value` is the evaluation at `x` of the polynomial +/// committed inside `comm`. +/// +/// This function takes +/// - num_var number of pairing product. +/// - num_var number of MSM +fn verify_internal( + verifier_param: &MultilinearVerifierParam, + commitment: &Commitment, + point: &[E::Fr], + value: &E::Fr, + proof: &Proof, +) -> Result { + let verify_timer = start_timer!(|| "verify"); + let prepare_inputs_timer = start_timer!(|| "prepare pairing inputs"); + + let scalar_size = E::Fr::size_in_bits(); + let window_size = FixedBaseMSM::get_mul_window_size(verifier_param.num_vars); + + let h_table = FixedBaseMSM::get_window_table( + scalar_size, + window_size, + verifier_param.h.into_projective(), + ); + let h_mul: Vec = + FixedBaseMSM::multi_scalar_mul(scalar_size, window_size, &h_table, point); + + let h_vec: Vec<_> = (0..verifier_param.num_vars) + .map(|i| verifier_param.h_mask[i].into_projective() - h_mul[i]) + .collect(); + let h_vec: Vec = E::G2Projective::batch_normalization_into_affine(&h_vec); + end_timer!(prepare_inputs_timer); + + let pairing_product_timer = start_timer!(|| "pairing product"); + + let mut pairings: Vec<_> = proof + .proofs + .iter() + .map(|&x| E::G1Prepared::from(x)) + .zip( + h_vec + .into_iter() + .take(verifier_param.num_vars) + .map(E::G2Prepared::from), + ) + .collect(); + + pairings.push(( + E::G1Prepared::from( + (verifier_param.g.mul(*value) - commitment.commitment.into_projective()).into_affine(), + ), + E::G2Prepared::from(verifier_param.h), + )); + + let res = E::product_of_pairings(pairings.iter()) == E::Fqk::one(); + + end_timer!(pairing_product_timer); + end_timer!(verify_timer); + Ok(res) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::StructuredReferenceString; + use ark_bls12_381::Bls12_381; + use ark_ec::PairingEngine; + use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; + use ark_std::{rand::RngCore, test_rng, vec::Vec, UniformRand}; + type E = Bls12_381; + type Fr = ::Fr; + + fn test_single_helper( + params: &(MultilinearUniversalParams, UnivariateUniversalParams), + poly: &DenseMultilinearExtension, + rng: &mut R, + ) -> Result<(), PCSErrors> { + let nv = poly.num_vars(); + assert_ne!(nv, 0); + let uni_degree = 1; + let (uni_ck, uni_vk) = params.1.trim(uni_degree)?; + let (ml_ck, ml_vk) = params.0.trim(nv)?; + let ck = (ml_ck, uni_ck); + let vk = (ml_vk, uni_vk); + + let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect(); + let com = KZGMultilinearPCS::commit(&ck, poly)?; + let (proof, value) = KZGMultilinearPCS::open(&ck, poly, &point)?; + + assert!(KZGMultilinearPCS::verify( + &vk, &com, &point, &value, &proof + )?); + + let value = Fr::rand(rng); + assert!(!KZGMultilinearPCS::verify( + &vk, &com, &point, &value, &proof + )?); + + Ok(()) + } + + #[test] + fn test_single_commit() -> Result<(), PCSErrors> { + let mut rng = test_rng(); + + let params = KZGMultilinearPCS::::gen_srs_for_testing(&mut rng, 10)?; + + // normal polynomials + let poly1 = DenseMultilinearExtension::rand(8, &mut rng); + test_single_helper(¶ms, &poly1, &mut rng)?; + + // single-variate polynomials + let poly2 = DenseMultilinearExtension::rand(1, &mut rng); + test_single_helper(¶ms, &poly2, &mut rng)?; + + Ok(()) + } + + #[test] + fn setup_commit_verify_constant_polynomial() { + let mut rng = test_rng(); + + // normal polynomials + assert!(KZGMultilinearPCS::::gen_srs_for_testing(&mut rng, 0).is_err()); + } +} diff --git a/pcs/src/multilinear_kzg/srs.rs b/pcs/src/multilinear_kzg/srs.rs new file mode 100644 index 0000000..7e7a93c --- /dev/null +++ b/pcs/src/multilinear_kzg/srs.rs @@ -0,0 +1,258 @@ +//! Implementing Structured Reference Strings for multilinear polynomial KZG + +use crate::{prelude::PCSErrors, StructuredReferenceString}; +use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve}; +use ark_ff::{Field, PrimeField}; +use ark_poly::DenseMultilinearExtension; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; +use ark_std::{end_timer, rand::RngCore, start_timer, vec::Vec, UniformRand}; +use std::collections::LinkedList; + +/// Evaluations over {0,1}^n for G1 or G2 +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +pub struct Evaluations { + /// The evaluations. + pub evals: Vec, +} + +/// Universal Parameter +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +pub struct MultilinearUniversalParams { + /// prover parameters + pub prover_param: MultilinearProverParam, + /// h^randomness: h^t1, h^t2, ..., **h^{t_nv}** + pub h_mask: Vec, +} + +/// Prover Parameters +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +pub struct MultilinearProverParam { + /// number of variables + pub num_vars: usize, + /// `pp_{num_vars}`, `pp_{num_vars - 1}`, `pp_{num_vars - 2}`, ..., defined + /// by XZZPD19 + pub powers_of_g: Vec>, + /// generator for G1 + pub g: E::G1Affine, + /// generator for G2 + pub h: E::G2Affine, +} + +/// Verifier Parameters +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +pub struct MultilinearVerifierParam { + /// number of variables + pub num_vars: usize, + /// generator of G1 + pub g: E::G1Affine, + /// generator of G2 + pub h: E::G2Affine, + /// h^randomness: h^t1, h^t2, ..., **h^{t_nv}** + pub h_mask: Vec, +} + +impl StructuredReferenceString for MultilinearUniversalParams { + type ProverParam = MultilinearProverParam; + type VerifierParam = MultilinearVerifierParam; + + /// Extract the prover parameters from the public parameters. + fn extract_prover_param(&self, supported_num_vars: usize) -> Self::ProverParam { + let to_reduce = self.prover_param.num_vars - supported_num_vars; + + Self::ProverParam { + powers_of_g: self.prover_param.powers_of_g[to_reduce..].to_vec(), + g: self.prover_param.g, + h: self.prover_param.h, + num_vars: supported_num_vars, + } + } + + /// Extract the verifier parameters from the public parameters. + fn extract_verifier_param(&self, supported_num_vars: usize) -> Self::VerifierParam { + let to_reduce = self.prover_param.num_vars - supported_num_vars; + Self::VerifierParam { + num_vars: supported_num_vars, + g: self.prover_param.g, + h: self.prover_param.h, + h_mask: self.h_mask[to_reduce..].to_vec(), + } + } + + /// Trim the universal parameters to specialize the public parameters + /// for multilinear polynomials to the given `supported_num_vars`, and + /// returns committer key and verifier key. `supported_num_vars` should + /// be in range `1..=params.num_vars` + fn trim( + &self, + supported_num_vars: usize, + ) -> Result<(Self::ProverParam, Self::VerifierParam), PCSErrors> { + if supported_num_vars > self.prover_param.num_vars { + return Err(PCSErrors::InvalidParameters(format!( + "SRS does not support target number of vars {}", + supported_num_vars + ))); + } + + let to_reduce = self.prover_param.num_vars - supported_num_vars; + let ck = Self::ProverParam { + powers_of_g: self.prover_param.powers_of_g[to_reduce..].to_vec(), + g: self.prover_param.g, + h: self.prover_param.h, + num_vars: supported_num_vars, + }; + let vk = Self::VerifierParam { + num_vars: supported_num_vars, + g: self.prover_param.g, + h: self.prover_param.h, + h_mask: self.h_mask[to_reduce..].to_vec(), + }; + Ok((ck, vk)) + } + + /// Build SRS for testing. + /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. + /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. + fn gen_srs_for_testing(rng: &mut R, num_vars: usize) -> Result { + if num_vars == 0 { + return Err(PCSErrors::InvalidParameters( + "constant polynomial not supported".to_string(), + )); + } + + let total_timer = start_timer!(|| "SRS generation"); + + let pp_generation_timer = start_timer!(|| "Prover Param generation"); + + let g = E::G1Projective::rand(rng); + let h = E::G2Projective::rand(rng); + + let mut powers_of_g = Vec::new(); + + let t: Vec<_> = (0..num_vars).map(|_| E::Fr::rand(rng)).collect(); + let scalar_bits = E::Fr::size_in_bits(); + + let mut eq: LinkedList> = + LinkedList::from_iter(eq_extension(&t).into_iter()); + let mut eq_arr = LinkedList::new(); + let mut base = eq.pop_back().unwrap().evaluations; + + for i in (0..num_vars).rev() { + eq_arr.push_front(remove_dummy_variable(&base, i)?); + if i != 0 { + let mul = eq.pop_back().unwrap().evaluations; + base = base + .into_iter() + .zip(mul.into_iter()) + .map(|(a, b)| a * b) + .collect(); + } + } + + let mut pp_powers = Vec::new(); + let mut total_scalars = 0; + for i in 0..num_vars { + let eq = eq_arr.pop_front().unwrap(); + let pp_k_powers = (0..(1 << (num_vars - i))).map(|x| eq[x]); + pp_powers.extend(pp_k_powers); + total_scalars += 1 << (num_vars - i); + } + let window_size = FixedBaseMSM::get_mul_window_size(total_scalars); + let g_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, g); + + let pp_g = E::G1Projective::batch_normalization_into_affine( + &FixedBaseMSM::multi_scalar_mul(scalar_bits, window_size, &g_table, &pp_powers), + ); + + let mut start = 0; + for i in 0..num_vars { + let size = 1 << (num_vars - i); + let pp_k_g = Evaluations { + evals: pp_g[start..(start + size)].to_vec(), + }; + powers_of_g.push(pp_k_g); + start += size; + } + + let pp = Self::ProverParam { + num_vars, + g: g.into_affine(), + h: h.into_affine(), + powers_of_g, + }; + + end_timer!(pp_generation_timer); + + let vp_generation_timer = start_timer!(|| "VP generation"); + let h_mask = { + let window_size = FixedBaseMSM::get_mul_window_size(num_vars); + let h_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, h); + E::G2Projective::batch_normalization_into_affine(&FixedBaseMSM::multi_scalar_mul( + scalar_bits, + window_size, + &h_table, + &t, + )) + }; + end_timer!(vp_generation_timer); + end_timer!(total_timer); + Ok(Self { + prover_param: pp, + h_mask, + }) + } +} + +/// fix first `pad` variables of `poly` represented in evaluation form to zero +fn remove_dummy_variable(poly: &[F], pad: usize) -> Result, PCSErrors> { + if pad == 0 { + return Ok(poly.to_vec()); + } + if !poly.len().is_power_of_two() { + return Err(PCSErrors::InvalidParameters( + "Size of polynomial should be power of two.".to_string(), + )); + } + let nv = ark_std::log2(poly.len()) as usize - pad; + + Ok((0..(1 << nv)).map(|x| poly[x << pad]).collect()) +} + +/// Generate eq(t,x), a product of multilinear polynomials with fixed t. +/// eq(a,b) is takes extensions of a,b in {0,1}^num_vars such that if a and b in +/// {0,1}^num_vars are equal then this polynomial evaluates to 1. +fn eq_extension(t: &[F]) -> Vec> { + let start = start_timer!(|| "eq extension"); + + let dim = t.len(); + let mut result = Vec::new(); + for (i, &ti) in t.iter().enumerate().take(dim) { + let mut poly = Vec::with_capacity(1 << dim); + for x in 0..(1 << dim) { + let xi = if x >> i & 1 == 1 { F::one() } else { F::zero() }; + let ti_xi = ti * xi; + poly.push(ti_xi + ti_xi - xi - ti + F::one()); + } + result.push(DenseMultilinearExtension::from_evaluations_vec(dim, poly)); + } + + end_timer!(start); + result +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_bls12_381::Bls12_381; + use ark_std::test_rng; + type E = Bls12_381; + + #[test] + fn test_srs_gen() -> Result<(), PCSErrors> { + let mut rng = test_rng(); + for nv in 4..10 { + let _ = MultilinearUniversalParams::::gen_srs_for_testing(&mut rng, nv)?; + } + + Ok(()) + } +} diff --git a/pcs/src/util.rs b/pcs/src/multilinear_kzg/util.rs similarity index 84% rename from pcs/src/util.rs rename to pcs/src/multilinear_kzg/util.rs index 39a7452..1846a04 100644 --- a/pcs/src/util.rs +++ b/pcs/src/multilinear_kzg/util.rs @@ -8,6 +8,36 @@ use ark_poly::{ }; use ark_std::{end_timer, log2, start_timer}; use poly_iop::bit_decompose; +use std::cmp::max; + +/// For an MLE w with `mle_num_vars` variables, and `point_len` number of +/// points, compute the degree of the univariate polynomial `q(x):= w(l(x))` +/// where l(x) is a list of polynomials that go through all points. +// uni_degree is computed as `mle_num_vars * point_len`: +// - each l(x) is of degree `point_len` +// - mle has degree one +// - worst case is `\prod_{i=0}^{mle_num_vars-1} l_i(x) < point_len * mle_num_vars` +#[inline] +#[allow(dead_code)] +pub(crate) fn compute_qx_degree(mle_num_vars: usize, point_len: usize) -> usize { + mle_num_vars * point_len +} + +/// get the domain for the univariate polynomial +#[inline] +pub(crate) fn get_uni_domain( + uni_poly_degree: usize, +) -> Result, PCSErrors> { + let domain = match Radix2EvaluationDomain::::new(uni_poly_degree) { + Some(p) => p, + None => { + return Err(PCSErrors::InvalidParameters( + "failed to build radix 2 domain".to_string(), + )) + }, + }; + Ok(domain) +} /// Compute W \circ l. /// @@ -64,9 +94,23 @@ pub(crate) fn get_batched_nv(num_var: usize, polynomials_len: usize) -> usize { num_var + log2(polynomials_len) as usize } +/// Return the SRS size +// We require an SRS that is the greater of the two: +// - Multilinear srs is bounded by the merged MLS size which is `get_batched_nv(num_var, +// num_witnesses)` +// - Univariate srs is bounded by q_x's degree which is `compute_uni_degree(num_vars, +// num_witnesses)` +#[inline] +#[allow(dead_code)] +pub(crate) fn get_srs_size(num_var: usize, num_wires: usize) -> usize { + max( + num_var + log2(num_wires) as usize, + (log2(num_var) as usize + 2) * num_wires, + ) +} /// merge a set of polynomials. Returns an error if the /// polynomials do not share a same number of nvs. -pub(crate) fn merge_polynomials( +pub fn merge_polynomials( polynomials: &[impl MultilinearExtension], ) -> Result, PCSErrors> { let nv = polynomials[0].num_vars(); @@ -93,26 +137,10 @@ pub(crate) fn merge_polynomials( /// polynomials that goes through the points pub(crate) fn build_l( num_var: usize, - points: &[&[F]], + points: &[Vec], + domain: &Radix2EvaluationDomain, ) -> Result>, PCSErrors> { let prefix_len = log2(points.len()) as usize; - - let uni_degree = points.len(); - let small_domain = match Radix2EvaluationDomain::::new(uni_degree) { - Some(p) => p, - None => { - return Err(PCSErrors::InvalidParameters( - "failed to build radix 2 domain".to_string(), - )) - }, - }; - - // The following code print out the roots for testing - // println!("domain root0: {}", small_domain.element(0)); - // println!("domain root1: {}", small_domain.element(1)); - // println!("domain root2: {}", small_domain.element(2)); - // println!("domain root3: {}", small_domain.element(3)); - let mut uni_polys = Vec::new(); // 1.1 build the indexes and the univariate polys that go through the indexes @@ -125,19 +153,56 @@ pub(crate) fn build_l( .map(|x| F::from(x[prefix_len - i - 1])) .collect(); - uni_polys.push(Evaluations::from_vec_and_domain(eval, small_domain).interpolate()); + uni_polys.push(Evaluations::from_vec_and_domain(eval, *domain).interpolate()); } // 1.2 build the actual univariate polys that go through the points for i in 0..num_var { let mut eval: Vec = points.iter().map(|x| x[i]).collect(); - eval.extend_from_slice(vec![F::zero(); small_domain.size as usize - eval.len()].as_slice()); - uni_polys.push(Evaluations::from_vec_and_domain(eval, small_domain).interpolate()) + eval.extend_from_slice(vec![F::zero(); domain.size as usize - eval.len()].as_slice()); + uni_polys.push(Evaluations::from_vec_and_domain(eval, *domain).interpolate()) } Ok(uni_polys) } +/// Input a list of multilinear polynomials and a list of points, +/// generate a list of evaluations. +// Note that this function is only used for testing verifications. +// In practice verifier does not see polynomials, and the `mle_values` +// are included in the `batch_proof`. +#[cfg(test)] +pub(crate) fn generate_evaluations( + polynomials: &[DenseMultilinearExtension], + points: &[Vec], +) -> Result, PCSErrors> { + if polynomials.len() != points.len() { + return Err(PCSErrors::InvalidParameters( + "polynomial length does not match point length".to_string(), + )); + } + + let num_var = polynomials[0].num_vars; + let uni_poly_degree = points.len(); + let merge_poly = merge_polynomials(polynomials)?; + + let domain = get_uni_domain::(uni_poly_degree)?; + let uni_polys = build_l(num_var, points, &domain)?; + let mut mle_values = vec![]; + + for i in 0..uni_poly_degree { + let point: Vec = uni_polys + .iter() + .rev() + .map(|poly| poly.evaluate(&domain.element(i))) + .collect(); + + let mle_value = merge_poly.evaluate(&point).unwrap(); + mle_values.push(mle_value) + } + Ok(mle_values) +} + #[cfg(test)] mod test { use super::*; @@ -341,16 +406,17 @@ mod test { fn test_build_l_helper() -> Result<(), PCSErrors> { // point 1 is [1, 2] - let point1 = [Fr::from(1u64), Fr::from(2u64)]; + let point1 = vec![Fr::from(1u64), Fr::from(2u64)]; // point 2 is [3, 4] - let point2 = [Fr::from(3u64), Fr::from(4u64)]; + let point2 = vec![Fr::from(3u64), Fr::from(4u64)]; // point 3 is [5, 6] - let point3 = [Fr::from(5u64), Fr::from(6u64)]; + let point3 = vec![Fr::from(5u64), Fr::from(6u64)]; { - let l = build_l(2, &[&point1, &point2])?; + let domain = get_uni_domain::(2)?; + let l = build_l(2, &[point1.clone(), point2.clone()], &domain)?; // roots: [1, -1] // l0 = -1/2 * x + 1/2 @@ -369,7 +435,8 @@ mod test { } { - let l = build_l(2, &[&point1, &point2, &point3])?; + let domain = get_uni_domain::(3)?; + let l = build_l(2, &[point1, point2, point3], &domain)?; // sage: q = 52435875175126190479447740508185965837690552500527637822603658699938581184513 // sage: P. = PolynomialRing(Zmod(q)) @@ -512,20 +579,21 @@ mod test { let r = Fr::from(42u64); // point 1 is [1, 2] - let point1 = [Fr::from(1u64), Fr::from(2u64)]; + let point1 = vec![Fr::from(1u64), Fr::from(2u64)]; // point 2 is [3, 4] - let point2 = [Fr::from(3u64), Fr::from(4u64)]; + let point2 = vec![Fr::from(3u64), Fr::from(4u64)]; // point 3 is [5, 6] - let point3 = [Fr::from(5u64), Fr::from(6u64)]; + let point3 = vec![Fr::from(5u64), Fr::from(6u64)]; { + let domain = get_uni_domain::(2)?; // w = (3x1x2 + 2x2)(1-x0) + (x1x2 + x1)x0 // with evaluations: [0,2,0,5,0,0,1,2] let w = merge_polynomials(&[w1.clone(), w2.clone()])?; - let l = build_l(2, &[&point1, &point2])?; + let l = build_l(2, &[point1.clone(), point2.clone()], &domain)?; // sage: P. = PolynomialRing(ZZ) // sage: l0 = -1/2 * x + 1/2 @@ -548,12 +616,13 @@ mod test { } { + let domain = get_uni_domain::(3)?; // W = (3x1x2 + 2x2) * (1-y1) * (1-y2) // + (x1x2 + x1) * (1-y1) * y2 // + (x1 + x2) * y1 * (1-y2) let w = merge_polynomials(&[w1, w2, w3])?; - let l = build_l(2, &[&point1, &point2, &point3])?; + let l = build_l(2, &[point1, point2, point3], &domain)?; // l0 = // 13108968793781547619861935127046491459422638125131909455650914674984645296128*x^3 + diff --git a/pcs/src/param.rs b/pcs/src/param.rs index f84a623..47fc0f2 100644 --- a/pcs/src/param.rs +++ b/pcs/src/param.rs @@ -1,252 +1,3 @@ -use std::collections::LinkedList; - -use crate::PCSErrors; -use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve}; -use ark_ff::{Field, PrimeField}; -use ark_poly::DenseMultilinearExtension; +use ark_ec::{AffineCurve, PairingEngine}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; -use ark_std::{end_timer, rand::RngCore, start_timer, vec::Vec, UniformRand}; - -/// Evaluations over {0,1}^n for G1 or G2 -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -pub struct Evaluations { - /// The evaluations. - pub evals: Vec, -} - -/// Universal Parameter -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -pub struct UniversalParams { - /// prover parameters - pub prover_param: ProverParam, - /// h^randomness: h^t1, h^t2, ..., **h^{t_nv}** - pub h_mask: Vec, -} - -/// Prover Parameters -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -pub struct ProverParam { - /// number of variables - pub num_vars: usize, - /// `pp_{num_vars}`, `pp_{num_vars - 1}`, `pp_{num_vars - 2}`, ..., defined - /// by XZZPD19 - pub powers_of_g: Vec>, - /// generator for G1 - pub g: E::G1Affine, - /// generator for G2 - pub h: E::G2Affine, -} - -/// Verifier Parameters -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -pub struct VerifierParam { - /// number of variables - pub num_vars: usize, - - /// generator of G1 - pub g: E::G1Affine, - - /// generator of G2 - pub h: E::G2Affine, - - /// h^randomness: h^t1, h^t2, ..., **h^{t_nv}** - pub h_mask: Vec, -} - -impl UniversalParams { - /// Extract the prover parameters from the public parameters. - pub fn extract_prover_param(&self) -> ProverParam { - self.prover_param.clone() - } - - /// Extract the verifier parameters from the public parameters. - pub fn extract_verifier_param(&self) -> VerifierParam { - VerifierParam { - num_vars: self.prover_param.num_vars, - g: self.prover_param.g, - h: self.prover_param.h, - h_mask: self.h_mask.clone(), - } - } - - /// Trim the universal parameters to specialize the public parameters - /// for multilinear polynomials to the given `supported_num_vars`, and - /// returns committer key and verifier key. `supported_num_vars` should - /// be in range `1..=params.num_vars` - pub fn trim( - &self, - supported_num_vars: usize, - ) -> Result<(ProverParam, VerifierParam), PCSErrors> { - if supported_num_vars > self.prover_param.num_vars { - return Err(PCSErrors::InvalidParameters(format!( - "SRS does not support target number of vars {}", - supported_num_vars - ))); - } - - let to_reduce = self.prover_param.num_vars - supported_num_vars; - let ck = ProverParam { - powers_of_g: self.prover_param.powers_of_g[to_reduce..].to_vec(), - g: self.prover_param.g, - h: self.prover_param.h, - num_vars: supported_num_vars, - }; - let vk = VerifierParam { - num_vars: supported_num_vars, - g: self.prover_param.g, - h: self.prover_param.h, - h_mask: self.h_mask[to_reduce..].to_vec(), - }; - Ok((ck, vk)) - } - - /// Build SRS for testing. - /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. - /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. - pub fn gen_srs_for_testing( - rng: &mut R, - num_vars: usize, - ) -> Result { - if num_vars == 0 { - return Err(PCSErrors::InvalidParameters( - "constant polynomial not supported".to_string(), - )); - } - - let total_timer = start_timer!(|| "SRS generation"); - - let pp_generation_timer = start_timer!(|| "Prover Param generation"); - - let g = E::G1Projective::rand(rng); - let h = E::G2Projective::rand(rng); - - let mut powers_of_g = Vec::new(); - - let t: Vec<_> = (0..num_vars).map(|_| E::Fr::rand(rng)).collect(); - let scalar_bits = E::Fr::size_in_bits(); - - let mut eq: LinkedList> = - LinkedList::from_iter(eq_extension(&t).into_iter()); - let mut eq_arr = LinkedList::new(); - let mut base = eq.pop_back().unwrap().evaluations; - - for i in (0..num_vars).rev() { - eq_arr.push_front(remove_dummy_variable(&base, i)?); - if i != 0 { - let mul = eq.pop_back().unwrap().evaluations; - base = base - .into_iter() - .zip(mul.into_iter()) - .map(|(a, b)| a * b) - .collect(); - } - } - - let mut pp_powers = Vec::new(); - let mut total_scalars = 0; - for i in 0..num_vars { - let eq = eq_arr.pop_front().unwrap(); - let pp_k_powers = (0..(1 << (num_vars - i))).map(|x| eq[x]); - pp_powers.extend(pp_k_powers); - total_scalars += 1 << (num_vars - i); - } - let window_size = FixedBaseMSM::get_mul_window_size(total_scalars); - let g_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, g); - - let pp_g = E::G1Projective::batch_normalization_into_affine( - &FixedBaseMSM::multi_scalar_mul(scalar_bits, window_size, &g_table, &pp_powers), - ); - - let mut start = 0; - for i in 0..num_vars { - let size = 1 << (num_vars - i); - let pp_k_g = Evaluations { - evals: pp_g[start..(start + size)].to_vec(), - }; - powers_of_g.push(pp_k_g); - start += size; - } - - let pp = ProverParam { - num_vars, - g: g.into_affine(), - h: h.into_affine(), - powers_of_g, - }; - - end_timer!(pp_generation_timer); - - let vp_generation_timer = start_timer!(|| "VP generation"); - let h_mask = { - let window_size = FixedBaseMSM::get_mul_window_size(num_vars); - let h_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, h); - E::G2Projective::batch_normalization_into_affine(&FixedBaseMSM::multi_scalar_mul( - scalar_bits, - window_size, - &h_table, - &t, - )) - }; - end_timer!(vp_generation_timer); - end_timer!(total_timer); - Ok(Self { - prover_param: pp, - h_mask, - }) - } -} - -/// fix first `pad` variables of `poly` represented in evaluation form to zero -fn remove_dummy_variable(poly: &[F], pad: usize) -> Result, PCSErrors> { - if pad == 0 { - return Ok(poly.to_vec()); - } - if !poly.len().is_power_of_two() { - return Err(PCSErrors::InvalidParameters( - "Size of polynomial should be power of two.".to_string(), - )); - } - let nv = ark_std::log2(poly.len()) as usize - pad; - - Ok((0..(1 << nv)).map(|x| poly[x << pad]).collect()) -} - -/// Generate eq(t,x), a product of multilinear polynomials with fixed t. -/// eq(a,b) is takes extensions of a,b in {0,1}^num_vars such that if a and b in -/// {0,1}^num_vars are equal then this polynomial evaluates to 1. -fn eq_extension(t: &[F]) -> Vec> { - let start = start_timer!(|| "eq extension"); - - let dim = t.len(); - let mut result = Vec::new(); - for (i, &ti) in t.iter().enumerate().take(dim) { - let mut poly = Vec::with_capacity(1 << dim); - for x in 0..(1 << dim) { - let xi = if x >> i & 1 == 1 { F::one() } else { F::zero() }; - let ti_xi = ti * xi; - poly.push(ti_xi + ti_xi - xi - ti + F::one()); - } - result.push(DenseMultilinearExtension::from_evaluations_vec(dim, poly)); - } - - end_timer!(start); - result -} - -#[cfg(test)] -mod tests { - use super::*; - use ark_bls12_381::Bls12_381; - use ark_std::test_rng; - type E = Bls12_381; - - #[test] - fn test_srs_gen() -> Result<(), PCSErrors> { - let mut rng = test_rng(); - for nv in 4..10 { - let _ = UniversalParams::::gen_srs_for_testing(&mut rng, nv)?; - } - - Ok(()) - } -} +use ark_std::vec::Vec; diff --git a/pcs/src/prelude.rs b/pcs/src/prelude.rs new file mode 100644 index 0000000..71acec5 --- /dev/null +++ b/pcs/src/prelude.rs @@ -0,0 +1,13 @@ +pub use crate::{ + errors::PCSErrors, + multilinear_kzg::{ + srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam}, + util::merge_polynomials, + BatchProof, KZGMultilinearPCS, Proof, + }, + structs::Commitment, + univariate_kzg::srs::{ + UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam, + }, + PolynomialCommitmentScheme, StructuredReferenceString, +}; diff --git a/pcs/src/structs.rs b/pcs/src/structs.rs new file mode 100644 index 0000000..915c52d --- /dev/null +++ b/pcs/src/structs.rs @@ -0,0 +1,9 @@ +use ark_ec::PairingEngine; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; + +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, Default, PartialEq)] +/// A commitment is an Affine point. +pub struct Commitment { + /// the actual commitment is an affine point. + pub commitment: E::G1Affine, +} diff --git a/pcs/src/univariate_kzg/mod.rs b/pcs/src/univariate_kzg/mod.rs new file mode 100644 index 0000000..5a18b52 --- /dev/null +++ b/pcs/src/univariate_kzg/mod.rs @@ -0,0 +1,384 @@ +//! Main module for univariate KZG commitment scheme + +use crate::{ + prelude::{Commitment, PCSErrors}, + PolynomialCommitmentScheme, StructuredReferenceString, +}; +use ark_ec::{msm::VariableBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve}; +use ark_ff::PrimeField; +use ark_poly::{univariate::DensePolynomial, Polynomial, UVPolynomial}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; +use ark_std::{end_timer, rand::RngCore, start_timer, One, UniformRand, Zero}; +use srs::{UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam}; +use std::marker::PhantomData; + +pub(crate) mod srs; + +/// KZG Polynomial Commitment Scheme on univariate polynomial. +pub struct KZGUnivariatePCS { + #[doc(hidden)] + phantom: PhantomData, +} + +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +/// proof of opening +pub struct KZGUnivariateOpening { + /// Evaluation of quotients + pub proof: E::G1Affine, +} + +impl PolynomialCommitmentScheme for KZGUnivariatePCS { + // Parameters + type ProverParam = UnivariateProverParam; + type VerifierParam = UnivariateVerifierParam; + type SRS = UnivariateUniversalParams; + // Polynomial and its associated types + type Polynomial = DensePolynomial; + type Point = E::Fr; + type Evaluation = E::Fr; + // Polynomial and its associated types + type Commitment = Commitment; + type Proof = KZGUnivariateOpening; + type BatchProof = Vec; + type BatchCommitment = Vec; + + /// Build SRS for testing. + /// + /// - For univariate polynomials, `log_size` is the log of maximum degree. + /// - For multilinear polynomials, `log_size` is the number of variables. + /// + /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. + /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. + fn gen_srs_for_testing( + rng: &mut R, + log_size: usize, + ) -> Result { + Self::SRS::gen_srs_for_testing(rng, log_size) + } + + /// Generate a commitment for a polynomial + /// Note that the scheme is not hidding + fn commit( + prover_param: &Self::ProverParam, + poly: &Self::Polynomial, + ) -> Result { + let commit_time = + start_timer!(|| format!("Committing to polynomial of degree {} ", poly.degree())); + + if poly.degree() > prover_param.powers_of_g.len() { + return Err(PCSErrors::InvalidParameters(format!( + "poly degree {} is larger than allowed {}", + poly.degree(), + prover_param.powers_of_g.len() + ))); + } + + let (num_leading_zeros, plain_coeffs) = skip_leading_zeros_and_convert_to_bigints(poly); + + let msm_time = start_timer!(|| "MSM to compute commitment to plaintext poly"); + let commitment = VariableBaseMSM::multi_scalar_mul( + &prover_param.powers_of_g[num_leading_zeros..], + &plain_coeffs, + ) + .into_affine(); + end_timer!(msm_time); + + end_timer!(commit_time); + Ok(Commitment { commitment }) + } + + /// Generate a commitment for a list of polynomials + fn multi_commit( + prover_param: &Self::ProverParam, + polys: &[Self::Polynomial], + ) -> Result { + let commit_time = start_timer!(|| format!("batch commit {} polynomials", polys.len())); + let res = polys + .iter() + .map(|poly| Self::commit(prover_param, poly)) + .collect::, PCSErrors>>()?; + + end_timer!(commit_time); + Ok(res) + } + + /// On input a polynomial `p` and a point `point`, outputs a proof for the + /// same. + fn open( + prover_param: &Self::ProverParam, + polynomial: &Self::Polynomial, + point: &Self::Point, + ) -> Result<(Self::Proof, Self::Evaluation), PCSErrors> { + let open_time = + start_timer!(|| format!("Opening polynomial of degree {}", polynomial.degree())); + let divisor = Self::Polynomial::from_coefficients_vec(vec![-*point, E::Fr::one()]); + + let witness_time = start_timer!(|| "Computing witness polynomial"); + let witness_polynomial = polynomial / &divisor; + end_timer!(witness_time); + + let (num_leading_zeros, witness_coeffs) = + skip_leading_zeros_and_convert_to_bigints(&witness_polynomial); + + let proof = VariableBaseMSM::multi_scalar_mul( + &prover_param.powers_of_g[num_leading_zeros..], + &witness_coeffs, + ) + .into_affine(); + + let eval = polynomial.evaluate(point); + + end_timer!(open_time); + Ok((Self::Proof { proof }, eval)) + } + + /// Input a list of polynomials, and a same number of points, + /// compute a multi-opening for all the polynomials. + // This is a naive approach + // TODO: to implement the more efficient batch opening algorithm + // (e.g., the appendix C.4 in https://eprint.iacr.org/2020/1536.pdf) + fn multi_open( + prover_param: &Self::ProverParam, + _multi_commitment: &Self::Commitment, + polynomials: &[Self::Polynomial], + points: &[Self::Point], + ) -> Result<(Self::BatchProof, Vec), PCSErrors> { + let open_time = start_timer!(|| format!("batch opening {} polynomials", polynomials.len())); + if polynomials.len() != points.len() { + return Err(PCSErrors::InvalidParameters(format!( + "poly length {} is different from points length {}", + polynomials.len(), + points.len() + ))); + } + let mut batch_proof = vec![]; + let mut evals = vec![]; + for (poly, point) in polynomials.iter().zip(points.iter()) { + let (proof, eval) = Self::open(prover_param, poly, point)?; + batch_proof.push(proof); + evals.push(eval); + } + + end_timer!(open_time); + Ok((batch_proof, evals)) + } + /// Verifies that `value` is the evaluation at `x` of the polynomial + /// committed inside `comm`. + fn verify( + verifier_param: &Self::VerifierParam, + commitment: &Self::Commitment, + point: &Self::Point, + value: &E::Fr, + proof: &Self::Proof, + ) -> Result { + let check_time = start_timer!(|| "Checking evaluation"); + let pairing_inputs: Vec<(E::G1Prepared, E::G2Prepared)> = vec![ + ( + (verifier_param.g.mul(value.into_repr()) + - proof.proof.mul(point.into_repr()) + - commitment.commitment.into_projective()) + .into_affine() + .into(), + verifier_param.h.into(), + ), + (proof.proof.into(), verifier_param.beta_h.into()), + ]; + + let res = E::product_of_pairings(pairing_inputs.iter()).is_one(); + + end_timer!(check_time, || format!("Result: {}", res)); + Ok(res) + } + + /// Verifies that `value_i` is the evaluation at `x_i` of the polynomial + /// `poly_i` committed inside `comm`. + // This is a naive approach + // TODO: to implement the more efficient batch verification algorithm + // (e.g., the appendix C.4 in https://eprint.iacr.org/2020/1536.pdf) + fn batch_verify( + verifier_param: &Self::VerifierParam, + multi_commitment: &Self::BatchCommitment, + points: &[Self::Point], + values: &[E::Fr], + batch_proof: &Self::BatchProof, + rng: &mut R, + ) -> Result { + let check_time = + start_timer!(|| format!("Checking {} evaluation proofs", multi_commitment.len())); + + let mut total_c = ::zero(); + let mut total_w = ::zero(); + + let combination_time = start_timer!(|| "Combining commitments and proofs"); + let mut randomizer = E::Fr::one(); + // Instead of multiplying g and gamma_g in each turn, we simply accumulate + // their coefficients and perform a final multiplication at the end. + let mut g_multiplier = E::Fr::zero(); + for (((c, z), v), proof) in multi_commitment + .iter() + .zip(points) + .zip(values) + .zip(batch_proof) + { + let w = proof.proof; + let mut temp = w.mul(*z); + temp.add_assign_mixed(&c.commitment); + let c = temp; + g_multiplier += &(randomizer * v); + total_c += &c.mul(randomizer.into_repr()); + total_w += &w.mul(randomizer.into_repr()); + // We don't need to sample randomizers from the full field, + // only from 128-bit strings. + randomizer = u128::rand(rng).into(); + } + total_c -= &verifier_param.g.mul(g_multiplier); + end_timer!(combination_time); + + let to_affine_time = start_timer!(|| "Converting results to affine for pairing"); + let affine_points = E::G1Projective::batch_normalization_into_affine(&[-total_w, total_c]); + let (total_w, total_c) = (affine_points[0], affine_points[1]); + end_timer!(to_affine_time); + + let pairing_time = start_timer!(|| "Performing product of pairings"); + let result = E::product_of_pairings(&[ + (total_w.into(), verifier_param.beta_h.into()), + (total_c.into(), verifier_param.h.into()), + ]) + .is_one(); + end_timer!(pairing_time); + end_timer!(check_time, || format!("Result: {}", result)); + Ok(result) + } +} + +fn skip_leading_zeros_and_convert_to_bigints>( + p: &P, +) -> (usize, Vec) { + let mut num_leading_zeros = 0; + while num_leading_zeros < p.coeffs().len() && p.coeffs()[num_leading_zeros].is_zero() { + num_leading_zeros += 1; + } + let coeffs = convert_to_bigints(&p.coeffs()[num_leading_zeros..]); + (num_leading_zeros, coeffs) +} + +fn convert_to_bigints(p: &[F]) -> Vec { + let to_bigint_time = start_timer!(|| "Converting polynomial coeffs to bigints"); + let coeffs = p.iter().map(|s| s.into_repr()).collect::>(); + end_timer!(to_bigint_time); + coeffs +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::StructuredReferenceString; + use ark_bls12_381::Bls12_381; + use ark_ec::PairingEngine; + use ark_poly::univariate::DensePolynomial; + use ark_std::{log2, test_rng, UniformRand}; + + fn end_to_end_test_template() -> Result<(), PCSErrors> + where + E: PairingEngine, + { + let rng = &mut test_rng(); + for _ in 0..100 { + let mut degree = 0; + while degree <= 1 { + degree = usize::rand(rng) % 20; + } + let log_degree = log2(degree) as usize; + let pp = KZGUnivariatePCS::::gen_srs_for_testing(rng, log_degree)?; + let (ck, vk) = pp.trim(log_degree)?; + let p = as UVPolynomial>::rand(degree, rng); + let comm = KZGUnivariatePCS::::commit(&ck, &p)?; + let point = E::Fr::rand(rng); + let (proof, value) = KZGUnivariatePCS::::open(&ck, &p, &point)?; + assert!( + KZGUnivariatePCS::::verify(&vk, &comm, &point, &value, &proof)?, + "proof was incorrect for max_degree = {}, polynomial_degree = {}", + degree, + p.degree(), + ); + } + Ok(()) + } + + fn linear_polynomial_test_template() -> Result<(), PCSErrors> + where + E: PairingEngine, + { + let rng = &mut test_rng(); + for _ in 0..100 { + let degree = 50; + let log_degree = log2(degree) as usize; + + let pp = KZGUnivariatePCS::::gen_srs_for_testing(rng, log_degree)?; + let (ck, vk) = pp.trim(log_degree)?; + let p = as UVPolynomial>::rand(degree, rng); + let comm = KZGUnivariatePCS::::commit(&ck, &p)?; + let point = E::Fr::rand(rng); + let (proof, value) = KZGUnivariatePCS::::open(&ck, &p, &point)?; + assert!( + KZGUnivariatePCS::::verify(&vk, &comm, &point, &value, &proof)?, + "proof was incorrect for max_degree = {}, polynomial_degree = {}", + degree, + p.degree(), + ); + } + Ok(()) + } + + fn batch_check_test_template() -> Result<(), PCSErrors> + where + E: PairingEngine, + { + let rng = &mut test_rng(); + for _ in 0..10 { + let mut degree = 0; + while degree <= 1 { + degree = usize::rand(rng) % 20; + } + let log_degree = log2(degree) as usize; + let pp = KZGUnivariatePCS::::gen_srs_for_testing(rng, log_degree)?; + let (ck, vk) = pp.trim(log_degree)?; + let mut comms = Vec::new(); + let mut values = Vec::new(); + let mut points = Vec::new(); + let mut proofs = Vec::new(); + for _ in 0..10 { + let p = as UVPolynomial>::rand(degree, rng); + let comm = KZGUnivariatePCS::::commit(&ck, &p)?; + let point = E::Fr::rand(rng); + let (proof, value) = KZGUnivariatePCS::::open(&ck, &p, &point)?; + + assert!(KZGUnivariatePCS::::verify( + &vk, &comm, &point, &value, &proof + )?); + comms.push(comm); + values.push(value); + points.push(point); + proofs.push(proof); + } + assert!(KZGUnivariatePCS::::batch_verify( + &vk, &comms, &points, &values, &proofs, rng + )?); + } + Ok(()) + } + + #[test] + fn end_to_end_test() { + end_to_end_test_template::().expect("test failed for bls12-381"); + } + + #[test] + fn linear_polynomial_test() { + linear_polynomial_test_template::().expect("test failed for bls12-381"); + } + #[test] + fn batch_check_test() { + batch_check_test_template::().expect("test failed for bls12-381"); + } +} diff --git a/pcs/src/univariate_kzg/srs.rs b/pcs/src/univariate_kzg/srs.rs new file mode 100644 index 0000000..e2791e4 --- /dev/null +++ b/pcs/src/univariate_kzg/srs.rs @@ -0,0 +1,127 @@ +//! Implementing Structured Reference Strings for univariate polynomial KZG + +use crate::{prelude::PCSErrors, StructuredReferenceString}; +use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve}; +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; +use ark_std::{end_timer, rand::RngCore, start_timer, One, UniformRand}; + +/// `UniversalParams` are the universal parameters for the KZG10 scheme. +/// Adapted from +/// https://github.com/arkworks-rs/poly-commit/blob/master/src/kzg10/data_structures.rs#L20 +#[derive(Clone, Debug)] +pub struct UnivariateUniversalParams { + /// Group elements of the form `{ \beta^i G }`, where `i` ranges from 0 to + /// `degree`. + pub powers_of_g: Vec, + /// The generator of G2. + pub h: E::G2Affine, + /// \beta times the above generator of G2. + pub beta_h: E::G2Affine, +} + +/// Prover Parameters +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +pub struct UnivariateProverParam { + pub powers_of_g: Vec, +} + +/// `VerifierKey` is used to check evaluation proofs for a given commitment. +/// https://github.com/arkworks-rs/poly-commit/blob/master/src/kzg10/data_structures.rs#L236 +#[derive(Clone, Debug)] +pub struct UnivariateVerifierParam { + /// The generator of G1. + pub g: E::G1Affine, + /// The generator of G2. + pub h: E::G2Affine, + /// \beta times the above generator of G2. + pub beta_h: E::G2Affine, +} + +impl StructuredReferenceString for UnivariateUniversalParams { + type ProverParam = UnivariateProverParam; + type VerifierParam = UnivariateVerifierParam; + + /// Extract the prover parameters from the public parameters. + fn extract_prover_param(&self, supported_log_size: usize) -> Self::ProverParam { + let support_size = 1usize << supported_log_size; + let powers_of_g = self.powers_of_g[..=support_size].to_vec(); + + Self::ProverParam { powers_of_g } + } + + /// Extract the verifier parameters from the public parameters. + fn extract_verifier_param(&self, _supported_log_size: usize) -> Self::VerifierParam { + Self::VerifierParam { + g: self.powers_of_g[0], + h: self.h, + beta_h: self.beta_h, + } + } + + /// Trim the universal parameters to specialize the public parameters + /// for univariate polynomials to the given `supported_log_size`, and + /// returns committer key and verifier key. `supported_log_size` should + /// be in range `1..log(params.len())` + fn trim( + &self, + supported_log_size: usize, + ) -> Result<(Self::ProverParam, Self::VerifierParam), PCSErrors> { + let support_size = 1usize << supported_log_size; + let powers_of_g = self.powers_of_g[..=support_size].to_vec(); + + let pk = Self::ProverParam { powers_of_g }; + let vk = Self::VerifierParam { + g: self.powers_of_g[0], + h: self.h, + beta_h: self.beta_h, + }; + Ok((pk, vk)) + } + + /// Build SRS for testing. + /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. + /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. + fn gen_srs_for_testing(rng: &mut R, log_degree: usize) -> Result { + let max_degree = 1usize << log_degree; + let setup_time = start_timer!(|| format!("KZG10::Setup with degree {}", max_degree)); + let beta = E::Fr::rand(rng); + let g = E::G1Projective::rand(rng); + let h = E::G2Projective::rand(rng); + + let mut powers_of_beta = vec![E::Fr::one()]; + + let mut cur = beta; + for _ in 0..max_degree { + powers_of_beta.push(cur); + cur *= β + } + + let window_size = FixedBaseMSM::get_mul_window_size(max_degree + 1); + + let scalar_bits = E::Fr::size_in_bits(); + let g_time = start_timer!(|| "Generating powers of G"); + // TODO: parallelization + let g_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, g); + let powers_of_g = FixedBaseMSM::multi_scalar_mul::( + scalar_bits, + window_size, + &g_table, + &powers_of_beta, + ); + end_timer!(g_time); + + let powers_of_g = E::G1Projective::batch_normalization_into_affine(&powers_of_g); + + let h = h.into_affine(); + let beta_h = h.mul(beta).into_affine(); + + let pp = Self { + powers_of_g, + h, + beta_h, + }; + end_timer!(setup_time); + Ok(pp) + } +} diff --git a/poly-iop/src/transcript.rs b/poly-iop/src/transcript.rs index 0a455d2..7202379 100644 --- a/poly-iop/src/transcript.rs +++ b/poly-iop/src/transcript.rs @@ -4,6 +4,7 @@ //! TODO(ZZ): decide which APIs need to be public. use ark_ff::PrimeField; +use ark_serialize::CanonicalSerialize; use merlin::Transcript; use std::marker::PhantomData; @@ -67,6 +68,15 @@ impl IOPTranscript { self.append_message(label, &to_bytes!(field_elem)?) } + // Append the message to the transcript. + pub fn append_serializable_element( + &mut self, + label: &'static [u8], + group_elem: &S, + ) -> Result<(), PolyIOPErrors> { + self.append_message(label, &to_bytes!(group_elem)?) + } + // Append a prover message to the transcript. pub(crate) fn append_prover_message( &mut self,