Skip to content

Commit 2faa44b

Browse files
authored
Merge pull request #3591 from asn-d6/peerdas_refactor_recovery_polynomial
peerDAS: Initial refactor of recover_polynomial()
2 parents daf6a0a + 90afb23 commit 2faa44b

File tree

1 file changed

+120
-41
lines changed

1 file changed

+120
-41
lines changed

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

Lines changed: 120 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
- [`verify_cell_proof`](#verify_cell_proof)
4343
- [`verify_cell_proof_batch`](#verify_cell_proof_batch)
4444
- [Reconstruction](#reconstruction)
45+
- [`construct_vanishing_polynomial`](#construct_vanishing_polynomial)
46+
- [`recover_shifted_data`](#recover_shifted_data)
47+
- [`recover_original_data`](#recover_original_data)
4548
- [`recover_polynomial`](#recover_polynomial)
4649

4750
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -76,9 +79,10 @@ Cells are the smallest unit of blob data that can come with their own KZG proofs
7679

7780
| Name | Value | Description |
7881
| - | - | - |
82+
| `FIELD_ELEMENTS_PER_EXT_BLOB` | `2 * FIELD_ELEMENTS_PER_BLOB` | Number of field elements in a Reed-Solomon extended blob |
7983
| `FIELD_ELEMENTS_PER_CELL` | `uint64(64)` | Number of field elements in a cell |
8084
| `BYTES_PER_CELL` | `FIELD_ELEMENTS_PER_CELL * BYTES_PER_FIELD_ELEMENT` | The number of bytes in a cell |
81-
| `CELLS_PER_BLOB` | `((2 * FIELD_ELEMENTS_PER_BLOB) // FIELD_ELEMENTS_PER_CELL)` | The number of cells in a blob |
85+
| `CELLS_PER_BLOB` | `FIELD_ELEMENTS_PER_EXT_BLOB // FIELD_ELEMENTS_PER_CELL` | The number of cells in a blob |
8286
| `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` | `b'RCKZGCBATCH__V1_'` |
8387

8488
## Helper functions
@@ -352,7 +356,7 @@ def coset_for_cell(cell_id: CellID) -> Cell:
352356
"""
353357
assert cell_id < CELLS_PER_BLOB
354358
roots_of_unity_brp = bit_reversal_permutation(
355-
compute_roots_of_unity(2 * FIELD_ELEMENTS_PER_BLOB)
359+
compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB)
356360
)
357361
return Cell(roots_of_unity_brp[FIELD_ELEMENTS_PER_CELL * cell_id:FIELD_ELEMENTS_PER_CELL * (cell_id + 1)])
358362
```
@@ -402,7 +406,7 @@ def compute_cells(blob: Blob) -> Vector[Cell, CELLS_PER_BLOB]:
402406
polynomial_coeff = polynomial_eval_to_coeff(polynomial)
403407

404408
extended_data = fft_field(polynomial_coeff + [0] * FIELD_ELEMENTS_PER_BLOB,
405-
compute_roots_of_unity(2 * FIELD_ELEMENTS_PER_BLOB))
409+
compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB))
406410
extended_data_rbo = bit_reversal_permutation(extended_data)
407411
return [extended_data_rbo[i * FIELD_ELEMENTS_PER_CELL:(i + 1) * FIELD_ELEMENTS_PER_CELL]
408412
for i in range(CELLS_PER_BLOB)]
@@ -471,84 +475,159 @@ def verify_cell_proof_batch(row_commitments_bytes: Sequence[Bytes48],
471475

472476
## Reconstruction
473477

474-
### `recover_polynomial`
478+
### `construct_vanishing_polynomial`
475479

476480
```python
477-
def recover_polynomial(cell_ids: Sequence[CellID],
478-
cells_bytes: Sequence[Vector[Bytes32, FIELD_ELEMENTS_PER_CELL]]) -> Polynomial:
481+
def construct_vanishing_polynomial(missing_cell_ids: Sequence[CellID]) -> Tuple[
482+
Sequence[BLSFieldElement],
483+
Sequence[BLSFieldElement]]:
479484
"""
480-
Recovers a polynomial from 2 * FIELD_ELEMENTS_PER_CELL evaluations, half of which can be missing.
481-
482-
This algorithm uses FFTs to recover cells faster than using Lagrange implementation. However,
483-
a faster version thanks to Qi Zhou can be found here:
484-
https://github.com/ethereum/research/blob/51b530a53bd4147d123ab3e390a9d08605c2cdb8/polynomial_reconstruction/polynomial_reconstruction_danksharding.py
485-
486-
Public method.
485+
Given the cells that are missing from the data, compute the polynomial that vanishes at every point that
486+
corresponds to a missing field element.
487487
"""
488-
assert len(cell_ids) == len(cells_bytes)
489-
490-
cells = [bytes_to_cell(cell_bytes) for cell_bytes in cells_bytes]
491-
492-
assert len(cells) >= CELLS_PER_BLOB // 2
493-
missing_cell_ids = [cell_id for cell_id in range(CELLS_PER_BLOB) if cell_id not in cell_ids]
488+
# Get the small domain
494489
roots_of_unity_reduced = compute_roots_of_unity(CELLS_PER_BLOB)
490+
491+
# Compute polynomial that vanishes at all the missing cells (over the small domain)
495492
short_zero_poly = vanishing_polynomialcoeff([
496-
roots_of_unity_reduced[reverse_bits(cell_id, CELLS_PER_BLOB)]
497-
for cell_id in missing_cell_ids
493+
roots_of_unity_reduced[reverse_bits(missing_cell_id, CELLS_PER_BLOB)]
494+
for missing_cell_id in missing_cell_ids
498495
])
499496

500-
full_zero_poly = []
501-
for i in short_zero_poly:
502-
full_zero_poly.append(i)
503-
full_zero_poly.extend([0] * (FIELD_ELEMENTS_PER_CELL - 1))
504-
full_zero_poly = full_zero_poly + [0] * (2 * FIELD_ELEMENTS_PER_BLOB - len(full_zero_poly))
497+
# Extend vanishing polynomial to full domain using the closed form of the vanishing polynomial over a coset
498+
zero_poly_coeff = [0] * FIELD_ELEMENTS_PER_EXT_BLOB
499+
for i, coeff in enumerate(short_zero_poly):
500+
zero_poly_coeff[i * FIELD_ELEMENTS_PER_CELL] = coeff
505501

506-
zero_poly_eval = fft_field(full_zero_poly,
507-
compute_roots_of_unity(2 * FIELD_ELEMENTS_PER_BLOB))
502+
# Compute evaluations of the extended vanishing polynomial
503+
zero_poly_eval = fft_field(zero_poly_coeff,
504+
compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB))
508505
zero_poly_eval_brp = bit_reversal_permutation(zero_poly_eval)
509-
for cell_id in missing_cell_ids:
510-
start = cell_id * FIELD_ELEMENTS_PER_CELL
511-
end = (cell_id + 1) * FIELD_ELEMENTS_PER_CELL
512-
assert zero_poly_eval_brp[start:end] == [0] * FIELD_ELEMENTS_PER_CELL
513-
for cell_id in cell_ids:
506+
507+
# Sanity check
508+
for cell_id in range(CELLS_PER_BLOB):
514509
start = cell_id * FIELD_ELEMENTS_PER_CELL
515510
end = (cell_id + 1) * FIELD_ELEMENTS_PER_CELL
516-
assert all(a != 0 for a in zero_poly_eval_brp[start:end])
511+
if cell_id in missing_cell_ids:
512+
assert all(a == 0 for a in zero_poly_eval_brp[start:end])
513+
else: # cell_id in cell_ids
514+
assert all(a != 0 for a in zero_poly_eval_brp[start:end])
515+
516+
return zero_poly_coeff, zero_poly_eval, zero_poly_eval_brp
517+
```
518+
519+
### `recover_shifted_data`
517520

518-
extended_evaluation_rbo = [0] * (FIELD_ELEMENTS_PER_BLOB * 2)
521+
```python
522+
def recover_shifted_data(cell_ids: Sequence[CellID],
523+
cells: Sequence[Cell],
524+
zero_poly_eval: Sequence[BLSFieldElement],
525+
zero_poly_coeff: Sequence[BLSFieldElement],
526+
roots_of_unity_extended: Sequence[BLSFieldElement]) -> Tuple[
527+
Sequence[BLSFieldElement],
528+
Sequence[BLSFieldElement],
529+
BLSFieldElement]:
530+
"""
531+
Given Z(x), return polynomial Q_1(x)=(E*Z)(k*x) and Q_2(x)=Z(k*x) and k^{-1}.
532+
"""
533+
shift_factor = BLSFieldElement(PRIMITIVE_ROOT_OF_UNITY)
534+
shift_inv = div(BLSFieldElement(1), shift_factor)
535+
536+
extended_evaluation_rbo = [0] * FIELD_ELEMENTS_PER_EXT_BLOB
519537
for cell_id, cell in zip(cell_ids, cells):
520538
start = cell_id * FIELD_ELEMENTS_PER_CELL
521539
end = (cell_id + 1) * FIELD_ELEMENTS_PER_CELL
522540
extended_evaluation_rbo[start:end] = cell
523541
extended_evaluation = bit_reversal_permutation(extended_evaluation_rbo)
524542

543+
# Compute (E*Z)(x)
525544
extended_evaluation_times_zero = [BLSFieldElement(int(a) * int(b) % BLS_MODULUS)
526545
for a, b in zip(zero_poly_eval, extended_evaluation)]
527546

528-
roots_of_unity_extended = compute_roots_of_unity(2 * FIELD_ELEMENTS_PER_BLOB)
529-
530547
extended_evaluations_fft = fft_field(extended_evaluation_times_zero, roots_of_unity_extended, inv=True)
531548

532-
shift_factor = BLSFieldElement(PRIMITIVE_ROOT_OF_UNITY)
533-
shift_inv = div(BLSFieldElement(1), shift_factor)
534-
549+
# Compute (E*Z)(k*x)
535550
shifted_extended_evaluation = shift_polynomialcoeff(extended_evaluations_fft, shift_factor)
536-
shifted_zero_poly = shift_polynomialcoeff(full_zero_poly, shift_factor)
551+
# Compute Z(k*x)
552+
shifted_zero_poly = shift_polynomialcoeff(zero_poly_coeff, shift_factor)
537553

538554
eval_shifted_extended_evaluation = fft_field(shifted_extended_evaluation, roots_of_unity_extended)
539555
eval_shifted_zero_poly = fft_field(shifted_zero_poly, roots_of_unity_extended)
540556

557+
return eval_shifted_extended_evaluation, eval_shifted_zero_poly, shift_inv
558+
```
559+
560+
### `recover_original_data`
561+
562+
```python
563+
def recover_original_data(eval_shifted_extended_evaluation: Sequence[BLSFieldElement],
564+
eval_shifted_zero_poly: Sequence[BLSFieldElement],
565+
shift_inv: BLSFieldElement,
566+
roots_of_unity_extended: Sequence[BLSFieldElement]) -> Sequence[BLSFieldElement]:
567+
"""
568+
Given Q_1, Q_2 and k^{-1}, compute P(x).
569+
"""
570+
# Compute Q_3 = Q_1(x)/Q_2(x) = P(k*x)
541571
eval_shifted_reconstructed_poly = [
542572
div(a, b)
543573
for a, b in zip(eval_shifted_extended_evaluation, eval_shifted_zero_poly)
544574
]
545575

546576
shifted_reconstructed_poly = fft_field(eval_shifted_reconstructed_poly, roots_of_unity_extended, inv=True)
547577

578+
# Unshift P(k*x) by k^{-1} to get P(x)
548579
reconstructed_poly = shift_polynomialcoeff(shifted_reconstructed_poly, shift_inv)
549580

550581
reconstructed_data = bit_reversal_permutation(fft_field(reconstructed_poly, roots_of_unity_extended))
551582

583+
return reconstructed_data
584+
```
585+
586+
### `recover_polynomial`
587+
588+
```python
589+
def recover_polynomial(cell_ids: Sequence[CellID],
590+
cells_bytes: Sequence[Vector[Bytes32, FIELD_ELEMENTS_PER_CELL]]) -> Polynomial:
591+
"""
592+
Recover original polynomial from FIELD_ELEMENTS_PER_EXT_BLOB evaluations, half of which can be missing. This
593+
algorithm uses FFTs to recover cells faster than using Lagrange implementation, as can be seen here:
594+
https://ethresear.ch/t/reed-solomon-erasure-code-recovery-in-n-log-2-n-time-with-ffts/3039
595+
596+
A faster version thanks to Qi Zhou can be found here:
597+
https://github.com/ethereum/research/blob/51b530a53bd4147d123ab3e390a9d08605c2cdb8/polynomial_reconstruction/polynomial_reconstruction_danksharding.py
598+
599+
Public method.
600+
"""
601+
assert len(cell_ids) == len(cells_bytes)
602+
# Check we have enough cells to be able to perform the reconstruction
603+
assert CELLS_PER_BLOB / 2 <= len(cell_ids) <= CELLS_PER_BLOB
604+
# Check for duplicates
605+
assert len(cell_ids) == len(set(cell_ids))
606+
607+
# Get the extended domain
608+
roots_of_unity_extended = compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB)
609+
610+
# Convert from bytes to cells
611+
cells = [bytes_to_cell(cell_bytes) for cell_bytes in cells_bytes]
612+
613+
missing_cell_ids = [cell_id for cell_id in range(CELLS_PER_BLOB) if cell_id not in cell_ids]
614+
zero_poly_coeff, zero_poly_eval, zero_poly_eval_brp = construct_vanishing_polynomial(missing_cell_ids)
615+
616+
eval_shifted_extended_evaluation, eval_shifted_zero_poly, shift_inv = recover_shifted_data(
617+
cell_ids,
618+
cells,
619+
zero_poly_eval,
620+
zero_poly_coeff,
621+
roots_of_unity_extended,
622+
)
623+
624+
reconstructed_data = recover_original_data(
625+
eval_shifted_extended_evaluation,
626+
eval_shifted_zero_poly,
627+
shift_inv,
628+
roots_of_unity_extended,
629+
)
630+
552631
for cell_id, cell in zip(cell_ids, cells):
553632
start = cell_id * FIELD_ELEMENTS_PER_CELL
554633
end = (cell_id + 1) * FIELD_ELEMENTS_PER_CELL

0 commit comments

Comments
 (0)