chore(tests): fix click, upgrade and persistence tests for new UI

pull/2643/head
grdddj 2 years ago committed by Martin Milata
parent e9a1bcc951
commit 5187be91fe

@ -9,7 +9,7 @@ from ..common import interact
from . import _RustLayout from . import _RustLayout
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Iterable, Callable, Any from typing import Iterable, Callable
from trezor.wire import GenericContext from trezor.wire import GenericContext
@ -42,18 +42,11 @@ async def request_word_count(ctx: GenericContext, dry_run: bool) -> int:
async def request_word( async def request_word(
ctx: GenericContext, word_index: int, word_count: int, is_slip39: bool ctx: GenericContext, word_index: int, word_count: int, is_slip39: bool
) -> str: ) -> str:
prompt = f"Type word {word_index + 1} of {word_count}:"
if is_slip39: if is_slip39:
keyboard: Any = _RustLayout( keyboard = _RustLayout(trezorui2.request_slip39(prompt=prompt))
trezorui2.request_bip39(
prompt=f"Type word {word_index + 1} of {word_count}:"
)
)
else: else:
keyboard = _RustLayout( keyboard = _RustLayout(trezorui2.request_bip39(prompt=prompt))
trezorui2.request_slip39(
prompt=f"Type word {word_index + 1} of {word_count}:"
)
)
word: str = await ctx.wait(keyboard) word: str = await ctx.wait(keyboard)
return word return word

@ -17,7 +17,7 @@ In the `trezor-firmware` checkout, in the root of the monorepo, install the envi
poetry install poetry install
``` ```
Switch to a shell inside theenvironment: Switch to a shell inside the environment:
```sh ```sh
poetry shell poetry shell

@ -1,3 +1,4 @@
import time
from typing import Iterator, Tuple from typing import Iterator, Tuple
DISPLAY_WIDTH = 240 DISPLAY_WIDTH = 240
@ -27,13 +28,14 @@ RESET_MINUS = (LEFT, grid(DISPLAY_HEIGHT, 5, 1))
RESET_PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 1)) RESET_PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 1))
RESET_WORD_CHECK = [ RESET_WORD_CHECK = [
(MID, grid(DISPLAY_HEIGHT, 6, 3)), (MID, grid(DISPLAY_HEIGHT, 5, 2)),
(MID, grid(DISPLAY_HEIGHT, 6, 4)), (MID, grid(DISPLAY_HEIGHT, 5, 3)),
(MID, grid(DISPLAY_HEIGHT, 6, 5)), (MID, grid(DISPLAY_HEIGHT, 5, 4)),
] ]
BUTTON_LETTERS = ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz") BUTTON_LETTERS_BIP39 = ("abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz")
BUTTON_LETTERS_SLIP39 = ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz")
def grid35(x: int, y: int) -> Tuple[int, int]: def grid35(x: int, y: int) -> Tuple[int, int]:
@ -44,9 +46,39 @@ def grid34(x: int, y: int) -> Tuple[int, int]:
return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 4, y) return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 4, y)
def type_word(word: str) -> Iterator[Tuple[int, int]]: def _grid34_from_index(idx: int) -> Tuple[int, int]:
grid_x = idx % 3
grid_y = idx // 3 + 1 # first line is empty
return grid34(grid_x, grid_y)
def type_word(word: str, is_slip39: bool = False) -> Iterator[Tuple[int, int]]:
if is_slip39:
yield from type_word_slip39(word)
else:
yield from type_word_bip39(word)
def type_word_slip39(word: str) -> Iterator[Tuple[int, int]]:
for l in word: for l in word:
idx = next(i for i, letters in enumerate(BUTTON_LETTERS) if l in letters) idx = next(i for i, letters in enumerate(BUTTON_LETTERS_SLIP39) if l in letters)
grid_x = idx % 3 yield _grid34_from_index(idx)
grid_y = idx // 3 + 1 # first line is empty
yield grid34(grid_x, grid_y)
def type_word_bip39(word: str) -> Iterator[Tuple[int, int]]:
coords_prev: Tuple[int, int] | None = None
for letter in word:
coords, amount = letter_coords_and_amount(letter)
# If the button is the same as for the previous letter,
# waiting a second before pressing it again.
if coords == coords_prev:
time.sleep(1)
coords_prev = coords
for _ in range(amount):
yield coords
def letter_coords_and_amount(letter: str) -> Tuple[Tuple[int, int], int]:
idx = next(i for i, letters in enumerate(BUTTON_LETTERS_BIP39) if letter in letters)
click_amount = BUTTON_LETTERS_BIP39[idx].index(letter) + 1
return _grid34_from_index(idx), click_amount

