1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-04 05:42:34 +00:00

tests: update, refactor and extend click tests

This commit is contained in:
grdddj 2023-03-30 15:54:45 +02:00
parent 846b21b07b
commit 58cb524be3
12 changed files with 1001 additions and 334 deletions

View File

@ -1,6 +1,8 @@
import time import time
from typing import Iterator, Tuple from typing import Iterator, Tuple
Coords = Tuple[int, int]
DISPLAY_WIDTH = 240 DISPLAY_WIDTH = 240
DISPLAY_HEIGHT = 240 DISPLAY_HEIGHT = 240
@ -22,7 +24,10 @@ OK = (RIGHT, BOTTOM)
CANCEL = (LEFT, BOTTOM) CANCEL = (LEFT, BOTTOM)
INFO = (MID, BOTTOM) INFO = (MID, BOTTOM)
CORNER_BUTTON = (215, 25)
CONFIRM_WORD = (MID, TOP) CONFIRM_WORD = (MID, TOP)
TOP_ROW = (MID, TOP)
RESET_MINUS = (LEFT, grid(DISPLAY_HEIGHT, 5, 1)) RESET_MINUS = (LEFT, grid(DISPLAY_HEIGHT, 5, 1))
RESET_PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 1)) RESET_PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 1))
@ -37,48 +42,89 @@ RESET_WORD_CHECK = [
BUTTON_LETTERS_BIP39 = ("abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz") BUTTON_LETTERS_BIP39 = ("abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz")
BUTTON_LETTERS_SLIP39 = ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz") BUTTON_LETTERS_SLIP39 = ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz")
# fmt: off
PASSPHRASE_LOWERCASE = (" ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz", "*#")
PASSPHRASE_UPPERCASE = (" ", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "*#")
PASSPHRASE_DIGITS = ("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
PASSPHRASE_SPECIAL = ("_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^=")
# fmt: on
def grid35(x: int, y: int) -> Tuple[int, int]:
def get_passphrase_choices(char: str) -> tuple[str, ...]:
if char in " *#":
return PASSPHRASE_LOWERCASE
if char.islower():
return PASSPHRASE_LOWERCASE
elif char.isupper():
return PASSPHRASE_UPPERCASE
elif char.isdigit():
return PASSPHRASE_DIGITS
else:
return PASSPHRASE_SPECIAL
def passphrase(char: str) -> Tuple[Coords, int]:
choices = get_passphrase_choices(char)
idx = next(i for i, letters in enumerate(choices) if char in letters)
click_amount = choices[idx].index(char) + 1
return pin_passphrase_index(idx), click_amount
def pin_passphrase_index(idx: int) -> Coords:
if idx == 9:
idx = 10 # last digit is in the middle
return pin_passphrase_grid(idx)
def pin_passphrase_grid(idx: int) -> Coords:
grid_x = idx % 3
grid_y = idx // 3 + 1 # first line is empty
return grid35(grid_x, grid_y)
def grid35(x: int, y: int) -> Coords:
return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 5, y) return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 5, y)
def grid34(x: int, y: int) -> Tuple[int, int]: def grid34(x: int, y: int) -> Coords:
return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 4, y) return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 4, y)
def _grid34_from_index(idx: int) -> Tuple[int, int]: def _grid34_from_index(idx: int) -> Coords:
grid_x = idx % 3 grid_x = idx % 3
grid_y = idx // 3 + 1 # first line is empty grid_y = idx // 3 + 1 # first line is empty
return grid34(grid_x, grid_y) return grid34(grid_x, grid_y)
def type_word(word: str, is_slip39: bool = False) -> Iterator[Tuple[int, int]]: def type_word(word: str, is_slip39: bool = False) -> Iterator[Coords]:
if is_slip39: if is_slip39:
yield from type_word_slip39(word) yield from _type_word_slip39(word)
else: else:
yield from type_word_bip39(word) yield from _type_word_bip39(word)
def type_word_slip39(word: str) -> Iterator[Tuple[int, int]]: def _type_word_slip39(word: str) -> Iterator[Coords]:
for l in word: for l in word:
idx = next(i for i, letters in enumerate(BUTTON_LETTERS_SLIP39) if l in letters) idx = next(i for i, letters in enumerate(BUTTON_LETTERS_SLIP39) if l in letters)
yield _grid34_from_index(idx) yield _grid34_from_index(idx)
def type_word_bip39(word: str) -> Iterator[Tuple[int, int]]: def _type_word_bip39(word: str) -> Iterator[Coords]:
coords_prev: Tuple[int, int] | None = None coords_prev: Coords | None = None
for letter in word: for letter in word:
coords, amount = letter_coords_and_amount(letter) time.sleep(0.1) # not being so quick to miss something
coords, amount = _letter_coords_and_amount(letter)
# If the button is the same as for the previous letter, # If the button is the same as for the previous letter,
# waiting a second before pressing it again. # waiting a second before pressing it again.
if coords == coords_prev: if coords == coords_prev:
time.sleep(1) time.sleep(1.1)
coords_prev = coords coords_prev = coords
for _ in range(amount): for _ in range(amount):
yield coords yield coords
def letter_coords_and_amount(letter: str) -> Tuple[Tuple[int, int], int]: def _letter_coords_and_amount(letter: str) -> Tuple[Coords, int]:
idx = next(i for i, letters in enumerate(BUTTON_LETTERS_BIP39) if letter in letters) idx = next(i for i, letters in enumerate(BUTTON_LETTERS_BIP39) if letter in letters)
click_amount = BUTTON_LETTERS_BIP39[idx].index(letter) + 1 click_amount = BUTTON_LETTERS_BIP39[idx].index(letter) + 1
return _grid34_from_index(idx), click_amount return _grid34_from_index(idx), click_amount

View File

@ -0,0 +1,48 @@
from enum import Enum
from typing import TYPE_CHECKING
from .. import buttons
if TYPE_CHECKING:
from trezorlib.debuglink import DebugLink
# 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) -> None:
debug.click(buttons.OK, wait=wait) # type: ignore
def go_back(debug: "DebugLink", wait: bool = False) -> None:
debug.click(buttons.CANCEL, wait=wait) # type: ignore

View File

@ -13,19 +13,15 @@ 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() layout = debug.wait_layout()
if legacy_ui: if legacy_ui:
assert layout.text.startswith("Recovery mode") layout.str_content.startswith("Recovery mode")
else: else:
assert layout.get_title().startswith("WALLET RECOVERY") assert layout.title().startswith("WALLET RECOVERY")
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
@ -35,13 +31,14 @@ def select_number_of_words(
layout = debug.read_layout() 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 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.str_content == "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 +48,11 @@ 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 num_of_words in (20, 33):
assert "Enter any share" in layout.str_content
else:
assert "enter your recovery seed" in layout.text_content()
def enter_share( def enter_share(
@ -60,9 +61,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.str_content == "Slip39Keyboard"
else: else:
assert layout.text == "< MnemonicKeyboard >" assert "MnemonicKeyboard" in layout.str_content
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 +76,27 @@ 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:
layout = debug.read_layout()
assert "enter" in layout.text_content()
layout = debug.click(buttons.OK, wait=True)
assert "MnemonicKeyboard" in layout.str_content
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 "Homescreen" in layout.str_content

View File

@ -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,47 +10,63 @@ 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() layout = debug.wait_layout()
assert title.upper() in layout.get_title() assert layout.title().startswith("WALLET CREATION")
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 assert "OK, I UNDERSTAND" in layout.str_content
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() layout = debug.read_layout()
assert "NumberInputDialog" in layout.text assert "NumberInputDialog" in layout.str_content
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) -> 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())
# It is hold-to-confirm
debug.press_yes() debug.press_yes()
return words return words
@ -58,13 +74,15 @@ def read_words(debug: "DebugLink", is_advanced: bool = False) -> list[str]:
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.buttons.tt_select_word_button_texts()
]
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)

View File

