Skip to content

Commit

Permalink
Adding optional mnemonic_language argument
Browse files Browse the repository at this point in the history
  • Loading branch information
valefar-on-discord committed Sep 26, 2024
1 parent 28d0845 commit d882e14
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 6 deletions.
2 changes: 2 additions & 0 deletions docs/src/existing_mnemonic.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Uses an existing BIP-39 mnemonic phrase for key generation.

- **`--mnemonic`**: The mnemonic you used to create withdrawal credentials. <span class="warning"></span>

- **`--mnemonic_language`**: The language of your mnemonic. If this is not provided we will attempt to determine it based on the mnemonic provided.

- **`--mnemonic_password`**: The mnemonic password you used in your key generation. Note: It's not the keystore password. <span class="warning"></span>

- **`--validator_start_index`**: The index of the first validator's keys you wish to generate. If this is your first time generating keys with this mnemonic, use 0. If you have generated keys using this mnemonic before, use the next index from which you want to start generating keys from. As an example if you've generated 4 keys before (keys #0, #1, #2, #3), then enter 4 here.
Expand Down
2 changes: 2 additions & 0 deletions docs/src/exit_transaction_mnemonic.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Creates an exit transaction using a mnemonic phrase.

- **`--mnemonic`**: The mnemonic you used during key generation. <span class="warning"></span>

- **`--mnemonic_language`**: The language of your mnemonic. If this is not provided we will attempt to determine it based on the mnemonic provided.

- **`--mnemonic_password`**: The mnemonic password you used in your key generation. Note: It's not the keystore password. <span class="warning"></span>

- **`--validator_start_index`**: The index position for the keys to start generating keystores in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters) format.
Expand Down
2 changes: 2 additions & 0 deletions docs/src/generate_bls_to_execution_change.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Generates a BLS to execution address change message. This is used to add a withd

- **`--mnemonic`**: The mnemonic you used to create withdrawal credentials. <span class="warning"></span>

- **`--mnemonic_language`**: The language of your mnemonic. If this is not provided we will attempt to determine it based on the mnemonic provided.

- **`--mnemonic_password`**: The mnemonic password you used in your key generation. Note: It's not the keystore password. <span class="warning"></span>

