enabling batch opening and mock tests (#80)

- add mock circuits
- add vanilla and jellyfish plonk gates
- performance tuning
This commit is contained in:
zhenfei
2022-09-27 14:51:30 -04:00
committed by GitHub
parent 3160ef17f2
commit baaa06b07b
51 changed files with 5637 additions and 1388 deletions

51
pcs/Cargo.toml Normal file
View File

@@ -0,0 +1,51 @@
[package]
name = "pcs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ark-std = { version = "^0.3.0", default-features = false }
ark-serialize = { version = "^0.3.0", default-features = false, features = [ "derive" ] }
ark-ff = { version = "^0.3.0", default-features = false }
ark-ec = { version = "^0.3.0", default-features = false }
ark-poly = {version = "^0.3.0", default-features = false }
ark-sponge = {version = "^0.3.0", default-features = false}
ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "curve" ] }
displaydoc = { version = "0.2.3", default-features = false }
derivative = { version = "2", features = ["use_core"] }
arithmetic = { path = "../arithmetic" }
transcript = { path = "../transcript" }
util = { path = "../util" }
rayon = { version = "1.5.2", default-features = false, optional = true }
itertools = { version = "0.10.4", optional = true }
# Benchmarks
[[bench]]
name = "pcs-benches"
path = "benches/bench.rs"
harness = false
[features]
# default = [ "parallel", "print-trace" ]
default = [ "parallel",]
extensive_sanity_checks = [ ]
parallel = [
"rayon",
"itertools",
"ark-std/parallel",
"ark-ff/parallel",
"ark-poly/parallel",
"ark-ec/parallel",
"util/parallel",
"arithmetic/parallel",
]
print-trace = [
"ark-std/print-trace",
"arithmetic/print-trace",
]

88
pcs/benches/bench.rs Normal file
View File

@@ -0,0 +1,88 @@
use ark_bls12_381::{Bls12_381, Fr};
use ark_ff::UniformRand;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::{rc::Rc, test_rng};
use pcs::{
prelude::{MultilinearKzgPCS, PCSError, PolynomialCommitmentScheme},
StructuredReferenceString,
};
use std::time::Instant;
fn main() -> Result<(), PCSError> {
bench_pcs()
}
fn bench_pcs() -> Result<(), PCSError> {
let mut rng = test_rng();
// normal polynomials
let uni_params = MultilinearKzgPCS::<Bls12_381>::gen_srs_for_testing(&mut rng, 18)?;
for nv in 4..19 {
let repetition = if nv < 10 {
100
} else if nv < 20 {
50
} else {
10
};
let poly = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
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 = MultilinearKzgPCS::commit(&ck, &poly)?;
}
println!(
"KZG commit for {} variables: {} ns",
nv,
start.elapsed().as_nanos() / repetition as u128
);
MultilinearKzgPCS::commit(&ck, &poly)?
};
// open
let (proof, value) = {
let start = Instant::now();
for _ in 0..repetition {
let _open = MultilinearKzgPCS::open(&ck, &poly, &point)?;
}
println!(
"KZG open for {} variables: {} ns",
nv,
start.elapsed().as_nanos() / repetition as u128
);
MultilinearKzgPCS::open(&ck, &poly, &point)?
};
// verify
{
let start = Instant::now();
for _ in 0..repetition {
assert!(MultilinearKzgPCS::verify(
&vk, &com, &point, &value, &proof
)?);
}
println!(
"KZG verify for {} variables: {} ns",
nv,
start.elapsed().as_nanos() / repetition as u128
);
}
println!("====================================");
}
Ok(())
}

7
pcs/readme.md Normal file
View File

@@ -0,0 +1,7 @@
KZG based multilinear polynomial commitment
-----
# Compiling features:
- `parallel`: use multi-threading when possible.
- `print-trace`: print out user friendly information about the running time for each micro component.
- `extensive_sanity_checks`: runs additional sanity checks that is not essential and will slow down the scheme.

50
pcs/src/errors.rs Normal file
View File

@@ -0,0 +1,50 @@
// 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 <https://mit-license.org/>.
//! Error module.
use arithmetic::ArithErrors;
use ark_serialize::SerializationError;
use ark_std::string::String;
use displaydoc::Display;
use transcript::TranscriptError;
/// A `enum` specifying the possible failure modes of the PCS.
#[derive(Display, Debug)]
pub enum PCSError {
/// Invalid Prover: {0}
InvalidProver(String),
/// Invalid Verifier: {0}
InvalidVerifier(String),
/// Invalid Proof: {0}
InvalidProof(String),
/// Invalid parameters: {0}
InvalidParameters(String),
/// An error during (de)serialization: {0}
SerializationError(SerializationError),
/// Transcript error {0}
TranscriptError(TranscriptError),
/// ArithErrors error {0}
ArithErrors(ArithErrors),
}
impl From<SerializationError> for PCSError {
fn from(e: ark_serialize::SerializationError) -> Self {
Self::SerializationError(e)
}
}
impl From<TranscriptError> for PCSError {
fn from(e: TranscriptError) -> Self {
Self::TranscriptError(e)
}
}
impl From<ArithErrors> for PCSError {
fn from(e: ArithErrors) -> Self {
Self::ArithErrors(e)
}
}

190
pcs/src/lib.rs Normal file
View File

