mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 15:58:48 +00:00
Add interactive seed word prompt
This commit is contained in:
@@ -28,3 +28,31 @@ def test_masked_input_windows_space(monkeypatch, capsys):
|
|||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert out.startswith("Password: ")
|
assert out.startswith("Password: ")
|
||||||
assert out.count("*") == 4
|
assert out.count("*") == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_prompt_seed_words_valid(monkeypatch):
|
||||||
|
from mnemonic import Mnemonic
|
||||||
|
|
||||||
|
m = Mnemonic("english")
|
||||||
|
phrase = m.generate(strength=128)
|
||||||
|
words = phrase.split()
|
||||||
|
|
||||||
|
inputs = iter(words + ["y"] * len(words))
|
||||||
|
monkeypatch.setattr("builtins.input", lambda *_: next(inputs))
|
||||||
|
|
||||||
|
result = seed_prompt.prompt_seed_words(len(words))
|
||||||
|
assert result == phrase
|
||||||
|
|
||||||
|
|
||||||
|
def test_prompt_seed_words_invalid_word(monkeypatch):
|
||||||
|
from mnemonic import Mnemonic
|
||||||
|
|
||||||
|
m = Mnemonic("english")
|
||||||
|
phrase = m.generate(strength=128)
|
||||||
|
words = phrase.split()
|
||||||
|
# Insert an invalid word for the first entry then the correct one
|
||||||
|
inputs = iter(["invalid"] + [words[0]] + words[1:] + ["y"] * len(words))
|
||||||
|
monkeypatch.setattr("builtins.input", lambda *_: next(inputs))
|
||||||
|
|
||||||
|
result = seed_prompt.prompt_seed_words(len(words))
|
||||||
|
assert result == phrase
|
||||||
|
@@ -25,7 +25,7 @@ try:
|
|||||||
update_checksum_file,
|
update_checksum_file,
|
||||||
)
|
)
|
||||||
from .password_prompt import prompt_for_password
|
from .password_prompt import prompt_for_password
|
||||||
from .seed_prompt import masked_input
|
from .seed_prompt import masked_input, prompt_seed_words
|
||||||
from .input_utils import timed_input
|
from .input_utils import timed_input
|
||||||
from .memory_protection import InMemorySecret
|
from .memory_protection import InMemorySecret
|
||||||
from .clipboard import copy_to_clipboard
|
from .clipboard import copy_to_clipboard
|
||||||
@@ -60,6 +60,7 @@ __all__ = [
|
|||||||
"shared_lock",
|
"shared_lock",
|
||||||
"prompt_for_password",
|
"prompt_for_password",
|
||||||
"masked_input",
|
"masked_input",
|
||||||
|
"prompt_seed_words",
|
||||||
"timed_input",
|
"timed_input",
|
||||||
"InMemorySecret",
|
"InMemorySecret",
|
||||||
"copy_to_clipboard",
|
"copy_to_clipboard",
|
||||||
|
@@ -71,3 +71,71 @@ def masked_input(prompt: str) -> str:
|
|||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
return _masked_input_windows(prompt)
|
return _masked_input_windows(prompt)
|
||||||
return _masked_input_posix(prompt)
|
return _masked_input_posix(prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_seed_words(count: int = 12) -> str:
|
||||||
|
"""Prompt the user for a BIP-39 seed phrase.
|
||||||
|
|
||||||
|
The user is asked for each word one at a time. A numbered list is
|
||||||
|
displayed showing ``*`` for entered words and ``_`` for words yet to be
|
||||||
|
provided. After all words are entered the user is asked to confirm each
|
||||||
|
word individually. If the user answers ``no`` to a confirmation prompt the
|
||||||
|
word can be re-entered.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
count:
|
||||||
|
Number of words to prompt for. Defaults to ``12``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
The complete seed phrase.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If the resulting phrase fails ``Mnemonic.check`` validation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from mnemonic import Mnemonic
|
||||||
|
|
||||||
|
m = Mnemonic("english")
|
||||||
|
words: list[str] = [""] * count
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
while idx < count:
|
||||||
|
progress = [f"{i+1}: {'*' if w else '_'}" for i, w in enumerate(words)]
|
||||||
|
print("\n".join(progress))
|
||||||
|
entered = input(f"Enter word number {idx+1}: ").strip().lower()
|
||||||
|
if entered not in m.wordlist:
|
||||||
|
print("Invalid word, try again.")
|
||||||
|
continue
|
||||||
|
words[idx] = entered
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
for i in range(count):
|
||||||
|
while True:
|
||||||
|
response = (
|
||||||
|
input(f"Is this the correct word for number {i+1}? {words[i]} (Y/N): ")
|
||||||
|
.strip()
|
||||||
|
.lower()
|
||||||
|
)
|
||||||
|
if response in ("y", "yes"):
|
||||||
|
break
|
||||||
|
if response in ("n", "no"):
|
||||||
|
while True:
|
||||||
|
new_word = input(f"Re-enter word number {i+1}: ").strip().lower()
|
||||||
|
if new_word in m.wordlist:
|
||||||
|
words[i] = new_word
|
||||||
|
break
|
||||||
|
print("Invalid word, try again.")
|
||||||
|
# Ask for confirmation again with the new word
|
||||||
|
else:
|
||||||
|
print("Please respond with 'Y' or 'N'.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
phrase = " ".join(words)
|
||||||
|
if not m.check(phrase):
|
||||||
|
raise ValueError("Invalid BIP-39 seed phrase")
|
||||||
|
return phrase
|
||||||
|
Reference in New Issue
Block a user