Skip to content

Commit 59129e4

Browse files
authored
Merge pull request #3236 from ethereum/kzg_multi_verify
Add KZG multi verify function
2 parents a7a1256 + 078d62e commit 59129e4

File tree

3 files changed

+108
-103
lines changed

3 files changed

+108
-103
lines changed

specs/deneb/fork-choice.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ def validate_blobs_sidecar(slot: Slot,
4545
assert slot == blobs_sidecar.beacon_block_slot
4646
assert beacon_block_root == blobs_sidecar.beacon_block_root
4747
blobs = blobs_sidecar.blobs
48-
kzg_aggregated_proof = blobs_sidecar.kzg_aggregated_proof
48+
# kzg_aggregated_proof = blobs_sidecar.kzg_aggregated_proof
4949
assert len(expected_kzg_commitments) == len(blobs)
5050

51-
assert verify_aggregate_kzg_proof(blobs, expected_kzg_commitments, kzg_aggregated_proof)
51+
# Disabled because not available before switch to single blob sidecars
52+
# assert verify_aggregate_kzg_proof(blobs, expected_kzg_commitments, kzg_aggregated_proof)
5253
```
5354

5455
#### `is_data_available`

specs/deneb/polynomial-commitments.md

Lines changed: 103 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,24 @@
2525
- [`bytes_to_kzg_commitment`](#bytes_to_kzg_commitment)
2626
- [`bytes_to_kzg_proof`](#bytes_to_kzg_proof)
2727
- [`blob_to_polynomial`](#blob_to_polynomial)
28-
- [`compute_challenges`](#compute_challenges)
28+
- [`compute_challenge`](#compute_challenge)
2929
- [`bls_modular_inverse`](#bls_modular_inverse)
3030
- [`div`](#div)
3131
- [`g1_lincomb`](#g1_lincomb)
32-
- [`poly_lincomb`](#poly_lincomb)
3332
- [`compute_powers`](#compute_powers)
3433
- [Polynomials](#polynomials)
3534
- [`evaluate_polynomial_in_evaluation_form`](#evaluate_polynomial_in_evaluation_form)
3635
- [KZG](#kzg)
3736
- [`blob_to_kzg_commitment`](#blob_to_kzg_commitment)
3837
- [`verify_kzg_proof`](#verify_kzg_proof)
3938
- [`verify_kzg_proof_impl`](#verify_kzg_proof_impl)
39+
- [`verify_kzg_proof_batch`](#verify_kzg_proof_batch)
4040
- [`compute_kzg_proof`](#compute_kzg_proof)
4141
- [`compute_quotient_eval_within_domain`](#compute_quotient_eval_within_domain)
4242
- [`compute_kzg_proof_impl`](#compute_kzg_proof_impl)
43-
- [`compute_aggregated_poly_and_commitment`](#compute_aggregated_poly_and_commitment)
44-
- [`compute_aggregate_kzg_proof`](#compute_aggregate_kzg_proof)
45-
- [`verify_aggregate_kzg_proof`](#verify_aggregate_kzg_proof)
43+
- [`compute_blob_kzg_proof`](#compute_blob_kzg_proof)
44+
- [`verify_blob_kzg_proof`](#verify_blob_kzg_proof)
45+
- [`verify_blob_kzg_proof_batch`](#verify_blob_kzg_proof_batch)
4646

4747
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
4848
<!-- /TOC -->
@@ -84,6 +84,7 @@ Public functions MUST accept raw bytes as input and perform the required cryptog
8484
| - | - |
8585
| `FIELD_ELEMENTS_PER_BLOB` | `uint64(4096)` |
8686
| `FIAT_SHAMIR_PROTOCOL_DOMAIN` | `b'FSBLOBVERIFY_V1_'` |
87+
| `RANDOM_CHALLENGE_KZG_BATCH_DOMAIN` | `b'RCKZGBATCH___V1_'` |
8788

8889
### Crypto
8990

@@ -223,44 +224,24 @@ def blob_to_polynomial(blob: Blob) -> Polynomial:
223224
return polynomial
224225
```
225226

226-
#### `compute_challenges`
227+
#### `compute_challenge`
227228

228229
```python
229-
def compute_challenges(polynomials: Sequence[Polynomial],
230-
commitments: Sequence[KZGCommitment]) -> Tuple[Sequence[BLSFieldElement], BLSFieldElement]:
230+
def compute_challenge(blob: Blob,
231+
commitment: KZGCommitment) -> BLSFieldElement:
231232
"""
232-
Return the Fiat-Shamir challenges required by the rest of the protocol.
233-
The Fiat-Shamir logic works as per the following pseudocode:
234-
235-
hashed_data = hash(DOMAIN_SEPARATOR, polynomials, commitments)
236-
r = hash(hashed_data, 0)
237-
r_powers = [1, r, r**2, r**3, ...]
238-
eval_challenge = hash(hashed_data, 1)
239-
240-
Then return `r_powers` and `eval_challenge` after converting them to BLS field elements.
241-
The resulting field elements are not uniform over the BLS field.
233+
Return the Fiat-Shamir challenge required by the rest of the protocol.
242234
"""
243-
# Append the number of polynomials and the degree of each polynomial as a domain separator
244-
num_polynomials = int.to_bytes(len(polynomials), 8, ENDIANNESS)
245-
degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, ENDIANNESS)
246-
data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly + num_polynomials
247235

248-
# Append each polynomial which is composed by field elements
249-
for poly in polynomials:
250-
for field_element in poly:
251-
data += int.to_bytes(field_element, BYTES_PER_FIELD_ELEMENT, ENDIANNESS)
236+
# Append the degree of the polynomial as a domain separator
237+
degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 16, ENDIANNESS)
238+
data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly
252239

253-
# Append serialized G1 points
254-
for commitment in commitments:
255-
data += commitment
256-
257-
# Transcript has been prepared: time to create the challenges
258-
hashed_data = hash(data)
259-
r = hash_to_bls_field(hashed_data + b'\x00')
260-
r_powers = compute_powers(r, len(commitments))
261-
eval_challenge = hash_to_bls_field(hashed_data + b'\x01')
240+
data += blob
241+
data += commitment
262242

263-
return r_powers, eval_challenge
243+
# Transcript has been prepared: time to create the challenge
244+
return hash_to_bls_field(data)
264245
```
265246

266247
#### `bls_modular_inverse`
@@ -298,23 +279,6 @@ def g1_lincomb(points: Sequence[KZGCommitment], scalars: Sequence[BLSFieldElemen
298279
return KZGCommitment(bls.G1_to_bytes48(result))
299280
```
300281

301-
#### `poly_lincomb`
302-
303-
```python
304-
def poly_lincomb(polys: Sequence[Polynomial],
305-
scalars: Sequence[BLSFieldElement]) -> Polynomial:
306-
"""
307-
Given a list of ``polynomials``, interpret it as a 2D matrix and compute the linear combination
308-
of each column with `scalars`: return the resulting polynomials.
309-
"""
310-
assert len(polys) == len(scalars)
311-
result = [0] * FIELD_ELEMENTS_PER_BLOB
312-
for v, s in zip(polys, scalars):
313-
for i, x in enumerate(v):
314-
result[i] = (result[i] + int(s) * int(x)) % BLS_MODULUS
315-
return Polynomial([BLSFieldElement(x) for x in result])
316-
```
317-
318282
#### `compute_powers`
319283

320284
```python
@@ -415,6 +379,50 @@ def verify_kzg_proof_impl(commitment: KZGCommitment,
415379
])
416380
```
417381

382+
#### `verify_kzg_proof_batch`
383+
384+
```python
385+
def verify_kzg_proof_batch(commitments: Sequence[KZGCommitment],
386+
zs: Sequence[BLSFieldElement],
387+
ys: Sequence[BLSFieldElement],
388+
proofs: Sequence[KZGProof]) -> bool:
389+
"""
390+
Verify multiple KZG proofs efficiently.
391+
"""
392+
393+
assert len(commitments) == len(zs) == len(ys) == len(proofs)
394+
395+
# Compute a random challenge. Note that it does not have to be computed from a hash,
396+
# r just has to be random.
397+
degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, ENDIANNESS)
398+
num_commitments = int.to_bytes(len(commitments), 8, ENDIANNESS)
399+
data = RANDOM_CHALLENGE_KZG_BATCH_DOMAIN + degree_poly + num_commitments
400+
401+
# Append all inputs to the transcript before we hash
402+
for commitment, z, y, proof in zip(commitments, zs, ys, proofs):
403+
data += commitment \
404+
+ int.to_bytes(z, BYTES_PER_FIELD_ELEMENT, ENDIANNESS) \
405+
+ int.to_bytes(y, BYTES_PER_FIELD_ELEMENT, ENDIANNESS) \
406+
+ proof
407+
408+
r = hash_to_bls_field(data)
409+
r_powers = compute_powers(r, len(commitments))
410+
411+
# Verify: e(sum r^i proof_i, [s]) ==
412+
# e(sum r^i (commitment_i - [y_i]) + sum r^i z_i proof_i, [1])
413+
proof_lincomb = g1_lincomb(proofs, r_powers)
414+
proof_z_lincomb = g1_lincomb(proofs, [z * r_power for z, r_power in zip(zs, r_powers)])
415+
C_minus_ys = [bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1, BLS_MODULUS - y))
416+
for commitment, y in zip(commitments, ys)]
417+
C_minus_y_as_KZGCommitments = [KZGCommitment(bls.G1_to_bytes48(x)) for x in C_minus_ys]
418+
C_minus_y_lincomb = g1_lincomb(C_minus_y_as_KZGCommitments, r_powers)
419+
420+
return bls.pairing_check([
421+
[proof_lincomb, bls.neg(KZG_SETUP_G2[1])],
422+
[bls.add(C_minus_y_lincomb, proof_z_lincomb), bls.G2]
423+
])
424+
```
425+
418426
#### `compute_kzg_proof`
419427

420428
```python
@@ -486,72 +494,67 @@ def compute_kzg_proof_impl(polynomial: Polynomial, z: BLSFieldElement) -> KZGPro
486494
return KZGProof(g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), quotient_polynomial))
487495
```
488496

489-
#### `compute_aggregated_poly_and_commitment`
497+
#### `compute_blob_kzg_proof`
490498

491499
```python
492-
def compute_aggregated_poly_and_commitment(
493-
blobs: Sequence[Blob],
494-
kzg_commitments: Sequence[KZGCommitment]) -> Tuple[Polynomial, KZGCommitment, BLSFieldElement]:
500+
def compute_blob_kzg_proof(blob: Blob) -> KZGProof:
495501
"""
496-
Return (1) the aggregated polynomial, (2) the aggregated KZG commitment,
497-
and (3) the polynomial evaluation random challenge.
498-
This function should also work with blobs == [] and kzg_commitments == []
502+
Given a blob, return the KZG proof that is used to verify it against the commitment.
503+
Public method.
499504
"""
500-
assert len(blobs) == len(kzg_commitments)
501-
502-
# Convert blobs to polynomials
503-
polynomials = [blob_to_polynomial(blob) for blob in blobs]
504-
505-
# Generate random linear combination and evaluation challenges
506-
r_powers, evaluation_challenge = compute_challenges(polynomials, kzg_commitments)
507-
508-
# Create aggregated polynomial in evaluation form
509-
aggregated_poly = poly_lincomb(polynomials, r_powers)
510-
511-
# Compute commitment to aggregated polynomial
512-
aggregated_poly_commitment = KZGCommitment(g1_lincomb(kzg_commitments, r_powers))
513-
514-
return aggregated_poly, aggregated_poly_commitment, evaluation_challenge
505+
commitment = blob_to_kzg_commitment(blob)
506+
polynomial = blob_to_polynomial(blob)
507+
evaluation_challenge = compute_challenge(blob, commitment)
508+
return compute_kzg_proof_impl(polynomial, evaluation_challenge)
515509
```
516510

517-
#### `compute_aggregate_kzg_proof`
511+
#### `verify_blob_kzg_proof`
518512

519513
```python
520-
def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof:
514+
def verify_blob_kzg_proof(blob: Blob,
515+
commitment_bytes: Bytes48,
516+
proof_bytes: Bytes48) -> bool:
521517
"""
522-
Given a list of blobs, return the aggregated KZG proof that is used to verify them against their commitments.
518+
Given a blob and a KZG proof, verify that the blob data corresponds to the provided commitment.
519+
523520
Public method.
524521
"""
525-
commitments = [blob_to_kzg_commitment(blob) for blob in blobs]
526-
aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment(
527-
blobs,
528-
commitments
529-
)
530-
return compute_kzg_proof_impl(aggregated_poly, evaluation_challenge)
522+
commitment = bytes_to_kzg_commitment(commitment_bytes)
523+
524+
polynomial = blob_to_polynomial(blob)
525+
evaluation_challenge = compute_challenge(blob, commitment)
526+
527+
# Evaluate polynomial at `evaluation_challenge`
528+
y = evaluate_polynomial_in_evaluation_form(polynomial, evaluation_challenge)
529+
530+
# Verify proof
531+
proof = bytes_to_kzg_proof(proof_bytes)
532+
return verify_kzg_proof_impl(commitment, evaluation_challenge, y, proof)
531533
```
532534

533-
#### `verify_aggregate_kzg_proof`
535+
#### `verify_blob_kzg_proof_batch`
534536

535537
```python
536-
def verify_aggregate_kzg_proof(blobs: Sequence[Blob],
537-
commitments_bytes: Sequence[Bytes48],
538-
aggregated_proof_bytes: Bytes48) -> bool:
538+
def verify_blob_kzg_proof_batch(blobs: Sequence[Blob],
539+
commitments_bytes: Sequence[Bytes48],
540+
proofs_bytes: Sequence[Bytes48]) -> bool:
539541
"""
540-
Given a list of blobs and an aggregated KZG proof, verify that they correspond to the provided commitments.
542+
Given a list of blobs and blob KZG proofs, verify that they correspond to the provided commitments.
541543
542544
Public method.
543545
"""
544-
commitments = [bytes_to_kzg_commitment(c) for c in commitments_bytes]
545-
546-
aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment(
547-
blobs,
548-
commitments
549-
)
550546

