Custody game changes (#866)

* Custody game changes

1. Don't store the full chunk bits, instead only store a Merkle root. Increased history size complexity from `N` to `N + log(N)` but with the benefit of decreasing storage requirements from `N` to a single 32 byte hash.
2. `custody_bit` is computed as the first bit of the hash of the custody bits, not the xor. This allows us to more safely use functions with more risky security assumptions for computing the chunk mix.

* Update specs/core/1_custody-game.md

* Update specs/core/1_custody-game.md

* Update specs/core/1_custody-game.md

* Update specs/core/1_custody-game.md

* XOR aggregation before SHA256 to reduce number of hashes

* Simplifed get_chunk_bits_root

* standalone -> indexed

* Fix missing "data" and ToC
This commit is contained in:
vbuterin 2019-05-03 17:20:54 +08:00 committed by GitHub
parent cdcb16dc1c
commit 4ca2f11827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -32,6 +32,7 @@
- [`empty`](#empty) - [`empty`](#empty)
- [`get_crosslink_chunk_count`](#get_crosslink_chunk_count) - [`get_crosslink_chunk_count`](#get_crosslink_chunk_count)
- [`get_custody_chunk_bit`](#get_custody_chunk_bit) - [`get_custody_chunk_bit`](#get_custody_chunk_bit)
- [`get_chunk_bits_root`](#get_chunk_bits_root)
- [`epoch_to_custody_period`](#epoch_to_custody_period) - [`epoch_to_custody_period`](#epoch_to_custody_period)
- [`replace_empty_or_append`](#replace_empty_or_append) - [`replace_empty_or_append`](#replace_empty_or_append)
- [`verify_custody_key`](#verify_custody_key) - [`verify_custody_key`](#verify_custody_key)
@ -148,7 +149,8 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
'responder_index': ValidatorIndex, 'responder_index': ValidatorIndex,
'deadline': Epoch, 'deadline': Epoch,
'crosslink_data_root': Hash, 'crosslink_data_root': Hash,
'chunk_bits': Bitfield, 'chunk_count': 'uint64',
'chunk_bits_merkle_root': Hash,
'responder_key': BLSSignature, 'responder_key': BLSSignature,
} }
``` ```
@ -160,7 +162,9 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
'challenge_index': 'uint64', 'challenge_index': 'uint64',
'chunk_index': 'uint64', 'chunk_index': 'uint64',
'chunk': ['byte', BYTES_PER_CUSTODY_CHUNK], 'chunk': ['byte', BYTES_PER_CUSTODY_CHUNK],
'branch': [Hash], 'data_branch': [Hash],
'chunk_bits_branch': [Hash],
'chunk_bits_leaf': Hash,
} }
``` ```
@ -233,6 +237,17 @@ def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool:
return get_bitfield_bit(hash(challenge.responder_key + chunk), 0) return get_bitfield_bit(hash(challenge.responder_key + chunk), 0)
``` ```
### `get_chunk_bits_root`
```python
def get_chunk_bits_root(chunk_bitfield: Bitfield) -> Bytes32:
aggregated_bits = bytearray([0] * 32)
for i in range(0, len(chunk_bitfield), 32):
for j in range(32):
aggregated_bits[j] ^= chunk_bitfield[i+j]
return hash(aggregated_bits)
```
### `epoch_to_custody_period` ### `epoch_to_custody_period`
```python ```python
@ -326,7 +341,7 @@ For each `challenge` in `block.body.custody_chunk_challenges`, run the following
def process_chunk_challenge(state: BeaconState, def process_chunk_challenge(state: BeaconState,
challenge: CustodyChunkChallenge) -> None: challenge: CustodyChunkChallenge) -> None:
# Verify the attestation # Verify the attestation
assert verify_standalone_attestation(state, convert_to_standalone(state, challenge.attestation)) assert verify_indexed_attestation(state, convert_to_indexed(state, challenge.attestation))
# Verify it is not too late to challenge # Verify it is not too late to challenge
assert slot_to_epoch(challenge.attestation.data.slot) >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY assert slot_to_epoch(challenge.attestation.data.slot) >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
responder = state.validator_registry[challenge.responder_index] responder = state.validator_registry[challenge.responder_index]
@ -380,7 +395,7 @@ def process_bit_challenge(state: BeaconState,
# Verify the challenger is not slashed # Verify the challenger is not slashed
assert challenger.slashed is False assert challenger.slashed is False
# Verify the attestation # Verify the attestation
assert verify_standalone_attestation(state, convert_to_standalone(state, challenge.attestation)) assert verify_indexed_attestation(state, convert_to_indexed(state, challenge.attestation))
# Verify the attestation is eligible for challenging # Verify the attestation is eligible for challenging
responder = state.validator_registry[challenge.responder_index] responder = state.validator_registry[challenge.responder_index]
min_challengeable_epoch = responder.exit_epoch - EPOCHS_PER_CUSTODY_PERIOD * (1 + responder.max_reveal_lateness) min_challengeable_epoch = responder.exit_epoch - EPOCHS_PER_CUSTODY_PERIOD * (1 + responder.max_reveal_lateness)
@ -403,20 +418,18 @@ def process_bit_challenge(state: BeaconState,
# Verify the chunk count # Verify the chunk count
chunk_count = get_custody_chunk_count(challenge.attestation) chunk_count = get_custody_chunk_count(challenge.attestation)
assert verify_bitfield(challenge.chunk_bits, chunk_count) assert verify_bitfield(challenge.chunk_bits, chunk_count)
# Verify the xor of the chunk bits does not equal the custody bit # Verify the first bit of the hash of the chunk bits does not equal the custody bit
chunk_bits_xor = 0b0
for i in range(chunk_count):
chunk_bits_xor ^ get_bitfield_bit(challenge.chunk_bits, i)
custody_bit = get_bitfield_bit(attestation.custody_bitfield, attesters.index(responder_index)) custody_bit = get_bitfield_bit(attestation.custody_bitfield, attesters.index(responder_index))
assert custody_bit != chunk_bits_xor assert custody_bit != get_bitfield_bit(get_chunk_bits_root(challenge.chunk_bits), 0)
# Add new bit challenge record # Add new bit challenge record
new_record = CustodyBitChallengeRecord( new_record = CustodyBitChallengeRecord(
challenge_index=state.custody_challenge_index, challenge_index=state.custody_challenge_index,
challenger_index=challenge.challenger_index, challenger_index=challenge.challenger_index,
responder_index=challenge.responder_index, responder_index=challenge.responder_index,
deadline=get_current_epoch(state) + CUSTODY_RESPONSE_DEADLINE deadline=get_current_epoch(state) + CUSTODY_RESPONSE_DEADLINE
crosslink_data_root=challenge.attestation.crosslink_data_root, crosslink_data_root=challenge.attestation.data.crosslink_data_root,
chunk_bits=challenge.chunk_bits, chunk_count=chunk_count,
chunk_bits_merkle_root=merkle_root(pad_to_power_of_2((challenge.chunk_bits))),
responder_key=challenge.responder_key, responder_key=challenge.responder_key,
) )
replace_empty_or_append(state.custody_bit_challenge_records, new_record) replace_empty_or_append(state.custody_bit_challenge_records, new_record)
@ -451,10 +464,12 @@ def process_chunk_challenge_response(state: BeaconState,
challenge: CustodyChunkChallengeRecord) -> None: challenge: CustodyChunkChallengeRecord) -> None:
# Verify chunk index # Verify chunk index
assert response.chunk_index == challenge.chunk_index assert response.chunk_index == challenge.chunk_index
# Verify bit challenge data is null
assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == ZERO_HASH
# Verify the chunk matches the crosslink data root # Verify the chunk matches the crosslink data root
assert verify_merkle_branch( assert verify_merkle_branch(
leaf=hash_tree_root(response.chunk), leaf=hash_tree_root(response.chunk),
branch=response.branch, branch=response.data_branch,
depth=challenge.depth, depth=challenge.depth,
index=response.chunk_index, index=response.chunk_index,
root=challenge.crosslink_data_root, root=challenge.crosslink_data_root,
@ -472,17 +487,25 @@ def process_bit_challenge_response(state: BeaconState,
response: CustodyResponse, response: CustodyResponse,
challenge: CustodyBitChallengeRecord) -> None: challenge: CustodyBitChallengeRecord) -> None:
# Verify chunk index # Verify chunk index
assert response.chunk_index < len(challenge.chunk_bits) assert response.chunk_index < challenge.chunk_count
# Verify the chunk matches the crosslink data root # Verify the chunk matches the crosslink data root
assert verify_merkle_branch( assert verify_merkle_branch(
leaf=hash_tree_root(response.chunk), leaf=hash_tree_root(response.chunk),
branch=response.branch, branch=response.data_branch,
depth=math.log2(next_power_of_two(len(challenge.chunk_bits))), depth=math.log2(next_power_of_two(challenge.chunk_count)),
index=response.chunk_index, index=response.chunk_index,
root=challenge.crosslink_data_root, root=challenge.crosslink_data_root,
) )
# Verify the chunk bit leaf matches the challenge data
assert verify_merkle_branch(
leaf=response.chunk_bits_leaf,
branch=response.chunk_bits_branch,
depth=math.log2(next_power_of_two(challenge.chunk_count) // 256),
index=response.chunk_index // 256,
root=challenge.chunk_bits_merkle_root
)
# Verify the chunk bit does not match the challenge chunk bit # Verify the chunk bit does not match the challenge chunk bit
assert get_custody_chunk_bit(challenge.responder_key, response.chunk) != get_bitfield_bit(challenge.chunk_bits, response.chunk_index) assert get_custody_chunk_bit(challenge.responder_key, response.chunk) != get_bitfield_bit(challenge.chunk_bits_leaf, response.chunk_index % 256)
# Clear the challenge # Clear the challenge
records = state.custody_bit_challenge_records records = state.custody_bit_challenge_records
records[records.index(challenge)] = CustodyBitChallengeRecord() records[records.index(challenge)] = CustodyBitChallengeRecord()