Skip to content

Commit 634d407

Browse files
authored
aggregation: cleanup and reorg (#245)
* aggregation: cleanup and reorg * cleanup * cleanup * fix Bytes32 * fix linter
1 parent ff3f2f2 commit 634d407

File tree

18 files changed

+349
-366
lines changed

18 files changed

+349
-366
lines changed

packages/testing/src/consensus_testing/keys.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@
3939

4040
from lean_spec.config import LEAN_ENV
4141
from lean_spec.subspecs.containers import AttestationData
42+
from lean_spec.subspecs.containers.attestation import AggregationBits
4243
from lean_spec.subspecs.containers.block.types import (
4344
AggregatedAttestations,
4445
AttestationSignatures,
4546
)
4647
from lean_spec.subspecs.containers.slot import Slot
4748
from lean_spec.subspecs.xmss.aggregation import (
48-
AttestationSignatureKey,
49-
MultisigAggregatedSignature,
49+
AggregatedSignatureProof,
50+
SignatureKey,
5051
)
5152
from lean_spec.subspecs.xmss.containers import KeyPair, PublicKey, Signature
5253
from lean_spec.subspecs.xmss.interface import (
@@ -276,7 +277,7 @@ def sign_attestation_data(
276277
def build_attestation_signatures(
277278
self,
278279
aggregated_attestations: AggregatedAttestations,
279-
signature_lookup: Mapping[AttestationSignatureKey, Signature] | None = None,
280+
signature_lookup: Mapping[SignatureKey, Signature] | None = None,
280281
) -> AttestationSignatures:
281282
"""
282283
Build `AttestationSignatures` for already-aggregated attestations.
@@ -286,29 +287,34 @@ def build_attestation_signatures(
286287
"""
287288
lookup = signature_lookup or {}
288289

289-
proof_blobs: list[MultisigAggregatedSignature] = []
290+
proofs: list[AggregatedSignatureProof] = []
290291
for agg in aggregated_attestations:
291292
validator_ids = agg.aggregation_bits.to_validator_indices()
292293
message = agg.data.data_root_bytes()
293294
epoch = agg.data.slot
294295

295296
public_keys: list[PublicKey] = [self.get_public_key(vid) for vid in validator_ids]
296297
signatures: list[Signature] = [
297-
(lookup.get((vid, message)) or self.sign_attestation_data(vid, agg.data))
298+
(
299+
lookup.get(SignatureKey(vid, message))
300+
or self.sign_attestation_data(vid, agg.data)
301+
)
298302
for vid in validator_ids
299303
]
300304

301305
# If the caller supplied raw signatures and any are invalid,
302306
# aggregation should fail with exception.
303-
aggregated_signature = MultisigAggregatedSignature.aggregate_signatures(
307+
participants = AggregationBits.from_validator_indices(validator_ids)
308+
proof = AggregatedSignatureProof.aggregate(
309+
participants=participants,
304310
public_keys=public_keys,
305311
signatures=signatures,
306312
message=message,
307313
epoch=epoch,
308314
)
309-
proof_blobs.append(aggregated_signature)
315+
proofs.append(proof)
310316

311-
return AttestationSignatures(data=proof_blobs)
317+
return AttestationSignatures(data=proofs)
312318

313319

314320
def _generate_single_keypair(

packages/testing/src/consensus_testing/test_fixtures/fork_choice.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from lean_spec.subspecs.forkchoice import Store
3030
from lean_spec.subspecs.koalabear import Fp
3131
from lean_spec.subspecs.ssz import hash_tree_root
32-
from lean_spec.subspecs.xmss.aggregation import AttestationSignatureKey
32+
from lean_spec.subspecs.xmss.aggregation import SignatureKey
3333
from lean_spec.subspecs.xmss.containers import Signature
3434
from lean_spec.subspecs.xmss.types import HashDigestList, HashTreeOpening, Randomness
3535
from lean_spec.types import Bytes32, Uint64
@@ -404,14 +404,14 @@ def _build_attestations_from_spec(
404404
block_registry: dict[str, Block],
405405
parent_root: Bytes32,
406406
key_manager: XmssKeyManager,
407-
) -> tuple[list[Attestation], dict[AttestationSignatureKey, Signature]]:
407+
) -> tuple[list[Attestation], dict[SignatureKey, Signature]]:
408408
"""Build attestations list from BlockSpec and their signatures."""
409409
if spec.attestations is None:
410410
return [], {}
411411

412412
parent_state = store.states[parent_root]
413413
attestations = []
414-
signature_lookup: dict[AttestationSignatureKey, Signature] = {}
414+
signature_lookup: dict[SignatureKey, Signature] = {}
415415

416416
for att_spec in spec.attestations:
417417
if isinstance(att_spec, SignedAttestationSpec):
@@ -423,9 +423,8 @@ def _build_attestations_from_spec(
423423

424424
attestation = Attestation(validator_id=signed_att.validator_id, data=signed_att.message)
425425
attestations.append(attestation)
426-
signature_lookup[(attestation.validator_id, attestation.data.data_root_bytes())] = (
427-
signed_att.signature
428-
)
426+
sig_key = SignatureKey(attestation.validator_id, attestation.data.data_root_bytes())
427+
signature_lookup[sig_key] = signed_att.signature
429428

430429
return attestations, signature_lookup
431430

packages/testing/src/consensus_testing/test_fixtures/state_transition.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from lean_spec.subspecs.containers.block.types import AggregatedAttestations
1010
from lean_spec.subspecs.containers.state.state import State
1111
from lean_spec.subspecs.ssz.hash import hash_tree_root
12+
from lean_spec.subspecs.xmss.aggregation import SignatureKey
1213
from lean_spec.types import Bytes32, Uint64
1314

1415
from ..keys import get_shared_key_manager
@@ -262,7 +263,9 @@ def _build_block_from_spec(self, spec: BlockSpec, state: State) -> tuple[Block,
262263
if plain_attestations:
263264
key_manager = get_shared_key_manager(max_slot=spec.slot)
264265
gossip_signatures = {
265-
(att.validator_id, att.data.data_root_bytes()): key_manager.sign_attestation_data(
266+
SignatureKey(
267+
att.validator_id, att.data.data_root_bytes()
268+
): key_manager.sign_attestation_data(
266269
att.validator_id,
267270
att.data,
268271
)

packages/testing/src/consensus_testing/test_fixtures/verify_signatures.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from lean_spec.subspecs.containers.state.state import State
2222
from lean_spec.subspecs.koalabear import Fp
2323
from lean_spec.subspecs.ssz import hash_tree_root
24+
from lean_spec.subspecs.xmss.aggregation import SignatureKey
2425
from lean_spec.subspecs.xmss.constants import TARGET_CONFIG
2526
from lean_spec.subspecs.xmss.containers import Signature
2627
from lean_spec.subspecs.xmss.types import HashDigestList, HashTreeOpening, Randomness
@@ -187,7 +188,7 @@ def _build_block_from_spec(
187188
# fixed-point collection when available_attestations/known_block_roots are used.
188189
# This might contain invalid signatures as we are not validating them here.
189190
gossip_signatures = {
190-
(att.validator_id, att.data.data_root_bytes()): sig
191+
SignatureKey(att.validator_id, att.data.data_root_bytes()): sig
191192
for att, sig in zip(attestations, attestation_signature_inputs, strict=True)
192193
}
193194

src/lean_spec/subspecs/chain/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
from typing_extensions import Final
99

10-
from lean_spec.types import StrictBaseModel, Uint64
10+
from lean_spec.types.base import StrictBaseModel
11+
from lean_spec.types.uint import Uint64
1112

1213
# --- Time Parameters ---
1314

src/lean_spec/subspecs/containers/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010

1111
from .attestation import (
1212
AggregatedAttestation,
13-
AggregationBits,
1413
Attestation,
1514
AttestationData,
16-
AttestationsByValidator,
1715
SignedAttestation,
1816
)
1917
from .block import (
@@ -30,10 +28,8 @@
3028

3129
__all__ = [
3230
"AggregatedAttestation",
33-
"AggregationBits",
3431
"Attestation",
3532
"AttestationData",
36-
"AttestationsByValidator",
3733
"Block",
3834
"BlockBody",
3935
"BlockHeader",
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
"""Attestation containers and related types for the Lean spec."""
22

3+
from .aggregation_bits import AggregationBits
34
from .attestation import (
45
AggregatedAttestation,
56
Attestation,
67
AttestationData,
78
SignedAttestation,
89
)
9-
from .types import AggregationBits, AttestationsByValidator
1010

1111
__all__ = [
1212
"AggregatedAttestation",
1313
"AggregationBits",
1414
"Attestation",
1515
"AttestationData",
16-
"AttestationsByValidator",
1716
"SignedAttestation",
1817
]

src/lean_spec/subspecs/containers/attestation/types.py renamed to src/lean_spec/subspecs/containers/attestation/aggregation_bits.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
1-
"""Attestation-related SSZ types for the Lean consensus specification."""
1+
"""Aggregation bits for tracking validator participation."""
22

33
from __future__ import annotations
44

5-
from typing import TYPE_CHECKING
6-
7-
from lean_spec.types import Uint64
5+
from lean_spec.subspecs.chain.config import VALIDATOR_REGISTRY_LIMIT
6+
from lean_spec.types import Boolean, Uint64
87
from lean_spec.types.bitfields import BaseBitlist
98

10-
from ...chain.config import VALIDATOR_REGISTRY_LIMIT
11-
12-
if TYPE_CHECKING:
13-
from .attestation import AttestationData
14-
15-
AttestationsByValidator = dict[Uint64, "AttestationData"]
16-
"""Mapping from validator index to attestation data."""
17-
189

1910
class AggregationBits(BaseBitlist):
20-
"""Bitlist representing validator participation in an attestation."""
11+
"""
12+
Bitlist representing validator participation in an attestation or signature.
13+
14+
A general-purpose bitfield for tracking which validators have participated
15+
in some collective action (attestation, signature aggregation, etc.).
16+
"""
2117

2218
LIMIT = int(VALIDATOR_REGISTRY_LIMIT)
2319

@@ -36,19 +32,23 @@ def from_validator_indices(cls, indices: list[Uint64]) -> AggregationBits:
3632
AssertionError: If no indices are provided.
3733
AssertionError: If any index is outside the supported LIMIT.
3834
"""
39-
ids = [int(i) for i in indices]
40-
if not ids:
35+
# Require at least one validator for a valid aggregation.
36+
if not indices:
4137
raise AssertionError("Aggregated attestation must reference at least one validator")
4238

43-
max_id = max(ids)
44-
if max_id >= cls.LIMIT:
45-
raise AssertionError("Validator index out of range for aggregation bits")
39+
# Convert to a set of native ints.
40+
#
41+
# This combines int conversion and deduplication in a single O(N) pass.
42+
ids = {int(i) for i in indices}
4643

47-
bits = [False] * (max_id + 1)
48-
for i in ids:
49-
bits[i] = True
44+
# Validate bounds: max index must be within registry limit.
45+
if (max_id := max(ids)) >= cls.LIMIT:
46+
raise AssertionError("Validator index out of range for aggregation bits")
5047

51-
return cls(data=bits)
48+
# Build bit list:
49+
# - True at positions present in indices,
50+
# - False elsewhere.
51+
return cls(data=[Boolean(i in ids) for i in range(max_id + 1)])
5252

5353
def to_validator_indices(self) -> list[Uint64]:
5454
"""
@@ -60,6 +60,7 @@ def to_validator_indices(self) -> list[Uint64]:
6060
Raises:
6161
AssertionError: If no bits are set.
6262
"""
63+
# Extract indices where bit is set; fail if none found.
6364
if not (indices := [Uint64(i) for i, bit in enumerate(self.data) if bool(bit)]):
6465
raise AssertionError("Aggregated attestation must reference at least one validator")
6566

src/lean_spec/subspecs/containers/attestation/attestation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818

1919
from lean_spec.subspecs.containers.slot import Slot
2020
from lean_spec.subspecs.ssz import hash_tree_root
21-
from lean_spec.types import Container, Uint64
21+
from lean_spec.types import Bytes32, Container, Uint64
2222

2323
from ...xmss.containers import Signature
2424
from ..checkpoint import Checkpoint
25-
from .types import AggregationBits
25+
from .aggregation_bits import AggregationBits
2626

2727

2828
class AttestationData(Container):
@@ -40,9 +40,9 @@ class AttestationData(Container):
4040
source: Checkpoint
4141
"""The checkpoint representing the source block as observed by the validator."""
4242

43-
def data_root_bytes(self) -> bytes:
43+
def data_root_bytes(self) -> Bytes32:
4444
"""The root of the attestation data."""
45-
return bytes(hash_tree_root(self))
45+
return hash_tree_root(self)
4646

4747

4848
class Attestation(Container):

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
from typing import TYPE_CHECKING
1313

1414
from lean_spec.subspecs.containers.slot import Slot
15-
from lean_spec.subspecs.xmss.aggregation import (
16-
MultisigError,
17-
)
15+
from lean_spec.subspecs.xmss.aggregation import AggregationError
1816
from lean_spec.subspecs.xmss.interface import TARGET_SIGNATURE_SCHEME, GeneralizedXmssScheme
1917
from lean_spec.types import Bytes32, Uint64
2018
from lean_spec.types.container import Container
@@ -189,12 +187,12 @@ def verify_signatures(
189187

190188
public_keys = [validators[vid].get_pubkey() for vid in validator_ids]
191189
try:
192-
aggregated_signature.verify_aggregated_payload(
190+
aggregated_signature.verify(
193191
public_keys=public_keys,
194192
message=attestation_data_root,
195193
epoch=aggregated_attestation.data.slot,
196194
)
197-
except MultisigError as exc:
195+
except AggregationError as exc:
198196
raise AssertionError(
199197
f"Attestation aggregated signature verification failed: {exc}"
200198
) from exc

0 commit comments

Comments
 (0)