Skip to content

Commit 311e922

Browse files
authored
block: mv verify_signatures to SignedBlockWithAttestation impl (leanEthereum#147)
* block: mv verify_signatures to SignedBlockWithAttestation impl * cleanup * touchup
1 parent 3e6d322 commit 311e922

2 files changed

Lines changed: 76 additions & 86 deletions

File tree

src/lean_spec/subspecs/containers/block/block.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@
99
can propose.
1010
"""
1111

12+
from typing import TYPE_CHECKING
13+
1214
from lean_spec.subspecs.containers.slot import Slot
15+
from lean_spec.subspecs.ssz.hash import hash_tree_root
1316
from lean_spec.types import Bytes32, Uint64
1417
from lean_spec.types.container import Container
1518

1619
from ..attestation import Attestation
1720
from .types import Attestations, BlockSignatures
1821

22+
if TYPE_CHECKING:
23+
from ..state import State
24+
1925

2026
class BlockBody(Container):
2127
"""
@@ -107,3 +113,72 @@ class SignedBlockWithAttestation(Container):
107113
Eventually this field will be replaced by a SNARK (which represents the
108114
aggregation of all signatures).
109115
"""
116+
117+
def verify_signatures(self, parent_state: "State") -> bool:
118+
"""
119+
Verify all XMSS signatures in this signed block.
120+
121+
This function ensures that every attestation included in the block
122+
(both on-chain attestations from the block body and the proposer's
123+
own attestation) is properly signed by the claimed validator using
124+
their registered XMSS public key.
125+
126+
Args:
127+
parent_state: The state at the parent block, used to retrieve
128+
validator public keys and verify signatures.
129+
130+
Returns:
131+
True if all signatures are cryptographically valid.
132+
133+
Raises:
134+
AssertionError: If signature verification fails, including:
135+
- Signature count mismatch
136+
- Validator index out of range
137+
- XMSS signature verification failure
138+
"""
139+
# Unpack the signed block components
140+
block = self.message.block
141+
signatures = self.signature
142+
143+
# Combine all attestations that need verification
144+
#
145+
# This creates a single list containing both:
146+
# 1. Block body attestations (from other validators)
147+
# 2. Proposer attestation (from the block producer)
148+
all_attestations = list(block.body.attestations) + [self.message.proposer_attestation]
149+
150+
# Verify signature count matches attestation count
151+
#
152+
# Each attestation must have exactly one corresponding signature.
153+
#
154+
# The ordering must be preserved:
155+
# 1. Block body attestations,
156+
# 2. The proposer attestation.
157+
assert len(signatures) == len(all_attestations), (
158+
"Number of signatures does not match number of attestations"
159+
)
160+
161+
validators = parent_state.validators
162+
163+
# Verify each attestation signature
164+
for attestation, signature in zip(all_attestations, signatures, strict=True):
165+
# Identify the validator who created this attestation
166+
validator_id = attestation.validator_id.as_int()
167+
168+
# Ensure validator exists in the active set
169+
assert validator_id < len(validators), "Validator index out of range"
170+
validator = validators[validator_id]
171+
172+
# Verify the XMSS signature
173+
#
174+
# This cryptographically proves that:
175+
# - The validator possesses the secret key for their public key
176+
# - The attestation has not been tampered with
177+
# - The signature was created at the correct epoch (slot)
178+
assert signature.verify(
179+
validator.get_pubkey(),
180+
attestation.data.slot.as_int(),
181+
bytes(hash_tree_root(attestation)),
182+
), "Attestation signature verification failed"
183+
184+
return True

src/lean_spec/subspecs/forkchoice/store.py

Lines changed: 1 addition & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -262,91 +262,6 @@ def on_attestation(
262262
}
263263
)
264264

265-
def _validate_block_signatures(
266-
self,
267-
signed_block_with_attestation: SignedBlockWithAttestation,
268-
) -> bool:
269-
"""
270-
Verify all XMSS signatures in a signed block.
271-
272-
This method ensures that every attestation included in the block
273-
(both on-chain attestations from the block body and the proposer's
274-
own attestation) is properly signed by the claimed validator using
275-
their registered XMSS public key.
276-
277-
Args:
278-
signed_block_with_attestation: Complete signed block containing:
279-
- Block body with included attestations
280-
- Proposer's attestation for this block
281-
- XMSS signatures for all attestations (ordered)
282-
283-
Returns:
284-
True if all signatures are cryptographically valid.
285-
286-
Raises:
287-
AssertionError: If signature verification fails, including:
288-
- Signature count mismatch
289-
- Parent state not found in store
290-
- Validator index out of range
291-
- XMSS signature verification failure
292-
"""
293-
# Unpack the signed block components
294-
message = signed_block_with_attestation.message
295-
block = message.block
296-
signatures = signed_block_with_attestation.signature
297-
298-
# Combine all attestations that need verification
299-
#
300-
# This creates a single list containing both:
301-
# 1. Block body attestations (from other validators)
302-
# 2. Proposer attestation (from the block producer)
303-
all_attestations = list(block.body.attestations) + [message.proposer_attestation]
304-
305-
# Verify signature count matches attestation count
306-
#
307-
# Each attestation must have exactly one corresponding signature.
308-
#
309-
# The ordering must be preserved:
310-
# 1. Block body attestations,
311-
# 2. The proposer attestation.
312-
assert len(signatures) == len(all_attestations), (
313-
"Number of signatures does not match number of attestations"
314-
)
315-
316-
# Retrieve parent state to access validator public keys
317-
#
318-
# We use the parent state because:
319-
# - Validator set is determined at the parent block
320-
# - Public keys must be registered before signing
321-
# - State root is committed in the block header
322-
parent_state = self.states.get(block.parent_root)
323-
assert parent_state is not None, "Parent state not found"
324-
325-
validators = parent_state.validators
326-
327-
# Verify each attestation signature
328-
for attestation, signature in zip(all_attestations, signatures, strict=True):
329-
# Identify the validator who created this attestation
330-
validator_id = attestation.validator_id.as_int()
331-
332-
# Ensure validator exists in the active set
333-
assert validator_id < len(validators), "Validator index out of range"
334-
validator = validators[validator_id]
335-
336-
# Verify the XMSS signature
337-
#
338-
# This cryptographically proves that:
339-
# - The validator possesses the secret key for their public key
340-
# - The attestation has not been tampered with
341-
# - The signature was created at the correct epoch (slot)
342-
assert signature.verify(
343-
validator.get_pubkey(),
344-
attestation.data.slot.as_int(),
345-
bytes(hash_tree_root(attestation)),
346-
), "Attestation signature verification failed"
347-
348-
return True
349-
350265
def _process_block_body_attestations(
351266
self, block: Block, signatures: BlockSignatures
352267
) -> "Store":
@@ -491,7 +406,7 @@ def on_block(self, signed_block_with_attestation: SignedBlockWithAttestation) ->
491406
)
492407

493408
# Validate cryptographic signatures
494-
valid_signatures = self._validate_block_signatures(signed_block_with_attestation)
409+
valid_signatures = signed_block_with_attestation.verify_signatures(parent_state)
495410

496411
# Execute state transition function to compute post-block state
497412
post_state = copy.deepcopy(parent_state).state_transition(block, valid_signatures)

0 commit comments

Comments
 (0)