@@ -0,0 +1,190 @@
mod errors;
mod multilinear_kzg;
mod structs;
mod univariate_kzg;
pub mod prelude;
use ark_ec::PairingEngine;
use ark_ff::Field;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::rand::{CryptoRng, RngCore};
use errors::PCSError;
use std::{borrow::Borrow, fmt::Debug, hash::Hash};
/// 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<E: PairingEngine> {
/// Prover parameters
type ProverParam: Clone;
/// Verifier parameters
type VerifierParam: Clone + CanonicalSerialize + CanonicalDeserialize;
/// Structured reference string
type SRS: Clone + Debug;
/// Polynomial and its associated types
type Polynomial: Clone
+ Debug
+ Hash
+ PartialEq
+ Eq
+ CanonicalSerialize
+ CanonicalDeserialize;
/// Polynomial input domain
type Point: Clone + Ord + Debug + Sync + Hash + PartialEq + Eq;
/// Polynomial Evaluation
type Evaluation: Field;
/// Commitments
type Commitment: Clone + CanonicalSerialize + CanonicalDeserialize + Debug + PartialEq + Eq;
/// Batch commitments
type BatchCommitment: Clone + CanonicalSerialize + CanonicalDeserialize + Debug + PartialEq + Eq;
/// Proofs
type Proof: Clone + CanonicalSerialize + CanonicalDeserialize + Debug + PartialEq + Eq;
/// Batch proofs
type BatchProof: Clone + CanonicalSerialize + CanonicalDeserialize + Debug + PartialEq + Eq;
/// Build SRS for testing.
///
/// - For univariate polynomials, `supported_size` is the maximum degree.
/// - For multilinear polynomials, `supported_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<R: RngCore + CryptoRng>(
rng: &mut R,
supported_size: usize,
) -> Result<Self::SRS, PCSError>;
/// Trim the universal parameters to specialize the public parameters.
/// Input both `supported_degree` for univariate and
/// `supported_num_vars` for multilinear.
/// ## Note on function signature
/// Usually, data structure like SRS and ProverParam are huge and users
/// might wish to keep them in heap using different kinds of smart pointers
/// (instead of only in stack) therefore our `impl Borrow<_>` interface
/// allows for passing in any pointer type, e.g.: `trim(srs: &Self::SRS,
/// ..)` or `trim(srs: Box<Self::SRS>, ..)` or `trim(srs: Arc<Self::SRS>,
/// ..)` etc.
fn trim(
srs: impl Borrow<Self::SRS>,
supported_degree: usize,
supported_num_vars: Option<usize>,
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError>;
/// Generate a commitment for a polynomial
/// ## Note on function signature
/// Usually, data structure like SRS and ProverParam are huge and users
/// might wish to keep them in heap using different kinds of smart pointers
/// (instead of only in stack) therefore our `impl Borrow<_>` interface
/// allows for passing in any pointer type, e.g.: `commit(prover_param:
/// &Self::ProverParam, ..)` or `commit(prover_param:
/// Box<Self::ProverParam>, ..)` or `commit(prover_param:
/// Arc<Self::ProverParam>, ..)` etc.
fn commit(
prover_param: impl Borrow<Self::ProverParam>,
poly: &Self::Polynomial,
) -> Result<Self::Commitment, PCSError>;
/// Generate a commitment for a list of polynomials
fn multi_commit(
prover_param: impl Borrow<Self::ProverParam>,
polys: &[Self::Polynomial],
) -> Result<Self::BatchCommitment, PCSError>;
/// On input a polynomial `p` and a point `point`, outputs a proof for the
/// same.
fn open(
prover_param: impl Borrow<Self::ProverParam>,
polynomial: &Self::Polynomial,
point: &Self::Point,
) -> Result<(Self::Proof, Self::Evaluation), PCSError>;
/// Input a list of multilinear extensions, and a same number of points, and
/// a transcript, compute a multi-opening for all the polynomials.
fn multi_open(
prover_param: impl Borrow<Self::ProverParam>,
multi_commitment: &Self::BatchCommitment,
polynomials: &[Self::Polynomial],
points: &[Self::Point],
) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError>;
/// 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<Self::ProverParam>,
commitment: &Self::Commitment,
polynomials: &Self::Polynomial,
points: &[Self::Point],
) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError>;
/// 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<bool, PCSError>;
/// Verifies that `value_i` is the evaluation at `x_i` of the polynomial
/// `poly_i` committed inside `comm`.
fn batch_verify<R: RngCore + CryptoRng>(
verifier_param: &Self::VerifierParam,
multi_commitment: &Self::BatchCommitment,
points: &[Self::Point],
values: &[E::Fr],
batch_proof: &Self::BatchProof,
rng: &mut R,
) -> Result<bool, PCSError>;
/// 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<bool, PCSError>;
}
/// API definitions for structured reference string
pub trait StructuredReferenceString<E: PairingEngine>: Sized {
/// Prover parameters
type ProverParam;
/// Verifier parameters
type VerifierParam;
/// Extract the prover parameters from the public parameters.
fn extract_prover_param(&self, supported_size: usize) -> Self::ProverParam;
/// Extract the verifier parameters from the public parameters.
fn extract_verifier_param(&self, supported_size: usize) -> Self::VerifierParam;
/// Trim the universal parameters to specialize the public parameters
/// for polynomials to the given `supported_size`, and
/// returns committer key and verifier key.
///
/// - For univariate polynomials, `supported_size` is the maximum degree.
/// - For multilinear polynomials, `supported_size` is 2 to the number of
/// variables.
///
/// `supported_log_size` should be in range `1..=params.log_size`
fn trim(
&self,
supported_size: usize,
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError>;
/// Build SRS for testing.
///
/// - For univariate polynomials, `supported_size` is the maximum degree.
/// - For multilinear polynomials, `supported_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<R: RngCore + CryptoRng>(
rng: &mut R,
supported_size: usize,
) -> Result<Self, PCSError>;
}

View File

@@ -0,0 +1,11 @@
// 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 <https://mit-license.org/>.
mod multi_poly;
mod single_poly;
pub(crate) use multi_poly::*;
pub(crate) use single_poly::*;

View File

