Skip to content

Commit

Permalink
Implement verify deposit data command (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsudmi authored Mar 20, 2022
1 parent c8721f9 commit 60d5987
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 50 deletions.
51 changes: 3 additions & 48 deletions stakewise_cli/commands/upload_deposit_data.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import json
from os import listdir
from os.path import basename, isfile, join
from typing import Dict, List, Set, Tuple
from typing import List, Set, Tuple

import click
from eth_typing import BLSPubkey, BLSSignature, HexStr
from eth_utils import add_0x_prefix
from gql import Client
from web3 import Web3

from stakewise_cli.eth1 import generate_specification, validate_operator_address
from stakewise_cli.eth2 import verify_deposit_data
from stakewise_cli.eth2 import check_public_keys_not_registered, verify_deposit_data
from stakewise_cli.ipfs import upload_deposit_data_to_ipfs
from stakewise_cli.merkle_tree import MerkleTree
from stakewise_cli.networks import GNOSIS_CHAIN, GOERLI, MAINNET, NETWORKS, PERM_GOERLI
from stakewise_cli.queries import (
REGISTRATIONS_QUERY,
get_ethereum_gql_client,
get_stakewise_gql_client,
)
from stakewise_cli.queries import get_ethereum_gql_client, get_stakewise_gql_client
from stakewise_cli.typings import Bytes4, Bytes32, Gwei, MerkleDepositData

w3 = Web3()
Expand Down Expand Up @@ -113,45 +107,6 @@ def process_file(
merkle_deposit_datum.append(merkle_deposit_data)


def check_public_keys_not_registered(
gql_client: Client, seen_public_keys: List[HexStr]
) -> None:
keys_count = len(seen_public_keys)
verified_public_keys: List[HexStr] = []
with click.progressbar(
length=keys_count,
label="Verifying validators are not registered...\t\t",
show_percent=False,
show_pos=True,
) as bar:
from_index = 0
while len(verified_public_keys) < keys_count:
curr_progress = len(verified_public_keys)
chunk_size = min(100, keys_count - curr_progress)

# verify keys in chunks
public_keys_chunk: List[HexStr] = []
while len(public_keys_chunk) != chunk_size:
public_key = add_0x_prefix(HexStr(seen_public_keys[from_index].lower()))
verified_public_keys.append(public_key)
public_keys_chunk.append(public_key)
from_index += 1

# check keys are not registered in beacon chain
result: Dict = gql_client.execute(
document=REGISTRATIONS_QUERY,
variable_values=dict(public_keys=public_keys_chunk),
)
registrations = result["validatorRegistrations"]
registered_keys = ",".join(r["publicKey"] for r in registrations)
if registered_keys:
raise click.ClickException(
f"Public keys already registered in beacon chain: {registered_keys}"
)

bar.update(len(verified_public_keys) - curr_progress)


@click.command(
help="Uploads deposit data to IPFS and generates a forum post specification"
)
Expand Down
118 changes: 118 additions & 0 deletions stakewise_cli/commands/verify_deposit_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from typing import List, Set

import click
from eth_typing import BLSPubkey, BLSSignature, HexStr
from py_ecc.bls import G2ProofOfPossession
from web3 import Web3

from stakewise_cli.eth2 import check_public_keys_not_registered, get_deposit_data_roots
from stakewise_cli.ipfs import ipfs_fetch
from stakewise_cli.merkle_tree import MerkleTree
from stakewise_cli.networks import GNOSIS_CHAIN, GOERLI, MAINNET, NETWORKS, PERM_GOERLI
from stakewise_cli.queries import get_ethereum_gql_client
from stakewise_cli.typings import Bytes32, Gwei

w3 = Web3()

deposit_amount = Web3.toWei(32, "ether")
deposit_amount_gwei = Gwei(int(w3.fromWei(deposit_amount, "gwei")))


@click.command(help="Verify deposit data")
@click.option(
"--network",
default=MAINNET,
help="The network deposit data was generated for.",
prompt="Please choose the network name",
type=click.Choice(
[MAINNET, GOERLI, PERM_GOERLI, GNOSIS_CHAIN], case_sensitive=False
),
)
@click.option(
"--ipfs-hash",
help="The IPFS hash with the deposit data",
prompt="Enter the IPFS hash of the file with the deposit data",
)
@click.option(
"--merkle-root",
help="The expected merkle root of the deposit data",
prompt="Enter the expected merkle root of the deposit data",
)
def verify_deposit_data(network: str, ipfs_hash: str, merkle_root: HexStr) -> None:
withdrawal_credentials = Bytes32(
Web3.toBytes(hexstr=NETWORKS[network]["WITHDRAWAL_CREDENTIALS"])
)
fork_version = NETWORKS[network]["GENESIS_FORK_VERSION"]

merkle_nodes: List[bytes] = []
seen_public_keys: Set[HexStr] = set()

deposit_datum = ipfs_fetch(ipfs_hash)
with click.progressbar(
deposit_datum,
label=f"Verifying deposit data from {ipfs_hash}...\t\t",
show_percent=False,
show_pos=True,
) as deposit_datum:
for deposit_data in deposit_datum:
signature = deposit_data["signature"]
deposit_data_root = deposit_data["deposit_data_root"]
public_key = deposit_data["public_key"]
if public_key in seen_public_keys:
raise click.ClickException(f"Public key {public_key} is repeated")

# verify deposit data root
expected_signing_root, expected_deposit_data_root = get_deposit_data_roots(
public_key=BLSPubkey(Web3.toBytes(hexstr=public_key)),
withdrawal_credentials=withdrawal_credentials,
signature=BLSSignature(Web3.toBytes(hexstr=signature)),
amount=deposit_amount_gwei,
fork_version=fork_version,
)
if expected_deposit_data_root != Web3.toBytes(hexstr=deposit_data_root):
raise click.ClickException(
f"Invalid deposit data root for public key {public_key}"
)

if (
G2ProofOfPossession.Verify(public_key, expected_signing_root, signature)
== expected_deposit_data_root
):
raise click.ClickException(
f"Invalid deposit data root for public key {public_key}"
)

seen_public_keys.add(public_key)
encoded_data: bytes = w3.codec.encode_abi(
["bytes", "bytes32", "bytes", "bytes32"],
[public_key, withdrawal_credentials, signature, deposit_data_root],
)
merkle_nodes.append(w3.keccak(primitive=encoded_data))

# check whether public keys are not registered in beacon chain
check_public_keys_not_registered(
gql_client=get_ethereum_gql_client(network),
seen_public_keys=list(seen_public_keys),
)

# check proofs
merkle_tree = MerkleTree(merkle_nodes)
for i, deposit_data in enumerate(deposit_datum):
proof: List[HexStr] = merkle_tree.get_hex_proof(merkle_nodes[i])
if proof != deposit_data["proof"]:
raise click.ClickException(
f"Invalid deposit data proof for public key {public_key}"
)

if merkle_tree.get_hex_root() != merkle_root:
raise click.ClickException(
f"Merkle roots does not match:"
f" expected={merkle_root},"
f" actual={merkle_tree.get_hex_root()}"
)

click.secho(
f"The deposit data from {ipfs_hash} has been successfully verified",
bold=True,
fg="green",
)
62 changes: 60 additions & 2 deletions stakewise_cli/eth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import backoff
import click
from eth_typing import BLSPubkey, BLSSignature, HexStr
from gql import Client as GqlClient
from eth_utils import add_0x_prefix
from gql import Client
from py_ecc.bls import G2ProofOfPossession
from staking_deposit.key_handling.key_derivation.mnemonic import (
get_mnemonic,
Expand Down Expand Up @@ -116,7 +117,7 @@ def create_new_mnemonic(mnemonic_language: str) -> str:


def generate_unused_validator_keys(
gql_client: GqlClient, mnemonic: str, keys_count: int
gql_client: Client, mnemonic: str, keys_count: int
) -> List[KeyPair]:
"""Generates specified number of unused validator key-pairs from the mnemonic."""
pub_key_to_priv_key: Dict[HexStr, BLSPrivkey] = {}
Expand Down Expand Up @@ -229,6 +230,24 @@ def get_deposit_data_signature(
return signature, deposit_data.hash_tree_root


def get_deposit_data_roots(
public_key: BLSPubkey,
withdrawal_credentials: Bytes32,
signature: BLSSignature,
amount: Gwei,
fork_version: Bytes4,
) -> Tuple[Bytes32, Bytes32]:
""":returns deposit data hash tree root."""
deposit_message = DepositMessage(
pubkey=public_key, withdrawal_credentials=withdrawal_credentials, amount=amount
)
domain = compute_deposit_domain(fork_version=fork_version)
signing_root = compute_signing_root(deposit_message, domain)
deposit_data = SSZDepositData(**deposit_message.as_dict(), signature=signature)

return Bytes32(signing_root), deposit_data.hash_tree_root


def verify_deposit_data(
signature: BLSSignature,
public_key: BLSPubkey,
Expand Down Expand Up @@ -309,3 +328,42 @@ def generate_merkle_deposit_datum(
merkle_root: HexStr = merkle_tree.get_hex_root()

return merkle_root, merkle_deposit_datum


def check_public_keys_not_registered(
gql_client: Client, seen_public_keys: List[HexStr]
) -> None:
keys_count = len(seen_public_keys)
verified_public_keys: List[HexStr] = []
with click.progressbar(
length=keys_count,
label="Verifying validators are not registered...\t\t",
show_percent=False,
show_pos=True,
) as bar:
from_index = 0
while len(verified_public_keys) < keys_count:
curr_progress = len(verified_public_keys)
chunk_size = min(100, keys_count - curr_progress)

# verify keys in chunks
public_keys_chunk: List[HexStr] = []
while len(public_keys_chunk) != chunk_size:
public_key = add_0x_prefix(HexStr(seen_public_keys[from_index].lower()))
verified_public_keys.append(public_key)
public_keys_chunk.append(public_key)
from_index += 1

# check keys are not registered in beacon chain
result: Dict = gql_client.execute(
document=REGISTRATIONS_QUERY,
variable_values=dict(public_keys=public_keys_chunk),
)
registrations = result["validatorRegistrations"]
registered_keys = ",".join(r["publicKey"] for r in registrations)
if registered_keys:
raise click.ClickException(
f"Public keys already registered in beacon chain: {registered_keys}"
)

bar.update(len(verified_public_keys) - curr_progress)
2 changes: 2 additions & 0 deletions stakewise_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from stakewise_cli.commands.sync_local import sync_local # noqa: E402
from stakewise_cli.commands.sync_vault import sync_vault # noqa: E402
from stakewise_cli.commands.upload_deposit_data import upload_deposit_data # noqa: E402
from stakewise_cli.commands.verify_deposit_data import verify_deposit_data # noqa: E402
from stakewise_cli.commands.verify_shard_pubkeys import ( # noqa: E402
verify_shard_pubkeys,
)
Expand All @@ -23,6 +24,7 @@ def cli() -> None:

cli.add_command(create_deposit_data)
cli.add_command(upload_deposit_data)
cli.add_command(verify_deposit_data)
cli.add_command(sync_vault)
cli.add_command(sync_local)
cli.add_command(create_shard_pubkeys)
Expand Down

0 comments on commit 60d5987

Please sign in to comment.