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

0.01 bit proof of custody [depends on 256 bit custody atoms] #1889

Merged
merged 7 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions configs/mainnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -196,19 +196,20 @@ GASPRICE_ADJUSTMENT_COEFFICIENT: 8
# Phase 1: Custody Game
# ---------------------------------------------------------------

# 1/1024 chance of custody bit 1
CUSTODY_PROBABILITY_EXPONENT: 10

# Time parameters
# 2**1 (= 2) epochs, 12.8 minutes
RANDAO_PENALTY_EPOCHS: 2
# 2**15 (= 32,768) epochs, ~146 days
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768
# 2**14 (= 16,384) epochs ~73 days
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384
# 2**11 (= 2,048) epochs, ~9 days
EPOCHS_PER_CUSTODY_PERIOD: 2048
EPOCHS_PER_CUSTODY_PERIOD: 16384
# 2**11 (= 2,048) epochs, ~9 days
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
# 2**7 (= 128) epochs, ~14 hours
MAX_REVEAL_LATENESS_DECREMENT: 128
# 2**14 (= 16,384) epochs
CUSTODY_RESPONSE_DEADLINE: 16384
# 2**15 (= 32,768) epochs, ~146 days
MAX_CHUNK_CHALLENGE_DELAY: 32768

# Max operations
# 2**8 (= 256)
Expand Down
19 changes: 11 additions & 8 deletions configs/minimal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,17 +199,20 @@ GASPRICE_ADJUSTMENT_COEFFICIENT: 8
# Phase 1: Custody Game
# ---------------------------------------------------------------

# 1/4 chance of custody bit 1 [customized for faster testing]
CUSTODY_PROBABILITY_EXPONENT: 2

# Time parameters
# 2**1 (= 2) epochs
RANDAO_PENALTY_EPOCHS: 2
# [customized] quicker for testing
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
# 2**11 (= 2,048) epochs
EPOCHS_PER_CUSTODY_PERIOD: 8
# 2**11 (= 2,048) epochs
CUSTODY_PERIOD_TO_RANDAO_PADDING: 8
# 2**14 (= 16,384) epochs
CUSTODY_RESPONSE_DEADLINE: 32
# 2**7 (= 128) epochs, ~13.7 hours [customized for faster testing]
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 128
# 2**6 (= 64) epochs ~6.8 hours [customized for faster testing]
EPOCHS_PER_CUSTODY_PERIOD: 64
# 2**4 (= 16) epochs, ~1.7 hours [customized for faster testing]
CUSTODY_PERIOD_TO_RANDAO_PADDING: 16
# 2**7 (= 128) epochs, ~13.7 hours [customize for faster testing]
MAX_CHUNK_CHALLENGE_DELAY: 128

