Skip to content

Commit

Permalink
Merge pull request #3236 from ethereum/kzg_multi_verify
Browse files Browse the repository at this point in the history
Add KZG multi verify function
  • Loading branch information
dankrad committed Feb 15, 2023
2 parents a7a1256 + 078d62e commit 59129e4
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 103 deletions.
5 changes: 3 additions & 2 deletions specs/deneb/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ def validate_blobs_sidecar(slot: Slot,
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
# 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)
# Disabled because not available before switch to single blob sidecars
# assert verify_aggregate_kzg_proof(blobs, expected_kzg_commitments, kzg_aggregated_proof)
```

#### `is_data_available`
Expand Down
203 changes: 103 additions & 100 deletions specs/deneb/polynomial-commitments.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,24 @@
- [`bytes_to_kzg_commitment`](#bytes_to_kzg_commitment)
- [`bytes_to_kzg_proof`](#bytes_to_kzg_proof)
- [`blob_to_polynomial`](#blob_to_polynomial)
- [`compute_challenges`](#compute_challenges)
- [`compute_challenge`](#compute_challenge)
- [`bls_modular_inverse`](#bls_modular_inverse)
- [`div`](#div)
- [`g1_lincomb`](#g1_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)
- [`verify_kzg_proof_impl`](#verify_kzg_proof_impl)
- [`verify_kzg_proof_batch`](#verify_kzg_proof_batch)
- [`compute_kzg_proof`](#compute_kzg_proof)
- [`compute_quotient_eval_within_domain`](#compute_quotient_eval_within_domain)
- [`compute_kzg_proof_impl`](#compute_kzg_proof_impl)
- [`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)
- [`compute_blob_kzg_proof`](#compute_blob_kzg_proof)
- [`verify_blob_kzg_proof`](#verify_blob_kzg_proof)
- [`verify_blob_kzg_proof_batch`](#verify_blob_kzg_proof_batch)

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

### Crypto

Expand Down Expand Up @@ -223,44 +224,24 @@ def blob_to_polynomial(blob: Blob) -> Polynomial:
return polynomial
```

#### `compute_challenges`
#### `compute_challenge`

```python
def compute_challenges(polynomials: Sequence[Polynomial],
commitments: Sequence[KZGCommitment]) -> Tuple[Sequence[BLSFieldElement], BLSFieldElement]:
def compute_challenge(blob: Blob,
commitment: KZGCommitment) -> BLSFieldElement:
"""
Return the Fiat-Shamir challenges required by the rest of the protocol.
The Fiat-Shamir logic works as per the following pseudocode:
hashed_data = hash(DOMAIN_SEPARATOR, polynomials, commitments)
r = hash(hashed_data, 0)
r_powers = [1, 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.
Return the Fiat-Shamir challenge required by the rest of the protocol.
"""
# Append the number of polynomials and the degree of each polynomial as a domain separator
num_polynomials = int.to_bytes(len(polynomials), 8, ENDIANNESS)
degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, ENDIANNESS)
data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly + num_polynomials

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

# Append serialized G1 points
for commitment in commitments:
data += commitment

# Transcript has been prepared: time to create the challenges
hashed_data = hash(data)
r = hash_to_bls_field(hashed_data + b'\x00')
r_powers = compute_powers(r, len(commitments))
eval_challenge = hash_to_bls_field(hashed_data + b'\x01')
data += blob
data += commitment

return r_powers, eval_challenge
# Transcript has been prepared: time to create the challenge
return hash_to_bls_field(data)
```

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

#### `poly_lincomb`

```python
def poly_lincomb(polys: Sequence[Polynomial],
scalars: Sequence[BLSFieldElement]) -> Polynomial:
"""
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.
"""
assert len(polys) == len(scalars)
result = [0] * FIELD_ELEMENTS_PER_BLOB
for v, s in zip(polys, scalars):
for i, x in enumerate(v):
result[i] = (result[i] + int(s) * int(x)) % BLS_MODULUS
return Polynomial([BLSFieldElement(x) for x in result])
```

#### `compute_powers`

```python
Expand Down Expand Up @@ -415,6 +379,50 @@ def verify_kzg_proof_impl(commitment: KZGCommitment,
])
```

#### `verify_kzg_proof_batch`

```python
def verify_kzg_proof_batch(commitments: Sequence[KZGCommitment],
zs: Sequence[BLSFieldElement],
ys: Sequence[BLSFieldElement],
proofs: Sequence[KZGProof]) -> bool:
"""
Verify multiple KZG proofs efficiently.
"""

assert len(commitments) == len(zs) == len(ys) == len(proofs)

# Compute a random challenge. Note that it does not have to be computed from a hash,
# r just has to be random.
degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, ENDIANNESS)
num_commitments = int.to_bytes(len(commitments), 8, ENDIANNESS)
data = RANDOM_CHALLENGE_KZG_BATCH_DOMAIN + degree_poly + num_commitments

# Append all inputs to the transcript before we hash
for commitment, z, y, proof in zip(commitments, zs, ys, proofs):
data += commitment \
+ int.to_bytes(z, BYTES_PER_FIELD_ELEMENT, ENDIANNESS) \
+ int.to_bytes(y, BYTES_PER_FIELD_ELEMENT, ENDIANNESS) \
+ proof

r = hash_to_bls_field(data)
r_powers = compute_powers(r, len(commitments))

