예제 #1
0
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']))
예제 #2
0
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
예제 #3
0
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
예제 #4
0
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()
예제 #5
0
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)
예제 #6
0
 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
예제 #7
0
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']))
예제 #8
0
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
예제 #9
0
 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])
예제 #10
0
def validate_password_strength(password: str) -> str:
    if len(password) < 8:
        raise ValidationError(load_text(['msg_password_length']))
    return password
예제 #11
0
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
예제 #12
0
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']))
예제 #13
0
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(
예제 #14
0
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'),
예제 #15
0
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)
예제 #17
0
 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)
예제 #18
0
 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]
예제 #19
0
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']))