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 )
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
431594def 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