Skip to content
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
36 changes: 20 additions & 16 deletions src/relayer/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from time import time

from eth_typing import HexStr
from eth_typing import BLSSignature, HexStr
from fastapi import APIRouter
from sw_utils import DepositData, get_v2_withdrawal_credentials
from web3 import Web3

from src.app_state import AppState
from src.common.contracts import VaultContract, validators_registry_contract
from src.relayer import schema
from src.relayer.typings import Validator
from src.relayer.typings import Validator, ValidatorType
from src.relayer.validators_manager import (
get_validators_manager_signature_consolidation,
get_validators_manager_signature_funding,
Expand All @@ -25,7 +24,10 @@ async def register_validators(
) -> schema.ValidatorsRegisterResponse:
app_state = AppState()
validators: list[Validator] = []
exit_signatures_ready = True

# Flag is True when all validators have deposit and exit signatures, False otherwise.
is_signatures_ready_for_all_validators = True

now = int(time())

for i, (public_key, amount) in enumerate(zip(app_state.public_keys, request.amounts)):
Expand All @@ -35,15 +37,16 @@ async def register_validators(
if validator is None or validator.validator_index != validator_index:
validator = Validator(
public_key=public_key,
vault=request.vault,
validator_index=validator_index,
created_at=now,
amount=amount,
validator_type=request.validator_type,
)
app_state.validators[public_key] = validator

if validator.exit_signature is None:
exit_signatures_ready = False
if validator.deposit_signature is None or validator.exit_signature is None:
is_signatures_ready_for_all_validators = False

validators.append(validator)

Expand All @@ -55,7 +58,11 @@ async def register_validators(
schema.ValidatorsRegisterResponseItem(
public_key=validator.public_key,
amount=validator.amount,
deposit_signature=validator.deposit_signature or None,
deposit_signature=(
Web3.to_hex(validator.deposit_signature)
if validator.deposit_signature is not None
else None
),
exit_signature=(
Web3.to_hex(validator.exit_signature)
if validator.exit_signature is not None
Expand All @@ -71,7 +78,7 @@ async def register_validators(

validators_manager_signature: HexStr | None = None

if exit_signatures_ready:
if is_signatures_ready_for_all_validators:
validators_registry_root = await validators_registry_contract.get_registry_root()
validators_manager_signature = get_validators_manager_signature_register(
Web3.to_checksum_address(request.vault),
Expand All @@ -94,17 +101,14 @@ async def fund_validators(
# use empty signature for funding
empty_signature = bytes(96)
for public_key, amount in zip(request.public_keys, request.amounts):
deposit_data = DepositData(
pubkey=Web3.to_bytes(hexstr=public_key),
withdrawal_credentials=get_v2_withdrawal_credentials(request.vault),
amount=amount,
signature=empty_signature,
)
validator = Validator(
public_key=public_key,
vault=Web3.to_checksum_address(request.vault),
amount=amount,
deposit_signature=Web3.to_hex(empty_signature),
deposit_data_root=Web3.to_hex(deposit_data.hash_tree_root),
validator_type=ValidatorType.V2,
validator_index=0,
created_at=0,
deposit_signature=BLSSignature(empty_signature),
)
validators.append(validator)

Expand Down
57 changes: 48 additions & 9 deletions src/relayer/typings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from dataclasses import dataclass, field
from enum import Enum

from eth_typing import BLSSignature, HexStr
from eth_typing import BLSSignature, ChecksumAddress, HexStr
from sw_utils import (
DepositData,
get_v1_withdrawal_credentials,
get_v2_withdrawal_credentials,
)
from web3 import Web3
from web3.types import Gwei

from src.validators.typings import OraclesExitSignatureShares
Expand All @@ -16,15 +22,48 @@ class ValidatorType(Enum):
class Validator: # pylint: disable=too-many-instance-attributes
public_key: HexStr

# Relayer fields
deposit_data_root: HexStr = HexStr('')
deposit_signature: HexStr = HexStr('')
amount: Gwei = Gwei(0)
validator_type: ValidatorType = ValidatorType.V2
# Deposit message signing fields
vault: ChecksumAddress
amount: Gwei
validator_type: ValidatorType

# DVT exit signature fields
validator_index: int = 0
created_at: int = 0
# Exit message signing fields
validator_index: int

# Metadata
created_at: int

# Exit signature
exit_signature: BLSSignature | None = None
exit_signature_shares: dict[int, BLSSignature] = field(default_factory=dict)
oracles_exit_signature_shares: OraclesExitSignatureShares | None = None

# Deposit signature
deposit_signature: BLSSignature | None = None
deposit_signature_shares: dict[int, BLSSignature] = field(default_factory=dict)

@property
def deposit_data(self) -> DepositData:
if not self.deposit_signature:
raise ValueError('Deposit signature is not set')

return DepositData(
pubkey=Web3.to_bytes(hexstr=self.public_key),
withdrawal_credentials=Web3.to_bytes(hexstr=self.withdrawal_credentials),
amount=self.amount,
signature=self.deposit_signature,
)

@property
def withdrawal_credentials(self) -> HexStr:
if self.validator_type == ValidatorType.V1:
return Web3.to_hex(get_v1_withdrawal_credentials(self.vault))

if self.validator_type == ValidatorType.V2:
return Web3.to_hex(get_v2_withdrawal_credentials(self.vault))

raise ValueError(f'Unknown validator type: {self.validator_type}')

@property
def deposit_data_root(self) -> HexStr:
return Web3.to_hex(self.deposit_data.hash_tree_root)
6 changes: 4 additions & 2 deletions src/relayer/validators_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ def get_validators_manager_signature_consolidation(


def _encode_validator(v: Validator) -> bytes:
encoded_validator = [
if v.deposit_signature is None:
raise ValueError(f'Deposit signature is not set for validator {v.public_key}')
encoded_validator: list[bytes] = [
Web3.to_bytes(hexstr=v.public_key),
Web3.to_bytes(hexstr=v.deposit_signature),
v.deposit_signature,
Web3.to_bytes(hexstr=v.deposit_data_root),
]
if v.validator_type == ValidatorType.V2:
Expand Down
88 changes: 54 additions & 34 deletions src/validators/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,89 @@
from eth_typing import BLSSignature, HexStr
from eth_typing import BLSSignature
from fastapi import APIRouter
from web3 import Web3

from src.app_state import AppState
from src.config import settings
from src.validators.exit_signature import (
get_oracles_exit_signature_shares,
validate_deposit_signature,
validate_exit_signature,
)
from src.validators.key_shares import reconstruct_shared_bls_signature
from src.validators.schema import (
ExitSignatureShareRequest,
ExitSignatureShareResponse,
ExitsResponse,
ExitsResponseItem,
SignatureShareRequest,
SignatureShareResponse,
ValidatorsResponse,
ValidatorsResponseItem,
)

router = APIRouter()


@router.get('/exits')
async def get_exits() -> ExitsResponse:
@router.get('/validators')
async def get_validators() -> ValidatorsResponse:
app_state = AppState()
response = ExitsResponse(exits=[])
response = ValidatorsResponse(validators=[])

for validator in app_state.validators.values():
response.exits.append(ExitsResponseItem.from_validator(validator))
response.validators.append(ValidatorsResponseItem.from_validator(validator))
return response


@router.post('/exit-signature')
async def create_exit_signature_shares(
request: ExitSignatureShareRequest,
) -> ExitSignatureShareResponse:
@router.post('/signatures')
async def submit_signature_shares(
request: SignatureShareRequest,
) -> SignatureShareResponse:
app_state = AppState()

for share in request.shares:
validator = app_state.validators.get(share.public_key)
if validator is None:
continue

current_share = validator.exit_signature_shares.get(request.share_index)
if current_share:
continue
# Handle exit signature shares
if not validator.exit_signature_shares.get(request.share_index):
validator.exit_signature_shares[request.share_index] = BLSSignature(
Web3.to_bytes(hexstr=share.exit_signature)
)

validator.exit_signature_shares[request.share_index] = BLSSignature(
Web3.to_bytes(hexstr=HexStr(share.exit_signature))
)
if len(validator.exit_signature_shares) >= settings.signature_threshold:
# Reconstruct and validate exit signature
exit_signature = reconstruct_shared_bls_signature(validator.exit_signature_shares)
if not validate_exit_signature(
validator.public_key, validator.validator_index, exit_signature
):
raise RuntimeError('invalid exit signature')

if len(validator.exit_signature_shares) < settings.signature_threshold:
continue
validator.exit_signature = exit_signature

# Split exit signature into shares for oracles
oracles_shares = await get_oracles_exit_signature_shares(
public_key=validator.public_key,
validator_index=validator.validator_index,
exit_signature=validator.exit_signature,
)
validator.oracles_exit_signature_shares = oracles_shares

exit_signature = reconstruct_shared_bls_signature(validator.exit_signature_shares)
if not validate_exit_signature(
validator.public_key, validator.validator_index, exit_signature
):
raise RuntimeError('invalid exit signature')
# Handle deposit signature shares
if not validator.deposit_signature_shares.get(request.share_index):
validator.deposit_signature_shares[request.share_index] = BLSSignature(
Web3.to_bytes(hexstr=share.deposit_signature)
)

validator.exit_signature = exit_signature
if len(validator.deposit_signature_shares) >= settings.signature_threshold:
# Reconstruct and validate deposit signature
deposit_signature = reconstruct_shared_bls_signature(
validator.deposit_signature_shares
)
if not validate_deposit_signature(
validator.public_key,
Web3.to_bytes(hexstr=validator.withdrawal_credentials),
validator.amount,
deposit_signature,
):
raise RuntimeError('invalid deposit signature')

oracles_shares = await get_oracles_exit_signature_shares(
public_key=validator.public_key,
validator_index=validator.validator_index,
exit_signature=validator.exit_signature,
)
validator.oracles_exit_signature_shares = oracles_shares
validator.deposit_signature = deposit_signature

return ExitSignatureShareResponse()
return SignatureShareResponse()
23 changes: 22 additions & 1 deletion src/validators/exit_signature.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import ecies
import milagro_bls_binding as bls
from eth_typing import BLSPubkey, BLSSignature, HexStr
from sw_utils import ConsensusFork, get_exit_message_signing_root
from sw_utils import (
ConsensusFork,
get_exit_message_signing_root,
is_valid_deposit_data_signature,
)
from sw_utils.typings import Bytes32
from web3 import Web3
from web3.types import Gwei

from src.app_state import AppState
from src.config import settings
Expand Down Expand Up @@ -76,3 +82,18 @@ def validate_exit_signature(
)

return bls.Verify(Web3.to_bytes(hexstr=public_key), message, exit_signature)


def validate_deposit_signature(
public_key: HexStr,
withdrawal_credentials: bytes,
amount: Gwei,
deposit_signature: BLSSignature,
) -> bool:
return is_valid_deposit_data_signature(
public_key=BLSPubkey(Web3.to_bytes(hexstr=public_key)),
withdrawal_credentials=Bytes32(withdrawal_credentials),
signature=deposit_signature,
amount=amount,
fork_version=settings.network_config.GENESIS_FORK_VERSION,
)
Loading
Loading