From 6c01fa968f03b6384278eeaeaad5a6c0bfbaa1c0 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Thu, 21 May 2020 14:43:02 +0200 Subject: [PATCH 1/5] Creates CredentialList --- cli/deposit.py | 15 ++- eth2deposit/credentials.py | 99 +++++++++---------- .../{eth2_deposit_check.py => validation.py} | 4 +- 3 files changed, 57 insertions(+), 61 deletions(-) rename eth2deposit/utils/{eth2_deposit_check.py => validation.py} (92%) diff --git a/cli/deposit.py b/cli/deposit.py index 796969a..a336360 100644 --- a/cli/deposit.py +++ b/cli/deposit.py @@ -3,16 +3,13 @@ import sys import click from eth2deposit.credentials import ( - mnemonic_to_credentials, - export_keystores, - export_deposit_data_json, - verify_keystores, + CredentialList, ) from eth2deposit.key_handling.key_derivation.mnemonic import ( get_languages, 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 ( WORD_LISTS_PATH, MAX_DEPOSIT_AMOUNT, @@ -78,13 +75,13 @@ def main(num_validators: int, mnemonic_language: str, folder: str, password: str click.clear() click.echo(RHINO_0) 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).') - 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).') - 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).') - 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).') assert verify_deposit_data_json(deposits_file) click.echo('\nSuccess!\nYour keys can be found at: %s' % folder) diff --git a/eth2deposit/credentials.py b/eth2deposit/credentials.py index e6411b3..a373be9 100644 --- a/eth2deposit/credentials.py +++ b/eth2deposit/credentials.py @@ -18,7 +18,7 @@ from eth2deposit.utils.ssz import ( ) -class ValidatorCredentials: +class Credential: def __init__(self, *, mnemonic: str, index: int, amount: int): self.signing_key_path = 'm/12381/3600/%s/0' % index self.signing_sk = mnemonic_and_path_to_key(mnemonic=mnemonic, path=self.signing_key_path) @@ -48,55 +48,54 @@ class ValidatorCredentials: secret_bytes = saved_keystore.decrypt(password) return self.signing_sk == int.from_bytes(secret_bytes, 'big') - -def mnemonic_to_credentials(*, mnemonic: str, num_keys: int, - amounts: List[int], start_index: int=0,) -> List[ValidatorCredentials]: - assert len(amounts) == num_keys - key_indices = range(start_index, start_index + num_keys) - 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=SHA256(credential.withdrawal_pk), - amount=credential.amount, + def sign_deposit_data(self, deposit_message: DepositMessage) -> Deposit: + ''' + Given a DepositMessage, it signs its root and returns a DepositData + ''' + assert bls.PrivToPub(self.signing_sk) == deposit_message.pubkey + domain = compute_domain() + signing_root = compute_signing_root(deposit_message, domain) + signed_deposit_data = Deposit( + **deposit_message.as_dict(), + signature=bls.Sign(self.signing_sk, signing_root) ) - 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()) - with open(filefolder, 'w') as f: - json.dump(deposit_data, f, default=lambda x: x.hex()) - return filefolder + return signed_deposit_data -def verify_keystores(*, credentials: List[ValidatorCredentials], - keystore_filefolders: List[str], password: str) -> bool: - return all(credential.verify_keystore(keystore_filefolder=filefolder, password=password) - for credential, filefolder in zip(credentials, keystore_filefolders)) +class CredentialList: + def __init__(self, credentials: List[Credential]): + self.credentials = credentials + + @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: + deposit_datum = DepositMessage( + pubkey=credential.signing_pk, + withdrawal_credentials=SHA256(credential.withdrawal_pk), + amount=credential.amount, + ) + signed_deposit_datum = credential.sign_deposit_data(deposit_datum) + + 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()) + 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)) diff --git a/eth2deposit/utils/eth2_deposit_check.py b/eth2deposit/utils/validation.py similarity index 92% rename from eth2deposit/utils/eth2_deposit_check.py rename to eth2deposit/utils/validation.py index fa07709..8c64295 100644 --- a/eth2deposit/utils/eth2_deposit_check.py +++ b/eth2deposit/utils/validation.py @@ -23,11 +23,11 @@ from eth2deposit.utils.constants import ( 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 all([validate_deposit(deposit) for deposit in deposit_json]) 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. https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#deposits From 050f3ca10a358bac7d479cf7d843d95cb1c88a4c Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Fri, 22 May 2020 16:09:20 +0200 Subject: [PATCH 2/5] Credential.deposit methods --- eth2deposit/credentials.py | 42 ++++++++++++++++----------------- eth2deposit/utils/ssz.py | 6 ++--- eth2deposit/utils/validation.py | 17 ++++++++----- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/eth2deposit/credentials.py b/eth2deposit/credentials.py index 0bb1f51..5873f29 100644 --- a/eth2deposit/credentials.py +++ b/eth2deposit/credentials.py @@ -9,13 +9,16 @@ from eth2deposit.key_handling.keystore import ( Keystore, 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.ssz import ( compute_domain, compute_signing_root, - DepositMessage, - Deposit, + SignedDeposit, + UnsignedDeposit, ) @@ -55,18 +58,21 @@ class Credential: secret_bytes = saved_keystore.decrypt(password) return self.signing_sk == int.from_bytes(secret_bytes, 'big') - def sign_deposit_data(self, deposit_data: DepositMessage) -> Deposit: - ''' - Given a DepositMessage, it signs its root and returns a Deposit - ''' - assert bls.PrivToPub(self.signing_sk) == deposit_data.pubkey - domain = compute_domain() - signing_root = compute_signing_root(deposit_data, domain) - signed_deposit_data = Deposit( - **deposit_data.as_dict(), + def unsigned_deposit(self) -> UnsignedDeposit: + return UnsignedDeposit( + pubkey=self.signing_pk, + withdrawal_credentials=self.withdrawal_credentials, + amount=self.amount, + ) + + def signed_deposit(self) -> SignedDeposit: + domain = compute_domain(domain_type=DOMAIN_DEPOSIT) + signing_root = compute_signing_root(self.unsigned_deposit(), domain) + signed_deposit = SignedDeposit( + **self.unsigned_deposit().as_dict(), signature=bls.Sign(self.signing_sk, signing_root) ) - return signed_deposit_data + return signed_deposit class CredentialList: @@ -86,15 +92,9 @@ class CredentialList: def export_deposit_data_json(self, folder: str) -> str: deposit_data: List[Dict[bytes, bytes]] = [] for credential in self.credentials: - deposit_datum = DepositMessage( - pubkey=credential.signing_pk, - withdrawal_credentials=credential.withdrawal_credentials, - amount=credential.amount, - ) - signed_deposit_datum = credential.sign_deposit_data(deposit_datum) - + signed_deposit_datum = credential.signed_deposit() datum_dict = signed_deposit_datum.as_dict() - datum_dict.update({'deposit_data_root': deposit_datum.hash_tree_root}) + 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) diff --git a/eth2deposit/utils/ssz.py b/eth2deposit/utils/ssz.py index f03f8a8..961bf3f 100644 --- a/eth2deposit/utils/ssz.py +++ b/eth2deposit/utils/ssz.py @@ -42,9 +42,7 @@ def compute_signing_root(ssz_object: Serializable, domain: bytes) -> bytes: return domain_wrapped_object.hash_tree_root -# DepositMessage SSZ - -class DepositMessage(Serializable): +class UnsignedDeposit(Serializable): fields = [ ('pubkey', bytes48), ('withdrawal_credentials', bytes32), @@ -52,7 +50,7 @@ class DepositMessage(Serializable): ] -class Deposit(Serializable): +class SignedDeposit(Serializable): fields = [ ('pubkey', bytes48), ('withdrawal_credentials', bytes32), diff --git a/eth2deposit/utils/validation.py b/eth2deposit/utils/validation.py index 8c64295..50bd941 100644 --- a/eth2deposit/utils/validation.py +++ b/eth2deposit/utils/validation.py @@ -10,8 +10,8 @@ from py_ecc.bls import G2ProofOfPossession as bls from eth2deposit.utils.ssz import ( compute_domain, compute_signing_root, - Deposit, - DepositMessage, + SignedDeposit, + UnsignedDeposit, ) from eth2deposit.utils.constants import ( DOMAIN_DEPOSIT, @@ -43,12 +43,17 @@ def validate_deposit(deposit_data_dict: Dict[str, Any]) -> bool: return False # Verify deposit signature && pubkey - deposit_message = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount) + unsigned_deposit = UnsignedDeposit(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount) 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): 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 + signed_deposit = SignedDeposit( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + signature=signature, + ) + return signed_deposit.hash_tree_root == deposit_data_root From f3eee88bc2af2c8b87e74f9a04c3507122bfa7b8 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Fri, 22 May 2020 16:11:38 +0200 Subject: [PATCH 3/5] adds build/ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7f10f4d..99f9e62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ validator_keys # Python testing & linting: +build/ venv/ .pytest_cache .hypothesis From d96670d4ffb7798f166544192b96a590ca9e34cc Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Mon, 25 May 2020 14:54:36 +0200 Subject: [PATCH 4/5] Revert to DepositMessage & DepositData as per spec --- eth2deposit/credentials.py | 12 ++++++------ eth2deposit/utils/ssz.py | 4 ++-- eth2deposit/utils/validation.py | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/eth2deposit/credentials.py b/eth2deposit/credentials.py index 5873f29..99ec291 100644 --- a/eth2deposit/credentials.py +++ b/eth2deposit/credentials.py @@ -17,8 +17,8 @@ from eth2deposit.utils.crypto import SHA256 from eth2deposit.utils.ssz import ( compute_domain, compute_signing_root, - SignedDeposit, - UnsignedDeposit, + DepositData, + DepositMessage, ) @@ -58,17 +58,17 @@ class Credential: secret_bytes = saved_keystore.decrypt(password) return self.signing_sk == int.from_bytes(secret_bytes, 'big') - def unsigned_deposit(self) -> UnsignedDeposit: - return UnsignedDeposit( + def unsigned_deposit(self) -> DepositMessage: + return DepositMessage( pubkey=self.signing_pk, withdrawal_credentials=self.withdrawal_credentials, amount=self.amount, ) - def signed_deposit(self) -> SignedDeposit: + def signed_deposit(self) -> DepositData: domain = compute_domain(domain_type=DOMAIN_DEPOSIT) signing_root = compute_signing_root(self.unsigned_deposit(), domain) - signed_deposit = SignedDeposit( + signed_deposit = DepositData( **self.unsigned_deposit().as_dict(), signature=bls.Sign(self.signing_sk, signing_root) ) diff --git a/eth2deposit/utils/ssz.py b/eth2deposit/utils/ssz.py index 961bf3f..0246028 100644 --- a/eth2deposit/utils/ssz.py +++ b/eth2deposit/utils/ssz.py @@ -42,7 +42,7 @@ def compute_signing_root(ssz_object: Serializable, domain: bytes) -> bytes: return domain_wrapped_object.hash_tree_root -class UnsignedDeposit(Serializable): +class DepositMessage(Serializable): fields = [ ('pubkey', bytes48), ('withdrawal_credentials', bytes32), @@ -50,7 +50,7 @@ class UnsignedDeposit(Serializable): ] -class SignedDeposit(Serializable): +class DepositData(Serializable): fields = [ ('pubkey', bytes48), ('withdrawal_credentials', bytes32), diff --git a/eth2deposit/utils/validation.py b/eth2deposit/utils/validation.py index 50bd941..eb41e58 100644 --- a/eth2deposit/utils/validation.py +++ b/eth2deposit/utils/validation.py @@ -10,8 +10,8 @@ from py_ecc.bls import G2ProofOfPossession as bls from eth2deposit.utils.ssz import ( compute_domain, compute_signing_root, - SignedDeposit, - UnsignedDeposit, + DepositData, + DepositMessage, ) from eth2deposit.utils.constants import ( DOMAIN_DEPOSIT, @@ -43,14 +43,14 @@ def validate_deposit(deposit_data_dict: Dict[str, Any]) -> bool: return False # Verify deposit signature && pubkey - unsigned_deposit = UnsignedDeposit(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) signing_root = compute_signing_root(unsigned_deposit, domain) if not bls.Verify(pubkey, signing_root, signature): return False # Verify Deposit Root - signed_deposit = SignedDeposit( + signed_deposit = DepositData( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, From 23fdbdcd9e5f501bb8e8bf211bba51f2b3367cab Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Mon, 25 May 2020 15:02:44 +0200 Subject: [PATCH 5/5] gitignore dist/ --- .gitignore | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 99f9e62..e43cf58 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ validator_keys # Python testing & linting: build/ +dist/ venv/ -.pytest_cache -.hypothesis -.mypy_cache +*.pytest_cache +*.hypothesis +*.mypy_cache +*.egg-info +*.egg __pycache__ \ No newline at end of file