Merge pull request #28 from ethereum/carl_CredentialList

CredentialList class
This commit is contained in:
Carl Beekhuizen 2020-05-26 16:25:47 +02:00 committed by GitHub
commit 1f6f63bce3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 76 additions and 73 deletions

10
.gitignore vendored
View File

@ -1,8 +1,12 @@
validator_keys validator_keys
# Python testing & linting: # Python testing & linting:
build/
dist/
venv/ venv/
.pytest_cache *.pytest_cache
.hypothesis *.hypothesis
.mypy_cache *.mypy_cache
*.egg-info
*.egg
__pycache__ __pycache__

View File

@ -9,17 +9,20 @@ from eth2deposit.key_handling.keystore import (
Keystore, Keystore,
ScryptKeystore, ScryptKeystore,
) )
from eth2deposit.utils.constants import BLS_WITHDRAWAL_PREFIX from eth2deposit.utils.constants import (
BLS_WITHDRAWAL_PREFIX,
DOMAIN_DEPOSIT,
)
from eth2deposit.utils.crypto import SHA256 from eth2deposit.utils.crypto import SHA256
from eth2deposit.utils.ssz import ( from eth2deposit.utils.ssz import (
compute_domain, compute_domain,
compute_signing_root, compute_signing_root,
DepositData,
DepositMessage, DepositMessage,
Deposit,
) )
class ValidatorCredentials: class Credential:
def __init__(self, *, mnemonic: str, index: int, amount: int): def __init__(self, *, mnemonic: str, index: int, amount: int):
# Set path as EIP-2334 format # Set path as EIP-2334 format
# https://eips.ethereum.org/EIPS/eip-2334 # https://eips.ethereum.org/EIPS/eip-2334
@ -63,55 +66,51 @@ class ValidatorCredentials:
secret_bytes = saved_keystore.decrypt(password) secret_bytes = saved_keystore.decrypt(password)
return self.signing_sk == int.from_bytes(secret_bytes, 'big') return self.signing_sk == int.from_bytes(secret_bytes, 'big')
def unsigned_deposit(self) -> DepositMessage:
def mnemonic_to_credentials(*, mnemonic: str, num_keys: int, return DepositMessage(
amounts: List[int], start_index: int=0,) -> List[ValidatorCredentials]: pubkey=self.signing_pk,
assert len(amounts) == num_keys withdrawal_credentials=self.withdrawal_credentials,
key_indices = range(start_index, start_index + num_keys) amount=self.amount,
credentials = [ValidatorCredentials(mnemonic=mnemonic, index=index, amount=amounts[index])
for index in key_indices]
return credentials
def export_keystores(*, credentials: List[ValidatorCredentials], password: str, folder: str) -> List[str]:
return [credential.save_signing_keystore(password=password, folder=folder) for credential in credentials]
def sign_deposit_data(deposit_data: DepositMessage, sk: int) -> Deposit:
'''
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 = Deposit(
**deposit_data.as_dict(),
signature=bls.Sign(sk, signing_root)
)
return signed_deposit_data
def export_deposit_data_json(*, credentials: List[ValidatorCredentials], folder: str) -> str:
deposit_data: List[Dict[bytes, bytes]] = []
for credential in credentials:
deposit_datum = DepositMessage(
pubkey=credential.signing_pk,
withdrawal_credentials=credential.withdrawal_credentials,
amount=credential.amount,
) )
signed_deposit_datum = sign_deposit_data(deposit_datum, credential.signing_sk)
datum_dict = signed_deposit_datum.as_dict()
datum_dict.update({'deposit_data_root': deposit_datum.hash_tree_root})
datum_dict.update({'signed_deposit_data_root': signed_deposit_datum.hash_tree_root})
deposit_data.append(datum_dict)
filefolder = os.path.join(folder, 'deposit_data-%i.json' % time.time()) def signed_deposit(self) -> DepositData:
with open(filefolder, 'w') as f: domain = compute_domain(domain_type=DOMAIN_DEPOSIT)
json.dump(deposit_data, f, default=lambda x: x.hex()) signing_root = compute_signing_root(self.unsigned_deposit(), domain)
return filefolder signed_deposit = DepositData(
**self.unsigned_deposit().as_dict(),
signature=bls.Sign(self.signing_sk, signing_root)
)
return signed_deposit
def verify_keystores(*, credentials: List[ValidatorCredentials], class CredentialList:
keystore_filefolders: List[str], password: str) -> bool: def __init__(self, credentials: List[Credential]):
return all(credential.verify_keystore(keystore_filefolder=filefolder, password=password) self.credentials = credentials
for credential, filefolder in zip(credentials, keystore_filefolders))
@classmethod
def from_mnemonic(cls, *, mnemonic: str, num_keys: int, amounts: List[int], start_index: int=0) -> 'CredentialList':
assert len(amounts) == num_keys
key_indices = range(start_index, start_index + num_keys)
return cls([Credential(mnemonic=mnemonic, index=index, amount=amounts[index])
for index in key_indices])
def export_keystores(self, password: str, folder: str) -> List[str]:
return [credential.save_signing_keystore(password=password, folder=folder) for credential in self.credentials]
def export_deposit_data_json(self, folder: str) -> str:
deposit_data: List[Dict[bytes, bytes]] = []
for credential in self.credentials:
signed_deposit_datum = credential.signed_deposit()
datum_dict = signed_deposit_datum.as_dict()
datum_dict.update({'deposit_data_root': credential.unsigned_deposit().hash_tree_root})
datum_dict.update({'signed_deposit_data_root': signed_deposit_datum.hash_tree_root})
deposit_data.append(datum_dict)
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
def verify_keystores(self, keystore_filefolders: List[str], password: str) -> bool:
return all(credential.verify_keystore(keystore_filefolder=filefolder, password=password)
for credential, filefolder in zip(self.credentials, keystore_filefolders))

View File

@ -3,16 +3,13 @@ import sys
import click import click
from eth2deposit.credentials import ( from eth2deposit.credentials import (
mnemonic_to_credentials, CredentialList,
export_keystores,
export_deposit_data_json,
verify_keystores,
) )
from eth2deposit.key_handling.key_derivation.mnemonic import ( from eth2deposit.key_handling.key_derivation.mnemonic import (
get_languages, get_languages,
get_mnemonic, get_mnemonic,
) )
from eth2deposit.utils.eth2_deposit_check import verify_deposit_data_json from eth2deposit.utils.validation import verify_deposit_data_json
from eth2deposit.utils.constants import ( from eth2deposit.utils.constants import (
WORD_LISTS_PATH, WORD_LISTS_PATH,
MAX_DEPOSIT_AMOUNT, MAX_DEPOSIT_AMOUNT,
@ -78,13 +75,13 @@ def main(num_validators: int, mnemonic_language: str, folder: str, password: str
click.clear() click.clear()
click.echo(RHINO_0) click.echo(RHINO_0)
click.echo('Creating your keys.') click.echo('Creating your keys.')
credentials = mnemonic_to_credentials(mnemonic=mnemonic, num_keys=num_validators, amounts=amounts) credentials = CredentialList.from_mnemonic(mnemonic=mnemonic, num_keys=num_validators, amounts=amounts)
click.echo('Saving your keystore(s).') click.echo('Saving your keystore(s).')
keystore_filefolders = export_keystores(credentials=credentials, password=password, folder=folder) keystore_filefolders = credentials.export_keystores(password=password, folder=folder)
click.echo('Creating your deposit(s).') click.echo('Creating your deposit(s).')
deposits_file = export_deposit_data_json(credentials=credentials, folder=folder) deposits_file = credentials.export_deposit_data_json(folder=folder)
click.echo('Verifying your keystore(s).') click.echo('Verifying your keystore(s).')
assert verify_keystores(credentials=credentials, keystore_filefolders=keystore_filefolders, password=password) assert credentials.verify_keystores(keystore_filefolders=keystore_filefolders, password=password)
click.echo('Verifying your deposit(s).') click.echo('Verifying your deposit(s).')
assert verify_deposit_data_json(deposits_file) assert verify_deposit_data_json(deposits_file)
click.echo('\nSuccess!\nYour keys can be found at: %s' % folder) click.echo('\nSuccess!\nYour keys can be found at: %s' % folder)

View File

@ -42,8 +42,6 @@ def compute_signing_root(ssz_object: Serializable, domain: bytes) -> bytes:
return domain_wrapped_object.hash_tree_root return domain_wrapped_object.hash_tree_root
# DepositMessage SSZ
class DepositMessage(Serializable): class DepositMessage(Serializable):
fields = [ fields = [
('pubkey', bytes48), ('pubkey', bytes48),
@ -52,7 +50,7 @@ class DepositMessage(Serializable):
] ]
class Deposit(Serializable): class DepositData(Serializable):
fields = [ fields = [
('pubkey', bytes48), ('pubkey', bytes48),
('withdrawal_credentials', bytes32), ('withdrawal_credentials', bytes32),

View File

@ -10,7 +10,7 @@ from py_ecc.bls import G2ProofOfPossession as bls
from eth2deposit.utils.ssz import ( from eth2deposit.utils.ssz import (
compute_domain, compute_domain,
compute_signing_root, compute_signing_root,
Deposit, DepositData,
DepositMessage, DepositMessage,
) )
from eth2deposit.utils.constants import ( from eth2deposit.utils.constants import (
@ -23,11 +23,11 @@ from eth2deposit.utils.constants import (
def verify_deposit_data_json(filefolder: str) -> bool: def verify_deposit_data_json(filefolder: str) -> bool:
with open(filefolder, 'r') as f: with open(filefolder, 'r') as f:
deposit_json = json.load(f) deposit_json = json.load(f)
return all([verify_deposit(deposit) for deposit in deposit_json]) return all([validate_deposit(deposit) for deposit in deposit_json])
return False return False
def verify_deposit(deposit_data_dict: Dict[str, Any]) -> bool: def validate_deposit(deposit_data_dict: Dict[str, Any]) -> bool:
''' '''
Checks whether a deposit is valid based on the eth2 rules. 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 https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#deposits
@ -43,12 +43,17 @@ def verify_deposit(deposit_data_dict: Dict[str, Any]) -> bool:
return False return False
# Verify deposit signature && pubkey # Verify deposit signature && pubkey
deposit_message = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount) unsigned_deposit = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount)
domain = compute_domain(domain_type=DOMAIN_DEPOSIT) domain = compute_domain(domain_type=DOMAIN_DEPOSIT)
signing_root = compute_signing_root(deposit_message, domain) signing_root = compute_signing_root(unsigned_deposit, domain)
if not bls.Verify(pubkey, signing_root, signature): if not bls.Verify(pubkey, signing_root, signature):
return False return False
# Verify Deposit Root # Verify Deposit Root
deposit = Deposit(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, signature=signature) signed_deposit = DepositData(
return deposit.hash_tree_root == deposit_data_root pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
amount=amount,
signature=signature,
)
return signed_deposit.hash_tree_root == deposit_data_root