@@ -0,0 +1,426 @@
use crate::{
multilinear_kzg::{
open_internal,
srs::{MultilinearProverParam, MultilinearVerifierParam},
util::compute_w_circ_l,
verify_internal, MultilinearKzgBatchProof,
},
prelude::{Commitment, UnivariateProverParam, UnivariateVerifierParam},
univariate_kzg::UnivariateKzgPCS,
PCSError, PolynomialCommitmentScheme,
};
use arithmetic::{build_l, get_uni_domain, merge_polynomials};
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, EvaluationDomain, MultilinearExtension, Polynomial};
use ark_std::{end_timer, format, rc::Rc, start_timer, string::ToString, vec, vec::Vec};
use transcript::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, and utilize 'multi_open_same_poly_internal'
/// API.
///
/// 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(crate) fn multi_open_internal<E: PairingEngine>(
uni_prover_param: &UnivariateProverParam<E::G1Affine>,
ml_prover_param: &MultilinearProverParam<E>,
polynomials: &[Rc<DenseMultilinearExtension<E::Fr>>],
multi_commitment: &Commitment<E>,
points: &[Vec<E::Fr>],
) -> Result<(MultilinearKzgBatchProof<E>, Vec<E::Fr>), PCSError> {
let open_timer = start_timer!(|| "multi open");
// ===================================
// Sanity checks on inputs
// ===================================
let points_len = points.len();
if points_len == 0 {
return Err(PCSError::InvalidParameters("points is empty".to_string()));
}
if points_len != polynomials.len() {
return Err(PCSError::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(PCSError::InvalidParameters(
"polynomials do not have same num_vars".to_string(),
));
}
}
for point in points.iter() {
if point.len() != num_var {
return Err(PCSError::InvalidParameters(
"points do not have same num_vars".to_string(),
));
}
}
let domain = get_uni_domain::<E::Fr>(points_len)?;
// 1. build `l(points)` which is a list of univariate polynomials that goes
// through the points
let uni_polys = build_l(points, &domain, true)?;
// 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, points.len(), true)?;
// 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 = UnivariateKzgPCS::<E>::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) =
UnivariateKzgPCS::<E>::open(uni_prover_param, &q_x, &domain.element(i))?;
q_x_opens.push(q_x_open);
q_x_evals.push(q_x_eval);
#[cfg(feature = "extensive_sanity_checks")]
{
// sanity check
let point: Vec<E::Fr> = uni_polys
.iter()
.map(|poly| poly.evaluate(&domain.element(i)))
.collect();
let mle_eval = merge_poly.evaluate(&point).unwrap();
if mle_eval != q_x_eval {
return Err(PCSError::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) = UnivariateKzgPCS::<E>::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<E::Fr> = uni_polys.iter().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(PCSError::InvalidProver(
"Q(r) does not match W(l(r))".to_string(),
));
}
end_timer!(open_timer);
Ok((
MultilinearKzgBatchProof {
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(crate) fn batch_verify_internal<E: PairingEngine>(
uni_verifier_param: &UnivariateVerifierParam<E>,
ml_verifier_param: &MultilinearVerifierParam<E>,
multi_commitment: &Commitment<E>,
points: &[Vec<E::Fr>],
values: &[E::Fr],
batch_proof: &MultilinearKzgBatchProof<E>,
) -> Result<bool, PCSError> {
let verify_timer = start_timer!(|| "batch verify");
// ===================================
// Sanity checks on inputs
// ===================================
let points_len = points.len();
if points_len == 0 {
return Err(PCSError::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(PCSError::InvalidParameters(format!(
"openings length {} does not match point length {}",
points_len + 1,
batch_proof.q_x_opens.len()
)));
}
if points_len + 1 != values.len() {
return Err(PCSError::InvalidParameters(format!(
"values length {} does not match point length {}",
points_len + 1,
values.len()
)));
}
let num_var = points[0].len();
for point in points.iter().skip(1) {
if point.len() != num_var {
return Err(PCSError::InvalidParameters(format!(
"points do not have same num_vars ({} vs {})",
point.len(),
num_var,
)));
}
}
let domain = get_uni_domain::<E::Fr>(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, value) in values.iter().enumerate().take(points_len) {
if !UnivariateKzgPCS::verify(
uni_verifier_param,
&batch_proof.q_x_commit,
&domain.element(i),
value,
&batch_proof.q_x_opens[i],
)? {
#[cfg(debug_assertion)]
println!("q(omega^{}) verification failed", i);
return Ok(false);
}
}
if !UnivariateKzgPCS::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(points, &domain, true)?;
// 5. get a point `p := l(r)`
let point: Vec<E::Fr> = uni_polys.iter().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::*;
use crate::{
multilinear_kzg::{
srs::MultilinearUniversalParams,
util::{compute_qx_degree, generate_evaluations_multi_poly},
MultilinearKzgPCS, MultilinearKzgProof,
},
prelude::UnivariateUniversalParams,
StructuredReferenceString,
};
use arithmetic::get_batched_nv;
use ark_bls12_381::Bls12_381 as E;
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::{
log2,
rand::{CryptoRng, RngCore},
test_rng,
vec::Vec,
UniformRand,
};
type Fr = <E as PairingEngine>::Fr;
fn test_multi_open_helper<R: RngCore + CryptoRng>(
uni_params: &UnivariateUniversalParams<E>,
ml_params: &MultilinearUniversalParams<E>,
polys: &[Rc<DenseMultilinearExtension<Fr>>],
rng: &mut R,
) -> Result<(), PCSError> {
let merged_nv = get_batched_nv(polys[0].num_vars(), polys.len());
let qx_degree = compute_qx_degree(merged_nv, polys.len());
let padded_qx_degree = 1usize << log2(qx_degree);
let (uni_ck, uni_vk) = uni_params.trim(padded_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::<Vec<Fr>>();
points.push(point);
}
let evals = generate_evaluations_multi_poly(polys, &points)?;
let com = MultilinearKzgPCS::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(<E as PairingEngine>::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,
&MultilinearKzgBatchProof {
proof: MultilinearKzgProof { proofs: Vec::new() },
q_x_commit: Commitment(<E as PairingEngine>::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;
wrong_proof.q_x_commit = Commitment(<E as PairingEngine>::G1Affine::default());
assert!(!batch_verify_internal(
&uni_vk,
&ml_vk,
&com,
&points,
&evaluations,
&wrong_proof,
)?);
Ok(())
}
#[test]
fn test_multi_open_internal() -> Result<(), PCSError> {
let mut rng = test_rng();
let uni_params =
UnivariateUniversalParams::<E>::gen_srs_for_testing(&mut rng, 1usize << 15)?;
let ml_params = MultilinearUniversalParams::<E>::gen_srs_for_testing(&mut rng, 15)?;
for num_poly in 1..10 {
for nv in 1..5 {
let polys1: Vec<_> = (0..num_poly)
.map(|_| Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)))
.collect();
test_multi_open_helper(&uni_params, &ml_params, &polys1, &mut rng)?;
}
}
Ok(())
}
}

View File

@@ -0,0 +1,368 @@
// 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 <https://mit-license.org/>.
use crate::{
multilinear_kzg::{
open_internal,
srs::{MultilinearProverParam, MultilinearVerifierParam},
util::compute_w_circ_l,
verify_internal, MultilinearKzgBatchProof,
},
prelude::{Commitment, UnivariateProverParam, UnivariateVerifierParam},
univariate_kzg::UnivariateKzgPCS,
PCSError, PolynomialCommitmentScheme,
};
use arithmetic::{build_l, get_uni_domain};
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, EvaluationDomain, MultilinearExtension, Polynomial};
use ark_std::{end_timer, format, rc::Rc, start_timer, string::ToString, vec, vec::Vec};
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use transcript::IOPTranscript;
/// Input
/// - the prover parameters for univariate KZG,
/// - the prover parameters for multilinear KZG,
/// - a single MLE,
/// - a commitment to the MLE
/// - and a list of points,
/// compute a multi-opening for this polynomial.
///
/// 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 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
/// 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(crate) fn multi_open_same_poly_internal<E: PairingEngine>(
uni_prover_param: &UnivariateProverParam<E::G1Affine>,
ml_prover_param: &MultilinearProverParam<E>,
polynomial: &Rc<DenseMultilinearExtension<E::Fr>>,
commitment: &Commitment<E>,
points: &[Vec<E::Fr>],
) -> Result<(MultilinearKzgBatchProof<E>, Vec<E::Fr>), PCSError> {
let open_timer = start_timer!(|| "multi open");
// ===================================
// Sanity checks on inputs
// ===================================
let points_len = points.len();
if points_len == 0 {
return Err(PCSError::InvalidParameters("points is empty".to_string()));
}
let num_var = polynomial.num_vars();
for point in points.iter() {
if point.len() != num_var {
return Err(PCSError::InvalidParameters(
"points do not have same num_vars".to_string(),
));
}
}
let domain = get_uni_domain::<E::Fr>(points_len)?;
// 1. build `l(points)` which is a list of univariate polynomials that goes
// through the points
let uni_polys = build_l(points, &domain, false)?;
// 3. build `q(x)` which is a univariate polynomial `W circ l`
let q_x = compute_w_circ_l(polynomial, &uni_polys, points.len(), false)?;
// 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", commitment)?;
for point in points {
transcript.append_serializable_element(b"w", point)?;
}
let q_x_commit = UnivariateKzgPCS::<E>::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) =
UnivariateKzgPCS::<E>::open(uni_prover_param, &q_x, &domain.element(i))?;
q_x_opens.push(q_x_open);
q_x_evals.push(q_x_eval);
#[cfg(feature = "extensive_sanity_checks")]
{
// sanity check
let point: Vec<E::Fr> = uni_polys
.iter()
.map(|poly| poly.evaluate(&domain.element(i)))
.collect();
let mle_eval = polynomial.evaluate(&point).unwrap();
if mle_eval != q_x_eval {
return Err(PCSError::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) = UnivariateKzgPCS::<E>::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<E::Fr> = uni_polys
.into_par_iter()
.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, polynomial, &point)?;
// 9. output value that is `w` evaluated at `p` (which should match `q(r)`)
if mle_eval != q_r_value {
return Err(PCSError::InvalidProver(
"Q(r) does not match W(l(r))".to_string(),
));
}
end_timer!(open_timer);
Ok((
MultilinearKzgBatchProof {
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
#[allow(dead_code)]
pub(crate) fn batch_verify_same_poly_internal<E: PairingEngine>(
uni_verifier_param: &UnivariateVerifierParam<E>,
ml_verifier_param: &MultilinearVerifierParam<E>,
multi_commitment: &Commitment<E>,
points: &[Vec<E::Fr>],
values: &[E::Fr],
batch_proof: &MultilinearKzgBatchProof<E>,
) -> Result<bool, PCSError> {
let verify_timer = start_timer!(|| "batch verify");
// ===================================
// Sanity checks on inputs
// ===================================
let points_len = points.len();
if points_len == 0 {
return Err(PCSError::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(PCSError::InvalidParameters(format!(
"openings length {} does not match point length {}",
points_len + 1,
batch_proof.q_x_opens.len()
)));
}
if points_len + 1 != values.len() {
return Err(PCSError::InvalidParameters(format!(
"values length {} does not match point length {}",
points_len + 1,
values.len()
)));
}
let num_var = points[0].len();
for point in points.iter().skip(1) {
if point.len() != num_var {
return Err(PCSError::InvalidParameters(format!(
"points do not have same num_vars ({} vs {})",
point.len(),
num_var,
)));
}
}
let domain = get_uni_domain::<E::Fr>(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, value) in values.iter().enumerate().take(points_len) {
if !UnivariateKzgPCS::verify(
uni_verifier_param,
&batch_proof.q_x_commit,
&domain.element(i),
value,
&batch_proof.q_x_opens[i],
)? {
#[cfg(debug_assertion)]
println!("q(omega^{}) verification failed", i);
return Ok(false);
}
}
if !UnivariateKzgPCS::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(points, &domain, false)?;
// 5. get a point `p := l(r)`
let point: Vec<E::Fr> = uni_polys.iter().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::*;
use crate::{
multilinear_kzg::{
srs::MultilinearUniversalParams,
util::{compute_qx_degree, generate_evaluations_single_poly},
MultilinearKzgPCS,
},
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::{CryptoRng, RngCore},
test_rng,
vec::Vec,
UniformRand,
};
type Fr = <E as PairingEngine>::Fr;
fn test_same_poly_multi_open_internal_helper<R: RngCore + CryptoRng>(
uni_params: &UnivariateUniversalParams<E>,
ml_params: &MultilinearUniversalParams<E>,
poly: &Rc<DenseMultilinearExtension<Fr>>,
point_len: usize,
rng: &mut R,
) -> Result<(), PCSError> {
let nv = poly.num_vars;
let qx_degree = compute_qx_degree(nv, point_len);
let padded_qx_degree = 1usize << log2(qx_degree);
let (uni_ck, uni_vk) = uni_params.trim(padded_qx_degree)?;
let (ml_ck, ml_vk) = ml_params.trim(nv)?;
let mut points = Vec::new();
let mut eval = Vec::new();
for _ in 0..point_len {
let point = (0..nv).map(|_| Fr::rand(rng)).collect::<Vec<Fr>>();
eval.push(poly.evaluate(&point).unwrap());
points.push(point);
}
let evals = generate_evaluations_single_poly(poly, &points)?;
let com = MultilinearKzgPCS::commit(&(ml_ck.clone(), uni_ck.clone()), poly)?;
let (batch_proof, evaluations) =
multi_open_same_poly_internal(&uni_ck, &ml_ck, poly, &com, &points)?;
for (a, b) in evals.iter().zip(evaluations.iter()) {
assert_eq!(a, b)
}
// good path
assert!(batch_verify_same_poly_internal(
&uni_vk,
&ml_vk,
&com,
&points,
&evaluations,
&batch_proof,
)?);
Ok(())
}
#[test]
fn test_same_poly_multi_open_internal() -> Result<(), PCSError> {
let mut rng = test_rng();
let uni_params =
UnivariateUniversalParams::<E>::gen_srs_for_testing(&mut rng, 1usize << 15)?;
let ml_params = MultilinearUniversalParams::<E>::gen_srs_for_testing(&mut rng, 15)?;
for nv in 1..10 {
for point_len in 1..10 {
// normal polynomials
let polys1 = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
test_same_poly_multi_open_internal_helper(
&uni_params,
&ml_params,
&polys1,
point_len,
&mut rng,
)?;
}
}
Ok(())
}
}

View File

@@ -0,0 +1,644 @@
// 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 <https://mit-license.org/>.
//! Main module for multilinear KZG commitment scheme
mod batching;
pub(crate) mod srs;
pub(crate) mod util;
use self::batching::{
batch_verify_internal, batch_verify_same_poly_internal, multi_open_internal,
multi_open_same_poly_internal,
};
use crate::{
prelude::{
Commitment, UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam,
},
univariate_kzg::UnivariateKzgProof,
PCSError, PolynomialCommitmentScheme, StructuredReferenceString,
};
use arithmetic::{evaluate_opt, merge_polynomials};
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::{
borrow::Borrow,
end_timer, format,
marker::PhantomData,
rand::{CryptoRng, RngCore},
rc::Rc,
start_timer,
string::ToString,
vec,
vec::Vec,
One, Zero,
};
// use batching::{batch_verify_internal, multi_open_internal};
use srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam};
/// KZG Polynomial Commitment Scheme on multilinear polynomials.
pub struct MultilinearKzgPCS<E: PairingEngine> {
#[doc(hidden)]
phantom: PhantomData<E>,
}
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
/// proof of opening
pub struct MultilinearKzgProof<E: PairingEngine> {
/// Evaluation of quotients
pub proofs: Vec<E::G1Affine>,
}
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
/// proof of batch opening
pub struct MultilinearKzgBatchProof<E: PairingEngine> {
/// The actual proof
pub proof: MultilinearKzgProof<E>,
/// 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<E>,
/// openings of q(x) at 1, omega, ..., and r
pub q_x_opens: Vec<UnivariateKzgProof<E>>,
}
impl<E: PairingEngine> PolynomialCommitmentScheme<E> for MultilinearKzgPCS<E> {
// Parameters
type ProverParam = (
MultilinearProverParam<E>,
UnivariateProverParam<E::G1Affine>,
);
type VerifierParam = (MultilinearVerifierParam<E>, UnivariateVerifierParam<E>);
type SRS = (MultilinearUniversalParams<E>, UnivariateUniversalParams<E>);
// Polynomial and its associated types
type Polynomial = Rc<DenseMultilinearExtension<E::Fr>>;
type Point = Vec<E::Fr>;
type Evaluation = E::Fr;
// Commitments and proofs
type Commitment = Commitment<E>;
type BatchCommitment = Commitment<E>;
type Proof = MultilinearKzgProof<E>;
type BatchProof = MultilinearKzgBatchProof<E>;
/// 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<R: RngCore + CryptoRng>(
rng: &mut R,
log_size: usize,
) -> Result<Self::SRS, PCSError> {
Ok((
MultilinearUniversalParams::<E>::gen_srs_for_testing(rng, log_size)?,
UnivariateUniversalParams::<E>::gen_srs_for_testing(rng, 1 << log_size)?,
))
}
/// Trim the universal parameters to specialize the public parameters.
/// Input both `supported_log_degree` for univariate and
/// `supported_num_vars` for multilinear.
fn trim(
srs: impl Borrow<Self::SRS>,
supported_degree: usize,
supported_num_vars: Option<usize>,
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError> {
let supported_num_vars = match supported_num_vars {
Some(p) => p,
None => {
return Err(PCSError::InvalidParameters(
"multilinear should receive a num_var param".to_string(),
))
},
};
let (uni_ck, uni_vk) = srs.borrow().1.trim(supported_degree)?;
let (ml_ck, ml_vk) = srs.borrow().0.trim(supported_num_vars)?;
Ok(((ml_ck, uni_ck), (ml_vk, uni_vk)))
}
/// Generate a commitment for a polynomial.
///
/// This function takes `2^num_vars` number of scalar multiplications over
/// G1.
fn commit(
prover_param: impl Borrow<Self::ProverParam>,
poly: &Self::Polynomial,
) -> Result<Self::Commitment, PCSError> {
let prover_param = prover_param.borrow();
let commit_timer = start_timer!(|| "commit");
if prover_param.0.num_vars < poly.num_vars {
return Err(PCSError::InvalidParameters(format!(
"MlE length ({}) exceeds param limit ({})",
poly.num_vars, prover_param.0.num_vars
)));
}
let ignored = prover_param.0.num_vars - poly.num_vars;
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[ignored].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: impl Borrow<Self::ProverParam>,
polys: &[Self::Polynomial],
) -> Result<Self::Commitment, PCSError> {
let prover_param = prover_param.borrow();
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: impl Borrow<Self::ProverParam>,
polynomial: &Self::Polynomial,
point: &Self::Point,
) -> Result<(Self::Proof, Self::Evaluation), PCSError> {
open_internal(&prover_param.borrow().0, polynomial, point)
}
/// Input
/// - the prover parameters for univariate KZG,
/// - the prover parameters for multilinear KZG,
/// - a list of multilinear extensions (MLEs),
/// - a commitment to all multilinear extensions,
/// - 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: impl Borrow<Self::ProverParam>,
multi_commitment: &Self::BatchCommitment,
polynomials: &[Self::Polynomial],
points: &[Self::Point],
) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError> {
multi_open_internal::<E>(
&prover_param.borrow().1,
&prover_param.borrow().0,
polynomials,
multi_commitment,
points,
)
}
/// 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<Self::ProverParam>,
commitment: &Self::Commitment,
polynomial: &Self::Polynomial,
points: &[Self::Point],
) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError> {
multi_open_same_poly_internal::<E>(
&prover_param.borrow().1,
&prover_param.borrow().0,
polynomial,
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<bool, PCSError> {
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<R: RngCore + CryptoRng>(
verifier_param: &Self::VerifierParam,
multi_commitment: &Self::BatchCommitment,
points: &[Self::Point],
values: &[E::Fr],
batch_proof: &Self::BatchProof,
_rng: &mut R,
) -> Result<bool, PCSError> {
batch_verify_internal(
&verifier_param.1,
&verifier_param.0,
multi_commitment,
points,
values,
batch_proof,
)
}
/// 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<bool, PCSError> {
batch_verify_same_poly_internal(
&verifier_param.1,
&verifier_param.0,
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<E: PairingEngine>(
prover_param: &MultilinearProverParam<E>,
polynomial: &DenseMultilinearExtension<E::Fr>,
point: &[E::Fr],
) -> Result<(MultilinearKzgProof<E>, E::Fr), PCSError> {
let open_timer = start_timer!(|| format!("open mle with {} variable", polynomial.num_vars));
if polynomial.num_vars() > prover_param.num_vars {
return Err(PCSError::InvalidParameters(format!(
"Polynomial num_vars {} exceed the limit {}",
polynomial.num_vars, prover_param.num_vars
)));
}
if polynomial.num_vars() != point.len() {
return Err(PCSError::InvalidParameters(format!(
"Polynomial num_vars {} does not match point len {}",
polynomial.num_vars,
point.len()
)));
}
let nv = polynomial.num_vars();
let ignored = prover_param.num_vars - nv;
let mut r: Vec<Vec<E::Fr>> = (0..nv + 1).map(|_| Vec::new()).collect();
let mut q: Vec<Vec<E::Fr>> = (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[ignored..].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];
let one_minus_point_at_k = E::Fr::one() - point_at_k;
let ith_round_eval = start_timer!(|| format!("{}-th round eval", i));
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] * one_minus_point_at_k + (r[k][(b << 1) + 1] * point_at_k);
}
end_timer!(ith_round_eval);
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 = evaluate_opt(polynomial, point);
end_timer!(open_timer);
Ok((MultilinearKzgProof { 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<E: PairingEngine>(
verifier_param: &MultilinearVerifierParam<E>,
commitment: &Commitment<E>,
point: &[E::Fr],
value: &E::Fr,
proof: &MultilinearKzgProof<E>,
) -> Result<bool, PCSError> {
let verify_timer = start_timer!(|| "verify");
let num_var = point.len();
if num_var > verifier_param.num_vars {
return Err(PCSError::InvalidParameters(format!(
"point length ({}) exceeds param limit ({})",
num_var, verifier_param.num_vars
)));
}
let ignored = verifier_param.num_vars - num_var;
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(num_var);
let h_table = FixedBaseMSM::get_window_table(
scalar_size,
window_size,
verifier_param.h.into_projective(),
);
let h_mul: Vec<E::G2Projective> =
FixedBaseMSM::multi_scalar_mul(scalar_size, window_size, &h_table, point);
let h_vec: Vec<_> = (0..num_var)
.map(|i| verifier_param.h_mask[ignored + i].into_projective() - h_mul[i])
.collect();
let h_vec: Vec<E::G2Affine> = 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(num_var).map(E::G2Prepared::from))
.collect();
pairings.push((
E::G1Prepared::from(
(verifier_param.g.mul(*value) - commitment.0.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::multilinear_kzg::util::compute_qx_degree;
use arithmetic::get_batched_nv;
use ark_bls12_381::Bls12_381;
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::{log2, rand::RngCore, test_rng, vec::Vec, UniformRand};
type E = Bls12_381;
type Fr = <E as PairingEngine>::Fr;
fn test_single_helper<R: RngCore + CryptoRng>(
params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
poly: &Rc<DenseMultilinearExtension<Fr>>,
rng: &mut R,
) -> Result<(), PCSError> {
let nv = poly.num_vars();
assert_ne!(nv, 0);
let uni_degree = 1;
let (ck, vk) = MultilinearKzgPCS::trim(params, uni_degree, Some(nv + 1))?;
let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
let com = MultilinearKzgPCS::commit(&ck, poly)?;
let (proof, value) = MultilinearKzgPCS::open(&ck, poly, &point)?;
assert!(MultilinearKzgPCS::verify(
&vk, &com, &point, &value, &proof
)?);
let value = Fr::rand(rng);
assert!(!MultilinearKzgPCS::verify(
&vk, &com, &point, &value, &proof
)?);
Ok(())
}
#[test]
fn test_single_commit() -> Result<(), PCSError> {
let mut rng = test_rng();
let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 10)?;
// normal polynomials
let poly1 = Rc::new(DenseMultilinearExtension::rand(8, &mut rng));
test_single_helper(&params, &poly1, &mut rng)?;
// single-variate polynomials
let poly2 = Rc::new(DenseMultilinearExtension::rand(1, &mut rng));
test_single_helper(&params, &poly2, &mut rng)?;
Ok(())
}
#[test]
fn setup_commit_verify_constant_polynomial() {
let mut rng = test_rng();
// normal polynomials
assert!(MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 0).is_err());
}
fn test_multi_open_single_poly_helper<R: RngCore + CryptoRng>(
params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
poly: Rc<DenseMultilinearExtension<Fr>>,
num_open: usize,
rng: &mut R,
) -> Result<(), PCSError> {
let nv = poly.num_vars();
assert_ne!(nv, 0);
let uni_degree = 1024;
let (ck, vk) = MultilinearKzgPCS::trim(params, uni_degree, Some(nv + 1))?;
let mut points = vec![];
for _ in 0..num_open {
let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
points.push(point)
}
let com = MultilinearKzgPCS::commit(&ck, &poly)?;
let (proof, mut values) =
MultilinearKzgPCS::multi_open_single_poly(&ck, &com, &poly, &points)?;
for (a, b) in values.iter().zip(points.iter()) {
let p = poly.evaluate(&b).unwrap();
assert_eq!(*a, p);
}
assert!(MultilinearKzgPCS::batch_verify_single_poly(
&vk, &com, &points, &values, &proof
)?);
values[0] = Fr::rand(rng);
assert!(!MultilinearKzgPCS::batch_verify_single_poly(
&vk, &com, &points, &values, &proof
)?);
Ok(())
}
#[test]
fn test_multi_open_single_poly() -> Result<(), PCSError> {
let mut rng = test_rng();
let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 15)?;
for nv in 1..10 {
for num_open in 2..10 {
let poly1 = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
test_multi_open_single_poly_helper(&params, poly1, num_open, &mut rng)?;
}
}
Ok(())
}
fn test_multi_open_helper<R: RngCore + CryptoRng>(
params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
polys: &[Rc<DenseMultilinearExtension<Fr>>],
num_open: usize,
rng: &mut R,
) -> Result<(), PCSError> {
let nv = polys[0].num_vars();
assert_ne!(nv, 0);
let merged_nv = get_batched_nv(nv, polys.len());
let qx_degree = compute_qx_degree(merged_nv, polys.len());
let padded_qx_degree = 1usize << log2(qx_degree);
let (ck, vk) = MultilinearKzgPCS::trim(params, padded_qx_degree, Some(merged_nv))?;
let mut points = vec![];
for _ in 0..num_open {
let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
points.push(point)
}
let com = MultilinearKzgPCS::multi_commit(&ck, &polys)?;
let (proof, mut values) = MultilinearKzgPCS::multi_open(&ck, &com, polys, &points)?;
assert!(MultilinearKzgPCS::batch_verify(
&vk, &com, &points, &values, &proof, rng
)?);
values[0] = Fr::rand(rng);
assert!(!MultilinearKzgPCS::batch_verify_single_poly(
&vk, &com, &points, &values, &proof
)?);
Ok(())
}
#[test]
fn test_multi_open() -> Result<(), PCSError> {
let mut rng = test_rng();
let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 15)?;
// normal polynomials
for nv in 1..10 {
for num_open in 1..4 {
let mut polys = vec![];
for _ in 0..num_open {
let poly = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
polys.push(poly)
}
test_multi_open_helper(&params, &polys, num_open, &mut rng)?;
}
}
Ok(())
}
}

View File

@@ -0,0 +1,273 @@
// 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 <https://mit-license.org/>.
//! Implementing Structured Reference Strings for multilinear polynomial KZG
use crate::{prelude::PCSError, 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::{
collections::LinkedList,
end_timer, format,
rand::{CryptoRng, RngCore},
start_timer,
string::ToString,
vec::Vec,
UniformRand,
};
use core::iter::FromIterator;
/// Evaluations over {0,1}^n for G1 or G2
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
pub struct Evaluations<C: AffineCurve> {
/// The evaluations.
pub evals: Vec<C>,
}
/// Universal Parameter
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
pub struct MultilinearUniversalParams<E: PairingEngine> {
/// prover parameters
pub prover_param: MultilinearProverParam<E>,
/// h^randomness: h^t1, h^t2, ..., **h^{t_nv}**
pub h_mask: Vec<E::G2Affine>,
}
/// Prover Parameters
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
pub struct MultilinearProverParam<E: PairingEngine> {
/// 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<Evaluations<E::G1Affine>>,
/// generator for G1
pub g: E::G1Affine,
/// generator for G2
pub h: E::G2Affine,
}
/// Verifier Parameters
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
pub struct MultilinearVerifierParam<E: PairingEngine> {
/// 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<E::G2Affine>,
}
impl<E: PairingEngine> StructuredReferenceString<E> for MultilinearUniversalParams<E> {
type ProverParam = MultilinearProverParam<E>;
type VerifierParam = MultilinearVerifierParam<E>;
/// 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), PCSError> {
if supported_num_vars > self.prover_param.num_vars {
return Err(PCSError::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<R: RngCore + CryptoRng>(
rng: &mut R,
num_vars: usize,
) -> Result<Self, PCSError> {
if num_vars == 0 {
return Err(PCSError::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<DenseMultilinearExtension<E::Fr>> =
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<F: Field>(poly: &[F], pad: usize) -> Result<Vec<F>, PCSError> {
if pad == 0 {
return Ok(poly.to_vec());
}
if !poly.len().is_power_of_two() {
return Err(PCSError::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<F: PrimeField>(t: &[F]) -> Vec<DenseMultilinearExtension<F>> {
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<(), PCSError> {
let mut rng = test_rng();
for nv in 4..10 {
let _ = MultilinearUniversalParams::<E>::gen_srs_for_testing(&mut rng, nv)?;
}
Ok(())
}
}

View File

@@ -0,0 +1,432 @@
// 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 <https://mit-license.org/>.
//! Useful utilities for KZG PCS
use crate::prelude::PCSError;
use arithmetic::evaluate_no_par;
use ark_ff::PrimeField;
use ark_poly::{
univariate::DensePolynomial, DenseMultilinearExtension, EvaluationDomain, Evaluations,
MultilinearExtension, Polynomial, Radix2EvaluationDomain,
};
use ark_std::{end_timer, format, log2, start_timer, string::ToString, vec::Vec};
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
/// 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]
pub fn compute_qx_degree(mle_num_vars: usize, point_len: usize) -> usize {
mle_num_vars * ((1 << log2(point_len)) - 1) + 1
}
/// Compute W \circ l.
///
/// Given an MLE W, and a list of univariate polynomials l, generate the
/// univariate polynomial that composes W with l.
///
/// Returns an error if l's length does not matches number of variables in W.
pub(crate) fn compute_w_circ_l<F: PrimeField>(
w: &DenseMultilinearExtension<F>,
l: &[DensePolynomial<F>],
num_points: usize,
with_suffix: bool,
) -> Result<DensePolynomial<F>, PCSError> {
let timer = start_timer!(|| "compute W \\circ l");
if w.num_vars != l.len() {
return Err(PCSError::InvalidParameters(format!(
"l's length ({}) does not match num_variables ({})",
l.len(),
w.num_vars(),
)));
}
let uni_degree = if with_suffix {
compute_qx_degree(w.num_vars() + log2(num_points) as usize, num_points)
} else {
compute_qx_degree(w.num_vars(), num_points)
};
let domain = match Radix2EvaluationDomain::<F>::new(uni_degree) {
Some(p) => p,
None => {
return Err(PCSError::InvalidParameters(
"failed to build radix 2 domain".to_string(),
))
},
};
let step = start_timer!(|| format!("compute eval {}-dim domain", domain.size()));
let res_eval = (0..domain.size())
.into_par_iter()
.map(|i| {
let l_eval: Vec<F> = l.iter().map(|x| x.evaluate(&domain.element(i))).collect();
evaluate_no_par(w, &l_eval)
})
.collect();
end_timer!(step);
let evaluation = Evaluations::from_vec_and_domain(res_eval, domain);
let res = evaluation.interpolate();
end_timer!(timer);
Ok(res)
}
/// 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_multi_poly<F: PrimeField>(
polynomials: &[std::rc::Rc<DenseMultilinearExtension<F>>],
points: &[Vec<F>],
) -> Result<Vec<F>, PCSError> {
use arithmetic::{build_l, get_uni_domain, merge_polynomials};
if polynomials.len() != points.len() {
return Err(PCSError::InvalidParameters(
"polynomial length does not match point length".to_string(),
));
}
let uni_poly_degree = points.len();
let merge_poly = merge_polynomials(polynomials)?;
let domain = get_uni_domain::<F>(uni_poly_degree)?;
let uni_polys = build_l(points, &domain, true)?;
let mut mle_values = vec![];
for i in 0..uni_poly_degree {
let point: Vec<F> = uni_polys
.iter()
.map(|poly| poly.evaluate(&domain.element(i)))
.collect();
let mle_value = merge_poly.evaluate(&point).unwrap();
mle_values.push(mle_value)
}
Ok(mle_values)
}
/// 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_single_poly<F: PrimeField>(
polynomial: &std::rc::Rc<DenseMultilinearExtension<F>>,
points: &[Vec<F>],
) -> Result<Vec<F>, PCSError> {
use arithmetic::{build_l, get_uni_domain};
let uni_poly_degree = points.len();
let domain = get_uni_domain::<F>(uni_poly_degree)?;
let uni_polys = build_l(points, &domain, false)?;
let mut mle_values = vec![];
for i in 0..uni_poly_degree {
let point: Vec<F> = uni_polys
.iter()
.map(|poly| poly.evaluate(&domain.element(i)))
.collect();
let mle_value = polynomial.evaluate(&point).unwrap();
mle_values.push(mle_value)
}
Ok(mle_values)
}
#[cfg(test)]
mod test {
use super::*;
use arithmetic::{build_l, get_uni_domain, merge_polynomials};
use ark_bls12_381::Fr;
use ark_poly::UVPolynomial;
use ark_std::{One, Zero};
use std::rc::Rc;
#[test]
fn test_w_circ_l() -> Result<(), PCSError> {
test_w_circ_l_helper::<Fr>()
}
fn test_w_circ_l_helper<F: PrimeField>() -> Result<(), PCSError> {
{
// Example from page 53:
// W = 3x1x2 + 2x2 whose evaluations are
// 0, 0 |-> 0
// 1, 0 |-> 0
// 0, 1 |-> 2
// 1, 1 |-> 5
let w_eval = vec![F::zero(), F::zero(), F::from(2u64), F::from(5u64)];
let w = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
// l0 = t + 2
// l1 = -2t + 4
let l0 = DensePolynomial::from_coefficients_vec(vec![F::from(2u64), F::one()]);
let l1 = DensePolynomial::from_coefficients_vec(vec![F::from(4u64), -F::from(2u64)]);
// res = -6t^2 - 4t + 32
let res = compute_w_circ_l(&w, [l0, l1].as_ref(), 4, false)?;
let res_rec = DensePolynomial::from_coefficients_vec(vec![
F::from(32u64),
-F::from(4u64),
-F::from(6u64),
]);
assert_eq!(res, res_rec);
}
{
// A random example
// W = x1x2x3 - 2x1x2 + 3x2x3 - 4x1x3 + 5x1 - 6x2 + 7x3
// 0, 0, 0 |-> 0
// 1, 0, 0 |-> 5
// 0, 1, 0 |-> -6
// 1, 1, 0 |-> -3
// 0, 0, 1 |-> 7
// 1, 0, 1 |-> 8
// 0, 1, 1 |-> 4
// 1, 1, 1 |-> 4
let w_eval = vec![
F::zero(),
F::from(5u64),
-F::from(6u64),
-F::from(3u64),
F::from(7u64),
F::from(8u64),
F::from(4u64),
F::from(4u64),
];
let w = DenseMultilinearExtension::from_evaluations_vec(3, w_eval);
// l0 = t + 2
// l1 = 3t - 4
// l2 = -5t + 6
let l0 = DensePolynomial::from_coefficients_vec(vec![F::from(2u64), F::one()]);
let l1 = DensePolynomial::from_coefficients_vec(vec![-F::from(4u64), F::from(3u64)]);
let l2 = DensePolynomial::from_coefficients_vec(vec![F::from(6u64), -F::from(5u64)]);
let res = compute_w_circ_l(&w, [l0, l1, l2].as_ref(), 8, false)?;
// res = -15t^3 - 23t^2 + 130t - 76
let res_rec = DensePolynomial::from_coefficients_vec(vec![
-F::from(76u64),
F::from(130u64),
-F::from(23u64),
-F::from(15u64),
]);
assert_eq!(res, res_rec);
}
Ok(())
}
#[test]
fn test_w_circ_l_with_prefix() -> Result<(), PCSError> {
test_w_circ_l_with_prefix_helper::<Fr>()
}
fn test_w_circ_l_with_prefix_helper<F: PrimeField>() -> Result<(), PCSError> {
{
// Example from page 53:
// W = 3x1x2 + 2x2 whose evaluations are
// 0, 0 |-> 0
// 1, 0 |-> 0
// 0, 1 |-> 2
// 1, 1 |-> 5
let w_eval = vec![F::zero(), F::zero(), F::from(2u64), F::from(5u64)];
let w = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
// l0 = t + 2
// l1 = -2t + 4
let l0 = DensePolynomial::from_coefficients_vec(vec![F::from(2u64), F::one()]);
let l1 = DensePolynomial::from_coefficients_vec(vec![F::from(4u64), -F::from(2u64)]);
// res = -6t^2 - 4t + 32
let res = compute_w_circ_l(&w, [l0, l1].as_ref(), 4, true)?;
let res_rec = DensePolynomial::from_coefficients_vec(vec![
F::from(32u64),
-F::from(4u64),
-F::from(6u64),
]);
assert_eq!(res, res_rec);
}
{
// A random example
// W = x1x2x3 - 2x1x2 + 3x2x3 - 4x1x3 + 5x1 - 6x2 + 7x3
// 0, 0, 0 |-> 0
// 1, 0, 0 |-> 5
// 0, 1, 0 |-> -6
// 1, 1, 0 |-> -3
// 0, 0, 1 |-> 7
// 1, 0, 1 |-> 8
// 0, 1, 1 |-> 4
// 1, 1, 1 |-> 4
let w_eval = vec![
F::zero(),
F::from(5u64),
-F::from(6u64),
-F::from(3u64),
F::from(7u64),
F::from(8u64),
F::from(4u64),
F::from(4u64),
];
let w = DenseMultilinearExtension::from_evaluations_vec(3, w_eval);
// l0 = t + 2
// l1 = 3t - 4
// l2 = -5t + 6
let l0 = DensePolynomial::from_coefficients_vec(vec![F::from(2u64), F::one()]);
let l1 = DensePolynomial::from_coefficients_vec(vec![-F::from(4u64), F::from(3u64)]);
let l2 = DensePolynomial::from_coefficients_vec(vec![F::from(6u64), -F::from(5u64)]);
let res = compute_w_circ_l(&w, [l0, l1, l2].as_ref(), 8, true)?;
// res = -15t^3 - 23t^2 + 130t - 76
let res_rec = DensePolynomial::from_coefficients_vec(vec![
-F::from(76u64),
F::from(130u64),
-F::from(23u64),
-F::from(15u64),
]);
assert_eq!(res, res_rec);
}
Ok(())
}
#[test]
fn test_qx() -> Result<(), PCSError> {
// Example from page 53:
// W1 = 3x1x2 + 2x2
let w_eval = vec![Fr::zero(), Fr::from(2u64), Fr::zero(), Fr::from(5u64)];
let w = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
let r = Fr::from(42u64);
// point 1 is [1, 2]
let point1 = vec![Fr::from(1u64), Fr::from(2u64)];
// point 2 is [3, 4]
let point2 = vec![Fr::from(3u64), Fr::from(4u64)];
// point 3 is [5, 6]
let point3 = vec![Fr::from(5u64), Fr::from(6u64)];
{
let domain = get_uni_domain::<Fr>(2)?;
let l = build_l(&[point1.clone(), point2.clone()], &domain, false)?;
let q_x = compute_w_circ_l(&w, &l, 2, false)?;
let point: Vec<Fr> = l.iter().map(|poly| poly.evaluate(&r)).collect();
assert_eq!(
q_x.evaluate(&r),
w.evaluate(&point).unwrap(),
"q(r) != w(l(r))"
);
}
{
let domain = get_uni_domain::<Fr>(3)?;
let l = build_l(&[point1, point2, point3], &domain, false)?;
let q_x = compute_w_circ_l(&w, &l, 3, false)?;
let point: Vec<Fr> = vec![l[0].evaluate(&r), l[1].evaluate(&r)];
assert_eq!(
q_x.evaluate(&r),
w.evaluate(&point).unwrap(),
"q(r) != w(l(r))"
);
}
Ok(())
}
#[test]
fn test_qx_with_prefix() -> Result<(), PCSError> {
// Example from page 53:
// W1 = 3x1x2 + 2x2
let w_eval = vec![Fr::zero(), Fr::from(2u64), Fr::zero(), Fr::from(5u64)];
let w1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W2 = x1x2 + x1
let w_eval = vec![Fr::zero(), Fr::zero(), Fr::from(1u64), Fr::from(2u64)];
let w2 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W3 = x1 + x2
let w_eval = vec![Fr::zero(), Fr::one(), Fr::from(1u64), Fr::from(2u64)];
let w3 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
let r = Fr::from(42u64);
// point 1 is [1, 2]
let point1 = vec![Fr::from(1u64), Fr::from(2u64)];
// point 2 is [3, 4]
let point2 = vec![Fr::from(3u64), Fr::from(4u64)];
// point 3 is [5, 6]
let point3 = vec![Fr::from(5u64), Fr::from(6u64)];
{
let domain = get_uni_domain::<Fr>(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(&[point1.clone(), point2.clone()], &domain, true)?;
// sage: P.<x> = PolynomialRing(ZZ)
// sage: l0 = -1/2 * x + 1/2
// sage: l1 = -x + 2
// sage: l2 = -x + 3
// sage: w = (3 * l1 * l2 + 2 * l2) * (1-l0) + (l1 * l2 + l1) * l0
// sage: w
// x^3 - 7/2*x^2 - 7/2*x + 16
//
// q(x) = x^3 - 7/2*x^2 - 7/2*x + 16
let q_x = compute_w_circ_l(&w, &l, 2, true)?;
let point: Vec<Fr> = l.iter().map(|poly| poly.evaluate(&r)).collect();
assert_eq!(
q_x.evaluate(&r),
w.evaluate(&point).unwrap(),
"q(r) != w(l(r))"
);
}
{
let domain = get_uni_domain::<Fr>(3)?;
let w = merge_polynomials(&[w1, w2, w3])?;
let l = build_l(&[point1, point2, point3], &domain, true)?;
let q_x = compute_w_circ_l(&w, &l, 3, true)?;
let point: Vec<Fr> = vec![
l[0].evaluate(&r),
l[1].evaluate(&r),
l[2].evaluate(&r),
l[3].evaluate(&r),
];
assert_eq!(
q_x.evaluate(&r),
w.evaluate(&point).unwrap(),
"q(r) != w(l(r))"
);
}
Ok(())
}
}

21
pcs/src/prelude.rs Normal file
View File

@@ -0,0 +1,21 @@
// 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 <https://mit-license.org/>.
//! Prelude
pub use crate::{
errors::PCSError,
multilinear_kzg::{
srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam},
util::compute_qx_degree,
MultilinearKzgBatchProof, MultilinearKzgPCS, MultilinearKzgProof,
},
structs::Commitment,
univariate_kzg::{
srs::{UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam},
UnivariateKzgBatchProof, UnivariateKzgPCS, UnivariateKzgProof,
},
PolynomialCommitmentScheme, StructuredReferenceString,
};

25
pcs/src/structs.rs Normal file
View File

@@ -0,0 +1,25 @@
// 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 <https://mit-license.org/>.
use ark_ec::PairingEngine;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
use derivative::Derivative;
#[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)]
#[derivative(
Default(bound = ""),
Hash(bound = ""),
Clone(bound = ""),
Copy(bound = ""),
Debug(bound = ""),
PartialEq(bound = ""),
Eq(bound = "")
)]
/// A commitment is an Affine point.
pub struct Commitment<E: PairingEngine>(
/// the actual commitment is an affine point.
pub E::G1Affine,
);

View File

@@ -0,0 +1,438 @@
// 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 <https://mit-license.org/>.
//! 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<E: PairingEngine> {
#[doc(hidden)]
phantom: PhantomData<E>,
}
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
/// proof of opening
pub struct UnivariateKzgProof<E: PairingEngine> {
/// Evaluation of quotients
pub proof: E::G1Affine,
}
/// batch proof
pub type UnivariateKzgBatchProof<E> = Vec<UnivariateKzgProof<E>>;
impl<E: PairingEngine> PolynomialCommitmentScheme<E> for UnivariateKzgPCS<E> {
// Parameters
type ProverParam = UnivariateProverParam<E::G1Affine>;
type VerifierParam = UnivariateVerifierParam<E>;
type SRS = UnivariateUniversalParams<E>;
// Polynomial and its associated types
type Polynomial = DensePolynomial<E::Fr>;
type Point = E::Fr;
type Evaluation = E::Fr;
// Polynomial and its associated types
type Commitment = Commitment<E>;
type BatchCommitment = Vec<Self::Commitment>;
type Proof = UnivariateKzgProof<E>;
type BatchProof = UnivariateKzgBatchProof<E>;
/// 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<R: RngCore + CryptoRng>(
rng: &mut R,
supported_size: usize,
) -> Result<Self::SRS, PCSError> {
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<Self::SRS>,
supported_degree: usize,
supported_num_vars: Option<usize>,
) -> 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<Self::ProverParam>,
poly: &Self::Polynomial,
) -> Result<Self::Commitment, PCSError> {
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<Self::ProverParam>,
polys: &[Self::Polynomial],
) -> Result<Self::BatchCommitment, PCSError> {
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::<Result<Vec<Self::Commitment>, 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<Self::ProverParam>,
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<Self::ProverParam>,
_multi_commitment: &Self::BatchCommitment,
polynomials: &[Self::Polynomial],
points: &[Self::Point],
) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), 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<Self::ProverParam>,
_commitment: &Self::Commitment,
_polynomials: &Self::Polynomial,
_points: &[Self::Point],
) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), 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<bool, PCSError> {
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<R: RngCore + CryptoRng>(
verifier_param: &Self::VerifierParam,
multi_commitment: &Self::BatchCommitment,
points: &[Self::Point],
values: &[E::Fr],
batch_proof: &Self::BatchProof,
rng: &mut R,
) -> Result<bool, PCSError> {
let check_time =
start_timer!(|| format!("Checking {} evaluation proofs", multi_commitment.len()));
let mut total_c = <E::G1Projective>::zero();
let mut total_w = <E::G1Projective>::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<bool, PCSError> {
unimplemented!()
}
}
fn skip_leading_zeros_and_convert_to_bigints<F: PrimeField, P: UVPolynomial<F>>(
p: &P,
) -> (usize, Vec<F::BigInt>) {
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<F: PrimeField>(p: &[F]) -> Vec<F::BigInt> {
let to_bigint_time = start_timer!(|| "Converting polynomial coeffs to bigints");
let coeffs = p.iter().map(|s| s.into_repr()).collect::<Vec<_>>();
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<E>() -> 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::<E>::gen_srs_for_testing(rng, degree)?;
let (ck, vk) = pp.trim(degree)?;
let p = <DensePolynomial<E::Fr> as UVPolynomial<E::Fr>>::rand(degree, rng);
let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
let point = E::Fr::rand(rng);
let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
assert!(
UnivariateKzgPCS::<E>::verify(&vk, &comm, &point, &value, &proof)?,
"proof was incorrect for max_degree = {}, polynomial_degree = {}",
degree,
p.degree(),
);
}
Ok(())
}
fn linear_polynomial_test_template<E>() -> Result<(), PCSError>
where
E: PairingEngine,
{
let rng = &mut test_rng();
for _ in 0..100 {
let degree = 50;
let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(rng, degree)?;
let (ck, vk) = pp.trim(degree)?;
let p = <DensePolynomial<E::Fr> as UVPolynomial<E::Fr>>::rand(degree, rng);
let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
let point = E::Fr::rand(rng);
let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
assert!(
UnivariateKzgPCS::<E>::verify(&vk, &comm, &point, &value, &proof)?,
"proof was incorrect for max_degree = {}, polynomial_degree = {}",
degree,
p.degree(),
);
}
Ok(())
}
fn batch_check_test_template<E>() -> 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::<E>::gen_srs_for_testing(rng, degree)?;
let (ck, vk) = UnivariateKzgPCS::<E>::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 = <DensePolynomial<E::Fr> as UVPolynomial<E::Fr>>::rand(degree, rng);
let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
let point = E::Fr::rand(rng);
let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
assert!(UnivariateKzgPCS::<E>::verify(
&vk, &comm, &point, &value, &proof
)?);
comms.push(comm);
values.push(value);
points.push(point);
proofs.push(proof);
}
assert!(UnivariateKzgPCS::<E>::batch_verify(
&vk, &comms, &points, &values, &proofs, rng
)?);
}
Ok(())
}
#[test]
fn end_to_end_test() {
end_to_end_test_template::<Bls12_381>().expect("test failed for bls12-381");
}
#[test]
fn linear_polynomial_test() {
linear_polynomial_test_template::<Bls12_381>().expect("test failed for bls12-381");
}
#[test]
fn batch_check_test() {
batch_check_test_template::<Bls12_381>().expect("test failed for bls12-381");
}
}

View File

@@ -0,0 +1,156 @@
// 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 <https://mit-license.org/>.
//! Implementing Structured Reference Strings for univariate polynomial KZG
use crate::{PCSError, 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::{CryptoRng, RngCore},
start_timer, vec,
vec::Vec,
One, UniformRand,
};
use derivative::Derivative;
/// `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(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize, Default)]
pub struct UnivariateUniversalParams<E: PairingEngine> {
/// Group elements of the form `{ \beta^i G }`, where `i` ranges from 0 to
/// `degree`.
pub powers_of_g: Vec<E::G1Affine>,
/// The generator of G2.
pub h: E::G2Affine,
/// \beta times the above generator of G2.
pub beta_h: E::G2Affine,
}
impl<E: PairingEngine> UnivariateUniversalParams<E> {
/// Returns the maximum supported degree
pub fn max_degree(&self) -> usize {
self.powers_of_g.len()
}
}
/// `UnivariateProverParam` is used to generate a proof
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, Eq, PartialEq, Default)]
pub struct UnivariateProverParam<C: AffineCurve> {
/// Parameters
pub powers_of_g: Vec<C>,
}
/// `UnivariateVerifierParam` is used to check evaluation proofs for a given
/// commitment.
#[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)]
#[derivative(
Default(bound = ""),
Clone(bound = ""),
Copy(bound = ""),
Debug(bound = ""),
PartialEq(bound = ""),
Eq(bound = "")
)]
pub struct UnivariateVerifierParam<E: PairingEngine> {
/// 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<E: PairingEngine> StructuredReferenceString<E> for UnivariateUniversalParams<E> {
type ProverParam = UnivariateProverParam<E::G1Affine>;
type VerifierParam = UnivariateVerifierParam<E>;
/// Extract the prover parameters from the public parameters.
fn extract_prover_param(&self, supported_size: usize) -> Self::ProverParam {
let powers_of_g = self.powers_of_g[..=supported_size].to_vec();
Self::ProverParam { powers_of_g }
}
/// Extract the verifier parameters from the public parameters.
fn extract_verifier_param(&self, _supported_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_size`, and
/// returns committer key and verifier key. `supported_size` should
/// be in range `1..params.len()`
fn trim(
&self,
supported_size: usize,
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError> {
let powers_of_g = self.powers_of_g[..=supported_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<R: RngCore + CryptoRng>(
rng: &mut R,
max_degree: usize,
) -> Result<Self, PCSError> {
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 *= &beta;
}
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::<E::G1Projective>(
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)
}
}