@ -1,26 +1,47 @@
from typing import TYPE_CHECKING
from .. import buttons from .. import buttons
if TYPE_CHECKING:
from trezorlib.debuglink import DebugLink, LayoutContent
def enter_word(debug, word): def enter_word(
word = word[:4] debug: "DebugLink", word: str, is_slip39: bool = False
for coords in buttons.type_word(word): ) -> "LayoutContent":
typed_word = word[:4]
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): def confirm_recovery(debug: "DebugLink", legacy_ui: bool = False) -> None:
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text.startswith("Recovery mode") if legacy_ui:
layout.text.startswith("Recovery mode")
else:
assert layout.get_title() == "RECOVERY MODE"
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
def select_number_of_words(debug, num_of_words=20): def select_number_of_words(
debug: "DebugLink", num_of_words: int = 20, legacy_ui: bool = False
) -> None:
layout = debug.read_layout() layout = debug.read_layout()
# select number of words # select number of words
assert "Select number of words" in layout.text assert "Select number of words" in layout.get_content()
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "WordSelector" if legacy_ui:
assert layout.text == "WordSelector"
else:
# Two title options
assert layout.get_title() in ("SEED CHECK", "RECOVERY MODE")
# click the number # click the number
word_option_offset = 6 word_option_offset = 6
@ -30,32 +51,38 @@ def select_number_of_words(debug, num_of_words=20):
) # 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.text assert "Enter any share" in layout.get_content()
def enter_share(debug, share: str): def enter_share(
debug: "DebugLink", share: str, legacy_ui: bool = False
) -> "LayoutContent":
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "Slip39Keyboard" if legacy_ui:
assert layout.text == "Slip39Keyboard"
else:
assert layout.text == "< MnemonicKeyboard >"
for word in share.split(" "): for word in share.split(" "):
layout = enter_word(debug, word) layout = enter_word(debug, word, is_slip39=True)
return layout return layout
def enter_shares(debug, shares: list): def enter_shares(debug: "DebugLink", shares: list[str]) -> None:
layout = debug.read_layout() layout = debug.read_layout()
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.text assert expected_text in layout.get_content()
layout = enter_share(debug, share) layout = enter_share(debug, share)
remaining -= 1 remaining -= 1
expected_text = f"RecoveryHomescreen {remaining} more" expected_text = f"{remaining} more share"
assert "You have successfully recovered your wallet" in layout.text assert "You have successfully recovered your wallet" in layout.get_content()
def finalize(debug): def finalize(debug: "DebugLink") -> None:
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "Homescreen" assert layout.text == "Homescreen"

@ -1,75 +1,76 @@
from typing import TYPE_CHECKING
from shamir_mnemonic import shamir from shamir_mnemonic import shamir
from trezorlib import messages from trezorlib import messages
from .. import buttons from .. import buttons
if TYPE_CHECKING:
from trezorlib.debuglink import DebugLink
def confirm_wait(debug, startswith): def confirm_wait(debug: "DebugLink", title: str) -> None:
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text.startswith(startswith) assert title.upper() in layout.get_title()
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
def confirm_read(debug, startswith): def confirm_read(debug: "DebugLink", title: str) -> None:
layout = debug.read_layout() layout = debug.read_layout()
assert layout.text.startswith(startswith) if title == "Caution":
assert "OK, I UNDERSTAND" in layout.text
elif title == "Success":
assert any(
text in layout.get_content() for text in ("success", "finished", "done")
)
else:
assert title.upper() in layout.get_title()
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
def set_selection(debug, button, diff): def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None:
layout = debug.read_layout() layout = debug.read_layout()
assert layout.text.startswith("Slip39NumInput") assert "NumberInputDialog" in layout.text
for _ in range(diff): for _ in range(diff):
debug.click(button, wait=False) debug.click(button, wait=False)
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
def read_words(debug, is_advanced=False): def read_words(debug: "DebugLink", is_advanced: bool = False) -> list[str]:
def read_word(line: str): words: list[str] = []
return line.split()[1]
words = []
layout = debug.read_layout() layout = debug.read_layout()
if is_advanced: if is_advanced:
assert layout.text.startswith("Group") assert layout.get_title().startswith("GROUP")
else: else:
assert layout.text.startswith("Recovery share") assert layout.get_title().startswith("RECOVERY SHARE #")
lines = layout.lines # Swiping through all the page and loading the words
# first screen for _ in range(layout.get_page_count() - 1):
words.append(read_word(lines[3])) words.extend(layout.get_seed_words())
words.append(read_word(lines[4])) layout = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True)
lines = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True).lines words.extend(layout.get_seed_words())
# screens 2 through
for _ in range(4):
words.append(read_word(lines[1]))
words.append(read_word(lines[2]))
words.append(read_word(lines[3]))
words.append(read_word(lines[4]))
lines = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True).lines
# final screen
words.append(read_word(lines[1]))
words.append(read_word(lines[2]))
debug.press_yes() debug.press_yes()
return words return words
def confirm_words(debug, words): 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
for _ in range(3): for _ in range(3):
# "Select word 3 of 20" # "Select word 3 of 20"
# ^ # ^
word_pos = int(layout.lines[1].split()[2]) word_pos = int(layout.get_content().split()[2])
button_pos = layout.lines.index(words[word_pos - 1]) - 2 # Unifying both the buttons and words to lowercase
btn_texts = [text.lower() for text in layout.get_button_texts()]
wanted_word = words[word_pos - 1].lower()
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)
def validate_mnemonics(mnemonics, expected_ems): def validate_mnemonics(mnemonics: list[str], expected_ems: bytes) -> None:
# We expect these combinations to recreate the secret properly # We expect these combinations to recreate the secret properly
# In case of click tests the mnemonics are always XofX so no need for combinations # In case of click tests the mnemonics are always XofX so no need for combinations
groups = shamir.decode_mnemonics(mnemonics) groups = shamir.decode_mnemonics(mnemonics)

