1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-02 20:08:31 +00:00

refactor(core/rust/ui): layouts for BIP-39 backups

[no changelog]
This commit is contained in:
Martin Milata 2022-07-07 18:07:07 +02:00
parent 4ff2c99f0a
commit 4782afbae3
14 changed files with 525 additions and 132 deletions

View File

@ -24,6 +24,7 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_modify_output; MP_QSTR_confirm_modify_output;
MP_QSTR_confirm_output; MP_QSTR_confirm_output;
MP_QSTR_confirm_payment_request; MP_QSTR_confirm_payment_request;
MP_QSTR_confirm_reset_device;
MP_QSTR_confirm_text; MP_QSTR_confirm_text;
MP_QSTR_confirm_total; MP_QSTR_confirm_total;
MP_QSTR_show_error; MP_QSTR_show_error;
@ -35,6 +36,8 @@ static void _librust_qstrs(void) {
MP_QSTR_request_passphrase; MP_QSTR_request_passphrase;
MP_QSTR_request_bip39; MP_QSTR_request_bip39;
MP_QSTR_request_slip39; MP_QSTR_request_slip39;
MP_QSTR_select_word;
MP_QSTR_show_share_words;
MP_QSTR_attach_timer_fn; MP_QSTR_attach_timer_fn;
MP_QSTR_touch_event; MP_QSTR_touch_event;
@ -70,4 +73,6 @@ static void _librust_qstrs(void) {
MP_QSTR_total_amount; MP_QSTR_total_amount;
MP_QSTR_total_fee_new; MP_QSTR_total_fee_new;
MP_QSTR_user_fee_change; MP_QSTR_user_fee_change;
MP_QSTR_words;
MP_QSTR_pages;
} }

View File

@ -429,6 +429,31 @@ impl<T> Button<T> {
}), }),
) )
} }
pub fn select_word(
words: [T; 3],
) -> CancelInfoConfirm<
T,
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
>
where
T: AsRef<str>,
{
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<T, F0, F1> = ( type CancelConfirm<T, F0, F1> = (
@ -452,3 +477,7 @@ pub enum CancelInfoConfirmMsg {
Info, Info,
Confirmed, Confirmed,
} }
pub enum SelectWordMsg {
Selected(usize),
}

View File

@ -111,7 +111,7 @@ where
} }
pub struct CancelHold { pub struct CancelHold {
cancel: Button<&'static str>, cancel: Option<Button<&'static str>>,
hold: Button<&'static str>, hold: Button<&'static str>,
} }
@ -123,7 +123,14 @@ pub enum CancelHoldMsg {
impl CancelHold { impl CancelHold {
pub fn new() -> Self { pub fn new() -> Self {
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()), hold: Button::with_text("HOLD TO CONFIRM").styled(theme::button_confirm()),
} }
} }
@ -133,10 +140,14 @@ impl Component for CancelHold {
type Msg = CancelHoldMsg; type Msg = CancelHoldMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
if self.cancel.is_some() {
let grid = Grid::new(bounds, 1, 4).with_spacing(theme::BUTTON_SPACING); let grid = Grid::new(bounds, 1, 4).with_spacing(theme::BUTTON_SPACING);
self.cancel.place(grid.row_col(0, 0)); self.cancel.place(grid.row_col(0, 0));
self.hold self.hold
.place(grid.row_col(0, 1).union(grid.row_col(0, 3))); .place(grid.row_col(0, 1).union(grid.row_col(0, 3)));
} else {
self.hold.place(bounds);
};
bounds bounds
} }

View File

@ -10,7 +10,7 @@ mod swipe;
pub use button::{ pub use button::{
Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg, Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg,
CancelInfoConfirmMsg, CancelInfoConfirmMsg, SelectWordMsg,
}; };
pub use dialog::{Dialog, DialogLayout, DialogMsg, IconDialog}; pub use dialog::{Dialog, DialogLayout, DialogMsg, IconDialog};
pub use frame::Frame; pub use frame::Frame;

