|
42 | 42 | - [`verify_cell_proof`](#verify_cell_proof) |
43 | 43 | - [`verify_cell_proof_batch`](#verify_cell_proof_batch) |
44 | 44 | - [Reconstruction](#reconstruction) |
| 45 | + - [`construct_vanishing_polynomial`](#construct_vanishing_polynomial) |
| 46 | + - [`recover_shifted_data`](#recover_shifted_data) |
| 47 | + - [`recover_original_data`](#recover_original_data) |
45 | 48 | - [`recover_polynomial`](#recover_polynomial) |
46 | 49 |
|
47 | 50 | <!-- 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 |
76 | 79 |
|
77 | 80 | | Name | Value | Description | |
78 | 81 | | - | - | - | |
| 82 | +| `FIELD_ELEMENTS_PER_EXT_BLOB` | `2 * FIELD_ELEMENTS_PER_BLOB` | Number of field elements in a Reed-Solomon extended blob | |
79 | 83 | | `FIELD_ELEMENTS_PER_CELL` | `uint64(64)` | Number of field elements in a cell | |
80 | 84 | | `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 | |
82 | 86 | | `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` | `b'RCKZGCBATCH__V1_'` | |
83 | 87 |
|
84 | 88 | ## Helper functions |
@@ -352,7 +356,7 @@ def coset_for_cell(cell_id: CellID) -> Cell: |
352 | 356 | """ |
353 | 357 | assert cell_id < CELLS_PER_BLOB |
354 | 358 | 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) |
356 | 360 | ) |
357 | 361 | return Cell(roots_of_unity_brp[FIELD_ELEMENTS_PER_CELL * cell_id:FIELD_ELEMENTS_PER_CELL * (cell_id + 1)]) |
358 | 362 | ``` |
@@ -402,7 +406,7 @@ def compute_cells(blob: Blob) -> Vector[Cell, CELLS_PER_BLOB]: |
402 | 406 | polynomial_coeff = polynomial_eval_to_coeff(polynomial) |
403 | 407 |
|
404 | 408 | 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)) |
406 | 410 | extended_data_rbo = bit_reversal_permutation(extended_data) |
407 | 411 | return [extended_data_rbo[i * FIELD_ELEMENTS_PER_CELL:(i + 1) * FIELD_ELEMENTS_PER_CELL] |
408 | 412 | for i in range(CELLS_PER_BLOB)] |
@@ -471,84 +475,159 @@ def verify_cell_proof_batch(row_commitments_bytes: Sequence[Bytes48], |
471 | 475 |
|
472 | 476 | ## Reconstruction |
473 | 477 |
|
474 | | -### `recover_polynomial` |
| 478 | +### `construct_vanishing_polynomial` |
475 | 479 |
|
476 | 480 | ```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]]: |
479 | 484 | """ |
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. |
487 | 487 | """ |
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 |
494 | 489 | 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) |
495 | 492 | 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 |
498 | 495 | ]) |
499 | 496 |
|
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 |
505 | 501 |
|
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)) |
508 | 505 | 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): |
514 | 509 | start = cell_id * FIELD_ELEMENTS_PER_CELL |
515 | 510 | 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` |
517 | 520 |
|
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 |
519 | 537 | for cell_id, cell in zip(cell_ids, cells): |
520 | 538 | start = cell_id * FIELD_ELEMENTS_PER_CELL |
521 | 539 | end = (cell_id + 1) * FIELD_ELEMENTS_PER_CELL |
522 | 540 | extended_evaluation_rbo[start:end] = cell |
523 | 541 | extended_evaluation = bit_reversal_permutation(extended_evaluation_rbo) |
524 | 542 |
|
| 543 | + # Compute (E*Z)(x) |
525 | 544 | extended_evaluation_times_zero = [BLSFieldElement(int(a) * int(b) % BLS_MODULUS) |
526 | 545 | for a, b in zip(zero_poly_eval, extended_evaluation)] |
527 | 546 |
|
528 | | - roots_of_unity_extended = compute_roots_of_unity(2 * FIELD_ELEMENTS_PER_BLOB) |
529 | | - |
530 | 547 | extended_evaluations_fft = fft_field(extended_evaluation_times_zero, roots_of_unity_extended, inv=True) |
531 | 548 |
|
532 | | - shift_factor = BLSFieldElement(PRIMITIVE_ROOT_OF_UNITY) |
533 | | - shift_inv = div(BLSFieldElement(1), shift_factor) |
534 | | - |
| 549 | + # Compute (E*Z)(k*x) |
535 | 550 | 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) |
537 | 553 |
|
538 | 554 | eval_shifted_extended_evaluation = fft_field(shifted_extended_evaluation, roots_of_unity_extended) |
539 | 555 | eval_shifted_zero_poly = fft_field(shifted_zero_poly, roots_of_unity_extended) |
540 | 556 |
|
| 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) |
541 | 571 | eval_shifted_reconstructed_poly = [ |
542 | 572 | div(a, b) |
543 | 573 | for a, b in zip(eval_shifted_extended_evaluation, eval_shifted_zero_poly) |
544 | 574 | ] |
545 | 575 |
|
546 | 576 | shifted_reconstructed_poly = fft_field(eval_shifted_reconstructed_poly, roots_of_unity_extended, inv=True) |
547 | 577 |
|
| 578 | + # Unshift P(k*x) by k^{-1} to get P(x) |
548 | 579 | reconstructed_poly = shift_polynomialcoeff(shifted_reconstructed_poly, shift_inv) |
549 | 580 |
|
550 | 581 | reconstructed_data = bit_reversal_permutation(fft_field(reconstructed_poly, roots_of_unity_extended)) |
551 | 582 |
|
| 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 | + |
552 | 631 | for cell_id, cell in zip(cell_ids, cells): |
553 | 632 | start = cell_id * FIELD_ELEMENTS_PER_CELL |
554 | 633 | end = (cell_id + 1) * FIELD_ELEMENTS_PER_CELL |
|
0 commit comments