diff --git a/specs/deneb/p2p-interface.md b/specs/deneb/p2p-interface.md index 107c90b9a..9f4c15849 100644 --- a/specs/deneb/p2p-interface.md +++ b/specs/deneb/p2p-interface.md @@ -73,6 +73,17 @@ class BlobIdentifier(Container): index: BlobIndex ``` +### Helpers + +#### `verify_sidecar_signature` + +```python +def verify_blob_sidecar_signature(state: BeaconState, signed_blob_sidecar: SignedBlobSidecar) -> bool: + proposer = state.validators[signed_blob_sidecar.message.proposer_index] + signing_root = compute_signing_root(signed_blob_sidecar.message, get_domain(state, DOMAIN_BLOB_SIDECAR)) + return bls.Verify(proposer.pubkey, signing_root, signed_blob_sidecar.signature) +``` + ## The gossip domain: gossipsub Some gossip meshes are upgraded in the fork of Deneb to support upgraded types. diff --git a/specs/deneb/validator.md b/specs/deneb/validator.md index d2209986e..a21fadd08 100644 --- a/specs/deneb/validator.md +++ b/specs/deneb/validator.md @@ -44,7 +44,8 @@ Note: This API is *unstable*. `get_blobs_and_kzg_commitments` and `get_payload` Implementers may also retrieve blobs individually per transaction. ```python -def get_blobs_and_kzg_commitments(payload_id: PayloadId) -> Tuple[Sequence[BLSFieldElement], Sequence[KZGCommitment], Sequence[KZGProof]]: +def get_blobs_and_kzg_commitments(payload_id: PayloadId) -> \ + Tuple[Sequence[BLSFieldElement], Sequence[KZGCommitment], Sequence[KZGProof]]: # pylint: disable=unused-argument ... ``` @@ -88,7 +89,9 @@ Blobs associated with a block are packaged into sidecar objects for distribution Each `sidecar` is obtained from: ```python -def get_blob_sidecars(block: BeaconBlock, blobs: Sequence[Blob]) -> Sequence[BlobSidecar]: +def get_blob_sidecars(block: BeaconBlock, + blobs: Sequence[Blob], + blob_kzg_proofs: Sequence[KZGProof]) -> Sequence[BlobSidecar]: return [ BlobSidecar( block_root=hash_tree_root(block), @@ -97,7 +100,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, block.body.blob_kzg_commitments[index]), + kzg_proof=blob_kzg_proofs[index], ) for index, blob in enumerate(blobs) ] diff --git a/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py index c7fb708b8..5e65dbd4e 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py @@ -22,7 +22,7 @@ def test_one_blob(spec, state): yield 'pre', state block = build_empty_block_for_next_slot(spec, state) - opaque_tx, _, blob_kzg_commitments = get_sample_opaque_tx(spec) + opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec) block.body.blob_kzg_commitments = blob_kzg_commitments block.body.execution_payload.transactions = [opaque_tx] block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) @@ -38,7 +38,7 @@ def test_max_blobs(spec, state): yield 'pre', state block = build_empty_block_for_next_slot(spec, state) - opaque_tx, _, blob_kzg_commitments = get_sample_opaque_tx(spec, blob_count=spec.MAX_BLOBS_PER_BLOCK) + opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec, blob_count=spec.MAX_BLOBS_PER_BLOCK) block.body.blob_kzg_commitments = blob_kzg_commitments block.body.execution_payload.transactions = [opaque_tx] block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/fork_choice/test_validate_blobs.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/fork_choice/test_validate_blobs.py index d9934c5ad..0d7bd53e5 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/unittests/fork_choice/test_validate_blobs.py +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/fork_choice/test_validate_blobs.py @@ -18,14 +18,13 @@ from eth2spec.test.helpers.sharding import ( def _run_validate_blobs(spec, state, blob_count): block = build_empty_block_for_next_slot(spec, state) - opaque_tx, blobs, blob_kzg_commitments = get_sample_opaque_tx(spec, blob_count=blob_count) + opaque_tx, blobs, blob_kzg_commitments, kzg_proofs = get_sample_opaque_tx(spec, blob_count=blob_count) block.body.blob_kzg_commitments = blob_kzg_commitments block.body.execution_payload.transactions = [opaque_tx] block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) state_transition_and_sign_block(spec, state, block) - # Also test the proof generation in `get_blob_sidecars` - blob_sidecars = spec.get_blob_sidecars(block, blobs) + blob_sidecars = spec.get_blob_sidecars(block, blobs, kzg_proofs) blobs = [sidecar.blob for sidecar in blob_sidecars] kzg_proofs = [sidecar.kzg_proof for sidecar in blob_sidecars] spec.validate_blobs(blob_kzg_commitments, blobs, kzg_proofs) diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/test_offset.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_offset.py index 13150180b..3c3b51ff1 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/unittests/test_offset.py +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_offset.py @@ -17,7 +17,7 @@ from eth2spec.test.context import ( @spec_state_test @with_presets([MINIMAL]) def test_tx_peek_blob_versioned_hashes(spec, state): - otx, blobs, commitments = get_sample_opaque_tx(spec) + otx, _, commitments, _ = get_sample_opaque_tx(spec) data_hashes = spec.tx_peek_blob_versioned_hashes(otx) expected = [spec.kzg_commitment_to_versioned_hash(blob_commitment) for blob_commitment in commitments] assert expected == data_hashes diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/test_validator.py index 33b216e87..7e5cf8bfe 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/test_validator.py @@ -1,6 +1,5 @@ -import random - from eth2spec.test.context import ( + always_bls, spec_state_test, with_deneb_and_later, expect_assertion_error @@ -14,8 +13,8 @@ from eth2spec.test.helpers.sharding import ( from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot ) -from eth2spec.test.helpers.state import ( - state_transition_and_sign_block, +from eth2spec.test.helpers.keys import ( + pubkey_to_privkey ) from eth2spec.utils import bls from eth2spec.utils.bls import BLS_MODULUS @@ -49,15 +48,11 @@ def test_validate_blobs_and_kzg_commitments(spec, state): """ blob_count = 4 block = build_empty_block_for_next_slot(spec, state) - opaque_tx, blobs, blob_kzg_commitments = get_sample_opaque_tx(spec, blob_count=blob_count) + opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count) block.body.blob_kzg_commitments = blob_kzg_commitments block.body.execution_payload.transactions = [opaque_tx] block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) - blob_sidecars = spec.get_blob_sidecars(block, blobs) - blobs = [sidecar.blob for sidecar in blob_sidecars] - proofs = [sidecar.kzg_proof for sidecar in blob_sidecars] - spec.validate_blobs_and_kzg_commitments(block.body.execution_payload, blobs, blob_kzg_commitments, @@ -72,21 +67,18 @@ def test_validate_blobs_and_kzg_commitments_missing_blob(spec, state): """ blob_count = 4 block = build_empty_block_for_next_slot(spec, state) - opaque_tx, blobs, blob_kzg_commitments = get_sample_opaque_tx(spec, blob_count=blob_count) + opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count) block.body.blob_kzg_commitments = blob_kzg_commitments block.body.execution_payload.transactions = [opaque_tx] block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) - # state_transition_and_sign_block(spec, state, block) - blob_sidecars = spec.get_blob_sidecars(block, blobs) - blobs = [sidecar.blob for sidecar in blob_sidecars][:-1] - proofs = [sidecar.kzg_proof for sidecar in blob_sidecars] - - expect_assertion_error(lambda: - spec.validate_blobs_and_kzg_commitments(block.body.execution_payload, - blobs, - blob_kzg_commitments, - proofs) + expect_assertion_error( + lambda: spec.validate_blobs_and_kzg_commitments( + block.body.execution_payload, + blobs[:-1], + blob_kzg_commitments, + proofs + ) ) @@ -98,21 +90,18 @@ def test_validate_blobs_and_kzg_commitments_missing_proof(spec, state): """ blob_count = 4 block = build_empty_block_for_next_slot(spec, state) - opaque_tx, blobs, blob_kzg_commitments = get_sample_opaque_tx(spec, blob_count=blob_count) + opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count) block.body.blob_kzg_commitments = blob_kzg_commitments block.body.execution_payload.transactions = [opaque_tx] block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) - # state_transition_and_sign_block(spec, state, block) - blob_sidecars = spec.get_blob_sidecars(block, blobs) - blobs = [sidecar.blob for sidecar in blob_sidecars] - proofs = [sidecar.kzg_proof for sidecar in blob_sidecars][:-1] - - expect_assertion_error(lambda: - spec.validate_blobs_and_kzg_commitments(block.body.execution_payload, - blobs, - blob_kzg_commitments, - proofs) + expect_assertion_error( + lambda: spec.validate_blobs_and_kzg_commitments( + block.body.execution_payload, + blobs, + blob_kzg_commitments, + proofs[:-1] + ) ) @@ -124,21 +113,68 @@ def test_validate_blobs_and_kzg_commitments_incorrect_blob(spec, state): """ blob_count = 4 block = build_empty_block_for_next_slot(spec, state) - opaque_tx, blobs, blob_kzg_commitments = get_sample_opaque_tx(spec, blob_count=blob_count) + opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count) block.body.blob_kzg_commitments = blob_kzg_commitments block.body.execution_payload.transactions = [opaque_tx] block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) - # state_transition_and_sign_block(spec, state, block) - - blob_sidecars = spec.get_blob_sidecars(block, blobs) - blobs = [sidecar.blob for sidecar in blob_sidecars] - proofs = [sidecar.kzg_proof for sidecar in blob_sidecars] blobs[1] = spec.Blob(blobs[1][:13] + bytes([(blobs[1][13] + 1) % 256]) + blobs[1][14:]) - expect_assertion_error(lambda: - spec.validate_blobs_and_kzg_commitments(block.body.execution_payload, - blobs, - blob_kzg_commitments, - proofs) - ) \ No newline at end of file + expect_assertion_error( + lambda: spec.validate_blobs_and_kzg_commitments( + block.body.execution_payload, + blobs, + blob_kzg_commitments, + proofs + ) + ) + + +@with_deneb_and_later +@spec_state_test +def test_blob_sidecar_signature(spec, state): + """ + Test `get_blob_sidecar_signature` + """ + blob_count = 4 + block = build_empty_block_for_next_slot(spec, state) + opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count) + block.body.blob_kzg_commitments = blob_kzg_commitments + block.body.execution_payload.transactions = [opaque_tx] + block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) + + blob_sidecars = spec.get_blob_sidecars(block, blobs, proofs) + proposer = state.validators[blob_sidecars[1].proposer_index] + privkey = pubkey_to_privkey[proposer.pubkey] + sidecar_signature = spec.get_blob_sidecar_signature(state, + blob_sidecars[1], + privkey) + + signed_blob_sidecar = spec.SignedBlobSidecar(message=blob_sidecars[1], signature=sidecar_signature) + + assert spec.verify_blob_sidecar_signature(state, signed_blob_sidecar) + + +@with_deneb_and_later +@spec_state_test +@always_bls +def test_blob_sidecar_signature_incorrect(spec, state): + """ + Test `get_blob_sidecar_signature` + """ + blob_count = 4 + block = build_empty_block_for_next_slot(spec, state) + opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count) + block.body.blob_kzg_commitments = blob_kzg_commitments + block.body.execution_payload.transactions = [opaque_tx] + block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) + + blob_sidecars = spec.get_blob_sidecars(block, blobs, proofs) + + sidecar_signature = spec.get_blob_sidecar_signature(state, + blob_sidecars[1], + 123) + + signed_blob_sidecar = spec.SignedBlobSidecar(message=blob_sidecars[1], signature=sidecar_signature) + + assert not spec.verify_blob_sidecar_signature(state, signed_blob_sidecar) diff --git a/tests/core/pyspec/eth2spec/test/helpers/sharding.py b/tests/core/pyspec/eth2spec/test/helpers/sharding.py index fd60d5d3b..89f03c3c3 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/sharding.py +++ b/tests/core/pyspec/eth2spec/test/helpers/sharding.py @@ -101,13 +101,16 @@ def get_poly_in_both_forms(spec, rng=None): def get_sample_opaque_tx(spec, blob_count=1, rng=None): blobs = [] blob_kzg_commitments = [] + blob_kzg_proofs = [] blob_versioned_hashes = [] for _ in range(blob_count): blob = get_sample_blob(spec, rng) blob_commitment = spec.KZGCommitment(spec.blob_to_kzg_commitment(blob)) + blob_kzg_proof = spec.compute_blob_kzg_proof(blob, blob_commitment) blob_versioned_hash = spec.kzg_commitment_to_versioned_hash(blob_commitment) blobs.append(blob) blob_kzg_commitments.append(blob_commitment) + blob_kzg_proofs.append(blob_kzg_proof) blob_versioned_hashes.append(blob_versioned_hash) signed_blob_tx = SignedBlobTransaction( @@ -117,4 +120,4 @@ def get_sample_opaque_tx(spec, blob_count=1, rng=None): ) serialized_tx = serialize(signed_blob_tx) opaque_tx = spec.uint_to_bytes(spec.BLOB_TX_TYPE) + serialized_tx - return opaque_tx, blobs, blob_kzg_commitments + return opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs