-
Notifications
You must be signed in to change notification settings - Fork 5.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimizing EIP-4844 transaction validation for mempool (using KZG proofs) #5088
Changes from all commits
0c399f1
e3f2bd4
b66c8aa
ab7eef0
603ab2a
7d3b449
ea9ae70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -46,6 +46,7 @@ Compared to full data sharding, this EIP has a reduced cap on the number of thes | |||||||
| `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` | | ||||||||
| `KZG_SETUP_G2` | `Vector[G2Point, FIELD_ELEMENTS_PER_BLOB]`, contents TBD | | ||||||||
| `KZG_SETUP_LAGRANGE` | `Vector[KZGCommitment, FIELD_ELEMENTS_PER_BLOB]`, contents TBD | | ||||||||
| `ROOTS_OF_UNITY` | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | | ||||||||
| `BLOB_COMMITMENT_VERSION_KZG` | `Bytes1(0x01)` | | ||||||||
| `POINT_EVALUATION_PRECOMPILE_ADDRESS` | `Bytes20(0x14)` | | ||||||||
| `POINT_EVALUATION_PRECOMPILE_GAS` | `50000` | | ||||||||
|
@@ -71,21 +72,24 @@ Compared to full data sharding, this EIP has a reduced cap on the number of thes | |||||||
| `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 | | ||||||||
| `KZGProof` | `Bytes48` | Same as for `KZGCommitment` | | ||||||||
|
||||||||
### Helpers | ||||||||
|
||||||||
Converts a blob to its corresponding KZG point: | ||||||||
|
||||||||
```python | ||||||||
def lincomb(points: List[KZGCommitment], scalars: List[BLSFieldElement]) -> KZGCommitment: | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although it's in EL's point of view, this EIP uses SSZ to define the parameters and constants. It would be better to use the more general
Suggested change
|
||||||||
""" | ||||||||
BLS multiscalar multiplication. This function can be optimized using Pippenger's algorithm and variants. | ||||||||
""" | ||||||||
r = bls.Z1 | ||||||||
for x, a in zip(points, scalars): | ||||||||
r = bls.add(r, bls.multiply(x, a)) | ||||||||
return r | ||||||||
|
||||||||
def blob_to_kzg(blob: Blob) -> KZGCommitment: | ||||||||
computed_kzg = bls.Z1 | ||||||||
for value, point_kzg in zip(blob, KZG_SETUP_LAGRANGE): | ||||||||
assert value < BLS_MODULUS | ||||||||
computed_kzg = bls.add( | ||||||||
computed_kzg, | ||||||||
bls.multiply(point_kzg, value) | ||||||||
) | ||||||||
return computed_kzg | ||||||||
return lincomb(KZG_SETUP_LAGRANGE, blob) | ||||||||
``` | ||||||||
|
||||||||
Converts a KZG point into a versioned hash: | ||||||||
|
@@ -101,7 +105,7 @@ Verifies a KZG evaluation proof: | |||||||
def verify_kzg_proof(polynomial_kzg: KZGCommitment, | ||||||||
x: BLSFieldElement, | ||||||||
y: BLSFieldElement, | ||||||||
quotient_kzg: KZGCommitment): | ||||||||
quotient_kzg: KZGProof) -> bool: | ||||||||
# Verify: P - y = Q * (X - x) | ||||||||
X_minus_x = bls.add(KZG_SETUP_G2[1], bls.multiply(bls.G2, BLS_MODULUS - x)) | ||||||||
P_minus_y = bls.add(polynomial_kzg, bls.multiply(bls.G1, BLS_MODULUS - y)) | ||||||||
|
@@ -111,6 +115,39 @@ def verify_kzg_proof(polynomial_kzg: KZGCommitment, | |||||||
]) | ||||||||
``` | ||||||||
|
||||||||
Efficiently evaluates a polynomial in evaluation form using the barycentric formula | ||||||||
|
||||||||
```python | ||||||||
def bls_modular_inverse(x: BLSFieldElement) -> BLSFieldElement: | ||||||||
""" | ||||||||
Compute the modular inverse of x | ||||||||
i.e. return y such that x * y % BLS_MODULUS == 1 and return 0 for x == 0 | ||||||||
""" | ||||||||
return pow(x, -1, BLS_MODULUS) if x != 0 else 0 | ||||||||
|
||||||||
|
||||||||
def div(x, y): | ||||||||
"""Divide two field elements: `x` by `y`""" | ||||||||
return x * bls_modular_inverse(y) % BLS_MODULUS | ||||||||
|
||||||||
|
||||||||
def evaluate_polynomial_in_evaluation_form(poly: List[BLSFieldElement], x: BLSFieldElement) -> BLSFieldElement: | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Difference from danksharding PR: Switch barycentric code with this one from research repo: https://github.com/ethereum/research/blob/master/verkle_trie/kzg_utils.py#L35 The barycentric formula code from the danksharding PR was not giving the right results based on some rudimentary tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto
Suggested change
|
||||||||
""" | ||||||||
Evaluate a polynomial (in evaluation form) at an arbitrary point `x` | ||||||||
Uses the barycentric formula: | ||||||||
f(x) = (1 - x**WIDTH) / WIDTH * sum_(i=0)^WIDTH (f(DOMAIN[i]) * DOMAIN[i]) / (x - DOMAIN[i]) | ||||||||
""" | ||||||||
width = len(poly) | ||||||||
assert width == FIELD_ELEMENTS_PER_BLOB | ||||||||
inverse_width = bls_modular_inverse(width) | ||||||||
|
||||||||
for i in range(width): | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. initialize
Suggested change
|
||||||||
r += div(poly[i] * ROOTS_OF_UNITY[i], (x - ROOTS_OF_UNITY[i]) ) | ||||||||
r = r * (pow(x, width, BLS_MODULUS) - 1) * inverse_width % BLS_MODULUS | ||||||||
|
||||||||
return r | ||||||||
``` | ||||||||
|
||||||||
Approximates `2 ** (numerator / denominator)`, with the simplest possible approximation that is continuous and has a continuous derivative: | ||||||||
|
||||||||
```python | ||||||||
|
@@ -321,20 +358,68 @@ class BlobTransactionNetworkWrapper(Container): | |||||||
blob_kzgs: List[KZGCommitment, MAX_TX_WRAP_KZG_COMMITMENTS] | ||||||||
# BLSFieldElement = uint256 | ||||||||
blobs: List[Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB], LIMIT_BLOBS_PER_TX] | ||||||||
# KZGProof = Bytes48 | ||||||||
kzg_aggregated_proof: KZGProof | ||||||||
``` | ||||||||
|
||||||||
We do network-level validation of `BlobTransactionNetworkWrapper` objects as follows: | ||||||||
|
||||||||
```python | ||||||||
def hash_to_bls_field(x: Container) -> BLSFieldElement: | ||||||||
""" | ||||||||
This function is used to generate Fiat-Shamir challenges. The output is not uniform over the BLS field. | ||||||||
""" | ||||||||
return int.from_bytes(hash_tree_root(x), "little") % BLS_MODULUS | ||||||||
|
||||||||
|
||||||||
def compute_powers(x: BLSFieldElement, n: uint64) -> List[BLSFieldElement]: | ||||||||
current_power = 1 | ||||||||
powers = [] | ||||||||
for _ in range(n): | ||||||||
powers.append(BLSFieldElement(current_power)) | ||||||||
current_power = current_power * int(x) % BLS_MODULUS | ||||||||
return powers | ||||||||
|
||||||||
def vector_lincomb(vectors: List[List[BLSFieldElement]], scalars: List[BLSFieldElement]) -> List[BLSFieldElement]: | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
""" | ||||||||
Given a list of vectors, compute the linear combination of each column with `scalars`, and return the resulting | ||||||||
vector. | ||||||||
""" | ||||||||
r = [0]*len(vectors[0]) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
for v, a in zip(vectors, scalars): | ||||||||
for i, x in enumerate(v): | ||||||||
r[i] = (r[i] + a * x) % BLS_MODULUS | ||||||||
return [BLSFieldElement(x) for x in r] | ||||||||
|
||||||||
def validate_blob_transaction_wrapper(wrapper: BlobTransactionNetworkWrapper): | ||||||||
versioned_hashes = wrapper.tx.message.blob_versioned_hashes | ||||||||
kzgs = wrapper.blob_kzgs | ||||||||
commitments = wrapper.blob_kzgs | ||||||||
blobs = wrapper.blobs | ||||||||
assert len(versioned_hashes) == len(kzgs) == len(blobs) | ||||||||
for versioned_hash, kzg, blob in zip(versioned_hashes, kzgs, blobs): | ||||||||
# note: assert blob is not malformatted | ||||||||
assert kzg == blob_to_kzg(blob) | ||||||||
assert versioned_hash == kzg_to_versioned_hash(kzg) | ||||||||
# note: assert blobs are not malformatted | ||||||||
|
||||||||
assert len(versioned_hashes) == len(commitments) == len(blobs) | ||||||||
number_of_blobs = len(blobs) | ||||||||
|
||||||||
# Generate random linear combination challenges | ||||||||
r = hash_to_bls_field([blobs, commitments]) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
class BlobsAndCommmitments(Container):
blobs: List[Blob, MAX_BLOBS_PER_BLOCK]
blob_kzgs: List[KZGCommitment, MAX_BLOBS_PER_BLOCK] and do r = hash_to_bls_field(BlobsAndCommmitments(blobs=blobs, blob_kzgs=commitments)) |
||||||||
r_powers = compute_powers(r, number_of_blobs) | ||||||||
|
||||||||
# Compute commitment to aggregated polynomial | ||||||||
aggregated_poly_commitment = lincomb(commitments, r_powers) | ||||||||
|
||||||||
# Create aggregated polynomial in evaluation form | ||||||||
aggregated_poly = vector_lincomb(blobs, r_powers) | ||||||||
|
||||||||
# Generate challenge `x` and evaluate the aggregated polynomial at `x` | ||||||||
x = hash_to_bls_field([aggregated_poly, aggregated_poly_commitment]) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto, need casting |
||||||||
y = evaluate_polynomial_in_evaluation_form(aggregated_poly, x) | ||||||||
|
||||||||
# Verify aggregated proof | ||||||||
assert verify_kzg_proof(aggregated_poly_commitment, x, y, wrapper.kzg_aggregated_proof) | ||||||||
|
||||||||
# Now that all commitments have been verified, check that versioned_hashes matches the commitments | ||||||||
for versioned_hash, commitment in zip(versioned_hashes, commitments): | ||||||||
assert versioned_hash == kzg_to_versioned_hash(commitment) | ||||||||
``` | ||||||||
|
||||||||
## Rationale | ||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Difference from danksharding PR: Add the roots of unity list as a global constant instead of having explicit code that generates them on demand.