diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/_features/eip7594/polynomial-commitments-sampling.md index d7090c673..10da2e58c 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) + - [`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) @@ -34,7 +35,9 @@ - [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_shift_for_cell`](#coset_shift_for_cell) - [`coset_for_cell`](#coset_for_cell) - [Cells](#cells-1) - [Cell computation](#cell-computation) @@ -193,17 +196,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) @@ -222,6 +225,52 @@ def coset_fft_field(vals: Sequence[BLSFieldElement], return fft_field(vals, roots_of_unity, inv) ``` +#### `compute_verify_cell_kzg_proof_batch_challenge` + +```python +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. + This is used in verify_cell_kzg_proof_batch_impl. + + 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) + + # 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 #### `polynomial_eval_to_coeff` @@ -362,12 +411,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 +450,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,14 +472,132 @@ def verify_kzg_proof_multi_impl(commitment: KZGCommitment, ])) ``` +#### `verify_cell_kzg_proof_batch_impl` + +```python +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]) -> 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]. + The list of all commitments is provided in row_commitments_bytes. + + 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 + # LL = sum_k r^k proofs[k], + # LR = [s^n] + # RL = RLC - RLI + RLP, where + # 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] + # + # 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 + # - 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 + + # Preparation + num_cells = len(row_indices) + n = FIELD_ELEMENTS_PER_CELL + num_rows = len(row_commitments) + + # 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 + ) + 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)) + + # 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 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, 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(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) + 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(num_cells): + 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)) + + # 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``. + 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( + compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB) + ) + return roots_of_unity_brp[FIELD_ELEMENTS_PER_CELL * cell_index] +``` + #### `coset_for_cell` ```python 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( @@ -457,7 +624,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 +658,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 +678,15 @@ 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. + 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 naive algorithm of checking every cell - individually; an efficient algorithm can be found here: + 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 - 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. - 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 @@ -535,18 +699,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] - 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(row_commitments, row_indices, column_indices, cosets_evals, proofs) ``` ## Reconstruction @@ -558,7 +717,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 +776,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 @@ -684,7 +843,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 diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index a7023ac4d..5fdefeb93 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -44,16 +44,16 @@ - [`BeaconState`](#beaconstate) - [Helper functions](#helper-functions) - [Predicates](#predicates) - - [Updated `compute_proposer_index`](#updated-compute_proposer_index) - - [Updated `is_eligible_for_activation_queue`](#updated-is_eligible_for_activation_queue) + - [Modified `compute_proposer_index`](#modified-compute_proposer_index) + - [Modified `is_eligible_for_activation_queue`](#modified-is_eligible_for_activation_queue) - [New `is_compounding_withdrawal_credential`](#new-is_compounding_withdrawal_credential) - [New `has_compounding_withdrawal_credential`](#new-has_compounding_withdrawal_credential) - [New `has_execution_withdrawal_credential`](#new-has_execution_withdrawal_credential) - - [Updated `is_fully_withdrawable_validator`](#updated-is_fully_withdrawable_validator) - - [Updated `is_partially_withdrawable_validator`](#updated-is_partially_withdrawable_validator) + - [Modified `is_fully_withdrawable_validator`](#modified-is_fully_withdrawable_validator) + - [Modified `is_partially_withdrawable_validator`](#modified-is_partially_withdrawable_validator) - [Misc](#misc-1) - - [`get_committee_indices`](#get_committee_indices) - - [`get_validator_max_effective_balance`](#get_validator_max_effective_balance) + - [New `get_committee_indices`](#new-get_committee_indices) + - [New `get_validator_max_effective_balance`](#new-get_validator_max_effective_balance) - [Beacon state accessors](#beacon-state-accessors) - [New `get_balance_churn_limit`](#new-get_balance_churn_limit) - [New `get_activation_exit_churn_limit`](#new-get_activation_exit_churn_limit) @@ -61,28 +61,27 @@ - [New `get_active_balance`](#new-get_active_balance) - [New `get_pending_balance_to_withdraw`](#new-get_pending_balance_to_withdraw) - [Modified `get_attesting_indices`](#modified-get_attesting_indices) - - [New `get_activation_churn_consumption`](#new-get_activation_churn_consumption) - [Modified `get_next_sync_committee_indices`](#modified-get_next_sync_committee_indices) - [Beacon state mutators](#beacon-state-mutators) - - [Updated `initiate_validator_exit`](#updated--initiate_validator_exit) + - [Modified `initiate_validator_exit`](#modified-initiate_validator_exit) - [New `switch_to_compounding_validator`](#new-switch_to_compounding_validator) - [New `queue_excess_active_balance`](#new-queue_excess_active_balance) - [New `queue_entire_balance_and_reset_validator`](#new-queue_entire_balance_and_reset_validator) - [New `compute_exit_epoch_and_update_churn`](#new-compute_exit_epoch_and_update_churn) - [New `compute_consolidation_epoch_and_update_churn`](#new-compute_consolidation_epoch_and_update_churn) - - [Updated `slash_validator`](#updated-slash_validator) + - [Modified `slash_validator`](#modified-slash_validator) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Epoch processing](#epoch-processing) - - [Updated `process_epoch`](#updated-process_epoch) - - [Updated `process_registry_updates`](#updated--process_registry_updates) + - [Modified `process_epoch`](#modified-process_epoch) + - [Modified `process_registry_updates`](#modified-process_registry_updates) - [New `apply_pending_deposit`](#new-apply_pending_deposit) - [New `process_pending_deposits`](#new-process_pending_deposits) - [New `process_pending_consolidations`](#new-process_pending_consolidations) - - [Updated `process_effective_balance_updates`](#updated-process_effective_balance_updates) + - [Modified `process_effective_balance_updates`](#modified-process_effective_balance_updates) - [Block processing](#block-processing) - [Withdrawals](#withdrawals) - - [Updated `get_expected_withdrawals`](#updated-get_expected_withdrawals) - - [Updated `process_withdrawals`](#updated-process_withdrawals) + - [Modified `get_expected_withdrawals`](#modified-get_expected_withdrawals) + - [Modified `process_withdrawals`](#modified-process_withdrawals) - [Execution payload](#execution-payload) - [Modified `process_execution_payload`](#modified-process_execution_payload) - [Operations](#operations) @@ -90,10 +89,10 @@ - [Attestations](#attestations) - [Modified `process_attestation`](#modified-process_attestation) - [Deposits](#deposits) - - [Updated `apply_deposit`](#updated--apply_deposit) + - [Modified `apply_deposit`](#modified-apply_deposit) - [New `is_valid_deposit_signature`](#new-is_valid_deposit_signature) - [Voluntary exits](#voluntary-exits) - - [Updated `process_voluntary_exit`](#updated-process_voluntary_exit) + - [Modified `process_voluntary_exit`](#modified-process_voluntary_exit) - [Execution layer withdrawal requests](#execution-layer-withdrawal-requests) - [New `process_withdrawal_request`](#new-process_withdrawal_request) - [Deposit requests](#deposit-requests) @@ -113,7 +112,7 @@ Electra is a consensus-layer upgrade containing a number of features. Including: * [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251): Increase the MAX_EFFECTIVE_BALANCE * [EIP-7549](https://eips.ethereum.org/EIPS/eip-7549): Move committee index outside Attestation -*Note:* This specification is built upon [Deneb](../../deneb/beacon_chain.md) and is under active development. +*Note:* This specification is built upon [Deneb](../deneb/beacon_chain.md) and is under active development. ## Constants @@ -440,9 +439,9 @@ class BeaconState(Container): ### Predicates -#### Updated `compute_proposer_index` +#### Modified `compute_proposer_index` -*Note*: The function is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` preset. +*Note*: The function `compute_proposer_index` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA`. ```python def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: @@ -463,7 +462,9 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex] i += 1 ``` -#### Updated `is_eligible_for_activation_queue` +#### Modified `is_eligible_for_activation_queue` + +*Note*: The function `is_eligible_for_activation_queue` is modified to use `MIN_ACTIVATION_BALANCE` instead of `MAX_EFFECTIVE_BALANCE`. ```python def is_eligible_for_activation_queue(validator: Validator) -> bool: @@ -503,7 +504,9 @@ def has_execution_withdrawal_credential(validator: Validator) -> bool: return has_compounding_withdrawal_credential(validator) or has_eth1_withdrawal_credential(validator) ``` -#### Updated `is_fully_withdrawable_validator` +#### Modified `is_fully_withdrawable_validator` + +*Note*: The function `is_fully_withdrawable_validator` is modified to use `has_execution_withdrawal_credential` instead of `has_eth1_withdrawal_credential`. ```python def is_fully_withdrawable_validator(validator: Validator, balance: Gwei, epoch: Epoch) -> bool: @@ -517,7 +520,9 @@ def is_fully_withdrawable_validator(validator: Validator, balance: Gwei, epoch: ) ``` -#### Updated `is_partially_withdrawable_validator` +#### Modified `is_partially_withdrawable_validator` + +*Note*: The function `is_partially_withdrawable_validator` is modified to use `get_validator_max_effective_balance` instead of `MAX_EFFECTIVE_BALANCE` and `has_execution_withdrawal_credential` instead of `has_eth1_withdrawal_credential`. ```python def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> bool: @@ -536,14 +541,14 @@ def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> ### Misc -#### `get_committee_indices` +#### New `get_committee_indices` ```python def get_committee_indices(committee_bits: Bitvector) -> Sequence[CommitteeIndex]: return [CommitteeIndex(index) for index, bit in enumerate(committee_bits) if bit] ``` -#### `get_validator_max_effective_balance` +#### New `get_validator_max_effective_balance` ```python def get_validator_max_effective_balance(validator: Validator) -> Gwei: @@ -608,6 +613,8 @@ def get_pending_balance_to_withdraw(state: BeaconState, validator_index: Validat #### Modified `get_attesting_indices` +*Note*: The function `get_attesting_indices` is modified to support EIP7549. + ```python def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]: """ @@ -627,30 +634,9 @@ def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[V return output ``` -#### New `get_activation_churn_consumption` - -```python -def get_activation_churn_consumption(state: BeaconState, deposit: PendingDeposit) -> Gwei: - """ - Return amount of activation churn consumed by the ``deposit``. - """ - validator_pubkeys = [v.pubkey for v in state.validators] - - if deposit.pubkey not in validator_pubkeys: - return deposit.amount - else: - validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey)) - validator = state.validators[validator_index] - # Validator is exiting, do not consume the churn - if validator.exit_epoch < FAR_FUTURE_EPOCH: - return Gwei(0) - else: - return deposit.amount -``` - #### Modified `get_next_sync_committee_indices` -*Note*: The function is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` preset. +*Note*: The function `get_next_sync_committee_indices` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA`. ```python def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: @@ -679,7 +665,9 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd ### Beacon state mutators -#### Updated `initiate_validator_exit` +#### Modified `initiate_validator_exit` + +*Note*: The function `initiate_validator_exit` is modified to use the new `compute_exit_epoch_and_update_churn` function. ```python def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: @@ -728,6 +716,7 @@ def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> No ``` #### New `queue_entire_balance_and_reset_validator` + ```python def queue_entire_balance_and_reset_validator(state: BeaconState, index: ValidatorIndex) -> None: balance = state.balances[index] @@ -797,7 +786,9 @@ def compute_consolidation_epoch_and_update_churn(state: BeaconState, consolidati return state.earliest_consolidation_epoch ``` -#### Updated `slash_validator` +#### Modified `slash_validator` + +*Note*: The function `slash_validator` is modified to change how the slashing penalty and proposer/whistleblower rewards are calculated in accordance with EIP7251. ```python def slash_validator(state: BeaconState, @@ -831,7 +822,10 @@ def slash_validator(state: BeaconState, ### Epoch processing -#### Updated `process_epoch` +#### Modified `process_epoch` + +*Note*: The function `process_epoch` is modified to call updated functions and to process pending balance deposits and pending consolidations which are new in Electra. + ```python def process_epoch(state: BeaconState) -> None: process_justification_and_finalization(state) @@ -850,9 +844,9 @@ def process_epoch(state: BeaconState) -> None: process_sync_committee_updates(state) ``` -#### Updated `process_registry_updates` +#### Modified `process_registry_updates` -`process_registry_updates` uses the updated definition of `initiate_validator_exit` +*Note*: The function `process_registry_updates` is modified to use the updated definition of `initiate_validator_exit` and changes how the activation epochs are computed for eligible validators. ```python @@ -878,11 +872,9 @@ def process_registry_updates(state: BeaconState) -> None: #### New `apply_pending_deposit` ```python -def apply_pending_deposit(state: BeaconState, deposit: PendingDeposit) -> bool: +def apply_pending_deposit(state: BeaconState, deposit: PendingDeposit) -> None: """ Applies ``deposit`` to the ``state``. - Returns ``True`` if ``deposit`` has been successfully applied - and can be removed from the queue. """ validator_pubkeys = [v.pubkey for v in state.validators] if deposit.pubkey not in validator_pubkeys: @@ -896,32 +888,22 @@ def apply_pending_deposit(state: BeaconState, deposit: PendingDeposit) -> bool: add_validator_to_registry(state, deposit.pubkey, deposit.withdrawal_credentials, deposit.amount) else: validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey)) + # Increase balance + increase_balance(state, validator_index, deposit.amount) + # If validator has not exited, check if valid deposit switch to compounding credentials. validator = state.validators[validator_index] - # Validator is exiting, postpone the deposit until after withdrawable epoch - if validator.exit_epoch < FAR_FUTURE_EPOCH: - if get_current_epoch(state) <= validator.withdrawable_epoch: - return False - # Deposited balance will never become active. Increase balance but do not consume churn - else: - increase_balance(state, validator_index, deposit.amount) - # Validator is not exiting, attempt to process deposit - else: - # Increase balance - increase_balance(state, validator_index, deposit.amount) - # Check if valid deposit switch to compounding credentials. - if ( - is_compounding_withdrawal_credential(deposit.withdrawal_credentials) - and has_eth1_withdrawal_credential(validator) - and is_valid_deposit_signature( - deposit.pubkey, - deposit.withdrawal_credentials, - deposit.amount, - deposit.signature - ) - ): - switch_to_compounding_validator(state, validator_index) - - return True + if ( + validator.exit_epoch < get_current_epoch(state) + and is_compounding_withdrawal_credential(deposit.withdrawal_credentials) + and has_eth1_withdrawal_credential(validator) + and is_valid_deposit_signature( + deposit.pubkey, + deposit.withdrawal_credentials, + deposit.amount, + deposit.signature + ) + ): + switch_to_compounding_validator(state, validator_index) ``` #### New `process_pending_deposits` @@ -959,19 +941,30 @@ def process_pending_deposits(state: BeaconState) -> None: if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH_PROCESSING: break - # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. - churn_consumption = get_activation_churn_consumption(state, deposit) - if processed_amount + churn_consumption > available_for_processing: - is_churn_limit_reached = True - break + # Read validator state + is_validator_exited = False + is_validator_withdrawn = False + validator_pubkeys = [v.pubkey for v in state.validators] + if deposit.pubkey: + validator = state.validators[ValidatorIndex(validator_pubkeys.index(deposit.pubkey))] + is_validator_exited = validator.exit_epoch < FAR_FUTURE_EPOCH + is_validator_withdrawn = validator.withdrawable_epoch < get_current_epoch(state) - # Consume churn and apply deposit - processed_amount += churn_consumption - is_deposit_applied = apply_pending_deposit(state, deposit) - - # Postpone deposit if it has not been applied. - if not is_deposit_applied: + if is_validator_withdrawn: + # Deposited balance will never become active. Increase balance but do not consume churn + apply_pending_deposit(state, deposit) + elif is_validator_exited: + # Validator is exiting, postpone the deposit until after withdrawable epoch deposits_to_postpone.append(deposit) + else: + # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. + is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing + if is_churn_limit_reached: + break + + # Consume churn and apply deposit. + processed_amount += deposit.amount + apply_pending_deposit(state, deposit) # Regardless of how the deposit was handled, we move on in the queue. next_deposit_index += 1 @@ -1011,9 +1004,9 @@ def process_pending_consolidations(state: BeaconState) -> None: state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:] ``` -#### Updated `process_effective_balance_updates` +#### Modified `process_effective_balance_updates` -`process_effective_balance_updates` is updated with a new limit for the maximum effective balance. +*Note*: The function `process_effective_balance_updates` is modified to use the new limit for the maximum effective balance. ```python def process_effective_balance_updates(state: BeaconState) -> None: @@ -1023,6 +1016,7 @@ def process_effective_balance_updates(state: BeaconState) -> None: HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER + # [Modified in Electra:EIP7251] EFFECTIVE_BALANCE_LIMIT = ( MAX_EFFECTIVE_BALANCE_ELECTRA if has_compounding_withdrawal_credential(validator) else MIN_ACTIVATION_BALANCE @@ -1050,7 +1044,9 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: #### Withdrawals -##### Updated `get_expected_withdrawals` +##### Modified `get_expected_withdrawals` + +*Note*: The function `get_expected_withdrawals` is modified to support EIP7251. ```python def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]: @@ -1106,7 +1102,9 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], return withdrawals, partial_withdrawals_count ``` -##### Updated `process_withdrawals` +##### Modified `process_withdrawals` + +*Note*: The function `process_withdrawals` is modified to support EIP7251. ```python def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: @@ -1272,9 +1270,9 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ##### Deposits -###### Updated `apply_deposit` +###### Modified `apply_deposit` -*NOTE*: `process_deposit` is updated with a new definition of `apply_deposit`. +*Note*: The function `process_deposit` is modified to support EIP7251. ```python def apply_deposit(state: BeaconState, @@ -1297,7 +1295,7 @@ def apply_deposit(state: BeaconState, )) else: # Increase balance by deposit amount - # [Modified in Electra:EIP-7251] + # [Modified in Electra:EIP7251] state.pending_deposits.append(PendingDeposit( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, @@ -1325,7 +1323,10 @@ def is_valid_deposit_signature(pubkey: BLSPubkey, ``` ##### Voluntary exits -###### Updated `process_voluntary_exit` + +###### Modified `process_voluntary_exit` + +*Note*: The function `process_voluntary_exit` is modified to ensure the validator has no pending withdrawals in the queue. ```python def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: @@ -1353,8 +1354,6 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu ###### New `process_withdrawal_request` -*Note*: This function is new in Electra following EIP-7002 and EIP-7251. - ```python def process_withdrawal_request( state: BeaconState, @@ -1422,8 +1421,6 @@ def process_withdrawal_request( ###### New `process_deposit_request` -*Note*: This function is new in Electra:EIP6110. - ```python def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None: # Set deposit request start index 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 e7f066501..dd5aece3f 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,6 +142,94 @@ 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) + + 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]], # 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 diff --git a/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py index 3bd6350b3..e569be35e 100644 --- a/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py +++ b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py @@ -80,3 +80,29 @@ def test_fork_random_misc_balances(spec, phases, state): @with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) def test_fork_random_large_validator_set(spec, phases, state): yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_pre_activation(spec, phases, state): + post_spec = phases[ELECTRA] + state.validators[0].activation_epoch = spec.FAR_FUTURE_EPOCH + post_state = yield from run_fork_test(post_spec, state) + + assert len(post_state.pending_deposits) > 0 + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_has_compounding_withdrawal_credential(spec, phases, state): + post_spec = phases[ELECTRA] + validator = state.validators[0] + state.balances[0] = post_spec.MIN_ACTIVATION_BALANCE + 1 + validator.withdrawal_credentials = post_spec.COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + post_state = yield from run_fork_test(post_spec, state) + + assert len(post_state.pending_deposits) > 0 diff --git a/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py b/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py index 39a43a523..0067a8cc0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py @@ -63,3 +63,5 @@ def run_fork_test(post_spec, pre_state): assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) yield 'post', post_state + + return post_state