EIP4844: Refactor Fiat-Shamir logic to handle empty sidecars (#3093)

Additionally, it makes the Fiat-Shamir hashing logic more robust by making the challenges independent of each other. It also makes it more efficient to implement by moving both challenge computations to a single function needing a single transcript hash.

Co-authored-by: George Kadianakis <desnacked@riseup.net>
Co-authored-by: Dankrad Feist <mail@dankradfeist.de>
This commit is contained in:
kevaundray 2022-11-23 14:52:47 +00:00 committed by GitHub
parent 2d99f1b551
commit f0ff15c14d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 18 deletions

View File

@ -21,7 +21,7 @@
- [BLS12-381 helpers](#bls12-381-helpers) - [BLS12-381 helpers](#bls12-381-helpers)
- [`bytes_to_bls_field`](#bytes_to_bls_field) - [`bytes_to_bls_field`](#bytes_to_bls_field)
- [`blob_to_polynomial`](#blob_to_polynomial) - [`blob_to_polynomial`](#blob_to_polynomial)
- [`hash_to_bls_field`](#hash_to_bls_field) - [`compute_challenges`](#compute_challenges)
- [`bls_modular_inverse`](#bls_modular_inverse) - [`bls_modular_inverse`](#bls_modular_inverse)
- [`div`](#div) - [`div`](#div)
- [`g1_lincomb`](#g1_lincomb) - [`g1_lincomb`](#g1_lincomb)
@ -41,7 +41,6 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC --> <!-- /TOC -->
## Introduction ## Introduction
This document specifies basic polynomial operations and KZG polynomial commitment operations as they are needed for the EIP-4844 specification. The implementations are not optimized for performance, but readability. All practical implementations should optimize the polynomial operations. This document specifies basic polynomial operations and KZG polynomial commitment operations as they are needed for the EIP-4844 specification. The implementations are not optimized for performance, but readability. All practical implementations should optimize the polynomial operations.
@ -163,31 +162,44 @@ def blob_to_polynomial(blob: Blob) -> Polynomial:
return polynomial return polynomial
``` ```
#### `hash_to_bls_field` #### `compute_challenges`
```python ```python
def hash_to_bls_field(polys: Sequence[Polynomial], def compute_challenges(polynomials: Sequence[Polynomial],
comms: Sequence[KZGCommitment]) -> BLSFieldElement: commitments: Sequence[KZGCommitment]) -> BLSFieldElement:
""" """
Compute 32-byte hash of serialized polynomials and commitments concatenated. Return the Fiat-Shamir challenges required by the rest of the protocol.
This hash is then converted to a BLS field element, where the result is not uniform over the BLS field. The Fiat-Shamir logic works as per the following pseudocode:
Return the BLS field element.
hashed_data = hash(DOMAIN_SEPARATOR, polynomials, commitments)
r = hash(hashed_data, 0)
r_powers = [r, r**2, r**3, ...]
eval_challenge = hash(hashed_data, 1)
Then return `r_powers` and `eval_challenge` after converting them to BLS field elements.
The resulting field elements are not uniform over the BLS field.
""" """
# Append the number of polynomials and the degree of each polynomial as a domain separator # Append the number of polynomials and the degree of each polynomial as a domain separator
num_polys = int.to_bytes(len(polys), 8, ENDIANNESS) num_polynomials = int.to_bytes(len(polynomials), 8, ENDIANNESS)
degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, ENDIANNESS) degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, ENDIANNESS)
data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly + num_polys data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly + num_polynomials
# Append each polynomial which is composed by field elements # Append each polynomial which is composed by field elements
for poly in polys: for poly in polynomials:
for field_element in poly: for field_element in poly:
data += int.to_bytes(field_element, BYTES_PER_FIELD_ELEMENT, ENDIANNESS) data += int.to_bytes(field_element, BYTES_PER_FIELD_ELEMENT, ENDIANNESS)
# Append serialized G1 points # Append serialized G1 points
for commitment in comms: for commitment in commitments:
data += commitment data += commitment
return bytes_to_bls_field(hash(data)) # Transcript has been prepared: time to create the challenges
hashed_data = hash(data)
r = hash(hashed_data + b'\x00')
r_powers = compute_powers(bytes_to_bls_field(r), len(commitments))
eval_challenge = hash(hashed_data + b'\x01')
return r_powers, bytes_to_bls_field(eval_challenge)
``` ```
#### `bls_modular_inverse` #### `bls_modular_inverse`
@ -234,7 +246,8 @@ def poly_lincomb(polys: Sequence[Polynomial],
Given a list of ``polynomials``, interpret it as a 2D matrix and compute the linear combination 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. of each column with `scalars`: return the resulting polynomials.
""" """
result = [0] * len(polys[0]) assert len(polys) == len(scalars)
result = [0] * FIELD_ELEMENTS_PER_BLOB
for v, s in zip(polys, scalars): for v, s in zip(polys, scalars):
for i, x in enumerate(v): for i, x in enumerate(v):
result[i] = (result[i] + int(s) * int(x)) % BLS_MODULUS result[i] = (result[i] + int(s) * int(x)) % BLS_MODULUS
@ -256,6 +269,7 @@ def compute_powers(x: BLSFieldElement, n: uint64) -> Sequence[BLSFieldElement]:
return powers return powers
``` ```
### Polynomials ### Polynomials
#### `evaluate_polynomial_in_evaluation_form` #### `evaluate_polynomial_in_evaluation_form`
@ -367,14 +381,15 @@ def compute_aggregated_poly_and_commitment(
""" """
Return (1) the aggregated polynomial, (2) the aggregated KZG commitment, Return (1) the aggregated polynomial, (2) the aggregated KZG commitment,
and (3) the polynomial evaluation random challenge. and (3) the polynomial evaluation random challenge.
This function should also work with blobs == [] and kzg_commitments == []
""" """
assert len(blobs) == len(kzg_commitments)
# Convert blobs to polynomials # Convert blobs to polynomials
polynomials = [blob_to_polynomial(blob) for blob in blobs] polynomials = [blob_to_polynomial(blob) for blob in blobs]
# Generate random linear combination challenges # Generate random linear combination and evaluation challenges
r = hash_to_bls_field(polynomials, kzg_commitments) r_powers, evaluation_challenge = compute_challenges(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 # Create aggregated polynomial in evaluation form
aggregated_poly = Polynomial(poly_lincomb(polynomials, r_powers)) aggregated_poly = Polynomial(poly_lincomb(polynomials, r_powers))
@ -390,6 +405,7 @@ def compute_aggregated_poly_and_commitment(
```python ```python
def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof: def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof:
""" """
Given a list of blobs, return the aggregated KZG proof that is used to verify them against their commitments.
Public method. Public method.
""" """
commitments = [blob_to_kzg_commitment(blob) for blob in blobs] commitments = [blob_to_kzg_commitment(blob) for blob in blobs]
@ -407,6 +423,8 @@ def verify_aggregate_kzg_proof(blobs: Sequence[Blob],
expected_kzg_commitments: Sequence[KZGCommitment], expected_kzg_commitments: Sequence[KZGCommitment],
kzg_aggregated_proof: KZGProof) -> bool: kzg_aggregated_proof: KZGProof) -> bool:
""" """
Given a list of blobs and an aggregated KZG proof, verify that they correspond to the provided commitments.
Public method. Public method.
""" """
aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment( aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment(

View File

@ -25,6 +25,12 @@ def _run_validate_blobs_sidecar_test(spec, state, blob_count):
spec.validate_blobs_sidecar(block.slot, block.hash_tree_root(), expected_commitments, blobs_sidecar) spec.validate_blobs_sidecar(block.slot, block.hash_tree_root(), expected_commitments, blobs_sidecar)
@with_eip4844_and_later
@spec_state_test
def test_validate_blobs_sidecar_zero_blobs(spec, state):
_run_validate_blobs_sidecar_test(spec, state, blob_count=0)
@with_eip4844_and_later @with_eip4844_and_later
@spec_state_test @spec_state_test
def test_validate_blobs_sidecar_one_blob(spec, state): def test_validate_blobs_sidecar_one_blob(spec, state):