You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

426 lines
14 KiB

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(())
}
}