Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 31 additions & 21 deletions eth/_utils/bls.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,22 @@ def privtopub(k: int) -> int:


def verify(message: bytes, pubkey: int, signature: bytes, domain: int) -> bool:
final_exponentiation = final_exponentiate(
pairing(FQP_point_to_FQ2_point(decompress_G2(signature)), G1, False) *
pairing(
FQP_point_to_FQ2_point(hash_to_G2(message, domain)),
neg(decompress_G1(pubkey)),
False
try:
final_exponentiation = final_exponentiate(
pairing(
FQP_point_to_FQ2_point(decompress_G2(signature)),
G1,
final_exponentiate=False,
) *
pairing(
FQP_point_to_FQ2_point(hash_to_G2(message, domain)),
neg(decompress_G1(pubkey)),
final_exponentiate=False,
)
)
)
return final_exponentiation == FQ12.one()
return final_exponentiation == FQ12.one()
except (ValidationError, ValueError, AssertionError):
return False


def aggregate_signatures(signatures: Sequence[bytes]) -> Tuple[int, int]:
Expand Down Expand Up @@ -208,16 +215,19 @@ def verify_multiple(pubkeys: Sequence[int],
)
)

o = FQ12([1] + [0] * 11)
for m_pubs in set(messages):
# aggregate the pubs
group_pub = Z1
for i in range(len_msgs):
if messages[i] == m_pubs:
group_pub = add(group_pub, decompress_G1(pubkeys[i]))

o *= pairing(hash_to_G2(m_pubs, domain), group_pub, False)
o *= pairing(decompress_G2(signature), neg(G1), False)

final_exponentiation = final_exponentiate(o)
return final_exponentiation == FQ12.one()
try:
o = FQ12([1] + [0] * 11)
for m_pubs in set(messages):
# aggregate the pubs
group_pub = Z1
for i in range(len_msgs):
if messages[i] == m_pubs:
group_pub = add(group_pub, decompress_G1(pubkeys[i]))

o *= pairing(hash_to_G2(m_pubs, domain), group_pub, final_exponentiate=False)
o *= pairing(decompress_G2(signature), neg(G1), final_exponentiate=False)

final_exponentiation = final_exponentiate(o)
return final_exponentiation == FQ12.one()
except (ValidationError, ValueError, AssertionError):
return False
159 changes: 159 additions & 0 deletions eth/beacon/deposit_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
from typing import (
Sequence,
Tuple,
)

from eth_typing import (
Hash32,
)
from eth_utils import (
ValidationError,
)

from eth._utils import bls

from eth.beacon.constants import (
EMPTY_SIGNATURE,
)
from eth.beacon.enums import (
SignatureDomain,
)
from eth.beacon.exceptions import (
MinEmptyValidatorIndexNotFound,
)
from eth.beacon.types.deposit_input import DepositInput
from eth.beacon.types.states import BeaconState
from eth.beacon.types.validator_records import ValidatorRecord
from eth.beacon.helpers import (
get_domain,
)


def get_min_empty_validator_index(validators: Sequence[ValidatorRecord],
current_slot: int,
zero_balance_validator_ttl: int) -> int:
for index, validator in enumerate(validators):
is_empty = (
validator.balance == 0 and
validator.latest_status_change_slot + zero_balance_validator_ttl <= current_slot
)
if is_empty:
return index
raise MinEmptyValidatorIndexNotFound()


def validate_proof_of_possession(state: BeaconState,
pubkey: int,
proof_of_possession: bytes,
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 add_pending_validator(state: BeaconState,
validator: ValidatorRecord,
zero_balance_validator_ttl: int) -> Tuple[BeaconState, int]:
"""
Add a validator to the existing minimum empty validator index or
append to ``validator_registry``.
"""
# Check if there's empty validator index in `validator_registry`
try:
index = get_min_empty_validator_index(
state.validator_registry,
state.slot,
zero_balance_validator_ttl,
)
except MinEmptyValidatorIndexNotFound:
index = None

# Append to the validator_registry
validator_registry = state.validator_registry + (validator,)
state = state.copy(
validator_registry=validator_registry,
)
index = len(state.validator_registry) - 1
else:
# Use the empty validator index
state = state.update_validator(index, validator)

return state, index


def process_deposit(*,
state: BeaconState,
pubkey: int,
deposit: int,
proof_of_possession: bytes,
withdrawal_credentials: Hash32,
randao_commitment: Hash32,
zero_balance_validator_ttl: int) -> Tuple[BeaconState, int]:
"""
Process a deposit from Ethereum 1.0.
"""
validate_proof_of_possession(
state,
pubkey,
proof_of_possession,
withdrawal_credentials,
randao_commitment,
)

validator_pubkeys = tuple(v.pubkey for v in state.validator_registry)
if pubkey not in validator_pubkeys:
validator = ValidatorRecord.get_pending_validator(
Copy link
Contributor

@djrtwo djrtwo Dec 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be clearer as create_pending_validator rather than get_?
get implies to me that it is trying to retrieve something that at least might already exist.

Not sure what the codebase naming conventions are in general. Will defer to @pipermerriam

pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
randao_commitment=randao_commitment,
balance=deposit,
latest_status_change_slot=state.slot,
)

state, index = add_pending_validator(
state,
validator,
zero_balance_validator_ttl,
)
else:
# Top-up - increase balance by deposit
index = validator_pubkeys.index(pubkey)
validator = state.validator_registry[index]

if validator.withdrawal_credentials != withdrawal_credentials:
raise ValidationError(
"`withdrawal_credentials` are incorrect:\n"
"\texpected: %s, found: %s" % (
validator.withdrawal_credentials,
validator.withdrawal_credentials,
)
)

# Update validator's balance and state
validator = validator.copy(
balance=validator.balance + deposit,
)
state = state.update_validator(index, validator)

return state, index
10 changes: 10 additions & 0 deletions eth/beacon/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from eth.exceptions import (
PyEVMError,
)


class MinEmptyValidatorIndexNotFound(PyEVMError):
"""
No empty slot in the validator registry
"""
pass
28 changes: 11 additions & 17 deletions eth/beacon/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,13 @@
get_bitfield_length,
has_voted,
)
from eth.beacon._utils.hash import (
hash_eth2,
)
from eth._utils.numeric import (
clamp,
)

