From de49350de763d0da0a498d674c843d8c5d7f899d Mon Sep 17 00:00:00 2001 From: "deepsource-io[bot]" <42547082+deepsource-io[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 12:28:24 +0000 Subject: [PATCH 1/5] ci: add .deepsource.toml --- .deepsource.toml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .deepsource.toml diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 00000000..fdddac50 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,22 @@ +version = 1 + +[[analyzers]] +name = "python" + + [analyzers.meta] + runtime_version = "3.x.x" + +[[transformers]] +name = "ruff" + +[[transformers]] +name = "yapf" + +[[transformers]] +name = "autopep8" + +[[transformers]] +name = "isort" + +[[transformers]] +name = "black" \ No newline at end of file From 3627ce8c4cc07d73feec12be3011a43aee6f07bb Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 12:29:12 +0000 Subject: [PATCH 2/5] style: format code with Autopep8, Black, isort, Ruff Formatter and Yapf This commit fixes the style issues introduced in de49350 according to the output from Autopep8, Black, isort, Ruff Formatter and Yapf. Details: None --- setup.py | 4 +- staking_deposit/cli/existing_mnemonic.py | 90 +++-- .../cli/generate_bls_to_execution_change.py | 176 +++++---- staking_deposit/cli/generate_keys.py | 168 +++++--- staking_deposit/cli/new_mnemonic.py | 55 +-- staking_deposit/credentials.py | 248 ++++++++---- staking_deposit/deposit.py | 42 +- staking_deposit/exceptions.py | 3 +- .../key_handling/key_derivation/mnemonic.py | 89 +++-- .../key_handling/key_derivation/path.py | 17 +- .../key_handling/key_derivation/tree.py | 37 +- staking_deposit/key_handling/keystore.py | 157 ++++---- staking_deposit/settings.py | 62 ++- staking_deposit/utils/ascii_art.py | 4 +- staking_deposit/utils/click.py | 55 +-- staking_deposit/utils/config.py | 9 +- staking_deposit/utils/constants.py | 97 ++--- staking_deposit/utils/crypto.py | 53 +-- staking_deposit/utils/intl.py | 79 ++-- staking_deposit/utils/ssz.py | 62 +-- staking_deposit/utils/validation.py | 227 ++++++----- test_binary_btec_script.py | 57 +-- test_binary_deposit_script.py | 51 +-- test_btec_script.py | 62 +-- test_deposit_script.py | 56 +-- tests/test_cli/helpers.py | 10 +- tests/test_cli/test_existing_menmonic.py | 252 +++++++----- .../test_generate_bls_to_execution_change.py | 116 +++--- tests/test_cli/test_new_mnemonic.py | 373 +++++++++++------- tests/test_cli/test_regeneration.py | 88 +++-- tests/test_intl/test_json_schema.py | 18 +- .../test_key_derivation/test_mnemonic.py | 74 ++-- .../test_key_derivation/test_path.py | 91 +++-- .../test_key_derivation/test_tree.py | 66 ++-- tests/test_key_handling/test_keystore.py | 67 ++-- tests/test_utils/test_constants.py | 23 +- tests/test_utils/test_crypto.py | 48 +-- tests/test_utils/test_intl.py | 107 +++-- tests/test_utils/test_ssz.py | 34 +- tests/test_utils/test_validation.py | 39 +- 40 files changed, 1962 insertions(+), 1404 deletions(-) diff --git a/setup.py b/setup.py index 4003f37e..16b10ae6 100644 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ setup( name="staking_deposit", - version='2.7.0', + version="2.7.0", py_modules=["staking_deposit"], - packages=find_packages(exclude=('tests', 'docs')), + packages=find_packages(exclude=("tests", "docs")), python_requires=">=3.8,<4", ) diff --git a/staking_deposit/cli/existing_mnemonic.py b/staking_deposit/cli/existing_mnemonic.py index 8aa87394..eed176e2 100644 --- a/staking_deposit/cli/existing_mnemonic.py +++ b/staking_deposit/cli/existing_mnemonic.py @@ -1,53 +1,54 @@ +from typing import Any, Callable + import click -from typing import ( - Any, - Callable, -) from staking_deposit.exceptions import ValidationError -from staking_deposit.key_handling.key_derivation.mnemonic import ( - reconstruct_mnemonic, -) -from staking_deposit.utils.constants import ( - WORD_LISTS_PATH, -) -from staking_deposit.utils.click import ( - captive_prompt_callback, - jit_option, -) +from staking_deposit.key_handling.key_derivation.mnemonic import reconstruct_mnemonic +from staking_deposit.utils.click import captive_prompt_callback, jit_option +from staking_deposit.utils.constants import WORD_LISTS_PATH from staking_deposit.utils.intl import load_text from staking_deposit.utils.validation import validate_int_range -from .generate_keys import ( - generate_keys, - generate_keys_arguments_decorator, -) + +from .generate_keys import generate_keys, generate_keys_arguments_decorator -def load_mnemonic_arguments_decorator(function: Callable[..., Any]) -> Callable[..., Any]: - ''' +def load_mnemonic_arguments_decorator( + function: Callable[..., Any], +) -> Callable[..., Any]: + """ This is a decorator that, when applied to a parent-command, implements the to obtain the necessary arguments for the generate_keys() subcommand. - ''' + """ decorators = [ jit_option( callback=validate_mnemonic, - help=lambda: load_text(['arg_mnemonic', 'help'], func='existing_mnemonic'), - param_decls='--mnemonic', - prompt=lambda: load_text(['arg_mnemonic', 'prompt'], func='existing_mnemonic'), + help=lambda: load_text(["arg_mnemonic", "help"], func="existing_mnemonic"), + param_decls="--mnemonic", + prompt=lambda: load_text( + ["arg_mnemonic", "prompt"], func="existing_mnemonic" + ), type=str, ), jit_option( callback=captive_prompt_callback( lambda x: x, - lambda: load_text(['arg_mnemonic_password', 'prompt'], func='existing_mnemonic'), - lambda: load_text(['arg_mnemonic_password', 'confirm'], func='existing_mnemonic'), - lambda: load_text(['arg_mnemonic_password', 'mismatch'], func='existing_mnemonic'), + lambda: load_text( + ["arg_mnemonic_password", "prompt"], func="existing_mnemonic" + ), + lambda: load_text( + ["arg_mnemonic_password", "confirm"], func="existing_mnemonic" + ), + lambda: load_text( + ["arg_mnemonic_password", "mismatch"], func="existing_mnemonic" + ), True, ), - default='', - help=lambda: load_text(['arg_mnemonic_password', 'help'], func='existing_mnemonic'), + default="", + help=lambda: load_text( + ["arg_mnemonic_password", "help"], func="existing_mnemonic" + ), hidden=True, - param_decls='--mnemonic-password', + param_decls="--mnemonic-password", prompt=False, ), ] @@ -61,27 +62,38 @@ def validate_mnemonic(ctx: click.Context, param: Any, mnemonic: str) -> str: if mnemonic is not None: return mnemonic else: - raise ValidationError(load_text(['err_invalid_mnemonic'])) + raise ValidationError(load_text(["err_invalid_mnemonic"])) @click.command( - help=load_text(['arg_existing_mnemonic', 'help'], func='existing_mnemonic'), + help=load_text(["arg_existing_mnemonic", "help"], func="existing_mnemonic"), ) @load_mnemonic_arguments_decorator @jit_option( callback=captive_prompt_callback( lambda num: validate_int_range(num, 0, 2**32), - lambda: load_text(['arg_validator_start_index', 'prompt'], func='existing_mnemonic'), - lambda: load_text(['arg_validator_start_index', 'confirm'], func='existing_mnemonic'), + lambda: load_text( + ["arg_validator_start_index", "prompt"], func="existing_mnemonic" + ), + lambda: load_text( + ["arg_validator_start_index", "confirm"], func="existing_mnemonic" + ), ), default=0, - help=lambda: load_text(['arg_validator_start_index', 'help'], func='existing_mnemonic'), + help=lambda: load_text( + ["arg_validator_start_index", "help"], func="existing_mnemonic" + ), param_decls="--validator_start_index", - prompt=lambda: load_text(['arg_validator_start_index', 'prompt'], func='existing_mnemonic'), + prompt=lambda: load_text( + ["arg_validator_start_index", "prompt"], func="existing_mnemonic" + ), ) @generate_keys_arguments_decorator @click.pass_context -def existing_mnemonic(ctx: click.Context, mnemonic: str, mnemonic_password: str, **kwargs: Any) -> None: - ctx.obj = {} if ctx.obj is None else ctx.obj # Create a new ctx.obj if it doesn't exist - ctx.obj.update({'mnemonic': mnemonic, 'mnemonic_password': mnemonic_password}) +def existing_mnemonic( + ctx: click.Context, mnemonic: str, mnemonic_password: str, **kwargs: Any +) -> None: + # Create a new ctx.obj if it doesn't exist + ctx.obj = {} if ctx.obj is None else ctx.obj + ctx.obj.update({"mnemonic": mnemonic, "mnemonic_password": mnemonic_password}) ctx.forward(generate_keys) diff --git a/staking_deposit/cli/generate_bls_to_execution_change.py b/staking_deposit/cli/generate_bls_to_execution_change.py index 0b3b0f49..1dc3245e 100644 --- a/staking_deposit/cli/generate_bls_to_execution_change.py +++ b/staking_deposit/cli/generate_bls_to_execution_change.py @@ -1,38 +1,12 @@ -import os -import click import json -from typing import ( - Any, - Sequence, -) +import os +from typing import Any, Sequence +import click from eth_typing import HexAddress -from staking_deposit.credentials import ( - CredentialList, -) -from staking_deposit.utils.validation import ( - validate_bls_withdrawal_credentials_list, - validate_bls_withdrawal_credentials_matching, - validate_eth1_withdrawal_address, - validate_int_range, - verify_bls_to_execution_change_json, - validate_validator_indices, -) -from staking_deposit.utils.constants import ( - DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME, - MAX_DEPOSIT_AMOUNT, -) -from staking_deposit.utils.click import ( - captive_prompt_callback, - choice_prompt_func, - jit_option, -) +from staking_deposit.credentials import CredentialList from staking_deposit.exceptions import ValidationError -from staking_deposit.utils.intl import ( - closest_match, - load_text, -) from staking_deposit.settings import ( ALL_CHAINS, MAINNET, @@ -40,104 +14,131 @@ get_chain_setting, get_devnet_chain_setting, ) -from .existing_mnemonic import ( - load_mnemonic_arguments_decorator, +from staking_deposit.utils.click import ( + captive_prompt_callback, + choice_prompt_func, + jit_option, +) +from staking_deposit.utils.constants import ( + DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME, + MAX_DEPOSIT_AMOUNT, +) +from staking_deposit.utils.intl import closest_match, load_text +from staking_deposit.utils.validation import ( + validate_bls_withdrawal_credentials_list, + validate_bls_withdrawal_credentials_matching, + validate_eth1_withdrawal_address, + validate_int_range, + validate_validator_indices, + verify_bls_to_execution_change_json, ) +from .existing_mnemonic import load_mnemonic_arguments_decorator + def get_password(text: str) -> str: return click.prompt(text, hide_input=True, show_default=False, type=str) -FUNC_NAME = 'generate_bls_to_execution_change' +FUNC_NAME = "generate_bls_to_execution_change" @click.command( - help=load_text(['arg_generate_bls_to_execution_change', 'help'], func=FUNC_NAME), + help=load_text(["arg_generate_bls_to_execution_change", "help"], func=FUNC_NAME), ) @jit_option( default=os.getcwd(), - help=lambda: load_text(['arg_bls_to_execution_changes_folder', 'help'], func=FUNC_NAME), - param_decls='--bls_to_execution_changes_folder', + help=lambda: load_text( + ["arg_bls_to_execution_changes_folder", "help"], func=FUNC_NAME + ), + param_decls="--bls_to_execution_changes_folder", type=click.Path(exists=True, file_okay=False, dir_okay=True), ) @jit_option( callback=captive_prompt_callback( lambda x: closest_match(x, list(ALL_CHAINS.keys())), choice_prompt_func( - lambda: load_text(['arg_chain', 'prompt'], func=FUNC_NAME), - list(ALL_CHAINS.keys()) + lambda: load_text(["arg_chain", "prompt"], func=FUNC_NAME), + list(ALL_CHAINS.keys()), ), ), default=MAINNET, - help=lambda: load_text(['arg_chain', 'help'], func=FUNC_NAME), - param_decls='--chain', + help=lambda: load_text(["arg_chain", "help"], func=FUNC_NAME), + param_decls="--chain", prompt=choice_prompt_func( - lambda: load_text(['arg_chain', 'prompt'], func=FUNC_NAME), + lambda: load_text(["arg_chain", "prompt"], func=FUNC_NAME), # Since `prater` is alias of `goerli`, do not show `prater` in the prompt message. - list(key for key in ALL_CHAINS.keys() if key != PRATER) + list(key for key in ALL_CHAINS.keys() if key != PRATER), ), ) @load_mnemonic_arguments_decorator @jit_option( callback=captive_prompt_callback( lambda num: validate_int_range(num, 0, 2**32), - lambda: load_text(['arg_validator_start_index', 'prompt'], func=FUNC_NAME), + lambda: load_text(["arg_validator_start_index", "prompt"], func=FUNC_NAME), ), default=0, - help=lambda: load_text(['arg_validator_start_index', 'help'], func=FUNC_NAME), + help=lambda: load_text(["arg_validator_start_index", "help"], func=FUNC_NAME), param_decls="--validator_start_index", - prompt=lambda: load_text(['arg_validator_start_index', 'prompt'], func=FUNC_NAME), + prompt=lambda: load_text(["arg_validator_start_index", "prompt"], func=FUNC_NAME), ) @jit_option( callback=captive_prompt_callback( lambda validator_indices: validate_validator_indices(validator_indices), - lambda: load_text(['arg_validator_indices', 'prompt'], func=FUNC_NAME), + lambda: load_text(["arg_validator_indices", "prompt"], func=FUNC_NAME), ), - help=lambda: load_text(['arg_validator_indices', 'help'], func=FUNC_NAME), - param_decls='--validator_indices', - prompt=lambda: load_text(['arg_validator_indices', 'prompt'], func=FUNC_NAME), + help=lambda: load_text(["arg_validator_indices", "help"], func=FUNC_NAME), + param_decls="--validator_indices", + prompt=lambda: load_text(["arg_validator_indices", "prompt"], func=FUNC_NAME), ) @jit_option( callback=captive_prompt_callback( - lambda bls_withdrawal_credentials_list: - validate_bls_withdrawal_credentials_list(bls_withdrawal_credentials_list), - lambda: load_text(['arg_bls_withdrawal_credentials_list', 'prompt'], func=FUNC_NAME), + lambda bls_withdrawal_credentials_list: validate_bls_withdrawal_credentials_list( + bls_withdrawal_credentials_list + ), + lambda: load_text( + ["arg_bls_withdrawal_credentials_list", "prompt"], func=FUNC_NAME + ), + ), + help=lambda: load_text( + ["arg_bls_withdrawal_credentials_list", "help"], func=FUNC_NAME + ), + param_decls="--bls_withdrawal_credentials_list", + prompt=lambda: load_text( + ["arg_bls_withdrawal_credentials_list", "prompt"], func=FUNC_NAME ), - help=lambda: load_text(['arg_bls_withdrawal_credentials_list', 'help'], func=FUNC_NAME), - param_decls='--bls_withdrawal_credentials_list', - prompt=lambda: load_text(['arg_bls_withdrawal_credentials_list', 'prompt'], func=FUNC_NAME), ) @jit_option( callback=captive_prompt_callback( lambda address: validate_eth1_withdrawal_address(None, None, address), - lambda: load_text(['arg_execution_address', 'prompt'], func=FUNC_NAME), - lambda: load_text(['arg_execution_address', 'confirm'], func=FUNC_NAME), - lambda: load_text(['arg_execution_address', 'mismatch'], func=FUNC_NAME), + lambda: load_text(["arg_execution_address", "prompt"], func=FUNC_NAME), + lambda: load_text(["arg_execution_address", "confirm"], func=FUNC_NAME), + lambda: load_text(["arg_execution_address", "mismatch"], func=FUNC_NAME), ), - help=lambda: load_text(['arg_execution_address', 'help'], func=FUNC_NAME), - param_decls=['--execution_address', '--eth1_withdrawal_address'], - prompt=lambda: load_text(['arg_execution_address', 'prompt'], func=FUNC_NAME), + help=lambda: load_text(["arg_execution_address", "help"], func=FUNC_NAME), + param_decls=["--execution_address", "--eth1_withdrawal_address"], + prompt=lambda: load_text(["arg_execution_address", "prompt"], func=FUNC_NAME), ) @jit_option( # Only for devnet tests default=None, help="[DEVNET ONLY] Set specific GENESIS_FORK_VERSION value", - param_decls='--devnet_chain_setting', + param_decls="--devnet_chain_setting", ) @click.pass_context def generate_bls_to_execution_change( - ctx: click.Context, - bls_to_execution_changes_folder: str, - chain: str, - mnemonic: str, - mnemonic_password: str, - validator_start_index: int, - validator_indices: Sequence[int], - bls_withdrawal_credentials_list: Sequence[bytes], - execution_address: HexAddress, - devnet_chain_setting: str, - **kwargs: Any) -> None: + ctx: click.Context, + bls_to_execution_changes_folder: str, + chain: str, + mnemonic: str, + mnemonic_password: str, + validator_start_index: int, + validator_indices: Sequence[int], + bls_withdrawal_credentials_list: Sequence[bytes], + execution_address: HexAddress, + devnet_chain_setting: str, + **kwargs: Any, +) -> None: # Generate folder bls_to_execution_changes_folder = os.path.join( bls_to_execution_changes_folder, @@ -150,12 +151,15 @@ def generate_bls_to_execution_change( chain_setting = get_chain_setting(chain) if devnet_chain_setting is not None: - click.echo('\n%s\n' % '**[Warning] Using devnet chain setting to generate the SignedBLSToExecutionChange.**\t') + click.echo( + "\n%s\n" + % "**[Warning] Using devnet chain setting to generate the SignedBLSToExecutionChange.**\t" + ) devnet_chain_setting_dict = json.loads(devnet_chain_setting) chain_setting = get_devnet_chain_setting( - network_name=devnet_chain_setting_dict['network_name'], - genesis_fork_version=devnet_chain_setting_dict['genesis_fork_version'], - genesis_validator_root=devnet_chain_setting_dict['genesis_validator_root'], + network_name=devnet_chain_setting_dict["network_name"], + genesis_fork_version=devnet_chain_setting_dict["genesis_fork_version"], + genesis_validator_root=devnet_chain_setting_dict["genesis_validator_root"], ) if len(validator_indices) != len(bls_withdrawal_credentials_list): @@ -180,12 +184,16 @@ def generate_bls_to_execution_change( # Check if the given old bls_withdrawal_credentials is as same as the mnemonic generated for i, credential in enumerate(credentials.credentials): try: - validate_bls_withdrawal_credentials_matching(bls_withdrawal_credentials_list[i], credential) + validate_bls_withdrawal_credentials_matching( + bls_withdrawal_credentials_list[i], credential + ) except ValidationError as e: - click.echo('\n[Error] ' + str(e)) + click.echo("\n[Error] " + str(e)) return - btec_file = credentials.export_bls_to_execution_change_json(bls_to_execution_changes_folder, validator_indices) + btec_file = credentials.export_bls_to_execution_change_json( + bls_to_execution_changes_folder, validator_indices + ) json_file_validation_result = verify_bls_to_execution_change_json( btec_file, @@ -195,8 +203,10 @@ def generate_bls_to_execution_change( chain_setting=chain_setting, ) if not json_file_validation_result: - raise ValidationError(load_text(['err_verify_btec'])) + raise ValidationError(load_text(["err_verify_btec"])) - click.echo(load_text(['msg_creation_success']) + str(bls_to_execution_changes_folder)) + click.echo( + load_text(["msg_creation_success"]) + str(bls_to_execution_changes_folder) + ) - click.pause(load_text(['msg_pause'])) + click.pause(load_text(["msg_pause"])) diff --git a/staking_deposit/cli/generate_keys.py b/staking_deposit/cli/generate_keys.py index 9163d55c..f75a1b71 100644 --- a/staking_deposit/cli/generate_keys.py +++ b/staking_deposit/cli/generate_keys.py @@ -1,40 +1,28 @@ import os -import click -from typing import ( - Any, - Callable, -) +from typing import Any, Callable +import click from eth_typing import HexAddress -from staking_deposit.credentials import ( - CredentialList, -) + +from staking_deposit.credentials import CredentialList from staking_deposit.exceptions import ValidationError -from staking_deposit.utils.validation import ( - verify_deposit_data_json, - validate_int_range, - validate_password_strength, - validate_eth1_withdrawal_address, -) -from staking_deposit.utils.constants import ( - MAX_DEPOSIT_AMOUNT, - DEFAULT_VALIDATOR_KEYS_FOLDER_NAME, -) +from staking_deposit.settings import ALL_CHAINS, MAINNET, PRATER, get_chain_setting from staking_deposit.utils.ascii_art import RHINO_0 from staking_deposit.utils.click import ( captive_prompt_callback, choice_prompt_func, jit_option, ) -from staking_deposit.utils.intl import ( - closest_match, - load_text, +from staking_deposit.utils.constants import ( + DEFAULT_VALIDATOR_KEYS_FOLDER_NAME, + MAX_DEPOSIT_AMOUNT, ) -from staking_deposit.settings import ( - ALL_CHAINS, - MAINNET, - PRATER, - get_chain_setting, +from staking_deposit.utils.intl import closest_match, load_text +from staking_deposit.utils.validation import ( + validate_eth1_withdrawal_address, + validate_int_range, + validate_password_strength, + verify_deposit_data_json, ) @@ -42,67 +30,110 @@ def get_password(text: str) -> str: return click.prompt(text, hide_input=True, show_default=False, type=str) -def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[..., Any]: - ''' +def generate_keys_arguments_decorator( + function: Callable[..., Any], +) -> Callable[..., Any]: + """ This is a decorator that, when applied to a parent-command, implements the to obtain the necessary arguments for the generate_keys() subcommand. - ''' + """ decorators = [ jit_option( callback=captive_prompt_callback( lambda num: validate_int_range(num, 1, 2**32), - lambda: load_text(['num_validators', 'prompt'], func='generate_keys_arguments_decorator') + lambda: load_text( + ["num_validators", "prompt"], + func="generate_keys_arguments_decorator", + ), + ), + help=lambda: load_text( + ["num_validators", "help"], func="generate_keys_arguments_decorator" ), - help=lambda: load_text(['num_validators', 'help'], func='generate_keys_arguments_decorator'), param_decls="--num_validators", - prompt=lambda: load_text(['num_validators', 'prompt'], func='generate_keys_arguments_decorator'), + prompt=lambda: load_text( + ["num_validators", "prompt"], func="generate_keys_arguments_decorator" + ), ), jit_option( default=os.getcwd(), - help=lambda: load_text(['folder', 'help'], func='generate_keys_arguments_decorator'), - param_decls='--folder', + help=lambda: load_text( + ["folder", "help"], func="generate_keys_arguments_decorator" + ), + param_decls="--folder", type=click.Path(exists=True, file_okay=False, dir_okay=True), ), jit_option( callback=captive_prompt_callback( lambda x: closest_match(x, list(ALL_CHAINS.keys())), choice_prompt_func( - lambda: load_text(['chain', 'prompt'], func='generate_keys_arguments_decorator'), - list(ALL_CHAINS.keys()) + lambda: load_text( + ["chain", "prompt"], func="generate_keys_arguments_decorator" + ), + list(ALL_CHAINS.keys()), ), ), default=MAINNET, - help=lambda: load_text(['chain', 'help'], func='generate_keys_arguments_decorator'), - param_decls='--chain', + help=lambda: load_text( + ["chain", "help"], func="generate_keys_arguments_decorator" + ), + param_decls="--chain", prompt=choice_prompt_func( - lambda: load_text(['chain', 'prompt'], func='generate_keys_arguments_decorator'), + lambda: load_text( + ["chain", "prompt"], func="generate_keys_arguments_decorator" + ), # Since `prater` is alias of `goerli`, do not show `prater` in the prompt message. - list(key for key in ALL_CHAINS.keys() if key != PRATER) + list(key for key in ALL_CHAINS.keys() if key != PRATER), ), ), jit_option( callback=captive_prompt_callback( validate_password_strength, - lambda:load_text(['keystore_password', 'prompt'], func='generate_keys_arguments_decorator'), - lambda:load_text(['keystore_password', 'confirm'], func='generate_keys_arguments_decorator'), - lambda: load_text(['keystore_password', 'mismatch'], func='generate_keys_arguments_decorator'), + lambda: load_text( + ["keystore_password", "prompt"], + func="generate_keys_arguments_decorator", + ), + lambda: load_text( + ["keystore_password", "confirm"], + func="generate_keys_arguments_decorator", + ), + lambda: load_text( + ["keystore_password", "mismatch"], + func="generate_keys_arguments_decorator", + ), True, ), - help=lambda: load_text(['keystore_password', 'help'], func='generate_keys_arguments_decorator'), + help=lambda: load_text( + ["keystore_password", "help"], func="generate_keys_arguments_decorator" + ), hide_input=True, - param_decls='--keystore_password', - prompt=lambda: load_text(['keystore_password', 'prompt'], func='generate_keys_arguments_decorator'), + param_decls="--keystore_password", + prompt=lambda: load_text( + ["keystore_password", "prompt"], + func="generate_keys_arguments_decorator", + ), ), jit_option( callback=captive_prompt_callback( lambda address: validate_eth1_withdrawal_address(None, None, address), - lambda: load_text(['arg_execution_address', 'prompt'], func='generate_keys_arguments_decorator'), - lambda: load_text(['arg_execution_address', 'confirm'], func='generate_keys_arguments_decorator'), - lambda: load_text(['arg_execution_address', 'mismatch'], func='generate_keys_arguments_decorator'), + lambda: load_text( + ["arg_execution_address", "prompt"], + func="generate_keys_arguments_decorator", + ), + lambda: load_text( + ["arg_execution_address", "confirm"], + func="generate_keys_arguments_decorator", + ), + lambda: load_text( + ["arg_execution_address", "mismatch"], + func="generate_keys_arguments_decorator", + ), ), default=None, - help=lambda: load_text(['arg_execution_address', 'help'], func='generate_keys_arguments_decorator'), - param_decls=['--execution_address', '--eth1_withdrawal_address'], + help=lambda: load_text( + ["arg_execution_address", "help"], + func="generate_keys_arguments_decorator", + ), + param_decls=["--execution_address", "--eth1_withdrawal_address"], ), ] for decorator in reversed(decorators): @@ -112,11 +143,18 @@ def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[ @click.command() @click.pass_context -def generate_keys(ctx: click.Context, validator_start_index: int, - num_validators: int, folder: str, chain: str, keystore_password: str, - execution_address: HexAddress, **kwargs: Any) -> None: - mnemonic = ctx.obj['mnemonic'] - mnemonic_password = ctx.obj['mnemonic_password'] +def generate_keys( + ctx: click.Context, + validator_start_index: int, + num_validators: int, + folder: str, + chain: str, + keystore_password: str, + execution_address: HexAddress, + **kwargs: Any, +) -> None: + mnemonic = ctx.obj["mnemonic"] + mnemonic_password = ctx.obj["mnemonic_password"] amounts = [MAX_DEPOSIT_AMOUNT] * num_validators folder = os.path.join(folder, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) chain_setting = get_chain_setting(chain) @@ -124,7 +162,7 @@ def generate_keys(ctx: click.Context, validator_start_index: int, os.mkdir(folder) click.clear() click.echo(RHINO_0) - click.echo(load_text(['msg_key_creation'])) + click.echo(load_text(["msg_key_creation"])) credentials = CredentialList.from_mnemonic( mnemonic=mnemonic, mnemonic_password=mnemonic_password, @@ -134,11 +172,15 @@ def generate_keys(ctx: click.Context, validator_start_index: int, start_index=validator_start_index, hex_eth1_withdrawal_address=execution_address, ) - keystore_filefolders = credentials.export_keystores(password=keystore_password, folder=folder) + keystore_filefolders = credentials.export_keystores( + password=keystore_password, folder=folder + ) deposits_file = credentials.export_deposit_data_json(folder=folder) - if not credentials.verify_keystores(keystore_filefolders=keystore_filefolders, password=keystore_password): - raise ValidationError(load_text(['err_verify_keystores'])) + if not credentials.verify_keystores( + keystore_filefolders=keystore_filefolders, password=keystore_password + ): + raise ValidationError(load_text(["err_verify_keystores"])) if not verify_deposit_data_json(deposits_file, credentials.credentials): - raise ValidationError(load_text(['err_verify_deposit'])) - click.echo(load_text(['msg_creation_success']) + folder) - click.pause(load_text(['msg_pause'])) + raise ValidationError(load_text(["err_verify_deposit"])) + click.echo(load_text(["msg_creation_success"]) + folder) + click.pause(load_text(["msg_pause"])) diff --git a/staking_deposit/cli/new_mnemonic.py b/staking_deposit/cli/new_mnemonic.py index f288aeac..28613ffd 100644 --- a/staking_deposit/cli/new_mnemonic.py +++ b/staking_deposit/cli/new_mnemonic.py @@ -1,7 +1,6 @@ +from typing import Any + import click -from typing import ( - Any, -) from staking_deposit.key_handling.key_derivation.mnemonic import ( get_mnemonic, @@ -12,52 +11,56 @@ choice_prompt_func, jit_option, ) -from staking_deposit.utils.constants import ( - MNEMONIC_LANG_OPTIONS, - WORD_LISTS_PATH, -) +from staking_deposit.utils.constants import MNEMONIC_LANG_OPTIONS, WORD_LISTS_PATH from staking_deposit.utils.intl import ( fuzzy_reverse_dict_lookup, - load_text, get_first_options, + load_text, ) -from .generate_keys import ( - generate_keys, - generate_keys_arguments_decorator, -) +from .generate_keys import generate_keys, generate_keys_arguments_decorator languages = get_first_options(MNEMONIC_LANG_OPTIONS) @click.command( - help=load_text(['arg_new_mnemonic', 'help'], func='new_mnemonic'), + help=load_text(["arg_new_mnemonic", "help"], func="new_mnemonic"), ) @click.pass_context @jit_option( callback=captive_prompt_callback( - lambda mnemonic_language: fuzzy_reverse_dict_lookup(mnemonic_language, MNEMONIC_LANG_OPTIONS), - choice_prompt_func(lambda: load_text(['arg_mnemonic_language', 'prompt'], func='new_mnemonic'), languages), + lambda mnemonic_language: fuzzy_reverse_dict_lookup( + mnemonic_language, MNEMONIC_LANG_OPTIONS + ), + choice_prompt_func( + lambda: load_text(["arg_mnemonic_language", "prompt"], func="new_mnemonic"), + languages, + ), + ), + default=lambda: load_text( + ["arg_mnemonic_language", "default"], func="new_mnemonic" + ), + help=lambda: load_text(["arg_mnemonic_language", "help"], func="new_mnemonic"), + param_decls="--mnemonic_language", + prompt=choice_prompt_func( + lambda: load_text(["arg_mnemonic_language", "prompt"], func="new_mnemonic"), + languages, ), - default=lambda: load_text(['arg_mnemonic_language', 'default'], func='new_mnemonic'), - help=lambda: load_text(['arg_mnemonic_language', 'help'], func='new_mnemonic'), - param_decls='--mnemonic_language', - prompt=choice_prompt_func(lambda: load_text(['arg_mnemonic_language', 'prompt'], func='new_mnemonic'), languages), ) @generate_keys_arguments_decorator def new_mnemonic(ctx: click.Context, mnemonic_language: str, **kwargs: Any) -> None: mnemonic = get_mnemonic(language=mnemonic_language, words_path=WORD_LISTS_PATH) - test_mnemonic = '' + test_mnemonic = "" while mnemonic != reconstruct_mnemonic(test_mnemonic, WORD_LISTS_PATH): click.clear() - click.echo(load_text(['msg_mnemonic_presentation'])) - click.echo('\n\n%s\n\n' % mnemonic) - click.pause(load_text(['msg_press_any_key'])) + click.echo(load_text(["msg_mnemonic_presentation"])) + click.echo("\n\n%s\n\n" % mnemonic) + click.pause(load_text(["msg_press_any_key"])) click.clear() - test_mnemonic = click.prompt(load_text(['msg_mnemonic_retype_prompt']) + '\n\n') + test_mnemonic = click.prompt(load_text(["msg_mnemonic_retype_prompt"]) + "\n\n") click.clear() # Do NOT use mnemonic_password. - ctx.obj = {'mnemonic': mnemonic, 'mnemonic_password': ''} - ctx.params['validator_start_index'] = 0 + ctx.obj = {"mnemonic": mnemonic, "mnemonic_password": ""} + ctx.params["validator_start_index"] = 0 ctx.forward(generate_keys) diff --git a/staking_deposit/credentials.py b/staking_deposit/credentials.py index 9d70ead8..6ecc4ac2 100644 --- a/staking_deposit/credentials.py +++ b/staking_deposit/credentials.py @@ -1,20 +1,17 @@ +import json import os -import click -from enum import Enum import time -import json -from typing import Dict, List, Optional, Any, Sequence +from enum import Enum +from typing import Any, Dict, List, Optional, Sequence +import click from eth_typing import Address, HexAddress from eth_utils import to_canonical_address from py_ecc.bls import G2ProofOfPossession as bls from staking_deposit.exceptions import ValidationError from staking_deposit.key_handling.key_derivation.path import mnemonic_and_path_to_key -from staking_deposit.key_handling.keystore import ( - Keystore, - ScryptKeystore, -) +from staking_deposit.key_handling.keystore import Keystore, ScryptKeystore from staking_deposit.settings import DEPOSIT_CLI_VERSION, BaseChainSetting from staking_deposit.utils.constants import ( BLS_WITHDRAWAL_PREFIX, @@ -26,13 +23,13 @@ from staking_deposit.utils.crypto import SHA256 from staking_deposit.utils.intl import load_text from staking_deposit.utils.ssz import ( - compute_deposit_domain, - compute_bls_to_execution_change_domain, - compute_signing_root, BLSToExecutionChange, DepositData, DepositMessage, SignedBLSToExecutionChange, + compute_bls_to_execution_change_domain, + compute_deposit_domain, + compute_signing_root, ) @@ -46,21 +43,31 @@ class Credential: A Credential object contains all of the information for a single validator and the corresponding functionality. Once created, it is the only object that should be required to perform any processing for a validator. """ - def __init__(self, *, mnemonic: str, mnemonic_password: str, - index: int, amount: int, chain_setting: BaseChainSetting, - hex_eth1_withdrawal_address: Optional[HexAddress]): + + def __init__( + self, + *, + mnemonic: str, + mnemonic_password: str, + index: int, + amount: int, + chain_setting: BaseChainSetting, + hex_eth1_withdrawal_address: Optional[HexAddress], + ): # Set path as EIP-2334 format # https://eips.ethereum.org/EIPS/eip-2334 - purpose = '12381' - coin_type = '3600' + purpose = "12381" + coin_type = "3600" account = str(index) - withdrawal_key_path = f'm/{purpose}/{coin_type}/{account}/0' - self.signing_key_path = f'{withdrawal_key_path}/0' + withdrawal_key_path = f"m/{purpose}/{coin_type}/{account}/0" + self.signing_key_path = f"{withdrawal_key_path}/0" self.withdrawal_sk = mnemonic_and_path_to_key( - mnemonic=mnemonic, path=withdrawal_key_path, password=mnemonic_password) + mnemonic=mnemonic, path=withdrawal_key_path, password=mnemonic_password + ) self.signing_sk = mnemonic_and_path_to_key( - mnemonic=mnemonic, path=self.signing_key_path, password=mnemonic_password) + mnemonic=mnemonic, path=self.signing_key_path, password=mnemonic_password + ) self.amount = amount self.chain_setting = chain_setting self.hex_eth1_withdrawal_address = hex_eth1_withdrawal_address @@ -93,7 +100,9 @@ def withdrawal_type(self) -> WithdrawalType: elif self.withdrawal_prefix == ETH1_ADDRESS_WITHDRAWAL_PREFIX: return WithdrawalType.ETH1_ADDRESS_WITHDRAWAL else: - raise ValueError(f"Invalid withdrawal_prefix {self.withdrawal_prefix.hex()}") + raise ValueError( + f"Invalid withdrawal_prefix {self.withdrawal_prefix.hex()}" + ) @property def withdrawal_credentials(self) -> bytes: @@ -105,7 +114,7 @@ def withdrawal_credentials(self) -> bytes: and self.eth1_withdrawal_address is not None ): withdrawal_credentials = ETH1_ADDRESS_WITHDRAWAL_PREFIX - withdrawal_credentials += b'\x00' * 11 + withdrawal_credentials += b"\x00" * 11 withdrawal_credentials += self.eth1_withdrawal_address else: raise ValueError(f"Invalid withdrawal_type {self.withdrawal_type}") @@ -114,7 +123,9 @@ def withdrawal_credentials(self) -> bytes: @property def deposit_message(self) -> DepositMessage: if not MIN_DEPOSIT_AMOUNT <= self.amount <= MAX_DEPOSIT_AMOUNT: - raise ValidationError(f"{self.amount / ETH2GWEI} ETH deposits are not within the bounds of this cli.") + raise ValidationError( + f"{self.amount / ETH2GWEI} ETH deposits are not within the bounds of this cli." + ) return DepositMessage( pubkey=self.signing_pk, withdrawal_credentials=self.withdrawal_credentials, @@ -123,11 +134,13 @@ def deposit_message(self) -> DepositMessage: @property def signed_deposit(self) -> DepositData: - domain = compute_deposit_domain(fork_version=self.chain_setting.GENESIS_FORK_VERSION) + domain = compute_deposit_domain( + fork_version=self.chain_setting.GENESIS_FORK_VERSION + ) signing_root = compute_signing_root(self.deposit_message, domain) signed_deposit = DepositData( **self.deposit_message.as_dict(), - signature=bls.Sign(self.signing_sk, signing_root) + signature=bls.Sign(self.signing_sk, signing_root), ) return signed_deposit @@ -139,29 +152,36 @@ def deposit_datum_dict(self) -> Dict[str, bytes]: """ signed_deposit_datum = self.signed_deposit datum_dict = signed_deposit_datum.as_dict() - datum_dict.update({'deposit_message_root': self.deposit_message.hash_tree_root}) - datum_dict.update({'deposit_data_root': signed_deposit_datum.hash_tree_root}) - datum_dict.update({'fork_version': self.chain_setting.GENESIS_FORK_VERSION}) - datum_dict.update({'network_name': self.chain_setting.NETWORK_NAME}) - datum_dict.update({'deposit_cli_version': DEPOSIT_CLI_VERSION}) + datum_dict.update({"deposit_message_root": self.deposit_message.hash_tree_root}) + datum_dict.update({"deposit_data_root": signed_deposit_datum.hash_tree_root}) + datum_dict.update({"fork_version": self.chain_setting.GENESIS_FORK_VERSION}) + datum_dict.update({"network_name": self.chain_setting.NETWORK_NAME}) + datum_dict.update({"deposit_cli_version": DEPOSIT_CLI_VERSION}) return datum_dict def signing_keystore(self, password: str) -> Keystore: - secret = self.signing_sk.to_bytes(32, 'big') - return ScryptKeystore.encrypt(secret=secret, password=password, path=self.signing_key_path) + secret = self.signing_sk.to_bytes(32, "big") + return ScryptKeystore.encrypt( + secret=secret, password=password, path=self.signing_key_path + ) def save_signing_keystore(self, password: str, folder: str) -> str: keystore = self.signing_keystore(password) - filefolder = os.path.join(folder, 'keystore-%s-%i.json' % (keystore.path.replace('/', '_'), time.time())) + filefolder = os.path.join( + folder, + "keystore-%s-%i.json" % (keystore.path.replace("/", "_"), time.time()), + ) keystore.save(filefolder) return filefolder def verify_keystore(self, keystore_filefolder: str, password: str) -> bool: saved_keystore = Keystore.from_file(keystore_filefolder) 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 get_bls_to_execution_change(self, validator_index: int) -> SignedBLSToExecutionChange: + def get_bls_to_execution_change( + self, validator_index: int + ) -> SignedBLSToExecutionChange: if self.eth1_withdrawal_address is None: raise ValueError("The execution address should NOT be empty.") @@ -182,25 +202,36 @@ def get_bls_to_execution_change(self, validator_index: int) -> SignedBLSToExecut signature=signature, ) - def get_bls_to_execution_change_dict(self, validator_index: int) -> Dict[str, bytes]: + def get_bls_to_execution_change_dict( + self, validator_index: int + ) -> Dict[str, bytes]: result_dict: Dict[str, Any] = {} - signed_bls_to_execution_change = self.get_bls_to_execution_change(validator_index) + signed_bls_to_execution_change = self.get_bls_to_execution_change( + validator_index + ) message = { - 'validator_index': str(signed_bls_to_execution_change.message.validator_index), - 'from_bls_pubkey': '0x' + signed_bls_to_execution_change.message.from_bls_pubkey.hex(), - 'to_execution_address': '0x' + signed_bls_to_execution_change.message.to_execution_address.hex(), + "validator_index": str( + signed_bls_to_execution_change.message.validator_index + ), + "from_bls_pubkey": "0x" + + signed_bls_to_execution_change.message.from_bls_pubkey.hex(), + "to_execution_address": "0x" + + signed_bls_to_execution_change.message.to_execution_address.hex(), } - result_dict.update({'message': message}) - result_dict.update({'signature': '0x' + signed_bls_to_execution_change.signature.hex()}) + result_dict.update({"message": message}) + result_dict.update( + {"signature": "0x" + signed_bls_to_execution_change.signature.hex()} + ) # metadata metadata: Dict[str, Any] = { - 'network_name': self.chain_setting.NETWORK_NAME, - 'genesis_validators_root': '0x' + self.chain_setting.GENESIS_VALIDATORS_ROOT.hex(), - 'deposit_cli_version': DEPOSIT_CLI_VERSION, + "network_name": self.chain_setting.NETWORK_NAME, + "genesis_validators_root": "0x" + + self.chain_setting.GENESIS_VALIDATORS_ROOT.hex(), + "deposit_cli_version": DEPOSIT_CLI_VERSION, } - result_dict.update({'metadata': metadata}) + result_dict.update({"metadata": metadata}) return result_dict @@ -208,63 +239,108 @@ class CredentialList: """ A collection of multiple Credentials, one for each validator. """ + def __init__(self, credentials: List[Credential]): self.credentials = credentials @classmethod - def from_mnemonic(cls, - *, - mnemonic: str, - mnemonic_password: str, - num_keys: int, - amounts: List[int], - chain_setting: BaseChainSetting, - start_index: int, - hex_eth1_withdrawal_address: Optional[HexAddress]) -> 'CredentialList': + def from_mnemonic( + cls, + *, + mnemonic: str, + mnemonic_password: str, + num_keys: int, + amounts: List[int], + chain_setting: BaseChainSetting, + start_index: int, + hex_eth1_withdrawal_address: Optional[HexAddress], + ) -> "CredentialList": if len(amounts) != num_keys: raise ValueError( f"The number of keys ({num_keys}) doesn't equal to the corresponding deposit amounts ({len(amounts)})." ) key_indices = range(start_index, start_index + num_keys) - with click.progressbar(key_indices, label=load_text(['msg_key_creation']), - show_percent=False, show_pos=True) as indices: - return cls([Credential(mnemonic=mnemonic, mnemonic_password=mnemonic_password, - index=index, amount=amounts[index - start_index], chain_setting=chain_setting, - hex_eth1_withdrawal_address=hex_eth1_withdrawal_address) - for index in indices]) + with click.progressbar( + key_indices, + label=load_text(["msg_key_creation"]), + show_percent=False, + show_pos=True, + ) as indices: + return cls( + [ + Credential( + mnemonic=mnemonic, + mnemonic_password=mnemonic_password, + index=index, + amount=amounts[index - start_index], + chain_setting=chain_setting, + hex_eth1_withdrawal_address=hex_eth1_withdrawal_address, + ) + for index in indices + ] + ) def export_keystores(self, password: str, folder: str) -> List[str]: - with click.progressbar(self.credentials, label=load_text(['msg_keystore_creation']), - show_percent=False, show_pos=True) as credentials: - return [credential.save_signing_keystore(password=password, folder=folder) for credential in credentials] + with click.progressbar( + self.credentials, + label=load_text(["msg_keystore_creation"]), + show_percent=False, + show_pos=True, + ) as credentials: + return [ + credential.save_signing_keystore(password=password, folder=folder) + for credential in credentials + ] def export_deposit_data_json(self, folder: str) -> str: - with click.progressbar(self.credentials, label=load_text(['msg_depositdata_creation']), - show_percent=False, show_pos=True) as credentials: + with click.progressbar( + self.credentials, + label=load_text(["msg_depositdata_creation"]), + show_percent=False, + show_pos=True, + ) as credentials: deposit_data = [cred.deposit_datum_dict for cred in credentials] - filefolder = os.path.join(folder, 'deposit_data-%i.json' % time.time()) - with open(filefolder, 'w') as f: + 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()) - if os.name == 'posix': - os.chmod(filefolder, int('440', 8)) # Read for owner & group + if os.name == "posix": + os.chmod(filefolder, int("440", 8)) # Read for owner & group return filefolder def verify_keystores(self, keystore_filefolders: List[str], password: str) -> bool: - with click.progressbar(zip(self.credentials, keystore_filefolders), - label=load_text(['msg_keystore_verification']), - length=len(self.credentials), show_percent=False, show_pos=True) as items: - return all(credential.verify_keystore(keystore_filefolder=filefolder, password=password) - for credential, filefolder in items) - - def export_bls_to_execution_change_json(self, folder: str, validator_indices: Sequence[int]) -> str: - with click.progressbar(self.credentials, label=load_text(['msg_bls_to_execution_change_creation']), - show_percent=False, show_pos=True) as credentials: - bls_to_execution_changes = [cred.get_bls_to_execution_change_dict(validator_indices[i]) - for i, cred in enumerate(credentials)] - - filefolder = os.path.join(folder, 'bls_to_execution_change-%i.json' % time.time()) - with open(filefolder, 'w') as f: + with click.progressbar( + zip(self.credentials, keystore_filefolders), + label=load_text(["msg_keystore_verification"]), + length=len(self.credentials), + show_percent=False, + show_pos=True, + ) as items: + return all( + credential.verify_keystore( + keystore_filefolder=filefolder, password=password + ) + for credential, filefolder in items + ) + + def export_bls_to_execution_change_json( + self, folder: str, validator_indices: Sequence[int] + ) -> str: + with click.progressbar( + self.credentials, + label=load_text(["msg_bls_to_execution_change_creation"]), + show_percent=False, + show_pos=True, + ) as credentials: + bls_to_execution_changes = [ + cred.get_bls_to_execution_change_dict(validator_indices[i]) + for i, cred in enumerate(credentials) + ] + + filefolder = os.path.join( + folder, "bls_to_execution_change-%i.json" % time.time() + ) + with open(filefolder, "w") as f: json.dump(bls_to_execution_changes, f) - if os.name == 'posix': - os.chmod(filefolder, int('440', 8)) # Read for owner & group + if os.name == "posix": + os.chmod(filefolder, int("440", 8)) # Read for owner & group return filefolder diff --git a/staking_deposit/deposit.py b/staking_deposit/deposit.py index 66acaa91..caa485f1 100644 --- a/staking_deposit/deposit.py +++ b/staking_deposit/deposit.py @@ -1,50 +1,57 @@ -import click import sys +import click + from staking_deposit.cli.existing_mnemonic import existing_mnemonic -from staking_deposit.cli.generate_bls_to_execution_change import generate_bls_to_execution_change +from staking_deposit.cli.generate_bls_to_execution_change import ( + generate_bls_to_execution_change, +) from staking_deposit.cli.new_mnemonic import new_mnemonic +from staking_deposit.utils import config from staking_deposit.utils.click import ( captive_prompt_callback, choice_prompt_func, jit_option, ) -from staking_deposit.utils import config from staking_deposit.utils.constants import INTL_LANG_OPTIONS from staking_deposit.utils.intl import ( - get_first_options, fuzzy_reverse_dict_lookup, + get_first_options, load_text, ) def check_python_version() -> None: - ''' + """ Checks that the python version running is sufficient and exits if not. - ''' + """ if sys.version_info < (3, 7): - click.pause(load_text(['err_python_version'])) + click.pause(load_text(["err_python_version"])) sys.exit() @click.group() @click.pass_context @jit_option( - '--language', + "--language", callback=captive_prompt_callback( lambda language: fuzzy_reverse_dict_lookup(language, INTL_LANG_OPTIONS), - choice_prompt_func(lambda: 'Please choose your language', get_first_options(INTL_LANG_OPTIONS)), + choice_prompt_func( + lambda: "Please choose your language", get_first_options(INTL_LANG_OPTIONS) + ), ), - default='English', - help='The language you wish to use the CLI in.', - prompt=choice_prompt_func(lambda: 'Please choose your language', get_first_options(INTL_LANG_OPTIONS))(), + default="English", + help="The language you wish to use the CLI in.", + prompt=choice_prompt_func( + lambda: "Please choose your language", get_first_options(INTL_LANG_OPTIONS) + )(), type=str, ) @click.option( - '--non_interactive', + "--non_interactive", default=False, is_flag=True, - help='Disables interactive prompts. Warning: with this flag, there will be no confirmation step(s) to verify the input value(s). Please use it carefully.', # noqa: E501 + help="Disables interactive prompts. Warning: with this flag, there will be no confirmation step(s) to verify the input value(s). Please use it carefully.", # noqa: E501 hidden=False, ) def cli(ctx: click.Context, language: str, non_interactive: bool) -> None: @@ -56,8 +63,9 @@ def cli(ctx: click.Context, language: str, non_interactive: bool) -> None: cli.add_command(new_mnemonic) cli.add_command(generate_bls_to_execution_change) - -if __name__ == '__main__': +if __name__ == "__main__": check_python_version() - print('\n***Using the tool on an offline and secure device is highly recommended to keep your mnemonic safe.***\n') + print( + "\n***Using the tool on an offline and secure device is highly recommended to keep your mnemonic safe.***\n" + ) cli() diff --git a/staking_deposit/exceptions.py b/staking_deposit/exceptions.py index 3b3c9dae..01a61386 100644 --- a/staking_deposit/exceptions.py +++ b/staking_deposit/exceptions.py @@ -1,2 +1 @@ -class ValidationError(Exception): - ... +class ValidationError(Exception): ... diff --git a/staking_deposit/key_handling/key_derivation/mnemonic.py b/staking_deposit/key_handling/key_derivation/mnemonic.py index d1d98b4a..438b9678 100644 --- a/staking_deposit/key_handling/key_derivation/mnemonic.py +++ b/staking_deposit/key_handling/key_derivation/mnemonic.py @@ -1,22 +1,11 @@ import os -from unicodedata import normalize from secrets import randbits -from typing import ( - List, - Optional, - Sequence, -) - -from staking_deposit.utils.constants import ( - MNEMONIC_LANG_OPTIONS, -) -from staking_deposit.utils.crypto import ( - SHA256, - PBKDF2, -) -from staking_deposit.utils.file_handling import ( - resource_path, -) +from typing import List, Optional, Sequence +from unicodedata import normalize + +from staking_deposit.utils.constants import MNEMONIC_LANG_OPTIONS +from staking_deposit.utils.crypto import PBKDF2, SHA256 +from staking_deposit.utils.file_handling import resource_path def _get_word_list(language: str, path: str) -> Sequence[str]: @@ -26,8 +15,10 @@ def _get_word_list(language: str, path: str) -> Sequence[str]: Ref: https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md """ path = resource_path(path) - dirty_list = open(os.path.join(path, '%s.txt' % language), encoding='utf-8').readlines() - return [word.replace('\n', '') for word in dirty_list] + dirty_list = open( + os.path.join(path, "%s.txt" % language), encoding="utf-8" + ).readlines() + return [word.replace("\n", "") for word in dirty_list] def _index_to_word(word_list: Sequence[str], index: int) -> str: @@ -43,7 +34,7 @@ def _word_to_index(word_list: Sequence[str], word: str) -> int: try: return word_list.index(word) except ValueError: - raise ValueError('Word %s not in BIP39 word-list' % word) + raise ValueError("Word %s not in BIP39 word-list" % word) def _uint11_array_to_uint(uint11_array: Sequence[int]) -> int: @@ -56,9 +47,9 @@ def get_seed(*, mnemonic: str, password: str) -> bytes: Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed """ - encoded_mnemonic = normalize('NFKD', mnemonic).encode('utf-8') - salt = normalize('NFKD', 'mnemonic' + password).encode('utf-8') - return PBKDF2(password=encoded_mnemonic, salt=salt, dklen=64, c=2048, prf='sha512') + encoded_mnemonic = normalize("NFKD", mnemonic).encode("utf-8") + salt = normalize("NFKD", "mnemonic" + password).encode("utf-8") + return PBKDF2(password=encoded_mnemonic, salt=salt, dklen=64, c=2048, prf="sha512") def determine_mnemonic_language(mnemonic: str, words_path: str) -> Sequence[str]: @@ -67,20 +58,32 @@ def determine_mnemonic_language(mnemonic: str, words_path: str) -> Sequence[str] There are collisions between word-lists, so multiple candidate languages are returned. """ languages = MNEMONIC_LANG_OPTIONS.keys() - word_language_map = {word: lang for lang in languages for word in _get_word_list(lang, words_path)} + word_language_map = { + word: lang for lang in languages for word in _get_word_list(lang, words_path) + } try: - mnemonic_list = [normalize('NFKC', word)[:4] for word in mnemonic.lower().split(' ')] - word_languages = [[lang for word, lang in word_language_map.items() if normalize('NFKC', word)[:4] == abbrev] - for abbrev in mnemonic_list] + mnemonic_list = [ + normalize("NFKC", word)[:4] for word in mnemonic.lower().split(" ") + ] + word_languages = [ + [ + lang + for word, lang in word_language_map.items() + if normalize("NFKC", word)[:4] == abbrev + ] + for abbrev in mnemonic_list + ] return list(set(sum(word_languages, []))) except KeyError: - raise ValueError('Word not found in mnemonic word lists for any language.') + raise ValueError("Word not found in mnemonic word lists for any language.") def _validate_entropy_length(entropy: bytes) -> None: entropy_length = len(entropy) * 8 if entropy_length not in range(128, 257, 32): - raise IndexError(f"`entropy_length` should be in [128, 160, 192, 224, 256]. Got {entropy_length}.") + raise IndexError( + f"`entropy_length` should be in [128, 160, 192, 224, 256]. Got {entropy_length}." + ) def _get_checksum(entropy: bytes) -> int: @@ -89,14 +92,14 @@ def _get_checksum(entropy: bytes) -> int: """ _validate_entropy_length(entropy) checksum_length = len(entropy) // 4 - return int.from_bytes(SHA256(entropy), 'big') >> (256 - checksum_length) + return int.from_bytes(SHA256(entropy), "big") >> (256 - checksum_length) def abbreviate_words(words: Sequence[str]) -> List[str]: """ Given a series of word strings, return the 4-letter version of each word (which is unique according to BIP39) """ - return [normalize('NFKC', word)[:4] for word in words] + return [normalize("NFKC", word)[:4] for word in words] def reconstruct_mnemonic(mnemonic: str, words_path: str) -> Optional[str]: @@ -112,15 +115,17 @@ def reconstruct_mnemonic(mnemonic: str, words_path: str) -> Optional[str]: for language in languages: try: abbrev_word_list = abbreviate_words(_get_word_list(language, words_path)) - abbrev_mnemonic_list = abbreviate_words(mnemonic.lower().split(' ')) + abbrev_mnemonic_list = abbreviate_words(mnemonic.lower().split(" ")) if len(abbrev_mnemonic_list) not in range(12, 25, 3): return None - word_indices = [_word_to_index(abbrev_word_list, word) for word in abbrev_mnemonic_list] + word_indices = [ + _word_to_index(abbrev_word_list, word) for word in abbrev_mnemonic_list + ] mnemonic_int = _uint11_array_to_uint(word_indices) checksum_length = len(abbrev_mnemonic_list) // 3 checksum = mnemonic_int & 2**checksum_length - 1 entropy = (mnemonic_int - checksum) >> checksum_length - entropy_bits = entropy.to_bytes(checksum_length * 4, 'big') + entropy_bits = entropy.to_bytes(checksum_length * 4, "big") full_word_list = _get_word_list(language, words_path) if _get_checksum(entropy_bits) == checksum: """ @@ -128,7 +133,9 @@ def reconstruct_mnemonic(mnemonic: str, words_path: str) -> Optional[str]: It is needed to ensure abbrivated words aren't valid in multiple languages """ assert reconstructed_mnemonic is None - reconstructed_mnemonic = ' '.join([_index_to_word(full_word_list, index) for index in word_indices]) + reconstructed_mnemonic = " ".join( + [_index_to_word(full_word_list, index) for index in word_indices] + ) else: pass except ValueError: @@ -136,18 +143,20 @@ def reconstruct_mnemonic(mnemonic: str, words_path: str) -> Optional[str]: return reconstructed_mnemonic -def get_mnemonic(*, language: str, words_path: str, entropy: Optional[bytes]=None) -> str: +def get_mnemonic( + *, language: str, words_path: str, entropy: Optional[bytes] = None +) -> str: """ Return a mnemonic string in a given `language` based on `entropy` via the calculated checksum. Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic """ if entropy is None: - entropy = randbits(256).to_bytes(32, 'big') + entropy = randbits(256).to_bytes(32, "big") entropy_length = len(entropy) * 8 - checksum_length = (entropy_length // 32) + checksum_length = entropy_length // 32 checksum = _get_checksum(entropy) - entropy_bits = int.from_bytes(entropy, 'big') << checksum_length + entropy_bits = int.from_bytes(entropy, "big") << checksum_length entropy_bits += checksum entropy_length += checksum_length mnemonic = [] @@ -156,4 +165,4 @@ def get_mnemonic(*, language: str, words_path: str, entropy: Optional[bytes]=Non index = (entropy_bits >> i * 11) & 2**11 - 1 word = _index_to_word(word_list, index) mnemonic.append(word) - return ' '.join(mnemonic) + return " ".join(mnemonic) diff --git a/staking_deposit/key_handling/key_derivation/path.py b/staking_deposit/key_handling/key_derivation/path.py index b5585198..72b32e1e 100644 --- a/staking_deposit/key_handling/key_derivation/path.py +++ b/staking_deposit/key_handling/key_derivation/path.py @@ -1,24 +1,23 @@ from typing import List from .mnemonic import get_seed -from .tree import ( - derive_master_SK, - derive_child_SK, -) +from .tree import derive_child_SK, derive_master_SK def path_to_nodes(path: str) -> List[int]: """ Maps from a path string to a list of indices where each index represents the corresponding level in the path. """ - path = path.replace(' ', '') - if not set(path).issubset(set('m1234567890/')): + path = path.replace(" ", "") + if not set(path).issubset(set("m1234567890/")): raise ValueError(f"Invalid path {path}") - indices = path.split('/') + indices = path.split("/") - if indices[0] != 'm': - raise ValueError(f"The first character of path should be `m`. Got {indices[0]}.") + if indices[0] != "m": + raise ValueError( + f"The first character of path should be `m`. Got {indices[0]}." + ) indices.pop(0) return [int(index) for index in indices] diff --git a/staking_deposit/key_handling/key_derivation/tree.py b/staking_deposit/key_handling/key_derivation/tree.py index 53fcd0ec..0f5afbb8 100644 --- a/staking_deposit/key_handling/key_derivation/tree.py +++ b/staking_deposit/key_handling/key_derivation/tree.py @@ -1,10 +1,9 @@ -from staking_deposit.utils.crypto import ( - HKDF, - SHA256, -) -from py_ecc.optimized_bls12_381 import curve_order as bls_curve_order from typing import List +from py_ecc.optimized_bls12_381 import curve_order as bls_curve_order + +from staking_deposit.utils.crypto import HKDF, SHA256 + def _flip_bits_256(input: int) -> int: """ @@ -20,7 +19,7 @@ def _IKM_to_lamport_SK(*, IKM: bytes, salt: bytes) -> List[bytes]: Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2333.md#ikm_to_lamport_sk """ OKM = HKDF(salt=salt, IKM=IKM, L=8160) - lamport_SK = [OKM[i: i + 32] for i in range(0, 8160, 32)] + lamport_SK = [OKM[i : i + 32] for i in range(0, 8160, 32)] return lamport_SK @@ -30,35 +29,35 @@ def _parent_SK_to_lamport_PK(*, parent_SK: int, index: int) -> bytes: Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2333.md#parent_sk_to_lamport_pk """ - salt = index.to_bytes(4, byteorder='big') - IKM = parent_SK.to_bytes(32, byteorder='big') + salt = index.to_bytes(4, byteorder="big") + IKM = parent_SK.to_bytes(32, byteorder="big") lamport_0 = _IKM_to_lamport_SK(IKM=IKM, salt=salt) - not_IKM = _flip_bits_256(parent_SK).to_bytes(32, byteorder='big') + not_IKM = _flip_bits_256(parent_SK).to_bytes(32, byteorder="big") lamport_1 = _IKM_to_lamport_SK(IKM=not_IKM, salt=salt) lamport_SKs = lamport_0 + lamport_1 lamport_PKs = [SHA256(sk) for sk in lamport_SKs] - compressed_PK = SHA256(b''.join(lamport_PKs)) + compressed_PK = SHA256(b"".join(lamport_PKs)) return compressed_PK -def _HKDF_mod_r(*, IKM: bytes, key_info: bytes=b'') -> int: +def _HKDF_mod_r(*, IKM: bytes, key_info: bytes = b"") -> int: """ Hashes the IKM using HKDF and returns the answer as an int modulo r, the BLS field order. Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2333.md#hkdf_mod_r """ L = 48 # `ceil((3 * ceil(log2(r))) / 16)`, where `r` is the order of the BLS 12-381 curve - salt = b'BLS-SIG-KEYGEN-SALT-' + salt = b"BLS-SIG-KEYGEN-SALT-" SK = 0 while SK == 0: salt = SHA256(salt) okm = HKDF( salt=salt, - IKM=IKM + b'\x00', # add postfix `I2OSP(0, 1)` + IKM=IKM + b"\x00", # add postfix `I2OSP(0, 1)` L=L, - info=key_info + L.to_bytes(2, 'big'), + info=key_info + L.to_bytes(2, "big"), ) - SK = int.from_bytes(okm, byteorder='big') % bls_curve_order + SK = int.from_bytes(okm, byteorder="big") % bls_curve_order return SK @@ -69,7 +68,9 @@ def derive_child_SK(*, parent_SK: int, index: int) -> int: Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2333.md#derive_child_sk """ if index < 0 or index >= 2**32: - raise IndexError(f"`index` should be greater than or equal to 0 and less than 2**32. Got index={index}.") + raise IndexError( + f"`index` should be greater than or equal to 0 and less than 2**32. Got index={index}." + ) lamport_PK = _parent_SK_to_lamport_PK(parent_SK=parent_SK, index=index) return _HKDF_mod_r(IKM=lamport_PK) @@ -81,5 +82,7 @@ def derive_master_SK(seed: bytes) -> int: Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2333.md#derive_master_sk """ if len(seed) < 32: - raise ValueError(f"`len(seed)` should be greater than or equal to 32. Got {len(seed)}.") + raise ValueError( + f"`len(seed)` should be greater than or equal to 32. Got {len(seed)}." + ) return _HKDF_mod_r(IKM=seed) diff --git a/staking_deposit/key_handling/keystore.py b/staking_deposit/key_handling/keystore.py index 75bcddba..62d203d0 100644 --- a/staking_deposit/key_handling/keystore.py +++ b/staking_deposit/key_handling/keystore.py @@ -1,28 +1,19 @@ -from dataclasses import ( - asdict, - dataclass, - fields, - field as dataclass_field -) import json import os -from py_ecc.bls import G2ProofOfPossession as bls +from dataclasses import asdict, dataclass +from dataclasses import field as dataclass_field +from dataclasses import fields from secrets import randbits from typing import Any, Dict, Union from unicodedata import normalize from uuid import uuid4 -from staking_deposit.utils.crypto import ( - AES_128_CTR, - PBKDF2, - scrypt, - SHA256, -) -from staking_deposit.utils.constants import ( - UNICODE_CONTROL_CHARS, -) +from py_ecc.bls import G2ProofOfPossession as bls + +from staking_deposit.utils.constants import UNICODE_CONTROL_CHARS +from staking_deposit.utils.crypto import AES_128_CTR, PBKDF2, SHA256, scrypt -hexdigits = set('0123456789abcdef') +hexdigits = set("0123456789abcdef") def encode_bytes(obj: Union[str, Dict[str, Any]]) -> Union[bytes, str, Dict[str, Any]]: @@ -42,11 +33,14 @@ class BytesDataclass: BytesDataClasses are DataClass objects that automatically encode hexstrings into bytes, and have an `as_json` function that encodes bytes back into hexstrings. """ + def __post_init__(self) -> None: for field in fields(self): if field.type in (bytes, Dict[str, Any]): # Convert hexstring to bytes - self.__setattr__(field.name, encode_bytes(self.__getattribute__(field.name))) + self.__setattr__( + field.name, encode_bytes(self.__getattribute__(field.name)) + ) def as_json(self) -> str: return json.dumps(asdict(self), default=lambda x: x.hex()) @@ -54,7 +48,7 @@ def as_json(self) -> str: @dataclass class KeystoreModule(BytesDataclass): - function: str = '' + function: str = "" params: Dict[str, Any] = dataclass_field(default_factory=dict) message: bytes = bytes() @@ -66,10 +60,10 @@ class KeystoreCrypto(BytesDataclass): cipher: KeystoreModule = KeystoreModule() @classmethod - def from_json(cls, json_dict: Dict[Any, Any]) -> 'KeystoreCrypto': - kdf = KeystoreModule(**json_dict['kdf']) - checksum = KeystoreModule(**json_dict['checksum']) - cipher = KeystoreModule(**json_dict['cipher']) + def from_json(cls, json_dict: Dict[Any, Any]) -> "KeystoreCrypto": + kdf = KeystoreModule(**json_dict["kdf"]) + checksum = KeystoreModule(**json_dict["checksum"]) + cipher = KeystoreModule(**json_dict["cipher"]) return cls(kdf=kdf, checksum=checksum, cipher=cipher) @@ -81,38 +75,50 @@ class Keystore(BytesDataclass): Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md """ + crypto: KeystoreCrypto = KeystoreCrypto() - description: str = '' - pubkey: str = '' - path: str = '' - uuid: str = '' + description: str = "" + pubkey: str = "" + path: str = "" + uuid: str = "" version: int = 4 def kdf(self, **kwargs: Any) -> bytes: - return scrypt(**kwargs) if 'scrypt' in self.crypto.kdf.function else PBKDF2(**kwargs) + return ( + scrypt(**kwargs) + if "scrypt" in self.crypto.kdf.function + else PBKDF2(**kwargs) + ) def save(self, filefolder: str) -> None: """ Save self as a JSON keystore. """ - with open(filefolder, 'w') as f: + with open(filefolder, "w") as f: f.write(self.as_json()) - if os.name == 'posix': - os.chmod(filefolder, int('440', 8)) # Read for owner & group + if os.name == "posix": + os.chmod(filefolder, int("440", 8)) # Read for owner & group @classmethod - def from_json(cls, json_dict: Dict[Any, Any]) -> 'Keystore': - crypto = KeystoreCrypto.from_json(json_dict['crypto']) - path = json_dict['path'] - uuid = json_dict['uuid'] - version = json_dict['version'] - description = json_dict.get('description', '') - pubkey = json_dict.get('pubkey', '') - return cls(crypto=crypto, description=description, pubkey=pubkey, path=path, uuid=uuid, version=version) + def from_json(cls, json_dict: Dict[Any, Any]) -> "Keystore": + crypto = KeystoreCrypto.from_json(json_dict["crypto"]) + path = json_dict["path"] + uuid = json_dict["uuid"] + version = json_dict["version"] + description = json_dict.get("description", "") + pubkey = json_dict.get("pubkey", "") + return cls( + crypto=crypto, + description=description, + pubkey=pubkey, + path=path, + uuid=uuid, + version=version, + ) @classmethod - def from_file(cls, path: str) -> 'Keystore': - with open(path, 'r') as f: + def from_file(cls, path: str) -> "Keystore": + with open(path, "r") as f: return cls.from_json(json.load(f)) @staticmethod @@ -121,29 +127,36 @@ def _process_password(password: str) -> bytes: Encode password as NFKD UTF-8 as per: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#password-requirements """ - password = normalize('NFKD', password) - password = ''.join(c for c in password if ord(c) not in UNICODE_CONTROL_CHARS) - return password.encode('UTF-8') + password = normalize("NFKD", password) + password = "".join(c for c in password if ord(c) not in UNICODE_CONTROL_CHARS) + return password.encode("UTF-8") @classmethod - def encrypt(cls, *, secret: bytes, password: str, path: str='', - kdf_salt: bytes=randbits(256).to_bytes(32, 'big'), - aes_iv: bytes=randbits(128).to_bytes(16, 'big')) -> 'Keystore': + def encrypt( + cls, + *, + secret: bytes, + password: str, + path: str = "", + kdf_salt: bytes = randbits(256).to_bytes(32, "big"), + aes_iv: bytes = randbits(128).to_bytes(16, "big"), + ) -> "Keystore": """ Encrypt a secret (BLS SK) as an EIP 2335 Keystore. """ keystore = cls() keystore.uuid = str(uuid4()) - keystore.crypto.kdf.params['salt'] = kdf_salt + keystore.crypto.kdf.params["salt"] = kdf_salt decryption_key = keystore.kdf( - password=cls._process_password(password), - **keystore.crypto.kdf.params + password=cls._process_password(password), **keystore.crypto.kdf.params ) - keystore.crypto.cipher.params['iv'] = aes_iv + keystore.crypto.cipher.params["iv"] = aes_iv cipher = AES_128_CTR(key=decryption_key[:16], **keystore.crypto.cipher.params) keystore.crypto.cipher.message = cipher.encrypt(secret) - keystore.crypto.checksum.message = SHA256(decryption_key[16:32] + keystore.crypto.cipher.message) - keystore.pubkey = bls.SkToPk(int.from_bytes(secret, 'big')).hex() + keystore.crypto.checksum.message = SHA256( + decryption_key[16:32] + keystore.crypto.cipher.message + ) + keystore.pubkey = bls.SkToPk(int.from_bytes(secret, "big")).hex() keystore.path = path return keystore @@ -152,10 +165,12 @@ def decrypt(self, password: str) -> bytes: Retrieve the secret (BLS SK) from the self keystore by decrypting it with `password` """ decryption_key = self.kdf( - password=self._process_password(password), - **self.crypto.kdf.params + password=self._process_password(password), **self.crypto.kdf.params ) - if SHA256(decryption_key[16:32] + self.crypto.cipher.message) != self.crypto.checksum.message: + if ( + SHA256(decryption_key[16:32] + self.crypto.cipher.message) + != self.crypto.checksum.message + ): raise ValueError("Checksum message error") cipher = AES_128_CTR(key=decryption_key[:16], **self.crypto.cipher.params) @@ -166,19 +181,15 @@ def decrypt(self, password: str) -> bytes: class Pbkdf2Keystore(Keystore): crypto: KeystoreCrypto = KeystoreCrypto( kdf=KeystoreModule( - function='pbkdf2', - params={ - 'c': 2**18, - 'dklen': 32, - "prf": 'hmac-sha256' - }, + function="pbkdf2", + params={"c": 2**18, "dklen": 32, "prf": "hmac-sha256"}, ), checksum=KeystoreModule( - function='sha256', + function="sha256", ), cipher=KeystoreModule( - function='aes-128-ctr', - ) + function="aes-128-ctr", + ), ) @@ -186,18 +197,18 @@ class Pbkdf2Keystore(Keystore): class ScryptKeystore(Keystore): crypto: KeystoreCrypto = KeystoreCrypto( kdf=KeystoreModule( - function='scrypt', + function="scrypt", params={ - 'dklen': 32, - 'n': 2**18, - 'r': 8, - 'p': 1, + "dklen": 32, + "n": 2**18, + "r": 8, + "p": 1, }, ), checksum=KeystoreModule( - function='sha256', + function="sha256", ), cipher=KeystoreModule( - function='aes-128-ctr', - ) + function="aes-128-ctr", + ), ) diff --git a/staking_deposit/settings.py b/staking_deposit/settings.py index e989d89b..9a2afefa 100644 --- a/staking_deposit/settings.py +++ b/staking_deposit/settings.py @@ -1,7 +1,8 @@ from typing import Dict, NamedTuple + from eth_utils import decode_hex -DEPOSIT_CLI_VERSION = '2.7.0' +DEPOSIT_CLI_VERSION = "2.7.0" class BaseChainSetting(NamedTuple): @@ -10,34 +11,53 @@ class BaseChainSetting(NamedTuple): GENESIS_VALIDATORS_ROOT: bytes -MAINNET = 'mainnet' -GOERLI = 'goerli' -PRATER = 'prater' -SEPOLIA = 'sepolia' -ZHEJIANG = 'zhejiang' -HOLESKY = 'holesky' +MAINNET = "mainnet" +GOERLI = "goerli" +PRATER = "prater" +SEPOLIA = "sepolia" +ZHEJIANG = "zhejiang" +HOLESKY = "holesky" # Mainnet setting MainnetSetting = BaseChainSetting( - NETWORK_NAME=MAINNET, GENESIS_FORK_VERSION=bytes.fromhex('00000000'), - GENESIS_VALIDATORS_ROOT=bytes.fromhex('4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95')) + NETWORK_NAME=MAINNET, + GENESIS_FORK_VERSION=bytes.fromhex("00000000"), + GENESIS_VALIDATORS_ROOT=bytes.fromhex( + "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95" + ), +) # Goerli setting GoerliSetting = BaseChainSetting( - NETWORK_NAME=GOERLI, GENESIS_FORK_VERSION=bytes.fromhex('00001020'), - GENESIS_VALIDATORS_ROOT=bytes.fromhex('043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb')) + NETWORK_NAME=GOERLI, + GENESIS_FORK_VERSION=bytes.fromhex("00001020"), + GENESIS_VALIDATORS_ROOT=bytes.fromhex( + "043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb" + ), +) # Sepolia setting SepoliaSetting = BaseChainSetting( - NETWORK_NAME=SEPOLIA, GENESIS_FORK_VERSION=bytes.fromhex('90000069'), - GENESIS_VALIDATORS_ROOT=bytes.fromhex('d8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078')) + NETWORK_NAME=SEPOLIA, + GENESIS_FORK_VERSION=bytes.fromhex("90000069"), + GENESIS_VALIDATORS_ROOT=bytes.fromhex( + "d8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078" + ), +) # Zhejiang setting ZhejiangSetting = BaseChainSetting( - NETWORK_NAME=ZHEJIANG, GENESIS_FORK_VERSION=bytes.fromhex('00000069'), - GENESIS_VALIDATORS_ROOT=bytes.fromhex('53a92d8f2bb1d85f62d16a156e6ebcd1bcaba652d0900b2c2f387826f3481f6f')) + NETWORK_NAME=ZHEJIANG, + GENESIS_FORK_VERSION=bytes.fromhex("00000069"), + GENESIS_VALIDATORS_ROOT=bytes.fromhex( + "53a92d8f2bb1d85f62d16a156e6ebcd1bcaba652d0900b2c2f387826f3481f6f" + ), +) # Holesky setting HoleskySetting = BaseChainSetting( - NETWORK_NAME=HOLESKY, GENESIS_FORK_VERSION=bytes.fromhex('01017000'), - GENESIS_VALIDATORS_ROOT=bytes.fromhex('9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1')) - + NETWORK_NAME=HOLESKY, + GENESIS_FORK_VERSION=bytes.fromhex("01017000"), + GENESIS_VALIDATORS_ROOT=bytes.fromhex( + "9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1" + ), +) ALL_CHAINS: Dict[str, BaseChainSetting] = { MAINNET: MainnetSetting, @@ -53,9 +73,9 @@ def get_chain_setting(chain_name: str = MAINNET) -> BaseChainSetting: return ALL_CHAINS[chain_name] -def get_devnet_chain_setting(network_name: str, - genesis_fork_version: str, - genesis_validator_root: str) -> BaseChainSetting: +def get_devnet_chain_setting( + network_name: str, genesis_fork_version: str, genesis_validator_root: str +) -> BaseChainSetting: return BaseChainSetting( NETWORK_NAME=network_name, GENESIS_FORK_VERSION=decode_hex(genesis_fork_version), diff --git a/staking_deposit/utils/ascii_art.py b/staking_deposit/utils/ascii_art.py index 79268e58..53655a13 100644 --- a/staking_deposit/utils/ascii_art.py +++ b/staking_deposit/utils/ascii_art.py @@ -1,5 +1,5 @@ # flake8: noqa -RHINO_0 = ''' +RHINO_0 = """ ##### ##### ## ##### ## @@ -21,4 +21,4 @@ ####### ################# ### ## ## ## ## ## ### ############## ############# - ''' + """ diff --git a/staking_deposit/utils/click.py b/staking_deposit/utils/click.py index ceb36d64..ea9d18df 100644 --- a/staking_deposit/utils/click.py +++ b/staking_deposit/utils/click.py @@ -1,28 +1,23 @@ +from typing import Any, Callable, Optional, Sequence, Tuple, Union + import click -from typing import ( - Any, - Callable, - Optional, - Sequence, - Tuple, - Union, -) from staking_deposit.exceptions import ValidationError from staking_deposit.utils import config def _value_of(f: Union[Callable[[], Any], Any]) -> Any: - ''' + """ If the input, f, is a function, return f(), else return f. - ''' - return(f() if callable(f) else f) + """ + return f() if callable(f) else f class JITOption(click.Option): - ''' + """ A click.Option, except certain values are recomputed before they are used. - ''' + """ + def __init__( self, param_decls: Union[str, Sequence[str]], @@ -31,7 +26,6 @@ def __init__( prompt: Union[Callable[[], str], str, None] = None, **kwargs: Any, ): - self.callable_default = default self.callable_help = help self.callable_prompt = prompt @@ -82,11 +76,11 @@ def decorator(f: Callable[[Any], Any]) -> Callable[[Any], Any]: def captive_prompt_callback( processing_func: Callable[[str], Any], prompt: Callable[[], str], - confirmation_prompt: Optional[Callable[[], str]]=None, - confirmation_mismatch_msg: Callable[[], str]=lambda: '', - hide_input: bool=False, + confirmation_prompt: Optional[Callable[[], str]] = None, + confirmation_mismatch_msg: Callable[[], str] = lambda: "", + hide_input: bool = False, ) -> Callable[[click.Context, str, str], Any]: - ''' + """ Traps the user in a prompt until the value chosen is acceptable as defined by `processing_func` not returning a ValidationError :param processing_func: A function to process the user's input that possibly raises a ValidationError @@ -94,7 +88,8 @@ def captive_prompt_callback( :param confirmation_prompt: the optional prompt for confirming user input (the user must repeat their input) :param confirmation_mismatch_msg: the message displayed to the user should their input and confirmation not match :param hide_input: bool, hides the input as the user types - ''' + """ + def callback(ctx: click.Context, param: Any, user_input: str) -> Any: if config.non_interactive: return processing_func(user_input) @@ -102,19 +97,27 @@ def callback(ctx: click.Context, param: Any, user_input: str) -> Any: try: processed_input = processing_func(user_input) # Logic for confirming user input: - if confirmation_prompt is not None and processed_input not in ('', None): - confirmation_input = click.prompt(confirmation_prompt(), hide_input=hide_input) + if confirmation_prompt is not None and processed_input not in ( + "", + None, + ): + confirmation_input = click.prompt( + confirmation_prompt(), hide_input=hide_input + ) if processing_func(confirmation_input) != processed_input: raise ValidationError(confirmation_mismatch_msg()) return processed_input except ValidationError as e: - click.echo('\n[Error] ' + str(e)) + click.echo("\n[Error] " + str(e)) user_input = click.prompt(prompt(), hide_input=hide_input) + return callback -def choice_prompt_func(prompt_func: Callable[[], str], choices: Sequence[str]) -> Callable[[], str]: - ''' +def choice_prompt_func( + prompt_func: Callable[[], str], choices: Sequence[str] +) -> Callable[[], str]: + """ Formats the prompt and choices in a printable manner. - ''' - return lambda: '%s %s: ' % (prompt_func(), choices) + """ + return lambda: "%s %s: " % (prompt_func(), choices) diff --git a/staking_deposit/utils/config.py b/staking_deposit/utils/config.py index 2dce13cd..a7ece7eb 100644 --- a/staking_deposit/utils/config.py +++ b/staking_deposit/utils/config.py @@ -1,6 +1,7 @@ -''' +""" This file contains global variables to required to parameterise click functionality -''' +""" -language = 'en' # The CLI language selected by the user -non_interactive = False # Whether or not to interactively prompt the user for input. (Useful for tests and debugging) +language = "en" # The CLI language selected by the user +# Whether or not to interactively prompt the user for input. (Useful for tests and debugging) +non_interactive = False diff --git a/staking_deposit/utils/constants.py b/staking_deposit/utils/constants.py index cd64ecde..346611a3 100644 --- a/staking_deposit/utils/constants.py +++ b/staking_deposit/utils/constants.py @@ -1,69 +1,72 @@ import os -from typing import ( - Dict, - List, -) - +from typing import Dict, List -ZERO_BYTES32 = b'\x00' * 32 +ZERO_BYTES32 = b"\x00" * 32 # Execution-spec constants taken from https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md -DOMAIN_DEPOSIT = bytes.fromhex('03000000') -DOMAIN_BLS_TO_EXECUTION_CHANGE = bytes.fromhex('0A000000') -BLS_WITHDRAWAL_PREFIX = bytes.fromhex('00') -ETH1_ADDRESS_WITHDRAWAL_PREFIX = bytes.fromhex('01') - -ETH2GWEI = 10 ** 9 -MIN_DEPOSIT_AMOUNT = 2 ** 0 * ETH2GWEI -MAX_DEPOSIT_AMOUNT = 2 ** 5 * ETH2GWEI +DOMAIN_DEPOSIT = bytes.fromhex("03000000") +DOMAIN_BLS_TO_EXECUTION_CHANGE = bytes.fromhex("0A000000") +BLS_WITHDRAWAL_PREFIX = bytes.fromhex("00") +ETH1_ADDRESS_WITHDRAWAL_PREFIX = bytes.fromhex("01") +ETH2GWEI = 10**9 +MIN_DEPOSIT_AMOUNT = 2**0 * ETH2GWEI +MAX_DEPOSIT_AMOUNT = 2**5 * ETH2GWEI # File/folder constants -WORD_LISTS_PATH = os.path.join('staking_deposit', 'key_handling', 'key_derivation', 'word_lists') -DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = 'validator_keys' -DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME = 'bls_to_execution_changes' +WORD_LISTS_PATH = os.path.join( + "staking_deposit", "key_handling", "key_derivation", "word_lists" +) +DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = "validator_keys" +DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME = "bls_to_execution_changes" # Internationalisation constants -INTL_CONTENT_PATH = os.path.join('staking_deposit', 'intl') +INTL_CONTENT_PATH = os.path.join("staking_deposit", "intl") def _add_index_to_options(d: Dict[str, List[str]]) -> Dict[str, List[str]]: - ''' + """ Adds the (1 indexed) index (in the dict) to the first element of value list. eg. {'en': ['English', 'en']} -> {'en': ['1. English', '1', 'English', 'en']} Requires dicts to be ordered (Python > 3.6) - ''' - keys = list(d.keys()) # Force copy dictionary keys top prevent iteration over changing dict + """ + keys = list( + d.keys() + ) # Force copy dictionary keys top prevent iteration over changing dict for i, key in enumerate(keys): - d.update({key: ['%s. %s' % (i + 1, d[key][0]), str(i + 1)] + d[key]}) + d.update({key: ["%s. %s" % (i + 1, d[key][0]), str(i + 1)] + d[key]}) return d -INTL_LANG_OPTIONS = _add_index_to_options({ - 'ar': ['اŲ„ØđØąØĻŲŠØĐ', 'ar', 'Arabic'], - 'el': ['ÎĩÎŧÎŧÎ·Î―ÎđΚΎ', 'el', 'Greek'], - 'en': ['English', 'en'], - 'fr': ['Français', 'Francais', 'fr', 'French'], - 'id': ['Bahasa melayu', 'Melayu', 'id', 'Malay'], - 'it': ['Italiano', 'it', 'Italian'], - 'ja': ['æ—ĨæœŽčŠž', 'ja', 'Japanese'], - 'ko': ['한ęĩ­ė–ī', 'ėĄ°ė„ ë§', 'éŸ“åœ‹čŠž', 'ko', 'Korean'], - 'pt-BR': ['PortuguÊs do Brasil', 'Brasil', 'pt-BR', 'Brazilian Portuguese'], - 'ro': ['romÃĒn', 'limba romÃĒnă', 'ro', 'Romainian'], - 'tr': ['TÞrkçe', 'tr', 'Turkish'], - 'zh-CN': ['įŪ€ä―“äļ­æ–‡', 'zh-CN', 'zh', 'Chinease'], -}) -MNEMONIC_LANG_OPTIONS = _add_index_to_options({ - 'chinese_simplified': ['įŪ€ä―“äļ­æ–‡', 'zh', 'zh-CN', 'Chinese Simplified'], - 'chinese_traditional': ['įđéŦ”äļ­æ–‡', 'zh-tw', 'Chinese Traditional'], - 'czech': ['čeÅĄtina', 'českÃ― jazyk', 'cs', 'Czech'], - 'english': ['English', 'en'], - 'italian': ['Italiano', 'it', 'Italian'], - 'korean': ['한ęĩ­ė–ī', 'ėĄ°ė„ ë§', 'éŸ“åœ‹čŠž', 'ko', 'Korean'], - # Portuguese mnemonics are in both pt & pt-BR - 'portuguese': ['PortuguÊs', 'PortuguÊs do Brasil', 'pt', 'pt-BR', 'Portuguese'], - 'spanish': ['EspaÃąol', 'es', 'Spanish'], -}) +INTL_LANG_OPTIONS = _add_index_to_options( + { + "ar": ["اŲ„ØđØąØĻŲŠØĐ", "ar", "Arabic"], + "el": ["ÎĩÎŧÎŧÎ·Î―ÎđΚΎ", "el", "Greek"], + "en": ["English", "en"], + "fr": ["Français", "Francais", "fr", "French"], + "id": ["Bahasa melayu", "Melayu", "id", "Malay"], + "it": ["Italiano", "it", "Italian"], + "ja": ["æ—ĨæœŽčŠž", "ja", "Japanese"], + "ko": ["한ęĩ­ė–ī", "ėĄ°ė„ ë§", "éŸ“åœ‹čŠž", "ko", "Korean"], + "pt-BR": ["PortuguÊs do Brasil", "Brasil", "pt-BR", "Brazilian Portuguese"], + "ro": ["romÃĒn", "limba romÃĒnă", "ro", "Romainian"], + "tr": ["TÞrkçe", "tr", "Turkish"], + "zh-CN": ["įŪ€ä―“äļ­æ–‡", "zh-CN", "zh", "Chinease"], + } +) +MNEMONIC_LANG_OPTIONS = _add_index_to_options( + { + "chinese_simplified": ["įŪ€ä―“äļ­æ–‡", "zh", "zh-CN", "Chinese Simplified"], + "chinese_traditional": ["įđéŦ”äļ­æ–‡", "zh-tw", "Chinese Traditional"], + "czech": ["čeÅĄtina", "českÃ― jazyk", "cs", "Czech"], + "english": ["English", "en"], + "italian": ["Italiano", "it", "Italian"], + "korean": ["한ęĩ­ė–ī", "ėĄ°ė„ ë§", "éŸ“åœ‹čŠž", "ko", "Korean"], + # Portuguese mnemonics are in both pt & pt-BR + "portuguese": ["PortuguÊs", "PortuguÊs do Brasil", "pt", "pt-BR", "Portuguese"], + "spanish": ["EspaÃąol", "es", "Spanish"], + } +) # Sundry constants UNICODE_CONTROL_CHARS = list(range(0x00, 0x20)) + list(range(0x7F, 0xA0)) diff --git a/staking_deposit/utils/crypto.py b/staking_deposit/utils/crypto.py index 48a9d76a..61453820 100644 --- a/staking_deposit/utils/crypto.py +++ b/staking_deposit/utils/crypto.py @@ -1,17 +1,11 @@ from typing import Any -from Crypto.Hash import ( - SHA256 as _sha256, - SHA512 as _sha512, -) -from Crypto.Protocol.KDF import ( - scrypt as _scrypt, - HKDF as _HKDF, - PBKDF2 as _PBKDF2, -) -from Crypto.Cipher import ( - AES as _AES -) +from Crypto.Cipher import AES as _AES +from Crypto.Hash import SHA256 as _sha256 +from Crypto.Hash import SHA512 as _sha512 +from Crypto.Protocol.KDF import HKDF as _HKDF +from Crypto.Protocol.KDF import PBKDF2 as _PBKDF2 +from Crypto.Protocol.KDF import scrypt as _scrypt def SHA256(x: bytes) -> bytes: @@ -21,35 +15,42 @@ def SHA256(x: bytes) -> bytes: def scrypt(*, password: str, salt: str, n: int, r: int, p: int, dklen: int) -> bytes: if n * r * p < 2**20: # 128 MB memory usage raise ValueError("The Scrypt parameters chosen are not secure.") - if n >= 2**(128 * r / 8): - raise ValueError("The given `n` should be less than `2**(128 * r / 8)`." - f"\tGot `n={n}`, r={r}, 2**(128 * r / 8)={2**(128 * r / 8)}") + if n >= 2 ** (128 * r / 8): + raise ValueError( + "The given `n` should be less than `2**(128 * r / 8)`." + f"\tGot `n={n}`, r={r}, 2**(128 * r / 8)={2**(128 * r / 8)}" + ) res = _scrypt(password=password, salt=salt, key_len=dklen, N=n, r=r, p=p) - return res if isinstance(res, bytes) else res[0] # PyCryptodome can return Tuple[bytes] + # PyCryptodome can return Tuple[bytes] + return res if isinstance(res, bytes) else res[0] def PBKDF2(*, password: bytes, salt: bytes, dklen: int, c: int, prf: str) -> bytes: - if 'sha' not in prf: + if "sha" not in prf: raise ValueError(f"String 'sha' is not in `prf`({prf})") - if 'sha256' in prf and c < 2**18: - ''' + if "sha256" in prf and c < 2**18: + """ Verify the number of rounds of SHA256-PBKDF2. SHA512 not checked as use in BIP39 does not require, and therefore doesn't use, safe parameters (c=2048). Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed - ''' + """ raise ValueError("The PBKDF2 parameters chosen are not secure.") - _hash = _sha256 if 'sha256' in prf else _sha512 - res = _PBKDF2(password=password, salt=salt, dkLen=dklen, count=c, hmac_hash_module=_hash) # type: ignore - return res if isinstance(res, bytes) else res[0] # PyCryptodome can return Tuple[bytes] + _hash = _sha256 if "sha256" in prf else _sha512 + res = _PBKDF2( + password=password, salt=salt, dkLen=dklen, count=c, hmac_hash_module=_hash + ) # type: ignore + # PyCryptodome can return Tuple[bytes] + return res if isinstance(res, bytes) else res[0] -def HKDF(*, salt: bytes, IKM: bytes, L: int, info: bytes=b'') -> bytes: +def HKDF(*, salt: bytes, IKM: bytes, L: int, info: bytes = b"") -> bytes: res = _HKDF(master=IKM, key_len=L, salt=salt, hashmod=_sha256, context=info) - return res if isinstance(res, bytes) else res[0] # PyCryptodome can return Tuple[bytes] + # PyCryptodome can return Tuple[bytes] + return res if isinstance(res, bytes) else res[0] def AES_128_CTR(*, key: bytes, iv: bytes) -> Any: if len(key) != 16: raise ValueError(f"The key length should be 16. Got {len(key)}.") - return _AES.new(key=key, mode=_AES.MODE_CTR, initial_value=iv, nonce=b'') + return _AES.new(key=key, mode=_AES.MODE_CTR, initial_value=iv, nonce=b"") diff --git a/staking_deposit/utils/intl.py b/staking_deposit/utils/intl.py index b92e6a73..f1256194 100644 --- a/staking_deposit/utils/intl.py +++ b/staking_deposit/utils/intl.py @@ -1,65 +1,56 @@ -import inspect import difflib -from functools import reduce +import inspect import json -from typing import ( - Any, - Dict, - Iterable, - List, - Mapping, - Sequence, -) import os +from functools import reduce +from typing import Any, Dict, Iterable, List, Mapping, Sequence -from staking_deposit.utils import config -from staking_deposit.utils.constants import ( - INTL_CONTENT_PATH, -) -from staking_deposit.utils.file_handling import ( - resource_path, -) from staking_deposit.exceptions import ValidationError +from staking_deposit.utils import config +from staking_deposit.utils.constants import INTL_CONTENT_PATH +from staking_deposit.utils.file_handling import resource_path def _get_from_dict(dataDict: Dict[str, Any], mapList: Iterable[str]) -> str: - ''' + """ Iterate nested dictionaries - ''' + """ try: ans = reduce(dict.get, mapList, dataDict) assert isinstance(ans, str) return ans except TypeError: - raise KeyError('%s not in internationalisation json file.' % mapList) + raise KeyError("%s not in internationalisation json file." % mapList) except AssertionError: - raise KeyError('The provided params (%s) were incomplete.' % mapList) + raise KeyError("The provided params (%s) were incomplete." % mapList) -def load_text(params: List[str], file_path: str='', func: str='', lang: str='') -> str: - ''' +def load_text( + params: List[str], file_path: str = "", func: str = "", lang: str = "" +) -> str: + """ Determine and return the appropriate internationalisation text for a given set of `params`. - ''' - if file_path == '': + """ + if file_path == "": # Auto-detect file-path based on call stack file_path = inspect.stack()[1].filename - if file_path[-4:] == '.pyc': - file_path = file_path[:-4] + '.json' # replace .pyc with .json - elif file_path[-3:] == '.py': - file_path = file_path[:-3] + '.json' # replace .py with .json + if file_path[-4:] == ".pyc": + file_path = file_path[:-4] + ".json" # replace .pyc with .json + elif file_path[-3:] == ".py": + file_path = file_path[:-3] + ".json" # replace .py with .json else: raise KeyError("Wrong file_path %s", file_path) - if func == '': + if func == "": # Auto-detect function based on call stack func = inspect.stack()[1].function - if lang == '': + if lang == "": lang = config.language # Determine path to json text file_path_list = os.path.normpath(file_path).split(os.path.sep) - rel_path_list = file_path_list[file_path_list.index('staking_deposit') + 1:] + rel_path_list = file_path_list[file_path_list.index("staking_deposit") + 1 :] json_path = resource_path(os.path.join(INTL_CONTENT_PATH, lang, *rel_path_list)) try: @@ -69,35 +60,37 @@ def load_text(params: List[str], file_path: str='', func: str='', lang: str='') return _get_from_dict(text_dict, [func] + params) except (KeyError, FileNotFoundError): # If text not found in lang, try return English version - if lang == 'en': - raise KeyError('%s not in %s file' % ([func] + params, json_path)) - return load_text(params, file_path, func, 'en') + if lang == "en": + raise KeyError("%s not in %s file" % ([func] + params, json_path)) + return load_text(params, file_path, func, "en") def get_first_options(options: Mapping[str, Sequence[str]]) -> List[str]: - ''' + """ Returns the first `option` in the values of the `options` dict. - ''' + """ return list(map(lambda x: x[0], options.values())) def closest_match(text: str, options: Iterable[str]) -> str: - ''' + """ Finds the closest match to `text` in the `options_list` - ''' + """ match = difflib.get_close_matches(text, options, n=1, cutoff=0.6) if len(match) == 0: - raise ValidationError('%s is not a valid language option' % text) + raise ValidationError("%s is not a valid language option" % text) return match[0] def fuzzy_reverse_dict_lookup(text: str, options: Mapping[str, Sequence[str]]) -> str: - ''' + """ Returns the closest match to `text` out of the `options` :param text: The test string that needs to be found :param options: A dict with keys (the value that will be returned) and values a list of the options to be matched against - ''' - reverse_lookup_dict = {value: key for key, values in options.items() for value in values} + """ + reverse_lookup_dict = { + value: key for key, values in options.items() for value in values + } match = closest_match(text, reverse_lookup_dict.keys()) return reverse_lookup_dict[match] diff --git a/staking_deposit/utils/ssz.py b/staking_deposit/utils/ssz.py index 513b0096..42130acb 100644 --- a/staking_deposit/utils/ssz.py +++ b/staking_deposit/utils/ssz.py @@ -1,12 +1,5 @@ -from ssz import ( - ByteVector, - Serializable, - uint64, - bytes4, - bytes32, - bytes48, - bytes96 -) +from ssz import ByteVector, Serializable, bytes4, bytes32, bytes48, bytes96, uint64 + from staking_deposit.utils.constants import ( DOMAIN_BLS_TO_EXECUTION_CHANGE, DOMAIN_DEPOSIT, @@ -16,29 +9,30 @@ bytes8 = ByteVector(8) bytes20 = ByteVector(20) - # Crypto Domain SSZ + class SigningData(Serializable): - fields = [ - ('object_root', bytes32), - ('domain', bytes32) - ] + fields = [("object_root", bytes32), ("domain", bytes32)] class ForkData(Serializable): fields = [ - ('current_version', bytes4), - ('genesis_validators_root', bytes32), + ("current_version", bytes4), + ("genesis_validators_root", bytes32), ] -def compute_fork_data_root(current_version: bytes, genesis_validators_root: bytes) -> bytes: +def compute_fork_data_root( + current_version: bytes, genesis_validators_root: bytes +) -> bytes: """ Return the appropriate ForkData root for a given deposit version. """ if len(current_version) != 4: - raise ValueError(f"Fork version should be in 4 bytes. Got {len(current_version)}.") + raise ValueError( + f"Fork version should be in 4 bytes. Got {len(current_version)}." + ) return ForkData( current_version=current_version, genesis_validators_root=genesis_validators_root, @@ -56,7 +50,9 @@ def compute_deposit_domain(fork_version: bytes) -> bytes: return domain_type + fork_data_root[:28] -def compute_bls_to_execution_change_domain(fork_version: bytes, genesis_validators_root: bytes) -> bytes: +def compute_bls_to_execution_change_domain( + fork_version: bytes, genesis_validators_root: bytes +) -> bytes: """ BLS_TO_EXECUTION_CHANGE-only `compute_domain` """ @@ -94,10 +90,11 @@ class DepositMessage(Serializable): """ Ref: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#depositmessage """ + fields = [ - ('pubkey', bytes48), - ('withdrawal_credentials', bytes32), - ('amount', uint64), + ("pubkey", bytes48), + ("withdrawal_credentials", bytes32), + ("amount", uint64), ] @@ -105,11 +102,12 @@ class DepositData(Serializable): """ Ref: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#depositdata """ + fields = [ - ('pubkey', bytes48), - ('withdrawal_credentials', bytes32), - ('amount', uint64), - ('signature', bytes96) + ("pubkey", bytes48), + ("withdrawal_credentials", bytes32), + ("amount", uint64), + ("signature", bytes96), ] @@ -117,10 +115,11 @@ class BLSToExecutionChange(Serializable): """ Ref: https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#blstoexecutionchange """ + fields = [ - ('validator_index', uint64), - ('from_bls_pubkey', bytes48), - ('to_execution_address', bytes20), + ("validator_index", uint64), + ("from_bls_pubkey", bytes48), + ("to_execution_address", bytes20), ] @@ -128,7 +127,8 @@ class SignedBLSToExecutionChange(Serializable): """ Ref: https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#signedblstoexecutionchange """ + fields = [ - ('message', BLSToExecutionChange), - ('signature', bytes96), + ("message", BLSToExecutionChange), + ("signature", bytes96), ] diff --git a/staking_deposit/utils/validation.py b/staking_deposit/utils/validation.py index ab6f9849..f55deeea 100644 --- a/staking_deposit/utils/validation.py +++ b/staking_deposit/utils/validation.py @@ -1,17 +1,27 @@ -import click import json import re from typing import Any, Dict, Sequence -from eth_typing import ( - BLSPubkey, - BLSSignature, - HexAddress, +import click +from eth_typing import BLSPubkey, BLSSignature, HexAddress +from eth_utils import ( + decode_hex, + is_checksum_address, + is_hex_address, + to_normalized_address, ) -from eth_utils import is_hex_address, is_checksum_address, to_normalized_address, decode_hex from py_ecc.bls import G2ProofOfPossession as bls +from staking_deposit.credentials import Credential from staking_deposit.exceptions import ValidationError +from staking_deposit.settings import BaseChainSetting +from staking_deposit.utils.constants import ( + BLS_WITHDRAWAL_PREFIX, + ETH1_ADDRESS_WITHDRAWAL_PREFIX, + MAX_DEPOSIT_AMOUNT, + MIN_DEPOSIT_AMOUNT, +) +from staking_deposit.utils.crypto import SHA256 from staking_deposit.utils.intl import load_text from staking_deposit.utils.ssz import ( BLSToExecutionChange, @@ -21,46 +31,46 @@ compute_deposit_domain, compute_signing_root, ) -from staking_deposit.credentials import ( - Credential, -) -from staking_deposit.utils.constants import ( - MAX_DEPOSIT_AMOUNT, - MIN_DEPOSIT_AMOUNT, - BLS_WITHDRAWAL_PREFIX, - ETH1_ADDRESS_WITHDRAWAL_PREFIX, -) -from staking_deposit.utils.crypto import SHA256 -from staking_deposit.settings import BaseChainSetting - # # Deposit # -def verify_deposit_data_json(filefolder: str, credentials: Sequence[Credential]) -> bool: + +def verify_deposit_data_json( + filefolder: str, credentials: Sequence[Credential] +) -> bool: """ Validate every deposit found in the deposit-data JSON file folder. """ - with open(filefolder, 'r') as f: + with open(filefolder, "r") as f: deposit_json = json.load(f) - with click.progressbar(deposit_json, label=load_text(['msg_deposit_verification']), - show_percent=False, show_pos=True) as deposits: - return all([validate_deposit(deposit, credential) for deposit, credential in zip(deposits, credentials)]) + with click.progressbar( + deposit_json, + label=load_text(["msg_deposit_verification"]), + show_percent=False, + show_pos=True, + ) as deposits: + return all( + [ + validate_deposit(deposit, credential) + for deposit, credential in zip(deposits, credentials) + ] + ) return False def validate_deposit(deposit_data_dict: Dict[str, Any], credential: Credential) -> bool: - ''' + """ Checks whether a deposit is valid based on the staking deposit rules. https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#deposits - ''' - pubkey = BLSPubkey(bytes.fromhex(deposit_data_dict['pubkey'])) - withdrawal_credentials = bytes.fromhex(deposit_data_dict['withdrawal_credentials']) - amount = deposit_data_dict['amount'] - signature = BLSSignature(bytes.fromhex(deposit_data_dict['signature'])) - deposit_message_root = bytes.fromhex(deposit_data_dict['deposit_data_root']) - fork_version = bytes.fromhex(deposit_data_dict['fork_version']) + """ + pubkey = BLSPubkey(bytes.fromhex(deposit_data_dict["pubkey"])) + withdrawal_credentials = bytes.fromhex(deposit_data_dict["withdrawal_credentials"]) + amount = deposit_data_dict["amount"] + signature = BLSSignature(bytes.fromhex(deposit_data_dict["signature"])) + deposit_message_root = bytes.fromhex(deposit_data_dict["deposit_data_root"]) + fork_version = bytes.fromhex(deposit_data_dict["fork_version"]) # Verify pubkey if len(pubkey) != 48: @@ -71,11 +81,19 @@ def validate_deposit(deposit_data_dict: Dict[str, Any], credential: Credential) # Verify withdrawal credential if len(withdrawal_credentials) != 32: return False - if withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX == credential.withdrawal_prefix: + if ( + withdrawal_credentials[:1] + == BLS_WITHDRAWAL_PREFIX + == credential.withdrawal_prefix + ): if withdrawal_credentials[1:] != SHA256(credential.withdrawal_pk)[1:]: return False - elif withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX == credential.withdrawal_prefix: - if withdrawal_credentials[1:12] != b'\x00' * 11: + elif ( + withdrawal_credentials[:1] + == ETH1_ADDRESS_WITHDRAWAL_PREFIX + == credential.withdrawal_prefix + ): + if withdrawal_credentials[1:12] != b"\x00" * 11: return False if credential.eth1_withdrawal_address is None: return False @@ -89,7 +107,9 @@ def validate_deposit(deposit_data_dict: Dict[str, Any], credential: Credential) return False # Verify deposit signature && pubkey - deposit_message = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount) + deposit_message = DepositMessage( + pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount + ) domain = compute_deposit_domain(fork_version) signing_root = compute_signing_root(deposit_message, domain) if not bls.Verify(pubkey, signing_root, signature): @@ -107,75 +127,94 @@ def validate_deposit(deposit_data_dict: Dict[str, Any], credential: Credential) def validate_password_strength(password: str) -> str: if len(password) < 8: - raise ValidationError(load_text(['msg_password_length'])) + raise ValidationError(load_text(["msg_password_length"])) return password def validate_int_range(num: Any, low: int, high: int) -> int: - ''' + """ Verifies that `num` is an `int` andlow <= num < high - ''' + """ try: num_int = int(num) # Try cast to int assert num_int == float(num) # Check num is not float assert low <= num_int < high # Check num in range return num_int except (ValueError, AssertionError): - raise ValidationError(load_text(['err_not_positive_integer'])) + raise ValidationError(load_text(["err_not_positive_integer"])) -def validate_eth1_withdrawal_address(cts: click.Context, param: Any, address: str) -> HexAddress: +def validate_eth1_withdrawal_address( + cts: click.Context, param: Any, address: str +) -> HexAddress: if address is None: return None if not is_hex_address(address): - raise ValidationError(load_text(['err_invalid_ECDSA_hex_addr'])) + raise ValidationError(load_text(["err_invalid_ECDSA_hex_addr"])) if not is_checksum_address(address): - raise ValidationError(load_text(['err_invalid_ECDSA_hex_addr_checksum'])) + raise ValidationError(load_text(["err_invalid_ECDSA_hex_addr_checksum"])) normalized_address = to_normalized_address(address) - click.echo('\n%s\n' % load_text(['msg_ECDSA_hex_addr_withdrawal'])) + click.echo("\n%s\n" % load_text(["msg_ECDSA_hex_addr_withdrawal"])) return normalized_address + # # BLSToExecutionChange # -def verify_bls_to_execution_change_json(filefolder: str, - credentials: Sequence[Credential], - *, - input_validator_indices: Sequence[int], - input_execution_address: str, - chain_setting: BaseChainSetting) -> bool: +def verify_bls_to_execution_change_json( + filefolder: str, + credentials: Sequence[Credential], + *, + input_validator_indices: Sequence[int], + input_execution_address: str, + chain_setting: BaseChainSetting, +) -> bool: """ Validate every BLSToExecutionChange found in the bls_to_execution_change JSON file folder. """ - with open(filefolder, 'r') as f: + with open(filefolder, "r") as f: btec_json = json.load(f) - with click.progressbar(btec_json, label=load_text(['msg_bls_to_execution_change_verification']), - show_percent=False, show_pos=True) as btecs: - return all([ - validate_bls_to_execution_change( - btec, credential, - input_validator_index=input_validator_index, - input_execution_address=input_execution_address, - chain_setting=chain_setting) - for btec, credential, input_validator_index in zip(btecs, credentials, input_validator_indices) - ]) + with click.progressbar( + btec_json, + label=load_text(["msg_bls_to_execution_change_verification"]), + show_percent=False, + show_pos=True, + ) as btecs: + return all( + [ + validate_bls_to_execution_change( + btec, + credential, + input_validator_index=input_validator_index, + input_execution_address=input_execution_address, + chain_setting=chain_setting, + ) + for btec, credential, input_validator_index in zip( + btecs, credentials, input_validator_indices + ) + ] + ) return False -def validate_bls_to_execution_change(btec_dict: Dict[str, Any], - credential: Credential, - *, - input_validator_index: int, - input_execution_address: str, - chain_setting: BaseChainSetting) -> bool: - validator_index = int(btec_dict['message']['validator_index']) - from_bls_pubkey = BLSPubkey(decode_hex(btec_dict['message']['from_bls_pubkey'])) - to_execution_address = decode_hex(btec_dict['message']['to_execution_address']) - signature = BLSSignature(decode_hex(btec_dict['signature'])) - genesis_validators_root = decode_hex(btec_dict['metadata']['genesis_validators_root']) +def validate_bls_to_execution_change( + btec_dict: Dict[str, Any], + credential: Credential, + *, + input_validator_index: int, + input_execution_address: str, + chain_setting: BaseChainSetting, +) -> bool: + validator_index = int(btec_dict["message"]["validator_index"]) + from_bls_pubkey = BLSPubkey(decode_hex(btec_dict["message"]["from_bls_pubkey"])) + to_execution_address = decode_hex(btec_dict["message"]["to_execution_address"]) + signature = BLSSignature(decode_hex(btec_dict["signature"])) + genesis_validators_root = decode_hex( + btec_dict["metadata"]["genesis_validators_root"] + ) if validator_index != input_validator_index: return False @@ -206,14 +245,16 @@ def validate_bls_to_execution_change(btec_dict: Dict[str, Any], return True -def normalize_bls_withdrawal_credentials_to_bytes(bls_withdrawal_credentials: str) -> bytes: - if bls_withdrawal_credentials.startswith('0x'): +def normalize_bls_withdrawal_credentials_to_bytes( + bls_withdrawal_credentials: str, +) -> bytes: + if bls_withdrawal_credentials.startswith("0x"): bls_withdrawal_credentials = bls_withdrawal_credentials[2:] try: bls_withdrawal_credentials_bytes = bytes.fromhex(bls_withdrawal_credentials) except Exception: - raise ValidationError(load_text(['err_incorrect_hex_form']) + '\n') + raise ValidationError(load_text(["err_incorrect_hex_form"]) + "\n") return bls_withdrawal_credentials_bytes @@ -221,46 +262,56 @@ def is_eth1_address_withdrawal_credentials(withdrawal_credentials: bytes) -> boo return ( len(withdrawal_credentials) == 32 and withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX - and withdrawal_credentials[1:12] == b'\x00' * 11 + and withdrawal_credentials[1:12] == b"\x00" * 11 ) def validate_bls_withdrawal_credentials(bls_withdrawal_credentials: str) -> bytes: - bls_withdrawal_credentials_bytes = normalize_bls_withdrawal_credentials_to_bytes(bls_withdrawal_credentials) + bls_withdrawal_credentials_bytes = normalize_bls_withdrawal_credentials_to_bytes( + bls_withdrawal_credentials + ) if is_eth1_address_withdrawal_credentials(bls_withdrawal_credentials_bytes): - raise ValidationError(load_text(['err_is_already_eth1_form']) + '\n') + raise ValidationError(load_text(["err_is_already_eth1_form"]) + "\n") try: assert len(bls_withdrawal_credentials_bytes) == 32 assert bls_withdrawal_credentials_bytes[:1] == BLS_WITHDRAWAL_PREFIX except (ValueError, AssertionError): - raise ValidationError(load_text(['err_not_bls_form']) + '\n') + raise ValidationError(load_text(["err_not_bls_form"]) + "\n") return bls_withdrawal_credentials_bytes def normalize_input_list(input: str) -> Sequence[str]: try: - input = input.strip('[({})]') - input = re.sub(' +', ' ', input) - result = re.split(r'; |, | |,|;', input) + input = input.strip("[({})]") + input = re.sub(" +", " ", input) + result = re.split(r"; |, | |,|;", input) except Exception: - raise ValidationError(load_text(['err_incorrect_list']) + '\n') + raise ValidationError(load_text(["err_incorrect_list"]) + "\n") return result -def validate_bls_withdrawal_credentials_list(input_bls_withdrawal_credentials_list: str) -> Sequence[bytes]: - bls_withdrawal_credentials_list = normalize_input_list(input_bls_withdrawal_credentials_list) - return [validate_bls_withdrawal_credentials(cred) for cred in bls_withdrawal_credentials_list] +def validate_bls_withdrawal_credentials_list( + input_bls_withdrawal_credentials_list: str, +) -> Sequence[bytes]: + bls_withdrawal_credentials_list = normalize_input_list( + input_bls_withdrawal_credentials_list + ) + return [ + validate_bls_withdrawal_credentials(cred) + for cred in bls_withdrawal_credentials_list + ] def validate_validator_indices(input_validator_indices: str) -> Sequence[int]: - normalized_list = normalize_input_list(input_validator_indices) return [validate_int_range(int(index), 0, 2**32) for index in normalized_list] -def validate_bls_withdrawal_credentials_matching(bls_withdrawal_credentials: bytes, credential: Credential) -> None: +def validate_bls_withdrawal_credentials_matching( + bls_withdrawal_credentials: bytes, credential: Credential +) -> None: if bls_withdrawal_credentials[1:] != SHA256(credential.withdrawal_pk)[1:]: - raise ValidationError(load_text(['err_not_matching']) + '\n') + raise ValidationError(load_text(["err_not_matching"]) + "\n") diff --git a/test_binary_btec_script.py b/test_binary_btec_script.py index 6fb92d32..1a5eb27f 100755 --- a/test_binary_btec_script.py +++ b/test_binary_btec_script.py @@ -2,45 +2,52 @@ import os import sys - # For not importing staking_deposit here -DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = 'bls_to_execution_changes' +DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = "bls_to_execution_changes" async def main(argv): binary_file_path = argv[1] - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) - if os.name == 'nt': # Windows - run_script_cmd = ".\\" + binary_file_path + '\deposit.exe' + if os.name == "nt": # Windows + run_script_cmd = ".\\" + binary_file_path + "\deposit.exe" else: # Mac or Linux - run_script_cmd = './' + binary_file_path + '/deposit' + run_script_cmd = "./" + binary_file_path + "/deposit" cmd_args = [ run_script_cmd, - '--language', 'english', - '--non_interactive', - 'generate-bls-to-execution-change', - '--bls_to_execution_changes_folder', my_folder_path, - '--chain', 'mainnet', - '--mnemonic', '\"sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry\"', - '--bls_withdrawal_credentials_list', '0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de', - '--validator_start_index', '0', - '--validator_indices', '1', - '--execution_address', '0x3434343434343434343434343434343434343434', + "--language", + "english", + "--non_interactive", + "generate-bls-to-execution-change", + "--bls_to_execution_changes_folder", + my_folder_path, + "--chain", + "mainnet", + "--mnemonic", + '"sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry"', + "--bls_withdrawal_credentials_list", + "0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de", + "--validator_start_index", + "0", + "--validator_indices", + "1", + "--execution_address", + "0x3434343434343434343434343434343434343434", ] proc = await asyncio.create_subprocess_shell( - ' '.join(cmd_args), + " ".join(cmd_args), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - seed_phrase = '' + seed_phrase = "" parsing = False async for out in proc.stdout: - output = out.decode('utf-8').rstrip() + output = out.decode("utf-8").rstrip() if output.startswith("***Using the tool"): parsing = True elif output.startswith("This is your mnemonic"): @@ -52,17 +59,19 @@ async def main(argv): if len(seed_phrase) > 0: encoded_phrase = seed_phrase.encode() proc.stdin.write(encoded_phrase) - proc.stdin.write(b'\n') + proc.stdin.write(b"\n") print(output) async for out in proc.stderr: - output = out.decode('utf-8').rstrip() - print(f'[stderr] {output}') + output = out.decode("utf-8").rstrip() + print(f"[stderr] {output}") assert len(seed_phrase) > 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) # Clean up @@ -72,7 +81,7 @@ async def main(argv): os.rmdir(my_folder_path) -if os.name == 'nt': # Windows +if os.name == "nt": # Windows loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) loop.run_until_complete(main(sys.argv)) diff --git a/test_binary_deposit_script.py b/test_binary_deposit_script.py index e041d0aa..57797a11 100755 --- a/test_binary_deposit_script.py +++ b/test_binary_deposit_script.py @@ -2,43 +2,48 @@ import os import sys - # For not importing staking_deposit here -DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = 'validator_keys' +DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = "validator_keys" async def main(argv): binary_file_path = argv[1] - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) - if os.name == 'nt': # Windows - run_script_cmd = ".\\" + binary_file_path + '\deposit.exe' + if os.name == "nt": # Windows + run_script_cmd = ".\\" + binary_file_path + "\deposit.exe" else: # Mac or Linux - run_script_cmd = './' + binary_file_path + '/deposit' + run_script_cmd = "./" + binary_file_path + "/deposit" cmd_args = [ run_script_cmd, - '--language', 'english', - '--non_interactive', - 'new-mnemonic', - '--num_validators', '1', - '--mnemonic_language', 'english', - '--chain', 'mainnet', - '--keystore_password', 'MyPassword', - '--folder', my_folder_path, + "--language", + "english", + "--non_interactive", + "new-mnemonic", + "--num_validators", + "1", + "--mnemonic_language", + "english", + "--chain", + "mainnet", + "--keystore_password", + "MyPassword", + "--folder", + my_folder_path, ] proc = await asyncio.create_subprocess_shell( - ' '.join(cmd_args), + " ".join(cmd_args), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - seed_phrase = '' + seed_phrase = "" parsing = False async for out in proc.stdout: - output = out.decode('utf-8').rstrip() + output = out.decode("utf-8").rstrip() if output.startswith("This is your mnemonic"): parsing = True elif output.startswith("Please type your mnemonic"): @@ -48,17 +53,19 @@ async def main(argv): if len(seed_phrase) > 0: encoded_phrase = seed_phrase.encode() proc.stdin.write(encoded_phrase) - proc.stdin.write(b'\n') + proc.stdin.write(b"\n") print(output) async for out in proc.stderr: - output = out.decode('utf-8').rstrip() - print(f'[stderr] {output}') + output = out.decode("utf-8").rstrip() + print(f"[stderr] {output}") assert len(seed_phrase) > 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) # Clean up @@ -68,7 +75,7 @@ async def main(argv): os.rmdir(my_folder_path) -if os.name == 'nt': # Windows +if os.name == "nt": # Windows loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) loop.run_until_complete(main(sys.argv)) diff --git a/test_btec_script.py b/test_btec_script.py index 6e6e22b7..644565de 100755 --- a/test_btec_script.py +++ b/test_btec_script.py @@ -2,50 +2,58 @@ import os # For not importing staking_deposit here -DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = 'bls_to_execution_changes' +DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = "bls_to_execution_changes" async def main(): - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) - if os.name == 'nt': # Windows - run_script_cmd = 'sh deposit.sh' + if os.name == "nt": # Windows + run_script_cmd = "sh deposit.sh" else: # Mac or Linux - run_script_cmd = './deposit.sh' + run_script_cmd = "./deposit.sh" - install_cmd = run_script_cmd + ' install' - print('[INFO] Creating subprocess 1: installation:' , install_cmd) + install_cmd = run_script_cmd + " install" + print("[INFO] Creating subprocess 1: installation:", install_cmd) proc = await asyncio.create_subprocess_shell( install_cmd, ) await proc.wait() - print('[INFO] Installed') + print("[INFO] Installed") cmd_args = [ run_script_cmd, - '--language', 'english', - '--non_interactive', - 'generate-bls-to-execution-change', - '--bls_to_execution_changes_folder', my_folder_path, - '--chain', 'mainnet', - '--mnemonic', '\"sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry\"', - '--bls_withdrawal_credentials_list', '0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de', - '--validator_start_index', '0', - '--validator_indices', '1', - '--execution_address', '0x3434343434343434343434343434343434343434', + "--language", + "english", + "--non_interactive", + "generate-bls-to-execution-change", + "--bls_to_execution_changes_folder", + my_folder_path, + "--chain", + "mainnet", + "--mnemonic", + '"sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry"', + "--bls_withdrawal_credentials_list", + "0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de", + "--validator_start_index", + "0", + "--validator_indices", + "1", + "--execution_address", + "0x3434343434343434343434343434343434343434", ] proc = await asyncio.create_subprocess_shell( - ' '.join(cmd_args), + " ".join(cmd_args), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - seed_phrase = '' + seed_phrase = "" parsing = False async for out in proc.stdout: - output = out.decode('utf-8').rstrip() + output = out.decode("utf-8").rstrip() if output.startswith("***Using the tool"): parsing = True elif output.startswith("This is your mnemonic"): @@ -57,17 +65,19 @@ async def main(): if len(seed_phrase) > 0: encoded_phrase = seed_phrase.encode() proc.stdin.write(encoded_phrase) - proc.stdin.write(b'\n') + proc.stdin.write(b"\n") print(output) async for out in proc.stderr: - output = out.decode('utf-8').rstrip() - print(f'[stderr] {output}') + output = out.decode("utf-8").rstrip() + print(f"[stderr] {output}") assert len(seed_phrase) > 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) # Clean up @@ -77,7 +87,7 @@ async def main(): os.rmdir(my_folder_path) -if os.name == 'nt': # Windows +if os.name == "nt": # Windows loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) loop.run_until_complete(main()) diff --git a/test_deposit_script.py b/test_deposit_script.py index 66317aa5..be47dd84 100755 --- a/test_deposit_script.py +++ b/test_deposit_script.py @@ -2,48 +2,54 @@ import os # For not importing staking_deposit here -DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = 'validator_keys' +DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = "validator_keys" async def main(): - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) - if os.name == 'nt': # Windows - run_script_cmd = 'sh deposit.sh' + if os.name == "nt": # Windows + run_script_cmd = "sh deposit.sh" else: # Mac or Linux - run_script_cmd = './deposit.sh' + run_script_cmd = "./deposit.sh" - install_cmd = run_script_cmd + ' install' - print('[INFO] Creating subprocess 1: installation:' , install_cmd) + install_cmd = run_script_cmd + " install" + print("[INFO] Creating subprocess 1: installation:", install_cmd) proc = await asyncio.create_subprocess_shell( install_cmd, ) await proc.wait() - print('[INFO] Installed') + print("[INFO] Installed") cmd_args = [ run_script_cmd, - '--language', 'english', - '--non_interactive', - 'new-mnemonic', - '--num_validators', '1', - '--mnemonic_language', 'english', - '--chain', 'mainnet', - '--keystore_password', 'MyPassword', - '--folder', my_folder_path, + "--language", + "english", + "--non_interactive", + "new-mnemonic", + "--num_validators", + "1", + "--mnemonic_language", + "english", + "--chain", + "mainnet", + "--keystore_password", + "MyPassword", + "--folder", + my_folder_path, ] proc = await asyncio.create_subprocess_shell( - ' '.join(cmd_args), + " ".join(cmd_args), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - seed_phrase = '' + seed_phrase = "" parsing = False async for out in proc.stdout: - output = out.decode('utf-8').rstrip() + output = out.decode("utf-8").rstrip() if output.startswith("This is your mnemonic"): parsing = True elif output.startswith("Please type your mnemonic"): @@ -53,17 +59,19 @@ async def main(): if len(seed_phrase) > 0: encoded_phrase = seed_phrase.encode() proc.stdin.write(encoded_phrase) - proc.stdin.write(b'\n') + proc.stdin.write(b"\n") print(output) async for out in proc.stderr: - output = out.decode('utf-8').rstrip() - print(f'[stderr] {output}') + output = out.decode("utf-8").rstrip() + print(f"[stderr] {output}") assert len(seed_phrase) > 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) # Clean up @@ -73,7 +81,7 @@ async def main(): os.rmdir(my_folder_path) -if os.name == 'nt': # Windows +if os.name == "nt": # Windows loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) loop.run_until_complete(main()) diff --git a/tests/test_cli/helpers.py b/tests/test_cli/helpers.py index 4342791b..a00fffc7 100644 --- a/tests/test_cli/helpers.py +++ b/tests/test_cli/helpers.py @@ -13,7 +13,9 @@ def clean_key_folder(my_folder_path: str) -> None: def clean_btec_folder(my_folder_path: str) -> None: - sub_folder_path = os.path.join(my_folder_path, DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME) + sub_folder_path = os.path.join( + my_folder_path, DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME + ) clean_folder(my_folder_path, sub_folder_path) @@ -38,12 +40,12 @@ def get_permissions(path: str, file_name: str) -> str: def verify_file_permission(os_ref, folder_path, files): - if os_ref.name == 'posix': + if os_ref.name == "posix": for file_name in files: - assert get_permissions(folder_path, file_name) == '0o440' + assert get_permissions(folder_path, file_name) == "0o440" -def prepare_testing_folder(os_ref, testing_folder_name='TESTING_TEMP_FOLDER'): +def prepare_testing_folder(os_ref, testing_folder_name="TESTING_TEMP_FOLDER"): my_folder_path = os_ref.path.join(os_ref.getcwd(), testing_folder_name) clean_btec_folder(my_folder_path) if not os_ref.path.exists(my_folder_path): diff --git a/tests/test_cli/test_existing_menmonic.py b/tests/test_cli/test_existing_menmonic.py index d41ce464..66c98656 100644 --- a/tests/test_cli/test_existing_menmonic.py +++ b/tests/test_cli/test_existing_menmonic.py @@ -4,113 +4,143 @@ import pytest from click.testing import CliRunner - from eth_utils import decode_hex from staking_deposit.deposit import cli -from staking_deposit.utils.constants import DEFAULT_VALIDATOR_KEYS_FOLDER_NAME, ETH1_ADDRESS_WITHDRAWAL_PREFIX +from staking_deposit.utils.constants import ( + DEFAULT_VALIDATOR_KEYS_FOLDER_NAME, + ETH1_ADDRESS_WITHDRAWAL_PREFIX, +) + from .helpers import clean_key_folder, get_permissions, get_uuid def test_existing_mnemonic_bls_withdrawal() -> None: # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) runner = CliRunner() inputs = [ - 'TREZOR', - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', - '2', '2', '5', 'mainnet', 'MyPassword', 'MyPassword'] - data = '\n'.join(inputs) + "TREZOR", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "2", + "2", + "5", + "mainnet", + "MyPassword", + "MyPassword", + ] + data = "\n".join(inputs) arguments = [ - '--language', 'english', - 'existing-mnemonic', - '--folder', my_folder_path, - '--mnemonic-password', 'TREZOR', + "--language", + "english", + "existing-mnemonic", + "--folder", + my_folder_path, + "--mnemonic-password", + "TREZOR", ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 5 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) def test_existing_mnemonic_eth1_address_withdrawal() -> None: # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) runner = CliRunner() - eth1_withdrawal_address = '0x00000000219ab540356cBB839Cbe05303d7705Fa' + eth1_withdrawal_address = "0x00000000219ab540356cBB839Cbe05303d7705Fa" inputs = [ - 'TREZOR', + "TREZOR", eth1_withdrawal_address, - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', - '2', '2', '5', 'mainnet', 'MyPassword', 'MyPassword'] - data = '\n'.join(inputs) + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "2", + "2", + "5", + "mainnet", + "MyPassword", + "MyPassword", + ] + data = "\n".join(inputs) arguments = [ - '--language', 'english', - 'existing-mnemonic', - '--folder', my_folder_path, - '--mnemonic-password', 'TREZOR', - '--eth1_withdrawal_address', eth1_withdrawal_address, + "--language", + "english", + "existing-mnemonic", + "--folder", + my_folder_path, + "--mnemonic-password", + "TREZOR", + "--eth1_withdrawal_address", + eth1_withdrawal_address, ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) - deposit_file = [key_file for key_file in key_files if key_file.startswith('deposit_data')][0] - with open(validator_keys_folder_path + '/' + deposit_file, 'r') as f: + deposit_file = [ + key_file for key_file in key_files if key_file.startswith("deposit_data") + ][0] + with open(validator_keys_folder_path + "/" + deposit_file, "r") as f: deposits_dict = json.load(f) for deposit in deposits_dict: - withdrawal_credentials = bytes.fromhex(deposit['withdrawal_credentials']) + withdrawal_credentials = bytes.fromhex(deposit["withdrawal_credentials"]) assert withdrawal_credentials == ( - ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + decode_hex(eth1_withdrawal_address) + ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b"\x00" * 11 + + decode_hex(eth1_withdrawal_address) ) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 5 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) def test_existing_mnemonic_eth1_address_withdrawal_bad_checksum() -> None: # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) @@ -118,67 +148,83 @@ def test_existing_mnemonic_eth1_address_withdrawal_bad_checksum() -> None: runner = CliRunner() # NOTE: final 'A' needed to be an 'a' - wrong_eth1_withdrawal_address = '0x00000000219ab540356cBB839Cbe05303d7705FA' - correct_eth1_withdrawal_address = '0x00000000219ab540356cBB839Cbe05303d7705Fa' + wrong_eth1_withdrawal_address = "0x00000000219ab540356cBB839Cbe05303d7705FA" + correct_eth1_withdrawal_address = "0x00000000219ab540356cBB839Cbe05303d7705Fa" inputs = [ - 'TREZOR', - correct_eth1_withdrawal_address, correct_eth1_withdrawal_address, - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', - '2', '2', '5', 'mainnet', 'MyPassword', 'MyPassword' + "TREZOR", + correct_eth1_withdrawal_address, + correct_eth1_withdrawal_address, + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "2", + "2", + "5", + "mainnet", + "MyPassword", + "MyPassword", ] - data = '\n'.join(inputs) + data = "\n".join(inputs) arguments = [ - '--language', 'english', - 'existing-mnemonic', - '--folder', my_folder_path, - '--mnemonic-password', 'TREZOR', - '--eth1_withdrawal_address', wrong_eth1_withdrawal_address, + "--language", + "english", + "existing-mnemonic", + "--folder", + my_folder_path, + "--mnemonic-password", + "TREZOR", + "--eth1_withdrawal_address", + wrong_eth1_withdrawal_address, ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) - deposit_file = [key_file for key_file in key_files if key_file.startswith('deposit_data')][0] - with open(validator_keys_folder_path + '/' + deposit_file, 'r') as f: + deposit_file = [ + key_file for key_file in key_files if key_file.startswith("deposit_data") + ][0] + with open(validator_keys_folder_path + "/" + deposit_file, "r") as f: deposits_dict = json.load(f) for deposit in deposits_dict: - withdrawal_credentials = bytes.fromhex(deposit['withdrawal_credentials']) + withdrawal_credentials = bytes.fromhex(deposit["withdrawal_credentials"]) assert withdrawal_credentials == ( - ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + decode_hex(correct_eth1_withdrawal_address) + ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b"\x00" * 11 + + decode_hex(correct_eth1_withdrawal_address) ) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 5 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) @pytest.mark.asyncio async def test_script() -> None: - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) - if os.name == 'nt': # Windows - run_script_cmd = 'sh deposit.sh' + if os.name == "nt": # Windows + run_script_cmd = "sh deposit.sh" else: # Mac or Linux - run_script_cmd = './deposit.sh' + run_script_cmd = "./deposit.sh" - install_cmd = run_script_cmd + ' install' + install_cmd = run_script_cmd + " install" proc = await asyncio.create_subprocess_shell( install_cmd, ) @@ -186,29 +232,38 @@ async def test_script() -> None: cmd_args = [ run_script_cmd, - '--language', 'english', - '--non_interactive', - 'existing-mnemonic', - '--num_validators', '1', + "--language", + "english", + "--non_interactive", + "existing-mnemonic", + "--num_validators", + "1", '--mnemonic="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"', - '--mnemonic-password', 'TREZOR', - '--validator_start_index', '1', - '--chain', 'mainnet', - '--keystore_password', 'MyPassword', - '--folder', my_folder_path, + "--mnemonic-password", + "TREZOR", + "--validator_start_index", + "1", + "--chain", + "mainnet", + "--keystore_password", + "MyPassword", + "--folder", + my_folder_path, ] proc = await asyncio.create_subprocess_shell( - ' '.join(cmd_args), + " ".join(cmd_args), ) await proc.wait() # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) @@ -216,16 +271,16 @@ async def test_script() -> None: @pytest.mark.asyncio async def test_script_abbreviated_mnemonic() -> None: - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) - if os.name == 'nt': # Windows - run_script_cmd = 'sh deposit.sh' + if os.name == "nt": # Windows + run_script_cmd = "sh deposit.sh" else: # Mac or Linux - run_script_cmd = './deposit.sh' + run_script_cmd = "./deposit.sh" - install_cmd = run_script_cmd + ' install' + install_cmd = run_script_cmd + " install" proc = await asyncio.create_subprocess_shell( install_cmd, ) @@ -233,29 +288,38 @@ async def test_script_abbreviated_mnemonic() -> None: cmd_args = [ run_script_cmd, - '--language', 'english', - '--non_interactive', - 'existing-mnemonic', - '--num_validators', '1', + "--language", + "english", + "--non_interactive", + "existing-mnemonic", + "--num_validators", + "1", '--mnemonic="aban aban aban aban aban aban aban aban aban aban aban abou"', - '--mnemonic-password', 'TREZOR', - '--validator_start_index', '1', - '--chain', 'mainnet', - '--keystore_password', 'MyPassword', - '--folder', my_folder_path, + "--mnemonic-password", + "TREZOR", + "--validator_start_index", + "1", + "--chain", + "mainnet", + "--keystore_password", + "MyPassword", + "--folder", + my_folder_path, ] proc = await asyncio.create_subprocess_shell( - ' '.join(cmd_args), + " ".join(cmd_args), ) await proc.wait() # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) diff --git a/tests/test_cli/test_generate_bls_to_execution_change.py b/tests/test_cli/test_generate_bls_to_execution_change.py index bdcb22cf..6939c307 100644 --- a/tests/test_cli/test_generate_bls_to_execution_change.py +++ b/tests/test_cli/test_generate_bls_to_execution_change.py @@ -4,11 +4,8 @@ from staking_deposit.deposit import cli from staking_deposit.utils.constants import DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME -from .helpers import ( - clean_btec_folder, - prepare_testing_folder, - verify_file_permission, -) + +from .helpers import clean_btec_folder, prepare_testing_folder, verify_file_permission def test_existing_mnemonic_bls_withdrawal() -> None: @@ -17,31 +14,43 @@ def test_existing_mnemonic_bls_withdrawal() -> None: runner = CliRunner() inputs = [] - data = '\n'.join(inputs) + data = "\n".join(inputs) arguments = [ - '--language', 'english', - '--non_interactive', - 'generate-bls-to-execution-change', - '--bls_to_execution_changes_folder', my_folder_path, - '--chain', 'mainnet', - '--mnemonic', 'sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry', # noqa: E501 - '--bls_withdrawal_credentials_list', '0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de', - '--validator_start_index', '0', - '--validator_indices', '1', - '--execution_address', '0x3434343434343434343434343434343434343434', + "--language", + "english", + "--non_interactive", + "generate-bls-to-execution-change", + "--bls_to_execution_changes_folder", + my_folder_path, + "--chain", + "mainnet", + "--mnemonic", + "sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry", # noqa: E501 + "--bls_withdrawal_credentials_list", + "0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de", + "--validator_start_index", + "0", + "--validator_indices", + "1", + "--execution_address", + "0x3434343434343434343434343434343434343434", ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - bls_to_execution_changes_folder_path = os.path.join(my_folder_path, DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME) + bls_to_execution_changes_folder_path = os.path.join( + my_folder_path, DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME + ) _, _, btec_files = next(os.walk(bls_to_execution_changes_folder_path)) # TODO verify file content assert len(set(btec_files)) == 1 # Verify file permissions - verify_file_permission(os, folder_path=bls_to_execution_changes_folder_path, files=btec_files) + verify_file_permission( + os, folder_path=bls_to_execution_changes_folder_path, files=btec_files + ) # Clean up clean_btec_folder(my_folder_path) @@ -53,33 +62,38 @@ def test_existing_mnemonic_bls_withdrawal_interactive() -> None: runner = CliRunner() inputs = [ - 'mainnet', # network/chain - 'sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry', # noqa: E501 - '0', # validator_start_index - '1', # validator_index - '0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de', - '0x3434343434343434343434343434343434343434', - '0x3434343434343434343434343434343434343434', - + "mainnet", # network/chain + "sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry", # noqa: E501 + "0", # validator_start_index + "1", # validator_index + "0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de", + "0x3434343434343434343434343434343434343434", + "0x3434343434343434343434343434343434343434", ] - data = '\n'.join(inputs) + data = "\n".join(inputs) arguments = [ - '--language', 'english', - 'generate-bls-to-execution-change', - '--bls_to_execution_changes_folder', my_folder_path, + "--language", + "english", + "generate-bls-to-execution-change", + "--bls_to_execution_changes_folder", + my_folder_path, ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - bls_to_execution_changes_folder_path = os.path.join(my_folder_path, DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME) + bls_to_execution_changes_folder_path = os.path.join( + my_folder_path, DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME + ) _, _, btec_files = next(os.walk(bls_to_execution_changes_folder_path)) # TODO verify file content assert len(set(btec_files)) == 1 # Verify file permissions - verify_file_permission(os, folder_path=bls_to_execution_changes_folder_path, files=btec_files) + verify_file_permission( + os, folder_path=bls_to_execution_changes_folder_path, files=btec_files + ) # Clean up clean_btec_folder(my_folder_path) @@ -91,31 +105,43 @@ def test_existing_mnemonic_bls_withdrawal_multiple() -> None: runner = CliRunner() inputs = [] - data = '\n'.join(inputs) + data = "\n".join(inputs) arguments = [ - '--language', 'english', - '--non_interactive', - 'generate-bls-to-execution-change', - '--bls_to_execution_changes_folder', my_folder_path, - '--chain', 'mainnet', - '--mnemonic', 'sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry', # noqa: E501 - '--bls_withdrawal_credentials_list', '0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de, 0x00a75d83f169fa6923f3dd78386d9608fab710d8f7fcf71ba9985893675d5382', # noqa: E501 - '--validator_start_index', '0', - '--validator_indices', '1,2', - '--execution_address', '0x3434343434343434343434343434343434343434', + "--language", + "english", + "--non_interactive", + "generate-bls-to-execution-change", + "--bls_to_execution_changes_folder", + my_folder_path, + "--chain", + "mainnet", + "--mnemonic", + "sister protect peanut hill ready work profit fit wish want small inflict flip member tail between sick setup bright duck morning sell paper worry", # noqa: E501 + "--bls_withdrawal_credentials_list", + "0x00bd0b5a34de5fb17df08410b5e615dda87caf4fb72d0aac91ce5e52fc6aa8de, 0x00a75d83f169fa6923f3dd78386d9608fab710d8f7fcf71ba9985893675d5382", # noqa: E501 + "--validator_start_index", + "0", + "--validator_indices", + "1,2", + "--execution_address", + "0x3434343434343434343434343434343434343434", ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - bls_to_execution_changes_folder_path = os.path.join(my_folder_path, DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME) + bls_to_execution_changes_folder_path = os.path.join( + my_folder_path, DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME + ) _, _, btec_files = next(os.walk(bls_to_execution_changes_folder_path)) # TODO verify file content assert len(set(btec_files)) == 1 # Verify file permissions - verify_file_permission(os, folder_path=bls_to_execution_changes_folder_path, files=btec_files) + verify_file_permission( + os, folder_path=bls_to_execution_changes_folder_path, files=btec_files + ) # Clean up clean_btec_folder(my_folder_path) diff --git a/tests/test_cli/test_new_mnemonic.py b/tests/test_cli/test_new_mnemonic.py index 0e091dd3..d406f272 100644 --- a/tests/test_cli/test_new_mnemonic.py +++ b/tests/test_cli/test_new_mnemonic.py @@ -4,7 +4,6 @@ import pytest from click.testing import CliRunner - from eth_utils import decode_hex from staking_deposit.cli import new_mnemonic @@ -16,6 +15,7 @@ ETH1_ADDRESS_WITHDRAWAL_PREFIX, ) from staking_deposit.utils.intl import load_text + from .helpers import clean_key_folder, get_permissions, get_uuid @@ -27,33 +27,44 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) runner = CliRunner() - inputs = ['english', 'english', '1', 'mainnet', 'MyPassword', 'MyPassword', - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'] - data = '\n'.join(inputs) - result = runner.invoke(cli, ['new-mnemonic', '--folder', my_folder_path], input=data) + inputs = [ + "english", + "english", + "1", + "mainnet", + "MyPassword", + "MyPassword", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + ] + data = "\n".join(inputs) + result = runner.invoke( + cli, ["new-mnemonic", "--folder", my_folder_path], input=data + ) assert result.exit_code == 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 1 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) @@ -67,49 +78,65 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) runner = CliRunner() - eth1_withdrawal_address = '0x00000000219ab540356cBB839Cbe05303d7705Fa' - inputs = [eth1_withdrawal_address, 'english', '1', 'mainnet', 'MyPassword', 'MyPassword', - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'] - data = '\n'.join(inputs) + eth1_withdrawal_address = "0x00000000219ab540356cBB839Cbe05303d7705Fa" + inputs = [ + eth1_withdrawal_address, + "english", + "1", + "mainnet", + "MyPassword", + "MyPassword", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + ] + data = "\n".join(inputs) arguments = [ - '--language', 'english', - 'new-mnemonic', - '--folder', my_folder_path, - '--eth1_withdrawal_address', eth1_withdrawal_address, + "--language", + "english", + "new-mnemonic", + "--folder", + my_folder_path, + "--eth1_withdrawal_address", + eth1_withdrawal_address, ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) - deposit_file = [key_file for key_file in key_files if key_file.startswith('deposit_data')][0] - with open(validator_keys_folder_path + '/' + deposit_file, 'r') as f: + deposit_file = [ + key_file for key_file in key_files if key_file.startswith("deposit_data") + ][0] + with open(validator_keys_folder_path + "/" + deposit_file, "r") as f: deposits_dict = json.load(f) for deposit in deposits_dict: - withdrawal_credentials = bytes.fromhex(deposit['withdrawal_credentials']) + withdrawal_credentials = bytes.fromhex(deposit["withdrawal_credentials"]) assert withdrawal_credentials == ( - ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + decode_hex(eth1_withdrawal_address) + ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b"\x00" * 11 + + decode_hex(eth1_withdrawal_address) ) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 1 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) @@ -123,7 +150,7 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) @@ -131,46 +158,62 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: runner = CliRunner() # NOTE: final 'A' needed to be an 'a' - wrong_eth1_withdrawal_address = '0x00000000219ab540356cBB839Cbe05303d7705FA' - correct_eth1_withdrawal_address = '0x00000000219ab540356cBB839Cbe05303d7705Fa' - - inputs = [correct_eth1_withdrawal_address, correct_eth1_withdrawal_address, - 'english', '1', 'mainnet', 'MyPassword', 'MyPassword', - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'] - data = '\n'.join(inputs) + wrong_eth1_withdrawal_address = "0x00000000219ab540356cBB839Cbe05303d7705FA" + correct_eth1_withdrawal_address = "0x00000000219ab540356cBB839Cbe05303d7705Fa" + + inputs = [ + correct_eth1_withdrawal_address, + correct_eth1_withdrawal_address, + "english", + "1", + "mainnet", + "MyPassword", + "MyPassword", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + ] + data = "\n".join(inputs) arguments = [ - '--language', 'english', - 'new-mnemonic', - '--folder', my_folder_path, - '--eth1_withdrawal_address', wrong_eth1_withdrawal_address, + "--language", + "english", + "new-mnemonic", + "--folder", + my_folder_path, + "--eth1_withdrawal_address", + wrong_eth1_withdrawal_address, ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) - deposit_file = [key_file for key_file in key_files if key_file.startswith('deposit_data')][0] - with open(validator_keys_folder_path + '/' + deposit_file, 'r') as f: + deposit_file = [ + key_file for key_file in key_files if key_file.startswith("deposit_data") + ][0] + with open(validator_keys_folder_path + "/" + deposit_file, "r") as f: deposits_dict = json.load(f) for deposit in deposits_dict: - withdrawal_credentials = bytes.fromhex(deposit['withdrawal_credentials']) + withdrawal_credentials = bytes.fromhex(deposit["withdrawal_credentials"]) assert withdrawal_credentials == ( - ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + decode_hex(correct_eth1_withdrawal_address) + ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b"\x00" * 11 + + decode_hex(correct_eth1_withdrawal_address) ) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 1 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) @@ -184,49 +227,66 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) runner = CliRunner() - execution_address = '0x00000000219ab540356cBB839Cbe05303d7705Fa' - inputs = [execution_address, 'english', '1', 'mainnet', 'MyPassword', 'MyPassword', - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'] - data = '\n'.join(inputs) + execution_address = "0x00000000219ab540356cBB839Cbe05303d7705Fa" + inputs = [ + execution_address, + "english", + "1", + "mainnet", + "MyPassword", + "MyPassword", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + ] + data = "\n".join(inputs) arguments = [ - '--language', 'english', - 'new-mnemonic', - '--folder', my_folder_path, - '--execution_address', execution_address, # execution_address and eth1_withdrawal_address are aliases + "--language", + "english", + "new-mnemonic", + "--folder", + my_folder_path, + # execution_address and eth1_withdrawal_address are aliases + "--execution_address", + execution_address, ] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) - deposit_file = [key_file for key_file in key_files if key_file.startswith('deposit_data')][0] - with open(validator_keys_folder_path + '/' + deposit_file, 'r') as f: + deposit_file = [ + key_file for key_file in key_files if key_file.startswith("deposit_data") + ][0] + with open(validator_keys_folder_path + "/" + deposit_file, "r") as f: deposits_dict = json.load(f) for deposit in deposits_dict: - withdrawal_credentials = bytes.fromhex(deposit['withdrawal_credentials']) + withdrawal_credentials = bytes.fromhex(deposit["withdrawal_credentials"]) assert withdrawal_credentials == ( - ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + decode_hex(execution_address) + ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b"\x00" * 11 + + decode_hex(execution_address) ) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 1 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) @@ -240,22 +300,33 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) runner = CliRunner() - execution_address = '0x00000000219ab540356cBB839Cbe05303d7705Fa' - inputs = [execution_address, 'english', '1', 'mainnet', 'MyPassword', 'MyPassword', - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'] - data = '\n'.join(inputs) + execution_address = "0x00000000219ab540356cBB839Cbe05303d7705Fa" + inputs = [ + execution_address, + "english", + "1", + "mainnet", + "MyPassword", + "MyPassword", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + ] + data = "\n".join(inputs) arguments = [ - '--language', 'english', - 'new-mnemonic', - '--folder', my_folder_path, - '--execution_address', execution_address, - '--eth1_withdrawal_address', execution_address, # double param + "--language", + "english", + "new-mnemonic", + "--folder", + my_folder_path, + "--execution_address", + execution_address, + "--eth1_withdrawal_address", + execution_address, # double param ] result = runner.invoke(cli, arguments, input=data) @@ -266,17 +337,17 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: @pytest.mark.asyncio async def test_script_bls_withdrawal() -> None: # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) - if os.name == 'nt': # Windows - run_script_cmd = 'sh deposit.sh' + if os.name == "nt": # Windows + run_script_cmd = "sh deposit.sh" else: # Mac or Linux - run_script_cmd = './deposit.sh' + run_script_cmd = "./deposit.sh" - install_cmd = run_script_cmd + ' install' + install_cmd = run_script_cmd + " install" proc = await asyncio.create_subprocess_shell( install_cmd, ) @@ -284,64 +355,82 @@ async def test_script_bls_withdrawal() -> None: cmd_args = [ run_script_cmd, - '--language', 'english', - '--non_interactive', - 'new-mnemonic', - '--num_validators', '5', - '--mnemonic_language', 'english', - '--chain', 'mainnet', - '--keystore_password', 'MyPassword', - '--folder', my_folder_path, + "--language", + "english", + "--non_interactive", + "new-mnemonic", + "--num_validators", + "5", + "--mnemonic_language", + "english", + "--chain", + "mainnet", + "--keystore_password", + "MyPassword", + "--folder", + my_folder_path, ] proc = await asyncio.create_subprocess_shell( - ' '.join(cmd_args), + " ".join(cmd_args), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, ) - seed_phrase = '' + seed_phrase = "" parsing = False - mnemonic_json_file = os.path.join(os.getcwd(), 'staking_deposit/../staking_deposit/cli/', 'new_mnemonic.json') + mnemonic_json_file = os.path.join( + os.getcwd(), "staking_deposit/../staking_deposit/cli/", "new_mnemonic.json" + ) async for out in proc.stdout: - output = out.decode('utf-8').rstrip() - if output.startswith(load_text(['msg_mnemonic_presentation'], mnemonic_json_file, 'new_mnemonic')): + output = out.decode("utf-8").rstrip() + if output.startswith( + load_text(["msg_mnemonic_presentation"], mnemonic_json_file, "new_mnemonic") + ): parsing = True - elif output.startswith(load_text(['msg_mnemonic_retype_prompt'], mnemonic_json_file, 'new_mnemonic')): + elif output.startswith( + load_text( + ["msg_mnemonic_retype_prompt"], mnemonic_json_file, "new_mnemonic" + ) + ): parsing = False elif parsing: seed_phrase += output if len(seed_phrase) > 0: encoded_phrase = seed_phrase.encode() proc.stdin.write(encoded_phrase) - proc.stdin.write(b'\n') + proc.stdin.write(b"\n") assert len(seed_phrase) > 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) - deposit_file = [key_file for key_file in key_files if key_file.startswith('deposit_data')][0] - with open(validator_keys_folder_path + '/' + deposit_file, 'r') as f: + deposit_file = [ + key_file for key_file in key_files if key_file.startswith("deposit_data") + ][0] + with open(validator_keys_folder_path + "/" + deposit_file, "r") as f: deposits_dict = json.load(f) for deposit in deposits_dict: - withdrawal_credentials = bytes.fromhex(deposit['withdrawal_credentials']) - print('withdrawal_credentials', withdrawal_credentials) + withdrawal_credentials = bytes.fromhex(deposit["withdrawal_credentials"]) + print("withdrawal_credentials", withdrawal_credentials) assert withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX _, _, key_files = next(os.walk(validator_keys_folder_path)) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 5 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) @@ -350,17 +439,17 @@ async def test_script_bls_withdrawal() -> None: @pytest.mark.asyncio async def test_script_abbreviated_mnemonic() -> None: # Prepare folder - my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + my_folder_path = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER") clean_key_folder(my_folder_path) if not os.path.exists(my_folder_path): os.mkdir(my_folder_path) - if os.name == 'nt': # Windows - run_script_cmd = 'sh deposit.sh' + if os.name == "nt": # Windows + run_script_cmd = "sh deposit.sh" else: # Mac or Linux - run_script_cmd = './deposit.sh' + run_script_cmd = "./deposit.sh" - install_cmd = run_script_cmd + ' install' + install_cmd = run_script_cmd + " install" proc = await asyncio.create_subprocess_shell( install_cmd, ) @@ -368,55 +457,73 @@ async def test_script_abbreviated_mnemonic() -> None: cmd_args = [ run_script_cmd, - '--language', 'english', - '--non_interactive', - 'new-mnemonic', - '--num_validators', '5', - '--mnemonic_language', 'english', - '--chain', 'mainnet', - '--keystore_password', 'MyPassword', - '--folder', my_folder_path, + "--language", + "english", + "--non_interactive", + "new-mnemonic", + "--num_validators", + "5", + "--mnemonic_language", + "english", + "--chain", + "mainnet", + "--keystore_password", + "MyPassword", + "--folder", + my_folder_path, ] proc = await asyncio.create_subprocess_shell( - ' '.join(cmd_args), + " ".join(cmd_args), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, ) - seed_phrase = '' + seed_phrase = "" parsing = False - mnemonic_json_file = os.path.join(os.getcwd(), 'staking_deposit/../staking_deposit/cli/', 'new_mnemonic.json') + mnemonic_json_file = os.path.join( + os.getcwd(), "staking_deposit/../staking_deposit/cli/", "new_mnemonic.json" + ) async for out in proc.stdout: - output = out.decode('utf-8').rstrip() - if output.startswith(load_text(['msg_mnemonic_presentation'], mnemonic_json_file, 'new_mnemonic')): + output = out.decode("utf-8").rstrip() + if output.startswith( + load_text(["msg_mnemonic_presentation"], mnemonic_json_file, "new_mnemonic") + ): parsing = True - elif output.startswith(load_text(['msg_mnemonic_retype_prompt'], mnemonic_json_file, 'new_mnemonic')): + elif output.startswith( + load_text( + ["msg_mnemonic_retype_prompt"], mnemonic_json_file, "new_mnemonic" + ) + ): parsing = False elif parsing: seed_phrase += output if len(seed_phrase) > 0: - abbreviated_mnemonic = ' '.join(abbreviate_words(seed_phrase.split(' '))) + abbreviated_mnemonic = " ".join( + abbreviate_words(seed_phrase.split(" ")) + ) encoded_phrase = abbreviated_mnemonic.encode() proc.stdin.write(encoded_phrase) - proc.stdin.write(b'\n') + proc.stdin.write(b"\n") assert len(seed_phrase) > 0 # Check files - validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path = os.path.join( + my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, key_files = next(os.walk(validator_keys_folder_path)) all_uuid = [ - get_uuid(validator_keys_folder_path + '/' + key_file) + get_uuid(validator_keys_folder_path + "/" + key_file) for key_file in key_files - if key_file.startswith('keystore') + if key_file.startswith("keystore") ] assert len(set(all_uuid)) == 5 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in key_files: - assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path, file_name) == "0o440" # Clean up clean_key_folder(my_folder_path) diff --git a/tests/test_cli/test_regeneration.py b/tests/test_cli/test_regeneration.py index 77687583..a16dba09 100644 --- a/tests/test_cli/test_regeneration.py +++ b/tests/test_cli/test_regeneration.py @@ -3,9 +3,11 @@ from pathlib import Path from click.testing import CliRunner + from staking_deposit.cli import new_mnemonic from staking_deposit.deposit import cli from staking_deposit.utils.constants import DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + from .helpers import clean_key_folder, get_permissions, get_uuid @@ -13,7 +15,9 @@ def test_regeneration(monkeypatch) -> None: # Part 1: new-mnemonic # monkeypatch get_mnemonic - mock_mnemonic = "legal winner thank year wave sausage worth useful legal winner thank yellow" + mock_mnemonic = ( + "legal winner thank year wave sausage worth useful legal winner thank yellow" + ) def mock_get_mnemonic(language, words_path, entropy=None) -> str: return mock_mnemonic @@ -21,8 +25,8 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) # Prepare folder - folder_path_1 = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER_1') - folder_path_2 = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER_2') + folder_path_1 = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER_1") + folder_path_2 = os.path.join(os.getcwd(), "TESTING_TEMP_FOLDER_2") clean_key_folder(folder_path_1) clean_key_folder(folder_path_2) if not os.path.exists(folder_path_1): @@ -33,61 +37,87 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: runner = CliRunner() # Create index 0 and 1 my_password = "MyPassword" - inputs = ['english', 'english', '2', 'mainnet', my_password, my_password, mock_mnemonic] - data = '\n'.join(inputs) - result = runner.invoke(cli, ['new-mnemonic', '--folder', folder_path_1], input=data) + inputs = [ + "english", + "english", + "2", + "mainnet", + my_password, + my_password, + mock_mnemonic, + ] + data = "\n".join(inputs) + result = runner.invoke(cli, ["new-mnemonic", "--folder", folder_path_1], input=data) assert result.exit_code == 0 # Check files - validator_keys_folder_path_1 = os.path.join(folder_path_1, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path_1 = os.path.join( + folder_path_1, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, files = next(os.walk(validator_keys_folder_path_1)) - part_1_key_files = sorted([key_file for key_file in files if key_file.startswith('keystore')]) - - all_uuid = [get_uuid(validator_keys_folder_path_1 + '/' + key_file) - for key_file in part_1_key_files] + part_1_key_files = sorted( + [key_file for key_file in files if key_file.startswith("keystore")] + ) + + all_uuid = [ + get_uuid(validator_keys_folder_path_1 + "/" + key_file) + for key_file in part_1_key_files + ] assert len(set(all_uuid)) == 2 # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in part_1_key_files: - assert get_permissions(validator_keys_folder_path_1, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path_1, file_name) == "0o440" # Part 2: existing-mnemonic runner = CliRunner() # Create index 1 and 2 inputs = [ - 'english', + "english", mock_mnemonic, - '1', '1', '2', 'mainnet', 'MyPassword', 'MyPassword'] - data = '\n'.join(inputs) - arguments = ['existing-mnemonic', '--folder', folder_path_2] + "1", + "1", + "2", + "mainnet", + "MyPassword", + "MyPassword", + ] + data = "\n".join(inputs) + arguments = ["existing-mnemonic", "--folder", folder_path_2] result = runner.invoke(cli, arguments, input=data) assert result.exit_code == 0 # Check files - validator_keys_folder_path_2 = os.path.join(folder_path_2, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + validator_keys_folder_path_2 = os.path.join( + folder_path_2, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME + ) _, _, files = next(os.walk(validator_keys_folder_path_2)) - part_2_key_files = sorted([key_file for key_file in files if key_file.startswith('keystore')]) - - all_uuid = [get_uuid(validator_keys_folder_path_2 + '/' + key_file) - for key_file in part_2_key_files] + part_2_key_files = sorted( + [key_file for key_file in files if key_file.startswith("keystore")] + ) + + all_uuid = [ + get_uuid(validator_keys_folder_path_2 + "/" + key_file) + for key_file in part_2_key_files + ] assert len(set(all_uuid)) == 2 # Finally: # Check the index=1 files have the same pubkey - assert '1_0_0' in part_1_key_files[1] and '1_0_0' in part_2_key_files[0] - with open(Path(validator_keys_folder_path_1 + '/' + part_1_key_files[1])) as f: + assert "1_0_0" in part_1_key_files[1] and "1_0_0" in part_2_key_files[0] + with open(Path(validator_keys_folder_path_1 + "/" + part_1_key_files[1])) as f: keystore_1_1 = json.load(f) - with open(Path(validator_keys_folder_path_2 + '/' + part_2_key_files[0])) as f: + with open(Path(validator_keys_folder_path_2 + "/" + part_2_key_files[0])) as f: keystore_2_0 = json.load(f) - assert keystore_1_1['pubkey'] == keystore_2_0['pubkey'] - assert keystore_1_1['path'] == keystore_2_0['path'] + assert keystore_1_1["pubkey"] == keystore_2_0["pubkey"] + assert keystore_1_1["path"] == keystore_2_0["path"] # Verify file permissions - if os.name == 'posix': + if os.name == "posix": for file_name in part_2_key_files: - assert get_permissions(validator_keys_folder_path_2, file_name) == '0o440' + assert get_permissions(validator_keys_folder_path_2, file_name) == "0o440" # Clean up clean_key_folder(folder_path_1) diff --git a/tests/test_intl/test_json_schema.py b/tests/test_intl/test_json_schema.py index d6710c16..0297cb31 100644 --- a/tests/test_intl/test_json_schema.py +++ b/tests/test_intl/test_json_schema.py @@ -1,16 +1,14 @@ import json -import jsonschema import os -import pytest import re -from typing import ( - List, -) +from typing import List -from staking_deposit.utils.constants import INTL_CONTENT_PATH +import jsonschema +import pytest +from staking_deposit.utils.constants import INTL_CONTENT_PATH -TEST_SCHEMAS_FOLDER = os.path.join(os.path.dirname(__file__), 'schemas') +TEST_SCHEMAS_FOLDER = os.path.join(os.path.dirname(__file__), "schemas") def files_to_check(root_dir: str) -> List[str]: @@ -25,17 +23,17 @@ def files_to_check(root_dir: str) -> List[str]: def languages_to_check(root_dir: str) -> List[str]: dirs = next(os.walk(root_dir))[1] - regex = re.compile('([A-Za-z]){2}(-([A-Za-z]){2})?') + regex = re.compile("([A-Za-z]){2}(-([A-Za-z]){2})?") return [d for d in dirs if re.fullmatch(regex, d)] @pytest.mark.parametrize( - 'lang, schema_path', + "lang, schema_path", [ (lang, schema) for schema in files_to_check(TEST_SCHEMAS_FOLDER) for lang in languages_to_check(INTL_CONTENT_PATH) - ] + ], ) def test_language_schemas(lang: str, schema_path: str) -> None: with open(os.path.join(TEST_SCHEMAS_FOLDER, schema_path)) as schema_file: diff --git a/tests/test_key_handling/test_key_derivation/test_mnemonic.py b/tests/test_key_handling/test_key_derivation/test_mnemonic.py index 9d1ae9d0..94164115 100644 --- a/tests/test_key_handling/test_key_derivation/test_mnemonic.py +++ b/tests/test_key_handling/test_key_derivation/test_mnemonic.py @@ -1,50 +1,60 @@ +import json import os +from typing import Sequence + import pytest -import json -from typing import ( - Sequence, -) -from staking_deposit.utils.constants import ( - MNEMONIC_LANG_OPTIONS, -) from staking_deposit.key_handling.key_derivation.mnemonic import ( - _index_to_word, _get_word_list, + _index_to_word, abbreviate_words, - get_seed, get_mnemonic, + get_seed, reconstruct_mnemonic, ) +from staking_deposit.utils.constants import MNEMONIC_LANG_OPTIONS - -WORD_LISTS_PATH = os.path.join(os.getcwd(), 'staking_deposit', 'key_handling', 'key_derivation', 'word_lists') +WORD_LISTS_PATH = os.path.join( + os.getcwd(), "staking_deposit", "key_handling", "key_derivation", "word_lists" +) all_languages = MNEMONIC_LANG_OPTIONS.keys() -test_vector_filefolder = os.path.join('tests', 'test_key_handling', - 'test_key_derivation', 'test_vectors', 'mnemonic.json') -with open(test_vector_filefolder, 'r', encoding='utf-8') as f: +test_vector_filefolder = os.path.join( + "tests", "test_key_handling", "test_key_derivation", "test_vectors", "mnemonic.json" +) +with open(test_vector_filefolder, "r", encoding="utf-8") as f: test_vectors = json.load(f) @pytest.mark.parametrize( - 'language,test', - [(language, test) for language, language_test_vectors in test_vectors.items() for test in language_test_vectors] + "language,test", + [ + (language, test) + for language, language_test_vectors in test_vectors.items() + for test in language_test_vectors + ], ) def test_bip39(language: str, test: Sequence[str]) -> None: test_entropy = bytes.fromhex(test[0]) test_mnemonic = test[1] test_seed = bytes.fromhex(test[2]) - assert get_mnemonic(language=language, words_path=WORD_LISTS_PATH, entropy=test_entropy) == test_mnemonic - assert get_seed(mnemonic=test_mnemonic, password='TREZOR') == test_seed + assert ( + get_mnemonic( + language=language, words_path=WORD_LISTS_PATH, entropy=test_entropy + ) + == test_mnemonic + ) + assert get_seed(mnemonic=test_mnemonic, password="TREZOR") == test_seed @pytest.mark.parametrize( - 'test_mnemonic', - [(test_mnemonic[1]) - for _, language_test_vectors in test_vectors.items() - for test_mnemonic in language_test_vectors] + "test_mnemonic", + [ + (test_mnemonic[1]) + for _, language_test_vectors in test_vectors.items() + for test_mnemonic in language_test_vectors + ], ) def test_reconstruct_mnemonic(test_mnemonic: str) -> None: assert reconstruct_mnemonic(test_mnemonic, WORD_LISTS_PATH) is not None @@ -54,29 +64,29 @@ def abbreviate_mnemonic(mnemonic: str) -> str: words = str.split(mnemonic) words = abbreviate_words(words) assert all([len(word) <= 4 for word in words]) - return str.join(' ', words) + return str.join(" ", words) @pytest.mark.parametrize( - 'test_mnemonic', - [abbreviate_mnemonic(test_mnemonic[1]) - for _, language_test_vectors in test_vectors.items() - for test_mnemonic in language_test_vectors] + "test_mnemonic", + [ + abbreviate_mnemonic(test_mnemonic[1]) + for _, language_test_vectors in test_vectors.items() + for test_mnemonic in language_test_vectors + ], ) def test_reconstruct_abbreviated_mnemonic(test_mnemonic: str) -> None: assert reconstruct_mnemonic(test_mnemonic, WORD_LISTS_PATH) is not None +@pytest.mark.parametrize("language", ["english"]) @pytest.mark.parametrize( - 'language', ['english'] -) -@pytest.mark.parametrize( - 'index, valid', + "index, valid", [ (0, True), (2047, True), (2048, False), - ] + ], ) def test_get_word(language: str, index: int, valid: bool) -> None: word_list = _get_word_list(language, WORD_LISTS_PATH) diff --git a/tests/test_key_handling/test_key_derivation/test_path.py b/tests/test_key_handling/test_key_derivation/test_path.py index 87e001ac..760fbae9 100644 --- a/tests/test_key_handling/test_key_derivation/test_path.py +++ b/tests/test_key_handling/test_key_derivation/test_path.py @@ -1,91 +1,88 @@ -import os import json +import os + import pytest +from staking_deposit.key_handling.key_derivation.path import ( + mnemonic_and_path_to_key, + path_to_nodes, +) from staking_deposit.key_handling.key_derivation.tree import ( _flip_bits_256, + _HKDF_mod_r, _IKM_to_lamport_SK, _parent_SK_to_lamport_PK, - _HKDF_mod_r, ) -from staking_deposit.key_handling.key_derivation.path import ( - mnemonic_and_path_to_key, - path_to_nodes, +test_vector_filefolder = os.path.join( + os.getcwd(), + "tests", + "test_key_handling", + "test_key_derivation", + "test_vectors", + "tree_kdf_intermediate.json", ) - -test_vector_filefolder = os.path.join(os.getcwd(), 'tests', 'test_key_handling', 'test_key_derivation', - 'test_vectors', 'tree_kdf_intermediate.json') -with open(test_vector_filefolder, 'r') as f: +with open(test_vector_filefolder, "r") as f: test_vector_dict = json.load(f) -@pytest.mark.parametrize( - 'test_vector', - [test_vector_dict] -) +@pytest.mark.parametrize("test_vector", [test_vector_dict]) def test_flip_bits_256(test_vector) -> None: - test_vector_int = int(test_vector['seed'][:64], 16) # 64 comes from string chars containing .5 bytes + # 64 comes from string chars containing .5 bytes + test_vector_int = int(test_vector["seed"][:64], 16) assert test_vector_int & _flip_bits_256(test_vector_int) == 0 -@pytest.mark.parametrize( - 'test_vector', - [test_vector_dict] -) +@pytest.mark.parametrize("test_vector", [test_vector_dict]) def test_IKM_to_lamport_SK(test_vector) -> None: - test_vector_lamport_0 = [bytes.fromhex(x) for x in test_vector['lamport_0']] - test_vector_lamport_1 = [bytes.fromhex(x) for x in test_vector['lamport_1']] - salt = test_vector['child_index'].to_bytes(4, 'big') - IKM = test_vector['master_SK'].to_bytes(32, 'big') + test_vector_lamport_0 = [bytes.fromhex(x) for x in test_vector["lamport_0"]] + test_vector_lamport_1 = [bytes.fromhex(x) for x in test_vector["lamport_1"]] + salt = test_vector["child_index"].to_bytes(4, "big") + IKM = test_vector["master_SK"].to_bytes(32, "big") lamport_0 = _IKM_to_lamport_SK(IKM=IKM, salt=salt) - not_IKM = _flip_bits_256(test_vector['master_SK']).to_bytes(32, 'big') + not_IKM = _flip_bits_256(test_vector["master_SK"]).to_bytes(32, "big") lamport_1 = _IKM_to_lamport_SK(IKM=not_IKM, salt=salt) assert test_vector_lamport_0 == lamport_0 assert test_vector_lamport_1 == lamport_1 -@pytest.mark.parametrize( - 'test_vector', - [test_vector_dict] -) +@pytest.mark.parametrize("test_vector", [test_vector_dict]) def test_parent_SK_to_lamport_PK(test_vector) -> None: - parent_SK = test_vector['master_SK'] - index = test_vector['child_index'] - lamport_PK = bytes.fromhex(test_vector['compressed_lamport_PK']) + parent_SK = test_vector["master_SK"] + index = test_vector["child_index"] + lamport_PK = bytes.fromhex(test_vector["compressed_lamport_PK"]) assert lamport_PK == _parent_SK_to_lamport_PK(parent_SK=parent_SK, index=index) -@pytest.mark.parametrize( - 'test_vector', - [test_vector_dict] -) +@pytest.mark.parametrize("test_vector", [test_vector_dict]) def test_HKDF_mod_r(test_vector) -> None: - test_0 = (bytes.fromhex(test_vector['seed']), test_vector['master_SK']) - test_1 = (bytes.fromhex(test_vector['compressed_lamport_PK']), test_vector['child_SK']) + test_0 = (bytes.fromhex(test_vector["seed"]), test_vector["master_SK"]) + test_1 = ( + bytes.fromhex(test_vector["compressed_lamport_PK"]), + test_vector["child_SK"], + ) for test in (test_0, test_1): assert _HKDF_mod_r(IKM=test[0]) == test[1] -@pytest.mark.parametrize( - 'test_vector', - [test_vector_dict] -) +@pytest.mark.parametrize("test_vector", [test_vector_dict]) def test_mnemonic_and_path_to_key(test_vector) -> None: - mnemonic = test_vector['mnemonic'] - password = test_vector['password'] - path = test_vector['path'] - key = test_vector['child_SK'] - assert mnemonic_and_path_to_key(mnemonic=mnemonic, path=path, password=password) == key + mnemonic = test_vector["mnemonic"] + password = test_vector["password"] + path = test_vector["path"] + key = test_vector["child_SK"] + assert ( + mnemonic_and_path_to_key(mnemonic=mnemonic, path=path, password=password) == key + ) @pytest.mark.parametrize( - 'path, valid', + "path, valid", [ ("m/12381/3600/0/0/0", True), ("x/12381/3600/0/0/0", False), ("m/qwert/3600/0/0/0", False), - ] + ], ) def test_path_to_nodes(path, valid): if valid: diff --git a/tests/test_key_handling/test_key_derivation/test_tree.py b/tests/test_key_handling/test_key_derivation/test_tree.py index 39511d07..c97308d0 100644 --- a/tests/test_key_handling/test_key_derivation/test_tree.py +++ b/tests/test_key_handling/test_key_derivation/test_tree.py @@ -1,8 +1,8 @@ -import os import json -from py_ecc.bls import G2ProofOfPossession as bls -import pytest +import os +import pytest +from py_ecc.bls import G2ProofOfPossession as bls from staking_deposit.key_handling.key_derivation.tree import ( _HKDF_mod_r, @@ -10,46 +10,38 @@ derive_master_SK, ) - -test_vector_filefolder = os.path.join(os.getcwd(), 'tests', 'test_key_handling', - 'test_key_derivation', 'test_vectors', 'tree_kdf.json') -with open(test_vector_filefolder, 'r') as f: - test_vectors = json.load(f)['kdf_tests'] +test_vector_filefolder = os.path.join( + os.getcwd(), + "tests", + "test_key_handling", + "test_key_derivation", + "test_vectors", + "tree_kdf.json", +) +with open(test_vector_filefolder, "r") as f: + test_vectors = json.load(f)["kdf_tests"] -@pytest.mark.parametrize( - 'test', - test_vectors -) +@pytest.mark.parametrize("test", test_vectors) def test_hkdf_mod_r(test) -> None: - seed = bytes.fromhex(test['seed']) + seed = bytes.fromhex(test["seed"]) assert bls.KeyGen(seed) == _HKDF_mod_r(IKM=seed) +@pytest.mark.parametrize("seed", [b"\x00" * 32]) @pytest.mark.parametrize( - 'seed', - [b'\x00' * 32] -) -@pytest.mark.parametrize( - 'key_info', - [b'\x00' * 32, b'\x01\x23\x45\x67\x89\xAB\xBC\xDE\xFF', b'\xFF' * 16] + "key_info", [b"\x00" * 32, b"\x01\x23\x45\x67\x89\xab\xbc\xde\xff", b"\xff" * 16] ) def test_hkdf_mod_r_key_info(seed: bytes, key_info: bytes) -> None: assert bls.KeyGen(seed, key_info) == _HKDF_mod_r(IKM=seed, key_info=key_info) -@pytest.mark.parametrize( - 'test', - test_vectors -) -@pytest.mark.parametrize( - 'is_valid_seed', - (True, False) -) +@pytest.mark.parametrize("test", test_vectors) +@pytest.mark.parametrize("is_valid_seed", (True, False)) def test_derive_master_SK(test, is_valid_seed) -> None: - master_SK = test['master_SK'] + master_SK = test["master_SK"] if is_valid_seed: - seed = bytes.fromhex(test['seed']) + seed = bytes.fromhex(test["seed"]) assert derive_master_SK(seed=seed) == master_SK else: seed = "\x12" * 31 @@ -57,19 +49,13 @@ def test_derive_master_SK(test, is_valid_seed) -> None: derive_master_SK(seed=seed) -@pytest.mark.parametrize( - 'test', - test_vectors -) -@pytest.mark.parametrize( - 'is_valid_index', - (True, False) -) +@pytest.mark.parametrize("test", test_vectors) +@pytest.mark.parametrize("is_valid_index", (True, False)) def test_derive_child_SK_valid(test, is_valid_index) -> None: - parent_SK = test['master_SK'] - child_SK = test['child_SK'] + parent_SK = test["master_SK"] + child_SK = test["child_SK"] if is_valid_index: - index = test['child_index'] + index = test["child_index"] assert derive_child_SK(parent_SK=parent_SK, index=index) == child_SK else: index = 2**32 diff --git a/tests/test_key_handling/test_keystore.py b/tests/test_key_handling/test_keystore.py index 696aa201..6843b5fe 100644 --- a/tests/test_key_handling/test_keystore.py +++ b/tests/test_key_handling/test_keystore.py @@ -1,19 +1,26 @@ -import os import json +import os + import pytest from staking_deposit.key_handling.keystore import ( Keystore, - ScryptKeystore, Pbkdf2Keystore, + ScryptKeystore, ) -test_vector_password = 'ð”ąð”Ēð”°ð”ąð”­ð”žð”°ð”°ð”ī𝔎ð”Ŋð”ĄðŸ”‘' -test_vector_secret = bytes.fromhex('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f') -test_vector_folder = os.path.join(os.getcwd(), 'tests', 'test_key_handling', 'keystore_test_vectors') +test_vector_password = "ð”ąð”Ēð”°ð”ąð”­ð”žð”°ð”°ð”ī𝔎ð”Ŋð”ĄðŸ”‘" +test_vector_secret = bytes.fromhex( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" +) +test_vector_folder = os.path.join( + os.getcwd(), "tests", "test_key_handling", "keystore_test_vectors" +) _, _, test_vector_files = next(os.walk(test_vector_folder)) # type: ignore -test_vector_keystores = [Keystore.from_file(os.path.join(test_vector_folder, f)) for f in test_vector_files] +test_vector_keystores = [ + Keystore.from_file(os.path.join(test_vector_folder, f)) for f in test_vector_files +] def test_json_serialization() -> None: @@ -25,53 +32,69 @@ def test_json_serialization() -> None: def test_encrypt_decrypt_test_vectors() -> None: for tv in test_vector_keystores: - aes_iv = tv.crypto.cipher.params['iv'] - kdf_salt = tv.crypto.kdf.params['salt'] - keystore = Pbkdf2Keystore if 'pbkdf' in tv.crypto.kdf.function else ScryptKeystore + aes_iv = tv.crypto.cipher.params["iv"] + kdf_salt = tv.crypto.kdf.params["salt"] + keystore = ( + Pbkdf2Keystore if "pbkdf" in tv.crypto.kdf.function else ScryptKeystore + ) generated_keystore = keystore.encrypt( secret=test_vector_secret, password=test_vector_password, aes_iv=aes_iv, - kdf_salt=kdf_salt) + kdf_salt=kdf_salt, + ) assert generated_keystore.decrypt(test_vector_password) == test_vector_secret def test_generated_keystores() -> None: for tv in test_vector_keystores: - aes_iv = tv.crypto.cipher.params['iv'] - kdf_salt = tv.crypto.kdf.params['salt'] - keystore = Pbkdf2Keystore if 'pbkdf' in tv.crypto.kdf.function else ScryptKeystore + aes_iv = tv.crypto.cipher.params["iv"] + kdf_salt = tv.crypto.kdf.params["salt"] + keystore = ( + Pbkdf2Keystore if "pbkdf" in tv.crypto.kdf.function else ScryptKeystore + ) generated_keystore = keystore.encrypt( secret=test_vector_secret, password=test_vector_password, aes_iv=aes_iv, - kdf_salt=kdf_salt) + kdf_salt=kdf_salt, + ) assert generated_keystore.crypto == tv.crypto def test_encrypt_decrypt_pbkdf2_random_iv() -> None: - generated_keystore = Pbkdf2Keystore.encrypt(secret=test_vector_secret, password=test_vector_password) + generated_keystore = Pbkdf2Keystore.encrypt( + secret=test_vector_secret, password=test_vector_password + ) assert generated_keystore.decrypt(test_vector_password) == test_vector_secret def test_encrypt_decrypt_scrypt_random_iv() -> None: - generated_keystore = ScryptKeystore.encrypt(secret=test_vector_secret, password=test_vector_password) + generated_keystore = ScryptKeystore.encrypt( + secret=test_vector_secret, password=test_vector_password + ) assert generated_keystore.decrypt(test_vector_password) == test_vector_secret def test_encrypt_decrypt_incorrect_password() -> None: - generated_keystore = ScryptKeystore.encrypt(secret=test_vector_secret, password=test_vector_password) - incorrect_password = test_vector_password + 'incorrect' + generated_keystore = ScryptKeystore.encrypt( + secret=test_vector_secret, password=test_vector_password + ) + incorrect_password = test_vector_password + "incorrect" with pytest.raises(ValueError): generated_keystore.decrypt(incorrect_password) @pytest.mark.parametrize( - 'password,processed_password', + "password,processed_password", [ - ['\a', b''], ['\b', b''], ['\t', b''], - ['a', b'a'], ['abc', b'abc'], ['a\bc', b'ac'], - ] + ["\a", b""], + ["\b", b""], + ["\t", b""], + ["a", b"a"], + ["abc", b"abc"], + ["a\bc", b"ac"], + ], ) def test_process_password(password: str, processed_password: bytes) -> None: assert Keystore._process_password(password) == processed_password diff --git a/tests/test_utils/test_constants.py b/tests/test_utils/test_constants.py index 3c414075..42328ead 100644 --- a/tests/test_utils/test_constants.py +++ b/tests/test_utils/test_constants.py @@ -1,18 +1,21 @@ +from typing import Dict, List + import pytest -from typing import ( - Dict, - List, -) from staking_deposit.utils.constants import _add_index_to_options @pytest.mark.parametrize( - 'arg, test', [ - ({'en': ['English', 'en']}, {'en': ['1. English', '1', 'English', 'en']}), - ({'a': ['a'], 'b': ['b'], 'c': ['c']}, - {'a': ['1. a', '1', 'a'], 'b': ['2. b', '2', 'b'], 'c': ['3. c', '3', 'c']}) - ] + "arg, test", + [ + ({"en": ["English", "en"]}, {"en": ["1. English", "1", "English", "en"]}), + ( + {"a": ["a"], "b": ["b"], "c": ["c"]}, + {"a": ["1. a", "1", "a"], "b": ["2. b", "2", "b"], "c": ["3. c", "3", "c"]}, + ), + ], ) -def test_add_index_to_options(arg: Dict[str, List[str]], test: Dict[str, List[str]]) -> None: +def test_add_index_to_options( + arg: Dict[str, List[str]], test: Dict[str, List[str]] +) -> None: assert _add_index_to_options(arg) == test diff --git a/tests/test_utils/test_crypto.py b/tests/test_utils/test_crypto.py index 2202c8da..6beb3b01 100644 --- a/tests/test_utils/test_crypto.py +++ b/tests/test_utils/test_crypto.py @@ -1,19 +1,15 @@ import pytest -from staking_deposit.utils.crypto import ( - scrypt, - PBKDF2, - AES_128_CTR, -) +from staking_deposit.utils.crypto import AES_128_CTR, PBKDF2, scrypt @pytest.mark.parametrize( - 'n, r, valid', + "n, r, valid", [ - (int(2**(128 * 1 / 8)) * 2, 8, True), - (int(2**(128 * 1 / 8)) * 1, 8, False), # Unsafe Parameters - (int(2**(128 * 1 / 8)) * 1, 1, False), # Invalid n - ] + (int(2 ** (128 * 1 / 8)) * 2, 8, True), + (int(2 ** (128 * 1 / 8)) * 1, 8, False), # Unsafe Parameters + (int(2 ** (128 * 1 / 8)) * 1, 1, False), # Invalid n + ], ) def test_scrypt_invalid_params(n, r, valid): if valid: @@ -38,21 +34,15 @@ def test_scrypt_invalid_params(n, r, valid): @pytest.mark.parametrize( - 'prf, valid', + "prf, valid", [ ("sha512", True), ("512", False), - ] + ], ) def test_PBKDF2_invalid_prf(prf, valid): if valid: - PBKDF2( - password="mypassword", - salt="mysalt", - dklen=64, - c=2048, - prf=prf - ) + PBKDF2(password="mypassword", salt="mysalt", dklen=64, c=2048, prf=prf) else: with pytest.raises(ValueError): PBKDF2( @@ -65,22 +55,16 @@ def test_PBKDF2_invalid_prf(prf, valid): @pytest.mark.parametrize( - 'count, prf, valid', + "count, prf, valid", [ (2**18, "sha256", True), (2**17, "sha256", False), (2**11, "sha512", True), - ] + ], ) def test_PBKDF2_invalid_count(count, prf, valid): if valid: - PBKDF2( - password="mypassword", - salt="mysalt", - dklen=64, - c=count, - prf=prf - ) + PBKDF2(password="mypassword", salt="mysalt", dklen=64, c=count, prf=prf) else: with pytest.raises(ValueError): PBKDF2( @@ -93,11 +77,11 @@ def test_PBKDF2_invalid_count(count, prf, valid): @pytest.mark.parametrize( - 'key, iv, valid', + "key, iv, valid", [ - (b'\x12' * 16, bytes.fromhex("edc2606468f9660ad222690db8836a9d"), True), - (b'\x12' * 15, bytes.fromhex("edc2606468f9660ad222690db8836a9d"), False), - ] + (b"\x12" * 16, bytes.fromhex("edc2606468f9660ad222690db8836a9d"), True), + (b"\x12" * 15, bytes.fromhex("edc2606468f9660ad222690db8836a9d"), False), + ], ) def test_AES_128_CTR(key, iv, valid): if valid: diff --git a/tests/test_utils/test_intl.py b/tests/test_utils/test_intl.py index 6a1a09fc..01810c35 100644 --- a/tests/test_utils/test_intl.py +++ b/tests/test_utils/test_intl.py @@ -1,13 +1,9 @@ import os +from typing import List + import pytest -from typing import ( - List, -) -from staking_deposit.utils.constants import ( - INTL_LANG_OPTIONS, - MNEMONIC_LANG_OPTIONS, -) +from staking_deposit.utils.constants import INTL_LANG_OPTIONS, MNEMONIC_LANG_OPTIONS from staking_deposit.utils.intl import ( fuzzy_reverse_dict_lookup, get_first_options, @@ -16,32 +12,70 @@ @pytest.mark.parametrize( - 'params, file_path, func, lang, found_str', [ - (['arg_mnemonic_language', 'prompt'], os.path.join('staking_deposit', 'cli', 'new_mnemonic.json'), - 'new_mnemonic', 'en', 'Please choose the language of the mnemonic word list'), - (['arg_mnemonic_language', 'prompt'], os.path.join('staking_deposit', 'cli', 'new_mnemonic.json'), - 'new_mnemonic', 'ja', 'ニマãƒĒニッã‚ŊãŪčĻ€čŠžã‚’éļ択しãĶください'), - ] + "params, file_path, func, lang, found_str", + [ + ( + ["arg_mnemonic_language", "prompt"], + os.path.join("staking_deposit", "cli", "new_mnemonic.json"), + "new_mnemonic", + "en", + "Please choose the language of the mnemonic word list", + ), + ( + ["arg_mnemonic_language", "prompt"], + os.path.join("staking_deposit", "cli", "new_mnemonic.json"), + "new_mnemonic", + "ja", + "ニマãƒĒニッã‚ŊãŪčĻ€čŠžã‚’éļ択しãĶください", + ), + ], ) -def test_load_text(params: List[str], file_path: str, func: str, lang: str, found_str: str) -> None: +def test_load_text( + params: List[str], file_path: str, func: str, lang: str, found_str: str +) -> None: assert found_str in load_text(params, file_path, func, lang) @pytest.mark.parametrize( - 'params, file_path, func, lang, valid', [ - (['arg_mnemonic_language', 'prompt'], os.path.join('staking_deposit', 'cli', 'new_mnemonic.json'), - 'new_mnemonic', 'zz', True), # invalid language, should revert to english - (['arg_mnemonic_language'], os.path.join('staking_deposit', 'cli', 'new_mnemonic.json'), - 'new_mnemonic', 'en', False), # incomplete params - (['arg_mnemonic_language', 'prompt'], os.path.join('staking_deposit', 'cli', 'invalid.json'), - 'new_mnemonic', 'en', False), # invalid json path - (['arg_mnemonic_language', 'prompt'], os.path.join('staking_deposit', 'cli', 'invalid.json'), - 'new_mnemonic', 'zz', False), # invalid json path in invalid language - ] + "params, file_path, func, lang, valid", + [ + ( + ["arg_mnemonic_language", "prompt"], + os.path.join("staking_deposit", "cli", "new_mnemonic.json"), + "new_mnemonic", + "zz", + True, + ), # invalid language, should revert to english + ( + ["arg_mnemonic_language"], + os.path.join("staking_deposit", "cli", "new_mnemonic.json"), + "new_mnemonic", + "en", + False, + ), # incomplete params + ( + ["arg_mnemonic_language", "prompt"], + os.path.join("staking_deposit", "cli", "invalid.json"), + "new_mnemonic", + "en", + False, + ), # invalid json path + ( + ["arg_mnemonic_language", "prompt"], + os.path.join("staking_deposit", "cli", "invalid.json"), + "new_mnemonic", + "zz", + False, + ), # invalid json path in invalid language + ], ) -def test_load_text_en_fallover(params: List[str], file_path: str, func: str, lang: str, valid: bool) -> None: +def test_load_text_en_fallover( + params: List[str], file_path: str, func: str, lang: str, valid: bool +) -> None: if valid: - assert load_text(params, file_path, func, lang) == load_text(params, file_path, func, 'en') + assert load_text(params, file_path, func, lang) == load_text( + params, file_path, func, "en" + ) else: try: load_text(params, file_path, func, lang) @@ -52,20 +86,25 @@ def test_load_text_en_fallover(params: List[str], file_path: str, func: str, lan @pytest.mark.parametrize( - 'options, first_options', [ - ({'a': ['a', 1], 'b': range(5), 'c': [chr(i) for i in range(65, 90)]}, ['a', 0, 'A']), - ] + "options, first_options", + [ + ( + {"a": ["a", 1], "b": range(5), "c": [chr(i) for i in range(65, 90)]}, + ["a", 0, "A"], + ), + ], ) def test_get_first_options(options, first_options): assert get_first_options(options) == first_options @pytest.mark.parametrize( - 'test, match, options', [ - ('English', 'english', MNEMONIC_LANG_OPTIONS), - ('한ęĩ­ė–ī', 'korean', MNEMONIC_LANG_OPTIONS), - ('Roman', 'ro', INTL_LANG_OPTIONS), - ] + "test, match, options", + [ + ("English", "english", MNEMONIC_LANG_OPTIONS), + ("한ęĩ­ė–ī", "korean", MNEMONIC_LANG_OPTIONS), + ("Roman", "ro", INTL_LANG_OPTIONS), + ], ) def test_fuzzy_reverse_dict_lookup(test, match, options): assert fuzzy_reverse_dict_lookup(test, options) == match diff --git a/tests/test_utils/test_ssz.py b/tests/test_utils/test_ssz.py index d8821de4..7473ccda 100644 --- a/tests/test_utils/test_ssz.py +++ b/tests/test_utils/test_ssz.py @@ -9,12 +9,16 @@ @pytest.mark.parametrize( - 'fork_version, valid, result', + "fork_version, valid, result", [ - (b"\x12" * 4, True, b'\x03\x00\x00\x00\rf`\x8a\xf5W\xf4\xfa\xdb\xfc\xe2H\xac7\xf6\xe7c\x9c\xe3q\x10\x0cC\xd1Z\xad\x05\xcb'), # noqa: E501 + ( + b"\x12" * 4, + True, + b"\x03\x00\x00\x00\rf`\x8a\xf5W\xf4\xfa\xdb\xfc\xe2H\xac7\xf6\xe7c\x9c\xe3q\x10\x0cC\xd1Z\xad\x05\xcb", + ), # noqa: E501 (b"\x12" * 5, False, None), (b"\x12" * 3, False, None), - ] + ], ) def test_compute_deposit_domain(fork_version, valid, result): if valid: @@ -25,12 +29,16 @@ def test_compute_deposit_domain(fork_version, valid, result): @pytest.mark.parametrize( - 'current_version, valid, result', + "current_version, valid, result", [ - (b"\x12" * 4, True, b'\rf`\x8a\xf5W\xf4\xfa\xdb\xfc\xe2H\xac7\xf6\xe7c\x9c\xe3q\x10\x0cC\xd1Z\xad\x05\xcb\x08\xac\x1d\xc2'), # noqa: E501 + ( + b"\x12" * 4, + True, + b"\rf`\x8a\xf5W\xf4\xfa\xdb\xfc\xe2H\xac7\xf6\xe7c\x9c\xe3q\x10\x0cC\xd1Z\xad\x05\xcb\x08\xac\x1d\xc2", + ), # noqa: E501 (b"\x12" * 5, False, None), (b"\x12" * 3, False, None), - ] + ], ) def test_compute_deposit_fork_data_root(current_version, valid, result): if valid: @@ -41,17 +49,21 @@ def test_compute_deposit_fork_data_root(current_version, valid, result): @pytest.mark.parametrize( - 'domain, valid, result', + "domain, valid, result", [ - (b"\x12" * 32, True, b'g\xa33\x0f\xf8{\xdbF\xbb{\x80\xcazd\x1e9\x8dj\xc4\xe8zhVR|\xac\xc8)\xfba\x89o'), # noqa: E501 + ( + b"\x12" * 32, + True, + b"g\xa33\x0f\xf8{\xdbF\xbb{\x80\xcazd\x1e9\x8dj\xc4\xe8zhVR|\xac\xc8)\xfba\x89o", + ), # noqa: E501 (b"\x12" * 31, False, None), (b"\x12" * 33, False, None), - ] + ], ) def test_compute_signing_root(domain, valid, result): deposit_message = DepositMessage( - pubkey=b'\x12' * 48, - withdrawal_credentials=b'\x12' * 32, + pubkey=b"\x12" * 48, + withdrawal_credentials=b"\x12" * 32, amount=100, ) if valid: diff --git a/tests/test_utils/test_validation.py b/tests/test_utils/test_validation.py index 9b918462..c903d286 100644 --- a/tests/test_utils/test_validation.py +++ b/tests/test_utils/test_validation.py @@ -1,7 +1,6 @@ +from typing import Any + import pytest -from typing import ( - Any, -) from staking_deposit.exceptions import ValidationError from staking_deposit.utils.validation import ( @@ -12,11 +11,11 @@ @pytest.mark.parametrize( - 'password, valid', + "password, valid", [ - ('12345678', True), - ('1234567', False), - ] + ("12345678", True), + ("1234567", False), + ], ) def test_validate_password_strength(password, valid): if valid: @@ -27,16 +26,16 @@ def test_validate_password_strength(password, valid): @pytest.mark.parametrize( - 'num, low, high, valid', + "num, low, high, valid", [ (2, 0, 4, True), (0, 0, 4, True), (-1, 0, 4, False), (4, 0, 4, False), (0.2, 0, 4, False), - ('0', 0, 4, True), - ('a', 0, 4, False), - ] + ("0", 0, 4, True), + ("a", 0, 4, False), + ], ) def test_validate_int_range(num: Any, low: int, high: int, valid: bool) -> None: if valid: @@ -47,16 +46,16 @@ def test_validate_int_range(num: Any, low: int, high: int, valid: bool) -> None: @pytest.mark.parametrize( - 'input, result', + "input, result", [ - ('1', ['1']), - ('1,2,3', ['1', '2', '3']), - ('[1,2,3]', ['1', '2', '3']), - ('(1,2,3)', ['1', '2', '3']), - ('{1,2,3}', ['1', '2', '3']), - ('1 2 3', ['1', '2', '3']), - ('1 2 3', ['1', '2', '3']), - ] + ("1", ["1"]), + ("1,2,3", ["1", "2", "3"]), + ("[1,2,3]", ["1", "2", "3"]), + ("(1,2,3)", ["1", "2", "3"]), + ("{1,2,3}", ["1", "2", "3"]), + ("1 2 3", ["1", "2", "3"]), + ("1 2 3", ["1", "2", "3"]), + ], ) def test_normalize_input_list(input, result): assert normalize_input_list(input) == result From 9a97e455d27bcfa6df073b68132850e93d4797b3 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 12:37:23 +0000 Subject: [PATCH 3/5] refactor: remove assert statement from non-test files Usage of `assert` statement in application logic is discouraged. `assert` is removed with compiling to optimized byte code. Consider raising an exception instead. Ideally, `assert` statement should be used only in tests. --- staking_deposit/utils/validation.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/staking_deposit/utils/validation.py b/staking_deposit/utils/validation.py index f55deeea..9b4fd948 100644 --- a/staking_deposit/utils/validation.py +++ b/staking_deposit/utils/validation.py @@ -137,8 +137,10 @@ def validate_int_range(num: Any, low: int, high: int) -> int: """ try: num_int = int(num) # Try cast to int - assert num_int == float(num) # Check num is not float - assert low <= num_int < high # Check num in range + if num_int != float(num): + raise AssertionError + if not low <= num_int < high: + raise AssertionError return num_int except (ValueError, AssertionError): raise ValidationError(load_text(["err_not_positive_integer"])) @@ -276,7 +278,8 @@ def validate_bls_withdrawal_credentials(bls_withdrawal_credentials: str) -> byte try: assert len(bls_withdrawal_credentials_bytes) == 32 - assert bls_withdrawal_credentials_bytes[:1] == BLS_WITHDRAWAL_PREFIX + if bls_withdrawal_credentials_bytes[:1] != BLS_WITHDRAWAL_PREFIX: + raise AssertionError except (ValueError, AssertionError): raise ValidationError(load_text(["err_not_bls_form"]) + "\n") From 02527e63653741f8cb0604ec18bcfb2fd05182c0 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 12:40:15 +0000 Subject: [PATCH 4/5] refactor: remove `return` from `__init__()` method The `__init__()` method is required to return `None`, but an explicit return value in its body was detected. Returning a value other than `None` will raise a `TypeError`. Even if the return value is `None` explicit return statement shall always be avoided. --- staking_deposit/utils/click.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staking_deposit/utils/click.py b/staking_deposit/utils/click.py index ea9d18df..f369c3b5 100644 --- a/staking_deposit/utils/click.py +++ b/staking_deposit/utils/click.py @@ -34,7 +34,7 @@ def __init__( if isinstance(param_decls, str): param_decls = [_value_of(param_decls)] - return super().__init__( + super().__init__( param_decls=param_decls, default=_value_of(default), help=_value_of(help), From c7fb67691ca56c3a8e12f6cbab3e20bfea768091 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 12:41:53 +0000 Subject: [PATCH 5/5] refactor: remove assert statement from non-test files Usage of `assert` statement in application logic is discouraged. `assert` is removed with compiling to optimized byte code. Consider raising an exception instead. Ideally, `assert` statement should be used only in tests. --- tests/test_cli/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_cli/helpers.py b/tests/test_cli/helpers.py index a00fffc7..6ec777dc 100644 --- a/tests/test_cli/helpers.py +++ b/tests/test_cli/helpers.py @@ -42,7 +42,8 @@ def get_permissions(path: str, file_name: str) -> str: def verify_file_permission(os_ref, folder_path, files): if os_ref.name == "posix": for file_name in files: - assert get_permissions(folder_path, file_name) == "0o440" + if get_permissions(folder_path, file_name) != "0o440": + raise AssertionError def prepare_testing_folder(os_ref, testing_folder_name="TESTING_TEMP_FOLDER"):