def _validate_voluntary_exit_signature( state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit, validator: Validator, slots_per_epoch: int, ) -> None: voluntary_exit = signed_voluntary_exit.message domain = get_domain( state, SignatureDomain.DOMAIN_VOLUNTARY_EXIT, slots_per_epoch, voluntary_exit.epoch, ) try: bls.validate( pubkey=validator.pubkey, message_hash=voluntary_exit.hash_tree_root, signature=signed_voluntary_exit.signature, domain=domain, ) except SignatureError as error: raise ValidationError( f"Invalid VoluntaryExit signature, validator_index={voluntary_exit.validator_index}", error, )
def validate_proposer_signature(state: BeaconState, block: BaseBeaconBlock, beacon_chain_shard_number: Shard, committee_config: CommitteeConfig) -> None: # TODO: Replace this with real signed_root message_hash = block.signed_root # Get the public key of proposer beacon_proposer_index = get_beacon_proposer_index( state, state.slot, committee_config, ) proposer_pubkey = state.validator_registry[beacon_proposer_index].pubkey domain = get_domain( state.fork, state.current_epoch(committee_config.SLOTS_PER_EPOCH), SignatureDomain.DOMAIN_BEACON_BLOCK ) is_valid_signature = bls.verify( pubkey=proposer_pubkey, message_hash=message_hash, signature=block.signature, domain=domain, ) if not is_valid_signature: raise ValidationError( f"Invalid Proposer Signature on block, beacon_proposer_index={beacon_proposer_index}, " f"pubkey={proposer_pubkey}, message_hash={message_hash}, " f"block.signature={block.signature}, domain={domain}" )
def verify_slashable_attestation_signature( state: BeaconState, slashable_attestation: SlashableAttestation, slots_per_epoch: int) -> bool: """ Ensure we have a valid aggregate signature for the ``slashable_attestation``. """ all_indices = slashable_attestation.custody_bit_indices pubkeys: Tuple[BLSPubkey, ...] = generate_aggregate_pubkeys_from_indices( state.validator_registry, *all_indices, ) message_hashes: Tuple[Hash32, ...] = slashable_attestation.message_hashes signature = slashable_attestation.aggregate_signature domain = get_domain( state.fork, slot_to_epoch(slashable_attestation.data.slot, slots_per_epoch), SignatureDomain.DOMAIN_ATTESTATION, ) # No custody bit 1 indice votes in phase 0, so we only need to process custody bit 0 # for efficiency. # TODO: to be removed in phase 1. if len(all_indices[1]) == 0: pubkeys = pubkeys[:1] message_hashes = message_hashes[:1] return bls.verify_multiple( pubkeys=pubkeys, message_hashes=message_hashes, signature=signature, domain=domain, )
def create_block_on_state( *, state: BeaconState, config: BeaconConfig, state_machine: BaseBeaconStateMachine, block_class: BaseBeaconBlock, parent_block: BaseBeaconBlock, slot: SlotNumber, validator_index: ValidatorIndex, privkey: int, attestations: Sequence[Attestation], check_proposer_index: bool = True) -> BaseBeaconBlock: """ Create a beacon block with the given parameters. """ # Check proposer if check_proposer_index: validate_proposer_index(state, config, slot, validator_index) # Prepare block: slot and parent_root block = block_class.from_parent( parent_block=parent_block, block_params=FromBlockParams(slot=slot), ) # TODO: Add more operations randao_reveal = ZERO_HASH32 eth1_data = Eth1Data.create_empty_data() body = BeaconBlockBody.create_empty_body().copy( attestations=attestations, ) block = block.copy( randao_reveal=randao_reveal, eth1_data=eth1_data, body=body, ) # Apply state transition to get state root state, block = state_machine.import_block(block, check_proposer_signature=True) # Sign empty_signature_block_root = block.block_without_signature_root proposal_root = ProposalSignedData( slot, config.BEACON_CHAIN_SHARD_NUMBER, empty_signature_block_root, ).root domain = get_domain( state.fork, slot_to_epoch(slot, config.EPOCH_LENGTH), SignatureDomain.DOMAIN_PROPOSAL, ) block = block.copy(signature=bls.sign( message=proposal_root, privkey=privkey, domain=domain, ), ) return block
def validate_proposer_signature(state: BeaconState, block: BaseBeaconBlock, committee_config: CommitteeConfig) -> None: message_hash = block.signing_root # Get the public key of proposer beacon_proposer_index = get_beacon_proposer_index( state, committee_config, ) proposer_pubkey = state.validators[beacon_proposer_index].pubkey domain = get_domain( state, SignatureDomain.DOMAIN_BEACON_PROPOSER, committee_config.SLOTS_PER_EPOCH, ) try: bls.validate( pubkey=proposer_pubkey, message_hash=message_hash, signature=block.signature, domain=domain, ) except SignatureError as error: raise ValidationError( f"Invalid Proposer Signature on block, beacon_proposer_index={beacon_proposer_index}", error, )
def validate_proof_of_possession(state: BeaconState, pubkey: BLSPubkey, proof_of_possession: BLSSignature, withdrawal_credentials: Hash32, randao_commitment: Hash32, custody_commitment: Hash32) -> None: deposit_input = DepositInput( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, proof_of_possession=EMPTY_SIGNATURE, ) is_valid_signature = bls.verify( pubkey=pubkey, # TODO: change to hash_tree_root(deposit_input) when we have SSZ tree hashing message=deposit_input.root, signature=proof_of_possession, domain=get_domain( state.fork_data, state.slot, SignatureDomain.DOMAIN_DEPOSIT, ), ) if not is_valid_signature: raise ValidationError("BLS signature verification error")
def test_randao_processing_validates_randao_reveal( sample_beacon_block_params, sample_beacon_block_body_params, sample_beacon_state_params, sample_fork_params, keymap, config, ): proposer_pubkey, proposer_privkey = first(keymap.items()) state = SerenityBeaconState.create(**sample_beacon_state_params).mset( "validators", tuple( create_mock_validator(proposer_pubkey, config) for _ in range(config.TARGET_COMMITTEE_SIZE)), "balances", (config.MAX_EFFECTIVE_BALANCE, ) * config.TARGET_COMMITTEE_SIZE, "randao_mixes", tuple(ZERO_HASH32 for _ in range(config.EPOCHS_PER_HISTORICAL_VECTOR)), ) epoch = state.current_epoch(config.SLOTS_PER_EPOCH) message_hash = (epoch + 1).to_bytes(32, byteorder="little") domain = get_domain(state, SignatureDomain.DOMAIN_RANDAO, config.SLOTS_PER_EPOCH) randao_reveal = bls.sign(message_hash, proposer_privkey, domain) block_body = BeaconBlockBody.create(**sample_beacon_block_body_params).set( "randao_reveal", randao_reveal) block = SerenityBeaconBlock.create(**sample_beacon_block_params).set( "body", block_body) with pytest.raises(ValidationError): process_randao(state, block, config)
def validate_indexed_attestation_aggregate_signature( state: BeaconState, indexed_attestation: IndexedAttestation, slots_per_epoch: int) -> None: bit_0_indices = indexed_attestation.custody_bit_0_indices bit_1_indices = indexed_attestation.custody_bit_1_indices pubkeys = ( bls.aggregate_pubkeys( tuple(state.validators[i].pubkey for i in bit_0_indices)), bls.aggregate_pubkeys( tuple(state.validators[i].pubkey for i in bit_1_indices)), ) message_hashes = ( AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=False).hash_tree_root, AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=True).hash_tree_root, ) domain = get_domain( state, SignatureDomain.DOMAIN_ATTESTATION, slots_per_epoch, indexed_attestation.data.target.epoch, ) bls.validate_multiple( pubkeys=pubkeys, message_hashes=message_hashes, signature=indexed_attestation.signature, domain=domain, )
def test_randao_reveal_validation( is_valid, epoch, expected_epoch, proposer_key_index, expected_proposer_key_index, privkeys, pubkeys, sample_fork_params, genesis_state, config, ): state = genesis_state.set( "slot", compute_start_slot_at_epoch(epoch, config.SLOTS_PER_EPOCH)) slots_per_epoch = config.SLOTS_PER_EPOCH domain = get_domain(state, SignatureDomain.DOMAIN_RANDAO, slots_per_epoch) signing_root = compute_signing_root(SerializableUint64(epoch), domain) proposer_privkey = privkeys[proposer_key_index] randao_reveal = bls.sign(proposer_privkey, signing_root) try: validate_randao_reveal( state=state, proposer_index=expected_proposer_key_index, epoch=expected_epoch, randao_reveal=randao_reveal, slots_per_epoch=slots_per_epoch, ) except ValidationError: if is_valid: raise else: if not is_valid: pytest.fail("Did not raise")
def sign_proof_of_possession(deposit_input, privkey, fork_data, slot): domain = get_domain( fork_data, slot, SignatureDomain.DOMAIN_DEPOSIT, ) return bls.sign(deposit_input.root, privkey, domain)
def test_randao_processing(sample_beacon_block_params, sample_beacon_state_params, sample_fork_params, keymap, config): proposer_pubkey, proposer_privkey = first(keymap.items()) state = SerenityBeaconState(**sample_beacon_state_params).copy( validator_registry=tuple( mock_validator_record(proposer_pubkey) for _ in range(config.TARGET_COMMITTEE_SIZE)), validator_balances=(config.MAX_DEPOSIT_AMOUNT, ) * config.TARGET_COMMITTEE_SIZE, latest_randao_mixes=tuple( ZERO_HASH32 for _ in range(config.LATEST_RANDAO_MIXES_LENGTH)), ) epoch = state.current_epoch(config.EPOCH_LENGTH) slot = epoch * config.EPOCH_LENGTH message = epoch.to_bytes(32, byteorder="big") fork = Fork(**sample_fork_params) domain = get_domain(fork, slot, SignatureDomain.DOMAIN_RANDAO) randao_reveal = bls.sign(message, proposer_privkey, domain) block = SerenityBeaconBlock(**sample_beacon_block_params).copy( randao_reveal=randao_reveal, ) new_state = process_randao(state, block, config) updated_index = epoch % config.LATEST_RANDAO_MIXES_LENGTH original_mixes = state.latest_randao_mixes updated_mixes = new_state.latest_randao_mixes assert all( updated == original if index != updated_index else updated != original for index, (updated, original) in enumerate(zip(updated_mixes, original_mixes)))
def test_randao_processing_validates_randao_reveal(sample_beacon_block_params, sample_beacon_state_params, sample_fork_params, keymap, config): proposer_pubkey, proposer_privkey = first(keymap.items()) state = SerenityBeaconState(**sample_beacon_state_params).copy( validator_registry=tuple( mock_validator_record(proposer_pubkey) for _ in range(config.TARGET_COMMITTEE_SIZE)), validator_balances=(config.MAX_DEPOSIT_AMOUNT, ) * config.TARGET_COMMITTEE_SIZE, latest_randao_mixes=tuple( ZERO_HASH32 for _ in range(config.LATEST_RANDAO_MIXES_LENGTH)), ) epoch = state.current_epoch(config.EPOCH_LENGTH) slot = epoch * config.EPOCH_LENGTH message = (epoch + 1).to_bytes(32, byteorder="big") fork = Fork(**sample_fork_params) domain = get_domain(fork, slot, SignatureDomain.DOMAIN_RANDAO) randao_reveal = bls.sign(message, proposer_privkey, domain) block = SerenityBeaconBlock(**sample_beacon_block_params).copy( randao_reveal=randao_reveal, ) with pytest.raises(ValidationError): process_randao(state, block, config)
def validate_serenity_proposer_signature( state: BeaconState, block: BaseBeaconBlock, beacon_chain_shard_number: ShardNumber, epoch_length: int) -> None: block_without_signature_root = block.block_without_signature_root # TODO: Replace this root with tree hash root proposal_root = ProposalSignedData( state.slot, beacon_chain_shard_number, block_without_signature_root, ).root # Get the public key of proposer beacon_proposer_index = get_beacon_proposer_index(state, state.slot, epoch_length) proposer_pubkey = state.validator_registry[beacon_proposer_index].pubkey is_valid_signature = bls.verify( pubkey=proposer_pubkey, message=proposal_root, signature=block.signature, domain=get_domain(state.fork_data, state.slot, SignatureDomain.DOMAIN_PROPOSAL), ) if not is_valid_signature: raise ValidationError("Invalid Proposer Signature on block")
def validate_proposer_signature(state: BeaconState, block: BaseBeaconBlock, beacon_chain_shard_number: ShardNumber, committee_config: CommitteeConfig) -> None: block_without_signature_root = block.block_without_signature_root # TODO: Replace this root with tree hash root proposal_root = ProposalSignedData( state.slot, beacon_chain_shard_number, block_without_signature_root, ).root # Get the public key of proposer beacon_proposer_index = get_beacon_proposer_index( state, state.slot, committee_config, ) proposer_pubkey = state.validator_registry[beacon_proposer_index].pubkey domain = get_domain(state.fork, state.current_epoch(committee_config.EPOCH_LENGTH), SignatureDomain.DOMAIN_PROPOSAL) is_valid_signature = bls.verify( pubkey=proposer_pubkey, message=proposal_root, signature=block.signature, domain=domain, ) if not is_valid_signature: raise ValidationError( f"Invalid Proposer Signature on block, beacon_proposer_index={beacon_proposer_index}, " f"pubkey={proposer_pubkey}, message={proposal_root}," f"block.signature={block.signature}, domain={domain}")
def test_randao_reveal_validation(is_valid, epoch, expected_epoch, proposer_key_index, expected_proposer_key_index, privkeys, pubkeys, sample_fork_params, config): message_hash = epoch.to_bytes(32, byteorder="little") slot = epoch * config.SLOTS_PER_EPOCH fork = Fork(**sample_fork_params) domain = get_domain(fork, slot, SignatureDomain.DOMAIN_RANDAO) proposer_privkey = privkeys[proposer_key_index] randao_reveal = bls.sign( message_hash=message_hash, privkey=proposer_privkey, domain=domain, ) expected_proposer_pubkey = pubkeys[expected_proposer_key_index] try: validate_randao_reveal( randao_reveal=randao_reveal, proposer_pubkey=expected_proposer_pubkey, epoch=expected_epoch, fork=fork, ) except ValidationError: if is_valid: raise else: if not is_valid: pytest.fail("Did not raise")
def process_deposit(state: BeaconState, deposit: Deposit, slots_per_epoch: int, deposit_contract_tree_depth: int) -> BeaconState: """ Process a deposit from Ethereum 1.0. """ validate_deposit(state, deposit, deposit_contract_tree_depth) # Increment the next deposit index we are expecting. Note that this # needs to be done here because while the deposit contract will never # create an invalid Merkle branch, it may admit an invalid deposit # object, and we need to be able to skip over it state = state.copy(deposit_index=state.deposit_index + 1, ) validator_pubkeys = tuple(v.pubkey for v in state.validator_registry) deposit_input = deposit.deposit_data.deposit_input pubkey = deposit_input.pubkey amount = deposit.deposit_data.amount withdrawal_credentials = deposit_input.withdrawal_credentials if pubkey not in validator_pubkeys: # Verify the proof of possession proof_is_valid = bls.verify( pubkey=pubkey, message_hash=deposit_input.signing_root, signature=deposit_input.signature, domain=get_domain( state.fork, state.current_epoch(slots_per_epoch), SignatureDomain.DOMAIN_DEPOSIT, ), ) if not proof_is_valid: return state validator = Validator.create_pending_validator( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, ) # Note: In phase 2 registry indices that has been withdrawn for a long time # will be recycled. state = add_pending_validator( state, validator, amount, ) else: # Top-up - increase balance by deposit index = ValidatorIndex(validator_pubkeys.index(pubkey)) validator = state.validator_registry[index] # Update validator's balance and state state = state.update_validator_balance( validator_index=index, balance=state.validator_balances[index] + amount, ) return state
def create_block_on_state(state: BeaconState, config: BeaconConfig, block_class: BaseBeaconBlock, parent_block: BaseBeaconBlock, slot: SlotNumber, validator_index: int, privkey: int, attestations: Sequence[Attestation]): """ Create a beacon block with the given parameters. """ # Check proposer beacon_proposer_index = get_beacon_proposer_index( state.copy(slot=slot, ), slot, config.EPOCH_LENGTH, config.TARGET_COMMITTEE_SIZE, config.SHARD_COUNT, ) if validator_index != beacon_proposer_index: raise ProposerIndexError # Prepare block: slot and parent_root block = block_class.from_parent( parent_block=parent_block, block_params=FromBlockParams(slot=slot), ) # TODO: Add more operations randao_reveal = ZERO_HASH32 eth1_data = Eth1Data.create_empty_data() body = BeaconBlockBody.create_empty_body().copy( attestations=attestations, ) block = block.copy( randao_reveal=randao_reveal, eth1_data=eth1_data, body=body, ) # Sign empty_signature_block_root = block.block_without_signature_root proposal_root = ProposalSignedData( slot, config.BEACON_CHAIN_SHARD_NUMBER, empty_signature_block_root, ).root domain = get_domain( state.fork, slot, SignatureDomain.DOMAIN_PROPOSAL, ) block = block.copy(signature=bls.sign( message=proposal_root, privkey=privkey, domain=domain, ), ) return block
def sign_transaction(*, message_hash: Hash32, privkey: int, state: BeaconState, slot: Slot, signature_domain: SignatureDomain, slots_per_epoch: int) -> BLSSignature: domain = get_domain( state, signature_domain, slots_per_epoch, message_epoch=compute_epoch_at_slot(slot, slots_per_epoch), ) return bls.sign(message_hash=message_hash, privkey=privkey, domain=domain)
def test_get_domain(previous_version, current_version, slot, current_slot, domain_type, expected): fork = Fork( previous_version=previous_version, current_version=current_version, slot=slot, ) assert expected == get_domain( fork=fork, slot=current_slot, domain_type=domain_type, )
def validate_attestation_aggregate_signature(state: BeaconState, attestation: Attestation, genesis_epoch: EpochNumber, epoch_length: int, target_committee_size: int, shard_count: int) -> None: """ Validate ``aggregate_signature`` field of ``attestation``. Raise ``ValidationError`` if it's invalid. Note: This is the phase 0 version of `aggregate_signature`` validation. All proof of custody bits are assumed to be 0 within the signed data. This will change to reflect real proof of custody bits in the Phase 1. """ participant_indices = get_attestation_participants( state=state, attestation_data=attestation.data, bitfield=attestation.aggregation_bitfield, genesis_epoch=genesis_epoch, epoch_length=epoch_length, target_committee_size=target_committee_size, shard_count=shard_count, ) pubkeys = tuple( state.validator_registry[validator_index].pubkey for validator_index in participant_indices ) group_public_key = bls.aggregate_pubkeys(pubkeys) # TODO: change to tree hashing when we have SSZ message = AttestationDataAndCustodyBit.create_attestation_message(attestation.data) domain = get_domain( fork=state.fork, epoch=slot_to_epoch(attestation.data.slot, epoch_length), domain_type=SignatureDomain.DOMAIN_ATTESTATION, ) is_valid_signature = bls.verify( message=message, pubkey=group_public_key, signature=attestation.aggregate_signature, domain=domain, ) if not is_valid_signature: raise ValidationError( "Attestation aggregate_signature is invalid. " "message={}, participant_indices={} " "domain={}".format( message, participant_indices, domain, ) )
def sign_attestation(message: bytes, privkey: int, fork: Fork, slot: SlotNumber, epoch_length: int) -> BLSSignature: domain = get_domain( fork, slot_to_epoch(slot, epoch_length), SignatureDomain.DOMAIN_ATTESTATION, ) return bls.sign( message=message, privkey=privkey, domain=domain, )
def _corrupt_signature(params, fork_data): message = bytes.fromhex("deadbeefcafe") privkey = 42 domain_type = SignatureDomain.DOMAIN_ATTESTATION domain = get_domain( fork_data=fork_data, slot=params["data"].slot, domain_type=domain_type, ) corrupt_signature = bls.sign(message, privkey, domain) return assoc(params, "aggregate_signature", corrupt_signature)
def get_slot_signature(state: BeaconState, slot: Slot, privkey: int, config: Eth2Config) -> BLSSignature: """ Sign on ``slot`` and return the signature. """ domain = get_domain( state, SignatureDomain.DOMAIN_BEACON_ATTESTER, config.SLOTS_PER_EPOCH, message_epoch=compute_epoch_at_slot(slot, config.SLOTS_PER_EPOCH), ) return bls.sign(get_hash_tree_root(slot, sedes=uint64), privkey, domain)
def _corrupt_signature(epoch_length, params, fork): message = bytes.fromhex("deadbeefcafe") privkey = 42 domain_type = SignatureDomain.DOMAIN_ATTESTATION domain = get_domain( fork=fork, epoch=slot_to_epoch(params["data"].slot, epoch_length), domain_type=domain_type, ) corrupt_signature = bls.sign(message, privkey, domain) return assoc(params, "aggregate_signature", corrupt_signature)
def get_block_signature(state: BeaconState, block: BeaconBlock, private_key: int, slots_per_epoch: int) -> BLSSignature: epoch = compute_epoch_at_slot(block.slot, slots_per_epoch) domain = get_domain( state, SignatureDomain.DOMAIN_BEACON_PROPOSER, slots_per_epoch, message_epoch=epoch, ) signing_root = compute_signing_root(block, domain) return bls.sign(private_key, signing_root)
def sign_proof_of_possession(deposit_input: DepositInput, privkey: int, fork: Fork, slot: SlotNumber) -> BLSSignature: domain = get_domain( fork, slot, SignatureDomain.DOMAIN_DEPOSIT, ) return bls.sign( message=deposit_input.root, privkey=privkey, domain=domain, )
def _corrupt_signature(slots_per_epoch, params, fork): message_hash = b'\x12' * 32 privkey = 42 domain_type = SignatureDomain.DOMAIN_ATTESTATION domain = get_domain( fork=fork, epoch=slot_to_epoch(params["data"].slot, slots_per_epoch), domain_type=domain_type, ) corrupt_signature = bls.sign(message_hash, privkey, domain) return assoc(params, "aggregate_signature", corrupt_signature)
def test_get_domain(previous_version, current_version, epoch, current_epoch, domain_type, expected): fork = Fork( previous_version=previous_version, current_version=current_version, epoch=epoch, ) assert expected == get_domain( fork=fork, epoch=current_epoch, domain_type=domain_type, )
def sign_transaction(*, message_hash: Hash32, privkey: int, fork: Fork, slot: Slot, signature_domain: SignatureDomain, slots_per_epoch: int) -> BLSSignature: domain = get_domain( fork, slot_to_epoch(slot, slots_per_epoch), signature_domain, ) return bls.sign( message_hash=message_hash, privkey=privkey, domain=domain, )
def sign_proof_of_possession(deposit_input: DepositInput, privkey: int, fork: Fork, slot: Slot, slots_per_epoch: int) -> BLSSignature: domain = get_domain( fork, slot_to_epoch(slot, slots_per_epoch), SignatureDomain.DOMAIN_DEPOSIT, ) return bls.sign( message_hash=deposit_input.root, privkey=privkey, domain=domain, )