diff --git a/src/blob.rs b/src/blob.rs index beb613c..553e8e8 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -1,7 +1,7 @@ use crate::{ errors::BlobError, helpers, - polynomial::{Polynomial, PolynomialFormat}, + polynomial::{PolynomialCoefForm, PolynomialEvalForm}, }; /// A blob which is Eigen DA spec aligned. @@ -56,12 +56,16 @@ impl Blob { self.blob_data.is_empty() } - /// Converts the blob data to a `Polynomial` if the data is padded. - pub fn to_polynomial(&self, form: PolynomialFormat) -> Result { + /// Convert the blob data to a `PolynomialEvalForm`. + pub fn to_polynomial_eval_form(&self) -> PolynomialEvalForm { let fr_vec = helpers::to_fr_array(&self.blob_data); - let poly = Polynomial::new(&fr_vec, self.len(), form) - .map_err(|err| BlobError::GenericError(err.to_string()))?; - Ok(poly) + PolynomialEvalForm::new(fr_vec) + } + + /// Convert the blob data to a `PolynomialCoefForm`. + pub fn to_polynomial_coeff_form(&self) -> PolynomialCoefForm { + let fr_vec = helpers::to_fr_array(&self.blob_data); + PolynomialCoefForm::new(fr_vec) } } diff --git a/src/kzg.rs b/src/kzg.rs index 22cc649..932ff39 100644 --- a/src/kzg.rs +++ b/src/kzg.rs @@ -3,7 +3,7 @@ use crate::{ consts::BYTES_PER_FIELD_ELEMENT, errors::KzgError, helpers, - polynomial::{Polynomial, PolynomialFormat}, + polynomial::{PolynomialCoefForm, PolynomialEvalForm}, traits::ReadPointFromBytes, }; use ark_bn254::{g1::G1Affine, Bn254, Fr, G1Projective, G2Affine}; @@ -462,55 +462,58 @@ impl Kzg { } /// commit the actual polynomial with the values setup - pub fn commit(&self, polynomial: &Polynomial) -> Result { + pub fn commit_eval_form(&self, polynomial: &PolynomialEvalForm) -> Result { if polynomial.len() > self.g1.len() { return Err(KzgError::SerializationError( "polynomial length is not correct".to_string(), )); } - let bases = match polynomial.get_form() { - PolynomialFormat::InEvaluationForm => { - // If the polynomial is in evaluation form, use the original g1 points - self.g1[..polynomial.len()].to_vec() - }, - PolynomialFormat::InCoefficientForm => { - // If the polynomial is in coefficient form, use inverse FFT - self.g1_ifft(polynomial.len())? - }, - }; + // When the polynomial is in evaluation form, use the original g1 points + let bases = self.g1[..polynomial.len()].to_vec(); - match G1Projective::msm(&bases, &polynomial.to_vec()) { + match G1Projective::msm(&bases, polynomial.evaluations()) { Ok(res) => Ok(res.into_affine()), Err(err) => Err(KzgError::CommitError(err.to_string())), } } - pub fn blob_to_kzg_commitment( - &self, - blob: &Blob, - form: PolynomialFormat, - ) -> Result { - let polynomial = blob - .to_polynomial(form) - .map_err(|err| KzgError::SerializationError(err.to_string()))?; - let commitment = self.commit(&polynomial)?; - Ok(commitment) + /// commit the actual polynomial with the values setup + pub fn commit_coef_form(&self, polynomial: &PolynomialCoefForm) -> Result { + if polynomial.len() > self.g1.len() { + return Err(KzgError::SerializationError( + "polynomial length is not correct".to_string(), + )); + } + // When the polynomial is in coefficient form, use inverse FFT on the srs points. + // TODO: is this correct? See https://github.com/Layr-Labs/rust-kzg-bn254/issues/20 + let bases = self.g1_ifft(polynomial.len())?; + + match G1Projective::msm(&bases, polynomial.coeffs()) { + Ok(res) => Ok(res.into_affine()), + Err(err) => Err(KzgError::CommitError(err.to_string())), + } + } + + pub fn blob_to_kzg_commitment(&self, blob: &Blob) -> Result { + let polynomial = blob.to_polynomial_eval_form(); + self.commit_eval_form(&polynomial) } /// helper function to work with the library and the env of the kzg instance pub fn compute_kzg_proof_with_roots_of_unity( &self, - polynomial: &Polynomial, + polynomial: &PolynomialEvalForm, index: u64, ) -> Result { self.compute_kzg_proof(polynomial, index, &self.expanded_roots_of_unity) } /// function to compute the kzg proof given the values. + /// TODO: do we want a separate function for polynomials in evaluation form? pub fn compute_kzg_proof( &self, - polynomial: &Polynomial, + polynomial: &PolynomialEvalForm, index: u64, root_of_unities: &[Fr], ) -> Result { @@ -526,7 +529,7 @@ impl Kzg { )); } - let eval_fr = polynomial.to_vec(); + let eval_fr = polynomial.evaluations(); let mut poly_shift: Vec = Vec::with_capacity(eval_fr.len()); let usized_index = if let Some(x) = index.to_usize() { x @@ -539,7 +542,7 @@ impl Kzg { let value_fr = eval_fr[usized_index]; let z_fr = root_of_unities[usized_index]; - for fr in &eval_fr { + for fr in eval_fr { poly_shift.push(*fr - value_fr); } @@ -563,16 +566,8 @@ impl Kzg { } } - let bases = match polynomial.get_form() { - PolynomialFormat::InEvaluationForm => { - // If the polynomial is in evaluation form, use the original g1 points - self.g1[..polynomial.len()].to_vec() - }, - PolynomialFormat::InCoefficientForm => { - // If the polynomial is in coefficient form, use inverse FFT - self.g1_ifft(polynomial.len())? - }, - }; + // Polynomial is in evaluation form, so use the original g1 points + let bases = self.g1[..polynomial.len()].to_vec(); match G1Projective::msm(&bases, "ient_poly) { Ok(res) => Ok(G1Affine::from(res)), diff --git a/src/polynomial.rs b/src/polynomial.rs index 0595f3a..8c0b532 100644 --- a/src/polynomial.rs +++ b/src/polynomial.rs @@ -1,137 +1,143 @@ -use crate::{errors::PolynomialError, helpers}; +use crate::{consts::BYTES_PER_FIELD_ELEMENT, errors::PolynomialError, helpers}; use ark_bn254::Fr; use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; use ark_std::Zero; -#[derive(Clone, Debug, PartialEq, Copy)] -pub enum PolynomialFormat { - InCoefficientForm, - InEvaluationForm, -} - #[derive(Clone, Debug, PartialEq)] -pub struct Polynomial { - elements: Vec, - // TODO: Remove this field, its just a duplicate of length_of_padded_blob_as_fr_vector. - // One can easily convert between them by *4 or /4. Also it should be calculated and not passed in, - // which is error prone (user might think length is in field elements). - length_of_padded_blob: usize, - length_of_padded_blob_as_fr_vector: usize, - form: PolynomialFormat, +pub struct PolynomialCoefForm { + /// coeffs contains the coefficients of the polynomial, padded with 0s to the next power of two. + /// Hence if the polynomial is created with coefficients [1, 2, 3], the internal representation + /// will be [1, 2, 3, 0]. + coeffs: Vec, + /// Number of bytes in the underlying blob, which was used to create the polynomial. + /// This is passed as is when converting between Coefficient and Evaluation forms, + /// so that the blob can be reconstructed with the same length. + /// + /// TODO: We should get rid of this: polynomial should not know about the blob. + /// This len is equivalent to the coeffs len before it gets padded. + /// Perhaps we can store the original coeffs and only pad when needed? + len_underlying_blob_bytes: usize, } -impl Polynomial { - /// Constructs a new `Polynomial` with a given vector of `Fr` elements. - pub fn new( - elements: &[Fr], - length_of_padded_blob: usize, - form: PolynomialFormat, - ) -> Result { - if elements.is_empty() { - return Err(PolynomialError::GenericError( - "elements are empty".to_string(), - )); - } - let mut padded_input_fr = vec![]; - for i in 0..elements.len().next_power_of_two() { - if i < elements.len() { - padded_input_fr.push(elements[i]); - } else { - padded_input_fr.push(Fr::zero()); - } +impl PolynomialCoefForm { + pub fn new(coeffs: Vec) -> Self { + let underlying_blob_len_in_bytes = coeffs.len() * BYTES_PER_FIELD_ELEMENT; + let next_power_of_two = coeffs.len().next_power_of_two(); + let mut padded_coeffs = coeffs; + padded_coeffs.resize(next_power_of_two, Fr::zero()); + + Self { + coeffs: padded_coeffs, + len_underlying_blob_bytes: underlying_blob_len_in_bytes, } - Ok(Polynomial { - elements: padded_input_fr, - length_of_padded_blob, - length_of_padded_blob_as_fr_vector: elements.len(), - form, - }) } - pub fn get_length_of_padded_blob_as_fr_vector(&self) -> usize { - self.length_of_padded_blob_as_fr_vector + pub fn coeffs(&self) -> &[Fr] { + &self.coeffs } - /// Returns the form of the polynomial. - pub fn get_form(&self) -> PolynomialFormat { - self.form + /// Returns the number of coefficients in the polynomial. Note that this returns the number of + /// coefficients in the padded polynomial, not the number of coefficients in the original + /// polynomial. + pub fn len(&self) -> usize { + self.coeffs.len() } - /// Returns the number of elements in the polynomial. - pub fn len(&self) -> usize { - self.elements.len() + /// TODO: we should deprecate this. See comment in the struct. + pub fn len_underlying_blob_bytes(&self) -> usize { + self.len_underlying_blob_bytes } pub fn get_at_index(&self, i: usize) -> Option<&Fr> { - self.elements.get(i) + self.coeffs.get(i) } /// Checks if the polynomial has no elements. pub fn is_empty(&self) -> bool { - self.elements.is_empty() + self.coeffs.is_empty() } /// Converts all `Fr` elements in the `Polynomial` to a single byte vector. pub fn to_bytes_be(&self) -> Vec { - helpers::to_byte_array(&self.elements, self.length_of_padded_blob) + helpers::to_byte_array(&self.coeffs, self.len_underlying_blob_bytes) } - /// Returns a clone of the elements as a `Vec`. - pub fn to_vec(&self) -> Vec { - self.elements.clone() + pub fn to_eval_form(&self) -> Result { + let evals = GeneralEvaluationDomain::::new(self.len()) + .ok_or(PolynomialError::FFTError( + "Failed to construct domain for FFT".to_string(), + ))? + .fft(&self.coeffs); + Ok(PolynomialEvalForm::new(evals)) } +} - /// Helper function to transform the polynomial to the given form. - pub fn transform_to_form(&mut self, form: PolynomialFormat) -> Result<(), PolynomialError> { - if self.form == form { - return Err(PolynomialError::IncorrectFormError( - "Polynomial is already in the given form".to_string(), - )); - } +#[derive(Clone, Debug, PartialEq)] +pub struct PolynomialEvalForm { + /// evaluations contains the evaluations of the polynomial, padded with 0s to the next power of two. + /// Hence if the polynomial is created with coefficients [1, 2, 3], the internal representation + /// will be [1, 2, 3, 0]. + evaluations: Vec, + /// Number of bytes in the underlying blob, which was used to create the polynomial. + /// This is passed as is when converting between Coefficient and Evaluation forms, + /// so that the blob can be reconstructed with the same length. + /// + /// TODO: We should get rid of this: polynomial should not know about the blob. + /// This len is equivalent to the coeffs len before it gets padded. + /// Perhaps we can store the original coeffs and only pad when needed? + len_underlying_blob_bytes: usize, +} + +impl PolynomialEvalForm { + pub fn new(evals: Vec) -> Self { + let underlying_blob_len_in_bytes = evals.len() * BYTES_PER_FIELD_ELEMENT; + let next_power_of_two = evals.len().next_power_of_two(); + let mut padded_evals = evals; + // TODO: does it make sense to pad evaluations with zeros? This changes the polynomial... + padded_evals.resize(next_power_of_two, Fr::zero()); - match form { - PolynomialFormat::InCoefficientForm => { - // Transform from evaluation form to coefficient form using FFT - self.form = form; - self.fft_on_elements(false) - }, - PolynomialFormat::InEvaluationForm => { - // Transform from coefficient form to evaluation form using IFFT - self.form = form; - self.fft_on_elements(true) - }, + Self { + evaluations: padded_evals, + len_underlying_blob_bytes: underlying_blob_len_in_bytes, } } - /// Performs an fft or ifft on the polynomial's elements - pub fn fft_on_elements(&mut self, inverse: bool) -> Result<(), PolynomialError> { - let fft_result = Self::fft(&self.to_vec(), inverse); - match fft_result { - Ok(fft_result) => { - self.elements = fft_result; - Ok(()) - }, - Err(e) => Err(e), - } + pub fn evaluations(&self) -> &[Fr] { + &self.evaluations } - /// helper function to perform fft or ifft on a vector of Fr - pub fn fft(vals: &Vec, inverse: bool) -> Result, PolynomialError> { - let length = vals.len(); - - match GeneralEvaluationDomain::::new(length) { - Some(domain) => { - if inverse { - let result = domain.ifft(vals); - Ok(result) - } else { - let result = domain.fft(vals); - Ok(result) - } - }, - None => Err(PolynomialError::FFTError( - "Failed to construct domain for FFT".to_string(), - )), - } + /// Returns the number of evaluations in the polynomial. Note that this returns the number of + /// evaluations in the padded polynomial, not the number of evaluations in the original + /// polynomial. + pub fn len(&self) -> usize { + self.evaluations.len() + } + + /// TODO: we should deprecate this. See comment in the struct. + pub fn len_underlying_blob_bytes(&self) -> usize { + self.len_underlying_blob_bytes + } + + pub fn get_at_index(&self, i: usize) -> Option<&Fr> { + self.evaluations.get(i) + } + + /// Checks if the polynomial has no elements. + pub fn is_empty(&self) -> bool { + self.evaluations.is_empty() + } + + /// Converts all `Fr` elements in the `Polynomial` to a single byte vector. + pub fn to_bytes_be(&self) -> Vec { + helpers::to_byte_array(&self.evaluations, self.len_underlying_blob_bytes) + } + + pub fn to_coef_form(&self) -> Result { + let coeffs = GeneralEvaluationDomain::::new(self.len()) + .ok_or(PolynomialError::FFTError( + "Failed to construct domain for IFFT".to_string(), + ))? + .ifft(&self.evaluations); + Ok(PolynomialCoefForm::new(coeffs)) } }