2121 - [ FFTs] ( #ffts )
2222 - [ ` _fft_field ` ] ( #_fft_field )
2323 - [ ` fft_field ` ] ( #fft_field )
24+ - [ ` coset_fft_field ` ] ( #coset_fft_field )
2425 - [ Polynomials in coefficient form] ( #polynomials-in-coefficient-form )
2526 - [ ` polynomial_eval_to_coeff ` ] ( #polynomial_eval_to_coeff )
2627 - [ ` add_polynomialcoeff ` ] ( #add_polynomialcoeff )
2728 - [ ` neg_polynomialcoeff ` ] ( #neg_polynomialcoeff )
2829 - [ ` multiply_polynomialcoeff ` ] ( #multiply_polynomialcoeff )
2930 - [ ` divide_polynomialcoeff ` ] ( #divide_polynomialcoeff )
30- - [ ` shift_polynomialcoeff ` ] ( #shift_polynomialcoeff )
3131 - [ ` interpolate_polynomialcoeff ` ] ( #interpolate_polynomialcoeff )
3232 - [ ` vanishing_polynomialcoeff ` ] ( #vanishing_polynomialcoeff )
3333 - [ ` evaluate_polynomialcoeff ` ] ( #evaluate_polynomialcoeff )
4444 - [ ` verify_cell_kzg_proof_batch ` ] ( #verify_cell_kzg_proof_batch )
4545- [ Reconstruction] ( #reconstruction )
4646 - [ ` construct_vanishing_polynomial ` ] ( #construct_vanishing_polynomial )
47- - [ ` recover_shifted_data ` ] ( #recover_shifted_data )
48- - [ ` recover_original_data ` ] ( #recover_original_data )
47+ - [ ` recover_data ` ] ( #recover_data )
4948 - [ ` recover_cells_and_kzg_proofs ` ] ( #recover_cells_and_kzg_proofs )
5049
5150<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -63,11 +62,10 @@ Public functions MUST accept raw bytes as input and perform the required cryptog
6362
6463The following is a list of the public methods:
6564
66- * [ ` compute_cells_and_kzg_proofs ` ] ( #compute_cells_and_kzg_proofs )
67- * [ ` verify_cell_kzg_proof ` ] ( #verify_cell_kzg_proof )
68- * [ ` verify_cell_kzg_proof_batch ` ] ( #verify_cell_kzg_proof_batch )
69- * [ ` recover_cells_and_kzg_proofs ` ] ( #recover_cells_and_kzg_proofs )
70-
65+ - [ ` compute_cells_and_kzg_proofs ` ] ( #compute_cells_and_kzg_proofs )
66+ - [ ` verify_cell_kzg_proof ` ] ( #verify_cell_kzg_proof )
67+ - [ ` verify_cell_kzg_proof_batch ` ] ( #verify_cell_kzg_proof_batch )
68+ - [ ` recover_cells_and_kzg_proofs ` ] ( #recover_cells_and_kzg_proofs )
7169
7270## Custom types
7371
@@ -188,6 +186,41 @@ def fft_field(vals: Sequence[BLSFieldElement],
188186 return _fft_field(vals, roots_of_unity)
189187```
190188
189+ #### ` coset_fft_field `
190+
191+ ``` python
192+ def coset_fft_field (vals : Sequence[BLSFieldElement],
193+ roots_of_unity : Sequence[BLSFieldElement],
194+ inv : bool = False ) -> Sequence[BLSFieldElement]:
195+ """
196+ Computes an FFT/IFFT over a coset of the roots of unity.
197+ This is useful for when one wants to divide by a polynomial which
198+ vanishes on one or more elements in the domain.
199+ """
200+ vals = vals.copy()
201+
202+ def shift_vals (vals : Sequence[BLSFieldElement], factor : BLSFieldElement) -> Sequence[BLSFieldElement]:
203+ """
204+ Multiply each entry in `vals` by succeeding powers of `factor`
205+ i.e., [vals[0] * factor^0, vals[1] * factor^1, ..., vals[n] * factor^n]
206+ """
207+ shift = 1
208+ for i in range (len (vals)):
209+ vals[i] = BLSFieldElement((int (vals[i]) * shift) % BLS_MODULUS )
210+ shift = (shift * int (factor)) % BLS_MODULUS
211+ return vals
212+
213+ # This is the coset generator; it is used to compute a FFT/IFFT over a coset of
214+ # the roots of unity.
215+ shift_factor = BLSFieldElement(PRIMITIVE_ROOT_OF_UNITY )
216+ if inv:
217+ vals = fft_field(vals, roots_of_unity, inv)
218+ shift_inv = bls_modular_inverse(shift_factor)
219+ return shift_vals(vals, shift_inv)
220+ else :
221+ vals = shift_vals(vals, shift_factor)
222+ return fft_field(vals, roots_of_unity, inv)
223+ ```
191224
192225### Polynomials in coefficient form
193226
@@ -265,23 +298,6 @@ def divide_polynomialcoeff(a: PolynomialCoeff, b: PolynomialCoeff) -> Polynomial
265298 return [x % BLS_MODULUS for x in o]
266299```
267300
268- #### ` shift_polynomialcoeff `
269-
270- ``` python
271- def shift_polynomialcoeff (polynomial_coeff : PolynomialCoeff, factor : BLSFieldElement) -> PolynomialCoeff:
272- """
273- Shift the evaluation of a polynomial in coefficient form by factor.
274- This returns a new polynomial g in coefficient form such that g(x) = f(factor * x).
275- In other words, each coefficient of f is scaled by a power of factor.
276- """
277- factor_power = 1
278- o = []
279- for p in polynomial_coeff:
280- o.append(int (p) * factor_power % BLS_MODULUS )
281- factor_power = factor_power * int (factor) % BLS_MODULUS
282- return o
283- ```
284-
285301#### ` interpolate_polynomialcoeff `
286302
287303``` python
@@ -494,7 +510,7 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48],
494510 cells : Sequence[Cell],
495511 proofs_bytes : Sequence[Bytes48]) -> bool :
496512 """
497- Verify a set of cells, given their corresponding proofs and their coordinates (row_id, column_id ) in the blob
513+ Verify a set of cells, given their corresponding proofs and their coordinates (row_index, column_index ) in the blob
498514 matrix. The list of all commitments is also provided in row_commitments_bytes.
499515
500516 This function implements the naive algorithm of checking every cell
@@ -519,7 +535,7 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48],
519535 for proof_bytes in proofs_bytes:
520536 assert len (proof_bytes) == BYTES_PER_PROOF
521537
522- # Get commitments via row IDs
538+ # Get commitments via row indices
523539 commitments_bytes = [row_commitments_bytes[row_index] for row_index in row_indices]
524540
525541 # Get objects from bytes
@@ -538,13 +554,20 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48],
538554### ` construct_vanishing_polynomial `
539555
540556``` python
541- def construct_vanishing_polynomial (missing_cell_indices : Sequence[CellIndex]) -> Tuple[
542- Sequence[BLSFieldElement],
543- Sequence[BLSFieldElement]]:
557+ def construct_vanishing_polynomial (missing_cell_indices : Sequence[CellIndex]) -> Sequence[BLSFieldElement]:
544558 """
545- Given the cells that are missing from the data, compute the polynomial that vanishes at every point that
559+ Given the cells indices that are missing from the data, compute the polynomial that vanishes at every point that
546560 corresponds to a missing field element.
561+
562+ This method assumes that all of the cells cannot be missing. In this case the vanishing polynomial
563+ could be computed as Z(x) = x^n - 1, where `n` is FIELD_ELEMENTS_PER_EXT_BLOB.
564+
565+ We never encounter this case however because this method is used solely for recovery and recovery only
566+ works if at least half of the cells are available.
547567 """
568+
569+ assert len (missing_cell_indices) != 0
570+
548571 # Get the small domain
549572 roots_of_unity_reduced = compute_roots_of_unity(CELLS_PER_EXT_BLOB )
550573
@@ -559,86 +582,64 @@ def construct_vanishing_polynomial(missing_cell_indices: Sequence[CellIndex]) ->
559582 for i, coeff in enumerate (short_zero_poly):
560583 zero_poly_coeff[i * FIELD_ELEMENTS_PER_CELL ] = coeff
561584
562- # Compute evaluations of the extended vanishing polynomial
563- zero_poly_eval = fft_field(zero_poly_coeff,
564- compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB ))
565- zero_poly_eval_brp = bit_reversal_permutation(zero_poly_eval)
566-
567- # Sanity check
568- for cell_index in range (CELLS_PER_EXT_BLOB ):
569- start = cell_index * FIELD_ELEMENTS_PER_CELL
570- end = (cell_index + 1 ) * FIELD_ELEMENTS_PER_CELL
571- if cell_index in missing_cell_indices:
572- assert all (a == 0 for a in zero_poly_eval_brp[start:end])
573- else : # cell_index in cell_indices
574- assert all (a != 0 for a in zero_poly_eval_brp[start:end])
575-
576- return zero_poly_coeff, zero_poly_eval
585+ return zero_poly_coeff
577586```
578587
579- ### ` recover_shifted_data `
588+ ### ` recover_data `
580589
581590``` python
582- def recover_shifted_data (cell_indices : Sequence[CellIndex],
583- cells : Sequence[Cell],
584- zero_poly_eval : Sequence[BLSFieldElement],
585- zero_poly_coeff : Sequence[BLSFieldElement],
586- roots_of_unity_extended : Sequence[BLSFieldElement]) -> Tuple[
587- Sequence[BLSFieldElement],
588- Sequence[BLSFieldElement],
589- BLSFieldElement]:
591+ def recover_data (cell_indices : Sequence[CellIndex],
592+ cells : Sequence[Cell],
593+ ) -> Sequence[BLSFieldElement]:
590594 """
591- Given Z(x), return polynomial Q_1(x)=(E*Z)(k*x) and Q_2(x)=Z(k*x) and k^{-1} .
595+ Recover the missing evaluations for the extended blob, given at least half of the evaluations .
592596 """
593- shift_factor = BLSFieldElement(PRIMITIVE_ROOT_OF_UNITY )
594- shift_inv = div(BLSFieldElement(1 ), shift_factor)
595597
598+ # Get the extended domain. This will be referred to as the FFT domain.
599+ roots_of_unity_extended = compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB )
600+
601+ # Flatten the cells into evaluations.
602+ # If a cell is missing, then its evaluation is zero.
596603 extended_evaluation_rbo = [0 ] * FIELD_ELEMENTS_PER_EXT_BLOB
597604 for cell_index, cell in zip (cell_indices, cells):
598605 start = cell_index * FIELD_ELEMENTS_PER_CELL
599606 end = (cell_index + 1 ) * FIELD_ELEMENTS_PER_CELL
600607 extended_evaluation_rbo[start:end] = cell
601608 extended_evaluation = bit_reversal_permutation(extended_evaluation_rbo)
602609
603- # Compute (E*Z)(x)
604- extended_evaluation_times_zero = [BLSFieldElement(int (a) * int (b) % BLS_MODULUS )
605- for a, b in zip (zero_poly_eval, extended_evaluation)]
610+ # Compute Z(x) in monomial form
611+ # Z(x) is the polynomial which vanishes on all of the evaluations which are missing
612+ missing_cell_indices = [CellIndex(cell_index) for cell_index in range (CELLS_PER_EXT_BLOB )
613+ if cell_index not in cell_indices]
614+ zero_poly_coeff = construct_vanishing_polynomial(missing_cell_indices)
606615
607- extended_evaluations_fft = fft_field(extended_evaluation_times_zero, roots_of_unity_extended, inv = True )
616+ # Convert Z(x) to evaluation form over the FFT domain
617+ zero_poly_eval = fft_field(zero_poly_coeff, roots_of_unity_extended)
608618
609- # Compute (E*Z)(k*x)
610- shifted_extended_evaluation = shift_polynomialcoeff(extended_evaluations_fft, shift_factor)
611- # Compute Z(k*x)
612- shifted_zero_poly = shift_polynomialcoeff(zero_poly_coeff, shift_factor)
619+ # Compute (E*Z)(x) = E(x) * Z(x) in evaluation form over the FFT domain
620+ extended_evaluation_times_zero = [BLSFieldElement(int (a) * int (b) % BLS_MODULUS )
621+ for a, b in zip (zero_poly_eval, extended_evaluation)]
613622
614- eval_shifted_extended_evaluation = fft_field(shifted_extended_evaluation, roots_of_unity_extended)
615- eval_shifted_zero_poly = fft_field(shifted_zero_poly , roots_of_unity_extended)
623+ # Convert (E*Z)(x) to monomial form
624+ extended_evaluation_times_zero_coeffs = fft_field(extended_evaluation_times_zero , roots_of_unity_extended, inv = True )
616625
617- return eval_shifted_extended_evaluation, eval_shifted_zero_poly, shift_inv
618- ```
626+ # Convert (E*Z)(x) to evaluation form over a coset of the FFT domain
627+ extended_evaluations_over_coset = coset_fft_field(extended_evaluation_times_zero_coeffs, roots_of_unity_extended)
619628
620- ### ` recover_original_data `
629+ # Convert Z(x) to evaluation form over a coset of the FFT domain
630+ zero_poly_over_coset = coset_fft_field(zero_poly_coeff, roots_of_unity_extended)
621631
622- ``` python
623- def recover_original_data (eval_shifted_extended_evaluation : Sequence[BLSFieldElement],
624- eval_shifted_zero_poly : Sequence[BLSFieldElement],
625- shift_inv : BLSFieldElement,
626- roots_of_unity_extended : Sequence[BLSFieldElement]) -> Sequence[BLSFieldElement]:
627- """
628- Given Q_1, Q_2 and k^{-1}, compute P(x).
629- """
630- # Compute Q_3 = Q_1(x)/Q_2(x) = P(k*x)
631- eval_shifted_reconstructed_poly = [
632+ # Compute Q_3(x) = (E*Z)(x) / Z(x) in evaluation form over a coset of the FFT domain
633+ reconstructed_poly_over_coset = [
632634 div(a, b)
633- for a, b in zip (eval_shifted_extended_evaluation, eval_shifted_zero_poly )
635+ for a, b in zip (extended_evaluations_over_coset, zero_poly_over_coset )
634636 ]
635637
636- shifted_reconstructed_poly = fft_field(eval_shifted_reconstructed_poly, roots_of_unity_extended, inv = True )
637-
638- # Unshift P(k*x) by k^{-1} to get P(x)
639- reconstructed_poly = shift_polynomialcoeff(shifted_reconstructed_poly, shift_inv)
638+ # Convert Q_3(x) to monomial form
639+ reconstructed_poly_coeff = coset_fft_field(reconstructed_poly_over_coset, roots_of_unity_extended, inv = True )
640640
641- reconstructed_data = bit_reversal_permutation(fft_field(reconstructed_poly, roots_of_unity_extended))
641+ # Convert Q_3(x) to evaluation form over the FFT domain and bit reverse the result
642+ reconstructed_data = bit_reversal_permutation(fft_field(reconstructed_poly_coeff, roots_of_unity_extended))
642643
643644 return reconstructed_data
644645```
@@ -677,30 +678,10 @@ def recover_cells_and_kzg_proofs(cell_indices: Sequence[CellIndex],
677678 for proof_bytes in proofs_bytes:
678679 assert len (proof_bytes) == BYTES_PER_PROOF
679680
680- # Get the extended domain
681- roots_of_unity_extended = compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB )
682-
683681 # Convert cells to coset evals
684682 cosets_evals = [cell_to_coset_evals(cell) for cell in cells]
685683
686- missing_cell_indices = [CellIndex(cell_index) for cell_index in range (CELLS_PER_EXT_BLOB )
687- if cell_index not in cell_indices]
688- zero_poly_coeff, zero_poly_eval = construct_vanishing_polynomial(missing_cell_indices)
689-
690- eval_shifted_extended_evaluation, eval_shifted_zero_poly, shift_inv = recover_shifted_data(
691- cell_indices,
692- cosets_evals,
693- zero_poly_eval,
694- zero_poly_coeff,
695- roots_of_unity_extended,
696- )
697-
698- reconstructed_data = recover_original_data(
699- eval_shifted_extended_evaluation,
700- eval_shifted_zero_poly,
701- shift_inv,
702- roots_of_unity_extended,
703- )
684+ reconstructed_data = recover_data(cell_indices, cosets_evals)
704685
705686 for cell_index, coset_evals in zip (cell_indices, cosets_evals):
706687 start = cell_index * FIELD_ELEMENTS_PER_CELL
0 commit comments