1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-15 20:19:23 +00:00
trezor-firmware/tests/input_flows.py
grdddj 7f1a5ac4c1 WIP - firmware translations
WIP - refactor and extend font generation for non-ascii characters

WIP - add czech characters mapping between UTF8 value and index

WIP - regenerate font files with czech characters

WIP - shorten czech button text, it was causing SHUTDOWN for some reason

WIP - support UTF8 characters in fonts.c

WIP - account for translation in tests

WIP - small fixes

WIP - fix last test

WIP - support UTF8 also in Rust font operations

WIP - add a script to find non-translated english strings in micropython code

WIP - add a validator script for checking missing micropython translations

WIP - translate remaining altcoins and other apps in core (fido, sdcard, TT layouts, ...)

WIP - generate czech glyphs for TT fonts

WIP - modify gen_font.py to account for negative bearing czech characters

WIP - extend translation validation scripts, move them into core/tools

WIP - translate TT layouts in Rust

WIP - fix tests

WIP - fix inverse coloring of nonprintable glyph

WIP - add build and test pipelines for Czech language

WIP - merge both JSON files together

WIP - run new isort

WIP - unify all the translation in Rust, expose to micropython

TEMP - leave en_merged.json file, so it is accessible by translators with old link

WIP - fixes

WIP - add french characters and translation via Google Translator

WIP - skip rustfmt in mako-created files

WIP - revert all the font height changes causing false-positive UI diff

WIP - fixes after rebase

WIP - fix broken translations

WIP - revert some wording changes causing UI diff

WIP - improve validation and translate scripts, translate missing strings

WIP - sort all keys alphabetically

WIP - remove any usage of translation in bootloader

WIP - add newline at the end of JSON file

WIP - fix bitcoin-only strings check

WIP - fix python support check

WIP - add some missing translations

WIP - fix SD card device test

WIP - fix pystyle

WIP - fix rust unittests

WIP - fix click tests

WIP - flag errors in french translations

WIP - add script transferring translations data into a byte blob

WIP - regenerate fr.rs

WIP - store and read language translations from flash

WIP - storing language name in storage

WIP - sending language_data in apply_settings protobuf message

WIP - separate protobuf message for translations, fixes

WIP - set up translations area for TT as well

WIP - get rid of TREZOR_LANG env variable during build

WIP - make the firmware buildable for TT

WIP - add basic device tests

WIP - set language for tests

WIP - counting with language when writing fixtures

WIP - add todos

WIP - fix CI

WIP - unify translations, make titles CAPITAL

WIP - translate missing english

WIP - skip translations messages for T1

WIP - not changing tests names for english

WIP - fix flake8

WIP - no test language setting for T1

WIP - clippy lint about complex data type

WIP - fix some english UI diff for TR

WIP - fix cstyle

WIP - minimize the usage of #[cfg(feature = "micropython")] outside translations module

WIP - minimize TT's UI diff

WIP - fix ruststyle

WIP - fix TR build

WIP - advanced Shamir text change

WIP - storing the language name as the first item in the translation data

WIP - modify and extend tests after storing language name

WIP - modify checklist sentence

WIP - add TEST_LANG into Makefile for all the emu tests

WIP - default arguments

WIP - reimplement default arguments

remove unneeded pub from get_info function

WIP - Rust handling of object attributes lookups from upy - thanks Matejcik!

WIP - generate mock interface for attribute-based translations lookups

WIP - change function calls to object attributes

WIP - symbolic link for unix/translations.c

WIP - fix and improve the reading of translations - thanks Matejcik!

WIP - add support for multiple languages in removing missing tests

WIP - fix multiple-accounts warning in tests

WIP - fix encoding of newlines in translations

WIP - fix czech tutorial text

WIP - fix czech click tests

WIP - do not translate wire error messages

WIP - add language options to click tests as well

WIP - setup czech device tests in CI

WIP - setup czech click tests in CI

WIP - record czech device tests for TR

WIP - record czech click tests for TR

WIP - record czech device tests for TT

WIP - record czech click tests for TT

WIP - pystyle

WIP - cstyle

WIP - fix Rust micropython import dependency

WIP - fix czech recordings

WIP - support french translations in tests

WIP - shorten some french words to fix the tests

WIP - fix micropython cfg compilation

WIP - record french click tests for TR

WIP - record french device tests for TR

WIP - record french device tests for TT

WIP - record french click tests for TT

WIP - fix french translations - shorten them

WIP - translate missing french words

WIP - fix click tests

WIP - add french tests into CI

WIP - pystyle

WIP - allow for czech/french tests in update script

WIP - update czech fixtures

WIP - update french fixtures

WIP - ruststyle

WIP - disallow MPU to run it on hardware

WIP - cstyle

WIP - change translations delimiter from * to \x00

WIP - change translations protobufs

WIP - remove language handling from storage

WIP - add header into JSON files

WIP - count with header in translations blob

WIP - yml style fixes

WIP - fix proto gen

WIP - verify version and data hash

WIP - fix loading test translations

feat(core): allow access to translations area in firmware

[no changelog]

WIP - fixes after rebase

WIP - increase the TT's translations area to 3 sectors

WIP - dynamically read the maximum translations size

WIP - record non-english tests from CI

WIP - loading font data from translations blob

WIP - bump translations version

WIP - include czech and french glyph data

WIP - whitelist another negative-bearing glyph

WIP - remove czech/french glyphs from common font files

WIP - fix language tests

WIP - specific fonts for specific models

WIP - revert the non-ascii font hardcoding

WIP - include missing BIG font into nonprintable logic

WIP - minor Rust code improvements

WIP - include newlines at the end of json files

WIP - move glyph Rust function to librust_fonts.h

WIP - add all fonts into translations file

WIP - move fonts into its own dir

WIP - reflect separate dir for fonts

WIP - not putting translations trezorhal into bootloader

WIP - write and read multiple fonts into translations data

WIP - silence pyright issue/notissue

