1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-04 21:05:29 +00:00

feat(core): new design of recovery dialogs

[no changelog]
This commit is contained in:
grdddj 2023-06-29 17:26:51 +02:00 committed by Jiří Musil
parent 03f77c50e9
commit bc502287fc
22 changed files with 387 additions and 676 deletions

View File

@ -146,6 +146,7 @@ static void _librust_qstrs(void) {
MP_QSTR_value; MP_QSTR_value;
MP_QSTR_verb; MP_QSTR_verb;
MP_QSTR_verb_cancel; MP_QSTR_verb_cancel;
MP_QSTR_warning;
MP_QSTR_words; MP_QSTR_words;
MP_QSTR_wrong_pin; MP_QSTR_wrong_pin;
MP_QSTR_xpubs; MP_QSTR_xpubs;

View File

@ -24,8 +24,7 @@ use crate::{
}, },
ComponentExt, FormattedText, LineBreaking, Timeout, ComponentExt, FormattedText, LineBreaking, Timeout,
}, },
display, display, geometry,
geometry::{self, Alignment},
layout::{ layout::{
obj::{ComponentMsgObj, LayoutObj}, obj::{ComponentMsgObj, LayoutObj},
result::{CANCELLED, CONFIRMED, INFO}, result::{CANCELLED, CONFIRMED, INFO},
@ -805,33 +804,6 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
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_show_error(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let get_page = move |page_index| {
assert!(page_index == 0);
let btn_layout = ButtonLayout::none_armed_none(button.clone());
let btn_actions = ButtonActions::none_confirm_none();
let ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL)
.alignment(Alignment::Center)
.text_bold(title.clone())
.newline()
.text_normal(description.clone());
let formatted = FormattedText::new(ops).vertically_aligned(Alignment::Center);
Page::new(btn_layout, btn_actions, formatted)
};
let pages = FlowPages::new(get_page, 1);
let obj = LayoutObj::new(Flow::new(pages))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_confirm_modify_fee(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 sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?;
@ -914,7 +886,7 @@ extern "C" fn new_multiple_pages_texts(n_args: usize, args: *const Obj, kwargs:
}; };
let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text); let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text);
let formatted = FormattedText::new(ops).vertically_aligned(Alignment::Center); let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted) Page::new(btn_layout, btn_actions, formatted)
}; };
@ -990,6 +962,35 @@ extern "C" fn new_confirm_fido(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_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let warning: StrBuffer = kwargs.get(Qstr::MP_QSTR_warning)?.try_into()?;
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let get_page = move |page_index| {
assert!(page_index == 0);
let btn_layout = ButtonLayout::none_armed_none(button.clone());
let btn_actions = ButtonActions::none_confirm_none();
let mut ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL);
ops = ops.alignment(geometry::Alignment::Center);
if !warning.is_empty() {
ops = ops.text_bold(warning.clone()).newline().newline();
}
if !description.is_empty() {
ops = ops.text_normal(description.clone());
}
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted)
};
let pages = FlowPages::new(get_page, 1);
let obj = LayoutObj::new(Flow::new(pages))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_show_info(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()?;
@ -1267,16 +1268,32 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let dry_run: bool = kwargs.get(Qstr::MP_QSTR_dry_run)?.try_into()?; let dry_run: bool = kwargs.get(Qstr::MP_QSTR_dry_run)?.try_into()?;
let show_info: bool = kwargs.get(Qstr::MP_QSTR_show_info)?.try_into()?;
let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]); let mut paragraphs = ParagraphVecShort::new();
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, description));
if show_info {
let first = "You'll only have to select the first 2-3 letters of each word.";
let second =
"Position of the cursor will change between entries for enhanced security.";
paragraphs
.add(Paragraph::new(&theme::TEXT_NORMAL, first.into()))
.add(Paragraph::new(&theme::TEXT_NORMAL, second.into()));
}
let title = if dry_run { let title = if dry_run {
"SEED CHECK" "BACKUP CHECK"
} else { } else {
"WALLET RECOVERY" "RECOVER WALLET"
}; };
content_in_button_page(title.into(), paragraphs, button, Some("".into()), false) content_in_button_page(
title.into(),
paragraphs.into_paragraphs(),
button,
Some("".into()),
false,
)
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
@ -1561,15 +1578,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Show user how to interact with the device.""" /// """Show user how to interact with the device."""
Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(), Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(),
/// def show_error(
/// *,
/// title: str,
/// description: str,
/// button: str,
/// ) -> object:
/// """Show a popup with text centered both vertically and horizontally. With just a middle button."""
Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(),
/// def confirm_modify_fee( /// def confirm_modify_fee(
/// *, /// *,
/// title: str, # ignored /// title: str, # ignored
@ -1603,6 +1611,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Show multiple texts, each on its own page.""" /// """Show multiple texts, each on its own page."""
Qstr::MP_QSTR_multiple_pages_texts => obj_fn_kw!(0, new_multiple_pages_texts).as_obj(), Qstr::MP_QSTR_multiple_pages_texts => obj_fn_kw!(0, new_multiple_pages_texts).as_obj(),
/// def show_warning(
/// *,
/// button: str,
/// warning: str,
/// description: str,
/// ) -> object:
/// """Warning modal with middle button and centered text."""
Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(),
/// def show_info( /// def show_info(
/// *, /// *,
/// title: str, /// title: str,
@ -1717,6 +1734,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// button: str, /// button: str,
/// dry_run: bool, /// dry_run: bool,
/// info_button: bool, # unused on TR /// info_button: bool, # unused on TR
/// show_info: bool,
/// ) -> object: /// ) -> object:
/// """Device recovery homescreen.""" /// """Device recovery homescreen."""
Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(), Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(),

View File

