// Copyright (c) 2022 Espresso Systems (espressosys.com) // This file is part of the Jellyfish library. // You should have received a copy of the MIT License // along with the Jellyfish library. If not, see . //! Main module for univariate KZG commitment scheme use crate::{prelude::Commitment, PCSError, 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::{ borrow::Borrow, end_timer, format, marker::PhantomData, rand::{CryptoRng, RngCore}, start_timer, string::ToString, vec, vec::Vec, One, UniformRand, Zero, }; #[cfg(feature = "parallel")] use rayon::prelude::*; use srs::{UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam}; use util::parallelizable_slice_iter; pub(crate) mod srs; /// KZG Polynomial Commitment Scheme on univariate polynomial. pub struct UnivariateKzgPCS { #[doc(hidden)] phantom: PhantomData, } #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)] /// proof of opening pub struct UnivariateKzgProof { /// Evaluation of quotients pub proof: E::G1Affine, } /// batch proof pub type UnivariateKzgBatchProof = Vec>; impl PolynomialCommitmentScheme for UnivariateKzgPCS { // 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 BatchCommitment = Vec; type Proof = UnivariateKzgProof; type BatchProof = UnivariateKzgBatchProof; /// Build SRS for testing. /// /// - For univariate polynomials, `supported_size` is the maximum degree. /// /// 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, supported_size: usize, ) -> Result { Self::SRS::gen_srs_for_testing(rng, supported_size) } /// Trim the universal parameters to specialize the public parameters. /// Input `max_degree` for univariate. /// `supported_num_vars` must be None or an error is returned. fn trim( srs: impl Borrow, supported_degree: usize, supported_num_vars: Option, ) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError> { if supported_num_vars.is_some() { return Err(PCSError::InvalidParameters( "univariate should not receive a num_var param".to_string(), )); } srs.borrow().trim(supported_degree) } /// Generate a commitment for a polynomial /// Note that the scheme is not hidding fn commit( prover_param: impl Borrow, poly: &Self::Polynomial, ) -> Result { let prover_param = prover_param.borrow(); let commit_time = start_timer!(|| format!("Committing to polynomial of degree {} ", poly.degree())); if poly.degree() >= prover_param.powers_of_g.len() { return Err(PCSError::InvalidParameters(format!( "uni 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: impl Borrow, polys: &[Self::Polynomial], ) -> Result { let prover_param = prover_param.borrow(); let commit_time = start_timer!(|| format!("batch commit {} polynomials", polys.len())); let res = parallelizable_slice_iter(polys) .map(|poly| Self::commit(prover_param, poly)) .collect::, PCSError>>()?; 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: impl Borrow, polynomial: &Self::Polynomial, point: &Self::Point, ) -> Result<(Self::Proof, Self::Evaluation), PCSError> { 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.borrow().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: impl Borrow, _multi_commitment: &Self::BatchCommitment, polynomials: &[Self::Polynomial], points: &[Self::Point], ) -> Result<(Self::BatchProof, Vec), PCSError> { let open_time = start_timer!(|| format!("batch opening {} polynomials", polynomials.len())); if polynomials.len() != points.len() { return Err(PCSError::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.borrow(), poly, point)?; batch_proof.push(proof); evals.push(eval); } end_timer!(open_time); Ok((batch_proof, evals)) } /// Input a multilinear extension, and a number of points, and /// a transcript, compute a multi-opening for all the polynomials. fn multi_open_single_poly( _prover_param: impl Borrow, _commitment: &Self::Commitment, _polynomials: &Self::Polynomial, _points: &[Self::Point], ) -> Result<(Self::BatchProof, Vec), PCSError> { unimplemented!() } /// 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.0.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.0); 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) } /// Verifies that `value_i` is the evaluation at `x_i` of the polynomial /// `poly` committed inside `comm`. fn batch_verify_single_poly( _verifier_param: &Self::VerifierParam, _commitment: &Self::Commitment, _points: &[Self::Point], _values: &[E::Fr], _batch_proof: &Self::BatchProof, ) -> Result { unimplemented!() } } 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::{test_rng, UniformRand}; fn end_to_end_test_template() -> Result<(), PCSError> 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 pp = UnivariateKzgPCS::::gen_srs_for_testing(rng, degree)?; let (ck, vk) = pp.trim(degree)?; let p = as UVPolynomial>::rand(degree, rng); let comm = UnivariateKzgPCS::::commit(&ck, &p)?; let point = E::Fr::rand(rng); let (proof, value) = UnivariateKzgPCS::::open(&ck, &p, &point)?; assert!( UnivariateKzgPCS::::verify(&vk, &comm, &point, &value, &proof)?, "proof was incorrect for max_degree = {}, polynomial_degree = {}", degree, p.degree(), ); } Ok(()) } fn linear_polynomial_test_template() -> Result<(), PCSError> where E: PairingEngine, { let rng = &mut test_rng(); for _ in 0..100 { let degree = 50; let pp = UnivariateKzgPCS::::gen_srs_for_testing(rng, degree)?; let (ck, vk) = pp.trim(degree)?; let p = as UVPolynomial>::rand(degree, rng); let comm = UnivariateKzgPCS::::commit(&ck, &p)?; let point = E::Fr::rand(rng); let (proof, value) = UnivariateKzgPCS::::open(&ck, &p, &point)?; assert!( UnivariateKzgPCS::::verify(&vk, &comm, &point, &value, &proof)?, "proof was incorrect for max_degree = {}, polynomial_degree = {}", degree, p.degree(), ); } Ok(()) } fn batch_check_test_template() -> Result<(), PCSError> 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 pp = UnivariateKzgPCS::::gen_srs_for_testing(rng, degree)?; let (ck, vk) = UnivariateKzgPCS::::trim(&pp, degree, None)?; 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 = UnivariateKzgPCS::::commit(&ck, &p)?; let point = E::Fr::rand(rng); let (proof, value) = UnivariateKzgPCS::::open(&ck, &p, &point)?; assert!(UnivariateKzgPCS::::verify( &vk, &comm, &point, &value, &proof )?); comms.push(comm); values.push(value); points.push(point); proofs.push(proof); } assert!(UnivariateKzgPCS::::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"); } }