Legendre custody construction (#1305)

* Stab at Legendre custody construction + some tests

* Fix some problems and fix function puller to remove phase0 only lines in phase1

* Pass the linter

* Add headings

* Fix domain for BLS stub

* Change Jacobi to Legendre

* n -> q to clarify notation

* Headings

* Another missing heading

* Custody subchunks via padding

* Fix max_reveal_lateness stuff

* Better names for reveal period functions

* Better parametrization of max_reveal_lateness computation and tests for custody reveal processing

* Fix linter

* Allow challenging for one period after the custody reveal, shorter periods for minimal tests

* Fix lint

* Fix linter error
This commit is contained in:
dankrad 2019-08-11 10:05:17 -07:00 committed by vbuterin
parent 13f6f18cd5
commit b345dc0f5f
13 changed files with 777 additions and 73 deletions

View File

@ -74,6 +74,10 @@ MAX_EPOCHS_PER_CROSSLINK: 4
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4 MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
# [customized] 2**12 (= 4,096) epochs # [customized] 2**12 (= 4,096) epochs
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
# 2**2 (= 4) epochs
EPOCHS_PER_CUSTODY_PERIOD: 4
# 2**2 (= 4) epochs
CUSTODY_PERIOD_TO_RANDAO_PADDING: 4
# State vector lengths # State vector lengths

View File

@ -58,10 +58,15 @@ from eth2spec.utils.bls import (
bls_aggregate_pubkeys, bls_aggregate_pubkeys,
bls_verify, bls_verify,
bls_verify_multiple, bls_verify_multiple,
bls_signature_to_G2,
) )
from eth2spec.utils.hash_function import hash from eth2spec.utils.hash_function import hash
''' '''
SUNDRY_CONSTANTS_FUNCTIONS = '''
def ceillog2(x: uint64) -> int:
return (x - 1).bit_length()
'''
SUNDRY_FUNCTIONS = ''' SUNDRY_FUNCTIONS = '''
# Monkey patch hash cache # Monkey patch hash cache
_hash = hash _hash = hash
@ -111,6 +116,13 @@ def apply_constants_preset(preset: Dict[str, Any]) -> None:
''' '''
def remove_for_phase1(functions: Dict[str, str]):
for key, value in functions.items():
lines = value.split("\n")
lines = filter(lambda s: "[to be removed in phase 1]" not in s, lines)
functions[key] = "\n".join(lines)
def strip_comments(raw: str) -> str: def strip_comments(raw: str) -> str:
comment_line_regex = re.compile(r'^\s+# ') comment_line_regex = re.compile(r'^\s+# ')
lines = raw.split('\n') lines = raw.split('\n')
@ -141,10 +153,15 @@ def objects_to_spec(functions: Dict[str, str],
] ]
) )
) )
for k in list(functions):
if "ceillog2" in k:
del functions[k]
functions_spec = '\n\n'.join(functions.values()) functions_spec = '\n\n'.join(functions.values())
for k in list(constants.keys()): for k in list(constants.keys()):
if k.startswith('DOMAIN_'): if k.startswith('DOMAIN_'):
constants[k] = f"DomainType(({constants[k]}).to_bytes(length=4, byteorder='little'))" constants[k] = f"DomainType(({constants[k]}).to_bytes(length=4, byteorder='little'))"
if k == "BLS12_381_Q":
constants[k] += " # noqa: E501"
constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, constants[x]), constants)) constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, constants[x]), constants))
ssz_objects_instantiation_spec = '\n\n'.join(ssz_objects.values()) ssz_objects_instantiation_spec = '\n\n'.join(ssz_objects.values())
ssz_objects_reinitialization_spec = ( ssz_objects_reinitialization_spec = (
@ -157,6 +174,7 @@ def objects_to_spec(functions: Dict[str, str],
spec = ( spec = (
imports imports
+ '\n\n' + new_type_definitions + '\n\n' + new_type_definitions
+ '\n' + SUNDRY_CONSTANTS_FUNCTIONS
+ '\n\n' + constants_spec + '\n\n' + constants_spec
+ '\n\n\n' + ssz_objects_instantiation_spec + '\n\n\n' + ssz_objects_instantiation_spec
+ '\n\n' + functions_spec + '\n\n' + functions_spec
@ -186,7 +204,7 @@ ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'Bytes', 'BytesN' 'bit', 'boolean', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'Bytes', 'BytesN'
'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector', 'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes' # to be removed after updating spec doc 'bytes', 'byte', 'BytesN' # to be removed after updating spec doc
] ]
@ -268,6 +286,7 @@ def build_phase1_spec(phase0_sourcefile: str,
fork_choice_sourcefile: str, fork_choice_sourcefile: str,
outfile: str=None) -> Optional[str]: outfile: str=None) -> Optional[str]:
phase0_spec = get_spec(phase0_sourcefile) phase0_spec = get_spec(phase0_sourcefile)
remove_for_phase1(phase0_spec[0])
phase1_custody = get_spec(phase1_custody_sourcefile) phase1_custody = get_spec(phase1_custody_sourcefile)
phase1_shard_data = get_spec(phase1_shard_sourcefile) phase1_shard_data = get_spec(phase1_shard_sourcefile)
fork_choice_spec = get_spec(fork_choice_sourcefile) fork_choice_spec = get_spec(fork_choice_sourcefile)

View File

@ -660,8 +660,8 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
bit_1_indices = indexed_attestation.custody_bit_1_indices bit_1_indices = indexed_attestation.custody_bit_1_indices
# Verify no index has custody bit equal to 1 [to be removed in phase 1] # Verify no index has custody bit equal to 1 [to be removed in phase 1]
if not len(bit_1_indices) == 0: if not len(bit_1_indices) == 0: # [to be removed in phase 1]
return False return False # [to be removed in phase 1]
# Verify max number of indices # Verify max number of indices
if not len(bit_0_indices) + len(bit_1_indices) <= MAX_VALIDATORS_PER_COMMITTEE: if not len(bit_0_indices) + len(bit_1_indices) <= MAX_VALIDATORS_PER_COMMITTEE:
return False return False
@ -1661,6 +1661,11 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
proposer_index=get_beacon_proposer_index(state), proposer_index=get_beacon_proposer_index(state),
) )
# Check bitlist lengths
committee_size = get_committee_count(state, attestation.data.target.epoch)
assert len(attestation.aggregation_bits) == committee_size
assert len(attestation.custody_bits) == committee_size
if data.target.epoch == get_current_epoch(state): if data.target.epoch == get_current_epoch(state):
assert data.source == state.current_justified_checkpoint assert data.source == state.current_justified_checkpoint
parent_crosslink = state.current_crosslinks[data.crosslink.shard] parent_crosslink = state.current_crosslinks[data.crosslink.shard]

