Adds verification of deposits against eth2 rules.

This commit is contained in:
Carl Beekhuizen 2020-02-28 15:35:52 +00:00
parent f69198970e
commit d57d08badd
No known key found for this signature in database
GPG Key ID: 8F29E54F49E7AAB5
5 changed files with 73 additions and 15 deletions

View File

@ -5,12 +5,16 @@ from key_handling.key_derivation.mnemonic import (
get_languages,
get_mnemonic,
)
from utils.eth2_deposit_check import verify_deposit_data_json
from utils.credentials import (
mnemonic_to_credentials,
export_keystores,
export_deposit_data_json,
)
from utils.constants import WORD_LISTS_PATH
from utils.constants import (
WORD_LISTS_PATH,
MAX_DEPOSIT_AMOUNT,
)
words_path = os.path.join(os.getcwd(), WORD_LISTS_PATH)
languages = get_languages(words_path)
@ -53,17 +57,19 @@ def generate_mnemonic(language: str, words_path: str) -> str:
)
def main(num_validators: int, mnemonic_language: str, password: str, folder: str):
mnemonic = generate_mnemonic(mnemonic_language, words_path)
amounts = [32 * 10 ** 9] * num_validators
amounts = [MAX_DEPOSIT_AMOUNT] * num_validators
folder = os.path.join(folder, 'validator_keys')
if not os.path.exists(folder):
os.mkdir(folder)
click.clear()
click.echo('Creating your keys.')
credentials = mnemonic_to_credentials(mnemonic=mnemonic, num_keys=num_validators, amounts=amounts)
click.echo('Saving your keystores.')
click.echo('Saving your keystore(s).')
export_keystores(credentials=credentials, password=password, folder=folder)
click.echo('Creating your deposits.')
export_deposit_data_json(credentials=credentials, folder=folder)
click.echo('Creating your deposit(s).')
deposits_file = export_deposit_data_json(credentials=credentials, folder=folder)
click.echo('Verifying your deposit(s).')
assert verify_deposit_data_json(deposits_file)
click.echo('\nSuccess!\nYour keys can be found at: %s' % folder)
click.pause('\n\nPress any key.')

View File

@ -1,4 +1,7 @@
DOMAIN_DEPOSIT = bytes.fromhex('03000000')
GENESIS_FORK_VERSION = bytes.fromhex('00000000')
MIN_DEPOSIT_AMOUNT = 2 ** 0 * 10 ** 9
MAX_DEPOSIT_AMOUNT = 2 ** 5 * 10 ** 9
WORD_LISTS_PATH = 'src/key_handling/key_derivation/word_lists/'

View File

@ -10,8 +10,8 @@ from utils.crypto import SHA256
from utils.ssz import (
compute_domain,
compute_signing_root,
DepositData,
SignedDepositData,
DepositMessage,
Deposit,
)
@ -54,14 +54,14 @@ def export_keystores(*, credentials: List[ValidatorCredentials], password: str,
credential.save_signing_keystore(password=password, folder=folder)
def sign_deposit_data(deposit_data: DepositData, sk: int) -> SignedDepositData:
def sign_deposit_data(deposit_data: DepositMessage, sk: int) -> Deposit:
'''
Given a DepositData, it signs its root and returns a SignedDepositData
Given a DepositMessage, it signs its root and returns a Deposit
'''
assert bls.PrivToPub(sk) == deposit_data.pubkey
domain = compute_domain()
signing_root = compute_signing_root(deposit_data, domain)
signed_deposit_data = SignedDepositData(
signed_deposit_data = Deposit(
**deposit_data.as_dict(),
signature=bls.Sign(sk, signing_root)
)
@ -70,9 +70,8 @@ def sign_deposit_data(deposit_data: DepositData, sk: int) -> SignedDepositData:
def export_deposit_data_json(*, credentials: List[ValidatorCredentials], folder: str):
deposit_data: List[dict] = []
domain = compute_domain()
for credential in credentials:
deposit_datum = DepositData(
deposit_datum = DepositMessage(
pubkey=credential.signing_pk,
withdrawal_credentials=SHA256(credential.withdrawal_pk),
amount=credential.amount,
@ -85,3 +84,4 @@ def export_deposit_data_json(*, credentials: List[ValidatorCredentials], folder:
filefolder = os.path.join(folder, 'deposit_data-%i.json' % time.time())
with open(filefolder, 'w') as f:
json.dump(deposit_data, f, default=lambda x: x.hex())
return filefolder

View File

@ -0,0 +1,48 @@
import json
from py_ecc.bls import G2ProofOfPossession as bls
from utils.ssz import (
compute_domain,
compute_signing_root,
Deposit,
DepositMessage,
)
from utils.constants import (
DOMAIN_DEPOSIT,
MAX_DEPOSIT_AMOUNT,
MIN_DEPOSIT_AMOUNT,
)
def verify_deposit_data_json(filefolder: str) -> bool:
with open(filefolder, 'r') as f:
deposit_json = json.load(f)
return all([verify_deposit(deposit) for deposit in deposit_json])
return False
def verify_deposit(deposit_data_dict: dict) -> bool:
'''
Checks whether a deposit is valid based on the eth2 rules.
https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#deposits
'''
pubkey = bytes.fromhex(deposit_data_dict['pubkey'])
withdrawal_credentials = bytes.fromhex(deposit_data_dict['withdrawal_credentials'])
amount = deposit_data_dict['amount']
signature = bytes.fromhex(deposit_data_dict['signature'])
deposit_data_root = bytes.fromhex(deposit_data_dict['deposit_data_root'])
# Verify deposit amount
if not MIN_DEPOSIT_AMOUNT < amount <= MAX_DEPOSIT_AMOUNT:
return False
# Verify deposit signature && pubkey
deposit_message = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount)
domain = compute_domain(domain_type=DOMAIN_DEPOSIT)
signing_root = compute_signing_root(deposit_message, domain)
if not bls.Verify(pubkey, signing_root, signature):
return False
# Verify Deposit Root
deposit = Deposit(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, signature=signature)
return deposit.hash_tree_root == deposit_data_root

View File

@ -14,6 +14,7 @@ from utils.constants import (
bytes8 = ByteVector(8)
# Crypto Domain SSZ
class SigningRoot(Serializable):
@ -41,9 +42,9 @@ def compute_signing_root(ssz_object: Serializable, domain: bytes) -> bytes:
return domain_wrapped_object.hash_tree_root
# DepositData SSZ
# DepositMessage SSZ
class DepositData(Serializable):
class DepositMessage(Serializable):
fields = [
('pubkey', bytes48),
('withdrawal_credentials', bytes32),
@ -51,7 +52,7 @@ class DepositData(Serializable):
]
class SignedDepositData(Serializable):
class Deposit(Serializable):
fields = [
('pubkey', bytes48),
('withdrawal_credentials', bytes32),