From 14b3463f2cd4ddc0114f342e081e1910c8243ac7 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Thu, 27 Oct 2022 00:01:22 +0200 Subject: [PATCH 01/18] Consistent variable name in LC test In light client tests, a variable is named `finality_header` but everywhere else it is called `finalized_header`. Rename for consistency. --- .../unittests/light_client/test_sync_protocol.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py index e7845292af..6cbbddf729 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py @@ -37,14 +37,14 @@ def test_process_light_client_update_not_timeout(spec, state): # Ensure that finality checkpoint is genesis assert state.finalized_checkpoint.epoch == 0 # Finality is unchanged - finality_header = spec.BeaconBlockHeader() + finalized_header = spec.BeaconBlockHeader() finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] update = spec.LightClientUpdate( attested_header=attested_header, next_sync_committee=next_sync_committee, next_sync_committee_branch=next_sync_committee_branch, - finalized_header=finality_header, + finalized_header=finalized_header, finality_branch=finality_branch, sync_aggregate=sync_aggregate, signature_slot=signature_slot, @@ -81,14 +81,14 @@ def test_process_light_client_update_at_period_boundary(spec, state): next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] # Finality is unchanged - finality_header = spec.BeaconBlockHeader() + finalized_header = spec.BeaconBlockHeader() finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] update = spec.LightClientUpdate( attested_header=attested_header, next_sync_committee=next_sync_committee, next_sync_committee_branch=next_sync_committee_branch, - finalized_header=finality_header, + finalized_header=finalized_header, finality_branch=finality_branch, sync_aggregate=sync_aggregate, signature_slot=signature_slot, @@ -126,14 +126,14 @@ def test_process_light_client_update_timeout(spec, state): next_sync_committee = state.next_sync_committee next_sync_committee_branch = spec.compute_merkle_proof_for_state(state, spec.NEXT_SYNC_COMMITTEE_INDEX) # Finality is unchanged - finality_header = spec.BeaconBlockHeader() + finalized_header = spec.BeaconBlockHeader() finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] update = spec.LightClientUpdate( attested_header=attested_header, next_sync_committee=next_sync_committee, next_sync_committee_branch=next_sync_committee_branch, - finalized_header=finality_header, + finalized_header=finalized_header, finality_branch=finality_branch, sync_aggregate=sync_aggregate, signature_slot=signature_slot, From 71775c6e877625fe9df550617095898bf1a798f7 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 27 Oct 2022 08:10:32 -0700 Subject: [PATCH 02/18] EIP4844: Remove signed blobs --- specs/eip4844/p2p-interface.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/specs/eip4844/p2p-interface.md b/specs/eip4844/p2p-interface.md index 0563ff7e65..1576fe96f7 100644 --- a/specs/eip4844/p2p-interface.md +++ b/specs/eip4844/p2p-interface.md @@ -13,7 +13,6 @@ The specification of these changes continues in the same format as the network s - [Configuration](#configuration) - [Containers](#containers) - [`BlobsSidecar`](#blobssidecar) - - [`SignedBlobsSidecar`](#signedblobssidecar) - [`SignedBeaconBlockAndBlobsSidecar`](#signedbeaconblockandblobssidecar) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - [Topics and messages](#topics-and-messages) @@ -50,14 +49,6 @@ class BlobsSidecar(Container): kzg_aggregated_proof: KZGProof ``` -### `SignedBlobsSidecar` - -```python -class SignedBlobsSidecar(Container): - message: BlobsSidecar - signature: BLSSignature -``` - ### `SignedBeaconBlockAndBlobsSidecar` ```python From c399e11aa717d7501a1b6cebd1ce5d09b558bade Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Thu, 27 Oct 2022 21:23:09 +0200 Subject: [PATCH 03/18] Document how to derive fork context for LC gossip For LC gossip, the documentation did not specify what slot number to use for deriving the gossip objects. This missing documentation is now added to document using `attested_header.slot`. --- specs/altair/light-client/p2p-interface.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/specs/altair/light-client/p2p-interface.md b/specs/altair/light-client/p2p-interface.md index 5c2b27b22d..f7575cf534 100644 --- a/specs/altair/light-client/p2p-interface.md +++ b/specs/altair/light-client/p2p-interface.md @@ -71,6 +71,17 @@ For light clients, the following validations MUST additionally pass before forwa Light clients SHOULD call `process_light_client_finality_update` even if the message is ignored. +The gossip `ForkDigest`-context is determined based on `compute_fork_version(compute_epoch_at_slot(finality_update.attested_header.slot))`. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Message SSZ type | +| ------------------------------- | ------------------------------------ | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` and later | `altair.LightClientFinalityUpdate` | + ###### `light_client_optimistic_update` This topic is used to propagate the latest `LightClientOptimisticUpdate` to light clients, allowing them to keep track of the latest `optimistic_header`. @@ -88,6 +99,17 @@ For light clients, the following validations MUST additionally pass before forwa Light clients SHOULD call `process_light_client_optimistic_update` even if the message is ignored. +The gossip `ForkDigest`-context is determined based on `compute_fork_version(compute_epoch_at_slot(optimistic_update.attested_header.slot))`. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Message SSZ type | +| ------------------------------- | ------------------------------------ | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` and later | `altair.LightClientOptimisticUpdate` | + ### The Req/Resp domain #### Messages From af54c97a6c8423613375fddbad35bb526d59b946 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Thu, 27 Oct 2022 21:36:55 +0200 Subject: [PATCH 04/18] Consistently use `compute_sync_committee_period_at_slot` A few LC functions were not yet updated to use a more concise function for computing sync committee period for a slot. Updating to that func. --- specs/altair/light-client/full-node.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/altair/light-client/full-node.md b/specs/altair/light-client/full-node.md index 09810a0373..e3f4d7a3e3 100644 --- a/specs/altair/light-client/full-node.md +++ b/specs/altair/light-client/full-node.md @@ -84,13 +84,13 @@ def create_light_client_update(state: BeaconState, header = state.latest_block_header.copy() header.state_root = hash_tree_root(state) assert hash_tree_root(header) == hash_tree_root(block.message) - update_signature_period = compute_sync_committee_period(compute_epoch_at_slot(block.message.slot)) + update_signature_period = compute_sync_committee_period_at_slot(block.message.slot) assert attested_state.slot == attested_state.latest_block_header.slot attested_header = attested_state.latest_block_header.copy() attested_header.state_root = hash_tree_root(attested_state) assert hash_tree_root(attested_header) == block.message.parent_root - update_attested_period = compute_sync_committee_period(compute_epoch_at_slot(attested_header.slot)) + update_attested_period = compute_sync_committee_period_at_slot(attested_header.slot) # `next_sync_committee` is only useful if the message is signed by the current sync committee if update_attested_period == update_signature_period: @@ -133,7 +133,7 @@ def create_light_client_update(state: BeaconState, Full nodes SHOULD provide the best derivable `LightClientUpdate` (according to `is_better_update`) for each sync committee period covering any epochs in range `[max(ALTAIR_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` where `current_epoch` is defined by the current wall-clock time. Full nodes MAY also provide `LightClientUpdate` for other sync committee periods. - `LightClientUpdate` are assigned to sync committee periods based on their `attested_header.slot` -- `LightClientUpdate` are only considered if `compute_sync_committee_period(compute_epoch_at_slot(update.attested_header.slot)) == compute_sync_committee_period(compute_epoch_at_slot(update.signature_slot))` +- `LightClientUpdate` are only considered if `compute_sync_committee_period_at_slot(update.attested_header.slot) == compute_sync_committee_period_at_slot(update.signature_slot)` - Only `LightClientUpdate` with `next_sync_committee` as selected by fork choice are provided, regardless of ranking by `is_better_update`. To uniquely identify a non-finalized sync committee fork, all of `period`, `current_sync_committee` and `next_sync_committee` need to be incorporated, as sync committees may reappear over time. ### `create_light_client_finality_update` From 5b6eb8007afc75d121aaaad8748500447f0f717f Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Thu, 27 Oct 2022 21:44:38 +0200 Subject: [PATCH 05/18] Clarify where LC proofs are rooted Add more detailed LC object documentation to explain that the various merkle proofs are relative to the beacon block's state root. Likewise, clarify that sync committees relate to the finalized header (not to the optimistic header, which can be a period ahead). --- specs/altair/light-client/sync-protocol.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/specs/altair/light-client/sync-protocol.md b/specs/altair/light-client/sync-protocol.md index ede86f852e..793483bc0c 100644 --- a/specs/altair/light-client/sync-protocol.md +++ b/specs/altair/light-client/sync-protocol.md @@ -77,9 +77,9 @@ Additional documents describe how the light client sync protocol can be used: ```python class LightClientBootstrap(Container): - # The requested beacon block header + # Header matching the requested beacon block root header: BeaconBlockHeader - # Current sync committee corresponding to `header` + # Current sync committee corresponding to `header.state_root` current_sync_committee: SyncCommittee current_sync_committee_branch: Vector[Bytes32, floorlog2(CURRENT_SYNC_COMMITTEE_INDEX)] ``` @@ -88,12 +88,12 @@ class LightClientBootstrap(Container): ```python class LightClientUpdate(Container): - # The beacon block header that is attested to by the sync committee + # Header attested to by the sync committee attested_header: BeaconBlockHeader - # Next sync committee corresponding to `attested_header` + # Next sync committee corresponding to `attested_header.state_root` next_sync_committee: SyncCommittee next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] - # The finalized beacon block header attested to by Merkle branch + # Finalized header corresponding to `attested_header.state_root` finalized_header: BeaconBlockHeader finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature @@ -106,9 +106,9 @@ class LightClientUpdate(Container): ```python class LightClientFinalityUpdate(Container): - # The beacon block header that is attested to by the sync committee + # Header attested to by the sync committee attested_header: BeaconBlockHeader - # The finalized beacon block header attested to by Merkle branch + # Finalized header corresponding to `attested_header.state_root` finalized_header: BeaconBlockHeader finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature @@ -121,7 +121,7 @@ class LightClientFinalityUpdate(Container): ```python class LightClientOptimisticUpdate(Container): - # The beacon block header that is attested to by the sync committee + # Header attested to by the sync committee attested_header: BeaconBlockHeader # Sync committee aggregate signature sync_aggregate: SyncAggregate @@ -134,9 +134,9 @@ class LightClientOptimisticUpdate(Container): ```python @dataclass class LightClientStore(object): - # Beacon block header that is finalized + # Header that is finalized finalized_header: BeaconBlockHeader - # Sync committees corresponding to the header + # Sync committees corresponding to the finalized header current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee # Best available header to switch finalized head to if we see nothing else From 990866dd9f28c98e00ed7fa427e34664eea5f038 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Thu, 27 Oct 2022 22:06:11 +0200 Subject: [PATCH 06/18] Rename LC test check from `root` to `beacon_root` Future light client tests will also incorporate execution payload data. To avoid confusion, rename the current `root` check to `beacon_root`. Doing this now, as #3066 already requires LC test runners to update. --- .../pyspec/eth2spec/test/altair/light_client/test_sync.py | 4 ++-- tests/formats/light_client/sync.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py index 47348717b5..1dd3ebd765 100644 --- a/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py @@ -63,11 +63,11 @@ def get_checks(store): return { "finalized_header": { 'slot': int(store.finalized_header.slot), - 'root': encode_hex(store.finalized_header.hash_tree_root()), + 'beacon_root': encode_hex(store.finalized_header.hash_tree_root()), }, "optimistic_header": { 'slot': int(store.optimistic_header.slot), - 'root': encode_hex(store.optimistic_header.hash_tree_root()), + 'beacon_root': encode_hex(store.optimistic_header.hash_tree_root()), }, } diff --git a/tests/formats/light_client/sync.md b/tests/formats/light_client/sync.md index ad597fa1db..4d7162c3bc 100644 --- a/tests/formats/light_client/sync.md +++ b/tests/formats/light_client/sync.md @@ -26,11 +26,11 @@ Each step includes checks to verify the expected impact on the `store` object. ```yaml finalized_header: { slot: int, -- Integer value from store.finalized_header.slot - root: string, -- Encoded 32-byte value from store.finalized_header.hash_tree_root() + beacon_root: string, -- Encoded 32-byte value from store.finalized_header.hash_tree_root() } optimistic_header: { slot: int, -- Integer value from store.optimistic_header.slot - root: string, -- Encoded 32-byte value from store.optimistic_header.hash_tree_root() + beacon_root: string, -- Encoded 32-byte value from store.optimistic_header.hash_tree_root() } ``` From 4fc80765ad2d087efb60df7e0be5a6f36460f0fa Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Thu, 27 Oct 2022 22:08:24 +0200 Subject: [PATCH 07/18] Also update test logic accordingly --- .../eth2spec/test/altair/light_client/test_sync.py | 2 +- .../unittests/light_client/test_sync_protocol.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py index 47348717b5..f8acdb13ca 100644 --- a/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py @@ -110,7 +110,7 @@ def compute_start_slot_at_sync_committee_period(spec, sync_committee_period): def compute_start_slot_at_next_sync_committee_period(spec, state): - sync_committee_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(state.slot)) + sync_committee_period = spec.compute_sync_committee_period_at_slot(state.slot) return compute_start_slot_at_sync_committee_period(spec, sync_committee_period + 1) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py index e7845292af..e4df8a6d00 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py @@ -68,8 +68,8 @@ def test_process_light_client_update_at_period_boundary(spec, state): # Forward to slot before next sync committee period so that next block is final one in period next_slots(spec, state, spec.UPDATE_TIMEOUT - 2) - store_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(store.optimistic_header.slot)) - update_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(state.slot)) + store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.slot) + update_period = spec.compute_sync_committee_period_at_slot(state.slot) assert store_period == update_period attested_block = state_transition_with_full_block(spec, state, False, False) @@ -112,8 +112,8 @@ def test_process_light_client_update_timeout(spec, state): # Forward to next sync committee period next_slots(spec, state, spec.UPDATE_TIMEOUT) - store_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(store.optimistic_header.slot)) - update_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(state.slot)) + store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.slot) + update_period = spec.compute_sync_committee_period_at_slot(state.slot) assert store_period + 1 == update_period attested_block = state_transition_with_full_block(spec, state, False, False) @@ -164,8 +164,8 @@ def test_process_light_client_update_finality_updated(spec, state): # Ensure that finality checkpoint has changed assert state.finalized_checkpoint.epoch == 3 # Ensure that it's same period - store_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(store.optimistic_header.slot)) - update_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(state.slot)) + store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.slot) + update_period = spec.compute_sync_committee_period_at_slot(state.slot) assert store_period == update_period attested_block = blocks[-1] From 9dc1a17b2d3d1dda34fcc6ea0d56800fdee43f38 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 28 Oct 2022 20:40:21 +0200 Subject: [PATCH 08/18] Update `remerkleable` to 0.1.25 `remerkleable` was updated to address potentially incorrect computation of `hash_tree_root` against default-initialized `Vector` objects. Switching to the fixed version. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6db4aa8707..79392e7d1a 100644 --- a/setup.py +++ b/setup.py @@ -1131,7 +1131,7 @@ def run(self): "pycryptodome==3.15.0", "py_ecc==6.0.0", "milagro_bls_binding==1.9.0", - "remerkleable==0.1.24", + "remerkleable==0.1.25", RUAMEL_YAML_VERSION, "lru-dict==1.1.8", MARKO_VERSION, From e453bfe018243f380c896cc83b949408f9d43fbf Mon Sep 17 00:00:00 2001 From: Manu NALEPA Date: Sat, 29 Oct 2022 20:15:24 +0200 Subject: [PATCH 09/18] README.md: Fix broken links --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3647eee04..4e99bf6363 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Join the chat at https://discord.gg/qGpsxSA](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/qGpsxSA) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -To learn more about proof-of-stake and sharding, see the [PoS FAQ](https://eth.wiki/en/concepts/proof-of-stake-faqs), [sharding FAQ](https://eth.wiki/sharding/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). +To learn more about proof-of-stake and sharding, see the [PoS documentation](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/), [sharding documentation](https://ethereum.org/en/upgrades/sharding/#main-content) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). This repository hosts the current Ethereum proof-of-stake specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed-upon changes to the spec can be made through pull requests. From 826c20ffc2dad1f38c1112b7a1e48958ac59fe7e Mon Sep 17 00:00:00 2001 From: Mark Mackey Date: Tue, 1 Nov 2022 12:54:33 -0500 Subject: [PATCH 10/18] Capella: Add Gossip Topic bls_to_execution_change --- specs/capella/p2p-interface.md | 110 +++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/specs/capella/p2p-interface.md b/specs/capella/p2p-interface.md index e69de29bb2..11a782fa0a 100644 --- a/specs/capella/p2p-interface.md +++ b/specs/capella/p2p-interface.md @@ -0,0 +1,110 @@ +# Capella -- Networking + +This document contains the networking specification for Capella. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. + +## Table of contents + + + + + +- [Modifications in Capella](#modifications-in-capella) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`bls_to_execution_change`](#bls_to_execution_change) + - [Transitioning the gossip](#transitioning-the-gossip) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + + + + + +# Modifications in Capella + +## The gossip domain: gossipsub + +A new topic is added to support the gossip of bls to execution change messages. + +### Topics and messages + +Topics follow the same specification as in prior upgrades. All topics remain stable except the beacon block topic which is updated with the modified type. + +The new topics along with the type of the `data` field of a gossipsub message are given in this table: + +| Name | Message Type | +| - | - | +| `beacon_block` | `SignedBeaconBlock` (modified) | +| `bls_to_execution_change` | `SignedBLSToExecutionChange` | + +Note that the `ForkDigestValue` path segment of the topic separates the old and the new `beacon_block` topics. + +#### Global topics + +Capella changes the type of the global beacon block topic and adds one global topic to propagate bls to execution change messages to all potential proposers of beacon blocks. + +##### `beacon_block` + +The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in Capella. +Specifically, this type changes with the addition of `bls_to_execution_changes` to the inner `BeaconBlockBody`. +See Capella [state transition document](./beacon-chain.md#beaconblockbody) for further details. + +##### `bls_to_execution_change` + +This topic is used to propagate signed bls to execution change messages to be included in future blocks. + +The following validations MUST pass before forwarding the `signed_bls_to_execution_change` on the network: + +- _[IGNORE]_ The `signed_bls_to_execution_change` is the first valid signed bls to execution change received + for the validator with index `signed_bls_to_execution_change.message.validator_index`. +- _[REJECT]_ All of the conditions within `process_bls_to_execution_change` pass validation. + +### Transitioning the gossip + +See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for +details on how to handle transitioning gossip topics for Capella. + +## The Req/Resp domain + +### Messages + +#### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + +The Capella fork-digest is introduced to the `context` enum to specify Capella block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +| ------------------------ | -------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | + +#### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +The Capella fork-digest is introduced to the `context` enum to specify Capella block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[1]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +| ------------------------ | -------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | + From 8788472679e78dce34fdfa49dd5e0a76d218e379 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 2 Nov 2022 13:26:59 -0600 Subject: [PATCH 11/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e99bf6363..aa22e05c5a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Join the chat at https://discord.gg/qGpsxSA](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/qGpsxSA) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -To learn more about proof-of-stake and sharding, see the [PoS documentation](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/), [sharding documentation](https://ethereum.org/en/upgrades/sharding/#main-content) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). +To learn more about proof-of-stake and sharding, see the [PoS documentation](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/), [sharding documentation](https://ethereum.org/en/upgrades/sharding/) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). This repository hosts the current Ethereum proof-of-stake specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed-upon changes to the spec can be made through pull requests. From 86e15764ad75d9a5a91918af19e9b2714ee25dc1 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Nov 2022 17:01:32 +0200 Subject: [PATCH 12/18] EIP4844: Update cryptography API (#3038) This commit changes the public API of the KZG library to the following high-level API: ``` - verify_kzg_proof() - compute_aggregate_kzg_proof() - verify_aggregate_kzg_proof() - blob_to_kzg_commitment() ``` compared to the previous much more low-level API: ``` - compute_powers() - matrix_lincomb() - lincomb() - bytes_to_bls_field() - evaluate_polynomial_in_evaluation_form() - verify_kzg_proof() - compute_kzg_proof() ``` This means that all the cryptographic logic (including Fiat-Shamir) is now isolated and hidden in the KZG library and the `validator.md` file ends up being significantly simplified, only calling high-level KZG functions. Some additional things that this commit does: - Moves all EIP4844 cryptography into polynomial-commitments.md - Improves the Fiat-Shamir stack by removing the need for SSZ and by introducing simple domain separators Co-authored-by: Kevaundray Wedderburn Co-authored-by: Hsiao-Wei Wang Co-authored-by: Dankrad Feist --- setup.py | 8 +- specs/eip4844/beacon-chain.md | 45 +++- specs/eip4844/polynomial-commitments.md | 209 ++++++++++++++---- specs/eip4844/validator.md | 144 +----------- .../polynomial_commitments/__init__.py | 0 .../test_polynomial_commitments.py | 20 ++ .../unittests/validator/test_validator.py | 16 -- .../pyspec/eth2spec/test/helpers/sharding.py | 10 +- 8 files changed, 247 insertions(+), 205 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/eip4844/unittests/polynomial_commitments/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/eip4844/unittests/polynomial_commitments/test_polynomial_commitments.py diff --git a/setup.py b/setup.py index 79392e7d1a..ec62706aa0 100644 --- a/setup.py +++ b/setup.py @@ -232,7 +232,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) -> if not _is_constant_id(name): # Check for short type declarations - if value.startswith(("uint", "Bytes", "ByteList", "Union", "Vector", "List")): + if value.startswith(("uint", "Bytes", "ByteList", "Union", "Vector", "List", "ByteVector")): custom_types[name] = value continue @@ -590,7 +590,6 @@ def imports(cls, preset_name: str): return super().imports(preset_name) + f''' from eth2spec.utils import kzg from eth2spec.bellatrix import {preset_name} as bellatrix -from eth2spec.utils.ssz.ssz_impl import serialize as ssz_serialize ''' @@ -617,12 +616,13 @@ def sundry_functions(cls) -> str: ROOTS_OF_UNITY = kzg.compute_roots_of_unity(TESTING_FIELD_ELEMENTS_PER_BLOB) -def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> BlobsSidecar: - pass''' +def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> Optional[BlobsSidecar]: + return "TEST"''' @classmethod def hardcoded_custom_type_dep_constants(cls, spec_object) -> str: constants = { + 'BYTES_PER_FIELD_ELEMENT': spec_object.constant_vars['BYTES_PER_FIELD_ELEMENT'].value, 'FIELD_ELEMENTS_PER_BLOB': spec_object.preset_vars['FIELD_ELEMENTS_PER_BLOB'].value, 'MAX_BLOBS_PER_BLOCK': spec_object.preset_vars['MAX_BLOBS_PER_BLOCK'].value, } diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md index 4cf9535933..a5f9124724 100644 --- a/specs/eip4844/beacon-chain.md +++ b/specs/eip4844/beacon-chain.md @@ -23,6 +23,8 @@ - [`ExecutionPayloadHeader`](#executionpayloadheader) - [Helper functions](#helper-functions) - [Misc](#misc) + - [`validate_blobs_sidecar`](#validate_blobs_sidecar) + - [`is_data_available`](#is_data_available) - [`kzg_commitment_to_versioned_hash`](#kzg_commitment_to_versioned_hash) - [`tx_peek_blob_versioned_hashes`](#tx_peek_blob_versioned_hashes) - [`verify_kzg_commitments_against_transactions`](#verify_kzg_commitments_against_transactions) @@ -44,9 +46,7 @@ This upgrade adds blobs to the beacon chain as part of EIP-4844. | Name | SSZ equivalent | Description | | - | - | - | -| `Blob` | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | | | `VersionedHash` | `Bytes32` | | -| `KZGCommitment` | `Bytes48` | Same as BLS standard "is valid pubkey" check but also allows `0x00..00` for point-at-infinity | ## Constants @@ -55,7 +55,6 @@ This upgrade adds blobs to the beacon chain as part of EIP-4844. | Name | Value | | - | - | | `BLOB_TX_TYPE` | `uint8(0x05)` | -| `FIELD_ELEMENTS_PER_BLOB` | `uint64(4096)` | | `VERSIONED_HASH_VERSION_KZG` | `Bytes1(0x01)` | ### Domain types @@ -150,6 +149,43 @@ class ExecutionPayloadHeader(Container): ### Misc +#### `validate_blobs_sidecar` + +```python +def validate_blobs_sidecar(slot: Slot, + beacon_block_root: Root, + expected_kzg_commitments: Sequence[KZGCommitment], + blobs_sidecar: BlobsSidecar) -> None: + assert slot == blobs_sidecar.beacon_block_slot + assert beacon_block_root == blobs_sidecar.beacon_block_root + blobs = blobs_sidecar.blobs + kzg_aggregated_proof = blobs_sidecar.kzg_aggregated_proof + assert len(expected_kzg_commitments) == len(blobs) + + assert verify_aggregate_kzg_proof(blobs, expected_kzg_commitments, kzg_aggregated_proof) +``` + +#### `is_data_available` + +The implementation of `is_data_available` is meant to change with later sharding upgrades. +Initially, it requires every verifying actor to retrieve the matching `BlobsSidecar`, +and validate the sidecar with `validate_blobs_sidecar`. + +Without the sidecar the block may be processed further optimistically, +but MUST NOT be considered valid until a valid `BlobsSidecar` has been downloaded. + +```python +def is_data_available(slot: Slot, beacon_block_root: Root, blob_kzg_commitments: Sequence[KZGCommitment]) -> bool: + # `retrieve_blobs_sidecar` is implementation dependent, raises an exception if not available. + sidecar = retrieve_blobs_sidecar(slot, beacon_block_root) + if sidecar == "TEST": + return True # For testing; remove once we have a way to inject `BlobsSidecar` into tests + validate_blobs_sidecar(slot, beacon_block_root, blob_kzg_commitments, sidecar) + + return True +``` + + #### `kzg_commitment_to_versioned_hash` ```python @@ -204,6 +240,9 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_operations(state, block.body) process_sync_aggregate(state, block.body.sync_aggregate) process_blob_kzg_commitments(state, block.body) # [New in EIP-4844] + + # New in EIP-4844, note: Can sync optimistically without this condition, see note on `is_data_available` + assert is_data_available(block.slot, hash_tree_root(block), block.body.blob_kzg_commitments) ``` #### Execution payload diff --git a/specs/eip4844/polynomial-commitments.md b/specs/eip4844/polynomial-commitments.md index 2b25763466..1ecfe4f369 100644 --- a/specs/eip4844/polynomial-commitments.md +++ b/specs/eip4844/polynomial-commitments.md @@ -10,6 +10,8 @@ - [Custom types](#custom-types) - [Constants](#constants) - [Preset](#preset) + - [Blob](#blob) + - [Crypto](#crypto) - [Trusted setup](#trusted-setup) - [Helper functions](#helper-functions) - [Bit-reversal permutation](#bit-reversal-permutation) @@ -18,16 +20,22 @@ - [`bit_reversal_permutation`](#bit_reversal_permutation) - [BLS12-381 helpers](#bls12-381-helpers) - [`bytes_to_bls_field`](#bytes_to_bls_field) + - [`blob_to_polynomial`](#blob_to_polynomial) + - [`hash_to_bls_field`](#hash_to_bls_field) - [`bls_modular_inverse`](#bls_modular_inverse) - [`div`](#div) - [`g1_lincomb`](#g1_lincomb) - - [`vector_lincomb`](#vector_lincomb) + - [`poly_lincomb`](#poly_lincomb) + - [`compute_powers`](#compute_powers) + - [Polynomials](#polynomials) + - [`evaluate_polynomial_in_evaluation_form`](#evaluate_polynomial_in_evaluation_form) - [KZG](#kzg) - [`blob_to_kzg_commitment`](#blob_to_kzg_commitment) - [`verify_kzg_proof`](#verify_kzg_proof) - [`compute_kzg_proof`](#compute_kzg_proof) - - [Polynomials](#polynomials) - - [`evaluate_polynomial_in_evaluation_form`](#evaluate_polynomial_in_evaluation_form) + - [`compute_aggregated_poly_and_commitment`](#compute_aggregated_poly_and_commitment) + - [`compute_aggregate_kzg_proof`](#compute_aggregate_kzg_proof) + - [`verify_aggregate_kzg_proof`](#verify_aggregate_kzg_proof) @@ -46,16 +54,31 @@ This document specifies basic polynomial operations and KZG polynomial commitmen | `BLSFieldElement` | `uint256` | `x < BLS_MODULUS` | | `KZGCommitment` | `Bytes48` | Same as BLS standard "is valid pubkey" check but also allows `0x00..00` for point-at-infinity | | `KZGProof` | `Bytes48` | Same as for `KZGCommitment` | +| `Polynomial` | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | a polynomial in evaluation form | +| `Blob` | `ByteVector[BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB]` | a basic blob data | ## Constants | Name | Value | Notes | | - | - | - | | `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` | Scalar field modulus of BLS12-381 | -| `ROOTS_OF_UNITY` | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | Roots of unity of order FIELD_ELEMENTS_PER_BLOB over the BLS12-381 field | +| `BYTES_PER_FIELD_ELEMENT` | `uint64(32)` | Bytes used to encode a BLS scalar field element | ## Preset +### Blob + +| Name | Value | +| - | - | +| `FIELD_ELEMENTS_PER_BLOB` | `uint64(4096)` | +| `FIAT_SHAMIR_PROTOCOL_DOMAIN` | `b'FSBLOBVERIFY_V1_'` | + +### Crypto + +| Name | Value | Notes | +| - | - | - | +| `ROOTS_OF_UNITY` | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | Roots of unity of order FIELD_ELEMENTS_PER_BLOB over the BLS12-381 field | + ### Trusted setup The trusted setup is part of the preset: during testing a `minimal` insecure variant may be used, @@ -91,7 +114,7 @@ def is_power_of_two(value: int) -> bool: ```python def reverse_bits(n: int, order: int) -> int: """ - Reverse the bit order of an integer n + Reverse the bit order of an integer ``n``. """ assert is_power_of_two(order) # Convert n to binary with the same number of bits as "order" - 1, then reverse its bit order @@ -117,9 +140,51 @@ def bit_reversal_permutation(sequence: Sequence[T]) -> Sequence[T]: ```python def bytes_to_bls_field(b: Bytes32) -> BLSFieldElement: """ - Convert bytes to a BLS field scalar. The output is not uniform over the BLS field. + Convert 32-byte value to a BLS field scalar. The output is not uniform over the BLS field. + """ + return int.from_bytes(b, ENDIANNESS) % BLS_MODULUS +``` + +#### `blob_to_polynomial` + +```python +def blob_to_polynomial(blob: Blob) -> Polynomial: + """ + Convert a blob to list of BLS field scalars. + """ + polynomial = Polynomial() + for i in range(FIELD_ELEMENTS_PER_BLOB): + value = int.from_bytes(blob[i * BYTES_PER_FIELD_ELEMENT: (i + 1) * BYTES_PER_FIELD_ELEMENT], ENDIANNESS) + assert value < BLS_MODULUS + polynomial[i] = value + return polynomial +``` + +#### `hash_to_bls_field` + +```python +def hash_to_bls_field(polys: Sequence[Polynomial], + comms: Sequence[KZGCommitment]) -> BLSFieldElement: + """ + Compute 32-byte hash of serialized polynomials and commitments concatenated. + This hash is then converted to a BLS field element, where the result is not uniform over the BLS field. + Return the BLS field element. """ - return int.from_bytes(b, "little") % BLS_MODULUS + # Append the number of polynomials and the degree of each polynomial as a domain separator + num_polys = int.to_bytes(len(polys), 8, ENDIANNESS) + degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, ENDIANNESS) + data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly + num_polys + + # Append each polynomial which is composed by field elements + for poly in polys: + for field_element in poly: + data += int.to_bytes(field_element, BYTES_PER_FIELD_ELEMENT, ENDIANNESS) + + # Append serialized G1 points + for commitment in comms: + data += commitment + + return bytes_to_bls_field(hash(data)) ``` #### `bls_modular_inverse` @@ -137,7 +202,9 @@ def bls_modular_inverse(x: BLSFieldElement) -> BLSFieldElement: ```python def div(x: BLSFieldElement, y: BLSFieldElement) -> BLSFieldElement: - """Divide two field elements: `x` by `y`""" + """ + Divide two field elements: ``x`` by `y``. + """ return (int(x) * int(bls_modular_inverse(y))) % BLS_MODULUS ``` @@ -155,22 +222,65 @@ def g1_lincomb(points: Sequence[KZGCommitment], scalars: Sequence[BLSFieldElemen return KZGCommitment(bls.G1_to_bytes48(result)) ``` -#### `vector_lincomb` +#### `poly_lincomb` ```python -def vector_lincomb(vectors: Sequence[Sequence[BLSFieldElement]], - scalars: Sequence[BLSFieldElement]) -> Sequence[BLSFieldElement]: +def poly_lincomb(polys: Sequence[Polynomial], + scalars: Sequence[BLSFieldElement]) -> Polynomial: """ - Given a list of ``vectors``, interpret it as a 2D matrix and compute the linear combination - of each column with `scalars`: return the resulting vector. + Given a list of ``polynomials``, interpret it as a 2D matrix and compute the linear combination + of each column with `scalars`: return the resulting polynomials. """ - result = [0] * len(vectors[0]) - for v, s in zip(vectors, scalars): + result = [0] * len(polys[0]) + for v, s in zip(polys, scalars): for i, x in enumerate(v): result[i] = (result[i] + int(s) * int(x)) % BLS_MODULUS return [BLSFieldElement(x) for x in result] ``` +#### `compute_powers` + +```python +def compute_powers(x: BLSFieldElement, n: uint64) -> Sequence[BLSFieldElement]: + """ + Return ``x`` to power of [0, n-1]. + """ + current_power = 1 + powers = [] + for _ in range(n): + powers.append(BLSFieldElement(current_power)) + current_power = current_power * int(x) % BLS_MODULUS + return powers +``` + +### Polynomials + +#### `evaluate_polynomial_in_evaluation_form` + +```python +def evaluate_polynomial_in_evaluation_form(polynomial: Polynomial, + z: BLSFieldElement) -> BLSFieldElement: + """ + Evaluate a polynomial (in evaluation form) at an arbitrary point ``z``. + Uses the barycentric formula: + f(z) = (z**WIDTH - 1) / WIDTH * sum_(i=0)^WIDTH (f(DOMAIN[i]) * DOMAIN[i]) / (z - DOMAIN[i]) + """ + width = len(polynomial) + assert width == FIELD_ELEMENTS_PER_BLOB + inverse_width = bls_modular_inverse(width) + + # Make sure we won't divide by zero during division + assert z not in ROOTS_OF_UNITY + + roots_of_unity_brp = bit_reversal_permutation(ROOTS_OF_UNITY) + + result = 0 + for i in range(width): + result += div(int(polynomial[i]) * int(roots_of_unity_brp[i]), (int(z) - roots_of_unity_brp[i])) + result = result * (pow(z, width, BLS_MODULUS) - 1) * inverse_width % BLS_MODULUS + return result +``` + ### KZG KZG core functions. These are also defined in EIP-4844 execution specs. @@ -179,7 +289,7 @@ KZG core functions. These are also defined in EIP-4844 execution specs. ```python def blob_to_kzg_commitment(blob: Blob) -> KZGCommitment: - return g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), blob) + return g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), blob_to_polynomial(blob)) ``` #### `verify_kzg_proof` @@ -204,16 +314,16 @@ def verify_kzg_proof(polynomial_kzg: KZGCommitment, #### `compute_kzg_proof` ```python -def compute_kzg_proof(polynomial: Sequence[BLSFieldElement], z: BLSFieldElement) -> KZGProof: +def compute_kzg_proof(polynomial: Polynomial, z: BLSFieldElement) -> KZGProof: """ Compute KZG proof at point `z` with `polynomial` being in evaluation form + Do this by computing the quotient polynomial in evaluation form: q(x) = (p(x) - p(z)) / (x - z) """ # To avoid SSZ overflow/underflow, convert element into int polynomial = [int(i) for i in polynomial] z = int(z) - # Shift our polynomial first (in evaluation form we can't handle the division remainder) y = evaluate_polynomial_in_evaluation_form(polynomial, z) polynomial_shifted = [(p - int(y)) % BLS_MODULUS for p in polynomial] @@ -226,31 +336,56 @@ def compute_kzg_proof(polynomial: Sequence[BLSFieldElement], z: BLSFieldElement) return KZGProof(g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), quotient_polynomial)) ``` -### Polynomials - -#### `evaluate_polynomial_in_evaluation_form` +#### `compute_aggregated_poly_and_commitment` ```python -def evaluate_polynomial_in_evaluation_form(polynomial: Sequence[BLSFieldElement], - z: BLSFieldElement) -> BLSFieldElement: +def compute_aggregated_poly_and_commitment( + blobs: Sequence[Blob], + kzg_commitments: Sequence[KZGCommitment]) -> Tuple[Polynomial, KZGCommitment, BLSFieldElement]: """ - Evaluate a polynomial (in evaluation form) at an arbitrary point `z` - Uses the barycentric formula: - f(z) = (1 - z**WIDTH) / WIDTH * sum_(i=0)^WIDTH (f(DOMAIN[i]) * DOMAIN[i]) / (z - DOMAIN[i]) + Return (1) the aggregated polynomial, (2) the aggregated KZG commitment, + and (3) the polynomial evaluation random challenge. """ - width = len(polynomial) - assert width == FIELD_ELEMENTS_PER_BLOB - inverse_width = bls_modular_inverse(width) + # Generate random linear combination challenges + r = hash_to_bls_field(blobs, kzg_commitments) + r_powers = compute_powers(r, len(kzg_commitments)) + evaluation_challenge = int(r_powers[-1]) * r % BLS_MODULUS - # Make sure we won't divide by zero during division - assert z not in ROOTS_OF_UNITY + # Create aggregated polynomial in evaluation form + aggregated_poly = Polynomial(poly_lincomb([blob_to_polynomial(blob) for blob in blobs], r_powers)) - roots_of_unity_brp = bit_reversal_permutation(ROOTS_OF_UNITY) + # Compute commitment to aggregated polynomial + aggregated_poly_commitment = KZGCommitment(g1_lincomb(kzg_commitments, r_powers)) - result = 0 - for i in range(width): - result += div(int(polynomial[i]) * int(roots_of_unity_brp[i]), (z - roots_of_unity_brp[i])) - result = result * (pow(z, width, BLS_MODULUS) - 1) * inverse_width % BLS_MODULUS - return result + return aggregated_poly, aggregated_poly_commitment, evaluation_challenge +``` + +#### `compute_aggregate_kzg_proof` + +```python +def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof: + commitments = [blob_to_kzg_commitment(blob) for blob in blobs] + aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment( + blobs, + commitments + ) + return compute_kzg_proof(aggregated_poly, evaluation_challenge) ``` +#### `verify_aggregate_kzg_proof` + +```python +def verify_aggregate_kzg_proof(blobs: Sequence[Blob], + expected_kzg_commitments: Sequence[KZGCommitment], + kzg_aggregated_proof: KZGCommitment) -> bool: + aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment( + blobs, + expected_kzg_commitments, + ) + + # Evaluate aggregated polynomial at `evaluation_challenge` (evaluation function checks for div-by-zero) + y = evaluate_polynomial_in_evaluation_form(aggregated_poly, evaluation_challenge) + + # Verify aggregated proof + return verify_kzg_proof(aggregated_poly_commitment, evaluation_challenge, y, kzg_aggregated_proof) +``` diff --git a/specs/eip4844/validator.md b/specs/eip4844/validator.md index 0cd420637f..d03b1842bb 100644 --- a/specs/eip4844/validator.md +++ b/specs/eip4844/validator.md @@ -10,17 +10,7 @@ - [Introduction](#introduction) - [Prerequisites](#prerequisites) -- [Custom types](#custom-types) -- [Containers](#containers) - - [`BlobsAndCommitments`](#blobsandcommitments) - - [`PolynomialAndCommitment`](#polynomialandcommitment) - [Helpers](#helpers) - - [`is_data_available`](#is_data_available) - - [`hash_to_bls_field`](#hash_to_bls_field) - - [`compute_powers`](#compute_powers) - - [`compute_aggregated_poly_and_commitment`](#compute_aggregated_poly_and_commitment) - - [`validate_blobs_sidecar`](#validate_blobs_sidecar) - - [`compute_proof_from_blobs`](#compute_proof_from_blobs) - [`get_blobs_and_kzg_commitments`](#get_blobs_and_kzg_commitments) - [Beacon chain responsibilities](#beacon-chain-responsibilities) - [Block and sidecar proposal](#block-and-sidecar-proposal) @@ -45,140 +35,8 @@ All behaviors and definitions defined in this document, and documents it extends All terminology, constants, functions, and protocol mechanics defined in the updated [Beacon Chain doc of EIP4844](./beacon-chain.md) are requisite for this document and used throughout. Please see related Beacon Chain doc before continuing and use them as a reference throughout. -## Custom types - -| Name | SSZ equivalent | Description | -| - | - | - | -| `Polynomial` | `List[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | a polynomial in evaluation form | - -## Containers - -### `BlobsAndCommitments` - -```python -class BlobsAndCommitments(Container): - blobs: List[Blob, MAX_BLOBS_PER_BLOCK] - kzg_commitments: List[KZGCommitment, MAX_BLOBS_PER_BLOCK] -``` - -### `PolynomialAndCommitment` - -```python -class PolynomialAndCommitment(Container): - polynomial: Polynomial - kzg_commitment: KZGCommitment -``` - - ## Helpers -### `is_data_available` - -The implementation of `is_data_available` is meant to change with later sharding upgrades. -Initially, it requires every verifying actor to retrieve the matching `BlobsSidecar`, -and validate the sidecar with `validate_blobs_sidecar`. - -Without the sidecar the block may be processed further optimistically, -but MUST NOT be considered valid until a valid `BlobsSidecar` has been downloaded. - -```python -def is_data_available(slot: Slot, beacon_block_root: Root, blob_kzg_commitments: Sequence[KZGCommitment]) -> bool: - # `retrieve_blobs_sidecar` is implementation dependent, raises an exception if not available. - sidecar = retrieve_blobs_sidecar(slot, beacon_block_root) - validate_blobs_sidecar(slot, beacon_block_root, blob_kzg_commitments, sidecar) - - return True -``` - -### `hash_to_bls_field` - -```python -def hash_to_bls_field(x: Container) -> BLSFieldElement: - """ - Compute 32-byte hash of serialized container and convert it to BLS field. - The output is not uniform over the BLS field. - """ - return bytes_to_bls_field(hash(ssz_serialize(x))) -``` - -### `compute_powers` -```python -def compute_powers(x: BLSFieldElement, n: uint64) -> Sequence[BLSFieldElement]: - """ - Return ``x`` to power of [0, n-1]. - """ - current_power = 1 - powers = [] - for _ in range(n): - powers.append(BLSFieldElement(current_power)) - current_power = current_power * int(x) % BLS_MODULUS - return powers -``` - -### `compute_aggregated_poly_and_commitment` - -```python -def compute_aggregated_poly_and_commitment( - blobs: Sequence[Blob], - kzg_commitments: Sequence[KZGCommitment]) -> Tuple[Polynomial, KZGCommitment]: - """ - Return the aggregated polynomial and aggregated KZG commitment. - """ - # Generate random linear combination challenges - r = hash_to_bls_field(BlobsAndCommitments(blobs=blobs, kzg_commitments=kzg_commitments)) - r_powers = compute_powers(r, len(kzg_commitments)) - - # Create aggregated polynomial in evaluation form - aggregated_poly = Polynomial(vector_lincomb(blobs, r_powers)) - - # Compute commitment to aggregated polynomial - aggregated_poly_commitment = KZGCommitment(g1_lincomb(kzg_commitments, r_powers)) - - return aggregated_poly, aggregated_poly_commitment -``` - -### `validate_blobs_sidecar` - -```python -def validate_blobs_sidecar(slot: Slot, - beacon_block_root: Root, - expected_kzg_commitments: Sequence[KZGCommitment], - blobs_sidecar: BlobsSidecar) -> None: - assert slot == blobs_sidecar.beacon_block_slot - assert beacon_block_root == blobs_sidecar.beacon_block_root - blobs = blobs_sidecar.blobs - kzg_aggregated_proof = blobs_sidecar.kzg_aggregated_proof - assert len(expected_kzg_commitments) == len(blobs) - - aggregated_poly, aggregated_poly_commitment = compute_aggregated_poly_and_commitment( - blobs, - expected_kzg_commitments, - ) - - # Generate challenge `x` and evaluate the aggregated polynomial at `x` - x = hash_to_bls_field( - PolynomialAndCommitment(polynomial=aggregated_poly, kzg_commitment=aggregated_poly_commitment) - ) - # Evaluate aggregated polynomial at `x` (evaluation function checks for div-by-zero) - y = evaluate_polynomial_in_evaluation_form(aggregated_poly, x) - - # Verify aggregated proof - assert verify_kzg_proof(aggregated_poly_commitment, x, y, kzg_aggregated_proof) -``` - -### `compute_proof_from_blobs` - -```python -def compute_proof_from_blobs(blobs: Sequence[Blob]) -> KZGProof: - commitments = [blob_to_kzg_commitment(blob) for blob in blobs] - aggregated_poly, aggregated_poly_commitment = compute_aggregated_poly_and_commitment(blobs, commitments) - x = hash_to_bls_field(PolynomialAndCommitment( - polynomial=aggregated_poly, - kzg_commitment=aggregated_poly_commitment, - )) - return compute_kzg_proof(aggregated_poly, x) -``` - ### `get_blobs_and_kzg_commitments` The interface to retrieve blobs and corresponding kzg commitments. @@ -236,7 +94,7 @@ def get_blobs_sidecar(block: BeaconBlock, blobs: Sequence[Blob]) -> BlobsSidecar beacon_block_root=hash_tree_root(block), beacon_block_slot=block.slot, blobs=blobs, - kzg_aggregated_proof=compute_proof_from_blobs(blobs), + kzg_aggregated_proof=compute_aggregate_kzg_proof(blobs), ) ``` diff --git a/tests/core/pyspec/eth2spec/test/eip4844/unittests/polynomial_commitments/__init__.py b/tests/core/pyspec/eth2spec/test/eip4844/unittests/polynomial_commitments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip4844/unittests/polynomial_commitments/test_polynomial_commitments.py b/tests/core/pyspec/eth2spec/test/eip4844/unittests/polynomial_commitments/test_polynomial_commitments.py new file mode 100644 index 0000000000..dea6aeb8c9 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip4844/unittests/polynomial_commitments/test_polynomial_commitments.py @@ -0,0 +1,20 @@ +from eth2spec.test.context import ( + spec_state_test, + with_eip4844_and_later, +) +from eth2spec.test.helpers.sharding import ( + get_sample_blob, +) + + +@with_eip4844_and_later +@spec_state_test +def test_verify_kzg_proof(spec, state): + x = 3 + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + polynomial = spec.blob_to_polynomial(blob) + proof = spec.compute_kzg_proof(polynomial, x) + + y = spec.evaluate_polynomial_in_evaluation_form(polynomial, x) + assert spec.verify_kzg_proof(commitment, x, y, proof) diff --git a/tests/core/pyspec/eth2spec/test/eip4844/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/eip4844/unittests/validator/test_validator.py index 680a0a9c4c..634daca2d5 100644 --- a/tests/core/pyspec/eth2spec/test/eip4844/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/eip4844/unittests/validator/test_validator.py @@ -10,25 +10,9 @@ ) from eth2spec.test.helpers.sharding import ( get_sample_opaque_tx, - get_sample_blob, ) -@with_eip4844_and_later -@spec_state_test -def test_verify_kzg_proof(spec, state): - x = 3 - polynomial = get_sample_blob(spec) - polynomial = [int(i) for i in polynomial] - commitment = spec.blob_to_kzg_commitment(polynomial) - - # Get the proof - proof = spec.compute_kzg_proof(polynomial, x) - - y = spec.evaluate_polynomial_in_evaluation_form(polynomial, x) - assert spec.verify_kzg_proof(commitment, x, y, proof) - - def _run_validate_blobs_sidecar_test(spec, state, blob_count): block = build_empty_block_for_next_slot(spec, state) opaque_tx, blobs, blob_kzg_commitments = get_sample_opaque_tx(spec, blob_count=blob_count) diff --git a/tests/core/pyspec/eth2spec/test/helpers/sharding.py b/tests/core/pyspec/eth2spec/test/helpers/sharding.py index 6c90153fca..3ce3215eb4 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/sharding.py +++ b/tests/core/pyspec/eth2spec/test/helpers/sharding.py @@ -53,10 +53,16 @@ def get_sample_blob(spec, rng=None): if rng is None: rng = random.Random(5566) - return spec.Blob([ + values = [ rng.randint(0, spec.BLS_MODULUS - 1) for _ in range(spec.FIELD_ELEMENTS_PER_BLOB) - ]) + ] + + b = bytes() + for v in values: + b += v.to_bytes(32, spec.ENDIANNESS) + + return spec.Blob(b) def get_sample_opaque_tx(spec, blob_count=1, rng=None): From b2d72a18f0727e2071be6f10f4393cab8f4b6f05 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Nov 2022 18:08:37 +0200 Subject: [PATCH 13/18] Fix type error in the inputs to hash_to_bls_field() --- specs/eip4844/polynomial-commitments.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/specs/eip4844/polynomial-commitments.md b/specs/eip4844/polynomial-commitments.md index 1ecfe4f369..c90c0d38d4 100644 --- a/specs/eip4844/polynomial-commitments.md +++ b/specs/eip4844/polynomial-commitments.md @@ -346,13 +346,16 @@ def compute_aggregated_poly_and_commitment( Return (1) the aggregated polynomial, (2) the aggregated KZG commitment, and (3) the polynomial evaluation random challenge. """ + # Convert blobs to polynomials + polynomials = [blob_to_polynomial(blob) for blob in blobs] + # Generate random linear combination challenges - r = hash_to_bls_field(blobs, kzg_commitments) + r = hash_to_bls_field(polynomials, kzg_commitments) r_powers = compute_powers(r, len(kzg_commitments)) evaluation_challenge = int(r_powers[-1]) * r % BLS_MODULUS # Create aggregated polynomial in evaluation form - aggregated_poly = Polynomial(poly_lincomb([blob_to_polynomial(blob) for blob in blobs], r_powers)) + aggregated_poly = Polynomial(poly_lincomb(polynomials, r_powers)) # Compute commitment to aggregated polynomial aggregated_poly_commitment = KZGCommitment(g1_lincomb(kzg_commitments, r_powers)) From cd5567b735a3faac37ffc3018165bf881c36a41d Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 4 Nov 2022 15:54:30 -0600 Subject: [PATCH 14/18] Update p2p-interface.md --- specs/bellatrix/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 02d4ef6c9f..4d4044689b 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -110,7 +110,7 @@ The following gossip validation from prior specifications MUST NOT be applied if ### Transitioning the gossip See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for -details on how to handle transitioning gossip topics for EIP-4844. +details on how to handle transitioning gossip topics. ## The Req/Resp domain From 1f68c57a7ba5f45f7857d6879758ab9cee948c01 Mon Sep 17 00:00:00 2001 From: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Date: Fri, 4 Nov 2022 17:13:07 -0500 Subject: [PATCH 15/18] Update specs/capella/p2p-interface.md Co-authored-by: Alex Stokes --- specs/capella/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/capella/p2p-interface.md b/specs/capella/p2p-interface.md index 11a782fa0a..6e86b998f5 100644 --- a/specs/capella/p2p-interface.md +++ b/specs/capella/p2p-interface.md @@ -30,7 +30,7 @@ The specification of these changes continues in the same format as the network s ## The gossip domain: gossipsub -A new topic is added to support the gossip of bls to execution change messages. +A new topic is added to support the gossip of withdrawal credential change messages. And an existing topic is upgraded for updated types in Capella. ### Topics and messages From 6c1fa0a48561f7564276cd574cb0c14e8317b5ad Mon Sep 17 00:00:00 2001 From: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Date: Fri, 4 Nov 2022 17:13:15 -0500 Subject: [PATCH 16/18] Update specs/capella/p2p-interface.md Co-authored-by: Alex Stokes --- specs/capella/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/capella/p2p-interface.md b/specs/capella/p2p-interface.md index 6e86b998f5..1552991c05 100644 --- a/specs/capella/p2p-interface.md +++ b/specs/capella/p2p-interface.md @@ -34,7 +34,7 @@ A new topic is added to support the gossip of withdrawal credential change messa ### Topics and messages -Topics follow the same specification as in prior upgrades. All topics remain stable except the beacon block topic which is updated with the modified type. +Topics follow the same specification as in prior upgrades. All existing topics remain stable except the beacon block topic which is updated with the modified type. The new topics along with the type of the `data` field of a gossipsub message are given in this table: From 355ca296b8c8c22520572e8896089917fe70f3f7 Mon Sep 17 00:00:00 2001 From: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Date: Fri, 4 Nov 2022 17:13:23 -0500 Subject: [PATCH 17/18] Update specs/capella/p2p-interface.md Co-authored-by: Alex Stokes --- specs/capella/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/capella/p2p-interface.md b/specs/capella/p2p-interface.md index 1552991c05..4cb23a67e1 100644 --- a/specs/capella/p2p-interface.md +++ b/specs/capella/p2p-interface.md @@ -47,7 +47,7 @@ Note that the `ForkDigestValue` path segment of the topic separates the old and #### Global topics -Capella changes the type of the global beacon block topic and adds one global topic to propagate bls to execution change messages to all potential proposers of beacon blocks. +Capella changes the type of the global beacon block topic and adds one global topic to propagate withdrawal credential change messages to all potential proposers of beacon blocks. ##### `beacon_block` From b5ecf44c911f6772ae1f2c2dfa567f07f9d27ec7 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 4 Nov 2022 16:41:07 -0600 Subject: [PATCH 18/18] add note about packing BLS to execution changes into a block --- specs/capella/validator.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/specs/capella/validator.md b/specs/capella/validator.md index 85dbd7e00b..90176e035e 100644 --- a/specs/capella/validator.md +++ b/specs/capella/validator.md @@ -18,6 +18,7 @@ - [Block proposal](#block-proposal) - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) - [ExecutionPayload](#executionpayload) + - [BLS to execution changes](#bls-to-execution-changes) @@ -106,3 +107,7 @@ def prepare_execution_payload(state: BeaconState, payload_attributes=payload_attributes, ) ``` + +##### BLS to execution changes + +Up to `MAX_BLS_TO_EXECUTION_CHANGES`, [`BLSToExecutionChange`](./beacon-chain.md#blstoexecutionchange) objects can be included in the `block`. The BLS to execution changes must satisfy the verification conditions found in [BLS to execution change processing](./beacon-chain.md#new-process_bls_to_execution_change).