def generate_keys(ctx: click.Context, validator_start_index: int, num_validators: int, folder: str, chain: str, keystore_password: str, eth1_withdrawal_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) if not os.path.exists(folder): os.mkdir(folder) click.clear() click.echo(RHINO_0) click.echo(load_text(['msg_key_creation'])) credentials = CredentialList.from_mnemonic( mnemonic=mnemonic, mnemonic_password=mnemonic_password, num_keys=num_validators, amounts=amounts, chain_setting=chain_setting, start_index=validator_start_index, hex_eth1_withdrawal_address=eth1_withdrawal_address, ) 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 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']))
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 ValueError(load_text(['err_invalid_ECDSA_hex_addr'])) normalized_address = to_normalized_address(address) click.echo('\n%s\n' % load_text(['msg_ECDSA_addr_withdrawal'])) return normalized_address
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') else: try: load_text(params, file_path, func, lang) except KeyError: pass else: assert False
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'])) sys.exit()
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 = '' 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.clear() 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.forward(generate_keys)
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: 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: json.dump(deposit_data, f, default=lambda x: x.hex()) if os.name == 'posix': os.chmod(filefolder, int('440', 8)) # Read for owner & group return filefolder
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']))
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: 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) ]) return False
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])
def validate_password_strength(password: str) -> str: if len(password) < 8: raise ValidationError(load_text(['msg_password_length'])) return password
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')), 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'), ), jit_option( default=os.getcwd(), 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())), ), default=MAINNET, 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'), list(ALL_CHAINS.keys())), ), 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'), True, ), 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'), ), jit_option( callback=validate_eth1_withdrawal_address, default=None, help=lambda: load_text(['eth1_withdrawal_address', 'help'], func='generate_keys_arguments_decorator'), param_decls='--eth1_withdrawal_address', ), ] for decorator in reversed(decorators): function = decorator(function) return function
def validate_mnemonic(ctx: click.Context, param: Any, mnemonic: str) -> str: mnemonic = reconstruct_mnemonic(mnemonic, WORD_LISTS_PATH) if mnemonic is not None: return mnemonic else: raise ValidationError(load_text(['err_invalid_mnemonic']))
from staking_deposit.utils.intl import ( fuzzy_reverse_dict_lookup, load_text, get_first_options, ) 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'), ) @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), ), 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(
from staking_deposit.utils.validation import validate_int_range from .generate_keys import ( generate_keys, generate_keys_arguments_decorator, ) def validate_mnemonic(ctx: click.Context, param: Any, mnemonic: str) -> str: if verify_mnemonic(mnemonic, WORD_LISTS_PATH): return mnemonic else: raise ValidationError(load_text(['err_invalid_mnemonic'])) @click.command( help=load_text(['arg_existing_mnemonic', 'help'], func='existing_mnemonic'), ) @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'), 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'),
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)
async def test_script() -> None: 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' else: # Mac or Linux run_script_cmd = './deposit.sh' install_cmd = run_script_cmd + ' install' proc = await asyncio.create_subprocess_shell(install_cmd, ) await proc.wait() 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, ] proc = await asyncio.create_subprocess_shell( ' '.join(cmd_args), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, ) seed_phrase = '' parsing = False 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')): parsing = True 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') assert len(seed_phrase) > 0 # Check files 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) for key_file in key_files if key_file.startswith('keystore') ] assert len(set(all_uuid)) == 5 # Verify file permissions if os.name == 'posix': for file_name in key_files: assert get_permissions(validator_keys_folder_path, file_name) == '0o440' # Clean up clean_key_folder(my_folder_path)
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_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]
def validate_mnemonic(ctx: click.Context, param: Any, mnemonic: str) -> str: if verify_mnemonic(mnemonic, WORD_LISTS_PATH): return mnemonic else: raise ValidationError(load_text(['err_invalid_mnemonic']))