View File

@ -237,6 +237,14 @@ where
loader: Loader::new(), 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<T> Component for SwipeHoldPage<T> impl<T> Component for SwipeHoldPage<T>

View File

@ -1,4 +1,5 @@
use core::{convert::TryInto, ops::Deref}; use core::{cmp::Ordering, convert::TryInto, ops::Deref};
use cstr_core::cstr;
use crate::{ use crate::{
error::Error, error::Error,
@ -13,7 +14,6 @@ use crate::{
}, },
ui::{ ui::{
component::{ component::{
self,
base::ComponentExt, base::ComponentExt,
paginated::{PageMsg, Paginate}, paginated::{PageMsg, Paginate},
painter, painter,
@ -30,10 +30,10 @@ use crate::{
use super::{ use super::{
component::{ component::{
Bip39Input, Button, ButtonMsg, CancelConfirmMsg, CancelInfoConfirmMsg, Dialog, DialogMsg, Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, CancelInfoConfirmMsg,
Frame, HoldToConfirm, HoldToConfirmMsg, IconDialog, MnemonicInput, MnemonicKeyboard, Dialog, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg, IconDialog, MnemonicInput,
MnemonicKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg,
PinKeyboardMsg, Slip39Input, SwipeHoldPage, SwipePage, PinKeyboard, PinKeyboardMsg, SelectWordMsg, Slip39Input, SwipeHoldPage, SwipePage,
}, },
theme, theme,
}; };
@ -61,6 +61,16 @@ impl TryFrom<CancelInfoConfirmMsg> for Obj {
} }
} }
impl TryFrom<SelectWordMsg> for Obj {
type Error = Error;
fn try_from(value: SelectWordMsg) -> Result<Self, Self::Error> {
match value {
SelectWordMsg::Selected(i) => i.try_into(),
}
}
}
impl<T, U> ComponentMsgObj for Dialog<T, U> impl<T, U> ComponentMsgObj for Dialog<T, U>
where where
T: ComponentMsgObj, 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) } 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 { extern "C" fn new_show_qr(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; 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) } 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] #[no_mangle]
pub static mp_module_trezorui2: Module = obj_module! { pub static mp_module_trezorui2: Module = obj_module! {
Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(), 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.""" /// """Confirm byte sequence data."""
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), 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( /// def show_qr(
/// *, /// *,
/// title: str, /// title: str,
@ -784,6 +876,24 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// ) -> str: /// ) -> str:
/// """SLIP39 word input keyboard.""" /// """SLIP39 word input keyboard."""
Qstr::MP_QSTR_request_slip39 => obj_fn_kw!(0, new_request_slip39).as_obj(), 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)] #[cfg(test)]

View File

@ -84,6 +84,15 @@ def confirm_blob(
"""Confirm byte sequence data.""" """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 # rust/src/ui/model_tt/layout.rs
def show_qr( def show_qr(
*, *,
@ -243,3 +252,23 @@ def request_slip39(
prompt: str, prompt: str,
) -> str: ) -> str:
"""SLIP39 word input keyboard.""" """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."""

View File

@ -1,11 +1,13 @@
from typing import Sequence from typing import Sequence
from trezor import ui, utils, wire from trezor import ui, utils, wire
from trezor.crypto import random
from trezor.enums import ButtonRequestType 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 from trezor.ui.layouts.reset import ( # noqa: F401
confirm_word, select_word,
show_share_words, show_share_words,
show_warning_backup,
slip39_advanced_prompt_group_threshold, slip39_advanced_prompt_group_threshold,
slip39_advanced_prompt_number_of_groups, slip39_advanced_prompt_number_of_groups,
slip39_prompt_number_of_shares, slip39_prompt_number_of_shares,
@ -13,6 +15,11 @@ from trezor.ui.layouts.reset import ( # noqa: F401
slip39_show_checklist, slip39_show_checklist,
) )
if __debug__:
from apps import debug
NUM_OF_CHOICES = 3
async def show_internal_entropy(ctx: wire.GenericContext, entropy: bytes) -> None: async def show_internal_entropy(ctx: wire.GenericContext, entropy: bytes) -> None:
await confirm_blob( 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( async def _confirm_share_words(
ctx: wire.GenericContext, ctx: wire.GenericContext,
share_index: int | None, share_index: int | None,
@ -39,7 +78,7 @@ async def _confirm_share_words(
offset = 0 offset = 0
count = len(share_words) count = len(share_words)
for part in utils.chunks(share_words, third): 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 return False
offset += len(part) offset += len(part)
@ -93,20 +132,7 @@ async def _show_confirmation_failure(
async def show_backup_warning(ctx: wire.GenericContext, slip39: bool = False) -> None: async def show_backup_warning(ctx: wire.GenericContext, slip39: bool = False) -> None:
if slip39: await show_warning_backup(ctx, 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,
)
async def show_backup_success(ctx: wire.GenericContext) -> None: async def show_backup_success(ctx: wire.GenericContext) -> None:

View File

@ -553,7 +553,7 @@ async def should_show_more(
br_code: ButtonRequestType = ButtonRequestType.Other, br_code: ButtonRequestType = ButtonRequestType.Other,
icon: str = ui.ICON_DEFAULT, icon: str = ui.ICON_DEFAULT,
icon_color: int = ui.ORANGE_ICON, icon_color: int = ui.ORANGE_ICON,
confirm: ButtonContent = Confirm.DEFAULT_CONFIRM, confirm: str | bytes | None = None,
major_confirm: bool = False, major_confirm: bool = False,
) -> bool: ) -> bool:
"""Return True if the user wants to show more (they click a special button) """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. Raises ActionCancelled if the user cancels.
""" """
if confirm is None:
confirm = Confirm.DEFAULT_CONFIRM
page = Text( page = Text(
title, title,
header_icon=icon, header_icon=icon,

View File

@ -1,7 +1,6 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor import ui, utils, wire from trezor import ui, utils, wire
from trezor.crypto import random
from trezor.enums import BackupType, ButtonRequestType from trezor.enums import BackupType, ButtonRequestType
from ...components.common.confirm import is_confirmed, raise_if_cancelled 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.scroll import Paginated
from ...components.tt.text import Text from ...components.tt.text import Text
from ..common import interact from ..common import interact
from . import confirm_action
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence from typing import Sequence
@ -100,35 +100,18 @@ async def show_share_words(
) )
async def confirm_word( async def select_word(
ctx: wire.GenericContext, ctx: wire.GenericContext,
words: Sequence[str],
share_index: int | None, share_index: int | None,
share_words: Sequence[str], checked_index: int,
offset: int,
count: int, count: int,
group_index: int | None = None, group_index: int | None = None,
) -> bool: ) -> str:
# 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)
# let the user pick a word # 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) selected_word: str = await ctx.wait(select)
# confirm it is the correct one return selected_word
return selected_word == checked_word
def _split_share_into_pages( def _split_share_into_pages(
@ -361,3 +344,20 @@ async def slip39_advanced_prompt_group_threshold(
await info await info
return count 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,
)

View File

@ -12,15 +12,18 @@ if TYPE_CHECKING:
from typing import Any, Awaitable, Iterable, NoReturn, Sequence from typing import Any, Awaitable, Iterable, NoReturn, Sequence
from ..common import PropertyType, ExceptionType from ..common import PropertyType, ExceptionType
from ...components.tt.button import ButtonContent
class _RustLayout(ui.Layout): class _RustLayout(ui.Layout):
# pylint: disable=super-init-not-called # pylint: disable=super-init-not-called
def __init__(self, layout: Any): def __init__(self, layout: Any, is_backup=False):
self.layout = layout self.layout = layout
self.timer = loop.Timer() self.timer = loop.Timer()
self.layout.attach_timer_fn(self.set_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: def set_timer(self, token: int, deadline: int) -> None:
self.timer.schedule(deadline, token) self.timer.schedule(deadline, token)
@ -78,8 +81,27 @@ class _RustLayout(ui.Layout):
if msg is not None: if msg is not None:
raise ui.Result(msg) raise ui.Result(msg)
if self.is_backup:
self.notify_backup()
notify_layout_change(self) 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: else:
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
@ -186,17 +208,60 @@ async def confirm_action(
async def confirm_reset_device( async def confirm_reset_device(
ctx: wire.GenericContext, prompt: str, recovery: bool = False ctx: wire.GenericContext, prompt: str, recovery: bool = False
) -> None: ) -> None:
return await confirm_action( if recovery:
title = "RECOVERY MODE"
else:
title = "CREATE NEW WALLET"
result = await interact(
ctx, ctx,
"recover_device" if recovery else "setup_device", _RustLayout(
"not implemented", trezorui2.confirm_reset_device(
action="not implemented", title=title.upper(),
prompt=prompt.replace("\n", " "),
) )
),
"recover_device" if recovery else "setup_device",
ButtonRequestType.ProtectCall if recovery else ButtonRequestType.ResetDevice,
)
if result is not trezorui2.CONFIRMED:
raise wire.ActionCancelled
# TODO cleanup @ redesign # TODO cleanup @ redesign
async def confirm_backup(ctx: wire.GenericContext) -> bool: 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( async def confirm_path_warning(
@ -482,7 +547,7 @@ async def should_show_more(
br_code: ButtonRequestType = ButtonRequestType.Other, br_code: ButtonRequestType = ButtonRequestType.Other,
icon: str = ui.ICON_DEFAULT, icon: str = ui.ICON_DEFAULT,
icon_color: int = ui.ORANGE_ICON, icon_color: int = ui.ORANGE_ICON,
confirm: ButtonContent = None, confirm: str | bytes | None = None,
major_confirm: bool = False, major_confirm: bool = False,
) -> bool: ) -> bool:
raise NotImplementedError raise NotImplementedError

View File

@ -58,4 +58,4 @@ async def continue_recovery(
subtext: str | None, subtext: str | None,
info_func: Callable | None, info_func: Callable | None,
) -> bool: ) -> bool:
raise NotImplementedError return False

View File

@ -1,10 +1,35 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor import wire from trezor import wire
from trezor.enums import ButtonRequestType
import trezorui2
from ..common import interact
from . import _RustLayout
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.enums import BackupType 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( async def show_share_words(
@ -13,18 +38,75 @@ async def show_share_words(
share_index: int | None = None, share_index: int | None = None,
group_index: int | None = None, group_index: int | None = 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, ctx: wire.GenericContext,
words: Sequence[str],
share_index: int | None, share_index: int | None,
share_words: Sequence[str], checked_index: int,
offset: int,
count: int, count: int,
group_index: int | None = None, group_index: int | None = None,
) -> bool: ) -> str:
raise NotImplementedError 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( async def slip39_show_checklist(
@ -53,3 +135,28 @@ async def slip39_advanced_prompt_group_threshold(
ctx: wire.GenericContext, num_of_groups: int ctx: wire.GenericContext, num_of_groups: int
) -> int: ) -> int:
raise NotImplementedError 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

View File

@ -1724,7 +1724,7 @@
"TTui2_bitcoin-test_getaddress.py::test_ltc": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_bitcoin-test_getaddress.py::test_ltc": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TTui2_bitcoin-test_getaddress.py::test_multisig": "f3c9e4df672d2acb27fc61238df1e2828294a6f1602f52f07deb908cd1eb2cd8", "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[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_public_ckd": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TTui2_bitcoin-test_getaddress.py::test_tbtc": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_bitcoin-test_getaddress.py::test_tbtc": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TTui2_bitcoin-test_getaddress.py::test_tgrs": "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_importance_transfer": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TTui2_nem-test_signtx_others.py::test_nem_signtx_provision_namespace": "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_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": "2491e0b47ce4509b549cc402916f99832024737700b407ccb51e8f6cd5fa63bf",
"TTui2_nem-test_signtx_transfers.py::test_nem_signtx_known_mosaic_with_levy": "c135119f81a060eb697e05249bbfcb269e8f8994d5d72f6b54701c7f7aa071fa", "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": "06d2cde39452bacb20743fd760e48b497b2d143b1f6dfeb0c25a1d14a3e8a127", "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_simple": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TTui2_nem-test_signtx_transfers.py::test_nem_signtx_unknown_mosaic": "f32f3ae5971dc6d1407ec49a4225e3128c67cd1f6298ac588d9e2809a58496cb", "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", "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[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[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_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_dry_run": "6163bdd261809e749bcc2eed7882b994eae98b5db28a4a6c27ded2c0ecce4294",
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "af8dcf90f3fd9e92334af8cbcd966ad08091cc873f1942b07ace3c52b5518b2b", "TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "6163bdd261809e749bcc2eed7882b994eae98b5db28a4a6c27ded2c0ecce4294",
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "af8dcf90f3fd9e92334af8cbcd966ad08091cc873f1942b07ace3c52b5518b2b", "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_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_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_nopin_nopassphrase": "3919d9404e9f9a4880bd084edbfa02fbb04641008e04b83458633691e69bf239",
"TTui2_reset_recovery-test_recovery_bip39_t2.py::test_tt_pin_passphrase": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "TTui2_reset_recovery-test_recovery_bip39_t2.py::test_tt_pin_passphrase": "3919d9404e9f9a4880bd084edbfa02fbb04641008e04b83458633691e69bf239",
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_abort": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_abort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_extra_share_entered": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_group_threshold_reached": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_noabort": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_noabort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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": "f27a0e59dc9a6a4fe81d202d96996e1e21e674aeab019cebd0dc3d30fa886283", "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": "f27a0e59dc9a6a4fe81d202d96996e1e21e674aeab019cebd0dc3d30fa886283", "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": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "TTui2_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_abort": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "TTui2_reset_recovery-test_recovery_slip39_basic.py::test_abort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_ask_word_number": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "TTui2_reset_recovery-test_recovery_slip39_basic.py::test_ask_word_number": "e53306364b3a4cc2d23da5adeafa6f02fd946dcf042c6c77efd1ce221a319ea8",
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_noabort": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "TTui2_reset_recovery-test_recovery_slip39_basic.py::test_noabort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_recover_with_pin_passphrase": "19f6161fb065ed7bbe02591e8d5785bb94158a26dc2529f2afe49e321d52d671", "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": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "TTui2_reset_recovery-test_recovery_slip39_basic.py::test_same_share": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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]": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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]": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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]": "f2ca361222973de3d635839e3a860231077127c4f7d85069d5b64a6728a198da", "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": "f27a0e59dc9a6a4fe81d202d96996e1e21e674aeab019cebd0dc3d30fa886283", "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": "f27a0e59dc9a6a4fe81d202d96996e1e21e674aeab019cebd0dc3d30fa886283", "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]": "82266d0d8b0baaa8212546a0d538d2d54168c874a213a35fabdb4043662982de", "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": "82266d0d8b0baaa8212546a0d538d2d54168c874a213a35fabdb4043662982de", "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": "82266d0d8b0baaa8212546a0d538d2d54168c874a213a35fabdb4043662982de", "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]": "8eefc05a61808386dd4b6ce4f96e7e963d019893c1d9047d8a1b86d7cc2bc1a0", "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": "809bc3086a900cc5bc0ee3a9d7218dc9b6b1da52543009e444e0bc3b6bb1571b", "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": "809bc3086a900cc5bc0ee3a9d7218dc9b6b1da52543009e444e0bc3b6bb1571b", "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_already_initialized": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "049ce514a8f8570d1a7fa948930226a2df8b4f3ba6fa823dd54a387a3571437d", "TTui2_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "e34eb8420d9e74571c36e39352ba2308d90a021b2f5ef2e78afb167764ea931d",
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "23bf8446c0c9b9f948b9734b0cb4062d004e6b7b02b950e557ad34e47b4b3889",
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "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": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "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": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "79e75a5a75aaae9c05f598e5ef273734d8de3b9416bd747a9d58326d57090b1f",
"TTui2_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "TTui2_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "26df7da23999be182499ccafc8252513afdb0d0e288adbe37b393cf80a7e0662",
"TTui2_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "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": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "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": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "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": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "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": "6d9ea6360e4dd1cd5d6d52e046be4dd7650337b4abfb5e25be6fe36133d78a4b", "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": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TTui2_ripple-test_get_address.py::test_ripple_get_address_other": "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", "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_interrupt_backup_fails": "a8b5bc47867681b496da4b7473cde4fa43027c01fb071c2b0dcf97804809643f",
"TTui2_test_msg_backup_device.py::test_no_backup_fails": "ffc38ab2b61939fea6883a4805b2a4eb17a0be03afe0fed3b1cca492b50bb25c", "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_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_pin_to_wipe_code": "7f9c7e2550d4e515c7c9785a11dfcc2ce583925024ea2f52c859dc96de681afc",
"TTui2_test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "e56192b67b4fd4ee741ea190408b7af0fb95336312898db4cf54d73f752364b1", "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_mismatch": "8fd746c535ec5add348b76002a7936cc85c3206edbb59f225ad075912329452d",
"TTui2_test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "25eac0cb6ea45c0cb9cfcad3b4ac3ec33af9212a7b812370c8132ef9f14c7700", "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_failed": "e207e2c62f6930e9e112d7a1a31b9a66c14580df8aac82ea40e2f243d987e878",
"TTui2_test_msg_changepin_t2.py::test_change_invalid_current": "1aa0bcb26493a94c4b8c4b6aec4080a00acce8f7fc9e7e9058dc227ebd0d79d1", "TTui2_test_msg_changepin_t2.py::test_change_invalid_current": "9dce14051a6872469af20e9e8d03fe033908e32837df6a9dc31feb2db15bf398",
"TTui2_test_msg_changepin_t2.py::test_change_pin": "431ef817370e02d85a05d1a96b96f44d034c16303aa41b4ef66eeca664657ae2", "TTui2_test_msg_changepin_t2.py::test_change_pin": "bef4ccf69a801159487ac24ff1218953e5592a6da618d4f10bcece03e42283cc",
"TTui2_test_msg_changepin_t2.py::test_remove_pin": "9c80fc89ab9a3b9a5cdc9f5017caae777ce7727895a91169e2625d40f7dd88ef", "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_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_1": "eeb5afb34b4bbf42b8c635fdd34bae5c1e3693facb16e6d64e629746612a2c3f",
"TTui2_test_msg_loaddevice.py::test_load_device_2": "a95020926a62b4078cb0034f6e7a772e49fc42121c9197b534437e26c306a994", "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_advanced": "eeb5afb34b4bbf42b8c635fdd34bae5c1e3693facb16e6d64e629746612a2c3f",
"TTui2_test_msg_loaddevice.py::test_load_device_slip39_basic": "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_loaddevice.py::test_load_device_utf": "7eddfcc018eb3b5847e2617b1a9495632430ca5494f69063082a5063c5702dcf",
"TTui2_test_msg_ping.py::test_ping": "9b44725459426439bc27f2cf72ee926ab7146f3ee1236d197382524cdf9a89a1", "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_enable_disable": "1e64e5d08faac781f2bbba45b114a588c8a63d5a1865deaa0bc11c67ae891ea9",
"TTui2_test_msg_sd_protect.py::test_refresh": "cef6f6f310d38678b9a70e3d6003f815b5501bb4360de1f47052889c3e0a3c3f", "TTui2_test_msg_sd_protect.py::test_refresh": "d295a23bfc9b0fdef75d6732a5dd7fea7059a05fc566c599eddef75dfc05fcbb",
"TTui2_test_msg_sd_protect.py::test_wipe": "41fc0f48b51daedef1ca19181b336a2b35117727ce64393bd1c2ae2e6b772e55", "TTui2_test_msg_sd_protect.py::test_wipe": "d6e2c4224f6d6970c9339bb29c08f8e88308fcfd9ef6bac2cd6f56b7a520747a",
"TTui2_test_msg_wipedevice.py::test_autolock_not_retained": "54c7800f784832b08e5505712e4f9e7052f49870ea1c50a66ed6520c97be166d", "TTui2_test_msg_wipedevice.py::test_autolock_not_retained": "5200f217377bac4a795e1d5bb5963d4bb03a0ec6875a3272faca2572f8c7da2e",
"TTui2_test_msg_wipedevice.py::test_wipe_device": "36fd19373828ac579ae2e0eaf34c050ac9ea95596cfe38c447737acba86ec706", "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_128bit_passphrase": "68e7d02ee3038fa20f0ccc226abdc29c422aa0d3b0c54533869276cd08a7a5b8",
"TTui2_test_passphrase_slip39_advanced.py::test_256bit_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_incorrect_pin_t2": "cecd9cc23e1fab56f7df9c0a88b309f5fdd9f29ef97e0f5ba0b808cea2d11759",
"TTui2_test_pin.py::test_no_protection": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_test_pin.py::test_no_protection": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TTui2_test_protection_levels.py::test_apply_settings": "294a58f6e0222746f27bdf80014de14cf0b2d298bf806456ee94fd814e301cba", "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_address": "ef09d088bf4ca767162d5017748158bb8dda9849ccb0bf9ca5acf32b872e260c",
"TTui2_test_protection_levels.py::test_get_entropy": "7eadf62627e7a2c5a69b94c72eb4daca0153afb93ab8a12fd85d0d4ddc0a5a1d", "TTui2_test_protection_levels.py::test_get_entropy": "7eadf62627e7a2c5a69b94c72eb4daca0153afb93ab8a12fd85d0d4ddc0a5a1d",
"TTui2_test_protection_levels.py::test_get_public_key": "ef09d088bf4ca767162d5017748158bb8dda9849ccb0bf9ca5acf32b872e260c", "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_unlocked": "ed7d5e2c6bac6b7e1ea4f23e8a91a1337e9bb6a03e093d69fb16df686f2fe68a",
"TTui2_test_protection_levels.py::test_verify_message_t2": "23efc0636e09bed8a73ac25ec6f760adbee906e777833c140d12355248959927", "TTui2_test_protection_levels.py::test_verify_message_t2": "23efc0636e09bed8a73ac25ec6f760adbee906e777833c140d12355248959927",
"TTui2_test_protection_levels.py::test_wipe_device": "40fdf3dafe49468bf8f948f36aa510927016f4803c1fb2d1d170fc40dff5c052", "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_no_format": "e48ac8dc3f81340d89746a9a6bc2b89f8ebce54568c4c1805e626178ff1c509c",
"TTui2_test_sdcard.py::test_sd_protect_unlock": "3ccb135f2a39a727be85f45ce5472c3c7439792239f990264f78848e851cd56d", "TTui2_test_sdcard.py::test_sd_protect_unlock": "3ccb135f2a39a727be85f45ce5472c3c7439792239f990264f78848e851cd56d",
"TTui2_test_session.py::test_cannot_resume_ended_session": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TTui2_test_session.py::test_cannot_resume_ended_session": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",