@ -15,6 +15,7 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>. # If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time import time
from typing import TYPE_CHECKING
import pytest import pytest
@ -25,6 +26,9 @@ from .. import buttons, common
from ..tx_cache import TxCache from ..tx_cache import TxCache
from . import recovery from . import recovery
if TYPE_CHECKING:
from ..device_handler import BackgroundDeviceHandler
TX_CACHE = TxCache("Bitcoin") TX_CACHE = TxCache("Bitcoin")
TXHASH_d5f65e = bytes.fromhex( TXHASH_d5f65e = bytes.fromhex(
@ -36,17 +40,20 @@ PIN4 = "1234"
WORDS_20 = buttons.grid34(2, 2) WORDS_20 = buttons.grid34(2, 2)
def set_autolock_delay(device_handler, delay_ms): 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)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "PinDialog" assert layout.text == "< PinKeyboard >"
debug.input("1234") debug.input("1234")
layout = debug.wait_layout() layout = debug.wait_layout()
assert f"auto-lock your device after {delay_ms // 1000} seconds" in layout.text assert (
f"auto-lock your device after {delay_ms // 1000} seconds"
in layout.get_content()
)
debug.click(buttons.OK) debug.click(buttons.OK)
layout = debug.wait_layout() layout = debug.wait_layout()
@ -55,7 +62,7 @@ def set_autolock_delay(device_handler, delay_ms):
@pytest.mark.setup_client(pin=PIN4) @pytest.mark.setup_client(pin=PIN4)
def test_autolock_interrupts_signing(device_handler): def test_autolock_interrupts_signing(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()
@ -76,10 +83,13 @@ def test_autolock_interrupts_signing(device_handler):
device_handler.run(btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE) device_handler.run(btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE)
layout = debug.wait_layout() layout = debug.wait_layout()
assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.text.replace(" ", "") 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.text assert "Total amount: 0.0039 BTC" in layout.get_content()
# wait for autolock to kick in # wait for autolock to kick in
time.sleep(10.1) time.sleep(10.1)
@ -89,7 +99,7 @@ def test_autolock_interrupts_signing(device_handler):
@pytest.mark.xfail(reason="depends on #922") @pytest.mark.xfail(reason="depends on #922")
@pytest.mark.setup_client(pin=PIN4, passphrase=True) @pytest.mark.setup_client(pin=PIN4, passphrase=True)
def test_autolock_passphrase_keyboard(device_handler): def test_autolock_passphrase_keyboard(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()
@ -109,7 +119,7 @@ def test_autolock_passphrase_keyboard(device_handler):
@pytest.mark.setup_client(pin=PIN4) @pytest.mark.setup_client(pin=PIN4)
def test_dryrun_locks_at_number_of_words(device_handler): 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()
@ -117,11 +127,11 @@ def test_dryrun_locks_at_number_of_words(device_handler):
# unlock # unlock
layout = debug.wait_layout() layout = debug.wait_layout()
assert "Do you really want to check the recovery seed?" in layout.text assert "Do you really want to check the recovery seed?" in layout.get_content()
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "PinDialog" assert layout.text == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
assert "Select number of words " in layout.text 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)
@ -132,15 +142,15 @@ def test_dryrun_locks_at_number_of_words(device_handler):
# unlock # unlock
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "PinDialog" assert layout.text == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
# we are back at homescreen # we are back at homescreen
assert "Select number of words" in layout.text assert "Select number of words" in layout.get_content()
@pytest.mark.setup_client(pin=PIN4) @pytest.mark.setup_client(pin=PIN4)
def test_dryrun_locks_at_word_entry(device_handler): 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()
@ -148,9 +158,9 @@ def test_dryrun_locks_at_word_entry(device_handler):
# unlock # unlock
layout = debug.wait_layout() layout = debug.wait_layout()
assert "Do you really want to check the recovery seed?" in layout.text assert "Do you really want to check the recovery seed?" in layout.get_content()
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "PinDialog" assert layout.text == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
# select 20 words # select 20 words
@ -158,7 +168,7 @@ def test_dryrun_locks_at_word_entry(device_handler):
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
# make sure keyboard locks # make sure keyboard locks
assert layout.text == "Slip39Keyboard" assert layout.text == "< MnemonicKeyboard >"
time.sleep(10.1) time.sleep(10.1)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "Lockscreen" assert layout.text == "Lockscreen"
@ -167,7 +177,7 @@ def test_dryrun_locks_at_word_entry(device_handler):
@pytest.mark.setup_client(pin=PIN4) @pytest.mark.setup_client(pin=PIN4)
def test_dryrun_enter_word_slowly(device_handler): 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()
@ -175,9 +185,9 @@ def test_dryrun_enter_word_slowly(device_handler):
# unlock # unlock
layout = debug.wait_layout() layout = debug.wait_layout()
assert "Do you really want to check the recovery seed?" in layout.text assert "Do you really want to check the recovery seed?" in layout.get_content()
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "PinDialog" assert layout.text == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
# select 20 words # select 20 words
@ -185,11 +195,11 @@ def test_dryrun_enter_word_slowly(device_handler):
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
# type the word OCEAN slowly # type the word OCEAN slowly
assert layout.text == "Slip39Keyboard" assert layout.text == "< MnemonicKeyboard >"
for coords in buttons.type_word("ocea"): 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 == "Slip39Keyboard" assert layout.text == "< MnemonicKeyboard >"
device_handler.kill_task() device_handler.kill_task()

@ -15,19 +15,24 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>. # If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time import time
from typing import TYPE_CHECKING
import pytest import pytest
from .. import buttons, common from .. import buttons, common
if TYPE_CHECKING:
from ..device_handler import BackgroundDeviceHandler
PIN4 = "1234" PIN4 = "1234"
@pytest.mark.setup_client(pin=PIN4) @pytest.mark.setup_client(pin=PIN4)
def test_hold_to_lock(device_handler): def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
debug = device_handler.debuglink() debug = device_handler.debuglink()
def hold(duration, wait=True): 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)
@ -36,7 +41,7 @@ def test_hold_to_lock(device_handler):
# 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 == "PinDialog" assert layout.text == "< PinKeyboard >"
debug.input("1234", wait=True) debug.input("1234", wait=True)
assert device_handler.result() assert device_handler.result()
@ -52,7 +57,7 @@ def test_hold_to_lock(device_handler):
# unlock by touching # unlock by touching
layout = debug.click(buttons.INFO, wait=True) layout = debug.click(buttons.INFO, wait=True)
assert layout.text == "PinDialog" assert layout.text == "< 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

@ -14,6 +14,8 @@
# 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
import pytest import pytest
from trezorlib import device, messages from trezorlib import device, messages
@ -21,10 +23,13 @@ from trezorlib import device, messages
from ..common import MNEMONIC_SLIP39_BASIC_20_3of6 from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
from . import recovery from . import recovery
if TYPE_CHECKING:
from ..device_handler import BackgroundDeviceHandler
@pytest.mark.skip_t1 @pytest.mark.skip_t1
@pytest.mark.setup_client(uninitialized=True) @pytest.mark.setup_client(uninitialized=True)
def test_recovery(device_handler): def test_recovery(device_handler: "BackgroundDeviceHandler"):
features = device_handler.features() features = device_handler.features()
debug = device_handler.debuglink() debug = device_handler.debuglink()

@ -14,6 +14,7 @@
# 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 unittest import mock from unittest import mock
import pytest import pytest
@ -24,6 +25,10 @@ from .. import buttons
from ..common import generate_entropy from ..common import generate_entropy
from . import reset from . import reset
if TYPE_CHECKING:
from ..device_handler import BackgroundDeviceHandler
EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2 EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
with_mock_urandom = mock.patch("os.urandom", mock.Mock(return_value=EXTERNAL_ENTROPY)) with_mock_urandom = mock.patch("os.urandom", mock.Mock(return_value=EXTERNAL_ENTROPY))
@ -32,7 +37,9 @@ with_mock_urandom = mock.patch("os.urandom", mock.Mock(return_value=EXTERNAL_ENT
@pytest.mark.skip_t1 @pytest.mark.skip_t1
@pytest.mark.setup_client(uninitialized=True) @pytest.mark.setup_client(uninitialized=True)
@with_mock_urandom @with_mock_urandom
def test_reset_slip39_advanced_2of2groups_2of2shares(device_handler): def test_reset_slip39_advanced_2of2groups_2of2shares(
device_handler: "BackgroundDeviceHandler",
):
features = device_handler.features() features = device_handler.features()
debug = device_handler.debuglink() debug = device_handler.debuglink()
@ -76,7 +83,7 @@ def test_reset_slip39_advanced_2of2groups_2of2shares(device_handler):
# confirm backup warning # confirm backup warning
reset.confirm_read(debug, "Caution") reset.confirm_read(debug, "Caution")
all_words = [] all_words: list[str] = []
for _ in range(2): for _ in range(2):
for _ in range(2): for _ in range(2):
# read words # read words
@ -95,6 +102,7 @@ def test_reset_slip39_advanced_2of2groups_2of2shares(device_handler):
# generate secret locally # generate secret locally
internal_entropy = debug.state().reset_entropy internal_entropy = debug.state().reset_entropy
assert internal_entropy is not None
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY) secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
# validate that all combinations will result in the correct master secret # validate that all combinations will result in the correct master secret
@ -114,7 +122,9 @@ def test_reset_slip39_advanced_2of2groups_2of2shares(device_handler):
@pytest.mark.setup_client(uninitialized=True) @pytest.mark.setup_client(uninitialized=True)
@pytest.mark.slow @pytest.mark.slow
@with_mock_urandom @with_mock_urandom
def test_reset_slip39_advanced_16of16groups_16of16shares(device_handler): def test_reset_slip39_advanced_16of16groups_16of16shares(
device_handler: "BackgroundDeviceHandler",
):
features = device_handler.features() features = device_handler.features()
debug = device_handler.debuglink() debug = device_handler.debuglink()
@ -158,7 +168,7 @@ def test_reset_slip39_advanced_16of16groups_16of16shares(device_handler):
# confirm backup warning # confirm backup warning
reset.confirm_read(debug, "Caution") reset.confirm_read(debug, "Caution")
all_words = [] all_words: list[str] = []
for _ in range(16): for _ in range(16):
for _ in range(16): for _ in range(16):
# read words # read words
@ -177,6 +187,7 @@ def test_reset_slip39_advanced_16of16groups_16of16shares(device_handler):
# generate secret locally # generate secret locally
internal_entropy = debug.state().reset_entropy internal_entropy = debug.state().reset_entropy
assert internal_entropy is not None
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY) secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
# validate that all combinations will result in the correct master secret # validate that all combinations will result in the correct master secret

@ -14,6 +14,7 @@
# 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 unittest import mock from unittest import mock
import pytest import pytest
@ -24,12 +25,16 @@ from .. import buttons
from ..common import generate_entropy from ..common import generate_entropy
from . import reset from . import reset
if TYPE_CHECKING:
from ..device_handler import BackgroundDeviceHandler
EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2 EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
@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): def test_reset_slip39_basic_1of1(device_handler: "BackgroundDeviceHandler"):
features = device_handler.features() features = device_handler.features()
debug = device_handler.debuglink() debug = device_handler.debuglink()
@ -84,6 +89,7 @@ def test_reset_slip39_basic_1of1(device_handler):
# generate secret locally # generate secret locally
internal_entropy = debug.state().reset_entropy internal_entropy = debug.state().reset_entropy
assert internal_entropy is not None
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY) secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
# validate that all combinations will result in the correct master secret # validate that all combinations will result in the correct master secret
@ -101,7 +107,7 @@ def test_reset_slip39_basic_1of1(device_handler):
@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_16of16(device_handler): def test_reset_slip39_basic_16of16(device_handler: "BackgroundDeviceHandler"):
features = device_handler.features() features = device_handler.features()
debug = device_handler.debuglink() debug = device_handler.debuglink()
@ -142,7 +148,7 @@ def test_reset_slip39_basic_16of16(device_handler):
# confirm backup warning # confirm backup warning
reset.confirm_read(debug, "Caution") reset.confirm_read(debug, "Caution")
all_words = [] all_words: list[str] = []
for _ in range(16): for _ in range(16):
# read words # read words
words = reset.read_words(debug) words = reset.read_words(debug)
@ -160,6 +166,7 @@ def test_reset_slip39_basic_16of16(device_handler):
# generate secret locally # generate secret locally
internal_entropy = debug.state().reset_entropy internal_entropy = debug.state().reset_entropy
assert internal_entropy is not None
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY) secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
# validate that all combinations will result in the correct master secret # validate that all combinations will result in the correct master secret

