parent
0641170751
commit
7a59e716bb
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,55 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor.enums import ButtonRequestType
|
||||
|
||||
import trezorui2
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor.wire import GenericContext
|
||||
|
||||
|
||||
async def confirm_fido(
|
||||
ctx: GenericContext | None,
|
||||
header: str,
|
||||
app_name: str,
|
||||
icon_name: str | None,
|
||||
accounts: list[str | None],
|
||||
) -> int:
|
||||
"""Webauthn confirmation for one or more credentials."""
|
||||
confirm = RustLayout(
|
||||
trezorui2.confirm_fido( # type: ignore [Arguments missing]
|
||||
app_name=app_name,
|
||||
accounts=accounts,
|
||||
)
|
||||
)
|
||||
|
||||
if ctx is None:
|
||||
result = await confirm
|
||||
else:
|
||||
result = await interact(ctx, confirm, "confirm_fido", ButtonRequestType.Other)
|
||||
|
||||
# The Rust side returns either an int or `CANCELLED`. We detect the int situation
|
||||
# and assume cancellation otherwise.
|
||||
if isinstance(result, int):
|
||||
return result
|
||||
|
||||
# Late import won't get executed on the happy path.
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
async def confirm_fido_reset() -> bool:
|
||||
confirm = RustLayout(
|
||||
trezorui2.confirm_action(
|
||||
title="FIDO2 RESET",
|
||||
description="Do you really want to erase all credentials?",
|
||||
action=None,
|
||||
verb_cancel="",
|
||||
verb="CONFIRM",
|
||||
)
|
||||
)
|
||||
return (await confirm) is trezorui2.CONFIRMED
|
@ -0,0 +1,134 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import storage.cache as storage_cache
|
||||
from trezor import ui
|
||||
|
||||
import trezorui2
|
||||
|
||||
from . import RustLayout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor import loop
|
||||
from typing import Any, Tuple
|
||||
|
||||
|
||||
class HomescreenBase(RustLayout):
|
||||
RENDER_INDICATOR: object | None = None
|
||||
|
||||
def __init__(self, layout: Any) -> None:
|
||||
super().__init__(layout=layout)
|
||||
|
||||
def _paint(self) -> None:
|
||||
self.layout.paint()
|
||||
ui.refresh()
|
||||
|
||||
def _first_paint(self) -> None:
|
||||
if storage_cache.homescreen_shown is not self.RENDER_INDICATOR:
|
||||
super()._first_paint()
|
||||
storage_cache.homescreen_shown = self.RENDER_INDICATOR
|
||||
else:
|
||||
self._paint()
|
||||
|
||||
# In __debug__ mode, ignore {confirm,swipe,input}_signal.
|
||||
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
|
||||
return self.handle_timers(), self.handle_input_and_rendering()
|
||||
|
||||
|
||||
class Homescreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage_cache.HOMESCREEN_ON
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str | None,
|
||||
notification: str | None,
|
||||
notification_is_error: bool,
|
||||
hold_to_lock: bool,
|
||||
) -> None:
|
||||
level = 1
|
||||
if notification is not None:
|
||||
notification = notification.rstrip("!")
|
||||
if "EXPERIMENTAL" in notification:
|
||||
level = 2
|
||||
elif notification_is_error:
|
||||
level = 0
|
||||
|
||||
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
|
||||
super().__init__(
|
||||
layout=trezorui2.show_homescreen(
|
||||
label=label or "My Trezor",
|
||||
notification=notification,
|
||||
notification_level=level,
|
||||
hold=hold_to_lock,
|
||||
skip_first_paint=skip,
|
||||
),
|
||||
)
|
||||
|
||||
async def usb_checker_task(self) -> None:
|
||||
from trezor import io, loop
|
||||
|
||||
usbcheck = loop.wait(io.USB_CHECK)
|
||||
while True:
|
||||
is_connected = await usbcheck
|
||||
self.layout.usb_event(is_connected)
|
||||
self.layout.paint()
|
||||
ui.refresh()
|
||||
|
||||
def create_tasks(self) -> Tuple[loop.AwaitableTask, ...]:
|
||||
return super().create_tasks() + (self.usb_checker_task(),)
|
||||
|
||||
|
||||
class Lockscreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage_cache.LOCKSCREEN_ON
|
||||
BACKLIGHT_LEVEL = ui.BACKLIGHT_LOW
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str | None,
|
||||
bootscreen: bool = False,
|
||||
) -> None:
|
||||
self.bootscreen = bootscreen
|
||||
if bootscreen:
|
||||
self.BACKLIGHT_LEVEL = ui.BACKLIGHT_NORMAL
|
||||
|
||||
skip = (
|
||||
not bootscreen and storage_cache.homescreen_shown is self.RENDER_INDICATOR
|
||||
)
|
||||
super().__init__(
|
||||
layout=trezorui2.show_lockscreen(
|
||||
label=label or "My Trezor",
|
||||
bootscreen=bootscreen,
|
||||
skip_first_paint=skip,
|
||||
),
|
||||
)
|
||||
|
||||
async def __iter__(self) -> Any:
|
||||
result = await super().__iter__()
|
||||
if self.bootscreen:
|
||||
self.request_complete_repaint()
|
||||
return result
|
||||
|
||||
|
||||
class Busyscreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage_cache.BUSYSCREEN_ON
|
||||
|
||||
def __init__(self, delay_ms: int) -> None:
|
||||
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
|
||||
super().__init__(
|
||||
# TODO: remove show_busyscreen in favor of show_progress_coinjoin
|
||||
layout=trezorui2.show_busyscreen(
|
||||
title="PLEASE WAIT",
|
||||
description="Coinjoin in progress.\n\nDo not disconnect your Trezor.",
|
||||
time_ms=delay_ms,
|
||||
skip_first_paint=skip,
|
||||
)
|
||||
)
|
||||
|
||||
async def __iter__(self) -> Any:
|
||||
from apps.base import set_homescreen
|
||||
|
||||
# Handle timeout.
|
||||
result = await super().__iter__()
|
||||
assert result == trezorui2.CANCELLED
|
||||
storage_cache.delete(storage_cache.APP_COMMON_BUSY_DEADLINE_MS)
|
||||
set_homescreen()
|
||||
return result
|
@ -0,0 +1,74 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor import ui
|
||||
|
||||
import trezorui2
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from ..common import ProgressLayout
|
||||
|
||||
|
||||
class RustProgress:
|
||||
def __init__(
|
||||
self,
|
||||
layout: Any,
|
||||
):
|
||||
self.layout = layout
|
||||
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
|
||||
ui.display.clear()
|
||||
self.layout.attach_timer_fn(self.set_timer)
|
||||
self.layout.paint()
|
||||
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
|
||||
|
||||
def set_timer(self, token: int, deadline: int) -> None:
|
||||
raise RuntimeError # progress layouts should not set timers
|
||||
|
||||
def report(self, value: int, description: str | None = None):
|
||||
msg = self.layout.progress_event(value, description or "")
|
||||
assert msg is None
|
||||
self.layout.paint()
|
||||
ui.refresh()
|
||||
|
||||
|
||||
def progress(
|
||||
message: str = "PLEASE WAIT",
|
||||
description: str | None = None,
|
||||
indeterminate: bool = False,
|
||||
) -> ProgressLayout:
|
||||
return RustProgress(
|
||||
layout=trezorui2.show_progress(
|
||||
title=message.upper(),
|
||||
indeterminate=indeterminate,
|
||||
description=description or "",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def bitcoin_progress(message: str) -> ProgressLayout:
|
||||
return progress(message)
|
||||
|
||||
|
||||
def coinjoin_progress(message: str) -> ProgressLayout:
|
||||
# TODO: create show_progress_coinjoin for TR
|
||||
return progress(message, description="Coinjoin")
|
||||
# return RustProgress(
|
||||
# layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False)
|
||||
# )
|
||||
|
||||
|
||||
def pin_progress(message: str, description: str) -> ProgressLayout:
|
||||
return progress(message, description=description)
|
||||
|
||||
|
||||
def monero_keyimage_sync_progress() -> ProgressLayout:
|
||||
return progress("SYNCING")
|
||||
|
||||
|
||||
def monero_live_refresh_progress() -> ProgressLayout:
|
||||
return progress("REFRESHING", indeterminate=True)
|
||||
|
||||
|
||||
def monero_transaction_progress_inner() -> ProgressLayout:
|
||||
return progress("SIGNING TRANSACTION")
|
@ -0,0 +1,103 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor.enums import ButtonRequestType
|
||||
|
||||
import trezorui2
|
||||
|
||||
from ..common import button_request, interact
|
||||
from . import RustLayout, raise_if_not_confirmed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor import wire
|
||||
from typing import Iterable, Callable
|
||||
|
||||
|
||||
async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
|
||||
await button_request(ctx, "word_count", code=ButtonRequestType.MnemonicWordCount)
|
||||
count = await interact(
|
||||
ctx,
|
||||
RustLayout(trezorui2.select_word_count(dry_run=dry_run)),
|
||||
"word_count",
|
||||
ButtonRequestType.MnemonicWordCount,
|
||||
)
|
||||
# It can be returning a string (for example for __debug__ in tests)
|
||||
return int(count)
|
||||
|
||||
|
||||
async def request_word(
|
||||
ctx: wire.GenericContext, word_index: int, word_count: int, is_slip39: bool
|
||||
) -> str:
|
||||
prompt = f"WORD {word_index + 1} OF {word_count}"
|
||||
|
||||
if is_slip39:
|
||||
word_choice = RustLayout(trezorui2.request_slip39(prompt=prompt))
|
||||
else:
|
||||
word_choice = RustLayout(trezorui2.request_bip39(prompt=prompt))
|
||||
|
||||
word: str = await ctx.wait(word_choice)
|
||||
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:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
async def show_group_share_success(
|
||||
ctx: wire.GenericContext, share_index: int, group_index: int
|
||||
) -> None:
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
ctx,
|
||||
RustLayout(
|
||||
trezorui2.show_group_share_success(
|
||||
lines=[
|
||||
"You have entered",
|
||||
f"Share {share_index + 1}",
|
||||
"from",
|
||||
f"Group {group_index + 1}",
|
||||
],
|
||||
)
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def continue_recovery(
|
||||
ctx: wire.GenericContext,
|
||||
button_label: str,
|
||||
text: str,
|
||||
subtext: str | None,
|
||||
info_func: Callable | None,
|
||||
dry_run: bool,
|
||||
) -> bool:
|
||||
# TODO: implement info_func?
|
||||
# There is very limited space on the screen
|
||||
# (and having middle button would mean shortening the right button text)
|
||||
|
||||
description = text
|
||||
if subtext:
|
||||
description += f"\n\n{subtext}"
|
||||
|
||||
homepage = RustLayout(
|
||||
trezorui2.confirm_recovery(
|
||||
title="",
|
||||
description=description,
|
||||
button=button_label.upper(),
|
||||
info_button=False,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
)
|
||||
result = await interact(
|
||||
ctx,
|
||||
homepage,
|
||||
"recovery",
|
||||
ButtonRequestType.RecoveryHomepage,
|
||||
)
|
||||
return result is trezorui2.CONFIRMED
|
@ -0,0 +1,286 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
import trezorui2
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout, confirm_action
|
||||
|
||||
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor.wire import GenericContext
|
||||
from trezor.enums import BackupType
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
async def show_share_words(
|
||||
ctx: GenericContext,
|
||||
share_words: Sequence[str],
|
||||
share_index: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> None:
|
||||
from . import get_bool
|
||||
|
||||
if share_index is None:
|
||||
title = "RECOVERY SEED"
|
||||
elif group_index is None:
|
||||
title = f"SHARE #{share_index + 1}"
|
||||
else:
|
||||
title = f"G{group_index + 1} - SHARE {share_index + 1}"
|
||||
|
||||
# Showing words, asking for write down confirmation and preparing for check
|
||||
# until user accepts everything.
|
||||
while True:
|
||||
await interact(
|
||||
ctx,
|
||||
RustLayout(
|
||||
trezorui2.show_share_words( # type: ignore [Argument missing for parameter "pages"]
|
||||
title=title,
|
||||
share_words=share_words, # type: ignore [No parameter named "share_words"]
|
||||
)
|
||||
),
|
||||
"backup_words",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
if share_index is None:
|
||||
check_title = "CHECK BACKUP"
|
||||
elif group_index is None:
|
||||
check_title = f"CHECK SHARE #{share_index + 1}"
|
||||
else:
|
||||
check_title = f"GROUP {group_index + 1} - SHARE {share_index + 1}"
|
||||
|
||||
if await get_bool(
|
||||
ctx,
|
||||
"backup_words",
|
||||
check_title,
|
||||
None,
|
||||
"Select correct word for each position.",
|
||||
verb_cancel="SEE AGAIN",
|
||||
verb="BEGIN",
|
||||
br_code=ButtonRequestType.ResetDevice,
|
||||
):
|
||||
# All went well, we can break the loop.
|
||||
break
|
||||
|
||||
|
||||
async def select_word(
|
||||
ctx: GenericContext,
|
||||
words: Sequence[str],
|
||||
share_index: int | None,
|
||||
checked_index: int,
|
||||
count: int,
|
||||
group_index: int | None = None,
|
||||
) -> str:
|
||||
from trezor.strings import format_ordinal
|
||||
|
||||
# It may happen (with a very low probability)
|
||||
# that there will be less than three unique words to choose from.
|
||||
# In that case, duplicating the last word to make it three.
|
||||
words = list(words)
|
||||
while len(words) < 3:
|
||||
words.append(words[-1])
|
||||
|
||||
result = await ctx.wait(
|
||||
RustLayout(
|
||||
trezorui2.select_word(
|
||||
title="",
|
||||
description=f"SELECT {format_ordinal(checked_index + 1).upper()} WORD",
|
||||
words=(words[0].lower(), words[1].lower(), words[2].lower()),
|
||||
)
|
||||
)
|
||||
)
|
||||
if __debug__ and isinstance(result, str):
|
||||
return result
|
||||
assert isinstance(result, int) and 0 <= result <= 2
|
||||
return words[result]
|
||||
|
||||
|
||||
async def slip39_show_checklist(
|
||||
ctx: GenericContext, step: int, backup_type: BackupType
|
||||
) -> None:
|
||||
from trezor.enums import BackupType
|
||||
|
||||
assert backup_type in (BackupType.Slip39_Basic, BackupType.Slip39_Advanced)
|
||||
|
||||
items = (
|
||||
(
|
||||
"Number of shares",
|
||||
"Set threshold",
|
||||
"Write down and check all shares",
|
||||
)
|
||||
if backup_type == BackupType.Slip39_Basic
|
||||
else (
|
||||
"Number of groups",
|
||||
"Number of shares",
|
||||
"Set sizes and thresholds",
|
||||
)
|
||||
)
|
||||
|
||||
result = await interact(
|
||||
ctx,
|
||||
RustLayout(
|
||||
trezorui2.show_checklist(
|
||||
title="BACKUP CHECKLIST",
|
||||
button="CONTINUE",
|
||||
active=step,
|
||||
items=items,
|
||||
)
|
||||
),
|
||||
"slip39_checklist",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
if result != CONFIRMED:
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
async def _prompt_number(
|
||||
ctx: GenericContext,
|
||||
title: str,
|
||||
count: int,
|
||||
min_count: int,
|
||||
max_count: int,
|
||||
br_name: str,
|
||||
) -> int:
|
||||
num_input = RustLayout(
|
||||
trezorui2.request_number(
|
||||
title=title.upper(),
|
||||
count=count,
|
||||
min_count=min_count,
|
||||
max_count=max_count,
|
||||
)
|
||||
)
|
||||
|
||||
result = await interact(
|
||||
ctx,
|
||||
num_input,
|
||||
br_name,
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
return int(result)
|
||||
|
||||
|
||||
async def slip39_prompt_threshold(
|
||||
ctx: GenericContext, num_of_shares: int, group_id: int | None = None
|
||||
) -> int:
|
||||
await confirm_action(
|
||||
ctx,
|
||||
"slip39_prompt_threshold",
|
||||
"Threshold",
|
||||
description="= number of shares needed for recovery",
|
||||
verb="BEGIN",
|
||||
verb_cancel=None,
|
||||
)
|
||||
|
||||
count = num_of_shares // 2 + 1
|
||||
# min value of share threshold is 2 unless the number of shares is 1
|
||||
# number of shares 1 is possible in advanced slip39
|
||||
min_count = min(2, num_of_shares)
|
||||
max_count = num_of_shares
|
||||
|
||||
if group_id is not None:
|
||||
title = f"THRESHOLD - GROUP {group_id + 1}"
|
||||
else:
|
||||
title = "SET THRESHOLD"
|
||||
|
||||
return await _prompt_number(
|
||||
ctx,
|
||||
title,
|
||||
count,
|
||||
min_count,
|
||||
max_count,
|
||||
"slip39_threshold",
|
||||
)
|
||||
|
||||
|
||||
async def slip39_prompt_number_of_shares(
|
||||
ctx: GenericContext, group_id: int | None = None
|
||||
) -> int:
|
||||
await confirm_action(
|
||||
ctx,
|
||||
"slip39_shares",
|
||||
"Number of shares",
|
||||
description="= total number of unique word lists used for wallet backup",
|
||||
verb="BEGIN",
|
||||
verb_cancel=None,
|
||||
)
|
||||
|
||||
count = 5
|
||||
min_count = 1
|
||||
max_count = 16
|
||||
|
||||
if group_id is not None:
|
||||
title = f"# SHARES - GROUP {group_id + 1}"
|
||||
else:
|
||||
title = "NUMBER OF SHARES"
|
||||
|
||||
return await _prompt_number(
|
||||
ctx,
|
||||
title,
|
||||
count,
|
||||
min_count,
|
||||
max_count,
|
||||
"slip39_shares",
|
||||
)
|
||||
|
||||
|
||||
async def slip39_advanced_prompt_number_of_groups(ctx: GenericContext) -> int:
|
||||
count = 5
|
||||
min_count = 2
|
||||
max_count = 16
|
||||
|
||||
return await _prompt_number(
|
||||
ctx,
|
||||
"NUMBER OF GROUPS",
|
||||
count,
|
||||
min_count,
|
||||
max_count,
|
||||
"slip39_groups",
|
||||
)
|
||||
|
||||
|
||||
async def slip39_advanced_prompt_group_threshold(
|
||||
ctx: GenericContext, num_of_groups: int
|
||||
) -> int:
|
||||
count = num_of_groups // 2 + 1
|
||||
min_count = 1
|
||||
max_count = num_of_groups
|
||||
|
||||
return await _prompt_number(
|
||||
ctx,
|
||||
"GROUP THRESHOLD",
|
||||
count,
|
||||
min_count,
|
||||
max_count,
|
||||
"slip39_group_threshold",
|
||||
)
|
||||
|
||||
|
||||
async def show_warning_backup(ctx: GenericContext, slip39: bool) -> None:
|
||||
await confirm_action(
|
||||
ctx,
|
||||
"backup_warning",
|
||||
"SHAMIR BACKUP" if slip39 else "WALLET BACKUP",
|
||||
description="You can use your backup to recover your wallet at any time.",
|
||||
verb="HOLD TO BEGIN",
|
||||
hold=True,
|
||||
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,
|
||||
)
|
Loading…
Reference in new issue