From 1b4840c96704a1f1bcaa9d37df4d9c368ad2d047 Mon Sep 17 00:00:00 2001 From: kevaundray Date: Sat, 4 Mar 2023 19:20:01 +0000 Subject: [PATCH 1/3] Fix comment for `evaluate_polynomial_in_evaluation_form` to reflect that it can now also be used in the domain --- specs/deneb/polynomial-commitments.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/specs/deneb/polynomial-commitments.md b/specs/deneb/polynomial-commitments.md index 7b65b44b6..1b9de42a3 100644 --- a/specs/deneb/polynomial-commitments.md +++ b/specs/deneb/polynomial-commitments.md @@ -303,8 +303,10 @@ def compute_powers(x: BLSFieldElement, n: uint64) -> Sequence[BLSFieldElement]: def evaluate_polynomial_in_evaluation_form(polynomial: Polynomial, z: BLSFieldElement) -> BLSFieldElement: """ - Evaluate a polynomial (in evaluation form) at an arbitrary point ``z`` that is not in the domain. - Uses the barycentric formula: + Evaluate a polynomial (in evaluation form) at an arbitrary point ``z``. + - When ``z`` is in the domain, the evaluation can be found by indexing the polynomial at the + position that ``z`` is in the domain. + - When ``z`` is not in the domain, the barycentric formula is used: f(z) = (z**WIDTH - 1) / WIDTH * sum_(i=0)^WIDTH (f(DOMAIN[i]) * DOMAIN[i]) / (z - DOMAIN[i]) """ width = len(polynomial) From 15033d28b9f0f3a05a26de6ec093d55a7860a305 Mon Sep 17 00:00:00 2001 From: dankrad Date: Tue, 7 Mar 2023 17:50:56 +0000 Subject: [PATCH 2/3] Modify compute_[blob_]kzg_proof to remove superfluous computations (#3280) Add parameter `commitment` to `compute_blob_kzg_proof` and output `y` to `compute_kzg_proof` --- specs/deneb/polynomial-commitments.md | 17 ++--- specs/deneb/validator.md | 2 +- .../test_polynomial_commitments.py | 6 +- tests/formats/kzg/compute_blob_kzg_proof.md | 2 + tests/formats/kzg/compute_kzg_proof.md | 5 +- tests/generators/kzg_4844/main.py | 62 +++++++++++++------ 6 files changed, 62 insertions(+), 32 deletions(-) diff --git a/specs/deneb/polynomial-commitments.md b/specs/deneb/polynomial-commitments.md index 1b9de42a3..24ae08822 100644 --- a/specs/deneb/polynomial-commitments.md +++ b/specs/deneb/polynomial-commitments.md @@ -431,14 +431,15 @@ def verify_kzg_proof_batch(commitments: Sequence[KZGCommitment], #### `compute_kzg_proof` ```python -def compute_kzg_proof(blob: Blob, z: Bytes32) -> KZGProof: +def compute_kzg_proof(blob: Blob, z: Bytes32) -> Tuple[KZGProof, Bytes32]: """ Compute KZG proof at point `z` for the polynomial represented by `blob`. Do this by computing the quotient polynomial in evaluation form: q(x) = (p(x) - p(z)) / (x - z). Public method. """ polynomial = blob_to_polynomial(blob) - return compute_kzg_proof_impl(polynomial, bytes_to_bls_field(z)) + proof, y = compute_kzg_proof_impl(polynomial, bytes_to_bls_field(z)) + return proof, y.to_bytes(BYTES_PER_FIELD_ELEMENT, ENDIANNESS) ``` #### `compute_quotient_eval_within_domain` @@ -472,7 +473,7 @@ def compute_quotient_eval_within_domain(z: BLSFieldElement, #### `compute_kzg_proof_impl` ```python -def compute_kzg_proof_impl(polynomial: Polynomial, z: BLSFieldElement) -> KZGProof: +def compute_kzg_proof_impl(polynomial: Polynomial, z: BLSFieldElement) -> Tuple[KZGProof, BLSFieldElement]: """ Helper function for `compute_kzg_proof()` and `compute_blob_kzg_proof()`. """ @@ -496,21 +497,23 @@ def compute_kzg_proof_impl(polynomial: Polynomial, z: BLSFieldElement) -> KZGPro # Compute: q(x_i) = (p(x_i) - p(z)) / (x_i - z). quotient_polynomial[i] = div(a, b) - return KZGProof(g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), quotient_polynomial)) + return KZGProof(g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), quotient_polynomial)), y ``` #### `compute_blob_kzg_proof` ```python -def compute_blob_kzg_proof(blob: Blob) -> KZGProof: +def compute_blob_kzg_proof(blob: Blob, commitment_bytes: Bytes48) -> KZGProof: """ Given a blob, return the KZG proof that is used to verify it against the commitment. + This method does not verify that the commitment is correct with respect to `blob`. Public method. """ - commitment = blob_to_kzg_commitment(blob) + commitment = bytes_to_kzg_commitment(commitment_bytes) polynomial = blob_to_polynomial(blob) evaluation_challenge = compute_challenge(blob, commitment) - return compute_kzg_proof_impl(polynomial, evaluation_challenge) + proof, _ = compute_kzg_proof_impl(polynomial, evaluation_challenge) + return proof ``` #### `verify_blob_kzg_proof` diff --git a/specs/deneb/validator.md b/specs/deneb/validator.md index 77edb957f..50ab832e0 100644 --- a/specs/deneb/validator.md +++ b/specs/deneb/validator.md @@ -96,7 +96,7 @@ def get_blob_sidecars(block: BeaconBlock, blobs: Sequence[Blob]) -> Sequence[Blo block_parent_root=block.parent_root, blob=blob, kzg_commitment=block.body.blob_kzg_commitments[index], - kzg_proof=compute_blob_kzg_proof(blob), + kzg_proof=compute_blob_kzg_proof(blob, block.body.blob_kzg_commitments[index]), ) for index, blob in enumerate(blobs) ] diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/test_polynomial_commitments.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/test_polynomial_commitments.py index 67dce5c5b..e1e67d639 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/test_polynomial_commitments.py +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/test_polynomial_commitments.py @@ -18,9 +18,8 @@ def test_verify_kzg_proof(spec, state): blob = get_sample_blob(spec) commitment = spec.blob_to_kzg_commitment(blob) polynomial = spec.blob_to_polynomial(blob) - proof = spec.compute_kzg_proof_impl(polynomial, x) + proof, y = spec.compute_kzg_proof_impl(polynomial, x) - y = spec.evaluate_polynomial_in_evaluation_form(polynomial, x) assert spec.verify_kzg_proof_impl(commitment, x, y, proof) @@ -103,7 +102,6 @@ def test_compute_kzg_proof_within_domain(spec, state): roots_of_unity_brp = spec.bit_reversal_permutation(spec.ROOTS_OF_UNITY) for i, z in enumerate(roots_of_unity_brp): - proof = spec.compute_kzg_proof_impl(polynomial, z) + proof, y = spec.compute_kzg_proof_impl(polynomial, z) - y = spec.evaluate_polynomial_in_evaluation_form(polynomial, z) assert spec.verify_kzg_proof_impl(commitment, z, y, proof) diff --git a/tests/formats/kzg/compute_blob_kzg_proof.md b/tests/formats/kzg/compute_blob_kzg_proof.md index 512f60ecb..62fce3723 100644 --- a/tests/formats/kzg/compute_blob_kzg_proof.md +++ b/tests/formats/kzg/compute_blob_kzg_proof.md @@ -9,10 +9,12 @@ The test data is declared in a `data.yaml` file: ```yaml input: blob: Blob -- the data blob + commitment: Bytes48 -- the commitment to the blob output: KZGProof -- The blob KZG proof ``` - `blob` here is encoded as a string: hexadecimal encoding of `4096 * 32 = 131072` bytes, prefixed with `0x`. +- `commitment` here is encoded as a string: hexadecimal encoding of `48` bytes, prefixed with `0x`. All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. diff --git a/tests/formats/kzg/compute_kzg_proof.md b/tests/formats/kzg/compute_kzg_proof.md index bba13638f..0713d50d8 100644 --- a/tests/formats/kzg/compute_kzg_proof.md +++ b/tests/formats/kzg/compute_kzg_proof.md @@ -10,14 +10,15 @@ The test data is declared in a `data.yaml` file: input: blob: Blob -- the data blob representing a polynomial z: Bytes32 -- bytes encoding the BLS field element at which the polynomial should be evaluated -output: KZGProof -- The KZG proof +output: Tuple[KZGProof, Bytes32] -- The KZG proof and the value y = f(z) ``` - `blob` here is encoded as a string: hexadecimal encoding of `4096 * 32 = 131072` bytes, prefixed with `0x`. - `z` here is encoded as a string: hexadecimal encoding of `32` bytes representing a little endian encoded field element, prefixed with `0x`. +- `y` here is encoded as a string: hexadecimal encoding of `32` bytes representing a little endian encoded field element, prefixed with `0x`. All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. ## Condition -The `compute_kzg_proof` handler should compute the KZG proof for evaluating the polynomial represented by `blob` at `z`, and the result should match the expected `output`. If the blob is invalid (e.g. incorrect length or one of the 32-byte blocks does not represent a BLS field element) or `z` is not a valid BLS field element, it should error, i.e. the output should be `null`. +The `compute_kzg_proof` handler should compute the KZG proof as well as the value `y` for evaluating the polynomial represented by `blob` at `z`, and the result should match the expected `output`. If the blob is invalid (e.g. incorrect length or one of the 32-byte blocks does not represent a BLS field element) or `z` is not a valid BLS field element, it should error, i.e. the output should be `null`. diff --git a/tests/generators/kzg_4844/main.py b/tests/generators/kzg_4844/main.py index 616e2cc46..65f6405e5 100644 --- a/tests/generators/kzg_4844/main.py +++ b/tests/generators/kzg_4844/main.py @@ -115,14 +115,14 @@ def case02_compute_kzg_proof(): # Valid cases for blob in VALID_BLOBS: for z in VALID_ZS: - proof = spec.compute_kzg_proof(blob, z) + proof, y = spec.compute_kzg_proof(blob, z) identifier = f'{encode_hex(hash(blob))}_{encode_hex(z)}' yield f'compute_kzg_proof_case_valid_blob_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { 'blob': encode_hex(blob), 'z': encode_hex(z), }, - 'output': encode_hex(proof) + 'output': (encode_hex(proof), encode_hex(y)) } # Edge case: Invalid blobs @@ -156,9 +156,8 @@ def case03_verify_kzg_proof(): # Valid cases for blob in VALID_BLOBS: for z in VALID_ZS: - proof = spec.compute_kzg_proof(blob, z) + proof, y = spec.compute_kzg_proof(blob, z) commitment = spec.blob_to_kzg_commitment(blob) - y = evaluate_blob_at(blob, z) assert spec.verify_kzg_proof(commitment, z, y, proof) identifier = f'{encode_hex(hash(blob))}_{encode_hex(z)}' yield f'verify_kzg_proof_case_correct_proof_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { @@ -174,9 +173,9 @@ def case03_verify_kzg_proof(): # Incorrect proofs for blob in VALID_BLOBS: for z in VALID_ZS: - proof = bls_add_one(spec.compute_kzg_proof(blob, z)) + proof_orig, y = spec.compute_kzg_proof(blob, z) + proof = bls_add_one(proof_orig) commitment = spec.blob_to_kzg_commitment(blob) - y = evaluate_blob_at(blob, z) assert not spec.verify_kzg_proof(commitment, z, y, proof) identifier = f'{encode_hex(hash(blob))}_{encode_hex(z)}' yield f'verify_kzg_proof_case_incorrect_proof_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { @@ -192,9 +191,8 @@ def case03_verify_kzg_proof(): # Edge case: Invalid z for z in INVALID_ZS: blob, validz = VALID_BLOBS[4], VALID_ZS[1] - proof = spec.compute_kzg_proof(blob, validz) + proof, y = spec.compute_kzg_proof(blob, validz) commitment = spec.blob_to_kzg_commitment(blob) - y = VALID_ZS[3] expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) identifier = f'{encode_hex(hash(blob))}_{encode_hex(z)}' yield f'verify_kzg_proof_case_invalid_z_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { @@ -209,7 +207,7 @@ def case03_verify_kzg_proof(): # Edge case: Invalid y blob, z = VALID_BLOBS[1], VALID_ZS[1] - proof = spec.compute_kzg_proof(blob, z) + proof, _ = spec.compute_kzg_proof(blob, z) commitment = spec.blob_to_kzg_commitment(blob) y = INVALID_ZS[0] expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) @@ -257,9 +255,8 @@ def case03_verify_kzg_proof(): # Edge case: Invalid commitment, not in G1 blob, z = VALID_BLOBS[4], VALID_ZS[3] - proof = spec.compute_kzg_proof(blob, z) + proof, y = spec.compute_kzg_proof(blob, z) commitment = P1_NOT_IN_G1 - y = VALID_ZS[2] expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) yield 'verify_kzg_proof_case_commitment_not_in_G1', { 'input': { @@ -273,9 +270,8 @@ def case03_verify_kzg_proof(): # Edge case: Invalid commitment, not on curve blob, z = VALID_BLOBS[1], VALID_ZS[4] - proof = spec.compute_kzg_proof(blob, z) + proof, y = spec.compute_kzg_proof(blob, z) commitment = P1_NOT_ON_CURVE - y = VALID_ZS[3] expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) yield 'verify_kzg_proof_case_commitment_not_on_curve', { 'input': { @@ -291,32 +287,62 @@ def case03_verify_kzg_proof(): def case04_compute_blob_kzg_proof(): # Valid cases for blob in VALID_BLOBS: - proof = spec.compute_blob_kzg_proof(blob) + commitment = spec.blob_to_kzg_commitment(blob) + proof = spec.compute_blob_kzg_proof(blob, commitment) identifier = f'{encode_hex(hash(blob))}' yield f'compute_blob_kzg_proof_case_valid_blob_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { 'blob': encode_hex(blob), + 'commitment': encode_hex(commitment), }, 'output': encode_hex(proof) } # Edge case: Invalid blob for blob in INVALID_BLOBS: - expect_exception(spec.compute_blob_kzg_proof, blob) + commitment = G1 + expect_exception(spec.compute_blob_kzg_proof, blob, commitment) identifier = f'{encode_hex(hash(blob))}' yield f'compute_blob_kzg_proof_case_invalid_blob_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { 'blob': encode_hex(blob), + 'commitment': encode_hex(commitment), }, 'output': None } + # Edge case: Invalid commitment, not in G1 + commitment = P1_NOT_IN_G1 + blob = VALID_BLOBS[1] + expect_exception(spec.compute_blob_kzg_proof, blob, commitment) + identifier = f'{encode_hex(hash(blob))}' + yield 'compute_blob_kzg_proof_case_invalid_commitment_not_in_G1', { + 'input': { + 'blob': encode_hex(blob), + 'commitment': encode_hex(commitment), + }, + 'output': None + } + + # Edge case: Invalid commitment, not on curve + commitment = P1_NOT_ON_CURVE + blob = VALID_BLOBS[1] + expect_exception(spec.compute_blob_kzg_proof, blob, commitment) + identifier = f'{encode_hex(hash(blob))}' + yield 'compute_blob_kzg_proof_case_invalid_commitment_not_on_curve', { + 'input': { + 'blob': encode_hex(blob), + 'commitment': encode_hex(commitment), + }, + 'output': None + } + def case05_verify_blob_kzg_proof(): # Valid cases for blob in VALID_BLOBS: - proof = spec.compute_blob_kzg_proof(blob) commitment = spec.blob_to_kzg_commitment(blob) + proof = spec.compute_blob_kzg_proof(blob, commitment) assert spec.verify_blob_kzg_proof(blob, commitment, proof) identifier = f'{encode_hex(hash(blob))}' yield f'verify_blob_kzg_proof_case_correct_proof_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { @@ -330,8 +356,8 @@ def case05_verify_blob_kzg_proof(): # Incorrect proofs for blob in VALID_BLOBS: - proof = bls_add_one(spec.compute_blob_kzg_proof(blob)) commitment = spec.blob_to_kzg_commitment(blob) + proof = bls_add_one(spec.compute_blob_kzg_proof(blob, commitment)) assert not spec.verify_blob_kzg_proof(blob, commitment, proof) identifier = f'{encode_hex(hash(blob))}' yield f'verify_blob_kzg_proof_case_incorrect_proof_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { @@ -420,8 +446,8 @@ def case06_verify_blob_kzg_proof_batch(): proofs = [] commitments = [] for blob in VALID_BLOBS: - proofs.append(spec.compute_blob_kzg_proof(blob)) commitments.append(spec.blob_to_kzg_commitment(blob)) + proofs.append(spec.compute_blob_kzg_proof(blob, commitments[-1])) for i in range(len(proofs)): assert spec.verify_blob_kzg_proof_batch(VALID_BLOBS[:i], commitments[:i], proofs[:i]) From ccfe576dcc466a129da19d13f0418700af6515ab Mon Sep 17 00:00:00 2001 From: Justin Traglia <95511699+jtraglia@users.noreply.github.com> Date: Tue, 7 Mar 2023 14:56:55 -0700 Subject: [PATCH 3/3] Add KZG tests for input length inputs (#3282) --- specs/deneb/polynomial-commitments.md | 32 ++- tests/generators/kzg_4844/main.py | 272 ++++++++++++++++++++++---- 2 files changed, 260 insertions(+), 44 deletions(-) diff --git a/specs/deneb/polynomial-commitments.md b/specs/deneb/polynomial-commitments.md index 24ae08822..edf2062b2 100644 --- a/specs/deneb/polynomial-commitments.md +++ b/specs/deneb/polynomial-commitments.md @@ -72,7 +72,10 @@ Public functions MUST accept raw bytes as input and perform the required cryptog | Name | Value | Notes | | - | - | - | | `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` | Scalar field modulus of BLS12-381 | +| `BYTES_PER_COMMITMENT` | `uint64(48)` | The number of bytes in a KZG commitment | +| `BYTES_PER_PROOF` | `uint64(48)` | The number of bytes in a KZG proof | | `BYTES_PER_FIELD_ELEMENT` | `uint64(32)` | Bytes used to encode a BLS scalar field element | +| `BYTES_PER_BLOB` | `uint64(BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB)` | The number of bytes in a blob | | `G1_POINT_AT_INFINITY` | `Bytes48(b'\xc0' + b'\x00' * 47)` | Serialized form of the point at infinity on the G1 group | @@ -340,6 +343,7 @@ def blob_to_kzg_commitment(blob: Blob) -> KZGCommitment: """ Public method. """ + assert len(blob) == BYTES_PER_BLOB return g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), blob_to_polynomial(blob)) ``` @@ -347,17 +351,22 @@ def blob_to_kzg_commitment(blob: Blob) -> KZGCommitment: ```python def verify_kzg_proof(commitment_bytes: Bytes48, - z: Bytes32, - y: Bytes32, + z_bytes: Bytes32, + y_bytes: Bytes32, proof_bytes: Bytes48) -> bool: """ Verify KZG proof that ``p(z) == y`` where ``p(z)`` is the polynomial represented by ``polynomial_kzg``. Receives inputs as bytes. Public method. """ + assert len(commitment_bytes) == BYTES_PER_COMMITMENT + assert len(z_bytes) == BYTES_PER_FIELD_ELEMENT + assert len(y_bytes) == BYTES_PER_FIELD_ELEMENT + assert len(proof_bytes) == BYTES_PER_PROOF + return verify_kzg_proof_impl(bytes_to_kzg_commitment(commitment_bytes), - bytes_to_bls_field(z), - bytes_to_bls_field(y), + bytes_to_bls_field(z_bytes), + bytes_to_bls_field(y_bytes), bytes_to_kzg_proof(proof_bytes)) ``` @@ -431,14 +440,16 @@ def verify_kzg_proof_batch(commitments: Sequence[KZGCommitment], #### `compute_kzg_proof` ```python -def compute_kzg_proof(blob: Blob, z: Bytes32) -> Tuple[KZGProof, Bytes32]: +def compute_kzg_proof(blob: Blob, z_bytes: Bytes32) -> Tuple[KZGProof, Bytes32]: """ Compute KZG proof at point `z` for the polynomial represented by `blob`. Do this by computing the quotient polynomial in evaluation form: q(x) = (p(x) - p(z)) / (x - z). Public method. """ + assert len(blob) == BYTES_PER_BLOB + assert len(z_bytes) == BYTES_PER_FIELD_ELEMENT polynomial = blob_to_polynomial(blob) - proof, y = compute_kzg_proof_impl(polynomial, bytes_to_bls_field(z)) + proof, y = compute_kzg_proof_impl(polynomial, bytes_to_bls_field(z_bytes)) return proof, y.to_bytes(BYTES_PER_FIELD_ELEMENT, ENDIANNESS) ``` @@ -509,6 +520,8 @@ def compute_blob_kzg_proof(blob: Blob, commitment_bytes: Bytes48) -> KZGProof: This method does not verify that the commitment is correct with respect to `blob`. Public method. """ + assert len(blob) == BYTES_PER_BLOB + assert len(commitment_bytes) == BYTES_PER_COMMITMENT commitment = bytes_to_kzg_commitment(commitment_bytes) polynomial = blob_to_polynomial(blob) evaluation_challenge = compute_challenge(blob, commitment) @@ -527,6 +540,10 @@ def verify_blob_kzg_proof(blob: Blob, Public method. """ + assert len(blob) == BYTES_PER_BLOB + assert len(commitment_bytes) == BYTES_PER_COMMITMENT + assert len(proof_bytes) == BYTES_PER_PROOF + commitment = bytes_to_kzg_commitment(commitment_bytes) polynomial = blob_to_polynomial(blob) @@ -556,6 +573,9 @@ def verify_blob_kzg_proof_batch(blobs: Sequence[Blob], commitments, evaluation_challenges, ys, proofs = [], [], [], [] for blob, commitment_bytes, proof_bytes in zip(blobs, commitments_bytes, proofs_bytes): + assert len(blob) == BYTES_PER_BLOB + assert len(commitment_bytes) == BYTES_PER_COMMITMENT + assert len(proof_bytes) == BYTES_PER_PROOF commitment = bytes_to_kzg_commitment(commitment_bytes) commitments.append(commitment) polynomial = blob_to_polynomial(blob) diff --git a/tests/generators/kzg_4844/main.py b/tests/generators/kzg_4844/main.py index 65f6405e5..699d1f369 100644 --- a/tests/generators/kzg_4844/main.py +++ b/tests/generators/kzg_4844/main.py @@ -27,7 +27,11 @@ def expect_exception(func, *args): def field_element_bytes(x): - return int.to_bytes(x % spec.BLS_MODULUS, 32, "little") + return int.to_bytes(x % spec.BLS_MODULUS, 32, spec.ENDIANNESS) + + +def field_element_bytes_unchecked(x): + return int.to_bytes(x, 32, spec.ENDIANNESS) def encode_hex_list(a): @@ -67,13 +71,30 @@ BLOB_INVALID = spec.Blob(b'\xFF' * 4096 * 32) BLOB_INVALID_CLOSE = spec.Blob(b''.join( [BLS_MODULUS_BYTES if n == 2111 else field_element_bytes(0) for n in range(4096)] )) +BLOB_INVALID_LENGTH_PLUS_ONE = BLOB_RANDOM_VALID1 + b"\x00" +BLOB_INVALID_LENGTH_MINUS_ONE = BLOB_RANDOM_VALID1[:-1] VALID_BLOBS = [BLOB_ALL_ZEROS, BLOB_RANDOM_VALID1, BLOB_RANDOM_VALID2, BLOB_RANDOM_VALID3, BLOB_ALL_MODULUS_MINUS_ONE, BLOB_ALMOST_ZERO] -INVALID_BLOBS = [BLOB_INVALID, BLOB_INVALID_CLOSE] -VALID_ZS = [field_element_bytes(x) for x in [0, 1, 2, pow(5, 1235, spec.BLS_MODULUS), - spec.BLS_MODULUS - 1, spec.ROOTS_OF_UNITY[1]]] -INVALID_ZS = [x.to_bytes(32, spec.ENDIANNESS) for x in [spec.BLS_MODULUS, 2**256 - 1, 2**256 - 2**128]] +INVALID_BLOBS = [BLOB_INVALID, BLOB_INVALID_CLOSE, BLOB_INVALID_LENGTH_PLUS_ONE, BLOB_INVALID_LENGTH_MINUS_ONE] + +FE_VALID1 = field_element_bytes(0) +FE_VALID2 = field_element_bytes(1) +FE_VALID3 = field_element_bytes(2) +FE_VALID4 = field_element_bytes(pow(5, 1235, spec.BLS_MODULUS)) +FE_VALID5 = field_element_bytes(spec.BLS_MODULUS - 1) +FE_VALID6 = field_element_bytes(spec.ROOTS_OF_UNITY[1]) +VALID_FIELD_ELEMENTS = [FE_VALID1, FE_VALID2, FE_VALID3, FE_VALID4, FE_VALID5, FE_VALID6] + +FE_INVALID_EQUAL_TO_MODULUS = field_element_bytes_unchecked(spec.BLS_MODULUS) +FE_INVALID_MODULUS_PLUS_ONE = field_element_bytes_unchecked(spec.BLS_MODULUS + 1) +FE_INVALID_UINT256_MAX = field_element_bytes_unchecked(2**256 - 1) +FE_INVALID_UINT256_MID = field_element_bytes_unchecked(2**256 - 2**128) +FE_INVALID_LENGTH_PLUS_ONE = VALID_FIELD_ELEMENTS[0] + b"\x00" +FE_INVALID_LENGTH_MINUS_ONE = VALID_FIELD_ELEMENTS[0][:-1] +INVALID_FIELD_ELEMENTS = [FE_INVALID_EQUAL_TO_MODULUS, FE_INVALID_MODULUS_PLUS_ONE, + FE_INVALID_UINT256_MAX, FE_INVALID_UINT256_MID, + FE_INVALID_LENGTH_PLUS_ONE, FE_INVALID_LENGTH_MINUS_ONE] def hash(x): @@ -114,7 +135,7 @@ def case01_blob_to_kzg_commitment(): def case02_compute_kzg_proof(): # Valid cases for blob in VALID_BLOBS: - for z in VALID_ZS: + for z in VALID_FIELD_ELEMENTS: proof, y = spec.compute_kzg_proof(blob, z) identifier = f'{encode_hex(hash(blob))}_{encode_hex(z)}' yield f'compute_kzg_proof_case_valid_blob_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { @@ -127,7 +148,7 @@ def case02_compute_kzg_proof(): # Edge case: Invalid blobs for blob in INVALID_BLOBS: - z = VALID_ZS[0] + z = VALID_FIELD_ELEMENTS[0] expect_exception(spec.compute_kzg_proof, blob, z) identifier = f'{encode_hex(hash(blob))}_{encode_hex(z)}' yield f'compute_kzg_proof_case_invalid_blob_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { @@ -139,7 +160,7 @@ def case02_compute_kzg_proof(): } # Edge case: Invalid z - for z in INVALID_ZS: + for z in INVALID_FIELD_ELEMENTS: blob = VALID_BLOBS[4] expect_exception(spec.compute_kzg_proof, blob, z) identifier = f'{encode_hex(hash(blob))}_{encode_hex(z)}' @@ -155,7 +176,7 @@ def case02_compute_kzg_proof(): def case03_verify_kzg_proof(): # Valid cases for blob in VALID_BLOBS: - for z in VALID_ZS: + for z in VALID_FIELD_ELEMENTS: proof, y = spec.compute_kzg_proof(blob, z) commitment = spec.blob_to_kzg_commitment(blob) assert spec.verify_kzg_proof(commitment, z, y, proof) @@ -172,7 +193,7 @@ def case03_verify_kzg_proof(): # Incorrect proofs for blob in VALID_BLOBS: - for z in VALID_ZS: + for z in VALID_FIELD_ELEMENTS: proof_orig, y = spec.compute_kzg_proof(blob, z) proof = bls_add_one(proof_orig) commitment = spec.blob_to_kzg_commitment(blob) @@ -189,8 +210,8 @@ def case03_verify_kzg_proof(): } # Edge case: Invalid z - for z in INVALID_ZS: - blob, validz = VALID_BLOBS[4], VALID_ZS[1] + for z in INVALID_FIELD_ELEMENTS: + blob, validz = VALID_BLOBS[4], VALID_FIELD_ELEMENTS[1] proof, y = spec.compute_kzg_proof(blob, validz) commitment = spec.blob_to_kzg_commitment(blob) expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) @@ -206,26 +227,27 @@ def case03_verify_kzg_proof(): } # Edge case: Invalid y - blob, z = VALID_BLOBS[1], VALID_ZS[1] - proof, _ = spec.compute_kzg_proof(blob, z) - commitment = spec.blob_to_kzg_commitment(blob) - y = INVALID_ZS[0] - expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) - yield 'verify_kzg_proof_case_invalid_y', { - 'input': { - 'commitment': encode_hex(commitment), - 'z': encode_hex(z), - 'y': encode_hex(y), - 'proof': encode_hex(proof), - }, - 'output': None - } + for y in INVALID_FIELD_ELEMENTS: + blob, z = VALID_BLOBS[4], VALID_FIELD_ELEMENTS[1] + proof, _ = spec.compute_kzg_proof(blob, z) + commitment = spec.blob_to_kzg_commitment(blob) + expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) + identifier = f'{encode_hex(hash(blob))}_{encode_hex(y)}' + yield f'verify_kzg_proof_case_invalid_y_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + 'input': { + 'commitment': encode_hex(commitment), + 'z': encode_hex(z), + 'y': encode_hex(y), + 'proof': encode_hex(proof), + }, + 'output': None + } # Edge case: Invalid proof, not in G1 - blob, z = VALID_BLOBS[2], VALID_ZS[0] + blob, z = VALID_BLOBS[2], VALID_FIELD_ELEMENTS[0] proof = P1_NOT_IN_G1 commitment = spec.blob_to_kzg_commitment(blob) - y = VALID_ZS[1] + y = VALID_FIELD_ELEMENTS[1] expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) yield 'verify_kzg_proof_case_proof_not_in_G1', { 'input': { @@ -238,10 +260,10 @@ def case03_verify_kzg_proof(): } # Edge case: Invalid proof, not on curve - blob, z = VALID_BLOBS[3], VALID_ZS[1] + blob, z = VALID_BLOBS[3], VALID_FIELD_ELEMENTS[1] proof = P1_NOT_ON_CURVE commitment = spec.blob_to_kzg_commitment(blob) - y = VALID_ZS[1] + y = VALID_FIELD_ELEMENTS[1] expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) yield 'verify_kzg_proof_case_proof_not_on_curve', { 'input': { @@ -253,8 +275,42 @@ def case03_verify_kzg_proof(): 'output': None } + # Edge case: Invalid proof, too few bytes + blob = VALID_BLOBS[1] + commitment = spec.blob_to_kzg_commitment(blob) + z = VALID_FIELD_ELEMENTS[4] + proof, y = spec.compute_kzg_proof(blob, z) + proof = proof[:-1] + expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) + yield 'verify_kzg_proof_case_proof_too_few_bytes', { + 'input': { + 'commitment': encode_hex(commitment), + 'z': encode_hex(z), + 'y': encode_hex(y), + 'proof': encode_hex(proof), + }, + 'output': None + } + + # Edge case: Invalid proof, too many bytes + blob = VALID_BLOBS[1] + commitment = spec.blob_to_kzg_commitment(blob) + z = VALID_FIELD_ELEMENTS[4] + proof, y = spec.compute_kzg_proof(blob, z) + proof = proof + b"\x00" + expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) + yield 'verify_kzg_proof_case_proof_too_many_bytes', { + 'input': { + 'commitment': encode_hex(commitment), + 'z': encode_hex(z), + 'y': encode_hex(y), + 'proof': encode_hex(proof), + }, + 'output': None + } + # Edge case: Invalid commitment, not in G1 - blob, z = VALID_BLOBS[4], VALID_ZS[3] + blob, z = VALID_BLOBS[4], VALID_FIELD_ELEMENTS[3] proof, y = spec.compute_kzg_proof(blob, z) commitment = P1_NOT_IN_G1 expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) @@ -269,7 +325,7 @@ def case03_verify_kzg_proof(): } # Edge case: Invalid commitment, not on curve - blob, z = VALID_BLOBS[1], VALID_ZS[4] + blob, z = VALID_BLOBS[1], VALID_FIELD_ELEMENTS[4] proof, y = spec.compute_kzg_proof(blob, z) commitment = P1_NOT_ON_CURVE expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) @@ -283,6 +339,38 @@ def case03_verify_kzg_proof(): 'output': None } + # Edge case: Invalid commitment, too few bytes + blob = VALID_BLOBS[1] + commitment = spec.blob_to_kzg_commitment(blob)[:-1] + z = VALID_FIELD_ELEMENTS[4] + proof, y = spec.compute_kzg_proof(blob, z) + expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) + yield 'verify_kzg_proof_case_commitment_too_few_bytes', { + 'input': { + 'commitment': encode_hex(commitment), + 'z': encode_hex(z), + 'y': encode_hex(y), + 'proof': encode_hex(proof), + }, + 'output': None + } + + # Edge case: Invalid commitment, too many bytes + blob = VALID_BLOBS[1] + commitment = spec.blob_to_kzg_commitment(blob) + b"\x00" + z = VALID_FIELD_ELEMENTS[4] + proof, y = spec.compute_kzg_proof(blob, z) + expect_exception(spec.verify_kzg_proof, commitment, z, y, proof) + yield 'verify_kzg_proof_case_commitment_too_many_bytes', { + 'input': { + 'commitment': encode_hex(commitment), + 'z': encode_hex(z), + 'y': encode_hex(y), + 'proof': encode_hex(proof), + }, + 'output': None + } + def case04_compute_blob_kzg_proof(): # Valid cases @@ -397,6 +485,34 @@ def case05_verify_blob_kzg_proof(): 'output': None } + # Edge case: Invalid proof, too few bytes + blob = VALID_BLOBS[1] + commitment = spec.blob_to_kzg_commitment(blob) + proof = spec.compute_blob_kzg_proof(blob, commitment)[:-1] + expect_exception(spec.verify_blob_kzg_proof, blob, commitment, proof) + yield 'verify_blob_kzg_proof_case_proof_too_few_bytes', { + 'input': { + 'blob': encode_hex(blob), + 'commitment': encode_hex(commitment), + 'proof': encode_hex(proof), + }, + 'output': None + } + + # Edge case: Invalid proof, too many bytes + blob = VALID_BLOBS[1] + commitment = spec.blob_to_kzg_commitment(blob) + proof = spec.compute_blob_kzg_proof(blob, commitment) + b"\x00" + expect_exception(spec.verify_blob_kzg_proof, blob, commitment, proof) + yield 'verify_blob_kzg_proof_case_proof_too_many_bytes', { + 'input': { + 'blob': encode_hex(blob), + 'commitment': encode_hex(commitment), + 'proof': encode_hex(proof), + }, + 'output': None + } + # Edge case: Invalid commitment, not in G1 blob = VALID_BLOBS[0] proof = G1 @@ -425,6 +541,36 @@ def case05_verify_blob_kzg_proof(): 'output': None } + # Edge case: Invalid commitment, too few bytes + blob = VALID_BLOBS[1] + commitment = spec.blob_to_kzg_commitment(blob) + proof = spec.compute_blob_kzg_proof(blob, commitment) + commitment = commitment[:-1] + expect_exception(spec.verify_blob_kzg_proof, blob, commitment, proof) + yield 'verify_blob_kzg_proof_case_commitment_too_few_bytes', { + 'input': { + 'blob': encode_hex(blob), + 'commitment': encode_hex(commitment), + 'proof': encode_hex(proof), + }, + 'output': None + } + + # Edge case: Invalid commitment, too many bytes + blob = VALID_BLOBS[1] + commitment = spec.blob_to_kzg_commitment(blob) + proof = spec.compute_blob_kzg_proof(blob, commitment) + commitment = commitment + b"\x00" + expect_exception(spec.verify_blob_kzg_proof, blob, commitment, proof) + yield 'verify_blob_kzg_proof_case_commitment_too_many_bytes', { + 'input': { + 'blob': encode_hex(blob), + 'commitment': encode_hex(commitment), + 'proof': encode_hex(proof), + }, + 'output': None + } + # Edge case: Invalid blob for blob in INVALID_BLOBS: proof = G1 @@ -473,6 +619,20 @@ def case06_verify_blob_kzg_proof_batch(): 'output': False } + # Edge case: Invalid blobs + for blob in INVALID_BLOBS: + blobs_invalid = VALID_BLOBS[:4] + [blob] + VALID_BLOBS[5:] + expect_exception(spec.verify_blob_kzg_proof_batch, blobs_invalid, commitments, proofs) + identifier = f'{encode_hex(hash(blob))}' + yield f'verify_blob_kzg_proof_batch_case_invalid_blob_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + 'input': { + 'blobs': encode_hex_list(blobs_invalid), + 'commitments': encode_hex_list(commitments), + 'proofs': encode_hex_list(proofs), + }, + 'output': None + } + # Edge case: Invalid proof, not in G1 proofs_invalid_notG1 = [P1_NOT_IN_G1] + proofs[1:] expect_exception(spec.verify_blob_kzg_proof_batch, VALID_BLOBS, commitments, proofs_invalid_notG1) @@ -497,6 +657,30 @@ def case06_verify_blob_kzg_proof_batch(): 'output': None } + # Edge case: Invalid proof, too few bytes + proofs_invalid_tooFewBytes = proofs[:1] + [proofs[1][:-1]] + proofs[2:] + expect_exception(spec.verify_blob_kzg_proof_batch, VALID_BLOBS, commitments, proofs_invalid_tooFewBytes) + yield 'verify_blob_kzg_proof_batch_case_proof_too_few_bytes', { + 'input': { + 'blobs': encode_hex_list(VALID_BLOBS), + 'commitments': encode_hex_list(commitments), + 'proofs': encode_hex_list(proofs_invalid_tooFewBytes), + }, + 'output': None + } + + # Edge case: Invalid proof, too many bytes + proofs_invalid_tooManyBytes = proofs[:1] + [proofs[1] + b"\x00"] + proofs[2:] + expect_exception(spec.verify_blob_kzg_proof_batch, VALID_BLOBS, commitments, proofs_invalid_tooManyBytes) + yield 'verify_blob_kzg_proof_batch_case_proof_too_many_bytes', { + 'input': { + 'blobs': encode_hex_list(VALID_BLOBS), + 'commitments': encode_hex_list(commitments), + 'proofs': encode_hex_list(proofs_invalid_tooManyBytes), + }, + 'output': None + } + # Edge case: Invalid commitment, not in G1 commitments_invalid_notG1 = commitments[:2] + [P1_NOT_IN_G1] + commitments[3:] expect_exception(spec.verify_blob_kzg_proof_batch, VALID_BLOBS, commitments, commitments_invalid_notG1) @@ -521,13 +705,25 @@ def case06_verify_blob_kzg_proof_batch(): 'output': None } - # Edge case: Invalid blob - blobs_invalid = VALID_BLOBS[:4] + [BLOB_INVALID] + VALID_BLOBS[5:] - expect_exception(spec.verify_blob_kzg_proof_batch, blobs_invalid, commitments, proofs) - yield 'verify_blob_kzg_proof_batch_case_invalid_blob', { + # Edge case: Invalid commitment, too few bytes + commitments_invalid_tooFewBytes = commitments[:3] + [commitments[3][:-1]] + commitments[4:] + expect_exception(spec.verify_blob_kzg_proof_batch, VALID_BLOBS, commitments, commitments_invalid_tooFewBytes) + yield 'verify_blob_kzg_proof_batch_case_too_few_bytes', { 'input': { - 'blobs': encode_hex_list(blobs_invalid), - 'commitments': encode_hex_list(commitments), + 'blobs': encode_hex_list(VALID_BLOBS), + 'commitments': encode_hex_list(commitments_invalid_tooFewBytes), + 'proofs': encode_hex_list(proofs), + }, + 'output': None + } + + # Edge case: Invalid commitment, too many bytes + commitments_invalid_tooManyBytes = commitments[:3] + [commitments[3] + b"\x00"] + commitments[4:] + expect_exception(spec.verify_blob_kzg_proof_batch, VALID_BLOBS, commitments, commitments_invalid_tooManyBytes) + yield 'verify_blob_kzg_proof_batch_case_too_many_bytes', { + 'input': { + 'blobs': encode_hex_list(VALID_BLOBS), + 'commitments': encode_hex_list(commitments_invalid_tooManyBytes), 'proofs': encode_hex_list(proofs), }, 'output': None