From 1a2c2554ec3de27c6a84ca81113da02f4e2ee90d Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sun, 17 Feb 2019 00:56:31 -0600 Subject: [PATCH 01/11] First draft of the PoC interactive game ~~Likely~~ certainly has many bugs and is missing important pieces; intended more as a sketch, to be cleaned up once #587 is merged. --- specs/core/1_shard-data-chains.md | 244 ++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index cbb84aa0e8..b7ab9062c2 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -222,3 +222,247 @@ The `shard_chain_commitment` is only valid if it equals `compute_commitment(head ### Shard block fork choice rule The fork choice rule for any shard is LMD GHOST using the shard chain attestations of the persistent committee and the beacon chain attestations of the crosslink committee currently assigned to that shard, but instead of being rooted in the genesis it is rooted in the block referenced in the most recent accepted crosslink (ie. `state.crosslinks[shard].shard_block_root`). Only blocks whose `beacon_chain_ref` is the block in the main beacon chain at the specified `slot` should be considered (if the beacon chain skips a slot, then the block at that slot is considered to be the block in the beacon chain at the highest slot lower than a slot). + +# Proof of custody interactive game + +### Constants + +MAX_POC_RESPONSE_DEPTH: 5 +DOMAIN_CUSTODY_INTERACTIVE: 132 +VALIDATOR_NULL: 2**64 - 1 +MAX_INTERACTIVE_CHALLENGE_INITIATIONS: 4 +MAX_INTERACTIVE_CHALLENGE_RESPONSES: 16 +MAX_INTERACTIVE_CHALLENGE_CONTINUTATIONS: 16 + +### Helpers + +Define the helper `get_merkle_depth`: + +```python +def get_merkle_depth(attestation: Attestation) -> int: + start_epoch = initiation.attestation.data.latest_crosslink.epoch + end_epoch = slot_to_epoch(initiation.attestation.data.slot) + chunks_per_slot = SHARD_BLOCK_SIZE // 32 + chunks = (end_epoch - start_epoch) * EPOCH_LENGTH * chunks_per_slot + return log2(next_power_of_two(chunks)) +``` + +And `epoch_to_period`: + +```python +def epoch_to_period(epoch: int) -> int: + return epoch // CUSTODY_PERIOD_LENGTH +``` + +And `slot_to_period`: + +```python +def slot_to_period(slot: int) -> int: + return epoch_to_period(slot_to_period(slot)) +``` + +Add the following data structure to the `Validator` record: + +```python + interactive_custody_challenge_data: InteractiveCustodyChallengeData, + now_challenging: 'uint64', +``` + +### Data structures and verification + +Where `InteractiveCustodyChallengeData` is defined as follows: + +```python +{ + # Who initiated the challenge + 'challenger': 'uint64', + # Initial data root + 'data_root': 'bytes32', + # Initial custody bit + 'custody_bit': 'bool', + # Responder subkey + 'responder_subkey': 'bytes96', + # The hash in the PoC tree in the position that we are currently at + 'current_custody_tree_node': 'bytes32', + # The position in the tree, in terms of depth and position offset + 'depth': 'uint64', + 'offset': 'uint64', + # Max depth of the branch + 'max_depth': 'uint64', + # Deadline to respond (as an epoch) + 'deadline': 'uint64', +} +``` + +The initial value is `EMPTY_CHALLENGE_DATA = InteractiveCustodyChallengeData(challenger=VALIDATOR_NULL, data_root=ZERO_HASH, custody_bit=False, responder_subkey=EMPTY_SIGNATURE, current_custody_tree_node=ZERO_HASH, depth=0, offset=0, max_depth=0, deadline=0)` + +We define an `InteractiveCustodyChallengeInitiation` as follows: + +```python +{ + 'attestation': SlashableAttestation, + 'responder_index': 'uint64', + 'challenger_index': 'uint64', + 'responder_subkey': 'bytes96', + 'signature': 'bytes96' +} +``` + +To validate the `initiation`, verify: + +* `bls_verify(message_hash=signed_root(initiation, "signature"), pubkey=state.validator_registry[challenger_index].pubkey, signature=initiation.signature, domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE))` returns `True`. +* `responder_index` is in `attestation.validator_indices`. +* `state.validator_registry[responder_index].interactive_custody_challenge_data.challenger_index == VALIDATOR_NULL`. +* `verify_custody_subkey_reveal(pubkey=state.validator_registry[responder_index].pubkey, subkey=responder_subkey, mask=ZERO_HASH, mask_pubkey=b'', period=slot_to_period(attestation.data.slot))` returns `True`. +* `state.validator_registry[challenger_index].now_challenging == VALIDATOR_NULL` +* `state.validator_registry[challenger_index].penalized_epoch == FAR_FUTURE_EPOCH` + +Set `state.validator_registry[responder_index].interactive_custody_challenge_data` to: + +```python +InteractiveCustodyChallengeData( + challenger=initiation.challenger_index, + data_root=attestation.custody_commitment, + custody_bit=get_bitfield_bit(attestation.custody_bitfield, attestation.validator_indices.index(responder_index)), + responder_subkey=responder_subkey, + current_custody_tree_node=ZERO_HASH, + depth=0, + offset=0, + max_depth=get_merkle_depth(initiation.attestation), + deadline=get_current_epoch(state) + CHALLENGE_RESPONSE_DEADLINE +) +``` + +Set `state.validator_registry[responder_index].withdrawable_epoch = FAR_FUTURE_EPOCH`, and set `state.validator_registry[challenger_index].now_challenging = responder_index` + +We define an `InteractiveCustodyChallengeResponse` as follows: + +```python +{ + 'responder_index': 'uint64', + 'hashes': ['bytes32'], + 'signature': 'bytes96', +} +``` + +A response provides 32 hashes that are under current known proof of custody tree node. Note that at the beginning the tree node is just one bit of the custody root, so we ask the responder to sign to commit to the top 5 levels of the tree and therefore the root hash; at all other stages in the game responses are self-verifying. + +Here's the function for verifying and processing a response: + +```python +def process_response(response: InteractiveCustodyChallengeResponse, + state: State): + responder = state.validator_registry[response.responder_index] + challenge_data = responder.interactive_custody_challenge_data + # Check that the right number of hashes was provided + expected_depth = min(challenge_data.max_depth - challenge_data.depth, MAX_POC_RESPONSE_DEPTH) + assert 2**expected_depth == len(response.hashes) + # Must make some progress! + assert expected_depth > 0 + # Check the hashes match the previously provided root + root = merkle_root(hashes) + if challenge_data.depth == 0: + assert get_bitfield_bit(root, 0) == challenge_data.custody_bit + else: + assert root == challenge_data.current_custody_tree_node + # Verify signature for depth 0 + if challenge_data.depth == 0: + assert bls_verify(message_hash=signed_root(response, 'signature'), + pubkey=responder.pubkey, + signature=response.signature, + domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE)) + # Update challenge data + challenge_data.deadline=FAR_FUTURE_EPOCH + validator.withdrawable_epoch = get_current_epoch(state) + MAX_POC_RESPONSE_DEPTH +``` + +Once a response provides 32 hashes, the challenger has the right to choose any one of them that they feel is constructed incorrectly to continue the game. Note that eventually, the game will get to the point where the `new_custody_tree_node` is a leaf node. We define an `InteractiveCustodyChallengeContinuation` object as follows: + +```python +{ + 'challenger_index: 'uint64', + 'responder_index': 'uint64', + 'sub_index': 'uint64', + 'new_custody_tree_node': 'bytes32', + 'proof': ['bytes32'], + 'signature': 'bytes96' +} +``` + +Here's the function for verifying and processing a continuation challenge: + +```python +def process_continuation(continuation: InteractiveCustodyChallengeContinuation, + state: State): + responder = state.validator_registry[continuation.responder_index] + challenge_data = responder.interactive_custody_challenge_data + expected_depth = min(challenge_data.max_depth - challenge_data.depth, MAX_POC_RESPONSE_DEPTH) + # Verify we're not too late + assert get_current_epoch(state) < responder.withdrawable_epoch + # Verify the Merkle branch (the previous custody response provided the next level of hashes so the + # challenger has the info to make any Merkle branch) + assert verify_merkle_branch( + leaf=new_custody_tree_node, + branch=continuation.proof, + depth=expected_depth, + index=sub_index, + root=challenge_data.current_custody_tree_node + ) + # Verify signature + assert bls_verify(message_hash=signed_root(continutation, 'signature'), + pubkey=responder.pubkey, + signature=continutation.signature, + domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE)) + # Update the challenge data + challenge_data.current_custody_tree_node = continuation.new_custody_tree_node + challenge_data.depth += expected_depth + challenge_data.deadline = get_current_epoch(state) + MAX_POC_RESPONSE_DEPTH + validator.withdrawable_epoch = FAR_FUTURE_EPOCH + challenge_data.offset = challenger_data.offset * 2**expected_depth + sub_index +``` + +Once the `new_custody_tree_node` reaches the leaves of the tree, the responder can no longer provide a valid `InteractiveCustodyChallengeResponse`; instead, the responder or the challenger must provide a branch response that provides a branch of the original data tree, at which point the custody leaf equation can be checked and either side of the custody game can "conclusively win". + +```python +def process_branch_response(response: BranchResponse, + state: State): + responder = state.validator_registry[response.responder_index] + challenge_data = responder.interactive_custody_challenge_data + assert challenge_data.depth == challenge_data.max_depth + # Verify we're not too late + assert get_current_epoch(state) < responder.withdrawable_epoch + # Verify the Merkle branch *of the data tree* + assert verify_merkle_branch( + leaf=response.data, + branch=response.branch, + depth=challenge_data.max_depth, + index=challenge_data.offset, + root=challenge_data.data_root + ) + # Responder wins + if hash(challenge_data.responder_subkey + response.data) == challenge_data.current_custody_tree_node: + penalize_validator(state, challenge_data.challenger_index, response.responder_index) + responder.interactive_custody_challenge_data = EMPTY_CHALLENGE_DATA + # Challenger wins + else: + penalize_validator(state, response.responder_index, challenge_data.challenger_index) + state.validator_registry[challenge_data.challenger_index].now_challenging = VALIDATOR_NULL +``` + +Amend `process_challenge_absences` as follows: + +``` +def process_challenge_absences(state: BeaconState) -> None: + """ + Iterate through the validator registry + and penalize validators with balance that did not answer challenges. + """ + for index, validator in enumerate(state.validator_registry): + if len(validator.open_branch_challenges) > 0 and get_current_epoch(state) > validator.open_branch_challenges[0].inclusion_epoch + CHALLENGE_RESPONSE_DEADLINE: + penalize_validator(state, index, validator.open_branch_challenges[0].challenger_index) + if validator.challenge_data.challenger != VALIDATOR_NULL and get_current_epoch(state) > validator.challenge.deadline: + penalize_validator(state, index, validator.challenge_data.challenger_index) + if get_current_epoch(state) >= state.validator_registry[validator.now_challenging].withdrawal_epoch: + penalize_validator(state, index, validator.now_challenging) + penalize_validator(state, index, validator.challenge_data.challenger_index) +``` From 19faba3c76fa62d38d45b390542e94e4041ecf78 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sun, 17 Feb 2019 02:32:17 -0600 Subject: [PATCH 02/11] Fixed up constants table --- specs/core/1_shard-data-chains.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index b7ab9062c2..8a52156611 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -227,12 +227,14 @@ The fork choice rule for any shard is LMD GHOST using the shard chain attestatio ### Constants -MAX_POC_RESPONSE_DEPTH: 5 -DOMAIN_CUSTODY_INTERACTIVE: 132 -VALIDATOR_NULL: 2**64 - 1 -MAX_INTERACTIVE_CHALLENGE_INITIATIONS: 4 -MAX_INTERACTIVE_CHALLENGE_RESPONSES: 16 -MAX_INTERACTIVE_CHALLENGE_CONTINUTATIONS: 16 +| Constant | Value | Unit | Approximation | +|--------------------------------------------|------------------|---------|---------------| +| `MAX_POC_RESPONSE_DEPTH` | 5 | layers | | +| `DOMAIN_CUSTODY_INTERACTIVE` | 132 | | | +| `VALIDATOR_NULL` | 2**64 - 1 | | | +| `MAX_INTERACTIVE_CHALLENGE_INITIATIONS` | 2 | | | +| `MAX_INTERACTIVE_CHALLENGE_RESPONSES` | 16 | | | +| `MAX_INTERACTIVE_CHALLENGE_CONTINUTATIONS` | 16 | | | ### Helpers From 45c21a2618a52dfb11f502c19ae018a613f1e04a Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sun, 17 Feb 2019 03:03:08 -0600 Subject: [PATCH 03/11] Moved helpers to earlier PR --- specs/core/1_shard-data-chains.md | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 8a52156611..6a209952e1 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -236,32 +236,7 @@ The fork choice rule for any shard is LMD GHOST using the shard chain attestatio | `MAX_INTERACTIVE_CHALLENGE_RESPONSES` | 16 | | | | `MAX_INTERACTIVE_CHALLENGE_CONTINUTATIONS` | 16 | | | -### Helpers - -Define the helper `get_merkle_depth`: - -```python -def get_merkle_depth(attestation: Attestation) -> int: - start_epoch = initiation.attestation.data.latest_crosslink.epoch - end_epoch = slot_to_epoch(initiation.attestation.data.slot) - chunks_per_slot = SHARD_BLOCK_SIZE // 32 - chunks = (end_epoch - start_epoch) * EPOCH_LENGTH * chunks_per_slot - return log2(next_power_of_two(chunks)) -``` - -And `epoch_to_period`: - -```python -def epoch_to_period(epoch: int) -> int: - return epoch // CUSTODY_PERIOD_LENGTH -``` - -And `slot_to_period`: - -```python -def slot_to_period(slot: int) -> int: - return epoch_to_period(slot_to_period(slot)) -``` +### Data structures and verification Add the following data structure to the `Validator` record: @@ -270,8 +245,6 @@ Add the following data structure to the `Validator` record: now_challenging: 'uint64', ``` -### Data structures and verification - Where `InteractiveCustodyChallengeData` is defined as follows: ```python @@ -315,7 +288,7 @@ To validate the `initiation`, verify: * `bls_verify(message_hash=signed_root(initiation, "signature"), pubkey=state.validator_registry[challenger_index].pubkey, signature=initiation.signature, domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE))` returns `True`. * `responder_index` is in `attestation.validator_indices`. * `state.validator_registry[responder_index].interactive_custody_challenge_data.challenger_index == VALIDATOR_NULL`. -* `verify_custody_subkey_reveal(pubkey=state.validator_registry[responder_index].pubkey, subkey=responder_subkey, mask=ZERO_HASH, mask_pubkey=b'', period=slot_to_period(attestation.data.slot))` returns `True`. +* `verify_custody_subkey_reveal(pubkey=state.validator_registry[responder_index].pubkey, subkey=responder_subkey, mask=ZERO_HASH, mask_pubkey=b'', period=slot_to_custody_period(attestation.data.slot))` returns `True`. * `state.validator_registry[challenger_index].now_challenging == VALIDATOR_NULL` * `state.validator_registry[challenger_index].penalized_epoch == FAR_FUTURE_EPOCH` From e1761b5d0cc7d34605f7a01c1497764c6fd97428 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 18 Feb 2019 04:33:54 -0600 Subject: [PATCH 04/11] Formatting changes --- specs/core/1_shard-data-chains.md | 69 ++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 6a209952e1..c1a4a03ad7 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -283,33 +283,54 @@ We define an `InteractiveCustodyChallengeInitiation` as follows: } ``` -To validate the `initiation`, verify: - -* `bls_verify(message_hash=signed_root(initiation, "signature"), pubkey=state.validator_registry[challenger_index].pubkey, signature=initiation.signature, domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE))` returns `True`. -* `responder_index` is in `attestation.validator_indices`. -* `state.validator_registry[responder_index].interactive_custody_challenge_data.challenger_index == VALIDATOR_NULL`. -* `verify_custody_subkey_reveal(pubkey=state.validator_registry[responder_index].pubkey, subkey=responder_subkey, mask=ZERO_HASH, mask_pubkey=b'', period=slot_to_custody_period(attestation.data.slot))` returns `True`. -* `state.validator_registry[challenger_index].now_challenging == VALIDATOR_NULL` -* `state.validator_registry[challenger_index].penalized_epoch == FAR_FUTURE_EPOCH` - -Set `state.validator_registry[responder_index].interactive_custody_challenge_data` to: +Here's the function for validating and processing an initiation: ```python -InteractiveCustodyChallengeData( - challenger=initiation.challenger_index, - data_root=attestation.custody_commitment, - custody_bit=get_bitfield_bit(attestation.custody_bitfield, attestation.validator_indices.index(responder_index)), - responder_subkey=responder_subkey, - current_custody_tree_node=ZERO_HASH, - depth=0, - offset=0, - max_depth=get_merkle_depth(initiation.attestation), - deadline=get_current_epoch(state) + CHALLENGE_RESPONSE_DEADLINE -) +def process_initiation(initiation: InteractiveCustodyChallengeInitiation, + state: BeaconState): + challenger = state.validator_registry[challenger_index] + responder = state.validator_registry[responder_index] + # Verify the signature + assert bls_verify( + message_hash=signed_root(initiation, 'signature'), + pubkey=state.validator_registry[challenger_index].pubkey, + signature=initiation.signature, + domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE) + ) + # Check that the responder actually participated in the attestation + assert responder_index in attestation.validator_indices + # Can only be challenged by one challenger at a time + assert responder.interactive_custody_challenge_data.challenger_index == VALIDATOR_NULL + # Can only challenge one responder at a time + assert challenger.now_challenging == VALIDATOR_NULL + # Can't challenge if you've been penalized + assert challenger.penalized_epoch == FAR_FUTURE_EPOCH + # Make sure the revealed subkey is valid + assert verify_custody_subkey_reveal( + pubkey=state.validator_registry[responder_index].pubkey, + subkey=responder_subkey, + mask=ZERO_HASH, + mask_pubkey=b'', + period=slot_to_custody_period(attestation.data.slot) + ) + # Set the challenge object + responder.interactive_custody_challenge_data = InteractiveCustodyChallengeData( + challenger=initiation.challenger_index, + data_root=attestation.custody_commitment, + custody_bit=get_bitfield_bit(attestation.custody_bitfield, attestation.validator_indices.index(responder_index)), + responder_subkey=responder_subkey, + current_custody_tree_node=ZERO_HASH, + depth=0, + offset=0, + max_depth=get_merkle_depth(initiation.attestation), + deadline=get_current_epoch(state) + CHALLENGE_RESPONSE_DEADLINE + ) + # Responder can't withdraw yet! + state.validator_registry[responder_index].withdrawable_epoch = FAR_FUTURE_EPOCH + # Challenger can't challenge anyone else + challenger.now_challenging = responder_index ``` -Set `state.validator_registry[responder_index].withdrawable_epoch = FAR_FUTURE_EPOCH`, and set `state.validator_registry[challenger_index].now_challenging = responder_index` - We define an `InteractiveCustodyChallengeResponse` as follows: ```python @@ -426,7 +447,7 @@ def process_branch_response(response: BranchResponse, Amend `process_challenge_absences` as follows: -``` +```python def process_challenge_absences(state: BeaconState) -> None: """ Iterate through the validator registry From 453abae4066490c5ad0e253b60d1e1d5a6c39b13 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 18 Feb 2019 18:28:17 -0600 Subject: [PATCH 05/11] Update specs/core/1_shard-data-chains.md Co-Authored-By: vbuterin --- specs/core/1_shard-data-chains.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index c1a4a03ad7..aff7c259dc 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -356,7 +356,7 @@ def process_response(response: InteractiveCustodyChallengeResponse, # Must make some progress! assert expected_depth > 0 # Check the hashes match the previously provided root - root = merkle_root(hashes) + root = merkle_root(response.hashes) if challenge_data.depth == 0: assert get_bitfield_bit(root, 0) == challenge_data.custody_bit else: From d56429f909db7fbfbcd3252925bf8a53edbdd99e Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 18 Feb 2019 20:25:18 -0600 Subject: [PATCH 06/11] Small fixes --- specs/core/1_shard-data-chains.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index aff7c259dc..ba0dcb3f86 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -357,19 +357,22 @@ def process_response(response: InteractiveCustodyChallengeResponse, assert expected_depth > 0 # Check the hashes match the previously provided root root = merkle_root(response.hashes) + # If this is the first response check the bit and the signature and set the root if challenge_data.depth == 0: assert get_bitfield_bit(root, 0) == challenge_data.custody_bit + assert bls_verify( + message_hash=signed_root(response, 'signature'), + pubkey=responder.pubkey, + signature=response.signature, + domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE) + ) + challenge_data.current_custody_tree_node = root + # Otherwise just check the response against the root else: assert root == challenge_data.current_custody_tree_node - # Verify signature for depth 0 - if challenge_data.depth == 0: - assert bls_verify(message_hash=signed_root(response, 'signature'), - pubkey=responder.pubkey, - signature=response.signature, - domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE)) # Update challenge data challenge_data.deadline=FAR_FUTURE_EPOCH - validator.withdrawable_epoch = get_current_epoch(state) + MAX_POC_RESPONSE_DEPTH + responder.withdrawable_epoch = get_current_epoch(state) + MAX_POC_RESPONSE_DEPTH ``` Once a response provides 32 hashes, the challenger has the right to choose any one of them that they feel is constructed incorrectly to continue the game. Note that eventually, the game will get to the point where the `new_custody_tree_node` is a leaf node. We define an `InteractiveCustodyChallengeContinuation` object as follows: @@ -413,7 +416,7 @@ def process_continuation(continuation: InteractiveCustodyChallengeContinuation, challenge_data.current_custody_tree_node = continuation.new_custody_tree_node challenge_data.depth += expected_depth challenge_data.deadline = get_current_epoch(state) + MAX_POC_RESPONSE_DEPTH - validator.withdrawable_epoch = FAR_FUTURE_EPOCH + responder.withdrawable_epoch = FAR_FUTURE_EPOCH challenge_data.offset = challenger_data.offset * 2**expected_depth + sub_index ``` From 62be7f54fc3a009209420b64a98625f6cf9e1fc7 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 19 Feb 2019 06:36:54 -0600 Subject: [PATCH 07/11] Properly do #645 on top of #587 --- specs/core/1_shard-data-chains.md | 389 +++++++++++++++++------------- 1 file changed, 219 insertions(+), 170 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index c3b781323f..5b46028c1e 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -71,6 +71,8 @@ Phase 1 depends upon all of the constants defined in [Phase 0](0_beacon-chain.md | `SHARD_CHUNK_SIZE` | 2**5 (= 32) | bytes | | `SHARD_BLOCK_SIZE` | 2**14 (= 16,384) | bytes | | `MINOR_REWARD_QUOTIENT` | 2**8 (= 256) | | +| `MAX_POC_RESPONSE_DEPTH` | 5 | | +| `VALIDATOR_NULL` | 2**64 - 1 | | #### Time parameters @@ -84,19 +86,39 @@ Phase 1 depends upon all of the constants defined in [Phase 0](0_beacon-chain.md #### Max operations per block -| Name | Value | -|-------------------------------|---------------| -| `MAX_BRANCH_CHALLENGES` | 2**2 (= 4) | -| `MAX_BRANCH_RESPONSES` | 2**4 (= 16) | -| `MAX_EARLY_SUBKEY_REVEALS` | 2**4 (= 16) | +| Name | Value | +|----------------------------------------------------|---------------| +| `MAX_BRANCH_CHALLENGES` | 2**2 (= 4) | +| `MAX_BRANCH_RESPONSES` | 2**4 (= 16) | +| `MAX_EARLY_SUBKEY_REVEALS` | 2**4 (= 16) | +| `MAX_INTERACTIVE_CUSTODY_CHALLENGE_INITIATIONS` | 2 | +| `MAX_INTERACTIVE_CUSTODY_CHALLENGE_RESPONSES` | 16 | +| `MAX_INTERACTIVE_CUSTODY_CHALLENGE_CONTINUTATIONS` | 16 | #### Signature domains -| Name | Value | -|------------------------|-----------------| -| `DOMAIN_SHARD_PROPOSER`| 129 | -| `DOMAIN_SHARD_ATTESTER`| 130 | -| `DOMAIN_CUSTODY_SUBKEY`| 131 | +| Name | Value | +|------------------------------|-----------------| +| `DOMAIN_SHARD_PROPOSER` | 129 | +| `DOMAIN_SHARD_ATTESTER` | 130 | +| `DOMAIN_CUSTODY_SUBKEY` | 131 | +| `DOMAIN_CUSTODY_INTERACTIVE` | 132 | + +`EMPTY_CHALLENGE_DATA` is defined as: + +```python +InteractiveCustodyChallengeData( + challenger=VALIDATOR_NULL, + data_root=ZERO_HASH, + custody_bit=False, + responder_subkey=EMPTY_SIGNATURE, + current_custody_tree_node=ZERO_HASH, + depth=0, + offset=0, + max_depth=0, + deadline=0 +) +``` ## Helper functions @@ -303,6 +325,8 @@ Add member values to the end of the `Validator` object: 'open_branch_challenges': [BranchChallengeRecord], 'next_subkey_to_reveal': 'uint64', 'reveal_max_periods_late': 'uint64', + 'interactive_custody_challenge_data': InteractiveCustodyChallengeData, + 'now_challenging': 'uint64', ``` And the initializers: @@ -311,6 +335,8 @@ And the initializers: 'open_branch_challenges': [], 'next_subkey_to_reveal': get_current_custody_period(state), 'reveal_max_periods_late': 0, + 'interactive_custody_challenge_data': EMPTY_CHALLENGE_DATA + 'now_challenging': VALIDATOR_NULL ``` ### `BeaconBlockBody` @@ -321,6 +347,10 @@ Add member values to the `BeaconBlockBody` structure: 'branch_challenges': [BranchChallenge], 'branch_responses': [BranchResponse], 'subkey_reveals': [SubkeyReveal], + 'interactive_custody_challenge_initiations': [InteractiveCustodyChallengeInitiation], + 'interactive_custody_challenge_responses': [InteractiveCustodyChallengeResponse], + 'interactive_custody_challenge_continuations': [InteractiveCustodyChallengeContinuation], + ``` And initialize to the following: @@ -385,6 +415,65 @@ Define a `SubkeyReveal` as follows: } ``` +### `InteractiveCustodyChallengeData` + +```python +{ + # Who initiated the challenge + 'challenger': 'uint64', + # Initial data root + 'data_root': 'bytes32', + # Initial custody bit + 'custody_bit': 'bool', + # Responder subkey + 'responder_subkey': 'bytes96', + # The hash in the PoC tree in the position that we are currently at + 'current_custody_tree_node': 'bytes32', + # The position in the tree, in terms of depth and position offset + 'depth': 'uint64', + 'offset': 'uint64', + # Max depth of the branch + 'max_depth': 'uint64', + # Deadline to respond (as an epoch) + 'deadline': 'uint64', +} +``` + +### `InteractiveCustodyChallengeInitiation` + +```python +{ + 'attestation': SlashableAttestation, + 'responder_index': 'uint64', + 'challenger_index': 'uint64', + 'responder_subkey': 'bytes96', + 'signature': 'bytes96' +} +``` + +### `InteractiveCustodyChallengeResponse` + +```python +{ + 'responder_index': 'uint64', + 'hashes': ['bytes32'], + 'signature': 'bytes96', +} +``` + +### `InteractiveCustodyChallengeContinuation` + +```python +{ + 'challenger_index: 'uint64', + 'responder_index': 'uint64', + 'sub_index': 'uint64', + 'new_custody_tree_node': 'bytes32', + 'proof': ['bytes32'], + 'signature': 'bytes96' +} +``` + ## Helpers ### `get_attestation_merkle_depth` @@ -510,11 +599,64 @@ Verify that `len(block.body.branch_responses) <= MAX_BRANCH_RESPONSES`. For each `response` in `block.body.branch_responses`: -* Find the `BranchChallengeRecord` in `state.validator_registry[response.responder_index].open_branch_challenges` whose (`root`, `data_index`) match the (`root`, `data_index`) of the `response`. Verify that one such record exists (it is not possible for there to be more than one), call it `record`. -* Verify that `verify_merkle_branch(leaf=response.data, branch=response.branch, depth=record.depth, index=record.data_index, root=record.root)` is True. -* Verify that `get_current_epoch(state) >= record.inclusion_epoch + ENTRY_EXIT_DELAY`. -* Remove the `record` from `state.validator_registry[response.responder_index].open_branch_challenges` -* Determine the proposer `proposer_index = get_beacon_proposer_index(state, state.slot)` and set `state.validator_balances[proposer_index] += base_reward(state, index) // MINOR_REWARD_QUOTIENT`. +* Find the `BranchChallengeRecord` in `state.validator_registry[response.responder_index].open_branch_challenges` whose (`root`, `data_index`) match the (`root`, `data_index`) of the `response`. Verify that one of the following two cases is true: + * Such a record exists (it is not possible for there to be more than one). In this case, run `process_branch_exploration_response(response, state)` + * Such a record does not exist, but `state.validator_registry[response.responder_index].interactive_custody_challenge_data.challenger != VALIDATOR_NULL`. In this case, run `process_branch_custody_response(response, state)`. + +Here is `process_branch_exploration_response`: + +```python +def process_branch_exploration_response(response: BranchResponse, + state: BeaconState): + record = [ + x for x in state.validator_registry[response.responder_index].open_branch_challenges + if x.root == response.root and x.data_index == response.data_index + ][0] + assert verify_merkle_branch( + leaf=response.data, + branch=response.branch, + depth=record.depth, + index=record.data_index, + root=record.root + ) + # Must wait at least ENTRY_EXIT_DELAY before responding to a branch challenge + assert get_current_epoch(state) >= record.inclusion_epoch + ENTRY_EXIT_DELAY + state.validator_registry[response.responder_index].open_branch_challenges = [ + x for x in state.validator_registry[response.responder_index].open_branch_challenges + if x.root != response.root or x.data_index != response.data_index + ] + # Reward the proposer + proposer_index = get_beacon_proposer_index(state, state.slot) + state.validator_balances[proposer_index] += base_reward(state, index) // MINOR_REWARD_QUOTIENT +``` + +Here is `process_branch_custody_response`: + +```python +def process_branch_custody_response(response: BranchResponse, + state: BeaconState): + responder = state.validator_registry[response.responder_index] + challenge_data = responder.interactive_custody_challenge_data + assert challenge_data.depth == challenge_data.max_depth + # Verify we're not too late + assert get_current_epoch(state) < responder.withdrawable_epoch + # Verify the Merkle branch *of the data tree* + assert verify_merkle_branch( + leaf=response.data, + branch=response.branch, + depth=challenge_data.max_depth, + index=challenge_data.offset, + root=challenge_data.data_root + ) + # Responder wins + if hash(challenge_data.responder_subkey + response.data) == challenge_data.current_custody_tree_node: + penalize_validator(state, challenge_data.challenger_index, response.responder_index) + responder.interactive_custody_challenge_data = EMPTY_CHALLENGE_DATA + # Challenger wins + else: + penalize_validator(state, response.responder_index, challenge_data.challenger_index) + state.validator_registry[challenge_data.challenger_index].now_challenging = VALIDATOR_NULL +``` #### Subkey reveals @@ -540,118 +682,11 @@ In case (ii): * Set `state.validator_registry[reveal.validator_index].next_subkey_to_reveal += 1` * Set `state.validator_registry[reveal.validator_index].reveal_max_periods_late = max(state.validator_registry[reveal.validator_index].reveal_max_periods_late, get_current_period(state) - reveal.period)`. -## Per-epoch processing +#### Interactive custody challenge initiations -Add the following loop immediately below the `process_ejections` loop: +Verify that `len(block.body.interactive_custody_challenge_initiations) <= MAX_INTERACTIVE_CUSTODY_CHALLENGE_INITIATIONS`. -```python -def process_challenge_absences(state: BeaconState) -> None: - """ - Iterate through the validator registry - and penalize validators with balance that did not answer challenges. - """ - for index, validator in enumerate(state.validator_registry): - if len(validator.open_branch_challenges) > 0 and get_current_epoch(state) > validator.open_branch_challenges[0].inclusion_epoch + CHALLENGE_RESPONSE_DEADLINE: - penalize_validator(state, index, validator.open_branch_challenges[0].challenger_index) - if validator.challenge_data.challenger != VALIDATOR_NULL and get_current_epoch(state) > validator.challenge.deadline: - penalize_validator(state, index, validator.challenge_data.challenger_index) - if get_current_epoch(state) >= state.validator_registry[validator.now_challenging].withdrawal_epoch: - penalize_validator(state, index, validator.now_challenging) - penalize_validator(state, index, validator.challenge_data.challenger_index) -``` - -In `process_penalties_and_exits`, change the definition of `eligible` to the following (note that it is not a pure function because `state` is declared in the surrounding scope): - -```python -def eligible(index): - validator = state.validator_registry[index] - # Cannot exit if there are still open branch challenges - if len(validator.open_branch_challenges) > 0: - return False - # Cannot exit if you have not revealed all of your subkeys - elif validator.next_subkey_to_reveal <= epoch_to_custody_period(validator.exit_epoch): - return False - # Cannot exit if you already have - elif validator.withdrawable_epoch < FAR_FUTURE_EPOCH: - return False - # Return minimum time - else: - return current_epoch >= validator.exit_epoch + MIN_VALIDATOR_WITHDRAWAL_EPOCHS -``` - -## One-time phase 1 initiation transition - -Run the following on the fork block after per-slot processing and before per-block and per-epoch processing. - -For all `validator` in `ValidatorRegistry`, update it to the new format and fill the new member values with: - -```python - 'open_branch_challenges': [], - 'next_subkey_to_reveal': get_current_custody_period(state), - 'reveal_max_periods_late': 0, -``` - -# Proof of custody interactive game - -### Constants - -| Constant | Value | Unit | Approximation | -|--------------------------------------------|------------------|---------|---------------| -| `MAX_POC_RESPONSE_DEPTH` | 5 | layers | | -| `DOMAIN_CUSTODY_INTERACTIVE` | 132 | | | -| `VALIDATOR_NULL` | 2**64 - 1 | | | -| `MAX_INTERACTIVE_CHALLENGE_INITIATIONS` | 2 | | | -| `MAX_INTERACTIVE_CHALLENGE_RESPONSES` | 16 | | | -| `MAX_INTERACTIVE_CHALLENGE_CONTINUTATIONS` | 16 | | | - -### Data structures and verification - -Add the following data structure to the `Validator` record: - -```python - interactive_custody_challenge_data: InteractiveCustodyChallengeData, - now_challenging: 'uint64', -``` - -Where `InteractiveCustodyChallengeData` is defined as follows: - -```python -{ - # Who initiated the challenge - 'challenger': 'uint64', - # Initial data root - 'data_root': 'bytes32', - # Initial custody bit - 'custody_bit': 'bool', - # Responder subkey - 'responder_subkey': 'bytes96', - # The hash in the PoC tree in the position that we are currently at - 'current_custody_tree_node': 'bytes32', - # The position in the tree, in terms of depth and position offset - 'depth': 'uint64', - 'offset': 'uint64', - # Max depth of the branch - 'max_depth': 'uint64', - # Deadline to respond (as an epoch) - 'deadline': 'uint64', -} -``` - -The initial value is `EMPTY_CHALLENGE_DATA = InteractiveCustodyChallengeData(challenger=VALIDATOR_NULL, data_root=ZERO_HASH, custody_bit=False, responder_subkey=EMPTY_SIGNATURE, current_custody_tree_node=ZERO_HASH, depth=0, offset=0, max_depth=0, deadline=0)` - -We define an `InteractiveCustodyChallengeInitiation` as follows: - -```python -{ - 'attestation': SlashableAttestation, - 'responder_index': 'uint64', - 'challenger_index': 'uint64', - 'responder_subkey': 'bytes96', - 'signature': 'bytes96' -} -``` - -Here's the function for validating and processing an initiation: +For each `initiation` in `block.body.interactive_custody_challenge_initiations`, use the following function to process it: ```python def process_initiation(initiation: InteractiveCustodyChallengeInitiation, @@ -699,19 +734,13 @@ def process_initiation(initiation: InteractiveCustodyChallengeInitiation, challenger.now_challenging = responder_index ``` -We define an `InteractiveCustodyChallengeResponse` as follows: - -```python -{ - 'responder_index': 'uint64', - 'hashes': ['bytes32'], - 'signature': 'bytes96', -} -``` +#### Interactive custody challenge responses A response provides 32 hashes that are under current known proof of custody tree node. Note that at the beginning the tree node is just one bit of the custody root, so we ask the responder to sign to commit to the top 5 levels of the tree and therefore the root hash; at all other stages in the game responses are self-verifying. -Here's the function for verifying and processing a response: +Verify that `len(block.body.interactive_custody_challenge_responses) <= MAX_INTERACTIVE_CUSTODY_CHALLENGE_RESPONSES`. + +For each `response` in `block.body.interactive_custody_challenge_responses`, use the following function to process it: ```python def process_response(response: InteractiveCustodyChallengeResponse, @@ -743,20 +772,15 @@ def process_response(response: InteractiveCustodyChallengeResponse, responder.withdrawable_epoch = get_current_epoch(state) + MAX_POC_RESPONSE_DEPTH ``` -Once a response provides 32 hashes, the challenger has the right to choose any one of them that they feel is constructed incorrectly to continue the game. Note that eventually, the game will get to the point where the `new_custody_tree_node` is a leaf node. We define an `InteractiveCustodyChallengeContinuation` object as follows: +Note that once the `new_custody_tree_node` reaches the leaves of the tree, the responder can no longer provide a valid `InteractiveCustodyChallengeResponse`; instead, the responder or the challenger must provide a `BranchResponse` that provides a branch of the original data tree, at which point the custody leaf equation can be checked and either side of the custody game can "conclusively win". -```python -{ - 'challenger_index: 'uint64', - 'responder_index': 'uint64', - 'sub_index': 'uint64', - 'new_custody_tree_node': 'bytes32', - 'proof': ['bytes32'], - 'signature': 'bytes96' -} -``` +#### Interactive custody challenge continuations -Here's the function for verifying and processing a continuation challenge: +Once a response provides 32 hashes, the challenger has the right to choose any one of them that they feel is constructed incorrectly to continue the game. Note that eventually, the game will get to the point where the `new_custody_tree_node` is a leaf node. + +Verify that `len(block.body.interactive_custody_challenge_continuations) <= MAX_INTERACTIVE_CUSTODY_CHALLENGE_CONTINUATIONS`. + +For each `continuation` in `block.body.interactive_custody_challenge_continuations`, use the following function to process it: ```python def process_continuation(continuation: InteractiveCustodyChallengeContinuation, @@ -788,30 +812,55 @@ def process_continuation(continuation: InteractiveCustodyChallengeContinuation, challenge_data.offset = challenger_data.offset * 2**expected_depth + sub_index ``` -Once the `new_custody_tree_node` reaches the leaves of the tree, the responder can no longer provide a valid `InteractiveCustodyChallengeResponse`; instead, the responder or the challenger must provide a branch response that provides a branch of the original data tree, at which point the custody leaf equation can be checked and either side of the custody game can "conclusively win". +## Per-epoch processing + +Add the following loop immediately below the `process_ejections` loop: ```python -def process_branch_response(response: BranchResponse, - state: State): - responder = state.validator_registry[response.responder_index] - challenge_data = responder.interactive_custody_challenge_data - assert challenge_data.depth == challenge_data.max_depth - # Verify we're not too late - assert get_current_epoch(state) < responder.withdrawable_epoch - # Verify the Merkle branch *of the data tree* - assert verify_merkle_branch( - leaf=response.data, - branch=response.branch, - depth=challenge_data.max_depth, - index=challenge_data.offset, - root=challenge_data.data_root - ) - # Responder wins - if hash(challenge_data.responder_subkey + response.data) == challenge_data.current_custody_tree_node: - penalize_validator(state, challenge_data.challenger_index, response.responder_index) - responder.interactive_custody_challenge_data = EMPTY_CHALLENGE_DATA - # Challenger wins +def process_challenge_absences(state: BeaconState) -> None: + """ + Iterate through the validator registry + and penalize validators with balance that did not answer challenges. + """ + for index, validator in enumerate(state.validator_registry): + if len(validator.open_branch_challenges) > 0 and get_current_epoch(state) > validator.open_branch_challenges[0].inclusion_epoch + CHALLENGE_RESPONSE_DEADLINE: + penalize_validator(state, index, validator.open_branch_challenges[0].challenger_index) + if validator.challenge_data.challenger != VALIDATOR_NULL and get_current_epoch(state) > validator.challenge.deadline: + penalize_validator(state, index, validator.challenge_data.challenger_index) + if get_current_epoch(state) >= state.validator_registry[validator.now_challenging].withdrawal_epoch: + penalize_validator(state, index, validator.now_challenging) + penalize_validator(state, index, validator.challenge_data.challenger_index) +``` + +In `process_penalties_and_exits`, change the definition of `eligible` to the following (note that it is not a pure function because `state` is declared in the surrounding scope): + +```python +def eligible(index): + validator = state.validator_registry[index] + # Cannot exit if there are still open branch challenges + if len(validator.open_branch_challenges) > 0: + return False + # Cannot exit if you have not revealed all of your subkeys + elif validator.next_subkey_to_reveal <= epoch_to_custody_period(validator.exit_epoch): + return False + # Cannot exit if you already have + elif validator.withdrawable_epoch < FAR_FUTURE_EPOCH: + return False + # Return minimum time else: - penalize_validator(state, response.responder_index, challenge_data.challenger_index) - state.validator_registry[challenge_data.challenger_index].now_challenging = VALIDATOR_NULL + return current_epoch >= validator.exit_epoch + MIN_VALIDATOR_WITHDRAWAL_EPOCHS +``` + +## One-time phase 1 initiation transition + +Run the following on the fork block after per-slot processing and before per-block and per-epoch processing. + +For all `validator` in `ValidatorRegistry`, update it to the new format and fill the new member values with: + +```python + 'open_branch_challenges': [], + 'next_subkey_to_reveal': get_current_custody_period(state), + 'reveal_max_periods_late': 0, + 'interactive_custody_challenge_data': EMPTY_CHALLENGE_DATA, + 'new_challenging': VALIDATOR_NULL, ``` From 46cc1523131be1d8cc8a44a30da3c57d394ce74d Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 19 Feb 2019 22:05:59 -0600 Subject: [PATCH 08/11] Attestation merkle length -> attestation data merkle length --- specs/core/1_shard-data-chains.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 5b46028c1e..86d920b886 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -35,7 +35,7 @@ - [`BranchChallengeRecord`](#branchchallengerecord) - [`SubkeyReveal`](#subkeyreveal) - [Helpers](#helpers) - - [`get_attestation_merkle_depth`](#get_attestation_merkle_depth) + - [`get_attestation_data_merkle_depth`](#get_attestation_data_merkle_depth) - [`epoch_to_custody_period`](#epoch_to_custody_period) - [`slot_to_custody_period`](#slot_to_custody_period) - [`get_current_custody_period`](#get_current_custody_period) @@ -476,12 +476,12 @@ Define a `SubkeyReveal` as follows: ## Helpers -### `get_attestation_merkle_depth` +### `get_attestation_data_merkle_depth` ```python -def get_attestation_merkle_depth(attestation: Attestation) -> int: - start_epoch = attestation.data.latest_crosslink.epoch - end_epoch = slot_to_epoch(attestation.data.slot) +def get_attestation_data_merkle_depth(attestation_data: AttestationData) -> int: + start_epoch = attestation_data.latest_crosslink.epoch + end_epoch = slot_to_epoch(attestation_data.slot) chunks_per_slot = SHARD_BLOCK_SIZE // 32 chunks = (end_epoch - start_epoch) * EPOCH_LENGTH * chunks_per_slot return log2(next_power_of_two(chunks)) @@ -587,7 +587,7 @@ For each `challenge` in `block.body.branch_challenges`: * Verify that `state.validator_registry[responder_index].exit_epoch >= get_current_epoch(state) - MAX_BRANCH_CHALLENGE_DELAY`. * Verify that `verify_slashable_attestation(state, challenge.attestation)` returns `True`. * Verify that `challenge.responder_index` is in `challenge.attestation.validator_indices`. -* Let `depth = get_attestation_merkle_depth(challenge.attestation)`. Verify that `challenge.data_index < 2**depth`. +* Let `depth = get_attestation_data_merkle_depth(challenge.attestation.data)`. Verify that `challenge.data_index < 2**depth`. * Verify that there does not exist a `BranchChallengeRecord` in `state.validator_registry[challenge.responder_index].open_branch_challenges` with `root == challenge.attestation.data.shard_chain_commitment` and `data_index == data_index`. * Append to `state.validator_registry[challenge.responder_index].open_branch_challenges` the object `BranchChallengeRecord(challenger_index=get_beacon_proposer_index(state, state.slot), root=challenge.attestation.data.shard_chain_commitment, depth=depth, inclusion_epoch=get_current_epoch(state), data_index=data_index)`. @@ -725,7 +725,7 @@ def process_initiation(initiation: InteractiveCustodyChallengeInitiation, current_custody_tree_node=ZERO_HASH, depth=0, offset=0, - max_depth=get_merkle_depth(initiation.attestation), + max_depth=get_attestation_data_merkle_depth(initiation.attestation.data), deadline=get_current_epoch(state) + CHALLENGE_RESPONSE_DEADLINE ) # Responder can't withdraw yet! From 32942ec10049689cd56ae7ff7b2542230b7abc35 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 20 Feb 2019 00:52:38 -0600 Subject: [PATCH 09/11] Adjusted order of arguments in verify_custody_subkey_reveal --- specs/core/1_shard-data-chains.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 86d920b886..34d52519b2 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -72,6 +72,7 @@ Phase 1 depends upon all of the constants defined in [Phase 0](0_beacon-chain.md | `SHARD_BLOCK_SIZE` | 2**14 (= 16,384) | bytes | | `MINOR_REWARD_QUOTIENT` | 2**8 (= 256) | | | `MAX_POC_RESPONSE_DEPTH` | 5 | | +| `ZERO_PUBKEY` | int_to_bytes48(0)| | | `VALIDATOR_NULL` | 2**64 - 1 | | #### Time parameters @@ -513,9 +514,9 @@ def get_current_custody_period(state: BeaconState) -> int: ```python def verify_custody_subkey_reveal(pubkey: bytes48, subkey: bytes96, - mask: bytes32, - mask_pubkey: bytes48, - period: int) -> bool: + period: int, + mask=ZERO_HASH: bytes32, + mask_pubkey=ZERO_PUBKEY: bytes48) -> bool: # Legitimate reveal: checking that the provided value actually is the subkey if mask == ZERO_HASH: pubkeys=[pubkey] @@ -712,8 +713,6 @@ def process_initiation(initiation: InteractiveCustodyChallengeInitiation, assert verify_custody_subkey_reveal( pubkey=state.validator_registry[responder_index].pubkey, subkey=responder_subkey, - mask=ZERO_HASH, - mask_pubkey=b'', period=slot_to_custody_period(attestation.data.slot) ) # Set the challenge object From cfe26c18b735c4689e1bdf9663b757f515d7674a Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 20 Feb 2019 01:34:22 -0600 Subject: [PATCH 10/11] Simplified continuation slightly --- specs/core/1_shard-data-chains.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 34d52519b2..e95540a482 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -466,7 +466,6 @@ Define a `SubkeyReveal` as follows: ```python { - 'challenger_index: 'uint64', 'responder_index': 'uint64', 'sub_index': 'uint64', 'new_custody_tree_node': 'bytes32', @@ -800,7 +799,7 @@ def process_continuation(continuation: InteractiveCustodyChallengeContinuation, ) # Verify signature assert bls_verify(message_hash=signed_root(continutation, 'signature'), - pubkey=responder.pubkey, + pubkey=state.validator_registry[challenge_data.challenger].pubkey, signature=continutation.signature, domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_INTERACTIVE)) # Update the challenge data @@ -808,7 +807,7 @@ def process_continuation(continuation: InteractiveCustodyChallengeContinuation, challenge_data.depth += expected_depth challenge_data.deadline = get_current_epoch(state) + MAX_POC_RESPONSE_DEPTH responder.withdrawable_epoch = FAR_FUTURE_EPOCH - challenge_data.offset = challenger_data.offset * 2**expected_depth + sub_index + challenge_data.offset = challenge_data.offset * 2**expected_depth + sub_index ``` ## Per-epoch processing From 189d7286c00f3f6baa71ccc449d50a1716b4e566 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 20 Feb 2019 20:39:48 -0700 Subject: [PATCH 11/11] minor formatting --- specs/core/1_shard-data-chains.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index e95540a482..98d26dca5b 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -117,7 +117,7 @@ InteractiveCustodyChallengeData( depth=0, offset=0, max_depth=0, - deadline=0 + deadline=0, ) ``` @@ -336,8 +336,8 @@ And the initializers: 'open_branch_challenges': [], 'next_subkey_to_reveal': get_current_custody_period(state), 'reveal_max_periods_late': 0, - 'interactive_custody_challenge_data': EMPTY_CHALLENGE_DATA - 'now_challenging': VALIDATOR_NULL + 'interactive_custody_challenge_data': EMPTY_CHALLENGE_DATA, + 'now_challenging': VALIDATOR_NULL, ``` ### `BeaconBlockBody` @@ -448,7 +448,7 @@ Define a `SubkeyReveal` as follows: 'responder_index': 'uint64', 'challenger_index': 'uint64', 'responder_subkey': 'bytes96', - 'signature': 'bytes96' + 'signature': 'bytes96', } ``` @@ -470,7 +470,7 @@ Define a `SubkeyReveal` as follows: 'sub_index': 'uint64', 'new_custody_tree_node': 'bytes32', 'proof': ['bytes32'], - 'signature': 'bytes96' + 'signature': 'bytes96', } ```