diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f78cb710..9c827473 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,7 @@ jobs: run: python3 ./scripts/install.py - name: Set RUSTFLAGS for AVX - if: matrix.feature != 'macos-latest' + if: matrix.os != 'macos-latest' run: echo "RUSTFLAGS=$RUSTFLAGS -C target-feature=+${{ matrix.feature }}" >> $GITHUB_ENV - name: Prepare binary diff --git a/Cargo.lock b/Cargo.lock index f459fe88..68513bb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1842,9 +1842,11 @@ dependencies = [ "ark-std", "criterion", "ethnum", + "field_hashers", "gf2", "gf2_128", "gkr_field_config", + "halo2curves", "itertools 0.13.0", "mersenne31", "mpi_config", diff --git a/arith/src/bn254.rs b/arith/src/bn254.rs index 3a02b3e1..587c66b1 100644 --- a/arith/src/bn254.rs +++ b/arith/src/bn254.rs @@ -1,7 +1,10 @@ use std::io::{Read, Write}; -use halo2curves::ff::{Field as Halo2Field, FromUniformBytes}; -use halo2curves::{bn256::Fr, ff::PrimeField}; +use halo2curves::{ + bn256::{Fr, G1Affine, G2Affine}, + ff::{Field as Halo2Field, FromUniformBytes, PrimeField}, + group::GroupEncoding, +}; use rand::RngCore; use crate::serde::{FieldSerdeError, FieldSerdeResult}; @@ -163,6 +166,44 @@ impl FieldSerde for Fr { } } +impl FieldSerde for G1Affine { + const SERIALIZED_SIZE: usize = 32; + + fn serialize_into(&self, writer: W) -> FieldSerdeResult<()> { + let bytes = self.to_bytes().as_ref().to_vec(); + bytes.serialize_into(writer) + } + + fn deserialize_from(reader: R) -> FieldSerdeResult { + let bytes: Vec = Vec::deserialize_from(reader)?; + let mut encoding = ::Repr::default(); + encoding.as_mut().copy_from_slice(bytes.as_ref()); + match G1Affine::from_bytes(&encoding).into_option() { + Some(a) => Ok(a), + None => Err(FieldSerdeError::DeserializeError), + } + } +} + +impl FieldSerde for G2Affine { + const SERIALIZED_SIZE: usize = 64; + + fn serialize_into(&self, writer: W) -> FieldSerdeResult<()> { + let bytes = self.to_bytes().as_ref().to_vec(); + bytes.serialize_into(writer) + } + + fn deserialize_from(reader: R) -> FieldSerdeResult { + let bytes: Vec = Vec::deserialize_from(reader)?; + let mut encoding = ::Repr::default(); + encoding.as_mut().copy_from_slice(bytes.as_ref()); + match G2Affine::from_bytes(&encoding).into_option() { + Some(a) => Ok(a), + None => Err(FieldSerdeError::DeserializeError), + } + } +} + impl ExtensionField for Fr { const DEGREE: usize = 1; diff --git a/poly_commit/Cargo.toml b/poly_commit/Cargo.toml index 5638a839..92f43d47 100644 --- a/poly_commit/Cargo.toml +++ b/poly_commit/Cargo.toml @@ -17,10 +17,12 @@ ethnum.workspace = true ark-std.workspace = true thiserror.workspace = true itertools.workspace = true +halo2curves.workspace = true [dev-dependencies] gf2_128 = { path = "../arith/gf2_128" } mersenne31 = { path = "../arith/mersenne31" } +field_hashers = { path = "../arith/field_hashers" } tynm.workspace = true criterion.workspace = true diff --git a/poly_commit/src/kzg.rs b/poly_commit/src/kzg.rs new file mode 100644 index 00000000..b019da1a --- /dev/null +++ b/poly_commit/src/kzg.rs @@ -0,0 +1,20 @@ +mod structs; +pub use structs::*; + +mod utils; +pub use utils::*; + +mod univariate; +pub use univariate::*; + +mod bivariate; +pub use bivariate::*; + +mod hyper_kzg; +pub use hyper_kzg::*; + +mod pcs_trait_impl; +pub use pcs_trait_impl::*; + +#[cfg(test)] +mod tests; diff --git a/poly_commit/src/kzg/bivariate.rs b/poly_commit/src/kzg/bivariate.rs new file mode 100644 index 00000000..fd2824f0 --- /dev/null +++ b/poly_commit/src/kzg/bivariate.rs @@ -0,0 +1,134 @@ +use halo2curves::{ + ff::Field, + group::{prime::PrimeCurveAffine, Curve, Group}, + msm::best_multiexp, + pairing::{MillerLoopResult, MultiMillerLoop}, + CurveAffine, +}; + +use crate::*; + +#[inline(always)] +pub fn generate_coef_form_bi_kzg_local_srs_for_testing( + local_length: usize, + distributed_parties: usize, + party_rank: usize, + mut rng: impl rand::RngCore, +) -> CoefFormBiKZGLocalSRS +where + E::G1Affine: CurveAffine, +{ + assert!(local_length.is_power_of_two()); + assert!(distributed_parties.is_power_of_two()); + assert!(party_rank < distributed_parties); + + let tau_x = E::Fr::random(&mut rng); + let tau_y = E::Fr::random(&mut rng); + + let g1 = E::G1Affine::generator(); + + let tau_x_geometric_progression = powers_series(&tau_x, local_length); + let tau_y_geometric_progression = powers_series(&tau_y, distributed_parties); + + let g1_prog = g1.to_curve(); + let x_coeff_bases = { + let mut proj_bases = vec![E::G1::identity(); local_length]; + proj_bases.iter_mut().enumerate().for_each(|(i, base)| { + *base = + g1_prog * tau_y_geometric_progression[party_rank] * tau_x_geometric_progression[i] + }); + + let mut g_bases = vec![E::G1Affine::default(); local_length]; + E::G1::batch_normalize(&proj_bases, &mut g_bases); + + drop(proj_bases); + g_bases + }; + + let tau_x_srs = CoefFormUniKZGSRS:: { + powers_of_tau: x_coeff_bases, + tau_g2: (E::G2Affine::generator() * tau_x).into(), + }; + + let y_coeff_bases = { + let mut proj_bases = vec![E::G1::identity(); distributed_parties]; + proj_bases + .iter_mut() + .enumerate() + .for_each(|(i, base)| *base = g1_prog * tau_y_geometric_progression[i]); + + let mut g_bases = vec![E::G1Affine::default(); distributed_parties]; + E::G1::batch_normalize(&proj_bases, &mut g_bases); + + drop(proj_bases); + g_bases + }; + + let tau_y_srs = CoefFormUniKZGSRS:: { + powers_of_tau: y_coeff_bases, + tau_g2: (E::G2Affine::generator() * tau_y).into(), + }; + + CoefFormBiKZGLocalSRS { + tau_x_srs, + tau_y_srs, + } +} + +#[inline(always)] +pub fn coeff_form_bi_kzg_open_leader( + srs: &CoefFormBiKZGLocalSRS, + evals_and_opens: &[(E::Fr, E::G1Affine)], + beta: E::Fr, +) -> (E::Fr, BiKZGProof) +where + E::G1Affine: CurveAffine, +{ + assert_eq!(srs.tau_y_srs.powers_of_tau.len(), evals_and_opens.len()); + + let x_open: E::G1 = evals_and_opens.iter().map(|(_, o)| o.to_curve()).sum(); + let gammas: Vec = evals_and_opens.iter().map(|(e, _)| *e).collect(); + + let (div, eval) = univariate_degree_one_quotient(&gammas, beta); + + let y_open = best_multiexp(&div, &srs.tau_y_srs.powers_of_tau[..div.len()]); + + ( + eval, + BiKZGProof { + quotient_x: x_open.into(), + quotient_y: y_open.into(), + }, + ) +} + +#[inline(always)] +pub fn coeff_form_bi_kzg_verify( + vk: BiKZGVerifierParam, + comm: E::G1Affine, + alpha: E::Fr, + beta: E::Fr, + eval: E::Fr, + opening: BiKZGProof, +) -> bool +where + E::G1Affine: CurveAffine, +{ + let g1_eval: E::G1Affine = (E::G1Affine::generator() * eval).into(); + let g2_alpha: E::G2 = E::G2Affine::generator() * alpha; + let g2_beta: E::G2 = E::G2Affine::generator() * beta; + + let gt_result = E::multi_miller_loop(&[ + ( + &opening.quotient_x, + &(vk.tau_x_g2.to_curve() - g2_alpha).to_affine().into(), + ), + ( + &opening.quotient_y, + &(vk.tau_y_g2.to_curve() - g2_beta).to_affine().into(), + ), + (&(g1_eval - comm).into(), &E::G2Affine::generator().into()), + ]); + + gt_result.final_exponentiation().is_identity().into() +} diff --git a/poly_commit/src/kzg/hyper_kzg.rs b/poly_commit/src/kzg/hyper_kzg.rs new file mode 100644 index 00000000..6d542006 --- /dev/null +++ b/poly_commit/src/kzg/hyper_kzg.rs @@ -0,0 +1,226 @@ +use std::iter; + +use arith::ExtensionField; +use halo2curves::{ + ff::Field, + group::{prime::PrimeCurveAffine, GroupEncoding}, + pairing::MultiMillerLoop, + CurveAffine, +}; +use itertools::izip; +use transcript::Transcript; + +use crate::*; + +#[inline(always)] +fn coeff_form_degree2_lagrange(roots: [F; 3], evals: [F; 3]) -> [F; 3] { + let [r0, r1, r2] = roots; + let [e0, e1, e2] = evals; + + let r0_nom = [r1 * r2, -r1 - r2, F::ONE]; + let r0_denom_inv = ((r0 - r1) * (r0 - r2)).invert().unwrap(); + let r0_weight = r0_denom_inv * e0; + + let r1_nom = [r0 * r2, -r0 - r2, F::ONE]; + let r1_denom_inv = ((r1 - r0) * (r1 - r2)).invert().unwrap(); + let r1_weight = r1_denom_inv * e1; + + let r2_nom = [r0 * r1, -r0 - r1, F::ONE]; + let r2_denom_inv = ((r2 - r0) * (r2 - r1)).invert().unwrap(); + let r2_weight = r2_denom_inv * e2; + + let combine = |a, b, c| a * r0_weight + b * r1_weight + c * r2_weight; + + [ + combine(r0_nom[0], r1_nom[0], r2_nom[0]), + combine(r0_nom[1], r1_nom[1], r2_nom[1]), + combine(r0_nom[2], r1_nom[2], r2_nom[2]), + ] +} + +pub fn coeff_form_uni_hyperkzg_open>( + srs: &CoefFormUniKZGSRS, + coeffs: &Vec, + alphas: &[E::Fr], + fs_transcript: &mut T, +) -> (E::Fr, HyperKZGOpening) +where + E::G1Affine: CurveAffine, + E::Fr: ExtensionField, +{ + let mut local_coeffs = coeffs.to_vec(); + + let (folded_oracle_commits, folded_oracle_coeffs): (Vec, Vec>) = alphas + [..alphas.len() - 1] + .iter() + .map(|alpha| { + let (evens, odds) = even_odd_coeffs_separate(&local_coeffs); + + local_coeffs = izip!(&evens, &odds) + .map(|(e, o)| (E::Fr::ONE - alpha) * e + *alpha * *o) + .collect(); + + let folded_oracle_commit = coeff_form_uni_kzg_commit(srs, &local_coeffs); + + fs_transcript.append_u8_slice(folded_oracle_commit.to_bytes().as_ref()); + + (folded_oracle_commit, local_coeffs.clone()) + }) + .unzip(); + + let beta = fs_transcript.generate_challenge_field_element(); + let beta2 = beta * beta; + let beta_inv = beta.invert().unwrap(); + let two_inv = E::Fr::ONE.double().invert().unwrap(); + let beta_pow_series = powers_series(&beta, coeffs.len()); + let neg_beta_pow_series = powers_series(&(-beta), coeffs.len()); + + let beta2_eval = { + let beta2_pow_series = powers_series(&beta2, coeffs.len()); + univariate_evaluate(coeffs, &beta2_pow_series) + }; + + fs_transcript.append_field_element(&beta2_eval); + let mut beta2_evals = vec![beta2_eval]; + + let (beta_evals, neg_beta_evals): (Vec, Vec) = izip!( + iter::once(coeffs).chain(folded_oracle_coeffs.iter()), + alphas + ) + .map(|(cs, alpha)| { + let beta_eval = univariate_evaluate(cs, &beta_pow_series); + let neg_beta_eval = univariate_evaluate(cs, &neg_beta_pow_series); + + let beta2_eval = two_inv + * ((beta_eval + neg_beta_eval) * (E::Fr::ONE - alpha) + + (beta_eval - neg_beta_eval) * beta_inv * alpha); + + beta2_evals.push(beta2_eval); + + fs_transcript.append_field_element(&beta_eval); + fs_transcript.append_field_element(&neg_beta_eval); + + (beta_eval, neg_beta_eval) + }) + .unzip(); + + let gamma = fs_transcript.generate_challenge_field_element(); + let gamma_pow_series = powers_series(&gamma, alphas.len()); + let v_beta = univariate_evaluate(&beta_evals, &gamma_pow_series); + let v_neg_beta = univariate_evaluate(&neg_beta_evals, &gamma_pow_series); + let v_beta2 = univariate_evaluate(&beta2_evals, &gamma_pow_series); + let f_gamma = { + let mut f = coeffs.clone(); + izip!(&gamma_pow_series[1..], &folded_oracle_coeffs) + .for_each(|(gamma_i, folded_f)| polynomial_add(&mut f, *gamma_i, folded_f)); + f + }; + + let lagrange_degree2 = + coeff_form_degree2_lagrange([beta, -beta, beta2], [v_beta, v_neg_beta, v_beta2]); + let f_gamma_quotient = { + let mut nom = f_gamma.clone(); + polynomial_add(&mut nom, -E::Fr::ONE, &lagrange_degree2); + univariate_roots_quotient(nom, &[beta, beta2, -beta]) + }; + let f_gamma_quotient_com = coeff_form_uni_kzg_commit(srs, &f_gamma_quotient); + fs_transcript.append_u8_slice(f_gamma_quotient_com.to_bytes().as_ref()); + + let tau = fs_transcript.generate_challenge_field_element(); + let vanishing_at_tau = { + let f_gamma_denom = (tau - beta) * (tau + beta) * (tau - beta2); + let lagrange_degree2_at_tau = + lagrange_degree2[0] + lagrange_degree2[1] * tau + lagrange_degree2[2] * tau * tau; + + let mut poly = f_gamma.clone(); + poly[0] -= lagrange_degree2_at_tau; + polynomial_add(&mut poly, -f_gamma_denom, &f_gamma_quotient); + univariate_roots_quotient(poly, &[tau]) + }; + let vanishing_at_tau_commitment = coeff_form_uni_kzg_commit(srs, &vanishing_at_tau); + + ( + beta2_evals[beta2_evals.len() - 1], + HyperKZGOpening { + folded_oracle_commitments: folded_oracle_commits, + f_beta2: beta2_eval, + evals_at_beta: beta_evals, + evals_at_neg_beta: neg_beta_evals, + beta_commitment: f_gamma_quotient_com, + tau_vanishing_commitment: vanishing_at_tau_commitment, + }, + ) +} + +pub fn coeff_form_uni_hyperkzg_verify>( + vk: UniKZGVerifierParams, + comm: E::G1Affine, + alphas: &[E::Fr], + eval: E::Fr, + opening: &HyperKZGOpening, + fs_transcript: &mut T, +) -> bool +where + E::G1Affine: CurveAffine, + E::Fr: ExtensionField, +{ + opening + .folded_oracle_commitments + .iter() + .for_each(|f| fs_transcript.append_u8_slice(f.to_bytes().as_ref())); + + let beta = fs_transcript.generate_challenge_field_element(); + let beta2 = beta * beta; + let beta_inv = beta.invert().unwrap(); + let two_inv = E::Fr::ONE.double().invert().unwrap(); + + fs_transcript.append_field_element(&opening.f_beta2); + let mut beta2_evals = vec![opening.f_beta2]; + izip!(&opening.evals_at_beta, &opening.evals_at_neg_beta, alphas).for_each( + |(beta_eval, neg_beta_eval, alpha)| { + let beta2_eval = two_inv + * ((*beta_eval + *neg_beta_eval) * (E::Fr::ONE - alpha) + + (*beta_eval - *neg_beta_eval) * beta_inv * alpha); + + beta2_evals.push(beta2_eval); + + fs_transcript.append_field_element(beta_eval); + fs_transcript.append_field_element(neg_beta_eval); + }, + ); + + if beta2_evals[beta2_evals.len() - 1] != eval { + return false; + } + + let gamma = fs_transcript.generate_challenge_field_element(); + let gamma_pow_series = powers_series(&gamma, alphas.len()); + let v_beta = univariate_evaluate(&opening.evals_at_beta, &gamma_pow_series); + let v_neg_beta = univariate_evaluate(&opening.evals_at_neg_beta, &gamma_pow_series); + let v_beta2 = univariate_evaluate(&beta2_evals, &gamma_pow_series); + let lagrange_degree2 = + coeff_form_degree2_lagrange([beta, -beta, beta2], [v_beta, v_neg_beta, v_beta2]); + + let folded_g1_oracle_comms: Vec = opening + .folded_oracle_commitments + .iter() + .map(|c| c.to_curve()) + .collect(); + let commitment_agg_g1: E::G1 = + comm.to_curve() + univariate_evaluate(&folded_g1_oracle_comms, &gamma_pow_series[1..]); + + fs_transcript.append_u8_slice(opening.beta_commitment.to_bytes().as_ref()); + let tau = fs_transcript.generate_challenge_field_element(); + + let q_weight = (tau - beta) * (tau - beta2) * (tau + beta); + let lagrange_eval = + lagrange_degree2[0] + lagrange_degree2[1] * tau + lagrange_degree2[2] * tau * tau; + + coeff_form_uni_kzg_verify( + vk, + (commitment_agg_g1 - opening.beta_commitment.to_curve() * q_weight).into(), + tau, + lagrange_eval, + opening.tau_vanishing_commitment, + ) +} diff --git a/poly_commit/src/kzg/pcs_trait_impl.rs b/poly_commit/src/kzg/pcs_trait_impl.rs new file mode 100644 index 00000000..70c6a90e --- /dev/null +++ b/poly_commit/src/kzg/pcs_trait_impl.rs @@ -0,0 +1,77 @@ +use std::marker::PhantomData; + +use arith::{BN254Fr, ExtensionField}; +use halo2curves::{bn256::Bn256, pairing::Engine}; +use polynomials::MultiLinearPoly; +use transcript::Transcript; + +use crate::*; + +pub struct HyperKZGPCS +where + E: Engine, + E::Fr: ExtensionField, + T: Transcript, +{ + _marker_e: PhantomData, + _marker_t: PhantomData, +} + +impl> PolynomialCommitmentScheme for HyperKZGPCS { + const NAME: &'static str = "HyperKZGPCS"; + + type Params = usize; + type Poly = MultiLinearPoly; + type EvalPoint = Vec; + type ScratchPad = (); + + type SRS = CoefFormUniKZGSRS; + type Commitment = KZGCommitment; + type Opening = HyperKZGOpening; + + fn init_scratch_pad(#[allow(unused)] params: &Self::Params) -> Self::ScratchPad {} + + fn gen_srs_for_testing(params: &Self::Params, rng: impl rand::RngCore) -> Self::SRS { + let length = 1 << params; + generate_coef_form_uni_kzg_srs_for_testing(length, rng) + } + + fn commit( + #[allow(unused)] params: &Self::Params, + proving_key: &::PKey, + poly: &Self::Poly, + #[allow(unused)] scratch_pad: &mut Self::ScratchPad, + ) -> Self::Commitment { + KZGCommitment(coeff_form_uni_kzg_commit(proving_key, &poly.coeffs)) + } + + fn open( + #[allow(unused)] params: &Self::Params, + proving_key: &::PKey, + poly: &Self::Poly, + x: &Self::EvalPoint, + #[allow(unused)] scratch_pad: &Self::ScratchPad, + transcript: &mut T, + ) -> (BN254Fr, Self::Opening) { + coeff_form_uni_hyperkzg_open(proving_key, &poly.coeffs, x, transcript) + } + + fn verify( + #[allow(unused)] params: &Self::Params, + verifying_key: &::VKey, + commitment: &Self::Commitment, + x: &Self::EvalPoint, + v: BN254Fr, + opening: &Self::Opening, + transcript: &mut T, + ) -> bool { + coeff_form_uni_hyperkzg_verify( + verifying_key.clone(), + commitment.0, + x, + v, + opening, + transcript, + ) + } +} diff --git a/poly_commit/src/kzg/structs.rs b/poly_commit/src/kzg/structs.rs new file mode 100644 index 00000000..fec7f0a5 --- /dev/null +++ b/poly_commit/src/kzg/structs.rs @@ -0,0 +1,214 @@ +use arith::FieldSerde; +use halo2curves::{pairing::Engine, CurveAffine}; + +use crate::StructuredReferenceString; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct KZGCommitment(pub E::G1Affine); + +impl Default for KZGCommitment +where + E::G1Affine: CurveAffine, +{ + fn default() -> Self { + Self(E::G1Affine::default()) + } +} + +impl FieldSerde for KZGCommitment +where + E::G1Affine: FieldSerde, +{ + const SERIALIZED_SIZE: usize = ::SERIALIZED_SIZE; + + fn serialize_into(&self, writer: W) -> arith::FieldSerdeResult<()> { + self.0.serialize_into(writer) + } + + fn deserialize_from(reader: R) -> arith::FieldSerdeResult { + Ok(Self(::deserialize_from(reader)?)) + } +} + +/// Structured reference string for univariate KZG polynomial commitment scheme. +/// The univariate polynomial here is of coefficient form. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CoefFormUniKZGSRS { + /// power of \tau times the generators of G1, yielding + /// \tau^i over G1 with i ranging in \[ 0, 2^n - 1 \] + pub powers_of_tau: Vec, + /// \tau over G2 + pub tau_g2: E::G2Affine, +} + +impl Default for CoefFormUniKZGSRS +where + E::G2Affine: CurveAffine, +{ + fn default() -> Self { + Self { + powers_of_tau: Vec::default(), + tau_g2: E::G2Affine::default(), + } + } +} + +impl FieldSerde for CoefFormUniKZGSRS +where + E::G1Affine: FieldSerde, + E::G2Affine: FieldSerde, +{ + const SERIALIZED_SIZE: usize = unimplemented!(); + + fn serialize_into(&self, mut writer: W) -> arith::FieldSerdeResult<()> { + self.powers_of_tau.serialize_into(&mut writer)?; + self.tau_g2.serialize_into(&mut writer) + } + + fn deserialize_from(mut reader: R) -> arith::FieldSerdeResult { + let powers_of_tau: Vec = Vec::deserialize_from(&mut reader)?; + let tau_g2: E::G2Affine = E::G2Affine::deserialize_from(&mut reader)?; + Ok(Self { + powers_of_tau, + tau_g2, + }) + } +} + +impl StructuredReferenceString for CoefFormUniKZGSRS +where + ::G1Affine: FieldSerde, + ::G2Affine: FieldSerde, +{ + type PKey = CoefFormUniKZGSRS; + type VKey = UniKZGVerifierParams; + + fn into_keys(self) -> (Self::PKey, Self::VKey) { + (self.clone(), From::from(&self)) + } +} + +/// Univariate KZG PCS verifier's params. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct UniKZGVerifierParams { + /// \tau over G2 + pub tau_g2: E::G2Affine, +} + +impl FieldSerde for UniKZGVerifierParams +where + E::G2Affine: FieldSerde, +{ + const SERIALIZED_SIZE: usize = ::SERIALIZED_SIZE; + + fn serialize_into(&self, writer: W) -> arith::FieldSerdeResult<()> { + self.tau_g2.serialize_into(writer) + } + + fn deserialize_from(reader: R) -> arith::FieldSerdeResult { + Ok(Self { + tau_g2: ::deserialize_from(reader)?, + }) + } +} + +impl From<&CoefFormUniKZGSRS> for UniKZGVerifierParams { + fn from(value: &CoefFormUniKZGSRS) -> Self { + Self { + tau_g2: value.tau_g2, + } + } +} + +#[derive(Clone, Debug)] +pub struct HyperKZGOpening { + pub folded_oracle_commitments: Vec, + pub f_beta2: E::Fr, + pub evals_at_beta: Vec, + pub evals_at_neg_beta: Vec, + pub beta_commitment: E::G1Affine, + pub tau_vanishing_commitment: E::G1Affine, +} + +impl Default for HyperKZGOpening +where + E::G1Affine: CurveAffine, +{ + fn default() -> Self { + Self { + folded_oracle_commitments: Vec::default(), + f_beta2: E::Fr::default(), + evals_at_beta: Vec::default(), + evals_at_neg_beta: Vec::default(), + beta_commitment: E::G1Affine::default(), + tau_vanishing_commitment: E::G1Affine::default(), + } + } +} + +impl FieldSerde for HyperKZGOpening +where + E::Fr: FieldSerde, + E::G1Affine: FieldSerde, + E::G2Affine: FieldSerde, +{ + const SERIALIZED_SIZE: usize = unimplemented!(); + + fn serialize_into(&self, mut writer: W) -> arith::FieldSerdeResult<()> { + self.folded_oracle_commitments.serialize_into(&mut writer)?; + self.f_beta2.serialize_into(&mut writer)?; + self.evals_at_beta.serialize_into(&mut writer)?; + self.evals_at_neg_beta.serialize_into(&mut writer)?; + self.beta_commitment.serialize_into(&mut writer)?; + self.tau_vanishing_commitment.serialize_into(&mut writer) + } + + fn deserialize_from(mut reader: R) -> arith::FieldSerdeResult { + let folded_oracle_commitments: Vec = Vec::deserialize_from(&mut reader)?; + let f_beta2: E::Fr = E::Fr::deserialize_from(&mut reader)?; + let evals_at_beta: Vec = Vec::deserialize_from(&mut reader)?; + let evals_at_neg_beta: Vec = Vec::deserialize_from(&mut reader)?; + let beta_commitment: E::G1Affine = E::G1Affine::deserialize_from(&mut reader)?; + let tau_vanishing_commitment: E::G1Affine = E::G1Affine::deserialize_from(&mut reader)?; + + Ok(Self { + folded_oracle_commitments, + f_beta2, + evals_at_beta, + evals_at_neg_beta, + beta_commitment, + tau_vanishing_commitment, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CoefFormBiKZGLocalSRS { + pub tau_x_srs: CoefFormUniKZGSRS, + pub tau_y_srs: CoefFormUniKZGSRS, +} + +/// Bivariate KZG PCS verifier's params. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub struct BiKZGVerifierParam { + /// tau_0 over G2. + pub tau_x_g2: E::G2Affine, + /// tau_y over G2. + pub tau_y_g2: E::G2Affine, +} + +impl From<&CoefFormBiKZGLocalSRS> for BiKZGVerifierParam { + fn from(srs: &CoefFormBiKZGLocalSRS) -> Self { + Self { + tau_x_g2: srs.tau_x_srs.tau_g2, + tau_y_g2: srs.tau_y_srs.tau_g2, + } + } +} + +/// Proof for Bi-KZG polynomial commitment scheme. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct BiKZGProof { + pub quotient_x: E::G1Affine, + pub quotient_y: E::G1Affine, +} diff --git a/poly_commit/src/kzg/tests.rs b/poly_commit/src/kzg/tests.rs new file mode 100644 index 00000000..2d5dc306 --- /dev/null +++ b/poly_commit/src/kzg/tests.rs @@ -0,0 +1,201 @@ +use arith::BN254Fr; +use ark_std::test_rng; +use field_hashers::MiMC5FiatShamirHasher; +use halo2curves::{ + bn256::{Bn256, Fr, G1Affine, G1}, + ff::Field, + group::{prime::PrimeCurveAffine, Curve}, +}; +use itertools::izip; +use polynomials::MultiLinearPoly; +use transcript::{FieldHashTranscript, Transcript}; + +use crate::*; + +#[test] +fn test_univariate_degree_one_quotient() { + { + // x^3 + 1 = (x + 1)(x^2 - x + 1) + let poly = vec![ + Fr::from(1u64), + Fr::from(0u64), + Fr::from(0u64), + Fr::from(1u64), + ]; + let point = -Fr::from(1u64); + let (div, remainder) = univariate_degree_one_quotient(&poly, point); + assert_eq!( + div, + vec![ + Fr::from(1u64), + -Fr::from(1u64), + Fr::from(1u64), + Fr::from(0u64) + ] + ); + assert_eq!(remainder, Fr::ZERO) + } + { + // x^3 - 1 = (x-1)(x^2 + x + 1) + let poly = vec![ + -Fr::from(1u64), + Fr::from(0u64), + Fr::from(0u64), + Fr::from(1u64), + ]; + let point = Fr::from(1u64); + let (div, remainder) = univariate_degree_one_quotient(&poly, point); + assert_eq!( + div, + vec![ + Fr::from(1u64), + Fr::from(1u64), + Fr::from(1u64), + Fr::from(0u64) + ] + ); + assert_eq!(remainder, Fr::ZERO) + } + { + // x^3 + 6x^2 + 11x + 6 = (x + 1)(x + 2)(x + 3) + let poly = vec![ + Fr::from(6u64), + Fr::from(11u64), + Fr::from(6u64), + Fr::from(1u64), + ]; + let point = Fr::from(1u64); + let (div, remainder) = univariate_degree_one_quotient(&poly, point); + assert_eq!( + div, + vec![ + Fr::from(18u64), + Fr::from(7u64), + Fr::from(1u64), + Fr::from(0u64), + ] + ); + assert_eq!(remainder, Fr::from(24u64)) + } +} + +#[test] +fn test_coefficient_form_univariate_kzg_e2e() { + // \prod_{i \in [1, 7]} (x + i) + let poly = vec![ + Fr::from(5040u32), + Fr::from(13068u64), + Fr::from(13132u64), + Fr::from(6769u64), + Fr::from(1960u64), + Fr::from(322u64), + Fr::from(28u64), + Fr::from(1u64), + ]; + let alpha = Fr::from(3u64); + let eval = Fr::from(604800u64); + + let mut rng = test_rng(); + let srs = generate_coef_form_uni_kzg_srs_for_testing::(8, &mut rng); + let vk: UniKZGVerifierParams = From::from(&srs); + let com = coeff_form_uni_kzg_commit(&srs, &poly); + + let (actual_eval, opening) = coeff_form_uni_kzg_open_eval(&srs, &poly, alpha); + assert_eq!(actual_eval, eval); + assert!(coeff_form_uni_kzg_verify(vk, com, alpha, eval, opening)) +} + +#[test] +fn test_coefficient_form_univariate_kzg_constant_e2e() { + let poly = vec![Fr::from(100u64)]; + let alpha = Fr::from(3u64); + let eval = Fr::from(100u64); + + let mut rng = test_rng(); + let srs = generate_coef_form_uni_kzg_srs_for_testing::(8, &mut rng); + let vk: UniKZGVerifierParams = From::from(&srs); + let com = coeff_form_uni_kzg_commit(&srs, &poly); + + let (actual_eval, opening) = coeff_form_uni_kzg_open_eval(&srs, &poly, alpha); + assert_eq!(actual_eval, eval); + assert!(coeff_form_uni_kzg_verify(vk, com, alpha, eval, opening)) +} + +#[test] +fn test_coefficient_form_bivariate_kzg_e2e() { + let x_degree = 15; + let y_degree = 7; + + let party_srs: Vec> = (0..=y_degree) + .map(|rank| { + let mut rng = test_rng(); + generate_coef_form_bi_kzg_local_srs_for_testing( + x_degree + 1, + y_degree + 1, + rank, + &mut rng, + ) + }) + .collect(); + + let mut rng = test_rng(); + let xy_coeffs: Vec> = (0..=y_degree) + .map(|_| (0..=x_degree).map(|_| Fr::random(&mut rng)).collect()) + .collect(); + + let commitments: Vec<_> = izip!(&party_srs, &xy_coeffs) + .map(|(srs, x_coeffs)| coeff_form_uni_kzg_commit(&srs.tau_x_srs, x_coeffs)) + .collect(); + + let global_commitment_g1: G1 = commitments.iter().map(|c| c.to_curve()).sum::(); + let global_commitment: G1Affine = global_commitment_g1.to_affine(); + + let alpha = Fr::random(&mut rng); + let evals_and_opens: Vec<(Fr, G1Affine)> = izip!(&party_srs, &xy_coeffs) + .map(|(srs, x_coeffs)| coeff_form_uni_kzg_open_eval(&srs.tau_x_srs, x_coeffs, alpha)) + .collect(); + + let beta = Fr::random(&mut rng); + let (final_eval, final_opening) = + coeff_form_bi_kzg_open_leader(&party_srs[0], &evals_and_opens, beta); + + let vk: BiKZGVerifierParam = From::from(&party_srs[0]); + assert!(coeff_form_bi_kzg_verify( + vk, + global_commitment, + alpha, + beta, + final_eval, + final_opening, + )); +} + +#[test] +fn test_hyperkzg_functionality_e2e() { + let mut rng = test_rng(); + let max_vars = 15; + let max_length = 1 << max_vars; + + let srs = generate_coef_form_uni_kzg_srs_for_testing::(max_length, &mut rng); + (2..max_vars).for_each(|vars| { + let multilinear = MultiLinearPoly::random(vars, &mut rng); + let alphas: Vec = (0..vars).map(|_| Fr::random(&mut rng)).collect(); + + let vk: UniKZGVerifierParams = From::from(&srs); + let com = coeff_form_uni_kzg_commit(&srs, &multilinear.coeffs); + let mut fs_transcript = + FieldHashTranscript::>::new(); + + let (eval, opening) = + coeff_form_uni_hyperkzg_open(&srs, &multilinear.coeffs, &alphas, &mut fs_transcript); + + assert!(coeff_form_uni_hyperkzg_verify( + vk, + com, + &alphas, + eval, + &opening, + &mut fs_transcript + )) + }); +} diff --git a/poly_commit/src/kzg/univariate.rs b/poly_commit/src/kzg/univariate.rs new file mode 100644 index 00000000..41c26663 --- /dev/null +++ b/poly_commit/src/kzg/univariate.rs @@ -0,0 +1,100 @@ +use halo2curves::{ + ff::Field, + group::{prime::PrimeCurveAffine, Curve, Group}, + msm::best_multiexp, + pairing::{MillerLoopResult, MultiMillerLoop}, + CurveAffine, +}; + +use crate::*; + +#[inline(always)] +pub fn generate_coef_form_uni_kzg_srs_for_testing( + length: usize, + mut rng: impl rand::RngCore, +) -> CoefFormUniKZGSRS +where + E::G1Affine: CurveAffine, +{ + assert!(length.is_power_of_two()); + + let tau = E::Fr::random(&mut rng); + let g1 = E::G1Affine::generator(); + + let tau_geometric_progression = powers_series(&tau, length); + + let g1_prog = g1.to_curve(); + let coeff_bases = { + let mut proj_bases = vec![E::G1::identity(); length]; + proj_bases + .iter_mut() + .enumerate() + .for_each(|(i, base)| *base = g1_prog * tau_geometric_progression[i]); + + let mut g_bases = vec![E::G1Affine::default(); length]; + E::G1::batch_normalize(&proj_bases, &mut g_bases); + + drop(proj_bases); + g_bases + }; + + CoefFormUniKZGSRS { + powers_of_tau: coeff_bases, + tau_g2: (E::G2Affine::generator() * tau).into(), + } +} + +#[inline(always)] +pub fn coeff_form_uni_kzg_commit( + srs: &CoefFormUniKZGSRS, + coeffs: &[E::Fr], +) -> E::G1Affine +where + E::G1Affine: CurveAffine, +{ + assert!(srs.powers_of_tau.len() >= coeffs.len()); + + best_multiexp(coeffs, &srs.powers_of_tau[..coeffs.len()]).into() +} + +#[inline(always)] +pub fn coeff_form_uni_kzg_open_eval( + srs: &CoefFormUniKZGSRS, + coeffs: &[E::Fr], + alpha: E::Fr, +) -> (E::Fr, E::G1Affine) +where + E::G1Affine: CurveAffine, +{ + assert!(srs.powers_of_tau.len() >= coeffs.len()); + + let (div, eval) = univariate_degree_one_quotient(coeffs, alpha); + let opening = best_multiexp(&div, &srs.powers_of_tau[..div.len()]).into(); + + (eval, opening) +} + +#[inline(always)] +pub fn coeff_form_uni_kzg_verify( + vk: UniKZGVerifierParams, + comm: E::G1Affine, + alpha: E::Fr, + eval: E::Fr, + opening: E::G1Affine, +) -> bool +where + E::G1Affine: CurveAffine, +{ + let g1_eval: E::G1Affine = (E::G1Affine::generator() * eval).into(); + let g2_alpha: E::G2 = E::G2Affine::generator() * alpha; + + let gt_result = E::multi_miller_loop(&[ + ( + &opening, + &(vk.tau_g2.to_curve() - g2_alpha).to_affine().into(), + ), + (&(g1_eval - comm).into(), &E::G2Affine::generator().into()), + ]); + + gt_result.final_exponentiation().is_identity().into() +} diff --git a/poly_commit/src/kzg/utils.rs b/poly_commit/src/kzg/utils.rs new file mode 100644 index 00000000..71c582e1 --- /dev/null +++ b/poly_commit/src/kzg/utils.rs @@ -0,0 +1,92 @@ +use std::{iter::Sum, ops::Mul}; + +use halo2curves::ff::{Field, PrimeField}; +use itertools::izip; + +pub fn primitive_root_of_unity(group_size: usize) -> F { + let omega = F::ROOT_OF_UNITY; + let omega = omega.pow_vartime([(1 << F::S) / group_size as u64]); + assert!( + omega.pow_vartime([group_size as u64]) == F::ONE, + "omega_0 is not root of unity for supported_n" + ); + omega +} + +#[inline(always)] +pub(crate) fn powers_series(x: &F, n: usize) -> Vec { + let mut powers = vec![F::ONE]; + let mut cur = *x; + for _ in 0..n - 1 { + powers.push(cur); + cur *= x; + } + powers +} + +/// Given a univariate polynomial of coefficient form f(X) = c0 + c1 X + ... + cn X^n +/// and perform the division f(X) / (X - \alpha). +#[inline(always)] +pub(crate) fn univariate_degree_one_quotient(coeffs: &[F], alpha: F) -> (Vec, F) { + let mut div_coeffs = coeffs.to_vec(); + + for i in (1..coeffs.len()).rev() { + // c X^n = c X^n - c \alpha X^(n - 1) + c \alpha X^(n - 1) + // = c (X - \alpha) X^(n - 1) + c \alpha X^(n - 1) + + let remainder = div_coeffs[i] * alpha; + div_coeffs[i - 1] += remainder; + } + + let final_remainder = div_coeffs[0]; + let mut final_div_coeffs = div_coeffs[1..].to_owned(); + final_div_coeffs.resize(coeffs.len(), F::ZERO); + + (final_div_coeffs, final_remainder) +} + +#[inline(always)] +pub(crate) fn univariate_roots_quotient(mut coeffs: Vec, roots: &[F]) -> Vec { + roots.iter().enumerate().for_each(|(ith_root, r)| { + for i in ((1 + ith_root)..coeffs.len()).rev() { + // c X^n = c X^n - c \alpha X^(n - 1) + c \alpha X^(n - 1) + // = c (X - \alpha) X^(n - 1) + c \alpha X^(n - 1) + + let remainder = coeffs[i] * r; + coeffs[i - 1] += remainder; + } + + assert_eq!(coeffs[ith_root], F::ZERO); + }); + + coeffs[roots.len()..].to_owned() +} + +#[inline(always)] +pub(crate) fn univariate_evaluate + Sum + Copy, F1: Field>( + coeffs: &[F], + power_series: &[F1], +) -> F { + izip!(coeffs, power_series).map(|(c, p)| *c * *p).sum() +} + +pub(crate) fn even_odd_coeffs_separate(coeffs: &[F]) -> (Vec, Vec) { + assert!(coeffs.len().is_power_of_two()); + + let mut even = Vec::with_capacity(coeffs.len() >> 1); + let mut odd = Vec::with_capacity(coeffs.len() >> 1); + + coeffs.chunks(2).for_each(|eo: &[F]| { + even.push(eo[0]); + odd.push(eo[1]); + }); + + (even, odd) +} + +#[inline(always)] +pub(crate) fn polynomial_add(coeffs: &mut [F], weight: F, another_coeffs: &[F]) { + assert!(coeffs.len() >= another_coeffs.len()); + + izip!(coeffs, another_coeffs).for_each(|(c, a)| *c += weight * *a); +} diff --git a/poly_commit/src/lib.rs b/poly_commit/src/lib.rs index 5184ea08..aecda260 100644 --- a/poly_commit/src/lib.rs +++ b/poly_commit/src/lib.rs @@ -13,3 +13,6 @@ pub use raw::RawExpanderGKR; pub mod orion; pub use orion::*; + +pub mod kzg; +pub use kzg::*; diff --git a/poly_commit/tests/common.rs b/poly_commit/tests/common.rs index 0145657b..f7635412 100644 --- a/poly_commit/tests/common.rs +++ b/poly_commit/tests/common.rs @@ -45,6 +45,7 @@ pub fn test_pcs, P: PolynomialCommitmentSche } } +#[allow(unused)] pub fn test_pcs_for_expander_gkr< C: GKRFieldConfig, T: Transcript, diff --git a/poly_commit/tests/test_kzg.rs b/poly_commit/tests/test_kzg.rs new file mode 100644 index 00000000..040e1ebe --- /dev/null +++ b/poly_commit/tests/test_kzg.rs @@ -0,0 +1,36 @@ +mod common; + +use arith::{BN254Fr, Field}; +use ark_std::test_rng; +use halo2curves::bn256::Bn256; +use poly_commit::HyperKZGPCS; +use polynomials::MultiLinearPoly; +use transcript::{BytesHashTranscript, Keccak256hasher}; + +const TEST_REPETITION: usize = 3; + +fn test_hyperkzg_pcs_generics(num_vars_start: usize, num_vars_end: usize) { + let mut rng = test_rng(); + + (num_vars_start..=num_vars_end).for_each(|num_vars| { + let xs: Vec<_> = (0..TEST_REPETITION) + .map(|_| -> Vec { + (0..num_vars) + .map(|_| BN254Fr::random_unsafe(&mut rng)) + .collect() + }) + .collect(); + let poly = MultiLinearPoly::::random(num_vars, &mut rng); + + common::test_pcs::< + BN254Fr, + BytesHashTranscript, + HyperKZGPCS>, + >(&num_vars, &poly, &xs); + }) +} + +#[test] +fn test_hyperkzg_pcs_full_e2e() { + test_hyperkzg_pcs_generics(2, 15) +}