|
25 | 25 | - [`bytes_to_kzg_commitment`](#bytes_to_kzg_commitment) |
26 | 26 | - [`bytes_to_kzg_proof`](#bytes_to_kzg_proof) |
27 | 27 | - [`blob_to_polynomial`](#blob_to_polynomial) |
28 | | - - [`compute_challenges`](#compute_challenges) |
| 28 | + - [`compute_challenge`](#compute_challenge) |
29 | 29 | - [`bls_modular_inverse`](#bls_modular_inverse) |
30 | 30 | - [`div`](#div) |
31 | 31 | - [`g1_lincomb`](#g1_lincomb) |
32 | | - - [`poly_lincomb`](#poly_lincomb) |
33 | 32 | - [`compute_powers`](#compute_powers) |
34 | 33 | - [Polynomials](#polynomials) |
35 | 34 | - [`evaluate_polynomial_in_evaluation_form`](#evaluate_polynomial_in_evaluation_form) |
36 | 35 | - [KZG](#kzg) |
37 | 36 | - [`blob_to_kzg_commitment`](#blob_to_kzg_commitment) |
38 | 37 | - [`verify_kzg_proof`](#verify_kzg_proof) |
39 | 38 | - [`verify_kzg_proof_impl`](#verify_kzg_proof_impl) |
| 39 | + - [`verify_kzg_proof_batch`](#verify_kzg_proof_batch) |
40 | 40 | - [`compute_kzg_proof`](#compute_kzg_proof) |
41 | 41 | - [`compute_quotient_eval_within_domain`](#compute_quotient_eval_within_domain) |
42 | 42 | - [`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) |
46 | 46 |
|
47 | 47 | <!-- END doctoc generated TOC please keep comment here to allow auto update --> |
48 | 48 | <!-- /TOC --> |
@@ -84,6 +84,7 @@ Public functions MUST accept raw bytes as input and perform the required cryptog |
84 | 84 | | - | - | |
85 | 85 | | `FIELD_ELEMENTS_PER_BLOB` | `uint64(4096)` | |
86 | 86 | | `FIAT_SHAMIR_PROTOCOL_DOMAIN` | `b'FSBLOBVERIFY_V1_'` | |
| 87 | +| `RANDOM_CHALLENGE_KZG_BATCH_DOMAIN` | `b'RCKZGBATCH___V1_'` | |
87 | 88 |
|
88 | 89 | ### Crypto |
89 | 90 |
|
@@ -223,44 +224,24 @@ def blob_to_polynomial(blob: Blob) -> Polynomial: |
223 | 224 | return polynomial |
224 | 225 | ``` |
225 | 226 |
|
226 | | -#### `compute_challenges` |
| 227 | +#### `compute_challenge` |
227 | 228 |
|
228 | 229 | ```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: |
231 | 232 | """ |
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. |
242 | 234 | """ |
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 |
247 | 235 |
|
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 |
252 | 239 |
|
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 |
262 | 242 |
|
263 | | - return r_powers, eval_challenge |
| 243 | + # Transcript has been prepared: time to create the challenge |
| 244 | + return hash_to_bls_field(data) |
264 | 245 | ``` |
265 | 246 |
|
266 | 247 | #### `bls_modular_inverse` |
@@ -298,23 +279,6 @@ def g1_lincomb(points: Sequence[KZGCommitment], scalars: Sequence[BLSFieldElemen |
298 | 279 | return KZGCommitment(bls.G1_to_bytes48(result)) |
299 | 280 | ``` |
300 | 281 |
|
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 | | - |
318 | 282 | #### `compute_powers` |
319 | 283 |
|
320 | 284 | ```python |
@@ -415,6 +379,50 @@ def verify_kzg_proof_impl(commitment: KZGCommitment, |
415 | 379 | ]) |
416 | 380 | ``` |
417 | 381 |
|
| 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 | + |
418 | 426 | #### `compute_kzg_proof` |
419 | 427 |
|
420 | 428 | ```python |
@@ -486,72 +494,67 @@ def compute_kzg_proof_impl(polynomial: Polynomial, z: BLSFieldElement) -> KZGPro |
486 | 494 | return KZGProof(g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), quotient_polynomial)) |
487 | 495 | ``` |
488 | 496 |
|
489 | | -#### `compute_aggregated_poly_and_commitment` |
| 497 | +#### `compute_blob_kzg_proof` |
490 | 498 |
|
491 | 499 | ```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: |
495 | 501 | """ |
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. |
499 | 504 | """ |
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) |
515 | 509 | ``` |
516 | 510 |
|
517 | | -#### `compute_aggregate_kzg_proof` |
| 511 | +#### `verify_blob_kzg_proof` |
518 | 512 |
|
519 | 513 | ```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: |
521 | 517 | """ |
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 | +
|
523 | 520 | Public method. |
524 | 521 | """ |
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) |
531 | 533 | ``` |
532 | 534 |
|
533 | | -#### `verify_aggregate_kzg_proof` |
| 535 | +#### `verify_blob_kzg_proof_batch` |
534 | 536 |
|
535 | 537 | ```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: |
539 | 541 | """ |
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. |
541 | 543 |
|
542 | 544 | Public method. |
543 | 545 | """ |
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 | | - ) |
550 | 546 |
|
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)) |
553 | 558 |
|
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) |
557 | 560 | ``` |
0 commit comments