mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 20:38:10 +00:00
feat(tests): update, refactor and extend click tests
This commit is contained in:
parent
87c7e33198
commit
00b83d1dca
50
tests/click_tests/common.py
Normal file
50
tests/click_tests/common.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from .. import buttons
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from trezorlib.debuglink import DebugLink, LayoutContent
|
||||||
|
|
||||||
|
|
||||||
|
# Passphrases and addresses for both models
|
||||||
|
class CommonPass:
|
||||||
|
RANDOM_25 = "Y@14lw%p)JN@f54MYvys@zj'g"
|
||||||
|
RANDOM_25_ADDRESS = "mnkoxeaMzLgfCxUdDSZWrGactyJJerQVW6"
|
||||||
|
|
||||||
|
SHORT = "abc123ABC_<>"
|
||||||
|
SHORT_ADDRESS = "mtHHfh6uHtJiACwp7kzJZ97yueT6sEdQiG"
|
||||||
|
|
||||||
|
WITH_SPACE = "abc 123"
|
||||||
|
WITH_SPACE_ADDRESS = "mvqzZUb9NaUc62Buk9WCP4L7hunsXFyamT"
|
||||||
|
|
||||||
|
EMPTY_ADDRESS = "mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q"
|
||||||
|
|
||||||
|
|
||||||
|
class PassphraseCategory(Enum):
|
||||||
|
MENU = "MENU"
|
||||||
|
DIGITS = "123"
|
||||||
|
LOWERCASE = "abc"
|
||||||
|
UPPERCASE = "ABC"
|
||||||
|
SPECIAL = "#$!"
|
||||||
|
|
||||||
|
|
||||||
|
def get_char_category(char: str) -> PassphraseCategory:
|
||||||
|
"""What is the category of a character"""
|
||||||
|
if char.isdigit():
|
||||||
|
return PassphraseCategory.DIGITS
|
||||||
|
if char.islower():
|
||||||
|
return PassphraseCategory.LOWERCASE
|
||||||
|
if char.isupper():
|
||||||
|
return PassphraseCategory.UPPERCASE
|
||||||
|
return PassphraseCategory.SPECIAL
|
||||||
|
|
||||||
|
|
||||||
|
def go_next(debug: "DebugLink", wait: bool = False) -> "LayoutContent" | None:
|
||||||
|
return debug.click(buttons.OK, wait=wait) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def go_back(debug: "DebugLink", wait: bool = False) -> "LayoutContent" | None:
|
||||||
|
return debug.click(buttons.CANCEL, wait=wait) # type: ignore
|
@ -13,35 +13,28 @@ def enter_word(
|
|||||||
for coords in buttons.type_word(typed_word, is_slip39=is_slip39):
|
for coords in buttons.type_word(typed_word, is_slip39=is_slip39):
|
||||||
debug.click(coords)
|
debug.click(coords)
|
||||||
|
|
||||||
# For BIP39 - double-click on CONFIRM WORD is needed in case the word
|
|
||||||
# is not already typed as a whole
|
|
||||||
if not is_slip39 and typed_word != word:
|
|
||||||
debug.click(buttons.CONFIRM_WORD)
|
|
||||||
return debug.click(buttons.CONFIRM_WORD, wait=True)
|
return debug.click(buttons.CONFIRM_WORD, wait=True)
|
||||||
|
|
||||||
|
|
||||||
def confirm_recovery(debug: "DebugLink", legacy_ui: bool = False) -> None:
|
def confirm_recovery(debug: "DebugLink", legacy_ui: bool = False) -> None:
|
||||||
layout = debug.wait_layout()
|
if not legacy_ui:
|
||||||
if legacy_ui:
|
layout = debug.wait_layout()
|
||||||
assert layout.text.startswith("Recovery mode")
|
assert layout.title().startswith("WALLET RECOVERY")
|
||||||
else:
|
|
||||||
assert layout.get_title().startswith("WALLET RECOVERY")
|
|
||||||
debug.click(buttons.OK, wait=True)
|
debug.click(buttons.OK, wait=True)
|
||||||
|
|
||||||
|
|
||||||
def select_number_of_words(
|
def select_number_of_words(
|
||||||
debug: "DebugLink", num_of_words: int = 20, legacy_ui: bool = False
|
debug: "DebugLink", num_of_words: int = 20, legacy_ui: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
layout = debug.read_layout()
|
|
||||||
|
|
||||||
# select number of words
|
# select number of words
|
||||||
assert "Select number of words" in layout.get_content()
|
if not legacy_ui:
|
||||||
|
assert "select the number of words" in debug.read_layout().text_content()
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
if legacy_ui:
|
if legacy_ui:
|
||||||
assert layout.text == "WordSelector"
|
assert layout.json_str == "WordSelector"
|
||||||
else:
|
else:
|
||||||
# Two title options
|
# Two title options
|
||||||
assert layout.get_title() in ("SEED CHECK", "WALLET RECOVERY")
|
assert layout.title() in ("SEED CHECK", "WALLET RECOVERY")
|
||||||
|
|
||||||
# click the number
|
# click the number
|
||||||
word_option_offset = 6
|
word_option_offset = 6
|
||||||
@ -51,7 +44,12 @@ def select_number_of_words(
|
|||||||
) # raises if num of words is invalid
|
) # raises if num of words is invalid
|
||||||
coords = buttons.grid34(index % 3, index // 3)
|
coords = buttons.grid34(index % 3, index // 3)
|
||||||
layout = debug.click(coords, wait=True)
|
layout = debug.click(coords, wait=True)
|
||||||
assert "Enter any share" in layout.get_content()
|
|
||||||
|
if not legacy_ui:
|
||||||
|
if num_of_words in (20, 33):
|
||||||
|
assert "Enter any share" in layout.text_content()
|
||||||
|
else:
|
||||||
|
assert "enter your recovery seed" in layout.text_content()
|
||||||
|
|
||||||
|
|
||||||
def enter_share(
|
def enter_share(
|
||||||
@ -60,9 +58,9 @@ def enter_share(
|
|||||||
layout = debug.click(buttons.OK, wait=True)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
|
|
||||||
if legacy_ui:
|
if legacy_ui:
|
||||||
assert layout.text == "Slip39Keyboard"
|
assert layout.json_str == "Slip39Keyboard"
|
||||||
else:
|
else:
|
||||||
assert layout.text == "< MnemonicKeyboard >"
|
assert layout.main_component() == "MnemonicKeyboard"
|
||||||
|
|
||||||
for word in share.split(" "):
|
for word in share.split(" "):
|
||||||
layout = enter_word(debug, word, is_slip39=True)
|
layout = enter_word(debug, word, is_slip39=True)
|
||||||
@ -75,14 +73,26 @@ def enter_shares(debug: "DebugLink", shares: list[str]) -> None:
|
|||||||
expected_text = "Enter any share"
|
expected_text = "Enter any share"
|
||||||
remaining = len(shares)
|
remaining = len(shares)
|
||||||
for share in shares:
|
for share in shares:
|
||||||
assert expected_text in layout.get_content()
|
assert expected_text in layout.text_content()
|
||||||
layout = enter_share(debug, share)
|
layout = enter_share(debug, share)
|
||||||
remaining -= 1
|
remaining -= 1
|
||||||
expected_text = f"{remaining} more share"
|
expected_text = f"{remaining} more share"
|
||||||
|
|
||||||
assert "You have successfully recovered your wallet" in layout.get_content()
|
assert "You have finished recovering your wallet" in layout.text_content()
|
||||||
|
|
||||||
|
|
||||||
|
def enter_seed(debug: "DebugLink", seed_words: list[str]) -> None:
|
||||||
|
assert "enter" in debug.read_layout().text_content()
|
||||||
|
|
||||||
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
|
assert layout.main_component() == "MnemonicKeyboard"
|
||||||
|
|
||||||
|
for word in seed_words:
|
||||||
|
layout = enter_word(debug, word, is_slip39=False)
|
||||||
|
|
||||||
|
assert "You have finished recovering your wallet" in layout.text_content()
|
||||||
|
|
||||||
|
|
||||||
def finalize(debug: "DebugLink") -> None:
|
def finalize(debug: "DebugLink") -> None:
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
assert layout.text.startswith("< Homescreen ")
|
assert layout.main_component() == "Homescreen"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from shamir_mnemonic import shamir
|
from shamir_mnemonic import shamir # type: ignore
|
||||||
|
|
||||||
from trezorlib import messages
|
from trezorlib import messages
|
||||||
|
|
||||||
@ -10,61 +10,83 @@ if TYPE_CHECKING:
|
|||||||
from trezorlib.debuglink import DebugLink
|
from trezorlib.debuglink import DebugLink
|
||||||
|
|
||||||
|
|
||||||
def confirm_wait(debug: "DebugLink", title: str) -> None:
|
def confirm_new_wallet(debug: "DebugLink") -> None:
|
||||||
layout = debug.wait_layout()
|
assert debug.wait_layout().title().startswith("WALLET CREATION")
|
||||||
assert title.upper() in layout.get_title()
|
|
||||||
debug.click(buttons.OK, wait=True)
|
debug.click(buttons.OK, wait=True)
|
||||||
|
|
||||||
|
|
||||||
def confirm_read(debug: "DebugLink", title: str) -> None:
|
def confirm_read(debug: "DebugLink", title: str, hold: bool = False) -> None:
|
||||||
layout = debug.read_layout()
|
layout = debug.read_layout()
|
||||||
if title == "Caution":
|
if title == "Caution":
|
||||||
assert "OK, I UNDERSTAND" in layout.text
|
# TODO: could look into button texts
|
||||||
|
assert "OK, I UNDERSTAND" in layout.json_str
|
||||||
elif title == "Success":
|
elif title == "Success":
|
||||||
|
# TODO: improve this
|
||||||
assert any(
|
assert any(
|
||||||
text in layout.get_content() for text in ("success", "finished", "done")
|
text in layout.text_content()
|
||||||
|
for text in (
|
||||||
|
"success",
|
||||||
|
"finished",
|
||||||
|
"done",
|
||||||
|
"has been created",
|
||||||
|
"Keep it safe",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
elif title == "Checklist":
|
||||||
|
assert "number of shares" in layout.text_content().lower()
|
||||||
else:
|
else:
|
||||||
assert title.upper() in layout.get_title()
|
assert title.upper() in layout.title()
|
||||||
|
|
||||||
debug.click(buttons.OK, wait=True)
|
debug.click(buttons.OK, wait=True)
|
||||||
|
|
||||||
|
|
||||||
def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None:
|
def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None:
|
||||||
layout = debug.read_layout()
|
assert "NumberInputDialog" in debug.read_layout().all_components()
|
||||||
assert "NumberInputDialog" in layout.text
|
|
||||||
for _ in range(diff):
|
for _ in range(diff):
|
||||||
debug.click(button, wait=False)
|
debug.click(button)
|
||||||
debug.click(buttons.OK, wait=True)
|
debug.click(buttons.OK, wait=True)
|
||||||
|
|
||||||
|
|
||||||
def read_words(debug: "DebugLink", is_advanced: bool = False) -> list[str]:
|
def read_words(
|
||||||
|
debug: "DebugLink", backup_type: messages.BackupType, do_htc: bool = True
|
||||||
|
) -> list[str]:
|
||||||
words: list[str] = []
|
words: list[str] = []
|
||||||
layout = debug.read_layout()
|
layout = debug.read_layout()
|
||||||
if is_advanced:
|
|
||||||
assert layout.get_title().startswith("GROUP")
|
if backup_type == messages.BackupType.Slip39_Advanced:
|
||||||
|
assert layout.title().startswith("GROUP")
|
||||||
|
elif backup_type == messages.BackupType.Slip39_Basic:
|
||||||
|
assert layout.title().startswith("RECOVERY SHARE #")
|
||||||
else:
|
else:
|
||||||
assert layout.get_title().startswith("RECOVERY SHARE #")
|
assert layout.title() == "RECOVERY SEED"
|
||||||
|
|
||||||
# Swiping through all the page and loading the words
|
# Swiping through all the page and loading the words
|
||||||
for _ in range(layout.get_page_count() - 1):
|
for _ in range(layout.page_count() - 1):
|
||||||
words.extend(layout.get_seed_words())
|
words.extend(layout.seed_words())
|
||||||
layout = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True)
|
layout = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True)
|
||||||
words.extend(layout.get_seed_words())
|
assert layout is not None
|
||||||
|
words.extend(layout.seed_words())
|
||||||
|
|
||||||
debug.press_yes()
|
# There is hold-to-confirm button
|
||||||
|
if do_htc:
|
||||||
|
debug.click_hold(buttons.OK, hold_ms=1500)
|
||||||
|
else:
|
||||||
|
# It would take a very long time to test 16-of-16 with doing 1500 ms HTC after
|
||||||
|
# each word set
|
||||||
|
debug.press_yes()
|
||||||
|
|
||||||
return words
|
return words
|
||||||
|
|
||||||
|
|
||||||
def confirm_words(debug: "DebugLink", words: list[str]) -> None:
|
def confirm_words(debug: "DebugLink", words: list[str]) -> None:
|
||||||
layout = debug.wait_layout()
|
layout = debug.wait_layout()
|
||||||
assert "Select word" in layout.text
|
assert "Select word" in layout.text_content()
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
# "Select word 3 of 20"
|
# "Select word 3 of 20"
|
||||||
# ^
|
# ^
|
||||||
word_pos = int(layout.get_content().split()[2])
|
word_pos = int(layout.text_content().split()[2])
|
||||||
# Unifying both the buttons and words to lowercase
|
# Unifying both the buttons and words to lowercase
|
||||||
btn_texts = [text.lower() for text in layout.get_button_texts()]
|
btn_texts = [text.lower() for text in layout.button_contents()]
|
||||||
wanted_word = words[word_pos - 1].lower()
|
wanted_word = words[word_pos - 1].lower()
|
||||||
button_pos = btn_texts.index(wanted_word)
|
button_pos = btn_texts.index(wanted_word)
|
||||||
layout = debug.click(buttons.RESET_WORD_CHECK[button_pos], wait=True)
|
layout = debug.click(buttons.RESET_WORD_CHECK[button_pos], wait=True)
|
||||||
|
@ -31,6 +31,7 @@ from . import recovery
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..device_handler import BackgroundDeviceHandler
|
from ..device_handler import BackgroundDeviceHandler
|
||||||
|
from trezorlib.debuglink import DebugLink, LayoutContent
|
||||||
|
|
||||||
TX_CACHE_MAINNET = TxCache("Bitcoin")
|
TX_CACHE_MAINNET = TxCache("Bitcoin")
|
||||||
TX_CACHE_TESTNET = TxCache("Testnet")
|
TX_CACHE_TESTNET = TxCache("Testnet")
|
||||||
@ -48,26 +49,24 @@ TXHASH_d5f65e = bytes.fromhex(
|
|||||||
PIN4 = "1234"
|
PIN4 = "1234"
|
||||||
|
|
||||||
WORDS_20 = buttons.grid34(2, 2)
|
WORDS_20 = buttons.grid34(2, 2)
|
||||||
|
CENTER_BUTTON = buttons.grid35(1, 2)
|
||||||
|
|
||||||
|
|
||||||
def set_autolock_delay(device_handler: "BackgroundDeviceHandler", delay_ms: int):
|
def set_autolock_delay(device_handler: "BackgroundDeviceHandler", delay_ms: int):
|
||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
device_handler.run(device.apply_settings, auto_lock_delay_ms=delay_ms)
|
device_handler.run(device.apply_settings, auto_lock_delay_ms=delay_ms) # type: ignore
|
||||||
|
|
||||||
|
assert debug.wait_layout().main_component() == "PinKeyboard"
|
||||||
|
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert layout.text == "< PinKeyboard >"
|
|
||||||
debug.input("1234")
|
debug.input("1234")
|
||||||
|
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert (
|
assert (
|
||||||
f"auto-lock your device after {delay_ms // 1000} seconds"
|
f"auto-lock your device after {delay_ms // 1000} seconds"
|
||||||
in layout.get_content()
|
in debug.wait_layout().text_content()
|
||||||
)
|
)
|
||||||
debug.click(buttons.OK)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
|
assert layout.main_component() == "Homescreen"
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert layout.text.startswith("< Homescreen")
|
|
||||||
assert device_handler.result() == "Settings applied"
|
assert device_handler.result() == "Settings applied"
|
||||||
|
|
||||||
|
|
||||||
@ -92,17 +91,16 @@ def test_autolock_interrupts_signing(device_handler: "BackgroundDeviceHandler"):
|
|||||||
script_type=messages.OutputScriptType.PAYTOADDRESS,
|
script_type=messages.OutputScriptType.PAYTOADDRESS,
|
||||||
)
|
)
|
||||||
|
|
||||||
device_handler.run(
|
device_handler.run(btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE_MAINNET) # type: ignore
|
||||||
btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE_MAINNET
|
|
||||||
|
assert (
|
||||||
|
"1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1"
|
||||||
|
in debug.wait_layout().text_content().replace(" ", "")
|
||||||
)
|
)
|
||||||
|
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.get_content().replace(" ", "")
|
|
||||||
|
|
||||||
debug.click(buttons.OK, wait=True)
|
debug.click(buttons.OK, wait=True)
|
||||||
|
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
assert "Total amount: 0.0039 BTC" in layout.get_content()
|
assert "Total amount: 0.0039 BTC" in layout.text_content()
|
||||||
|
|
||||||
# wait for autolock to kick in
|
# wait for autolock to kick in
|
||||||
time.sleep(10.1)
|
time.sleep(10.1)
|
||||||
@ -135,13 +133,15 @@ def test_autolock_does_not_interrupt_signing(device_handler: "BackgroundDeviceHa
|
|||||||
btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE_MAINNET
|
btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE_MAINNET
|
||||||
)
|
)
|
||||||
|
|
||||||
layout = debug.wait_layout()
|
assert (
|
||||||
assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.get_content().replace(" ", "")
|
"1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1"
|
||||||
|
in debug.wait_layout().text_content().replace(" ", "")
|
||||||
|
)
|
||||||
|
|
||||||
debug.click(buttons.OK, wait=True)
|
debug.click(buttons.OK, wait=True)
|
||||||
|
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
assert "Total amount: 0.0039 BTC" in layout.get_content()
|
assert "Total amount: 0.0039 BTC" in layout.text_content()
|
||||||
|
|
||||||
def sleepy_filter(msg: MessageType) -> MessageType:
|
def sleepy_filter(msg: MessageType) -> MessageType:
|
||||||
time.sleep(10.1)
|
time.sleep(10.1)
|
||||||
@ -166,18 +166,18 @@ def test_autolock_passphrase_keyboard(device_handler: "BackgroundDeviceHandler")
|
|||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
# get address
|
# get address
|
||||||
device_handler.run(common.get_test_address)
|
device_handler.run(common.get_test_address) # type: ignore
|
||||||
|
|
||||||
|
assert debug.wait_layout().main_component() == "PassphraseKeyboard"
|
||||||
|
|
||||||
# enter passphrase - slowly
|
# enter passphrase - slowly
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert layout.text == "< PassphraseKeyboard >"
|
|
||||||
|
|
||||||
CENTER_BUTTON = buttons.grid35(1, 2)
|
|
||||||
# keep clicking for long enough to trigger the autolock if it incorrectly ignored key presses
|
# keep clicking for long enough to trigger the autolock if it incorrectly ignored key presses
|
||||||
for _ in range(math.ceil(11 / 1.5)):
|
for _ in range(math.ceil(11 / 1.5)):
|
||||||
|
# click at "j"
|
||||||
debug.click(CENTER_BUTTON)
|
debug.click(CENTER_BUTTON)
|
||||||
time.sleep(1.5)
|
time.sleep(1.5)
|
||||||
|
|
||||||
|
# Confirm the passphrase
|
||||||
debug.click(buttons.OK, wait=True)
|
debug.click(buttons.OK, wait=True)
|
||||||
assert device_handler.result() == "mnF4yRWJXmzRB6EuBzuVigqeqTqirQupxJ"
|
assert device_handler.result() == "mnF4yRWJXmzRB6EuBzuVigqeqTqirQupxJ"
|
||||||
|
|
||||||
@ -188,13 +188,11 @@ def test_autolock_interrupts_passphrase(device_handler: "BackgroundDeviceHandler
|
|||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
# get address
|
# get address
|
||||||
device_handler.run(common.get_test_address)
|
device_handler.run(common.get_test_address) # type: ignore
|
||||||
|
|
||||||
|
assert debug.wait_layout().main_component() == "PassphraseKeyboard"
|
||||||
|
|
||||||
# enter passphrase - slowly
|
# enter passphrase - slowly
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert layout.text == "< PassphraseKeyboard >"
|
|
||||||
|
|
||||||
CENTER_BUTTON = buttons.grid35(1, 2)
|
|
||||||
# autolock must activate even if we pressed some buttons
|
# autolock must activate even if we pressed some buttons
|
||||||
for _ in range(math.ceil(6 / 1.5)):
|
for _ in range(math.ceil(6 / 1.5)):
|
||||||
debug.click(CENTER_BUTTON)
|
debug.click(CENTER_BUTTON)
|
||||||
@ -202,41 +200,51 @@ def test_autolock_interrupts_passphrase(device_handler: "BackgroundDeviceHandler
|
|||||||
|
|
||||||
# wait for autolock to kick in
|
# wait for autolock to kick in
|
||||||
time.sleep(10.1)
|
time.sleep(10.1)
|
||||||
layout = debug.wait_layout()
|
assert debug.wait_layout().main_component() == "Lockscreen"
|
||||||
assert layout.text.startswith("< Lockscreen")
|
|
||||||
with pytest.raises(exceptions.Cancelled):
|
with pytest.raises(exceptions.Cancelled):
|
||||||
device_handler.result()
|
device_handler.result()
|
||||||
|
|
||||||
|
|
||||||
|
def unlock_dry_run(debug: "DebugLink") -> "LayoutContent":
|
||||||
|
assert (
|
||||||
|
"Do you really want to check the recovery seed?"
|
||||||
|
in debug.wait_layout().text_content()
|
||||||
|
)
|
||||||
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
|
assert layout.main_component() == "PinKeyboard"
|
||||||
|
|
||||||
|
layout = debug.input(PIN4, wait=True)
|
||||||
|
assert layout is not None
|
||||||
|
return layout
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=PIN4)
|
@pytest.mark.setup_client(pin=PIN4)
|
||||||
def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandler"):
|
def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandler"):
|
||||||
set_autolock_delay(device_handler, 10_000)
|
set_autolock_delay(device_handler, 10_000)
|
||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
device_handler.run(device.recover, dry_run=True)
|
device_handler.run(device.recover, dry_run=True) # type: ignore
|
||||||
|
|
||||||
# unlock
|
layout = unlock_dry_run(debug)
|
||||||
layout = debug.wait_layout()
|
assert "select the number of words " in layout.text_content()
|
||||||
assert "Do you really want to check the recovery seed?" in layout.get_content()
|
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
|
||||||
assert layout.text == "< PinKeyboard >"
|
|
||||||
layout = debug.input(PIN4, wait=True)
|
|
||||||
assert "Select number of words " in layout.get_content()
|
|
||||||
|
|
||||||
# wait for autolock to trigger
|
# wait for autolock to trigger
|
||||||
time.sleep(10.1)
|
time.sleep(10.1)
|
||||||
layout = debug.wait_layout()
|
assert debug.wait_layout().main_component() == "Lockscreen"
|
||||||
assert layout.text.startswith("< Lockscreen")
|
|
||||||
with pytest.raises(exceptions.Cancelled):
|
with pytest.raises(exceptions.Cancelled):
|
||||||
device_handler.result()
|
device_handler.result()
|
||||||
|
|
||||||
# unlock
|
# unlock
|
||||||
|
debug.wait_layout(
|
||||||
|
wait_for_external_change=True
|
||||||
|
) # lockscreen triggered automatically
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
assert layout.text == "< PinKeyboard >"
|
assert layout.main_component() == "PinKeyboard"
|
||||||
layout = debug.input(PIN4, wait=True)
|
layout = debug.input(PIN4, wait=True)
|
||||||
|
assert layout is not None
|
||||||
|
|
||||||
# we are back at homescreen
|
# we are back at homescreen
|
||||||
assert "Select number of words" in layout.get_content()
|
assert "select the number of words" in layout.text_content()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=PIN4)
|
@pytest.mark.setup_client(pin=PIN4)
|
||||||
@ -244,24 +252,18 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"):
|
|||||||
set_autolock_delay(device_handler, 10_000)
|
set_autolock_delay(device_handler, 10_000)
|
||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
device_handler.run(device.recover, dry_run=True)
|
device_handler.run(device.recover, dry_run=True) # type: ignore
|
||||||
|
|
||||||
# unlock
|
unlock_dry_run(debug)
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert "Do you really want to check the recovery seed?" in layout.get_content()
|
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
|
||||||
assert layout.text == "< PinKeyboard >"
|
|
||||||
layout = debug.input(PIN4, wait=True)
|
|
||||||
|
|
||||||
# select 20 words
|
# select 20 words
|
||||||
recovery.select_number_of_words(debug, 20)
|
recovery.select_number_of_words(debug, 20)
|
||||||
|
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
|
assert layout.main_component() == "MnemonicKeyboard"
|
||||||
# make sure keyboard locks
|
# make sure keyboard locks
|
||||||
assert layout.text == "< MnemonicKeyboard >"
|
|
||||||
time.sleep(10.1)
|
time.sleep(10.1)
|
||||||
layout = debug.wait_layout()
|
assert debug.wait_layout().main_component() == "Lockscreen"
|
||||||
assert layout.text.startswith("< Lockscreen")
|
|
||||||
with pytest.raises(exceptions.Cancelled):
|
with pytest.raises(exceptions.Cancelled):
|
||||||
device_handler.result()
|
device_handler.result()
|
||||||
|
|
||||||
@ -271,27 +273,24 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"):
|
|||||||
set_autolock_delay(device_handler, 10_000)
|
set_autolock_delay(device_handler, 10_000)
|
||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
device_handler.run(device.recover, dry_run=True)
|
device_handler.run(device.recover, dry_run=True) # type: ignore
|
||||||
|
|
||||||
# unlock
|
unlock_dry_run(debug)
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert "Do you really want to check the recovery seed?" in layout.get_content()
|
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
|
||||||
assert layout.text == "< PinKeyboard >"
|
|
||||||
layout = debug.input(PIN4, wait=True)
|
|
||||||
|
|
||||||
# select 20 words
|
# select 20 words
|
||||||
recovery.select_number_of_words(debug, 20)
|
recovery.select_number_of_words(debug, 20)
|
||||||
|
|
||||||
layout = debug.click(buttons.OK, wait=True)
|
layout = debug.click(buttons.OK, wait=True)
|
||||||
|
assert layout.main_component() == "MnemonicKeyboard"
|
||||||
|
|
||||||
# type the word OCEAN slowly
|
# type the word OCEAN slowly
|
||||||
assert layout.text == "< MnemonicKeyboard >"
|
|
||||||
for coords in buttons.type_word("ocea", is_slip39=True):
|
for coords in buttons.type_word("ocea", is_slip39=True):
|
||||||
time.sleep(9)
|
time.sleep(9)
|
||||||
debug.click(coords)
|
debug.click(coords)
|
||||||
layout = debug.click(buttons.CONFIRM_WORD, wait=True)
|
layout = debug.click(buttons.CONFIRM_WORD, wait=True)
|
||||||
# should not have locked, even though we took 9 seconds to type each letter
|
# should not have locked, even though we took 9 seconds to type each letter
|
||||||
assert layout.text == "< MnemonicKeyboard >"
|
assert layout.main_component() == "MnemonicKeyboard"
|
||||||
|
|
||||||
device_handler.kill_task()
|
device_handler.kill_task()
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,36 +32,40 @@ PIN4 = "1234"
|
|||||||
def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
|
def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
|
||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
|
short_duration = 1000
|
||||||
|
lock_duration = 3500
|
||||||
|
|
||||||
def hold(duration: int, wait: bool = True) -> None:
|
def hold(duration: int, wait: bool = True) -> None:
|
||||||
debug.input(x=13, y=37, hold_ms=duration, wait=wait)
|
debug.input(x=13, y=37, hold_ms=duration, wait=wait)
|
||||||
time.sleep(duration / 1000 + 0.5)
|
|
||||||
|
|
||||||
assert device_handler.features().unlocked is False
|
assert device_handler.features().unlocked is False
|
||||||
|
|
||||||
# unlock with message
|
# unlock with message
|
||||||
device_handler.run(common.get_test_address)
|
device_handler.run(common.get_test_address)
|
||||||
layout = debug.wait_layout()
|
|
||||||
assert layout.text == "< PinKeyboard >"
|
assert debug.wait_layout().main_component() == "PinKeyboard"
|
||||||
debug.input("1234", wait=True)
|
debug.input("1234", wait=True)
|
||||||
assert device_handler.result()
|
assert device_handler.result()
|
||||||
|
|
||||||
assert device_handler.features().unlocked is True
|
assert device_handler.features().unlocked is True
|
||||||
|
|
||||||
# short touch
|
# short touch
|
||||||
hold(1000, wait=False)
|
hold(short_duration)
|
||||||
|
|
||||||
|
time.sleep(0.5) # so that the homescreen appears again (hacky)
|
||||||
assert device_handler.features().unlocked is True
|
assert device_handler.features().unlocked is True
|
||||||
|
|
||||||
# lock
|
# lock
|
||||||
hold(3500)
|
hold(lock_duration)
|
||||||
assert device_handler.features().unlocked is False
|
assert device_handler.features().unlocked is False
|
||||||
|
|
||||||
# unlock by touching
|
# unlock by touching
|
||||||
layout = debug.click(buttons.INFO, wait=True)
|
layout = debug.click(buttons.INFO, wait=True)
|
||||||
assert layout.text == "< PinKeyboard >"
|
assert layout.main_component() == "PinKeyboard"
|
||||||
debug.input("1234", wait=True)
|
debug.input("1234", wait=True)
|
||||||
|
|
||||||
assert device_handler.features().unlocked is True
|
assert device_handler.features().unlocked is True
|
||||||
|
|
||||||
# lock
|
# lock
|
||||||
hold(3500)
|
hold(lock_duration)
|
||||||
assert device_handler.features().unlocked is False
|
assert device_handler.features().unlocked is False
|
||||||
|
299
tests/click_tests/test_passphrase_tt.py
Normal file
299
tests/click_tests/test_passphrase_tt.py
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
# This file is part of the Trezor project.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2012-2023 SatoshiLabs and contributors
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3
|
||||||
|
# as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the License along with this library.
|
||||||
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
|
import time
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import TYPE_CHECKING, Generator, Optional
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .. import buttons
|
||||||
|
from ..common import get_test_address
|
||||||
|
from .common import CommonPass, PassphraseCategory, get_char_category
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..device_handler import BackgroundDeviceHandler
|
||||||
|
from trezorlib.debuglink import DebugLink
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skip_t1
|
||||||
|
|
||||||
|
# TODO: it is not possible to cancel the passphrase entry on TT
|
||||||
|
# NOTE: the prompt (underscoring) is not there when a space is entered
|
||||||
|
|
||||||
|
TT_CATEGORIES = [
|
||||||
|
PassphraseCategory.DIGITS,
|
||||||
|
PassphraseCategory.LOWERCASE,
|
||||||
|
PassphraseCategory.UPPERCASE,
|
||||||
|
PassphraseCategory.SPECIAL,
|
||||||
|
]
|
||||||
|
# TODO: better read this from the trace
|
||||||
|
TT_CATEGORY = PassphraseCategory.LOWERCASE
|
||||||
|
TT_COORDS_PREV: buttons.Coords = (0, 0)
|
||||||
|
|
||||||
|
# Testing the maximum length is really 50
|
||||||
|
# TODO: show some UI message when length reaches 50?
|
||||||
|
# (it currently disabled typing and greys out the buttons)
|
||||||
|
|
||||||
|
DA_50 = 25 * "da"
|
||||||
|
DA_50_ADDRESS = "mg5L2i8HZKUvceK1sfmGHhE4gichFSsdvm"
|
||||||
|
assert len(DA_50) == 50
|
||||||
|
|
||||||
|
DA_49 = DA_50[:-1]
|
||||||
|
DA_49_ADDRESS = "mxrB75ydMS3ZzqmYKK28fj4bNMEx7dDw6e"
|
||||||
|
assert len(DA_49) == 49
|
||||||
|
assert DA_49_ADDRESS != DA_50_ADDRESS
|
||||||
|
|
||||||
|
DA_51 = DA_50 + "d"
|
||||||
|
DA_51_ADDRESS = DA_50_ADDRESS
|
||||||
|
assert len(DA_51) == 51
|
||||||
|
assert DA_51_ADDRESS == DA_50_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def prepare_passphrase_dialogue(
|
||||||
|
device_handler: "BackgroundDeviceHandler", address: Optional[str] = None
|
||||||
|
) -> Generator["DebugLink", None, None]:
|
||||||
|
debug = device_handler.debuglink()
|
||||||
|
device_handler.run(get_test_address) # type: ignore
|
||||||
|
assert debug.wait_layout().main_component() == "PassphraseKeyboard"
|
||||||
|
|
||||||
|
# Resetting the category as it could have been changed by previous tests
|
||||||
|
global TT_CATEGORY
|
||||||
|
TT_CATEGORY = PassphraseCategory.LOWERCASE # type: ignore
|
||||||
|
|
||||||
|
yield debug
|
||||||
|
|
||||||
|
result = device_handler.result()
|
||||||
|
if address is not None:
|
||||||
|
assert result == address
|
||||||
|
|
||||||
|
|
||||||
|
def go_to_category(debug: "DebugLink", category: PassphraseCategory) -> None:
|
||||||
|
"""Go to a specific category"""
|
||||||
|
global TT_CATEGORY
|
||||||
|
global TT_COORDS_PREV
|
||||||
|
|
||||||
|
# Already there
|
||||||
|
if TT_CATEGORY == category:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_index = TT_CATEGORIES.index(TT_CATEGORY)
|
||||||
|
target_index = TT_CATEGORIES.index(category)
|
||||||
|
if target_index > current_index:
|
||||||
|
for _ in range(target_index - current_index):
|
||||||
|
debug.swipe_left(wait=True)
|
||||||
|
else:
|
||||||
|
for _ in range(current_index - target_index):
|
||||||
|
debug.swipe_right(wait=True)
|
||||||
|
TT_CATEGORY = category # type: ignore
|
||||||
|
# Category changed, reset coordinates
|
||||||
|
TT_COORDS_PREV = (0, 0) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def press_char(debug: "DebugLink", char: str) -> None:
|
||||||
|
"""Press a character"""
|
||||||
|
global TT_COORDS_PREV
|
||||||
|
|
||||||
|
# Space and couple others are a special case
|
||||||
|
if char in " *#":
|
||||||
|
char_category = PassphraseCategory.LOWERCASE
|
||||||
|
else:
|
||||||
|
char_category = get_char_category(char)
|
||||||
|
|
||||||
|
go_to_category(debug, char_category)
|
||||||
|
|
||||||
|
coords, amount = buttons.passphrase(char)
|
||||||
|
# If the button is the same as for the previous char,
|
||||||
|
# waiting a second before pressing it again.
|
||||||
|
# (not for a space)
|
||||||
|
if coords == TT_COORDS_PREV and char != " ":
|
||||||
|
time.sleep(1.1)
|
||||||
|
TT_COORDS_PREV = coords # type: ignore
|
||||||
|
for _ in range(amount):
|
||||||
|
debug.click(coords, wait=True)
|
||||||
|
|
||||||
|
|
||||||
|
def input_passphrase(debug: "DebugLink", passphrase: str, check: bool = True) -> None:
|
||||||
|
"""Input a passphrase with validation it got added"""
|
||||||
|
if check:
|
||||||
|
before = debug.read_layout().passphrase()
|
||||||
|
for char in passphrase:
|
||||||
|
press_char(debug, char)
|
||||||
|
if check:
|
||||||
|
after = debug.read_layout().passphrase()
|
||||||
|
assert after == before + passphrase
|
||||||
|
|
||||||
|
|
||||||
|
def enter_passphrase(debug: "DebugLink") -> None:
|
||||||
|
"""Enter a passphrase"""
|
||||||
|
coords = buttons.pin_passphrase_grid(11)
|
||||||
|
debug.click(coords, wait=True)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_char(debug: "DebugLink") -> None:
|
||||||
|
"""Deletes the last char"""
|
||||||
|
coords = buttons.pin_passphrase_grid(9)
|
||||||
|
debug.click(coords, wait=True)
|
||||||
|
|
||||||
|
|
||||||
|
VECTORS = ( # passphrase, address
|
||||||
|
(CommonPass.SHORT, CommonPass.SHORT_ADDRESS),
|
||||||
|
(CommonPass.WITH_SPACE, CommonPass.WITH_SPACE_ADDRESS),
|
||||||
|
(CommonPass.RANDOM_25, CommonPass.RANDOM_25_ADDRESS),
|
||||||
|
(DA_49, DA_49_ADDRESS),
|
||||||
|
(DA_50, DA_50_ADDRESS),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("passphrase, address", VECTORS)
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_input(
|
||||||
|
device_handler: "BackgroundDeviceHandler", passphrase: str, address: str
|
||||||
|
):
|
||||||
|
with prepare_passphrase_dialogue(device_handler, address) as debug:
|
||||||
|
input_passphrase(debug, passphrase)
|
||||||
|
enter_passphrase(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_input_over_50_chars(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare_passphrase_dialogue(device_handler, DA_51_ADDRESS) as debug: # type: ignore
|
||||||
|
input_passphrase(debug, DA_51, check=False)
|
||||||
|
assert debug.read_layout().passphrase() == DA_50
|
||||||
|
enter_passphrase(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_delete(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare_passphrase_dialogue(device_handler, CommonPass.SHORT_ADDRESS) as debug:
|
||||||
|
input_passphrase(debug, CommonPass.SHORT[:8])
|
||||||
|
|
||||||
|
for _ in range(4):
|
||||||
|
delete_char(debug)
|
||||||
|
debug.wait_layout()
|
||||||
|
|
||||||
|
input_passphrase(debug, CommonPass.SHORT[8 - 4 :])
|
||||||
|
enter_passphrase(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_delete_all(
|
||||||
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
):
|
||||||
|
with prepare_passphrase_dialogue(device_handler, CommonPass.EMPTY_ADDRESS) as debug:
|
||||||
|
passphrase = "trezor"
|
||||||
|
input_passphrase(debug, passphrase)
|
||||||
|
|
||||||
|
for _ in range(len(passphrase)):
|
||||||
|
delete_char(debug)
|
||||||
|
|
||||||
|
enter_passphrase(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_loop_all_characters(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare_passphrase_dialogue(device_handler, CommonPass.EMPTY_ADDRESS) as debug:
|
||||||
|
for category in (
|
||||||
|
PassphraseCategory.DIGITS,
|
||||||
|
PassphraseCategory.LOWERCASE,
|
||||||
|
PassphraseCategory.UPPERCASE,
|
||||||
|
PassphraseCategory.SPECIAL,
|
||||||
|
):
|
||||||
|
go_to_category(debug, category)
|
||||||
|
debug.wait_layout()
|
||||||
|
|
||||||
|
enter_passphrase(debug)
|
||||||
|
coords = buttons.pin_passphrase_grid(11)
|
||||||
|
debug.click(coords)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_click_same_button_many_times(
|
||||||
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
):
|
||||||
|
with prepare_passphrase_dialogue(device_handler) as debug:
|
||||||
|
a_coords, _ = buttons.passphrase("a")
|
||||||
|
for _ in range(10):
|
||||||
|
debug.click(a_coords)
|
||||||
|
|
||||||
|
enter_passphrase(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_prompt_disappears(
|
||||||
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
):
|
||||||
|
with prepare_passphrase_dialogue(device_handler) as debug:
|
||||||
|
input_passphrase(debug, "a")
|
||||||
|
|
||||||
|
# Wait a second for the prompt to disappear
|
||||||
|
time.sleep(1.1)
|
||||||
|
|
||||||
|
enter_passphrase(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_long_spaces_deletion(
|
||||||
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
):
|
||||||
|
with prepare_passphrase_dialogue(device_handler) as debug:
|
||||||
|
input_passphrase(
|
||||||
|
debug,
|
||||||
|
"a"
|
||||||
|
+ " " * 7
|
||||||
|
+ "b"
|
||||||
|
+ " " * 7
|
||||||
|
+ "c"
|
||||||
|
+ " " * 7
|
||||||
|
+ "d"
|
||||||
|
+ " " * 7
|
||||||
|
+ "e"
|
||||||
|
+ " " * 7
|
||||||
|
+ "f",
|
||||||
|
)
|
||||||
|
for _ in range(12):
|
||||||
|
delete_char(debug)
|
||||||
|
|
||||||
|
enter_passphrase(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_dollar_sign_deletion(
|
||||||
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
):
|
||||||
|
# Checks that dollar signs will not leave one pixel on the top after deleting
|
||||||
|
# (was a bug previously)
|
||||||
|
with prepare_passphrase_dialogue(device_handler, CommonPass.EMPTY_ADDRESS) as debug:
|
||||||
|
passphrase = "$$ I want $$"
|
||||||
|
input_passphrase(debug, passphrase)
|
||||||
|
|
||||||
|
for _ in range(len(passphrase)):
|
||||||
|
delete_char(debug)
|
||||||
|
|
||||||
|
enter_passphrase(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_cycle_through_last_character(
|
||||||
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
):
|
||||||
|
# Checks that we can cycle through the last (50th) passphrase character
|
||||||
|
# (was a bug previously)
|
||||||
|
with prepare_passphrase_dialogue(device_handler) as debug:
|
||||||
|
passphrase = DA_49 + "i" # for i we need to cycle through "ghi" three times
|
||||||
|
input_passphrase(debug, passphrase)
|
||||||
|
enter_passphrase(debug)
|
274
tests/click_tests/test_pin.py
Normal file
274
tests/click_tests/test_pin.py
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
# This file is part of the Trezor project.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2012-2023 SatoshiLabs and contributors
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3
|
||||||
|
# as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the License along with this library.
|
||||||
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING, Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from trezorlib import device, exceptions
|
||||||
|
|
||||||
|
from .. import buttons
|
||||||
|
from .common import go_back, go_next
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..device_handler import BackgroundDeviceHandler
|
||||||
|
from trezorlib.debuglink import DebugLink
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skip_t1
|
||||||
|
|
||||||
|
PIN_CANCELLED = pytest.raises(exceptions.TrezorFailure, match="PIN entry cancelled")
|
||||||
|
PIN_INVALID = pytest.raises(exceptions.TrezorFailure, match="PIN invalid")
|
||||||
|
|
||||||
|
PIN4 = "1234"
|
||||||
|
PIN24 = "875163065288639289952973"
|
||||||
|
PIN50 = "31415926535897932384626433832795028841971693993751"
|
||||||
|
PIN60 = PIN50 + "9" * 10
|
||||||
|
|
||||||
|
TR_PIN_ACTIONS = [
|
||||||
|
"DELETE",
|
||||||
|
"SHOW",
|
||||||
|
"ENTER",
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
"9",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Situation(Enum):
|
||||||
|
PIN_INPUT = 1
|
||||||
|
PIN_SETUP = 2
|
||||||
|
PIN_CHANGE = 3
|
||||||
|
WIPE_CODE_SETUP = 4
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def prepare(
|
||||||
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
situation: Situation = Situation.PIN_INPUT,
|
||||||
|
old_pin: str = "",
|
||||||
|
) -> Generator["DebugLink", None, None]:
|
||||||
|
debug = device_handler.debuglink()
|
||||||
|
# So that the digit order is the same. Needed for UI tests.
|
||||||
|
# Even though it should be done in conftest::client fixture (used by device_handler),
|
||||||
|
# without reseeding "again", the results are still random.
|
||||||
|
debug.reseed(0)
|
||||||
|
|
||||||
|
# Setup according to the wanted situation
|
||||||
|
if situation == Situation.PIN_INPUT:
|
||||||
|
# Any action triggering the PIN dialogue
|
||||||
|
device_handler.run(device.apply_settings, auto_lock_delay_ms=300_000) # type: ignore
|
||||||
|
elif situation == Situation.PIN_SETUP:
|
||||||
|
# Set new PIN
|
||||||
|
device_handler.run(device.change_pin) # type: ignore
|
||||||
|
assert "enable PIN protection" in debug.wait_layout().text_content()
|
||||||
|
layout = go_next(debug, wait=True)
|
||||||
|
assert "access this device" in layout.text_content()
|
||||||
|
go_next(debug)
|
||||||
|
elif situation == Situation.PIN_CHANGE:
|
||||||
|
# Change PIN
|
||||||
|
device_handler.run(device.change_pin) # type: ignore
|
||||||
|
_input_see_confirm(debug, old_pin)
|
||||||
|
assert "change your PIN" in debug.read_layout().text_content()
|
||||||
|
go_next(debug, wait=True)
|
||||||
|
_input_see_confirm(debug, old_pin)
|
||||||
|
elif situation == Situation.WIPE_CODE_SETUP:
|
||||||
|
# Set wipe code
|
||||||
|
device_handler.run(device.change_wipe_code) # type: ignore
|
||||||
|
if old_pin:
|
||||||
|
_input_see_confirm(debug, old_pin)
|
||||||
|
assert "enable wipe code" in debug.wait_layout().text_content()
|
||||||
|
layout = go_next(debug, wait=True)
|
||||||
|
assert "erase all data" in layout.text_content()
|
||||||
|
go_next(debug)
|
||||||
|
if old_pin:
|
||||||
|
debug.wait_layout()
|
||||||
|
_input_see_confirm(debug, old_pin)
|
||||||
|
|
||||||
|
debug.wait_layout()
|
||||||
|
_assert_pin_entry(debug)
|
||||||
|
yield debug
|
||||||
|
go_next(debug)
|
||||||
|
device_handler.result()
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_pin_entry(debug: "DebugLink") -> None:
|
||||||
|
assert debug.read_layout().main_component() == "PinKeyboard"
|
||||||
|
|
||||||
|
|
||||||
|
def _input_pin(debug: "DebugLink", pin: str, check: bool = False) -> None:
|
||||||
|
"""Input the PIN"""
|
||||||
|
before = debug.read_layout().pin()
|
||||||
|
|
||||||
|
digits_order = debug.read_layout().tt_pin_digits_order()
|
||||||
|
for digit in pin:
|
||||||
|
digit_index = digits_order.index(digit)
|
||||||
|
coords = buttons.pin_passphrase_index(digit_index)
|
||||||
|
debug.click(coords, wait=True)
|
||||||
|
|
||||||
|
if check:
|
||||||
|
after = debug.read_layout().pin()
|
||||||
|
assert before + pin == after
|
||||||
|
|
||||||
|
|
||||||
|
def _see_pin(debug: "DebugLink") -> None:
|
||||||
|
"""Navigate to "SHOW" and press it"""
|
||||||
|
debug.click(buttons.TOP_ROW, wait=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_pin(debug: "DebugLink", digits_to_delete: int, check: bool = True) -> None:
|
||||||
|
"""Navigate to "DELETE" and press it how many times requested"""
|
||||||
|
if check:
|
||||||
|
before = debug.read_layout().pin()
|
||||||
|
|
||||||
|
for _ in range(digits_to_delete):
|
||||||
|
debug.click(buttons.pin_passphrase_grid(9), wait=True)
|
||||||
|
|
||||||
|
if check:
|
||||||
|
after = debug.read_layout().pin()
|
||||||
|
assert before[:-digits_to_delete] == after
|
||||||
|
|
||||||
|
|
||||||
|
def _cancel_pin(debug: "DebugLink") -> None:
|
||||||
|
"""Navigate to "CANCEL" and press it"""
|
||||||
|
# It is the same button as DELETE
|
||||||
|
_delete_pin(debug, 1, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _confirm_pin(debug: "DebugLink") -> None:
|
||||||
|
"""Navigate to "ENTER" and press it"""
|
||||||
|
debug.click(buttons.pin_passphrase_grid(11), wait=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _input_see_confirm(debug: "DebugLink", pin: str) -> None:
|
||||||
|
_input_pin(debug, pin)
|
||||||
|
_see_pin(debug)
|
||||||
|
_confirm_pin(debug)
|
||||||
|
|
||||||
|
|
||||||
|
def _enter_two_times(debug: "DebugLink", pin1: str, pin2: str) -> None:
|
||||||
|
_input_see_confirm(debug, pin1)
|
||||||
|
# Please re-enter
|
||||||
|
debug.click(buttons.OK, wait=True)
|
||||||
|
_input_see_confirm(debug, pin2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin=PIN4)
|
||||||
|
def test_pin_short(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler) as debug:
|
||||||
|
_input_see_confirm(debug, PIN4)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin=PIN24)
|
||||||
|
def test_pin_long(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler) as debug:
|
||||||
|
_input_see_confirm(debug, PIN24)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin=PIN24)
|
||||||
|
def test_pin_long_delete(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler) as debug:
|
||||||
|
_input_pin(debug, PIN24)
|
||||||
|
_see_pin(debug)
|
||||||
|
|
||||||
|
_delete_pin(debug, 10)
|
||||||
|
_see_pin(debug)
|
||||||
|
|
||||||
|
_input_see_confirm(debug, PIN24[-10:])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin=PIN60[:50])
|
||||||
|
def test_pin_longer_than_max(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler) as debug:
|
||||||
|
_input_pin(debug, PIN60, check=False)
|
||||||
|
|
||||||
|
# What is over 50 digits was not entered
|
||||||
|
# TODO: do some UI change when limit is reached?
|
||||||
|
assert debug.read_layout().pin() == PIN60[:50]
|
||||||
|
|
||||||
|
_see_pin(debug)
|
||||||
|
_confirm_pin(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin=PIN4)
|
||||||
|
def test_pin_incorrect(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler) as debug:
|
||||||
|
_input_see_confirm(debug, "1235")
|
||||||
|
# debug.wait_layout()
|
||||||
|
_input_see_confirm(debug, PIN4)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin=PIN4)
|
||||||
|
def test_pin_cancel(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with PIN_CANCELLED, prepare(device_handler) as debug:
|
||||||
|
_input_pin(debug, PIN4)
|
||||||
|
_see_pin(debug)
|
||||||
|
_delete_pin(debug, len(PIN4))
|
||||||
|
_see_pin(debug)
|
||||||
|
_cancel_pin(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client()
|
||||||
|
def test_pin_setup(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler, Situation.PIN_SETUP) as debug:
|
||||||
|
_enter_two_times(debug, PIN4, PIN4)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client()
|
||||||
|
def test_pin_setup_mismatch(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with PIN_CANCELLED, prepare(device_handler, Situation.PIN_SETUP) as debug:
|
||||||
|
_enter_two_times(debug, "1", "2")
|
||||||
|
go_next(debug)
|
||||||
|
_cancel_pin(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin="1")
|
||||||
|
def test_pin_change(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler, Situation.PIN_CHANGE, old_pin="1") as debug:
|
||||||
|
_enter_two_times(debug, "2", "2")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin="1")
|
||||||
|
def test_wipe_code_setup(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler, Situation.WIPE_CODE_SETUP, old_pin="1") as debug:
|
||||||
|
_enter_two_times(debug, "2", "2")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(pin="1")
|
||||||
|
def test_wipe_code_same_as_pin(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler, Situation.WIPE_CODE_SETUP, old_pin="1") as debug:
|
||||||
|
_input_see_confirm(debug, "1")
|
||||||
|
# Try again
|
||||||
|
go_next(debug, wait=True)
|
||||||
|
_enter_two_times(debug, "2", "2")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client()
|
||||||
|
def test_pin_same_as_wipe_code(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare(device_handler, Situation.WIPE_CODE_SETUP) as debug:
|
||||||
|
_enter_two_times(debug, "1", "1")
|
||||||
|
with PIN_INVALID, prepare(device_handler, Situation.PIN_SETUP) as debug:
|
||||||
|
_enter_two_times(debug, "1", "1")
|
||||||
|
go_back(debug)
|
@ -14,35 +14,56 @@
|
|||||||
# You should have received a copy of the License along with this library.
|
# You should have received a copy of the License along with this library.
|
||||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from contextlib import contextmanager
|
||||||
|
from typing import TYPE_CHECKING, Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import device, messages
|
from trezorlib import device, messages
|
||||||
|
|
||||||
from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
|
from ..common import MNEMONIC12, MNEMONIC_SLIP39_BASIC_20_3of6
|
||||||
from . import recovery
|
from . import recovery
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..device_handler import BackgroundDeviceHandler
|
from ..device_handler import BackgroundDeviceHandler
|
||||||
|
from trezorlib.debuglink import DebugLink
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t1
|
pytestmark = [pytest.mark.skip_t1]
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
|
||||||
def test_recovery(device_handler: "BackgroundDeviceHandler"):
|
|
||||||
|
@contextmanager
|
||||||
|
def prepare_recovery_and_evaluate(
|
||||||
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
) -> Generator["DebugLink", None, None]:
|
||||||
features = device_handler.features()
|
features = device_handler.features()
|
||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
assert features.initialized is False
|
assert features.initialized is False
|
||||||
device_handler.run(device.recover, pin_protection=False)
|
device_handler.run(device.recover, pin_protection=False) # type: ignore
|
||||||
|
|
||||||
recovery.confirm_recovery(debug)
|
yield debug
|
||||||
|
|
||||||
recovery.select_number_of_words(debug)
|
|
||||||
recovery.enter_shares(debug, MNEMONIC_SLIP39_BASIC_20_3of6)
|
|
||||||
recovery.finalize(debug)
|
|
||||||
|
|
||||||
assert isinstance(device_handler.result(), messages.Success)
|
assert isinstance(device_handler.result(), messages.Success)
|
||||||
features = device_handler.features()
|
features = device_handler.features()
|
||||||
assert features.initialized is True
|
assert features.initialized is True
|
||||||
assert features.recovery_mode is False
|
assert features.recovery_mode is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
|
def test_recovery_slip39_basic(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare_recovery_and_evaluate(device_handler) as debug:
|
||||||
|
recovery.confirm_recovery(debug)
|
||||||
|
|
||||||
|
recovery.select_number_of_words(debug)
|
||||||
|
recovery.enter_shares(debug, MNEMONIC_SLIP39_BASIC_20_3of6)
|
||||||
|
recovery.finalize(debug)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
|
def test_recovery_bip39(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
with prepare_recovery_and_evaluate(device_handler) as debug:
|
||||||
|
recovery.confirm_recovery(debug)
|
||||||
|
|
||||||
|
recovery.select_number_of_words(debug, num_of_words=12)
|
||||||
|
recovery.enter_seed(debug, MNEMONIC12.split())
|
||||||
|
recovery.finalize(debug)
|
||||||
|
77
tests/click_tests/test_reset_bip39.py
Normal file
77
tests/click_tests/test_reset_bip39.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# This file is part of the Trezor project.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2012-2019 SatoshiLabs and contributors
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3
|
||||||
|
# as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the License along with this library.
|
||||||
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from trezorlib import device, messages
|
||||||
|
|
||||||
|
from ..common import WITH_MOCK_URANDOM
|
||||||
|
from . import reset
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..device_handler import BackgroundDeviceHandler
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = [pytest.mark.skip_t1]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
|
@WITH_MOCK_URANDOM
|
||||||
|
def test_reset_bip39(device_handler: "BackgroundDeviceHandler"):
|
||||||
|
features = device_handler.features()
|
||||||
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
|
assert features.initialized is False
|
||||||
|
|
||||||
|
device_handler.run(
|
||||||
|
device.reset,
|
||||||
|
strength=128,
|
||||||
|
backup_type=messages.BackupType.Bip39,
|
||||||
|
pin_protection=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# confirm new wallet
|
||||||
|
reset.confirm_new_wallet(debug)
|
||||||
|
|
||||||
|
# confirm back up
|
||||||
|
reset.confirm_read(debug, "Success")
|
||||||
|
|
||||||
|
# confirm backup warning (hold-to-confirm on TR)
|
||||||
|
reset.confirm_read(debug, "Caution", hold=True)
|
||||||
|
|
||||||
|
# read words
|
||||||
|
words = reset.read_words(debug, messages.BackupType.Bip39)
|
||||||
|
|
||||||
|
# confirm words
|
||||||
|
reset.confirm_words(debug, words)
|
||||||
|
|
||||||
|
# confirm backup done
|
||||||
|
reset.confirm_read(debug, "Success")
|
||||||
|
|
||||||
|
# Your backup is done
|
||||||
|
debug.press_yes()
|
||||||
|
|
||||||
|
# TODO: some validation of the generated secret?
|
||||||
|
|
||||||
|
assert device_handler.result() == "Initialized"
|
||||||
|
features = device_handler.features()
|
||||||
|
assert features.initialized is True
|
||||||
|
assert features.needs_backup is False
|
||||||
|
assert features.pin_protection is False
|
||||||
|
assert features.passphrase_protection is False
|
||||||
|
assert features.backup_type is messages.BackupType.Bip39
|
@ -15,30 +15,37 @@
|
|||||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import device, messages
|
from trezorlib import device, messages
|
||||||
|
|
||||||
from .. import buttons
|
from .. import buttons
|
||||||
from ..common import generate_entropy
|
from ..common import EXTERNAL_ENTROPY, WITH_MOCK_URANDOM, generate_entropy
|
||||||
from . import reset
|
from . import reset
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..device_handler import BackgroundDeviceHandler
|
from ..device_handler import BackgroundDeviceHandler
|
||||||
|
|
||||||
|
|
||||||
EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
|
pytestmark = [pytest.mark.skip_t1]
|
||||||
|
|
||||||
with_mock_urandom = mock.patch("os.urandom", mock.Mock(return_value=EXTERNAL_ENTROPY))
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t1
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
@with_mock_urandom
|
@pytest.mark.parametrize(
|
||||||
def test_reset_slip39_advanced_2of2groups_2of2shares(
|
"group_count, group_threshold, share_count, share_threshold",
|
||||||
|
[
|
||||||
|
pytest.param(2, 2, 2, 2, id="2of2"),
|
||||||
|
pytest.param(16, 16, 16, 16, id="16of16", marks=pytest.mark.slow),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@WITH_MOCK_URANDOM
|
||||||
|
def test_reset_slip39_advanced(
|
||||||
device_handler: "BackgroundDeviceHandler",
|
device_handler: "BackgroundDeviceHandler",
|
||||||
|
group_count: int,
|
||||||
|
group_threshold: int,
|
||||||
|
share_count: int,
|
||||||
|
share_threshold: int,
|
||||||
):
|
):
|
||||||
features = device_handler.features()
|
features = device_handler.features()
|
||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
@ -52,7 +59,7 @@ def test_reset_slip39_advanced_2of2groups_2of2shares(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# confirm new wallet
|
# confirm new wallet
|
||||||
reset.confirm_wait(debug, "Wallet creation")
|
reset.confirm_new_wallet(debug)
|
||||||
|
|
||||||
# confirm back up
|
# confirm back up
|
||||||
reset.confirm_read(debug, "Success")
|
reset.confirm_read(debug, "Success")
|
||||||
@ -60,119 +67,54 @@ def test_reset_slip39_advanced_2of2groups_2of2shares(
|
|||||||
# confirm checklist
|
# confirm checklist
|
||||||
reset.confirm_read(debug, "Checklist")
|
reset.confirm_read(debug, "Checklist")
|
||||||
|
|
||||||
# set num of groups
|
# set num of groups - default is 5
|
||||||
reset.set_selection(debug, buttons.RESET_MINUS, 3)
|
if group_count < 5:
|
||||||
|
reset.set_selection(debug, buttons.RESET_MINUS, 5 - group_count)
|
||||||
|
else:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, group_count - 5)
|
||||||
|
|
||||||
# confirm checklist
|
# confirm checklist
|
||||||
reset.confirm_read(debug, "Checklist")
|
reset.confirm_read(debug, "Checklist")
|
||||||
|
|
||||||
# set group threshold
|
# set group threshold
|
||||||
reset.set_selection(debug, buttons.RESET_MINUS, 0)
|
# TODO: could make it general as well
|
||||||
|
if group_count == 2 and group_threshold == 2:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, 0)
|
||||||
|
elif group_count == 16 and group_threshold == 16:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("not a supported combination")
|
||||||
|
|
||||||
# confirm checklist
|
# confirm checklist
|
||||||
reset.confirm_read(debug, "Checklist")
|
reset.confirm_read(debug, "Checklist")
|
||||||
|
|
||||||
# set share num and threshold for groups
|
# set share num and threshold for groups
|
||||||
for _ in range(2):
|
for _ in range(group_count):
|
||||||
# set num of shares
|
# set num of shares - default is 5
|
||||||
reset.set_selection(debug, buttons.RESET_MINUS, 3)
|
if share_count < 5:
|
||||||
|
reset.set_selection(debug, buttons.RESET_MINUS, 5 - share_count)
|
||||||
|
else:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, share_count - 5)
|
||||||
|
|
||||||
# set share threshold
|
# set share threshold
|
||||||
reset.set_selection(debug, buttons.RESET_MINUS, 0)
|
# TODO: could make it general as well
|
||||||
|
if share_count == 2 and share_threshold == 2:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, 0)
|
||||||
|
elif share_count == 16 and share_threshold == 16:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("not a supported combination")
|
||||||
|
|
||||||
# confirm backup warning
|
# confirm backup warning (hold-to-confirm on TR)
|
||||||
reset.confirm_read(debug, "Caution")
|
reset.confirm_read(debug, "Caution", hold=True)
|
||||||
|
|
||||||
all_words: list[str] = []
|
all_words: list[str] = []
|
||||||
for _ in range(2):
|
for _ in range(group_count):
|
||||||
for _ in range(2):
|
for _ in range(share_count):
|
||||||
# read words
|
# read words
|
||||||
words = reset.read_words(debug, True)
|
words = reset.read_words(
|
||||||
|
debug, messages.BackupType.Slip39_Advanced, do_htc=False
|
||||||
# confirm words
|
)
|
||||||
reset.confirm_words(debug, words)
|
|
||||||
|
|
||||||
# confirm share checked
|
|
||||||
reset.confirm_read(debug, "Success")
|
|
||||||
|
|
||||||
all_words.append(" ".join(words))
|
|
||||||
|
|
||||||
# confirm backup done
|
|
||||||
reset.confirm_read(debug, "Success")
|
|
||||||
|
|
||||||
# generate secret locally
|
|
||||||
internal_entropy = debug.state().reset_entropy
|
|
||||||
assert internal_entropy is not None
|
|
||||||
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
|
|
||||||
|
|
||||||
# validate that all combinations will result in the correct master secret
|
|
||||||
reset.validate_mnemonics(all_words, secret)
|
|
||||||
|
|
||||||
assert device_handler.result() == "Initialized"
|
|
||||||
|
|
||||||
features = device_handler.features()
|
|
||||||
assert features.initialized is True
|
|
||||||
assert features.needs_backup is False
|
|
||||||
assert features.pin_protection is False
|
|
||||||
assert features.passphrase_protection is False
|
|
||||||
assert features.backup_type is messages.BackupType.Slip39_Advanced
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t1
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
|
||||||
@pytest.mark.slow
|
|
||||||
@with_mock_urandom
|
|
||||||
def test_reset_slip39_advanced_16of16groups_16of16shares(
|
|
||||||
device_handler: "BackgroundDeviceHandler",
|
|
||||||
):
|
|
||||||
features = device_handler.features()
|
|
||||||
debug = device_handler.debuglink()
|
|
||||||
|
|
||||||
assert features.initialized is False
|
|
||||||
|
|
||||||
device_handler.run(
|
|
||||||
device.reset,
|
|
||||||
backup_type=messages.BackupType.Slip39_Advanced,
|
|
||||||
pin_protection=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# confirm new wallet
|
|
||||||
reset.confirm_wait(debug, "Wallet creation")
|
|
||||||
|
|
||||||
# confirm back up
|
|
||||||
reset.confirm_read(debug, "Success")
|
|
||||||
|
|
||||||
# confirm checklist
|
|
||||||
reset.confirm_read(debug, "Checklist")
|
|
||||||
|
|
||||||
# set num of groups
|
|
||||||
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
|
||||||
|
|
||||||
# confirm checklist
|
|
||||||
reset.confirm_read(debug, "Checklist")
|
|
||||||
|
|
||||||
# set group threshold
|
|
||||||
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
|
||||||
|
|
||||||
# confirm checklist
|
|
||||||
reset.confirm_read(debug, "Checklist")
|
|
||||||
|
|
||||||
# set share num and threshold for groups
|
|
||||||
for _ in range(16):
|
|
||||||
# set num of shares
|
|
||||||
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
|
||||||
|
|
||||||
# set share threshold
|
|
||||||
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
|
||||||
|
|
||||||
# confirm backup warning
|
|
||||||
reset.confirm_read(debug, "Caution")
|
|
||||||
|
|
||||||
all_words: list[str] = []
|
|
||||||
for _ in range(16):
|
|
||||||
for _ in range(16):
|
|
||||||
# read words
|
|
||||||
words = reset.read_words(debug, True)
|
|
||||||
|
|
||||||
# confirm words
|
# confirm words
|
||||||
reset.confirm_words(debug, words)
|
reset.confirm_words(debug, words)
|
||||||
|
@ -15,68 +15,83 @@
|
|||||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import device, messages
|
from trezorlib import device, messages
|
||||||
|
|
||||||
from .. import buttons
|
from .. import buttons
|
||||||
from ..common import generate_entropy
|
from ..common import EXTERNAL_ENTROPY, WITH_MOCK_URANDOM, generate_entropy
|
||||||
from . import reset
|
from . import reset
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..device_handler import BackgroundDeviceHandler
|
from ..device_handler import BackgroundDeviceHandler
|
||||||
|
|
||||||
|
|
||||||
EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
|
pytestmark = [pytest.mark.skip_t1]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t1
|
@pytest.mark.parametrize(
|
||||||
|
"num_of_shares, threshold",
|
||||||
|
[
|
||||||
|
pytest.param(1, 1, id="1of1"),
|
||||||
|
pytest.param(16, 16, id="16of16"),
|
||||||
|
],
|
||||||
|
)
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
def test_reset_slip39_basic_1of1(device_handler: "BackgroundDeviceHandler"):
|
@WITH_MOCK_URANDOM
|
||||||
|
def test_reset_slip39_basic(
|
||||||
|
device_handler: "BackgroundDeviceHandler", num_of_shares: int, threshold: int
|
||||||
|
):
|
||||||
features = device_handler.features()
|
features = device_handler.features()
|
||||||
debug = device_handler.debuglink()
|
debug = device_handler.debuglink()
|
||||||
|
|
||||||
assert features.initialized is False
|
assert features.initialized is False
|
||||||
|
|
||||||
os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY)
|
device_handler.run(
|
||||||
with mock.patch("os.urandom", os_urandom), device_handler:
|
device.reset,
|
||||||
device_handler.run(
|
strength=128,
|
||||||
device.reset,
|
backup_type=messages.BackupType.Slip39_Basic,
|
||||||
strength=128,
|
pin_protection=False,
|
||||||
backup_type=messages.BackupType.Slip39_Basic,
|
)
|
||||||
pin_protection=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# confirm new wallet
|
# confirm new wallet
|
||||||
reset.confirm_wait(debug, "Wallet creation")
|
reset.confirm_new_wallet(debug)
|
||||||
|
|
||||||
# confirm back up
|
# confirm back up
|
||||||
reset.confirm_read(debug, "Success")
|
reset.confirm_read(debug, "Success")
|
||||||
|
|
||||||
# confirm checklist
|
# confirm checklist
|
||||||
reset.confirm_read(debug, "Checklist")
|
reset.confirm_read(debug, "Checklist")
|
||||||
|
|
||||||
# set num of shares
|
# set num of shares - default is 5
|
||||||
# default is 5 so we press RESET_MINUS 4 times
|
if num_of_shares < 5:
|
||||||
reset.set_selection(debug, buttons.RESET_MINUS, 4)
|
reset.set_selection(debug, buttons.RESET_MINUS, 5 - num_of_shares)
|
||||||
|
else:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, num_of_shares - 5)
|
||||||
|
|
||||||
# confirm checklist
|
# confirm checklist
|
||||||
reset.confirm_read(debug, "Checklist")
|
reset.confirm_read(debug, "Checklist")
|
||||||
|
|
||||||
# set threshold
|
# set threshold
|
||||||
# threshold will default to 1
|
# TODO: could make it general as well
|
||||||
reset.set_selection(debug, buttons.RESET_MINUS, 0)
|
if num_of_shares == 1 and threshold == 1:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, 0)
|
||||||
|
elif num_of_shares == 16 and threshold == 16:
|
||||||
|
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("not a supported combination")
|
||||||
|
|
||||||
# confirm checklist
|
# confirm checklist
|
||||||
reset.confirm_read(debug, "Checklist")
|
reset.confirm_read(debug, "Checklist")
|
||||||
|
|
||||||
# confirm backup warning
|
# confirm backup warning (hold-to-confirm on TR)
|
||||||
reset.confirm_read(debug, "Caution")
|
reset.confirm_read(debug, "Caution", hold=True)
|
||||||
|
|
||||||
|
all_words: list[str] = []
|
||||||
|
for _ in range(num_of_shares):
|
||||||
# read words
|
# read words
|
||||||
words = reset.read_words(debug)
|
words = reset.read_words(debug, messages.BackupType.Slip39_Basic)
|
||||||
|
|
||||||
# confirm words
|
# confirm words
|
||||||
reset.confirm_words(debug, words)
|
reset.confirm_words(debug, words)
|
||||||
@ -84,98 +99,23 @@ def test_reset_slip39_basic_1of1(device_handler: "BackgroundDeviceHandler"):
|
|||||||
# confirm share checked
|
# confirm share checked
|
||||||
reset.confirm_read(debug, "Success")
|
reset.confirm_read(debug, "Success")
|
||||||
|
|
||||||
# confirm backup done
|
all_words.append(" ".join(words))
|
||||||
reset.confirm_read(debug, "Success")
|
|
||||||
|
|
||||||
# generate secret locally
|
# confirm backup done
|
||||||
internal_entropy = debug.state().reset_entropy
|
reset.confirm_read(debug, "Success")
|
||||||
assert internal_entropy is not None
|
|
||||||
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
|
|
||||||
|
|
||||||
# validate that all combinations will result in the correct master secret
|
# generate secret locally
|
||||||
validate = [" ".join(words)]
|
internal_entropy = debug.state().reset_entropy
|
||||||
reset.validate_mnemonics(validate, secret)
|
assert internal_entropy is not None
|
||||||
|
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
|
||||||
|
|
||||||
assert device_handler.result() == "Initialized"
|
# validate that all combinations will result in the correct master secret
|
||||||
features = device_handler.features()
|
reset.validate_mnemonics(all_words, secret)
|
||||||
assert features.initialized is True
|
|
||||||
assert features.needs_backup is False
|
|
||||||
assert features.pin_protection is False
|
|
||||||
assert features.passphrase_protection is False
|
|
||||||
assert features.backup_type is messages.BackupType.Slip39_Basic
|
|
||||||
|
|
||||||
|
assert device_handler.result() == "Initialized"
|
||||||
@pytest.mark.skip_t1
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
|
||||||
def test_reset_slip39_basic_16of16(device_handler: "BackgroundDeviceHandler"):
|
|
||||||
features = device_handler.features()
|
features = device_handler.features()
|
||||||
debug = device_handler.debuglink()
|
assert features.initialized is True
|
||||||
|
assert features.needs_backup is False
|
||||||
assert features.initialized is False
|
assert features.pin_protection is False
|
||||||
|
assert features.passphrase_protection is False
|
||||||
os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY)
|
assert features.backup_type is messages.BackupType.Slip39_Basic
|
||||||
with mock.patch("os.urandom", os_urandom), device_handler:
|
|
||||||
device_handler.run(
|
|
||||||
device.reset,
|
|
||||||
strength=128,
|
|
||||||
backup_type=messages.BackupType.Slip39_Basic,
|
|
||||||
pin_protection=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# confirm new wallet
|
|
||||||
reset.confirm_wait(debug, "Wallet creation")
|
|
||||||
|
|
||||||
# confirm back up
|
|
||||||
reset.confirm_read(debug, "Success")
|
|
||||||
|
|
||||||
# confirm checklist
|
|
||||||
reset.confirm_read(debug, "Checklist")
|
|
||||||
|
|
||||||
# set num of shares
|
|
||||||
# default is 5 so we add 11
|
|
||||||
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
|
||||||
|
|
||||||
# confirm checklist
|
|
||||||
reset.confirm_read(debug, "Checklist")
|
|
||||||
|
|
||||||
# set threshold
|
|
||||||
# default is 5 so we add 11
|
|
||||||
reset.set_selection(debug, buttons.RESET_PLUS, 11)
|
|
||||||
|
|
||||||
# confirm checklist
|
|
||||||
reset.confirm_read(debug, "Checklist")
|
|
||||||
|
|
||||||
# confirm backup warning
|
|
||||||
reset.confirm_read(debug, "Caution")
|
|
||||||
|
|
||||||
all_words: list[str] = []
|
|
||||||
for _ in range(16):
|
|
||||||
# read words
|
|
||||||
words = reset.read_words(debug)
|
|
||||||
|
|
||||||
# confirm words
|
|
||||||
reset.confirm_words(debug, words)
|
|
||||||
|
|
||||||
# confirm share checked
|
|
||||||
reset.confirm_read(debug, "Success")
|
|
||||||
|
|
||||||
all_words.append(" ".join(words))
|
|
||||||
|
|
||||||
# confirm backup done
|
|
||||||
reset.confirm_read(debug, "Success")
|
|
||||||
|
|
||||||
# generate secret locally
|
|
||||||
internal_entropy = debug.state().reset_entropy
|
|
||||||
assert internal_entropy is not None
|
|
||||||
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
|
|
||||||
|
|
||||||
# validate that all combinations will result in the correct master secret
|
|
||||||
reset.validate_mnemonics(all_words, secret)
|
|
||||||
|
|
||||||
assert device_handler.result() == "Initialized"
|
|
||||||
features = device_handler.features()
|
|
||||||
assert features.initialized is True
|
|
||||||
assert features.needs_backup is False
|
|
||||||
assert features.pin_protection is False
|
|
||||||
assert features.passphrase_protection is False
|
|
||||||
assert features.backup_type is messages.BackupType.Slip39_Basic
|
|
||||||
|
Loading…
Reference in New Issue
Block a user