mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 14:58:09 +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:
parent
25a9ef3cf5
commit
fd59c2ce88
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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"))
|
||||
|
@ -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]:
|
||||
|
@ -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]:
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -382,7 +382,6 @@ async def prompt_backup() -> bool:
|
||||
"backup_device",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
return result is CONFIRMED
|
||||
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user