from eth.beacon.block_committees_info import (
BlockCommitteesInfo,
)
from eth.beacon.types.shard_committees import (
ShardCommittee,
)
from eth.beacon.block_committees_info import BlockCommitteesInfo
from eth.beacon.types.shard_committees import ShardCommittee
from eth.beacon.types.validator_registry_delta_block import ValidatorRegistryDeltaBlock
from eth.beacon._utils.random import (
shuffle,
split,
Expand Down Expand Up @@ -364,19 +358,19 @@ def get_effective_balance(validator: 'ValidatorRecord', max_deposit: int) -> int


def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_chain_tip: Hash32,
index: int,
validator_index: int,
pubkey: int,
flag: int) -> Hash32:
"""
Compute the next hash in the validator registry delta hash chain.
"""
return hash_eth2(
current_validator_registry_delta_chain_tip +
flag.to_bytes(1, 'big') +
index.to_bytes(3, 'big') +
# TODO: currently, we use 256-bit pubkey which is different form the spec
pubkey.to_bytes(32, 'big')
)
# TODO: switch to SSZ tree hashing
return ValidatorRegistryDeltaBlock(
latest_registry_delta_root=current_validator_registry_delta_chain_tip,
validator_index=validator_index,
pubkey=pubkey,
flag=flag,
).root


def get_fork_version(fork_data: 'ForkData',
Expand Down
21 changes: 15 additions & 6 deletions eth/beacon/types/deposit_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
hash32,
uint384,
)
from eth.beacon._utils.hash import hash_eth2


class DepositInput(rlp.Serializable):
Expand All @@ -23,12 +24,12 @@ class DepositInput(rlp.Serializable):
fields = [
# BLS pubkey
('pubkey', uint384),
# BLS proof of possession (a BLS signature)
('proof_of_possession', CountableList(uint384)),
# Withdrawal credentials
('withdrawal_credentials', hash32),
# Initial RANDAO commitment
('randao_commitment', hash32),
# BLS proof of possession (a BLS signature)
('proof_of_possession', CountableList(uint384)),
]

def __init__(self,
Expand All @@ -37,8 +38,16 @@ def __init__(self,
randao_commitment: Hash32,
proof_of_possession: Sequence[int]=(0, 0)) -> None:
super().__init__(
pubkey,
proof_of_possession,
withdrawal_credentials,
randao_commitment,
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
randao_commitment=randao_commitment,
proof_of_possession=proof_of_possession,
)

_root = None

@property
def root(self) -> Hash32:
if self._root is None:
self._root = hash_eth2(rlp.encode(self))
return self._root
10 changes: 10 additions & 0 deletions eth/beacon/types/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,13 @@ def num_validators(self) -> int:
@property
def num_crosslinks(self) -> int:
return len(self.latest_crosslinks)

def update_validator(self,
validator_index: int,
validator: ValidatorRecord) -> 'BeaconState':
validator_registry = list(self.validator_registry)
validator_registry[validator_index] = validator
updated_state = self.copy(
validator_registry=tuple(validator_registry),
)
return updated_state
21 changes: 21 additions & 0 deletions eth/beacon/types/validator_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,24 @@ def is_active(self) -> bool:
Returns ``True`` if the validator is active.
"""
return self.status in VALIDATOR_RECORD_ACTIVE_STATUSES

@classmethod
def get_pending_validator(cls,
pubkey: int,
withdrawal_credentials: Hash32,
randao_commitment: Hash32,
balance: int,
latest_status_change_slot: int) -> 'ValidatorRecord':
"""
Return a new pending ``ValidatorRecord`` with the given fields.
"""
return cls(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
randao_commitment=randao_commitment,
randao_layers=0,
balance=balance,
status=ValidatorStatusCode.PENDING_ACTIVATION,
latest_status_change_slot=latest_status_change_slot,
exit_count=0,
)
Loading