Merge branch 'dev' into lc-blockfuncs
This commit is contained in:
commit
08ff71688f
|
@ -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/) 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.
|
||||
|
||||
|
|
10
setup.py
10
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 @@ class EIP4844SpecBuilder(BellatrixSpecBuilder):
|
|||
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 @@ KZG_SETUP_LAGRANGE = TESTING_KZG_SETUP_LAGRANGE
|
|||
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,
|
||||
}
|
||||
|
@ -1131,7 +1131,7 @@ setup(
|
|||
"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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
<!-- TOC -->
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
- [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)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
||||
|
||||
# Modifications in Capella
|
||||
|
||||
## The gossip domain: gossipsub
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
| 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 withdrawal credential 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` |
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
- [Block proposal](#block-proposal)
|
||||
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
|
||||
- [ExecutionPayload](#executionpayload)
|
||||
- [BLS to execution changes](#bls-to-execution-changes)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
@ -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, "little") % BLS_MODULUS
|
||||
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.
|
||||
"""
|
||||
# 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,59 @@ 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)
|
||||
# Convert blobs to polynomials
|
||||
polynomials = [blob_to_polynomial(blob) for blob in blobs]
|
||||
|
||||
# Make sure we won't divide by zero during division
|
||||
assert z not in ROOTS_OF_UNITY
|
||||
# Generate random linear combination challenges
|
||||
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
|
||||
|
||||
roots_of_unity_brp = bit_reversal_permutation(ROOTS_OF_UNITY)
|
||||
# Create aggregated polynomial in evaluation form
|
||||
aggregated_poly = Polynomial(poly_lincomb(polynomials, 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
|
||||
# Compute commitment to aggregated polynomial
|
||||
aggregated_poly_commitment = KZGCommitment(g1_lincomb(kzg_commitments, r_powers))
|
||||
|
||||
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)
|
||||
```
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
```
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -10,25 +10,9 @@ from eth2spec.test.context import (
|
|||
)
|
||||
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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue