1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-04 12:56:25 +00:00

WIP - some feedback from product

This commit is contained in:
grdddj 2023-01-08 17:48:53 +01:00
parent cf538e5789
commit aa4c3551d4
29 changed files with 213 additions and 125 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 B

After

Width:  |  Height:  |  Size: 134 B

View File

@ -10,11 +10,23 @@ pub fn shuffle<T>(slice: &mut [T]) {
} }
} }
/// Returns a random number in the range [min, max].
pub fn uniform_between(min: u32, max: u32) -> u32 { pub fn uniform_between(min: u32, max: u32) -> u32 {
assert!(max > min); assert!(max > min);
uniform(max - min + 1) + min uniform(max - min + 1) + min
} }
/// Returns a random number in the range [min, max] except one `except` number.
pub fn uniform_between_except(min: u32, max: u32, except: u32) -> u32 {
// Generate uniform_between as long as it is not except
loop {
let rand = uniform_between(min, max);
if rand != except {
return rand;
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -27,4 +39,11 @@ mod tests {
assert!((256..=512).contains(&uniform_between(256, 512))); assert!((256..=512).contains(&uniform_between(256, 512)));
} }
} }
#[test]
fn uniform_between_except_test() {
for _ in 0..10 {
assert!(uniform_between_except(10, 12, 11) != 11);
}
}
} }

View File

@ -545,6 +545,15 @@ impl ButtonLayout {
) )
} }
/// Left text and right arrow.
pub fn text_none_arrow(text: StrBuffer) -> Self {
Self::new(
Some(ButtonDetails::text(text)),
None,
Some(ButtonDetails::right_arrow_icon()),
)
}
/// Only right text. /// Only right text.
pub fn none_none_text(text: StrBuffer) -> Self { pub fn none_none_text(text: StrBuffer) -> Self {
Self::new(None, None, Some(ButtonDetails::text(text))) Self::new(None, None, Some(ButtonDetails::text(text)))

View File

@ -36,13 +36,10 @@ where
} }
/// Aligning the title to the center, instead of the left. /// Aligning the title to the center, instead of the left.
/// Also disabling scrollbar in the positive case, as they are not /// Also disabling scrollbar, as they are not compatible.
/// compatible. pub fn with_title_centered(mut self) -> Self {
pub fn with_title_center(mut self, title_centered: bool) -> Self { self.title_centered = true;
self.title_centered = title_centered;
if title_centered {
self.account_for_scrollbar = false; self.account_for_scrollbar = false;
}
self self
} }

View File

@ -44,6 +44,17 @@ impl ChoiceItem {
self self
} }
/// Getting the offset of the icon to center it vertically.
/// Depending on its size and used font.
fn icon_vertical_offset(&self) -> Offset {
if let Some(icon) = self.icon {
let height_diff = self.font.text_height() - icon.height();
Offset::y(-height_diff / 2)
} else {
Offset::zero()
}
}
/// Getting the text width in pixels. /// Getting the text width in pixels.
pub fn text_width(&self) -> i16 { pub fn text_width(&self) -> i16 {
self.font.text_width(&self.text) self.font.text_width(&self.text)
@ -88,7 +99,11 @@ impl ChoiceItem {
/// Showing only the icon, if available, otherwise the text. /// Showing only the icon, if available, otherwise the text.
pub fn render_left(&self, area: Rect) { pub fn render_left(&self, area: Rect) {
if let Some(icon) = self.icon { if let Some(icon) = self.icon {
icon.draw_bottom_right(area.bottom_right(), theme::FG, theme::BG); icon.draw_bottom_right(
area.bottom_right() + self.icon_vertical_offset(),
theme::FG,
theme::BG,
);
} else { } else {
display_right(area.bottom_right(), &self.text, self.font); display_right(area.bottom_right(), &self.text, self.font);
} }
@ -98,7 +113,11 @@ impl ChoiceItem {
/// Showing only the icon, if available, otherwise the text. /// Showing only the icon, if available, otherwise the text.
pub fn render_right(&self, area: Rect) { pub fn render_right(&self, area: Rect) {
if let Some(icon) = self.icon { if let Some(icon) = self.icon {
icon.draw_bottom_left(area.bottom_left(), theme::FG, theme::BG); icon.draw_bottom_left(
area.bottom_left() + self.icon_vertical_offset(),
theme::FG,
theme::BG,
);
} else { } else {
display(area.bottom_left(), &self.text, self.font); display(area.bottom_left(), &self.text, self.font);
} }
@ -135,7 +154,7 @@ impl Choice for ChoiceItem {
if let Some(icon) = self.icon { if let Some(icon) = self.icon {
let fg_color = if inverse { theme::BG } else { theme::FG }; let fg_color = if inverse { theme::BG } else { theme::FG };
let bg_color = if inverse { theme::FG } else { theme::BG }; let bg_color = if inverse { theme::FG } else { theme::BG };
icon.draw_bottom_left(baseline, fg_color, bg_color); icon.draw_bottom_left(baseline + self.icon_vertical_offset(), fg_color, bg_color);
baseline = baseline + Offset::x(icon.width() + 2); baseline = baseline + Offset::x(icon.width() + 2);
} }
if inverse { if inverse {

View File

@ -193,10 +193,12 @@ impl Component for PinEntry {
_ => { _ => {
if !self.is_full() { if !self.is_full() {
self.append_new_digit(ctx, page_counter); self.append_new_digit(ctx, page_counter);
// Choosing any random digit to be shown next // Choosing random digit to be shown next, but different
let new_page_counter = random::uniform_between( // from the current choice.
let new_page_counter = random::uniform_between_except(
NUMBER_START_INDEX as u32, NUMBER_START_INDEX as u32,
(CHOICE_LENGTH - 1) as u32, (CHOICE_LENGTH - 1) as u32,
page_counter as u32,
); );
self.choice_page self.choice_page
.set_page_counter(ctx, new_page_counter as u8); .set_page_counter(ctx, new_page_counter as u8);

View File

@ -75,6 +75,13 @@ impl<const N: usize> SimpleChoice<N> {
self self
} }
/// Show choices even when they do not fit entirely.
pub fn with_show_incomplete(mut self) -> Self {
self.choice_page = self.choice_page.with_incomplete(true);
self
}
/// Returning chosen page index instead of the string result.
pub fn with_return_index(mut self) -> Self { pub fn with_return_index(mut self) -> Self {
self.return_index = true; self.return_index = true;
self self

View File

@ -149,8 +149,6 @@ impl Loader {
// NOTE: need to calculate this in `i32`, it would overflow using `i16` // NOTE: need to calculate this in `i32`, it would overflow using `i16`
let invert_from = ((self.area.width() as i32 + 1) * done) / (display::LOADER_MAX as i32); let invert_from = ((self.area.width() as i32 + 1) * done) / (display::LOADER_MAX as i32);
// TODO: the text should be moved one pixel to the top so it is centered in the
// loader
display::bar_with_text_and_fill( display::bar_with_text_and_fill(
self.area, self.area,
Some(&self.text_overlay), Some(&self.text_overlay),
@ -167,7 +165,8 @@ impl Component for Loader {
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds; self.area = bounds;
let baseline = bounds.bottom_center() + Offset::new(1, -1); // Centering the text in the loader rectangle.
let baseline = bounds.bottom_center() + Offset::new(1, -2);
self.text_overlay.place(baseline); self.text_overlay.place(baseline);
self.area self.area
} }

View File

@ -414,7 +414,7 @@ extern "C" fn new_show_receive_address(n_args: usize, args: *const Obj, kwargs:
.text_bold("ADDRESS MISMATCH?".into()) .text_bold("ADDRESS MISMATCH?".into())
.newline() .newline()
.newline_half() .newline_half()
.text_mono("Please contact Trezor support on trezor.io/support".into()) .text_mono("Please contact Trezor support at trezor.io/support".into())
} }
_ => unreachable!(), _ => unreachable!(),
} }
@ -565,7 +565,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
tutorial_screen( tutorial_screen(
"HELLO".into(), "HELLO".into(),
"Welcome to Trezor.\nPress right to continue.".into(), "Welcome to Trezor.\nPress right to continue.".into(),
ButtonLayout::cancel_none_arrow(), ButtonLayout::text_none_arrow("SKIP".into()),
ButtonActions::last_none_next(), ButtonActions::last_none_next(),
) )
}, },
@ -609,7 +609,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
Font::MONO, Font::MONO,
) )
.newline() .newline()
.text_mono("Tutorial finished.".into()) .text_mono("Tutorial complete.".into())
.newline() .newline()
.newline() .newline()
.alignment(Alignment::Center) .alignment(Alignment::Center)
@ -655,10 +655,9 @@ extern "C" fn new_request_number(n_args: usize, args: *const Obj, kwargs: *mut M
let max_count: u32 = kwargs.get(Qstr::MP_QSTR_max_count)?.try_into()?; let max_count: u32 = kwargs.get(Qstr::MP_QSTR_max_count)?.try_into()?;
let count: u32 = kwargs.get(Qstr::MP_QSTR_count)?.try_into()?; let count: u32 = kwargs.get(Qstr::MP_QSTR_count)?.try_into()?;
let obj = LayoutObj::new(Frame::new( let obj = LayoutObj::new(
title, Frame::new(title, NumberInput::new(min_count, max_count, count)).with_title_centered(),
NumberInput::new(min_count, max_count, count), )?;
))?;
Ok(obj.into()) Ok(obj.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -736,10 +735,10 @@ extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map)
Frame::new( Frame::new(
title, title,
SimpleChoice::new(words, false) SimpleChoice::new(words, false)
.with_only_one_item() .with_show_incomplete()
.with_return_index(), .with_return_index(),
) )
.with_title_center(true), .with_title_centered(),
)?; )?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -756,7 +755,9 @@ extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mu
.into_iter() .into_iter()
.collect(); .collect();
let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(choices, false)))?; let obj = LayoutObj::new(
Frame::new(title, SimpleChoice::new(choices, false)).with_title_centered(),
)?;
Ok(obj.into()) Ok(obj.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -767,7 +768,7 @@ extern "C" fn new_request_bip39(n_args: usize, args: *const Obj, kwargs: *mut Ma
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let obj = LayoutObj::new( let obj = LayoutObj::new(
Frame::new(prompt, WordlistEntry::new(WordlistType::Bip39)).with_title_center(true), Frame::new(prompt, WordlistEntry::new(WordlistType::Bip39)).with_title_centered(),
)?; )?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -779,7 +780,7 @@ extern "C" fn new_request_slip39(n_args: usize, args: *const Obj, kwargs: *mut M
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let obj = LayoutObj::new( let obj = LayoutObj::new(
Frame::new(prompt, WordlistEntry::new(WordlistType::Slip39)).with_title_center(true), Frame::new(prompt, WordlistEntry::new(WordlistType::Slip39)).with_title_centered(),
)?; )?;
Ok(obj.into()) Ok(obj.into())
}; };

View File

@ -48,7 +48,7 @@ pub const ICON_CANCEL: IconAndName = IconAndName::new(
"cancel", "cancel",
); // 8*8 ); // 8*8
pub const ICON_DELETE: IconAndName = pub const ICON_DELETE: IconAndName =
IconAndName::new(include_res!("model_tr/res/delete.toif"), "delete"); // 12*8 IconAndName::new(include_res!("model_tr/res/delete.toif"), "delete"); // 10*7
pub const ICON_EYE: IconAndName = pub const ICON_EYE: IconAndName =
IconAndName::new(include_res!("model_tr/res/eye_round.toif"), "eye"); // 12*7 IconAndName::new(include_res!("model_tr/res/eye_round.toif"), "eye"); // 12*7
pub const ICON_LOCK: IconAndName = IconAndName::new(include_res!("model_tr/res/lock.toif"), "lock"); // 10*10 pub const ICON_LOCK: IconAndName = IconAndName::new(include_res!("model_tr/res/lock.toif"), "lock"); // 10*10

View File

@ -9,6 +9,7 @@ from trezor.enums import (
) )
from trezor.strings import format_amount from trezor.strings import format_amount
from trezor.ui import layouts from trezor.ui import layouts
from trezor.ui.layouts import confirm_metadata, confirm_properties
from apps.common.paths import address_n_to_str from apps.common.paths import address_n_to_str
@ -21,9 +22,6 @@ from .helpers.utils import (
format_stake_pool_id, format_stake_pool_id,
) )
confirm_metadata = layouts.confirm_metadata # global_import_cache
confirm_properties = layouts.confirm_properties # global_import_cache
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Literal from typing import Literal

View File

@ -121,7 +121,7 @@ async def error_pin_invalid(ctx: Context) -> NoReturn:
await show_error_and_raise( await show_error_and_raise(
ctx, ctx,
"warning_wrong_pin", "warning_wrong_pin",
"The PIN you entered is invalid.", "PIN you have entered is not valid.",
"Wrong PIN", # header "Wrong PIN", # header
exc=wire.PinInvalid, exc=wire.PinInvalid,
) )

View File

@ -28,7 +28,11 @@ async def change_pin(ctx: Context, msg: ChangePin) -> Success:
await _require_confirm_change_pin(ctx, msg) await _require_confirm_change_pin(ctx, msg)
# get old pin # get old pin
curpin, salt = await request_pin_and_sd_salt(ctx, "Enter old PIN") if msg.remove:
prompt = "Enter PIN"
else:
prompt = "Enter old PIN"
curpin, salt = await request_pin_and_sd_salt(ctx, prompt)
# if changing pin, pre-check the entered pin before getting new pin # if changing pin, pre-check the entered pin before getting new pin
if curpin and not msg.remove: if curpin and not msg.remove:

View File

@ -90,7 +90,7 @@ def _require_confirm_action(
title, title,
"enable wipe code?", "enable wipe code?",
[ [
"Wipe code will\nbe used to delete this device.", "Wipe code can be used to erase all data from this device.",
], ],
) )

View File

@ -65,7 +65,9 @@ async def _continue_recovery_process(ctx: GenericContext) -> 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(ctx, "Select", "Select number of words") await layout.homescreen_dialog(
ctx, "Select", "First select the number of words in your recovery seed"
)
# ask for the number of words # ask for the number of words
word_count = await layout.request_word_count(ctx, dry_run) word_count = await layout.request_word_count(ctx, dry_run)
# ...and only then show the starting screen with word count. # ...and only then show the starting screen with word count.
@ -158,7 +160,7 @@ async def _finish_recovery(
storage_recovery.end_progress() storage_recovery.end_progress()
await show_success( await show_success(
ctx, "success_recovery", "You have successfully recovered your wallet." ctx, "success_recovery", "You have finished recovering your wallet."
) )
return Success(message="Device recovered") return Success(message="Device recovered")
@ -196,7 +198,7 @@ async def _request_share_first_screen(ctx: GenericContext, word_count: int) -> N
) )
else: # BIP-39 else: # BIP-39
await layout.homescreen_dialog( await layout.homescreen_dialog(
ctx, "Enter seed", "Enter recovery seed", f"({word_count} words)" ctx, "Enter seed", "Now enter your recovery seed", f"({word_count} words)"
) )

View File

@ -52,7 +52,7 @@ async def request_mnemonic(
ctx, ctx,
"request_word", "request_word",
"WORD ENTERING", "WORD ENTERING",
description="You'll only have to select first 2-3 letters.", description="You'll only have to select the first 2-3 letters.",
verb="CONTINUE", verb="CONTINUE",
verb_cancel=None, verb_cancel=None,
br_code=ButtonRequestType.MnemonicInput, br_code=ButtonRequestType.MnemonicInput,
@ -103,14 +103,7 @@ async def show_dry_run_result(
from trezor.ui.layouts import show_success from trezor.ui.layouts import show_success
if result: if result:
if is_slip39: text = "You have finished verifying your recovery seed"
text = text_r(
"The entered recovery\nshares are valid and\nmatch what is currently\nin the device."
)
else:
text = text_r(
"The entered recovery\nseed is valid and\nmatches the one\nin the device."
)
await show_success(ctx, "success_dry_recovery", text, button="Continue") await show_success(ctx, "success_dry_recovery", text, button="Continue")
else: else:
if is_slip39: if is_slip39:

View File

@ -12,8 +12,6 @@ from trezor.ui.layouts.reset import ( # noqa: F401
slip39_show_checklist, slip39_show_checklist,
) )
from .. import text_r
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence from typing import Sequence
from trezor.wire import GenericContext from trezor.wire import GenericContext
@ -170,8 +168,9 @@ async def show_backup_warning(ctx: GenericContext, slip39: bool = False) -> None
async def show_backup_success(ctx: GenericContext) -> None: async def show_backup_success(ctx: GenericContext) -> None:
text = text_r("Use your backup\nwhen you need to\nrecover your wallet.") from trezor.ui.layouts.reset import show_success_backup
await show_success(ctx, "success_backup", text, "Your backup is done.")
await show_success_backup(ctx)
# BIP39 # BIP39

View File

@ -24,11 +24,20 @@ async def cipher_key_value(ctx: Context, msg: CipherKeyValue) -> CipheredKeyValu
encrypt = msg.encrypt encrypt = msg.encrypt
decrypt = not msg.encrypt decrypt = not msg.encrypt
if (encrypt and msg.ask_on_encrypt) or (decrypt and msg.ask_on_decrypt): if (encrypt and msg.ask_on_encrypt) or (decrypt and msg.ask_on_decrypt):
# Special case for Trezor Suite, which asks for setting up labels
if msg.key == "Enable labeling?":
title = "SUITE LABELING"
verb = "ENABLE"
else:
if encrypt: if encrypt:
title = "Encrypt value" title = "Encrypt value"
else: else:
title = "Decrypt value" title = "Decrypt value"
await confirm_action(ctx, "cipher_key_value", title, description=msg.key) verb = "CONFIRM"
await confirm_action(
ctx, "cipher_key_value", title, description=msg.key, verb=verb
)
node = keychain.derive(msg.address_n) node = keychain.derive(msg.address_n)

View File

@ -510,6 +510,7 @@ async def confirm_action(
hold: bool = False, hold: bool = False,
hold_danger: bool = False, hold_danger: bool = False,
reverse: bool = False, reverse: bool = False,
uppercase_title: bool = True,
exc: ExceptionType = ActionCancelled, exc: ExceptionType = ActionCancelled,
br_code: ButtonRequestType = BR_TYPE_OTHER, br_code: ButtonRequestType = BR_TYPE_OTHER,
) -> None: ) -> None:
@ -535,7 +536,7 @@ async def confirm_action(
ctx, ctx,
RustLayout( RustLayout(
trezorui2.confirm_action( trezorui2.confirm_action(
title=title.upper(), title=title.upper() if uppercase_title else title,
action=action, action=action,
description=description, description=description,
verb=verb, verb=verb,
@ -566,15 +567,12 @@ async def confirm_reset_device(
if show_tutorial: if show_tutorial:
await tutorial(ctx) await tutorial(ctx)
to_show = "By continuing you agree to our terms and conditions.\nMore info at trezor.io/tos."
if not recovery:
to_show += "\nUse you backup to recover your wallet."
return await _placeholder_confirm( return await _placeholder_confirm(
ctx, ctx,
"recover_device" if recovery else "setup_device", "recover_device" if recovery else "setup_device",
"WALLET RECOVERY" if recovery else "WALLET BACKUP", "WALLET RECOVERY" if recovery else "WALLET CREATION",
description=to_show, description="By continuing you agree to Trezor Company terms and conditions.\n\rMore info at trezor.io/tos",
verb="RECOVER WALLET" if recovery else "CREATE WALLET",
br_code=ButtonRequestType.ProtectCall br_code=ButtonRequestType.ProtectCall
if recovery if recovery
else ButtonRequestType.ResetDevice, else ButtonRequestType.ResetDevice,
@ -587,14 +585,14 @@ async def confirm_backup(ctx: GenericContext) -> bool:
ctx, ctx,
"backup_device", "backup_device",
"SUCCESS", "SUCCESS",
"New wallet created successfully!\nYou should back up your new wallet right now.", description="New wallet has been created.\nIt should be backed up now!",
verb="BACK UP", verb="BACK UP",
verb_cancel="SKIP", verb_cancel="SKIP",
br_code=ButtonRequestType.ResetDevice, br_code=ButtonRequestType.ResetDevice,
): ):
return True return True
confirmed = await get_bool( return await get_bool(
ctx, ctx,
"backup_device", "backup_device",
"WARNING", "WARNING",
@ -604,7 +602,6 @@ async def confirm_backup(ctx: GenericContext) -> bool:
verb_cancel="SKIP", verb_cancel="SKIP",
br_code=ButtonRequestType.ResetDevice, br_code=ButtonRequestType.ResetDevice,
) )
return confirmed
async def confirm_path_warning( async def confirm_path_warning(
@ -771,10 +768,25 @@ def show_success(
subheader: str | None = None, subheader: str | None = None,
button: str = "Continue", button: str = "Continue",
) -> Awaitable[None]: ) -> Awaitable[None]:
title = "Success"
# In case only subheader is supplied, showing it
# in regular font, not bold.
if not content and subheader:
content = subheader
subheader = None
# Special case for Shamir backup - to show everything just on one page
# in regular font.
if "Continue with" in content:
content = f"{subheader}\n{content}"
subheader = None
title = ""
return _show_modal( return _show_modal(
ctx, ctx,
br_type, br_type,
"Success", title,
subheader, subheader,
content, content,
button_confirm=button, button_confirm=button,
@ -1192,7 +1204,7 @@ async def show_popup(
def request_passphrase_on_host() -> None: def request_passphrase_on_host() -> None:
draw_simple( draw_simple(
trezorui2.show_info( trezorui2.show_info(
title="", title="HIDDEN WALLET",
description="Please type your passphrase on the connected host.", description="Please type your passphrase on the connected host.",
) )
) )
@ -1226,7 +1238,9 @@ async def request_pin_on_device(
) -> str: ) -> str:
from trezor import wire from trezor import wire
if attempts_remaining is None: # Not showing the prompt in case user did not enter it badly yet
# (has full 16 attempts left)
if attempts_remaining is None or attempts_remaining == 16:
subprompt = "" subprompt = ""
elif attempts_remaining == 1: elif attempts_remaining == 1:
subprompt = "Last attempt" subprompt = "Last attempt"
@ -1260,7 +1274,7 @@ async def confirm_reenter_pin(
ctx, ctx,
br_type, br_type,
"CHECK PIN", "CHECK PIN",
"Please re-enter to confirm.", description="Please re-enter PIN to confirm.",
verb="BEGIN", verb="BEGIN",
br_code=br_code, br_code=br_code,
) )
@ -1294,7 +1308,7 @@ async def confirm_pin_action(
ctx, ctx,
br_type, br_type,
title, title,
f"{description} {action}", description=f"{description} {action}",
br_code=br_code, br_code=br_code,
) )
@ -1317,21 +1331,22 @@ async def confirm_set_new_pin(
br_code=br_code, br_code=br_code,
) )
# TODO: this is a hack to put the next info on new screen in case of wipe code if "wipe_code" in br_type:
# TODO: there should be a possibility to give a list of strings and each of them title = "WIPE CODE INFO"
# would be rendered on a new screen verb = "HODL TO BEGIN" # Easter egg from @Hannsek
if len(information) == 1: else:
information.append("\n") title = "PIN INFORMATION"
information.append( information.append(
"Position of individual numbers will change between entries for more security." "Position of individual numbers will change between entries for enhanced security."
) )
verb = "HOLD TO BEGIN"
return await confirm_action( return await confirm_action(
ctx, ctx,
br_type, br_type,
title="", title=title,
description="\n".join(information), description="\n".join(information),
verb="HOLD TO BEGIN", verb=verb,
hold=True, hold=True,
br_code=br_code, br_code=br_code,
) )

View File

@ -79,7 +79,7 @@ async def continue_recovery(
if dry_run: if dry_run:
title = "SEED CHECK" title = "SEED CHECK"
else: else:
title = "RECOVERY MODE" title = "WALLET RECOVERY"
return await get_bool( return await get_bool(
ctx, ctx,

View File

@ -47,18 +47,18 @@ async def show_share_words(
) )
if share_index is None: if share_index is None:
check_title = "CHECK SEED" check_title = "CHECK BACKUP"
elif group_index is None: elif group_index is None:
check_title = f"CHECK SHARE #{share_index + 1}" check_title = f"CHECK SHARE #{share_index + 1}"
else: else:
check_title = f"CHECK G{group_index + 1} - SHARE {share_index + 1}" check_title = f"GROUP {group_index + 1} - SHARE {share_index + 1}"
if await get_bool( if await get_bool(
ctx, ctx,
"backup_words", "backup_words",
check_title, check_title,
None, None,
"Select correct words in correct positions.", "Select correct word for each position.",
verb_cancel="SEE AGAIN", verb_cancel="SEE AGAIN",
verb="BEGIN", verb="BEGIN",
br_code=ButtonRequestType.ResetDevice, br_code=ButtonRequestType.ResetDevice,
@ -84,7 +84,7 @@ async def select_word(
RustLayout( RustLayout(
trezorui2.select_word( # type: ignore [Argument missing for parameter "description"] trezorui2.select_word( # type: ignore [Argument missing for parameter "description"]
title=f"SELECT {format_ordinal(checked_index + 1).upper()} WORD", title=f"SELECT {format_ordinal(checked_index + 1).upper()} WORD",
words=(words[0].upper(), words[1].upper(), words[2].upper()), words=(words[0].lower(), words[1].lower(), words[2].lower()),
) )
) )
) )
@ -165,10 +165,11 @@ async def slip39_prompt_threshold(
await confirm_action( await confirm_action(
ctx, ctx,
"slip39_prompt_threshold", "slip39_prompt_threshold",
"Set threshold", "Threshold",
description="= number of shares needed for recovery", description="= number of shares needed for recovery",
verb="BEGIN", verb="BEGIN",
verb_cancel=None, verb_cancel=None,
uppercase_title=False,
) )
count = num_of_shares // 2 + 1 count = num_of_shares // 2 + 1
@ -199,9 +200,10 @@ async def slip39_prompt_number_of_shares(
ctx, ctx,
"slip39_shares", "slip39_shares",
"Number of shares", "Number of shares",
description="= total number of unique word lists used for wallet backup.", description="= total number of unique word lists used for wallet backup",
verb="BEGIN", verb="BEGIN",
verb_cancel=None, verb_cancel=None,
uppercase_title=False,
) )
count = 5 count = 5
@ -256,21 +258,26 @@ async def slip39_advanced_prompt_group_threshold(
async def show_warning_backup(ctx: GenericContext, slip39: bool) -> None: async def show_warning_backup(ctx: GenericContext, slip39: bool) -> None:
if slip39:
description = (
"Never make a digital copy of your shares and never upload them online."
)
else:
description = (
"Never make a digital copy of your seed and never upload it online."
)
await confirm_action( await confirm_action(
ctx, ctx,
"backup_warning", "backup_warning",
"Caution", "SHAMIR BACKUP" if slip39 else "WALLET BACKUP",
description=description, description="You can use your backup to recover your wallet at any time.",
verb="I understand", verb="HOLD TO BEGIN",
verb_cancel=None, hold=True,
br_code=ButtonRequestType.ResetDevice, br_code=ButtonRequestType.ResetDevice,
) )
async def show_success_backup(ctx: GenericContext) -> None:
from . import confirm_action
await confirm_action(
ctx,
"success_backup",
"BACKUP IS DONE",
description="Keep it safe!",
verb="CONTINUE",
verb_cancel=None,
br_code=ButtonRequestType.Success,
)

View File

@ -331,3 +331,10 @@ async def show_warning_backup(ctx: GenericContext, slip39: bool) -> None:
) )
if result != CONFIRMED: if result != CONFIRMED:
raise ActionCancelled raise ActionCancelled
async def show_success_backup(ctx: GenericContext) -> None:
from . import show_success
text = "Use your backup when you need to recover your wallet."
await show_success(ctx, "success_backup", text, "Your backup is done.")

View File

@ -992,6 +992,7 @@ static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
static void ensure_not_wipe_code(const uint8_t *pin, size_t pin_len) { static void ensure_not_wipe_code(const uint8_t *pin, size_t pin_len) {
if (sectrue != is_not_wipe_code(pin, pin_len)) { if (sectrue != is_not_wipe_code(pin, pin_len)) {
storage_wipe(); storage_wipe();
// TODO: need to account for smaller model R - smaller font and different copy
error_shutdown("You have entered the", "wipe code. All private", error_shutdown("You have entered the", "wipe code. All private",
"data has been erased.", NULL); "data has been erased.", NULL);
} }

View File

@ -35,7 +35,7 @@ def select_number_of_words(
layout = debug.read_layout() layout = debug.read_layout()
# select number of words # select number of words
assert "Select number of words" in layout.get_content() assert "select the number of words" in layout.get_content()
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
if legacy_ui: if legacy_ui:
assert layout.text == "WordSelector" assert layout.text == "WordSelector"

View File

@ -131,7 +131,7 @@ def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandle
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >" assert layout.text == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
assert "Select number of words " in layout.get_content() assert "select the number of words " in layout.get_content()
# wait for autolock to trigger # wait for autolock to trigger
time.sleep(10.1) time.sleep(10.1)
@ -146,7 +146,7 @@ def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandle
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
# we are back at homescreen # we are back at homescreen
assert "Select number of words" in layout.get_content() assert "select the number of words" in layout.get_content()
@pytest.mark.setup_client(pin=PIN4) @pytest.mark.setup_client(pin=PIN4)

View File

@ -58,7 +58,7 @@ def do_recover_core(client: Client, mnemonic: List[str], **kwargs: Any):
client.debug.click(buttons.OK) client.debug.click(buttons.OK)
yield yield
assert "Select number of words" in layout().get_content() assert "select the number of words" in layout().get_content()
client.debug.click(buttons.OK) client.debug.click(buttons.OK)
yield yield
@ -70,7 +70,7 @@ def do_recover_core(client: Client, mnemonic: List[str], **kwargs: Any):
client.debug.click(buttons.grid34(index % 3, index // 3)) client.debug.click(buttons.grid34(index % 3, index // 3))
yield yield
assert "Enter recovery seed" in layout().get_content() assert "enter your recovery seed" in layout().get_content()
client.debug.click(buttons.OK) client.debug.click(buttons.OK)
yield yield
@ -97,7 +97,7 @@ def do_recover_r(client: Client, mnemonic: List[str], **kwargs: Any):
client.debug.press_right() client.debug.press_right()
yield yield
assert "Select number of words" in layout().text assert "select the number of words" in layout().text
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -110,7 +110,7 @@ def do_recover_r(client: Client, mnemonic: List[str], **kwargs: Any):
client.debug.input(str(len(mnemonic))) client.debug.input(str(len(mnemonic)))
yield yield
assert "Enter recovery seed" in layout().text assert "enter your recovery seed" in layout().text
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -173,7 +173,7 @@ def test_invalid_seed_core(client: Client):
client.debug.click(buttons.OK) client.debug.click(buttons.OK)
yield yield
assert "Select number of words" in layout().get_content() assert "select the number of words" in layout().get_content()
client.debug.click(buttons.OK) client.debug.click(buttons.OK)
yield yield
@ -182,7 +182,7 @@ def test_invalid_seed_core(client: Client):
client.debug.click(buttons.grid34(0, 2)) client.debug.click(buttons.grid34(0, 2))
yield yield
assert "Enter recovery seed" in layout().get_content() assert "enter your recovery seed" in layout().get_content()
client.debug.click(buttons.OK) client.debug.click(buttons.OK)
yield yield
@ -197,7 +197,7 @@ def test_invalid_seed_core(client: Client):
yield yield
# retry screen # retry screen
assert "Select number of words" in layout().get_content() assert "select the number of words" in layout().get_content()
client.debug.click(buttons.CANCEL) client.debug.click(buttons.CANCEL)
yield yield
@ -210,7 +210,7 @@ def test_invalid_seed_core(client: Client):
client.debug.press_right() client.debug.press_right()
yield yield
assert "Select number of words" in layout().text assert "select the number of words" in layout().text
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -220,7 +220,7 @@ def test_invalid_seed_core(client: Client):
client.debug.press_middle() client.debug.press_middle()
yield yield
assert "Enter recovery seed" in layout().text assert "enter your recovery seed" in layout().text
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -239,7 +239,7 @@ def test_invalid_seed_core(client: Client):
yield yield
# retry screen # retry screen
assert "Select number of words" in layout().text assert "select the number of words" in layout().text
client.debug.press_left() client.debug.press_left()
yield yield

View File

@ -43,7 +43,7 @@ def test_tt_pin_passphrase(client: Client):
client.debug.input("654") client.debug.input("654")
yield yield
assert "Select number of words" in layout().get_content() assert "select the number of words" in layout().get_content()
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -51,7 +51,7 @@ def test_tt_pin_passphrase(client: Client):
client.debug.input(str(len(mnemonic))) client.debug.input(str(len(mnemonic)))
yield yield
assert "Enter recovery seed" in layout().get_content() assert "enter your recovery seed" in layout().get_content()
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -75,7 +75,7 @@ def test_tt_pin_passphrase(client: Client):
client.debug.input("654") client.debug.input("654")
yield yield
assert "re-enter to confirm" in layout().text assert "re-enter PIN to confirm" in layout().text
client.debug.press_right() client.debug.press_right()
yield yield
@ -83,7 +83,7 @@ def test_tt_pin_passphrase(client: Client):
client.debug.input("654") client.debug.input("654")
yield yield
assert "Select number of words" in layout().text assert "select the number of words" in layout().text
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -92,7 +92,7 @@ def test_tt_pin_passphrase(client: Client):
client.debug.input(str(len(mnemonic))) client.debug.input(str(len(mnemonic)))
yield yield
assert "Enter recovery seed" in layout().text assert "enter your recovery seed" in layout().text
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -105,7 +105,7 @@ def test_tt_pin_passphrase(client: Client):
client.debug.input(word) client.debug.input(word)
yield yield
assert "You have successfully recovered your wallet." in layout().text assert "You have finished recovering your wallet." in layout().text
client.debug.press_yes() client.debug.press_yes()
with client: with client:
@ -141,7 +141,7 @@ def test_tt_nopin_nopassphrase(client: Client):
client.debug.press_yes() client.debug.press_yes()
yield yield
assert "Select number of words" in layout().get_content() assert "select the number of words" in layout().get_content()
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -149,7 +149,7 @@ def test_tt_nopin_nopassphrase(client: Client):
client.debug.input(str(len(mnemonic))) client.debug.input(str(len(mnemonic)))
yield yield
assert "Enter recovery seed" in layout().get_content() assert "enter your recovery seed" in layout().get_content()
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -169,7 +169,7 @@ def test_tt_nopin_nopassphrase(client: Client):
client.debug.press_yes() client.debug.press_yes()
yield yield
assert "Select number of words" in layout().text assert "select the number of words" in layout().text
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -178,7 +178,7 @@ def test_tt_nopin_nopassphrase(client: Client):
client.debug.input(str(len(mnemonic))) client.debug.input(str(len(mnemonic)))
yield yield
assert "Enter recovery seed" in layout().text assert "enter your recovery seed" in layout().text
client.debug.press_yes() client.debug.press_yes()
yield yield
@ -191,7 +191,7 @@ def test_tt_nopin_nopassphrase(client: Client):
client.debug.input(word) client.debug.input(word)
yield yield
assert "You have successfully recovered your wallet." in layout().text assert "You have finished recovering your wallet." in layout().text
client.debug.press_yes() client.debug.press_yes()
with client: with client:

View File

@ -53,7 +53,7 @@ def test_abort(emulator: Emulator):
assert layout.get_title() == "RECOVERY MODE" assert layout.get_title() == "RECOVERY MODE"
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert "Select number of words" in layout.text assert "select the number of words" in layout.text
device_handler.restart(emulator) device_handler.restart(emulator)
debug = device_handler.debuglink() debug = device_handler.debuglink()
@ -63,7 +63,7 @@ def test_abort(emulator: Emulator):
# no waiting for layout because layout doesn't change # no waiting for layout because layout doesn't change
layout = debug.read_layout() layout = debug.read_layout()
assert "Select number of words" in layout.text assert "select the number of words" in layout.text
layout = debug.click(buttons.CANCEL, wait=True) layout = debug.click(buttons.CANCEL, wait=True)
assert layout.get_title() == "ABORT RECOVERY" assert layout.get_title() == "ABORT RECOVERY"