@ -23,7 +23,7 @@ class NullUI:
raise NotImplementedError("NullUI should not be used with T1") raise NotImplementedError("NullUI should not be used with T1")
@staticmethod @staticmethod
def get_passphrase(available_on_device=False): def get_passphrase(available_on_device: bool = False):
if available_on_device: if available_on_device:
return PASSPHRASE_ON_DEVICE return PASSPHRASE_ON_DEVICE
else: else:
@ -42,7 +42,7 @@ class BackgroundDeviceHandler:
self.client.ui = NullUI # type: ignore [NullUI is OK UI] self.client.ui = NullUI # type: ignore [NullUI is OK UI]
self.client.watch_layout(True) self.client.watch_layout(True)
def run(self, function, *args, **kwargs): def run(self, function, *args, **kwargs) -> None:
if self.task is not None: if self.task is not None:
raise RuntimeError("Wait for previous task first") raise RuntimeError("Wait for previous task first")
self.task = self._pool.submit(function, self.client, *args, **kwargs) self.task = self._pool.submit(function, self.client, *args, **kwargs)
@ -60,7 +60,7 @@ class BackgroundDeviceHandler:
pass pass
self.task = None self.task = None
def restart(self, emulator: "Emulator"): def restart(self, emulator: "Emulator") -> None:
# TODO handle actual restart as well # TODO handle actual restart as well
self.kill_task() self.kill_task()
emulator.restart() emulator.restart()

