Skip to content

Commit 83da380

Browse files
b-wagnjtraglia
andauthored
EIP7594: Do universal verification in verify_cell_kzg_proof_batch() (#3812)
* restructure verify_cell_kzg_proof_batch a bit * first draft of universal verification equation * add one more empty line to make linter happy * make linter happy * more testcases for verify_cell_kzg_proof_batch * verify_cell_kzg_proof_batch: derive coefficient via hash * rename verify_cell_kzg_proof_batch_challenge -> compute_verify_cell_kzg_proof_batch_challenge * verify_cell_kzg_proof_batch: editorial + some refactoring * Improve documentation and variable naming. * remove k_i from code and doc --------- Co-authored-by: Justin Traglia <[email protected]>
1 parent a3a6c91 commit 83da380

File tree

2 files changed

+279
-30
lines changed

2 files changed

+279
-30
lines changed

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

Lines changed: 189 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- [`_fft_field`](#_fft_field)
2323
- [`fft_field`](#fft_field)
2424
- [`coset_fft_field`](#coset_fft_field)
25+
- [`compute_verify_cell_kzg_proof_batch_challenge`](#compute_verify_cell_kzg_proof_batch_challenge)
2526
- [Polynomials in coefficient form](#polynomials-in-coefficient-form)
2627
- [`polynomial_eval_to_coeff`](#polynomial_eval_to_coeff)
2728
- [`add_polynomialcoeff`](#add_polynomialcoeff)
@@ -34,7 +35,9 @@
3435
- [KZG multiproofs](#kzg-multiproofs)
3536
- [`compute_kzg_proof_multi_impl`](#compute_kzg_proof_multi_impl)
3637
- [`verify_kzg_proof_multi_impl`](#verify_kzg_proof_multi_impl)
38+
- [`verify_cell_kzg_proof_batch_impl`](#verify_cell_kzg_proof_batch_impl)
3739
- [Cell cosets](#cell-cosets)
40+
- [`coset_shift_for_cell`](#coset_shift_for_cell)
3841
- [`coset_for_cell`](#coset_for_cell)
3942
- [Cells](#cells-1)
4043
- [Cell computation](#cell-computation)
@@ -193,17 +196,17 @@ def coset_fft_field(vals: Sequence[BLSFieldElement],
193196
roots_of_unity: Sequence[BLSFieldElement],
194197
inv: bool=False) -> Sequence[BLSFieldElement]:
195198
"""
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
199+
Computes an FFT/IFFT over a coset of the roots of unity.
200+
This is useful for when one wants to divide by a polynomial which
198201
vanishes on one or more elements in the domain.
199202
"""
200203
vals = vals.copy()
201-
204+
202205
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-
"""
206+
"""
207+
Multiply each entry in `vals` by succeeding powers of `factor`
208+
i.e., [vals[0] * factor^0, vals[1] * factor^1, ..., vals[n] * factor^n]
209+
"""
207210
shift = 1
208211
for i in range(len(vals)):
209212
vals[i] = BLSFieldElement((int(vals[i]) * shift) % BLS_MODULUS)
@@ -222,6 +225,52 @@ def coset_fft_field(vals: Sequence[BLSFieldElement],
222225
return fft_field(vals, roots_of_unity, inv)
223226
```
224227

228+
#### `compute_verify_cell_kzg_proof_batch_challenge`
229+
230+
```python
231+
def compute_verify_cell_kzg_proof_batch_challenge(row_commitments: Sequence[KZGCommitment],
232+
row_indices: Sequence[RowIndex],
233+
column_indices: Sequence[ColumnIndex],
234+
cosets_evals: Sequence[CosetEvals],
235+
proofs: Sequence[KZGProof]) -> BLSFieldElement:
236+
"""
237+
Compute a random challenge r used in the universal verification equation.
238+
This is used in verify_cell_kzg_proof_batch_impl.
239+
240+
To compute the challenge, `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` is used as a hash prefix.
241+
"""
242+
# input the domain separator
243+
hashinput = RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN
244+
245+
# input the degree bound of the polynomial
246+
hashinput += int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, KZG_ENDIANNESS)
247+
248+
# input the field elements per cell
249+
hashinput += int.to_bytes(FIELD_ELEMENTS_PER_CELL, 8, KZG_ENDIANNESS)
250+
251+
# input the number of commitments
252+
num_commitments = len(row_commitments)
253+
hashinput += int.to_bytes(num_commitments, 8, KZG_ENDIANNESS)
254+
255+
# input the number of cells
256+
num_cells = len(row_indices)
257+
hashinput += int.to_bytes(num_cells, 8, KZG_ENDIANNESS)
258+
259+
# input all commitments
260+
for commitment in row_commitments:
261+
hashinput += commitment
262+
263+
# input each cell with its indices and proof
264+
for k in range(num_cells):
265+
hashinput += int.to_bytes(row_indices[k], 8, KZG_ENDIANNESS)
266+
hashinput += int.to_bytes(column_indices[k], 8, KZG_ENDIANNESS)
267+
for eval in cosets_evals[k]:
268+
hashinput += bls_field_to_bytes(eval)
269+
hashinput += proofs[k]
270+
271+
return hash_to_bls_field(hashinput)
272+
```
273+
225274
### Polynomials in coefficient form
226275

227276
#### `polynomial_eval_to_coeff`
@@ -362,12 +411,12 @@ def compute_kzg_proof_multi_impl(
362411
"""
363412
Compute a KZG multi-evaluation proof for a set of `k` points.
364413
365-
This is done by committing to the following quotient polynomial:
414+
This is done by committing to the following quotient polynomial:
366415
Q(X) = f(X) - I(X) / Z(X)
367416
Where:
368417
- I(X) is the degree `k-1` polynomial that agrees with f(x) at all `k` points
369418
- Z(X) is the degree `k` polynomial that evaluates to zero on all `k` points
370-
419+
371420
We further note that since the degree of I(X) is less than the degree of Z(X),
372421
the computation can be simplified in monomial form to Q(X) = f(X) / Z(X)
373422
"""
@@ -401,7 +450,7 @@ def verify_kzg_proof_multi_impl(commitment: KZGCommitment,
401450
Q(X) is the quotient polynomial computed by the prover
402451
I(X) is the degree k-1 polynomial that evaluates to `ys` at all `zs`` points
403452
Z(X) is the polynomial that evaluates to zero on all `k` points
404-
453+
405454
The verifier receives the commitments to Q(X) and f(X), so they check the equation
406455
holds by using the following pairing equation:
407456
e([Q(X)]_1, [Z(X)]_2) == e([f(X)]_1 - [I(X)]_1, [1]_2)
@@ -423,14 +472,132 @@ def verify_kzg_proof_multi_impl(commitment: KZGCommitment,
423472
]))
424473
```
425474

475+
#### `verify_cell_kzg_proof_batch_impl`
476+
477+
```python
478+
def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment],
479+
row_indices: Sequence[RowIndex],
480+
column_indices: Sequence[ColumnIndex],
481+
cosets_evals: Sequence[CosetEvals],
482+
proofs: Sequence[KZGProof]) -> bool:
483+
"""
484+
Verify a set of cells, given their corresponding proofs and their coordinates (row_index, column_index) in the blob
485+
matrix. The i-th cell is in row row_indices[i] and in column column_indices[i].
486+
The list of all commitments is provided in row_commitments_bytes.
487+
488+
This function is the internal implementation of verify_cell_kzg_proof_batch.
489+
"""
490+
491+
# The verification equation that we will check is pairing (LL, LR) = pairing (RL, [1]), where
492+
# LL = sum_k r^k proofs[k],
493+
# LR = [s^n]
494+
# RL = RLC - RLI + RLP, where
495+
# RLC = sum_i weights[i] commitments[i]
496+
# RLI = [sum_k r^k interpolation_poly_k(s)]
497+
# RLP = sum_k (r^k * h_k^n) proofs[k]
498+
#
499+
# Here, the variables have the following meaning:
500+
# - k < len(row_indices) is an index iterating over all cells in the input
501+
# - r is a random coefficient, derived from hashing all data provided by the prover
502+
# - s is the secret embedded in the KZG setup
503+
# - n = FIELD_ELEMENTS_PER_CELL is the size of the evaluation domain
504+
# - i ranges over all rows that are touched
505+
# - weights[i] is a weight computed for row i. It depends on r and on which cells are in row i
506+
# - interpolation_poly_k is the interpolation polynomial for the kth cell
507+
# - h_k is the coset shift specifying the evaluation domain of the kth cell
508+
509+
# Preparation
510+
num_cells = len(row_indices)
511+
n = FIELD_ELEMENTS_PER_CELL
512+
num_rows = len(row_commitments)
513+
514+
# Step 1: Compute a challenge r and its powers r^0, ..., r^{num_cells-1}
515+
r = compute_verify_cell_kzg_proof_batch_challenge(
516+
row_commitments,
517+
row_indices,
518+
column_indices,
519+
cosets_evals,
520+
proofs
521+
)
522+
r_powers = compute_powers(r, num_cells)
523+
524+
# Step 2: Compute LL = sum_k r^k proofs[k]
525+
ll = bls.bytes48_to_G1(g1_lincomb(proofs, r_powers))
526+
527+
# Step 3: Compute LR = [s^n]
528+
lr = bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[n])
529+
530+
# Step 4: Compute RL = RLC - RLI + RLP
531+
# Step 4.1: Compute RLC = sum_i weights[i] commitments[i]
532+
# Step 4.1a: Compute weights[i]: the sum of all r^k for which cell k is in row i.
533+
# Note: we do that by iterating over all k and updating the correct weights[i] accordingly
534+
weights = [0] * num_rows
535+
for k in range(num_cells):
536+
i = row_indices[k]
537+
weights[i] = (weights[i] + int(r_powers[k])) % BLS_MODULUS
538+
# Step 4.1b: Linearly combine the weights with the commitments to get RLC
539+
rlc = bls.bytes48_to_G1(g1_lincomb(row_commitments, weights))
540+
541+
# Step 4.2: Compute RLI = [sum_k r^k interpolation_poly_k(s)]
542+
# Note: an efficient implementation would use the IDFT based method explained in the blog post
543+
sum_interp_polys_coeff = [0]
544+
for k in range(num_cells):
545+
interp_poly_coeff = interpolate_polynomialcoeff(coset_for_cell(column_indices[k]), cosets_evals[k])
546+
interp_poly_scaled_coeff = multiply_polynomialcoeff([r_powers[k]], interp_poly_coeff)
547+
sum_interp_polys_coeff = add_polynomialcoeff(sum_interp_polys_coeff, interp_poly_scaled_coeff)
548+
rli = bls.bytes48_to_G1(g1_lincomb(KZG_SETUP_G1_MONOMIAL[:n], sum_interp_polys_coeff))
549+
550+
# Step 4.3: Compute RLP = sum_k (r^k * h_k^n) proofs[k]
551+
weighted_r_powers = []
552+
for k in range(num_cells):
553+
h_k = int(coset_shift_for_cell(column_indices[k]))
554+
h_k_pow = pow(h_k, n, BLS_MODULUS)
555+
wrp = (int(r_powers[k]) * h_k_pow) % BLS_MODULUS
556+
weighted_r_powers.append(wrp)
557+
rlp = bls.bytes48_to_G1(g1_lincomb(proofs, weighted_r_powers))
558+
559+
# Step 4.4: Compute RL = RLC - RLI + RLP
560+
rl = bls.add(rlc, bls.neg(rli))
561+
rl = bls.add(rl, rlp)
562+
563+
# Step 5: Check pairing (LL, LR) = pairing (RL, [1])
564+
return (bls.pairing_check([
565+
[ll, lr],
566+
[rl, bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[0]))],
567+
]))
568+
```
569+
570+
426571
### Cell cosets
427572

573+
#### `coset_shift_for_cell`
574+
575+
```python
576+
def coset_shift_for_cell(cell_index: CellIndex) -> BLSFieldElement:
577+
"""
578+
Get the shift that determines the coset for a given ``cell_index``.
579+
Precisely, consider the group of roots of unity of order FIELD_ELEMENTS_PER_CELL * CELLS_PER_EXT_BLOB.
580+
Let G = {1, g, g^2, ...} denote its subgroup of order FIELD_ELEMENTS_PER_CELL.
581+
Then, the coset is defined as h * G = {h, hg, hg^2, ...} for an element h.
582+
This function returns h.
583+
"""
584+
assert cell_index < CELLS_PER_EXT_BLOB
585+
roots_of_unity_brp = bit_reversal_permutation(
586+
compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB)
587+
)
588+
return roots_of_unity_brp[FIELD_ELEMENTS_PER_CELL * cell_index]
589+
```
590+
428591
#### `coset_for_cell`
429592

430593
```python
431594
def coset_for_cell(cell_index: CellIndex) -> Coset:
432595
"""
433596
Get the coset for a given ``cell_index``.
597+
Precisely, consider the group of roots of unity of order FIELD_ELEMENTS_PER_CELL * CELLS_PER_EXT_BLOB.
598+
Let G = {1, g, g^2, ...} denote its subgroup of order FIELD_ELEMENTS_PER_CELL.
599+
Then, the coset is defined as h * G = {h, hg, hg^2, ...}.
600+
This function, returns the coset.
434601
"""
435602
assert cell_index < CELLS_PER_EXT_BLOB
436603
roots_of_unity_brp = bit_reversal_permutation(
@@ -457,7 +624,7 @@ def compute_cells_and_kzg_proofs(blob: Blob) -> Tuple[
457624
Public method.
458625
"""
459626
assert len(blob) == BYTES_PER_BLOB
460-
627+
461628
polynomial = blob_to_polynomial(blob)
462629
polynomial_coeff = polynomial_eval_to_coeff(polynomial)
463630

@@ -491,7 +658,7 @@ def verify_cell_kzg_proof(commitment_bytes: Bytes48,
491658
assert cell_index < CELLS_PER_EXT_BLOB
492659
assert len(cell) == BYTES_PER_CELL
493660
assert len(proof_bytes) == BYTES_PER_PROOF
494-
661+
495662
coset = coset_for_cell(cell_index)
496663

497664
return verify_kzg_proof_multi_impl(
@@ -511,18 +678,15 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48],
511678
proofs_bytes: Sequence[Bytes48]) -> bool:
512679
"""
513680
Verify a set of cells, given their corresponding proofs and their coordinates (row_index, column_index) in the blob
514-
matrix. The list of all commitments is also provided in row_commitments_bytes.
681+
matrix. The i-th cell is in row = row_indices[i] and in column = column_indices[i].
682+
The list of all commitments is provided in row_commitments_bytes.
515683
516-
This function implements the naive algorithm of checking every cell
517-
individually; an efficient algorithm can be found here:
684+
This function implements the universal verification equation that has been introduced here:
518685
https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240
519686
520-
This implementation does not require randomness, but for the algorithm that
521-
requires it, `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` should be used to compute
522-
the challenge value.
523-
524687
Public method.
525688
"""
689+
526690
assert len(cells) == len(proofs_bytes) == len(row_indices) == len(column_indices)
527691
for commitment_bytes in row_commitments_bytes:
528692
assert len(commitment_bytes) == BYTES_PER_COMMITMENT
@@ -535,18 +699,13 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48],
535699
for proof_bytes in proofs_bytes:
536700
assert len(proof_bytes) == BYTES_PER_PROOF
537701

538-
# Get commitments via row indices
539-
commitments_bytes = [row_commitments_bytes[row_index] for row_index in row_indices]
540-
541702
# Get objects from bytes
542-
commitments = [bytes_to_kzg_commitment(commitment_bytes) for commitment_bytes in commitments_bytes]
703+
row_commitments = [bytes_to_kzg_commitment(commitment_bytes) for commitment_bytes in row_commitments_bytes]
543704
cosets_evals = [cell_to_coset_evals(cell) for cell in cells]
544705
proofs = [bytes_to_kzg_proof(proof_bytes) for proof_bytes in proofs_bytes]
545706

546-
return all(
547-
verify_kzg_proof_multi_impl(commitment, coset_for_cell(column_index), coset_evals, proof)
548-
for commitment, column_index, coset_evals, proof in zip(commitments, column_indices, cosets_evals, proofs)
549-
)
707+
# Do the actual verification
708+
return verify_cell_kzg_proof_batch_impl(row_commitments, row_indices, column_indices, cosets_evals, proofs)
550709
```
551710

552711
## Reconstruction
@@ -558,7 +717,7 @@ def construct_vanishing_polynomial(missing_cell_indices: Sequence[CellIndex]) ->
558717
"""
559718
Given the cells indices that are missing from the data, compute the polynomial that vanishes at every point that
560719
corresponds to a missing field element.
561-
720+
562721
This method assumes that all of the cells cannot be missing. In this case the vanishing polynomial
563722
could be computed as Z(x) = x^n - 1, where `n` is FIELD_ELEMENTS_PER_EXT_BLOB.
564723
@@ -617,7 +776,7 @@ def recover_data(cell_indices: Sequence[CellIndex],
617776
extended_evaluation_times_zero = [BLSFieldElement(int(a) * int(b) % BLS_MODULUS)
618777
for a, b in zip(zero_poly_eval, extended_evaluation)]
619778

620-
# Convert (E*Z)(x) to monomial form
779+
# Convert (E*Z)(x) to monomial form
621780
extended_evaluation_times_zero_coeffs = fft_field(extended_evaluation_times_zero, roots_of_unity_extended, inv=True)
622781

623782
# Convert (E*Z)(x) to evaluation form over a coset of the FFT domain
@@ -684,7 +843,7 @@ def recover_cells_and_kzg_proofs(cell_indices: Sequence[CellIndex],
684843
recovered_cells = [
685844
coset_evals_to_cell(reconstructed_data[i * FIELD_ELEMENTS_PER_CELL:(i + 1) * FIELD_ELEMENTS_PER_CELL])
686845
for i in range(CELLS_PER_EXT_BLOB)]
687-
846+
688847
polynomial_eval = reconstructed_data[:FIELD_ELEMENTS_PER_BLOB]
689848
polynomial_coeff = polynomial_eval_to_coeff(polynomial_eval)
690849
recovered_proofs = [None] * CELLS_PER_EXT_BLOB

0 commit comments

Comments
 (0)