Skip to content

Commit 6efab19

Browse files
kevaundrayjtraglia
andauthored
chore: Refactor cell recovery code (#3781)
* multi: - Remove shift_polynomial_coeff - Remove recover_shifted_data - Remove recover_original_data - Move `zero_poly_eval_brp ` under sanity check comment as its only used for sanity checking * chore: remove sanity check -- this was doing a wasteful `compute_root_of_unity` operation * chore: add previous sanity check as a unit test * chore: copy values python was taking a reference, so it passes in our regular codepaths but not in isolated test * chore: add coset_fft test * Update specs/_features/eip7594/polynomial-commitments-sampling.md Co-authored-by: Justin Traglia <[email protected]> * Update specs/_features/eip7594/polynomial-commitments-sampling.md Co-authored-by: Justin Traglia <[email protected]> * chore: linter * chore: asn (switch to bls_modular_inverse) * chore: (ben) rename func to test_construct_vanishing_polynomial * chore: (ben) rename `extended_evaluations_coeffs` to `extended_evaluation_times_zero_coeffs` * chore: compute `roots_of_unity_extended` in recover_data method * chore: add more comments explaining whats happening in recover_data * chore: compute_zero_poly_coeff in recover_data * chore: make lint * chore: add doc comment to coset_fft_field * chore: (ben) add code to generate the vanishing polynomial when all cells are missing * chore: remove handling of edge case when constructing a vanishing polynomial * chore: rename H(x) to Q_3(x) * chore: remove trailing whitespace * chore: add whitespace between comments * chore: (asn) add assert that num missing cells is not 0 * chore: (justin) address comments * chore: merge resolution * chore: fixup remaining IDs -> indices * chore: use indice nomenclature in tests --------- Co-authored-by: Justin Traglia <[email protected]>
1 parent 973f9cd commit 6efab19

File tree

2 files changed

+130
-108
lines changed

2 files changed

+130
-108
lines changed

specs/_features/eip7594/polynomial-commitments-sampling.md

Lines changed: 89 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
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)
@@ -44,8 +44,7 @@
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

6463
The 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

tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,47 @@ def test_fft(spec):
2828
assert poly_coeff_inversed == poly_coeff
2929

3030

31+
@with_eip7594_and_later
32+
@spec_test
33+
@single_phase
34+
def test_coset_fft(spec):
35+
rng = random.Random(5566)
36+
37+
roots_of_unity = spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_BLOB)
38+
39+
poly_coeff = [rng.randint(0, BLS_MODULUS - 1) for _ in range(spec.FIELD_ELEMENTS_PER_BLOB)]
40+
41+
poly_eval = spec.coset_fft_field(poly_coeff, roots_of_unity)
42+
poly_coeff_inversed = spec.coset_fft_field(poly_eval, roots_of_unity, inv=True)
43+
44+
assert len(poly_eval) == len(poly_coeff) == len(poly_coeff_inversed)
45+
assert poly_coeff_inversed == poly_coeff
46+
47+
48+
@with_eip7594_and_later
49+
@spec_test
50+
@single_phase
51+
def test_construct_vanishing_polynomial(spec):
52+
rng = random.Random(5566)
53+
54+
num_missing_cells = rng.randint(0, spec.CELLS_PER_EXT_BLOB - 1)
55+
# Get a unique list of `num_missing_cells` cell indices
56+
unique_missing_cell_indices = rng.sample(range(spec.CELLS_PER_EXT_BLOB), num_missing_cells)
57+
58+
zero_poly_coeff = spec.construct_vanishing_polynomial(unique_missing_cell_indices)
59+
roots_of_unity = spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_EXT_BLOB)
60+
zero_poly_eval = spec.fft_field(zero_poly_coeff, roots_of_unity)
61+
zero_poly_eval_brp = spec.bit_reversal_permutation(zero_poly_eval)
62+
63+
for cell_index in range(spec.CELLS_PER_EXT_BLOB):
64+
start = cell_index * spec.FIELD_ELEMENTS_PER_CELL
65+
end = (cell_index + 1) * spec.FIELD_ELEMENTS_PER_CELL
66+
if cell_index in unique_missing_cell_indices:
67+
assert all(a == 0 for a in zero_poly_eval_brp[start:end])
68+
else: # cell_index in cell_indices
69+
assert all(a != 0 for a in zero_poly_eval_brp[start:end])
70+
71+
3172
@with_eip7594_and_later
3273
@spec_test
3374
@single_phase

0 commit comments

Comments
 (0)