Merge pull request #1889 from ethereum/dankrad-custody-0.01bit

0.01 bit proof of custody [depends on 256 bit custody atoms]
This commit is contained in:
Danny Ryan 2020-06-16 07:20:16 -06:00 committed by GitHub
commit eec323c7b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 166 additions and 457 deletions

View File

@ -151,3 +151,9 @@ DOMAIN_DEPOSIT: 0x03000000
DOMAIN_VOLUNTARY_EXIT: 0x04000000
DOMAIN_SELECTION_PROOF: 0x05000000
DOMAIN_AGGREGATE_AND_PROOF: 0x06000000
<<<<<<< HEAD:configs/mainnet.yaml
# Phase 1
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000
DOMAIN_LIGHT_CLIENT: 0x82000000
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000

View File

@ -62,14 +62,26 @@ DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
# 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**14 (= 16,384) epochs
CUSTODY_RESPONSE_DEADLINE: 16384
# 2**15 (= 32,768) epochs, ~146 days
MAX_CHUNK_CHALLENGE_DELAY: 32768
# Misc parameters
# 2**256 - 189
CUSTODY_PRIME: 115792089237316195423570985008687907853269984665640564039457584007913129639747
# 3
CUSTODY_SECRETS: 3
# 2**5 (= 32) bytes
BYTES_PER_CUSTODY_ATOM: 32
# 1/1024 chance of custody bit 1
CUSTODY_PROBABILITY_EXPONENT: 10
# Max operations
# 2**8 (= 256)

View File

@ -65,13 +65,27 @@ DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
# 2**1 (= 2) epochs
RANDAO_PENALTY_EPOCHS: 2
# [customized] quicker for testing
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 128
# [customized] quicker for testing
EPOCHS_PER_CUSTODY_PERIOD: 8
EPOCHS_PER_CUSTODY_PERIOD: 64
# [customized] quicker for testing
CUSTODY_PERIOD_TO_RANDAO_PADDING: 8
# [customized] quicker for testing
CUSTODY_RESPONSE_DEADLINE: 32
CUSTODY_RESPONSE_DEADLINE: 128
# [customize for faster testing]
MAX_CHUNK_CHALLENGE_DELAY: 128
# Misc parameters
# 2**256 - 189
CUSTODY_PRIME: 115792089237316195423570985008687907853269984665640564039457584007913129639747
# 3
CUSTODY_SECRETS: 3
# 2**5 (= 32) bytes
BYTES_PER_CUSTODY_ATOM: 32
# 1/4 chance of custody bit 1 [customized for faster testing]
CUSTODY_PROBABILITY_EXPONENT: 2
# Max operations
# 2**8 (= 256)

View File

@ -53,15 +53,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)
@ -78,7 +75,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)
@ -186,7 +182,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
```
@ -206,8 +201,9 @@ class PendingAttestation(Container):
```python
class IndexedAttestation(Container):
committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
attestation: Attestation
attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
signature: BLSSignature
```
### Extended `AttesterSlashing`
@ -593,17 +589,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
@ -673,65 +658,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
@ -849,16 +775,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)
@ -1081,46 +1002,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

View File

@ -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
@ -68,11 +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 |
| `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 |
| `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
### Max operations per block
@ -140,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
@ -276,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`
@ -517,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
@ -544,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:
@ -576,7 +570,7 @@ 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))
```
@ -584,7 +578,7 @@ def process_reveal_deadlines(state: BeaconState) -> None:
```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()

View File

@ -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 -->
@ -22,12 +20,6 @@
This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1.
## Fork choice
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`
@ -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)
```

View File

@ -16,18 +16,13 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
indexed_attestation = spec.get_indexed_attestation(state, attestation)
spec.on_attestation(store, attestation)
if spec.fork == PHASE0:
sample_index = indexed_attestation.attesting_indices[0]
if spec.fork == PHASE0:
latest_message = spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,
)
else:
attesting_indices = [
index for i, index in enumerate(indexed_attestation.committee)
if attestation.aggregation_bits[i]
]
sample_index = attesting_indices[0]
latest_message = spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,

View File