@ -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,29 @@ 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
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "< PinKeyboard >"
assert "PinKeyboard" in layout.str_content
debug.input("1234") debug.input("1234")
layout = debug.wait_layout() 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 layout.text_content()
) )
debug.click(buttons.OK) debug.click(buttons.OK)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text.startswith("< Homescreen") assert "Homescreen" in layout.str_content
assert device_handler.result() == "Settings applied" assert device_handler.result() == "Settings applied"
@ -92,21 +96,15 @@ 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
)
layout = debug.wait_layout() layout = debug.wait_layout()
assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.get_content().replace(" ", "") assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in 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()
# wait for autolock to kick in
time.sleep(10.1)
with pytest.raises(exceptions.Cancelled):
device_handler.result() device_handler.result()
@ -136,12 +134,12 @@ def test_autolock_does_not_interrupt_signing(device_handler: "BackgroundDeviceHa
) )
layout = debug.wait_layout() layout = debug.wait_layout()
assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.get_content().replace(" ", "") assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in 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 +164,19 @@ 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
# enter passphrase - slowly # enter passphrase - slowly
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "< PassphraseKeyboard >" assert "PassphraseKeyboard" in layout.str_content
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 +187,12 @@ 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
# enter passphrase - slowly # enter passphrase - slowly
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "< PassphraseKeyboard >" assert "PassphraseKeyboard" in layout.str_content
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)
@ -203,40 +201,47 @@ 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() layout = debug.wait_layout()
assert layout.text.startswith("< Lockscreen") assert "Lockscreen" in layout.str_content
with pytest.raises(exceptions.Cancelled): with pytest.raises(exceptions.Cancelled):
device_handler.result() device_handler.result()
def unlock_dry_run(debug: "DebugLink", wait_r: bool = True) -> "LayoutContent":
layout = debug.wait_layout()
assert "Do you really want to check the recovery seed?" in layout.text_content()
layout = debug.click(buttons.OK, wait=True)
assert "PinKeyboard" in layout.str_content
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() layout = debug.wait_layout()
assert layout.text.startswith("< Lockscreen") assert "Lockscreen" in layout.str_content
with pytest.raises(exceptions.Cancelled): with pytest.raises(exceptions.Cancelled):
device_handler.result() device_handler.result()
# unlock # unlock
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >" assert "PinKeyboard" in layout.str_content
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 +249,19 @@ 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 "MnemonicKeyboard" in layout.str_content
# make sure keyboard locks # make sure keyboard locks
assert layout.text == "< MnemonicKeyboard >"
time.sleep(10.1) time.sleep(10.1)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text.startswith("< Lockscreen") assert "Lockscreen" in layout.str_content
with pytest.raises(exceptions.Cancelled): with pytest.raises(exceptions.Cancelled):
device_handler.result() device_handler.result()
@ -271,27 +271,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 "MnemonicKeyboard" in layout.str_content
# 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 "MnemonicKeyboard" in layout.str_content
device_handler.kill_task() device_handler.kill_task()

View File

@ -32,6 +32,9 @@ 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) time.sleep(duration / 1000 + 0.5)
@ -41,27 +44,27 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
# unlock with message # unlock with message
device_handler.run(common.get_test_address) device_handler.run(common.get_test_address)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "< PinKeyboard >" assert "PinKeyboard" in layout.str_content
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, wait=False)
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 "PinKeyboard" in layout.str_content
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

View File

@ -0,0 +1,286 @@
# 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 (which does not get updated now)
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 = "mg5L2i8HZKUvceK1sfmGHhE4gichFSsdvm"
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
layout = debug.wait_layout()
assert "PassphraseKeyboard" in layout.str_content
# 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()
time.sleep(0.1)
else:
for _ in range(current_index - target_index):
debug.swipe_right()
time.sleep(0.1)
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):
time.sleep(0.05)
debug.click(coords)
def input_passphrase(debug: "DebugLink", passphrase: str) -> None:
"""Input a passphrase with validation it got added"""
for char in passphrase:
press_char(debug, char)
def enter_passphrase(debug: "DebugLink") -> None:
"""Enter a passphrase"""
coords = buttons.pin_passphrase_grid(11)
debug.click(coords)
def delete_char(debug: "DebugLink") -> None:
"""Deletes the last char"""
coords = buttons.pin_passphrase_grid(9)
debug.click(coords)
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)
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)
time.sleep(0.1)
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)
time.sleep(0.1)
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)
enter_passphrase(debug)
@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)
time.sleep(0.1)
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)
time.sleep(0.1)
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)
time.sleep(0.1)
enter_passphrase(debug)

