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 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 create_mock_signed_attestation(state, shard_committee, voting_committee_indices, attestation_data): message = hash_eth2( rlp.encode(attestation_data) + (0).to_bytes(1, "big")) # participants sign message signatures = [ bls.sign(message, privkeys[shard_committee.committee[committee_index]], domain=get_domain( fork_data=state.fork_data, slot=attestation_data.slot, domain_type=SignatureDomain.DOMAIN_ATTESTATION, )) for committee_index in voting_committee_indices ] # aggregate signatures and construct participant bitfield participation_bitfield, aggregate_signature = aggregate_votes( bitfield=get_empty_bitfield(len(shard_committee.committee)), sigs=(), voting_sigs=signatures, voting_committee_indices=voting_committee_indices, ) # create attestation from attestation_data, particpipant_bitfield, and signature return Attestation( data=attestation_data, participation_bitfield=participation_bitfield, custody_bitfield=b'', aggregate_signature=aggregate_signature, )
def validate_proof_of_possession(state: BeaconState, pubkey: BLSPubkey, proof_of_possession: BLSSignature, withdrawal_credentials: Hash32, randao_commitment: Hash32) -> None: deposit_input = DepositInput( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_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_get_domain(pre_fork_version, post_fork_version, fork_slot, current_slot, domain_type, expected): fork_data = ForkData( pre_fork_version=pre_fork_version, post_fork_version=post_fork_version, fork_slot=fork_slot, ) assert expected == get_domain( fork_data=fork_data, slot=current_slot, domain_type=domain_type, )
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_indices_and_signatures(num_validators, message, privkeys, fork_data, slot): num_indices = 5 assert num_validators >= num_indices indices = random.sample(range(num_validators), num_indices) privkeys = [privkeys[i] for i in indices] domain_type = SignatureDomain.DOMAIN_ATTESTATION domain = get_domain( fork_data=fork_data, slot=slot, domain_type=domain_type, ) signatures = tuple( map(lambda key: bls.sign(message, key, domain), privkeys)) return (indices, signatures)
def test_validate_proof_of_possession(sample_beacon_state_params, pubkeys, privkeys, expected): state = BeaconState(**sample_beacon_state_params) privkey = privkeys[0] pubkey = pubkeys[0] withdrawal_credentials = b'\x34' * 32 custody_commitment = b'\x12' * 32 randao_commitment = b'\x56' * 32 domain = get_domain( state.fork_data, state.slot, SignatureDomain.DOMAIN_DEPOSIT, ) deposit_input = make_deposit_input( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, ) if expected is True: proof_of_possession = sign_proof_of_possession(deposit_input, privkey, domain) validate_proof_of_possession( state=state, pubkey=pubkey, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, ) else: proof_of_possession = b'\x11' * 32 with pytest.raises(ValidationError): validate_proof_of_possession( state=state, pubkey=pubkey, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, )
def validate_serenity_attestation_aggregate_signature( state: BeaconState, attestation: Attestation, epoch_length: 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, slot=attestation.data.slot, shard=attestation.data.shard, participation_bitfield=attestation.participation_bitfield, epoch_length=epoch_length, ) 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 # TODO: Replace with AttestationAndCustodyBit data structure message = hash_eth2(rlp.encode(attestation.data) + (0).to_bytes(1, "big")) is_valid_signature = bls.verify( message=message, pubkey=group_public_key, signature=attestation.aggregate_signature, domain=get_domain( fork_data=state.fork_data, slot=attestation.data.slot, domain_type=SignatureDomain.DOMAIN_ATTESTATION, ), ) if not is_valid_signature: raise ValidationError( "Attestation ``aggregate_signature`` is invalid.")
def test_process_deposit(sample_beacon_state_params, zero_balance_validator_ttl, privkeys, pubkeys): state = BeaconState(**sample_beacon_state_params).copy( slot=zero_balance_validator_ttl + 1, validator_registry=(), ) privkey_1 = privkeys[0] pubkey_1 = pubkeys[0] deposit = 32 * denoms.gwei withdrawal_credentials = b'\x34' * 32 custody_commitment = b'\x11' * 32 randao_commitment = b'\x56' * 32 domain = get_domain( state.fork_data, state.slot, SignatureDomain.DOMAIN_DEPOSIT, ) deposit_input = make_deposit_input( pubkey=pubkey_1, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, ) proof_of_possession = sign_proof_of_possession(deposit_input, privkey_1, domain) # Add the first validator result_state, index = process_deposit( state=state, pubkey=pubkey_1, deposit=deposit, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, zero_balance_validator_ttl=zero_balance_validator_ttl, ) assert len(result_state.validator_registry) == 1 index = 0 assert result_state.validator_registry[0].pubkey == pubkey_1 assert result_state.validator_registry[ index].withdrawal_credentials == withdrawal_credentials assert result_state.validator_registry[ index].randao_commitment == randao_commitment assert result_state.validator_balances[index] == deposit # test immutable assert len(state.validator_registry) == 0 # Add the second validator privkey_2 = privkeys[1] pubkey_2 = pubkeys[1] deposit_input = make_deposit_input( pubkey=pubkey_2, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, ) proof_of_possession = sign_proof_of_possession(deposit_input, privkey_2, domain) result_state, index = process_deposit( state=result_state, pubkey=pubkey_2, deposit=deposit, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, zero_balance_validator_ttl=zero_balance_validator_ttl, ) assert len(result_state.validator_registry) == 2 assert result_state.validator_registry[1].pubkey == pubkey_2 # Force the first validator exited -> a empty slot in state.validator_registry. result_state = result_state.copy(validator_registry=( result_state.validator_registry[0].copy(latest_status_change_slot=0, ), result_state.validator_registry[1], ), validator_balances=(0, ) + result_state.validator_balances[1:]) # Add the third validator. # Should overwrite previously exited validator. privkey_3 = privkeys[2] pubkey_3 = pubkeys[2] deposit_input = make_deposit_input( pubkey=pubkey_3, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, ) proof_of_possession = sign_proof_of_possession(deposit_input, privkey_3, domain) result_state, index = process_deposit( state=result_state, pubkey=pubkey_3, deposit=deposit, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, zero_balance_validator_ttl=zero_balance_validator_ttl, ) # Overwrite the second validator. assert len(result_state.validator_registry) == 2 assert result_state.validator_registry[0].pubkey == pubkey_3