1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-19 12:58:13 +00:00

feat(core/ui): T3T1 backup cannot be skipped

This commit disallows the user to exit backup flow once initiated.

It also ensurer the  intro screen is shown when initiating backup via
protobuf msg after it was previously skipped during wallet creation.

[no changelog]
This commit is contained in:
obrusvit 2024-06-04 01:24:09 +02:00 committed by matejcik
parent 25a9ef3cf5
commit fd59c2ce88
13 changed files with 99 additions and 24 deletions

View File

@ -97,13 +97,9 @@ pub extern "C" fn new_prompt_backup(n_args: usize, args: *const Obj, kwargs: *mu
impl PromptBackup {
fn new_obj(_args: &[Obj], _kwargs: &Map) -> Result<Obj, error::Error> {
let title: TString = TR::backup__title_create_wallet_backup.into();
let par_array: [Paragraph<'static>; 1] = [Paragraph::new(
&theme::TEXT_MAIN_GREY_LIGHT,
// FIXME: should be "contains X words" but the mnemonic/shares are not yet generated at
// this point. We might need to merge the PromptBackup and ShowShareWords flows
TString::from_str("Your wallet backup contains words in a specific order."),
)];
let paragraphs = Paragraphs::new(par_array);
let text_intro: TString = TR::backup__it_should_be_backed_up.into();
let paragraphs = Paragraphs::new(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text_intro));
let content_intro = Frame::left_aligned(title, SwipeContent::new(paragraphs))
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)

View File

@ -52,7 +52,6 @@ impl FlowState for RequestNumber {
(RequestNumber::Menu, FlowMsg::Choice(0)) => {
Decision::Goto(RequestNumber::Info, SwipeDirection::Left)
}
(RequestNumber::Menu, FlowMsg::Choice(1)) => Decision::Return(FlowMsg::Cancelled),
(RequestNumber::Menu, FlowMsg::Cancelled) => {
Decision::Goto(RequestNumber::Number, SwipeDirection::Right)
}
@ -119,9 +118,7 @@ impl RequestNumber {
let content_menu = Frame::left_aligned(
"".into(),
VerticalMenu::empty()
.item(theme::ICON_CHEVRON_RIGHT, TR::buttons__more_info.into())
.danger(theme::ICON_CANCEL, TR::backup__title_skip.into()),
VerticalMenu::empty().item(theme::ICON_CHEVRON_RIGHT, TR::buttons__more_info.into()),
)
.with_cancel_button()
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())

View File

@ -79,6 +79,10 @@ impl ShowShareWords {
let subtitle: TString = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into()?;
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
let share_words_vec: Vec<TString, 33> = util::iter_into_vec(share_words_obj)?;
let description: Option<TString> = kwargs
.get(Qstr::MP_QSTR_description)?
.try_into_option()?
.and_then(|desc: TString| if desc.is_empty() { None } else { Some(desc) });
let text_info: Obj = kwargs.get(Qstr::MP_QSTR_text_info)?;
let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?;
let nwords = share_words_vec.len();
@ -98,7 +102,7 @@ impl ShowShareWords {
),
)
.with_subtitle(TR::words__instructions.into())
.with_footer(TR::instructions__swipe_up.into(), None)
.with_footer(TR::instructions__swipe_up.into(), description)
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
.map(|msg| matches!(msg, FrameMsg::Content(_)).then_some(FlowMsg::Confirmed))
.one_button_request(ButtonRequestCode::ResetDevice.with_type("share_words"))

View File

@ -1688,6 +1688,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str,
/// subtitle: str,
/// words: Iterable[str],
/// description: str,
/// text_info: Iterable[str],
/// text_confirm: str,
/// ) -> LayoutObj[UiResult]:

View File

@ -402,6 +402,7 @@ def flow_show_share_words(
title: str,
subtitle: str,
words: Iterable[str],
description: str,
text_info: Iterable[str],
text_confirm: str,
) -> LayoutObj[UiResult]:

View File

@ -126,13 +126,21 @@ async def reset_device(msg: ResetDevice) -> Success:
return Success(message="Initialized")
async def _backup_bip39(mnemonic: str) -> None:
words = mnemonic.split()
await layout.show_backup_intro(single_share=True, num_of_words=len(words))
await layout.show_and_confirm_single_share(words)
async def _backup_slip39_single(
encrypted_master_secret: bytes, extendable: bool
) -> None:
mnemonics = _get_slip39_mnemonics(encrypted_master_secret, 1, ((1, 1),), extendable)
words = mnemonics[0][0].split()
# for a single 1-of-1 group, we use the same layouts as for BIP39
await layout.show_and_confirm_mnemonic(mnemonics[0][0])
await layout.show_backup_intro(single_share=True, num_of_words=len(words))
await layout.show_and_confirm_single_share(words)
async def _backup_slip39_basic(
@ -140,6 +148,8 @@ async def _backup_slip39_basic(
) -> None:
group_threshold = 1
await layout.show_backup_intro(single_share=False)
# get number of shares
await layout.slip39_show_checklist(0, advanced=False)
share_count = await layout.slip39_prompt_number_of_shares()
@ -165,6 +175,9 @@ async def _backup_slip39_basic(
async def _backup_slip39_advanced(
encrypted_master_secret: bytes, extendable: bool
) -> None:
await layout.show_backup_intro(single_share=False)
# get number of groups
await layout.slip39_show_checklist(0, advanced=True)
groups_count = await layout.slip39_advanced_prompt_number_of_groups()
@ -287,4 +300,4 @@ async def backup_seed(backup_type: BackupType, mnemonic_secret: bytes) -> None:
else:
await _backup_slip39_basic(mnemonic_secret, extendable)
else:
await layout.show_and_confirm_mnemonic(mnemonic_secret.decode())
await _backup_bip39(mnemonic_secret.decode())

View File

@ -3,7 +3,6 @@ from typing import Sequence
from trezor import TR
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import show_success
from trezor.ui.layouts.reset import ( # noqa: F401
show_share_words,
slip39_advanced_prompt_group_threshold,
@ -70,8 +69,8 @@ async def _share_words_confirmed(
Return true if the words are confirmed successfully.
"""
from trezor.ui.layouts.reset import (
show_share_confirmation_success,
show_share_confirmation_failure,
show_share_confirmation_success,
)
if await _do_confirm_share_words(share_index, share_words, group_index):
@ -108,6 +107,14 @@ async def _do_confirm_share_words(
return True
async def show_backup_intro(
single_share: bool, num_of_words: int | None = None
) -> None:
from trezor.ui.layouts.reset import show_intro_backup
await show_intro_backup(single_share, num_of_words)
async def show_backup_warning() -> None:
from trezor.ui.layouts.reset import show_warning_backup
@ -124,12 +131,10 @@ async def show_backup_success() -> None:
# ===
async def show_and_confirm_mnemonic(mnemonic: str) -> None:
async def show_and_confirm_single_share(words: Sequence[str]) -> None:
# warn user about mnemonic safety
await show_backup_warning()
words = mnemonic.split()
while True:
# display paginated mnemonic on the screen
await show_share_words(words)

View File

@ -382,7 +382,6 @@ async def prompt_backup() -> bool:
"backup_device",
ButtonRequestType.ResetDevice,
)
return result is CONFIRMED

View File

@ -32,12 +32,14 @@ async def show_share_words(
group_index + 1, share_index + 1
)
words_count = len(share_words)
description = ""
text_info = [TR.reset__write_down_words_template.format(words_count)]
if words_count == 20 and share_index is None:
# 1-of-1 SLIP39: inform the user about repeated words
text_info.append(TR.reset__words_may_repeat)
if share_index == 0:
# regular SLIP39, 1st share
description = TR.instructions__shares_start_with_1
text_info.append(TR.reset__repeat_for_all_shares)
text_confirm = TR.reset__words_written_down_template.format(words_count)
@ -46,6 +48,7 @@ async def show_share_words(
title=title,
subtitle=subtitle,
words=share_words,
description=description,
text_info=text_info,
text_confirm=text_confirm,
)
@ -304,6 +307,24 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
)
async def show_intro_backup(single_share: bool, num_of_words: int | None) -> None:
if single_share:
assert num_of_words is not None
description = TR.backup__info_single_share_backup.format(num_of_words)
else:
description = TR.backup__info_multi_share_backup
await interact(
RustLayout(
trezorui2.show_info(
title=TR.backup__title_create_wallet_backup, description=description
)
),
"backup_warning",
ButtonRequestType.ResetDevice,
)
async def show_warning_backup() -> None:
result = await interact(
RustLayout(

View File

@ -6,7 +6,7 @@ from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled
from ..common import interact
from . import RustLayout, confirm_action, show_warning, show_success
from . import RustLayout, confirm_action, show_success, show_warning
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
@ -254,6 +254,23 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
)
async def show_intro_backup(single_share: bool, num_of_words: int | None) -> None:
if single_share:
assert num_of_words is not None
description = TR.backup__info_single_share_backup.format(num_of_words)
else:
description = TR.backup__info_multi_share_backup
await confirm_action(
"backup_warning",
title=TR.backup__title_backup_wallet,
verb=TR.buttons__continue,
description=description,
verb_cancel=None,
br_code=ButtonRequestType.ResetDevice,
)
async def show_warning_backup() -> None:
await show_warning(
"backup_warning",

View File

@ -309,6 +309,27 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
)
async def show_intro_backup(single_share: bool, num_of_words: int | None) -> None:
if single_share:
assert num_of_words is not None
description = TR.backup__info_single_share_backup.format(num_of_words)
else:
description = TR.backup__info_multi_share_backup
await interact(
RustLayout(
trezorui2.show_info(
title="",
button=TR.buttons__continue,
description=description,
allow_cancel=False,
)
),
"backup_warning",
ButtonRequestType.ResetDevice,
)
async def show_warning_backup() -> None:
result = await interact(
RustLayout(

View File

@ -1310,7 +1310,7 @@ class InputFlowBip39ResetBackup(InputFlowBase):
# 1. Confirm Reset x3
# 2. Backup your seed
# 3. Confirm warning
yield from click_through(self.debug, screens=4, code=B.ResetDevice)
yield from click_through(self.debug, screens=5, code=B.ResetDevice)
# mnemonic phrases and rest
self.mnemonic = yield from get_mnemonic_and_confirm_success(self.debug)

View File

@ -138,7 +138,7 @@ def test_recovery_on_old_wallet(core_emulator: Emulator):
words = first_share.split(" ")
# start entering first share
assert "the first 2-4 letters" in debug.read_layout().text_content()
assert "Enter each word of your wallet backup" in debug.read_layout().text_content()
debug.press_yes()
assert debug.wait_layout().main_component() == "MnemonicKeyboard"
@ -176,7 +176,7 @@ def test_recovery_multiple_resets(core_emulator: Emulator):
def enter_shares_with_restarts(debug: DebugLink) -> None:
shares = MNEMONIC_SLIP39_ADVANCED_20
layout = debug.read_layout()
expected_text = "the first 2-4 letters"
expected_text = "Enter each word of your wallet backup"
remaining = len(shares)
for share in shares:
assert expected_text in layout.text_content()