View File

@ -0,0 +1,268 @@
# 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 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 random digits are always the same
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()
go_next(debug, wait=True)
go_next(debug)
elif situation == Situation.PIN_CHANGE:
# Change PIN
device_handler.run(device.change_pin) # type: ignore
debug.wait_layout()
_input_see_confirm(debug, old_pin)
layout = debug.wait_layout()
assert "change your PIN" in 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:
debug.wait_layout()
_input_see_confirm(debug, old_pin)
layout = debug.wait_layout()
else:
layout = debug.wait_layout()
assert "enable wipe code" in layout.text_content()
go_next(debug, wait=True)
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 "PinKeyboard" in debug.read_layout().str_content
def _input_pin(debug: "DebugLink", pin: str) -> None:
"""Input the PIN"""
order = debug.read_layout().buttons.tt_pin_digits_order()
for digit in pin:
digit_index = order.index(digit)
coords = buttons.pin_passphrase_index(digit_index)
debug.click(coords)
def _see_pin(debug: "DebugLink") -> None:
"""Navigate to "SHOW" and press it"""
debug.click(buttons.TOP_ROW)
def _delete_pin(debug: "DebugLink", digits_to_delete: int) -> None:
"""Navigate to "DELETE" and press it how many times requested"""
for _ in range(digits_to_delete):
debug.click(buttons.pin_passphrase_grid(9))
def _cancel_pin(debug: "DebugLink") -> None:
"""Navigate to "CANCEL" and press it"""
# It is the same button as DELETE
_delete_pin(debug, 1)
def _confirm_pin(debug: "DebugLink") -> None:
"""Navigate to "ENTER" and press it"""
debug.click(buttons.pin_passphrase_grid(11))
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, reenter_screen: bool = True
) -> None:
_input_see_confirm(debug, pin1)
debug.wait_layout()
if reenter_screen:
# 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)
_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", reenter_screen=False)
@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")
time.sleep(4) # popup
debug.wait_layout()
_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", reenter_screen=False)
with PIN_INVALID, prepare(device_handler, Situation.PIN_SETUP) as debug:
_enter_two_times(debug, "1", "1")
go_back(debug)

View File

@ -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
yield debug
assert isinstance(device_handler.result(), messages.Success)
features = device_handler.features()
assert features.initialized is True
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.confirm_recovery(debug)
recovery.select_number_of_words(debug) recovery.select_number_of_words(debug)
recovery.enter_shares(debug, MNEMONIC_SLIP39_BASIC_20_3of6) recovery.enter_shares(debug, MNEMONIC_SLIP39_BASIC_20_3of6)
recovery.finalize(debug) recovery.finalize(debug)
assert isinstance(device_handler.result(), messages.Success)
features = device_handler.features() @pytest.mark.setup_client(uninitialized=True)
assert features.initialized is True def test_recovery_bip39(device_handler: "BackgroundDeviceHandler"):
assert features.recovery_mode is False 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)

View 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

View File

