1
0
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:
grdddj 2023-05-04 14:16:40 +02:00 committed by Martin Milata
parent 87c7e33198
commit 00b83d1dca
11 changed files with 989 additions and 351 deletions

View 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

View File

@ -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"

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,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)

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,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()

View File

@ -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

View 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)

View 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)

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
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)

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,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)

View File

@ -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