Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add subnet validations for DoS resistance #1615

Merged
merged 9 commits into from
Feb 12, 2020
2 changes: 2 additions & 0 deletions configs/mainnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ DOMAIN_BEACON_ATTESTER: 0x01000000
DOMAIN_RANDAO: 0x02000000
DOMAIN_DEPOSIT: 0x03000000
DOMAIN_VOLUNTARY_EXIT: 0x04000000
DOMAIN_SELECTION_PROOF: 0x05000000
DOMAIN_AGGREGATE_AND_PROOF: 0x06000000
# Phase 1
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000
Expand Down
2 changes: 2 additions & 0 deletions configs/minimal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ DOMAIN_BEACON_ATTESTER: 0x01000000
DOMAIN_RANDAO: 0x02000000
DOMAIN_DEPOSIT: 0x03000000
DOMAIN_VOLUNTARY_EXIT: 0x04000000
DOMAIN_SELECTION_PROOF: 0x05000000
DOMAIN_AGGREGATE_AND_PROOF: 0x06000000
# Phase 1
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000
Expand Down
13 changes: 8 additions & 5 deletions specs/phase0/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,14 @@ The following values are (non-configurable) constants used throughout the specif

| Name | Value |
| - | - |
| `DOMAIN_BEACON_PROPOSER` | `DomainType('0x00000000')` |
| `DOMAIN_BEACON_ATTESTER` | `DomainType('0x01000000')` |
| `DOMAIN_RANDAO` | `DomainType('0x02000000')` |
| `DOMAIN_DEPOSIT` | `DomainType('0x03000000')` |
| `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` |
| `DOMAIN_BEACON_PROPOSER` | `DomainType('0x00000000')` |
| `DOMAIN_BEACON_ATTESTER` | `DomainType('0x01000000')` |
| `DOMAIN_RANDAO` | `DomainType('0x02000000')` |
| `DOMAIN_DEPOSIT` | `DomainType('0x03000000')` |
| `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` |
| `DOMAIN_SELECTION_PROOF` | `DomainType('0x05000000')` |
| `DOMAIN_AGGREGATE_AND_PROOF` | `DomainType('0x06000000')` |


## Containers

Expand Down
42 changes: 23 additions & 19 deletions specs/phase0/p2p-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,15 @@ where `base64` is the [URL-safe base64 alphabet](https://tools.ietf.org/html/rfc

The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic:

| Topic | Message Type |
|------------------------------------------------|----------------------|
| beacon_block | SignedBeaconBlock |
| beacon_aggregate_and_proof | AggregateAndProof |
| beacon_attestation\* | Attestation |
| committee_index{subnet_id}\_beacon_attestation | Attestation |
| voluntary_exit | SignedVoluntaryExit |
| proposer_slashing | ProposerSlashing |
| attester_slashing | AttesterSlashing |
| Topic | Message Type |
|------------------------------------------------|-------------------------|
| beacon_block | SignedBeaconBlock |
| beacon_aggregate_and_proof | SignedAggregateAndProof |
| beacon_attestation\* | Attestation |
| committee_index{subnet_id}\_beacon_attestation | Attestation |
| voluntary_exit | SignedVoluntaryExit |
| proposer_slashing | ProposerSlashing |
| attester_slashing | AttesterSlashing |

Clients MUST reject (fail validation) messages containing an incorrect type, or invalid payload.

Expand All @@ -250,16 +250,19 @@ When processing incoming gossip, clients MAY descore or disconnect peers who fai
There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `TopicName`s are:

- `beacon_block` - This topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. The following validations MUST pass before forwarding the `signed_beacon_block` on the network
- The proposer signature, `signed_beacon_block.signature` is valid.
- The block is the first block received for the proposer for the slot, `signed_beacon_block.message.slot`.
- The proposer signature, `signed_beacon_block.signature`, is valid.
- The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot).
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `AggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `aggregate_and_proof` on the network.
- The aggregate attestation defined by `hash_tree_root(aggregate_and_proof.aggregate)` has _not_ already been seen (via aggregate gossip, within a block, or through the creation of an equivalent aggregate locally).
- The block being voted for (`aggregate_and_proof.aggregate.data.beacon_block_root`) passes validation.
- `aggregate_and_proof.aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate_and_proof.aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate_and_proof.aggregate.data.slot`.
- The validator index is within the aggregate's committee -- i.e. `aggregate_and_proof.aggregator_index in get_attesting_indices(state, aggregate_and_proof.aggregate.data, aggregate_and_proof.aggregate.aggregation_bits)`.
- `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate_and_proof.aggregate.data.slot, aggregate_and_proof.aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`.
- The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate_and_proof.aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`.
- The signature of `aggregate_and_proof.aggregate` is valid.
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`)
- The aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a block, or through the creation of an equivalent aggregate locally).
- The block being voted for (`aggregate.data.beacon_block_root`) passes validation.
- `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot`.
- The aggregator's validator index is within the aggregate's committee -- i.e. `aggregate_and_proof.aggregator_index in get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)`.
- `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`.
- The `aggregate` is the first aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the slot `aggregate.data.slot`.
- The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`.
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
- The aggregator signature, `signed_aggregate_and_proof.signature`, is valid.
- The signature of `aggregate` is valid.