@ -15,115 +15,44 @@
# 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
def test_reset_slip39_advanced_2of2groups_2of2shares( def test_reset_slip39_advanced_2of2groups_2of2shares(
device_handler: "BackgroundDeviceHandler", device_handler: "BackgroundDeviceHandler",
): ):
features = device_handler.features() _slip39_advanced_reset(device_handler, 2, 2, 2, 2)
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_MINUS, 3)
# confirm checklist
reset.confirm_read(debug, "Checklist")
# set group threshold
reset.set_selection(debug, buttons.RESET_MINUS, 0)
# confirm checklist
reset.confirm_read(debug, "Checklist")
# set share num and threshold for groups
for _ in range(2):
# set num of shares
reset.set_selection(debug, buttons.RESET_MINUS, 3)
# set share threshold
reset.set_selection(debug, buttons.RESET_MINUS, 0)
# confirm backup warning
reset.confirm_read(debug, "Caution")
all_words: list[str] = []
for _ in range(2):
for _ in range(2):
# read words
words = reset.read_words(debug, True)
# 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.setup_client(uninitialized=True)
@pytest.mark.slow @pytest.mark.slow
@with_mock_urandom
def test_reset_slip39_advanced_16of16groups_16of16shares( def test_reset_slip39_advanced_16of16groups_16of16shares(
device_handler: "BackgroundDeviceHandler", device_handler: "BackgroundDeviceHandler",
):
_slip39_advanced_reset(device_handler, 16, 16, 16, 16)
@WITH_MOCK_URANDOM
def _slip39_advanced_reset(
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()
@ -137,7 +66,7 @@ def test_reset_slip39_advanced_16of16groups_16of16shares(
) )
# 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")
@ -145,34 +74,52 @@ def test_reset_slip39_advanced_16of16groups_16of16shares(
# 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_PLUS, 11) 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
# 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) 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(16): for _ in range(group_count):
# set num of shares # set num of shares - default is 5
reset.set_selection(debug, buttons.RESET_PLUS, 11) 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
# 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) 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(16): for _ in range(group_count):
for _ in range(16): for _ in range(share_count):
# read words # read words
words = reset.read_words(debug, True) words = reset.read_words(debug, messages.BackupType.Slip39_Advanced)
# confirm words # confirm words
reset.confirm_words(debug, words) reset.confirm_words(debug, words)

View File

@ -15,106 +15,41 @@
# 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.setup_client(uninitialized=True) @pytest.mark.setup_client(uninitialized=True)
def test_reset_slip39_basic_1of1(device_handler: "BackgroundDeviceHandler"): def test_reset_slip39_basic_1of1(device_handler: "BackgroundDeviceHandler"):
features = device_handler.features() _slip39_basic_reset(device_handler, 1, 1)
debug = device_handler.debuglink()
assert features.initialized is False
os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY)
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 press RESET_MINUS 4 times
reset.set_selection(debug, buttons.RESET_MINUS, 4)
# confirm checklist
reset.confirm_read(debug, "Checklist")
# set threshold
# threshold will default to 1
reset.set_selection(debug, buttons.RESET_MINUS, 0)
# confirm checklist
reset.confirm_read(debug, "Checklist")
# confirm backup warning
reset.confirm_read(debug, "Caution")
# read words
words = reset.read_words(debug)
# confirm words
reset.confirm_words(debug, words)
# confirm share checked
reset.confirm_read(debug, "Success")
# 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
validate = [" ".join(words)]
reset.validate_mnemonics(validate, 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
@pytest.mark.skip_t1
@pytest.mark.setup_client(uninitialized=True) @pytest.mark.setup_client(uninitialized=True)
def test_reset_slip39_basic_16of16(device_handler: "BackgroundDeviceHandler"): def test_reset_slip39_basic_16of16(device_handler: "BackgroundDeviceHandler"):
_slip39_basic_reset(device_handler, 16, 16)
@WITH_MOCK_URANDOM
def _slip39_basic_reset(
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)
with mock.patch("os.urandom", os_urandom), device_handler:
device_handler.run( device_handler.run(
device.reset, device.reset,
strength=128, strength=128,
@ -123,7 +58,7 @@ def test_reset_slip39_basic_16of16(device_handler: "BackgroundDeviceHandler"):
) )
# 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")
@ -131,27 +66,34 @@ def test_reset_slip39_basic_16of16(device_handler: "BackgroundDeviceHandler"):
# 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 add 11 if num_of_shares < 5:
reset.set_selection(debug, buttons.RESET_PLUS, 11) 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
# default is 5 so we add 11 # TODO: could make it general as well
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) 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] = [] all_words: list[str] = []
for _ in range(16): 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)