diff --git a/eip7594/src/errors.rs b/eip7594/src/errors.rs index 543352be..bea3667e 100644 --- a/eip7594/src/errors.rs +++ b/eip7594/src/errors.rs @@ -6,6 +6,7 @@ use erasure_codes::errors::RSError; pub enum Error { Prover(ProverError), Verifier(VerifierError), + Recovery(RecoveryError), Serialization(SerializationError), } @@ -38,35 +39,56 @@ impl From for Error { Error::Serialization(value) } } +impl From for Error { + fn from(value: RecoveryError) -> Self { + Error::Recovery(value) + } +} /// Errors that can occur while calling a method in the Prover API #[derive(Debug)] pub enum ProverError { - RecoveryFailure(VerifierError), + RecoveryFailure(RecoveryError), } -impl From for ProverError { - fn from(value: VerifierError) -> Self { +impl From for ProverError { + fn from(value: RecoveryError) -> Self { ProverError::RecoveryFailure(value) } } -/// Errors that can occur while calling a method in the Verifier API #[derive(Debug)] -pub enum VerifierError { - NumCellIndicesNotEqualToNumCells { - num_cell_indices: usize, - num_cells: usize, - }, - CellIndicesNotUnique, +/// Errors that can occur while calling the recovery procedure +pub enum RecoveryError { NotEnoughCellsToReconstruct { num_cells_received: usize, min_cells_needed: usize, }, + NumCellIndicesNotEqualToNumCells { + num_cell_indices: usize, + num_cells: usize, + }, TooManyCellsReceived { num_cells_received: usize, max_cells_needed: usize, }, + CellIndexOutOfRange { + cell_index: CellIndex, + max_number_of_cells: u64, + }, + CellIndicesNotUnique, + ReedSolomon(RSError), +} + +impl From for RecoveryError { + fn from(value: RSError) -> Self { + RecoveryError::ReedSolomon(value) + } +} + +/// Errors that can occur while calling a method in the Verifier API +#[derive(Debug)] +pub enum VerifierError { CellIndexOutOfRange { cell_index: CellIndex, max_number_of_cells: u64, @@ -82,7 +104,6 @@ pub enum VerifierError { cells_len: usize, proofs_len: usize, }, - ReedSolomon(RSError), FK20(kzg_multi_open::VerifierError), PolynomialHasInvalidLength { num_coefficients: usize, @@ -90,12 +111,6 @@ pub enum VerifierError { }, } -impl From for VerifierError { - fn from(value: RSError) -> Self { - VerifierError::ReedSolomon(value) - } -} - impl From for VerifierError { fn from(value: kzg_multi_open::VerifierError) -> Self { VerifierError::FK20(value) diff --git a/eip7594/src/lib.rs b/eip7594/src/lib.rs index e3e1f4cc..814530e4 100644 --- a/eip7594/src/lib.rs +++ b/eip7594/src/lib.rs @@ -4,6 +4,7 @@ compile_error!("feature_a and feature_b cannot be enabled simultaneously"); pub mod constants; mod errors; mod prover; +mod recovery; mod serialization; mod trusted_setup; mod verifier; diff --git a/eip7594/src/prover.rs b/eip7594/src/prover.rs index db7f039f..115b48ff 100644 --- a/eip7594/src/prover.rs +++ b/eip7594/src/prover.rs @@ -1,4 +1,5 @@ use bls12_381::fixed_base_msm::UsePrecomp; +use erasure_codes::ReedSolomon; use kzg_multi_open::{ commit_key::CommitKey, {Prover, ProverInput}, @@ -6,10 +7,11 @@ use kzg_multi_open::{ use crate::{ constants::{ - CELLS_PER_EXT_BLOB, FIELD_ELEMENTS_PER_BLOB, FIELD_ELEMENTS_PER_CELL, + CELLS_PER_EXT_BLOB, EXPANSION_FACTOR, FIELD_ELEMENTS_PER_BLOB, FIELD_ELEMENTS_PER_CELL, FIELD_ELEMENTS_PER_EXT_BLOB, }, errors::Error, + recovery::recover_polynomial_coeff, serialization::{ deserialize_blob_to_scalars, serialize_cells_and_proofs, serialize_g1_compressed, }, @@ -23,6 +25,7 @@ use crate::{ #[derive(Debug)] pub struct ProverContext { kzg_multipoint_prover: Prover, + rs: ReedSolomon, } impl Default for ProverContext { @@ -54,8 +57,15 @@ impl ProverContext { use_precomp, ); + let rs = ReedSolomon::new( + FIELD_ELEMENTS_PER_BLOB, + EXPANSION_FACTOR, + CELLS_PER_EXT_BLOB, + ); + ProverContext { kzg_multipoint_prover, + rs, } } } @@ -117,7 +127,7 @@ impl DASContext { with_optional_threadpool!(self, { // Recover polynomial // - let poly_coeff = self.recover_polynomial_coeff(cell_indices, cells)?; + let poly_coeff = recover_polynomial_coeff(&self.prover_ctx.rs, cell_indices, cells)?; // Compute proofs and evaluation sets // diff --git a/eip7594/src/recovery.rs b/eip7594/src/recovery.rs new file mode 100644 index 00000000..34ac4210 --- /dev/null +++ b/eip7594/src/recovery.rs @@ -0,0 +1,170 @@ +use std::collections::HashSet; + +use bls12_381::Scalar; +use erasure_codes::{BlockErasureIndices, ReedSolomon}; +use kzg_multi_open::recover_evaluations_in_domain_order; + +use crate::{ + constants::{CELLS_PER_EXT_BLOB, FIELD_ELEMENTS_PER_EXT_BLOB}, + errors::{Error, RecoveryError}, + serialization::deserialize_cells, + CellIndex, CellRef, +}; + +pub(crate) fn recover_polynomial_coeff( + rs: &ReedSolomon, + cell_indices: Vec, + cells: Vec, +) -> Result, Error> { + // Validation + // + validation::recover_polynomial_coeff(&cell_indices, &cells)?; + + // Deserialization + // + let coset_evaluations = deserialize_cells(cells)?; + let cell_indices: Vec = cell_indices + .into_iter() + .map(|index| index as usize) + .collect(); + + // Computation + // + // Permute the cells, so they are in the order that you would expect, if you were + // to compute an fft on the monomial form of the polynomial. + // + // This comment does leak the fact that the cells are not in the "correct" order, + // which the API tries to hide. + let (cell_indices_normal_order, flattened_coset_evaluations_normal_order) = + recover_evaluations_in_domain_order( + FIELD_ELEMENTS_PER_EXT_BLOB, + cell_indices, + coset_evaluations, + ) + // This should never trigger since: + // - cell_indices is non-empty + // - all coset evaluations are checked to have the same size + // - all coset indices are checked to be valid + .expect("infallible: could not recover evaluations in domain order"); + + // Find all of the missing cell indices. This is needed for recovery. + let missing_cell_indices = find_missing_cell_indices(&cell_indices_normal_order); + + // Recover the polynomial in monomial form, that one can use to generate the cells. + let recovered_polynomial_coeff = rs + .recover_polynomial_coefficient( + flattened_coset_evaluations_normal_order, + BlockErasureIndices(missing_cell_indices), + ) + .map_err(RecoveryError::from)?; + + Ok(recovered_polynomial_coeff) +} + +fn find_missing_cell_indices(present_cell_indices: &[usize]) -> Vec { + let cell_indices: HashSet<_> = present_cell_indices.iter().cloned().collect(); + + let mut missing = Vec::new(); + + for i in 0..CELLS_PER_EXT_BLOB { + if !cell_indices.contains(&i) { + missing.push(i); + } + } + + missing +} + +mod validation { + use std::collections::HashSet; + + use crate::{ + constants::{BYTES_PER_CELL, CELLS_PER_EXT_BLOB, EXPANSION_FACTOR}, + errors::RecoveryError, + CellIndex, CellRef, + }; + + /// Validation logic for `recover_polynomial_coeff` + pub(crate) fn recover_polynomial_coeff( + cell_indices: &[CellIndex], + cells: &[CellRef], + ) -> Result<(), RecoveryError> { + // Check that the number of cell indices is equal to the number of cells + if cell_indices.len() != cells.len() { + return Err(RecoveryError::NumCellIndicesNotEqualToNumCells { + num_cell_indices: cell_indices.len(), + num_cells: cells.len(), + }); + } + + // Check that the Cell indices are within the expected range + for cell_index in cell_indices.iter() { + if *cell_index >= (CELLS_PER_EXT_BLOB as u64) { + return Err(RecoveryError::CellIndexOutOfRange { + cell_index: *cell_index, + max_number_of_cells: CELLS_PER_EXT_BLOB as u64, + }); + } + } + + // Check that each cell has the right amount of bytes + // + // This should be infallible. + for (i, cell) in cells.iter().enumerate() { + assert_eq!(cell.len(), BYTES_PER_CELL, "the number of bytes in a cell should always equal {} since the type is a reference to an array. Check cell at index {}", BYTES_PER_CELL, i); + } + + // Check that we have no duplicate cell indices + if !are_cell_indices_unique(cell_indices) { + return Err(RecoveryError::CellIndicesNotUnique); + } + + // Check that we have enough cells to perform a reconstruction + if cell_indices.len() < CELLS_PER_EXT_BLOB / EXPANSION_FACTOR { + return Err(RecoveryError::NotEnoughCellsToReconstruct { + num_cells_received: cell_indices.len(), + min_cells_needed: CELLS_PER_EXT_BLOB / EXPANSION_FACTOR, + }); + } + + // Check that we don't have too many cells + // ie more than we initially generated from the blob + // + // Note: Since we check that there are no duplicates and that all cell_indices + // are between 0 and CELLS_PER_EXT_BLOB. This check should never fail. + // It is kept here to be compliant with the specs. + if cell_indices.len() > CELLS_PER_EXT_BLOB { + return Err(RecoveryError::TooManyCellsReceived { + num_cells_received: cell_indices.len(), + max_cells_needed: CELLS_PER_EXT_BLOB, + }); + } + + Ok(()) + } + + /// Check if all of the cell indices are unique + fn are_cell_indices_unique(cell_indices: &[CellIndex]) -> bool { + let len_cell_indices_non_dedup = cell_indices.len(); + let cell_indices_dedup: HashSet<_> = cell_indices.iter().collect(); + cell_indices_dedup.len() == len_cell_indices_non_dedup + } + + #[cfg(test)] + mod tests { + + use super::are_cell_indices_unique; + + #[test] + fn test_cell_indices_unique() { + let cell_indices = vec![1, 2, 3]; + assert!(are_cell_indices_unique(&cell_indices)); + let cell_indices = vec![]; + assert!(are_cell_indices_unique(&cell_indices)); + let cell_indices = vec![1, 1, 2, 3]; + assert!(!are_cell_indices_unique(&cell_indices)); + let cell_indices = vec![0, 0, 0]; + assert!(!are_cell_indices_unique(&cell_indices)); + } + } +} diff --git a/eip7594/src/verifier.rs b/eip7594/src/verifier.rs index 1c56e697..24af2760 100644 --- a/eip7594/src/verifier.rs +++ b/eip7594/src/verifier.rs @@ -1,27 +1,20 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; pub use crate::errors::VerifierError; use crate::{ - constants::{ - CELLS_PER_EXT_BLOB, EXPANSION_FACTOR, FIELD_ELEMENTS_PER_BLOB, FIELD_ELEMENTS_PER_EXT_BLOB, - }, + constants::{CELLS_PER_EXT_BLOB, FIELD_ELEMENTS_PER_EXT_BLOB}, errors::Error, serialization::{deserialize_cells, deserialize_compressed_g1_points}, trusted_setup::TrustedSetup, with_optional_threadpool, Bytes48Ref, CellIndex, CellRef, DASContext, }; -use bls12_381::Scalar; -use erasure_codes::{BlockErasureIndices, ReedSolomon}; -use kzg_multi_open::{ - recover_evaluations_in_domain_order, verification_key::VerificationKey, Verifier, -}; +use kzg_multi_open::{verification_key::VerificationKey, Verifier}; /// The context object that is used to call functions in the verifier API. #[derive(Debug)] pub struct VerifierContext { kzg_multipoint_verifier: Verifier, - rs: ReedSolomon, } impl Default for VerifierContext { @@ -42,30 +35,11 @@ impl VerifierContext { ); VerifierContext { - rs: ReedSolomon::new( - FIELD_ELEMENTS_PER_BLOB, - EXPANSION_FACTOR, - CELLS_PER_EXT_BLOB, - ), kzg_multipoint_verifier: multipoint_verifier, } } } -fn find_missing_cell_indices(present_cell_indices: &[usize]) -> Vec { - let cell_indices: HashSet<_> = present_cell_indices.iter().cloned().collect(); - - let mut missing = Vec::new(); - - for i in 0..CELLS_PER_EXT_BLOB { - if !cell_indices.contains(&i) { - missing.push(i); - } - } - - missing -} - /// Deduplicates a vector and creates a mapping of original indices to deduplicated indices. /// /// This function takes a vector of items and returns two vectors: @@ -145,69 +119,13 @@ impl DASContext { ok.map_err(VerifierError::from).map_err(Into::into) }) } - - pub(crate) fn recover_polynomial_coeff( - &self, - cell_indices: Vec, - cells: Vec, - ) -> Result, Error> { - // Validation - // - validation::recover_polynomial_coeff(&cell_indices, &cells)?; - - // Deserialization - // - let coset_evaluations = deserialize_cells(cells)?; - let cell_indices: Vec = cell_indices - .into_iter() - .map(|index| index as usize) - .collect(); - - // Computation - // - // Permute the cells, so they are in the order that you would expect, if you were - // to compute an fft on the monomial form of the polynomial. - // - // This comment does leak the fact that the cells are not in the "correct" order, - // which the API tries to hide. - let (cell_indices_normal_order, flattened_coset_evaluations_normal_order) = - recover_evaluations_in_domain_order( - FIELD_ELEMENTS_PER_EXT_BLOB, - cell_indices, - coset_evaluations, - ) - // This should never trigger since: - // - cell_indices is non-empty - // - all coset evaluations are checked to have the same size - // - all coset indices are checked to be valid - .expect("infallible: could not recover evaluations in domain order"); - - // Find all of the missing cell indices. This is needed for recovery. - let missing_cell_indices = find_missing_cell_indices(&cell_indices_normal_order); - - // Recover the polynomial in monomial form, that one can use to generate the cells. - let recovered_polynomial_coeff = self - .verifier_ctx - .rs - .recover_polynomial_coefficient( - flattened_coset_evaluations_normal_order, - BlockErasureIndices(missing_cell_indices), - ) - .map_err(VerifierError::from)?; - - Ok(recovered_polynomial_coeff) - } } mod validation { - use std::collections::HashSet; - use kzg_multi_open::CommitmentIndex; use crate::{ - constants::{BYTES_PER_CELL, CELLS_PER_EXT_BLOB, EXPANSION_FACTOR}, - verifier::VerifierError, - Bytes48Ref, CellIndex, CellRef, + constants::CELLS_PER_EXT_BLOB, verifier::VerifierError, Bytes48Ref, CellIndex, CellRef, }; /// Validation logic for `verify_cell_kzg_proof_batch` @@ -253,102 +171,21 @@ mod validation { Ok(()) } - - /// Validation logic for `recover_polynomial_coeff` - pub(crate) fn recover_polynomial_coeff( - cell_indices: &[CellIndex], - cells: &[CellRef], - ) -> Result<(), VerifierError> { - // Check that the number of cell indices is equal to the number of cells - if cell_indices.len() != cells.len() { - return Err(VerifierError::NumCellIndicesNotEqualToNumCells { - num_cell_indices: cell_indices.len(), - num_cells: cells.len(), - }); - } - - // Check that the Cell indices are within the expected range - for cell_index in cell_indices.iter() { - if *cell_index >= (CELLS_PER_EXT_BLOB as u64) { - return Err(VerifierError::CellIndexOutOfRange { - cell_index: *cell_index, - max_number_of_cells: CELLS_PER_EXT_BLOB as u64, - }); - } - } - - // Check that each cell has the right amount of bytes - // - // This should be infallible. - for (i, cell) in cells.iter().enumerate() { - assert_eq!(cell.len(), BYTES_PER_CELL, "the number of bytes in a cell should always equal {} since the type is a reference to an array. Check cell at index {}", BYTES_PER_CELL, i); - } - - // Check that we have no duplicate cell indices - if !are_cell_indices_unique(cell_indices) { - return Err(VerifierError::CellIndicesNotUnique); - } - - // Check that we have enough cells to perform a reconstruction - if cell_indices.len() < CELLS_PER_EXT_BLOB / EXPANSION_FACTOR { - return Err(VerifierError::NotEnoughCellsToReconstruct { - num_cells_received: cell_indices.len(), - min_cells_needed: CELLS_PER_EXT_BLOB / EXPANSION_FACTOR, - }); - } - - // Check that we don't have too many cells - // ie more than we initially generated from the blob - // - // Note: Since we check that there are no duplicates and that all cell_indices - // are between 0 and CELLS_PER_EXT_BLOB. This check should never fail. - // It is kept here to be compliant with the specs. - if cell_indices.len() > CELLS_PER_EXT_BLOB { - return Err(VerifierError::TooManyCellsReceived { - num_cells_received: cell_indices.len(), - max_cells_needed: CELLS_PER_EXT_BLOB, - }); - } - - Ok(()) - } - - /// Check if all of the cell indices are unique - fn are_cell_indices_unique(cell_indices: &[CellIndex]) -> bool { - let len_cell_indices_non_dedup = cell_indices.len(); - let cell_indices_dedup: HashSet<_> = cell_indices.iter().collect(); - cell_indices_dedup.len() == len_cell_indices_non_dedup - } - #[cfg(test)] mod tests { - use super::are_cell_indices_unique; - #[test] - fn test_cell_indices_unique() { - let cell_indices = vec![1, 2, 3]; - assert!(are_cell_indices_unique(&cell_indices)); - let cell_indices = vec![]; - assert!(are_cell_indices_unique(&cell_indices)); - let cell_indices = vec![1, 1, 2, 3]; - assert!(!are_cell_indices_unique(&cell_indices)); - let cell_indices = vec![0, 0, 0]; - assert!(!are_cell_indices_unique(&cell_indices)); - } - } - - #[test] - fn test_deduplicate_with_indices() { - let duplicated_vector: Vec = vec![0, 1, 0, 2, 3, 4, 0]; + fn test_deduplicate_with_indices() { + let duplicated_vector: Vec = vec![0, 1, 0, 2, 3, 4, 0]; - let (deduplicated_vec, indices) = - crate::verifier::deduplicate_with_indices(duplicated_vector); + let (deduplicated_vec, indices) = + crate::verifier::deduplicate_with_indices(duplicated_vector); - let expected_vec = vec![0, 1, 2, 3, 4]; - let expected_indices = vec![0, 1, 0, 2, 3, 4, 0]; + let expected_vec = vec![0, 1, 2, 3, 4]; + let expected_indices = vec![0, 1, 0, 2, 3, 4, 0]; - assert_eq!(expected_vec, deduplicated_vec); - assert_eq!(expected_indices, indices); + assert_eq!(expected_vec, deduplicated_vec); + assert_eq!(expected_indices, indices); + } } }