From d41a0cf62357bbee9239e175839bfbc4e46131e2 Mon Sep 17 00:00:00 2001 From: zhenfei Date: Thu, 30 Jun 2022 12:48:10 -0400 Subject: [PATCH] multi-commiting/opening (#34) --- pcs/Cargo.toml | 7 +- pcs/benches/bench.rs | 2 +- pcs/src/commit.rs | 402 ++++++++++++++++++++++-- pcs/src/errors.rs | 14 +- pcs/src/lib.rs | 34 ++- pcs/src/param.rs | 1 + pcs/src/util.rs | 610 +++++++++++++++++++++++++++++++++++++ poly-iop/src/lib.rs | 2 + poly-iop/src/transcript.rs | 9 +- poly-iop/src/utils.rs | 2 +- 10 files changed, 1049 insertions(+), 34 deletions(-) create mode 100644 pcs/src/util.rs diff --git a/pcs/Cargo.toml b/pcs/Cargo.toml index 669ba65..42929f5 100644 --- a/pcs/Cargo.toml +++ b/pcs/Cargo.toml @@ -17,6 +17,9 @@ ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "cur displaydoc = { version = "0.2.3", default-features = false } +poly-iop = { path = "../poly-iop" } + + # Benchmarks [[bench]] name = "pcs-benches" @@ -31,7 +34,9 @@ parallel = [ "ark-ff/parallel", "ark-poly/parallel", "ark-ec/parallel", + "poly-iop/parallel" ] print-trace = [ - "ark-std/print-trace" + "ark-std/print-trace", + "poly-iop/print-trace" ] \ No newline at end of file diff --git a/pcs/benches/bench.rs b/pcs/benches/bench.rs index b27e1b1..eb2f274 100644 --- a/pcs/benches/bench.rs +++ b/pcs/benches/bench.rs @@ -65,7 +65,7 @@ fn bench_pcs() -> Result<(), PCSErrors> { { let start = Instant::now(); for _ in 0..repetition { - assert!(KZGMultilinearPC::verify(&vk, &com, &point, value, &proof)?); + assert!(KZGMultilinearPC::verify(&vk, &com, &point, &value, &proof)?); } println!( "KZG verify for {} variables: {} ns", diff --git a/pcs/src/commit.rs b/pcs/src/commit.rs index 7ad2ef6..29e6c8a 100644 --- a/pcs/src/commit.rs +++ b/pcs/src/commit.rs @@ -1,16 +1,17 @@ +use crate::{ + util::{build_l, compute_w_circ_l, merge_polynomials}, + KZGMultilinearPC, MultilinearCommitmentScheme, PCSErrors, ProverParam, UniversalParams, + VerifierParam, +}; use ark_ec::{ msm::{FixedBaseMSM, VariableBaseMSM}, AffineCurve, PairingEngine, ProjectiveCurve, }; use ark_ff::PrimeField; -use ark_poly::MultilinearExtension; +use ark_poly::{univariate::DensePolynomial, MultilinearExtension, Polynomial, UVPolynomial}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; -use ark_std::{end_timer, rand::RngCore, start_timer, vec::Vec, One, Zero}; - -use crate::{ - KZGMultilinearPC, MultilinearCommitmentScheme, PCSErrors, ProverParam, UniversalParams, - VerifierParam, -}; +use ark_std::{end_timer, log2, rand::RngCore, start_timer, vec::Vec, One, Zero}; +use poly_iop::IOPTranscript; #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] /// commitment @@ -28,12 +29,30 @@ pub struct Proof { pub proofs: Vec, } +#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] +/// proof of batch opening +pub struct BatchProof { + /// The actual proof + pub proof: Proof, + /// The value which is `w` evaluated at `p:= l(r)`, where + /// - `w` is the merged MLE + /// - `l` is the list of univariate polys that goes through all points + /// - `r` is sampled from the transcript. + pub value: E::Fr, + /// Commitment to q(x) + // This is currently set to the entire coefficient list of q(x) + // TODO: replace me with a KZG commit + pub q_x_com: Vec, +} + impl MultilinearCommitmentScheme for KZGMultilinearPC { type ProverParam = ProverParam; type VerifierParam = VerifierParam; type SRS = UniversalParams; type Commitment = Commitment; type Proof = Proof; + type Transcript = IOPTranscript; + type BatchProof = BatchProof; /// Generate SRS from RNG. /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. @@ -71,12 +90,42 @@ impl MultilinearCommitmentScheme for KZGMultilinearPC { Ok(Commitment { nv, g_product }) } + /// Generate a commitment for a list of polynomials. + /// + /// This function takes `2^(num_vars + log(polys.len())` number of scalar + /// multiplications over G1. + fn multi_commit( + prover_param: &Self::ProverParam, + polys: &[impl MultilinearExtension], + ) -> Result { + let commit_timer = start_timer!(|| "multi commit"); + let poly = merge_polynomials(polys)?; + + let scalars: Vec<_> = poly + .to_evaluations() + .iter() + .map(|x| x.into_repr()) + .collect(); + + let g_product = VariableBaseMSM::multi_scalar_mul( + &prover_param.powers_of_g[0].evals, + scalars.as_slice(), + ) + .into_affine(); + + end_timer!(commit_timer); + Ok(Commitment { + nv: poly.num_vars, + g_product, + }) + } + /// On input a polynomial `p` and a point `point`, outputs a proof for the /// same. This function does not need to take the evaluation value as an /// input. /// /// This function takes 2^{num_var +1} number of scalar multiplications over - /// G2: + /// 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. @@ -136,6 +185,117 @@ impl MultilinearCommitmentScheme for KZGMultilinearPC { Ok(Proof { proofs }) } + /// Input + /// - the prover parameters, + /// - a list of MLEs, + /// - and a same number of points, + /// - and a transcript, + /// compute a multi-opening for all the polynomials. + /// + /// For simplicity, this API requires each MLE to have only one point. If + /// the caller wish to use more than one points per MLE, it should be + /// handled at the caller layer. + /// + /// Returns an error if the lengths do not match. + /// + /// Returns: + /// - the proof, + /// - q(x), which is a univariate polynomial `w circ l` where `w` is the + /// merged MLE, and `l` is a list of polynomials that go through all the + /// points. TODO: change this field to a commitment to `q(x)` + /// - and a value which is `w` evaluated at `p:= l(r)` from some `r` from + /// the transcript. + /// + /// Steps: + /// 1. build `l(points)` which is a list of univariate polynomials that goes + /// through the points + /// 2. build MLE `w` which is the merge of all MLEs. + /// 3. build `q(x)` which is a univariate polynomial `W circ l` + /// 4. output `q(x)`' and put it into transcript + /// 5. sample `r` from transcript + /// 6. get a point `p := l(r)` + /// 7. output an opening of `w` over point `p` + /// 8. output `w(p)` + fn multi_open( + prover_param: &Self::ProverParam, + polynomials: &[impl MultilinearExtension], + points: &[&[E::Fr]], + transcript: &mut IOPTranscript, + ) -> Result { + let open_timer = start_timer!(|| "multi open"); + + if points.len() != polynomials.len() { + return Err(PCSErrors::InvalidParameters( + "polynomial length does not match point length".to_string(), + )); + } + + let num_var = polynomials[0].num_vars(); + for poly in polynomials.iter().skip(1) { + if poly.num_vars() != num_var { + return Err(PCSErrors::InvalidParameters( + "polynomials do not have same num_vars".to_string(), + )); + } + } + for &point in points.iter() { + if point.len() != num_var { + return Err(PCSErrors::InvalidParameters( + "points do not have same num_vars".to_string(), + )); + } + } + + // 1. build `l(points)` which is a list of univariate polynomials that goes + // through the points + let uni_polys = build_l(num_var, points)?; + + // 2. build MLE `w` which is the merge of all MLEs. + let merge_poly = merge_polynomials(polynomials)?; + + // 3. build `q(x)` which is a univariate polynomial `W circ l` + let q_x = compute_w_circ_l(&merge_poly, &uni_polys)?; + + // 4. output `q(x)`' and put it into transcript + // + // TODO: use KZG commit for q(x) + // TODO: unwrap + q_x.coeffs + .iter() + .for_each(|x| transcript.append_field_element(b"q(x)", x).unwrap()); + + // 5. sample `r` from transcript + let r = transcript.get_and_append_challenge(b"r")?; + + // 6. get a point `p := l(r)` + let point: Vec = uni_polys + .iter() + .rev() + .map(|poly| poly.evaluate(&r)) + .collect(); + + // 7. output an opening of `w` over point `p` + let opening = Self::open(prover_param, &merge_poly, &point)?; + + // 8. output value that is `w` evaluated at `p` (which should match `q(r)`) + let value = merge_poly.evaluate(&point).unwrap(); + let value2 = q_x.evaluate(&r); + + if value != value2 { + return Err(PCSErrors::InvalidProver( + "Q(r) does not match W(l(r))".to_string(), + )); + } + + end_timer!(open_timer); + + Ok(Self::BatchProof { + proof: opening, + q_x_com: q_x.coeffs, + value, + }) + } + /// Verifies that `value` is the evaluation at `x` of the polynomial /// committed inside `comm`. /// @@ -146,7 +306,7 @@ impl MultilinearCommitmentScheme for KZGMultilinearPC { verifier_param: &Self::VerifierParam, commitment: &Self::Commitment, point: &[E::Fr], - value: E::Fr, + value: &E::Fr, proof: &Self::Proof, ) -> Result { let verify_timer = start_timer!(|| "verify"); @@ -185,7 +345,7 @@ impl MultilinearCommitmentScheme for KZGMultilinearPC { pairings.push(( E::G1Prepared::from( - (verifier_param.g.mul(value) - commitment.g_product.into_projective()) + (verifier_param.g.mul(*value) - commitment.g_product.into_projective()) .into_affine(), ), E::G2Prepared::from(verifier_param.h), @@ -197,19 +357,98 @@ impl MultilinearCommitmentScheme for KZGMultilinearPC { end_timer!(verify_timer); Ok(res) } + + /// Verifies that `value` is the evaluation at `x_i` of the polynomial + /// `poly_i` committed inside `comm`. + /// steps: + /// + /// 1. put `q(x)`'s evaluations over `(1, omega,...)` into transcript + /// 2. sample `r` from transcript + /// 3. check `q(r) == value` + /// 4. build `l(points)` which is a list of univariate polynomials that goes + /// through the points + /// 5. get a point `p := l(r)` + /// 6. verifies `p` is verifies against proof + fn batch_verify( + verifier_param: &Self::VerifierParam, + multi_commitment: &Self::Commitment, + points: &[&[E::Fr]], + batch_proof: &Self::BatchProof, + transcript: &mut IOPTranscript, + ) -> Result { + let verify_timer = start_timer!(|| "batch verify"); + let num_var = points[0].len(); + + for &point in points.iter().skip(1) { + if point.len() != num_var { + return Err(PCSErrors::InvalidParameters(format!( + "points do not have same num_vars ({} vs {})", + point.len(), + num_var, + ))); + } + } + if num_var + log2(points.len()) as usize != multi_commitment.nv { + return Err(PCSErrors::InvalidParameters(format!( + "points and multi_commitment do not have same num_vars ({} vs {})", + num_var + log2(points.len()) as usize, + num_var, + ))); + } + + // TODO: verify commitment of `q(x)` instead of receiving full `q(x)` + + // 1. put `q(x)`'s evaluations over `(1, omega,...)` into transcript + // TODO: unwrap + batch_proof + .q_x_com + .iter() + .for_each(|x| transcript.append_field_element(b"q(x)", x).unwrap()); + + // 2. sample `r` from transcript + let r = transcript.get_and_append_challenge(b"r")?; + + // 3. check `q(r) == value` + let q_x = DensePolynomial::from_coefficients_slice(&batch_proof.q_x_com); + let q_r = q_x.evaluate(&r); + if q_r != batch_proof.value { + return Ok(false); + } + + // 4. build `l(points)` which is a list of univariate polynomials that goes + // through the points + let uni_polys = build_l(num_var, points)?; + + // 5. get a point `p := l(r)` + let point: Vec = uni_polys.iter().rev().map(|x| x.evaluate(&r)).collect(); + + // 6. verifies `p` is verifies against proof + let res = Self::verify( + verifier_param, + multi_commitment, + &point, + &batch_proof.value, + &batch_proof.proof, + ); + end_timer!(verify_timer); + + res + } } #[cfg(test)] mod tests { + use crate::util::get_batched_nv; + use super::*; use ark_bls12_381::Bls12_381; use ark_ec::PairingEngine; - use ark_poly::{DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension}; + use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_std::{rand::RngCore, test_rng, vec::Vec, UniformRand}; type E = Bls12_381; type Fr = ::Fr; - fn test_kzg_mlpc_helper( + fn test_single_helper( uni_params: &UniversalParams, poly: &impl MultilinearExtension, rng: &mut R, @@ -222,33 +461,152 @@ mod tests { let proof = KZGMultilinearPC::open(&ck, poly, &point)?; let value = poly.evaluate(&point).unwrap(); - assert!(KZGMultilinearPC::verify(&vk, &com, &point, value, &proof)?); + assert!(KZGMultilinearPC::verify(&vk, &com, &point, &value, &proof)?); let value = Fr::rand(rng); - assert!(!KZGMultilinearPC::verify(&vk, &com, &point, value, &proof)?); + assert!(!KZGMultilinearPC::verify( + &vk, &com, &point, &value, &proof + )?); Ok(()) } #[test] - fn setup_commit_verify_correct_polynomials() -> Result<(), PCSErrors> { + fn test_single_commit() -> Result<(), PCSErrors> { let mut rng = test_rng(); let uni_params = KZGMultilinearPC::::setup(&mut rng, 10)?; // normal polynomials let poly1 = DenseMultilinearExtension::rand(8, &mut rng); - test_kzg_mlpc_helper(&uni_params, &poly1, &mut rng)?; + test_single_helper(&uni_params, &poly1, &mut rng)?; - let poly2 = SparseMultilinearExtension::rand_with_config(9, 1 << 5, &mut rng); - test_kzg_mlpc_helper(&uni_params, &poly2, &mut rng)?; + // single-variate polynomials + let poly2 = DenseMultilinearExtension::rand(1, &mut rng); + test_single_helper(&uni_params, &poly2, &mut rng)?; + + Ok(()) + } + + fn test_multi_commit_helper( + uni_params: &UniversalParams, + polys: &[impl MultilinearExtension], + rng: &mut R, + ) -> Result<(), PCSErrors> { + let mut transcript = IOPTranscript::new(b"test"); + + let nv = get_batched_nv(polys[0].num_vars(), polys.len()); + let (ck, vk) = uni_params.trim(nv)?; + let mut points = Vec::new(); + + for poly in polys.iter() { + let point = (0..poly.num_vars()) + .map(|_| Fr::rand(rng)) + .collect::>(); + points.push(point); + } + let points_ref: Vec<&[Fr]> = points.iter().map(|x| x.as_ref()).collect(); + + let com = KZGMultilinearPC::multi_commit(&ck, polys)?; + let batch_proof = KZGMultilinearPC::multi_open(&ck, polys, &points_ref, &mut transcript)?; + + // good path + let mut transcript = IOPTranscript::new(b"test"); + assert!(KZGMultilinearPC::batch_verify( + &vk, + &com, + &points_ref, + &batch_proof, + &mut transcript + )?); + + // bad commitment + assert!(KZGMultilinearPC::batch_verify( + &vk, + &Commitment { + nv: 0, + g_product: ::G1Affine::default() + }, + &points_ref, + &batch_proof, + &mut transcript + ) + .is_err()); + + // bad points + let points_ref: Vec<&[Fr]> = points.iter().skip(1).map(|x| x.as_ref()).collect(); + assert!(KZGMultilinearPC::batch_verify( + &vk, + &com, + &points_ref, + &batch_proof, + &mut transcript + ) + .is_err()); + + // bad proof + assert!(KZGMultilinearPC::batch_verify( + &vk, + &com, + &points_ref, + &BatchProof { + proof: Proof { proofs: Vec::new() }, + value: batch_proof.value, + q_x_com: batch_proof.q_x_com.clone() + }, + &mut transcript + ) + .is_err()); + + // bad value + assert!(KZGMultilinearPC::batch_verify( + &vk, + &com, + &points_ref, + &BatchProof { + proof: batch_proof.proof.clone(), + value: Fr::one(), + q_x_com: batch_proof.q_x_com + }, + &mut transcript + ) + .is_err()); + + // bad q(x) commit + assert!(KZGMultilinearPC::batch_verify( + &vk, + &com, + &points_ref, + &BatchProof { + proof: batch_proof.proof, + value: batch_proof.value, + q_x_com: Vec::new() + }, + &mut transcript + ) + .is_err()); + + Ok(()) + } + + #[test] + fn test_multi_commit() -> Result<(), PCSErrors> { + let mut rng = test_rng(); + + let uni_params = KZGMultilinearPC::::setup(&mut rng, 15)?; + + // normal polynomials + let polys1: Vec<_> = (0..2) + .map(|_| DenseMultilinearExtension::rand(4, &mut rng)) + .collect(); + test_multi_commit_helper(&uni_params, &polys1, &mut rng)?; // single-variate polynomials - let poly3 = DenseMultilinearExtension::rand(1, &mut rng); - test_kzg_mlpc_helper(&uni_params, &poly3, &mut rng)?; + let polys1: Vec<_> = (0..5) + .map(|_| DenseMultilinearExtension::rand(1, &mut rng)) + .collect(); + test_multi_commit_helper(&uni_params, &polys1, &mut rng)?; - let poly4 = SparseMultilinearExtension::rand_with_config(1, 1 << 1, &mut rng); - test_kzg_mlpc_helper(&uni_params, &poly4, &mut rng)?; Ok(()) } diff --git a/pcs/src/errors.rs b/pcs/src/errors.rs index bf4c61f..99d060d 100644 --- a/pcs/src/errors.rs +++ b/pcs/src/errors.rs @@ -1,7 +1,9 @@ //! Error module. +use ark_serialize::SerializationError; use ark_std::string::String; use displaydoc::Display; +use poly_iop::PolyIOPErrors; /// A `enum` specifying the possible failure modes of the PCS. #[derive(Display, Debug)] @@ -15,11 +17,19 @@ pub enum PCSErrors { /// Invalid parameters: {0} InvalidParameters(String), /// An error during (de)serialization: {0} - SerializationError(ark_serialize::SerializationError), + SerializationError(SerializationError), + /// PolyIOP error {0} + PolyIOPErrors(PolyIOPErrors), } -impl From for PCSErrors { +impl From for PCSErrors { fn from(e: ark_serialize::SerializationError) -> Self { Self::SerializationError(e) } } + +impl From for PCSErrors { + fn from(e: PolyIOPErrors) -> Self { + Self::PolyIOPErrors(e) + } +} diff --git a/pcs/src/lib.rs b/pcs/src/lib.rs index 52c247b..2663490 100644 --- a/pcs/src/lib.rs +++ b/pcs/src/lib.rs @@ -1,10 +1,12 @@ mod commit; mod errors; mod param; +mod util; use ark_ec::PairingEngine; use ark_poly::MultilinearExtension; use ark_std::rand::RngCore; +use poly_iop::IOPTranscript; use std::marker::PhantomData; pub use errors::PCSErrors; @@ -12,6 +14,7 @@ pub use param::{ProverParam, UniversalParams, VerifierParam}; /// KZG Polynomial Commitment Scheme on multilinear extensions. pub struct KZGMultilinearPC { + #[doc(hidden)] phantom: PhantomData, } @@ -21,6 +24,8 @@ pub trait MultilinearCommitmentScheme { type SRS; type Commitment; type Proof; + type BatchProof; + type Transcript; /// Generate SRS from RNG. /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. @@ -33,6 +38,12 @@ pub trait MultilinearCommitmentScheme { poly: &impl MultilinearExtension, ) -> Result; + /// Generate a commitment for a list of polynomials + fn multi_commit( + prover_param: &Self::ProverParam, + polys: &[impl MultilinearExtension], + ) -> Result; + /// On input a polynomial `p` and a point `point`, outputs a proof for the /// same. fn open( @@ -41,13 +52,34 @@ pub trait MultilinearCommitmentScheme { point: &[E::Fr], ) -> Result; + /// Input a list of MLEs, and a same number of points, and a transcript, + /// compute a multi-opening for all the polynomials. + #[allow(clippy::type_complexity)] + // TODO: remove after we KZG-commit q(x) + fn multi_open( + prover_param: &Self::ProverParam, + polynomials: &[impl MultilinearExtension], + point: &[&[E::Fr]], + transcript: &mut Self::Transcript, + ) -> Result; + /// Verifies that `value` is the evaluation at `x` of the polynomial /// committed inside `comm`. fn verify( verifier_param: &Self::VerifierParam, commitment: &Self::Commitment, point: &[E::Fr], - value: E::Fr, + value: &E::Fr, proof: &Self::Proof, ) -> Result; + + /// Verifies that `value_i` is the evaluation at `x_i` of the polynomial + /// `poly_i` committed inside `comm`. + fn batch_verify( + verifier_param: &Self::VerifierParam, + multi_commitment: &Self::Commitment, + points: &[&[E::Fr]], + batch_proof: &Self::BatchProof, + transcript: &mut IOPTranscript, + ) -> Result; } diff --git a/pcs/src/param.rs b/pcs/src/param.rs index c70beb1..f84a623 100644 --- a/pcs/src/param.rs +++ b/pcs/src/param.rs @@ -10,6 +10,7 @@ use ark_std::{end_timer, rand::RngCore, start_timer, vec::Vec, UniformRand}; /// Evaluations over {0,1}^n for G1 or G2 #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] pub struct Evaluations { + /// The evaluations. pub evals: Vec, } diff --git a/pcs/src/util.rs b/pcs/src/util.rs new file mode 100644 index 0000000..39a7452 --- /dev/null +++ b/pcs/src/util.rs @@ -0,0 +1,610 @@ +//! Useful utilities for KZG PCS + +use crate::PCSErrors; +use ark_ff::PrimeField; +use ark_poly::{ + univariate::DensePolynomial, DenseMultilinearExtension, EvaluationDomain, Evaluations, + MultilinearExtension, Polynomial, Radix2EvaluationDomain, +}; +use ark_std::{end_timer, log2, start_timer}; +use poly_iop::bit_decompose; + +/// 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( + w: &DenseMultilinearExtension, + l: &[DensePolynomial], +) -> Result, PCSErrors> { + let timer = start_timer!(|| "compute W \\circ l"); + + if w.num_vars != l.len() { + return Err(PCSErrors::InvalidParameters(format!( + "l's length ({}) does not match num_variables ({})", + l.len(), + w.num_vars(), + ))); + } + + let mut res_eval: Vec = vec![]; + + // TODO: consider to pass this in from caller + // uni_degree is (product of each prefix's) + (2 * MLEs) + // = (l.len() - (num_vars - log(l.len())) + 2) * l[0].degree + let uni_degree = (l.len() - w.num_vars + log2(l.len()) as usize + 2) * l[0].degree(); + + let domain = match Radix2EvaluationDomain::::new(uni_degree) { + Some(p) => p, + None => { + return Err(PCSErrors::InvalidParameters( + "failed to build radix 2 domain".to_string(), + )) + }, + }; + for point in domain.elements() { + // we reverse the order here because the coefficient vec are stored in + // bit-reversed order + let l_eval: Vec = l.iter().rev().map(|x| x.evaluate(&point)).collect(); + res_eval.push(w.evaluate(l_eval.as_ref()).unwrap()) + } + let evaluation = Evaluations::from_vec_and_domain(res_eval, domain); + let res = evaluation.interpolate(); + + end_timer!(timer); + Ok(res) +} + +/// Return the number of variables that one need for an MLE to +/// batch the list of MLEs +#[inline] +pub(crate) fn get_batched_nv(num_var: usize, polynomials_len: usize) -> usize { + num_var + log2(polynomials_len) as usize +} + +/// merge a set of polynomials. Returns an error if the +/// polynomials do not share a same number of nvs. +pub(crate) fn merge_polynomials( + polynomials: &[impl MultilinearExtension], +) -> Result, PCSErrors> { + let nv = polynomials[0].num_vars(); + for poly in polynomials.iter() { + if nv != poly.num_vars() { + return Err(PCSErrors::InvalidParameters( + "num_vars do not match for polynomials".to_string(), + )); + } + } + + let merged_nv = get_batched_nv(nv, polynomials.len()); + let mut scalars = vec![]; + for poly in polynomials.iter() { + scalars.extend_from_slice(poly.to_evaluations().as_slice()); + } + scalars.extend_from_slice(vec![F::zero(); (1 << merged_nv) - scalars.len()].as_ref()); + Ok(DenseMultilinearExtension::from_evaluations_vec( + merged_nv, scalars, + )) +} + +/// Given a list of points, build `l(points)` which is a list of univariate +/// polynomials that goes through the points +pub(crate) fn build_l( + num_var: usize, + points: &[&[F]], +) -> Result>, PCSErrors> { + let prefix_len = log2(points.len()) as usize; + + let uni_degree = points.len(); + let small_domain = match Radix2EvaluationDomain::::new(uni_degree) { + Some(p) => p, + None => { + return Err(PCSErrors::InvalidParameters( + "failed to build radix 2 domain".to_string(), + )) + }, + }; + + // The following code print out the roots for testing + // println!("domain root0: {}", small_domain.element(0)); + // println!("domain root1: {}", small_domain.element(1)); + // println!("domain root2: {}", small_domain.element(2)); + // println!("domain root3: {}", small_domain.element(3)); + + let mut uni_polys = Vec::new(); + + // 1.1 build the indexes and the univariate polys that go through the indexes + let indexes: Vec> = (0..points.len()) + .map(|x| bit_decompose(x as u64, prefix_len)) + .collect(); + for i in 0..prefix_len { + let eval: Vec = indexes + .iter() + .map(|x| F::from(x[prefix_len - i - 1])) + .collect(); + + uni_polys.push(Evaluations::from_vec_and_domain(eval, small_domain).interpolate()); + } + + // 1.2 build the actual univariate polys that go through the points + for i in 0..num_var { + let mut eval: Vec = points.iter().map(|x| x[i]).collect(); + eval.extend_from_slice(vec![F::zero(); small_domain.size as usize - eval.len()].as_slice()); + uni_polys.push(Evaluations::from_vec_and_domain(eval, small_domain).interpolate()) + } + + Ok(uni_polys) +} + +#[cfg(test)] +mod test { + use super::*; + use ark_bls12_381::Fr; + use ark_ff::field_new; + use ark_poly::UVPolynomial; + use ark_std::{One, Zero}; + + #[test] + fn test_w_circ_l() -> Result<(), PCSErrors> { + test_w_circ_l_helper::() + } + + fn test_w_circ_l_helper() -> Result<(), PCSErrors> { + { + // Example from page 53: + // W = 3x1x2 + 2x2 whose evaluations are + // 0, 0 |-> 0 + // 0, 1 |-> 2 + // 1, 0 |-> 0 + // 1, 1 |-> 5 + let w_eval = vec![F::zero(), F::from(2u64), F::zero(), 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())?; + 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 + // 0, 0, 1 |-> 7 + // 0, 1, 0 |-> -6 + // 0, 1, 1 |-> 4 + // 1, 0, 0 |-> 5 + // 1, 0, 1 |-> 8 + // 1, 1, 0 |-> -3 + // 1, 1, 1 |-> 4 + let w_eval = vec![ + F::zero(), + F::from(7u64), + -F::from(6u64), + F::from(4u64), + F::from(5u64), + F::from(8u64), + -F::from(3u64), + 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())?; + + // 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_merge_poly() -> Result<(), PCSErrors> { + test_merge_poly_helper::() + } + fn test_merge_poly_helper() -> Result<(), PCSErrors> { + // Example from page 53: + // W1 = 3x1x2 + 2x2 whose evaluations are + // 0, 0 |-> 0 + // 0, 1 |-> 2 + // 1, 0 |-> 0 + // 1, 1 |-> 5 + let w_eval = vec![F::zero(), F::from(2u64), F::zero(), F::from(5u64)]; + let w1 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval); + + // W2 = x1x2 + x1 whose evaluations are + // 0, 0 |-> 0 + // 0, 1 |-> 0 + // 1, 0 |-> 1 + // 1, 1 |-> 2 + let w_eval = vec![F::zero(), F::zero(), F::from(1u64), F::from(2u64)]; + let w2 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval); + + // W3 = x1 + x2 whose evaluations are + // 0, 0 |-> 0 + // 0, 1 |-> 1 + // 1, 0 |-> 1 + // 1, 1 |-> 2 + let w_eval = vec![F::zero(), F::one(), F::from(1u64), F::from(2u64)]; + let w3 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval); + + { + // W = (3x1x2 + 2x2)(1-x0) + (x1x2 + x1)x0 + // = -2x0x1x2 + x0x1 - 2x0x2 + 3x1x2 + 2x2 + // with evaluation map + // + // x0 x1 x2 + // 0, 0, 0 |-> 0 + // 0, 0, 1 |-> 2 + // 0, 1, 0 |-> 0 + // 0, 1, 1 |-> 5 + // 1, 0, 0 |-> 0 + // 1, 0, 1 |-> 0 + // 1, 1, 0 |-> 1 + // 1, 1, 1 |-> 2 + // + let w = merge_polynomials(&[w1.clone(), w2.clone()])?; + // w is [0,2,0,5,0,0,1,2] + let w_eval = vec![ + F::zero(), + F::from(2u64), + F::zero(), + F::from(5u64), + F::zero(), + F::zero(), + F::from(1u64), + F::from(2u64), + ]; + let w_rec = DenseMultilinearExtension::from_evaluations_vec(3, w_eval); + + assert_eq!(w, w_rec); + } + + { + // W = (3x1x2 + 2x2) * (1-y1) * (1-y2) + // + (x1x2 + x1) * (1-y1) * y2 + // + (x1 + x2) * y1 * (1-y2) + // + // with evaluation map + // + // y1 y2 x1 x2 + // 0, 0, 0, 0 |-> 0 + // 0, 0, 0, 1 |-> 2 + // 0, 0, 1, 0 |-> 0 + // 0, 0, 1, 1 |-> 5 + // 0, 1, 0, 0 |-> 0 + // 0, 1, 0, 1 |-> 0 + // 0, 1, 1, 0 |-> 1 + // 0, 1, 1, 1 |-> 2 + // 1, 0, 0, 0 |-> 0 + // 1, 0, 0, 1 |-> 1 + // 1, 0, 1, 0 |-> 1 + // 1, 0, 1, 1 |-> 2 + // 1, 1, 0, 0 |-> 0 + // 1, 1, 0, 1 |-> 0 + // 1, 1, 1, 0 |-> 0 + // 1, 1, 1, 1 |-> 0 + // + let w = merge_polynomials(&[w1, w2, w3])?; + // w is [0,2,0,5,0,0,1,2, 0,1,1,2] + let w_eval = vec![ + F::zero(), + F::from(2u64), + F::zero(), + F::from(5u64), + F::zero(), + F::zero(), + F::from(1u64), + F::from(2u64), + F::zero(), + F::one(), + F::from(1u64), + F::from(2u64), + F::zero(), + F::zero(), + F::zero(), + F::zero(), + ]; + let w_rec = DenseMultilinearExtension::from_evaluations_vec(4, w_eval); + + assert_eq!(w, w_rec); + } + Ok(()) + } + + #[test] + fn test_build_l() -> Result<(), PCSErrors> { + test_build_l_helper::() + } + + fn test_build_l_helper() -> Result<(), PCSErrors> { + // point 1 is [1, 2] + let point1 = [Fr::from(1u64), Fr::from(2u64)]; + + // point 2 is [3, 4] + let point2 = [Fr::from(3u64), Fr::from(4u64)]; + + // point 3 is [5, 6] + let point3 = [Fr::from(5u64), Fr::from(6u64)]; + + { + let l = build_l(2, &[&point1, &point2])?; + + // roots: [1, -1] + // l0 = -1/2 * x + 1/2 + // l1 = -x + 2 + // l2 = -x + 3 + let l0 = DensePolynomial::from_coefficients_vec(vec![ + Fr::one() / Fr::from(2u64), + -Fr::one() / Fr::from(2u64), + ]); + let l1 = DensePolynomial::from_coefficients_vec(vec![Fr::from(2u64), -Fr::one()]); + let l2 = DensePolynomial::from_coefficients_vec(vec![Fr::from(3u64), -Fr::one()]); + + assert_eq!(l0, l[0], "l0 not equal"); + assert_eq!(l1, l[1], "l1 not equal"); + assert_eq!(l2, l[2], "l2 not equal"); + } + + { + let l = build_l(2, &[&point1, &point2, &point3])?; + + // sage: q = 52435875175126190479447740508185965837690552500527637822603658699938581184513 + // sage: P. = PolynomialRing(Zmod(q)) + // sage: root1 = 1 + // sage: root2 = 0x8D51CCCE760304D0EC030002760300000001000000000000 + // sage: root3 = -1 + // sage: root4 = -root2 + // Arkwork's code is a bit wired: it also interpolate (root4, 0) + // which returns a degree 3 polynomial, instead of degree 2 + + // ======================== + // l0: [0, 0, 1] + // ======================== + // sage: points = [(root1, 0), (root2, 0), (root3, 1), (root4, 0)] + // sage: P.lagrange_polynomial(points) + // 13108968793781547619861935127046491459422638125131909455650914674984645296128*x^3 + + // 39326906381344642859585805381139474378267914375395728366952744024953935888385*x^2 + + // 13108968793781547619861935127046491459422638125131909455650914674984645296128*x + + // 39326906381344642859585805381139474378267914375395728366952744024953935888385 + let l0 = DensePolynomial::from_coefficients_vec(vec![ + field_new!( + Fr, + "39326906381344642859585805381139474378267914375395728366952744024953935888385" + ), + field_new!( + Fr, + "13108968793781547619861935127046491459422638125131909455650914674984645296128" + ), + field_new!( + Fr, + "39326906381344642859585805381139474378267914375395728366952744024953935888385" + ), + field_new!( + Fr, + "13108968793781547619861935127046491459422638125131909455650914674984645296128" + ), + ]); + + // ======================== + // l1: [0, 1, 0] + // ======================== + // sage: points = [(root1, 0), (root2, 1), (root3, 0), (root4, 0)] + // sage: P.lagrange_polynomial(points) + // 866286206518413079694067382671935694567563117191340490752*x^3 + + // 13108968793781547619861935127046491459422638125131909455650914674984645296128*x^2 + + // 52435875175126190478581454301667552757996485117855702128036095582747240693761*x + + // 39326906381344642859585805381139474378267914375395728366952744024953935888385 + let l1 = DensePolynomial::from_coefficients_vec(vec![ + field_new!( + Fr, + "39326906381344642859585805381139474378267914375395728366952744024953935888385" + ), + field_new!( + Fr, + "52435875175126190478581454301667552757996485117855702128036095582747240693761" + ), + field_new!( + Fr, + "13108968793781547619861935127046491459422638125131909455650914674984645296128" + ), + field_new!( + Fr, + "866286206518413079694067382671935694567563117191340490752" + ), + ]); + + // ======================== + // l2: [1, 3, 5] + // ======================== + // sage: points = [(root1, 1), (root2, 3), (root3, 5), (root4, 0)] + // sage: P.lagrange_polynomial(points) + // 2598858619555239239082202148015807083702689351574021472255*x^3 + + // 13108968793781547619861935127046491459422638125131909455650914674984645296129*x^2 + + // 52435875175126190476848881888630726598608350352511830738900969348364559712256*x + + // 39326906381344642859585805381139474378267914375395728366952744024953935888387 + let l2 = DensePolynomial::from_coefficients_vec(vec![ + field_new!( + Fr, + "39326906381344642859585805381139474378267914375395728366952744024953935888387" + ), + field_new!( + Fr, + "52435875175126190476848881888630726598608350352511830738900969348364559712256" + ), + field_new!( + Fr, + "13108968793781547619861935127046491459422638125131909455650914674984645296129" + ), + field_new!( + Fr, + "2598858619555239239082202148015807083702689351574021472255" + ), + ]); + + // ======================== + // l3: [2, 4, 6] + // ======================== + // sage: points = [(root1, 2), (root2, 4), (root3, 6), (root4, 0)] + // sage: P.lagrange_polynomial(points) + // 3465144826073652318776269530687742778270252468765361963007*x^3 + + // x^2 + + // 52435875175126190475982595682112313518914282969839895044333406231173219221504*x + + // 3 + let l3 = DensePolynomial::from_coefficients_vec(vec![ + Fr::from(3u64), + field_new!( + Fr, + "52435875175126190475982595682112313518914282969839895044333406231173219221504" + ), + Fr::one(), + field_new!( + Fr, + "3465144826073652318776269530687742778270252468765361963007" + ), + ]); + + assert_eq!(l0, l[0], "l0 not equal"); + assert_eq!(l1, l[1], "l1 not equal"); + assert_eq!(l2, l[2], "l2 not equal"); + assert_eq!(l3, l[3], "l3 not equal"); + } + Ok(()) + } + + #[test] + fn test_qx() -> Result<(), PCSErrors> { + // Example from page 53: + // W1 = 3x1x2 + 2x2 + let w_eval = vec![Fr::zero(), Fr::from(2u64), Fr::zero(), Fr::from(5u64)]; + let w1 = 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 = 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 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval); + + let r = Fr::from(42u64); + + // point 1 is [1, 2] + let point1 = [Fr::from(1u64), Fr::from(2u64)]; + + // point 2 is [3, 4] + let point2 = [Fr::from(3u64), Fr::from(4u64)]; + + // point 3 is [5, 6] + let point3 = [Fr::from(5u64), Fr::from(6u64)]; + + { + // w = (3x1x2 + 2x2)(1-x0) + (x1x2 + x1)x0 + // with evaluations: [0,2,0,5,0,0,1,2] + let w = merge_polynomials(&[w1.clone(), w2.clone()])?; + + let l = build_l(2, &[&point1, &point2])?; + + // sage: P. = 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)?; + + let point: Vec = l.iter().rev().map(|poly| poly.evaluate(&r)).collect(); + + assert_eq!( + q_x.evaluate(&r), + w.evaluate(&point).unwrap(), + "q(r) != w(l(r))" + ); + } + + { + // W = (3x1x2 + 2x2) * (1-y1) * (1-y2) + // + (x1x2 + x1) * (1-y1) * y2 + // + (x1 + x2) * y1 * (1-y2) + let w = merge_polynomials(&[w1, w2, w3])?; + + let l = build_l(2, &[&point1, &point2, &point3])?; + + // l0 = + // 13108968793781547619861935127046491459422638125131909455650914674984645296128*x^3 + + // 39326906381344642859585805381139474378267914375395728366952744024953935888385*x^2 + + // 13108968793781547619861935127046491459422638125131909455650914674984645296128*x + + // 39326906381344642859585805381139474378267914375395728366952744024953935888385 + // + // l1 = + // 866286206518413079694067382671935694567563117191340490752*x^3 + + // 13108968793781547619861935127046491459422638125131909455650914674984645296128*x^2 + + // 52435875175126190478581454301667552757996485117855702128036095582747240693761*x + + // 39326906381344642859585805381139474378267914375395728366952744024953935888385 + // + // l2 = + // 2598858619555239239082202148015807083702689351574021472255*x^3 + + // 13108968793781547619861935127046491459422638125131909455650914674984645296129*x^2 + + // 52435875175126190476848881888630726598608350352511830738900969348364559712256*x + + // 39326906381344642859585805381139474378267914375395728366952744024953935888387 + // + // l3 = + // 3465144826073652318776269530687742778270252468765361963007*x^3 + + // x^2 + + // 52435875175126190475982595682112313518914282969839895044333406231173219221504*x + + // 3 + // + // q_x = (3*l2*l3 + 2*l3) * (1-l0) *(1-l1) + // + (l2*l3+l2)*(1-l0)*l1 + // + (l2+l3)*l0*(1-l1) + // q_x(42) = 42675783400755005965526147011103024780845819057955866345013183657072368533932 + let q_x = compute_w_circ_l(&w, &l)?; + + let point: Vec = vec![ + l[3].evaluate(&r), + l[2].evaluate(&r), + l[1].evaluate(&r), + l[0].evaluate(&r), + ]; + + assert_eq!( + q_x.evaluate(&r), + field_new!( + Fr, + "42675783400755005965526147011103024780845819057955866345013183657072368533932" + ), + ); + assert_eq!( + q_x.evaluate(&r), + w.evaluate(&point).unwrap(), + "q(r) != w(l(r))" + ); + } + Ok(()) + } +} diff --git a/poly-iop/src/lib.rs b/poly-iop/src/lib.rs index 897a582..326dff1 100644 --- a/poly-iop/src/lib.rs +++ b/poly-iop/src/lib.rs @@ -16,6 +16,8 @@ pub use perm_check::{ PermutationCheck, }; pub use sum_check::SumCheck; +pub use transcript::IOPTranscript; +pub use utils::*; pub use virtual_poly::{VPAuxInfo, VirtualPolynomial}; pub use zero_check::ZeroCheck; diff --git a/poly-iop/src/transcript.rs b/poly-iop/src/transcript.rs index d14fb17..0a455d2 100644 --- a/poly-iop/src/transcript.rs +++ b/poly-iop/src/transcript.rs @@ -28,7 +28,7 @@ pub struct IOPTranscript { impl IOPTranscript { /// Create a new IOP transcript. - pub(crate) fn new(label: &'static [u8]) -> Self { + pub fn new(label: &'static [u8]) -> Self { Self { transcript: Transcript::new(label), is_empty: true, @@ -59,7 +59,7 @@ impl IOPTranscript { } // Append the message to the transcript. - pub(crate) fn append_field_element( + pub fn append_field_element( &mut self, label: &'static [u8], field_elem: &F, @@ -83,10 +83,7 @@ impl IOPTranscript { // // The output field element is statistical uniform as long // as the field has a size less than 2^384. - pub(crate) fn get_and_append_challenge( - &mut self, - label: &'static [u8], - ) -> Result { + pub fn get_and_append_challenge(&mut self, label: &'static [u8]) -> Result { // we need to reject when transcript is empty if self.is_empty { return Err(PolyIOPErrors::InvalidTranscript( diff --git a/poly-iop/src/utils.rs b/poly-iop/src/utils.rs index 274ee51..8d43b8b 100644 --- a/poly-iop/src/utils.rs +++ b/poly-iop/src/utils.rs @@ -13,7 +13,7 @@ macro_rules! to_bytes { /// Decompose an integer into a binary vector in little endian. #[allow(dead_code)] -pub(crate) fn bit_decompose(input: u64, num_var: usize) -> Vec { +pub fn bit_decompose(input: u64, num_var: usize) -> Vec { let mut res = Vec::with_capacity(num_var); let mut i = input; for _ in 0..num_var {