@ -2,14 +2,13 @@ from lru import LRU
from typing import List
from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1
from eth2spec.test.context import expect_assertion_error, PHASE1
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils import bls
from eth2spec.utils.ssz.ssz_typing import Bitlist
from eth2spec.test.helpers.custody import get_custody_test_vector
def run_attestation_processing(spec, state, attestation, valid=True):
@ -97,44 +96,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t
return attestation_data
def convert_to_valid_on_time_attestation(spec, state, attestation, shard_transition,
signed=False, valid_custody_bits=None):
shard = spec.get_shard(state, attestation)
offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1)
if valid_custody_bits is not None:
beacon_committee = spec.get_beacon_committee(
state,
attestation.data.slot,
attestation.data.index,
)
custody_secrets = [None for i in beacon_committee]
for i in range(len(beacon_committee)):
period = spec.get_custody_period_for_validator(beacon_committee[i], attestation.data.target.epoch)
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, beacon_committee[i])
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign)
signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
custody_secrets[i] = bls.Sign(privkeys[beacon_committee[i]], signing_root)
for i in range(len(offset_slots)):
attestation.custody_bits_blocks.append(
Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
)
if valid_custody_bits is not None:
test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[i])
for j in range(len(attestation.custody_bits_blocks[i])):
if attestation.aggregation_bits[j]:
attestation.custody_bits_blocks[i][j] = \
spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits)
if signed:
sign_attestation(spec, state, attestation)
return attestation
def get_valid_on_time_attestation(spec, state, slot=None, index=None,
shard_transition=None, valid_custody_bits=None, signed=False):
def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False):
'''
Construct on-time attestation for next slot
'''
@ -149,7 +111,6 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None,
slot=slot,
index=index,
shard_transition=shard_transition,
valid_custody_bits=valid_custody_bits,
signed=signed,
on_time=True,
)
@ -174,7 +135,6 @@ def get_valid_attestation(spec,
index=None,
filter_participant_set=None,
shard_transition=None,
valid_custody_bits=None,
signed=False,
on_time=True):
# If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed.
@ -203,14 +163,6 @@ def get_valid_attestation(spec,
# fill the attestation with (optionally filtered) participants, and optionally sign it
fill_aggregate_attestation(spec, state, attestation, signed=signed, filter_participant_set=filter_participant_set)
if spec.fork == PHASE1 and on_time:
attestation = convert_to_valid_on_time_attestation(
spec, state, attestation,
shard_transition,
valid_custody_bits=valid_custody_bits,
signed=signed,
)
return attestation
@ -230,43 +182,9 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List
def sign_indexed_attestation(spec, state, indexed_attestation):
if spec.fork == PHASE0:
participants = indexed_attestation.attesting_indices
data = indexed_attestation.data
indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
else:
participants = spec.get_indices_from_committee(
indexed_attestation.committee,
indexed_attestation.attestation.aggregation_bits,
)
data = indexed_attestation.attestation.data
if any(indexed_attestation.attestation.custody_bits_blocks):
sign_on_time_attestation(spec, state, indexed_attestation.attestation)
else:
indexed_attestation.attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
def sign_on_time_attestation(spec, state, attestation):
if not any(attestation.custody_bits_blocks):
sign_attestation(spec, state, attestation)
return
committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index)
signatures = []
for block_index, custody_bits in enumerate(attestation.custody_bits_blocks):
for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits):
if not abit:
continue
signatures.append(get_attestation_custody_signature(
spec,
state,
attestation.data,
block_index,
cbit,
privkeys[participant]
))
attestation.signature = bls.Aggregate(signatures)
def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey):
@ -283,10 +201,6 @@ def get_attestation_custody_signature(spec, state, attestation_data, block_index
def sign_attestation(spec, state, attestation):
if spec.fork == PHASE1 and any(attestation.custody_bits_blocks):
sign_on_time_attestation(spec, state, attestation)
return
participants = spec.get_attesting_indices(
state,
attestation.data,

View File

@ -1,4 +1,3 @@
from eth2spec.test.context import PHASE1
from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation
@ -41,12 +40,6 @@ def get_indexed_attestation_participants(spec, indexed_att):
"""
Wrapper around index-attestation to return the list of participant indices, regardless of spec phase.
"""
if spec.fork == PHASE1:
return list(spec.get_indices_from_committee(
indexed_att.committee,
indexed_att.attestation.aggregation_bits,
))
else:
return list(indexed_att.attesting_indices)
@ -54,21 +47,12 @@ def set_indexed_attestation_participants(spec, indexed_att, participants):
"""
Wrapper around index-attestation to return the list of participant indices, regardless of spec phase.
"""
if spec.fork == PHASE1:
indexed_att.attestation.aggregation_bits = [bool(i in participants) for i in indexed_att.committee]
else:
indexed_att.attesting_indices = participants
def get_attestation_1_data(spec, att_slashing):
if spec.fork == PHASE1:
return att_slashing.attestation_1.attestation.data
else:
return att_slashing.attestation_1.data
def get_attestation_2_data(spec, att_slashing):
if spec.fork == PHASE1:
return att_slashing.attestation_2.attestation.data
else:
return att_slashing.attestation_2.data

View File

@ -59,7 +59,7 @@ def bitlist_from_int(max_len, num_bits, n):
return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)])
def get_valid_custody_slashing(spec, state, attestation, shard_transition, invalid_custody_bit=False):
def get_valid_custody_slashing(spec, state, attestation, shard_transition, custody_secret, data, data_index=0):
beacon_committee = spec.get_beacon_committee(
state,
attestation.data.slot,
@ -68,21 +68,10 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval
malefactor_index = beacon_committee[0]
whistleblower_index = beacon_committee[-1]
epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch,
malefactor_index)
# Generate the responder key
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch)
signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain)
malefactor_key = bls.Sign(privkeys[malefactor_index], signing_root)
data_index = 0
data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](
get_custody_test_vector(shard_transition.shard_block_lengths[data_index]))
slashing = spec.CustodySlashing(
data_index=data_index,
malefactor_index=malefactor_index,
malefactor_secret=malefactor_key,
malefactor_secret=custody_secret,
whistleblower_index=whistleblower_index,
shard_transition=shard_transition,
attestation=attestation,
@ -165,9 +154,9 @@ def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length,
)
def get_custody_test_vector(bytelength):
def get_custody_test_vector(bytelength, offset=0):
ints = bytelength // 4 + 1
return (b"".join(i.to_bytes(4, "little") for i in range(ints)))[:bytelength]
return (b"".join((i + offset).to_bytes(4, "little") for i in range(ints)))[:bytelength]
def get_shard_transition(spec, start_slot, block_lengths):
@ -181,3 +170,30 @@ def get_shard_transition(spec, start_slot, block_lengths):
proposer_signature_aggregate=spec.BLSSignature(),
)
return shard_transition
def get_custody_secret(spec, state, validator_index, epoch=None):
period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None
else spec.get_current_epoch(state))
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index)
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign)
signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
return bls.Sign(privkeys[validator_index], signing_root)
def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=True):
test_vector = get_custody_test_vector(length)
offset = 0
while spec.compute_custody_bit(custody_secret, test_vector) != slashable:
offset += 1
test_vector = test_vector = get_custody_test_vector(length, offset)
return test_vector
def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, custody_secret, slashable=True):
shard_transition = get_shard_transition(spec, start_slot, block_lengths)
slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret,
block_lengths[0], slashable=slashable)
block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector)
shard_transition.shard_data_roots[0] = block_data.get_backing().get_left().merkle_root()
return shard_transition, slashable_test_vector