Additional global topics are used to propagate lower frequency validator messages. Their `TopicName`s are:

Expand All @@ -273,9 +276,10 @@ Attestation subnets are used to propagate unaggregated attestations to subsectio

- `committee_index{subnet_id}_beacon_attestation` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet.
- The attestation's committee index (`attestation.data.index`) is for the correct subnet.
- `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`.
- The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit for bit in attestation.aggregation_bits if bit == 0b1]) == 1`).
- The attestation is the first attestation received for the participating validator for the slot, `attestation.data.slot`.
- The block being voted for (`attestation.data.beacon_block_root`) passes validation.
- `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`.
- The signature of `attestation` is valid.

#### Interop
Expand Down
46 changes: 39 additions & 7 deletions specs/phase0/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
- [Aggregate signature](#aggregate-signature-1)
- [Broadcast aggregate](#broadcast-aggregate)
- [`AggregateAndProof`](#aggregateandproof)
- [`SignedAggregateAndProof`](#signedaggregateandproof)
- [Phase 0 attestation subnet stability](#phase-0-attestation-subnet-stability)
- [How to avoid slashing](#how-to-avoid-slashing)
- [Proposer slashing](#proposer-slashing)
Expand Down Expand Up @@ -423,7 +424,7 @@ A validator is selected to aggregate based upon the return value of `is_aggregat

```python
def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, compute_epoch_at_slot(slot))
domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot))
signing_root = compute_signing_root(slot, domain)
return bls.Sign(privkey, signing_root)
```
Expand Down Expand Up @@ -461,9 +462,37 @@ def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature

#### Broadcast aggregate

If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate to the global aggregate channel (`beacon_aggregate_and_proof`) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`.
If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate as a `SignedAggregateAndProof` to the global aggregate channel (`beacon_aggregate_and_proof`) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`.

Aggregate attestations are broadcast as `AggregateAndProof` objects to prove to the gossip channel that the validator has been selected as an aggregator.
Selection proofs are provided in `AggregateAndProof` to prove to the gossip channel that the validator has been selected as an aggregator.

`AggregateAndProof` messages are signed by the aggregator and broadcast inside of `SignedAggregateAndProof` objects to prevent a class of DoS attacks and message forgeries.

First, `aggregate_and_proof = get_aggregate_and_proof(state, validator_index, aggregate_attestation, privkey)` is constructed.

```python
def get_aggregate_and_proof(state: BeaconState,
aggregator_index: ValidatorIndex,
aggregate: Attestation,
privkey: int) -> AggregateAndProof:
return AggregateAndProof(
aggregator_index=aggregator_index,
aggregate=aggregate,
selection_proof=get_slot_signature(state, aggregate.data.slot, privkey),
)
```

Then `signed_aggregate_and_proof = SignedAggregateAndProof(message=aggregate_and_proof, signature=signature)` is constructed and broadast. Where `signature` is obtained from:

```python
def get_aggregate_and_proof_signature(state: BeaconState,
aggregate_and_proof: AggregateAndProof,
privkey: int) -> BLSSignature:
aggregate = aggregate_and_proof.aggregate
domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot))
signing_root = compute_signing_root(aggregate_and_proof, domain)
return bls.Sign(privkey, signing_root)
```

##### `AggregateAndProof`

Expand All @@ -474,10 +503,13 @@ class AggregateAndProof(Container):
selection_proof: BLSSignature
```

Where
* `aggregator_index` is the validator's `ValidatorIndex`.
* `aggregate` is the `aggregate_attestation` constructed in the previous section.
* `selection_proof` is the signature of the slot (`get_slot_signature()`).
##### `SignedAggregateAndProof`

```python
class SignedAggregateAndProof(Container):
message: AggregateAndProof
signature: BLSSignature
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
```

## Phase 0 attestation subnet stability

Expand Down