|
| 1 | +from typing import Sequence |
| 2 | + |
1 | 3 | from eth_typing import BLSSignature
|
| 4 | +from eth_utils import ValidationError |
2 | 5 | from ssz import get_hash_tree_root, uint64
|
3 | 6 |
|
4 | 7 | from eth2._utils.bls import bls
|
5 | 8 | from eth2._utils.hash import hash_eth2
|
| 9 | +from eth2.beacon.attestation_helpers import ( |
| 10 | + validate_indexed_attestation_aggregate_signature, |
| 11 | +) |
6 | 12 | from eth2.beacon.committee_helpers import get_beacon_committee
|
| 13 | +from eth2.beacon.epoch_processing_helpers import ( |
| 14 | + get_attesting_indices, |
| 15 | + get_indexed_attestation, |
| 16 | +) |
7 | 17 | from eth2.beacon.helpers import compute_epoch_at_slot, get_domain
|
8 | 18 | from eth2.beacon.signature_domain import SignatureDomain
|
| 19 | +from eth2.beacon.types.aggregate_and_proof import AggregateAndProof |
| 20 | +from eth2.beacon.types.attestations import Attestation |
9 | 21 | from eth2.beacon.types.states import BeaconState
|
10 |
| -from eth2.beacon.typing import CommitteeIndex, Slot |
| 22 | +from eth2.beacon.typing import Bitfield, CommitteeIndex, Slot |
11 | 23 | from eth2.configs import CommitteeConfig
|
12 | 24 |
|
13 | 25 | # TODO: TARGET_AGGREGATORS_PER_COMMITTEE is not in Eth2Config now.
|
14 | 26 | TARGET_AGGREGATORS_PER_COMMITTEE = 16
|
15 | 27 |
|
16 | 28 |
|
17 |
| -def slot_signature( |
| 29 | +def get_slot_signature( |
18 | 30 | state: BeaconState, slot: Slot, privkey: int, config: CommitteeConfig
|
19 | 31 | ) -> BLSSignature:
|
20 | 32 | """
|
@@ -54,3 +66,123 @@ def is_aggregator(
|
54 | 66 | committee = get_beacon_committee(state, slot, index, config)
|
55 | 67 | modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE)
|
56 | 68 | return int.from_bytes(hash_eth2(signature)[0:8], byteorder="little") % modulo == 0
|
| 69 | + |
| 70 | + |
| 71 | +def get_aggregate_from_valid_committee_attestations( |
| 72 | + attestations: Sequence[Attestation] |
| 73 | +) -> Attestation: |
| 74 | + """ |
| 75 | + Return the aggregate attestation. |
| 76 | +
|
| 77 | + The given attestations SHOULD have the same `data: AttestationData` and are valid. |
| 78 | + """ |
| 79 | + signatures = [attestation.signature for attestation in attestations] |
| 80 | + aggregate_signature = bls.aggregate_signatures(signatures) |
| 81 | + |
| 82 | + all_aggregation_bits = [ |
| 83 | + attestation.aggregation_bits for attestation in attestations |
| 84 | + ] |
| 85 | + aggregation_bits = tuple(map(any, zip(*all_aggregation_bits))) |
| 86 | + |
| 87 | + assert len(attestations) > 0 |
| 88 | + |
| 89 | + return Attestation.create( |
| 90 | + data=attestations[0].data, |
| 91 | + aggregation_bits=Bitfield(aggregation_bits), |
| 92 | + signature=aggregate_signature, |
| 93 | + ) |
| 94 | + |
| 95 | + |
| 96 | +# |
| 97 | +# Validation |
| 98 | +# |
| 99 | + |
| 100 | + |
| 101 | +def validate_aggregate_and_proof( |
| 102 | + state: BeaconState, |
| 103 | + aggregate_and_proof: AggregateAndProof, |
| 104 | + attestation_propagation_slot_range: int, |
| 105 | + config: CommitteeConfig, |
| 106 | +) -> None: |
| 107 | + """ |
| 108 | + Validate aggregate_and_proof |
| 109 | +
|
| 110 | + Reference: https://github.com/ethereum/eth2.0-specs/blob/master/specs/networking/p2p-interface.md#global-topics # noqa: E501 |
| 111 | + """ |
| 112 | + attestation = aggregate_and_proof.aggregate |
| 113 | + |
| 114 | + validate_attestation_propagation_slot_range( |
| 115 | + state, attestation, attestation_propagation_slot_range |
| 116 | + ) |
| 117 | + |
| 118 | + attesting_indices = get_attesting_indices( |
| 119 | + state, attestation.data, attestation.aggregation_bits, config |
| 120 | + ) |
| 121 | + if aggregate_and_proof.aggregator_index not in attesting_indices: |
| 122 | + raise ValidationError( |
| 123 | + f"The aggregator index ({aggregate_and_proof.aggregator_index}) is not within" |
| 124 | + f" the aggregate's committee {attesting_indices}" |
| 125 | + ) |
| 126 | + |
| 127 | + if not is_aggregator( |
| 128 | + state, |
| 129 | + attestation.data.slot, |
| 130 | + attestation.data.index, |
| 131 | + aggregate_and_proof.selection_proof, |
| 132 | + config, |
| 133 | + ): |
| 134 | + raise ValidationError( |
| 135 | + f"The given validator {aggregate_and_proof.aggregator_index}" |
| 136 | + " is not a selected aggregator" |
| 137 | + ) |
| 138 | + |
| 139 | + validate_aggregator_proof(state, aggregate_and_proof, config) |
| 140 | + |
| 141 | + validate_attestation_signature(state, attestation, config) |
| 142 | + |
| 143 | + |
| 144 | +def validate_attestation_propagation_slot_range( |
| 145 | + state: BeaconState, |
| 146 | + attestation: Attestation, |
| 147 | + attestation_propagation_slot_range: int, |
| 148 | +) -> None: |
| 149 | + if ( |
| 150 | + attestation.data.slot + attestation_propagation_slot_range < state.slot |
| 151 | + or attestation.data.slot > state.slot |
| 152 | + ): |
| 153 | + raise ValidationError( |
| 154 | + "attestation.data.slot should be within the last" |
| 155 | + " {attestation_propagation_slot_range} slots. Got" |
| 156 | + f" attestationdata.slot={attestation.data.slot}," |
| 157 | + f" current slot={state.slot}" |
| 158 | + ) |
| 159 | + |
| 160 | + |
| 161 | +def validate_aggregator_proof( |
| 162 | + state: BeaconState, aggregate_and_proof: AggregateAndProof, config: CommitteeConfig |
| 163 | +) -> None: |
| 164 | + slot = aggregate_and_proof.aggregate.data.slot |
| 165 | + pubkey = state.validators[aggregate_and_proof.aggregator_index].pubkey |
| 166 | + domain = get_domain( |
| 167 | + state, |
| 168 | + SignatureDomain.DOMAIN_BEACON_ATTESTER, |
| 169 | + config.SLOTS_PER_EPOCH, |
| 170 | + message_epoch=compute_epoch_at_slot(slot, config.SLOTS_PER_EPOCH), |
| 171 | + ) |
| 172 | + message_hash = get_hash_tree_root(slot, sedes=uint64) |
| 173 | + |
| 174 | + bls.validate( |
| 175 | + message_hash=message_hash, |
| 176 | + pubkey=pubkey, |
| 177 | + signature=aggregate_and_proof.selection_proof, |
| 178 | + domain=domain, |
| 179 | + ) |
| 180 | + |
| 181 | + |
| 182 | +def validate_attestation_signature( |
| 183 | + state: BeaconState, attestation: Attestation, config: CommitteeConfig |
| 184 | +) -> None: |
| 185 | + indexed_attestation = get_indexed_attestation(state, attestation, config) |
| 186 | + validate_indexed_attestation_aggregate_signature( |
| 187 | + state, indexed_attestation, config.SLOTS_PER_EPOCH |
| 188 | + ) |
0 commit comments