From 4ad14ea4ce9473e13ed6437140fcbbff3a8ccce1 Mon Sep 17 00:00:00 2001 From: anupsv Date: Sat, 11 Jan 2025 09:22:19 +0530 Subject: [PATCH] feat: kzg batch verification (#24) * adding bare changes for batch verification * adding some comments * adding more comments * moving back to sha2 * removing a test which is no longer needed. Removing methods no longer needed * updates to method visibility, updating tests * fmt fixes * clean up * cleanup, optimization, inline docs * removing unwanted const * more docs and cleanup * formatting * removing unwanted comments * cargo fmt and clippy * adding test for point at infinity * cleaner errors, cleanup * adding another test case * removing unwanted errors * adding fixes per comments * adding 4844 spec references * comment fixes * formatting, adding index out of bound check, removing print statement * removing unwanted test, adding test for evaluate_polynomial_in_evaluation_form * moving test to bottom section * Update src/polynomial.rs Co-authored-by: Samuel Laferriere * Update src/kzg.rs Co-authored-by: Samuel Laferriere * Update src/kzg.rs Co-authored-by: Samuel Laferriere * Update src/kzg.rs Co-authored-by: Samuel Laferriere * Update src/helpers.rs Co-authored-by: Samuel Laferriere * updating deps, and toolchain to 1.84 * removing errors test, no longer useful * adding to_byte_array arg explanation * fmt fixes * fmt and clippy fixes * fixing function names and fmt * clippy fixes * Update src/helpers.rs Co-authored-by: Samuel Laferriere * changes based on comments and discussion --------- Co-authored-by: anupsv <6407789+anupsv@users.noreply.github.com> Co-authored-by: Samuel Laferriere Co-authored-by: Bowen Xue <93296844+bxue-l2@users.noreply.github.com> --- Cargo.toml | 18 +- benches/bench_kzg_proof.rs | 6 +- benches/bench_kzg_verify.rs | 12 +- rust-toolchain | 2 +- src/blob.rs | 17 +- src/consts.rs | 14 + src/errors.rs | 104 ++-- src/helpers.rs | 133 ++++- src/kzg.rs | 955 +++++++++++++++++++++++++++++++----- src/polynomial.rs | 23 +- tests/error_tests.rs | 90 ---- tests/kzg_test.rs | 601 ++++++++++------------- 12 files changed, 1328 insertions(+), 647 deletions(-) delete mode 100644 tests/error_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 6b9f2e1..b922614 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "rust-kzg-bn254" version = "0.2.1" edition = "2021" authors = ["Anup Swamy Veena", "Teddy Knox"] -rust-version = "1.73" +rust-version = "1.81" description = "This library offers a set of functions for generating and interacting with bn254 KZG commitments and proofs in rust, with the motivation of supporting fraud and validity proof logic in EigenDA rollup integrations." readme = "README.md" repository = "https://github.com/Layr-Labs/rust-kzg-bn254" @@ -22,21 +22,23 @@ directories = "5.0.1" hex-literal = "0.4.1" rand = "0.8.5" sha2 = "0.10.8" -ureq = "2.9.6" +ureq = "2.12.1" num-bigint = "0.4" -rayon = "^1.5" +rayon = "1.10" num-traits = "0.2" -byteorder = "1.4" +byteorder = "1.5" ark-poly = { version = "0.5.0", features = ["parallel"] } crossbeam-channel = "0.5" -num_cpus = "1.13.0" +num_cpus = "1.16.0" sys-info = "0.9" +itertools = "0.13.0" +thiserror = "2.0.10" [dev-dependencies] criterion = "0.5" -lazy_static = "1.4" -tracing = { version = "^0.1.34", features = ["log"] } -tracing-subscriber = "0.3.18" +lazy_static = "1.5" +tracing = { version = "0.1.41", features = ["log"] } +tracing-subscriber = "0.3.19" [[test]] name = "kzg" diff --git a/benches/bench_kzg_proof.rs b/benches/bench_kzg_proof.rs index d15cda1..53b59c7 100644 --- a/benches/bench_kzg_proof.rs +++ b/benches/bench_kzg_proof.rs @@ -23,7 +23,7 @@ fn bench_kzg_proof(c: &mut Criterion) { let index = rand::thread_rng().gen_range(0..input_poly.len_underlying_blob_field_elements()); b.iter(|| { - kzg.compute_proof_with_roots_of_unity(&input_poly, index.try_into().unwrap()) + kzg.compute_proof_with_known_z_fr_index(&input_poly, index.try_into().unwrap()) .unwrap() }); }); @@ -37,7 +37,7 @@ fn bench_kzg_proof(c: &mut Criterion) { let index = rand::thread_rng().gen_range(0..input_poly.len_underlying_blob_field_elements()); b.iter(|| { - kzg.compute_proof_with_roots_of_unity(&input_poly, index.try_into().unwrap()) + kzg.compute_proof_with_known_z_fr_index(&input_poly, index.try_into().unwrap()) .unwrap() }); }); @@ -51,7 +51,7 @@ fn bench_kzg_proof(c: &mut Criterion) { let index = rand::thread_rng().gen_range(0..input_poly.len_underlying_blob_field_elements()); b.iter(|| { - kzg.compute_proof_with_roots_of_unity(&input_poly, index.try_into().unwrap()) + kzg.compute_proof_with_known_z_fr_index(&input_poly, index.try_into().unwrap()) .unwrap() }); }); diff --git a/benches/bench_kzg_verify.rs b/benches/bench_kzg_verify.rs index 21f0834..9112c50 100644 --- a/benches/bench_kzg_verify.rs +++ b/benches/bench_kzg_verify.rs @@ -24,9 +24,9 @@ fn bench_kzg_verify(c: &mut Criterion) { rand::thread_rng().gen_range(0..input_poly.len_underlying_blob_field_elements()); let commitment = kzg.commit_eval_form(&input_poly).unwrap(); let proof = kzg - .compute_proof_with_roots_of_unity(&input_poly, index.try_into().unwrap()) + .compute_proof_with_known_z_fr_index(&input_poly, index.try_into().unwrap()) .unwrap(); - let value_fr = input_poly.get_at_index(index).unwrap(); + let value_fr = input_poly.get_evalualtion(index).unwrap(); let z_fr = kzg.get_nth_root_of_unity(index).unwrap(); b.iter(|| kzg.verify_proof(commitment, proof, *value_fr, *z_fr)); }); @@ -41,9 +41,9 @@ fn bench_kzg_verify(c: &mut Criterion) { rand::thread_rng().gen_range(0..input_poly.len_underlying_blob_field_elements()); let commitment = kzg.commit_eval_form(&input_poly).unwrap(); let proof = kzg - .compute_proof_with_roots_of_unity(&input_poly, index.try_into().unwrap()) + .compute_proof_with_known_z_fr_index(&input_poly, index.try_into().unwrap()) .unwrap(); - let value_fr = input_poly.get_at_index(index).unwrap(); + let value_fr = input_poly.get_evalualtion(index).unwrap(); let z_fr = kzg.get_nth_root_of_unity(index).unwrap(); b.iter(|| kzg.verify_proof(commitment, proof, *value_fr, *z_fr)); }); @@ -58,9 +58,9 @@ fn bench_kzg_verify(c: &mut Criterion) { rand::thread_rng().gen_range(0..input_poly.len_underlying_blob_field_elements()); let commitment = kzg.commit_eval_form(&input_poly).unwrap(); let proof = kzg - .compute_proof_with_roots_of_unity(&input_poly, index.try_into().unwrap()) + .compute_proof_with_known_z_fr_index(&input_poly, index.try_into().unwrap()) .unwrap(); - let value_fr = input_poly.get_at_index(index).unwrap(); + let value_fr = input_poly.get_evalualtion(index).unwrap(); let z_fr = kzg.get_nth_root_of_unity(index).unwrap(); b.iter(|| kzg.verify_proof(commitment, proof, *value_fr, *z_fr)); }); diff --git a/rust-toolchain b/rust-toolchain index d2d67a3..9b97c89 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,5 +1,5 @@ [toolchain] -channel = '1.75' +channel = '1.81' profile = 'minimal' components = ['clippy', 'rustfmt'] targets = ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-gnu", "wasm32-unknown-unknown", "aarch64-apple-darwin"] diff --git a/src/blob.rs b/src/blob.rs index 2dd9a55..b34f5aa 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -3,11 +3,12 @@ use crate::{ polynomial::{PolynomialCoeffForm, PolynomialEvalForm}, }; -/// A blob which is Eigen DA spec aligned. +/// A blob aligned with the Eigen DA specification. /// TODO: we should probably move to a transparent repr like /// #[derive(Clone, Debug, PartialEq, Eq)] pub struct Blob { + /// The binary data contained within the blob. blob_data: Vec, } @@ -48,12 +49,22 @@ impl Blob { &self.blob_data } - /// Returns the length of the data in the blob. + /// Returns the length of the blob data. + /// + /// This length reflects the size of the data, including any padding if applied. + /// + /// # Returns + /// + /// The length of the blob data as a `usize`. pub fn len(&self) -> usize { self.blob_data.len() } - /// Checks if the blob data is empty. + /// Checks whether the blob data is empty. + /// + /// # Returns + /// + /// `true` if the blob data is empty, `false` otherwise. pub fn is_empty(&self) -> bool { self.blob_data.is_empty() } diff --git a/src/consts.rs b/src/consts.rs index ac2793f..31dfb6d 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,3 +1,17 @@ pub const BYTES_PER_FIELD_ELEMENT: usize = 32; pub const SIZE_OF_G1_AFFINE_COMPRESSED: usize = 32; // in bytes pub const SIZE_OF_G2_AFFINE_COMPRESSED: usize = 64; // in bytes + +/// Ref: https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#blob +pub const FIAT_SHAMIR_PROTOCOL_DOMAIN: &[u8] = b"EIGENDA_FSBLOBVERIFY_V1_"; // Adapted from 4844 + +/// Ref: https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#blob +pub const RANDOM_CHALLENGE_KZG_BATCH_DOMAIN: &[u8] = b"EIGENDA_RCKZGBATCH___V1_"; // Adapted from 4844 + +pub const KZG_ENDIANNESS: Endianness = Endianness::Big; // Choose between Big or Little. + +#[derive(Debug, Clone, Copy)] +pub enum Endianness { + Big, + Little, +} diff --git a/src/errors.rs b/src/errors.rs index a83bb0f..aa981a2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,64 +1,64 @@ -use std::{error::Error, fmt}; +use thiserror::Error; -#[derive(Clone, Debug, PartialEq)] -pub enum BlobError { - GenericError(String), -} - -impl fmt::Display for BlobError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - BlobError::GenericError(ref msg) => write!(f, "generic error: {}", msg), - } - } -} - -impl Error for BlobError {} - -#[derive(Clone, Debug, PartialEq)] +/// Errors related to Polynomial operations. +/// +/// The `PolynomialError` enum encapsulates all possible errors that can occur +/// during operations on the `Polynomial` struct, such as FFT transformations +/// and serialization errors. +#[derive(Clone, Debug, PartialEq, Error)] pub enum PolynomialError { - SerializationFromStringError, + /// Error related to commitment operations with a descriptive message. + #[error("commitment error: {0}")] CommitError(String), - GenericError(String), + + /// Error related to Fast Fourier Transform (FFT) operations with a descriptive message. + #[error("FFT error: {0}")] FFTError(String), - IncorrectFormError(String), -} -impl fmt::Display for PolynomialError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - PolynomialError::SerializationFromStringError => { - write!(f, "couldn't load string to fr vector") - }, - PolynomialError::CommitError(ref msg) => write!(f, "Commitment error: {}", msg), - PolynomialError::FFTError(ref msg) => write!(f, "FFT error: {}", msg), - PolynomialError::GenericError(ref msg) => write!(f, "generic error: {}", msg), - PolynomialError::IncorrectFormError(ref msg) => { - write!(f, "Incorrect form error: {}", msg) - }, - } - } + /// A generic error with a descriptive message. + #[error("generic error: {0}")] + GenericError(String), } -impl Error for PolynomialError {} - -#[derive(Clone, Debug, PartialEq)] +/// Errors related to KZG operations. +/// +/// The `KzgError` enum encapsulates all possible errors that can occur during +/// KZG-related operations, including those from `PolynomialError` and `BlobError`. +/// It also includes additional errors specific to KZG operations. +#[derive(Clone, Debug, PartialEq, Error)] pub enum KzgError { - CommitError(String), + /// Wraps errors originating from Polynomial operations. + #[error("polynomial error: {0}")] + PolynomialError(#[from] PolynomialError), + + #[error("MSM error: {0}")] + MsmError(String), + + /// Error related to serialization with a descriptive message. + #[error("serialization error: {0}")] SerializationError(String), - FftError(String), + + /// Error related to commitment processes with a descriptive message. + #[error("not on curve error: {0}")] + NotOnCurveError(String), + + /// Error indicating an invalid commit operation with a descriptive message. + #[error("commit error: {0}")] + CommitError(String), + + /// Error related to Fast Fourier Transform (FFT) operations with a descriptive message. + #[error("FFT error: {0}")] + FFTError(String), + + /// A generic error with a descriptive message. + #[error("generic error: {0}")] GenericError(String), -} -impl fmt::Display for KzgError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - KzgError::CommitError(ref msg) => write!(f, "Commitment error: {}", msg), - KzgError::SerializationError(ref msg) => write!(f, "Serialization error: {}", msg), - KzgError::FftError(ref msg) => write!(f, "FFT error: {}", msg), - KzgError::GenericError(ref msg) => write!(f, "Generic error: {}", msg), - } - } -} + /// Error indicating an invalid denominator scenario, typically in mathematical operations. + #[error("invalid denominator")] + InvalidDenominator, -impl Error for KzgError {} + /// Error indicating an invalid input length scenario, typically in data processing. + #[error("invalid input length")] + InvalidInputLength, +} diff --git a/src/helpers.rs b/src/helpers.rs index f049240..db8b300 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,5 +1,5 @@ use ark_bn254::{Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective}; -use ark_ec::AffineRepr; +use ark_ec::{AffineRepr, CurveGroup, VariableBaseMSM}; use ark_ff::{sbb, BigInt, BigInteger, Field, LegendreSymbol, PrimeField}; use ark_std::{str::FromStr, vec::Vec, One, Zero}; use crossbeam_channel::Receiver; @@ -7,7 +7,11 @@ use std::cmp; use crate::{ arith, - consts::{BYTES_PER_FIELD_ELEMENT, SIZE_OF_G1_AFFINE_COMPRESSED, SIZE_OF_G2_AFFINE_COMPRESSED}, + consts::{ + Endianness, BYTES_PER_FIELD_ELEMENT, KZG_ENDIANNESS, SIZE_OF_G1_AFFINE_COMPRESSED, + SIZE_OF_G2_AFFINE_COMPRESSED, + }, + errors::KzgError, traits::ReadPointFromBytes, }; use ark_ec::AdditiveGroup; @@ -40,7 +44,7 @@ pub fn convert_by_padding_empty_byte(data: &[u8]) -> Vec { let parse_size = BYTES_PER_FIELD_ELEMENT - 1; let put_size = BYTES_PER_FIELD_ELEMENT; - let data_len = (data_size + parse_size - 1) / parse_size; + let data_len = data_size.div_ceil(parse_size); let mut valid_data = vec![0u8; data_len * put_size]; let mut valid_end = valid_data.len(); @@ -66,7 +70,7 @@ pub fn convert_by_padding_empty_byte(data: &[u8]) -> Vec { pub fn remove_empty_byte_from_padded_bytes_unchecked(data: &[u8]) -> Vec { let data_size = data.len(); let parse_size = BYTES_PER_FIELD_ELEMENT; - let data_len = (data_size + parse_size - 1) / parse_size; + let data_len = data_size.div_ceil(parse_size); let put_size = BYTES_PER_FIELD_ELEMENT - 1; let mut valid_data = vec![0u8; data_len * put_size]; @@ -95,7 +99,7 @@ pub fn set_bytes_canonical(data: &[u8]) -> Fr { } pub fn get_num_element(data_len: usize, symbol_size: usize) -> usize { - (data_len + symbol_size - 1) / symbol_size + data_len.div_ceil(symbol_size) } pub fn to_fr_array(data: &[u8]) -> Vec { @@ -116,26 +120,76 @@ pub fn to_fr_array(data: &[u8]) -> Vec { eles } -pub fn to_byte_array(data_fr: &[Fr], max_data_size: usize) -> Vec { +/// Converts a slice of field elements to a byte array with size constraints +/// +/// # Arguments +/// * `data_fr` - Slice of field elements to convert to bytes +/// * `max_data_size` - Maximum allowed size in bytes for the output buffer +/// +/// # Returns +/// * `Vec` - Byte array containing the encoded field elements, truncated if needed +/// +/// # Details +/// - Each field element is converted to BYTES_PER_FIELD_ELEMENT bytes +/// - Output is truncated to max_data_size if total bytes would exceed it +/// +/// # Example +/// ``` +/// use rust_kzg_bn254::kzg::KZG; +/// use rust_kzg_bn254::blob::Blob; +/// +/// let mut kzg = KZG::setup( +/// "tests/test-files/mainnet-data/g1.131072.point", +/// "", +/// "tests/test-files/mainnet-data/g2.point.powerOf2", +/// 268435456, +/// 131072, +/// ).unwrap(); +/// let input = Blob::from_raw_data(b"random data for blob"); +/// kzg.calculate_roots_of_unity(input.len().try_into().unwrap()).unwrap(); +/// ``` +pub fn to_byte_array(data_fr: &[Fr], max_output_size: usize) -> Vec { + // Calculate the number of field elements in input let n = data_fr.len(); - let data_size = cmp::min(n * BYTES_PER_FIELD_ELEMENT, max_data_size); + + let data_size = cmp::min(n * BYTES_PER_FIELD_ELEMENT, max_output_size); + let mut data = vec![0u8; data_size]; + // Iterate through each field element + // Using enumerate().take(n) to process elements up to n for (i, element) in data_fr.iter().enumerate().take(n) { - let v: Vec = element.into_bigint().to_bytes_be(); - + // Convert field element to bytes based on configured endianness + // TODO(anupsv): To be removed and default to Big endian. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/27 + let v: Vec = match KZG_ENDIANNESS { + Endianness::Big => element.into_bigint().to_bytes_be(), // Big-endian conversion + Endianness::Little => element.into_bigint().to_bytes_le(), // Little-endian conversion + }; + + // Calculate start and end indices for this element in output buffer let start = i * BYTES_PER_FIELD_ELEMENT; let end = (i + 1) * BYTES_PER_FIELD_ELEMENT; - if end > max_data_size { - let slice_end = cmp::min(v.len(), max_data_size - start); + if end > max_output_size { + // Handle case where this element would exceed max_output_size + // Calculate how many bytes we can actually copy + let slice_end = cmp::min(v.len(), max_output_size - start); + + // Copy partial element and break the loop + // We can't fit any more complete elements data[start..start + slice_end].copy_from_slice(&v[..slice_end]); break; } else { + // Normal case: element fits within max_output_size + // Calculate actual end index considering data_size limit let actual_end = cmp::min(end, data_size); + + // Copy element bytes to output buffer + // Only copy up to actual_end in case this is the last partial element data[start..actual_end].copy_from_slice(&v[..actual_end - start]); } } + data } @@ -179,11 +233,6 @@ pub fn lexicographically_largest(z: &Fq) -> bool { let tmp = arith::montgomery_reduce(&z.0 .0[0], &z.0 .0[1], &z.0 .0[2], &z.0 .0[3]); let mut borrow: u64 = 0; - // (_, borrow) = sbb(tmp.0, 0x9E10460B6C3E7EA4, 0); - // (_, borrow) = sbb(tmp.1, 0xCBC0B548B438E546, borrow); - // (_, borrow) = sbb(tmp.2, 0xDC2822DB40C0AC2E, borrow); - // (_, borrow) = sbb(tmp.3, 0x183227397098D014, borrow); - sbb!(tmp.0, 0x9E10460B6C3E7EA4, &mut borrow); sbb!(tmp.1, 0xCBC0B548B438E546, &mut borrow); sbb!(tmp.2, 0xDC2822DB40C0AC2E, &mut borrow); @@ -374,3 +423,55 @@ pub fn is_on_curve_g2(g2: &G2Projective) -> bool { right += &tmp; left == right } + +/// Computes powers of a field element up to a given exponent. +/// Ref: https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#compute_powers +/// +/// For a given field element x, computes [1, x, x², x³, ..., x^(count-1)] +/// +/// # Arguments +/// * `base` - The field element to compute powers of +/// * `count` - The number of powers to compute (0 to count-1) +/// +/// # Returns +/// * Vector of field elements containing powers: [x⁰, x¹, x², ..., x^(count-1)] +pub fn compute_powers(base: &Fr, count: usize) -> Vec { + // Pre-allocate vector to avoid reallocations + let mut powers = Vec::with_capacity(count); + + // Start with x⁰ = 1 + let mut current = Fr::one(); + + // Compute successive powers by multiplying by base + for _ in 0..count { + // Add current power to vector + powers.push(current); + // Compute next power: x^(i+1) = x^i * x + current *= base; + } + + powers +} + +/// Computes a linear combination of G1 points weighted by scalar coefficients. +/// +/// Given points P₁, P₂, ..., Pₙ and scalars s₁, s₂, ..., sₙ +/// Computes: s₁P₁ + s₂P₂ + ... + sₙPₙ +/// Uses Multi-Scalar Multiplication (MSM) for efficient computation. +/// +/// # Arguments +/// * `points` - Array of G1 points in affine form +/// * `scalars` - Array of field elements as scalar weights +/// +/// # Returns +/// * Single G1 point in affine form representing the linear combination +pub fn g1_lincomb(points: &[G1Affine], scalars: &[Fr]) -> Result { + // Use MSM (Multi-Scalar Multiplication) for efficient linear combination + // MSM is much faster than naive point addition and scalar multiplication + let lincomb = + G1Projective::msm(points, scalars).map_err(|e| KzgError::MsmError(e.to_string()))?; + + // Convert result back to affine coordinates + // This is typically needed as most protocols expect points in affine form + Ok(lincomb.into_affine()) +} diff --git a/src/kzg.rs b/src/kzg.rs index f986b38..cda9495 100644 --- a/src/kzg.rs +++ b/src/kzg.rs @@ -1,19 +1,26 @@ use crate::{ blob::Blob, - consts::BYTES_PER_FIELD_ELEMENT, + consts::{BYTES_PER_FIELD_ELEMENT, SIZE_OF_G1_AFFINE_COMPRESSED}, errors::KzgError, helpers, polynomial::{PolynomialCoeffForm, PolynomialEvalForm}, traits::ReadPointFromBytes, }; -use ark_bn254::{g1::G1Affine, Bn254, Fr, G1Projective, G2Affine}; + +use crate::consts::{ + Endianness, FIAT_SHAMIR_PROTOCOL_DOMAIN, KZG_ENDIANNESS, RANDOM_CHALLENGE_KZG_BATCH_DOMAIN, +}; +use crate::helpers::is_on_curve_g1; +use ark_bn254::{Bn254, Fr, G1Affine, G1Projective, G2Affine, G2Projective}; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, VariableBaseMSM}; +use ark_ff::{BigInteger, Field, PrimeField}; use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; -use ark_serialize::Read; -use ark_std::{ops::Div, str::FromStr, One, Zero}; +use ark_serialize::{CanonicalSerialize, Read}; +use ark_std::{iterable::Iterable, ops::Div, str::FromStr, One, Zero}; use crossbeam_channel::{bounded, Sender}; use num_traits::ToPrimitive; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use sha2::{Digest, Sha256}; use std::{ fs::File, io::{self, BufReader}, @@ -97,22 +104,116 @@ impl KZG { } pub fn read_g2_point_on_power_of_2(g2_power_of2_path: &str) -> Result, KzgError> { - let mut file = File::open(g2_power_of2_path).unwrap(); + let mut file = + File::open(g2_power_of2_path).map_err(|e| KzgError::GenericError(e.to_string()))?; // Calculate the start position in bytes and seek to that position // Read in 64-byte chunks let mut chunks = Vec::new(); let mut buffer = [0u8; 64]; loop { - let bytes_read = file.read(&mut buffer).unwrap(); + let bytes_read = file + .read(&mut buffer) + .map_err(|e| KzgError::GenericError(e.to_string()))?; if bytes_read == 0 { break; // End of file reached } - chunks.push(G2Affine::read_point_from_bytes_be(&buffer[..bytes_read]).unwrap()); + chunks.push( + G2Affine::read_point_from_bytes_be(&buffer[..bytes_read]) + .map_err(|e| KzgError::GenericError(e.to_string()))?, + ); } Ok(chunks) } + /// Calculates the roots of unities but doesn't assign it to the struct + /// Used in batch verification process as the roots need to be calculated for each blob + /// because of different length. + /// + /// # Arguments + /// * `length_of_data_after_padding` - Length of the blob data after padding in bytes. + /// + /// # Returns + /// * `Result<(Params, Vec), KzgError>` - Tuple containing: + /// - Params: KZG library operational parameters + /// - Vec: Vector of roots of unity + /// + /// # Details + /// - Generates roots of unity needed for FFT operations + /// - Calculates KZG operational parameters for commitment scheme + /// + /// # Example + /// ``` + /// use ark_std::One; + /// use rust_kzg_bn254::helpers::to_byte_array; + /// use ark_bn254::Fr; + /// + /// let elements = vec![Fr::one(), Fr::one(), Fr::one()]; + /// let max_size = 64; + /// let bytes = to_byte_array(&elements, max_size); + /// assert_eq!(bytes.len(), 64); + /// // bytes will contain up to max_size bytes from the encoded elements + /// ``` + fn calculate_roots_of_unity_standalone( + length_of_data_after_padding: u64, + srs_order: u64, + ) -> Result<(Params, Vec), KzgError> { + // Initialize parameters + let mut params = Params { + num_chunks: 0_u64, + chunk_length: 0_u64, + max_fft_width: 0_u64, + completed_setup: false, + }; + + // Calculate log2 of the next power of two of the length of data after padding + let log2_of_evals = (length_of_data_after_padding + .div_ceil(32) + .next_power_of_two() as f64) + .log2() + .to_u8() + .ok_or_else(|| { + KzgError::GenericError( + "Failed to convert length_of_data_after_padding to u8".to_string(), + ) + })?; + + // Set the maximum FFT width + params.max_fft_width = 1_u64 << log2_of_evals; + + // Check if the length of data after padding is valid with respect to the SRS order + if length_of_data_after_padding + .div_ceil(BYTES_PER_FIELD_ELEMENT as u64) + .next_power_of_two() + > srs_order + { + return Err(KzgError::SerializationError( + "the supplied encoding parameters are not valid with respect to the SRS." + .to_string(), + )); + } + + // Get the primitive roots of unity + let primitive_roots_of_unity = Self::get_primitive_roots_of_unity()?; + + // Find the root of unity corresponding to the calculated log2 value + let found_root_of_unity = primitive_roots_of_unity + .get(log2_of_evals as usize) + .ok_or_else(|| KzgError::GenericError("Root of unity not found".to_string()))?; + + // Expand the root to get all the roots of unity + let mut expanded_roots_of_unity = Self::expand_root_of_unity(found_root_of_unity); + + // Remove the last element to avoid duplication + expanded_roots_of_unity.truncate(expanded_roots_of_unity.len() - 1); + + // Mark the setup as completed + params.completed_setup = true; + + // Return the parameters and the expanded roots of unity + Ok((params, expanded_roots_of_unity)) + } + /// Similar to [Kzg::data_setup_mins], but mainly used for setting up Kzg /// for testing purposes. Used to specify the number of chunks and chunk /// length. These parameters are then used to calculate the FFT params @@ -147,19 +248,27 @@ impl KZG { let number_of_evaluations = params.chunk_length * params.num_chunks; let mut log2_of_evals = number_of_evaluations .to_f64() - .unwrap() + .ok_or_else(|| { + KzgError::GenericError("Failed to convert number_of_evaluations to f64".to_string()) + })? .log2() .to_u8() - .unwrap(); + .ok_or_else(|| { + KzgError::GenericError("Failed to convert number_of_evaluations to u8".to_string()) + })?; params.max_fft_width = 1_u64 << log2_of_evals; if params.chunk_length == 1 { log2_of_evals = (2 * params.num_chunks) .to_f64() - .unwrap() + .ok_or_else(|| { + KzgError::GenericError("Failed to convert num_chunks to f64".to_string()) + })? .log2() .to_u8() - .unwrap(); + .ok_or_else(|| { + KzgError::GenericError("Failed to convert num_chunks to u8".to_string()) + })?; } if params.chunk_length * params.num_chunks >= self.srs_order { @@ -169,10 +278,12 @@ impl KZG { )); } - let primitive_roots_of_unity = Self::get_primitive_roots_of_unity(); + let primitive_roots_of_unity = Self::get_primitive_roots_of_unity()?; let found_root_of_unity = primitive_roots_of_unity - .get(log2_of_evals.to_usize().unwrap()) - .unwrap(); + .get(log2_of_evals.to_usize().ok_or_else(|| { + KzgError::GenericError("Failed to convert log2_of_evals to usize".to_string()) + })?) + .ok_or_else(|| KzgError::GenericError("Root of unity not found".to_string()))?; let mut expanded_roots_of_unity = Self::expand_root_of_unity(found_root_of_unity); expanded_roots_of_unity.truncate(expanded_roots_of_unity.len() - 1); @@ -187,40 +298,20 @@ impl KZG { &mut self, length_of_data_after_padding: u64, ) -> Result<(), KzgError> { - let log2_of_evals = length_of_data_after_padding - .div_ceil(32) - .next_power_of_two() - .to_f64() - .unwrap() - .log2() - .to_u8() - .unwrap(); - self.params.max_fft_width = 1_u64 << log2_of_evals; - - if length_of_data_after_padding - .div_ceil(BYTES_PER_FIELD_ELEMENT.try_into().unwrap()) - .next_power_of_two() - >= self.srs_order - { - return Err(KzgError::SerializationError( - "the supplied encoding parameters are not valid with respect to the SRS." - .to_string(), - )); - } - - let primitive_roots_of_unity = Self::get_primitive_roots_of_unity(); - let found_root_of_unity = primitive_roots_of_unity - .get(log2_of_evals.to_usize().unwrap()) - .unwrap(); - let mut expanded_roots_of_unity = Self::expand_root_of_unity(found_root_of_unity); - expanded_roots_of_unity.truncate(expanded_roots_of_unity.len() - 1); - + let (params, roots_of_unity) = Self::calculate_roots_of_unity_standalone( + length_of_data_after_padding, + self.srs_order, + )?; + self.params = params; self.params.completed_setup = true; - self.expanded_roots_of_unity = expanded_roots_of_unity; - + self.expanded_roots_of_unity = roots_of_unity; Ok(()) } + pub fn get_roots_of_unities(&self) -> Vec { + self.expanded_roots_of_unity.clone() + } + /// helper function to get the pub fn get_nth_root_of_unity(&self, i: usize) -> Option<&Fr> { self.expanded_roots_of_unity.get(i) @@ -242,8 +333,9 @@ impl KZG { roots } - /// refer to DA code for more context - fn get_primitive_roots_of_unity() -> Vec { + /// Precompute the primitive roots of unity for binary powers that divide r - 1 + /// TODO(anupsv): Move this to the constants file. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/31 + fn get_primitive_roots_of_unity() -> Result, KzgError> { let data: [&str; 29] = [ "1", "21888242871839275222246405745257275088548364400416034343698204186575808495616", @@ -276,8 +368,11 @@ impl KZG { "19103219067921713944291392827692070036145651957329286315305642004821462161904", ]; data.iter() - .map(|each| Fr::from_str(each).unwrap()) - .collect() + .map(Fr::from_str) + .collect::, _>>() + .map_err(|_| { + KzgError::GenericError("Failed to parse primitive roots of unity".to_string()) + }) } /// helper function to get g1 points @@ -374,7 +469,8 @@ impl KZG { /// bytes from the file, and fans them out to worker threads (one per /// cpu) which parse the bytes into G1Affine points. The worker threads /// then fan in the parsed points to the main thread, which sorts them by - /// their original position in the file to maintain order. + /// their original position in the file to maintain order. Not used anywhere + /// but kept as a reference. /// /// # Arguments /// * `file_path` - The path to the file containing the G1 points @@ -522,33 +618,11 @@ impl KZG { } } - /// commit to a [Blob], by transforming it into a [PolynomialEvalForm] and - /// then calling [Kzg::commit_eval_form]. - pub fn commit_blob(&self, blob: &Blob) -> Result { - let polynomial = blob.to_polynomial_eval_form(); - self.commit_eval_form(&polynomial) - } - - /// Wrapper around [Kzg::compute_proof] that uses the roots of unity - /// that were expanded and stored in [Kzg::setup]. - pub fn compute_proof_with_roots_of_unity( + /// Helper function for `compute_kzg_proof()` and `compute_blob_kzg_proof()` + fn compute_proof_impl( &self, polynomial: &PolynomialEvalForm, - index: u64, - ) -> Result { - self.compute_proof(polynomial, index, &self.expanded_roots_of_unity) - } - - /// Compute a kzg proof from a polynomial in evaluation form. - /// We don't currently support proofs for polynomials in coefficient form, - /// but one can take the FFT of the polynomial in coefficient form to - /// get the polynomial in evaluation form. This is available via the - /// method [PolynomialCoeffForm::to_eval_form]. - pub fn compute_proof( - &self, - polynomial: &PolynomialEvalForm, - index: u64, - root_of_unities: &[Fr], + z_fr: &Fr, ) -> Result { if !self.params.completed_setup { return Err(KzgError::GenericError( @@ -556,82 +630,134 @@ impl KZG { )); } - if polynomial.len() != root_of_unities.len() { + // Verify polynomial length matches that of the roots of unity + if polynomial.len() != self.expanded_roots_of_unity.len() { return Err(KzgError::GenericError( "inconsistent length between blob and root of unities".to_string(), )); } let eval_fr = polynomial.evaluations(); + // Pre-allocate vector for shifted polynomial p(x) - y let mut poly_shift: Vec = Vec::with_capacity(eval_fr.len()); - let usized_index = if let Some(x) = index.to_usize() { - x - } else { - return Err(KzgError::SerializationError( - "index couldn't be converted to usize".to_string(), - )); - }; - let value_fr = eval_fr[usized_index]; - let z_fr = root_of_unities[usized_index]; + // Evaluate polynomial at the point z + // This gives us y = p(z) + let y_fr = Self::evaluate_polynomial_in_evaluation_form(polynomial, z_fr, self.srs_order)?; + // Compute p(x) - y for each evaluation point + // This is the numerator of the quotient polynomial for fr in eval_fr { - poly_shift.push(*fr - value_fr); + poly_shift.push(*fr - y_fr); } - let mut denom_poly = Vec::::with_capacity(root_of_unities.len()); - for root_of_unity in root_of_unities.iter().take(eval_fr.len()) { + // Compute denominator polynomial (x - z) at each root of unity + let mut denom_poly = Vec::::with_capacity(self.expanded_roots_of_unity.len()); + for root_of_unity in self.expanded_roots_of_unity.iter().take(eval_fr.len()) { denom_poly.push(*root_of_unity - z_fr); } - let mut quotient_poly = Vec::::with_capacity(root_of_unities.len()); + // Pre-allocate vector for quotient polynomial evaluations + let mut quotient_poly = Vec::::with_capacity(self.expanded_roots_of_unity.len()); - for i in 0..root_of_unities.len() { + // Compute quotient polynomial q(x) = (p(x) - y)/(x - z) at each root of unity + for i in 0..self.expanded_roots_of_unity.len() { if denom_poly[i].is_zero() { - quotient_poly.push(self.compute_quotient_eval_on_domain( - z_fr, - eval_fr, - value_fr, - root_of_unities, - )); + // Special case: when x = z, use L'Hôpital's rule + // Compute the derivative evaluation instead + quotient_poly.push(self.compute_quotient_eval_on_domain(z_fr, eval_fr, &y_fr)); } else { + // Normal case: direct polynomial division quotient_poly.push(poly_shift[i].div(denom_poly[i])); } } - let bases = self.g1_ifft(polynomial.len())?; + let quotient_poly_eval_form = PolynomialEvalForm::new(quotient_poly); + self.commit_eval_form("ient_poly_eval_form) + } + + /// commit to a [Blob], by transforming it into a [PolynomialEvalForm] and + /// then calling [Kzg::commit_eval_form]. + pub fn commit_blob(&self, blob: &Blob) -> Result { + let polynomial = blob.to_polynomial_eval_form(); + self.commit_eval_form(&polynomial) + } + + pub fn compute_proof_with_known_z_fr_index( + &self, + polynomial: &PolynomialEvalForm, + index: u64, + ) -> Result { + // Convert u64 index to usize for array indexing + let usized_index = index.to_usize().ok_or(KzgError::GenericError( + "Index conversion to usize failed".to_string(), + ))?; + + // Get the root of unity at the specified index + let z_fr = self + .get_nth_root_of_unity(usized_index) + .ok_or_else(|| KzgError::GenericError("Root of unity not found".to_string()))?; + + // Compute the KZG proof at the selected root of unity + // This delegates to the main proof computation function + // using our selected evaluation point + self.compute_proof(polynomial, z_fr) + } - match G1Projective::msm(&bases, "ient_poly) { - Ok(res) => Ok(G1Affine::from(res)), - Err(err) => Err(KzgError::SerializationError(err.to_string())), + /// Compute a kzg proof from a polynomial in evaluation form. + /// We don't currently support proofs for polynomials in coefficient form, + /// but one can take the FFT of the polynomial in coefficient form to + /// get the polynomial in evaluation form. This is available via the + /// method [PolynomialCoeffForm::to_eval_form]. + /// TODO(anupsv): Accept bytes instead of Fr element. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/29 + pub fn compute_proof( + &self, + polynomial: &PolynomialEvalForm, + z_fr: &Fr, + ) -> Result { + if !self.params.completed_setup { + return Err(KzgError::GenericError( + "setup is not complete, run one of the setup functions".to_string(), + )); } + + // Verify that polynomial length matches roots of unity length + if polynomial.len() != self.expanded_roots_of_unity.len() { + return Err(KzgError::GenericError( + "inconsistent length between blob and root of unities".to_string(), + )); + } + + // Call the implementation to compute the actual proof + // This will: + // 1. Evaluate polynomial at z + // 2. Compute quotient polynomial q(x) = (p(x) - p(z)) / (x - z) + // 3. Generate KZG proof as commitment to q(x) + self.compute_proof_impl(polynomial, z_fr) } /// refer to DA for more context - pub fn compute_quotient_eval_on_domain( - &self, - z_fr: Fr, - eval_fr: &[Fr], - value_fr: Fr, - roots_of_unity: &[Fr], - ) -> Fr { + pub fn compute_quotient_eval_on_domain(&self, z_fr: &Fr, eval_fr: &[Fr], value_fr: &Fr) -> Fr { let mut quotient = Fr::zero(); let mut fi: Fr = Fr::zero(); let mut numerator: Fr = Fr::zero(); let mut denominator: Fr = Fr::zero(); let mut temp: Fr = Fr::zero(); - roots_of_unity.iter().enumerate().for_each(|(i, omega_i)| { - if *omega_i == z_fr { - return; - } - fi = eval_fr[i] - value_fr; - numerator = fi * omega_i; - denominator = z_fr - omega_i; - denominator *= z_fr; - temp = numerator.div(denominator); - quotient += temp; - }); + self.expanded_roots_of_unity + .iter() + .enumerate() + .for_each(|(i, omega_i)| { + if *omega_i == *z_fr { + return; + } + fi = eval_fr[i] - value_fr; + numerator = fi * omega_i; + denominator = z_fr - omega_i; + denominator *= z_fr; + temp = numerator.div(denominator); + quotient += temp; + }); quotient } @@ -640,7 +766,7 @@ impl KZG { pub fn g1_ifft(&self, length: usize) -> Result, KzgError> { // is not power of 2 if !length.is_power_of_two() { - return Err(KzgError::FftError( + return Err(KzgError::FFTError( "length provided is not a power of 2".to_string(), )); } @@ -650,7 +776,7 @@ impl KZG { .map(|&p| G1Projective::from(p)) .collect(); let ifft_result: Vec<_> = GeneralEvaluationDomain::::new(length) - .ok_or(KzgError::FftError( + .ok_or(KzgError::FFTError( "Could not perform IFFT due to domain consturction error".to_string(), ))? .ifft(&points_projective) @@ -661,23 +787,58 @@ impl KZG { Ok(ifft_result) } + /// TODO(anupsv): Accept bytes instead of Fr element and Affine points. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/30 pub fn verify_proof( &self, commitment: G1Affine, proof: G1Affine, value_fr: Fr, z_fr: Fr, - ) -> bool { - let g2_tau = if self.g2.len() > 28 { - *self.g2.get(1).unwrap() - } else { - *self.g2.first().unwrap() - }; + ) -> Result { + // Get τ*G2 from the trusted setup + // This is the second generator point multiplied by the trusted setup secret + let g2_tau = self.get_g2_tau()?; + + // Compute [value]*G1 + // This encrypts the claimed evaluation value as a point in G1 let value_g1 = (G1Affine::generator() * value_fr).into_affine(); + + // Compute [C - value*G1] + // This represents the difference between the commitment and claimed value + // If the claim is valid, this equals H(X)(X - z) in the polynomial equation let commit_minus_value = (commitment - value_g1).into_affine(); + + // Compute [z]*G2 + // This encrypts the evaluation point as a point in G2 let z_g2 = (G2Affine::generator() * z_fr).into_affine(); - let x_minus_z = (g2_tau - z_g2).into_affine(); - Self::pairings_verify(commit_minus_value, G2Affine::generator(), proof, x_minus_z) + + // Compute [τ - z]*G2 + // This represents (X - z) in the polynomial equation + // τ is the secret from the trusted setup representing the variable X + let x_minus_z = (*g2_tau - z_g2).into_affine(); + + // Verify the pairing equation: + // e([C - value*G1], G2) = e(proof, [τ - z]*G2) + // This checks if (C - value*G1) = proof * (τ - z) + // which verifies the polynomial quotient relationship + Ok(Self::pairings_verify( + commit_minus_value, // Left side first argument + G2Affine::generator(), // Left side second argument (G2 generator) + proof, // Right side first argument + x_minus_z, // Right side second argument + )) + } + + pub fn get_g2_tau(&self) -> Result<&G2Affine, KzgError> { + if self.g2.len() > 28 { + self.g2 + .get(1) + .ok_or(KzgError::GenericError("g2 tau not found".to_string())) + } else { + self.g2 + .first() + .ok_or(KzgError::GenericError("g2 tau not found".to_string())) + } } fn pairings_verify(a1: G1Affine, a2: G2Affine, b1: G1Affine, b2: G2Affine) -> bool { @@ -687,4 +848,544 @@ impl KZG { let result = Bn254::multi_pairing(p, q); result.is_zero() } + + /// TODO(anupsv): Accept bytes instead of Affine points. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/31 + pub fn verify_blob_kzg_proof( + &self, + blob: &Blob, + commitment: &G1Affine, + proof: &G1Affine, + ) -> Result { + // Convert blob to polynomial + let polynomial = blob.to_polynomial_eval_form(); + + // Compute the evaluation challenge for the blob and commitment + let evaluation_challenge = Self::compute_challenge(blob, commitment)?; + + // Evaluate the polynomial in evaluation form + let y = Self::evaluate_polynomial_in_evaluation_form( + &polynomial, + &evaluation_challenge, + self.srs_order, + )?; + + // Verify the KZG proof + self.verify_proof(*commitment, *proof, y, evaluation_challenge) + } + + /// TODO(anupsv): Match 4844 specs w.r.t to the inputs. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/30 + pub fn compute_blob_proof( + &self, + blob: &Blob, + commitment: &G1Affine, + ) -> Result { + // Validate that the commitment is a valid point on the G1 curve + // This prevents potential invalid curve attacks + if !commitment.is_on_curve() || !commitment.is_in_correct_subgroup_assuming_on_curve() { + return Err(KzgError::NotOnCurveError( + "commitment not on curve".to_string(), + )); + } + + // Convert the blob to a polynomial in evaluation form + // This is necessary because KZG proofs work with polynomials + let blob_poly = blob.to_polynomial_eval_form(); + + // Compute the evaluation challenge using Fiat-Shamir heuristic + // This challenge determines the point at which we evaluate the polynomial + let evaluation_challenge = Self::compute_challenge(blob, commitment)?; + + // Compute the actual KZG proof using the polynomial and evaluation point + // This creates a proof that the polynomial evaluates to a specific value at the challenge point + // The proof is a single G1 point that can be used to verify the evaluation + self.compute_proof_impl(&blob_poly, &evaluation_challenge) + } + + /// Maps a byte slice to a field element (`Fr`) using SHA-256 from SHA3 family as the + /// hash function. + /// + /// # Arguments + /// + /// * `msg` - The input byte slice to hash. + /// + /// # Returns + /// + /// * `Fr` - The resulting field element. + fn hash_to_field_element(msg: &[u8]) -> Fr { + // Perform the hash operation. + let msg_digest = Sha256::digest(msg); + let hash_elements = msg_digest.as_slice(); + + // TODO(anupsv): To be removed and default to Big endian. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/27 + let fr_element: Fr = match KZG_ENDIANNESS { + Endianness::Big => Fr::from_be_bytes_mod_order(hash_elements), + Endianness::Little => Fr::from_le_bytes_mod_order(hash_elements), + }; + + fr_element + } + + /// Computes the Fiat-Shamir challenge from a blob and its commitment. + /// + /// # Arguments + /// + /// * `blob` - A reference to the `Blob` struct. + /// * `commitment` - A reference to the `G1Affine` commitment. + /// + /// # Returns + /// + /// * `Ok(Fr)` - The resulting field element challenge. + /// * `Err(KzgError)` - If any step fails. + pub fn compute_challenge(blob: &Blob, commitment: &G1Affine) -> Result { + // Convert the blob to a polynomial in evaluation form + // This is needed to process the blob data for the challenge + let blob_poly = blob.to_polynomial_eval_form(); + + // Calculate total size needed for the challenge input buffer: + // - Length of domain separator + // - 8 bytes for number of field elements + // - Size of blob data (number of field elements * bytes per element) + // - Size of compressed G1 point (commitment) + let challenge_input_size = FIAT_SHAMIR_PROTOCOL_DOMAIN.len() + + 8 + + (blob_poly.len() * BYTES_PER_FIELD_ELEMENT) + + SIZE_OF_G1_AFFINE_COMPRESSED; + + // Initialize buffer to store all data that will be hashed + let mut digest_bytes = vec![0; challenge_input_size]; + let mut offset = 0; + + // Step 1: Copy the Fiat-Shamir domain separator + // This provides domain separation for the hash function to prevent + // attacks that try to confuse different protocol messages + digest_bytes[offset..offset + FIAT_SHAMIR_PROTOCOL_DOMAIN.len()] + .copy_from_slice(FIAT_SHAMIR_PROTOCOL_DOMAIN); + offset += FIAT_SHAMIR_PROTOCOL_DOMAIN.len(); + + // Step 2: Copy the number of field elements (blob polynomial length) + // Convert to bytes using the configured endianness + // TODO(anupsv): To be removed and default to Big endian. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/27 + let number_of_field_elements = match KZG_ENDIANNESS { + Endianness::Big => blob_poly.len().to_be_bytes(), + Endianness::Little => blob_poly.len().to_le_bytes(), + }; + digest_bytes[offset..offset + 8].copy_from_slice(&number_of_field_elements); + offset += 8; + + // Step 3: Copy the blob data + // Convert polynomial to bytes using helper function + let blob_data = helpers::to_byte_array( + blob_poly.evaluations(), + blob_poly.len() * BYTES_PER_FIELD_ELEMENT, + ); + digest_bytes[offset..offset + blob_data.len()].copy_from_slice(&blob_data); + offset += blob_data.len(); + + // Step 4: Copy the commitment (compressed G1 point) + // Serialize the commitment point in compressed form + let mut commitment_bytes = Vec::with_capacity(SIZE_OF_G1_AFFINE_COMPRESSED); + commitment + .serialize_compressed(&mut commitment_bytes) + .map_err(|_| { + KzgError::SerializationError("Failed to serialize commitment".to_string()) + })?; + digest_bytes[offset..offset + SIZE_OF_G1_AFFINE_COMPRESSED] + .copy_from_slice(&commitment_bytes); + + // Verify that we wrote exactly the amount of bytes we expected + // This helps catch any buffer overflow/underflow bugs + if offset + SIZE_OF_G1_AFFINE_COMPRESSED != challenge_input_size { + return Err(KzgError::InvalidInputLength); + } + + // Hash all the data to generate the challenge field element + // This implements the Fiat-Shamir transform to generate a "random" challenge + Ok(Self::hash_to_field_element(&digest_bytes)) + } + + /// Ref: https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#evaluate_polynomial_in_evaluation_form + pub fn evaluate_polynomial_in_evaluation_form( + polynomial: &PolynomialEvalForm, + z: &Fr, + srs_order: u64, + ) -> Result { + // Step 1: Retrieve the length of the padded blob + let blob_size = polynomial.len_underlying_blob_bytes(); + + // Step 2: Calculate roots of unity for the given blob size and SRS order + let (_, roots_of_unity) = + Self::calculate_roots_of_unity_standalone(blob_size as u64, srs_order)?; + + // Step 3: Ensure the polynomial length matches the domain length + if polynomial.len() != roots_of_unity.len() { + return Err(KzgError::InvalidInputLength); + } + + let width = polynomial.len(); + + // Step 4: Compute inverse_width = 1 / width + let inverse_width = Fr::from(width as u64) + .inverse() + .ok_or(KzgError::InvalidDenominator)?; + + // Step 5: Check if `z` is in the domain + if let Some(index) = roots_of_unity.iter().position(|&domain_i| domain_i == *z) { + return polynomial + .get_evalualtion(index) + .cloned() + .ok_or(KzgError::GenericError( + "Polynomial element missing at the found index.".to_string(), + )); + } + + // Step 6: Use the barycentric formula to compute the evaluation + let sum = polynomial + .evaluations() + .iter() + .zip(roots_of_unity.iter()) + .map(|(f_i, &domain_i)| { + let a = *f_i * domain_i; + let b = *z - domain_i; + // Since `z` is not in the domain, `b` should never be zero + a / b + }) + .fold(Fr::zero(), |acc, val| acc + val); + + // Step 7: Compute r = z^width - 1 + let r = z.pow([width as u64]) - Fr::one(); + + // Step 8: Compute f(z) = (z^width - 1) / width * sum + let f_z = sum * r * inverse_width; + + Ok(f_z) + } + + /// A helper function for the `verify_blob_kzg_proof_batch` function. + fn compute_challenges_and_evaluate_polynomial( + blobs: &[Blob], + commitments: &[G1Affine], + srs_order: u64, + ) -> Result<(Vec, Vec), KzgError> { + // Pre-allocate vectors to store: + // - evaluation_challenges: Points where polynomials will be evaluated + // - ys: Results of polynomial evaluations at challenge points + let mut evaluation_challenges = Vec::with_capacity(blobs.len()); + let mut ys = Vec::with_capacity(blobs.len()); + + // Process each blob sequentially + // TODO: Potential optimizations: + // 1. Cache roots of unity calculations across iterations + // 2. Parallelize processing for large numbers of blobs + // 3. Batch polynomial conversions if possible + for i in 0..blobs.len() { + // Step 1: Convert blob to polynomial form + // This is necessary because we need to evaluate the polynomial + let polynomial = blobs[i].to_polynomial_eval_form(); + + // Step 2: Generate Fiat-Shamir challenge + // This creates a "random" evaluation point based on the blob and commitment + // The challenge is deterministic but unpredictable, making the proof non-interactive + let evaluation_challenge = Self::compute_challenge(&blobs[i], &commitments[i])?; + + // Step 3: Evaluate the polynomial at the challenge point + // This uses the evaluation form for efficient computation + // The srs_order parameter ensures compatibility with the trusted setup + let y = Self::evaluate_polynomial_in_evaluation_form( + &polynomial, + &evaluation_challenge, + srs_order, + )?; + + // Store both: + // - The challenge point (where we evaluated) + // - The evaluation result (what the polynomial equals at that point) + evaluation_challenges.push(evaluation_challenge); + ys.push(y); + } + + // Return tuple of: + // 1. Vector of evaluation points (challenges) + // 2. Vector of polynomial evaluations at those points + // These will be used in the KZG proof verification process + Ok((evaluation_challenges, ys)) + } + + /// Ref: https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#verify_blob_kzg_proof_batch + pub fn verify_blob_kzg_proof_batch( + &self, + blobs: &Vec, + commitments: &Vec, + proofs: &Vec, + ) -> Result { + // First validation check: Ensure all input vectors have matching lengths + // This is critical for batch verification to work correctly + if !(commitments.len() == blobs.len() && proofs.len() == blobs.len()) { + return Err(KzgError::GenericError( + "length's of the input are not the same".to_owned(), + )); + } + + // Validate that all commitments are valid points on the G1 curve + // Using parallel iterator (par_iter) for better performance on large batches + // This prevents invalid curve attacks + if commitments.iter().any(|commitment| { + commitment == &G1Affine::identity() + || !commitment.is_on_curve() + || !commitment.is_in_correct_subgroup_assuming_on_curve() + }) { + return Err(KzgError::NotOnCurveError( + "commitment not on curve".to_owned(), + )); + } + + // Validate that all proofs are valid points on the G1 curve + // Using parallel iterator for efficiency + if proofs.iter().any(|proof| { + proof == &G1Affine::identity() + || !proof.is_on_curve() + || !proof.is_in_correct_subgroup_assuming_on_curve() + }) { + return Err(KzgError::NotOnCurveError("proof not on curve".to_owned())); + } + + // Compute evaluation challenges and evaluate polynomials at those points + // This step: + // 1. Generates random evaluation points for each blob + // 2. Evaluates each blob's polynomial at its corresponding point + let (evaluation_challenges, ys) = + Self::compute_challenges_and_evaluate_polynomial(blobs, commitments, self.srs_order)?; + + // Convert each blob to its polynomial evaluation form and get the length of number of field elements + // This length value is needed for computing the challenge + let blobs_as_field_elements_length: Vec = blobs + .iter() + .map(|blob| blob.to_polynomial_eval_form().evaluations().len() as u64) + .collect(); + + // Perform the actual batch verification using the computed values: + // - commitments: Original KZG commitments + // - evaluation_challenges: Points where polynomials are evaluated + // - ys: Values of polynomials at evaluation points + // - proofs: KZG proofs for each evaluation + // - blobs_as_field_elements_length: Length of each blob's polynomial + self.verify_kzg_proof_batch( + commitments, + &evaluation_challenges, + &ys, + proofs, + &blobs_as_field_elements_length, + ) + } + + /// Ref: https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#verify_kzg_proof_batch + /// A helper function to the `helpers::compute_powers` function. This does the below reference code from the 4844 spec. + /// Ref: `# Append all inputs to the transcript before we hash + /// for commitment, z, y, proof in zip(commitments, zs, ys, proofs): + /// data += commitment + bls_field_to_bytes(z) + bls_field_to_bytes(y) + proof`` + fn compute_r_powers( + &self, + commitments: &[G1Affine], + zs: &[Fr], + ys: &[Fr], + proofs: &[G1Affine], + blobs_as_field_elements_length: &[u64], + ) -> Result, KzgError> { + // Get the number of commitments/proofs we're processing + let n = commitments.len(); + + // Initial data length includes: + // - 24 bytes for domain separator + // - 8 bytes for number of field elements per blob + // - 8 bytes for number of commitments + let mut initial_data_length: usize = 40; + + // Calculate total input size: + // - initial_data_length (40 bytes) + // - For the number of commitments/zs/ys/proofs/blobs_as_field_elements_length (which are all the same length): + // * BYTES_PER_FIELD_ELEMENT for commitment + // * 2 * BYTES_PER_FIELD_ELEMENT for z and y values + // * BYTES_PER_FIELD_ELEMENT for proof + // * 8 bytes for blob length + let input_size = initial_data_length + + n * (BYTES_PER_FIELD_ELEMENT + + 2 * BYTES_PER_FIELD_ELEMENT + + BYTES_PER_FIELD_ELEMENT + + 8); + + // Initialize buffer for data to be hashed + let mut data_to_be_hashed: Vec = vec![0; input_size]; + + // Copy domain separator to start of buffer + // This provides domain separation for the hash function + data_to_be_hashed[0..24].copy_from_slice(RANDOM_CHALLENGE_KZG_BATCH_DOMAIN); + + // Convert number of commitments to bytes and copy to buffer + // Uses configured endianness (Big or Little) + // TODO(anupsv): To be removed and default to Big endian. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/27 + let n_bytes: [u8; 8] = match KZG_ENDIANNESS { + Endianness::Big => n.to_be_bytes(), + Endianness::Little => n.to_le_bytes(), + }; + data_to_be_hashed[32..40].copy_from_slice(&n_bytes); + + let target_slice = &mut data_to_be_hashed[24..24 + (n * 8)]; + for (chunk, &length) in target_slice + .chunks_mut(8) + .zip(blobs_as_field_elements_length) + { + chunk.copy_from_slice(&length.to_be_bytes()); + } + initial_data_length += n * 8; + + // Process each commitment, proof, and evaluation point/value + for i in 0..n { + // Serialize and copy commitment + let mut v = vec![]; + + // TODO(anupsv): Move serialization to helper function. Ref: https://github.com/Layr-Labs/rust-kzg-bn254/issues/32 + commitments[i].serialize_compressed(&mut v).map_err(|_| { + KzgError::SerializationError("Failed to serialize commitment".to_string()) + })?; + data_to_be_hashed[initial_data_length..(v.len() + initial_data_length)] + .copy_from_slice(&v[..]); + initial_data_length += BYTES_PER_FIELD_ELEMENT; + + // Convert z point to bytes and copy + let v = zs[i].into_bigint().to_bytes_be(); + data_to_be_hashed[initial_data_length..(v.len() + initial_data_length)] + .copy_from_slice(&v[..]); + initial_data_length += BYTES_PER_FIELD_ELEMENT; + + // Convert y value to bytes and copy + let v = ys[i].into_bigint().to_bytes_be(); + data_to_be_hashed[initial_data_length..(v.len() + initial_data_length)] + .copy_from_slice(&v[..]); + initial_data_length += BYTES_PER_FIELD_ELEMENT; + + // Serialize and copy proof + let mut proof_bytes = vec![]; + proofs[i] + .serialize_compressed(&mut proof_bytes) + .map_err(|_| { + KzgError::SerializationError("Failed to serialize proof".to_string()) + })?; + data_to_be_hashed[initial_data_length..(proof_bytes.len() + initial_data_length)] + .copy_from_slice(&proof_bytes[..]); + initial_data_length += BYTES_PER_FIELD_ELEMENT; + } + + // Verify we filled the entire buffer + // This ensures we didn't make any buffer overflow or underflow errors + if initial_data_length != input_size { + return Err(KzgError::InvalidInputLength); + } + + // Hash all the data to get our random challenge + let r = Self::hash_to_field_element(&data_to_be_hashed); + + // Compute powers of the random challenge: [r^0, r^1, r^2, ..., r^(n-1)] + Ok(helpers::compute_powers(&r, n)) + } + + /// Verifies multiple KZG proofs efficiently. + /// Ref: https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#verify_kzg_proof_batch + /// # Arguments + /// + /// * `commitments` - A slice of `G1Affine` commitments. + /// * `zs` - A slice of `Fr` elements representing z values. + /// * `ys` - A slice of `Fr` elements representing y values. + /// * `proofs` - A slice of `G1Affine` proofs. + /// + /// # Returns + /// + /// * `Ok(true)` if all proofs are valid. + /// * `Ok(false)` if any proof is invalid. + /// * `Err(KzgError)` if an error occurs during verification. + /// + fn verify_kzg_proof_batch( + &self, + commitments: &[G1Affine], + zs: &[Fr], + ys: &[Fr], + proofs: &[G1Affine], + blobs_as_field_elements_length: &[u64], + ) -> Result { + // Verify that all input arrays have the same length + // This is crucial for batch verification to work correctly + if !(commitments.len() == zs.len() && zs.len() == ys.len() && ys.len() == proofs.len()) { + return Err(KzgError::GenericError( + "length's of the input are not the same".to_owned(), + )); + } + + // Check that all commitments are valid points on the G1 curve + // This prevents invalid curve attacks + if !commitments + .iter() + .all(|commitment| is_on_curve_g1(&G1Projective::from(*commitment))) + { + return Err(KzgError::NotOnCurveError( + "commitment not on curve".to_owned(), + )); + } + + // Check that all proofs are valid points on the G1 curve + if !proofs + .iter() + .all(|proof| is_on_curve_g1(&G1Projective::from(*proof))) + { + return Err(KzgError::NotOnCurveError("proof".to_owned())); + } + + // Verify that the trusted setup point τ*G2 is on the G2 curve + if !helpers::is_on_curve_g2(&G2Projective::from(*self.get_g2_tau()?)) { + return Err(KzgError::NotOnCurveError("g2 tau".to_owned())); + } + + let n = commitments.len(); + + // Initialize vectors to store: + // c_minus_y: [C_i - [y_i]] (commitment minus the evaluation point encrypted) + // r_times_z: [r^i * z_i] (powers of random challenge times evaluation points) + let mut c_minus_y: Vec = Vec::with_capacity(n); + let mut r_times_z: Vec = Vec::with_capacity(n); + + // Compute powers of the random challenge: [r^0, r^1, r^2, ..., r^(n-1)] + let r_powers = + self.compute_r_powers(commitments, zs, ys, proofs, blobs_as_field_elements_length)?; + + // Compute Σ(r^i * proof_i) + let proof_lincomb = helpers::g1_lincomb(proofs, &r_powers)?; + + // For each proof i: + // 1. Compute C_i - [y_i] + // 2. Compute r^i * z_i + for i in 0..n { + // Encrypt y_i as a point on G1 + let ys_encrypted = G1Affine::generator() * ys[i]; + // Compute C_i - [y_i] and convert to affine coordinates + c_minus_y.push((commitments[i] - ys_encrypted).into_affine()); + // Compute r^i * z_i + r_times_z.push(r_powers[i] * zs[i]); + } + + // Compute: + // proof_z_lincomb = Σ(r^i * z_i * proof_i) + // c_minus_y_lincomb = Σ(r^i * (C_i - [y_i])) + let proof_z_lincomb = helpers::g1_lincomb(proofs, &r_times_z)?; + let c_minus_y_lincomb = helpers::g1_lincomb(&c_minus_y, &r_powers)?; + + // Compute right-hand side of the pairing equation + let rhs_g1 = c_minus_y_lincomb + proof_z_lincomb; + + // Verify the pairing equation: + // e(Σ(r^i * proof_i), [τ]) = e(Σ(r^i * (C_i - [y_i])) + Σ(r^i * z_i * proof_i), [1]) + let result = Self::pairings_verify( + proof_lincomb, + *self.get_g2_tau()?, + rhs_g1.into(), + G2Affine::generator(), + ); + Ok(result) + } } diff --git a/src/polynomial.rs b/src/polynomial.rs index ca6ba16..2712475 100644 --- a/src/polynomial.rs +++ b/src/polynomial.rs @@ -65,16 +65,33 @@ impl PolynomialEvalForm { self.len_underlying_blob_bytes / BYTES_PER_FIELD_ELEMENT } - pub fn get_at_index(&self, i: usize) -> Option<&Fr> { + /// Retrieves a reference to the element at the specified index. + /// + /// # Arguments + /// + /// * `i` - The index of the element to retrieve. + /// + /// # Returns + /// + /// An `Option` containing a reference to the `Fr` element if the index is within bounds, or `None` otherwise. + pub fn get_evalualtion(&self, i: usize) -> Option<&Fr> { self.evaluations.get(i) } - /// Checks if the polynomial has no elements. + /// Checks whether the polynomial has no elements. + /// + /// # Returns + /// + /// `true` if the `elements` vector is empty, `false` otherwise. pub fn is_empty(&self) -> bool { self.evaluations.is_empty() } - /// Converts all `Fr` elements in the `Polynomial` to a single byte vector. + /// Converts all `Fr` elements in the polynomial to a single big-endian byte vector. + /// + /// # Returns + /// + /// A `Vec` containing the big-endian byte representation of the polynomial elements. pub fn to_bytes_be(&self) -> Vec { helpers::to_byte_array(&self.evaluations, self.len_underlying_blob_bytes) } diff --git a/tests/error_tests.rs b/tests/error_tests.rs deleted file mode 100644 index 251c8d3..0000000 --- a/tests/error_tests.rs +++ /dev/null @@ -1,90 +0,0 @@ -#[cfg(test)] -mod tests { - use rust_kzg_bn254::errors::{KzgError, PolynomialError}; - - #[test] - fn test_polynomial_error_serialization_from_string() { - let error = PolynomialError::SerializationFromStringError; - assert_eq!(format!("{}", error), "couldn't load string to fr vector"); - } - - #[test] - fn test_polynomial_error_commit() { - let msg = String::from("test commit error"); - let error = PolynomialError::CommitError(msg.clone()); - assert_eq!(format!("{}", error), format!("Commitment error: {}", msg)); - } - - #[test] - fn test_polynomial_error_generic() { - let msg = String::from("test generic error"); - let error = PolynomialError::GenericError(msg.clone()); - assert_eq!(format!("{}", error), format!("generic error: {}", msg)); - } - - #[test] - fn test_polynomial_error_fft() { - let msg = String::from("test fft error"); - let error = PolynomialError::FFTError(msg.clone()); - assert_eq!(format!("{}", error), format!("FFT error: {}", msg)); - } - #[test] - fn test_polynomial_error_incorrect_form() { - let msg = String::from("test incorrect form error"); - let error = PolynomialError::IncorrectFormError(msg.clone()); - assert_eq!( - format!("{}", error), - format!("Incorrect form error: {}", msg) - ); - } - - #[test] - fn test_polynomial_error_equality() { - let error1 = PolynomialError::SerializationFromStringError; - let error2 = PolynomialError::SerializationFromStringError; - let error3 = PolynomialError::CommitError(String::from("error")); - assert_eq!(error1, error2); - assert_ne!(error1, error3); - } - - // KzgError tests - #[test] - fn test_kzg_error_commit() { - let msg = String::from("test commit error"); - let error = KzgError::CommitError(msg.clone()); - assert_eq!(format!("{}", error), format!("Commitment error: {}", msg)); - } - - #[test] - fn test_kzg_error_serialization() { - let msg = String::from("test serialization error"); - let error = KzgError::SerializationError(msg.clone()); - assert_eq!( - format!("{}", error), - format!("Serialization error: {}", msg) - ); - } - - #[test] - fn test_kzg_error_fft() { - let msg = String::from("test fft error"); - let error = KzgError::FftError(msg.clone()); - assert_eq!(format!("{}", error), format!("FFT error: {}", msg)); - } - - #[test] - fn test_kzg_error_generic() { - let msg = String::from("test generic error"); - let error = KzgError::GenericError(msg.clone()); - assert_eq!(format!("{}", error), format!("Generic error: {}", msg)); - } - - #[test] - fn test_kzg_error_equality() { - let error1 = KzgError::CommitError(String::from("error")); - let error2 = KzgError::CommitError(String::from("error")); - let error3 = KzgError::SerializationError(String::from("different error")); - assert_eq!(error1, error2); - assert_ne!(error1, error3); - } -} diff --git a/tests/kzg_test.rs b/tests/kzg_test.rs index bfa5b35..4850a6d 100644 --- a/tests/kzg_test.rs +++ b/tests/kzg_test.rs @@ -1,19 +1,12 @@ #[cfg(test)] mod tests { - use ark_bn254::{Fr, G1Affine, G2Affine}; + use ark_bn254::{Fq, Fr, G1Affine, G2Affine}; + use ark_ec::AffineRepr; + use ark_ff::UniformRand; use lazy_static::lazy_static; - use rust_kzg_bn254::{ - blob::Blob, - errors::KzgError, - helpers, - kzg::KZG, - polynomial::{PolynomialCoeffForm, PolynomialEvalForm}, - }; - use std::{ - env, - fs::File, - io::{BufRead, BufReader}, - }; + use rand::Rng; + use rust_kzg_bn254::{blob::Blob, errors::KzgError, kzg::KZG, polynomial::PolynomialCoeffForm}; + use std::{env, fs::File, io::BufReader}; const GETTYSBURG_ADDRESS_BYTES: &[u8] = "Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.".as_bytes(); use ark_std::{str::FromStr, One}; @@ -214,7 +207,7 @@ mod tests { let mut rng = rand::thread_rng(); let mut kzg = KZG_INSTANCE.clone(); - (0..100).for_each(|_| { + (0..1).for_each(|_| { let blob_length = rand::thread_rng().gen_range(35..50000); let random_blob: Vec = (0..blob_length) .map(|_| rng.gen_range(32..=126) as u8) @@ -230,12 +223,13 @@ mod tests { rand::thread_rng().gen_range(0..input_poly.len_underlying_blob_field_elements()); let commitment = kzg.commit_eval_form(&input_poly.clone()).unwrap(); let proof = kzg - .compute_proof_with_roots_of_unity(&input_poly, index.try_into().unwrap()) + .compute_proof_with_known_z_fr_index(&input_poly, index.try_into().unwrap()) .unwrap(); - let value_fr = input_poly.get_at_index(index).unwrap(); + let value_fr = input_poly.get_evalualtion(index).unwrap(); let z_fr = kzg.get_nth_root_of_unity(index).unwrap(); - let pairing_result = - kzg.verify_proof(commitment, proof, value_fr.clone(), z_fr.clone()); + let pairing_result = kzg + .verify_proof(commitment, proof, value_fr.clone(), z_fr.clone()) + .unwrap(); assert_eq!(pairing_result, true); // take random index, not the same index and check @@ -249,7 +243,8 @@ mod tests { ) .unwrap() .clone() - ), + ) + .unwrap(), false ) }) @@ -279,188 +274,30 @@ mod tests { } let commitment = kzg.commit_eval_form(&input_poly).unwrap(); let proof = kzg - .compute_proof_with_roots_of_unity(&input_poly, index.try_into().unwrap()) + .compute_proof_with_known_z_fr_index(&input_poly, index.try_into().unwrap()) .unwrap(); - let value_fr = input_poly.get_at_index(index).unwrap(); + + let value_fr = input_poly.get_evalualtion(index).unwrap(); let z_fr = kzg.get_nth_root_of_unity(index).unwrap(); - let pairing_result = - kzg.verify_proof(commitment, proof, value_fr.clone(), z_fr.clone()); + let pairing_result = kzg + .verify_proof(commitment, proof, value_fr.clone(), z_fr.clone()) + .unwrap(); + assert_eq!(pairing_result, true); + assert_eq!( kzg.verify_proof( commitment, proof, value_fr.clone(), kzg.get_nth_root_of_unity(rand_index).unwrap().clone() - ), + ) + .unwrap(), false ) } } - #[test] - fn test_compute_kzg_proof_output_from_da() { - use ark_bn254::Fq; - use rust_kzg_bn254::helpers::str_vec_to_fr_vec; - let mut kzg = KZG_3000.clone(); - - let padded_input_fr_elements_raw: Vec<&str> = vec![ - "124448554745810004944228143885327110275920855486363883336842102793103679599", - "207508779162842735480548510602597324319082308236775252882533101718680401000", - "186313515821661738828935773908502628014528503825682615305243860329822383982", - "175617779057046250607386263835676382877324402797999043923860409846702634085", - "176908701417764592253495595071883691502347870932091779502876015283829219437", - "179211618621408803906861370832182601073979563282871012483254698763530297714", - "178675144007207845453916698249955375488211072406922195772122332854753522220", - "57342443762551981711519063259175130140327164323119403383994481075796320367", - "201644048016840536514201229857164309383055459782299704545143570201060467744", - "203954379585240811567952376700119386006707415102080467720847989508363595296", - "154413643997390308462567944070940706665567667980552003158571865495684605545", - "179199641558557109502508265885652506531258925160729980997532492238197956724", - "196343586746013098463529914279508021337660652896452822254975184458999686761", - "179199642789798378766954615916637942576983085081216829572950655633119846502", - "196907698251416180188206806476118527217227835524517227212890708462578723945", - "209188135065833850053292603115533125810196283005470024563599194921554962806", - "178769904328431539945589819940519599680679301078162293895893458713281916516", - "57315186833570416806491652511576227840442154124102492634747207086848439086", - "56997787879934999878051099065093180857197870434076438449626313283955024238", - "195122401735223296672399273363582347617293258088862337245338589498286891890", - "172187514667817006797016147089450681237387563021330251172649930984059510887", - "202189825168553442339042346633289285996072565593325159962613855263274328430", - "176908269032208360895799213956941641962632779042122566173195460097279025526", - "178675090195535348079425008943654955291233237035453597549103224288057848352", - "198655969672698814635678440561840379961683740854293905470589343214280253524", - "184450046414280497382771444868504084637083498078940578643710020946530103840", - "191588553295206552672446505441400871035933706577055546498217912677470201132", - "57218643758213157866498392310103913473502406903700483504908744830152351860", - "184452436682824846772926756876560010960143362270644037512475344570444965152", - "191547358739393032699638562397393592082434780603568324919651475504456033636", - "57259622694790292569095949658502840145070150663520147255610723074247260008", - "186205021942396728157785116391788484694464475366678317619183801399752597620", - "184562702865503477544474983818908595115462442551772541350836446300829130857", - "203411352029711233470829194006802304117968683302211457541840894875429856361", - "175590466840243348133688030338994426426205333357416292443952411731112324713", - "195064930079953233979471617089854997241218347662186974737524940518540404000", - "184521165912303293767845148683223315441296689539961647976806104757436769312", - "177384975870124439001759657886337745043336278262654552223156680275429714275", - "183976088968084624324785031346616746677350639582380167858351783587217173536", - "193286033715924828384520581373366850088713852669139898226901243602529493096", - "179241078993710153255069385145856351420066197647806384293982409561076998244", - "179123722350391539550068374677188552845397193776842784699159030602666174830", - "400194862503576342918173310331854693478403117005444701857659884415883371564", - "57335620997137264681921969532598204329752055368260135437058948058890528101", - "177453743603580340760143914089201876349834419692598030679062113821757040741", - "57314836354274911098352906734004791591005704793885798411715484369110198373", - "57314836354274911098359242714508940270452740705366016780345068008093216032", - "205674767500671097980546524606502860210905462284178340164141948154901692416", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - ]; - - let roots_of_unities_raw: Vec<&str> = vec![ - "1", - "9088801421649573101014283686030284801466796108869023335878462724291607593530", - "4419234939496763621076330863786513495701855246241724391626358375488475697872", - "10685529837057339195284478417809549783849082573663680590416001084635768485990", - "14940766826517323942636479241147756311199852622225275649687664389641784935947", - "1267043552012899406804021742775506022406234352662757946107381425245432910045", - "8353089677377103612376907029239831201621163137110616679113215703556701300027", - "2441140650056668192559128307232955355464329046106249160729745552573818873507", - "19540430494807482326159819597004422086093766032135589407132600596362845576832", - "7638532900060318363441136974856672991261030096006837046428044865340598824945", - "21593175090660679728966189540082956087710442206243643852421469785983375007422", - "1938211124727238839182731185938102527032692606309510708934917132548164554613", - "7453743110195651009871841175551411207906567694170420694440975759997908783171", - "18272962628503604941710624384101461447671738503426463821117705461905178580283", - "398060900184764123111996659293386330445164342166284510961681463198684035472", - "2283482550034800628111070180390673268453179470922704452226293886212258993410", - "21888242871839275217838484774961031246007050428528088939761107053157389710902", - "20789857765414837569378861847135321604271811148012132377696013003867187003108", - "15480425210935858833842661136375613442295926160997485829640439761218028937032", - "18528082246067560296180016805056907225377865863446968862116791721065802134110", - "15634706786522089014999940912207647497621112715300598509090847765194894752723", - "10638720336917081690638245448031473930540403837643333986712680212230728663233", - "9222527969605388450625148037496647087331675164191659244434925070698893435503", - "1517838647035931137528481530777492051607999820652391703425676009405898040794", - "13274704216607947843011480449124596415239537050559949017414504948711435969894", - "8682033663657132234291766569813810281833069931144526641976190784581352362959", - "10550721784764313104495045260998680866741519845912303749987955721122349694799", - "10234189842755395200346026196803257362626336236511351459013434557394886321135", - "20580681596408674675161806693190042586237586932987042748222592033583012763427", - "21262384822466439274137541430102393376441243110026393623692977826997277779276", - "4183653929190742691274098379026487729755080010366834215927449156672627370084", - "4658854783519236281304787251426829785380272013053939496434657852755686889074", - "-1", - "12799441450189702121232122059226990287081568291547011007819741462284200902087", - "17469007932342511601170074881470761592846509154174309952071845811087332797745", - "11202713034781936026961927327447725304699281826752353753282203101940040009627", - "6947476045321951279609926504109518777348511778190758694010539796934023559670", - "20621199319826375815442384002481769066142130047753276397590822761330375585572", - "13535153194462171609869498716017443886927201263305417664584988483019107195590", - "19447102221782607029687277438024319733084035354309785182968458634001989622110", - "2347812377031792896086586148252853002454598368280444936565603590212962918785", - "14249709971778956858805268770400602097287334304409197297270159321235209670672", - "295067781178595493280216205174319000837922194172390491276734400592433488195", - "19950031747112036383063674559319172561515671794106523634763287054027643941004", - "14434499761643624212374564569705863880641796706245613649257228426577899712446", - "3615280243335670280535781361155813640876625896989570522580498724670629915334", - "21490181971654511099134409085963888758103200058249749832736522723377124460145", - "19604760321804474594135335564866601820095184929493329891471910300363549502207", - "4407920970296243842541313971887945403937097133418418784715", - "1098385106424437652867543898121953484276553252403901966002191182708621492509", - "6407817660903416388403744608881661646252438239418548514057764425357779558585", - "3360160625771714926066388940200367863170498536969065481581412465510006361507", - "6253536085317186207246464833049627590927251685115435834607356421380913742894", - "11249522534922193531608160297225801158007960562772700356985523974345079832384", - "12665714902233886771621257707760628001216689236224375099263279115876915060114", - "20370404224803344084717924214479783036940364579763642640272528177169910454823", - "8613538655231327379234925296132678673308827349856085326283699237864372525723", - "13206209208182142987954639175443464806715294469271507701722013401994456132658", - "11337521087074962117751360484258594221806844554503730593710248465453458800818", - "11654053029083880021900379548454017725922028163904682884684769629180922174482", - "1307561275430600547084599052067232502310777467428991595475612152992795732190", - "625858049372835948108864315154881712107121290389640720005226359578530716341", - "17704588942648532530972307366230787358793284390049200127770755029903181125533", - "17229388088320038940941618493830445303168092387362094847263546333820121606543", - ]; - - let roots_of_unities: Vec = str_vec_to_fr_vec(roots_of_unities_raw).unwrap(); - let padded_input_fr_elements: Vec = - str_vec_to_fr_vec(padded_input_fr_elements_raw).unwrap(); - - let file2 = File::open("tests/test-files/kzg.proof.eq.input").unwrap(); - let reader2 = BufReader::new(file2); - - for line in reader2.lines() { - let line = line.unwrap(); - let trimmed_line = line.trim_end(); // Trim whitespace from the end - let the_strings_str: Vec<&str> = trimmed_line.split(',').collect(); // Split the line on commas - let index = u64::from_str(the_strings_str[0]).unwrap(); - let hard_coded_x = Fq::from_str(the_strings_str[1]).expect("should be fine"); - let hard_coded_y = Fq::from_str(the_strings_str[2]).expect("should be fine"); - let gnark_proof = G1Affine::new(hard_coded_x, hard_coded_y); - let poly = PolynomialEvalForm::new(padded_input_fr_elements.to_vec()); - kzg.data_setup_custom(4, poly.len().try_into().unwrap()) - .unwrap(); - let result = kzg.compute_proof(&poly, index, &roots_of_unities).unwrap(); - assert_eq!(gnark_proof, result) - } - } - #[test] fn test_g1_ifft() { use ark_bn254::Fq; @@ -547,162 +384,250 @@ mod tests { } #[test] - fn test_compute_quotient_eval_on_domain() { - let z_fr = Fr::from_str( - "18272962628503604941710624384101461447671738503426463821117705461905178580283", - ) - .expect("yes"); - let value_fr = Fr::from_str( - "179199642789798378766954615916637942576983085081216829572950655633119846502", - ) - .expect("yes"); - let eval_raw: Vec<&str> = vec![ - "124448554745810004944228143885327110275920855486363883336842102793103679599", - "207508779162842735480548510602597324319082308236775252882533101718680401000", - "186313515821661738828935773908502628014528503825682615305243860329822383982", - "175617779057046250607386263835676382877324402797999043923860409846702634085", - "176908701417764592253495595071883691502347870932091779502876015283829219437", - "179211618621408803906861370832182601073979563282871012483254698763530297714", - "178675144007207845453916698249955375488211072406922195772122332854753522220", - "57342443762551981711519063259175130140327164323119403383994481075796320367", - "201644048016840536514201229857164309383055459782299704545143570201060467744", - "203954379585240811567952376700119386006707415102080467720847989508363595296", - "154413643997390308462567944070940706665567667980552003158571865495684605545", - "179199641558557109502508265885652506531258925160729980997532492238197956724", - "196343586746013098463529914279508021337660652896452822254975184458999686761", - "179199642789798378766954615916637942576983085081216829572950655633119846502", - "196907698251416180188206806476118527217227835524517227212890708462578723945", - "209188135065833850053292603115533125810196283005470024563599194921554962806", - "178769904328431539945589819940519599680679301078162293895893458713281916516", - "57315186833570416806491652511576227840442154124102492634747207086848439086", - "56997787879934999878051099065093180857197870434076438449626313283955024238", - "195122401735223296672399273363582347617293258088862337245338589498286891890", - "172187514667817006797016147089450681237387563021330251172649930984059510887", - "202189825168553442339042346633289285996072565593325159962613855263274328430", - "176908269032208360895799213956941641962632779042122566173195460097279025526", - "178675090195535348079425008943654955291233237035453597549103224288057848352", - "198655969672698814635678440561840379961683740854293905470589343214280253524", - "184450046414280497382771444868504084637083498078940578643710020946530103840", - "191588553295206552672446505441400871035933706577055546498217912677470201132", - "57218643758213157866498392310103913473502406903700483504908744830152351860", - "184452436682824846772926756876560010960143362270644037512475344570444965152", - "191547358739393032699638562397393592082434780603568324919651475504456033636", - "57259622694790292569095949658502840145070150663520147255610723074247260008", - "186205021942396728157785116391788484694464475366678317619183801399752597620", - "184562702865503477544474983818908595115462442551772541350836446300829130857", - "203411352029711233470829194006802304117968683302211457541840894875429856361", - "175590466840243348133688030338994426426205333357416292443952411731112324713", - "195064930079953233979471617089854997241218347662186974737524940518540404000", - "184521165912303293767845148683223315441296689539961647976806104757436769312", - "177384975870124439001759657886337745043336278262654552223156680275429714275", - "183976088968084624324785031346616746677350639582380167858351783587217173536", - "193286033715924828384520581373366850088713852669139898226901243602529493096", - "179241078993710153255069385145856351420066197647806384293982409561076998244", - "179123722350391539550068374677188552845397193776842784699159030602666174830", - "400194862503576342918173310331854693478403117005444701857659884415883371564", - "57335620997137264681921969532598204329752055368260135437058948058890528101", - "177453743603580340760143914089201876349834419692598030679062113821757040741", - "57314836354274911098352906734004791591005704793885798411715484369110198373", - "57314836354274911098359242714508940270452740705366016780345068008093216032", - "205674767500671097980546524606502860210905462284178340164141948154901692416", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - ]; + fn test_multiple_proof_random_100_blobs() { + let mut rng = rand::thread_rng(); + let mut kzg = KZG_INSTANCE.clone(); - let roots_of_unities_raw: Vec<&str> = vec![ - "1", - "9088801421649573101014283686030284801466796108869023335878462724291607593530", - "4419234939496763621076330863786513495701855246241724391626358375488475697872", - "10685529837057339195284478417809549783849082573663680590416001084635768485990", - "14940766826517323942636479241147756311199852622225275649687664389641784935947", - "1267043552012899406804021742775506022406234352662757946107381425245432910045", - "8353089677377103612376907029239831201621163137110616679113215703556701300027", - "2441140650056668192559128307232955355464329046106249160729745552573818873507", - "19540430494807482326159819597004422086093766032135589407132600596362845576832", - "7638532900060318363441136974856672991261030096006837046428044865340598824945", - "21593175090660679728966189540082956087710442206243643852421469785983375007422", - "1938211124727238839182731185938102527032692606309510708934917132548164554613", - "7453743110195651009871841175551411207906567694170420694440975759997908783171", - "18272962628503604941710624384101461447671738503426463821117705461905178580283", - "398060900184764123111996659293386330445164342166284510961681463198684035472", - "2283482550034800628111070180390673268453179470922704452226293886212258993410", - "21888242871839275217838484774961031246007050428528088939761107053157389710902", - "20789857765414837569378861847135321604271811148012132377696013003867187003108", - "15480425210935858833842661136375613442295926160997485829640439761218028937032", - "18528082246067560296180016805056907225377865863446968862116791721065802134110", - "15634706786522089014999940912207647497621112715300598509090847765194894752723", - "10638720336917081690638245448031473930540403837643333986712680212230728663233", - "9222527969605388450625148037496647087331675164191659244434925070698893435503", - "1517838647035931137528481530777492051607999820652391703425676009405898040794", - "13274704216607947843011480449124596415239537050559949017414504948711435969894", - "8682033663657132234291766569813810281833069931144526641976190784581352362959", - "10550721784764313104495045260998680866741519845912303749987955721122349694799", - "10234189842755395200346026196803257362626336236511351459013434557394886321135", - "20580681596408674675161806693190042586237586932987042748222592033583012763427", - "21262384822466439274137541430102393376441243110026393623692977826997277779276", - "4183653929190742691274098379026487729755080010366834215927449156672627370084", - "4658854783519236281304787251426829785380272013053939496434657852755686889074", - "-1", - "12799441450189702121232122059226990287081568291547011007819741462284200902087", - "17469007932342511601170074881470761592846509154174309952071845811087332797745", - "11202713034781936026961927327447725304699281826752353753282203101940040009627", - "6947476045321951279609926504109518777348511778190758694010539796934023559670", - "20621199319826375815442384002481769066142130047753276397590822761330375585572", - "13535153194462171609869498716017443886927201263305417664584988483019107195590", - "19447102221782607029687277438024319733084035354309785182968458634001989622110", - "2347812377031792896086586148252853002454598368280444936565603590212962918785", - "14249709971778956858805268770400602097287334304409197297270159321235209670672", - "295067781178595493280216205174319000837922194172390491276734400592433488195", - "19950031747112036383063674559319172561515671794106523634763287054027643941004", - "14434499761643624212374564569705863880641796706245613649257228426577899712446", - "3615280243335670280535781361155813640876625896989570522580498724670629915334", - "21490181971654511099134409085963888758103200058249749832736522723377124460145", - "19604760321804474594135335564866601820095184929493329891471910300363549502207", - "4407920970296243842541313971887945403937097133418418784715", - "1098385106424437652867543898121953484276553252403901966002191182708621492509", - "6407817660903416388403744608881661646252438239418548514057764425357779558585", - "3360160625771714926066388940200367863170498536969065481581412465510006361507", - "6253536085317186207246464833049627590927251685115435834607356421380913742894", - "11249522534922193531608160297225801158007960562772700356985523974345079832384", - "12665714902233886771621257707760628001216689236224375099263279115876915060114", - "20370404224803344084717924214479783036940364579763642640272528177169910454823", - "8613538655231327379234925296132678673308827349856085326283699237864372525723", - "13206209208182142987954639175443464806715294469271507701722013401994456132658", - "11337521087074962117751360484258594221806844554503730593710248465453458800818", - "11654053029083880021900379548454017725922028163904682884684769629180922174482", - "1307561275430600547084599052067232502310777467428991595475612152992795732190", - "625858049372835948108864315154881712107121290389640720005226359578530716341", - "17704588942648532530972307366230787358793284390049200127770755029903181125533", - "17229388088320038940941618493830445303168092387362094847263546333820121606543", + let mut blobs: Vec = Vec::new(); + let mut commitments: Vec = Vec::new(); + let mut proofs: Vec = Vec::new(); + + (0..1).for_each(|_| { + let blob_length = rand::thread_rng().gen_range(35..50000); + let random_blob: Vec = (0..blob_length) + .map(|_| rng.gen_range(32..=126) as u8) + .collect(); + + let input = Blob::from_raw_data(&random_blob); + let input_poly = input.to_polynomial_eval_form(); + kzg.data_setup_custom(1, input.len().try_into().unwrap()) + .unwrap(); + + let commitment = kzg.commit_eval_form(&input_poly).unwrap(); + let proof = kzg.compute_blob_proof(&input, &commitment).unwrap(); + + blobs.push(input); + commitments.push(commitment); + proofs.push(proof); + }); + + let mut bad_blobs = blobs.clone(); + let mut bad_commitments = commitments.clone(); + let mut bad_proofs = proofs.clone(); + + let pairing_result = kzg + .verify_blob_kzg_proof_batch(&blobs, &commitments, &proofs) + .unwrap(); + assert_eq!(pairing_result, true); + + bad_blobs.pop(); + bad_blobs.push(Blob::from_raw_data(b"random")); + let pairing_result_bad_blobs = kzg + .verify_blob_kzg_proof_batch(&bad_blobs, &commitments, &proofs) + .unwrap(); + assert_eq!(pairing_result_bad_blobs, false); + + bad_commitments.pop(); + bad_commitments.push(G1Affine::rand(&mut rng)); + let pairing_result_bad_commitments = kzg + .verify_blob_kzg_proof_batch(&blobs, &bad_commitments, &proofs) + .unwrap(); + assert_eq!(pairing_result_bad_commitments, false); + + bad_proofs.pop(); + bad_proofs.push(G1Affine::rand(&mut rng)); + let pairing_result_bad_proofs = kzg + .verify_blob_kzg_proof_batch(&blobs, &commitments, &bad_proofs) + .unwrap(); + assert_eq!(pairing_result_bad_proofs, false); + + let pairing_result_everything_bad = kzg + .verify_blob_kzg_proof_batch(&bad_blobs, &bad_commitments, &bad_proofs) + .unwrap(); + assert_eq!(pairing_result_everything_bad, false); + } + + #[test] + fn test_compute_multiple_kzg_proof() { + let mut kzg = KZG_INSTANCE.clone(); + let mut kzg2 = KZG_INSTANCE.clone(); + + let input1 = Blob::from_raw_data(GETTYSBURG_ADDRESS_BYTES); + kzg.data_setup_custom(4, input1.len().try_into().unwrap()) + .unwrap(); + + let input_poly1 = input1.to_polynomial_eval_form(); + + let commitment1 = kzg.commit_eval_form(&input_poly1.clone()).unwrap(); + let proof_1 = kzg.compute_blob_proof(&input1, &commitment1).unwrap(); + + let mut reversed_input: Vec = vec![0; GETTYSBURG_ADDRESS_BYTES.len()]; + reversed_input.clone_from_slice(GETTYSBURG_ADDRESS_BYTES); + reversed_input.reverse(); + + let input2 = Blob::from_raw_data( + b"17704588942648532530972307366230787358793284390049200127770755029903181125533", + ); + kzg2.calculate_roots_of_unity(input2.len().try_into().unwrap()) + .unwrap(); + let input_poly2 = input2.to_polynomial_eval_form(); + + let commitment2 = kzg2.commit_eval_form(&input_poly2).unwrap(); + + let proof_2 = kzg2.compute_blob_proof(&input2, &commitment2).unwrap(); + + let blobs = vec![input1, input2]; + let commitments = vec![commitment1, commitment2]; + let proofs = vec![proof_1, proof_2]; + // let res = kzg.verify_blob_kzg_proof(&input1, &commitment1, &auto_proof).unwrap(); + + let pairing_result = kzg + .verify_blob_kzg_proof_batch(&blobs, &commitments, &proofs) + .unwrap(); + + assert_eq!(pairing_result, true); + } + + #[test] + fn test_kzg_batch_proof_with_infinity() { + let mut kzg = KZG_INSTANCE.clone(); + + // Setup with consistent domain size + let input_size = GETTYSBURG_ADDRESS_BYTES.len(); + kzg.data_setup_custom(4, input_size.try_into().unwrap()) + .unwrap(); + + // First blob and proof - regular case + let input1 = Blob::from_raw_data(GETTYSBURG_ADDRESS_BYTES); + let input_poly1 = input1.to_polynomial_eval_form(); + let commitment1 = kzg.commit_eval_form(&input_poly1).unwrap(); + let proof_1 = kzg.compute_blob_proof(&input1, &commitment1).unwrap(); + + // Create a proof point at infinity + let proof_at_infinity = G1Affine::identity(); + + let blobs = vec![input1.clone()]; + let commitments = vec![commitment1]; + let proofs = vec![proof_at_infinity]; + + // This should fail since a proof point at infinity is invalid + let result = kzg.verify_blob_kzg_proof_batch(&blobs, &commitments, &proofs); + + assert!(result.is_err()); + + // Also test mixed case - one valid proof, one at infinity + let input2 = Blob::from_raw_data(b"second input"); + let input_poly2 = input2.to_polynomial_eval_form(); + let commitment2 = kzg.commit_eval_form(&input_poly2).unwrap(); + + let blobs_mixed = vec![input1, input2]; + let commitments_mixed = vec![commitment1, commitment2]; + let proofs_mixed = vec![proof_1, proof_at_infinity]; + + let result_mixed = + kzg.verify_blob_kzg_proof_batch(&blobs_mixed, &commitments_mixed, &proofs_mixed); + assert!(result_mixed.is_err()); + } + + #[test] + fn test_kzg_batch_proof_invalid_curve_points() { + let mut kzg = KZG_INSTANCE.clone(); + kzg.data_setup_custom(4, GETTYSBURG_ADDRESS_BYTES.len().try_into().unwrap()) + .unwrap(); + + // Create valid inputs first + let input = Blob::from_raw_data(GETTYSBURG_ADDRESS_BYTES); + let input_poly = input.to_polynomial_eval_form(); + let valid_commitment = kzg.commit_eval_form(&input_poly).unwrap(); + let valid_proof = kzg.compute_blob_proof(&input, &valid_commitment).unwrap(); + + // Create points not on the curve + let invalid_point_commitment = generate_point_wrong_subgroup(); + + let invalid_point_proof = generate_point_wrong_subgroup(); + let invalid_proof_from_valid_proof_plus_1 = G1Affine::new_unchecked( + valid_proof.x().unwrap(), + valid_proof.y().unwrap() + Fq::one(), + ); // This is not a valid proof + + // Test cases with different combinations + let test_cases = vec![ + ( + vec![invalid_point_commitment.clone(), valid_commitment], + vec![valid_proof.clone(), valid_proof.clone()], + "Invalid commitment point", + ), + ( + vec![valid_commitment, valid_commitment], + vec![invalid_point_proof.clone(), valid_proof.clone()], + "Invalid proof point", + ), + ( + vec![invalid_point_commitment.clone(), valid_commitment], + vec![invalid_point_proof.clone(), valid_proof.clone()], + "Both invalid commitment and proof", + ), + ( + vec![valid_commitment, invalid_point_commitment], + vec![valid_proof.clone(), invalid_point_proof], + "Invalid points in second position", + ), + ( + vec![valid_commitment, invalid_point_commitment], + vec![valid_proof.clone(), invalid_proof_from_valid_proof_plus_1], + "Invalid proof from valid proof", + ), + ( + vec![invalid_point_commitment, invalid_point_commitment], + vec![invalid_point_proof, invalid_proof_from_valid_proof_plus_1], + "all invalid commitments and proofs", + ), ]; - let mut eval_fr: Vec = vec![]; - let roots_of_unities: Vec = helpers::str_vec_to_fr_vec(roots_of_unities_raw).unwrap(); - for i in 0..eval_raw.len() { - eval_fr.push(Fr::from_str(eval_raw[i]).expect("yes")); + for (commitments, proofs, case_description) in test_cases { + let blobs = vec![input.clone(), input.clone()]; + let result = kzg.verify_blob_kzg_proof_batch(&blobs, &commitments, &proofs); + + assert!( + result.is_err(), + "Failed to detect invalid curve point - {}", + case_description + ); } + } - let result = - KZG_3000.compute_quotient_eval_on_domain(z_fr, &eval_fr, value_fr, &roots_of_unities); - let confirmed_result = Fr::from_str( - "20008798420615294489302706738008175134837093401197634135729610787152508035605", - ) - .expect("yes"); + #[test] + fn test_evaluate_polynomial_in_evaluation_form_random_blob_all_indexes() { + let mut rng = rand::thread_rng(); + let mut kzg = KZG_INSTANCE.clone(); + let blob_length: u64 = rand::thread_rng().gen_range(35..40000); + let random_blob: Vec = (0..blob_length) + .map(|_| rng.gen_range(32..=126) as u8) + .collect(); + + let input = Blob::from_raw_data(&random_blob); + let input_poly = input.to_polynomial_eval_form(); + + for i in 0..input_poly.len_underlying_blob_field_elements() { + kzg.calculate_roots_of_unity(input.len().try_into().unwrap()) + .unwrap(); + let z_fr = kzg.get_nth_root_of_unity(i).unwrap(); + let claimed_y_fr = + KZG::evaluate_polynomial_in_evaluation_form(&input_poly, z_fr, 3000).unwrap(); + assert_eq!(claimed_y_fr, input_poly.evaluations()[i]); + } + } - assert_eq!(confirmed_result, result); + // Helper function to generate a point in the wrong subgroup + fn generate_point_wrong_subgroup() -> G1Affine { + let x = Fq::from_str( + "17704588942648532530972307366230787358793284390049200127770755029903181125533", + ) + .unwrap(); + let y = Fq::from_str( + "17704588942648532530972307366230787358793284390049200127770755029903181125533", + ) + .unwrap(); + G1Affine::new_unchecked(x, y) } }