WIP - delete no more used translations/*.py imports

WIP - fix bootloader builds by introducing translations feature and TRANSLATIONS flag

WIP - fix TT's bootloader Rust build

WIP - fix tests in non-english languages

WIP - not search for UTF-8 when there are no translations data

WIP - add colons to strings where missing

WIP - fix language loading in tests

WIP - fix signmessage input flow to work in all languages

WIP - create offset table for translation strings

WIP - code improvements

WIP - record foreign language fixtures + sync with main in english

WIP - do alignment check before reading u16 data

WIP - allocate blob in RAM for translations data

WIP - add TODO for blob generation

WIP - record non-english device tests

WIP - use bytes.align_to instead of messing with pointers

WIP - fixtures

WIP - remove unused import

WIP - add order.py

WIP - add order.json

WIP - take order.json into account in creating general.rs

WIP - take order.json into account in generating the blob

WIP - style

WIP - sort the language files

WIP - remove unused file

WIP - code improvements

WIP - add TODO for homescreen notification

WIP - translate plural forms

WIP - translate time intervals

WIP - sign translations with dev keys, validate signatures, improve robustness

WIP - improve tests for translations

WIP - add `trezorctl utils sign-translations` for production signing of the blob

WIP - pyright fix

WIP - changing TR progress loader offset - it was colliding with title

WIP - show indeterminate loader when loading translations data

WIP - record new and updated language tests

WIP - show the change language title/prompt in the target language

WIP - sort keys

WIP - add crowdin-cli into shell.nix

WIP - add crowdin sync script
2024-01-02 14:55:16 +01:00

1614 lines
52 KiB
Python

"""
Central place for defining all input flows for the device tests.
Each model has potentially its own input flow, and in most cases
we need to distinguish between them. Doing it at one place
offers a better overview of the differences and makes it easier
to maintain. The whole `device_tests` folder can then focus
only on the actual tests and data-assertions, not on the lower-level
input flow details.
"""
from __future__ import annotations
import time
from typing import Callable, Generator
from trezorlib import messages
from trezorlib.debuglink import DebugLink, LayoutContent
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.debuglink import multipage_content
from . import buttons
from . import translations as TR
from .common import (
BRGeneratorType,
check_pin_backoff_time,
click_info_button_tt,
click_through,
read_and_confirm_mnemonic,
)
from .input_flows_helpers import BackupFlow, EthereumFlow, PinFlow, RecoveryFlow
B = messages.ButtonRequestType
def swipe_if_necessary(debug: DebugLink, br_code: B | None = None) -> BRGeneratorType:
br = yield
if br_code is not None:
assert br.code == br_code
if br.pages is not None:
for _ in range(br.pages - 1):
debug.swipe_up()
class InputFlowBase:
def __init__(self, client: Client):
self.client = client
self.debug: DebugLink = client.debug
self.PIN = PinFlow(self.client)
self.REC = RecoveryFlow(self.client)
self.BAK = BackupFlow(self.client)
self.ETH = EthereumFlow(self.client)
def model(self) -> str | None:
return self.client.features.model
def get(self) -> Callable[[], BRGeneratorType]:
self.client.watch_layout(True)
# There could be one common input flow for all models
if hasattr(self, "input_flow_common"):
return getattr(self, "input_flow_common")
elif self.model() == "T":
return self.input_flow_tt
elif self.model() == "Safe 3":
return self.input_flow_tr
else:
raise ValueError("Unknown model")
def input_flow_tt(self) -> BRGeneratorType:
"""Special for TT"""
raise NotImplementedError
def input_flow_tr(self) -> BRGeneratorType:
"""Special for TR"""
raise NotImplementedError
def text_content(self) -> str:
return self.debug.wait_layout().text_content()
def main_component(self) -> str:
return self.debug.wait_layout().main_component()
def all_components(self) -> list[str]:
return self.debug.wait_layout().all_components()
def title(self) -> str:
return self.debug.wait_layout().title()
class InputFlowSetupDevicePINWIpeCode(InputFlowBase):
def __init__(self, client: Client, pin: str, wipe_code: str):
super().__init__(client)
self.pin = pin
self.wipe_code = wipe_code
def input_flow_common(self) -> BRGeneratorType:
yield # do you want to set/change the wipe code?
self.debug.press_yes()
if self.debug.model == "Safe 3":
yield from swipe_if_necessary(self.debug) # wipe code info
self.debug.press_yes()
yield # enter current pin
self.debug.input(self.pin)
yield # enter new wipe code
self.debug.input(self.wipe_code)
yield # enter new wipe code again
self.debug.input(self.wipe_code)
yield # success
self.debug.press_yes()
class InputFlowNewCodeMismatch(InputFlowBase):
def __init__(
self,
client: Client,
first_code: str,
second_code: str,
):
super().__init__(client)
self.first_code = first_code
self.second_code = second_code
def input_flow_common(self) -> BRGeneratorType:
yield # do you want to set/change the pin/wipe code?
self.debug.press_yes()
if self.debug.model == "Safe 3":
yield from swipe_if_necessary(self.debug) # code info
self.debug.press_yes()
def input_two_different_pins() -> BRGeneratorType:
yield from self.PIN.setup_new_pin(self.first_code, self.second_code)
yield from input_two_different_pins()
yield # PIN mismatch
self.debug.press_yes() # try again
yield from input_two_different_pins()
yield # PIN mismatch
self.debug.press_yes() # try again
yield # PIN entry again
self.debug.press_no() # cancel
class InputFlowCodeChangeFail(InputFlowBase):
def __init__(
self, client: Client, current_pin: str, new_pin_1: str, new_pin_2: str
):
super().__init__(client)
self.current_pin = current_pin
self.new_pin_1 = new_pin_1
self.new_pin_2 = new_pin_2
def input_flow_common(self) -> BRGeneratorType:
yield # do you want to change pin?
self.debug.press_yes()
yield # enter current pin
self.debug.input(self.current_pin)
yield from self.PIN.setup_new_pin(self.new_pin_1, self.new_pin_2)
yield # PIN mismatch
self.debug.press_yes() # try again
# failed retry
yield # enter current pin again
self.client.cancel()
class InputFlowWrongPIN(InputFlowBase):
def __init__(self, client: Client, wrong_pin: str):
super().__init__(client)
self.wrong_pin = wrong_pin
def input_flow_common(self) -> BRGeneratorType:
yield # do you want to change pin?
self.debug.press_yes()
yield # enter wrong current pin
self.debug.input(self.wrong_pin)
yield
self.debug.press_no()
class InputFlowPINBackoff(InputFlowBase):
def __init__(self, client: Client, wrong_pin: str, good_pin: str):
super().__init__(client)
self.wrong_pin = wrong_pin
self.good_pin = good_pin
def input_flow_common(self) -> BRGeneratorType:
"""Inputting some bad PINs and finally the correct one"""
yield # PIN entry
for attempt in range(3):
start = time.time()
self.debug.input(self.wrong_pin)
yield # PIN entry
check_pin_backoff_time(attempt, start)
self.debug.input(self.good_pin)
class InputFlowSignMessagePagination(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.message_read = ""
def input_flow_tt(self) -> BRGeneratorType:
# collect screen contents into `message_read`.
# Using a helper debuglink function to assemble the final text.
layouts: list[LayoutContent] = []
br = yield # confirm address
self.debug.wait_layout()
self.debug.press_yes()
br = yield
assert br.pages is not None
for i in range(br.pages):
layout = self.debug.wait_layout()
layouts.append(layout)
if i < br.pages - 1:
self.debug.swipe_up()
self.message_read = multipage_content(layouts)
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
# confirm address
yield
self.debug.press_yes()
# press info
yield
self.debug.press_right()
# paginate through the whole message
br = yield
# TODO: try load the message_read the same way as in model T
if br.pages is not None:
for i in range(br.pages):
if i < br.pages - 1:
self.debug.swipe_up()
self.debug.press_yes()
# confirm message
yield
self.debug.press_yes()
class InputFlowSignMessageInfo(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_tt(self) -> BRGeneratorType:
yield
# show address/message info
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.press_no(wait=True)
self.debug.synchronize_at("IconDialog")
# address mismatch?
self.debug.press_no()
yield
self.debug.press_yes()
yield
self.debug.press_no()
yield
self.debug.press_no(wait=True)
self.debug.press_yes(wait=True)
class InputFlowShowAddressQRCode(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_tt(self) -> BRGeneratorType:
yield
self.debug.click(buttons.CORNER_BUTTON, wait=True)
# synchronize; TODO get rid of this once we have single-global-layout
self.debug.synchronize_at("SimplePage")
self.debug.swipe_left(wait=True)
self.debug.swipe_right(wait=True)
self.debug.swipe_left(wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.press_no(wait=True)
self.debug.press_no(wait=True)
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
# Find out the page-length of the address
br = yield
if br.pages is not None:
address_swipes = br.pages - 1
else:
address_swipes = 0
for _ in range(address_swipes):
self.debug.press_right()
# Go into details
self.debug.press_right()
# Go through details and back
self.debug.press_right()
self.debug.press_left()
self.debug.press_left()
# Confirm
for _ in range(address_swipes):
self.debug.press_right()
self.debug.press_middle()
class InputFlowShowAddressQRCodeCancel(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_tt(self) -> BRGeneratorType:
yield
self.debug.click(buttons.CORNER_BUTTON, wait=True)
# synchronize; TODO get rid of this once we have single-global-layout
self.debug.synchronize_at("SimplePage")
self.debug.swipe_left(wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.press_no(wait=True)
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
yield
# Go into details
self.debug.press_right()
# Go through details and back
self.debug.press_right()
self.debug.press_left()
self.debug.press_left()
# Cancel
self.debug.press_left()
# Confirm address mismatch
self.debug.press_right()
class InputFlowShowMultisigXPUBs(InputFlowBase):
def __init__(self, client: Client, address: str, xpubs: list[str], index: int):
super().__init__(client)
self.address = address
self.xpubs = xpubs
self.index = index
def input_flow_tt(self) -> BRGeneratorType:
yield # multisig address warning
self.debug.press_yes()
yield # show address
layout = self.debug.wait_layout()
TR.assert_in(layout.title(), "address.title_receive_address")
assert "(MULTISIG)" in layout.title()
assert layout.text_content().replace(" ", "") == self.address
self.debug.click(buttons.CORNER_BUTTON)
assert "Qr" in self.all_components()
layout = self.debug.swipe_left(wait=True)
# address details
assert "Multisig 2 of 3" in layout.screen_content()
TR.assert_in(layout.screen_content(), "address_details.derivation_path")
# Three xpub pages with the same testing logic
for xpub_num in range(3):
# TODO: might also check YOURS vs COSIGNER
expected_title = f"MULTISIG XPUB #{xpub_num + 1}"
layout = self.debug.swipe_left(wait=True)
assert expected_title in layout.title()
content = layout.text_content().replace(" ", "")
assert self.xpubs[xpub_num] in content
self.debug.click(buttons.CORNER_BUTTON, wait=True)
# show address
self.debug.press_no(wait=True)
# address mismatch
self.debug.press_no(wait=True)
# show address
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
yield # multisig address warning
self.debug.press_middle()
yield # show address
layout = self.debug.wait_layout()
TR.assert_in(layout.title(), "address.title_receive_address")
assert "(MULTISIG)" in layout.title()
assert layout.text_content().replace(" ", "") == self.address
self.debug.press_right()
assert "Qr" in self.all_components()
layout = self.debug.press_right(wait=True)
# address details
# TODO: locate it more precisely
assert "Multisig 2 of 3" in layout.json_str
# Three xpub pages with the same testing logic
for xpub_num in range(3):
# TODO: might also check YOURS vs COSIGNER
expected_title = f"MULTISIG XPUB #{xpub_num + 1}"
layout = self.debug.press_right(wait=True)
assert expected_title in layout.title()
xpub_part_1 = layout.text_content().replace(" ", "")
# Press "SHOW MORE"
layout = self.debug.press_middle(wait=True)
xpub_part_2 = layout.text_content().replace(" ", "")
# Go back
self.debug.press_left(wait=True)
assert self.xpubs[xpub_num] == xpub_part_1 + xpub_part_2
for _ in range(5):
self.debug.press_left()
# show address
self.debug.press_left()
# address mismatch
self.debug.press_left()
# show address
self.debug.press_middle()
class InputFlowShowXpubQRCode(InputFlowBase):
def __init__(self, client: Client, passphrase: bool = False):
super().__init__(client)
self.passphrase = passphrase
def input_flow_tt(self) -> BRGeneratorType:
if self.passphrase:
yield
self.debug.press_yes()
yield
self.debug.press_yes()
br = yield
layout = self.debug.wait_layout()
if "coinjoin" in layout.title().lower() or br.code == B.UnknownDerivationPath:
self.debug.press_yes()
br = yield
self.debug.click(buttons.CORNER_BUTTON, wait=True)
# synchronize; TODO get rid of this once we have single-global-layout
self.debug.synchronize_at("SimplePage")
self.debug.swipe_left(wait=True)
self.debug.swipe_right(wait=True)
self.debug.swipe_left(wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.press_no(wait=True)
self.debug.press_no(wait=True)
for _ in range(br.pages - 1):
self.debug.swipe_up(wait=True)
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
if self.passphrase:
yield
self.debug.press_right()
yield
self.debug.press_right()
br = yield
layout = self.debug.wait_layout()
if "coinjoin" in layout.title().lower() or br.code == B.UnknownDerivationPath:
self.debug.press_yes()
br = yield
# Go into details
self.debug.press_right(wait=True)
# Go through details and back
self.debug.press_right(wait=True)
self.debug.press_right(wait=True)
self.debug.press_left(wait=True)
self.debug.press_left(wait=True)
assert br.pages is not None
for _ in range(br.pages - 1):
self.debug.press_right()
# Confirm
self.debug.press_middle()
class InputFlowPaymentRequestDetails(InputFlowBase):
def __init__(self, client: Client, outputs: list[messages.TxOutputType]):
super().__init__(client)
self.outputs = outputs
def input_flow_tt(self) -> BRGeneratorType:
yield # request to see details
self.debug.wait_layout()
self.debug.press_info()
yield # confirm first output
assert self.outputs[0].address[:16] in self.text_content() # type: ignore
self.debug.press_yes()
yield # confirm first output
self.debug.wait_layout()
self.debug.press_yes()
yield # confirm second output
assert self.outputs[1].address[:16] in self.text_content() # type: ignore
self.debug.press_yes()
yield # confirm second output
self.debug.wait_layout()
self.debug.press_yes()
yield # confirm transaction
self.debug.press_yes()
yield # confirm transaction
self.debug.press_yes()
class InputFlowSignTxHighFee(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.finished = False
def go_through_all_screens(self, screens: list[B]) -> BRGeneratorType:
for expected in screens:
br = yield
assert br.code == expected
self.debug.press_yes()
self.finished = True
def input_flow_common(self) -> BRGeneratorType:
screens = [
B.ConfirmOutput,
B.ConfirmOutput,
B.FeeOverThreshold,
B.SignTx,
]
yield from self.go_through_all_screens(screens)
def sign_tx_go_to_info(client: Client) -> Generator[None, None, str]:
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm transaction
client.debug.wait_layout()
client.debug.press_info()
layout = client.debug.wait_layout()
content = layout.text_content()
client.debug.click(buttons.CORNER_BUTTON, wait=True)
return content
def sign_tx_go_to_info_tr(
client: Client,
) -> Generator[None, None, str]:
yield # confirm address
client.debug.wait_layout()
client.debug.press_yes() # CONTINUE
yield # confirm amount
client.debug.wait_layout()
client.debug.press_yes() # CONFIRM
screen_texts: list[str] = []
yield # confirm total
layout = client.debug.wait_layout()
if "multiple accounts" in layout.text_content().lower():
client.debug.press_middle()
yield
layout = client.debug.press_right(wait=True)
screen_texts.append(layout.text_content())
layout = client.debug.press_right(wait=True)
screen_texts.append(layout.text_content())
client.debug.press_left()
client.debug.press_left()
return "\n".join(screen_texts)
class InputFlowSignTxInformation(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def assert_content(self, content: str, title_path: str) -> None:
TR.assert_in(content, title_path)
assert "Legacy #6" in content
TR.assert_in(content, "confirm_total.fee_rate")
assert "71.56 sat" in content
def input_flow_tt(self) -> BRGeneratorType:
content = yield from sign_tx_go_to_info(self.client)
self.assert_content(content, "confirm_total.sending_from_account")
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
content = yield from sign_tx_go_to_info_tr(self.client)
self.assert_content(content, "confirm_total.title_sending_from")
self.debug.press_yes()
class InputFlowSignTxInformationMixed(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def assert_content(self, content: str, title_path: str) -> None:
TR.assert_in(content, title_path)
TR.assert_in(content, "bitcoin.multiple_accounts")
TR.assert_in(content, "confirm_total.fee_rate")
assert "18.33 sat" in content
def input_flow_tt(self) -> BRGeneratorType:
# multiple accounts warning
yield
self.debug.press_yes()
content = yield from sign_tx_go_to_info(self.client)
self.assert_content(content, "confirm_total.sending_from_account")
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
# multiple accounts warning
yield
self.debug.press_yes()
content = yield from sign_tx_go_to_info_tr(self.client)
self.assert_content(content, "confirm_total.title_sending_from")
self.debug.press_yes()
class InputFlowSignTxInformationCancel(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_tt(self) -> BRGeneratorType:
yield from sign_tx_go_to_info(self.client)
self.debug.press_no()
def input_flow_tr(self) -> BRGeneratorType:
yield from sign_tx_go_to_info_tr(self.client)
self.debug.press_left()
class InputFlowSignTxInformationReplacement(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_tt(self) -> BRGeneratorType:
yield # confirm txid
self.debug.press_yes()
yield # confirm address
self.debug.press_yes()
# go back to address
self.debug.press_no()
# confirm address
self.debug.press_yes()
yield # confirm amount
self.debug.press_yes()
yield # transaction summary, press info
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
yield # confirm txid
self.debug.press_right()
self.debug.press_right()
yield # confirm address
self.debug.press_right()
self.debug.press_right()
self.debug.press_right()
yield # confirm amount
self.debug.press_right()
self.debug.press_right()
self.debug.press_right()
yield
def lock_time_input_flow_tt(
debug: DebugLink,
layout_assert_func: Callable[[DebugLink], None],
double_confirm: bool = False,
) -> BRGeneratorType:
yield # confirm output
debug.wait_layout()
debug.press_yes()
yield # confirm output
debug.wait_layout()
debug.press_yes()
yield # confirm locktime
layout_assert_func(debug)
debug.press_yes()
yield # confirm transaction
debug.press_yes()
if double_confirm:
yield # confirm transaction
debug.press_yes()
def lock_time_input_flow_tr(
debug: DebugLink, layout_assert_func: Callable[[DebugLink], None]
) -> BRGeneratorType:
yield # confirm address
debug.wait_layout()
debug.press_yes()
yield # confirm amount
debug.wait_layout()
debug.press_yes()
yield # confirm locktime
layout_assert_func(debug)
debug.press_yes()
yield # confirm transaction
debug.press_yes()
class InputFlowLockTimeBlockHeight(InputFlowBase):
def __init__(self, client: Client, block_height: str):
super().__init__(client)
self.block_height = block_height
def assert_func(self, debug: DebugLink) -> None:
layout_text = debug.wait_layout().text_content()
TR.assert_in(layout_text, "bitcoin.locktime_set_to_blockheight")
assert self.block_height in layout_text
def input_flow_tt(self) -> BRGeneratorType:
yield from lock_time_input_flow_tt(
self.debug, self.assert_func, double_confirm=True
)
def input_flow_tr(self) -> BRGeneratorType:
yield from lock_time_input_flow_tr(self.debug, self.assert_func)
class InputFlowLockTimeDatetime(InputFlowBase):
def __init__(self, client: Client, lock_time_str: str):
super().__init__(client)
self.lock_time_str = lock_time_str
def assert_func(self, debug: DebugLink):
layout_text = debug.wait_layout().text_content()
TR.assert_in(layout_text, "bitcoin.locktime_set_to")
assert self.lock_time_str in layout_text
def input_flow_tt(self) -> BRGeneratorType:
yield from lock_time_input_flow_tt(self.debug, self.assert_func)
def input_flow_tr(self) -> BRGeneratorType:
yield from lock_time_input_flow_tr(self.debug, self.assert_func)
class InputFlowEIP712ShowMore(InputFlowBase):
SHOW_MORE = (143, 167)
def __init__(self, client: Client):
super().__init__(client)
self.same_for_all_models = True
def _confirm_show_more(self) -> None:
"""Model-specific, either clicks a screen or presses a button."""
if self.model() == "T":
self.debug.click(self.SHOW_MORE)
elif self.model() == "Safe 3":
self.debug.press_right()
def input_flow_common(self) -> BRGeneratorType:
"""Triggers show more wherever possible"""
yield # confirm address
self.debug.press_yes()
yield # confirm domain
self.debug.wait_layout()
self._confirm_show_more()
# confirm domain properties
for _ in range(4):
yield from swipe_if_necessary(self.debug) # EIP712 DOMAIN
self.debug.press_yes()
yield # confirm message
self.debug.wait_layout()
self._confirm_show_more()
yield # confirm message.from
self.debug.wait_layout()
self._confirm_show_more()
# confirm message.from properties
for _ in range(2):
yield from swipe_if_necessary(self.debug)
self.debug.press_yes()
yield # confirm message.to
self.debug.wait_layout()
self._confirm_show_more()
# confirm message.to properties
for _ in range(2):
yield from swipe_if_necessary(self.debug)
self.debug.press_yes()
yield # confirm message.contents
self.debug.press_yes()
yield # confirm final hash
self.debug.press_yes()
class InputFlowEIP712Cancel(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_common(self) -> BRGeneratorType:
"""Clicks cancelling button"""
yield # confirm address
self.debug.press_yes()
yield # confirm domain
self.debug.press_no()
class InputFlowEthereumSignTxShowFeeInfo(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_common(self) -> BRGeneratorType:
yield from self.ETH.confirm_tx(info=True)
class InputFlowEthereumSignTxGoBackFromSummary(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_common(self) -> BRGeneratorType:
yield from self.ETH.confirm_tx(go_back_from_summary=True)
class InputFlowEthereumSignTxDataSkip(InputFlowBase):
def __init__(self, client: Client, cancel: bool = False):
super().__init__(client)
self.cancel = cancel
def input_flow_common(self) -> BRGeneratorType:
yield from self.ETH.confirm_data()
yield from self.ETH.confirm_tx(cancel=self.cancel)
class InputFlowEthereumSignTxDataScrollDown(InputFlowBase):
def __init__(self, client: Client, cancel: bool = False):
super().__init__(client)
self.cancel = cancel
def input_flow_common(self) -> BRGeneratorType:
yield from self.ETH.confirm_data(info=True)
yield from self.ETH.paginate_data()
if self.cancel:
yield from self.ETH.confirm_data(cancel=True)
else:
yield from self.ETH.confirm_data()
yield from self.ETH.confirm_tx()
class InputFlowEthereumSignTxDataGoBack(InputFlowBase):
def __init__(self, client: Client, cancel: bool = False):
super().__init__(client)
self.cancel = cancel
def input_flow_common(self) -> BRGeneratorType:
yield from self.ETH.confirm_data(info=True)
yield from self.ETH.paginate_data_go_back()
if self.cancel:
yield from self.ETH.confirm_data(cancel=True)
else:
yield from self.ETH.confirm_data()
yield from self.ETH.confirm_tx()
def get_mnemonic_and_confirm_success(
debug: DebugLink,
) -> Generator[None, "messages.ButtonRequest", str]:
# mnemonic phrases
mnemonic = yield from read_and_confirm_mnemonic(debug)
br = yield # confirm recovery seed check
assert br.code == B.Success
debug.press_yes()
br = yield # confirm success
assert br.code == B.Success
debug.press_yes()
assert mnemonic is not None
return mnemonic
class InputFlowBip39Backup(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.mnemonic = None
def input_flow_common(self) -> BRGeneratorType:
# 1. Confirm Reset
yield from click_through(self.debug, screens=1, code=B.ResetDevice)
# mnemonic phrases and rest
self.mnemonic = yield from get_mnemonic_and_confirm_success(self.debug)
class InputFlowBip39ResetBackup(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.mnemonic = None
# NOTE: same as above, just two more YES
def input_flow_common(self) -> BRGeneratorType:
# 1. Confirm Reset
# 2. Backup your seed
# 3. Confirm warning
yield from click_through(self.debug, screens=3, code=B.ResetDevice)
# mnemonic phrases and rest
self.mnemonic = yield from get_mnemonic_and_confirm_success(self.debug)
class InputFlowBip39ResetPIN(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.mnemonic = None
def input_flow_common(self) -> BRGeneratorType:
br = yield # Confirm Reset
assert br.code == B.ResetDevice
self.debug.press_yes()
yield from self.PIN.setup_new_pin("654")
br = yield # Confirm entropy
assert br.code == B.ResetDevice
self.debug.press_yes()
br = yield # Backup your seed
assert br.code == B.ResetDevice
self.debug.press_yes()
br = yield # Confirm warning
assert br.code == B.ResetDevice
self.debug.press_yes()
# mnemonic phrases
self.mnemonic = yield from read_and_confirm_mnemonic(self.debug)
br = yield # confirm recovery seed check
assert br.code == B.Success
self.debug.press_yes()
br = yield # confirm success
assert br.code == B.Success
self.debug.press_yes()
class InputFlowBip39ResetFailedCheck(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.mnemonic = None
def input_flow_common(self) -> BRGeneratorType:
# 1. Confirm Reset
# 2. Backup your seed
# 3. Confirm warning
yield from click_through(self.debug, screens=3, code=B.ResetDevice)
# mnemonic phrases, wrong answer
self.mnemonic = yield from read_and_confirm_mnemonic(
self.debug, choose_wrong=True
)
br = yield # warning screen
assert br.code == B.ResetDevice
self.debug.press_yes()
# mnemonic phrases
self.mnemonic = yield from read_and_confirm_mnemonic(self.debug)
br = yield # confirm recovery seed check
assert br.code == B.Success
self.debug.press_yes()
br = yield # confirm success
assert br.code == B.Success
self.debug.press_yes()
def load_5_shares(
debug: DebugLink,
) -> Generator[None, "messages.ButtonRequest", list[str]]:
mnemonics: list[str] = []
for _ in range(5):
# Phrase screen
mnemonic = yield from read_and_confirm_mnemonic(debug)
assert mnemonic is not None
mnemonics.append(mnemonic)
br = yield # Confirm continue to next
assert br.code == B.Success
debug.press_yes()
return mnemonics
class InputFlowSlip39BasicBackup(InputFlowBase):
def __init__(self, client: Client, click_info: bool):
super().__init__(client)
self.mnemonics: list[str] = []
self.click_info = click_info
def input_flow_tt(self) -> BRGeneratorType:
yield # 1. Checklist
self.debug.press_yes()
if self.click_info:
yield from click_info_button_tt(self.debug)
yield # 2. Number of shares (5)
self.debug.press_yes()
yield # 3. Checklist
self.debug.press_yes()
if self.click_info:
yield from click_info_button_tt(self.debug)
yield # 4. Threshold (3)
self.debug.press_yes()
yield # 5. Checklist
self.debug.press_yes()
yield # 6. Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
br = yield # Confirm backup
assert br.code == B.Success
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
yield # 1. Checklist
self.debug.press_yes()
yield # 1.5 Number of shares info
self.debug.press_yes()
yield # 2. Number of shares (5)
self.debug.input("5")
yield # 3. Checklist
self.debug.press_yes()
yield # 3.5 Threshold info
self.debug.press_yes()
yield # 4. Threshold (3)
self.debug.input("3")
yield # 5. Checklist
self.debug.press_yes()
yield # 6. Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
br = yield # Confirm backup
assert br.code == B.Success
self.debug.press_yes()
class InputFlowSlip39BasicResetRecovery(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.mnemonics: list[str] = []
def input_flow_tt(self) -> BRGeneratorType:
# 1. Confirm Reset
# 2. Backup your seed
# 3. Confirm warning
# 4. shares info
# 5. Set & Confirm number of shares
# 6. threshold info
# 7. Set & confirm threshold value
# 8. Confirm show seeds
yield from click_through(self.debug, screens=8, code=B.ResetDevice)
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
br = yield # safety warning
assert br.code == B.Success
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
yield # Confirm Reset
self.debug.press_yes()
yield # Backup your seed
self.debug.press_yes()
yield # Checklist
self.debug.press_yes()
yield # Number of shares info
self.debug.press_yes()
yield # Number of shares (5)
self.debug.input("5")
yield # Checklist
self.debug.press_yes()
yield # Threshold info
self.debug.press_yes()
yield # Threshold (3)
self.debug.input("3")
yield # Checklist
self.debug.press_yes()
yield # Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
br = yield # Confirm backup
assert br.code == B.Success
self.debug.press_yes()
def load_5_groups_5_shares(
debug: DebugLink,
) -> Generator[None, "messages.ButtonRequest", list[str]]:
mnemonics: list[str] = []
for _g in range(5):
for _s in range(5):
# Phrase screen
mnemonic = yield from read_and_confirm_mnemonic(debug)
assert mnemonic is not None
mnemonics.append(mnemonic)
# Confirm continue to next
yield from swipe_if_necessary(debug, B.Success)
debug.press_yes()
return mnemonics
class InputFlowSlip39AdvancedBackup(InputFlowBase):
def __init__(self, client: Client, click_info: bool):
super().__init__(client)
self.mnemonics: list[str] = []
self.click_info = click_info
def input_flow_tt(self) -> BRGeneratorType:
yield # 1. Checklist
self.debug.press_yes()
if self.click_info:
yield from click_info_button_tt(self.debug)
yield # 2. Set and confirm group count
self.debug.press_yes()
yield # 3. Checklist
self.debug.press_yes()
if self.click_info:
yield from click_info_button_tt(self.debug)
yield # 4. Set and confirm group threshold
self.debug.press_yes()
yield # 5. Checklist
self.debug.press_yes()
for _ in range(5): # for each of 5 groups
if self.click_info:
yield from click_info_button_tt(self.debug)
yield # Set & Confirm number of shares
self.debug.press_yes()
if self.click_info:
yield from click_info_button_tt(self.debug)
yield # Set & confirm share threshold value
self.debug.press_yes()
yield # Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases - show & confirm shares for all groups
self.mnemonics = yield from load_5_groups_5_shares(self.debug)
br = yield # Confirm backup
assert br.code == B.Success
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
yield # 1. Checklist
self.debug.press_yes()
yield # 2. Set and confirm group count
self.debug.input("5")
yield # 3. Checklist
self.debug.press_yes()
yield # 4. Set and confirm group threshold
self.debug.input("3")
yield # 5. Checklist
self.debug.press_yes()
for _ in range(5): # for each of 5 groups
yield # Number of shares info
self.debug.press_yes()
yield # Number of shares (5)
self.debug.input("5")
yield # Threshold info
self.debug.press_yes()
yield # Threshold (3)
self.debug.input("3")
yield # Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases - show & confirm shares for all groups
self.mnemonics = yield from load_5_groups_5_shares(self.debug)
br = yield # Confirm backup
assert br.code == B.Success
self.debug.press_yes()
class InputFlowSlip39AdvancedResetRecovery(InputFlowBase):
def __init__(self, client: Client, click_info: bool):
super().__init__(client)
self.mnemonics: list[str] = []
self.click_info = click_info
def input_flow_tt(self) -> BRGeneratorType:
# 1. Confirm Reset
# 2. Backup your seed
# 3. Confirm warning
# 4. shares info
# 5. Set & Confirm number of groups
# 6. threshold info
# 7. Set & confirm group threshold value
# 8-17: for each of 5 groups:
# 1. Set & Confirm number of shares
# 2. Set & confirm share threshold value
# 18. Confirm show seeds
yield from click_through(self.debug, screens=18, code=B.ResetDevice)
# Mnemonic phrases - show & confirm shares for all groups
self.mnemonics = yield from load_5_groups_5_shares(self.debug)
br = yield # safety warning
assert br.code == B.Success
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
yield # Wallet backup
self.debug.press_yes()
yield # Wallet creation
self.debug.press_yes()
yield # Checklist
self.debug.press_yes()
yield # Set and confirm group count
self.debug.input("5")
yield # Checklist
self.debug.press_yes()
yield # Set and confirm group threshold
self.debug.input("3")
yield # Checklist
self.debug.press_yes()
for _ in range(5): # for each of 5 groups
yield # Number of shares info
self.debug.press_yes()
yield # Number of shares (5)
self.debug.input("5")
yield # Threshold info
self.debug.press_yes()
yield # Threshold (3)
self.debug.input("3")
yield # Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases - show & confirm shares for all groups
self.mnemonics = yield from load_5_groups_5_shares(self.debug)
br = yield # safety warning
assert br.code == B.Success
self.debug.press_yes()
class InputFlowBip39RecoveryDryRun(InputFlowBase):
def __init__(self, client: Client, mnemonic: list[str], mismatch: bool = False):
super().__init__(client)
self.mnemonic = mnemonic
self.mismatch = mismatch
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_dry_run()
yield from self.REC.setup_bip39_recovery(len(self.mnemonic))
yield from self.REC.input_mnemonic(self.mnemonic)
if self.mismatch:
yield from self.REC.warning_bip39_dryrun_mismatch()
else:
yield from self.REC.success_bip39_dry_run_valid()
class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.invalid_mnemonic = ["stick"] * 12
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_dry_run()
yield from self.REC.setup_bip39_recovery(len(self.invalid_mnemonic))
yield from self.REC.input_mnemonic(self.invalid_mnemonic)
yield from self.REC.warning_invalid_recovery_seed()
yield
self.client.cancel()
class InputFlowBip39Recovery(InputFlowBase):
def __init__(self, client: Client, mnemonic: list[str], pin: str | None = None):
super().__init__(client)
self.mnemonic = mnemonic
self.pin = pin
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
if self.pin is not None:
yield from self.PIN.setup_new_pin(self.pin)
yield from self.REC.setup_bip39_recovery(len(self.mnemonic))
yield from self.REC.input_mnemonic(self.mnemonic)
yield from self.REC.success_wallet_recovered()
class InputFlowSlip39AdvancedRecoveryDryRun(InputFlowBase):
def __init__(self, client: Client, shares: list[str], mismatch: bool = False):
super().__init__(client)
self.shares = shares
self.mismatch = mismatch
self.word_count = len(shares[0].split(" "))
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_dry_run()
yield from self.REC.setup_slip39_recovery(self.word_count)
yield from self.REC.input_all_slip39_shares(self.shares, has_groups=True)
if self.mismatch:
yield from self.REC.warning_slip39_dryrun_mismatch()
else:
yield from self.REC.success_slip39_dryrun_valid()
class InputFlowSlip39AdvancedRecovery(InputFlowBase):
def __init__(self, client: Client, shares: list[str], click_info: bool):
super().__init__(client)
self.shares = shares
self.click_info = click_info
self.word_count = len(shares[0].split(" "))
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
yield from self.REC.setup_slip39_recovery(self.word_count)
yield from self.REC.input_all_slip39_shares(
self.shares, has_groups=True, click_info=self.click_info
)
yield from self.REC.success_wallet_recovered()
class InputFlowSlip39AdvancedRecoveryAbort(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
if self.debug.model == "T":
yield from self.REC.input_number_of_words(20)
yield from self.REC.abort_recovery(True)
class InputFlowSlip39AdvancedRecoveryNoAbort(InputFlowBase):
def __init__(self, client: Client, shares: list[str]):
super().__init__(client)
self.shares = shares
self.word_count = len(shares[0].split(" "))
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
if self.debug.model == "T":
yield from self.REC.input_number_of_words(self.word_count)
yield from self.REC.abort_recovery(False)
else:
yield from self.REC.abort_recovery(False)
yield from self.REC.tr_recovery_homescreen()
yield from self.REC.input_number_of_words(self.word_count)
yield from self.REC.enter_any_share()
yield from self.REC.input_all_slip39_shares(self.shares, has_groups=True)
yield from self.REC.success_wallet_recovered()
class InputFlowSlip39AdvancedRecoveryThresholdReached(InputFlowBase):
def __init__(
self,
client: Client,
first_share: list[str],
second_share: list[str],
):
super().__init__(client)
self.first_share = first_share
self.second_share = second_share
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
yield from self.REC.setup_slip39_recovery(len(self.first_share))
yield from self.REC.input_mnemonic(self.first_share)
yield from self.REC.success_share_group_entered()
yield from self.REC.success_more_shares_needed()
yield from self.REC.input_mnemonic(self.second_share)
yield from self.REC.warning_group_threshold_reached()
yield
self.client.cancel()
class InputFlowSlip39AdvancedRecoveryShareAlreadyEntered(InputFlowBase):
def __init__(
self,
client: Client,
first_share: list[str],
second_share: list[str],
):
super().__init__(client)
self.first_share = first_share
self.second_share = second_share
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
yield from self.REC.setup_slip39_recovery(len(self.first_share))
yield from self.REC.input_mnemonic(self.first_share)
yield from self.REC.success_share_group_entered()
yield from self.REC.success_more_shares_needed()
yield from self.REC.input_mnemonic(self.second_share)
yield from self.REC.warning_share_already_entered()
yield
self.client.cancel()
class InputFlowSlip39BasicRecoveryDryRun(InputFlowBase):
def __init__(self, client: Client, shares: list[str], mismatch: bool = False):
super().__init__(client)
self.shares = shares
self.mismatch = mismatch
self.word_count = len(shares[0].split(" "))
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_dry_run()
yield from self.REC.setup_slip39_recovery(self.word_count)
yield from self.REC.input_all_slip39_shares(self.shares)
if self.mismatch:
yield from self.REC.warning_slip39_dryrun_mismatch()
else:
yield from self.REC.success_slip39_dryrun_valid()
class InputFlowSlip39BasicRecovery(InputFlowBase):
def __init__(self, client: Client, shares: list[str], pin: str | None = None):
super().__init__(client)
self.shares = shares
self.pin = pin
self.word_count = len(shares[0].split(" "))
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
if self.pin is not None:
yield from self.PIN.setup_new_pin(self.pin)
yield from self.REC.setup_slip39_recovery(self.word_count)
yield from self.REC.input_all_slip39_shares(self.shares)
yield from self.REC.success_wallet_recovered()
class InputFlowSlip39BasicRecoveryAbort(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
if self.debug.model == "T":
yield from self.REC.input_number_of_words(20)
yield from self.REC.abort_recovery(True)
class InputFlowSlip39BasicRecoveryNoAbort(InputFlowBase):
def __init__(self, client: Client, shares: list[str]):
super().__init__(client)
self.shares = shares
self.word_count = len(shares[0].split(" "))
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
if self.debug.model == "T":
yield from self.REC.input_number_of_words(self.word_count)
yield from self.REC.abort_recovery(False)
else:
yield from self.REC.abort_recovery(False)
yield from self.REC.tr_recovery_homescreen()
yield from self.REC.input_number_of_words(self.word_count)
yield from self.REC.enter_any_share()
yield from self.REC.input_all_slip39_shares(self.shares)
yield from self.REC.success_wallet_recovered()
class InputFlowSlip39BasicRecoveryInvalidFirstShare(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
self.first_invalid = ["slush"] * 20
self.second_invalid = ["slush"] * 33
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
yield from self.REC.setup_slip39_recovery(len(self.first_invalid))
yield from self.REC.input_mnemonic(self.first_invalid)
yield from self.REC.warning_invalid_recovery_share()
yield from self.REC.setup_slip39_recovery(len(self.second_invalid))
yield from self.REC.input_mnemonic(self.second_invalid)
yield from self.REC.warning_invalid_recovery_share()
yield
self.client.cancel()
class InputFlowSlip39BasicRecoveryInvalidSecondShare(InputFlowBase):
def __init__(self, client: Client, shares: list[str]):
super().__init__(client)
self.shares = shares
self.first_share = shares[0].split(" ")
self.invalid_share = self.first_share[:3] + ["slush"] * 17
self.second_share = shares[1].split(" ")
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
yield from self.REC.setup_slip39_recovery(len(self.first_share))
yield from self.REC.input_mnemonic(self.first_share)
yield from self.REC.success_more_shares_needed(2)
yield from self.REC.input_mnemonic(self.invalid_share)
yield from self.REC.warning_invalid_recovery_share()
yield from self.REC.input_mnemonic(self.second_share)
yield from self.REC.success_more_shares_needed(1)
yield
self.client.cancel()
class InputFlowSlip39BasicRecoveryWrongNthWord(InputFlowBase):
def __init__(self, client: Client, share: list[str], nth_word: int):
super().__init__(client)
self.share = share
self.nth_word = nth_word
# Invalid share - just enough words to trigger the warning
self.modified_share = share[:nth_word] + [self.share[-1]]
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
yield from self.REC.setup_slip39_recovery(len(self.share))
yield from self.REC.input_mnemonic(self.share)
yield from self.REC.success_more_shares_needed()
yield from self.REC.input_mnemonic(self.modified_share)
yield from self.REC.warning_share_from_another_shamir()
yield
self.client.cancel()
class InputFlowSlip39BasicRecoverySameShare(InputFlowBase):
def __init__(self, client: Client, share: list[str]):
super().__init__(client)
self.share = share
# Second duplicate share - only 4 words are needed to verify it
self.duplicate_share = self.share[:4]
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
yield from self.REC.setup_slip39_recovery(len(self.share))
yield from self.REC.input_mnemonic(self.share)
yield from self.REC.success_more_shares_needed()
yield from self.REC.input_mnemonic(self.duplicate_share)
yield from self.REC.warning_share_already_entered()
yield
self.client.cancel()
class InputFlowResetSkipBackup(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_common(self) -> BRGeneratorType:
yield from self.BAK.confirm_new_wallet()
yield # Skip Backup
info_path = (
"backup.new_wallet_created"
if self.debug.model == "Safe 3"
else "backup.new_wallet_successfully_created"
)
TR.assert_in(self.text_content(), info_path)
if self.debug.model == "Safe 3":
self.debug.press_right()
self.debug.press_no()
yield # Confirm skip backup
TR.assert_in(self.text_content(), "backup.want_to_skip")
self.debug.press_no()