--- eip: 8384 title: Deterministic sync committee aggregators description: Select sync committee aggregators as a function of beacon state author: Kevaundray Wedderburn (@kevaundray), Tom Wambsgans (@tomWambsgans) discussions-to: https://ethereum-magicians.org/t/eip-8384-deterministic-sync-committee-aggregators/29465 status: Draft type: Standards Track category: Core created: 2026-08-19 --- ## Abstract Sync committee aggregators are currently self-selected, so each member signs a `SyncAggregatorSelectionData` under `DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF` and aggregates if the hash of that signature falls below a threshold. This EIP replaces the VRF-like lottery with `get_sync_aggregator_pubkeys`, a pure function of beacon state, and removes the selection proof from `ContributionAndProof`. The main feature that we lose is lookahead aggregator privacy, however we don't have this for includers and proposers; roles that actually affect consensus. ## Motivation Secrecy of the aggregator set until publication is the main property that the current BLS signature contribution proof is giving us. Using BLS however means two things: - Post-quantum transition: self-selection relies on BLS signatures being *unique*, so that a validator cannot retry into a win. Hash-based schemes using target-sum encoding (leanSig) are not unique since the signer grinds a randomizer to hit the target sum, so many valid signatures exist per message and a validator could grind itself into the aggregator set. - Attributable non-performance: A validator that lost the lottery is today indistinguishable from one that won and stayed silent, so aggregation duty cannot be monitored or penalised (if we wanted to do this). The more important aspect here is post quantum and we solve this by having a deterministic selection since that does not require key material. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174). ### New constants | Name | Value | | - | - | | `DOMAIN_SYNC_COMMITTEE_AGGREGATOR` | `DomainType('0x12000000')` | ### New `get_sync_aggregator_pubkeys` ```python def get_sync_aggregator_pubkeys(state: BeaconState, slot: Slot, subcommittee_index: uint64) -> Sequence[BLSPubkey]: """ Return the aggregator pubkeys for ``subcommittee_index`` at ``slot``. """ seed = hash( get_seed(state, compute_epoch_at_slot(slot), DOMAIN_SYNC_COMMITTEE_AGGREGATOR) + uint_to_bytes(slot) + uint_to_bytes(subcommittee_index) ) subcommittee = get_sync_subcommittee_pubkeys(state, subcommittee_index) # Clamped so that small presets, where the subcommittee is smaller than the # target, select the whole subcommittee rather than exceeding its bounds count = min(TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE, uint64(len(subcommittee))) return [ subcommittee[compute_shuffled_index(uint64(i), uint64(len(subcommittee)), seed)] for i in range(count) ] ``` - The seed MUST be derived via `get_seed` as shown, not from `get_randao_mix(state, get_current_epoch(state))`. - `get_sync_subcommittee_pubkeys` is unchanged, including its rule for resolving `current_sync_committee` against `next_sync_committee` at a period boundary. - Similar to today, aggregating is based on honest validator behavior rather than a consensus obligation, so there is no penalty for an aggregator that stays silent. ### Modified `is_sync_committee_aggregator` ```python def is_sync_committee_aggregator(state: BeaconState, slot: Slot, subcommittee_index: uint64, pubkey: BLSPubkey) -> bool: return pubkey in get_sync_aggregator_pubkeys(state, slot, subcommittee_index) ``` ### Modified `ContributionAndProof` ```python class ContributionAndProof(Container): aggregator_index: ValidatorIndex contribution: SyncCommitteeContribution ``` ### Modified gossip validation On the `sync_committee_contribution_and_proof` topic, the condition that verified `selection_proof` as a signature over `SyncAggregatorSelectionData` is removed, and we have: - `[REJECT]` `is_sync_committee_aggregator(state, contribution.slot, contribution.subcommittee_index, aggregator_pubkey)`, where `aggregator_pubkey` is `state.validators[contribution_and_proof.aggregator_index].pubkey`. ### Removed - `DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF` - `SyncAggregatorSelectionData` - `get_sync_committee_selection_proof` - the `selection_proof` field of `ContributionAndProof` `TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE` is retained, with its meaning changed from an expected count to an exact count. ## Rationale ### Precedence [EIP-7805](./eip-7805.md) is the closest analogue, it doesn't have lookahead protection. This proposal does the same for a duty that is *not* consensus critical. ### Seeding from `get_seed` First, gossip validation for this topic runs against the head state, so the seed must not depend on recent blocks. `process_randao` mixes each block's reveal into `randao_mixes[current_epoch]`, so `get_randao_mix(state, get_current_epoch(state))` changes with every block. Two honest nodes that momentarily disagree about the head, which happens routinely at every slot, would derive different aggregator sets and reject each other's otherwise valid contributions. `get_seed` avoids this since it reads `randao_mixes[epoch - MIN_SEED_LOOKAHEAD - 1]`, and the protocol stops writing that entry once its epoch ends. Within epoch `e` the seed therefore depends only on blocks up to the final slot of epoch `e - 2`. Under normal finalization those blocks are at least justified, so in order for two nodes to disagree, they would need to have forked off at a justified checkpoint. Note: `slot` is in the seed because `get_seed` changes every epoch, and we need a new set of aggregators on every slot. ### Prefix of a shuffling `compute_shuffled_index` is a bijection over the subcommittee, so a fixed-size prefix of it is a *sample without replacement*. Note: `compute_committee` derives every beacon committee the same way. ### Reduced Cost As a secondary benefit, removing the selection proof also removes one BLS verification per contribution message during gossip validation, and 96 bytes from each message. ## Backwards Compatibility This is a consensus change requiring a hard fork. `ContributionAndProof` changes so the `sync_committee_contribution_and_proof` topic is not wire-compatible across the fork boundary and requires a new fork. Sync committee membership, duties, rewards, penalties, and the on-chain `SyncAggregate` are unchanged. Light client behaviour is also unchanged. ## Security Considerations ### Loss of aggregator secrecy We note that every other duty is public in advance: - attestation committees one to two epochs ahead - Includers one epoch ahead - proposers one epoch - sync committee membership about 27 hours. Aggregation is the exception here since selection proofs are unpredictable until published. We also note that attestation aggregator secrecy is already partly leaked, because `is_aggregator()` decides whether a validator subscribes to `beacon_attestation_{subnet_id}` and non-aggregators need not subscribe, so the subscription signals the outcome to peers before anything is published. This EIP does not claim that lookahead privacy is not important. A complete solution given privacy would be welcome. ### RANDAO grinding Using randao to select the aggregator set does not introduce any new attack vectors because we were already using randao to select the sync committee. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).