View File

@ -1,5 +1,5 @@
from eth2spec.test.context import (
PHASE0, PHASE1,
PHASE0,
spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
)
from eth2spec.test.helpers.attestations import sign_indexed_attestation
@ -162,9 +162,6 @@ def test_same_data(spec, state):
indexed_att_1 = attester_slashing.attestation_1
att_2_data = get_attestation_2_data(spec, attester_slashing)
if spec.fork == PHASE1:
indexed_att_1.attestation.data = att_2_data
else:
indexed_att_1.data = att_2_data
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)

View File

@ -25,22 +25,9 @@ def test_on_time_success(spec, state):
@with_all_phases_except(['phase0'])
@spec_state_test
@always_bls
def test_on_time_empty_custody_bits_blocks(spec, state):
def test_late_success(spec, state):
attestation = get_valid_late_attestation(spec, state, signed=True)
assert not any(attestation.custody_bits_blocks)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases_except(['phase0'])
@spec_state_test
@always_bls
def test_late_with_custody_bits_blocks(spec, state):
attestation = get_valid_on_time_attestation(spec, state, signed=True)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + 1)
yield from run_attestation_processing(spec, state, attestation, False)
yield from run_attestation_processing(spec, state, attestation)

View File

@ -1,6 +1,7 @@
from eth2spec.test.helpers.custody import (
get_valid_custody_slashing,
get_shard_transition,
get_custody_secret,
get_custody_slashable_shard_transition,
)
from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
@ -52,15 +53,39 @@ def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, c
yield 'post', state
@with_all_phases_except(['phase0'])
@spec_state_test
def test_custody_slashing(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
def run_standard_custody_slashing_test(spec,
state,
shard_lateness=None,
shard=None,
validator_index=None,
block_lengths=None,
slashing_message_data=None,
correct=True,
valid=True):
if shard_lateness is None:
shard_lateness = spec.SLOTS_PER_EPOCH
transition_to(spec, state, state.slot + shard_lateness)
if shard is None:
shard = 0
if validator_index is None:
validator_index = spec.get_beacon_committee(state, state.slot, shard)[0]
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
if block_lengths is None:
block_lengths = [2**15 // 3] * len(offset_slots)
custody_secret = get_custody_secret(spec, state, validator_index)
shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(
spec,
state.slot,
block_lengths,
custody_secret,
slashable=correct,
)
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
shard_transition=shard_transition, valid_custody_bits=False)
shard_transition=shard_transition)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
@ -68,109 +93,45 @@ def test_custody_slashing(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition)
slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition,
custody_secret, slashable_test_vector)
yield from run_custody_slashing_processing(spec, state, slashing, correct=True)
if slashing_message_data is not None:
slashing.message.data = slashing_message_data
yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_custody_slashing(spec, state):
yield from run_standard_custody_slashing_test(spec, state)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_incorrect_custody_slashing(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
shard_transition=shard_transition, valid_custody_bits=True)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
_, _, _ = run_attestation_processing(spec, state, attestation)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition)
yield from run_custody_slashing_processing(spec, state, slashing, correct=False)
yield from run_standard_custody_slashing_test(spec, state, correct=False)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_multiple_epochs_custody(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
shard_transition=shard_transition, valid_custody_bits=False)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
_, _, _ = run_attestation_processing(spec, state, attestation)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition)
yield from run_custody_slashing_processing(spec, state, slashing, correct=True)
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_many_epochs_custody(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20)
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
shard_transition=shard_transition, valid_custody_bits=False)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
_, _, _ = run_attestation_processing(spec, state, attestation)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition)
yield from run_custody_slashing_processing(spec, state, slashing, correct=True)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_off_chain_attestation(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
shard_transition=shard_transition, valid_custody_bits=False)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition)
yield from run_custody_slashing_processing(spec, state, slashing, correct=True)
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 10)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_invalid_custody_slashing(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
shard_transition=shard_transition, valid_custody_bits=False)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
_, _, _ = run_attestation_processing(spec, state, attestation)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition)
slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]()
yield from run_custody_slashing_processing(spec, state, slashing, valid=False)
yield from run_standard_custody_slashing_test(
spec,
state,
slashing_message_data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](),
valid=False,
)