@ -1399,7 +1399,7 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
.with_spacing(theme::RECOVERY_SPACING); .with_spacing(theme::RECOVERY_SPACING);
let notification = if dry_run { let notification = if dry_run {
"SEED CHECK" "BACKUP CHECK"
} else { } else {
"RECOVERY MODE" "RECOVERY MODE"
}; };
@ -1427,9 +1427,9 @@ extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mu
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let dry_run: bool = kwargs.get(Qstr::MP_QSTR_dry_run)?.try_into()?; let dry_run: bool = kwargs.get(Qstr::MP_QSTR_dry_run)?.try_into()?;
let title = if dry_run { let title = if dry_run {
"SEED CHECK" "BACKUP CHECK"
} else { } else {
"WALLET RECOVERY" "RECOVER WALLET"
}; };
let paragraphs = Paragraphs::new( let paragraphs = Paragraphs::new(

View File

@ -1,19 +1,17 @@
from typing import * from typing import *
CONFIRMED: object CONFIRMED: object
CANCELLED: object CANCELLED: object
INFO: object INFO: object
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def disable_animation(disable: bool) -> None: def disable_animation(disable: bool) -> None:
"""Disable animations, debug builds only.""" """Disable animations, debug builds only."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def toif_info(data: bytes) -> tuple[int, int, bool]: def toif_info(data: bytes) -> tuple[int, int, bool]:
"""Get TOIF image dimensions and format (width: int, height: int, is_grayscale: bool).""" """Get TOIF image dimensions and format (width: int, height: int, is_grayscale: bool)."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_action( def confirm_action(
*, *,
@ -28,7 +26,6 @@ def confirm_action(
) -> object: ) -> object:
"""Confirm action.""" """Confirm action."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_blob( def confirm_blob(
*, *,
@ -42,7 +39,6 @@ def confirm_blob(
) -> object: ) -> object:
"""Confirm byte sequence data.""" """Confirm byte sequence data."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_address( def confirm_address(
*, *,
@ -53,7 +49,6 @@ def confirm_address(
) -> object: ) -> object:
"""Confirm address.""" """Confirm address."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_properties( def confirm_properties(
*, *,
@ -65,7 +60,6 @@ def confirm_properties(
the value is to be rendered as binary with monospace font, False otherwise. the value is to be rendered as binary with monospace font, False otherwise.
This only concerns the text style, you need to decode the value to UTF-8 in python.""" This only concerns the text style, you need to decode the value to UTF-8 in python."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_reset_device( def confirm_reset_device(
*, *,
@ -74,12 +68,10 @@ def confirm_reset_device(
) -> object: ) -> object:
"""Confirm TOS before device setup.""" """Confirm TOS before device setup."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_backup() -> object: def confirm_backup() -> object:
"""Strongly recommend user to do backup.""" """Strongly recommend user to do backup."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_address_details( def show_address_details(
*, *,
@ -91,7 +83,6 @@ def show_address_details(
) -> object: ) -> object:
"""Show address details - QR code, account, path, cosigner xpubs.""" """Show address details - QR code, account, path, cosigner xpubs."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_value( def confirm_value(
*, *,
@ -103,7 +94,6 @@ def confirm_value(
) -> object: ) -> object:
"""Confirm value.""" """Confirm value."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_joint_total( def confirm_joint_total(
*, *,
@ -112,7 +102,6 @@ def confirm_joint_total(
) -> object: ) -> object:
"""Confirm total if there are external inputs.""" """Confirm total if there are external inputs."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_modify_output( def confirm_modify_output(
*, *,
@ -123,7 +112,6 @@ def confirm_modify_output(
) -> object: ) -> object:
"""Decrease or increase amount for given address.""" """Decrease or increase amount for given address."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_output( def confirm_output(
*, *,
@ -135,7 +123,6 @@ def confirm_output(
) -> object: ) -> object:
"""Confirm output.""" """Confirm output."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_total( def confirm_total(
*, *,
@ -148,12 +135,10 @@ def confirm_total(
) -> object: ) -> object:
"""Confirm summary of a transaction.""" """Confirm summary of a transaction."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def tutorial() -> object: def tutorial() -> object:
"""Show user how to interact with the device.""" """Show user how to interact with the device."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_error( def show_error(
*, *,
@ -163,7 +148,6 @@ def show_error(
) -> object: ) -> object:
"""Show a popup with text centered both vertically and horizontally. With just a middle button.""" """Show a popup with text centered both vertically and horizontally. With just a middle button."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_modify_fee( def confirm_modify_fee(
*, *,
@ -175,7 +159,6 @@ def confirm_modify_fee(
) -> object: ) -> object:
"""Decrease or increase transaction fee.""" """Decrease or increase transaction fee."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_fido( def confirm_fido(
*, *,
@ -188,7 +171,6 @@ def confirm_fido(
Returns page index in case of confirmation and CANCELLED otherwise. Returns page index in case of confirmation and CANCELLED otherwise.
""" """
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def multiple_pages_texts( def multiple_pages_texts(
*, *,
@ -198,6 +180,13 @@ def multiple_pages_texts(
) -> object: ) -> object:
"""Show multiple texts, each on its own page.""" """Show multiple texts, each on its own page."""
def show_warning(
*,
button: str,
warning: str,
description: str,
) -> object:
"""Warning modal with middle button and centered text."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_info( def show_info(
@ -208,17 +197,14 @@ def show_info(
) -> object: ) -> object:
"""Info modal.""" """Info modal."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_passphrase() -> object: def show_passphrase() -> object:
"""Show passphrase on host dialog.""" """Show passphrase on host dialog."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_mismatch() -> object: def show_mismatch() -> object:
"""Warning modal, receiving address mismatch.""" """Warning modal, receiving address mismatch."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_with_info( def confirm_with_info(
*, *,
@ -230,7 +216,6 @@ def confirm_with_info(
"""Confirm given items but with third button. Always single page """Confirm given items but with third button. Always single page
without scrolling.""" without scrolling."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_coinjoin( def confirm_coinjoin(
*, *,
@ -239,7 +224,6 @@ def confirm_coinjoin(
) -> object: ) -> object:
"""Confirm coinjoin authorization.""" """Confirm coinjoin authorization."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def request_pin( def request_pin(
*, *,
@ -250,7 +234,6 @@ def request_pin(
) -> str | object: ) -> str | object:
"""Request pin on device.""" """Request pin on device."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def request_passphrase( def request_passphrase(
*, *,
@ -259,7 +242,6 @@ def request_passphrase(
) -> str | object: ) -> str | object:
"""Get passphrase.""" """Get passphrase."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def request_bip39( def request_bip39(
*, *,
@ -267,14 +249,12 @@ def request_bip39(
) -> str: ) -> str:
"""Get recovery word for BIP39.""" """Get recovery word for BIP39."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def request_slip39( def request_slip39(
*, *,
prompt: str, prompt: str,
) -> str: ) -> str:
"""SLIP39 word input keyboard.""" """SLIP39 word input keyboard."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def select_word( def select_word(
@ -283,9 +263,8 @@ def select_word(
description: str, description: str,
words: Iterable[str], words: Iterable[str],
) -> int: ) -> int:
"""Select mnemonic word from three possibilities - seed check after backup. The """Select mnemonic word from three possibilities - seed check after backup. The
iterable must be of exact size. Returns index in range `0..3`.""" iterable must be of exact size. Returns index in range `0..3`."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_share_words( def show_share_words(
@ -294,7 +273,6 @@ def show_share_words(
) -> object: ) -> object:
"""Shows a backup seed.""" """Shows a backup seed."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def request_number( def request_number(
*, *,
@ -304,8 +282,7 @@ def request_number(
max_count: int, max_count: int,
description: Callable[[int], str] | None = None, # unused on TR description: Callable[[int], str] | None = None, # unused on TR
) -> object: ) -> object:
"""Number input with + and - buttons, description, and info button.""" """Number input with + and - buttons, description, and info button."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_checklist( def show_checklist(
@ -315,9 +292,8 @@ def show_checklist(
active: int, active: int,
button: str, button: str,
) -> object: ) -> object:
"""Checklist of backup steps. Active index is highlighted, previous items have check """Checklist of backup steps. Active index is highlighted, previous items have check
mark next to them.""" mark next to them."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_recovery( def confirm_recovery(
@ -327,25 +303,23 @@ def confirm_recovery(
button: str, button: str,
dry_run: bool, dry_run: bool,
info_button: bool, # unused on TR info_button: bool, # unused on TR
show_info: bool,
) -> object: ) -> object:
"""Device recovery homescreen.""" """Device recovery homescreen."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def select_word_count( def select_word_count(
*, *,
dry_run: bool, # unused on TR dry_run: bool, # unused on TR
) -> int | str: # TR returns str ) -> int | str: # TR returns str
"""Select mnemonic word count from (12, 18, 20, 24, 33).""" """Select mnemonic word count from (12, 18, 20, 24, 33)."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_group_share_success( def show_group_share_success(
*, *,
lines: Iterable[str], lines: Iterable[str],
) -> int: ) -> int:
"""Shown after successfully finishing a group.""" """Shown after successfully finishing a group."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_progress( def show_progress(
@ -354,10 +328,9 @@ def show_progress(
indeterminate: bool = False, indeterminate: bool = False,
description: str = "", description: str = "",
) -> object: ) -> object:
"""Show progress loader. Please note that the number of lines reserved on screen for """Show progress loader. Please note that the number of lines reserved on screen for
description is determined at construction time. If you want multiline descriptions description is determined at construction time. If you want multiline descriptions
make sure the initial description has at least that amount of lines.""" make sure the initial description has at least that amount of lines."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_progress_coinjoin( def show_progress_coinjoin(
@ -367,9 +340,8 @@ def show_progress_coinjoin(
time_ms: int = 0, time_ms: int = 0,
skip_first_paint: bool = False, skip_first_paint: bool = False,
) -> object: ) -> object:
"""Show progress loader for coinjoin. Returns CANCELLED after a specified time when """Show progress loader for coinjoin. Returns CANCELLED after a specified time when
time_ms timeout is passed.""" time_ms timeout is passed."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_homescreen( def show_homescreen(
@ -382,7 +354,6 @@ def show_homescreen(
) -> CANCELLED: ) -> CANCELLED:
"""Idle homescreen.""" """Idle homescreen."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def show_lockscreen( def show_lockscreen(
*, *,
@ -392,30 +363,26 @@ def show_lockscreen(
) -> CANCELLED: ) -> CANCELLED:
"""Homescreen for locked device.""" """Homescreen for locked device."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def draw_welcome_screen() -> None: def draw_welcome_screen() -> None:
"""Show logo icon with the model name at the bottom and return.""" """Show logo icon with the model name at the bottom and return."""
CONFIRMED: object CONFIRMED: object
CANCELLED: object CANCELLED: object
INFO: object INFO: object
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def disable_animation(disable: bool) -> None: def disable_animation(disable: bool) -> None:
"""Disable animations, debug builds only.""" """Disable animations, debug builds only."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def jpeg_info(data: bytes) -> tuple[int, int, int]: def jpeg_info(data: bytes) -> tuple[int, int, int]:
"""Get JPEG image dimensions (width: int, height: int, mcu_height: int).""" """Get JPEG image dimensions (width: int, height: int, mcu_height: int)."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def jpeg_test(data: bytes) -> bool: def jpeg_test(data: bytes) -> bool:
"""Test JPEG image.""" """Test JPEG image."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_action( def confirm_action(
*, *,
@ -430,7 +397,6 @@ def confirm_action(
) -> object: ) -> object:
"""Confirm action.""" """Confirm action."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_emphasized( def confirm_emphasized(
*, *,
@ -441,7 +407,6 @@ def confirm_emphasized(
"""Confirm formatted text that has been pre-split in python. For tuples """Confirm formatted text that has been pre-split in python. For tuples
the first component is a bool indicating whether this part is emphasized.""" the first component is a bool indicating whether this part is emphasized."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_homescreen( def confirm_homescreen(
*, *,
@ -450,7 +415,6 @@ def confirm_homescreen(
) -> object: ) -> object:
"""Confirm homescreen.""" """Confirm homescreen."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_blob( def confirm_blob(
*, *,
@ -464,7 +428,6 @@ def confirm_blob(
) -> object: ) -> object:
"""Confirm byte sequence data.""" """Confirm byte sequence data."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_address( def confirm_address(
*, *,
@ -476,7 +439,6 @@ def confirm_address(
"""Confirm address. Similar to `confirm_blob` but has corner info button """Confirm address. Similar to `confirm_blob` but has corner info button
and allows left swipe which does the same thing as the button.""" and allows left swipe which does the same thing as the button."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_properties( def confirm_properties(
*, *,
@ -487,7 +449,6 @@ def confirm_properties(
"""Confirm list of key-value pairs. The third component in the tuple should be True if """Confirm list of key-value pairs. The third component in the tuple should be True if
the value is to be rendered as binary with monospace font, False otherwise.""" the value is to be rendered as binary with monospace font, False otherwise."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_reset_device( def confirm_reset_device(
*, *,
@ -496,7 +457,6 @@ def confirm_reset_device(
) -> object: ) -> object:
"""Confirm TOS before device setup.""" """Confirm TOS before device setup."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_address_details( def show_address_details(
*, *,
@ -508,7 +468,6 @@ def show_address_details(
) -> object: ) -> object:
"""Show address details - QR code, account, path, cosigner xpubs.""" """Show address details - QR code, account, path, cosigner xpubs."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_spending_details( def show_spending_details(
*, *,
@ -519,7 +478,6 @@ def show_spending_details(
) -> object: ) -> object:
"""Show metadata when for outgoing transaction.""" """Show metadata when for outgoing transaction."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_value( def confirm_value(
*, *,
@ -534,7 +492,6 @@ def confirm_value(
) -> object: ) -> object:
"""Confirm value. Merge of confirm_total and confirm_output.""" """Confirm value. Merge of confirm_total and confirm_output."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_total( def confirm_total(
*, *,
@ -544,7 +501,6 @@ def confirm_total(
) -> object: ) -> object:
"""Transaction summary. Always hold to confirm.""" """Transaction summary. Always hold to confirm."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_modify_output( def confirm_modify_output(
*, *,
@ -555,7 +511,6 @@ def confirm_modify_output(
) -> object: ) -> object:
"""Decrease or increase amount for given address.""" """Decrease or increase amount for given address."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_modify_fee( def confirm_modify_fee(
*, *,
@ -567,7 +522,6 @@ def confirm_modify_fee(
) -> object: ) -> object:
"""Decrease or increase transaction fee.""" """Decrease or increase transaction fee."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_fido( def confirm_fido(
*, *,
@ -580,7 +534,6 @@ def confirm_fido(
Returns page index in case of confirmation and CANCELLED otherwise. Returns page index in case of confirmation and CANCELLED otherwise.
""" """
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_error( def show_error(
*, *,
@ -592,7 +545,6 @@ def show_error(
) -> object: ) -> object:
"""Error modal. No buttons shown when `button` is empty string.""" """Error modal. No buttons shown when `button` is empty string."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_warning( def show_warning(
*, *,
@ -604,7 +556,6 @@ def show_warning(
) -> object: ) -> object:
"""Warning modal. No buttons shown when `button` is empty string.""" """Warning modal. No buttons shown when `button` is empty string."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_success( def show_success(
*, *,
@ -616,7 +567,6 @@ def show_success(
) -> object: ) -> object:
"""Success modal. No buttons shown when `button` is empty string.""" """Success modal. No buttons shown when `button` is empty string."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_info( def show_info(
*, *,
@ -628,12 +578,10 @@ def show_info(
) -> object: ) -> object:
"""Info modal. No buttons shown when `button` is empty string.""" """Info modal. No buttons shown when `button` is empty string."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_mismatch() -> object: def show_mismatch() -> object:
"""Warning modal, receiving address mismatch.""" """Warning modal, receiving address mismatch."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_simple( def show_simple(
*, *,
@ -643,7 +591,6 @@ def show_simple(
) -> object: ) -> object:
"""Simple dialog with text and one button.""" """Simple dialog with text and one button."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_with_info( def confirm_with_info(
*, *,
@ -655,7 +602,6 @@ def confirm_with_info(
"""Confirm given items but with third button. Always single page """Confirm given items but with third button. Always single page
without scrolling.""" without scrolling."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_more( def confirm_more(
*, *,
@ -666,7 +612,6 @@ def confirm_more(
"""Confirm long content with the possibility to go back from any page. """Confirm long content with the possibility to go back from any page.
Meant to be used with confirm_with_info.""" Meant to be used with confirm_with_info."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_coinjoin( def confirm_coinjoin(
*, *,
@ -675,7 +620,6 @@ def confirm_coinjoin(
) -> object: ) -> object:
"""Confirm coinjoin authorization.""" """Confirm coinjoin authorization."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def request_pin( def request_pin(
*, *,
@ -686,7 +630,6 @@ def request_pin(
) -> str | object: ) -> str | object:
"""Request pin on device.""" """Request pin on device."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def request_passphrase( def request_passphrase(
*, *,
@ -695,7 +638,6 @@ def request_passphrase(
) -> str | object: ) -> str | object:
"""Passphrase input keyboard.""" """Passphrase input keyboard."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def request_bip39( def request_bip39(
*, *,
@ -703,7 +645,6 @@ def request_bip39(
) -> str: ) -> str:
"""BIP39 word input keyboard.""" """BIP39 word input keyboard."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def request_slip39( def request_slip39(
*, *,
@ -711,7 +652,6 @@ def request_slip39(
) -> str: ) -> str:
"""SLIP39 word input keyboard.""" """SLIP39 word input keyboard."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def select_word( def select_word(
*, *,
@ -720,8 +660,7 @@ def select_word(
words: Iterable[str], words: Iterable[str],
) -> int: ) -> int:
"""Select mnemonic word from three possibilities - seed check after backup. The """Select mnemonic word from three possibilities - seed check after backup. The
iterable must be of exact size. Returns index in range `0..3`.""" iterable must be of exact size. Returns index in range `0..3`."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_share_words( def show_share_words(
@ -731,7 +670,6 @@ def show_share_words(
) -> object: ) -> object:
"""Show mnemonic for backup. Expects the words pre-divided into individual pages.""" """Show mnemonic for backup. Expects the words pre-divided into individual pages."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def request_number( def request_number(
*, *,
@ -743,7 +681,6 @@ def request_number(
) -> object: ) -> object:
"""Number input with + and - buttons, description, and info button.""" """Number input with + and - buttons, description, and info button."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_checklist( def show_checklist(
*, *,
@ -753,8 +690,7 @@ def show_checklist(
button: str, button: str,
) -> object: ) -> object:
"""Checklist of backup steps. Active index is highlighted, previous items have check """Checklist of backup steps. Active index is highlighted, previous items have check
mark next to them.""" mark next to them."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_recovery( def confirm_recovery(
@ -767,7 +703,6 @@ def confirm_recovery(
) -> object: ) -> object:
"""Device recovery homescreen.""" """Device recovery homescreen."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def select_word_count( def select_word_count(
*, *,
@ -775,15 +710,10 @@ def select_word_count(
) -> int | str: # TT returns int ) -> int | str: # TT returns int
"""Select mnemonic word count from (12, 18, 20, 24, 33).""" """Select mnemonic word count from (12, 18, 20, 24, 33)."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_group_share_success( def show_group_share_success(*, lines: Iterable[str]) -> int:
*,
lines: Iterable[str]
) -> int:
"""Shown after successfully finishing a group.""" """Shown after successfully finishing a group."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_remaining_shares( def show_remaining_shares(
*, *,
@ -791,7 +721,6 @@ def show_remaining_shares(
) -> int: ) -> int:
"""Shows SLIP39 state after info button is pressed on `confirm_recovery`.""" """Shows SLIP39 state after info button is pressed on `confirm_recovery`."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_progress( def show_progress(
*, *,
@ -800,9 +729,8 @@ def show_progress(
description: str = "", description: str = "",
) -> object: ) -> object:
"""Show progress loader. Please note that the number of lines reserved on screen for """Show progress loader. Please note that the number of lines reserved on screen for
description is determined at construction time. If you want multiline descriptions description is determined at construction time. If you want multiline descriptions
make sure the initial description has at least that amount of lines.""" make sure the initial description has at least that amount of lines."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_progress_coinjoin( def show_progress_coinjoin(
@ -813,8 +741,7 @@ def show_progress_coinjoin(
skip_first_paint: bool = False, skip_first_paint: bool = False,
) -> object: ) -> object:
"""Show progress loader for coinjoin. Returns CANCELLED after a specified time when """Show progress loader for coinjoin. Returns CANCELLED after a specified time when
time_ms timeout is passed.""" time_ms timeout is passed."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_homescreen( def show_homescreen(
@ -827,7 +754,6 @@ def show_homescreen(
) -> CANCELLED: ) -> CANCELLED:
"""Idle homescreen.""" """Idle homescreen."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def show_lockscreen( def show_lockscreen(
*, *,
@ -837,7 +763,6 @@ def show_lockscreen(
) -> CANCELLED: ) -> CANCELLED:
"""Homescreen for locked device.""" """Homescreen for locked device."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def draw_welcome_screen() -> None: def draw_welcome_screen() -> None:
"""Show logo icon with the model name at the bottom and return.""" """Show logo icon with the model name at the bottom and return."""

View File

@ -56,11 +56,11 @@ async def recovery_device(msg: RecoveryDevice) -> Success:
# -------------------------------------------------------- # --------------------------------------------------------
# _continue_dialog # _continue_dialog
if not dry_run: if not dry_run:
await confirm_reset_device("Wallet recovery", recovery=True) await confirm_reset_device("Recover wallet", recovery=True)
else: else:
await confirm_action( await confirm_action(
"confirm_seedcheck", "confirm_seedcheck",
"Seed check", "Backup check",
description="Do you really want to check the recovery seed?", description="Do you really want to check the recovery seed?",
br_code=ButtonRequestType.ProtectCall, br_code=ButtonRequestType.ProtectCall,
) )

View File

@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
import storage.device as storage_device import storage.device as storage_device
import storage.recovery as storage_recovery import storage.recovery as storage_recovery
import storage.recovery_shares as storage_recovery_shares
from trezor import wire from trezor import wire
from trezor.messages import Success from trezor.messages import Success
@ -62,7 +63,9 @@ async def _continue_recovery_process() -> Success:
if is_first_step: if is_first_step:
# If we are starting recovery, ask for word count first... # If we are starting recovery, ask for word count first...
# _request_word_count # _request_word_count
await layout.homescreen_dialog("Select", "Select number of words") await layout.homescreen_dialog(
"Continue", "Select the number of words in your backup."
)
# ask for the number of words # ask for the number of words
word_count = await layout.request_word_count(dry_run) word_count = await layout.request_word_count(dry_run)
# ...and only then show the starting screen with word count. # ...and only then show the starting screen with word count.
@ -150,7 +153,7 @@ async def _finish_recovery(secret: bytes, backup_type: BackupType) -> Success:
storage_recovery.end_progress() storage_recovery.end_progress()
await show_success("success_recovery", "You have finished recovering your wallet.") await show_success("success_recovery", "Wallet recovered successfully.")
return Success(message="Device recovered") return Success(message="Device recovered")
@ -181,11 +184,17 @@ async def _request_share_first_screen(word_count: int) -> None:
await _request_share_next_screen() await _request_share_next_screen()
else: else:
await layout.homescreen_dialog( await layout.homescreen_dialog(
"Enter share", "Enter any share", f"({word_count} words)" "Enter share",
"Enter any share",
f"({word_count} words)",
show_info=True,
) )
else: # BIP-39 else: # BIP-39
await layout.homescreen_dialog( await layout.homescreen_dialog(
"Enter seed", "Enter recovery seed", f"({word_count} words)" "Continue",
"Enter your backup.",
f"({word_count} words)",
show_info=True,
) )
@ -205,8 +214,16 @@ async def _request_share_next_screen() -> None:
info_func=_show_remaining_groups_and_shares, info_func=_show_remaining_groups_and_shares,
) )
else: else:
text = strings.format_plural("{count} more {plural}", remaining[0], "share") still_needed_shares = remaining[0]
await layout.homescreen_dialog("Enter share", text, "needed to enter") already_entered_shares = len(storage_recovery_shares.fetch_group(0))
overall_needed = still_needed_shares + already_entered_shares
entered = (
f"{already_entered_shares} of {overall_needed} shares entered successfully."
)
needed = strings.format_plural(
"{count} more {plural} needed.", still_needed_shares, "share"
)
await layout.homescreen_dialog("Enter share", entered, needed)
async def _show_remaining_groups_and_shares() -> None: async def _show_remaining_groups_and_shares() -> None:
@ -214,7 +231,6 @@ async def _show_remaining_groups_and_shares() -> None:
Show info dialog for Slip39 Advanced - what shares are to be entered. Show info dialog for Slip39 Advanced - what shares are to be entered.
""" """
from trezor.crypto import slip39 from trezor.crypto import slip39
import storage.recovery_shares as storage_recovery_shares
shares_remaining = storage_recovery.fetch_slip39_remaining_shares() shares_remaining = storage_recovery.fetch_slip39_remaining_shares()
# should be stored at this point # should be stored at this point

View File

@ -20,8 +20,8 @@ async def _confirm_abort(dry_run: bool = False) -> None:
if dry_run: if dry_run:
await confirm_action( await confirm_action(
"abort_recovery", "abort_recovery",
"Abort seed check", "Abort backup check",
description="Do you really want to abort the seed check?", description="Do you really want to abort the backup check?",
br_code=ButtonRequestType.ProtectCall, br_code=ButtonRequestType.ProtectCall,
) )
else: else:
@ -41,9 +41,6 @@ async def request_mnemonic(
from . import word_validity from . import word_validity
from trezor.ui.layouts.common import button_request from trezor.ui.layouts.common import button_request
from trezor.ui.layouts.recovery import request_word from trezor.ui.layouts.recovery import request_word
from trezor.ui.layouts import mnemonic_word_entering
await mnemonic_word_entering()
await button_request("mnemonic", code=ButtonRequestType.MnemonicInput) await button_request("mnemonic", code=ButtonRequestType.MnemonicInput)
@ -60,7 +57,8 @@ async def request_mnemonic(
# show_share_already_added # show_share_already_added
await show_recovery_warning( await show_recovery_warning(
"warning_known_share", "warning_known_share",
"Share already entered, please enter a different share.", "Share already entered",
"Please enter a different share.",
) )
return None return None
except word_validity.IdentifierMismatch: except word_validity.IdentifierMismatch:
@ -74,7 +72,8 @@ async def request_mnemonic(
# show_group_threshold_reached # show_group_threshold_reached
await show_recovery_warning( await show_recovery_warning(
"warning_group_threshold", "warning_group_threshold",
"Threshold of this group has been reached. Input share from different group.", "Group threshold reached.",
"Enter share from a different group.",
) )
return None return None
@ -97,19 +96,21 @@ async def show_dry_run_result(result: bool, is_slip39: bool) -> None:
text = "The entered recovery shares are valid but do not match what is currently in the device." text = "The entered recovery shares are valid but do not match what is currently in the device."
else: else:
text = "The entered recovery seed is valid but does not match the one in the device." text = "The entered recovery seed is valid but does not match the one in the device."
await show_recovery_warning("warning_dry_recovery", text, button="Continue") await show_recovery_warning("warning_dry_recovery", "", text, button="Continue")
async def show_invalid_mnemonic(word_count: int) -> None: async def show_invalid_mnemonic(word_count: int) -> None:
if backup_types.is_slip39_word_count(word_count): if backup_types.is_slip39_word_count(word_count):
await show_recovery_warning( await show_recovery_warning(
"warning_invalid_share", "warning_invalid_share",
"You have entered an invalid recovery share.", "Invalid recovery share entered.",
"Please try again",
) )
else: else:
await show_recovery_warning( await show_recovery_warning(
"warning_invalid_seed", "warning_invalid_seed",
"You have entered an invalid recovery seed.", "Invalid recovery seed entered.",
"Please try again",
) )
@ -118,6 +119,7 @@ async def homescreen_dialog(
text: str, text: str,
subtext: str | None = None, subtext: str | None = None,
info_func: Callable | None = None, info_func: Callable | None = None,
show_info: bool = False,
) -> None: ) -> None:
from .recover import RecoveryAborted from .recover import RecoveryAborted
import storage.recovery as storage_recovery import storage.recovery as storage_recovery
@ -126,7 +128,9 @@ async def homescreen_dialog(
while True: while True:
dry_run = storage_recovery.is_dry_run() dry_run = storage_recovery.is_dry_run()
if await continue_recovery(button_label, text, subtext, info_func, dry_run): if await continue_recovery(
button_label, text, subtext, info_func, dry_run, show_info
):
# go forward in the recovery process # go forward in the recovery process
break break
# user has chosen to abort, confirm the choice # user has chosen to abort, confirm the choice

View File

@ -1,7 +1,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import storage.recovery as storage_recovery import storage.recovery as storage_recovery
import storage.recovery_shares import storage.recovery_shares as storage_recovery_shares
from trezor.crypto import slip39 from trezor.crypto import slip39
if TYPE_CHECKING: if TYPE_CHECKING:
@ -42,7 +42,7 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]:
storage_recovery.set_slip39_iteration_exponent(share.iteration_exponent) storage_recovery.set_slip39_iteration_exponent(share.iteration_exponent)
storage_recovery.set_slip39_identifier(share.identifier) storage_recovery.set_slip39_identifier(share.identifier)
storage_recovery.set_slip39_remaining_shares(share.threshold - 1, group_index) storage_recovery.set_slip39_remaining_shares(share.threshold - 1, group_index)
storage.recovery_shares.set(share.index, group_index, words) storage_recovery_shares.set(share.index, group_index, words)
# if share threshold and group threshold are 1 # if share threshold and group threshold are 1
# we can calculate the secret right away # we can calculate the secret right away
@ -58,7 +58,7 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]:
raise RuntimeError("Slip39: Share identifiers do not match") raise RuntimeError("Slip39: Share identifiers do not match")
if share.iteration_exponent != storage_recovery.get_slip39_iteration_exponent(): if share.iteration_exponent != storage_recovery.get_slip39_iteration_exponent():
raise RuntimeError("Slip39: Share exponents do not match") raise RuntimeError("Slip39: Share exponents do not match")
if storage.recovery_shares.get(share.index, group_index): if storage_recovery_shares.get(share.index, group_index):
raise RuntimeError("Slip39: This mnemonic was already entered") raise RuntimeError("Slip39: This mnemonic was already entered")
if share.group_count != storage_recovery.get_slip39_group_count(): if share.group_count != storage_recovery.get_slip39_group_count():
raise RuntimeError("Slip39: Group count does not match") raise RuntimeError("Slip39: Group count does not match")
@ -68,7 +68,7 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]:
) )
storage_recovery.set_slip39_remaining_shares(remaining_for_share - 1, group_index) storage_recovery.set_slip39_remaining_shares(remaining_for_share - 1, group_index)
remaining[group_index] = remaining_for_share - 1 remaining[group_index] = remaining_for_share - 1
storage.recovery_shares.set(share.index, group_index, words) storage_recovery_shares.set(share.index, group_index, words)
if remaining.count(0) < share.group_threshold: if remaining.count(0) < share.group_threshold:
# we need more shares # we need more shares
@ -79,11 +79,11 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]:
for i, r in enumerate(remaining): for i, r in enumerate(remaining):
# if we have multiple groups pass only the ones with threshold reached # if we have multiple groups pass only the ones with threshold reached
if r == 0: if r == 0:
group = storage.recovery_shares.fetch_group(i) group = storage_recovery_shares.fetch_group(i)
mnemonics.extend(group) mnemonics.extend(group)
else: else:
# in case of slip39 basic we only need the first and only group # in case of slip39 basic we only need the first and only group
mnemonics = storage.recovery_shares.fetch_group(0) mnemonics = storage_recovery_shares.fetch_group(0)
_, _, secret = slip39.recover_ems(mnemonics) _, _, secret = slip39.recover_ems(mnemonics)
return secret, share return secret, share
@ -111,7 +111,7 @@ def fetch_previous_mnemonics() -> list[list[str]] | None:
if not storage_recovery.get_slip39_group_count(): if not storage_recovery.get_slip39_group_count():
return None return None
for i in range(storage_recovery.get_slip39_group_count()): for i in range(storage_recovery.get_slip39_group_count()):
mnemonics.append(storage.recovery_shares.fetch_group(i)) mnemonics.append(storage_recovery_shares.fetch_group(i))
if not any(p for p in mnemonics): if not any(p for p in mnemonics):
return None return None
return mnemonics return mnemonics

View File

@ -10,13 +10,13 @@ _NAMESPACE = common.APP_RECOVERY
_IN_PROGRESS = const(0x00) # bool _IN_PROGRESS = const(0x00) # bool
_DRY_RUN = const(0x01) # bool _DRY_RUN = const(0x01) # bool
_SLIP39_IDENTIFIER = const(0x03) # bytes _SLIP39_IDENTIFIER = const(0x03) # bytes
_SLIP39_THRESHOLD = const(0x04) # int
_REMAINING = const(0x05) # int _REMAINING = const(0x05) # int
_SLIP39_ITERATION_EXPONENT = const(0x06) # int _SLIP39_ITERATION_EXPONENT = const(0x06) # int
_SLIP39_GROUP_COUNT = const(0x07) # int _SLIP39_GROUP_COUNT = const(0x07) # int
# Deprecated Keys: # Deprecated Keys:
# _WORD_COUNT = const(0x02) # int # _WORD_COUNT = const(0x02) # int
# _SLIP39_THRESHOLD = const(0x04) # int
# fmt: on # fmt: on
# Default values: # Default values:
@ -130,7 +130,6 @@ def end_progress() -> None:
_IN_PROGRESS, _IN_PROGRESS,
_DRY_RUN, _DRY_RUN,
_SLIP39_IDENTIFIER, _SLIP39_IDENTIFIER,
_SLIP39_THRESHOLD,
_REMAINING, _REMAINING,
_SLIP39_ITERATION_EXPONENT, _SLIP39_ITERATION_EXPONENT,
_SLIP39_GROUP_COUNT, _SLIP39_GROUP_COUNT,

View File

@ -399,6 +399,16 @@ async def confirm_reset_device(
) )
) )
if recovery:
await confirm_action(
ctx,
"recover_device",
title,
description="It is safe to eject your Trezor anytime and continue later.",
verb="CONTINUE",
br_code=ButtonRequestType.ProtectCall,
)
async def confirm_backup() -> bool: async def confirm_backup() -> bool:
br_type = "backup_device" br_type = "backup_device"
@ -597,21 +607,23 @@ async def show_error_and_raise(
raise exc raise exc
def show_warning( async def show_warning(
br_type: str, br_type: str,
content: str, content: str,
subheader: str | None = None, subheader: str | None = None,
button: str = "Try again", button: str = "CONTINUE",
br_code: ButtonRequestType = ButtonRequestType.Warning, br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> Awaitable[None]: ) -> None:
return _show_modal( await interact(
RustLayout(
trezorui2.show_warning( # type: ignore [Argument missing for parameter "title"]
button=button.upper(),
warning=content, # type: ignore [No parameter named "warning"]
description=subheader or "",
)
),
br_type, br_type,
"", br_code,
subheader or "WARNING",
content,
button_confirm=button,
button_cancel=None,
br_code=br_code,
) )
@ -1160,26 +1172,6 @@ async def confirm_reenter_pin(
) )
async def show_error(
br_type: str,
title: str,
description: str,
button: str,
br_code: ButtonRequestType = BR_TYPE_OTHER,
) -> None:
await interact(
RustLayout(
trezorui2.show_error(
title=title,
description=description,
button=button,
)
),
br_type,
br_code,
)
async def confirm_multiple_pages_texts( async def confirm_multiple_pages_texts(
br_type: str, br_type: str,
title: str, title: str,
@ -1207,7 +1199,7 @@ async def pin_mismatch_popup(
) -> None: ) -> None:
description = "wipe codes" if is_wipe_code else "PINs" description = "wipe codes" if is_wipe_code else "PINs"
br_code = "wipe_code_mismatch" if is_wipe_code else "pin_mismatch" br_code = "wipe_code_mismatch" if is_wipe_code else "pin_mismatch"
return await show_error( return await show_warning(
br_code, br_code,
f"Entered {description} do not match!", f"Entered {description} do not match!",
"Please check again.", "Please check again.",
@ -1259,14 +1251,3 @@ async def confirm_set_new_pin(
"CONTINUE", "CONTINUE",
br_code, br_code,
) )
async def mnemonic_word_entering() -> None:
await confirm_action(
"request_word",
"WORD ENTERING",
description="You'll only have to select the first 2-3 letters.",
verb="CONTINUE",
verb_cancel=None,
br_code=ButtonRequestType.MnemonicInput,
)

View File

@ -4,12 +4,11 @@ from trezor.enums import ButtonRequestType
import trezorui2 import trezorui2
from ..common import button_request, interact from ..common import interact
from . import RustLayout, raise_if_not_confirmed, show_warning from . import RustLayout, raise_if_not_confirmed, show_warning
async def request_word_count(dry_run: bool) -> int: async def request_word_count(dry_run: bool) -> int:
await button_request("word_count", code=ButtonRequestType.MnemonicWordCount)
count = await interact( count = await interact(
RustLayout(trezorui2.select_word_count(dry_run=dry_run)), RustLayout(trezorui2.select_word_count(dry_run=dry_run)),
"word_count", "word_count",
@ -66,6 +65,7 @@ async def continue_recovery(
subtext: str | None, subtext: str | None,
info_func: Callable | None, info_func: Callable | None,
dry_run: bool, dry_run: bool,
show_info: bool = False,
) -> bool: ) -> bool:
# TODO: implement info_func? # TODO: implement info_func?
# There is very limited space on the screen # There is very limited space on the screen
@ -82,6 +82,7 @@ async def continue_recovery(
button=button_label.upper(), button=button_label.upper(),
info_button=False, info_button=False,
dry_run=dry_run, dry_run=dry_run,
show_info=show_info, # type: ignore [No parameter named "show_info"]
) )
) )
result = await interact( result = await interact(

View File

@ -6,7 +6,7 @@ from trezor.wire import ActionCancelled
import trezorui2 import trezorui2
from ..common import interact from ..common import interact
from . import RustLayout, confirm_action, show_error from . import RustLayout, confirm_action, show_warning
CONFIRMED = trezorui2.CONFIRMED # global_import_cache CONFIRMED = trezorui2.CONFIRMED # global_import_cache
@ -247,7 +247,7 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
async def show_warning_backup(slip39: bool) -> None: async def show_warning_backup(slip39: bool) -> None:
await show_error( await show_warning(
"backup_warning", "backup_warning",
"REMEMBER", "REMEMBER",
"Never make a digital copy of your backup or upload it online!", "Never make a digital copy of your backup or upload it online!",
@ -275,7 +275,7 @@ async def show_reset_warning(
button: str = "TRY AGAIN", button: str = "TRY AGAIN",
br_code: ButtonRequestType = ButtonRequestType.Warning, br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> None: ) -> None:
await show_error( await show_warning(
ctx, ctx,
br_type, br_type,
button.upper(), button.upper(),

View File

@ -1238,17 +1238,3 @@ async def confirm_set_new_pin(
br_code, br_code,
) )
) )
# await confirm_action(
# ctx,
# br_type,
# title,
# description=description,
# verb="TURN ON",
# br_code=br_code,
# )
async def mnemonic_word_entering() -> None:
"""Not supported for TT."""
pass

View File

@ -102,6 +102,7 @@ async def continue_recovery(
subtext: str | None, subtext: str | None,
info_func: Callable | None, info_func: Callable | None,
dry_run: bool, dry_run: bool,
show_info: bool = False, # unused on TT
) -> bool: ) -> bool:
from ..common import button_request from ..common import button_request

View File

@ -42,12 +42,16 @@ def confirm_recovery(debug: "DebugLink") -> None:
if debug.model == "T": if debug.model == "T":
if not debug.legacy_ui and not debug.legacy_debug: if not debug.legacy_ui and not debug.legacy_debug:
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.title().startswith("WALLET RECOVERY") assert layout.title().startswith(
("WALLET RECOVERY", "RECOVER WALLET", "BACKUP CHECK")
)
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
elif debug.model == "R": elif debug.model == "R":
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.title() == "WALLET RECOVERY" assert layout.title() == "RECOVER WALLET"
debug.press_right(wait=True) debug.press_right(wait=True)
layout = debug.press_right(wait=True)
assert "safe to eject" in layout.text_content()
debug.press_right() debug.press_right()
@ -66,8 +70,11 @@ def select_number_of_words(
elif debug.legacy_debug: elif debug.legacy_debug:
assert "SelectWordCount" in layout.json_str assert "SelectWordCount" in layout.json_str
else: else:
# Two title options assert layout.title() in (
assert layout.title() in ("SEED CHECK", "WALLET RECOVERY") "WALLET RECOVERY",
"BACKUP CHECK",
"RECOVER WALLET",
)
# click the number # click the number
word_option_offset = 6 word_option_offset = 6
@ -96,10 +103,12 @@ def select_number_of_words(
if num_of_words in (20, 33): if num_of_words in (20, 33):
assert "Enter any share" in layout.text_content() assert "Enter any share" in layout.text_content()
else: else:
assert "Enter recovery seed" in layout.text_content() assert "Enter your backup" in layout.text_content()
def enter_share(debug: "DebugLink", share: str) -> "LayoutContent": def enter_share(
debug: "DebugLink", share: str, is_first: bool = True
) -> "LayoutContent":
if debug.model == "T": if debug.model == "T":
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
@ -115,10 +124,12 @@ def enter_share(debug: "DebugLink", share: str) -> "LayoutContent":
return layout return layout
elif debug.model == "R": elif debug.model == "R":
assert "RECOVER WALLET" in debug.wait_layout().title()
layout = debug.press_right(wait=True) layout = debug.press_right(wait=True)
assert layout.title() == "WORD ENTERING" if is_first:
# Word entering info
layout = debug.press_right(wait=True) debug.press_right()
layout = debug.press_right(wait=True)
assert "Slip39Entry" in layout.all_components() assert "Slip39Entry" in layout.all_components()
for word in share.split(" "): for word in share.split(" "):
@ -133,39 +144,32 @@ def enter_shares(debug: "DebugLink", shares: list[str]) -> None:
layout = debug.read_layout() layout = debug.read_layout()
expected_text = "Enter any share" expected_text = "Enter any share"
remaining = len(shares) remaining = len(shares)
for share in shares: for index, share in enumerate(shares):
assert expected_text in layout.text_content() assert expected_text in layout.text_content()
layout = enter_share(debug, share) layout = enter_share(debug, share, is_first=index == 0)
remaining -= 1 remaining -= 1
expected_text = f"{remaining} more share" expected_text = f"{remaining} more share"
assert "You have finished recovering your wallet" in layout.text_content() assert "Wallet recovered successfully" in layout.text_content()
def enter_seed(debug: "DebugLink", seed_words: list[str]) -> None: def enter_seed(debug: "DebugLink", seed_words: list[str]) -> None:
assert "Enter" in debug.read_layout().text_content()
if debug.model == "T": if debug.model == "T":
assert "Enter" in debug.read_layout().text_content()
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.main_component() == "MnemonicKeyboard" assert layout.main_component() == "MnemonicKeyboard"
for word in seed_words:
layout = enter_word(debug, word, is_slip39=False)
assert "You have finished recovering your wallet" in layout.text_content()
elif debug.model == "R": elif debug.model == "R":
assert "Enter" in debug.read_layout().text_content()
layout = debug.press_right(wait=True) layout = debug.press_right(wait=True)
assert layout.title() == "WORD ENTERING" assert "RECOVER WALLET" in layout.title()
debug.press_right()
layout = debug.press_right(wait=True) layout = debug.press_right(wait=True)
assert "Bip39Entry" in layout.all_components() assert "Bip39Entry" in layout.all_components()
for word in seed_words: for word in seed_words:
layout = enter_word(debug, word, is_slip39=False) layout = enter_word(debug, word, is_slip39=False)
assert "You have finished recovering your wallet" in layout.text_content() assert "Wallet recovered successfully" in layout.text_content() # type: ignore
def finalize(debug: "DebugLink") -> None: def finalize(debug: "DebugLink") -> None:

View File

@ -301,7 +301,8 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"):
assert layout.main_component() == "MnemonicKeyboard" assert layout.main_component() == "MnemonicKeyboard"
elif debug.model == "R": elif debug.model == "R":
layout = debug.press_right(wait=True) layout = debug.press_right(wait=True)
assert "WORD ENTERING" in layout.title() assert "RECOVER WALLET" in layout.title()
debug.press_right()
layout = debug.press_right(wait=True) layout = debug.press_right(wait=True)
assert "Slip39Entry" in layout.all_components() assert "Slip39Entry" in layout.all_components()
@ -337,7 +338,8 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"):
assert layout.main_component() == "MnemonicKeyboard" assert layout.main_component() == "MnemonicKeyboard"
elif debug.model == "R": elif debug.model == "R":
layout = debug.press_right(wait=True) layout = debug.press_right(wait=True)
assert "WORD ENTERING" in layout.title() assert "RECOVER WALLET" in layout.title()
debug.press_right()
layout = debug.press_right(wait=True) layout = debug.press_right(wait=True)
assert "Slip39Entry" in layout.all_components() assert "Slip39Entry" in layout.all_components()

View File

@ -227,16 +227,15 @@ def recovery_enter_shares_tr(
debug.input(str(word_count)) debug.input(str(word_count))
# Homescreen - proceed to share entry # Homescreen - proceed to share entry
yield yield
assert "Enter any share" in debug.wait_layout().text_content()
debug.press_right()
debug.press_right()
debug.press_yes() debug.press_yes()
# Enter shares # Enter shares
for share in shares: for share in shares:
br = yield br = yield
assert br.code == ButtonRequestType.RecoveryHomepage assert br.code == ButtonRequestType.MnemonicInput
# Word entering
yield
debug.press_yes()
# Enter mnemonic words # Enter mnemonic words
for word in share.split(" "): for word in share.split(" "):
@ -368,7 +367,7 @@ def read_and_confirm_mnemonic_tr(
return " ".join(mnemonic) return " ".join(mnemonic)
def click_info_button(debug: "DebugLink"): def click_info_button_tt(debug: "DebugLink"):
"""Click Shamir backup info button and return back.""" """Click Shamir backup info button and return back."""
debug.press_info() debug.press_info()
yield # Info screen with text yield # Info screen with text

View File

@ -30,7 +30,6 @@ def test_tt_pin_passphrase(client: Client):
with client: with client:
IF = InputFlowBip39RecoveryPIN(client, MNEMONIC12.split(" ")) IF = InputFlowBip39RecoveryPIN(client, MNEMONIC12.split(" "))
client.set_input_flow(IF.get()) client.set_input_flow(IF.get())
client.watch_layout()
device.recover( device.recover(
client, client,
pin_protection=True, pin_protection=True,
@ -51,7 +50,6 @@ def test_tt_nopin_nopassphrase(client: Client):
with client: with client:
IF = InputFlowBip39RecoveryNoPIN(client, MNEMONIC12.split(" ")) IF = InputFlowBip39RecoveryNoPIN(client, MNEMONIC12.split(" "))
client.set_input_flow(IF.get()) client.set_input_flow(IF.get())
client.watch_layout()
device.recover( device.recover(
client, client,
pin_protection=False, pin_protection=False,

View File

@ -117,8 +117,6 @@ def test_noabort(client: Client):
@pytest.mark.setup_client(uninitialized=True) @pytest.mark.setup_client(uninitialized=True)
def test_ask_word_number(client: Client): def test_ask_word_number(client: Client):
if client.features.model == "R":
pytest.skip("Flow is not working correctly for TR")
with client: with client:
IF = InputFlowSlip39BasicRecoveryRetryFirst(client) IF = InputFlowSlip39BasicRecoveryRetryFirst(client)
client.set_input_flow(IF.get()) client.set_input_flow(IF.get())

View File

@ -23,7 +23,7 @@ from trezorlib.debuglink import (
from . import buttons from . import buttons
from .common import ( from .common import (
check_pin_backoff_time, check_pin_backoff_time,
click_info_button, click_info_button_tt,
click_through, click_through,
read_and_confirm_mnemonic, read_and_confirm_mnemonic,
recovery_enter_shares, recovery_enter_shares,
@ -396,14 +396,14 @@ class InputFlowPaymentRequestDetails(InputFlowBase):
self.debug.press_info() self.debug.press_info()
yield # confirm first output yield # confirm first output
assert self.outputs[0].address[:16] in self.layout().text_content() assert self.outputs[0].address[:16] in self.layout().text_content() # type: ignore
self.debug.press_yes() self.debug.press_yes()
yield # confirm first output yield # confirm first output
self.debug.wait_layout() self.debug.wait_layout()
self.debug.press_yes() self.debug.press_yes()
yield # confirm second output yield # confirm second output
assert self.outputs[1].address[:16] in self.layout().text_content() assert self.outputs[1].address[:16] in self.layout().text_content() # type: ignore
self.debug.press_yes() self.debug.press_yes()
yield # confirm second output yield # confirm second output
self.debug.wait_layout() self.debug.wait_layout()
@ -503,12 +503,12 @@ class InputFlowSignTxInformation(InputFlowBase):
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
content = yield from sign_tx_go_to_info(self.client) content = yield from sign_tx_go_to_info(self.client)
self.assert_content(content) self.assert_content(content)
self.client.debug.press_yes() self.debug.press_yes()
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
content = yield from sign_tx_go_to_info_tr(self.client) content = yield from sign_tx_go_to_info_tr(self.client)
self.assert_content(content.lower()) self.assert_content(content.lower())
self.client.debug.press_yes() self.debug.press_yes()
class InputFlowSignTxInformationMixed(InputFlowBase): class InputFlowSignTxInformationMixed(InputFlowBase):
@ -524,12 +524,12 @@ class InputFlowSignTxInformationMixed(InputFlowBase):
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
content = yield from sign_tx_go_to_info(self.client) content = yield from sign_tx_go_to_info(self.client)
self.assert_content(content) self.assert_content(content)
self.client.debug.press_yes() self.debug.press_yes()
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
content = yield from sign_tx_go_to_info_tr(self.client) content = yield from sign_tx_go_to_info_tr(self.client)
self.assert_content(content.lower()) self.assert_content(content.lower())
self.client.debug.press_yes() self.debug.press_yes()
class InputFlowSignTxInformationCancel(InputFlowBase): class InputFlowSignTxInformationCancel(InputFlowBase):
@ -538,11 +538,11 @@ class InputFlowSignTxInformationCancel(InputFlowBase):
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
yield from sign_tx_go_to_info(self.client) yield from sign_tx_go_to_info(self.client)
self.client.debug.press_no() self.debug.press_no()
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
yield from sign_tx_go_to_info_tr(self.client) yield from sign_tx_go_to_info_tr(self.client)
self.client.debug.press_left() self.debug.press_left()
class InputFlowSignTxInformationReplacement(InputFlowBase): class InputFlowSignTxInformationReplacement(InputFlowBase):
@ -551,15 +551,15 @@ class InputFlowSignTxInformationReplacement(InputFlowBase):
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
yield # confirm txid yield # confirm txid
self.client.debug.press_yes() self.debug.press_yes()
yield # confirm address yield # confirm address
self.client.debug.press_yes() self.debug.press_yes()
# go back to address # go back to address
self.client.debug.press_no() self.debug.press_no()
# confirm address # confirm address
self.client.debug.press_yes() self.debug.press_yes()
yield # confirm amount yield # confirm amount
self.client.debug.press_yes() self.debug.press_yes()
yield # transaction summary, press info yield # transaction summary, press info
self.client.debug.click(buttons.CORNER_BUTTON, wait=True) self.client.debug.click(buttons.CORNER_BUTTON, wait=True)
@ -568,16 +568,16 @@ class InputFlowSignTxInformationReplacement(InputFlowBase):
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
yield # confirm txid yield # confirm txid
self.client.debug.press_right() self.debug.press_right()
self.client.debug.press_right() self.debug.press_right()
yield # confirm address yield # confirm address
self.client.debug.press_right() self.debug.press_right()
self.client.debug.press_right() self.debug.press_right()
self.client.debug.press_right() self.debug.press_right()
yield # confirm amount yield # confirm amount
self.client.debug.press_right() self.debug.press_right()
self.client.debug.press_right() self.debug.press_right()
self.client.debug.press_right() self.debug.press_right()
def lock_time_input_flow_tt( def lock_time_input_flow_tt(
@ -1015,13 +1015,13 @@ class InputFlowSlip39BasicBackup(InputFlowBase):
yield # 1. Checklist yield # 1. Checklist
self.debug.press_yes() self.debug.press_yes()
if self.click_info: if self.click_info:
yield from click_info_button(self.debug) yield from click_info_button_tt(self.debug)
yield # 2. Number of shares (5) yield # 2. Number of shares (5)
self.debug.press_yes() self.debug.press_yes()
yield # 3. Checklist yield # 3. Checklist
self.debug.press_yes() self.debug.press_yes()
if self.click_info: if self.click_info:
yield from click_info_button(self.debug) yield from click_info_button_tt(self.debug)
yield # 4. Threshold (3) yield # 4. Threshold (3)
self.debug.press_yes() self.debug.press_yes()
yield # 5. Checklist yield # 5. Checklist
@ -1037,21 +1037,21 @@ class InputFlowSlip39BasicBackup(InputFlowBase):
self.debug.press_yes() self.debug.press_yes()
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
yield # Checklist yield # 1. Checklist
self.debug.press_yes() self.debug.press_yes()
yield # Number of shares info yield # 1.5 Number of shares info
self.debug.press_yes() self.debug.press_yes()
yield # Number of shares (5) yield # 2. Number of shares (5)
self.debug.input("5") self.debug.input("5")
yield # Checklist yield # 3. Checklist
self.debug.press_yes() self.debug.press_yes()
yield # Threshold info yield # 3.5 Threshold info
self.debug.press_yes() self.debug.press_yes()
yield # Threshold (3) yield # 4. Threshold (3)
self.debug.input("3") self.debug.input("3")
yield # Checklist yield # 5. Checklist
self.debug.press_yes() self.debug.press_yes()
yield # Confirm show seeds yield # 6. Confirm show seeds
self.debug.press_yes() self.debug.press_yes()
# Mnemonic phrases # Mnemonic phrases
@ -1143,24 +1143,24 @@ class InputFlowSlip39AdvancedBackup(InputFlowBase):
yield # 1. Checklist yield # 1. Checklist
self.debug.press_yes() self.debug.press_yes()
if self.click_info: if self.click_info:
yield from click_info_button(self.debug) yield from click_info_button_tt(self.debug)
yield # 2. Set and confirm group count yield # 2. Set and confirm group count
self.debug.press_yes() self.debug.press_yes()
yield # 3. Checklist yield # 3. Checklist
self.debug.press_yes() self.debug.press_yes()
if self.click_info: if self.click_info:
yield from click_info_button(self.debug) yield from click_info_button_tt(self.debug)
yield # 4. Set and confirm group threshold yield # 4. Set and confirm group threshold
self.debug.press_yes() self.debug.press_yes()
yield # 5. Checklist yield # 5. Checklist
self.debug.press_yes() self.debug.press_yes()
for _ in range(5): # for each of 5 groups for _ in range(5): # for each of 5 groups
if self.click_info: if self.click_info:
yield from click_info_button(self.debug) yield from click_info_button_tt(self.debug)
yield # Set & Confirm number of shares yield # Set & Confirm number of shares
self.debug.press_yes() self.debug.press_yes()
if self.click_info: if self.click_info:
yield from click_info_button(self.debug) yield from click_info_button_tt(self.debug)
yield # Set & confirm share threshold value yield # Set & confirm share threshold value
self.debug.press_yes() self.debug.press_yes()
yield # Confirm show seeds yield # Confirm show seeds
@ -1266,7 +1266,9 @@ class InputFlowSlip39AdvancedResetRecovery(InputFlowBase):
self.debug.press_yes() self.debug.press_yes()
def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> GeneratorType: def enter_recovery_seed_dry_run_tt(
debug: DebugLink, mnemonic: list[str]
) -> GeneratorType:
yield yield
assert "check the recovery seed" in debug.wait_layout().text_content() assert "check the recovery seed" in debug.wait_layout().text_content()
debug.click(buttons.OK) debug.click(buttons.OK)
@ -1284,7 +1286,7 @@ def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> Genera
debug.click(buttons.grid34(index % 3, index // 3)) debug.click(buttons.grid34(index % 3, index // 3))
yield yield
assert "Enter recovery seed" in debug.wait_layout().text_content() assert "Enter your backup" in debug.wait_layout().text_content()
debug.click(buttons.OK) debug.click(buttons.OK)
yield yield
@ -1299,7 +1301,7 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase):
self.mnemonic = mnemonic self.mnemonic = mnemonic
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
yield from enter_recovery_seed_dry_run(self.debug, self.mnemonic) yield from enter_recovery_seed_dry_run_tt(self.debug, self.mnemonic)
yield yield
self.debug.wait_layout() self.debug.wait_layout()
@ -1310,33 +1312,9 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase):
assert "check the recovery seed" in self.layout().text_content() assert "check the recovery seed" in self.layout().text_content()
self.debug.press_yes() self.debug.press_yes()
yield yield from enter_recovery_seed_tr(self.debug, self.mnemonic)
assert "number of words" in self.layout().text_content()
self.debug.press_yes()
yield yield
yield
assert "NUMBER OF WORDS" in self.layout().title()
word_options = (12, 18, 20, 24, 33)
index = word_options.index(len(self.mnemonic))
for _ in range(index):
self.debug.press_right()
self.debug.input(str(len(self.mnemonic)))
yield
assert "Enter recovery seed" in self.layout().text_content()
self.debug.press_yes()
yield
self.debug.press_yes()
yield
for index, word in enumerate(self.mnemonic):
assert "WORD" in self.layout().title()
assert str(index + 1) in self.layout().title()
self.debug.input(word)
yield
self.debug.press_right()
self.debug.press_yes() self.debug.press_yes()
@ -1346,11 +1324,11 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase):
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
mnemonic = ["stick"] * 12 mnemonic = ["stick"] * 12
yield from enter_recovery_seed_dry_run(self.debug, mnemonic) yield from enter_recovery_seed_dry_run_tt(self.debug, mnemonic)
br = yield br = yield
assert br.code == messages.ButtonRequestType.Warning assert br.code == messages.ButtonRequestType.Warning
assert "invalid recovery seed" in self.layout().text_content() assert "Invalid recovery seed" in self.layout().text_content()
self.debug.click(buttons.OK) self.debug.click(buttons.OK)
yield # retry screen yield # retry screen
@ -1358,7 +1336,7 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase):
self.debug.click(buttons.CANCEL) self.debug.click(buttons.CANCEL)
yield yield
assert "ABORT SEED CHECK" == self.layout().title() assert "ABORT BACKUP CHECK" == self.layout().title()
self.debug.click(buttons.OK) self.debug.click(buttons.OK)
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
@ -1366,33 +1344,13 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase):
assert "check the recovery seed" in self.layout().text_content() assert "check the recovery seed" in self.layout().text_content()
self.debug.press_right() self.debug.press_right()
yield mnemonic = ["stick"] * 12
assert "number of words" in self.layout().text_content() yield from enter_recovery_seed_tr(self.debug, mnemonic)
self.debug.press_yes()
yield
yield
assert "NUMBER OF WORDS" in self.layout().title()
# select 12 words
self.debug.press_middle()
yield
assert "Enter recovery seed" in self.layout().text_content()
self.debug.press_yes()
yield
assert "WORD ENTERING" in self.layout().title()
self.debug.press_yes()
yield
for _ in range(12):
assert "WORD" in self.layout().title()
self.debug.input("stick")
br = yield br = yield
assert br.code == messages.ButtonRequestType.Warning assert br.code == messages.ButtonRequestType.Warning
assert "invalid recovery seed" in self.layout().text_content() assert "Invalid recovery seed" in self.layout().text_content()
self.debug.press_right() self.debug.press_middle()
yield # retry screen yield # retry screen
assert "number of words" in self.layout().text_content() assert "number of words" in self.layout().text_content()
@ -1403,7 +1361,7 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase):
self.debug.press_right() self.debug.press_right()
def bip39_recovery_possible_pin( def bip39_recovery_possible_pin_tt(
debug: DebugLink, mnemonic: list[str], pin: Optional[str] debug: DebugLink, mnemonic: list[str], pin: Optional[str]
) -> GeneratorType: ) -> GeneratorType:
yield yield
@ -1429,7 +1387,7 @@ def bip39_recovery_possible_pin(
debug.input(str(len(mnemonic))) debug.input(str(len(mnemonic)))
yield yield
assert "Enter recovery seed" in debug.wait_layout().text_content() assert "Enter your backup" in debug.wait_layout().text_content()
debug.press_yes() debug.press_yes()
yield yield
@ -1438,65 +1396,76 @@ def bip39_recovery_possible_pin(
debug.input(word) debug.input(word)
yield yield
assert ( assert "Wallet recovered successfully" in debug.wait_layout().text_content()
"You have finished recovering your wallet."
in debug.wait_layout().text_content()
)
debug.press_yes() debug.press_yes()
def bip39_recovery_possible_pin_tr(
debug: DebugLink, mnemonic: list[str], pin: Optional[str]
) -> GeneratorType:
yield
assert "By continuing you agree" in debug.wait_layout().text_content()
debug.press_right()
assert "trezor.io/tos" in debug.wait_layout().text_content()
debug.press_yes()
yield
assert "safe to eject" in debug.wait_layout().text_content()
debug.press_yes()
# PIN when requested
if pin is not None:
yield
debug.input("654")
yield
assert "re-enter to confirm" in debug.wait_layout().text_content()
debug.press_right()
yield
debug.input("654")
yield from enter_recovery_seed_tr(debug, mnemonic)
yield
assert "Wallet recovered successfully" in debug.wait_layout().text_content()
debug.press_yes()
def enter_recovery_seed_tr(debug: DebugLink, mnemonic: list[str]) -> GeneratorType:
yield
assert "number of words" in debug.wait_layout().text_content()
debug.press_yes()
yield
assert "NUMBER OF WORDS" in debug.wait_layout().title()
debug.input(str(len(mnemonic)))
yield
assert "Enter your backup" in debug.wait_layout().text_content()
# Paginate to see info
debug.press_right()
debug.press_right()
debug.press_yes()
yield
for index, word in enumerate(mnemonic):
title = debug.wait_layout().title()
assert "WORD" in title
assert str(index + 1) in title
debug.input(word)
class InputFlowBip39RecoveryPIN(InputFlowBase): class InputFlowBip39RecoveryPIN(InputFlowBase):
def __init__(self, client: Client, mnemonic: list[str]): def __init__(self, client: Client, mnemonic: list[str]):
super().__init__(client) super().__init__(client)
self.mnemonic = mnemonic self.mnemonic = mnemonic
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
yield from bip39_recovery_possible_pin(self.debug, self.mnemonic, pin="654") yield from bip39_recovery_possible_pin_tt(self.debug, self.mnemonic, pin="654")
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
yield yield from bip39_recovery_possible_pin_tr(self.debug, self.mnemonic, pin="654")
assert "By continuing you agree" in self.layout().text_content()
self.debug.press_right()
assert "trezor.io/tos" in self.layout().text_content()
self.debug.press_yes()
yield
self.debug.input("654")
yield
assert "re-enter PIN" in self.layout().text_content()
self.debug.press_right()
yield
self.debug.input("654")
yield
assert "number of words" in self.layout().text_content()
self.debug.press_yes()
yield
yield
assert "NUMBER OF WORDS" in self.layout().title()
self.debug.input(str(len(self.mnemonic)))
yield
assert "Enter recovery seed" in self.layout().text_content()
self.debug.press_yes()
yield
assert "WORD ENTERING" in self.layout().title()
self.debug.press_right()
yield
for word in self.mnemonic:
assert "WORD" in self.layout().title()
self.debug.input(word)
yield
assert (
"You have finished recovering your wallet." in self.layout().text_content()
)
self.debug.press_yes()
class InputFlowBip39RecoveryNoPIN(InputFlowBase): class InputFlowBip39RecoveryNoPIN(InputFlowBase):
@ -1505,28 +1474,10 @@ class InputFlowBip39RecoveryNoPIN(InputFlowBase):
self.mnemonic = mnemonic self.mnemonic = mnemonic
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
yield from bip39_recovery_possible_pin(self.debug, self.mnemonic, pin=None) yield from bip39_recovery_possible_pin_tt(self.debug, self.mnemonic, pin=None)
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
yield # Confirm recovery yield from bip39_recovery_possible_pin_tr(self.debug, self.mnemonic, pin=None)
self.debug.press_yes()
yield # Homescreen
self.debug.press_yes()
yield # Enter word count
self.debug.input(str(len(self.mnemonic)))
yield # Homescreen
self.debug.press_yes()
yield # Homescreen
self.debug.press_yes()
yield # Enter words
for word in self.mnemonic:
self.debug.input(word)
yield # confirm success
self.debug.press_yes()
yield
class InputFlowSlip39AdvancedRecoveryDryRun(InputFlowBase): class InputFlowSlip39AdvancedRecoveryDryRun(InputFlowBase):
@ -1541,6 +1492,18 @@ class InputFlowSlip39AdvancedRecoveryDryRun(InputFlowBase):
yield from recovery_enter_shares(self.debug, self.shares, groups=True) yield from recovery_enter_shares(self.debug, self.shares, groups=True)
def confirm_recovery(debug: DebugLink) -> GeneratorType:
if debug.model == "T":
yield # Confirm Recovery
debug.press_yes()
elif debug.model == "R":
yield # Confirm Recovery
debug.press_right()
debug.press_yes()
yield # Safe to eject
debug.press_yes()
class InputFlowSlip39AdvancedRecovery(InputFlowBase): class InputFlowSlip39AdvancedRecovery(InputFlowBase):
def __init__(self, client: Client, shares: list[str], click_info: bool): def __init__(self, client: Client, shares: list[str], click_info: bool):
super().__init__(client) super().__init__(client)
@ -1548,9 +1511,7 @@ class InputFlowSlip39AdvancedRecovery(InputFlowBase):
self.click_info = click_info self.click_info = click_info
def input_flow_common(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
# Proceed with recovery
yield from recovery_enter_shares( yield from recovery_enter_shares(
self.debug, self.shares, groups=True, click_info=self.click_info self.debug, self.shares, groups=True, click_info=self.click_info
) )
@ -1561,8 +1522,7 @@ class InputFlowSlip39AdvancedRecoveryAbort(InputFlowBase):
super().__init__(client) super().__init__(client)
def input_flow_common(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
yield # Homescreen - abort process yield # Homescreen - abort process
self.debug.press_no() self.debug.press_no()
yield # Homescreen - confirm abort yield # Homescreen - confirm abort
@ -1575,11 +1535,12 @@ class InputFlowSlip39AdvancedRecoveryNoAbort(InputFlowBase):
self.shares = shares self.shares = shares
def input_flow_common(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
yield # Homescreen - abort process yield # Homescreen - abort process
self.debug.press_no() self.debug.press_no()
yield # Homescreen - go back to process yield # Homescreen - go back to process
if self.debug.model == "R":
self.debug.press_right()
self.debug.press_no() self.debug.press_no()
yield from recovery_enter_shares(self.debug, self.shares, groups=True) yield from recovery_enter_shares(self.debug, self.shares, groups=True)
@ -1590,18 +1551,9 @@ class InputFlowSlip39AdvancedRecoveryTwoSharesWarning(InputFlowBase):
self.first_share = first_share self.first_share = first_share
self.second_share = second_share self.second_share = second_share
def input_flow_tt(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes() yield from slip39_recovery_setup_and_first_share(self.debug, self.first_share)
yield # Homescreen - start process
self.debug.press_yes()
yield # Enter number of words
self.debug.input(str(len(self.first_share)))
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield # Enter first share
for word in self.first_share:
self.debug.input(word)
yield # Continue to next share yield # Continue to next share
self.debug.press_yes() self.debug.press_yes()
@ -1613,38 +1565,6 @@ class InputFlowSlip39AdvancedRecoveryTwoSharesWarning(InputFlowBase):
br = yield br = yield
assert br.code == messages.ButtonRequestType.Warning assert br.code == messages.ButtonRequestType.Warning
self.client.cancel()
def input_flow_tr(self) -> GeneratorType:
yield # Confirm Recovery
self.debug.press_yes()
yield # Homescreen - start process
self.debug.press_yes()
yield # Enter number of words
self.debug.input(str(len(self.first_share)))
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield # Enter first share
self.debug.press_yes()
yield # Enter first share
for word in self.first_share:
self.debug.input(word)
yield # Continue to next share
self.debug.press_yes()
yield # Homescreen - next share
self.debug.press_yes()
yield # Homescreen - next share
self.debug.press_yes()
yield # Enter next share
for word in self.second_share:
self.debug.input(word)
yield
br = yield
assert br.code == messages.ButtonRequestType.Warning
self.debug.press_right()
self.debug.press_yes() self.debug.press_yes()
yield yield
@ -1655,6 +1575,11 @@ def slip39_recovery_possible_pin(
debug: DebugLink, shares: list[str], pin: Optional[str] debug: DebugLink, shares: list[str], pin: Optional[str]
) -> GeneratorType: ) -> GeneratorType:
yield # Confirm Recovery/Dryrun yield # Confirm Recovery/Dryrun
if debug.model == "R" and "BACKUP CHECK" not in debug.wait_layout().title():
# dryruns do not have extra dialogs
debug.press_right()
debug.press_yes()
yield
debug.press_yes() debug.press_yes()
if pin is not None: if pin is not None:
@ -1694,8 +1619,7 @@ class InputFlowSlip39BasicRecoveryAbort(InputFlowBase):
super().__init__(client) super().__init__(client)
def input_flow_common(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
yield # Homescreen - abort process yield # Homescreen - abort process
self.debug.press_no() self.debug.press_no()
yield # Homescreen - confirm abort yield # Homescreen - confirm abort
@ -1708,11 +1632,12 @@ class InputFlowSlip39BasicRecoveryNoAbort(InputFlowBase):
self.shares = shares self.shares = shares
def input_flow_common(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
yield # Homescreen - abort process yield # Homescreen - abort process
self.debug.press_no() self.debug.press_no()
yield # Homescreen - go back to process yield # Homescreen - go back to process
if self.debug.model == "R":
self.debug.press_right()
self.debug.press_no() self.debug.press_no()
# run recovery flow # run recovery flow
yield from recovery_enter_shares(self.debug, self.shares) yield from recovery_enter_shares(self.debug, self.shares)
@ -1726,6 +1651,9 @@ def slip39_recovery_setup_and_first_share(
yield # Enter number of words yield # Enter number of words
debug.input(str(len(first_share))) debug.input(str(len(first_share)))
yield # Homescreen - proceed to share entry yield # Homescreen - proceed to share entry
if debug.model == "R":
debug.press_right(wait=True)
debug.press_right(wait=True)
debug.press_yes() debug.press_yes()
yield # Enter first share yield # Enter first share
for word in first_share: for word in first_share:
@ -1736,9 +1664,8 @@ class InputFlowSlip39BasicRecoveryRetryFirst(InputFlowBase):
def __init__(self, client: Client): def __init__(self, client: Client):
super().__init__(client) super().__init__(client)
def input_flow_tt(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
first_share = ["slush"] * 20 first_share = ["slush"] * 20
yield from slip39_recovery_setup_and_first_share(self.debug, first_share) yield from slip39_recovery_setup_and_first_share(self.debug, first_share)
@ -1757,55 +1684,8 @@ class InputFlowSlip39BasicRecoveryRetryFirst(InputFlowBase):
yield # Homescreen yield # Homescreen
self.debug.press_no() self.debug.press_no()
yield # Confirm abort yield # Confirm abort
self.debug.press_yes() if self.debug.model == "R":
self.debug.press_right(wait=True)
def input_flow_tr(self) -> GeneratorType:
yield # Confirm Recovery
self.debug.press_right()
self.debug.press_yes()
yield # Homescreen - start process
self.debug.press_yes()
yield # Enter number of words
self.debug.input("20")
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield # Enter first share
self.debug.press_yes()
for _ in range(20):
self.debug.input("slush")
yield
# assert br.code == messages.ButtonRequestType.Warning
self.debug.press_yes()
yield # Homescreen - start process
self.debug.press_yes()
yield # Enter number of words
self.debug.input("33")
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield
for _ in range(33):
self.debug.input("slush")
yield
self.debug.press_yes()
yield
self.debug.press_no()
yield
self.debug.press_right()
yield
self.debug.press_right()
yield
self.debug.press_right()
yield
self.debug.press_yes() self.debug.press_yes()
@ -1814,9 +1694,8 @@ class InputFlowSlip39BasicRecoveryRetrySecond(InputFlowBase):
super().__init__(client) super().__init__(client)
self.shares = shares self.shares = shares
def input_flow_tt(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
# First valid share # First valid share
first_share = self.shares[0].split(" ") first_share = self.shares[0].split(" ")
@ -1842,45 +1721,8 @@ class InputFlowSlip39BasicRecoveryRetrySecond(InputFlowBase):
yield # More shares needed yield # More shares needed
self.debug.press_no() self.debug.press_no()
yield # Confirm abort yield # Confirm abort
self.debug.press_yes() if self.debug.model == "R":
self.debug.press_right(wait=True)
def input_flow_tr(self) -> GeneratorType:
yield # Confirm Recovery
self.debug.press_right()
self.debug.press_yes()
yield # Homescreen - start process
self.debug.press_yes()
yield # Enter number of words
self.debug.input("20")
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield # Enter first share
self.debug.press_yes()
yield # Enter first share
share = self.shares[0].split(" ")
for word in share:
self.debug.input(word)
yield # More shares needed
self.debug.press_yes()
yield # Enter another share
share = share[:3] + ["slush"] * 17
for word in share:
self.debug.input(word)
yield # Invalid share
# assert br.code == messages.ButtonRequestType.Warning
self.debug.press_yes()
yield # Proceed to next share
share = self.shares[1].split(" ")
for word in share:
self.debug.input(word)
yield # More shares needed
self.debug.press_no()
yield # Confirm abort
self.debug.press_yes() self.debug.press_yes()
@ -1890,9 +1732,8 @@ class InputFlowSlip39BasicRecoveryWrongNthWord(InputFlowBase):
self.share = share self.share = share
self.nth_word = nth_word self.nth_word = nth_word
def input_flow_tt(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
# First complete share # First complete share
yield from slip39_recovery_setup_and_first_share(self.debug, self.share) yield from slip39_recovery_setup_and_first_share(self.debug, self.share)
@ -1909,39 +1750,8 @@ class InputFlowSlip39BasicRecoveryWrongNthWord(InputFlowBase):
br = yield br = yield
assert br.code == messages.ButtonRequestType.Warning assert br.code == messages.ButtonRequestType.Warning
self.client.cancel()
def input_flow_tr(self) -> GeneratorType:
yield # Confirm Recovery
self.debug.press_right()
self.debug.press_yes() self.debug.press_yes()
yield # Homescreen - start process
self.debug.press_yes()
yield # Enter number of words
self.debug.input(str(len(self.share)))
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield # Enter first share
self.debug.press_yes()
yield # Enter first share
for word in self.share:
self.debug.input(word)
yield # Continue to next share
self.debug.press_yes()
yield # Enter next share
self.debug.press_yes()
yield # Enter next share
for i, word in enumerate(self.share):
if i < self.nth_word:
self.debug.input(word)
else:
self.debug.input(self.share[-1])
break
yield yield
# assert br.code == messages.ButtonRequestType.Warning
self.client.cancel() self.client.cancel()
@ -1952,9 +1762,8 @@ class InputFlowSlip39BasicRecoverySameShare(InputFlowBase):
self.first_share = first_share self.first_share = first_share
self.second_share = second_share self.second_share = second_share
def input_flow_tt(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield from confirm_recovery(self.debug)
self.debug.press_yes()
# First complete share # First complete share
yield from slip39_recovery_setup_and_first_share(self.debug, self.first_share) yield from slip39_recovery_setup_and_first_share(self.debug, self.first_share)
@ -1967,44 +1776,9 @@ class InputFlowSlip39BasicRecoverySameShare(InputFlowBase):
br = yield br = yield
assert br.code == messages.ButtonRequestType.Warning assert br.code == messages.ButtonRequestType.Warning
# To catch the WARNING screen
self.debug.press_yes() self.debug.press_yes()
yield yield
self.client.cancel()
def input_flow_tr(self) -> GeneratorType:
yield # Confirm Recovery
self.debug.press_right()
self.debug.press_yes()
yield # Homescreen - start process
self.debug.press_yes()
yield # Enter number of words
self.debug.input(str(len(self.first_share)))
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield # Homescreen - proceed to share entry
self.debug.press_yes()
yield # Enter first share
for word in self.first_share:
self.debug.input(word)
yield # Continue to next share
self.debug.press_yes()
yield # Continue to next share
self.debug.press_yes()
yield # Enter next share
for word in self.second_share:
self.debug.input(word)
br = yield
br = yield
assert br.code == messages.ButtonRequestType.Warning
self.debug.press_right()
self.debug.press_yes()
yield
self.client.cancel() self.client.cancel()
@ -2012,19 +1786,14 @@ class InputFlowResetSkipBackup(InputFlowBase):
def __init__(self, client: Client): def __init__(self, client: Client):
super().__init__(client) super().__init__(client)
def input_flow_tt(self) -> GeneratorType: def input_flow_common(self) -> GeneratorType:
yield # Confirm Recovery yield # Confirm Recovery
if self.debug.model == "R":
self.debug.press_right()
self.debug.press_yes() self.debug.press_yes()
yield # Skip Backup yield # Skip Backup
self.debug.press_no() self.debug.press_no()
yield # Confirm skip backup yield # Confirm skip backup
self.debug.press_no() if self.debug.model == "R":
self.debug.press_right()
def input_flow_tr(self) -> GeneratorType:
yield # Confirm Recovery
self.debug.press_right()
self.debug.press_yes()
yield # Skip Backup
self.debug.press_no()
yield # Confirm skip backup
self.debug.press_no() self.debug.press_no()

View File

@ -42,7 +42,7 @@ def test_abort(core_emulator: Emulator):
device_handler.run(device.recover, pin_protection=False) device_handler.run(device.recover, pin_protection=False)
assert debug.wait_layout().title() == "WALLET RECOVERY" assert debug.wait_layout().title() == "RECOVER WALLET"
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert "number of words" in layout.text_content() assert "number of words" in layout.text_content()
@ -174,7 +174,7 @@ def test_recovery_multiple_resets(core_emulator: Emulator):
expected_text = "You have entered" expected_text = "You have entered"
debug = _restart(device_handler, core_emulator) debug = _restart(device_handler, core_emulator)
assert "You have finished recovering your wallet" in layout.text_content() assert "Wallet recovered successfully" in layout.text_content()
device_handler = BackgroundDeviceHandler(core_emulator.client) device_handler = BackgroundDeviceHandler(core_emulator.client)
debug = device_handler.debuglink() debug = device_handler.debuglink()

View File

@ -313,7 +313,10 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]):
recovery.select_number_of_words(debug, wait=not debug.legacy_debug) recovery.select_number_of_words(debug, wait=not debug.legacy_debug)
layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[0]) layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[0])
if not debug.legacy_ui and not debug.legacy_debug: if not debug.legacy_ui and not debug.legacy_debug:
assert "2 more shares" in layout.text_content() assert (
"1 of 3 shares entered" in layout.text_content()
or "2 more shares" in layout.text_content()
)
device_id = emu.client.features.device_id device_id = emu.client.features.device_id
storage = emu.get_storage() storage = emu.get_storage()
@ -327,11 +330,17 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]):
# second share # second share
layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[2]) layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[2])
assert "1 more share" in layout.text_content() assert (
"2 of 3 shares entered" in layout.text_content()
or "1 more share" in layout.text_content()
)
# last one # last one
layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[1]) layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[1])
assert "You have finished recovering your wallet" in layout.text_content() assert (
"Wallet recovered successfully" in layout.text_content()
or "finished recovering" in layout.text_content()
)
# Check the result # Check the result
state = debug.state() state = debug.state()