diff --git a/Cargo.toml b/Cargo.toml index 925f990..9ef612f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["plonk", "kzg"] +members = ["plonk", "kzg", "lookup"] resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html \ No newline at end of file diff --git a/lookup/Cargo.toml b/lookup/Cargo.toml new file mode 100644 index 0000000..c885c1a --- /dev/null +++ b/lookup/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "lookup" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +merlin = "3.0.0" +ark-ff = "0.4.2" +ark-poly = "0.4.2" +ark-std = "0.4.0" +ark-bls12-381 = "0.4.0" +kzg = { path = "../kzg" } +ark-serialize = "0.4.2" +rand = "0.9.0-alpha.1" +ark-ec = "0.4.2" diff --git a/lookup/src/errors.rs b/lookup/src/errors.rs new file mode 100644 index 0000000..b5f94f8 --- /dev/null +++ b/lookup/src/errors.rs @@ -0,0 +1,16 @@ +/// Defines all possible errors that can be encountered in lookup schemes. +#[derive(Debug)] +pub enum Error { + /// Error indicating that elements cannot be aggregated. + NonAggregatable, + /// Error indicating that an incorrect lookup scheme was used. + WrongLookupScheme, + /// Error indicating that the length of a witness does not match the element length in the table + WitnessLengthNotMatch(String), + /// Error indicating that a witness is not found in the table. + WitnessNotInTable, + /// Error indicating that a vector is empty. + EmptyVec, + /// Error indicating that a polynomial does not fit in the specified domain. + PolyNotFitInDomain(String), +} diff --git a/lookup/src/lib.rs b/lookup/src/lib.rs new file mode 100644 index 0000000..d1a798a --- /dev/null +++ b/lookup/src/lib.rs @@ -0,0 +1,9 @@ +pub mod errors; +pub mod lookup; +pub mod pcs; +pub mod plookup; +pub mod poly; +pub mod row; +pub mod template_table; +pub mod transcript; +pub mod types; diff --git a/lookup/src/lookup.rs b/lookup/src/lookup.rs new file mode 100644 index 0000000..d36f909 --- /dev/null +++ b/lookup/src/lookup.rs @@ -0,0 +1,45 @@ +use ark_ff::PrimeField; + +use crate::errors::Error; +use crate::pcs::base_pcs::BasePCS; +use crate::transcript::TranscriptProtocol; + +/// This trait defines the basic methods of a lookup scheme. +pub trait Lookup> { + /// The type representing the proof of the lookup table. + type Proof; + /// The type representing the elements stored in the lookup table. + type Element; + + /// Generates a proof of the lookup table. + /// + /// # Arguments + /// + /// * `transcript`: The transcript generator. + /// + /// returns: The proof of the lookup table. + fn prove(&self, transcript: &mut TranscriptProtocol) -> Self::Proof; + + /// Verifies a proof of the lookup table. + /// + /// # Arguments + /// + /// * `transcript`: The transcript generator. + /// * `proof`: The proof of the lookup table. + /// + /// returns: Returns `true` if the proof is valid, otherwise `false`. + fn verify( + &self, + transcript: &mut TranscriptProtocol, + proof: &Self::Proof, + ) -> Result; + + /// Adds a witness that needs to be proven to exist in the table + /// + /// # Arguments + /// + /// * `witness`: The witness. + /// + /// returns: Returns `Ok(())` if the witness is successfully added, otherwise returns an `Error`. + fn add_witness(&mut self, witness: Self::Element) -> Result<(), Error>; +} diff --git a/lookup/src/pcs/additive_homomorphic.rs b/lookup/src/pcs/additive_homomorphic.rs new file mode 100644 index 0000000..8520ad6 --- /dev/null +++ b/lookup/src/pcs/additive_homomorphic.rs @@ -0,0 +1,70 @@ +use std::ops::{Add, Mul}; + +use ark_ff::PrimeField; + +use crate::errors::Error; +use crate::pcs::base_pcs::BasePCS; +use crate::types::Poly; + +/// This trait represents the additive homomorphic PCS, i.e. +/// - `com(a + b) = com(a) + com(b)` +/// - `z * com(a) = com(za)` +pub trait AdditiveHomomorphicPCS: BasePCS +where + Self::Commitment: Add + Mul, +{ + /// Aggregates a list of elements using a random challenge. + /// + /// Let `z` be the challenge, and the elements be `e0, e2, ..., en`. The aggregate result is + /// `e0 + e1*z + e2*z^2 + ... + en*z^n`. + /// + /// # Arguments + /// + /// * `elements`: The vector of the elements to be aggregated. + /// * `challenge`: The challenge used for aggregation. + /// + /// returns: `Ok(E)` if the aggregation is successful, otherwise returns an `Error`. + #[allow(clippy::ptr_arg)] + #[allow(clippy::needless_range_loop)] + fn aggregate(elements: &Vec<&E>, challenge: &F) -> Result + where + E: Add, + E: Mul, + E: Clone, + { + if elements.is_empty() { + return Err(Error::EmptyVec); + } + let mut res = elements[0].clone(); + let mut pow = *challenge; + for i in 1..elements.len() { + res = res + (elements[i].clone() * pow); + pow *= challenge; + } + Ok(res) + } + /// This function treats like `aggregate` function, but separated because + /// `DensePolynomial + DensePolynomial` does not work (which also means it does not + /// satisfy `E: Add`). + /// + /// # Arguments + /// + /// * `elements`:The vector of the polynomials to be aggregated. + /// * `challenge`: The challenge used for aggregation. + /// + /// returns: `Ok(DensePolynomial)` if the aggregation is successful, otherwise returns an `Error`. + #[allow(clippy::ptr_arg)] + #[allow(clippy::needless_range_loop)] + fn aggregate_polys(elements: &Vec<&Poly>, challenge: &F) -> Result, Error> { + if elements.is_empty() { + return Err(Error::EmptyVec); + } + let mut res = elements[0].clone(); + let mut pow = *challenge; + for i in 1..elements.len() { + res = res + (elements[i] * pow); + pow *= challenge; + } + Ok(res) + } +} diff --git a/lookup/src/pcs/base_pcs.rs b/lookup/src/pcs/base_pcs.rs new file mode 100644 index 0000000..e1a4430 --- /dev/null +++ b/lookup/src/pcs/base_pcs.rs @@ -0,0 +1,78 @@ +use ark_ff::PrimeField; + +use crate::errors::Error; +use crate::transcript::{ToBytes, TranscriptProtocol}; +use crate::types::{LookupProof, LookupProofTransferData, LookupVerifyTransferData, Poly}; + +/// This trait represents the base polynomial commitment scheme (PCS). +/// Other PCS traits should inherit this trait. +/// +/// > [!NOTE] +/// > +/// > Functions `lookup_prove` and `lookup_verify` are implemented based on the idea that the lookup +/// > scheme will generate common data, then the rest of the `prove` and `verify` functions in lookup scheme +/// > will be completed depending on the PCS. +pub trait BasePCS: Sized { + /// The type of commitment. + type Commitment: ToBytes + Clone; + /// The type of opening data. + type Opening; + /// The type of proof for verifying the correctness of an opening. + type Proof; + /// The type of extra data that PCS need to verify a lookup proof. + type LookupProof; + + /// Commits to a polynomial. + /// + /// # Arguments + /// + /// * `poly`: The polynomial to be committed to. + /// + /// returns: `Self::Commitment` as the commitment + fn commit(&self, poly: &Poly) -> Self::Commitment; + /// Opens a commitment at a specified point. + /// + /// # Arguments + /// + /// * `poly`: The polynomial to be opened. + /// * `z`: The point at which the polynomial is opened. + /// + /// returns: `Self::Opening` as the opening at the specified point. + fn open(&self, poly: &Poly, z: F) -> Self::Opening; + /// Verifies a proof of PCS. + /// + /// # Arguments + /// + /// * `proof`: The proof to be verified. + /// + /// returns: `true` if the proof is valid, otherwise `false`. + fn verify(&self, proof: &Self::Proof) -> bool; + /// Generates a PCS proof for lookup scheme. + /// + /// # Arguments + /// + /// * `transcript`: The transcript generator. + /// * `data`: The forwarded data from function `prove` of lookup scheme. + /// + /// returns: `Ok(Self::LookupProof)` if the proof is successfully generated, otherwise returns an `Error`. + fn lookup_prove( + &self, + transcript: &mut TranscriptProtocol, + data: &LookupProofTransferData, + ) -> Result; + /// Verifies a lookup PCS proof. + /// + /// # Arguments + /// + /// * `transcript`: The transcript generator. + /// * `proof`: The proof needs to be verified. + /// * `data`: The forwarded data from function `verify` of lookup scheme. + /// + /// returns: `Ok(true)` if the proof is valid, `Ok(false)` if invalid, otherwise returns an `Error`. + fn lookup_verify( + &self, + transcript: &mut TranscriptProtocol, + proof: &LookupProof, + data: &LookupVerifyTransferData, + ) -> Result; +} diff --git a/lookup/src/pcs/kzg10.rs b/lookup/src/pcs/kzg10.rs new file mode 100644 index 0000000..d04de0b --- /dev/null +++ b/lookup/src/pcs/kzg10.rs @@ -0,0 +1,197 @@ +use ark_bls12_381::Fr; +use ark_ff::PrimeField; +use ark_serialize::CanonicalSerialize; +use ark_std::test_rng; + +use crate::errors::Error; +use crate::pcs::additive_homomorphic::AdditiveHomomorphicPCS; +use crate::pcs::base_pcs::BasePCS; +use crate::plookup::transcript_label::TranscriptLabel; +use crate::poly::ExtraDensePoly; +use crate::transcript::{ToBytes, TranscriptProtocol}; +use crate::types::{LookupProof, LookupProofTransferData, LookupVerifyTransferData, Poly}; +use kzg::commitment::KzgCommitment; +use kzg::opening::KzgOpening as OtherKzgOpening; +use kzg::scheme::KzgScheme; + +impl ToBytes for KzgCommitment { + fn to_bytes(&self) -> Vec { + let mut bytes = vec![]; + self.inner().serialize_uncompressed(&mut bytes).expect(""); + bytes + } +} +pub type KzgField = Fr; + +/// This struct represents extra proof that used to verify in `Plookup` using KZG +pub struct PlookupKZGProof { + /// Commitment of aggregated quotient polynomial that evaluated at `evaluation_challenge` + pub agg_quotient_commitment: Commitment, + /// Commitment of aggregated quotient polynomial that evaluated at `evaluation_challenge * g` + pub shifted_agg_quotient_commitment: Commitment, +} + +/// The KZG proof +pub struct KzgProof { + f_com: Commitment, + opening: KzgOpening, + z: F, +} + +/// The KZG opening +pub struct KzgOpening { + q_com: Commitment, + fz: F, +} + +/// Implements KZG to deal with `Plookup` +impl BasePCS for KzgScheme { + type Commitment = KzgCommitment; + type Opening = KzgOpening; + type Proof = KzgProof; + type LookupProof = PlookupKZGProof; + + fn commit(&self, poly: &Poly) -> KzgCommitment { + self.commit(poly) + } + + fn open(&self, poly: &Poly, z: KzgField) -> Self::Opening { + let c = self.open(poly.clone(), z); + KzgOpening { + q_com: KzgCommitment(c.0), + fz: c.1, + } + } + + fn verify(&self, proof: &Self::Proof) -> bool { + self.verify( + &proof.f_com, + &OtherKzgOpening(proof.opening.q_com.0, proof.opening.fz), + proof.z, + ) + } + + #[allow(irrefutable_let_patterns)] + fn lookup_prove( + &self, + transcript: &mut TranscriptProtocol, + data: &LookupProofTransferData, + ) -> Result, Error> { + if let LookupProofTransferData::Plookup(data) = data { + let aggregation_challenge = + transcript.challenge_scalar(TranscriptLabel::WITNESS_AGGREGATION); + + // Calculates the aggregated quotient polynomials for all polynomials that evaluated at `evaluation_challenge` + let agg_quotient_witness_poly = Self::aggregate_polys( + &vec![ + &data.f_poly, + &data.t_poly, + &data.h1_poly, + &data.h2_poly, + &data.z_poly, + &data.quotient_poly, + ], + &aggregation_challenge, + ) + .unwrap() + .quotient_poly(&data.evaluation_challenge); + let agg_quotient_witness_commit = self.commit(&agg_quotient_witness_poly); + + // Calculates the aggregated quotient polynomials for all polynomials that evaluated at `evaluation_challenge * g` + let shifted_agg_quotient_witness_poly = Self::aggregate_polys( + &vec![&data.t_poly, &data.h1_poly, &data.h2_poly, &data.z_poly], + &aggregation_challenge, + ) + .unwrap() + .quotient_poly(&data.shifted_evaluation_challenge); + let shifted_agg_quotient_witness_commit = + self.commit(&shifted_agg_quotient_witness_poly); + + return Ok(Self::LookupProof { + agg_quotient_commitment: agg_quotient_witness_commit, + shifted_agg_quotient_commitment: shifted_agg_quotient_witness_commit, + }); + } + Err(Error::WrongLookupScheme) + } + + #[allow(irrefutable_let_patterns)] + fn lookup_verify( + &self, + transcript: &mut TranscriptProtocol, + proof: &LookupProof, + data: &LookupVerifyTransferData, + ) -> Result { + if let LookupProof::Plookup(p_proof) = proof { + if let LookupVerifyTransferData::Plookup(p_data) = data { + let aggregation_challenge = + transcript.challenge_scalar(TranscriptLabel::WITNESS_AGGREGATION); + let agg_witness_commit = Self::aggregate( + &vec![ + &p_data.f_commit, + &p_data.t_commit, + &p_proof.base_proof.commitments.h1, + &p_proof.base_proof.commitments.h2, + &p_proof.base_proof.commitments.z, + &p_proof.base_proof.commitments.q, + ], + &aggregation_challenge, + ) + .unwrap(); + let agg_witness_eval = Self::aggregate( + &vec![ + &p_proof.base_proof.evaluations.f, + &p_proof.base_proof.evaluations.t, + &p_proof.base_proof.evaluations.h1, + &p_proof.base_proof.evaluations.h2, + &p_proof.base_proof.evaluations.z, + &p_data.quotient_eval, + ], + &aggregation_challenge, + ) + .unwrap(); + let shifted_agg_witness_commit = Self::aggregate( + &vec![ + &p_data.t_commit, + &p_proof.base_proof.commitments.h1, + &p_proof.base_proof.commitments.h2, + &p_proof.base_proof.commitments.z, + ], + &aggregation_challenge, + ) + .unwrap(); + let shifted_agg_witness_eval = Self::aggregate( + &vec![ + &p_proof.base_proof.evaluations.t_g, + &p_proof.base_proof.evaluations.h1_g, + &p_proof.base_proof.evaluations.h2_g, + &p_proof.base_proof.evaluations.z_g, + ], + &aggregation_challenge, + ) + .unwrap(); + return Ok(self.batch_verify( + &[agg_witness_commit, shifted_agg_witness_commit], + &[ + p_data.evaluation_challenge, + p_data.shifted_evaluation_challenge, + ], + &[ + OtherKzgOpening( + p_proof.pcs_proof.agg_quotient_commitment.clone().0, + agg_witness_eval, + ), + OtherKzgOpening( + p_proof.pcs_proof.shifted_agg_quotient_commitment.clone().0, + shifted_agg_witness_eval, + ), + ], + &mut test_rng(), + )); + } + } + Err(Error::WrongLookupScheme) + } +} + +impl AdditiveHomomorphicPCS for KzgScheme {} diff --git a/lookup/src/pcs/mod.rs b/lookup/src/pcs/mod.rs new file mode 100644 index 0000000..2ada054 --- /dev/null +++ b/lookup/src/pcs/mod.rs @@ -0,0 +1,3 @@ +pub mod additive_homomorphic; +pub mod base_pcs; +pub mod kzg10; diff --git a/lookup/src/plookup/mod.rs b/lookup/src/plookup/mod.rs new file mode 100644 index 0000000..8d1aa2b --- /dev/null +++ b/lookup/src/plookup/mod.rs @@ -0,0 +1,4 @@ +pub mod quotient_poly; +pub mod scheme; +pub mod transcript_label; +pub mod types; diff --git a/lookup/src/plookup/quotient_poly.rs b/lookup/src/plookup/quotient_poly.rs new file mode 100644 index 0000000..893f094 --- /dev/null +++ b/lookup/src/plookup/quotient_poly.rs @@ -0,0 +1,265 @@ +use std::marker::PhantomData; + +use ark_ff::{PrimeField, Zero}; +use ark_poly::{DenseUVPolynomial, EvaluationDomain}; + +use crate::plookup::types::PlookupEvaluations; +use crate::poly::ExtraDensePoly; +use crate::types::{Domain, Poly}; + +pub struct QuotientPoly(PhantomData); + +#[allow(clippy::too_many_arguments)] +impl QuotientPoly { + /// Computes quotient polynomial in `Plookup`. + /// + /// # Arguments + /// + /// * `f` + /// * `t` + /// * `h1` + /// * `h2` + /// * `z` + /// * `domain` + /// * `beta` + /// * `gamma` + /// + /// returns: The quotient polynomial. + pub fn compute_quotient_poly( + f: &Poly, + t: &Poly, + h1: &Poly, + h2: &Poly, + z: &Poly, + domain: &Domain, + beta: F, + gamma: F, + ) -> Poly { + let domain_2n = &Domain::::new(2 * domain.size()).unwrap(); + let z1_zn_equal_one_poly = Self::z1_zn_equal_one_poly(z, domain, domain_2n); + let z_i_constraints_poly = + Self::z_i_constraints_poly(f, t, h1, h2, z, gamma, beta, domain, domain_2n); + let last_h1_equal_first_h2_poly = + Self::last_h1_equal_first_h2_poly(h1, h2, domain, domain_2n); + + let aggregate = z1_zn_equal_one_poly + z_i_constraints_poly + last_h1_equal_first_h2_poly; + let (q, r) = aggregate.divide_by_vanishing_poly(*domain).unwrap(); + assert!(r.is_zero()); + q + } + + /// Evaluates quotient polynomial at `evaluation_challenge`. + /// + /// # Arguments + /// + /// * `evaluations`: The computed evaluations. + /// * `beta` + /// * `gamma` + /// * `evaluation_challenge` + /// * `domain`: The domain over which the evaluations are defined. + /// + /// returns: The quotient evaluation result. + pub fn compute_quotient_evaluation( + evaluations: &PlookupEvaluations, + beta: &F, + gamma: &F, + evaluation_challenge: &F, + domain: &Domain, + ) -> F { + let one = F::one(); + // g^n + let gn = domain.elements().last().unwrap(); + // These evaluations are computed in O(|domain|) time + let lagrange_evaluations = domain.evaluate_all_lagrange_coefficients(*evaluation_challenge); + // L_0(x); + let l0_eval = lagrange_evaluations[0]; + // L_n(x); + let ln_eval = lagrange_evaluations.last().unwrap(); + + // (Z(x) - 1) * (L_0(x) + L_n(x)) + let z1_zn_equal_one_eval = (evaluations.z - one) * (l0_eval + ln_eval); + + // L_n(x) * (h1(x) - h2(g*x)) + let last_h1_equal_first_h2_eval = *ln_eval * (evaluations.h1 - evaluations.h2_g); + + // (x - g^n) * Z(X) * (1 + beta) * (gamma + f(x)) * (gamma * (1 + beta) + t(x) + beta*t(gx)) + // - + // (x - g^n) * Z(gx) * (gamma * (1 + beta) + h1(x) + beta * h1(gx)) * (gamma * (1 + beta) + h2(x) + beta * h2(gx)) + let check_z_eval: F = { + let beta_plus_one = *beta + one; + let gamma_mul_beta_plus_one = *gamma * beta_plus_one; + + let mut left_side = evaluations.z; + left_side *= beta_plus_one; + left_side *= *gamma + evaluations.f; + left_side *= gamma_mul_beta_plus_one + evaluations.t + (*beta * evaluations.t_g); + + let mut right_side = evaluations.z_g; + right_side *= gamma_mul_beta_plus_one + evaluations.h1 + (*beta * evaluations.h1_g); + right_side *= gamma_mul_beta_plus_one + evaluations.h2 + (*beta * evaluations.h2_g); + + (*evaluation_challenge - gn) * (left_side - right_side) + }; + + // Evaluates vanishing polynomial at evaluation_challenge + let v_h = domain.evaluate_vanishing_polynomial(*evaluation_challenge); + + (z1_zn_equal_one_eval + last_h1_equal_first_h2_eval + check_z_eval) / v_h + } + + /// Checks if `Z(1) = 1` and `Z(g^n) = 1`. + /// + /// # Arguments + /// + /// * `z` + /// * `domain`: The domain has size `n`. + /// * `domain_2n`: The domain has size `2n`. + /// + /// returns: The polynomial represents above condition. + fn z1_zn_equal_one_poly(z: &Poly, domain: &Domain, domain_2n: &Domain) -> Poly { + // L_0 + let l0 = Poly::lagrange_basis(0, domain); + // L_n + let ln = Poly::lagrange_basis(domain.size() - 1usize, domain); + // (L_0(x) + L_n(x)) * (Z(x) - 1) + let mut res = z - &Poly::from_constant(&F::one()); + res.mul_poly_over_domain_in_place(&(l0 + ln), domain_2n) + .unwrap(); + res + } + + /// Checks if `Z` is computed correctly. + /// + /// # Arguments + /// + /// * `f` + /// * `t` + /// * `h1` + /// * `h2` + /// * `z` + /// * `gamma` + /// * `beta` + /// * `domain`: The domain has size `n`. + /// * `domain_2n`: The domain has size `2n`. + /// + /// returns: The polynomial represents above condition. + fn z_i_constraints_poly( + f: &Poly, + t: &Poly, + h1: &Poly, + h2: &Poly, + z: &Poly, + gamma: F, + beta: F, + domain: &Domain, + domain_2n: &Domain, + ) -> Poly { + // g^n + let gn = domain.element(domain.size() - 1); + // x - g^n + let x_minus_gn: Poly = Poly::from_coefficients_vec(vec![gn.neg(), F::one()]); + // beta + 1 + let beta_plus_one = beta + F::one(); + // gamma * (1 + beta) + let gamma_mul_beta_plus_one = Poly::::from_constant(&(gamma * beta_plus_one)); + let domain_4n = &Domain::::new(domain_2n.size() * 2).unwrap(); + + // Z(x) * (1 + beta) * (gamma + f(x)) * (gamma*(1 + beta) + t(x) + beta*t(gx)) + let left_side = { + let mut left_side = z.clone(); + + // The degree is n after this + left_side.mul_scalar_in_place(&beta_plus_one); + + // The degree is 2n after this + left_side + .mul_poly_over_domain_in_place(&(f + &Poly::from_constant(&gamma)), domain_2n) + .unwrap(); + + // The degree is 3n after this + let mut t_gx_beta = Self::compute_f_gx_poly(t, domain); + t_gx_beta.mul_scalar_in_place(&beta); + left_side + .mul_poly_over_domain_in_place( + &(&gamma_mul_beta_plus_one + t + t_gx_beta), + domain_4n, + ) + .unwrap(); + left_side + }; + + // Z(gx) * (gamma*(1 + beta) + h1(x) + beta*h1(gx)) * (gamma*(1 + beta) + h2(x) + beta*h2(gx)) + let right_side = { + let mut right_side = Self::compute_f_gx_poly(z, domain); + + // The degree is 2n after this + let mut h1_gx_beta = Self::compute_f_gx_poly(h1, domain); + h1_gx_beta.mul_scalar_in_place(&beta); + right_side + .mul_poly_over_domain_in_place( + &(gamma_mul_beta_plus_one.clone() + h1.clone() + h1_gx_beta), + domain_2n, + ) + .unwrap(); + + // The degree is 3n after this + let mut h2_gx_beta = Self::compute_f_gx_poly(h2, domain); + h2_gx_beta.mul_scalar_in_place(&beta); + right_side + .mul_poly_over_domain_in_place( + &(gamma_mul_beta_plus_one + h2.clone() + h2_gx_beta), + domain_4n, + ) + .unwrap(); + right_side + }; + + (&left_side - &right_side).naive_mul(&x_minus_gn) + } + + /// Checks if `h1[n+1] = h2[1]`. + /// + /// # Arguments + /// + /// * `h1` + /// * `h2` + /// * `domain`: The domain has size `n` + /// * `domain_2n`: The domain has size `2n` + /// + /// returns: The polynomial represents above condition. + fn last_h1_equal_first_h2_poly( + h1: &Poly, + h2: &Poly, + domain: &Domain, + domain_2n: &Domain, + ) -> Poly { + let mut res = Poly::lagrange_basis(domain.size() - 1, domain); + let h1_minus_h2 = h1 - &Self::compute_f_gx_poly(h2, domain); + res.mul_poly_over_domain_in_place(&h1_minus_h2, domain_2n) + .unwrap(); + res + } + + /// Computes `f(gx)` from `f(x)` where `g` is group generator. + /// + /// # Arguments + /// + /// * `f`: The polynomial `f(x)`. + /// * `domain`: The domain over which `f(x)` is defined. + /// + /// returns: The polynomial representation of `f(gx)`. + fn compute_f_gx_poly(f: &Poly, domain: &Domain) -> Poly { + assert!(f.coeffs().len() <= domain.size()); + let mut g_pow = F::one(); + Poly::from_coefficients_vec( + f.coeffs() + .iter() + .map(|c| { + let gx_c = *c * g_pow; + g_pow *= domain.group_gen(); + gx_c + }) + .collect(), + ) + } +} diff --git a/lookup/src/plookup/scheme.rs b/lookup/src/plookup/scheme.rs new file mode 100644 index 0000000..02c0eb7 --- /dev/null +++ b/lookup/src/plookup/scheme.rs @@ -0,0 +1,513 @@ +use std::collections::HashSet; +use std::ops::{Add, Mul}; + +use ark_ff::PrimeField; +use ark_poly::{EvaluationDomain, Polynomial}; +use kzg::scheme::KzgScheme; + +use crate::errors::Error; +use crate::lookup::Lookup; +use crate::pcs::additive_homomorphic::AdditiveHomomorphicPCS; +use crate::plookup::quotient_poly::QuotientPoly; +use crate::plookup::transcript_label::TranscriptLabel; +use crate::plookup::types::{ + PlookupBaseProof, PlookupCommitments, PlookupEvaluations, PlookupProof, + PlookupProveTransferData, PlookupVerifyTransferData, +}; +use crate::poly::ExtraDensePoly; +use crate::row::Row; +use crate::transcript::TranscriptProtocol; +use crate::types::{Domain, LookupProof, LookupProofTransferData, LookupVerifyTransferData, Poly}; + +pub struct Plookup = KzgScheme> +where + P::Commitment: Add + Mul, +{ + /// The length of an element + w: usize, + /// The witnesses + f: Vec>, + /// The table + t: Vec>, + /// The hash table of elements in the table + hash_t: HashSet>, + /// The PCS + pcs: P, +} + +#[allow(clippy::type_complexity)] +impl> Lookup for Plookup +where + P::Commitment: Add + Mul, +{ + type Proof = PlookupProof; + type Element = Row; + + fn prove(&self, transcript: &mut TranscriptProtocol) -> Self::Proof { + let (fold_f, f_i_commit, fold_t, t_i_commit) = self.preprocess(transcript); + let domain: Domain = EvaluationDomain::::new(fold_t.0.len()).unwrap(); + + // Computes f,t,h1,h2 and send their commitments to verifier + let f_poly = Poly::from_evaluations(&fold_f.0, &domain); + let f_commit = self.pcs.commit(&f_poly); + let t_poly = Poly::from_evaluations(&fold_t.0, &domain); + let t_commit = self.pcs.commit(&t_poly); + let (h1, h2) = Self::compute_h1_h2(&fold_f, &fold_t).unwrap(); + let h1_poly = Poly::from_evaluations(&h1.0, &domain); + let h2_poly = Poly::from_evaluations(&h2.0, &domain); + let h1_commit = self.pcs.commit(&h1_poly); + let h2_commit = self.pcs.commit(&h2_poly); + transcript.append_commitment(TranscriptLabel::F_COMMIT, &f_commit); + transcript.append_commitment(TranscriptLabel::T_COMMIT, &t_commit); + transcript.append_commitment(TranscriptLabel::H1_COMMIT, &h1_commit); + transcript.append_commitment(TranscriptLabel::H2_COMMIT, &h2_commit); + + // Verifier sends random beta, gamma + let beta = transcript.challenge_scalar(TranscriptLabel::BETA); + let gamma = transcript.challenge_scalar(TranscriptLabel::GAMMA); + + // Computes z polynomial and send its commitment to verifier + let z_poly = + Self::compute_accumulator_poly(&fold_f, &fold_t, &h1, &h2, &beta, &gamma, &domain); + let z_commit = self.pcs.commit(&z_poly); + transcript.append_commitment(TranscriptLabel::Z_COMMIT, &z_commit); + + // Computes quotient polynomial and send its commitment to verifier + let quotient_poly = QuotientPoly::compute_quotient_poly( + &f_poly, &t_poly, &h1_poly, &h2_poly, &z_poly, &domain, beta, gamma, + ); + let quotient_commit = self.pcs.commit("ient_poly); + transcript.append_commitment(TranscriptLabel::Q_COMMIT, "ient_commit); + + // Verifier sends evaluation challenge + let evaluation_challenge = transcript.challenge_scalar(TranscriptLabel::OPENING); + let shifted_evaluation_challenge = evaluation_challenge * domain.group_gen(); + + // Computes evaluations at `challenge` + let f_eval = f_poly.evaluate(&evaluation_challenge); + let t_eval = t_poly.evaluate(&evaluation_challenge); + let h1_eval = h1_poly.evaluate(&evaluation_challenge); + let h2_eval = h2_poly.evaluate(&evaluation_challenge); + let z_eval = z_poly.evaluate(&evaluation_challenge); + let q_eval = quotient_poly.evaluate(&evaluation_challenge); + + // Computes evaluations at `challenge * g` + let t_g_eval = t_poly.evaluate(&shifted_evaluation_challenge); + let h1_g_eval = h1_poly.evaluate(&shifted_evaluation_challenge); + let h2_g_eval = h2_poly.evaluate(&shifted_evaluation_challenge); + let z_g_eval = z_poly.evaluate(&shifted_evaluation_challenge); + + // Sends evaluations to verifier + transcript.append_scalar(TranscriptLabel::F_EVAL, &f_eval); + transcript.append_scalar(TranscriptLabel::T_EVAL, &t_eval); + transcript.append_scalar(TranscriptLabel::H1_EVAL, &h1_eval); + transcript.append_scalar(TranscriptLabel::H2_EVAL, &h2_eval); + transcript.append_scalar(TranscriptLabel::Z_EVAL, &z_eval); + transcript.append_scalar(TranscriptLabel::Q_EVAL, &q_eval); + transcript.append_scalar(TranscriptLabel::T_G_EVAL, &t_g_eval); + transcript.append_scalar(TranscriptLabel::H1_G_EVAL, &h1_g_eval); + transcript.append_scalar(TranscriptLabel::H2_G_EVAL, &h2_g_eval); + transcript.append_scalar(TranscriptLabel::Z_G_EVAL, &z_g_eval); + + let base_proof = PlookupBaseProof { + d: fold_t.0.len(), + evaluations: PlookupEvaluations { + f: f_eval, + t: t_eval, + t_g: t_g_eval, + h1: h1_eval, + h1_g: h1_g_eval, + h2: h2_eval, + h2_g: h2_g_eval, + z: z_eval, + z_g: z_g_eval, + }, + commitments: PlookupCommitments { + f_i: f_i_commit, + t_i: t_i_commit, + q: quotient_commit, + h1: h1_commit, + h2: h2_commit, + z: z_commit, + }, + }; + + Self::Proof { + base_proof, + pcs_proof: self + .pcs + .lookup_prove( + transcript, + &LookupProofTransferData::Plookup(PlookupProveTransferData { + f_poly, + t_poly, + h1_poly, + h2_poly, + z_poly, + quotient_poly, + evaluation_challenge, + shifted_evaluation_challenge, + }), + ) + .unwrap(), + } + } + + fn verify( + &self, + transcript: &mut TranscriptProtocol, + proof: &Self::Proof, + ) -> Result { + let domain = Domain::::new(proof.base_proof.d).unwrap(); + // Appends `com(f_i)` + for fi_commit in &proof.base_proof.commitments.f_i { + transcript.append_commitment(TranscriptLabel::F_I_COMMIT, fi_commit); + } + let zeta = transcript.challenge_scalar(TranscriptLabel::ZETA); + let f_commit = + P::aggregate(&proof.base_proof.commitments.f_i.iter().collect(), &zeta).unwrap(); + let t_commit = + P::aggregate(&proof.base_proof.commitments.t_i.iter().collect(), &zeta).unwrap(); + transcript.append_commitment(TranscriptLabel::F_COMMIT, &f_commit); + transcript.append_commitment(TranscriptLabel::T_COMMIT, &t_commit); + transcript.append_commitment(TranscriptLabel::H1_COMMIT, &proof.base_proof.commitments.h1); + transcript.append_commitment(TranscriptLabel::H2_COMMIT, &proof.base_proof.commitments.h2); + + let beta = transcript.challenge_scalar(TranscriptLabel::BETA); + let gamma = transcript.challenge_scalar(TranscriptLabel::GAMMA); + + transcript.append_commitment(TranscriptLabel::Z_COMMIT, &proof.base_proof.commitments.z); + transcript.append_commitment(TranscriptLabel::Q_COMMIT, &proof.base_proof.commitments.q); + + let evaluation_challenge = transcript.challenge_scalar(TranscriptLabel::OPENING); + let shifted_evaluation_challenge = evaluation_challenge * domain.group_gen(); + // Computes quotient evaluation from the prover's messages + let quotient_eval = QuotientPoly::compute_quotient_evaluation( + &proof.base_proof.evaluations, + &beta, + &gamma, + &evaluation_challenge, + &domain, + ); + + transcript.append_scalar(TranscriptLabel::F_EVAL, &proof.base_proof.evaluations.f); + transcript.append_scalar(TranscriptLabel::T_EVAL, &proof.base_proof.evaluations.t); + transcript.append_scalar(TranscriptLabel::H1_EVAL, &proof.base_proof.evaluations.h1); + transcript.append_scalar(TranscriptLabel::H2_EVAL, &proof.base_proof.evaluations.h2); + transcript.append_scalar(TranscriptLabel::Z_EVAL, &proof.base_proof.evaluations.z); + transcript.append_scalar(TranscriptLabel::Q_EVAL, "ient_eval); + transcript.append_scalar(TranscriptLabel::T_G_EVAL, &proof.base_proof.evaluations.t_g); + transcript.append_scalar( + TranscriptLabel::H1_G_EVAL, + &proof.base_proof.evaluations.h1_g, + ); + transcript.append_scalar( + TranscriptLabel::H2_G_EVAL, + &proof.base_proof.evaluations.h2_g, + ); + transcript.append_scalar(TranscriptLabel::Z_G_EVAL, &proof.base_proof.evaluations.z_g); + + self.pcs.lookup_verify( + transcript, + &LookupProof::Plookup(proof), + &LookupVerifyTransferData::Plookup(PlookupVerifyTransferData { + f_commit, + t_commit, + quotient_eval, + evaluation_challenge, + shifted_evaluation_challenge, + }), + ) + } + + fn add_witness(&mut self, witness: Self::Element) -> Result<(), Error> { + if witness.0.len() != self.w { + return Err(Error::WitnessLengthNotMatch(format!( + "The length of witness is {}, not {}", + witness.0.len(), + self.w + ))); + } + if !self.hash_t.contains(&witness.0) { + return Err(Error::WitnessNotInTable); + } + self.f.push(witness); + Ok(()) + } +} + +impl> Plookup +where + P::Commitment: Add + Mul, +{ + /// Creates a new instance from a table and a PCS. + /// + /// # Arguments + /// + /// * `table`: The table. + /// * `pcs`: The PCS. + /// + /// returns: `Plookup` as a new instance. + #[allow(dead_code)] + fn new(table: Vec>, pcs: P) -> Self { + let mut hash_t = HashSet::>::new(); + for record in &table { + assert_eq!( + record.0.len(), + table[0].0.len(), + "The number of columns in the table must be equal" + ); + hash_t.insert(record.0.clone()); + } + Self { + w: if table.is_empty() { + 0 + } else { + table[0].0.len() + }, + f: Vec::new(), + t: table, + hash_t, + pcs, + } + } + + /// Calculates folded `f`, commitments for `f_i`, folded `t`, commitments for `t_i` + /// + /// # Arguments + /// + /// * `transcript`: The transcript generator. + /// + /// returns: `(fold_f, f_i_commit, fold_t, t_i_commit)`. + #[allow(clippy::type_complexity)] + fn preprocess( + &self, + transcript: &mut TranscriptProtocol, + ) -> (Row, Vec, Row, Vec) { + let mut t = self.t.clone(); + let mut f = self.f.clone(); + + f.sort(); + t.sort(); + let n = f.len(); + let d = t.len(); + if d <= n { + t.extend(vec![t.last().unwrap().clone(); n + 1 - d]); + } else { + f.extend(vec![f.last().unwrap().clone(); d - n - 1]); + } + assert_eq!(t.len(), f.len() + 1); + + // Calculates com(f_i) and com(t_i) + let domain = Domain::::new(t.len()).unwrap(); + let mut f_i_commit: Vec = vec![]; + let mut t_i_commit: Vec = vec![]; + for i in 0..self.w { + let mut fi: Vec = vec![]; + for f_tuple in &f { + fi.push(f_tuple.0[i]); + } + f_i_commit.push(self.pcs.commit(&Poly::from_evaluations(&fi, &domain))); + transcript.append_commitment(TranscriptLabel::F_I_COMMIT, f_i_commit.last().unwrap()); + let mut ti: Vec = vec![]; + for t_tuple in &t { + ti.push(t_tuple.0[i]); + } + t_i_commit.push(self.pcs.commit(&Poly::from_evaluations(&ti, &domain))); + } + + // Folds elements into field elements + let zeta = transcript.challenge_scalar(TranscriptLabel::ZETA); + let fold_f = Row(f + .iter() + .map(|fi| P::aggregate(&fi.0.iter().collect(), &zeta).unwrap()) + .collect()); + let fold_t = Row(t + .iter() + .map(|ti| P::aggregate(&ti.0.iter().collect(), &zeta).unwrap()) + .collect()); + + (fold_f, f_i_commit, fold_t, t_i_commit) + } + + /// Computes `Z` + pub fn compute_accumulator_poly( + f: &Row, + t: &Row, + h1: &Row, + h2: &Row, + beta: &F, + gamma: &F, + domain: &Domain, + ) -> Poly { + let n = f.0.len(); + // Calculates Z(x) for all x in domain + let mut evaluations: Vec = vec![F::one()]; + // beta + 1 + let beta_plus_one = *beta + F::one(); + // gamma * (beta + 1) + let gamma_beta_one = *gamma * beta_plus_one; + + for i in 0..n { + // (beta + 1)(gamma + f[i])(gamma * (beta + 1) + t[i] + beta * t[i+1]) + let mut numerator = beta_plus_one; + numerator *= *gamma + f.0[i]; + numerator *= gamma_beta_one + t.0[i] + (*beta) * t.0[i + 1]; + + // (gamma * (beta + 1) + s[i] + beta * s[i+1]) * (gamma * (beta + 1) + s[n+i] + beta * s[n+i+1]) + let mut denominator = gamma_beta_one + h1.0[i] + (*beta) * h1.0[i + 1]; + denominator *= gamma_beta_one + h2.0[i] + (*beta) * h2.0[i + 1]; + + // z[i+1] = z[i] * (numerator / denominator) + evaluations.push(*evaluations.last().unwrap() * (numerator / denominator)); + } + assert_eq!(evaluations.len(), n + 1); + assert_eq!(*evaluations.last().unwrap(), F::one()); + + Poly::from_evaluations(&evaluations, domain) + } + + /// Computes `h1` and `h2` from `f` and `t`. + /// + /// # Arguments + /// + /// * `f` + /// * `t` + /// + /// returns: `(h1, h2)`. + pub fn compute_h1_h2(f: &Row, t: &Row) -> Result<(Row, Row), Error> { + // check if all elements in `f` are also in `t` + for fi in &f.0 { + if !t.0.contains(fi) { + return Err(Error::WitnessNotInTable); + } + } + + // sort `s` by `t` + let mut s = t.clone(); + for fi in &f.0 { + let index = s.0.iter().position(|si| si == fi).unwrap(); + s.0.insert(index, *fi); + } + assert_eq!(s.0.len() % 2, 1); + + // h1[i] = s[i], i = 1...n+1 + let h1 = s.0[0..=s.0.len() / 2].to_vec(); + // h2[i] = s[i+n], i = 1..n+1 + let h2 = s.0[s.0.len() / 2..s.0.len()].to_vec(); + assert_eq!(h1.len(), h2.len()); + Ok((Row(h1), Row(h2))) + } +} + +#[cfg(test)] +mod test { + use ark_poly::EvaluationDomain; + use kzg::scheme::KzgScheme; + use kzg::srs::Srs; + use rand::Rng; + + use crate::lookup::Lookup; + use crate::pcs::kzg10::KzgField; + use crate::plookup::scheme::Plookup; + use crate::plookup::transcript_label::TranscriptLabel; + use crate::row::{ints_to_fields, Row}; + use crate::template_table::xor::XorTable; + use crate::transcript::TranscriptProtocol; + use crate::types::Domain; + + #[test] + /// Tests the correctness of `compute_h1_h2` function. + /// + /// This test validates the correctness of splitting `(f,t)` into `(h1,h2)` + fn compute_h1_h2() { + let f = ints_to_fields::(&[5, 2, 2, 4, 4]); + let t = ints_to_fields::(&[5, 3, 4, 2, 6, 7]); + let (h1, h2) = Plookup::::compute_h1_h2(&f, &t).unwrap(); + assert_eq!(h1, ints_to_fields::(&[5, 5, 3, 4, 4, 4])); + assert_eq!(h2, ints_to_fields::(&[4, 2, 2, 2, 6, 7])); + } + + #[test] + /// Tests the `add_witness` method of the `Plookup` struct. + /// + /// This test creates an instance of the `XorTable` and initializes a `Plookup` object. + /// It then adds a valid witness and verifies that adding an invalid witness results in an error. + /// + /// # Panics + /// + /// This test will panic if adding the valid witness fails. + fn add_witness() { + let t = XorTable::new(4).table().clone(); + let pcs = KzgScheme::new(Srs::new(t.len() * 2 + 3)); + let mut lookup = Plookup::::new(t, pcs); + lookup + .add_witness(Row(vec![ + KzgField::from(6), + KzgField::from(5), + KzgField::from(5 ^ 6), + ])) + .unwrap(); + assert!(lookup + .add_witness(Row(vec![ + KzgField::from(3651), + KzgField::from(5), + KzgField::from(5 ^ 6), + ])) + .is_err()); + } + + #[test] + /// Tests the `compute_accumulator_poly` method of the `Plookup` struct. + /// + /// The method is tested to ensure it processes the inputs correctly without panicking. + fn compute_accumulator_poly() { + let domain: Domain = EvaluationDomain::::new(4).unwrap(); + + Plookup::::compute_accumulator_poly( + &ints_to_fields(&[0, 2, 2]), // f = [0,2] + &ints_to_fields(&[0, 1, 2, 3]), // t = [0,1,2,3] + &ints_to_fields(&[0, 0, 1, 2]), // h1 + &ints_to_fields(&[2, 2, 2, 3]), // h2 + &KzgField::from(5659), + &KzgField::from(13954), + &domain, + ); + } + + #[test] + /// Tests the `prove` and `verify` methods of the `Plookup` struct. + /// + /// This test runs a series of test cases to validate the proof generation and verification process of the `Plookup` scheme. + /// + /// # Panics + /// + /// This test will panic if adding a witness or generating a proof fails, or if the verification of a proof does not pass. + fn prove_and_verify() { + const TEST_CASES: usize = 5; + let t = XorTable::new(4).table().clone(); + + let mut rnd = rand::thread_rng(); + let l = t.len(); + + for test in 0..TEST_CASES { + let mut transcript = TranscriptProtocol::::new(TranscriptLabel::NAME); + let pcs = KzgScheme::new(Srs::new(t.len() * 2 + 5)); + let mut lookup = Plookup::::new(t.clone(), pcs); + let w = rnd.gen_range(1..=l); + let mut v = vec![]; + for _ in 0..w { + let idx = rnd.gen_range(0..l); + v.push(idx); + } + for idx in &v { + lookup.add_witness(t[*idx].clone()).expect(""); + } + let proof = lookup.prove(&mut transcript); + let mut verifier_transcript = + TranscriptProtocol::::new(TranscriptLabel::NAME); + assert!(lookup.verify(&mut verifier_transcript, &proof).unwrap()); + println!("Test {} passed, v = {:?}", test, v); + } + } +} diff --git a/lookup/src/plookup/transcript_label.rs b/lookup/src/plookup/transcript_label.rs new file mode 100644 index 0000000..c482769 --- /dev/null +++ b/lookup/src/plookup/transcript_label.rs @@ -0,0 +1,29 @@ +pub struct TranscriptLabel; + +/// The labels used in transcript generator +impl TranscriptLabel { + pub const NAME: &'static [u8] = b"lookup"; + pub const ZETA: &'static [u8] = b"zeta"; + pub const T_COMMIT: &'static [u8] = b"t_commit"; + pub const T_I_COMMIT: &'static [u8] = b"t_i_commit"; + pub const F_COMMIT: &'static [u8] = b"f_commit"; + pub const F_I_COMMIT: &'static [u8] = b"f_i_commit"; + pub const H1_COMMIT: &'static [u8] = b"h1_commit"; + pub const H2_COMMIT: &'static [u8] = b"h2_commit"; + pub const BETA: &'static [u8] = b"beta"; + pub const GAMMA: &'static [u8] = b"gamma"; + pub const Z_COMMIT: &'static [u8] = b"z_commit"; + pub const Q_COMMIT: &'static [u8] = b"q_commit"; + pub const OPENING: &'static [u8] = b"opening"; + pub const F_EVAL: &'static [u8] = b"f_eval"; + pub const T_EVAL: &'static [u8] = b"t_eval"; + pub const H1_EVAL: &'static [u8] = b"h1_eval"; + pub const H2_EVAL: &'static [u8] = b"h2_eval"; + pub const Z_EVAL: &'static [u8] = b"z_eval"; + pub const Q_EVAL: &'static [u8] = b"q_eval"; + pub const T_G_EVAL: &'static [u8] = b"t_g_eval"; + pub const H1_G_EVAL: &'static [u8] = b"h1_g_eval"; + pub const H2_G_EVAL: &'static [u8] = b"h2_g_eval"; + pub const Z_G_EVAL: &'static [u8] = b"z_g_eval"; + pub const WITNESS_AGGREGATION: &'static [u8] = b"witness_aggregation"; +} diff --git a/lookup/src/plookup/types.rs b/lookup/src/plookup/types.rs new file mode 100644 index 0000000..8329322 --- /dev/null +++ b/lookup/src/plookup/types.rs @@ -0,0 +1,80 @@ +use ark_ff::PrimeField; + +use crate::pcs::base_pcs::BasePCS; +use crate::types::Poly; + +/// This struct represents the proof in Plookup protocol +pub struct PlookupProof> { + /// The proof that can be integrated with arbitrary PCS proof. + pub base_proof: PlookupBaseProof, + /// The proof depending on PCS. + pub pcs_proof: P::LookupProof, +} + +/// The base proof. +pub struct PlookupBaseProof> { + /// Size of the table. + pub d: usize, + /// All necessary evaluations + pub evaluations: PlookupEvaluations, + /// All necessary commitments + pub commitments: PlookupCommitments, +} + +/// Necessary evaluations of base proof. +pub struct PlookupEvaluations { + /// `f(x)`. + pub f: F, + /// `t(x)`. + pub t: F, + /// `t(gx)`. + pub t_g: F, + /// `h1(x)`. + pub h1: F, + /// `h1(gx)`. + pub h1_g: F, + /// `h2(x)`. + pub h2: F, + /// `h2(gx)`. + pub h2_g: F, + /// `z(x)`. + pub z: F, + /// `z(gx)`. + pub z_g: F, +} + +/// Necessary commitments of base proof. +pub struct PlookupCommitments { + /// `com(f_i)`, `i = 1...w` where `w` is the length of an element in table. + pub f_i: Vec, + /// `com(t_i)`, `i = 1...w`. + pub t_i: Vec, + /// `com(q)`. + pub q: Commitment, + /// `com(h1)`. + pub h1: Commitment, + /// `com(h2)`. + pub h2: Commitment, + /// `com(z)`. + pub z: Commitment, +} + +/// The proof data to forward to PCS, more details in [`prove`](crate::plookup::scheme::Plookup::prove) function +pub struct PlookupProveTransferData { + pub f_poly: Poly, + pub t_poly: Poly, + pub h1_poly: Poly, + pub h2_poly: Poly, + pub z_poly: Poly, + pub quotient_poly: Poly, + pub evaluation_challenge: F, + pub shifted_evaluation_challenge: F, +} +/// The verification data to forward to PCS, more details in [`verify`](crate::plookup::scheme::Plookup::verify) function +pub struct PlookupVerifyTransferData { + pub f_commit: Commitment, + pub t_commit: Commitment, + pub quotient_eval: F, + pub evaluation_challenge: F, + pub shifted_evaluation_challenge: F, +} diff --git a/lookup/src/poly.rs b/lookup/src/poly.rs new file mode 100644 index 0000000..3b1d607 --- /dev/null +++ b/lookup/src/poly.rs @@ -0,0 +1,162 @@ +use std::ops::SubAssign; + +use ark_ff::{PrimeField, Zero}; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{DenseUVPolynomial, EvaluationDomain, Polynomial}; +use ark_std::cfg_iter_mut; + +use crate::errors::Error; +use crate::types::{Domain, Poly}; + +/// Provides some extra methods for `DensePolynomial` +pub trait ExtraDensePoly { + fn from_evaluations(v: &[F], domain: &Domain) -> Self; + fn lagrange_basis(n: usize, domain: &Domain) -> Self; + fn mul_poly_over_domain_in_place( + &mut self, + other: &Self, + domain: &Domain, + ) -> Result<(), Error>; + fn mul_over_domain(&self, other: &Self, domain: &Domain) -> Self; + fn mul_scalar_in_place(&mut self, scalar: &F); + fn from_constant(c: &F) -> Self; + fn quotient_poly(&self, z: &F) -> Self; +} + +impl ExtraDensePoly for DensePolynomial { + /// Converts a vector of field elements into a polynomial over the specified domain. + /// + /// # Arguments + /// + /// * `evaluations`: A slice of field elements representing the coefficients of the polynomial. + /// * `domain`: The domain over which the polynomial is defined. + /// + /// returns: A `DensePolynomial` representing the polynomial over the specified domain. + fn from_evaluations(evaluations: &[F], domain: &Domain) -> Self { + Poly::from_coefficients_vec(domain.ifft(evaluations)) + } + + /// Computes the nth Lagrange basis polynomial over the given domain. + /// + /// # Arguments + /// + /// * `n`: The index of the Lagrange basis polynomial to compute. + /// * `domain`: The domain over which the Lagrange basis polynomials are defined. + /// + /// returns: A `DensePolynomial` representing the nth Lagrange basis polynomial over the specified domain. + fn lagrange_basis(n: usize, domain: &Domain) -> Self { + assert!(n < domain.size()); + let mut evaluations = vec![F::zero(); domain.size()]; + evaluations[n] = F::one(); + Self::from_evaluations(&evaluations, domain) + } + + /// Multiplies the polynomial by another polynomial over the given domain in place. + /// + /// # Arguments + /// + /// * `other`: Another polynomial to multiply with. + /// * `domain`: The domain over which the polynomials are defined. + /// + /// # Errors + /// + /// Returns an error if the sum of the lengths of coefficients of both polynomials is larger than the size of the domain. + fn mul_poly_over_domain_in_place( + &mut self, + other: &Self, + domain: &Domain, + ) -> Result<(), Error> { + if self.coeffs().len() + other.coeffs().len() > domain.size() { + return Err(Error::PolyNotFitInDomain( + "The number of coefficients of two polynomials cannot be larger than domain's size" + .to_string(), + )); + } + let mut self_evaluations = self.evaluate_over_domain_by_ref(*domain); + let other_evaluations = other.evaluate_over_domain_by_ref(*domain); + self_evaluations *= &other_evaluations; + *self = self_evaluations.interpolate(); + Ok(()) + } + + /// Multiply by another polynomial over a given domain + /// + /// # Arguments + /// + /// * `other`: Another polynomial to multiply with. + /// * `domain`: The domain over which the polynomials are defined. + /// + /// returns: The product of two polynomials + fn mul_over_domain(&self, other: &Self, domain: &Domain) -> Self { + let mut res = self.clone(); + res.mul_poly_over_domain_in_place(other, domain).unwrap(); + res + } + + /// Multiplies the polynomial by a scalar in place. + /// + /// # Arguments + /// + /// * `scalar`: The scalar value to multiply the polynomial by. + fn mul_scalar_in_place(&mut self, scalar: &F) { + if self.is_zero() || scalar.is_zero() { + *self = Self::zero(); + } else { + cfg_iter_mut!(self).for_each(|e| { + *e *= scalar; + }); + } + } + + /// Constructs a polynomial from a constant value. + /// + /// # Arguments + /// + /// * `c`: The constant value. + /// + /// returns: A new polynomial with the constant value as its only coefficient. + fn from_constant(c: &F) -> Self { + Self::from_coefficients_vec(vec![*c]) + } + + /// Computes `(f(x) - f(z)) / (x - z)` + /// + /// # Arguments + /// + /// * `z`: The value at which the polynomial is evaluated. + /// + /// returns: The quotient polynomial + fn quotient_poly(&self, z: &F) -> Self { + let fz = self.evaluate(z); + let divisor = Self::from_coefficients_vec(vec![z.neg(), F::one()]); + let mut res = self.clone(); + res.sub_assign(&Poly::from_constant(&fz)); + &res / &divisor + } +} + +// + +#[cfg(test)] +mod test { + use ark_poly::{DenseUVPolynomial, EvaluationDomain}; + + use crate::pcs::kzg10::KzgField; + use crate::poly::ExtraDensePoly; + use crate::row::ints_to_fields; + use crate::types::{Domain, Poly}; + + #[test] + /// Tests the correctness of `mul_over_domain` function. + /// + /// This test validates the correctness of multiplying two polynomials over a given domain + fn mul_over_domain() { + // x^2 + 2x + 1 + let f1 = Poly::::from_coefficients_vec(ints_to_fields(&[1, 2, 1]).0); + // x^3 + 3x + 2 + let f2 = Poly::::from_coefficients_vec(ints_to_fields(&[2, 3, 0, 1]).0); + let domain = Domain::::new(10).unwrap(); + let f3 = f1.mul_over_domain(&f2, &domain); + assert_eq!(f3, &f1 * &f2); + } +} diff --git a/lookup/src/row.rs b/lookup/src/row.rs new file mode 100644 index 0000000..2276cbe --- /dev/null +++ b/lookup/src/row.rs @@ -0,0 +1,46 @@ +use std::ops::{Add, AddAssign, Mul}; + +use ark_ff::PrimeField; + +/// This struct represents a row in a table. +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +pub struct Row(pub Vec); + +impl Add for Row { + type Output = Self; + + /// Adds two rows element-wise. + fn add(self, rhs: Self) -> Self::Output { + assert_eq!(self.0.len(), rhs.0.len()); + Self(rhs.0.iter().zip(self.0).map(|(x, y)| *x + y).collect()) + } +} + +impl AddAssign for Row { + fn add_assign(&mut self, rhs: Self) { + assert_eq!(self.0.len(), rhs.0.len()); + for i in 0..self.0.len() { + self.0[i] += rhs.0[i]; + } + } +} + +impl Mul for Row { + type Output = Row; + + /// Multiplies each element of the row by a scalar. + fn mul(self, rhs: F) -> Self::Output { + Row(self.0.iter().map(|x| *x * rhs).collect()) + } +} + +/// Converts a slice of 128-bit unsigned integers to a `Row` of field elements. +/// +/// # Arguments +/// +/// * `v`: A slice of `u128` values to be converted to field elements. +/// +/// returns: A `Row` containing the field elements corresponding to the input integers. +pub fn ints_to_fields(v: &[u128]) -> Row { + Row(v.iter().map(|e| F::from(*e)).collect()) +} diff --git a/lookup/src/template_table/mod.rs b/lookup/src/template_table/mod.rs new file mode 100644 index 0000000..4d8aae4 --- /dev/null +++ b/lookup/src/template_table/mod.rs @@ -0,0 +1 @@ +pub mod xor; diff --git a/lookup/src/template_table/xor.rs b/lookup/src/template_table/xor.rs new file mode 100644 index 0000000..0dcd3d9 --- /dev/null +++ b/lookup/src/template_table/xor.rs @@ -0,0 +1,34 @@ +use ark_ff::PrimeField; + +use crate::row::Row; + +/// This struct represents a table generated from XOR operations. +/// +/// Each element has the form (a, b, a xor b). +pub struct XorTable { + table: Vec>, +} + +impl XorTable { + /// Creates a new instance of the XOR table with a specified size. + /// + /// # Arguments + /// + /// * `n`: The size of the XOR table. + /// + /// returns: a new instance of the XOR table. + pub fn new(n: usize) -> Self { + let mut table = vec![]; + for i in 0..(1 << (n - 1)) as u128 { + for j in 0..(1 << (n - 1)) as u128 { + table.push(Row(vec![F::from(i), F::from(j), F::from(i ^ j)])); + } + } + Self { table } + } + + /// Returns a reference to the XOR table. + pub fn table(&self) -> &Vec> { + &self.table + } +} diff --git a/lookup/src/transcript.rs b/lookup/src/transcript.rs new file mode 100644 index 0000000..95b1ff8 --- /dev/null +++ b/lookup/src/transcript.rs @@ -0,0 +1,40 @@ +use std::marker::PhantomData; + +use ark_ff::{BigInteger, PrimeField}; +use merlin::Transcript; + +/// A transcript generator used in Fiat-Shamir transformation. +/// +/// This struct is built on top of [Merlin](https://crates.io/crates/merlin) +pub struct TranscriptProtocol(Transcript, PhantomData); + +impl TranscriptProtocol { + /// Size of a challenge, depending on the field used. + const MODULUS_BYTE_SIZE: usize = (F::MODULUS_BIT_SIZE / 8) as usize; + pub fn new(name: &'static [u8]) -> Self { + Self(Transcript::new(name), PhantomData) + } + + pub fn append_commitment( + &mut self, + label: &'static [u8], + comm: &Commitment, + ) { + self.0.append_message(label, &comm.to_bytes()); + } + + pub fn append_scalar(&mut self, label: &'static [u8], s: &F) { + self.0.append_message(label, &s.into_bigint().to_bytes_le()) + } + + pub fn challenge_scalar(&mut self, label: &'static [u8]) -> F { + let mut buf = vec![0u8; Self::MODULUS_BYTE_SIZE]; + self.0.challenge_bytes(label, &mut buf); + F::from_le_bytes_mod_order(&buf) + } +} + +/// Elements appended in the transcript must implement this trait. +pub trait ToBytes { + fn to_bytes(&self) -> Vec; +} diff --git a/lookup/src/types.rs b/lookup/src/types.rs new file mode 100644 index 0000000..8aca685 --- /dev/null +++ b/lookup/src/types.rs @@ -0,0 +1,20 @@ +use crate::pcs::base_pcs::BasePCS; +use crate::plookup::types::{PlookupProof, PlookupProveTransferData, PlookupVerifyTransferData}; +use ark_ff::PrimeField; +use ark_poly::univariate::DensePolynomial; +use ark_poly::Radix2EvaluationDomain; + +pub type Poly = DensePolynomial; +pub type Domain = Radix2EvaluationDomain; + +pub enum LookupProofTransferData { + Plookup(PlookupProveTransferData), +} + +pub enum LookupVerifyTransferData { + Plookup(PlookupVerifyTransferData), +} + +pub enum LookupProof<'a, F: PrimeField, P: BasePCS> { + Plookup(&'a PlookupProof), +}