From b6be9e1830f9fff278c2d247cfce3d843be8e8f7 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sun, 31 Mar 2019 04:55:24 -0500 Subject: [PATCH 1/6] Possible aesthetic rework to get_domain In general I dislike how domains, which should be an unobtrusive out-of-the-way thing that we don't think about much, are taking up so much space in code to express, to the point of them being the single thing preventing `bls_verify` from being expressed in one line of code. Here I reorder arguments and add a default, and make `bls_verify` a one-liner. Not necessarily convinced that exactly this approach is the way to go, but IMO it's worth considering. --- specs/core/0_beacon-chain.md | 60 +++++++++--------------------------- 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index d08828692..6e2ebbcba 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1121,13 +1121,14 @@ def get_fork_version(fork: Fork, ### `get_domain` ```python -def get_domain(fork: Fork, - epoch: Epoch, - domain_type: int) -> int: +def get_domain(state: BeaconState, + domain_type: int, + epoch=None: int) -> int: """ Get the domain number that represents the fork meta and signature domain. """ - return bytes_to_int(get_fork_version(fork, epoch) + int_to_bytes4(domain_type)) + epoch_of_message = get_current_epoch(state) if epoch is None else epoch + return bytes_to_int(get_fork_version(fork, epoch_of_message) + int_to_bytes4(domain_type)) ``` ### `get_bitfield_bit` @@ -1210,7 +1211,7 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)), ], signature=indexed_attestation.aggregate_signature, - domain=get_domain(state.fork, slot_to_epoch(indexed_attestation.data.slot), DOMAIN_ATTESTATION), + domain=get_domain(state, DOMAIN_ATTESTATION, slot_to_epoch(indexed_attestation.data.slot)), ) ``` @@ -1316,17 +1317,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: if pubkey not in validator_pubkeys: # Verify the proof of possession - proof_is_valid = bls_verify( - pubkey=pubkey, - message_hash=signed_root(deposit.data), - signature=deposit.data.proof_of_possession, - domain=get_domain( - state.fork, - get_current_epoch(state), - DOMAIN_DEPOSIT, - ) - ) - if not proof_is_valid: + if not bls_verify(pubkey, signed_root(deposit.data), deposit.data.proof_of_possession, get_domain(state, DOMAIN_DEPOSIT)): return # Add new validator @@ -2194,12 +2185,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)] assert not proposer.slashed # Verify proposer signature - assert bls_verify( - pubkey=proposer.pubkey, - message_hash=signed_root(block), - signature=block.signature, - domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_BEACON_BLOCK) - ) + assert bls_verify(proposer.pubkey, signed_root(block), block.signature, get_domain(state, DOMAIN_BEACON_BLOCK)) ``` #### RANDAO @@ -2208,12 +2194,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: def process_randao(state: BeaconState, block: BeaconBlock) -> None: proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)] # Verify that the provided randao value is valid - assert bls_verify( - pubkey=proposer.pubkey, - message_hash=hash_tree_root(get_current_epoch(state)), - signature=block.body.randao_reveal, - domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO) - ) + assert bls_verify(proposer.pubkey, hash_tree_root(get_current_epoch(state)), block.body.randao_reveal, get_domain(state, DOMAIN_RANDAO)) # Mix it in state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = ( xor(get_randao_mix(state, get_current_epoch(state)), @@ -2258,12 +2239,8 @@ def process_proposer_slashing(state: BeaconState, assert is_slashable_validator(proposer, get_current_epoch(state)) # Signatures are valid for header in (proposer_slashing.header_1, proposer_slashing.header_2): - assert bls_verify( - pubkey=proposer.pubkey, - message_hash=signed_root(header), - signature=header.signature, - domain=get_domain(state.fork, slot_to_epoch(header.slot), DOMAIN_BEACON_BLOCK) - ) + domain = get_domain(state, DOMAIN_BEACON_BLOCK, slot_to_epoch(header.slot)) + assert bls_verify(proposer.pubkey, signed_root(header), header.signature, domain) slash_validator(state, proposer_slashing.proposer_index) ``` @@ -2382,12 +2359,8 @@ def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None: # Verify the validator has been active long enough assert get_current_epoch(state) - validator.activation_epoch >= PERSISTENT_COMMITTEE_PERIOD # Verify signature - assert bls_verify( - pubkey=validator.pubkey, - message_hash=signed_root(exit), - signature=exit.signature, - domain=get_domain(state.fork, exit.epoch, DOMAIN_VOLUNTARY_EXIT) - ) + domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, exit.epoch) + assert bls_verify(validator.pubkey, signed_root(exit), exit.signature, domain) # Initiate exit initiate_validator_exit(state, exit.validator_index) ``` @@ -2427,12 +2400,7 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer.pubkey)[1:] ) # Verify that the signature is valid - assert bls_verify( - pubkey=transfer.pubkey, - message_hash=signed_root(transfer), - signature=transfer.signature, - domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER) - ) + assert bls_verify(transfer.pubkey, signed_root(transfer), transfer.signature, get_domain(state, DOMAIN_TRANSFER)) # Process the transfer decrease_balance(state, transfer.sender, transfer.amount + transfer.fee) increase_balance(state, transfer.recipient, transfer.amount) From 79d1f9fb76ae0c82a9e34159ddca63b59ab00fc6 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 31 Mar 2019 20:45:57 +0400 Subject: [PATCH 2/6] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 6e2ebbcba..49bbe4d9a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -396,7 +396,7 @@ The types are defined topologically to aid in facilitating an executable version # Amount in Gwei 'amount': 'uint64', # Container self-signature - 'proof_of_possession': 'bytes96', + 'signature': 'bytes96', } ``` @@ -1123,7 +1123,7 @@ def get_fork_version(fork: Fork, ```python def get_domain(state: BeaconState, domain_type: int, - epoch=None: int) -> int: + epoch: int=None) -> int: """ Get the domain number that represents the fork meta and signature domain. """ @@ -1316,8 +1316,8 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: amount = deposit.data.amount if pubkey not in validator_pubkeys: - # Verify the proof of possession - if not bls_verify(pubkey, signed_root(deposit.data), deposit.data.proof_of_possession, get_domain(state, DOMAIN_DEPOSIT)): + # Verify the deposit signature (proof of possession) + if not bls_verify(pubkey, signed_root(deposit.data), deposit.data.signature, get_domain(state, DOMAIN_DEPOSIT)): return # Add new validator From c2edcebee3dedf6f6b143cfce9a8c03f6654b6be Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 31 Mar 2019 20:48:44 +0400 Subject: [PATCH 3/6] Update 0_beacon-chain.md --- 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 49bbe4d9a..0330ef1f0 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -277,7 +277,7 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | Name | Value | | - | - | -| `DOMAIN_BEACON_BLOCK` | `0` | +| `DOMAIN_BEACON_PROPOSER` | `0` | | `DOMAIN_RANDAO` | `1` | | `DOMAIN_ATTESTATION` | `2` | | `DOMAIN_DEPOSIT` | `3` | @@ -2185,7 +2185,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)] assert not proposer.slashed # Verify proposer signature - assert bls_verify(proposer.pubkey, signed_root(block), block.signature, get_domain(state, DOMAIN_BEACON_BLOCK)) + assert bls_verify(proposer.pubkey, signed_root(block), block.signature, get_domain(state, DOMAIN_BEACON_PROPOSER)) ``` #### RANDAO @@ -2239,7 +2239,7 @@ def process_proposer_slashing(state: BeaconState, assert is_slashable_validator(proposer, get_current_epoch(state)) # Signatures are valid for header in (proposer_slashing.header_1, proposer_slashing.header_2): - domain = get_domain(state, DOMAIN_BEACON_BLOCK, slot_to_epoch(header.slot)) + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, slot_to_epoch(header.slot)) assert bls_verify(proposer.pubkey, signed_root(header), header.signature, domain) slash_validator(state, proposer_slashing.proposer_index) ``` From 37fc79cb94cc26959f0cb96691583cf4569f08ea Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 2 Apr 2019 22:30:26 +0400 Subject: [PATCH 4/6] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 0330ef1f0..094e7e49d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -83,7 +83,6 @@ - [`bytes_to_int`](#bytes_to_int) - [`get_effective_balance`](#get_effective_balance) - [`get_total_balance`](#get_total_balance) - - [`get_fork_version`](#get_fork_version) - [`get_domain`](#get_domain) - [`get_bitfield_bit`](#get_bitfield_bit) - [`verify_bitfield`](#verify_bitfield) @@ -1104,31 +1103,18 @@ def get_total_balance(state: BeaconState, validators: List[ValidatorIndex]) -> G return sum([get_effective_balance(state, i) for i in validators]) ``` -### `get_fork_version` - -```python -def get_fork_version(fork: Fork, - epoch: Epoch) -> bytes: - """ - Return the fork version of the given ``epoch``. - """ - if epoch < fork.epoch: - return fork.previous_version - else: - return fork.current_version -``` - ### `get_domain` ```python def get_domain(state: BeaconState, domain_type: int, - epoch: int=None) -> int: + message_epoch: int=None) -> int: """ - Get the domain number that represents the fork meta and signature domain. + Return the signature domain (fork version concatenated with domain type) of a message. """ - epoch_of_message = get_current_epoch(state) if epoch is None else epoch - return bytes_to_int(get_fork_version(fork, epoch_of_message) + int_to_bytes4(domain_type)) + epoch = get_current_epoch(state) if message_epoch is None else message_epoch + fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version + return bytes_to_int(fork_version + int_to_bytes4(domain_type)) ``` ### `get_bitfield_bit` From dd520c6162a3a9efc72923ea7d89bf9acf338ce3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 3 Apr 2019 11:22:14 +1100 Subject: [PATCH 5/6] fix tests for get_domain PR --- tests/phase0/helpers.py | 26 ++++++++++++-------------- tests/phase0/test_sanity.py | 6 ++---- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index 33f394def..6a7ffd5dd 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -50,7 +50,7 @@ pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkey def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=None): if not deposit_data_leaves: deposit_data_leaves = [] - proof_of_possession = b'\x33' * 96 + signature = b'\x33' * 96 deposit_data_list = [] for i in range(num_validators): @@ -60,7 +60,7 @@ def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=N # insecurely use pubkey as withdrawal key as well withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:], amount=spec.MAX_DEPOSIT_AMOUNT, - proof_of_possession=proof_of_possession, + signature=signature, ) item = hash(deposit_data.serialize()) deposit_data_leaves.append(item) @@ -120,18 +120,17 @@ def build_deposit_data(state, pubkey, privkey, amount): # insecurely use pubkey as withdrawal key as well withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:], amount=amount, - proof_of_possession=EMPTY_SIGNATURE, + signature=EMPTY_SIGNATURE, ) - proof_of_possession = bls.sign( + signature = bls.sign( message_hash=signed_root(deposit_data), privkey=privkey, domain=get_domain( - state.fork, - get_current_epoch(state), + state, spec.DOMAIN_DEPOSIT, ) ) - deposit_data.proof_of_possession = proof_of_possession + deposit_data.signature = signature return deposit_data @@ -173,9 +172,9 @@ def build_voluntary_exit(state, epoch, validator_index, privkey): message_hash=signed_root(voluntary_exit), privkey=privkey, domain=get_domain( - fork=state.fork, - epoch=epoch, + state=state, domain_type=spec.DOMAIN_VOLUNTARY_EXIT, + message_epoch=epoch, ) ) @@ -224,9 +223,8 @@ def get_valid_proposer_slashing(state): header_2.slot = slot + 1 domain = get_domain( - fork=state.fork, - epoch=get_current_epoch(state), - domain_type=spec.DOMAIN_BEACON_BLOCK, + state=state, + domain_type=spec.DOMAIN_BEACON_PROPOSER, ) header_1.signature = bls.sign( message_hash=signed_root(header_1), @@ -307,8 +305,8 @@ def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0) message_hash=message_hash, privkey=privkey, domain=get_domain( - fork=state.fork, - epoch=slot_to_epoch(attestation_data.slot), + state=state, domain_type=spec.DOMAIN_ATTESTATION, + message_epoch=slot_to_epoch(attestation_data.slot), ) ) diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index 90825242f..b86187ec8 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -303,8 +303,7 @@ def test_voluntary_exit(state): message_hash=signed_root(voluntary_exit), privkey=privkeys[validator_index], domain=get_domain( - fork=pre_state.fork, - epoch=get_current_epoch(pre_state), + state=pre_state, domain_type=spec.DOMAIN_VOLUNTARY_EXIT, ) ) @@ -390,8 +389,7 @@ def test_transfer(state): message_hash=signed_root(transfer), privkey=transfer_privkey, domain=get_domain( - fork=pre_state.fork, - epoch=get_current_epoch(pre_state), + state=pre_state, domain_type=spec.DOMAIN_TRANSFER, ) ) From 24492aa36f51d2a3ee9fa07a057be5454c3be656 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 17 Apr 2019 10:16:01 +1000 Subject: [PATCH 6/6] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 0104a16ae..dc359b056 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -379,7 +379,7 @@ The types are defined topologically to aid in facilitating an executable version # Attestation data 'data': AttestationData, # Aggregate signature - 'aggregate_signature': 'bytes96', + 'signature': 'bytes96', } ``` @@ -495,7 +495,7 @@ The types are defined topologically to aid in facilitating an executable version # Custody bitfield 'custody_bitfield': 'bytes', # BLS aggregate signature - 'aggregate_signature': 'bytes96', + 'signature': 'bytes96', } ``` @@ -1159,7 +1159,7 @@ def convert_to_indexed(state: BeaconState, attestation: Attestation): custody_bit_0_indices=custody_bit_0_indices, custody_bit_1_indices=custody_bit_1_indices, data=attestation.data, - aggregate_signature=attestation.aggregate_signature + signature=attestation.signature ) ``` @@ -1198,7 +1198,7 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b0)), hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)), ], - signature=indexed_attestation.aggregate_signature, + signature=indexed_attestation.signature, domain=get_domain(state, DOMAIN_ATTESTATION, slot_to_epoch(indexed_attestation.data.slot)), ) ```