# Verify: e(sum r^i proof_i, [s]) ==
# e(sum r^i (commitment_i - [y_i]) + sum r^i z_i proof_i, [1])
proof_lincomb = g1_lincomb(proofs, r_powers)
proof_z_lincomb = g1_lincomb(proofs, [z * r_power for z, r_power in zip(zs, r_powers)])
C_minus_ys = [bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1, BLS_MODULUS - y))
for commitment, y in zip(commitments, ys)]
C_minus_y_as_KZGCommitments = [KZGCommitment(bls.G1_to_bytes48(x)) for x in C_minus_ys]
C_minus_y_lincomb = g1_lincomb(C_minus_y_as_KZGCommitments, r_powers)

return bls.pairing_check([
[proof_lincomb, bls.neg(KZG_SETUP_G2[1])],
[bls.add(C_minus_y_lincomb, proof_z_lincomb), bls.G2]
])
```

#### `compute_kzg_proof`

```python
Expand Down Expand Up @@ -486,72 +494,67 @@ def compute_kzg_proof_impl(polynomial: Polynomial, z: BLSFieldElement) -> KZGPro
return KZGProof(g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), quotient_polynomial))
```

#### `compute_aggregated_poly_and_commitment`
#### `compute_blob_kzg_proof`

```python
def compute_aggregated_poly_and_commitment(
blobs: Sequence[Blob],
kzg_commitments: Sequence[KZGCommitment]) -> Tuple[Polynomial, KZGCommitment, BLSFieldElement]:
def compute_blob_kzg_proof(blob: Blob) -> KZGProof:
"""
Return (1) the aggregated polynomial, (2) the aggregated KZG commitment,
and (3) the polynomial evaluation random challenge.
This function should also work with blobs == [] and kzg_commitments == []
Given a blob, return the KZG proof that is used to verify it against the commitment.
Public method.
"""
assert len(blobs) == len(kzg_commitments)

# Convert blobs to polynomials
polynomials = [blob_to_polynomial(blob) for blob in blobs]

# Generate random linear combination and evaluation challenges
r_powers, evaluation_challenge = compute_challenges(polynomials, kzg_commitments)

# Create aggregated polynomial in evaluation form
aggregated_poly = poly_lincomb(polynomials, r_powers)

# Compute commitment to aggregated polynomial
aggregated_poly_commitment = KZGCommitment(g1_lincomb(kzg_commitments, r_powers))

return aggregated_poly, aggregated_poly_commitment, evaluation_challenge
commitment = blob_to_kzg_commitment(blob)
polynomial = blob_to_polynomial(blob)
evaluation_challenge = compute_challenge(blob, commitment)
return compute_kzg_proof_impl(polynomial, evaluation_challenge)
```

#### `compute_aggregate_kzg_proof`
#### `verify_blob_kzg_proof`

```python
def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof:
def verify_blob_kzg_proof(blob: Blob,
commitment_bytes: Bytes48,
proof_bytes: Bytes48) -> bool:
"""
Given a list of blobs, return the aggregated KZG proof that is used to verify them against their commitments.
Given a blob and a KZG proof, verify that the blob data corresponds to the provided commitment.
Public method.
"""
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_impl(aggregated_poly, evaluation_challenge)
commitment = bytes_to_kzg_commitment(commitment_bytes)

polynomial = blob_to_polynomial(blob)
evaluation_challenge = compute_challenge(blob, commitment)

# Evaluate polynomial at `evaluation_challenge`
y = evaluate_polynomial_in_evaluation_form(polynomial, evaluation_challenge)

# Verify proof
proof = bytes_to_kzg_proof(proof_bytes)
return verify_kzg_proof_impl(commitment, evaluation_challenge, y, proof)
```

#### `verify_aggregate_kzg_proof`
#### `verify_blob_kzg_proof_batch`

```python
def verify_aggregate_kzg_proof(blobs: Sequence[Blob],
commitments_bytes: Sequence[Bytes48],
aggregated_proof_bytes: Bytes48) -> bool:
def verify_blob_kzg_proof_batch(blobs: Sequence[Blob],
commitments_bytes: Sequence[Bytes48],
proofs_bytes: Sequence[Bytes48]) -> bool:
"""
Given a list of blobs and an aggregated KZG proof, verify that they correspond to the provided commitments.
Given a list of blobs and blob KZG proofs, verify that they correspond to the provided commitments.
Public method.
"""
commitments = [bytes_to_kzg_commitment(c) for c in commitments_bytes]

aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment(
blobs,
commitments
)

# Evaluate aggregated polynomial at `evaluation_challenge` (evaluation function checks for div-by-zero)
y = evaluate_polynomial_in_evaluation_form(aggregated_poly, evaluation_challenge)
assert len(blobs) == len(commitments_bytes) == len(proofs_bytes)

commitments, evaluation_challenges, ys, proofs = [], [], [], []
for blob, commitment_bytes, proof_bytes in zip(blobs, commitments_bytes, proofs_bytes):
commitment = bytes_to_kzg_commitment(commitment_bytes)
commitments.append(commitment)
polynomial = blob_to_polynomial(blob)
evaluation_challenge = compute_challenge(blob, commitment)
evaluation_challenges.append(evaluation_challenge)
ys.append(evaluate_polynomial_in_evaluation_form(polynomial, evaluation_challenge))
proofs.append(bytes_to_kzg_proof(proof_bytes))

# Verify aggregated proof
aggregated_proof = bytes_to_kzg_proof(aggregated_proof_bytes)
return verify_kzg_proof_impl(aggregated_poly_commitment, evaluation_challenge, y, aggregated_proof)
return verify_kzg_proof_batch(commitments, evaluation_challenges, ys, proofs)
```
3 changes: 2 additions & 1 deletion specs/deneb/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ 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_aggregate_kzg_proof(blobs),
# Disabled because not available before switch to single blob sidecars
kzg_aggregated_proof=KZGProof(), # compute_aggregate_kzg_proof(blobs),
)
```

Expand Down

0 comments on commit 59129e4

Please sign in to comment.