View File

@ -12,6 +12,7 @@
- [Terminology](#terminology) - [Terminology](#terminology)
- [Constants](#constants) - [Constants](#constants)
- [Misc](#misc) - [Misc](#misc)
- [Custody game parameters](#custody-game-parameters)
- [Time parameters](#time-parameters) - [Time parameters](#time-parameters)
- [Max operations per block](#max-operations-per-block) - [Max operations per block](#max-operations-per-block)
- [Reward and penalty quotients](#reward-and-penalty-quotients) - [Reward and penalty quotients](#reward-and-penalty-quotients)
@ -33,12 +34,14 @@
- [`BeaconBlockBody`](#beaconblockbody) - [`BeaconBlockBody`](#beaconblockbody)
- [Helpers](#helpers) - [Helpers](#helpers)
- [`ceillog2`](#ceillog2) - [`ceillog2`](#ceillog2)
- [`is_valid_merkle_branch_with_mixin`](#is_valid_merkle_branch_with_mixin)
- [`get_crosslink_chunk_count`](#get_crosslink_chunk_count) - [`get_crosslink_chunk_count`](#get_crosslink_chunk_count)
- [`get_bit`](#get_bit) - [`legendre_bit`](#legendre_bit)
- [`custody_subchunkify`](#custody_subchunkify)
- [`get_custody_chunk_bit`](#get_custody_chunk_bit) - [`get_custody_chunk_bit`](#get_custody_chunk_bit)
- [`get_chunk_bits_root`](#get_chunk_bits_root) - [`get_chunk_bits_root`](#get_chunk_bits_root)
- [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period) - [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period)
- [`get_reveal_period`](#get_reveal_period) - [`get_custody_period_for_validator`](#get_custody_period_for_validator)
- [`replace_empty_or_append`](#replace_empty_or_append) - [`replace_empty_or_append`](#replace_empty_or_append)
- [Per-block processing](#per-block-processing) - [Per-block processing](#per-block-processing)
- [Operations](#operations) - [Operations](#operations)
@ -75,11 +78,20 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
### Misc ### Misc
| `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` |
| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) |
### Custody game parameters
| Name | Value | | Name | Value |
| - | - | | - | - |
| `BYTES_PER_SHARD_BLOCK` | `2**14` (= 16,384) | | `BYTES_PER_SHARD_BLOCK` | `2**14` (= 16,384) |
| `BYTES_PER_CUSTODY_CHUNK` | `2**9` (= 512) | | `BYTES_PER_CUSTODY_CHUNK` | `2**9` (= 512) |
| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) | | `BYTES_PER_CUSTODY_SUBCHUNK` | `48` |
| `CHUNKS_PER_EPOCH` | `2 * BYTES_PER_SHARD_BLOCK * SLOTS_PER_EPOCH // BYTES_PER_CUSTODY_CHUNK` |
| `MAX_CUSTODY_CHUNKS` | `MAX_EPOCHS_PER_CROSSLINK * CHUNKS_PER_EPOCH` |
| `CUSTODY_DATA_DEPTH` | `ceillog2(MAX_CUSTODY_CHUNKS) + 1` |
| `CUSTODY_CHUNK_BIT_DEPTH` | `ceillog2(MAX_EPOCHS_PER_CROSSLINK * CHUNKS_PER_EPOCH // 256) + 2` |
### Time parameters ### Time parameters
@ -144,7 +156,7 @@ class CustodyBitChallenge(Container):
attestation: Attestation attestation: Attestation
challenger_index: ValidatorIndex challenger_index: ValidatorIndex
responder_key: BLSSignature responder_key: BLSSignature
chunk_bits: Bytes[PLACEHOLDER] chunk_bits: Bitlist[MAX_CUSTODY_CHUNKS]
signature: BLSSignature signature: BLSSignature
``` ```
@ -181,10 +193,10 @@ class CustodyBitChallengeRecord(Container):
class CustodyResponse(Container): class CustodyResponse(Container):
challenge_index: uint64 challenge_index: uint64
chunk_index: uint64 chunk_index: uint64
chunk: Vector[Bytes[PLACEHOLDER], BYTES_PER_CUSTODY_CHUNK] chunk: BytesN[BYTES_PER_CUSTODY_CHUNK]
data_branch: List[Hash, PLACEHOLDER] data_branch: List[Hash, CUSTODY_DATA_DEPTH]
chunk_bits_branch: List[Hash, PLACEHOLDER] chunk_bits_branch: List[Hash, CUSTODY_CHUNK_BIT_DEPTH]
chunk_bits_leaf: Hash chunk_bits_leaf: Bitvector[256]
``` ```
### New beacon operations ### New beacon operations
@ -225,11 +237,11 @@ Add the following fields to the end of the specified container objects. Fields w
```python ```python
class Validator(Container): class Validator(Container):
# next_custody_reveal_period is initialised to the custody period # next_custody_secret_to_reveal is initialised to the custody period
# (of the particular validator) in which the validator is activated # (of the particular validator) in which the validator is activated
# = get_reveal_period(...) # = get_custody_period_for_validator(...)
next_custody_reveal_period: uint64 next_custody_secret_to_reveal: uint64
max_reveal_lateness: uint64 max_reveal_lateness: Epoch
``` ```
#### `BeaconState` #### `BeaconState`
@ -263,7 +275,26 @@ class BeaconBlockBody(Container):
```python ```python
def ceillog2(x: uint64) -> int: def ceillog2(x: uint64) -> int:
return x.bit_length() return (x - 1).bit_length()
```
### `is_valid_merkle_branch_with_mixin`
```python
def is_valid_merkle_branch_with_mixin(leaf: Hash,
branch: Sequence[Hash],
depth: uint64,
index: uint64,
root: Hash,
mixin: uint64) -> bool:
value = leaf
for i in range(depth):
if index // (2**i) % 2:
value = hash(branch[i] + value)
else:
value = hash(value + branch[i])
value = hash(value + mixin.to_bytes(32, "little"))
return value == root
``` ```
### `get_crosslink_chunk_count` ### `get_crosslink_chunk_count`
@ -271,37 +302,69 @@ def ceillog2(x: uint64) -> int:
```python ```python
def get_custody_chunk_count(crosslink: Crosslink) -> int: def get_custody_chunk_count(crosslink: Crosslink) -> int:
crosslink_length = min(MAX_EPOCHS_PER_CROSSLINK, crosslink.end_epoch - crosslink.start_epoch) crosslink_length = min(MAX_EPOCHS_PER_CROSSLINK, crosslink.end_epoch - crosslink.start_epoch)
chunks_per_epoch = 2 * BYTES_PER_SHARD_BLOCK * SLOTS_PER_EPOCH // BYTES_PER_CUSTODY_CHUNK return crosslink_length * CHUNKS_PER_EPOCH
return crosslink_length * chunks_per_epoch
``` ```
### `get_bit` ### `legendre_bit`
Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this.
```python ```python
def get_bit(serialization: bytes, i: uint64) -> int: def legendre_bit(a: int, q: int) -> int:
""" if a >= q:
Extract the bit in ``serialization`` at position ``i``. return legendre_bit(a % q, q)
""" if a == 0:
return (serialization[i // 8] >> (i % 8)) % 2 return 0
assert(q > a > 0 and q % 2 == 1)
t = 1
n = q
while a != 0:
while a % 2 == 0:
a //= 2
r = n % 8
if r == 3 or r == 5:
t = -t
a, n = n, a
if a % 4 == n % 4 == 3:
t = -t
a %= n
if n == 1:
return (t + 1) // 2
else:
return 0
```
### ```custody_subchunkify```
Given one proof of custody chunk, returns the proof of custody subchunks of the correct sizes.
```python
def custody_subchunkify(bytez: bytes) -> list:
bytez += b'\x00' * (-len(bytez) % BYTES_PER_CUSTODY_SUBCHUNK)
return [bytez[i:i + BYTES_PER_CUSTODY_SUBCHUNK]
for i in range(0, len(bytez), BYTES_PER_CUSTODY_SUBCHUNK)]
``` ```
### `get_custody_chunk_bit` ### `get_custody_chunk_bit`
```python ```python
def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool: def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool:
# TODO: Replace with something MPC-friendly, e.g. the Legendre symbol full_G2_element = bls_signature_to_G2(key)
return bool(get_bit(hash(key + chunk), 0)) s = full_G2_element[0].coeffs
bits = [legendre_bit((i + 1) * s[i % 2] + int.from_bytes(subchunk, "little"), BLS12_381_Q)
for i, subchunk in enumerate(custody_subchunkify(chunk))]
return bool(sum(bits) % 2)
``` ```
### `get_chunk_bits_root` ### `get_chunk_bits_root`
```python ```python
def get_chunk_bits_root(chunk_bits: bytes) -> Hash: def get_chunk_bits_root(chunk_bits: Bitlist[MAX_CUSTODY_CHUNKS]) -> bit:
aggregated_bits = bytearray([0] * 32) aggregated_bits = 0
for i in range(0, len(chunk_bits), 32): for i, b in enumerate(chunk_bits):
for j in range(32): aggregated_bits += 2**i * b
aggregated_bits[j] ^= chunk_bits[i + j] return legendre_bit(aggregated_bits, BLS12_381_Q)
return hash(aggregated_bits)
``` ```
### `get_randao_epoch_for_custody_period` ### `get_randao_epoch_for_custody_period`
@ -312,10 +375,10 @@ def get_randao_epoch_for_custody_period(period: uint64, validator_index: Validat
return Epoch(next_period_start + CUSTODY_PERIOD_TO_RANDAO_PADDING) return Epoch(next_period_start + CUSTODY_PERIOD_TO_RANDAO_PADDING)
``` ```
### `get_reveal_period` ### `get_custody_period_for_validator`
```python ```python
def get_reveal_period(state: BeaconState, validator_index: ValidatorIndex, epoch: Epoch=None) -> int: def get_custody_period_for_validator(state: BeaconState, validator_index: ValidatorIndex, epoch: Epoch=None) -> int:
''' '''
Return the reveal period for a given validator. Return the reveal period for a given validator.
''' '''
@ -354,9 +417,9 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
Note that this function mutates ``state``. Note that this function mutates ``state``.
""" """
revealer = state.validators[reveal.revealer_index] revealer = state.validators[reveal.revealer_index]
epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_reveal_period, reveal.revealed_index) epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index)
assert revealer.next_custody_reveal_period < get_reveal_period(state, reveal.revealed_index) assert revealer.next_custody_secret_to_reveal < get_custody_period_for_validator(state, reveal.revealer_index)
# Revealed validator is active or exited, but not withdrawn # Revealed validator is active or exited, but not withdrawn
assert is_slashable_validator(revealer, get_current_epoch(state)) assert is_slashable_validator(revealer, get_current_epoch(state))
@ -374,15 +437,19 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
) )
# Decrement max reveal lateness if response is timely # Decrement max reveal lateness if response is timely
if revealer.next_custody_reveal_period == get_reveal_period(state, reveal.revealer_index) - 2: if epoch_to_sign + EPOCHS_PER_CUSTODY_PERIOD >= get_current_epoch(state):
revealer.max_reveal_lateness -= MAX_REVEAL_LATENESS_DECREMENT if revealer.max_reveal_lateness >= MAX_REVEAL_LATENESS_DECREMENT:
revealer.max_reveal_lateness = max( revealer.max_reveal_lateness -= MAX_REVEAL_LATENESS_DECREMENT
revealer.max_reveal_lateness, else:
get_reveal_period(state, reveal.revealed_index) - revealer.next_custody_reveal_period revealer.max_reveal_lateness = 0
) else:
revealer.max_reveal_lateness = max(
revealer.max_reveal_lateness,
get_current_epoch(state) - epoch_to_sign - EPOCHS_PER_CUSTODY_PERIOD
)
# Process reveal # Process reveal
revealer.next_custody_reveal_period += 1 revealer.next_custody_secret_to_reveal += 1
# Reward Block Preposer # Reward Block Preposer
proposer_index = get_beacon_proposer_index(state) proposer_index = get_beacon_proposer_index(state)
@ -520,7 +587,7 @@ For each `challenge` in `block.body.custody_bit_challenges`, run the following f
```python ```python
def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> None: def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> None:
attestation = challenge.attestation attestation = challenge.attestation
epoch = compute_epoch_of_slot(attestation.data.slot) epoch = attestation.data.target.epoch
shard = attestation.data.crosslink.shard shard = attestation.data.crosslink.shard
# Verify challenge signature # Verify challenge signature
@ -533,7 +600,10 @@ def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) ->
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
# Verify attestation is eligible for challenging # Verify attestation is eligible for challenging
responder = state.validators[challenge.responder_index] responder = state.validators[challenge.responder_index]
assert epoch + responder.max_reveal_lateness <= get_reveal_period(state, challenge.responder_index) assert get_current_epoch(state) <= get_randao_epoch_for_custody_period(
get_custody_period_for_validator(state, challenge.responder_index, epoch),
challenge.responder_index
) + 2 * EPOCHS_PER_CUSTODY_PERIOD + responder.max_reveal_lateness
# Verify the responder participated in the attestation # Verify the responder participated in the attestation
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
@ -543,17 +613,18 @@ def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) ->
assert record.challenger_index != challenge.challenger_index assert record.challenger_index != challenge.challenger_index
# Verify the responder custody key # Verify the responder custody key
epoch_to_sign = get_randao_epoch_for_custody_period( epoch_to_sign = get_randao_epoch_for_custody_period(
get_reveal_period(state, challenge.responder_index, epoch), get_custody_period_for_validator(state, challenge.responder_index, epoch),
challenge.responder_index, challenge.responder_index,
) )
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign) domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
assert bls_verify(responder.pubkey, hash_tree_root(epoch_to_sign), challenge.responder_key, domain) assert bls_verify(responder.pubkey, hash_tree_root(epoch_to_sign), challenge.responder_key, domain)
# Verify the chunk count # Verify the chunk count
chunk_count = get_custody_chunk_count(attestation.data.crosslink) chunk_count = get_custody_chunk_count(attestation.data.crosslink)
# Verify the first bit of the hash of the chunk bits does not equal the custody bit assert chunk_count == len(challenge.chunk_bits)
# Verify custody bit is incorrect
committee = get_crosslink_committee(state, epoch, shard) committee = get_crosslink_committee(state, epoch, shard)
custody_bit = attestation.custody_bits[committee.index(challenge.responder_index)] custody_bit = attestation.custody_bits[committee.index(challenge.responder_index)]
assert custody_bit != get_bit(get_chunk_bits_root(challenge.chunk_bits), 0) assert custody_bit != get_chunk_bits_root(challenge.chunk_bits)
# 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,
@ -636,16 +707,17 @@ def process_bit_challenge_response(state: BeaconState,
root=challenge.data_root, root=challenge.data_root,
) )
# Verify the chunk bit leaf matches the challenge data # Verify the chunk bit leaf matches the challenge data
assert is_valid_merkle_branch( assert is_valid_merkle_branch_with_mixin(
leaf=response.chunk_bits_leaf, leaf=hash_tree_root(response.chunk_bits_leaf),
branch=response.chunk_bits_branch, branch=response.chunk_bits_branch,
depth=ceillog2(challenge.chunk_count) >> 8, depth=ceillog2(MAX_CUSTODY_CHUNKS // 256),
index=response.chunk_index // 256, index=response.chunk_index // 256,
root=challenge.chunk_bits_merkle_root root=challenge.chunk_bits_merkle_root,
mixin=challenge.chunk_count,
) )
# 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) assert (get_custody_chunk_bit(challenge.responder_key, response.chunk)
!= get_bit(challenge.chunk_bits_leaf, response.chunk_index % 256)) != response.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()
@ -665,8 +737,8 @@ Run `process_reveal_deadlines(state)` immediately after `process_registry_update
# end insert @process_reveal_deadlines # end insert @process_reveal_deadlines
def process_reveal_deadlines(state: BeaconState) -> None: def process_reveal_deadlines(state: BeaconState) -> None:
for index, validator in enumerate(state.validators): for index, validator in enumerate(state.validators):
deadline = validator.next_custody_reveal_period + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD) deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD)
if get_reveal_period(state, ValidatorIndex(index)) > deadline: if get_custody_period_for_validator(state, ValidatorIndex(index)) > deadline:
slash_validator(state, ValidatorIndex(index)) slash_validator(state, ValidatorIndex(index))
``` ```

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import with_all_phases, with_state, bls_switch from eth2spec.test.context import with_all_phases, with_state, bls_switch, with_phases
from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.attestations import get_valid_attestation
@ -103,7 +103,7 @@ def test_on_attestation_same_slot(spec, state):
run_on_attestation(spec, state, store, attestation, False) run_on_attestation(spec, state, store, attestation, False)
@with_all_phases @with_phases(['phase0'])
@with_state @with_state
@bls_switch @bls_switch
def test_on_attestation_invalid_attestation(spec, state): def test_on_attestation_invalid_attestation(spec, state):

View File

@ -1,6 +1,11 @@
from eth2spec.test.helpers.keys import privkeys from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures
from eth2spec.utils.hash_function import hash from eth2spec.utils.hash_function import hash
from eth2spec.utils.ssz.ssz_typing import Bitlist, BytesN, Bitvector
from eth2spec.utils.ssz.ssz_impl import chunkify, pack, hash_tree_root
from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof
BYTES_PER_CHUNK = 32
def get_valid_early_derived_secret_reveal(spec, state, epoch=None): def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
@ -13,7 +18,7 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
# Generate the secret that is being revealed # Generate the secret that is being revealed
reveal = bls_sign( reveal = bls_sign(
message_hash=spec.hash_tree_root(spec.Epoch(epoch)), message_hash=hash_tree_root(spec.Epoch(epoch)),
privkey=privkeys[revealed_index], privkey=privkeys[revealed_index],
domain=spec.get_domain( domain=spec.get_domain(
state=state, state=state,
@ -42,3 +47,128 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
masker_index=masker_index, masker_index=masker_index,
mask=mask, mask=mask,
) )
def get_valid_custody_key_reveal(spec, state, period=None):
current_epoch = spec.get_current_epoch(state)
revealer_index = spec.get_active_validator_indices(state, current_epoch)[0]
revealer = state.validators[revealer_index]
if period is None:
period = revealer.next_custody_secret_to_reveal
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, revealer_index)
# Generate the secret that is being revealed
reveal = bls_sign(
message_hash=hash_tree_root(spec.Epoch(epoch_to_sign)),
privkey=privkeys[revealer_index],
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_RANDAO,
message_epoch=epoch_to_sign,
),
)
return spec.CustodyKeyReveal(
revealer_index=revealer_index,
reveal=reveal,
)
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_bit_challenge(spec, state, attestation, invalid_custody_bit=False):
crosslink_committee = spec.get_crosslink_committee(
state,
attestation.data.target.epoch,
attestation.data.crosslink.shard,
)
responder_index = crosslink_committee[0]
challenger_index = crosslink_committee[-1]
epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch,
responder_index)
# Generate the responder key
responder_key = bls_sign(
message_hash=hash_tree_root(spec.Epoch(epoch)),
privkey=privkeys[responder_index],
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_RANDAO,
message_epoch=epoch,
),
)
chunk_count = spec.get_custody_chunk_count(attestation.data.crosslink)
chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, 0)
n = 0
while spec.get_chunk_bits_root(chunk_bits) == attestation.custody_bits[0] ^ invalid_custody_bit:
chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, n)
n += 1
return spec.CustodyBitChallenge(
responder_index=responder_index,
attestation=attestation,
challenger_index=challenger_index,
responder_key=responder_key,
chunk_bits=chunk_bits,
)
def custody_chunkify(spec, x):
chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)]
chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0")
return chunks
def get_valid_custody_response(spec, state, bit_challenge, custody_data, challenge_index, invalid_chunk_bit=False):
chunks = custody_chunkify(spec, custody_data)
chunk_index = len(chunks) - 1
chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
while chunk_bit == bit_challenge.chunk_bits[chunk_index] ^ invalid_chunk_bit:
chunk_index -= 1
chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
chunks_hash_tree_roots = [hash_tree_root(BytesN[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks]
chunks_hash_tree_roots += [
hash_tree_root(BytesN[spec.BYTES_PER_CUSTODY_CHUNK](b"\0" * spec.BYTES_PER_CUSTODY_CHUNK))
for i in range(2 ** spec.ceillog2(len(chunks)) - len(chunks))]
data_tree = get_merkle_tree(chunks_hash_tree_roots)
data_branch = get_merkle_proof(data_tree, chunk_index)
bitlist_chunk_index = chunk_index // BYTES_PER_CHUNK
bitlist_chunks = chunkify(pack(bit_challenge.chunk_bits))
bitlist_tree = get_merkle_tree(bitlist_chunks, pad_to=spec.MAX_CUSTODY_CHUNKS // 256)
bitlist_chunk_branch = get_merkle_proof(bitlist_tree, chunk_index // 256) + \
[len(bit_challenge.chunk_bits).to_bytes(32, "little")]
bitlist_chunk_index = chunk_index // 256
chunk_bits_leaf = Bitvector[256](bit_challenge.chunk_bits[bitlist_chunk_index * 256:
(bitlist_chunk_index + 1) * 256])
return spec.CustodyResponse(
challenge_index=challenge_index,
chunk_index=chunk_index,
chunk=BytesN[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
data_branch=data_branch,
chunk_bits_branch=bitlist_chunk_branch,
chunk_bits_leaf=chunk_bits_leaf,
)
def get_custody_test_vector(bytelength):
ints = bytelength // 4
return b"".join(i.to_bytes(4, "little") for i in range(ints))
def get_custody_merkle_root(data):
return get_merkle_tree(chunkify(data))[-1][0]

View File

@ -47,7 +47,7 @@ def build_deposit(spec,
deposit_data_list.append(deposit_data) deposit_data_list.append(deposit_data)
root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list)) root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list))
tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list])) tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list]))
proof = list(get_merkle_proof(tree, item_index=index)) + [(index + 1).to_bytes(32, 'little')] proof = list(get_merkle_proof(tree, item_index=index, tree_len=32)) + [(index + 1).to_bytes(32, 'little')]
leaf = deposit_data.hash_tree_root() leaf = deposit_data.hash_tree_root()
assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root) assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root)
deposit = spec.Deposit(proof=proof, data=deposit_data) deposit = spec.Deposit(proof=proof, data=deposit_data)

View File

@ -363,7 +363,7 @@ def test_inconsistent_bits(spec, state):
attestation = get_valid_attestation(spec, state) attestation = get_valid_attestation(spec, state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
custody_bits = attestation.aggregation_bits[:] custody_bits = attestation.custody_bits[:]
custody_bits.append(False) custody_bits.append(False)
attestation.custody_bits = custody_bits attestation.custody_bits = custody_bits

View File

@ -0,0 +1,347 @@
from eth2spec.test.helpers.custody import (
get_valid_bit_challenge,
get_valid_custody_response,
get_custody_test_vector,
get_custody_merkle_root
)
from eth2spec.test.helpers.attestations import (
get_valid_attestation,
)
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.helpers.state import next_epoch, get_balance
from eth2spec.test.helpers.block import apply_empty_block
from eth2spec.test.context import (
with_all_phases_except,
spec_state_test,
expect_assertion_error,
)
from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing
def run_bit_challenge_processing(spec, state, custody_bit_challenge, valid=True):
"""
Run ``process_bit_challenge``, yielding:
- pre-state ('pre')
- CustodyBitChallenge ('custody_bit_challenge')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
yield 'pre', state
yield 'custody_bit_challenge', custody_bit_challenge
if not valid:
expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge))
yield 'post', None
return
spec.process_bit_challenge(state, custody_bit_challenge)
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].chunk_bits_merkle_root == \
hash_tree_root(custody_bit_challenge.chunk_bits)
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].challenger_index == \
custody_bit_challenge.challenger_index
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].responder_index == \
custody_bit_challenge.responder_index
yield 'post', state
def run_custody_response_processing(spec, state, custody_response, valid=True):
"""
Run ``process_bit_challenge_response``, yielding:
- pre-state ('pre')
- CustodyResponse ('custody_response')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
yield 'pre', state
yield 'custody_response', custody_response
if not valid:
expect_assertion_error(lambda: spec.process_custody_response(state, custody_response))
yield 'post', None
return
# TODO: Add capability to also process chunk challenges, not only bit challenges
challenge = state.custody_bit_challenge_records[custody_response.challenge_index]
pre_slashed_balance = get_balance(state, challenge.challenger_index)
spec.process_custody_response(state, custody_response)
slashed_validator = state.validators[challenge.challenger_index]
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
assert get_balance(state, challenge.challenger_index) < pre_slashed_balance
yield 'post', state
@with_all_phases_except(['phase0'])
@spec_state_test
def test_challenge_appended(spec, state):
state.slot = spec.SLOTS_PER_EPOCH
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_multiple_epochs_custody(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 3
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)
challenge = get_valid_bit_challenge(spec, state, attestation)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_many_epochs_custody(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 100
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)
challenge = get_valid_bit_challenge(spec, state, attestation)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_off_chain_attestation(spec, state):
state.slot = spec.SLOTS_PER_EPOCH
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_invalid_custody_bit_challenge(spec, state):
state.slot = spec.SLOTS_PER_EPOCH
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True)
yield from run_bit_challenge_processing(spec, state, challenge, valid=False)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_max_reveal_lateness_1(spec, state):
next_epoch(spec, state)
apply_empty_block(spec, state)
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
next_epoch(spec, state)
apply_empty_block(spec, state)
_, _, _ = run_attestation_processing(spec, state, attestation)
challenge = get_valid_bit_challenge(spec, state, attestation)
responder_index = challenge.responder_index
state.validators[responder_index].max_reveal_lateness = 3
for i in range(spec.get_randao_epoch_for_custody_period(
spec.get_custody_period_for_validator(state, responder_index),
responder_index
) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 2):
next_epoch(spec, state)
apply_empty_block(spec, state)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_max_reveal_lateness_2(spec, state):
next_epoch(spec, state)
apply_empty_block(spec, state)
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
next_epoch(spec, state)
apply_empty_block(spec, state)
_, _, _ = run_attestation_processing(spec, state, attestation)
challenge = get_valid_bit_challenge(spec, state, attestation)
responder_index = challenge.responder_index
state.validators[responder_index].max_reveal_lateness = 3
for i in range(spec.get_randao_epoch_for_custody_period(
spec.get_custody_period_for_validator(state, responder_index),
responder_index
) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 1):
next_epoch(spec, state)
apply_empty_block(spec, state)
yield from run_bit_challenge_processing(spec, state, challenge, False)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_custody_response(spec, state):
state.slot = spec.SLOTS_PER_EPOCH
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
bit_challenge_index = state.custody_challenge_index - 1
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
yield from run_custody_response_processing(spec, state, custody_response)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_custody_response_multiple_epochs(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 3
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
bit_challenge_index = state.custody_challenge_index - 1
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
yield from run_custody_response_processing(spec, state, custody_response)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_custody_response_many_epochs(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 100
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
bit_challenge_index = state.custody_challenge_index - 1
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
yield from run_custody_response_processing(spec, state, custody_response)

View File

@ -0,0 +1,118 @@
from eth2spec.test.helpers.custody import get_valid_custody_key_reveal
from eth2spec.test.context import (
with_all_phases_except,
spec_state_test,
expect_assertion_error,
always_bls,
)
def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=True):
"""
Run ``process_custody_key_reveal``, yielding:
- pre-state ('pre')
- custody_key_reveal ('custody_key_reveal')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
yield 'pre', state
yield 'custody_key_reveal', custody_key_reveal
if not valid:
expect_assertion_error(lambda: spec.process_custody_key_reveal(state, custody_key_reveal))
yield 'post', None
return
revealer_index = custody_key_reveal.revealer_index
pre_next_custody_secret_to_reveal = \
state.validators[revealer_index].next_custody_secret_to_reveal
pre_reveal_lateness = state.validators[revealer_index].max_reveal_lateness
spec.process_custody_key_reveal(state, custody_key_reveal)
post_next_custody_secret_to_reveal = \
state.validators[revealer_index].next_custody_secret_to_reveal
post_reveal_lateness = state.validators[revealer_index].max_reveal_lateness
assert post_next_custody_secret_to_reveal == pre_next_custody_secret_to_reveal + 1
if spec.get_current_epoch(state) > spec.get_randao_epoch_for_custody_period(
pre_next_custody_secret_to_reveal,
revealer_index
) + spec.EPOCHS_PER_CUSTODY_PERIOD:
assert post_reveal_lateness > 0
if pre_reveal_lateness == 0:
assert post_reveal_lateness == spec.get_current_epoch(state) - spec.get_randao_epoch_for_custody_period(
pre_next_custody_secret_to_reveal,
revealer_index
) - spec.EPOCHS_PER_CUSTODY_PERIOD
else:
if pre_reveal_lateness > 0:
assert post_reveal_lateness < pre_reveal_lateness
yield 'post', state
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_success(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal)
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_reveal_too_early(spec, state):
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_wrong_period(spec, state):
custody_key_reveal = get_valid_custody_key_reveal(spec, state, period=5)
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_late_reveal(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal)
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_double_reveal(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 2
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_max_decrement(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
custody_key_reveal2 = get_valid_custody_key_reveal(spec, state)
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal2)

View File

@ -98,25 +98,21 @@ def test_reveal_with_custody_padding_minus_one(spec, state):
@never_bls @never_bls
@spec_state_test @spec_state_test
def test_double_reveal(spec, state): def test_double_reveal(spec, state):
epoch = spec.get_current_epoch(state) + spec.RANDAO_PENALTY_EPOCHS
randao_key_reveal1 = get_valid_early_derived_secret_reveal( randao_key_reveal1 = get_valid_early_derived_secret_reveal(
spec, spec,
state, state,
spec.get_current_epoch(state) + spec.RANDAO_PENALTY_EPOCHS + 1, epoch,
) )
res = dict(run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal1)) _, _, _ = dict(run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal1))
pre_state = res['pre']
yield 'pre', pre_state
intermediate_state = res['post']
randao_key_reveal2 = get_valid_early_derived_secret_reveal( randao_key_reveal2 = get_valid_early_derived_secret_reveal(
spec, spec,
intermediate_state, state,
spec.get_current_epoch(pre_state) + spec.RANDAO_PENALTY_EPOCHS + 1, epoch,
) )
res = dict(run_early_derived_secret_reveal_processing(spec, intermediate_state, randao_key_reveal2, False))
post_state = res['post'] yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal2, False)
yield 'randao_key_reveal', [randao_key_reveal1, randao_key_reveal2]
yield 'post', post_state
@with_all_phases_except(['phase0']) @with_all_phases_except(['phase0'])

View File

@ -5,6 +5,7 @@ bls_active = True
STUB_SIGNATURE = b'\x11' * 96 STUB_SIGNATURE = b'\x11' * 96
STUB_PUBKEY = b'\x22' * 48 STUB_PUBKEY = b'\x22' * 48
STUB_COORDINATES = bls.api.signature_to_G2(bls.sign(b"", 0, b"\0" * 8))
def only_with_bls(alt_return=None): def only_with_bls(alt_return=None):
@ -47,3 +48,8 @@ def bls_aggregate_signatures(signatures):
def bls_sign(message_hash, privkey, domain): def bls_sign(message_hash, privkey, domain):
return bls.sign(message_hash=message_hash, privkey=privkey, return bls.sign(message_hash=message_hash, privkey=privkey,
domain=domain) domain=domain)
@only_with_bls(alt_return=STUB_COORDINATES)
def bls_signature_to_G2(signature):
return bls.api.signature_to_G2(signature)

View File

@ -20,6 +20,13 @@ def calc_merkle_tree_from_leaves(values, layer_count=32):
return tree return tree
def get_merkle_tree(values, pad_to=None):
layer_count = (len(values) - 1).bit_length() if pad_to is None else (pad_to - 1).bit_length()
if len(values) == 0:
return zerohashes[layer_count]
return calc_merkle_tree_from_leaves(values, layer_count)
def get_merkle_root(values, pad_to=1): def get_merkle_root(values, pad_to=1):
if pad_to == 0: if pad_to == 0:
return zerohashes[0] return zerohashes[0]
@ -29,9 +36,9 @@ def get_merkle_root(values, pad_to=1):
return calc_merkle_tree_from_leaves(values, layer_count)[-1][0] return calc_merkle_tree_from_leaves(values, layer_count)[-1][0]
def get_merkle_proof(tree, item_index): def get_merkle_proof(tree, item_index, tree_len=None):
proof = [] proof = []
for i in range(32): for i in range(tree_len if tree_len is not None else len(tree)):
subindex = (item_index // 2**i) ^ 1 subindex = (item_index // 2**i) ^ 1
proof.append(tree[i][subindex] if subindex < len(tree[i]) else zerohashes[i]) proof.append(tree[i][subindex] if subindex < len(tree[i]) else zerohashes[i])
return proof return proof