View File

@ -1,12 +1,11 @@
from eth2spec.test.helpers.custody import (
get_valid_chunk_challenge,
get_shard_transition,
get_valid_custody_key_reveal,
)
from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
)
from eth2spec.test.helpers.state import next_epoch_via_block, transition_to
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.context import (
with_all_phases_except,
spec_state_test,
@ -17,7 +16,6 @@ from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_ep
from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import (
run_chunk_challenge_processing,
)
from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing
def run_process_challenge_deadlines(spec, state):
@ -44,32 +42,15 @@ def test_validator_slashed_after_chunk_challenge(spec, state):
attestation.data.index
)[0]
spec.initiate_validator_exit(state, validator_index)
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch:
next_epoch_via_block(spec, state)
while (state.validators[validator_index].next_custody_secret_to_reveal
<= spec.get_custody_period_for_validator(
validator_index,
state.validators[validator_index].exit_epoch - 1)):
custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index)
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
next_epoch_via_block(spec, state)
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
assert state.validators[validator_index].slashed == 0
transition_to(spec, state, state.slot + (spec.CUSTODY_RESPONSE_DEADLINE +
spec.EPOCHS_PER_CUSTODY_PERIOD) * spec.SLOTS_PER_EPOCH)
transition_to(spec, state, state.slot + spec.MAX_CHUNK_CHALLENGE_DELAY * spec.SLOTS_PER_EPOCH)
state.validators[validator_index].slashed = 0
yield from run_process_challenge_deadlines(spec, state)

View File

@ -18,11 +18,13 @@ def run_process_challenge_deadlines(spec, state):
@spec_state_test
def test_validator_slashed_after_reveal_deadline(spec, state):
assert state.validators[0].slashed == 0
transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH)
transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE)
* spec.SLOTS_PER_EPOCH))
# Need to run at least one reveal so that not all validators are slashed (otherwise spec fails to find proposers)
custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=1)
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
state.validators[0].slashed = 0
@ -34,15 +36,14 @@ def test_validator_slashed_after_reveal_deadline(spec, state):
@with_all_phases_except(['phase0'])
@spec_state_test
def test_validator_not_slashed_after_reveal(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH
transition_to(spec, state, spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
assert state.validators[0].slashed == 0
transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE)
* spec.SLOTS_PER_EPOCH))
transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
yield from run_process_challenge_deadlines(spec, state)