# Max operations
# 2**8 (= 256)
Expand Down
131 changes: 6 additions & 125 deletions specs/phase1/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,12 @@
- [`get_shard_committee`](#get_shard_committee)
- [`get_light_client_committee`](#get_light_client_committee)
- [`get_shard_proposer_index`](#get_shard_proposer_index)
- [`get_indexed_attestation`](#get_indexed_attestation)
- [`get_committee_count_delta`](#get_committee_count_delta)
- [`get_start_shard`](#get_start_shard)
- [`get_shard`](#get_shard)
- [`get_latest_slot_for_shard`](#get_latest_slot_for_shard)
- [`get_offset_slots`](#get_offset_slots)
- [Predicates](#predicates)
- [`verify_attestation_custody`](#verify_attestation_custody)
- [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation)
- [`is_on_time_attestation`](#is_on_time_attestation)
- [`is_winning_attestation`](#is_winning_attestation)
- [`optional_aggregate_verify`](#optional_aggregate_verify)
Expand All @@ -77,7 +74,6 @@
- [`verify_empty_shard_transition`](#verify_empty_shard_transition)
- [`process_shard_transitions`](#process_shard_transitions)
- [New default validator for deposits](#new-default-validator-for-deposits)
- [New Attester slashing processing](#new-attester-slashing-processing)
- [Light client processing](#light-client-processing)
- [Epoch transition](#epoch-transition)
- [Phase 1 final updates](#phase-1-final-updates)
Expand Down Expand Up @@ -192,7 +188,6 @@ class AttestationData(Container):
class Attestation(Container):
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
custody_bits_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]
signature: BLSSignature
```

Expand All @@ -212,8 +207,9 @@ class PendingAttestation(Container):

```python
class IndexedAttestation(Container):
dankrad marked this conversation as resolved.
Show resolved Hide resolved
committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
attestation: Attestation
attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
signature: BLSSignature
```

### Extended `AttesterSlashing`
Expand Down Expand Up @@ -599,17 +595,6 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard
return committee[r % len(committee)]
```

#### `get_indexed_attestation`

```python
def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) -> IndexedAttestation:
committee = get_beacon_committee(beacon_state, attestation.data.slot, attestation.data.index)
return IndexedAttestation(
committee=committee,
attestation=attestation,
)
```

#### `get_committee_count_delta`

```python
Expand Down Expand Up @@ -679,65 +664,6 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]:

### Predicates

#### `verify_attestation_custody`

```python
def verify_attestation_custody(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
"""
Check if ``indexed_attestation`` has valid signature against non-empty custody bits.
"""
attestation = indexed_attestation.attestation
aggregation_bits = attestation.aggregation_bits
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
all_pubkeys = []
all_signing_roots = []
for block_index, custody_bits in enumerate(attestation.custody_bits_blocks):
assert len(custody_bits) == len(indexed_attestation.committee)
for participant, aggregation_bit, custody_bit in zip(
indexed_attestation.committee, aggregation_bits, custody_bits
):
if aggregation_bit:
all_pubkeys.append(state.validators[participant].pubkey)
# Note: only 2N distinct message hashes
attestation_wrapper = AttestationCustodyBitWrapper(
attestation_data_root=hash_tree_root(attestation.data),
block_index=block_index,
bit=custody_bit,
)
all_signing_roots.append(compute_signing_root(attestation_wrapper, domain))
else:
assert not custody_bit
return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature)
```

#### Updated `is_valid_indexed_attestation`

Note that this replaces the Phase 0 `is_valid_indexed_attestation`.

```python
def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
"""
Check if ``indexed_attestation`` has valid indices and signature.
"""
# Verify aggregate signature
attestation = indexed_attestation.attestation
aggregation_bits = attestation.aggregation_bits
if not any(aggregation_bits) or len(aggregation_bits) != len(indexed_attestation.committee):
return False

if len(attestation.custody_bits_blocks) == 0:
# fall back on phase0 behavior if there is no shard data.
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
all_pubkeys = []
for participant, aggregation_bit in zip(indexed_attestation.committee, aggregation_bits):
if aggregation_bit:
all_pubkeys.append(state.validators[participant].pubkey)
signing_root = compute_signing_root(indexed_attestation.attestation.data, domain)
return bls.FastAggregateVerify(all_pubkeys, signing_root, signature=attestation.signature)
else:
return verify_attestation_custody(state, indexed_attestation)
```

#### `is_on_time_attestation`

```python
Expand Down Expand Up @@ -855,16 +781,11 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
else:
assert attestation.data.source == state.previous_justified_checkpoint

# Type 1: on-time attestations, the custody bits should be non-empty.
if attestation.custody_bits_blocks != []:
# Ensure on-time attestation
assert is_on_time_attestation(state, attestation)
# Correct data root count
shard = get_shard(state, attestation)
assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard))
# Type 1: on-time attestations
if is_on_time_attestation(state, attestation):
# Correct parent block root
assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))
# Type 2: no shard transition, no custody bits
# Type 2: no shard transition
else:
# Ensure delayed attestation
assert data.slot < compute_previous_slot(state.slot)
Expand Down Expand Up @@ -1087,46 +1008,6 @@ def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validato
)
```

##### New Attester slashing processing

```python
def get_indices_from_committee(
committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE],
bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Sequence[ValidatorIndex]:
assert len(bits) == len(committee)
return [validator_index for i, validator_index in enumerate(committee) if bits[i]]
```

```python
def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None:
indexed_attestation_1 = attester_slashing.attestation_1
indexed_attestation_2 = attester_slashing.attestation_2

assert is_slashable_attestation_data(
indexed_attestation_1.attestation.data,
indexed_attestation_2.attestation.data,
)
assert is_valid_indexed_attestation(state, indexed_attestation_1)
assert is_valid_indexed_attestation(state, indexed_attestation_2)

indices_1 = get_indices_from_committee(
indexed_attestation_1.committee,
indexed_attestation_1.attestation.aggregation_bits,
)
indices_2 = get_indices_from_committee(
indexed_attestation_2.committee,
indexed_attestation_2.attestation.aggregation_bits,
)

slashed_any = False
indices = set(indices_1).intersection(indices_2)
for index in sorted(indices):
if is_slashable_validator(state.validators[index], get_current_epoch(state)):
slash_validator(state, index)
slashed_any = True
assert slashed_any
```

#### Light client processing

```python
Expand Down
29 changes: 11 additions & 18 deletions specs/phase1/custody-game.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
| `CUSTODY_PRIME` | `2 ** 256 - 189` | - |
| `CUSTODY_SECRETS` | `3` | - |
| `BYTES_PER_CUSTODY_ATOM` | `32` | bytes |
| `CUSTODY_PROBABILITY_EXPONENT` | `10` | - |

## Configuration

Expand All @@ -68,12 +69,11 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `RANDAO_PENALTY_EPOCHS` | `2**1` (= 2) | epochs | 12.8 minutes |
| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` (= 16,384) | epochs | ~73 days |
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days |
| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**15` (= 32,768) | epochs | ~146 days |
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**14` (= 16,384) | epochs | ~73 days |
| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days |
| `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 2,048) | epochs | ~9 days |
| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
| `MAX_CHUNK_CHALLENGE_DELAY` | `2**15` (= 32,768) | epochs | ~146 days |

### Max operations per block

Expand Down Expand Up @@ -141,7 +141,6 @@ class CustodyChunkResponse(Container):

```python
class CustodySlashing(Container):
# Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check.
# (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data.
data_index: uint64
malefactor_index: ValidatorIndex
Expand Down Expand Up @@ -277,7 +276,8 @@ def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE])
custody_atoms = get_custody_atoms(data)
secrets = get_custody_secrets(key)
uhf = universal_hash_function(custody_atoms, secrets)
return legendre_bit(uhf + secrets[0], CUSTODY_PRIME)
legendre_bits = [legendre_bit(uhf + secrets[0] + i, CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)]
return all(legendre_bits)
```

### `get_randao_epoch_for_custody_period`
Expand Down Expand Up @@ -518,9 +518,6 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
# Verify the attestation
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))

# TODO: custody_slashing.data is not chunked like shard blocks yet, result is lots of padding.
# ??? What does this mean?

# TODO: can do a single combined merkle proof of data being attested.
# Verify the shard transition is indeed attested by the attestation
shard_transition = custody_slashing.shard_transition
Expand All @@ -545,18 +542,14 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
signing_root = compute_signing_root(epoch_to_sign, domain)
assert bls.Verify(malefactor.pubkey, signing_root, custody_slashing.malefactor_secret)

# Get the custody bit
custody_bits = attestation.custody_bits_blocks[custody_slashing.data_index]
committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
claimed_custody_bit = custody_bits[committee.index(custody_slashing.malefactor_index)]

# Compute the custody bit
computed_custody_bit = compute_custody_bit(custody_slashing.malefactor_secret, custody_slashing.data)

# Verify the claim
if claimed_custody_bit != computed_custody_bit:
if computed_custody_bit == 1:
# Slash the malefactor, reward the other committee members
slash_validator(state, custody_slashing.malefactor_index)
committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
others_count = len(committee) - 1
whistleblower_reward = Gwei(malefactor.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT // others_count)
for attester_index in attesters:
Expand All @@ -577,15 +570,15 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
def process_reveal_deadlines(state: BeaconState) -> None:
epoch = get_current_epoch(state)
for index, validator in enumerate(state.validators):
deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD)
deadline = validator.next_custody_secret_to_reveal + 1
if get_custody_period_for_validator(ValidatorIndex(index), epoch) > deadline:
slash_validator(state, ValidatorIndex(index))
```

```python
def process_challenge_deadlines(state: BeaconState) -> None:
for custody_chunk_challenge in state.custody_chunk_challenge_records:
if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + EPOCHS_PER_CUSTODY_PERIOD:
slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index)
index_in_records = state.custody_chunk_challenge_records.index(custody_chunk_challenge)
state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord()
Expand Down
34 changes: 0 additions & 34 deletions specs/phase1/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@


- [Introduction](#introduction)
- [Fork choice](#fork-choice)
- [Helpers](#helpers)
- [Extended `LatestMessage`](#extended-latestmessage)
- [Updated `update_latest_messages`](#updated-update_latest_messages)
- [Handlers](#handlers)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
Expand All @@ -22,12 +20,6 @@

This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1.

## Fork choice
dankrad marked this conversation as resolved.
Show resolved Hide resolved

Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_attestation` must be re-specified to handle this. The bulk of `on_attestation` has been moved out into a few helpers to reduce code duplication where possible.

The rest of the fork choice remains stable.

### Helpers

#### Extended `LatestMessage`
Expand All @@ -54,29 +46,3 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn
epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.shard_head_root
)
```

### Handlers

```python
def on_attestation(store: Store, attestation: Attestation) -> None:
"""
Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire.

An ``attestation`` that is asserted as invalid may be valid at a later time,
consider scheduling it for later processing in such case.
"""
validate_on_attestation(store, attestation)
store_target_checkpoint_state(store, attestation.data.target)

# Get state at the `target` to fully validate attestation
target_state = store.checkpoint_states[attestation.data.target]
indexed_attestation = get_indexed_attestation(target_state, attestation)
assert is_valid_indexed_attestation(target_state, indexed_attestation)

# Update latest messages for attesting indices
attesting_indices = [
index for i, index in enumerate(indexed_attestation.committee)
if attestation.aggregation_bits[i]
]
update_latest_messages(store, attesting_indices, attestation)
```
Loading