From ac15a2b5eab896e6de89d7ca252c70f09c34df6e Mon Sep 17 00:00:00 2001 From: b-wagn Date: Fri, 21 Jun 2024 10:57:09 +0200 Subject: [PATCH 01/10] restructure verify_cell_kzg_proof_batch a bit --- .../polynomial-commitments-sampling.md | 76 ++++++++++++------- .../test_polynomial_commitments.py | 18 +++++ 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index 7be1a4a059..a194092188 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -34,6 +34,7 @@ - [KZG multiproofs](#kzg-multiproofs) - [`compute_kzg_proof_multi_impl`](#compute_kzg_proof_multi_impl) - [`verify_kzg_proof_multi_impl`](#verify_kzg_proof_multi_impl) + - [`verify_cell_kzg_proof_batch_impl`](#verify_cell_kzg_proof_batch_impl) - [Cell cosets](#cell-cosets) - [`coset_for_cell`](#coset_for_cell) - [Cells](#cells-1) @@ -193,17 +194,17 @@ def coset_fft_field(vals: Sequence[BLSFieldElement], roots_of_unity: Sequence[BLSFieldElement], inv: bool=False) -> Sequence[BLSFieldElement]: """ - Computes an FFT/IFFT over a coset of the roots of unity. - This is useful for when one wants to divide by a polynomial which + Computes an FFT/IFFT over a coset of the roots of unity. + This is useful for when one wants to divide by a polynomial which vanishes on one or more elements in the domain. """ vals = vals.copy() - + def shift_vals(vals: Sequence[BLSFieldElement], factor: BLSFieldElement) -> Sequence[BLSFieldElement]: - """ - Multiply each entry in `vals` by succeeding powers of `factor` - i.e., [vals[0] * factor^0, vals[1] * factor^1, ..., vals[n] * factor^n] - """ + """ + Multiply each entry in `vals` by succeeding powers of `factor` + i.e., [vals[0] * factor^0, vals[1] * factor^1, ..., vals[n] * factor^n] + """ shift = 1 for i in range(len(vals)): vals[i] = BLSFieldElement((int(vals[i]) * shift) % BLS_MODULUS) @@ -362,12 +363,12 @@ def compute_kzg_proof_multi_impl( """ Compute a KZG multi-evaluation proof for a set of `k` points. - This is done by committing to the following quotient polynomial: + This is done by committing to the following quotient polynomial: Q(X) = f(X) - I(X) / Z(X) Where: - I(X) is the degree `k-1` polynomial that agrees with f(x) at all `k` points - Z(X) is the degree `k` polynomial that evaluates to zero on all `k` points - + We further note that since the degree of I(X) is less than the degree of Z(X), the computation can be simplified in monomial form to Q(X) = f(X) / Z(X) """ @@ -401,7 +402,7 @@ def verify_kzg_proof_multi_impl(commitment: KZGCommitment, Q(X) is the quotient polynomial computed by the prover I(X) is the degree k-1 polynomial that evaluates to `ys` at all `zs`` points Z(X) is the polynomial that evaluates to zero on all `k` points - + The verifier receives the commitments to Q(X) and f(X), so they check the equation holds by using the following pairing equation: e([Q(X)]_1, [Z(X)]_2) == e([f(X)]_1 - [I(X)]_1, [1]_2) @@ -423,6 +424,31 @@ def verify_kzg_proof_multi_impl(commitment: KZGCommitment, ])) ``` +#### `verify_cell_kzg_proof_batch_impl` + +```python +def verify_cell_kzg_proof_batch_impl(commitments: Sequence[KZGCommitment], + row_indices: Sequence[RowIndex], + column_indices: Sequence[ColumnIndex], + cosets_evals: Sequence[CosetEvals], + proofs: Sequence[KZGProof]): + """ + Verify a set of cells, given their corresponding proofs and their coordinates (row_index, column_index) in the blob + matrix. The i-th cell is in row row_indices[i] and in column column_indices[i]. + The list of all commitments is provided in row_commitments_bytes. + + This function implements the universal verification equation introduced here: + https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 + """ + + ## Currently still a naive solution + return all( + verify_kzg_proof_multi_impl(commitment, coset_for_cell(column_index), coset_evals, proof) + for commitment, column_index, coset_evals, proof in zip(commitments, column_indices, cosets_evals, proofs) + ) +``` + + ### Cell cosets #### `coset_for_cell` @@ -457,7 +483,7 @@ def compute_cells_and_kzg_proofs(blob: Blob) -> Tuple[ Public method. """ assert len(blob) == BYTES_PER_BLOB - + polynomial = blob_to_polynomial(blob) polynomial_coeff = polynomial_eval_to_coeff(polynomial) @@ -491,7 +517,7 @@ def verify_cell_kzg_proof(commitment_bytes: Bytes48, assert cell_index < CELLS_PER_EXT_BLOB assert len(cell) == BYTES_PER_CELL assert len(proof_bytes) == BYTES_PER_PROOF - + coset = coset_for_cell(cell_index) return verify_kzg_proof_multi_impl( @@ -511,18 +537,12 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48], proofs_bytes: Sequence[Bytes48]) -> bool: """ Verify a set of cells, given their corresponding proofs and their coordinates (row_index, column_index) in the blob - matrix. The list of all commitments is also provided in row_commitments_bytes. - - This function implements the naive algorithm of checking every cell - individually; an efficient algorithm can be found here: - https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 - - This implementation does not require randomness, but for the algorithm that - requires it, `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` should be used to compute - the challenge value. + matrix. The i-th cell is in row row_indices[i] and in column column_indices[i]. + The list of all commitments is provided in row_commitments_bytes. Public method. """ + assert len(cells) == len(proofs_bytes) == len(row_indices) == len(column_indices) for commitment_bytes in row_commitments_bytes: assert len(commitment_bytes) == BYTES_PER_COMMITMENT @@ -543,10 +563,8 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48], cosets_evals = [cell_to_coset_evals(cell) for cell in cells] proofs = [bytes_to_kzg_proof(proof_bytes) for proof_bytes in proofs_bytes] - return all( - verify_kzg_proof_multi_impl(commitment, coset_for_cell(column_index), coset_evals, proof) - for commitment, column_index, coset_evals, proof in zip(commitments, column_indices, cosets_evals, proofs) - ) + # Do the actual verification + return verify_cell_kzg_proof_batch_impl(commitments, row_indices, column_indices, cosets_evals, proofs) ``` ## Reconstruction @@ -558,7 +576,7 @@ def construct_vanishing_polynomial(missing_cell_indices: Sequence[CellIndex]) -> """ Given the cells indices that are missing from the data, compute the polynomial that vanishes at every point that corresponds to a missing field element. - + This method assumes that all of the cells cannot be missing. In this case the vanishing polynomial could be computed as Z(x) = x^n - 1, where `n` is FIELD_ELEMENTS_PER_EXT_BLOB. @@ -617,7 +635,7 @@ def recover_data(cell_indices: Sequence[CellIndex], extended_evaluation_times_zero = [BLSFieldElement(int(a) * int(b) % BLS_MODULUS) for a, b in zip(zero_poly_eval, extended_evaluation)] - # Convert (E*Z)(x) to monomial form + # Convert (E*Z)(x) to monomial form extended_evaluation_times_zero_coeffs = fft_field(extended_evaluation_times_zero, roots_of_unity_extended, inv=True) # Convert (E*Z)(x) to evaluation form over a coset of the FFT domain @@ -688,7 +706,7 @@ def recover_cells_and_kzg_proofs(cell_indices: Sequence[CellIndex], recovered_cells = [ coset_evals_to_cell(reconstructed_data[i * FIELD_ELEMENTS_PER_CELL:(i + 1) * FIELD_ELEMENTS_PER_CELL]) for i in range(CELLS_PER_EXT_BLOB)] - + polynomial_eval = reconstructed_data[:FIELD_ELEMENTS_PER_BLOB] polynomial_coeff = polynomial_eval_to_coeff(polynomial_eval) recovered_proofs = [None] * CELLS_PER_EXT_BLOB @@ -700,6 +718,6 @@ def recover_cells_and_kzg_proofs(cell_indices: Sequence[CellIndex], proof, ys = compute_kzg_proof_multi_impl(polynomial_coeff, coset) assert coset_evals_to_cell(ys) == recovered_cells[i] recovered_proofs[i] = proof - + return recovered_cells, recovered_proofs ``` diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py b/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py index 5bc3a4330a..3dddee4333 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py +++ b/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py @@ -140,6 +140,24 @@ def test_verify_cell_kzg_proof_batch(spec): proofs_bytes=[proofs[0], proofs[4]], ) +@with_eip7594_and_later +@spec_test +@single_phase +def test_verify_cell_kzg_proof_batch_invalid(spec): + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + cells, proofs = spec.compute_cells_and_kzg_proofs(blob) + + assert len(cells) == len(proofs) + + assert not spec.verify_cell_kzg_proof_batch( + row_commitments_bytes=[commitment], + row_indices=[0, 0], + column_indices=[0, 4], + cells=[cells[0], cells[5]], + proofs_bytes=[proofs[0], proofs[4]], + ) + @with_eip7594_and_later @spec_test From 084e8cbc566c56c929f691e0ba33614b40c55341 Mon Sep 17 00:00:00 2001 From: b-wagn Date: Fri, 21 Jun 2024 13:08:19 +0200 Subject: [PATCH 02/10] first draft of universal verification equation --- .../polynomial-commitments-sampling.md | 119 ++++++++++++++++-- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index a194092188..c7937f0917 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -22,6 +22,7 @@ - [`_fft_field`](#_fft_field) - [`fft_field`](#fft_field) - [`coset_fft_field`](#coset_fft_field) + - [`verify_cell_kzg_proof_batch_challenge`](#verify_cell_kzg_proof_batch_challenge) - [Polynomials in coefficient form](#polynomials-in-coefficient-form) - [`polynomial_eval_to_coeff`](#polynomial_eval_to_coeff) - [`add_polynomialcoeff`](#add_polynomialcoeff) @@ -36,6 +37,7 @@ - [`verify_kzg_proof_multi_impl`](#verify_kzg_proof_multi_impl) - [`verify_cell_kzg_proof_batch_impl`](#verify_cell_kzg_proof_batch_impl) - [Cell cosets](#cell-cosets) + - [`coset_shift_for_cell`](#coset_shift_for_cell) - [`coset_for_cell`](#coset_for_cell) - [Cells](#cells-1) - [Cell computation](#cell-computation) @@ -223,6 +225,24 @@ def coset_fft_field(vals: Sequence[BLSFieldElement], return fft_field(vals, roots_of_unity, inv) ``` +#### `verify_cell_kzg_proof_batch_challenge` + +```python +def verify_cell_kzg_proof_batch_challenge(row_commitments: Sequence[KZGCommitment], + row_indices: Sequence[RowIndex], + column_indices: Sequence[ColumnIndex], + cosets_evals: Sequence[CosetEvals], + proofs: Sequence[KZGProof]) -> BLSFieldElement: + """ + Compute a random challenge r used in the universal verification equation, see + https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 + + To compute the challenge, `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` should be used as a hash prefix + """ + # TODO + return BLSFieldElement(2) +``` + ### Polynomials in coefficient form #### `polynomial_eval_to_coeff` @@ -427,11 +447,11 @@ def verify_kzg_proof_multi_impl(commitment: KZGCommitment, #### `verify_cell_kzg_proof_batch_impl` ```python -def verify_cell_kzg_proof_batch_impl(commitments: Sequence[KZGCommitment], +def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], row_indices: Sequence[RowIndex], column_indices: Sequence[ColumnIndex], cosets_evals: Sequence[CosetEvals], - proofs: Sequence[KZGProof]): + proofs: Sequence[KZGProof]) -> bool: """ Verify a set of cells, given their corresponding proofs and their coordinates (row_index, column_index) in the blob matrix. The i-th cell is in row row_indices[i] and in column column_indices[i]. @@ -441,16 +461,94 @@ def verify_cell_kzg_proof_batch_impl(commitments: Sequence[KZGCommitment], https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 """ - ## Currently still a naive solution - return all( - verify_kzg_proof_multi_impl(commitment, coset_for_cell(column_index), coset_evals, proof) - for commitment, column_index, coset_evals, proof in zip(commitments, column_indices, cosets_evals, proofs) - ) + # The verification equation that we will check is pairing ( LL, LR ) = pairing (RL, [1]), where + # LL = sum_k r^k proofs[k], + # LR = [s^n] + # RL = RLC - RLI + RLP, with + # RLC = sum_i (sum_{k_i} r^{k_i}) commitments[i] + # RLI = [sum_k r^k * interpolation_poly_k(s)] + # RLP = sum_k r^k * h_k^n * proofs[k] + # + # Here, the variables have the following meaning: + # - k < len(row_indices) is an index iterating over all cells in the input + # - r is a random coefficient, derived from hashing all data provided by the prover + # - s is the secret embedded in the KZG setup + # - n = FIELD_ELEMENTS_PER_CELL is the size of the evaluation domain + # - i ranges over all rows that are touched + # - k_i < len(row_indices) ranges over all cells that are in row i + # - interpolation_poly_k is the interpolation polynomial for the kth cell + # - h_k is the coset shift specifying the evaluation domain of the kth cell + + + # Preparation + l = len(row_indices) + n = FIELD_ELEMENTS_PER_CELL + num_rows = len(row_commitments) + + # Step 1: Derive powers of r, i.e., r^0, ..., r^{l-1}, where l = len(row_indices) + r = int(verify_cell_kzg_proof_batch_challenge(row_commitments, row_indices, column_indices, cosets_evals, proofs)) + current_power = 1 + r_powers = [] + for _ in range(l): + r_powers.append(current_power) + current_power = (current_power * r) % BLS_MODULUS + + # Step 2: Compute LL = sum_k r^k proofs[k] + ll = bls.bytes48_to_G1(g1_lincomb(proofs, r_powers)) + + # Step 3: Compute LR = [s^n] + lr = bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[n]) + + # Step 4: Compute RL = RLC - RLI + RLP + # Step 4.1: Compute RLC = sum_i (sum_{k_i} r^{k_i}) commitments[i] + commitment_weights = [0] * num_rows + for k in range(l): + commitment_weights[row_indices[k]] = (commitment_weights[row_indices[k]] + r_powers[k]) % BLS_MODULUS + rlc = bls.bytes48_to_G1(g1_lincomb(row_commitments, commitment_weights)) + + # Step 4.2: Compute RLI = [sum_k r^k * interpolation_poly_k(s)] + # Note: an efficient implementation would use the IDFT based method explained in the blog post + sum_interp_polys_coeff = [0] + for k in range(l): + interp_poly_coeff = interpolate_polynomialcoeff(coset_for_cell(column_indices[k]), cosets_evals[k]) + interp_poly_scaled_coeff = multiply_polynomialcoeff([r_powers[k]], interp_poly_coeff) + sum_interp_polys_coeff = add_polynomialcoeff(sum_interp_polys_coeff,interp_poly_scaled_coeff) + rli = bls.bytes48_to_G1(g1_lincomb(KZG_SETUP_G1_MONOMIAL[:n], sum_interp_polys_coeff)) + + # Step 4.3: Compute RLP = sum_k r^k * h_k^n * proofs[k] + weighted_r_powers = [] + for k in range(l): + cosetshift = coset_shift_for_cell(column_indices[k]) + cosetshift_pow = pow(cosetshift, n, BLS_MODULUS) + wrp = (r_powers[k] * cosetshift_pow) % BLS_MODULUS + weighted_r_powers.append(wrp) + rlp = bls.bytes48_to_G1(g1_lincomb(proofs, weighted_r_powers)) #TODO: Maybe proofs need to be turned into g1 things first? + + # Step 4.4: Compute RL = RLC - RLI + RLP + rl = bls.add(rlc, bls.neg(rli)) + rl = bls.add(rl, rlp) + + # Step 5: Check pairing (LL, LR) = pairing (RL, [1]) + return (bls.pairing_check([[ll, lr], [rl, bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[0])),],])) ``` ### Cell cosets +#### `coset_shift_for_cell` + +```python +def coset_shift_for_cell(cell_index: CellIndex) -> BLSFieldElement: + """ + Get the shift that determines the coset for a given ``cell_index``. + """ + assert cell_index < CELLS_PER_EXT_BLOB + roots_of_unity_brp = bit_reversal_permutation( + compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB) + ) + return roots_of_unity_brp[FIELD_ELEMENTS_PER_CELL * cell_index] +``` + #### `coset_for_cell` ```python @@ -555,16 +653,13 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48], for proof_bytes in proofs_bytes: assert len(proof_bytes) == BYTES_PER_PROOF - # Get commitments via row indices - commitments_bytes = [row_commitments_bytes[row_index] for row_index in row_indices] - # Get objects from bytes - commitments = [bytes_to_kzg_commitment(commitment_bytes) for commitment_bytes in commitments_bytes] + row_commitments = [bytes_to_kzg_commitment(commitment_bytes) for commitment_bytes in row_commitments_bytes] cosets_evals = [cell_to_coset_evals(cell) for cell in cells] proofs = [bytes_to_kzg_proof(proof_bytes) for proof_bytes in proofs_bytes] # Do the actual verification - return verify_cell_kzg_proof_batch_impl(commitments, row_indices, column_indices, cosets_evals, proofs) + return verify_cell_kzg_proof_batch_impl(row_commitments, row_indices, column_indices, cosets_evals, proofs) ``` ## Reconstruction From 3e86a6c627e21d632cb50f81e68429a829bccd1c Mon Sep 17 00:00:00 2001 From: b-wagn Date: Fri, 21 Jun 2024 13:33:37 +0200 Subject: [PATCH 03/10] add one more empty line to make linter happy --- .../polynomial_commitments/test_polynomial_commitments.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py b/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py index 3dddee4333..7a748d0192 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py +++ b/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py @@ -140,6 +140,7 @@ def test_verify_cell_kzg_proof_batch(spec): proofs_bytes=[proofs[0], proofs[4]], ) + @with_eip7594_and_later @spec_test @single_phase From 53b3944de121c2dd52671dcb98653eb1c6e8c9fc Mon Sep 17 00:00:00 2001 From: b-wagn Date: Fri, 21 Jun 2024 13:53:24 +0200 Subject: [PATCH 04/10] make linter happy --- .../polynomial-commitments-sampling.md | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index c7937f0917..48a52d6cb5 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -479,17 +479,24 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # - interpolation_poly_k is the interpolation polynomial for the kth cell # - h_k is the coset shift specifying the evaluation domain of the kth cell - # Preparation - l = len(row_indices) + num_cells = len(row_indices) n = FIELD_ELEMENTS_PER_CELL num_rows = len(row_commitments) # Step 1: Derive powers of r, i.e., r^0, ..., r^{l-1}, where l = len(row_indices) - r = int(verify_cell_kzg_proof_batch_challenge(row_commitments, row_indices, column_indices, cosets_evals, proofs)) + r = int( + verify_cell_kzg_proof_batch_challenge( + row_commitments, + row_indices, + column_indices, + cosets_evals, + proofs + ) + ) current_power = 1 r_powers = [] - for _ in range(l): + for _ in range(num_cells): r_powers.append(current_power) current_power = (current_power * r) % BLS_MODULUS @@ -502,34 +509,34 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # Step 4: Compute RL = RLC - RLI + RLP # Step 4.1: Compute RLC = sum_i (sum_{k_i} r^{k_i}) commitments[i] commitment_weights = [0] * num_rows - for k in range(l): + for k in range(num_cells): commitment_weights[row_indices[k]] = (commitment_weights[row_indices[k]] + r_powers[k]) % BLS_MODULUS rlc = bls.bytes48_to_G1(g1_lincomb(row_commitments, commitment_weights)) # Step 4.2: Compute RLI = [sum_k r^k * interpolation_poly_k(s)] # Note: an efficient implementation would use the IDFT based method explained in the blog post sum_interp_polys_coeff = [0] - for k in range(l): + for k in range(num_cells): interp_poly_coeff = interpolate_polynomialcoeff(coset_for_cell(column_indices[k]), cosets_evals[k]) interp_poly_scaled_coeff = multiply_polynomialcoeff([r_powers[k]], interp_poly_coeff) - sum_interp_polys_coeff = add_polynomialcoeff(sum_interp_polys_coeff,interp_poly_scaled_coeff) + sum_interp_polys_coeff = add_polynomialcoeff(sum_interp_polys_coeff, interp_poly_scaled_coeff) rli = bls.bytes48_to_G1(g1_lincomb(KZG_SETUP_G1_MONOMIAL[:n], sum_interp_polys_coeff)) # Step 4.3: Compute RLP = sum_k r^k * h_k^n * proofs[k] weighted_r_powers = [] - for k in range(l): + for k in range(num_cells): cosetshift = coset_shift_for_cell(column_indices[k]) cosetshift_pow = pow(cosetshift, n, BLS_MODULUS) wrp = (r_powers[k] * cosetshift_pow) % BLS_MODULUS weighted_r_powers.append(wrp) - rlp = bls.bytes48_to_G1(g1_lincomb(proofs, weighted_r_powers)) #TODO: Maybe proofs need to be turned into g1 things first? + rlp = bls.bytes48_to_G1(g1_lincomb(proofs, weighted_r_powers)) # Step 4.4: Compute RL = RLC - RLI + RLP rl = bls.add(rlc, bls.neg(rli)) rl = bls.add(rl, rlp) # Step 5: Check pairing (LL, LR) = pairing (RL, [1]) - return (bls.pairing_check([[ll, lr], [rl, bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[0])),],])) + return (bls.pairing_check([[ll, lr], [rl, bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[0])), ], ])) ``` From fb4a3f0f3133bd89b3309c8bb642728a87175dc3 Mon Sep 17 00:00:00 2001 From: b-wagn Date: Mon, 24 Jun 2024 10:10:54 +0200 Subject: [PATCH 05/10] more testcases for verify_cell_kzg_proof_batch --- .../polynomial-commitments-sampling.md | 2 +- .../test_polynomial_commitments.py | 73 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index 48a52d6cb5..97a1a0e4aa 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -525,7 +525,7 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # Step 4.3: Compute RLP = sum_k r^k * h_k^n * proofs[k] weighted_r_powers = [] for k in range(num_cells): - cosetshift = coset_shift_for_cell(column_indices[k]) + cosetshift = int(coset_shift_for_cell(column_indices[k])) cosetshift_pow = pow(cosetshift, n, BLS_MODULUS) wrp = (r_powers[k] * cosetshift_pow) % BLS_MODULUS weighted_r_powers.append(wrp) diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py b/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py index 7a748d0192..12a4de06b2 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py +++ b/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py @@ -126,6 +126,8 @@ def test_verify_cell_kzg_proof(spec): @spec_test @single_phase def test_verify_cell_kzg_proof_batch(spec): + + # test with a single blob / commitment blob = get_sample_blob(spec) commitment = spec.blob_to_kzg_commitment(blob) cells, proofs = spec.compute_cells_and_kzg_proofs(blob) @@ -140,11 +142,45 @@ def test_verify_cell_kzg_proof_batch(spec): proofs_bytes=[proofs[0], proofs[4]], ) + # now test with three blobs / commitments + all_blobs = [] + all_commitments = [] + all_cells = [] + all_proofs = [] + for _ in range(3): + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + cells, proofs = spec.compute_cells_and_kzg_proofs(blob) + + assert len(cells) == len(proofs) + + all_blobs.append(blob) + all_commitments.append(commitment) + all_cells.append(cells) + all_proofs.append(proofs) + + # the cells of interest + row_indices = [0, 0, 1, 2, 1] + column_indices = [0, 4, 0, 1, 2] + cells = [all_cells[i][j] for (i, j) in zip(row_indices, column_indices)] + proofs = [all_proofs[i][j] for (i, j) in zip(row_indices, column_indices)] + + # do the check + assert spec.verify_cell_kzg_proof_batch( + row_commitments_bytes=all_commitments, + row_indices=row_indices, + column_indices=column_indices, + cells=cells, + proofs_bytes=proofs, + ) + @with_eip7594_and_later @spec_test @single_phase def test_verify_cell_kzg_proof_batch_invalid(spec): + + # test with a single blob / commitment blob = get_sample_blob(spec) commitment = spec.blob_to_kzg_commitment(blob) cells, proofs = spec.compute_cells_and_kzg_proofs(blob) @@ -155,10 +191,45 @@ def test_verify_cell_kzg_proof_batch_invalid(spec): row_commitments_bytes=[commitment], row_indices=[0, 0], column_indices=[0, 4], - cells=[cells[0], cells[5]], + cells=[cells[0], cells[5]], # Note: this is where it should go wrong proofs_bytes=[proofs[0], proofs[4]], ) + # now test with three blobs / commitments + all_blobs = [] + all_commitments = [] + all_cells = [] + all_proofs = [] + for _ in range(3): + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + cells, proofs = spec.compute_cells_and_kzg_proofs(blob) + + assert len(cells) == len(proofs) + + all_blobs.append(blob) + all_commitments.append(commitment) + all_cells.append(cells) + all_proofs.append(proofs) + + # the cells of interest + row_indices = [0, 0, 1, 2, 1] + column_indices = [0, 4, 0, 1, 2] + cells = [all_cells[i][j] for (i, j) in zip(row_indices, column_indices)] + proofs = [all_proofs[i][j] for (i, j) in zip(row_indices, column_indices)] + + # let's change one of the cells. Then it should not verify + cells[1] = all_cells[1][3] + + # do the check + assert not spec.verify_cell_kzg_proof_batch( + row_commitments_bytes=all_commitments, + row_indices=row_indices, + column_indices=column_indices, + cells=cells, + proofs_bytes=proofs, + ) + @with_eip7594_and_later @spec_test From c21b4cac9b780654f0bc4208c80401140376f553 Mon Sep 17 00:00:00 2001 From: b-wagn Date: Mon, 24 Jun 2024 10:53:21 +0200 Subject: [PATCH 06/10] verify_cell_kzg_proof_batch: derive coefficient via hash --- .../polynomial-commitments-sampling.md | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index 97a1a0e4aa..55860478d8 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -239,8 +239,33 @@ def verify_cell_kzg_proof_batch_challenge(row_commitments: Sequence[KZGCommitmen To compute the challenge, `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` should be used as a hash prefix """ - # TODO - return BLSFieldElement(2) + # input the domain separator + hashinput = RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN + + # input the field elements per cell + hashinput += int.to_bytes(FIELD_ELEMENTS_PER_CELL, 8, KZG_ENDIANNESS) + + # input the number of commitments + num_commitments = len(row_commitments) + hashinput += int.to_bytes(num_commitments, 8, KZG_ENDIANNESS) + + # input the number of cells + num_cells = len(row_indices) + hashinput += int.to_bytes(num_cells, 8, KZG_ENDIANNESS) + + # input all commitments + for commitment in row_commitments: + hashinput += commitment + + # input each cell with its indices and proof + for k in range(num_cells): + hashinput += int.to_bytes(row_indices[k], 8, KZG_ENDIANNESS) + hashinput += int.to_bytes(column_indices[k], 8, KZG_ENDIANNESS) + for eval in cosets_evals[k]: + hashinput += bls_field_to_bytes(eval) + hashinput += proofs[k] + + return hash_to_bls_field(hashinput) ``` ### Polynomials in coefficient form @@ -484,7 +509,7 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], n = FIELD_ELEMENTS_PER_CELL num_rows = len(row_commitments) - # Step 1: Derive powers of r, i.e., r^0, ..., r^{l-1}, where l = len(row_indices) + # Step 1: Derive powers of r, i.e., r^0, ..., r^{num_cells-1} r = int( verify_cell_kzg_proof_batch_challenge( row_commitments, From b43598f339b97ac1933ac92f62ef3e3de997ad48 Mon Sep 17 00:00:00 2001 From: b-wagn Date: Tue, 25 Jun 2024 16:42:05 +0200 Subject: [PATCH 07/10] rename verify_cell_kzg_proof_batch_challenge -> compute_verify_cell_kzg_proof_batch_challenge --- .../eip7594/polynomial-commitments-sampling.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index 55860478d8..72f939e228 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -22,7 +22,7 @@ - [`_fft_field`](#_fft_field) - [`fft_field`](#fft_field) - [`coset_fft_field`](#coset_fft_field) - - [`verify_cell_kzg_proof_batch_challenge`](#verify_cell_kzg_proof_batch_challenge) + - [`compute_verify_cell_kzg_proof_batch_challenge`](#compute_verify_cell_kzg_proof_batch_challenge) - [Polynomials in coefficient form](#polynomials-in-coefficient-form) - [`polynomial_eval_to_coeff`](#polynomial_eval_to_coeff) - [`add_polynomialcoeff`](#add_polynomialcoeff) @@ -225,14 +225,14 @@ def coset_fft_field(vals: Sequence[BLSFieldElement], return fft_field(vals, roots_of_unity, inv) ``` -#### `verify_cell_kzg_proof_batch_challenge` +#### `compute_verify_cell_kzg_proof_batch_challenge` ```python -def verify_cell_kzg_proof_batch_challenge(row_commitments: Sequence[KZGCommitment], - row_indices: Sequence[RowIndex], - column_indices: Sequence[ColumnIndex], - cosets_evals: Sequence[CosetEvals], - proofs: Sequence[KZGProof]) -> BLSFieldElement: +def compute_verify_cell_kzg_proof_batch_challenge(row_commitments: Sequence[KZGCommitment], + row_indices: Sequence[RowIndex], + column_indices: Sequence[ColumnIndex], + cosets_evals: Sequence[CosetEvals], + proofs: Sequence[KZGProof]) -> BLSFieldElement: """ Compute a random challenge r used in the universal verification equation, see https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 @@ -511,7 +511,7 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # Step 1: Derive powers of r, i.e., r^0, ..., r^{num_cells-1} r = int( - verify_cell_kzg_proof_batch_challenge( + compute_verify_cell_kzg_proof_batch_challenge( row_commitments, row_indices, column_indices, From 8f130bde33b5671024bf0717c536f66a589c12fd Mon Sep 17 00:00:00 2001 From: b-wagn Date: Tue, 25 Jun 2024 17:25:09 +0200 Subject: [PATCH 08/10] verify_cell_kzg_proof_batch: editorial + some refactoring --- .../polynomial-commitments-sampling.md | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index 72f939e228..f26754f566 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -486,13 +486,13 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 """ - # The verification equation that we will check is pairing ( LL, LR ) = pairing (RL, [1]), where + # The verification equation that we will check is pairing (LL, LR) = pairing (RL, [1]), where # LL = sum_k r^k proofs[k], # LR = [s^n] - # RL = RLC - RLI + RLP, with + # RL = RLC - RLI + RLP, where # RLC = sum_i (sum_{k_i} r^{k_i}) commitments[i] - # RLI = [sum_k r^k * interpolation_poly_k(s)] - # RLP = sum_k r^k * h_k^n * proofs[k] + # RLI = [sum_k r^k interpolation_poly_k(s)] + # RLP = sum_k (r^k * h_k^n) proofs[k] # # Here, the variables have the following meaning: # - k < len(row_indices) is an index iterating over all cells in the input @@ -509,21 +509,15 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], n = FIELD_ELEMENTS_PER_CELL num_rows = len(row_commitments) - # Step 1: Derive powers of r, i.e., r^0, ..., r^{num_cells-1} - r = int( - compute_verify_cell_kzg_proof_batch_challenge( - row_commitments, - row_indices, - column_indices, - cosets_evals, - proofs - ) + # Step 1: Compute a challenge r and its powers r^0, ..., r^{num_cells-1} + r = compute_verify_cell_kzg_proof_batch_challenge( + row_commitments, + row_indices, + column_indices, + cosets_evals, + proofs ) - current_power = 1 - r_powers = [] - for _ in range(num_cells): - r_powers.append(current_power) - current_power = (current_power * r) % BLS_MODULUS + r_powers = compute_powers(r, num_cells) # Step 2: Compute LL = sum_k r^k proofs[k] ll = bls.bytes48_to_G1(g1_lincomb(proofs, r_powers)) @@ -535,10 +529,10 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # Step 4.1: Compute RLC = sum_i (sum_{k_i} r^{k_i}) commitments[i] commitment_weights = [0] * num_rows for k in range(num_cells): - commitment_weights[row_indices[k]] = (commitment_weights[row_indices[k]] + r_powers[k]) % BLS_MODULUS + commitment_weights[row_indices[k]] = (commitment_weights[row_indices[k]] + int(r_powers[k])) % BLS_MODULUS rlc = bls.bytes48_to_G1(g1_lincomb(row_commitments, commitment_weights)) - # Step 4.2: Compute RLI = [sum_k r^k * interpolation_poly_k(s)] + # Step 4.2: Compute RLI = [sum_k r^k interpolation_poly_k(s)] # Note: an efficient implementation would use the IDFT based method explained in the blog post sum_interp_polys_coeff = [0] for k in range(num_cells): @@ -547,12 +541,12 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], sum_interp_polys_coeff = add_polynomialcoeff(sum_interp_polys_coeff, interp_poly_scaled_coeff) rli = bls.bytes48_to_G1(g1_lincomb(KZG_SETUP_G1_MONOMIAL[:n], sum_interp_polys_coeff)) - # Step 4.3: Compute RLP = sum_k r^k * h_k^n * proofs[k] + # Step 4.3: Compute RLP = sum_k (r^k * h_k^n) proofs[k] weighted_r_powers = [] for k in range(num_cells): cosetshift = int(coset_shift_for_cell(column_indices[k])) cosetshift_pow = pow(cosetshift, n, BLS_MODULUS) - wrp = (r_powers[k] * cosetshift_pow) % BLS_MODULUS + wrp = (int(r_powers[k]) * cosetshift_pow) % BLS_MODULUS weighted_r_powers.append(wrp) rlp = bls.bytes48_to_G1(g1_lincomb(proofs, weighted_r_powers)) @@ -667,9 +661,12 @@ def verify_cell_kzg_proof_batch(row_commitments_bytes: Sequence[Bytes48], proofs_bytes: Sequence[Bytes48]) -> bool: """ Verify a set of cells, given their corresponding proofs and their coordinates (row_index, column_index) in the blob - matrix. The i-th cell is in row row_indices[i] and in column column_indices[i]. + matrix. The i-th cell is in row = row_indices[i] and in column = column_indices[i]. The list of all commitments is provided in row_commitments_bytes. + This function implements the universal verification equation that has been introduced here: + https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 + Public method. """ From 73f3898217f96e7e37133bc7dfe6f18963d1f512 Mon Sep 17 00:00:00 2001 From: b-wagn Date: Thu, 27 Jun 2024 10:26:24 +0200 Subject: [PATCH 09/10] Improve documentation and variable naming. --- .../polynomial-commitments-sampling.md | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index f26754f566..2ce350d527 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -234,14 +234,17 @@ def compute_verify_cell_kzg_proof_batch_challenge(row_commitments: Sequence[KZGC cosets_evals: Sequence[CosetEvals], proofs: Sequence[KZGProof]) -> BLSFieldElement: """ - Compute a random challenge r used in the universal verification equation, see - https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 + Compute a random challenge r used in the universal verification equation. + This is used in verify_cell_kzg_proof_batch_impl. - To compute the challenge, `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` should be used as a hash prefix + To compute the challenge, `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` is used as a hash prefix. """ # input the domain separator hashinput = RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN + # input the degree bound of the polynomial + hashinput += int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, KZG_ENDIANNESS) + # input the field elements per cell hashinput += int.to_bytes(FIELD_ELEMENTS_PER_CELL, 8, KZG_ENDIANNESS) @@ -482,8 +485,7 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], matrix. The i-th cell is in row row_indices[i] and in column column_indices[i]. The list of all commitments is provided in row_commitments_bytes. - This function implements the universal verification equation introduced here: - https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 + This function is the internal implementation of verify_cell_kzg_proof_batch. """ # The verification equation that we will check is pairing (LL, LR) = pairing (RL, [1]), where @@ -527,9 +529,13 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # Step 4: Compute RL = RLC - RLI + RLP # Step 4.1: Compute RLC = sum_i (sum_{k_i} r^{k_i}) commitments[i] + # Step 4.1a: Compute commitment_weights[i] = (sum_{k_i} r^{k_i}) for all rows i + # Note: we do that by iterating over all k_i and updating commitment_weights[i] accordingly commitment_weights = [0] * num_rows - for k in range(num_cells): - commitment_weights[row_indices[k]] = (commitment_weights[row_indices[k]] + int(r_powers[k])) % BLS_MODULUS + for k_i in range(num_cells): + i = row_indices[k_i] + commitment_weights[i] = (commitment_weights[i] + int(r_powers[k_i])) % BLS_MODULUS + # Step 4.1b: Linearly combine the weights with the commitments to get RLC rlc = bls.bytes48_to_G1(g1_lincomb(row_commitments, commitment_weights)) # Step 4.2: Compute RLI = [sum_k r^k interpolation_poly_k(s)] @@ -544,9 +550,9 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # Step 4.3: Compute RLP = sum_k (r^k * h_k^n) proofs[k] weighted_r_powers = [] for k in range(num_cells): - cosetshift = int(coset_shift_for_cell(column_indices[k])) - cosetshift_pow = pow(cosetshift, n, BLS_MODULUS) - wrp = (int(r_powers[k]) * cosetshift_pow) % BLS_MODULUS + h_k = int(coset_shift_for_cell(column_indices[k])) + h_k_pow = pow(h_k, n, BLS_MODULUS) + wrp = (int(r_powers[k]) * h_k_pow) % BLS_MODULUS weighted_r_powers.append(wrp) rlp = bls.bytes48_to_G1(g1_lincomb(proofs, weighted_r_powers)) @@ -555,7 +561,10 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], rl = bls.add(rl, rlp) # Step 5: Check pairing (LL, LR) = pairing (RL, [1]) - return (bls.pairing_check([[ll, lr], [rl, bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[0])), ], ])) + return (bls.pairing_check([ + [ll, lr], + [rl, bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[0]))], + ])) ``` @@ -567,6 +576,10 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], def coset_shift_for_cell(cell_index: CellIndex) -> BLSFieldElement: """ Get the shift that determines the coset for a given ``cell_index``. + Precisely, consider the group of roots of unity of order FIELD_ELEMENTS_PER_CELL * CELLS_PER_EXT_BLOB. + Let G = {1, g, g^2, ...} denote its subgroup of order FIELD_ELEMENTS_PER_CELL. + Then, the coset is defined as h * G = {h, hg, hg^2, ...} for an element h. + This function returns h. """ assert cell_index < CELLS_PER_EXT_BLOB roots_of_unity_brp = bit_reversal_permutation( @@ -581,6 +594,10 @@ def coset_shift_for_cell(cell_index: CellIndex) -> BLSFieldElement: def coset_for_cell(cell_index: CellIndex) -> Coset: """ Get the coset for a given ``cell_index``. + Precisely, consider the group of roots of unity of order FIELD_ELEMENTS_PER_CELL * CELLS_PER_EXT_BLOB. + Let G = {1, g, g^2, ...} denote its subgroup of order FIELD_ELEMENTS_PER_CELL. + Then, the coset is defined as h * G = {h, hg, hg^2, ...}. + This function, returns the coset. """ assert cell_index < CELLS_PER_EXT_BLOB roots_of_unity_brp = bit_reversal_permutation( From 285eb54a47f6a6019194e8bc600b27926cf8cc55 Mon Sep 17 00:00:00 2001 From: b-wagn Date: Thu, 27 Jun 2024 17:12:14 +0200 Subject: [PATCH 10/10] remove k_i from code and doc --- .../polynomial-commitments-sampling.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index 2ce350d527..ae5b5c7185 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/_features/eip7594/polynomial-commitments-sampling.md @@ -492,7 +492,7 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # LL = sum_k r^k proofs[k], # LR = [s^n] # RL = RLC - RLI + RLP, where - # RLC = sum_i (sum_{k_i} r^{k_i}) commitments[i] + # RLC = sum_i weights[i] commitments[i] # RLI = [sum_k r^k interpolation_poly_k(s)] # RLP = sum_k (r^k * h_k^n) proofs[k] # @@ -502,7 +502,7 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], # - s is the secret embedded in the KZG setup # - n = FIELD_ELEMENTS_PER_CELL is the size of the evaluation domain # - i ranges over all rows that are touched - # - k_i < len(row_indices) ranges over all cells that are in row i + # - weights[i] is a weight computed for row i. It depends on r and on which cells are in row i # - interpolation_poly_k is the interpolation polynomial for the kth cell # - h_k is the coset shift specifying the evaluation domain of the kth cell @@ -528,15 +528,15 @@ def verify_cell_kzg_proof_batch_impl(row_commitments: Sequence[KZGCommitment], lr = bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[n]) # Step 4: Compute RL = RLC - RLI + RLP - # Step 4.1: Compute RLC = sum_i (sum_{k_i} r^{k_i}) commitments[i] - # Step 4.1a: Compute commitment_weights[i] = (sum_{k_i} r^{k_i}) for all rows i - # Note: we do that by iterating over all k_i and updating commitment_weights[i] accordingly - commitment_weights = [0] * num_rows - for k_i in range(num_cells): - i = row_indices[k_i] - commitment_weights[i] = (commitment_weights[i] + int(r_powers[k_i])) % BLS_MODULUS + # Step 4.1: Compute RLC = sum_i weights[i] commitments[i] + # Step 4.1a: Compute weights[i]: the sum of all r^k for which cell k is in row i. + # Note: we do that by iterating over all k and updating the correct weights[i] accordingly + weights = [0] * num_rows + for k in range(num_cells): + i = row_indices[k] + weights[i] = (weights[i] + int(r_powers[k])) % BLS_MODULUS # Step 4.1b: Linearly combine the weights with the commitments to get RLC - rlc = bls.bytes48_to_G1(g1_lincomb(row_commitments, commitment_weights)) + rlc = bls.bytes48_to_G1(g1_lincomb(row_commitments, weights)) # Step 4.2: Compute RLI = [sum_k r^k interpolation_poly_k(s)] # Note: an efficient implementation would use the IDFT based method explained in the blog post