diff --git a/staking_deposit/cli/new_mnemonic.py b/staking_deposit/cli/new_mnemonic.py index da2e713..f288aea 100644 --- a/staking_deposit/cli/new_mnemonic.py +++ b/staking_deposit/cli/new_mnemonic.py @@ -5,6 +5,7 @@ from typing import ( from staking_deposit.key_handling.key_derivation.mnemonic import ( get_mnemonic, + reconstruct_mnemonic, ) from staking_deposit.utils.click import ( captive_prompt_callback, @@ -47,7 +48,7 @@ languages = get_first_options(MNEMONIC_LANG_OPTIONS) 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 != 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) @@ -55,7 +56,6 @@ def new_mnemonic(ctx: click.Context, mnemonic_language: str, **kwargs: Any) -> N click.clear() test_mnemonic = click.prompt(load_text(['msg_mnemonic_retype_prompt']) + '\n\n') - test_mnemonic = test_mnemonic.lower() click.clear() # Do NOT use mnemonic_password. ctx.obj = {'mnemonic': mnemonic, 'mnemonic_password': ''} diff --git a/staking_deposit/intl/en/cli/new_mnemonic.json b/staking_deposit/intl/en/cli/new_mnemonic.json index 4e6042c..feee3ac 100644 --- a/staking_deposit/intl/en/cli/new_mnemonic.json +++ b/staking_deposit/intl/en/cli/new_mnemonic.json @@ -10,6 +10,6 @@ }, "msg_mnemonic_presentation": "This is your mnemonic (seed phrase). Write it down and store it safely. It is the ONLY way to retrieve your deposit.", "msg_press_any_key": "Press any key when you have written down your mnemonic.", - "msg_mnemonic_retype_prompt": "Please type your mnemonic (separated by spaces) to confirm you have written it down." + "msg_mnemonic_retype_prompt": "Please type your mnemonic (separated by spaces) to confirm you have written it down. Note: you only need to enter the first 4 letters of each word if you'd prefer." } } diff --git a/tests/test_cli/test_new_mnemonic.py b/tests/test_cli/test_new_mnemonic.py index 098bc6a..fb70a25 100644 --- a/tests/test_cli/test_new_mnemonic.py +++ b/tests/test_cli/test_new_mnemonic.py @@ -9,6 +9,7 @@ from eth_utils import decode_hex from staking_deposit.cli import new_mnemonic from staking_deposit.deposit import cli +from staking_deposit.key_handling.key_derivation.mnemonic import abbreviate_words from staking_deposit.utils.constants import DEFAULT_VALIDATOR_KEYS_FOLDER_NAME, ETH1_ADDRESS_WITHDRAWAL_PREFIX from staking_deposit.utils.intl import load_text from .helpers import clean_key_folder, get_permissions, get_uuid @@ -17,7 +18,7 @@ from .helpers import clean_key_folder, get_permissions, get_uuid def test_new_mnemonic_bls_withdrawal(monkeypatch) -> None: # monkeypatch get_mnemonic def mock_get_mnemonic(language, words_path, entropy=None) -> str: - return "fakephrase" + return "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) @@ -28,7 +29,8 @@ def test_new_mnemonic_bls_withdrawal(monkeypatch) -> None: os.mkdir(my_folder_path) runner = CliRunner() - inputs = ['english', 'english', '1', 'mainnet', 'MyPassword', 'MyPassword', 'fakephrase'] + 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 @@ -56,7 +58,7 @@ def test_new_mnemonic_bls_withdrawal(monkeypatch) -> None: def test_new_mnemonic_eth1_address_withdrawal(monkeypatch) -> None: # monkeypatch get_mnemonic def mock_get_mnemonic(language, words_path, entropy=None) -> str: - return "fakephrase" + return "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) @@ -67,7 +69,8 @@ def test_new_mnemonic_eth1_address_withdrawal(monkeypatch) -> None: os.mkdir(my_folder_path) runner = CliRunner() - inputs = ['english', '1', 'mainnet', 'MyPassword', 'MyPassword', 'fakephrase'] + inputs = ['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' arguments = [ @@ -178,3 +181,76 @@ async def test_script() -> None: # Clean up clean_key_folder(my_folder_path) + + +@pytest.mark.asyncio +async def test_script_abbreviated_mnemonic() -> 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: + abbreviated_mnemonic = ' '.join(abbreviate_words(seed_phrase.split(' '))) + encoded_phrase = abbreviated_mnemonic.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)