551-
# Evaluate aggregated polynomial at `evaluation_challenge` (evaluation function checks for div-by-zero)
552-
y = evaluate_polynomial_in_evaluation_form(aggregated_poly, evaluation_challenge)
547+
assert len(blobs) == len(commitments_bytes) == len(proofs_bytes)
548+
549+
commitments, evaluation_challenges, ys, proofs = [], [], [], []
550+
for blob, commitment_bytes, proof_bytes in zip(blobs, commitments_bytes, proofs_bytes):
551+
commitment = bytes_to_kzg_commitment(commitment_bytes)
552+
commitments.append(commitment)
553+
polynomial = blob_to_polynomial(blob)
554+
evaluation_challenge = compute_challenge(blob, commitment)
555+
evaluation_challenges.append(evaluation_challenge)
556+
ys.append(evaluate_polynomial_in_evaluation_form(polynomial, evaluation_challenge))
557+
proofs.append(bytes_to_kzg_proof(proof_bytes))
553558

554-
# Verify aggregated proof
555-
aggregated_proof = bytes_to_kzg_proof(aggregated_proof_bytes)
556-
return verify_kzg_proof_impl(aggregated_poly_commitment, evaluation_challenge, y, aggregated_proof)
559+
return verify_kzg_proof_batch(commitments, evaluation_challenges, ys, proofs)
557560
```

specs/deneb/validator.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ def get_blobs_sidecar(block: BeaconBlock, blobs: Sequence[Blob]) -> BlobsSidecar
9595
beacon_block_root=hash_tree_root(block),
9696
beacon_block_slot=block.slot,
9797
blobs=blobs,
98-
kzg_aggregated_proof=compute_aggregate_kzg_proof(blobs),
98+
# Disabled because not available before switch to single blob sidecars
99+
kzg_aggregated_proof=KZGProof(), # compute_aggregate_kzg_proof(blobs),
99100
)
100101
```
101102

0 commit comments

Comments
 (0)