- **`--validator_start_index`**: The index position for the keys to start generating withdrawal credentials for.
Expand Down
22 changes: 17 additions & 5 deletions ethstaker_deposit/cli/existing_mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ def load_mnemonic_arguments_decorator(function: Callable[..., Any]) -> Callable[
'''
decorators = [
jit_option(
callback=captive_prompt_callback(
lambda mnemonic: validate_mnemonic(mnemonic=mnemonic),
prompt=lambda: load_text(['arg_mnemonic', 'prompt'], func='existing_mnemonic'),
prompt_if_none=True,
),
callback=lambda c, _, mnemonic:
captive_prompt_callback(
lambda mnemonic: validate_mnemonic(mnemonic=mnemonic, language=c.params.get('mnemonic_language')),
prompt=lambda: load_text(['arg_mnemonic', 'prompt'], func='existing_mnemonic'),
prompt_if_none=True,
)(c, _, mnemonic),
help=lambda: load_text(['arg_mnemonic', 'help'], func='existing_mnemonic'),
param_decls='--mnemonic',
prompt=False,
Expand All @@ -58,6 +59,13 @@ def load_mnemonic_arguments_decorator(function: Callable[..., Any]) -> Callable[
param_decls='--mnemonic_password',
prompt=False,
),
jit_option(
callback=validate_mnemonic_language,
default=None,
help=lambda: load_text(['arg_mnemonic_language', 'help'], func='existing_mnemonic'),
param_decls='--mnemonic_language',
prompt=None,
),
]
for decorator in reversed(decorators):
function = decorator(function)
Expand All @@ -83,6 +91,10 @@ def validate_mnemonic(mnemonic: str, language: Optional[str] = None) -> str:
raise ValidationError(load_text(['err_invalid_mnemonic']))


def validate_mnemonic_language(ctx: click.Context, param: Any, language: str) -> Optional[str]:
return fuzzy_reverse_dict_lookup(language, MNEMONIC_LANG_OPTIONS) if language else None


@click.command(
help=load_text(['arg_existing_mnemonic', 'help'], func='existing_mnemonic'),
)
Expand Down
2 changes: 1 addition & 1 deletion ethstaker_deposit/cli/new_mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
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):
while mnemonic != reconstruct_mnemonic(test_mnemonic, WORD_LISTS_PATH, mnemonic_language):
clear_terminal()
click.echo(load_text(['msg_mnemonic_presentation']))
click.echo('\n\n%s\n\n' % mnemonic)
Expand Down
3 changes: 3 additions & 0 deletions ethstaker_deposit/intl/en/cli/existing_mnemonic.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"help": "The mnemonic that you used to generate your keys. (It is recommended not to use this argument, and wait for the CLI to ask you for your mnemonic as otherwise it will appear in your shell history.)",
"prompt": "Please enter your mnemonic separated by spaces (\" \"). Note: you only need to enter the first 4 letters of each word if you'd prefer."
},
"arg_mnemonic_language": {
"help": "The language of your mnemonic. If this is not provided we will attempt to determine it based on the mnemonic provided."
},
"arg_mnemonic_password": {
"help": "This is almost certainly not the argument you are looking for: it is for mnemonic passwords, not keystore passwords. Providing a password here when you didn't use one initially, can result in lost keys (and therefore funds)! Also note that if you used this tool to generate your mnemonic initially, then you did not use a mnemonic password. However, if you are certain you used a password to \"increase\" the security of your mnemonic, this is where you enter it.",
"prompt": "Enter your mnemonic password (if you used one). Make sure you won't forget it, it can not be recovered.",
Expand Down
1 change: 1 addition & 0 deletions ethstaker_deposit/key_handling/key_derivation/mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def reconstruct_mnemonic(mnemonic: str, words_path: str, language: Optional[str]
pass

if len(valid_languages) > 1:
valid_languages.sort()
raise MultiLanguageError(valid_languages)

return reconstructed_mnemonic
Expand Down
4 changes: 4 additions & 0 deletions ethstaker_deposit/utils/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@


def clear_terminal() -> None:
# Do not clear if running unit tests as stdout can be used to determine state
if "PYTEST_CURRENT_TEST" in os.environ:
return

# We bundle libtinfo via pyinstaller, which messes with the system tput.
# Remove LD_LIBRARY_PATH just for subprocess.run()
if sys.platform == 'linux':
Expand Down
91 changes: 91 additions & 0 deletions tests/test_cli/test_existing_mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,94 @@ def test_existing_mnemonic_custom_testnet() -> None:
assert get_permissions(validator_keys_folder_path, file_name) == '0o440'
# Clean up
clean_key_folder(my_folder_path)


def test_existing_mnemonic_multiple_languages() -> None:
# Prepare 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',
'的 的 的 的 的 的 的 的 的 的 的 在', '1',
'2', '2', '5', 'MyPasswordIs', 'MyPasswordIs']
data = '\n'.join(inputs)
arguments = [
'--language', 'english',
'--ignore_connectivity',
'existing-mnemonic',
'--chain', 'holesky',
'--withdrawal_address', '',
'--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)
_, _, 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 test_existing_mnemonic_multiple_languages_argument() -> None:
# Prepare 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',
'的 的 的 的 的 的 的 的 的 的 的 在',
'2', '2', '5', 'MyPasswordIs', 'MyPasswordIs']
data = '\n'.join(inputs)
arguments = [
'--language', 'english',
'--ignore_connectivity',
'existing-mnemonic',
'--chain', 'holesky',
'--withdrawal_address', '',
'--folder', my_folder_path,
'--mnemonic_language', '简体中文',
'--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)
_, _, 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)

0 comments on commit d882e14

Please sign in to comment.