diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 1b0dec8883..be36188533 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -24,6 +24,7 @@ static void _librust_qstrs(void) { MP_QSTR_confirm_modify_output; MP_QSTR_confirm_output; MP_QSTR_confirm_payment_request; + MP_QSTR_confirm_reset_device; MP_QSTR_confirm_text; MP_QSTR_confirm_total; MP_QSTR_show_error; @@ -35,6 +36,8 @@ static void _librust_qstrs(void) { MP_QSTR_request_passphrase; MP_QSTR_request_bip39; MP_QSTR_request_slip39; + MP_QSTR_select_word; + MP_QSTR_show_share_words; MP_QSTR_attach_timer_fn; MP_QSTR_touch_event; @@ -70,4 +73,6 @@ static void _librust_qstrs(void) { MP_QSTR_total_amount; MP_QSTR_total_fee_new; MP_QSTR_user_fee_change; + MP_QSTR_words; + MP_QSTR_pages; } diff --git a/core/embed/rust/src/ui/model_tt/component/button.rs b/core/embed/rust/src/ui/model_tt/component/button.rs index 5026262c5e..7e79f593a8 100644 --- a/core/embed/rust/src/ui/model_tt/component/button.rs +++ b/core/embed/rust/src/ui/model_tt/component/button.rs @@ -429,6 +429,31 @@ impl Button { }), ) } + + pub fn select_word( + words: [T; 3], + ) -> CancelInfoConfirm< + T, + impl Fn(ButtonMsg) -> Option, + impl Fn(ButtonMsg) -> Option, + impl Fn(ButtonMsg) -> Option, + > + where + T: AsRef, + { + let btn = move |i, word| { + GridPlaced::new(Button::with_text(word)) + .with_grid(3, 1) + .with_spacing(theme::BUTTON_SPACING) + .with_row_col(i, 0) + .map(move |msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| SelectWordMsg::Selected(i)) + }) + }; + + let [top, middle, bottom] = words; + (btn(0, top), btn(1, middle), btn(2, bottom)) + } } type CancelConfirm = ( @@ -452,3 +477,7 @@ pub enum CancelInfoConfirmMsg { Info, Confirmed, } + +pub enum SelectWordMsg { + Selected(usize), +} diff --git a/core/embed/rust/src/ui/model_tt/component/hold_to_confirm.rs b/core/embed/rust/src/ui/model_tt/component/hold_to_confirm.rs index 58f5e0f96f..2b1a8fc382 100644 --- a/core/embed/rust/src/ui/model_tt/component/hold_to_confirm.rs +++ b/core/embed/rust/src/ui/model_tt/component/hold_to_confirm.rs @@ -111,7 +111,7 @@ where } pub struct CancelHold { - cancel: Button<&'static str>, + cancel: Option>, hold: Button<&'static str>, } @@ -123,7 +123,14 @@ pub enum CancelHoldMsg { impl CancelHold { pub fn new() -> Self { Self { - cancel: Button::with_icon(theme::ICON_CANCEL), + cancel: Some(Button::with_icon(theme::ICON_CANCEL)), + hold: Button::with_text("HOLD TO CONFIRM").styled(theme::button_confirm()), + } + } + + pub fn without_cancel() -> Self { + Self { + cancel: None, hold: Button::with_text("HOLD TO CONFIRM").styled(theme::button_confirm()), } } @@ -133,10 +140,14 @@ impl Component for CancelHold { type Msg = CancelHoldMsg; fn place(&mut self, bounds: Rect) -> Rect { - let grid = Grid::new(bounds, 1, 4).with_spacing(theme::BUTTON_SPACING); - self.cancel.place(grid.row_col(0, 0)); - self.hold - .place(grid.row_col(0, 1).union(grid.row_col(0, 3))); + if self.cancel.is_some() { + let grid = Grid::new(bounds, 1, 4).with_spacing(theme::BUTTON_SPACING); + self.cancel.place(grid.row_col(0, 0)); + self.hold + .place(grid.row_col(0, 1).union(grid.row_col(0, 3))); + } else { + self.hold.place(bounds); + }; bounds } diff --git a/core/embed/rust/src/ui/model_tt/component/mod.rs b/core/embed/rust/src/ui/model_tt/component/mod.rs index dff2ec3b8d..3ebd21bd53 100644 --- a/core/embed/rust/src/ui/model_tt/component/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/mod.rs @@ -10,7 +10,7 @@ mod swipe; pub use button::{ Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg, - CancelInfoConfirmMsg, + CancelInfoConfirmMsg, SelectWordMsg, }; pub use dialog::{Dialog, DialogLayout, DialogMsg, IconDialog}; pub use frame::Frame; diff --git a/core/embed/rust/src/ui/model_tt/component/page.rs b/core/embed/rust/src/ui/model_tt/component/page.rs index f33051c30e..10e99094cb 100644 --- a/core/embed/rust/src/ui/model_tt/component/page.rs +++ b/core/embed/rust/src/ui/model_tt/component/page.rs @@ -237,6 +237,14 @@ where loader: Loader::new(), } } + + pub fn without_cancel(content: T, background: Color) -> Self { + let buttons = CancelHold::without_cancel(); + Self { + inner: SwipePage::new(content, buttons, background), + loader: Loader::new(), + } + } } impl Component for SwipeHoldPage diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index cc84838901..43780f9018 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -1,4 +1,5 @@ -use core::{convert::TryInto, ops::Deref}; +use core::{cmp::Ordering, convert::TryInto, ops::Deref}; +use cstr_core::cstr; use crate::{ error::Error, @@ -13,7 +14,6 @@ use crate::{ }, ui::{ component::{ - self, base::ComponentExt, paginated::{PageMsg, Paginate}, painter, @@ -30,10 +30,10 @@ use crate::{ use super::{ component::{ - Bip39Input, Button, ButtonMsg, CancelConfirmMsg, CancelInfoConfirmMsg, Dialog, DialogMsg, - Frame, HoldToConfirm, HoldToConfirmMsg, IconDialog, MnemonicInput, MnemonicKeyboard, - MnemonicKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, - PinKeyboardMsg, Slip39Input, SwipeHoldPage, SwipePage, + Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, CancelInfoConfirmMsg, + Dialog, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg, IconDialog, MnemonicInput, + MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg, + PinKeyboard, PinKeyboardMsg, SelectWordMsg, Slip39Input, SwipeHoldPage, SwipePage, }, theme, }; @@ -61,6 +61,16 @@ impl TryFrom for Obj { } } +impl TryFrom for Obj { + type Error = Error; + + fn try_from(value: SelectWordMsg) -> Result { + match value { + SelectWordMsg::Selected(i) => i.try_into(), + } + } +} + impl ComponentMsgObj for Dialog where T: ComponentMsgObj, @@ -265,6 +275,27 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; + let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; + let description: StrBuffer = "\nBy continuing you agree to".into(); + let url: StrBuffer = "https://trezor.io/tos".into(); + + let paragraphs = Paragraphs::new() + .add(theme::TEXT_BOLD, prompt) + .add(theme::TEXT_NORMAL, description) + .add(theme::TEXT_BOLD, url); + + let buttons = Button::cancel_confirm_text(None, "CONTINUE"); + let obj = LayoutObj::new( + Frame::new(title, SwipePage::new(paragraphs, buttons, theme::BG)).into_child(), + )?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_show_qr(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; @@ -601,6 +632,59 @@ extern "C" fn new_request_slip39(n_args: usize, args: *const Obj, kwargs: *mut M unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; + let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; + let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?; + + let mut words = [StrBuffer::empty(), StrBuffer::empty(), StrBuffer::empty()]; + let mut iter_buf = IterBuf::new(); + let mut iter = Iter::try_from_obj_with_buf(words_iterable, &mut iter_buf)?; + let words_err = || Error::ValueError(cstr!("Invalid words count")); + for item in &mut words { + *item = iter.next().ok_or_else(words_err)?.try_into()?; + } + if iter.next().is_some() { + return Err(words_err()); + } + + let paragraphs = Paragraphs::new().add(theme::TEXT_NORMAL, description); + let buttons = Button::select_word(words); + + let obj = LayoutObj::new( + Frame::new( + title, + SwipePage::new(paragraphs, buttons, theme::BG).with_button_rows(3), + ) + .into_child(), + )?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; + let pages: Obj = kwargs.get(Qstr::MP_QSTR_pages)?; + + let mut paragraphs = Paragraphs::new(); + let mut iter_buf = IterBuf::new(); + let iter = Iter::try_from_obj_with_buf(pages, &mut iter_buf)?; + for page in iter { + let text: StrBuffer = page.try_into()?; + paragraphs = paragraphs.add(theme::TEXT_MONO, text).add_break(); + } + + let obj = LayoutObj::new( + Frame::new(title, SwipeHoldPage::without_cancel(paragraphs, theme::BG)).into_child(), + )?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + #[no_mangle] pub static mp_module_trezorui2: Module = obj_module! { Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(), @@ -640,6 +724,14 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Confirm byte sequence data.""" Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), + /// def confirm_reset_device( + /// *, + /// title: str, + /// prompt: str, + /// ) -> object: + /// """Confirm TOS before device setup.""" + Qstr::MP_QSTR_confirm_reset_device => obj_fn_kw!(0, new_confirm_reset_device).as_obj(), + /// def show_qr( /// *, /// title: str, @@ -784,6 +876,24 @@ pub static mp_module_trezorui2: Module = obj_module! { /// ) -> str: /// """SLIP39 word input keyboard.""" Qstr::MP_QSTR_request_slip39 => obj_fn_kw!(0, new_request_slip39).as_obj(), + + /// def select_word( + /// *, + /// title: str, + /// description: str, + /// words: Iterable[str], + /// ) -> int: + /// """Select mnemonic word from three possibilities - seed check after backup. The + /// iterable must be of exact size. Returns index in range `0..3`.""" + Qstr::MP_QSTR_select_word => obj_fn_kw!(0, new_select_word).as_obj(), + + /// def show_share_words( + /// *, + /// title: str, + /// pages: Iterable[str], + /// ) -> object: + /// """Show mnemonic for backup. Expects the words pre-divided into individual pages.""" + Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), }; #[cfg(test)] diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 5cbc0f6654..5a9af2d353 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -84,6 +84,15 @@ def confirm_blob( """Confirm byte sequence data.""" +# rust/src/ui/model_tt/layout.rs +def confirm_reset_device( + *, + title: str, + prompt: str, +) -> object: + """Confirm TOS before device setup.""" + + # rust/src/ui/model_tt/layout.rs def show_qr( *, @@ -243,3 +252,23 @@ def request_slip39( prompt: str, ) -> str: """SLIP39 word input keyboard.""" + + +# rust/src/ui/model_tt/layout.rs +def select_word( + *, + title: str, + description: str, + words: Iterable[str], +) -> int: + """Select mnemonic word from three possibilities - seed check after backup. The + iterable must be of exact size. Returns index in range `0..3`.""" + + +# rust/src/ui/model_tt/layout.rs +def show_share_words( + *, + title: str, + pages: Iterable[str], +) -> object: + """Show mnemonic for backup. Expects the words pre-divided into individual pages.""" diff --git a/core/src/apps/management/reset_device/layout.py b/core/src/apps/management/reset_device/layout.py index 87c80b0a01..3b046fa3e9 100644 --- a/core/src/apps/management/reset_device/layout.py +++ b/core/src/apps/management/reset_device/layout.py @@ -1,11 +1,13 @@ from typing import Sequence from trezor import ui, utils, wire +from trezor.crypto import random from trezor.enums import ButtonRequestType -from trezor.ui.layouts import confirm_action, confirm_blob, show_success, show_warning +from trezor.ui.layouts import confirm_blob, show_success, show_warning from trezor.ui.layouts.reset import ( # noqa: F401 - confirm_word, + select_word, show_share_words, + show_warning_backup, slip39_advanced_prompt_group_threshold, slip39_advanced_prompt_number_of_groups, slip39_prompt_number_of_shares, @@ -13,6 +15,11 @@ from trezor.ui.layouts.reset import ( # noqa: F401 slip39_show_checklist, ) +if __debug__: + from apps import debug + +NUM_OF_CHOICES = 3 + async def show_internal_entropy(ctx: wire.GenericContext, entropy: bytes) -> None: await confirm_blob( @@ -26,6 +33,38 @@ async def show_internal_entropy(ctx: wire.GenericContext, entropy: bytes) -> Non ) +async def _confirm_word( + ctx: wire.GenericContext, + share_index: int | None, + share_words: Sequence[str], + offset: int, + count: int, + group_index: int | None = None, +) -> bool: + # remove duplicates + non_duplicates = list(set(share_words)) + # shuffle list + random.shuffle(non_duplicates) + # take top NUM_OF_CHOICES words + choices = non_duplicates[:NUM_OF_CHOICES] + # select first of them + checked_word = choices[0] + # find its index + checked_index = share_words.index(checked_word) + offset + # shuffle again so the confirmed word is not always the first choice + random.shuffle(choices) + + if __debug__: + debug.reset_word_index.publish(checked_index) + + # let the user pick a word + selected_word: str = await select_word( + ctx, choices, share_index, checked_index, count, group_index + ) + # confirm it is the correct one + return selected_word == checked_word + + async def _confirm_share_words( ctx: wire.GenericContext, share_index: int | None, @@ -39,7 +78,7 @@ async def _confirm_share_words( offset = 0 count = len(share_words) for part in utils.chunks(share_words, third): - if not await confirm_word(ctx, share_index, part, offset, count, group_index): + if not await _confirm_word(ctx, share_index, part, offset, count, group_index): return False offset += len(part) @@ -93,20 +132,7 @@ async def _show_confirmation_failure( async def show_backup_warning(ctx: wire.GenericContext, slip39: bool = False) -> None: - if slip39: - description = "Never make a digital copy of your recovery shares and never upload them online!" - else: - description = "Never make a digital copy of your recovery seed and never upload\nit online!" - await confirm_action( - ctx, - "backup_warning", - "Caution", - description=description, - verb="I understand", - verb_cancel=None, - icon=ui.ICON_NOCOPY, - br_code=ButtonRequestType.ResetDevice, - ) + await show_warning_backup(ctx, slip39) async def show_backup_success(ctx: wire.GenericContext) -> None: diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index 9a87cd6b9b..2df9a80eb6 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -553,7 +553,7 @@ async def should_show_more( br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_DEFAULT, icon_color: int = ui.ORANGE_ICON, - confirm: ButtonContent = Confirm.DEFAULT_CONFIRM, + confirm: str | bytes | None = None, major_confirm: bool = False, ) -> bool: """Return True if the user wants to show more (they click a special button) @@ -561,6 +561,9 @@ async def should_show_more( Raises ActionCancelled if the user cancels. """ + if confirm is None: + confirm = Confirm.DEFAULT_CONFIRM + page = Text( title, header_icon=icon, diff --git a/core/src/trezor/ui/layouts/tt/reset.py b/core/src/trezor/ui/layouts/tt/reset.py index 519212e2e9..5172eb61f1 100644 --- a/core/src/trezor/ui/layouts/tt/reset.py +++ b/core/src/trezor/ui/layouts/tt/reset.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING from trezor import ui, utils, wire -from trezor.crypto import random from trezor.enums import BackupType, ButtonRequestType from ...components.common.confirm import is_confirmed, raise_if_cancelled @@ -13,6 +12,7 @@ from ...components.tt.reset import MnemonicWordSelect, Slip39NumInput from ...components.tt.scroll import Paginated from ...components.tt.text import Text from ..common import interact +from . import confirm_action if TYPE_CHECKING: from typing import Sequence @@ -100,35 +100,18 @@ async def show_share_words( ) -async def confirm_word( +async def select_word( ctx: wire.GenericContext, + words: Sequence[str], share_index: int | None, - share_words: Sequence[str], - offset: int, + checked_index: int, count: int, group_index: int | None = None, -) -> bool: - # remove duplicates - non_duplicates = list(set(share_words)) - # shuffle list - random.shuffle(non_duplicates) - # take top NUM_OF_CHOICES words - choices = non_duplicates[: MnemonicWordSelect.NUM_OF_CHOICES] - # select first of them - checked_word = choices[0] - # find its index - checked_index = share_words.index(checked_word) + offset - # shuffle again so the confirmed word is not always the first choice - random.shuffle(choices) - - if __debug__: - debug.reset_word_index.publish(checked_index) - +) -> str: # let the user pick a word - select = MnemonicWordSelect(choices, share_index, checked_index, count, group_index) + select = MnemonicWordSelect(words, share_index, checked_index, count, group_index) selected_word: str = await ctx.wait(select) - # confirm it is the correct one - return selected_word == checked_word + return selected_word def _split_share_into_pages( @@ -361,3 +344,20 @@ async def slip39_advanced_prompt_group_threshold( await info return count + + +async def show_warning_backup(ctx: wire.GenericContext, slip39: bool) -> None: + if slip39: + description = "Never make a digital copy of your recovery shares and never upload them online!" + else: + description = "Never make a digital copy of your recovery seed and never upload\nit online!" + await confirm_action( + ctx, + "backup_warning", + "Caution", + description=description, + verb="I understand", + verb_cancel=None, + icon=ui.ICON_NOCOPY, + br_code=ButtonRequestType.ResetDevice, + ) diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index 11dda96d5e..64f51d8ca2 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -12,15 +12,18 @@ if TYPE_CHECKING: from typing import Any, Awaitable, Iterable, NoReturn, Sequence from ..common import PropertyType, ExceptionType - from ...components.tt.button import ButtonContent class _RustLayout(ui.Layout): # pylint: disable=super-init-not-called - def __init__(self, layout: Any): + def __init__(self, layout: Any, is_backup=False): self.layout = layout self.timer = loop.Timer() self.layout.attach_timer_fn(self.set_timer) + self.is_backup = is_backup + + if __debug__ and self.is_backup: + self.notify_backup() def set_timer(self, token: int, deadline: int) -> None: self.timer.schedule(deadline, token) @@ -78,8 +81,27 @@ class _RustLayout(ui.Layout): if msg is not None: raise ui.Result(msg) + if self.is_backup: + self.notify_backup() notify_layout_change(self) + def notify_backup(self): + from apps.debug import reset_current_words + + content = "\n".join(self.read_content()) + start = "< Paragraphs " + end = ">" + start_pos = content.index(start) + end_pos = content.index(end, start_pos) + words = [] + for line in content[start_pos + len(start) : end_pos].split("\n"): + line = line.strip() + if not line: + continue + space_pos = line.index(" ") + words.append(line[space_pos + 1 :]) + reset_current_words.publish(words) + else: def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: @@ -186,17 +208,60 @@ async def confirm_action( async def confirm_reset_device( ctx: wire.GenericContext, prompt: str, recovery: bool = False ) -> None: - return await confirm_action( + if recovery: + title = "RECOVERY MODE" + else: + title = "CREATE NEW WALLET" + + result = await interact( ctx, + _RustLayout( + trezorui2.confirm_reset_device( + title=title.upper(), + prompt=prompt.replace("\n", " "), + ) + ), "recover_device" if recovery else "setup_device", - "not implemented", - action="not implemented", + ButtonRequestType.ProtectCall if recovery else ButtonRequestType.ResetDevice, ) + if result is not trezorui2.CONFIRMED: + raise wire.ActionCancelled # TODO cleanup @ redesign async def confirm_backup(ctx: wire.GenericContext) -> bool: - raise NotImplementedError + result = await interact( + ctx, + _RustLayout( + trezorui2.confirm_action( + title="SUCCESS", + action="New wallet created successfully.", + description="You should back up your new wallet right now.", + verb="BACK UP", + verb_cancel="SKIP", + ) + ), + "backup_device", + ButtonRequestType.ResetDevice, + ) + if result is trezorui2.CONFIRMED: + return True + + result = await interact( + ctx, + _RustLayout( + trezorui2.confirm_action( + title="WARNING", + action="Are you sure you want to skip the backup?", + description="You can back up your Trezor once, at any time.", + verb="BACK UP", + verb_cancel="SKIP", + ) + ), + "backup_device", + ButtonRequestType.ResetDevice, + ) + return result is trezorui2.CONFIRMED async def confirm_path_warning( @@ -482,7 +547,7 @@ async def should_show_more( br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_DEFAULT, icon_color: int = ui.ORANGE_ICON, - confirm: ButtonContent = None, + confirm: str | bytes | None = None, major_confirm: bool = False, ) -> bool: raise NotImplementedError diff --git a/core/src/trezor/ui/layouts/tt_v2/recovery.py b/core/src/trezor/ui/layouts/tt_v2/recovery.py index 9e6e28c73d..c599fed486 100644 --- a/core/src/trezor/ui/layouts/tt_v2/recovery.py +++ b/core/src/trezor/ui/layouts/tt_v2/recovery.py @@ -58,4 +58,4 @@ async def continue_recovery( subtext: str | None, info_func: Callable | None, ) -> bool: - raise NotImplementedError + return False diff --git a/core/src/trezor/ui/layouts/tt_v2/reset.py b/core/src/trezor/ui/layouts/tt_v2/reset.py index eec1e19273..1bfc927b2d 100644 --- a/core/src/trezor/ui/layouts/tt_v2/reset.py +++ b/core/src/trezor/ui/layouts/tt_v2/reset.py @@ -1,10 +1,35 @@ from typing import TYPE_CHECKING from trezor import wire +from trezor.enums import ButtonRequestType + +import trezorui2 + +from ..common import interact +from . import _RustLayout if TYPE_CHECKING: from trezor.enums import BackupType - from typing import Sequence + from typing import Sequence, List + + +def _split_share_into_pages(share_words: Sequence[str], per_page: int = 4) -> List[str]: + pages = [] + current = "" + + for i, word in enumerate(share_words): + if i % per_page == 0: + if i != 0: + pages.append(current) + current = "" + else: + current += "\n" + current += f"{i + 1}. {word}" + + if current: + pages.append(current) + + return pages async def show_share_words( @@ -13,18 +38,75 @@ async def show_share_words( share_index: int | None = None, group_index: int | None = None, ) -> None: - raise NotImplementedError + if share_index is None: + title = "RECOVERY SEED" + elif group_index is None: + title = f"RECOVERY SHARE #{share_index + 1}" + else: + title = f"GROUP {group_index + 1} - SHARE {share_index + 1}" + + # result = await interact( + # ctx, + # _RustLayout( + # trezorui2.show_simple( + # title=title, + # description=f"Write down these {len(share_words)} words in the exact order:", + # button="SHOW WORDS", + # ), + # ), + # "confirm_backup_words", + # ButtonRequestType.ResetDevice, + # ) + # if result != trezorui2.CONFIRMED: + # raise wire.ActionCancelled + + pages = _split_share_into_pages(share_words) + + result = await interact( + ctx, + _RustLayout( + trezorui2.show_share_words( + title=title, + pages=pages, + ), + is_backup=True, + ), + "backup_words", + ButtonRequestType.ResetDevice, + ) + if result != trezorui2.CONFIRMED: + raise wire.ActionCancelled -async def confirm_word( +async def select_word( ctx: wire.GenericContext, + words: Sequence[str], share_index: int | None, - share_words: Sequence[str], - offset: int, + checked_index: int, count: int, group_index: int | None = None, -) -> bool: - raise NotImplementedError +) -> str: + assert len(words) == 3 + if share_index is None: + title: str = "CHECK SEED" + elif group_index is None: + title = f"CHECK SHARE #{share_index + 1}" + else: + title = f"CHECK G{group_index + 1} - SHARE {share_index + 1}" + + result = await ctx.wait( + _RustLayout( + trezorui2.select_word( + title=title, + description=f"Select word {checked_index + 1} of {count}:", + words=(words[0].upper(), words[1].upper(), words[2].upper()), + ) + ) + ) + if __debug__ and isinstance(result, str): + return result + assert isinstance(result, int) and 0 <= result <= 2 + return words[result] async def slip39_show_checklist( @@ -53,3 +135,28 @@ async def slip39_advanced_prompt_group_threshold( ctx: wire.GenericContext, num_of_groups: int ) -> int: raise NotImplementedError + + +async def show_warning_backup(ctx: wire.GenericContext, slip39: bool) -> None: + if slip39: + description = ( + "Never make a digital copy of your shares and never upload them online." + ) + else: + description = ( + "Never make a digital copy of your seed and never upload it online." + ) + result = await interact( + ctx, + _RustLayout( + trezorui2.show_info( + title=description, + button="OK, I UNDERSTAND", + allow_cancel=False, + ) + ), + "backup_warning", + ButtonRequestType.ResetDevice, + ) + if result != trezorui2.CONFIRMED: + raise wire.ActionCancelled diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index fd569e9f60..0a3da1b97e 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -1724,7 +1724,7 @@ "TTui2_bitcoin-test_getaddress.py::test_ltc": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_bitcoin-test_getaddress.py::test_multisig": "f3c9e4df672d2acb27fc61238df1e2828294a6f1602f52f07deb908cd1eb2cd8", "TTui2_bitcoin-test_getaddress.py::test_multisig_missing[False]": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", -"TTui2_bitcoin-test_getaddress.py::test_multisig_missing[True]": "6ff1829ad3c148f012adffb37eb4bcc7570c10a8b95b0920bdbf6339a1bbc0d8", +"TTui2_bitcoin-test_getaddress.py::test_multisig_missing[True]": "d868aec455776eba425411e94901a649d386cadacbeaa11be2f5864c5f00084a", "TTui2_bitcoin-test_getaddress.py::test_public_ckd": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_bitcoin-test_getaddress.py::test_tbtc": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_bitcoin-test_getaddress.py::test_tgrs": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", @@ -2469,9 +2469,9 @@ "TTui2_nem-test_signtx_others.py::test_nem_signtx_importance_transfer": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_nem-test_signtx_others.py::test_nem_signtx_provision_namespace": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_nem-test_signtx_transfers.py::test_nem_signtx_encrypted_payload": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", -"TTui2_nem-test_signtx_transfers.py::test_nem_signtx_known_mosaic": "c135119f81a060eb697e05249bbfcb269e8f8994d5d72f6b54701c7f7aa071fa", -"TTui2_nem-test_signtx_transfers.py::test_nem_signtx_known_mosaic_with_levy": "c135119f81a060eb697e05249bbfcb269e8f8994d5d72f6b54701c7f7aa071fa", -"TTui2_nem-test_signtx_transfers.py::test_nem_signtx_multiple_mosaics": "06d2cde39452bacb20743fd760e48b497b2d143b1f6dfeb0c25a1d14a3e8a127", +"TTui2_nem-test_signtx_transfers.py::test_nem_signtx_known_mosaic": "2491e0b47ce4509b549cc402916f99832024737700b407ccb51e8f6cd5fa63bf", +"TTui2_nem-test_signtx_transfers.py::test_nem_signtx_known_mosaic_with_levy": "2491e0b47ce4509b549cc402916f99832024737700b407ccb51e8f6cd5fa63bf", +"TTui2_nem-test_signtx_transfers.py::test_nem_signtx_multiple_mosaics": "709a7db8f7c70bf31db8050f42512e43ad83be5fc3e5bd710b02e1288bc09890", "TTui2_nem-test_signtx_transfers.py::test_nem_signtx_simple": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_nem-test_signtx_transfers.py::test_nem_signtx_unknown_mosaic": "f32f3ae5971dc6d1407ec49a4225e3128c67cd1f6298ac588d9e2809a58496cb", "TTui2_nem-test_signtx_transfers.py::test_nem_signtx_xem_as_mosaic": "08add8db0fa244b5ac5b0ea459a210d6f897df57407351904c9f23d785818140", @@ -2480,55 +2480,55 @@ "TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[passphrase_protection-True]": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[pin_protection-True]": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[u2f_counter-1]": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", -"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_dry_run": "af8dcf90f3fd9e92334af8cbcd966ad08091cc873f1942b07ace3c52b5518b2b", -"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "af8dcf90f3fd9e92334af8cbcd966ad08091cc873f1942b07ace3c52b5518b2b", -"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "af8dcf90f3fd9e92334af8cbcd966ad08091cc873f1942b07ace3c52b5518b2b", +"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_dry_run": "6163bdd261809e749bcc2eed7882b994eae98b5db28a4a6c27ded2c0ecce4294", +"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "6163bdd261809e749bcc2eed7882b994eae98b5db28a4a6c27ded2c0ecce4294", +"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "6163bdd261809e749bcc2eed7882b994eae98b5db28a4a6c27ded2c0ecce4294", "TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_uninitialized": "8711e2fa6f7b301add7641e08ffb4bacf29bcd41530b1dd435fdbddb49b4bdf8", "TTui2_reset_recovery-test_recovery_bip39_t2.py::test_already_initialized": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", -"TTui2_reset_recovery-test_recovery_bip39_t2.py::test_tt_nopin_nopassphrase": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_recovery_bip39_t2.py::test_tt_pin_passphrase": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_abort": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_extra_share_entered": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_group_threshold_reached": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_noabort": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "f27a0e59dc9a6a4fe81d202d96996e1e21e674aeab019cebd0dc3d30fa886283", -"TTui2_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "f27a0e59dc9a6a4fe81d202d96996e1e21e674aeab019cebd0dc3d30fa886283", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_abort": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_ask_word_number": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_noabort": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_recover_with_pin_passphrase": "19f6161fb065ed7bbe02591e8d5785bb94158a26dc2529f2afe49e321d52d671", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_same_share": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39-a50896b7": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[0]": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[1]": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", -"TTui2_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "f27a0e59dc9a6a4fe81d202d96996e1e21e674aeab019cebd0dc3d30fa886283", -"TTui2_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "f27a0e59dc9a6a4fe81d202d96996e1e21e674aeab019cebd0dc3d30fa886283", -"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "82266d0d8b0baaa8212546a0d538d2d54168c874a213a35fabdb4043662982de", -"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "82266d0d8b0baaa8212546a0d538d2d54168c874a213a35fabdb4043662982de", -"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "82266d0d8b0baaa8212546a0d538d2d54168c874a213a35fabdb4043662982de", -"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "8eefc05a61808386dd4b6ce4f96e7e963d019893c1d9047d8a1b86d7cc2bc1a0", -"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "809bc3086a900cc5bc0ee3a9d7218dc9b6b1da52543009e444e0bc3b6bb1571b", -"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "809bc3086a900cc5bc0ee3a9d7218dc9b6b1da52543009e444e0bc3b6bb1571b", +"TTui2_reset_recovery-test_recovery_bip39_t2.py::test_tt_nopin_nopassphrase": "3919d9404e9f9a4880bd084edbfa02fbb04641008e04b83458633691e69bf239", +"TTui2_reset_recovery-test_recovery_bip39_t2.py::test_tt_pin_passphrase": "3919d9404e9f9a4880bd084edbfa02fbb04641008e04b83458633691e69bf239", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_abort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_extra_share_entered": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_group_threshold_reached": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_noabort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "7a5048ee96f76bb2e2a6d64fd89dfc22eb6fe792eaa769058249d0f552ee59d3", +"TTui2_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "7a5048ee96f76bb2e2a6d64fd89dfc22eb6fe792eaa769058249d0f552ee59d3", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_abort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_ask_word_number": "e53306364b3a4cc2d23da5adeafa6f02fd946dcf042c6c77efd1ce221a319ea8", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_noabort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_recover_with_pin_passphrase": "ff0120b13a8ec8ecfe3a70d3dce62a9eaafa116632284d85983e7d1f040d6d4a", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_same_share": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39-a50896b7": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[0]": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[1]": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f", +"TTui2_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "7a5048ee96f76bb2e2a6d64fd89dfc22eb6fe792eaa769058249d0f552ee59d3", +"TTui2_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "7a5048ee96f76bb2e2a6d64fd89dfc22eb6fe792eaa769058249d0f552ee59d3", +"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "4f4ba72d808efd3b1c90493dc287fe22d64f1fa28d1b055c05b4b13bb662946e", +"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "1d2e196f1f194c41aa4ddc6a5968fea311583f2b1447812fee59410fd19aaca0", +"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "b99b45445bb22a6a2954c87c895770580e069d61eebd5bb13f0e7411d69b692e", +"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "19654fc05ca617863fd0788e34bb57a0c209d863f780f16e4ecb952446ca6409", +"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "536cf8b5905ed9e36b37bbef57567e3bfc0a48e192073d81e2bfce1da3cb8e03", +"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "c36cf19489c96fc349d584d9cd3ba4f9ff86f16ad3d202d55dc0570a5b0b5b9e", "TTui2_reset_recovery-test_reset_bip39_t2.py::test_already_initialized": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", -"TTui2_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "049ce514a8f8570d1a7fa948930226a2df8b4f3ba6fa823dd54a387a3571437d", -"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", -"TTui2_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", +"TTui2_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "e34eb8420d9e74571c36e39352ba2308d90a021b2f5ef2e78afb167764ea931d", +"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "23bf8446c0c9b9f948b9734b0cb4062d004e6b7b02b950e557ad34e47b4b3889", +"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "44f01af26a3a1a6abde60a60de781b69bab9d7cf41c50ca16522e423e01da9a5", +"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "32ce727d1add7d2150296a10332250f768544a088e35a123091c6a5ff7d8b549", +"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "79e75a5a75aaae9c05f598e5ef273734d8de3b9416bd747a9d58326d57090b1f", +"TTui2_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "26df7da23999be182499ccafc8252513afdb0d0e288adbe37b393cf80a7e0662", +"TTui2_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "92a0fd39f25845744194dc1ae04d5eebf268f658161ab7e685b4179d2c092e82", +"TTui2_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "38eacc7bb14cdbd21bcc971cd2916f288b7929bd7b4e9cf2d49c66a81b981126", +"TTui2_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "92a0fd39f25845744194dc1ae04d5eebf268f658161ab7e685b4179d2c092e82", +"TTui2_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "38eacc7bb14cdbd21bcc971cd2916f288b7929bd7b4e9cf2d49c66a81b981126", +"TTui2_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "38eacc7bb14cdbd21bcc971cd2916f288b7929bd7b4e9cf2d49c66a81b981126", "TTui2_ripple-test_get_address.py::test_ripple_get_address": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_ripple-test_get_address.py::test_ripple_get_address_other": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_ripple-test_sign_tx.py::test_ripple_sign_invalid_fee": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", @@ -2619,26 +2619,26 @@ "TTui2_test_msg_backup_device.py::test_interrupt_backup_fails": "a8b5bc47867681b496da4b7473cde4fa43027c01fb071c2b0dcf97804809643f", "TTui2_test_msg_backup_device.py::test_no_backup_fails": "ffc38ab2b61939fea6883a4805b2a4eb17a0be03afe0fed3b1cca492b50bb25c", "TTui2_test_msg_backup_device.py::test_no_backup_show_entropy_fails": "8711e2fa6f7b301add7641e08ffb4bacf29bcd41530b1dd435fdbddb49b4bdf8", -"TTui2_test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "b8e61812384abdef4b074655bd10e8a5166ec066911de7636064b3dd81dc490f", -"TTui2_test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "e56192b67b4fd4ee741ea190408b7af0fb95336312898db4cf54d73f752364b1", +"TTui2_test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "7f9c7e2550d4e515c7c9785a11dfcc2ce583925024ea2f52c859dc96de681afc", +"TTui2_test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "b09db51b47e37ec94d4a2d8b063e1ed0ad95cb11215f43acf10453aadf7b7bb2", "TTui2_test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "8fd746c535ec5add348b76002a7936cc85c3206edbb59f225ad075912329452d", "TTui2_test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "25eac0cb6ea45c0cb9cfcad3b4ac3ec33af9212a7b812370c8132ef9f14c7700", "TTui2_test_msg_changepin_t2.py::test_change_failed": "e207e2c62f6930e9e112d7a1a31b9a66c14580df8aac82ea40e2f243d987e878", -"TTui2_test_msg_changepin_t2.py::test_change_invalid_current": "1aa0bcb26493a94c4b8c4b6aec4080a00acce8f7fc9e7e9058dc227ebd0d79d1", -"TTui2_test_msg_changepin_t2.py::test_change_pin": "431ef817370e02d85a05d1a96b96f44d034c16303aa41b4ef66eeca664657ae2", -"TTui2_test_msg_changepin_t2.py::test_remove_pin": "9c80fc89ab9a3b9a5cdc9f5017caae777ce7727895a91169e2625d40f7dd88ef", +"TTui2_test_msg_changepin_t2.py::test_change_invalid_current": "9dce14051a6872469af20e9e8d03fe033908e32837df6a9dc31feb2db15bf398", +"TTui2_test_msg_changepin_t2.py::test_change_pin": "bef4ccf69a801159487ac24ff1218953e5592a6da618d4f10bcece03e42283cc", +"TTui2_test_msg_changepin_t2.py::test_remove_pin": "7770b9d9ba1c74f2184bf4413816fd20722996c12b9ed67880a45fd674cf8a16", "TTui2_test_msg_changepin_t2.py::test_set_failed": "391b309cadaefcaab9086f7e003faec88b7e38c13f2738b5ad1aa4bfd5d89566", -"TTui2_test_msg_changepin_t2.py::test_set_pin": "f66d5c7faffdb91a739061b7f8cc059e1079c379e80e28aef79136f8af0ce96c", +"TTui2_test_msg_changepin_t2.py::test_set_pin": "8240ad8ff105e7cbc3e01e3cc917b55b177f3a7e67643e76678d49f92e7b0a04", "TTui2_test_msg_loaddevice.py::test_load_device_1": "eeb5afb34b4bbf42b8c635fdd34bae5c1e3693facb16e6d64e629746612a2c3f", "TTui2_test_msg_loaddevice.py::test_load_device_2": "a95020926a62b4078cb0034f6e7a772e49fc42121c9197b534437e26c306a994", "TTui2_test_msg_loaddevice.py::test_load_device_slip39_advanced": "eeb5afb34b4bbf42b8c635fdd34bae5c1e3693facb16e6d64e629746612a2c3f", "TTui2_test_msg_loaddevice.py::test_load_device_slip39_basic": "eeb5afb34b4bbf42b8c635fdd34bae5c1e3693facb16e6d64e629746612a2c3f", "TTui2_test_msg_loaddevice.py::test_load_device_utf": "7eddfcc018eb3b5847e2617b1a9495632430ca5494f69063082a5063c5702dcf", "TTui2_test_msg_ping.py::test_ping": "9b44725459426439bc27f2cf72ee926ab7146f3ee1236d197382524cdf9a89a1", -"TTui2_test_msg_sd_protect.py::test_enable_disable": "3d007f86722cf8f74c0544a8f03ef12b5be0e1da880c6d2543527116a2d6aa2c", -"TTui2_test_msg_sd_protect.py::test_refresh": "cef6f6f310d38678b9a70e3d6003f815b5501bb4360de1f47052889c3e0a3c3f", -"TTui2_test_msg_sd_protect.py::test_wipe": "41fc0f48b51daedef1ca19181b336a2b35117727ce64393bd1c2ae2e6b772e55", -"TTui2_test_msg_wipedevice.py::test_autolock_not_retained": "54c7800f784832b08e5505712e4f9e7052f49870ea1c50a66ed6520c97be166d", +"TTui2_test_msg_sd_protect.py::test_enable_disable": "1e64e5d08faac781f2bbba45b114a588c8a63d5a1865deaa0bc11c67ae891ea9", +"TTui2_test_msg_sd_protect.py::test_refresh": "d295a23bfc9b0fdef75d6732a5dd7fea7059a05fc566c599eddef75dfc05fcbb", +"TTui2_test_msg_sd_protect.py::test_wipe": "d6e2c4224f6d6970c9339bb29c08f8e88308fcfd9ef6bac2cd6f56b7a520747a", +"TTui2_test_msg_wipedevice.py::test_autolock_not_retained": "5200f217377bac4a795e1d5bb5963d4bb03a0ec6875a3272faca2572f8c7da2e", "TTui2_test_msg_wipedevice.py::test_wipe_device": "36fd19373828ac579ae2e0eaf34c050ac9ea95596cfe38c447737acba86ec706", "TTui2_test_passphrase_slip39_advanced.py::test_128bit_passphrase": "68e7d02ee3038fa20f0ccc226abdc29c422aa0d3b0c54533869276cd08a7a5b8", "TTui2_test_passphrase_slip39_advanced.py::test_256bit_passphrase": "68e7d02ee3038fa20f0ccc226abdc29c422aa0d3b0c54533869276cd08a7a5b8", @@ -2649,7 +2649,7 @@ "TTui2_test_pin.py::test_incorrect_pin_t2": "cecd9cc23e1fab56f7df9c0a88b309f5fdd9f29ef97e0f5ba0b808cea2d11759", "TTui2_test_pin.py::test_no_protection": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_test_protection_levels.py::test_apply_settings": "294a58f6e0222746f27bdf80014de14cf0b2d298bf806456ee94fd814e301cba", -"TTui2_test_protection_levels.py::test_change_pin_t2": "cfcb922326b22471bd868eb355b1e9c18d34a8c425e33f8c5b52fad15f357b0f", +"TTui2_test_protection_levels.py::test_change_pin_t2": "733e6e94ad06fdb8f270a0bc50ba613de7fd40cff2f764f4828f4ffd7c3c285a", "TTui2_test_protection_levels.py::test_get_address": "ef09d088bf4ca767162d5017748158bb8dda9849ccb0bf9ca5acf32b872e260c", "TTui2_test_protection_levels.py::test_get_entropy": "7eadf62627e7a2c5a69b94c72eb4daca0153afb93ab8a12fd85d0d4ddc0a5a1d", "TTui2_test_protection_levels.py::test_get_public_key": "ef09d088bf4ca767162d5017748158bb8dda9849ccb0bf9ca5acf32b872e260c", @@ -2663,7 +2663,7 @@ "TTui2_test_protection_levels.py::test_unlocked": "ed7d5e2c6bac6b7e1ea4f23e8a91a1337e9bb6a03e093d69fb16df686f2fe68a", "TTui2_test_protection_levels.py::test_verify_message_t2": "23efc0636e09bed8a73ac25ec6f760adbee906e777833c140d12355248959927", "TTui2_test_protection_levels.py::test_wipe_device": "40fdf3dafe49468bf8f948f36aa510927016f4803c1fb2d1d170fc40dff5c052", -"TTui2_test_sdcard.py::test_sd_format": "25fc8238413dac9aa7ff503d46de66351262400dfa9298b129e32a2e14f8df8c", +"TTui2_test_sdcard.py::test_sd_format": "53a16049a6ab7df7e5eb63f748e0e449f33b82013373ab8658238f9ac13dd3e3", "TTui2_test_sdcard.py::test_sd_no_format": "e48ac8dc3f81340d89746a9a6bc2b89f8ebce54568c4c1805e626178ff1c509c", "TTui2_test_sdcard.py::test_sd_protect_unlock": "3ccb135f2a39a727be85f45ce5472c3c7439792239f990264f78848e851cd56d", "TTui2_test_session.py::test_cannot_resume_ended_session": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",