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:
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 {
|
impl PromptBackup {
|
||||||
fn new_obj(_args: &[Obj], _kwargs: &Map) -> Result<Obj, error::Error> {
|
fn new_obj(_args: &[Obj], _kwargs: &Map) -> Result<Obj, error::Error> {
|
||||||
let title: TString = TR::backup__title_create_wallet_backup.into();
|
let title: TString = TR::backup__title_create_wallet_backup.into();
|
||||||
let par_array: [Paragraph<'static>; 1] = [Paragraph::new(
|
let text_intro: TString = TR::backup__it_should_be_backed_up.into();
|
||||||
&theme::TEXT_MAIN_GREY_LIGHT,
|
|
||||||
// FIXME: should be "contains X words" but the mnemonic/shares are not yet generated at
|
let paragraphs = Paragraphs::new(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text_intro));
|
||||||
// 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 content_intro = Frame::left_aligned(title, SwipeContent::new(paragraphs))
|
let content_intro = Frame::left_aligned(title, SwipeContent::new(paragraphs))
|
||||||
.with_menu_button()
|
.with_menu_button()
|
||||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||||
|
@ -52,7 +52,6 @@ impl FlowState for RequestNumber {
|
|||||||
(RequestNumber::Menu, FlowMsg::Choice(0)) => {
|
(RequestNumber::Menu, FlowMsg::Choice(0)) => {
|
||||||
Decision::Goto(RequestNumber::Info, SwipeDirection::Left)
|
Decision::Goto(RequestNumber::Info, SwipeDirection::Left)
|
||||||
}
|
}
|
||||||
(RequestNumber::Menu, FlowMsg::Choice(1)) => Decision::Return(FlowMsg::Cancelled),
|
|
||||||
(RequestNumber::Menu, FlowMsg::Cancelled) => {
|
(RequestNumber::Menu, FlowMsg::Cancelled) => {
|
||||||
Decision::Goto(RequestNumber::Number, SwipeDirection::Right)
|
Decision::Goto(RequestNumber::Number, SwipeDirection::Right)
|
||||||
}
|
}
|
||||||
@ -119,9 +118,7 @@ impl RequestNumber {
|
|||||||
|
|
||||||
let content_menu = Frame::left_aligned(
|
let content_menu = Frame::left_aligned(
|
||||||
"".into(),
|
"".into(),
|
||||||
VerticalMenu::empty()
|
VerticalMenu::empty().item(theme::ICON_CHEVRON_RIGHT, TR::buttons__more_info.into()),
|
||||||
.item(theme::ICON_CHEVRON_RIGHT, TR::buttons__more_info.into())
|
|
||||||
.danger(theme::ICON_CANCEL, TR::backup__title_skip.into()),
|
|
||||||
)
|
)
|
||||||
.with_cancel_button()
|
.with_cancel_button()
|
||||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||||
|
@ -79,6 +79,10 @@ impl ShowShareWords {
|
|||||||
let subtitle: TString = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into()?;
|
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_obj: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
|
||||||
let share_words_vec: Vec<TString, 33> = util::iter_into_vec(share_words_obj)?;
|
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_info: Obj = kwargs.get(Qstr::MP_QSTR_text_info)?;
|
||||||
let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?;
|
let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?;
|
||||||
let nwords = share_words_vec.len();
|
let nwords = share_words_vec.len();
|
||||||
@ -98,7 +102,7 @@ impl ShowShareWords {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.with_subtitle(TR::words__instructions.into())
|
.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())
|
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||||
.map(|msg| matches!(msg, FrameMsg::Content(_)).then_some(FlowMsg::Confirmed))
|
.map(|msg| matches!(msg, FrameMsg::Content(_)).then_some(FlowMsg::Confirmed))
|
||||||
.one_button_request(ButtonRequestCode::ResetDevice.with_type("share_words"))
|
.one_button_request(ButtonRequestCode::ResetDevice.with_type("share_words"))
|
||||||
|
@ -1688,6 +1688,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// title: str,
|
/// title: str,
|
||||||
/// subtitle: str,
|
/// subtitle: str,
|
||||||
/// words: Iterable[str],
|
/// words: Iterable[str],
|
||||||
|
/// description: str,
|
||||||
/// text_info: Iterable[str],
|
/// text_info: Iterable[str],
|
||||||
/// text_confirm: str,
|
/// text_confirm: str,
|
||||||
/// ) -> LayoutObj[UiResult]:
|
/// ) -> LayoutObj[UiResult]:
|
||||||
|
@ -402,6 +402,7 @@ def flow_show_share_words(
|
|||||||
title: str,
|
title: str,
|
||||||
subtitle: str,
|
subtitle: str,
|
||||||
words: Iterable[str],
|
words: Iterable[str],
|
||||||
|
description: str,
|
||||||
text_info: Iterable[str],
|
text_info: Iterable[str],
|
||||||
text_confirm: str,
|
text_confirm: str,
|
||||||
) -> LayoutObj[UiResult]:
|
) -> LayoutObj[UiResult]:
|
||||||
|
@ -126,13 +126,21 @@ async def reset_device(msg: ResetDevice) -> Success:
|
|||||||
return Success(message="Initialized")
|
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(
|
async def _backup_slip39_single(
|
||||||
encrypted_master_secret: bytes, extendable: bool
|
encrypted_master_secret: bytes, extendable: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
mnemonics = _get_slip39_mnemonics(encrypted_master_secret, 1, ((1, 1),), extendable)
|
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
|
# 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(
|
async def _backup_slip39_basic(
|
||||||
@ -140,6 +148,8 @@ async def _backup_slip39_basic(
|
|||||||
) -> None:
|
) -> None:
|
||||||
group_threshold = 1
|
group_threshold = 1
|
||||||
|
|
||||||
|
await layout.show_backup_intro(single_share=False)
|
||||||
|
|
||||||
# get number of shares
|
# get number of shares
|
||||||
await layout.slip39_show_checklist(0, advanced=False)
|
await layout.slip39_show_checklist(0, advanced=False)
|
||||||
share_count = await layout.slip39_prompt_number_of_shares()
|
share_count = await layout.slip39_prompt_number_of_shares()
|
||||||
@ -165,6 +175,9 @@ async def _backup_slip39_basic(
|
|||||||
async def _backup_slip39_advanced(
|
async def _backup_slip39_advanced(
|
||||||
encrypted_master_secret: bytes, extendable: bool
|
encrypted_master_secret: bytes, extendable: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
|
await layout.show_backup_intro(single_share=False)
|
||||||
|
|
||||||
# get number of groups
|
# get number of groups
|
||||||
await layout.slip39_show_checklist(0, advanced=True)
|
await layout.slip39_show_checklist(0, advanced=True)
|
||||||
groups_count = await layout.slip39_advanced_prompt_number_of_groups()
|
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:
|
else:
|
||||||
await _backup_slip39_basic(mnemonic_secret, extendable)
|
await _backup_slip39_basic(mnemonic_secret, extendable)
|
||||||
else:
|
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 import TR
|
||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
from trezor.ui.layouts import show_success
|
|
||||||
from trezor.ui.layouts.reset import ( # noqa: F401
|
from trezor.ui.layouts.reset import ( # noqa: F401
|
||||||
show_share_words,
|
show_share_words,
|
||||||
slip39_advanced_prompt_group_threshold,
|
slip39_advanced_prompt_group_threshold,
|
||||||
@ -70,8 +69,8 @@ async def _share_words_confirmed(
|
|||||||
Return true if the words are confirmed successfully.
|
Return true if the words are confirmed successfully.
|
||||||
"""
|
"""
|
||||||
from trezor.ui.layouts.reset import (
|
from trezor.ui.layouts.reset import (
|
||||||
show_share_confirmation_success,
|
|
||||||
show_share_confirmation_failure,
|
show_share_confirmation_failure,
|
||||||
|
show_share_confirmation_success,
|
||||||
)
|
)
|
||||||
|
|
||||||
if await _do_confirm_share_words(share_index, share_words, group_index):
|
if await _do_confirm_share_words(share_index, share_words, group_index):
|
||||||
@ -108,6 +107,14 @@ async def _do_confirm_share_words(
|
|||||||
return True
|
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:
|
async def show_backup_warning() -> None:
|
||||||
from trezor.ui.layouts.reset import show_warning_backup
|
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
|
# warn user about mnemonic safety
|
||||||
await show_backup_warning()
|
await show_backup_warning()
|
||||||
|
|
||||||
words = mnemonic.split()
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# display paginated mnemonic on the screen
|
# display paginated mnemonic on the screen
|
||||||
await show_share_words(words)
|
await show_share_words(words)
|
||||||
|
@ -382,7 +382,6 @@ async def prompt_backup() -> bool:
|
|||||||
"backup_device",
|
"backup_device",
|
||||||
ButtonRequestType.ResetDevice,
|
ButtonRequestType.ResetDevice,
|
||||||
)
|
)
|
||||||
|
|
||||||
return result is CONFIRMED
|
return result is CONFIRMED
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,12 +32,14 @@ async def show_share_words(
|
|||||||
group_index + 1, share_index + 1
|
group_index + 1, share_index + 1
|
||||||
)
|
)
|
||||||
words_count = len(share_words)
|
words_count = len(share_words)
|
||||||
|
description = ""
|
||||||
text_info = [TR.reset__write_down_words_template.format(words_count)]
|
text_info = [TR.reset__write_down_words_template.format(words_count)]
|
||||||
if words_count == 20 and share_index is None:
|
if words_count == 20 and share_index is None:
|
||||||
# 1-of-1 SLIP39: inform the user about repeated words
|
# 1-of-1 SLIP39: inform the user about repeated words
|
||||||
text_info.append(TR.reset__words_may_repeat)
|
text_info.append(TR.reset__words_may_repeat)
|
||||||
if share_index == 0:
|
if share_index == 0:
|
||||||
# regular SLIP39, 1st share
|
# regular SLIP39, 1st share
|
||||||
|
description = TR.instructions__shares_start_with_1
|
||||||
text_info.append(TR.reset__repeat_for_all_shares)
|
text_info.append(TR.reset__repeat_for_all_shares)
|
||||||
text_confirm = TR.reset__words_written_down_template.format(words_count)
|
text_confirm = TR.reset__words_written_down_template.format(words_count)
|
||||||
|
|
||||||
@ -46,6 +48,7 @@ async def show_share_words(
|
|||||||
title=title,
|
title=title,
|
||||||
subtitle=subtitle,
|
subtitle=subtitle,
|
||||||
words=share_words,
|
words=share_words,
|
||||||
|
description=description,
|
||||||
text_info=text_info,
|
text_info=text_info,
|
||||||
text_confirm=text_confirm,
|
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:
|
async def show_warning_backup() -> None:
|
||||||
result = await interact(
|
result = await interact(
|
||||||
RustLayout(
|
RustLayout(
|
||||||
|
@ -6,7 +6,7 @@ from trezor.enums import ButtonRequestType
|
|||||||
from trezor.wire import ActionCancelled
|
from trezor.wire import ActionCancelled
|
||||||
|
|
||||||
from ..common import interact
|
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
|
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:
|
async def show_warning_backup() -> None:
|
||||||
await show_warning(
|
await show_warning(
|
||||||
"backup_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:
|
async def show_warning_backup() -> None:
|
||||||
result = await interact(
|
result = await interact(
|
||||||
RustLayout(
|
RustLayout(
|
||||||
|
@ -1310,7 +1310,7 @@ class InputFlowBip39ResetBackup(InputFlowBase):
|
|||||||
# 1. Confirm Reset x3
|
# 1. Confirm Reset x3
|
||||||
# 2. Backup your seed
|
# 2. Backup your seed
|
||||||
# 3. Confirm warning
|
# 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
|
# mnemonic phrases and rest
|
||||||
self.mnemonic = yield from get_mnemonic_and_confirm_success(self.debug)
|
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(" ")
|
words = first_share.split(" ")
|
||||||
|
|
||||||
# start entering first share
|
# 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()
|
debug.press_yes()
|
||||||
assert debug.wait_layout().main_component() == "MnemonicKeyboard"
|
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:
|
def enter_shares_with_restarts(debug: DebugLink) -> None:
|
||||||
shares = MNEMONIC_SLIP39_ADVANCED_20
|
shares = MNEMONIC_SLIP39_ADVANCED_20
|
||||||
layout = debug.read_layout()
|
layout = debug.read_layout()
|
||||||
expected_text = "the first 2-4 letters"
|
expected_text = "Enter each word of your wallet backup"
|
||||||
remaining = len(shares)
|
remaining = len(shares)
|
||||||
for share in shares:
|
for share in shares:
|
||||||
assert expected_text in layout.text_content()
|
assert expected_text in layout.text_content()
|
||||||
|
Loading…
Reference in New Issue
Block a user