@ -1,3 +1,5 @@
from typing import Iterator
import pytest import pytest
from trezorlib import debuglink, device from trezorlib import debuglink, device
@ -9,7 +11,7 @@ from ..upgrade_tests import core_only
@pytest.fixture @pytest.fixture
def emulator() -> Emulator: def emulator() -> Iterator[Emulator]:
with EmulatorWrapper("core") as emu: with EmulatorWrapper("core") as emu:
yield emu yield emu
@ -26,7 +28,6 @@ def emulator() -> Emulator:
def test_safety_checks_level_after_reboot( def test_safety_checks_level_after_reboot(
emulator: Emulator, set_level: SafetyCheckLevel, after_level: SafetyCheckLevel emulator: Emulator, set_level: SafetyCheckLevel, after_level: SafetyCheckLevel
): ):
assert emulator.client is not None
device.wipe(emulator.client) device.wipe(emulator.client)
debuglink.load_device( debuglink.load_device(
emulator.client, emulator.client,

@ -14,6 +14,8 @@
# 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 Iterator
import pytest import pytest
from trezorlib import device from trezorlib import device
@ -28,7 +30,7 @@ from ..upgrade_tests import core_only
@pytest.fixture @pytest.fixture
def emulator() -> Emulator: def emulator() -> Iterator[Emulator]:
with EmulatorWrapper("core") as emu: with EmulatorWrapper("core") as emu:
yield emu yield emu
@ -48,7 +50,7 @@ def test_abort(emulator: Emulator):
device_handler.run(device.recover, pin_protection=False) device_handler.run(device.recover, pin_protection=False)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text.startswith("Recovery mode") assert layout.get_title() == "RECOVERY MODE"
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert "Select number of words" in layout.text assert "Select number of words" in layout.text
@ -64,7 +66,7 @@ def test_abort(emulator: Emulator):
assert "Select number of words" in layout.text assert "Select number of words" in layout.text
layout = debug.click(buttons.CANCEL, wait=True) layout = debug.click(buttons.CANCEL, wait=True)
assert layout.text.startswith("Abort recovery") assert layout.get_title() == "ABORT RECOVERY"
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "Homescreen" assert layout.text == "Homescreen"
@ -137,7 +139,7 @@ def test_recovery_on_old_wallet(emulator: Emulator):
assert "Enter any share" in layout.text assert "Enter any share" in layout.text
debug.press_yes() debug.press_yes()
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "Slip39Keyboard" assert layout.text == "< MnemonicKeyboard >"
# enter first word # enter first word
debug.input(words[0]) debug.input(words[0])
@ -149,7 +151,7 @@ def test_recovery_on_old_wallet(emulator: Emulator):
# try entering remaining 19 words # try entering remaining 19 words
for word in words[1:]: for word in words[1:]:
assert layout.text == "Slip39Keyboard" assert layout.text == "< MnemonicKeyboard >"
debug.input(word) debug.input(word)
layout = debug.wait_layout() layout = debug.wait_layout()
@ -179,10 +181,10 @@ def test_recovery_multiple_resets(emulator: Emulator):
assert expected_text in layout.text assert expected_text in layout.text
layout = recovery.enter_share(debug, share) layout = recovery.enter_share(debug, share)
remaining -= 1 remaining -= 1
expected_text = "Success You have entered" expected_text = "You have entered"
debug = _restart(device_handler, emulator) debug = _restart(device_handler, emulator)
assert "You have successfully recovered your wallet" in layout.text assert "You have successfully recovered your wallet" in layout.get_content()
device_handler = BackgroundDeviceHandler(emulator.client) device_handler = BackgroundDeviceHandler(emulator.client)
debug = device_handler.debuglink() debug = device_handler.debuglink()

@ -50,7 +50,6 @@ def setup_device_core(client: Client, pin: str, wipe_code: str) -> None:
@core_only @core_only
def test_wipe_code_activate_core(): def test_wipe_code_activate_core():
with EmulatorWrapper("core") as emu: with EmulatorWrapper("core") as emu:
assert emu.client is not None
# set up device # set up device
setup_device_core(emu.client, PIN, WIPE_CODE) setup_device_core(emu.client, PIN, WIPE_CODE)
@ -82,7 +81,6 @@ def test_wipe_code_activate_core():
@legacy_only @legacy_only
def test_wipe_code_activate_legacy(): def test_wipe_code_activate_legacy():
with EmulatorWrapper("legacy") as emu: with EmulatorWrapper("legacy") as emu:
assert emu.client is not None
# set up device # set up device
setup_device_legacy(emu.client, PIN, WIPE_CODE) setup_device_legacy(emu.client, PIN, WIPE_CODE)

@ -47,7 +47,7 @@ core_only = pytest.mark.skipif(
def for_all( def for_all(
*args, *args: str,
legacy_minimum_version: Tuple[int, int, int] = (1, 0, 0), legacy_minimum_version: Tuple[int, int, int] = (1, 0, 0),
core_minimum_version: Tuple[int, int, int] = (2, 0, 0) core_minimum_version: Tuple[int, int, int] = (2, 0, 0)
) -> "MarkDecorator": ) -> "MarkDecorator":
@ -71,7 +71,7 @@ def for_all(
# If any gens were selected, use them. If none, select all. # If any gens were selected, use them. If none, select all.
enabled_gens = SELECTED_GENS or args enabled_gens = SELECTED_GENS or args
all_params = [] all_params: list[tuple[str, str | None]] = []
for gen in args: for gen in args:
if gen == "legacy": if gen == "legacy":
minimum_version = legacy_minimum_version minimum_version = legacy_minimum_version

@ -15,7 +15,7 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>. # If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import dataclasses import dataclasses
from typing import List from typing import TYPE_CHECKING, List, Optional
import pytest import pytest
@ -29,6 +29,9 @@ from ..device_handler import BackgroundDeviceHandler
from ..emulators import ALL_TAGS, EmulatorWrapper from ..emulators import ALL_TAGS, EmulatorWrapper
from . import for_all, for_tags from . import for_all, for_tags
if TYPE_CHECKING:
from trezorlib.debuglink import TrezorClientDebugLink as Client
models.TREZOR_ONE = dataclasses.replace(models.TREZOR_ONE, minimum_version=(1, 0, 0)) models.TREZOR_ONE = dataclasses.replace(models.TREZOR_ONE, minimum_version=(1, 0, 0))
models.TREZOR_T = dataclasses.replace(models.TREZOR_T, minimum_version=(2, 0, 0)) models.TREZOR_T = dataclasses.replace(models.TREZOR_T, minimum_version=(2, 0, 0))
models.TREZORS = {models.TREZOR_ONE, models.TREZOR_T} models.TREZORS = {models.TREZOR_ONE, models.TREZOR_T}
@ -44,8 +47,8 @@ STRENGTH = 128
@for_all() @for_all()
def test_upgrade_load(gen: str, tag: str): def test_upgrade_load(gen: str, tag: str) -> None:
def asserts(client): def asserts(client: "Client"):
assert not client.features.pin_protection assert not client.features.pin_protection
assert not client.features.passphrase_protection assert not client.features.passphrase_protection
assert client.features.initialized assert client.features.initialized
@ -72,10 +75,10 @@ def test_upgrade_load(gen: str, tag: str):
@for_all("legacy") @for_all("legacy")
def test_upgrade_load_pin(gen: str, tag: str): def test_upgrade_load_pin(gen: str, tag: str) -> None:
PIN = "1234" PIN = "1234"
def asserts(client): def asserts(client: "Client") -> None:
assert client.features.pin_protection assert client.features.pin_protection
assert not client.features.passphrase_protection assert not client.features.passphrase_protection
assert client.features.initialized assert client.features.initialized
@ -117,7 +120,7 @@ def test_upgrade_load_pin(gen: str, tag: str):
def test_storage_upgrade_progressive(gen: str, tags: List[str]): def test_storage_upgrade_progressive(gen: str, tags: List[str]):
PIN = "1234" PIN = "1234"
def asserts(client): def asserts(client: "Client") -> None:
assert client.features.pin_protection assert client.features.pin_protection
assert not client.features.passphrase_protection assert not client.features.passphrase_protection
assert client.features.initialized assert client.features.initialized
@ -153,7 +156,7 @@ def test_upgrade_wipe_code(gen: str, tag: str):
PIN = "1234" PIN = "1234"
WIPE_CODE = "4321" WIPE_CODE = "4321"
def asserts(client): def asserts(client: "Client"):
assert client.features.pin_protection assert client.features.pin_protection
assert not client.features.passphrase_protection assert not client.features.passphrase_protection
assert client.features.initialized assert client.features.initialized
@ -195,7 +198,7 @@ def test_upgrade_wipe_code(gen: str, tag: str):
@for_all("legacy") @for_all("legacy")
def test_upgrade_reset(gen: str, tag: str): def test_upgrade_reset(gen: str, tag: str):
def asserts(client): def asserts(client: "Client"):
assert not client.features.pin_protection assert not client.features.pin_protection
assert not client.features.passphrase_protection assert not client.features.passphrase_protection
assert client.features.initialized assert client.features.initialized
@ -228,7 +231,7 @@ def test_upgrade_reset(gen: str, tag: str):
@for_all() @for_all()
def test_upgrade_reset_skip_backup(gen: str, tag: str): def test_upgrade_reset_skip_backup(gen: str, tag: str):
def asserts(client): def asserts(client: "Client"):
assert not client.features.pin_protection assert not client.features.pin_protection
assert not client.features.passphrase_protection assert not client.features.passphrase_protection
assert client.features.initialized assert client.features.initialized
@ -262,7 +265,7 @@ def test_upgrade_reset_skip_backup(gen: str, tag: str):
@for_all(legacy_minimum_version=(1, 7, 2)) @for_all(legacy_minimum_version=(1, 7, 2))
def test_upgrade_reset_no_backup(gen: str, tag: str): def test_upgrade_reset_no_backup(gen: str, tag: str):
def asserts(client): def asserts(client: "Client"):
assert not client.features.pin_protection assert not client.features.pin_protection
assert not client.features.passphrase_protection assert not client.features.passphrase_protection
assert client.features.initialized assert client.features.initialized
@ -296,7 +299,7 @@ def test_upgrade_reset_no_backup(gen: str, tag: str):
# Although Shamir was introduced in 2.1.2 already, the debug instrumentation was not present until 2.1.9. # Although Shamir was introduced in 2.1.2 already, the debug instrumentation was not present until 2.1.9.
@for_all("core", core_minimum_version=(2, 1, 9)) @for_all("core", core_minimum_version=(2, 1, 9))
def test_upgrade_shamir_recovery(gen: str, tag: str): def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]):
with EmulatorWrapper(gen, tag) as emu, BackgroundDeviceHandler( with EmulatorWrapper(gen, tag) as emu, BackgroundDeviceHandler(
emu.client emu.client
) as device_handler: ) as device_handler:
@ -306,9 +309,14 @@ def test_upgrade_shamir_recovery(gen: str, tag: str):
device_handler.run(device.recover, pin_protection=False) device_handler.run(device.recover, pin_protection=False)
recovery.confirm_recovery(debug) # Flow is different for old UI and new UI
recovery.select_number_of_words(debug) legacy_ui = emu.client.version < (2, 5, 4)
layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[0])
recovery.confirm_recovery(debug, legacy_ui=legacy_ui)
recovery.select_number_of_words(debug, legacy_ui=legacy_ui)
layout = recovery.enter_share(
debug, MNEMONIC_SLIP39_BASIC_20_3of6[0], legacy_ui=legacy_ui
)
assert "2 more shares" in layout.text assert "2 more shares" in layout.text
device_id = emu.client.features.device_id device_id = emu.client.features.device_id
@ -331,6 +339,7 @@ def test_upgrade_shamir_recovery(gen: str, tag: str):
# Check the result # Check the result
state = debug.state() state = debug.state()
assert state.mnemonic_secret is not None
assert state.mnemonic_secret.hex() == MNEMONIC_SLIP39_BASIC_20_3of6_SECRET assert state.mnemonic_secret.hex() == MNEMONIC_SLIP39_BASIC_20_3of6_SECRET
assert state.mnemonic_type == BackupType.Slip39_Basic assert state.mnemonic_type == BackupType.Slip39_Basic

@ -14,6 +14,8 @@
# 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 Iterator
import pytest import pytest
from trezorlib import btc, device, mapping, messages, models, protobuf from trezorlib import btc, device, mapping, messages, models, protobuf
@ -41,9 +43,8 @@ mapping.DEFAULT_MAPPING.register(ApplySettingsCompat)
@pytest.fixture @pytest.fixture
def emulator(gen: str, tag: str) -> Emulator: def emulator(gen: str, tag: str) -> Iterator[Emulator]:
with EmulatorWrapper(gen, tag) as emu: with EmulatorWrapper(gen, tag) as emu:
assert emu.client is not None
# set up a passphrase-protected device # set up a passphrase-protected device
device.reset( device.reset(
emu.client, emu.client,
@ -64,7 +65,6 @@ def emulator(gen: str, tag: str) -> Emulator:
) )
def test_passphrase_works(emulator: Emulator): def test_passphrase_works(emulator: Emulator):
"""Check that passphrase handling in trezorlib works correctly in all versions.""" """Check that passphrase handling in trezorlib works correctly in all versions."""
assert emulator.client is not None
if emulator.client.features.model == "T" and emulator.client.version < (2, 3, 0): if emulator.client.features.model == "T" and emulator.client.version < (2, 3, 0):
expected_responses = [ expected_responses = [
messages.PassphraseRequest, messages.PassphraseRequest,
@ -102,7 +102,6 @@ def test_init_device(emulator: Emulator):
"""Check that passphrase caching and session_id retaining works correctly across """Check that passphrase caching and session_id retaining works correctly across
supported versions. supported versions.
""" """
assert emulator.client is not None
if emulator.client.features.model == "T" and emulator.client.version < (2, 3, 0): if emulator.client.features.model == "T" and emulator.client.version < (2, 3, 0):
expected_responses = [ expected_responses = [
messages.PassphraseRequest, messages.PassphraseRequest,

Loading…
Cancel
Save