From db9fdb9b72fdc5ee5b27b5655fbb2669b84da003 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sat, 17 Nov 2018 18:20:39 -0500 Subject: [PATCH 01/20] Switch from alt_bn255 to BLS12-381 --- specs/core/0_beacon-chain.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 273bf75f5..0304b11b2 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -137,7 +137,7 @@ An `AttestationRecord` has the following fields: # Hash of last justified beacon block 'justified_block_hash': 'hash32', # BLS aggregate signature - 'aggregate_sig': ['uint256'] + 'aggregate_sig': ['uint384'] } ``` @@ -227,7 +227,7 @@ A `ValidatorRecord` has the following fields: ```python { # BLS public key - 'pubkey': 'uint256', + 'pubkey': 'uint384', # Withdrawal shard number 'withdrawal_shard': 'uint16', # Withdrawal address @@ -609,6 +609,8 @@ def add_validator(validators: List[ValidatorRecord], return index ``` +`BLSVerify` is a function for verifying a BLS-12-381 signature, defined in the BLS-12-381 spec. + ## Routine for removing a validator ```python From 8a2e1d55a506f85cb35970361e7150cce80931b2 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 18 Nov 2018 10:34:23 +0900 Subject: [PATCH 02/20] fix bls12-381 to be in standard notation --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 0304b11b2..681ff876f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -609,7 +609,7 @@ def add_validator(validators: List[ValidatorRecord], return index ``` -`BLSVerify` is a function for verifying a BLS-12-381 signature, defined in the BLS-12-381 spec. +`BLSVerify` is a function for verifying a BLS12-381 signature, defined in the BLS12-381 spec. ## Routine for removing a validator From 54217772fed49499a980b9ac1d947c6a84f52f51 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 19 Nov 2018 20:13:58 -0500 Subject: [PATCH 03/20] Added domain separation --- specs/core/0_beacon-chain.md | 39 ++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 6914c64e7..22c55a997 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -90,6 +90,15 @@ The primary source of load on the beacon chain are "attestations". Attestations | `ENTRY` | `0` | | `EXIT` | `1` | +**Domains for BLS signatures** + +| Name | Value | +| - | :-: | +| `DOMAIN_DEPOSIT` | `0` | +| `DOMAIN_ATTESTATION` | `1` | +| `DOMAIN_PROPOSAL` | `2` | +| `DOMAIN_LOGOUT` | `3` | + ### PoW chain registration contract The initial deployment phases of Ethereum 2.0 are implemented without consensus changes to the PoW chain. A registration contract is added to the PoW chain to deposit ETH. This contract has a `registration` function which takes as arguments `pubkey`, `withdrawal_shard`, `withdrawal_address`, `randao_commitment` as defined in a `ValidatorRecord` below. A BLS `proof_of_possession` of types `bytes` is given as a final argument. @@ -152,8 +161,6 @@ An `AttestationSignedData` has the following fields: ```python { - # Fork version - 'fork_version': 'uint64', # Slot number 'slot': 'uint64', # Shard number @@ -652,7 +659,7 @@ The `add_validator` routine is defined below. This routine should be run for every validator that is inducted as part of a log created on the PoW chain [TODO: explain where to check for these logs]. The status of the validators added after genesis is `PENDING_ACTIVATION`. These logs should be processed in the order in which they are emitted by the PoW chain. -First, a helper function: +First, some helper functions: ```python def min_empty_validator(validators: List[ValidatorRecord], current_slot: int): @@ -662,10 +669,15 @@ def min_empty_validator(validators: List[ValidatorRecord], current_slot: int): return None ``` +```python +def get_fork_version(state: State, slot: uint64): + return state.pre_fork_version if slot < state.fork_slot_number else state.post_fork_version +``` + Now, to add a validator: ```python -def add_validator(validators: List[ValidatorRecord], +def add_validator(state: State, pubkey: int, proof_of_possession: bytes, withdrawal_shard: int, @@ -678,7 +690,8 @@ def add_validator(validators: List[ValidatorRecord], signed_message = as_bytes32(pubkey) + as_bytes2(withdrawal_shard) + withdrawal_address + randao_commitment assert BLSVerify(pub=pubkey, msg=hash(signed_message), - sig=proof_of_possession) + sig=proof_of_possession, + domain=get_fork_version(state, current_slot) * 2**32 + DOMAIN_DEPOSIT) # Pubkey uniqueness assert pubkey not in [v.pubkey for v in validators] rec = ValidatorRecord( @@ -815,9 +828,9 @@ For each one of these attestations: * Compute `parent_hashes` = `[get_block_hash(state, block, slot - CYCLE_LENGTH + i) for i in range(1, CYCLE_LENGTH - len(oblique_parent_hashes) + 1)] + oblique_parent_hashes` (eg, if `CYCLE_LENGTH = 4`, `slot = 5`, the actual block hashes starting from slot 0 are `Z A B C D E F G H I J`, and `oblique_parent_hashes = [D', E']` then `parent_hashes = [B, C, D' E']`). Note that when *creating* an attestation for a block, the hash of that block itself won't yet be in the `state`, so you would need to add it explicitly. * Let `attestation_indices` be `get_shards_and_committees_for_slot(state, slot)[x]`, choosing `x` so that `attestation_indices.shard` equals the `shard` value provided to find the set of validators that is creating this attestation record. * Verify that `len(attester_bitfield) == ceil_div8(len(attestation_indices))`, where `ceil_div8 = (x + 7) // 8`. Verify that bits `len(attestation_indices)....` and higher, if present (i.e. `len(attestation_indices)` is not a multiple of 8), are all zero. -* Derive a group public key by adding the public keys of all of the attesters in `attestation_indices` for whom the corresponding bit in `attester_bitfield` (the ith bit is `(attester_bitfield[i // 8] >> (7 - (i %8))) % 2`) equals 1. -* Let `fork_version = pre_fork_version if slot < fork_slot_number else post_fork_version`. -* Verify that `aggregate_sig` verifies using the group pubkey generated and the serialized form of `AttestationSignedData(fork_version, slot, shard, parent_hashes, shard_block_hash, last_crosslinked_hash, shard_block_combined_data_root, justified_slot)` as the message. +* Derive a `group_public_key` by adding the public keys of all of the attesters in `attestation_indices` for whom the corresponding bit in `attester_bitfield` (the ith bit is `(attester_bitfield[i // 8] >> (7 - (i %8))) % 2`) equals 1. +* Let `data = AttestationSignedData(slot, shard, parent_hashes, shard_block_hash, last_crosslinked_hash, shard_block_combined_data_root, justified_slot)` +* Check `BLSVerify(pubkey=group_public_key, msg=data, sig=aggregate_sig, domain=get_fork_version(state, slot) * 2**32 + DOMAIN_ATTESTATION)` Extend the list of `AttestationRecord` objects in the `state` with those included in the block, ordering the new additions in the same order as they came in the block. @@ -843,12 +856,12 @@ For each `SpecialRecord` `obj` in `block.specials`, verify that its `kind` is on ```python { 'validator_index': 'uint64', - 'signature': '[uint256]' + 'signature': '[uint384]' } ``` Perform the following checks: -* Let `fork_version = pre_fork_version if block.slot < fork_slot_number else post_fork_version`. Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=data.signature)` +* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=data.signature, domain=get_fork_version(state, current_slot) * 2**32 + DOMAIN_LOGOUT)` * Verify that `validators[validator_index].status == ACTIVE`. Run `exit_validator(data.validator_index, state, penalize=False, current_slot=block.slot)`. @@ -859,16 +872,16 @@ Run `exit_validator(data.validator_index, state, penalize=False, current_slot=bl { 'vote1_aggregate_sig_indices': '[uint24]', 'vote1_data': AttestationSignedData, - 'vote1_aggregate_sig': '[uint256]', + 'vote1_aggregate_sig': '[uint384]', 'vote2_aggregate_sig_indices': '[uint24]', 'vote2_data': AttestationSignedData, - 'vote2_aggregate_sig': '[uint256]', + 'vote2_aggregate_sig': '[uint384]', } ``` Perform the following checks: -* For each `aggregate_sig`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in aggregate_sig_indices]), msg=vote_data, sig=aggsig)` passes. +* For each `aggregate_sig`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in aggregate_sig_indices]), msg=vote_data, sig=aggsig, domain=get_fork_version(state, vote_data.slot) * 2**32 + DOMAIN_ATTESTATION)` passes. * Verify that `vote1_data != vote2_data`. * Let `intersection = [x for x in vote1_aggregate_sig_indices if x in vote2_aggregate_sig_indices]`. Verify that `len(intersection) >= 1`. * Verify that `vote1_data.justified_slot < vote2_data.justified_slot < vote2_data.slot <= vote1_data.slot`. From e925fe5705f2b112b1682c52ea781aba3b0b4950 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 19 Nov 2018 20:15:10 -0500 Subject: [PATCH 04/20] Forgot to change validators -> state.validators --- specs/core/0_beacon-chain.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 22c55a997..61ad95c28 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -693,7 +693,7 @@ def add_validator(state: State, sig=proof_of_possession, domain=get_fork_version(state, current_slot) * 2**32 + DOMAIN_DEPOSIT) # Pubkey uniqueness - assert pubkey not in [v.pubkey for v in validators] + assert pubkey not in [v.pubkey for v in state.validators] rec = ValidatorRecord( pubkey=pubkey, withdrawal_shard=withdrawal_shard, @@ -705,12 +705,12 @@ def add_validator(state: State, exit_slot=0, exit_seq=0 ) - index = min_empty_validator(validators) + index = min_empty_validator(state.validators) if index is None: - validators.append(rec) - return len(validators) - 1 + state.validators.append(rec) + return len(state.validators) - 1 else: - validators[index] = rec + state.validators[index] = rec return index ``` From 048090acf6371c0b6455fc14649fb838bb147007 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 23 Nov 2018 15:10:59 -0500 Subject: [PATCH 05/20] Update specs/core/0_beacon-chain.md Co-Authored-By: vbuterin --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1f2f5c9c5..ef1b27235 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -699,7 +699,7 @@ def min_empty_validator(validators: List[ValidatorRecord], current_slot: int): ``` ```python -def get_fork_version(state: State, slot: uint64): +def get_fork_version(state: State, slot: int) -> int: return state.pre_fork_version if slot < state.fork_slot_number else state.post_fork_version ``` From 5508a0f9c5d3696777e2b65cbbe1f656156b60e8 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 23 Nov 2018 15:13:21 -0500 Subject: [PATCH 06/20] get_fork_version -> get_domain --- specs/core/0_beacon-chain.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index ef1b27235..99d15269f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -701,6 +701,9 @@ def min_empty_validator(validators: List[ValidatorRecord], current_slot: int): ```python def get_fork_version(state: State, slot: int) -> int: return state.pre_fork_version if slot < state.fork_slot_number else state.post_fork_version + +def get_domain(state: State, slot: int, base_domain: int) -> int: + return get_fork_version(state, slot) * 2**32 + base_domain ``` Now, to add a validator: @@ -720,7 +723,7 @@ def add_validator(state: State, assert BLSVerify(pub=pubkey, msg=hash(signed_message), sig=proof_of_possession, - domain=get_fork_version(state, current_slot) * 2**32 + DOMAIN_DEPOSIT) + domain=get_domain(state, current_slot, DOMAIN_DEPOSIT)) # Pubkey uniqueness assert pubkey not in [v.pubkey for v in state.validators] rec = ValidatorRecord( @@ -862,7 +865,7 @@ For each `AttestationRecord` object: * Verify that `len(attester_bitfield) == ceil_div8(len(attestation_indices))`, where `ceil_div8 = (x + 7) // 8`. Verify that bits `len(attestation_indices)....` and higher, if present (i.e. `len(attestation_indices)` is not a multiple of 8), are all zero. * Derive a `group_public_key` by adding the public keys of all of the attesters in `attestation_indices` for whom the corresponding bit in `attester_bitfield` (the ith bit is `(attester_bitfield[i // 8] >> (7 - (i %8))) % 2`) equals 1. * Let `data = AttestationSignedData(slot, shard, parent_hashes, shard_block_hash, last_crosslinked_hash, shard_block_combined_data_root, justified_slot)` -* Check `BLSVerify(pubkey=group_public_key, msg=data, sig=aggregate_sig, domain=get_fork_version(state, slot) * 2**32 + DOMAIN_ATTESTATION)` +* Check `BLSVerify(pubkey=group_public_key, msg=data, sig=aggregate_sig, domain=get_domain(state, slot, DOMAIN_ATTESTATION))` Extend the list of `AttestationRecord` objects in the `state` with those included in the block, ordering the new additions in the same order as they came in the block. @@ -897,7 +900,7 @@ For each `SpecialRecord` `obj` in `block.specials`, verify that its `kind` is on ``` Perform the following checks: -* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=data.signature, domain=get_fork_version(state, current_slot) * 2**32 + DOMAIN_LOGOUT)` +* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=data.signature, domain=get_domain(state, current_slot, DOMAIN_LOGOUT))` * Verify that `validators[validator_index].status == ACTIVE`. Run `exit_validator(data.validator_index, state, block, penalize=False, current_slot=block.slot)`. @@ -917,7 +920,7 @@ Run `exit_validator(data.validator_index, state, block, penalize=False, current_ Perform the following checks: -* For each `aggregate_sig`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in aggregate_sig_indices]), msg=vote_data, sig=aggsig, domain=get_fork_version(state, vote_data.slot) * 2**32 + DOMAIN_ATTESTATION)` passes. +* For each `aggregate_sig`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in aggregate_sig_indices]), msg=vote_data, sig=aggsig, domain=get_domain(state, vote_data.slot, DOMAIN_ATTESTATION))` passes. * Verify that `vote1_data != vote2_data`. * Let `intersection = [x for x in vote1_aggregate_sig_indices if x in vote2_aggregate_sig_indices]`. Verify that `len(intersection) >= 1`. * Verify that `vote1_data.justified_slot < vote2_data.justified_slot < vote2_data.slot <= vote1_data.slot`. From c098978efb5c384d839fcc596b9a6a79875f5cc1 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 23 Nov 2018 15:14:53 -0500 Subject: [PATCH 07/20] Fixed domain separation for proposals --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 99d15269f..559b90b16 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -873,7 +873,7 @@ Extend the list of `AttestationRecord` objects in the `state` with those include Let `proposal_hash = hash(ProposalSignedData(fork_version, block.slot, 2**64 - 1, block_hash_without_sig))` where `block_hash_without_sig` is the hash of the block except setting `proposer_signature` to `[0, 0]`. -Verify that `BLSVerify(pubkey=get_beacon_proposer(state, block.slot).pubkey, data=proposal_hash, sig=block.proposer_signature)` passes. +Verify that `BLSVerify(pubkey=get_beacon_proposer(state, block.slot).pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state, block.slot, DOMAIN_PROPOSAL))` passes. ### Verify and process RANDAO reveal @@ -938,7 +938,7 @@ For each validator index `v` in `intersection`, if `state.validators[v].status` 'proposal1_signature': '[uint256]', } ``` -For each `proposal_signature`, verify that `BLSVerify(pubkey=validators[proposer_index].pubkey, msg=hash(proposal_data), sig=proposal_signature)` passes. Verify that `proposal1_data.slot == proposal2_data.slot` but `proposal1 != proposal2`. If `state.validators[proposer_index].status` does not equal `PENALIZED`, then run `exit_validator(proposer_index, state, penalize=True, current_slot=block.slot)` +For each `proposal_signature`, verify that `BLSVerify(pubkey=validators[proposer_index].pubkey, msg=hash(proposal_data), sig=proposal_signature, domain=get_domain(state, proposal_data.slot, DOMAIN_PROPOSAL))` passes. Verify that `proposal1_data.slot == proposal2_data.slot` but `proposal1 != proposal2`. If `state.validators[proposer_index].status` does not equal `PENALIZED`, then run `exit_validator(proposer_index, state, penalize=True, current_slot=block.slot)` #### DEPOSIT_PROOF From 020ec39260f1aaa2a6df11111df9ce1d7e9c9807 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 23 Nov 2018 15:25:45 -0500 Subject: [PATCH 08/20] Clarify variables --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 559b90b16..999a5b566 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -920,7 +920,7 @@ Run `exit_validator(data.validator_index, state, block, penalize=False, current_ Perform the following checks: -* For each `aggregate_sig`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in aggregate_sig_indices]), msg=vote_data, sig=aggsig, domain=get_domain(state, vote_data.slot, DOMAIN_ATTESTATION))` passes. +* For each `vote`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in vote_aggregate_sig_indices]), msg=vote_data, sig=vote_aggregate_sig, domain=get_domain(state, vote_data.slot, DOMAIN_ATTESTATION))` passes. * Verify that `vote1_data != vote2_data`. * Let `intersection = [x for x in vote1_aggregate_sig_indices if x in vote2_aggregate_sig_indices]`. Verify that `len(intersection) >= 1`. * Verify that `vote1_data.justified_slot < vote2_data.justified_slot < vote2_data.slot <= vote1_data.slot`. From d0f43663a34e7c1844d8838032732615acc4b29a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sat, 24 Nov 2018 14:40:48 -0600 Subject: [PATCH 09/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 999a5b566..72103e732 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -863,7 +863,7 @@ For each `AttestationRecord` object: * Compute `parent_hashes` = `[get_block_hash(state, block, slot - CYCLE_LENGTH + i) for i in range(1, CYCLE_LENGTH - len(oblique_parent_hashes) + 1)] + oblique_parent_hashes` (eg, if `CYCLE_LENGTH = 4`, `slot = 5`, the actual block hashes starting from slot 0 are `Z A B C D E F G H I J`, and `oblique_parent_hashes = [D', E']` then `parent_hashes = [B, C, D' E']`). Note that when *creating* an attestation for a block, the hash of that block itself won't yet be in the `state`, so you would need to add it explicitly. * Let `attestation_indices` be `get_shards_and_committees_for_slot(state, slot)[x]`, choosing `x` so that `attestation_indices.shard` equals the `shard` value provided to find the set of validators that is creating this attestation record. * Verify that `len(attester_bitfield) == ceil_div8(len(attestation_indices))`, where `ceil_div8 = (x + 7) // 8`. Verify that bits `len(attestation_indices)....` and higher, if present (i.e. `len(attestation_indices)` is not a multiple of 8), are all zero. -* Derive a `group_public_key` by adding the public keys of all of the attesters in `attestation_indices` for whom the corresponding bit in `attester_bitfield` (the ith bit is `(attester_bitfield[i // 8] >> (7 - (i %8))) % 2`) equals 1. +* Derive a `group_public_key` by adding the public keys of all of the attesters in `attestation_indices` for whom the corresponding bit in `attester_bitfield` (the ith bit is `(attester_bitfield[i // 8] >> (7 - (i % 8))) % 2`) equals 1. * Let `data = AttestationSignedData(slot, shard, parent_hashes, shard_block_hash, last_crosslinked_hash, shard_block_combined_data_root, justified_slot)` * Check `BLSVerify(pubkey=group_public_key, msg=data, sig=aggregate_sig, domain=get_domain(state, slot, DOMAIN_ATTESTATION))` From 6fe5928a84d445d45c806bc217aa8c889fd63e87 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sun, 25 Nov 2018 08:06:37 -0500 Subject: [PATCH 10/20] Blocks to fixed size, add proposal signatures --- specs/core/1_shard-data-chains.md | 76 ++++++++++++++----------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index c01c6e98f..18341265e 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -19,7 +19,14 @@ Phase 1 depends upon all of the constants defined in [Phase 0](0_beacon-chain.md | Constant | Value | Unit | Approximation | |------------------------|-----------------|-------|---------------| | `CHUNK_SIZE` | 2**8 (= 256) | bytes | | -| `MAX_SHARD_BLOCK_SIZE` | 2**15 (= 32768) | bytes | | +| `SHARD_BLOCK_SIZE` | 2**14 (= 16384) | bytes | | + +### Flags, domains, etc. + +| Constant | Value | +|------------------------|-----------------| +| `SHARD_PROPOSER_DOMAIN`| 129 | +| `SHARD_ATTESTER_DOMAIN`| 130 | ## Data Structures @@ -43,7 +50,9 @@ A `ShardBlock` object has the following fields: 'data_root': 'hash32' # State root (placeholder for now) 'state_root': 'hash32', - # Attestation (including block signature) + # Block signature + 'signature': ['uint256'], + # Attestation 'attester_bitfield': 'bytes', 'aggregate_sig': ['uint256'], } @@ -61,31 +70,27 @@ To validate a block header on shard `shard_id`, compute as follows: * Verify that `beacon_chain_ref` is the hash of a block in the beacon chain with slot less than or equal to `slot`. Verify that `beacon_chain_ref` is equal to or a descendant of the `beacon_chain_ref` specified in the `ShardBlock` pointed to by `parent_hash`. * Let `state` be the state of the beacon chain block referred to by `beacon_chain_ref`. Let `validators` be `[validators[i] for i in state.current_persistent_committees[shard_id]]`. * Assert `len(attester_bitfield) == ceil_div8(len(validators))` -* Let `curblock_proposer_index = hash(state.randao_mix + bytes8(shard_id) + bytes8(slot)) % len(validators)`. Let `parent_proposer_index` be the same value calculated for the parent block. -* Make sure that the `parent_proposer_index`'th bit in the `attester_bitfield` is set to 1. -* Generate the group public key by adding the public keys of all the validators for whom the corresponding position in the bitfield is set to 1. Verify the `aggregate_sig` using this as the pubkey and the `parent_hash` as the message. +* Let `proposer_index = hash(state.randao_mix + bytes8(shard_id) + bytes8(slot)) % len(validators)`. Let `msg` be the block but with the `block.signature` set to `[0, 0]`. Verify that `BLSVerify(pub=validators[proposer_index].pubkey, msg=hash(msg), sig=block.signature, domain=get_domain(state, slot, SHARD_PROPOSER_DOMAIN))` passes. +* Generate the `group_public_key` by adding the public keys of all the validators for whom the corresponding position in the bitfield is set to 1. Verify that `BLSVerify(pub=group_public_key, msg=parent_hash, sig=block.aggregate_sig, domain=get_domain(state, slot, SHARD_ATTESTER_DOMAIN))` passes. + +### Block Merklization heper + +```python +def merkle_root(block_body): + assert len(block_body) == SHARD_BLOCK_SIZE + chunks = SHARD_BLOCK_SIZE // CHUNK_SIZE + o = [0] * chunks + [block_body[i * CHUNK_SIZE: (i+1) * CHUNK_SIZE] for i in range(chunks)] + for i in range(chunks-1, 0, -1): + o[i] = hash(o[i*2] + o[i*2+1]) + return o[1] +``` ### Verifying shard block data -At network layer, we expect a shard block header to be broadcast along with its `block_body`. First, we define a helper function that takes as input beacon chain state and outputs the max block size in bytes: +At network layer, we expect a shard block header to be broadcast along with its `block_body`. -```python -def shard_block_maxbytes(state): - max_grains = MAX_SHARD_BLOCK_SIZE // CHUNK_SIZE - validators_at_target_committee_size = SHARD_COUNT * TARGET_COMMITTEE_SIZE - - # number of grains per block is proportional to the number of validators - # up until `validators_at_target_committee_size` - grains = min( - len(get_active_validator_indices(state.validators)) * max_grains // validators_at_target_committee_size, - max_grains - ) - - return CHUNK_SIZE * grains -``` - -* Verify that `len(block_body) == shard_block_maxbytes(state)` -* Define `filler_bytes = next_power_of_2(len(block_body)) - len(block_body)`. Compute a simple binary Merkle tree of `block_body + bytes([0] * filler_bytes)` and verify that the root equals the `data_root` in the header. +* Verify that `len(block_body) == SHARD_BLOCK_SIZE` +* Verify that `merkle_root(block_body)` equals the `data_root` in the header. ### Verifying a crosslink @@ -93,23 +98,13 @@ A node should sign a crosslink only if the following conditions hold. **If a nod First, the conditions must recursively apply to the crosslink referenced in `last_crosslink_hash` for the same shard (unless `last_crosslink_hash` equals zero, in which case we are at the genesis). -Second, we verify the `shard_block_combined_data_root`. Let `h` be the slot _immediately after_ the slot of the shard block included by the last crosslink, and `h+n-1` be the slot number of the block directly referenced by the current `shard_block_hash`. Let `B[i]` be the block at slot `h+i` in the shard chain. Let `bodies[0] .... bodies[n-1]` be the bodies of these blocks and `roots[0] ... roots[n-1]` the data roots. If there is a missing slot in the shard chain at position `h+i`, then `bodies[i] == b'\x00' * shard_block_maxbytes(state[i])` and `roots[i]` be the Merkle root of the empty data. Define `compute_merkle_root` be a simple Merkle root calculating function that takes as input a list of objects, where the list's length must be an exact power of two. Let `state[i]` be the beacon chain state at height `h+i` (if the beacon chain is missing a block at some slot, the state is unchanged), and `depths[i]` be equal to `log2(next_power_of_2(shard_block_maxbytes(state[i]) // CHUNK_SIZE))` (ie. the expected depth of the i'th data tree). We define the function for computing the combined data root as follows: +Second, we verify the `shard_block_combined_data_root`. Let `h` be the slot _immediately after_ the slot of the shard block included by the last crosslink, and `h+n-1` be the slot number of the block directly referenced by the current `shard_block_hash`. Let `B[i]` be the block at slot `h+i` in the shard chain. Let `bodies[0] .... bodies[n-1]` be the bodies of these blocks and `roots[0] ... roots[n-1]` the data roots. If there is a missing slot in the shard chain at position `h+i`, then `bodies[i] == b'\x00' * shard_block_maxbytes(state[i])` and `roots[i]` be the Merkle root of the empty data. Define `compute_merkle_root` be a simple Merkle root calculating function that takes as input a list of objects, where the list's length must be an exact power of two. We define the function for computing the combined data root as follows: ```python -def get_zeroroot_at_depth(n): - o = b'\x00' * CHUNK_SIZE - for i in range(n): - o = hash(o + o) - return o +ZERO_ROOT = merkle_root(bytes([0] * SHARD_BLOCK_SIZE)) -def mk_combined_data_root(depths, roots): - default_value = get_zeroroot_at_depth(max(depths)) - data = [default_value for _ in range(next_power_of_2(len(roots)))] - for i, (depth, root) in enumerate(zip(depths, roots)): - value = root - for j in range(depth, max(depths)): - value = hash(value, get_zeroroot_at_depth(depth + j)) - data[i] = value +def mk_combined_data_root(roots): + data = roots + [ZERO_ROOT for _ in range(len(roots), next_power_of_2(len(roots)))] return compute_merkle_root(data) ``` @@ -117,12 +112,7 @@ This outputs the root of a tree of the data roots, with the data roots all adjus ```python def mk_combined_data_root(depths, bodies): - default_value = get_zeroroot_at_depth(max(depths)) - padded_body_length = max([CHUNK_SIZE * 2**d for d in depths]) - data = b'' - for body in bodies: - padded_body = body + bytes([0] * (padded_body_length - len(body))) - data += padded_body + data = b''.join(bodies) data += bytes([0] * (next_power_of_2(len(data)) - len(data)) return compute_merkle_root([data[pos:pos+CHUNK_SIZE] for pos in range(0, len(data), CHUNK_SIZE)]) ``` From 16d05b353a86f787e2c1dcf0bcdca8fd7463c942 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 25 Nov 2018 09:54:29 -0600 Subject: [PATCH 11/20] remove `GENESIS_TIME` from constants This constant has been superseded by `BeaconState.genesis_time` --- specs/core/0_beacon-chain.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 81a4ebdeb..376cb5c66 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -38,7 +38,6 @@ The primary source of load on the beacon chain are "attestations". Attestations | `GWEI_PER_ETH` | 10**9 | Gwei/ETH | | `DEPOSIT_CONTRACT_ADDRESS` | **TBD** | - | | `TARGET_COMMITTEE_SIZE` | 2**8 (= 256) | validators | -| `GENESIS_TIME` | **TBD** | seconds | | `SLOT_DURATION` | 6 | seconds | | `CYCLE_LENGTH` | 2**6 (= 64) | slots | ~6 minutes | | `MIN_VALIDATOR_SET_CHANGE_INTERVAL` | 2**8 (= 256) | slots | ~25 minutes | From e7110767234cc5aaea356c61c1877ef46b8156a3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 25 Nov 2018 10:02:43 -0600 Subject: [PATCH 12/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 376cb5c66..e5c2da3bb 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -343,7 +343,7 @@ For a block on the beacon chain to be processed by a node, four conditions have * The parent pointed to by the `ancestor_hashes[0]` has already been processed and accepted * An attestation from the _proposer_ of the block (see later for definition) is included along with the block in the network message object * The PoW chain block pointed to by the `processed_pow_receipt_root` has already been processed and accepted -* The node's local clock time is greater than or equal to the minimum timestamp as computed by `GENESIS_TIME + block.slot * SLOT_DURATION` +* The node's local clock time is greater than or equal to the minimum timestamp as computed by `state.genesis_time + block.slot * SLOT_DURATION` If these conditions are not met, the client should delay processing the beacon block until the conditions are all satisfied. From c0aa43804bf1b07f45fabc41769e0237b5e6e16d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 25 Nov 2018 10:12:48 -0600 Subject: [PATCH 13/20] block proposer sig to uint384 --- specs/core/0_beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e5c2da3bb..611aa8ce6 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -129,7 +129,7 @@ A `BeaconBlock` has the following fields: # Specials (e.g. logouts, penalties) 'specials': [SpecialRecord], # Proposer signature - 'proposer_signature': ['uint256'], + 'proposer_signature': ['uint384'], } ``` @@ -900,9 +900,9 @@ For each validator index `v` in `intersection`, if `state.validators[v].status` { 'proposer_index': 'uint24', 'proposal1_data': ProposalSignedData, - 'proposal1_signature': '[uint256]', + 'proposal1_signature': '[uint384]', 'proposal2_data': ProposalSignedData, - 'proposal1_signature': '[uint256]', + 'proposal1_signature': '[uint384]', } ``` For each `proposal_signature`, verify that `BLSVerify(pubkey=validators[proposer_index].pubkey, msg=hash(proposal_data), sig=proposal_signature, domain=get_domain(state, proposal_data.slot, DOMAIN_PROPOSAL))` passes. Verify that `proposal1_data.slot == proposal2_data.slot` but `proposal1 != proposal2`. If `state.validators[proposer_index].status` does not equal `PENALIZED`, then run `exit_validator(proposer_index, state, penalize=True, current_slot=block.slot)` From e539de99225ffcd41cc30cade5f3790d552ba408 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 25 Nov 2018 10:37:52 -0600 Subject: [PATCH 14/20] remove fork_version from ProposalSignedData --- specs/core/0_beacon-chain.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 611aa8ce6..47178dc4b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -164,8 +164,6 @@ A `ProposalSignedData` has the following fields: ```python { - # Fork version - 'fork_version': 'uint64', # Slot number 'slot': 'uint64', # Shard number (or `2**64 - 1` for beacon chain) @@ -835,7 +833,7 @@ Extend the list of `AttestationRecord` objects in the `state` with those include ### Verify proposer signature -Let `proposal_hash = hash(ProposalSignedData(fork_version, block.slot, 2**64 - 1, block_hash_without_sig))` where `block_hash_without_sig` is the hash of the block except setting `proposer_signature` to `[0, 0]`. +Let `proposal_hash = hash(ProposalSignedData(block.slot, 2**64 - 1, block_hash_without_sig))` where `block_hash_without_sig` is the hash of the block except setting `proposer_signature` to `[0, 0]`. Verify that `BLSVerify(pubkey=get_beacon_proposer(state, block.slot).pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state, block.slot, DOMAIN_PROPOSAL))` passes. From 2a7103d1f67f6e25fce3f59601caba4d8e6a8ff3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 25 Nov 2018 10:41:59 -0600 Subject: [PATCH 15/20] change SpecialRecord.kind to uint64 --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 611aa8ce6..634a1f7e5 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -201,7 +201,7 @@ A `SpecialRecord` has the following fields: ```python { # Kind - 'kind': 'uint8', + 'kind': 'uint64', # Data 'data': 'bytes' } From 6c660de8ff3c424c96cb260a15e7dfc611e7d412 Mon Sep 17 00:00:00 2001 From: chainsafe Date: Sun, 25 Nov 2018 12:03:27 -0500 Subject: [PATCH 16/20] updated spelling --- 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 18341265e..59a6f6c1a 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -73,7 +73,7 @@ To validate a block header on shard `shard_id`, compute as follows: * Let `proposer_index = hash(state.randao_mix + bytes8(shard_id) + bytes8(slot)) % len(validators)`. Let `msg` be the block but with the `block.signature` set to `[0, 0]`. Verify that `BLSVerify(pub=validators[proposer_index].pubkey, msg=hash(msg), sig=block.signature, domain=get_domain(state, slot, SHARD_PROPOSER_DOMAIN))` passes. * Generate the `group_public_key` by adding the public keys of all the validators for whom the corresponding position in the bitfield is set to 1. Verify that `BLSVerify(pub=group_public_key, msg=parent_hash, sig=block.aggregate_sig, domain=get_domain(state, slot, SHARD_ATTESTER_DOMAIN))` passes. -### Block Merklization heper +### Block Merklization helper ```python def merkle_root(block_body): From 13866e3e049629738413559324c625fe22a53cc8 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 25 Nov 2018 18:19:22 +0000 Subject: [PATCH 17/20] Remove fork_version for DOMAIN_LOGOUT Also remove `LOGOUT_MESSAGE ` --- specs/core/0_beacon-chain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 47178dc4b..dfead4820 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -54,7 +54,6 @@ The primary source of load on the beacon chain are "attestations". Attestations | `BASE_REWARD_QUOTIENT` | 2**15 (= 32,768) | — | | `MAX_VALIDATOR_CHURN_QUOTIENT` | 2**5 (= 32) | — | | `POW_CONTRACT_MERKLE_TREE_DEPTH` | 2**5 (= 32) | - | -| `LOGOUT_MESSAGE` | `"LOGOUT"` | — | | `INITIAL_FORK_VERSION` | 0 | — | **Notes** @@ -864,7 +863,7 @@ For each `SpecialRecord` `obj` in `block.specials`, verify that its `kind` is on ``` Perform the following checks: -* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=data.signature, domain=get_domain(state, current_slot, DOMAIN_LOGOUT))` +* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg="", sig=data.signature, domain=get_domain(state, current_slot, DOMAIN_LOGOUT))` * Verify that `validators[validator_index].status == ACTIVE`. * Verify that `block.slot >= last_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD` From 2902170b261451ac33fc98fe1f19d6d541a0477f Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sun, 25 Nov 2018 22:22:15 -0500 Subject: [PATCH 18/20] Reduce width of vyper code --- specs/core/0_beacon-chain.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 47178dc4b..c4fe82c27 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -362,10 +362,13 @@ The beacon chain fork choice rule is a hybrid that combines justification and fi ```python def lmd_ghost(store, start): validators = start.state.validators - active_validators = [validators[i] for i in get_active_validator_indices(validators, start.slot)] - attestation_targets = [get_latest_attestation_target(store, validator) for validator in active_validators] + active_validators = [validators[i] for i in + get_active_validator_indices(validators, start.slot)] + attestation_targets = [get_latest_attestation_target(store, validator) + for validator in active_validators] def get_vote_count(block): - return len([target for target in attestation_targets if get_ancestor(store, target, block.slot) == block]) + return len([target for target in attestation_targets if + get_ancestor(store, target, block.slot) == block]) head = start while 1: From 6a7b280d4908c29dc3c962ff75487e2e0d2e29e4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 26 Nov 2018 10:45:39 +0000 Subject: [PATCH 19/20] Update specs/core/0_beacon-chain.md Co-Authored-By: JustinDrake --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index dfead4820..27ada3007 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -863,7 +863,7 @@ For each `SpecialRecord` `obj` in `block.specials`, verify that its `kind` is on ``` Perform the following checks: -* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg="", sig=data.signature, domain=get_domain(state, current_slot, DOMAIN_LOGOUT))` +* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=bytes([0] * 32), sig=data.signature, domain=get_domain(state, current_slot, DOMAIN_LOGOUT))` * Verify that `validators[validator_index].status == ACTIVE`. * Verify that `block.slot >= last_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD` From 1bf04c8c17b8a8696fe0c90a82429a6ecd12f10c Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 26 Nov 2018 10:47:32 +0000 Subject: [PATCH 20/20] Make status uint64 --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 634a1f7e5..e8419fde0 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -274,7 +274,7 @@ A `ValidatorRecord` has the following fields: # Balance in Gwei 'balance': 'uint64', # Status code - 'status': 'uint8', + 'status': 'uint64', # Slot when validator last changed status (or 0) 'last_status_change_slot': 'uint64' # Sequence number when validator exited (or 0)