Skip to content

Commit 0fc446a

Browse files
authored
chore: move recovery code to its own module (#290)
* add new recovery module * add recovery errors * refactor code to use recovery method and move recovery out of the verifier * add back comment
1 parent fe72775 commit 0fc446a

File tree

5 files changed

+228
-195
lines changed

5 files changed

+228
-195
lines changed

eip7594/src/errors.rs

+32-17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use erasure_codes::errors::RSError;
66
pub enum Error {
77
Prover(ProverError),
88
Verifier(VerifierError),
9+
Recovery(RecoveryError),
910
Serialization(SerializationError),
1011
}
1112

@@ -38,35 +39,56 @@ impl From<SerializationError> for Error {
3839
Error::Serialization(value)
3940
}
4041
}
42+
impl From<RecoveryError> for Error {
43+
fn from(value: RecoveryError) -> Self {
44+
Error::Recovery(value)
45+
}
46+
}
4147

4248
/// Errors that can occur while calling a method in the Prover API
4349
#[derive(Debug)]
4450
pub enum ProverError {
45-
RecoveryFailure(VerifierError),
51+
RecoveryFailure(RecoveryError),
4652
}
4753

48-
impl From<VerifierError> for ProverError {
49-
fn from(value: VerifierError) -> Self {
54+
impl From<RecoveryError> for ProverError {
55+
fn from(value: RecoveryError) -> Self {
5056
ProverError::RecoveryFailure(value)
5157
}
5258
}
5359

54-
/// Errors that can occur while calling a method in the Verifier API
5560
#[derive(Debug)]
56-
pub enum VerifierError {
57-
NumCellIndicesNotEqualToNumCells {
58-
num_cell_indices: usize,
59-
num_cells: usize,
60-
},
61-
CellIndicesNotUnique,
61+
/// Errors that can occur while calling the recovery procedure
62+
pub enum RecoveryError {
6263
NotEnoughCellsToReconstruct {
6364
num_cells_received: usize,
6465
min_cells_needed: usize,
6566
},
67+
NumCellIndicesNotEqualToNumCells {
68+
num_cell_indices: usize,
69+
num_cells: usize,
70+
},
6671
TooManyCellsReceived {
6772
num_cells_received: usize,
6873
max_cells_needed: usize,
6974
},
75+
CellIndexOutOfRange {
76+
cell_index: CellIndex,
77+
max_number_of_cells: u64,
78+
},
79+
CellIndicesNotUnique,
80+
ReedSolomon(RSError),
81+
}
82+
83+
impl From<RSError> for RecoveryError {
84+
fn from(value: RSError) -> Self {
85+
RecoveryError::ReedSolomon(value)
86+
}
87+
}
88+
89+
/// Errors that can occur while calling a method in the Verifier API
90+
#[derive(Debug)]
91+
pub enum VerifierError {
7092
CellIndexOutOfRange {
7193
cell_index: CellIndex,
7294
max_number_of_cells: u64,
@@ -82,20 +104,13 @@ pub enum VerifierError {
82104
cells_len: usize,
83105
proofs_len: usize,
84106
},
85-
ReedSolomon(RSError),
86107
FK20(kzg_multi_open::VerifierError),
87108
PolynomialHasInvalidLength {
88109
num_coefficients: usize,
89110
expected_num_coefficients: usize,
90111
},
91112
}
92113

93-
impl From<RSError> for VerifierError {
94-
fn from(value: RSError) -> Self {
95-
VerifierError::ReedSolomon(value)
96-
}
97-
}
98-
99114
impl From<kzg_multi_open::VerifierError> for VerifierError {
100115
fn from(value: kzg_multi_open::VerifierError) -> Self {
101116
VerifierError::FK20(value)

eip7594/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ compile_error!("feature_a and feature_b cannot be enabled simultaneously");
44
pub mod constants;
55
mod errors;
66
mod prover;
7+
mod recovery;
78
mod serialization;
89
mod trusted_setup;
910
mod verifier;

eip7594/src/prover.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
use bls12_381::fixed_base_msm::UsePrecomp;
2+
use erasure_codes::ReedSolomon;
23
use kzg_multi_open::{
34
commit_key::CommitKey,
45
{Prover, ProverInput},
56
};
67

78
use crate::{
89
constants::{
9-
CELLS_PER_EXT_BLOB, FIELD_ELEMENTS_PER_BLOB, FIELD_ELEMENTS_PER_CELL,
10+
CELLS_PER_EXT_BLOB, EXPANSION_FACTOR, FIELD_ELEMENTS_PER_BLOB, FIELD_ELEMENTS_PER_CELL,
1011
FIELD_ELEMENTS_PER_EXT_BLOB,
1112
},
1213
errors::Error,
14+
recovery::recover_polynomial_coeff,
1315
serialization::{
1416
deserialize_blob_to_scalars, serialize_cells_and_proofs, serialize_g1_compressed,
1517
},
@@ -23,6 +25,7 @@ use crate::{
2325
#[derive(Debug)]
2426
pub struct ProverContext {
2527
kzg_multipoint_prover: Prover,
28+
rs: ReedSolomon,
2629
}
2730

2831
impl Default for ProverContext {
@@ -54,8 +57,15 @@ impl ProverContext {
5457
use_precomp,
5558
);
5659

60+
let rs = ReedSolomon::new(
61+
FIELD_ELEMENTS_PER_BLOB,
62+
EXPANSION_FACTOR,
63+
CELLS_PER_EXT_BLOB,
64+
);
65+
5766
ProverContext {
5867
kzg_multipoint_prover,
68+
rs,
5969
}
6070
}
6171
}
@@ -117,7 +127,7 @@ impl DASContext {
117127
with_optional_threadpool!(self, {
118128
// Recover polynomial
119129
//
120-
let poly_coeff = self.recover_polynomial_coeff(cell_indices, cells)?;
130+
let poly_coeff = recover_polynomial_coeff(&self.prover_ctx.rs, cell_indices, cells)?;
121131

122132
// Compute proofs and evaluation sets
123133
//

eip7594/src/recovery.rs

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use std::collections::HashSet;
2+
3+
use bls12_381::Scalar;
4+
use erasure_codes::{BlockErasureIndices, ReedSolomon};
5+
use kzg_multi_open::recover_evaluations_in_domain_order;
6+
7+
use crate::{
8+
constants::{CELLS_PER_EXT_BLOB, FIELD_ELEMENTS_PER_EXT_BLOB},
9+
errors::{Error, RecoveryError},
10+
serialization::deserialize_cells,
11+
CellIndex, CellRef,
12+
};
13+
14+
pub(crate) fn recover_polynomial_coeff(
15+
rs: &ReedSolomon,
16+
cell_indices: Vec<CellIndex>,
17+
cells: Vec<CellRef>,
18+
) -> Result<Vec<Scalar>, Error> {
19+
// Validation
20+
//
21+
validation::recover_polynomial_coeff(&cell_indices, &cells)?;
22+
23+
// Deserialization
24+
//
25+
let coset_evaluations = deserialize_cells(cells)?;
26+
let cell_indices: Vec<usize> = cell_indices
27+
.into_iter()
28+
.map(|index| index as usize)
29+
.collect();
30+
31+
// Computation
32+
//
33+
// Permute the cells, so they are in the order that you would expect, if you were
34+
// to compute an fft on the monomial form of the polynomial.
35+
//
36+
// This comment does leak the fact that the cells are not in the "correct" order,
37+
// which the API tries to hide.
38+
let (cell_indices_normal_order, flattened_coset_evaluations_normal_order) =
39+
recover_evaluations_in_domain_order(
40+
FIELD_ELEMENTS_PER_EXT_BLOB,
41+
cell_indices,
42+
coset_evaluations,
43+
)
44+
// This should never trigger since:
45+
// - cell_indices is non-empty
46+
// - all coset evaluations are checked to have the same size
47+
// - all coset indices are checked to be valid
48+
.expect("infallible: could not recover evaluations in domain order");
49+
50+
// Find all of the missing cell indices. This is needed for recovery.
51+
let missing_cell_indices = find_missing_cell_indices(&cell_indices_normal_order);
52+
53+
// Recover the polynomial in monomial form, that one can use to generate the cells.
54+
let recovered_polynomial_coeff = rs
55+
.recover_polynomial_coefficient(
56+
flattened_coset_evaluations_normal_order,
57+
BlockErasureIndices(missing_cell_indices),
58+
)
59+
.map_err(RecoveryError::from)?;
60+
61+
Ok(recovered_polynomial_coeff)
62+
}
63+
64+
fn find_missing_cell_indices(present_cell_indices: &[usize]) -> Vec<usize> {
65+
let cell_indices: HashSet<_> = present_cell_indices.iter().cloned().collect();
66+
67+
let mut missing = Vec::new();
68+
69+
for i in 0..CELLS_PER_EXT_BLOB {
70+
if !cell_indices.contains(&i) {
71+
missing.push(i);
72+
}
73+
}
74+
75+
missing
76+
}
77+
78+
mod validation {
79+
use std::collections::HashSet;
80+
81+
use crate::{
82+
constants::{BYTES_PER_CELL, CELLS_PER_EXT_BLOB, EXPANSION_FACTOR},
83+
errors::RecoveryError,
84+
CellIndex, CellRef,
85+
};
86+
87+
/// Validation logic for `recover_polynomial_coeff`
88+
pub(crate) fn recover_polynomial_coeff(
89+
cell_indices: &[CellIndex],
90+
cells: &[CellRef],
91+
) -> Result<(), RecoveryError> {
92+
// Check that the number of cell indices is equal to the number of cells
93+
if cell_indices.len() != cells.len() {
94+
return Err(RecoveryError::NumCellIndicesNotEqualToNumCells {
95+
num_cell_indices: cell_indices.len(),
96+
num_cells: cells.len(),
97+
});
98+
}
99+
100+
// Check that the Cell indices are within the expected range
101+
for cell_index in cell_indices.iter() {
102+
if *cell_index >= (CELLS_PER_EXT_BLOB as u64) {
103+
return Err(RecoveryError::CellIndexOutOfRange {
104+
cell_index: *cell_index,
105+
max_number_of_cells: CELLS_PER_EXT_BLOB as u64,
106+
});
107+
}
108+
}
109+
110+
// Check that each cell has the right amount of bytes
111+
//
112+
// This should be infallible.
113+
for (i, cell) in cells.iter().enumerate() {
114+
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);
115+
}
116+
117+
// Check that we have no duplicate cell indices
118+
if !are_cell_indices_unique(cell_indices) {
119+
return Err(RecoveryError::CellIndicesNotUnique);
120+
}
121+
122+
// Check that we have enough cells to perform a reconstruction
123+
if cell_indices.len() < CELLS_PER_EXT_BLOB / EXPANSION_FACTOR {
124+
return Err(RecoveryError::NotEnoughCellsToReconstruct {
125+
num_cells_received: cell_indices.len(),
126+
min_cells_needed: CELLS_PER_EXT_BLOB / EXPANSION_FACTOR,
127+
});
128+
}
129+
130+
// Check that we don't have too many cells
131+
// ie more than we initially generated from the blob
132+
//
133+
// Note: Since we check that there are no duplicates and that all cell_indices
134+
// are between 0 and CELLS_PER_EXT_BLOB. This check should never fail.
135+
// It is kept here to be compliant with the specs.
136+
if cell_indices.len() > CELLS_PER_EXT_BLOB {
137+
return Err(RecoveryError::TooManyCellsReceived {
138+
num_cells_received: cell_indices.len(),
139+
max_cells_needed: CELLS_PER_EXT_BLOB,
140+
});
141+
}
142+
143+
Ok(())
144+
}
145+
146+
/// Check if all of the cell indices are unique
147+
fn are_cell_indices_unique(cell_indices: &[CellIndex]) -> bool {
148+
let len_cell_indices_non_dedup = cell_indices.len();
149+
let cell_indices_dedup: HashSet<_> = cell_indices.iter().collect();
150+
cell_indices_dedup.len() == len_cell_indices_non_dedup
151+
}
152+
153+
#[cfg(test)]
154+
mod tests {
155+
156+
use super::are_cell_indices_unique;
157+
158+
#[test]
159+
fn test_cell_indices_unique() {
160+
let cell_indices = vec![1, 2, 3];
161+
assert!(are_cell_indices_unique(&cell_indices));
162+
let cell_indices = vec![];
163+
assert!(are_cell_indices_unique(&cell_indices));
164+
let cell_indices = vec![1, 1, 2, 3];
165+
assert!(!are_cell_indices_unique(&cell_indices));
166+
let cell_indices = vec![0, 0, 0];
167+
assert!(!are_cell_indices_unique(&cell_indices));
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)