mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-18 05:28:40 +00:00
refactor(core): convert apps.management.recovery_device to layouts
This commit is contained in:
parent
312876ab67
commit
5a0ea3f146
@ -4,9 +4,8 @@ import storage.recovery
|
|||||||
from trezor import config, ui, wire, workflow
|
from trezor import config, ui, wire, workflow
|
||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
from trezor.messages import Success
|
from trezor.messages import Success
|
||||||
from trezor.ui.components.tt.text import Text
|
from trezor.ui.layouts import confirm_action, confirm_reset_device
|
||||||
|
|
||||||
from apps.common.confirm import require_confirm
|
|
||||||
from apps.common.request_pin import (
|
from apps.common.request_pin import (
|
||||||
error_pin_invalid,
|
error_pin_invalid,
|
||||||
request_pin_and_sd_salt,
|
request_pin_and_sd_salt,
|
||||||
@ -90,15 +89,15 @@ def _validate(msg: RecoveryDevice) -> None:
|
|||||||
|
|
||||||
async def _continue_dialog(ctx: wire.Context, msg: RecoveryDevice) -> None:
|
async def _continue_dialog(ctx: wire.Context, msg: RecoveryDevice) -> None:
|
||||||
if not msg.dry_run:
|
if not msg.dry_run:
|
||||||
text = Text("Recovery mode", ui.ICON_RECOVERY, new_lines=False)
|
await confirm_reset_device(
|
||||||
text.bold("Do you really want to recover a wallet?")
|
ctx, "Do you really want to\nrecover a wallet?", recovery=True
|
||||||
text.br()
|
)
|
||||||
text.br_half()
|
|
||||||
text.normal("By continuing you agree")
|
|
||||||
text.br()
|
|
||||||
text.normal("to ")
|
|
||||||
text.bold("https://trezor.io/tos")
|
|
||||||
else:
|
else:
|
||||||
text = Text("Seed check", ui.ICON_RECOVERY, new_lines=False)
|
await confirm_action(
|
||||||
text.normal("Do you really want to check the recovery seed?")
|
ctx,
|
||||||
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)
|
"confirm_seedcheck",
|
||||||
|
title="Seed check",
|
||||||
|
description="Do you really want to check the recovery seed?",
|
||||||
|
icon=ui.ICON_RECOVERY,
|
||||||
|
br_code=ButtonRequestType.ProtectCall,
|
||||||
|
)
|
||||||
|
@ -150,8 +150,7 @@ async def _finish_recovery(
|
|||||||
|
|
||||||
|
|
||||||
async def _request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
|
async def _request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
|
||||||
homepage = layout.RecoveryHomescreen("Select number of words")
|
await layout.homescreen_dialog(ctx, "Select", "Select number of words")
|
||||||
await layout.homescreen_dialog(ctx, homepage, "Select")
|
|
||||||
|
|
||||||
# ask for the number of words
|
# ask for the number of words
|
||||||
return await layout.request_word_count(ctx, dry_run)
|
return await layout.request_word_count(ctx, dry_run)
|
||||||
@ -187,15 +186,13 @@ async def _request_share_first_screen(
|
|||||||
if remaining:
|
if remaining:
|
||||||
await _request_share_next_screen(ctx)
|
await _request_share_next_screen(ctx)
|
||||||
else:
|
else:
|
||||||
content = layout.RecoveryHomescreen(
|
await layout.homescreen_dialog(
|
||||||
"Enter any share", "(%d words)" % word_count
|
ctx, "Enter share", "Enter any share", "(%d words)" % word_count
|
||||||
)
|
)
|
||||||
await layout.homescreen_dialog(ctx, content, "Enter share")
|
|
||||||
else: # BIP-39
|
else: # BIP-39
|
||||||
content = layout.RecoveryHomescreen(
|
await layout.homescreen_dialog(
|
||||||
"Enter recovery seed", "(%d words)" % word_count
|
ctx, "Enter seed", "Enter recovery seed", "(%d words)" % word_count
|
||||||
)
|
)
|
||||||
await layout.homescreen_dialog(ctx, content, "Enter seed")
|
|
||||||
|
|
||||||
|
|
||||||
async def _request_share_next_screen(ctx: wire.GenericContext) -> None:
|
async def _request_share_next_screen(ctx: wire.GenericContext) -> None:
|
||||||
@ -206,14 +203,15 @@ async def _request_share_next_screen(ctx: wire.GenericContext) -> None:
|
|||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
|
|
||||||
if group_count > 1:
|
if group_count > 1:
|
||||||
content = layout.RecoveryHomescreen("More shares needed")
|
|
||||||
await layout.homescreen_dialog(
|
await layout.homescreen_dialog(
|
||||||
ctx, content, "Enter", _show_remaining_groups_and_shares
|
ctx,
|
||||||
|
"Enter",
|
||||||
|
"More shares needed",
|
||||||
|
info_func=_show_remaining_groups_and_shares,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text = strings.format_plural("{count} more {plural}", remaining[0], "share")
|
text = strings.format_plural("{count} more {plural}", remaining[0], "share")
|
||||||
content = layout.RecoveryHomescreen(text, "needed to enter")
|
await layout.homescreen_dialog(ctx, "Enter share", text, "needed to enter")
|
||||||
await layout.homescreen_dialog(ctx, content, "Enter share")
|
|
||||||
|
|
||||||
|
|
||||||
async def _show_remaining_groups_and_shares(ctx: wire.GenericContext) -> None:
|
async def _show_remaining_groups_and_shares(ctx: wire.GenericContext) -> None:
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
import storage.recovery
|
import storage.recovery
|
||||||
from trezor import strings, ui, wire
|
from trezor import ui, wire
|
||||||
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
|
||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
from trezor.ui.components.tt.scroll import Paginated
|
|
||||||
from trezor.ui.components.tt.text import Text
|
|
||||||
from trezor.ui.components.tt.word_select import WordSelector
|
|
||||||
from trezor.ui.layouts import confirm_action, show_success, show_warning
|
from trezor.ui.layouts import confirm_action, show_success, show_warning
|
||||||
from trezor.ui.layouts.common import button_request
|
from trezor.ui.layouts.common import button_request
|
||||||
|
from trezor.ui.layouts.tt.recovery import ( # noqa: F401
|
||||||
from apps.common.confirm import confirm, info_confirm, require_confirm
|
continue_recovery,
|
||||||
|
request_word,
|
||||||
|
request_word_count,
|
||||||
|
show_group_share_success,
|
||||||
|
show_remaining_shares,
|
||||||
|
)
|
||||||
|
|
||||||
from .. import backup_types
|
from .. import backup_types
|
||||||
from . import word_validity
|
from . import word_validity
|
||||||
from .keyboard_bip39 import Bip39Keyboard
|
|
||||||
from .keyboard_slip39 import Slip39Keyboard
|
|
||||||
from .recover import RecoveryAborted
|
from .recover import RecoveryAborted
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Callable, Iterable
|
from typing import Callable
|
||||||
from trezor.enums import BackupType
|
from trezor.enums import BackupType
|
||||||
|
|
||||||
|
|
||||||
@ -44,37 +43,16 @@ async def confirm_abort(ctx: wire.GenericContext, dry_run: bool = False) -> None
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
|
|
||||||
await button_request(ctx, code=ButtonRequestType.MnemonicWordCount)
|
|
||||||
|
|
||||||
if dry_run:
|
|
||||||
text = Text("Seed check", ui.ICON_RECOVERY)
|
|
||||||
else:
|
|
||||||
text = Text("Recovery mode", ui.ICON_RECOVERY)
|
|
||||||
text.normal("Number of words?")
|
|
||||||
|
|
||||||
count = await ctx.wait(WordSelector(text))
|
|
||||||
# WordSelector can return int, or string if the value came from debuglink
|
|
||||||
# ctx.wait has a return type Any
|
|
||||||
# Hence, it is easier to convert the returned value to int explicitly
|
|
||||||
return int(count)
|
|
||||||
|
|
||||||
|
|
||||||
async def request_mnemonic(
|
async def request_mnemonic(
|
||||||
ctx: wire.GenericContext, word_count: int, backup_type: BackupType | None
|
ctx: wire.GenericContext, word_count: int, backup_type: BackupType | None
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
await button_request(ctx, code=ButtonRequestType.MnemonicInput)
|
await button_request(ctx, "mnemonic", code=ButtonRequestType.MnemonicInput)
|
||||||
|
|
||||||
words: list[str] = []
|
words: list[str] = []
|
||||||
for i in range(word_count):
|
for i in range(word_count):
|
||||||
if backup_types.is_slip39_word_count(word_count):
|
word = await request_word(
|
||||||
keyboard: Slip39Keyboard | Bip39Keyboard = Slip39Keyboard(
|
ctx, i, word_count, is_slip39=backup_types.is_slip39_word_count(word_count)
|
||||||
"Type word %s of %s:" % (i + 1, word_count)
|
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
keyboard = Bip39Keyboard("Type word %s of %s:" % (i + 1, word_count))
|
|
||||||
|
|
||||||
word = await ctx.wait(keyboard)
|
|
||||||
words.append(word)
|
words.append(word)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -92,52 +70,6 @@ async def request_mnemonic(
|
|||||||
return " ".join(words)
|
return " ".join(words)
|
||||||
|
|
||||||
|
|
||||||
async def show_remaining_shares(
|
|
||||||
ctx: wire.GenericContext,
|
|
||||||
groups: Iterable[tuple[int, tuple[str, ...]]], # remaining + list 3 words
|
|
||||||
shares_remaining: list[int],
|
|
||||||
group_threshold: int,
|
|
||||||
) -> None:
|
|
||||||
pages: list[ui.Component] = []
|
|
||||||
for remaining, group in groups:
|
|
||||||
if 0 < remaining < MAX_SHARE_COUNT:
|
|
||||||
text = Text("Remaining Shares")
|
|
||||||
text.bold(
|
|
||||||
strings.format_plural(
|
|
||||||
"{count} more {plural} starting", remaining, "share"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for word in group:
|
|
||||||
text.normal(word)
|
|
||||||
pages.append(text)
|
|
||||||
elif (
|
|
||||||
remaining == MAX_SHARE_COUNT and shares_remaining.count(0) < group_threshold
|
|
||||||
):
|
|
||||||
text = Text("Remaining Shares")
|
|
||||||
groups_remaining = group_threshold - shares_remaining.count(0)
|
|
||||||
text.bold(
|
|
||||||
strings.format_plural(
|
|
||||||
"{count} more {plural} starting", groups_remaining, "group"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for word in group:
|
|
||||||
text.normal(word)
|
|
||||||
pages.append(text)
|
|
||||||
await confirm(ctx, Paginated(pages), cancel=None)
|
|
||||||
|
|
||||||
|
|
||||||
async def show_group_share_success(
|
|
||||||
ctx: wire.GenericContext, share_index: int, group_index: int
|
|
||||||
) -> None:
|
|
||||||
text = Text("Success", ui.ICON_CONFIRM)
|
|
||||||
text.bold("You have entered")
|
|
||||||
text.bold("Share %s" % (share_index + 1))
|
|
||||||
text.normal("from")
|
|
||||||
text.bold("Group %s" % (group_index + 1))
|
|
||||||
|
|
||||||
await confirm(ctx, text, confirm="Continue", cancel=None)
|
|
||||||
|
|
||||||
|
|
||||||
async def show_dry_run_result(
|
async def show_dry_run_result(
|
||||||
ctx: wire.GenericContext, result: bool, is_slip39: bool
|
ctx: wire.GenericContext, result: bool, is_slip39: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -156,12 +88,14 @@ async def show_dry_run_result(
|
|||||||
|
|
||||||
|
|
||||||
async def show_dry_run_different_type(ctx: wire.GenericContext) -> None:
|
async def show_dry_run_different_type(ctx: wire.GenericContext) -> None:
|
||||||
text = Text("Dry run failure", ui.ICON_CANCEL)
|
await show_warning(
|
||||||
text.normal("Seed in the device was")
|
ctx,
|
||||||
text.normal("created using another")
|
"warning_dry_recovery",
|
||||||
text.normal("backup mechanism.")
|
header="Dry run failure",
|
||||||
await require_confirm(
|
content="Seed in the device was\ncreated using another\nbackup mechanism.",
|
||||||
ctx, text, ButtonRequestType.ProtectCall, cancel=None, confirm="Continue"
|
icon=ui.ICON_CANCEL,
|
||||||
|
icon_color=ui.ORANGE_ICON,
|
||||||
|
br_code=ButtonRequestType.ProtectCall,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -204,72 +138,15 @@ async def show_group_threshold_reached(ctx: wire.GenericContext) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RecoveryHomescreen(ui.Component):
|
|
||||||
def __init__(self, text: str, subtext: str | None = None):
|
|
||||||
super().__init__()
|
|
||||||
self.text = text
|
|
||||||
self.subtext = subtext
|
|
||||||
self.dry_run = storage.recovery.is_dry_run()
|
|
||||||
|
|
||||||
def on_render(self) -> None:
|
|
||||||
if not self.repaint:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.dry_run:
|
|
||||||
heading = "SEED CHECK"
|
|
||||||
else:
|
|
||||||
heading = "RECOVERY MODE"
|
|
||||||
ui.header_warning(heading, clear=False)
|
|
||||||
|
|
||||||
if not self.subtext:
|
|
||||||
ui.display.text_center(ui.WIDTH // 2, 80, self.text, ui.BOLD, ui.FG, ui.BG)
|
|
||||||
else:
|
|
||||||
ui.display.text_center(ui.WIDTH // 2, 65, self.text, ui.BOLD, ui.FG, ui.BG)
|
|
||||||
ui.display.text_center(
|
|
||||||
ui.WIDTH // 2, 92, self.subtext, ui.NORMAL, ui.FG, ui.BG
|
|
||||||
)
|
|
||||||
|
|
||||||
ui.display.text_center(
|
|
||||||
ui.WIDTH // 2, 130, "It is safe to eject Trezor", ui.NORMAL, ui.GREY, ui.BG
|
|
||||||
)
|
|
||||||
ui.display.text_center(
|
|
||||||
ui.WIDTH // 2, 155, "and continue later", ui.NORMAL, ui.GREY, ui.BG
|
|
||||||
)
|
|
||||||
|
|
||||||
self.repaint = False
|
|
||||||
|
|
||||||
if __debug__:
|
|
||||||
|
|
||||||
def read_content(self) -> list[str]:
|
|
||||||
return [self.__class__.__name__, self.text, self.subtext or ""]
|
|
||||||
|
|
||||||
|
|
||||||
async def homescreen_dialog(
|
async def homescreen_dialog(
|
||||||
ctx: wire.GenericContext,
|
ctx: wire.GenericContext,
|
||||||
homepage: RecoveryHomescreen,
|
|
||||||
button_label: str,
|
button_label: str,
|
||||||
|
text: str,
|
||||||
|
subtext: str | None = None,
|
||||||
info_func: Callable | None = None,
|
info_func: Callable | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
while True:
|
while True:
|
||||||
if info_func:
|
if await continue_recovery(ctx, button_label, text, subtext, info_func):
|
||||||
continue_recovery = await info_confirm(
|
|
||||||
ctx,
|
|
||||||
homepage,
|
|
||||||
code=ButtonRequestType.RecoveryHomepage,
|
|
||||||
confirm=button_label,
|
|
||||||
info_func=info_func,
|
|
||||||
info="Info",
|
|
||||||
cancel="Abort",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
continue_recovery = await confirm(
|
|
||||||
ctx,
|
|
||||||
homepage,
|
|
||||||
code=ButtonRequestType.RecoveryHomepage,
|
|
||||||
confirm=button_label,
|
|
||||||
major_confirm=True,
|
|
||||||
)
|
|
||||||
if continue_recovery:
|
|
||||||
# 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
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from trezor import loop, ui, wire
|
from trezor import loop, ui, wire
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Any, Awaitable
|
from typing import Callable, Any, Awaitable
|
||||||
|
|
||||||
CONFIRMED = object()
|
CONFIRMED = object()
|
||||||
CANCELLED = object()
|
CANCELLED = object()
|
||||||
@ -18,6 +18,20 @@ async def raise_if_cancelled(a: Awaitable, exc: Any = wire.ActionCancelled) -> N
|
|||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
|
async def is_confirmed_info(
|
||||||
|
ctx: wire.GenericContext,
|
||||||
|
dialog: ui.Layout,
|
||||||
|
info_func: Callable,
|
||||||
|
) -> bool:
|
||||||
|
while True:
|
||||||
|
result = await ctx.wait(dialog)
|
||||||
|
|
||||||
|
if result is INFO:
|
||||||
|
await info_func(ctx)
|
||||||
|
else:
|
||||||
|
return is_confirmed(result)
|
||||||
|
|
||||||
|
|
||||||
class ConfirmBase(ui.Layout):
|
class ConfirmBase(ui.Layout):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
from trezor import io, loop, res, ui, workflow
|
from trezor import io, loop, res, ui, workflow
|
||||||
from trezor.crypto import bip39
|
from trezor.crypto import bip39
|
||||||
from trezor.ui import display
|
from trezor.ui import display
|
||||||
from trezor.ui.components.tt.button import (
|
|
||||||
Button,
|
from .button import Button, ButtonClear, ButtonMono, ButtonMonoConfirm
|
||||||
ButtonClear,
|
|
||||||
ButtonMono,
|
|
||||||
ButtonMonoConfirm,
|
|
||||||
)
|
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from trezor.ui.components.tt.button import ButtonContent, ButtonStyleStateType
|
from .button import ButtonContent, ButtonStyleStateType
|
||||||
|
|
||||||
|
|
||||||
def compute_mask(text: str) -> int:
|
def compute_mask(text: str) -> int:
|
@ -1,15 +1,11 @@
|
|||||||
from trezor import io, loop, res, ui, workflow
|
from trezor import io, loop, res, ui, workflow
|
||||||
from trezor.crypto import slip39
|
from trezor.crypto import slip39
|
||||||
from trezor.ui import display
|
from trezor.ui import display
|
||||||
from trezor.ui.components.tt.button import (
|
|
||||||
Button,
|
from .button import Button, ButtonClear, ButtonMono, ButtonMonoConfirm
|
||||||
ButtonClear,
|
|
||||||
ButtonMono,
|
|
||||||
ButtonMonoConfirm,
|
|
||||||
)
|
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from trezor.ui.components.tt.button import ButtonContent, ButtonStyleStateType
|
from .button import ButtonContent, ButtonStyleStateType
|
||||||
|
|
||||||
|
|
||||||
class KeyButton(Button):
|
class KeyButton(Button):
|
42
core/src/trezor/ui/components/tt/recovery.py
Normal file
42
core/src/trezor/ui/components/tt/recovery.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import storage.recovery
|
||||||
|
from trezor import ui
|
||||||
|
|
||||||
|
|
||||||
|
class RecoveryHomescreen(ui.Component):
|
||||||
|
def __init__(self, text: str, subtext: str | None = None):
|
||||||
|
super().__init__()
|
||||||
|
self.text = text
|
||||||
|
self.subtext = subtext
|
||||||
|
self.dry_run = storage.recovery.is_dry_run()
|
||||||
|
|
||||||
|
def on_render(self) -> None:
|
||||||
|
if not self.repaint:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.dry_run:
|
||||||
|
heading = "SEED CHECK"
|
||||||
|
else:
|
||||||
|
heading = "RECOVERY MODE"
|
||||||
|
ui.header_warning(heading, clear=False)
|
||||||
|
|
||||||
|
if not self.subtext:
|
||||||
|
ui.display.text_center(ui.WIDTH // 2, 80, self.text, ui.BOLD, ui.FG, ui.BG)
|
||||||
|
else:
|
||||||
|
ui.display.text_center(ui.WIDTH // 2, 65, self.text, ui.BOLD, ui.FG, ui.BG)
|
||||||
|
ui.display.text_center(
|
||||||
|
ui.WIDTH // 2, 92, self.subtext, ui.NORMAL, ui.FG, ui.BG
|
||||||
|
)
|
||||||
|
|
||||||
|
ui.display.text_center(
|
||||||
|
ui.WIDTH // 2, 130, "It is safe to eject Trezor", ui.NORMAL, ui.GREY, ui.BG
|
||||||
|
)
|
||||||
|
ui.display.text_center(
|
||||||
|
ui.WIDTH // 2, 155, "and continue later", ui.NORMAL, ui.GREY, ui.BG
|
||||||
|
)
|
||||||
|
|
||||||
|
self.repaint = False
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
|
||||||
|
def read_content(self) -> list[str]:
|
||||||
|
return [self.__class__.__name__, self.text, self.subtext or ""]
|
@ -149,7 +149,12 @@ async def confirm_action(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> None:
|
async def confirm_reset_device(
|
||||||
|
ctx: wire.GenericContext, prompt: str, recovery: bool = False
|
||||||
|
) -> None:
|
||||||
|
if recovery:
|
||||||
|
text = Text("Recovery mode", ui.ICON_RECOVERY, new_lines=False)
|
||||||
|
else:
|
||||||
text = Text("Create new wallet", ui.ICON_RESET, new_lines=False)
|
text = Text("Create new wallet", ui.ICON_RESET, new_lines=False)
|
||||||
text.bold(prompt)
|
text.bold(prompt)
|
||||||
text.br()
|
text.br()
|
||||||
@ -161,9 +166,11 @@ async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> None:
|
|||||||
await raise_if_cancelled(
|
await raise_if_cancelled(
|
||||||
interact(
|
interact(
|
||||||
ctx,
|
ctx,
|
||||||
Confirm(text, major_confirm=True),
|
Confirm(text, major_confirm=not recovery),
|
||||||
"setup_device",
|
"recover_device" if recovery else "setup_device",
|
||||||
ButtonRequestType.ResetDevice,
|
ButtonRequestType.ProtectCall
|
||||||
|
if recovery
|
||||||
|
else ButtonRequestType.ResetDevice,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -442,6 +449,8 @@ def show_warning(
|
|||||||
subheader: str | None = None,
|
subheader: str | None = None,
|
||||||
button: str = "Try again",
|
button: str = "Try again",
|
||||||
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
||||||
|
icon: str = ui.ICON_WRONG,
|
||||||
|
icon_color: int = ui.RED,
|
||||||
) -> Awaitable[None]:
|
) -> Awaitable[None]:
|
||||||
return _show_modal(
|
return _show_modal(
|
||||||
ctx,
|
ctx,
|
||||||
@ -452,8 +461,8 @@ def show_warning(
|
|||||||
content=content,
|
content=content,
|
||||||
button_confirm=button,
|
button_confirm=button,
|
||||||
button_cancel=None,
|
button_cancel=None,
|
||||||
icon=ui.ICON_WRONG,
|
icon=icon,
|
||||||
icon_color=ui.RED,
|
icon_color=icon_color,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
135
core/src/trezor/ui/layouts/tt/recovery.py
Normal file
135
core/src/trezor/ui/layouts/tt/recovery.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
from trezor import strings, ui, wire
|
||||||
|
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
||||||
|
from trezor.enums import ButtonRequestType
|
||||||
|
|
||||||
|
from ...components.common.confirm import (
|
||||||
|
is_confirmed,
|
||||||
|
is_confirmed_info,
|
||||||
|
raise_if_cancelled,
|
||||||
|
)
|
||||||
|
from ...components.tt.confirm import Confirm, InfoConfirm
|
||||||
|
from ...components.tt.keyboard_bip39 import Bip39Keyboard
|
||||||
|
from ...components.tt.keyboard_slip39 import Slip39Keyboard
|
||||||
|
from ...components.tt.recovery import RecoveryHomescreen
|
||||||
|
from ...components.tt.scroll import Paginated
|
||||||
|
from ...components.tt.text import Text
|
||||||
|
from ...components.tt.word_select import WordSelector
|
||||||
|
from ..common import button_request, interact
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from typing import Callable, Iterable
|
||||||
|
|
||||||
|
|
||||||
|
async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
|
||||||
|
await button_request(ctx, "word_count", code=ButtonRequestType.MnemonicWordCount)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
text = Text("Seed check", ui.ICON_RECOVERY)
|
||||||
|
else:
|
||||||
|
text = Text("Recovery mode", ui.ICON_RECOVERY)
|
||||||
|
text.normal("Number of words?")
|
||||||
|
|
||||||
|
count = await ctx.wait(WordSelector(text))
|
||||||
|
# WordSelector can return int, or string if the value came from debuglink
|
||||||
|
# ctx.wait has a return type Any
|
||||||
|
# Hence, it is easier to convert the returned value to int explicitly
|
||||||
|
return int(count)
|
||||||
|
|
||||||
|
|
||||||
|
async def request_word(
|
||||||
|
ctx: wire.GenericContext, word_index: int, word_count: int, is_slip39: bool
|
||||||
|
) -> str:
|
||||||
|
if is_slip39:
|
||||||
|
keyboard: Slip39Keyboard | Bip39Keyboard = Slip39Keyboard(
|
||||||
|
"Type word %s of %s:" % (word_index + 1, word_count)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
keyboard = Bip39Keyboard("Type word %s of %s:" % (word_index + 1, word_count))
|
||||||
|
|
||||||
|
word: str = await ctx.wait(keyboard)
|
||||||
|
return word
|
||||||
|
|
||||||
|
|
||||||
|
async def show_remaining_shares(
|
||||||
|
ctx: wire.GenericContext,
|
||||||
|
groups: Iterable[tuple[int, tuple[str, ...]]], # remaining + list 3 words
|
||||||
|
shares_remaining: list[int],
|
||||||
|
group_threshold: int,
|
||||||
|
) -> None:
|
||||||
|
pages: list[ui.Component] = []
|
||||||
|
for remaining, group in groups:
|
||||||
|
if 0 < remaining < MAX_SHARE_COUNT:
|
||||||
|
text = Text("Remaining Shares")
|
||||||
|
text.bold(
|
||||||
|
strings.format_plural(
|
||||||
|
"{count} more {plural} starting", remaining, "share"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for word in group:
|
||||||
|
text.normal(word)
|
||||||
|
pages.append(text)
|
||||||
|
elif (
|
||||||
|
remaining == MAX_SHARE_COUNT and shares_remaining.count(0) < group_threshold
|
||||||
|
):
|
||||||
|
text = Text("Remaining Shares")
|
||||||
|
groups_remaining = group_threshold - shares_remaining.count(0)
|
||||||
|
text.bold(
|
||||||
|
strings.format_plural(
|
||||||
|
"{count} more {plural} starting", groups_remaining, "group"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for word in group:
|
||||||
|
text.normal(word)
|
||||||
|
pages.append(text)
|
||||||
|
|
||||||
|
pages[-1] = Confirm(pages[-1], cancel=None)
|
||||||
|
await raise_if_cancelled(
|
||||||
|
interact(ctx, Paginated(pages), "show_shares", ButtonRequestType.Other)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def show_group_share_success(
|
||||||
|
ctx: wire.GenericContext, share_index: int, group_index: int
|
||||||
|
) -> None:
|
||||||
|
text = Text("Success", ui.ICON_CONFIRM)
|
||||||
|
text.bold("You have entered")
|
||||||
|
text.bold("Share %s" % (share_index + 1))
|
||||||
|
text.normal("from")
|
||||||
|
text.bold("Group %s" % (group_index + 1))
|
||||||
|
|
||||||
|
await raise_if_cancelled(
|
||||||
|
interact(
|
||||||
|
ctx,
|
||||||
|
Confirm(text, confirm="Continue", cancel=None),
|
||||||
|
"share_success",
|
||||||
|
ButtonRequestType.Other,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def continue_recovery(
|
||||||
|
ctx: wire.GenericContext,
|
||||||
|
button_label: str,
|
||||||
|
text: str,
|
||||||
|
subtext: str | None,
|
||||||
|
info_func: Callable | None,
|
||||||
|
) -> bool:
|
||||||
|
homepage = RecoveryHomescreen(text, subtext)
|
||||||
|
if info_func is not None:
|
||||||
|
content = InfoConfirm(
|
||||||
|
homepage,
|
||||||
|
confirm=button_label,
|
||||||
|
info="Info",
|
||||||
|
cancel="Abort",
|
||||||
|
)
|
||||||
|
await button_request(ctx, "recovery", ButtonRequestType.RecoveryHomepage)
|
||||||
|
return await is_confirmed_info(ctx, content, info_func)
|
||||||
|
else:
|
||||||
|
return is_confirmed(
|
||||||
|
await interact(
|
||||||
|
ctx,
|
||||||
|
Confirm(homepage, confirm=button_label, major_confirm=True),
|
||||||
|
"recovery",
|
||||||
|
ButtonRequestType.RecoveryHomepage,
|
||||||
|
)
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user