from typing import TYPE_CHECKING from trezorlib.debuglink import LayoutType from .. import buttons from .. import translations as TR from .common import get_possible_btn_texts, go_next if TYPE_CHECKING: from trezorlib.debuglink import DebugLink, LayoutContent DELETE_BTN_TEXTS = get_possible_btn_texts("inputs__delete") + get_possible_btn_texts( "inputs__previous" ) def enter_word( debug: "DebugLink", word: str, is_slip39: bool = False ) -> "LayoutContent": if debug.layout_type in (LayoutType.TT, LayoutType.Mercury): typed_word = word[:4] for coords in buttons.type_word(typed_word, is_slip39=is_slip39): debug.click(coords) if debug.layout_type is LayoutType.Mercury and not is_slip39 and len(word) > 4: # T3T1 (mercury) BIP39 keyboard allows to "confirm" only if the word is fully written, you need to click the word to auto-complete debug.click(buttons.CONFIRM_WORD) return debug.click(buttons.CONFIRM_WORD) elif debug.layout_type is LayoutType.TR: letter_index = 0 layout = debug.read_layout() # Letter choices while layout.find_values_by_key("letter_choices"): letter = word[letter_index] while not layout.get_middle_choice() == letter: layout = debug.press_right() layout = debug.press_middle() letter_index += 1 # Word choices while not layout.get_middle_choice() == word: layout = debug.press_right() return debug.press_middle() else: raise ValueError("Unknown model") def confirm_recovery(debug: "DebugLink", title: str = "recovery__title") -> None: layout = debug.read_layout() TR.assert_equals(layout.title(), title) if debug.layout_type is LayoutType.TT: debug.click(buttons.OK) elif debug.layout_type is LayoutType.Mercury: debug.swipe_up() elif debug.layout_type is LayoutType.TR: debug.press_right() def select_number_of_words( debug: "DebugLink", num_of_words: int = 20, unlock_repeated_backup=False, ) -> None: def select_tt() -> "LayoutContent": # click the button from ValuePad if unlock_repeated_backup: coords_map = {20: buttons.grid34(0, 2), 33: buttons.grid34(1, 2)} else: coords_map = { 12: buttons.grid34(0, 2), 18: buttons.grid34(1, 2), 20: buttons.grid34(2, 2), 24: buttons.grid34(0, 3), 33: buttons.grid34(1, 3), } coords = coords_map.get(num_of_words) if coords is None: raise ValueError("Invalid num_of_words") return debug.click(coords) def select_tr() -> "LayoutContent": # navigate to the number and confirm it word_options = (20, 33) if unlock_repeated_backup else (12, 18, 20, 24, 33) index = word_options.index(num_of_words) for _ in range(index): debug.press_right() return debug.press_middle() def select_mercury() -> "LayoutContent": # click the button from ValuePad if unlock_repeated_backup: coords_map = {20: buttons.MERCURY_NO, 33: buttons.MERCURY_YES} else: coords_map = { 12: buttons.grid34(0, 1), 18: buttons.grid34(2, 1), 20: buttons.grid34(0, 2), 24: buttons.grid34(2, 2), 33: buttons.grid34(1, 3), } coords = coords_map.get(num_of_words) if coords is None: raise ValueError("Invalid num_of_words") return debug.click(coords) if debug.layout_type is LayoutType.TT: TR.assert_equals(debug.read_layout().text_content(), "recovery__num_of_words") layout = select_tt() elif debug.layout_type is LayoutType.TR: layout = debug.press_right() TR.assert_equals(layout.title(), "word_count__title") layout = select_tr() elif debug.layout_type is LayoutType.Mercury: layout = select_mercury() else: raise ValueError("Unknown model") if unlock_repeated_backup: if debug.layout_type is LayoutType.TR: TR.assert_in(layout.text_content(), "recovery__enter_backup") else: TR.assert_in_multiple( layout.text_content(), ["recovery__only_first_n_letters", "recovery__enter_each_word"], ) elif num_of_words in (20, 33): TR.assert_in_multiple( layout.text_content(), [ "recovery__enter_backup", "recovery__enter_any_share", "recovery__only_first_n_letters", "recovery__enter_each_word", ], ) else: # BIP-39 TR.assert_in_multiple( layout.text_content(), [ "recovery__enter_backup", "recovery__only_first_n_letters", "recovery__enter_each_word", ], ) def enter_share( debug: "DebugLink", share: str, is_first: bool = True, before_title: str = "recovery__title_recover", ) -> "LayoutContent": if debug.layout_type is LayoutType.TR: TR.assert_in(debug.read_layout().title(), before_title) layout = debug.read_layout() for _ in range(layout.page_count()): layout = debug.press_right() elif debug.layout_type is LayoutType.Mercury: layout = debug.swipe_up() else: TR.assert_in(debug.read_layout().title(), before_title) layout = debug.click(buttons.OK) assert "MnemonicKeyboard" in layout.all_components() for word in share.split(" "): layout = enter_word(debug, word, is_slip39=True) return layout def enter_shares( debug: "DebugLink", shares: list[str], enter_share_before_title: str = "recovery__title_recover", text: str = "recovery__enter_any_share", after_layout_text: str = "recovery__wallet_recovered", ) -> None: TR.assert_in_multiple( debug.read_layout().text_content(), [ "recovery__enter_backup", "recovery__enter_any_share", "recovery__only_first_n_letters", "recovery__enter_each_word", text, ], ) for index, share in enumerate(shares): enter_share( debug, share, is_first=index == 0, before_title=enter_share_before_title ) if index < len(shares) - 1: # FIXME: when ui-t3t1 done for shamir, we want to check the template below TR.assert_in(debug.read_layout().title(), enter_share_before_title) # TR.assert_in( # debug.read_layout().text_content(), # "recovery__x_of_y_entered_template", # template=(index + 1, len(shares)), # ) TR.assert_in(debug.read_layout().text_content(), after_layout_text) def enter_seed( debug: "DebugLink", seed_words: list[str], is_slip39=False, prepare_layout_text: str = "recovery__enter_backup", after_layout_text: str = "recovery__wallet_recovered", ) -> None: prepare_enter_seed(debug, prepare_layout_text) for word in seed_words: enter_word(debug, word, is_slip39=is_slip39) TR.assert_in(debug.read_layout().text_content(), after_layout_text) def enter_seed_previous_correct( debug: "DebugLink", seed_words: list[str], bad_indexes: dict[int, str] ) -> None: prepare_enter_seed(debug) i = 0 go_back = False bad_word = "" while True: assert i >= 0 if i >= len(seed_words): break if go_back: go_back = False if debug.layout_type is LayoutType.TT: debug.swipe_right() for _ in range(len(bad_word)): debug.click(buttons.RECOVERY_DELETE) elif debug.layout_type is LayoutType.TR: layout = debug.read_layout() while layout.get_middle_choice() not in DELETE_BTN_TEXTS: layout = debug.press_right() layout = debug.press_middle() for _ in range(len(bad_word)): while layout.get_middle_choice() not in DELETE_BTN_TEXTS: layout = debug.press_left() layout = debug.press_middle() elif debug.layout_type is LayoutType.Mercury: debug.click(buttons.RECOVERY_DELETE) # Top-left for _ in range(len(bad_word)): debug.click(buttons.RECOVERY_DELETE) continue if i in bad_indexes: word = bad_indexes.pop(i) bad_word = word go_back = True else: word = seed_words[i] i += 1 layout = enter_word(debug, word, is_slip39=False) # TR.assert_in(debug.read_layout().text_content(), "recovery__wallet_recovered") def prepare_enter_seed( debug: "DebugLink", layout_text: str = "recovery__enter_backup" ) -> None: TR.assert_in_multiple( debug.read_layout().text_content(), [ "recovery__enter_backup", "recovery__only_first_n_letters", "recovery__enter_each_word", layout_text, ], ) if debug.layout_type is LayoutType.TT: debug.click(buttons.OK) elif debug.layout_type is LayoutType.Mercury: debug.swipe_up() debug.swipe_up() elif debug.layout_type is LayoutType.TR: debug.press_right() debug.press_right() layout = debug.press_right() assert "MnemonicKeyboard" in layout.all_components() def finalize(debug: "DebugLink") -> None: layout = go_next(debug) assert layout is not None assert layout.main_component() == "Homescreen"