From cdf29b2aa033b6d9626ef2e3ae28253e603f3b22 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 10 Feb 2021 00:14:11 +0100 Subject: [PATCH 01/13] fix(core/ui): don't return CONFIRMED/CANCELLED from layouts --- .../trezor/ui/components/common/confirm.py | 6 +- core/src/trezor/ui/layouts/common.py | 6 +- core/src/trezor/ui/layouts/tt.py | 221 ++++++++++-------- 3 files changed, 136 insertions(+), 97 deletions(-) diff --git a/core/src/trezor/ui/components/common/confirm.py b/core/src/trezor/ui/components/common/confirm.py index 09da8e78643..941d364095e 100644 --- a/core/src/trezor/ui/components/common/confirm.py +++ b/core/src/trezor/ui/components/common/confirm.py @@ -4,13 +4,17 @@ from apps.debug import confirm_signal if False: - from typing import List, Tuple, Optional + from typing import List, Tuple, Optional, Any CONFIRMED = object() CANCELLED = object() INFO = object() +def is_confirmed(x: Any) -> bool: + return x is CONFIRMED + + class ConfirmBase(ui.Layout): def __init__( self, diff --git a/core/src/trezor/ui/layouts/common.py b/core/src/trezor/ui/layouts/common.py index f8c7afcbe48..0b41ed59504 100644 --- a/core/src/trezor/ui/layouts/common.py +++ b/core/src/trezor/ui/layouts/common.py @@ -3,8 +3,6 @@ from trezor.messages.ButtonAck import ButtonAck from trezor.messages.ButtonRequest import ButtonRequest -from ..components.common.confirm import CONFIRMED - if False: from typing import Any, Awaitable @@ -13,9 +11,9 @@ LayoutType = Awaitable[Any] -async def require(a: LayoutType) -> None: +async def require(a: Awaitable[bool]) -> None: result = await a - if result is not CONFIRMED: + if not result: raise wire.ActionCancelled diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index 5ecdc689675..e25cf2560b1 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -8,7 +8,7 @@ from trezor.utils import chunks from ..components.common import break_path_to_lines -from ..components.common.confirm import CONFIRMED +from ..components.common.confirm import is_confirmed from ..components.tt.button import ButtonCancel, ButtonDefault from ..components.tt.confirm import Confirm, HoldToConfirm from ..components.tt.scroll import Paginated @@ -29,8 +29,6 @@ from trezor import wire from trezor.messages.ButtonRequest import EnumTypeButtonRequestType - from . import LayoutType - __all__ = ( "confirm_action", @@ -55,39 +53,49 @@ ) -def confirm_action( +async def confirm_action( ctx: wire.GenericContext, br_type: str, title: str, action: str, description: str = None, verb: Union[str, bytes] = Confirm.DEFAULT_CONFIRM, + verb_cancel: Union[str, bytes] = Confirm.DEFAULT_CANCEL, icon: str = None, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, **kwargs: Any, -) -> LayoutType: +) -> bool: text = Text(title, icon if icon is not None else ui.ICON_DEFAULT, new_lines=False) text.bold(action) text.br() if description: text.normal(description) - return interact(ctx, Confirm(text, confirm=verb), br_type, br_code) + return is_confirmed( + await interact( + ctx, + Confirm(text, confirm=verb, cancel=verb_cancel), + br_type, + br_code, + ) + ) -def confirm_wipe(ctx: wire.GenericContext) -> LayoutType: +async def confirm_wipe(ctx: wire.GenericContext) -> bool: text = Text("Wipe device", ui.ICON_WIPE, ui.RED) text.normal("Do you really want to", "wipe the device?", "") text.bold("All data will be lost.") - return interact( - ctx, - HoldToConfirm(text, confirm_style=ButtonCancel, loader_style=LoaderDanger), - "wipe_device", - ButtonRequestType.WipeDevice, + return is_confirmed( + await interact( + ctx, + HoldToConfirm(text, confirm_style=ButtonCancel, loader_style=LoaderDanger), + "wipe_device", + ButtonRequestType.WipeDevice, + ) ) -def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> LayoutType: +async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> bool: text = Text("Create new wallet", ui.ICON_RESET, new_lines=False) text.bold(prompt) text.br() @@ -96,11 +104,13 @@ def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> LayoutType: text.br() text.normal("to") text.bold("https://trezor.io/tos") - return interact( - ctx, - Confirm(text, major_confirm=True), - "setup_device", - ButtonRequestType.ResetDevice, + return is_confirmed( + await interact( + ctx, + Confirm(text, major_confirm=True), + "setup_device", + ButtonRequestType.ResetDevice, + ) ) @@ -115,38 +125,39 @@ async def confirm_backup(ctx: wire.GenericContext) -> bool: text2.br_half() text2.normal("You can back up your", "Trezor once, at any time.") - if ( + if is_confirmed( await interact( ctx, Confirm(text1, cancel="Skip", confirm="Back up", major_confirm=True), "backup_device", ButtonRequestType.ResetDevice, ) - is CONFIRMED ): return True - confirmed = ( + confirmed = is_confirmed( await interact( ctx, Confirm(text2, cancel="Skip", confirm="Back up", major_confirm=True), "backup_device", ButtonRequestType.ResetDevice, ) - ) is CONFIRMED + ) return confirmed -def confirm_path_warning(ctx: wire.GenericContext, path: str) -> LayoutType: +async def confirm_path_warning(ctx: wire.GenericContext, path: str) -> bool: text = Text("Confirm path", ui.ICON_WRONG, ui.RED) text.normal("Path") text.mono(*break_path_to_lines(path, MONO_CHARS_PER_LINE)) text.normal("is unknown.", "Are you sure?") - return interact( - ctx, - Confirm(text), - "path_warning", - ButtonRequestType.UnknownDerivationPath, + return is_confirmed( + await interact( + ctx, + Confirm(text), + "path_warning", + ButtonRequestType.UnknownDerivationPath, + ) ) @@ -203,11 +214,16 @@ def _show_xpub(xpub: str, desc: str, cancel: str) -> Paginated: return content -def show_xpub( +async def show_xpub( ctx: wire.GenericContext, xpub: str, desc: str, cancel: str -) -> LayoutType: - return interact( - ctx, _show_xpub(xpub, desc, cancel), "show_xpub", ButtonRequestType.PublicKey +) -> bool: + return is_confirmed( + await interact( + ctx, + _show_xpub(xpub, desc, cancel), + "show_xpub", + ButtonRequestType.PublicKey, + ) ) @@ -222,17 +238,16 @@ async def show_address( ) -> None: is_multisig = len(xpubs) > 0 while True: - if ( + if is_confirmed( await interact( ctx, _show_address(address, desc, network), "show_address", ButtonRequestType.Address, ) - is CONFIRMED ): break - if ( + if is_confirmed( await interact( ctx, _show_qr( @@ -243,7 +258,6 @@ async def show_address( "show_qr", ButtonRequestType.Address, ) - is CONFIRMED ): break @@ -252,169 +266,188 @@ async def show_address( cancel = "Next" if i < len(xpubs) - 1 else "Address" desc_xpub = "XPUB #%d" % (i + 1) desc_xpub += " (yours)" if i == multisig_index else " (cosigner)" - if ( + if is_confirmed( await interact( ctx, _show_xpub(xpub, desc=desc_xpub, cancel=cancel), "show_xpub", ButtonRequestType.PublicKey, ) - is CONFIRMED ): return # FIXME: this is basically same as confirm_hex # TODO: pagination for long keys -def show_pubkey( +async def show_pubkey( ctx: wire.Context, pubkey: str, title: str = "Confirm public key" -) -> LayoutType: +) -> bool: text = Text(title, ui.ICON_RECEIVE, ui.GREEN) text.mono(*_hex_lines(pubkey)) - return interact(ctx, Confirm(text), "show_pubkey", ButtonRequestType.PublicKey) + return is_confirmed( + await interact(ctx, Confirm(text), "show_pubkey", ButtonRequestType.PublicKey) + ) -def show_warning( +async def show_warning( ctx: wire.GenericContext, br_type: str, content: str, subheader: Optional[str] = None, button: str = "Try again", -) -> LayoutType: +) -> bool: text = Text("Warning", ui.ICON_WRONG, ui.RED, new_lines=False) if subheader: text.bold(subheader) text.br() text.br_half() text.normal(content) - return interact( - ctx, - Confirm(text, confirm=button, cancel=None), - br_type, - ButtonRequestType.Warning, + return is_confirmed( + await interact( + ctx, + Confirm(text, confirm=button, cancel=None), + br_type, + ButtonRequestType.Warning, + ) ) -def show_success( +async def show_success( ctx: wire.GenericContext, br_type: str, content: str, subheader: Optional[str] = None, button: str = "Continue", -) -> LayoutType: +) -> bool: text = Text("Success", ui.ICON_CONFIRM, ui.GREEN, new_lines=False) if subheader: text.bold(subheader) text.br() text.br_half() text.normal(content) - return interact( - ctx, - Confirm(text, confirm=button, cancel=None), - br_type, - ButtonRequestType.Success, + return is_confirmed( + await interact( + ctx, + Confirm(text, confirm=button, cancel=None), + br_type, + ButtonRequestType.Success, + ) ) -def confirm_output( +async def confirm_output( ctx: wire.GenericContext, address: str, amount: str, -) -> LayoutType: +) -> bool: text = Text("Confirm sending", ui.ICON_SEND, ui.GREEN) text.normal(amount + " to") text.mono(*_split_address(address)) - return interact( - ctx, Confirm(text), "confirm_output", ButtonRequestType.ConfirmOutput + return is_confirmed( + await interact( + ctx, Confirm(text), "confirm_output", ButtonRequestType.ConfirmOutput + ) ) -def confirm_decred_sstx_submission( +async def confirm_decred_sstx_submission( ctx: wire.GenericContext, address: str, amount: str, -) -> LayoutType: +) -> bool: text = Text("Purchase ticket", ui.ICON_SEND, ui.GREEN) text.normal(amount) text.normal("with voting rights to") text.mono(*_split_address(address)) - return interact( - ctx, - Confirm(text), - "confirm_decred_sstx_submission", - ButtonRequestType.ConfirmOutput, + return is_confirmed( + await interact( + ctx, + Confirm(text), + "confirm_decred_sstx_submission", + ButtonRequestType.ConfirmOutput, + ) ) -def confirm_hex( +async def confirm_hex( ctx: wire.GenericContext, br_type: str, title: str, data: str, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, -) -> LayoutType: +) -> bool: text = Text(title, ui.ICON_SEND, ui.GREEN) text.mono(*_hex_lines(data)) - return interact(ctx, Confirm(text), br_type, br_code) + return is_confirmed(await interact(ctx, Confirm(text), br_type, br_code)) -def confirm_total( +async def confirm_total( ctx: wire.GenericContext, total_amount: str, fee_amount: str -) -> LayoutType: +) -> bool: text = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) text.normal("Total amount:") text.bold(total_amount) text.normal("including fee:") text.bold(fee_amount) - return interact(ctx, HoldToConfirm(text), "confirm_total", ButtonRequestType.SignTx) + return is_confirmed( + await interact( + ctx, HoldToConfirm(text), "confirm_total", ButtonRequestType.SignTx + ) + ) -def confirm_joint_total( +async def confirm_joint_total( ctx: wire.GenericContext, spending_amount: str, total_amount: str -) -> LayoutType: +) -> bool: text = Text("Joint transaction", ui.ICON_SEND, ui.GREEN) text.normal("You are contributing:") text.bold(spending_amount) text.normal("to the total amount:") text.bold(total_amount) - return interact( - ctx, HoldToConfirm(text), "confirm_joint_total", ButtonRequestType.SignTx + return is_confirmed( + await interact( + ctx, HoldToConfirm(text), "confirm_joint_total", ButtonRequestType.SignTx + ) ) -def confirm_metadata( +async def confirm_metadata( ctx: wire.GenericContext, br_type: str, title: str, content: str, param: Optional[str] = None, br_code: EnumTypeButtonRequestType = ButtonRequestType.SignTx, -) -> LayoutType: +) -> bool: text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) text.format_parametrized(content, param if param is not None else "") text.br() text.normal("Continue?") - return interact(ctx, Confirm(text), br_type, br_code) + return is_confirmed(await interact(ctx, Confirm(text), br_type, br_code)) -def confirm_replacement( +async def confirm_replacement( ctx: wire.GenericContext, description: str, txid: str -) -> LayoutType: +) -> bool: text = Text(description, ui.ICON_SEND, ui.GREEN) text.normal("Confirm transaction ID:") text.mono(*_hex_lines(txid, TEXT_MAX_LINES - 1)) - return interact(ctx, Confirm(text), "confirm_replacement", ButtonRequestType.SignTx) + return is_confirmed( + await interact( + ctx, Confirm(text), "confirm_replacement", ButtonRequestType.SignTx + ) + ) -def confirm_modify_output( +async def confirm_modify_output( ctx: wire.GenericContext, address: str, sign: int, amount_change: str, amount_new: str, -) -> LayoutType: +) -> bool: page1 = Text("Modify amount", ui.ICON_SEND, ui.GREEN) page1.normal("Address:") page1.br_half() @@ -430,20 +463,22 @@ def confirm_modify_output( page2.normal("New amount:") page2.bold(amount_new) - return interact( - ctx, - Paginated([page1, Confirm(page2)]), - "modify_output", - ButtonRequestType.ConfirmOutput, + return is_confirmed( + interact( + ctx, + Paginated([page1, Confirm(page2)]), + "modify_output", + ButtonRequestType.ConfirmOutput, + ) ) -def confirm_modify_fee( +async def confirm_modify_fee( ctx: wire.GenericContext, sign: int, user_fee_change: str, total_fee_new: str, -) -> LayoutType: +) -> bool: text = Text("Modify fee", ui.ICON_SEND, ui.GREEN) if sign == 0: text.normal("Your fee did not change.") @@ -456,4 +491,6 @@ def confirm_modify_fee( text.br_half() text.normal("Transaction fee:") text.bold(total_fee_new) - return interact(ctx, HoldToConfirm(text), "modify_fee", ButtonRequestType.SignTx) + return is_confirmed( + await interact(ctx, HoldToConfirm(text), "modify_fee", ButtonRequestType.SignTx) + ) From cb58165574a8d3fb535f8218117fd017a78be5a4 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 10 Feb 2021 18:36:08 +0100 Subject: [PATCH 02/13] refactor(core): convert apps.common.sdcard to layouts --- core/src/apps/common/sdcard.py | 113 +++++++++++++-------- core/src/apps/management/sd_protect.py | 28 ++---- core/src/trezor/ui/layouts/tt.py | 130 +++++++++++++++++++------ 3 files changed, 180 insertions(+), 91 deletions(-) diff --git a/core/src/apps/common/sdcard.py b/core/src/apps/common/sdcard.py index 569c24b9512..e52815a3aa8 100644 --- a/core/src/apps/common/sdcard.py +++ b/core/src/apps/common/sdcard.py @@ -1,9 +1,7 @@ import storage.sd_salt from storage.sd_salt import SD_CARD_HOT_SWAPPABLE from trezor import fatfs, sdcard, ui, wire -from trezor.ui.components.tt.text import Text - -from apps.common.confirm import confirm, hold_to_confirm +from trezor.ui.layouts import confirm_action, show_error if False: from typing import Optional @@ -14,60 +12,95 @@ class SdCardUnavailable(wire.ProcessError): async def _wrong_card_dialog(ctx: wire.GenericContext) -> bool: - text = Text("SD card protection", ui.ICON_WRONG) - text.bold("Wrong SD card.") - text.br_half() if SD_CARD_HOT_SWAPPABLE: - text.normal("Please insert the", "correct SD card for", "this device.") - btn_confirm: Optional[str] = "Retry" - btn_cancel = "Abort" + return await confirm_action( + ctx, + "warning_wrong_sd", + "SD card protection", + action="Wrong SD card.", + description="Please insert the correct SD card for this device.", + verb="Retry", + verb_cancel="Abort", + icon=ui.ICON_WRONG, + larger_vspace=True, + ) else: - text.normal("Please unplug the", "device and insert the", "correct SD card.") - btn_confirm = None - btn_cancel = "Close" - - return await confirm(ctx, text, confirm=btn_confirm, cancel=btn_cancel) + return await show_error( + ctx, + "warning_wrong_sd", + header="SD card protection", + subheader="Wrong SD card.", + content="Please unplug the\ndevice and insert the correct SD card.", + ) async def insert_card_dialog(ctx: wire.GenericContext) -> bool: - text = Text("SD card protection", ui.ICON_WRONG) - text.bold("SD card required.") - text.br_half() if SD_CARD_HOT_SWAPPABLE: - text.normal("Please insert your", "SD card.") - btn_confirm: Optional[str] = "Retry" - btn_cancel = "Abort" + return await confirm_action( + ctx, + "warning_no_sd", + "SD card protection", + action="SD card required.", + description="Please insert your SD card.", + verb="Retry", + verb_cancel="Abort", + icon=ui.ICON_WRONG, + larger_vspace=True, + ) else: - text.normal("Please unplug the", "device and insert your", "SD card.") - btn_confirm = None - btn_cancel = "Close" - - return await confirm(ctx, text, confirm=btn_confirm, cancel=btn_cancel) + return await show_error( + ctx, + "warning_no_sd", + header="SD card protection", + subheader="SD card required.", + content="Please unplug the\ndevice and insert your SD card.", + ) async def format_card_dialog(ctx: wire.GenericContext) -> bool: # Format card? yes/no - text = Text("SD card error", ui.ICON_WRONG, ui.RED) - text.bold("Unknown filesystem.") - text.br_half() - text.normal("Use a different card or") - text.normal("format the SD card to") - text.normal("the FAT32 filesystem.") - if not await confirm(ctx, text, confirm="Format", cancel="Cancel"): + if not await confirm_action( + ctx, + "warning_format_sd", + "SD card error", + action="Unknown filesystem.", + description="Use a different card or format the SD card to the FAT32 filesystem.", + icon=ui.ICON_WRONG, + icon_color=ui.RED, + verb="Format", + verb_cancel="Cancel", + larger_vspace=True, + ): return False # Confirm formatting - text = Text("Format SD card", ui.ICON_WIPE, ui.RED) - text.normal("Do you really want to", "format the SD card?") - text.br_half() - text.bold("All data on the SD card", "will be lost.") - return await hold_to_confirm(ctx, text, confirm="Format SD card") + return await confirm_action( + ctx, + "confirm_format_sd", + "Format SD card", + action="All data on the SD card will be lost.", + description="Do you really want to format the SD card?", + reverse=True, + verb="Format SD card", + icon=ui.ICON_WIPE, + icon_color=ui.RED, + hold=True, + larger_vspace=True, + ) async def sd_problem_dialog(ctx: wire.GenericContext) -> bool: - text = Text("SD card problem", ui.ICON_WRONG, ui.RED) - text.normal("There was a problem", "accessing the SD card.") - return await confirm(ctx, text, confirm="Retry", cancel="Abort") + return await confirm_action( + ctx, + "warning_sd_retry", + "SD card problem", + action=None, + description="There was a problem accessing the SD card.", + icon=ui.ICON_WRONG, + icon_color=ui.RED, + verb="Retry", + verb_cancel="Abort", + ) async def ensure_sdcard( diff --git a/core/src/apps/management/sd_protect.py b/core/src/apps/management/sd_protect.py index eb477ef0afe..a113bcae508 100644 --- a/core/src/apps/management/sd_protect.py +++ b/core/src/apps/management/sd_protect.py @@ -1,13 +1,11 @@ import storage.device import storage.sd_salt -from trezor import config, ui, wire +from trezor import config, wire from trezor.crypto import random from trezor.messages import SdProtectOperationType from trezor.messages.Success import Success -from trezor.ui.components.tt.text import Text -from trezor.ui.layouts import require, show_success +from trezor.ui.layouts import confirm_action, require, show_success -from apps.common.confirm import require_confirm from apps.common.request_pin import ( error_pin_invalid, request_pin, @@ -167,24 +165,14 @@ async def sd_protect_refresh(ctx: wire.Context, msg: SdProtect) -> Success: def require_confirm_sd_protect(ctx: wire.Context, msg: SdProtect) -> Awaitable[None]: if msg.operation == SdProtectOperationType.ENABLE: - text = Text("SD card protection", ui.ICON_CONFIG) - text.normal( - "Do you really want to", "secure your device with", "SD card protection?" - ) + text = "Do you really want to secure your device with SD card protection?" elif msg.operation == SdProtectOperationType.DISABLE: - text = Text("SD card protection", ui.ICON_CONFIG) - text.normal( - "Do you really want to", "remove SD card", "protection from your", "device?" - ) + text = "Do you really want to remove SD card protection from your device?" elif msg.operation == SdProtectOperationType.REFRESH: - text = Text("SD card protection", ui.ICON_CONFIG) - text.normal( - "Do you really want to", - "replace the current", - "SD card secret with a", - "newly generated one?", - ) + text = "Do you really want to replace the current\nSD card secret with a newly generated one?" else: raise wire.ProcessError("Unknown operation") - return require_confirm(ctx, text) + return require( + confirm_action(ctx, "set_sd", "SD card protection", description=text) + ) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index e25cf2560b1..bf0002952b2 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -24,7 +24,7 @@ from .common import interact if False: - from typing import Any, Iterator, List, Sequence, Union, Optional + from typing import Any, Iterator, List, Sequence, Union, Optional, Awaitable from trezor import wire from trezor.messages.ButtonRequest import EnumTypeButtonRequestType @@ -37,6 +37,7 @@ "confirm_backup", "confirm_path_warning", "show_address", + "show_error", "show_pubkey", "show_success", "show_xpub", @@ -57,24 +58,45 @@ async def confirm_action( ctx: wire.GenericContext, br_type: str, title: str, - action: str, + action: str = None, description: str = None, - verb: Union[str, bytes] = Confirm.DEFAULT_CONFIRM, - verb_cancel: Union[str, bytes] = Confirm.DEFAULT_CANCEL, - icon: str = None, + verb: Union[str, bytes, None] = Confirm.DEFAULT_CONFIRM, + verb_cancel: Union[str, bytes, None] = Confirm.DEFAULT_CANCEL, + hold: bool = False, + icon: str = None, # TODO cleanup @ redesign + icon_color: int = None, # TODO cleanup @ redesign + reverse: bool = False, # TODO cleanup @ redesign + larger_vspace: bool = False, # TODO cleanup @ redesign br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, **kwargs: Any, ) -> bool: - text = Text(title, icon if icon is not None else ui.ICON_DEFAULT, new_lines=False) - text.bold(action) - text.br() - if description: + text = Text( + title, + icon if icon is not None else ui.ICON_DEFAULT, + icon_color if icon_color is not None else ui.ORANGE_ICON, + new_lines=False, + ) + + if reverse and description is not None: text.normal(description) + elif action is not None: + text.bold(action) + + if action is not None and description is not None: + text.br() + if larger_vspace: + text.br_half() + if reverse and action is not None: + text.bold(action) + elif description is not None: + text.normal(description) + + cls = HoldToConfirm if hold else Confirm return is_confirmed( await interact( ctx, - Confirm(text, confirm=verb, cancel=verb_cancel), + cls(text, confirm=verb, cancel=verb_cancel), br_type, br_code, ) @@ -289,14 +311,19 @@ async def show_pubkey( ) -async def show_warning( +async def _show_modal( ctx: wire.GenericContext, br_type: str, + br_code: EnumTypeButtonRequestType, + header: str, + subheader: Optional[str], content: str, - subheader: Optional[str] = None, - button: str = "Try again", + button_confirm: Optional[str], + button_cancel: Optional[str], + icon: str, + icon_color: int, ) -> bool: - text = Text("Warning", ui.ICON_WRONG, ui.RED, new_lines=False) + text = Text(header, icon, icon_color, new_lines=False) if subheader: text.bold(subheader) text.br() @@ -305,33 +332,74 @@ async def show_warning( return is_confirmed( await interact( ctx, - Confirm(text, confirm=button, cancel=None), + Confirm(text, confirm=button_confirm, cancel=button_cancel), br_type, - ButtonRequestType.Warning, + br_code, ) ) -async def show_success( +def show_error( + ctx: wire.GenericContext, + br_type: str, + content: str, + header: str = "Error", + subheader: Optional[str] = None, + button: str = "Close", +) -> Awaitable[bool]: + return _show_modal( + ctx, + br_type=br_type, + br_code=ButtonRequestType.Other, + header=header, + subheader=subheader, + content=content, + button_confirm=None, + button_cancel=button, + icon=ui.ICON_WRONG, + icon_color=ui.ORANGE_ICON, + ) + + +def show_warning( + ctx: wire.GenericContext, + br_type: str, + content: str, + subheader: Optional[str] = None, + button: str = "Try again", +) -> Awaitable[bool]: + return _show_modal( + ctx, + br_type=br_type, + br_code=ButtonRequestType.Warning, + header="Warning", + subheader=subheader, + content=content, + button_confirm=button, + button_cancel=None, + icon=ui.ICON_WRONG, + icon_color=ui.RED, + ) + + +def show_success( ctx: wire.GenericContext, br_type: str, content: str, subheader: Optional[str] = None, button: str = "Continue", -) -> bool: - text = Text("Success", ui.ICON_CONFIRM, ui.GREEN, new_lines=False) - if subheader: - text.bold(subheader) - text.br() - text.br_half() - text.normal(content) - return is_confirmed( - await interact( - ctx, - Confirm(text, confirm=button, cancel=None), - br_type, - ButtonRequestType.Success, - ) +) -> Awaitable[bool]: + return _show_modal( + ctx, + br_type=br_type, + br_code=ButtonRequestType.Success, + header="Success", + subheader=subheader, + content=content, + button_confirm=button, + button_cancel=None, + icon=ui.ICON_CONFIRM, + icon_color=ui.GREEN, ) From 581d6dd6ff3ea4e5a164b230ab37da16e3cc8976 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 10 Feb 2021 18:37:06 +0100 Subject: [PATCH 03/13] refactor(core/ui): merge confirm_hexdata and show_pubkey --- core/src/trezor/ui/layouts/tt.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index bf0002952b2..cff99796fd1 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -103,6 +103,7 @@ async def confirm_action( ) +# TODO cleanup @ redesign async def confirm_wipe(ctx: wire.GenericContext) -> bool: text = Text("Wipe device", ui.ICON_WIPE, ui.RED) text.normal("Do you really want to", "wipe the device?", "") @@ -136,6 +137,7 @@ async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> bool: ) +# TODO cleanup @ redesign async def confirm_backup(ctx: wire.GenericContext) -> bool: text1 = Text("Success", ui.ICON_CONFIRM, ui.GREEN) text1.bold("New wallet created", "successfully!") @@ -299,15 +301,16 @@ async def show_address( return -# FIXME: this is basically same as confirm_hex -# TODO: pagination for long keys -async def show_pubkey( +def show_pubkey( ctx: wire.Context, pubkey: str, title: str = "Confirm public key" -) -> bool: - text = Text(title, ui.ICON_RECEIVE, ui.GREEN) - text.mono(*_hex_lines(pubkey)) - return is_confirmed( - await interact(ctx, Confirm(text), "show_pubkey", ButtonRequestType.PublicKey) +) -> Awaitable[bool]: + return confirm_hex( + ctx, + br_type="show_pubkey", + title="Confirm public key", + data=pubkey, + br_code=ButtonRequestType.PublicKey, + icon=ui.ICON_RECEIVE, ) @@ -443,8 +446,10 @@ async def confirm_hex( title: str, data: str, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, + icon: str = ui.ICON_SEND, # TODO cleanup @ redesign + icon_color: int = ui.GREEN, # TODO cleanup @ redesign ) -> bool: - text = Text(title, ui.ICON_SEND, ui.GREEN) + text = Text(title, icon, icon_color) text.mono(*_hex_lines(data)) return is_confirmed(await interact(ctx, Confirm(text), br_type, br_code)) From eefc8b44615a6e4b4f4eaf10de661e3659e3d5b7 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 10 Feb 2021 23:08:05 +0100 Subject: [PATCH 04/13] refactor(core): convert apps.misc.* to layouts --- core/src/apps/misc/cipher_key_value.py | 9 ++++---- core/src/apps/misc/get_ecdh_session_key.py | 20 ++++++++++-------- core/src/apps/misc/get_entropy.py | 18 +++++++++------- core/src/apps/misc/sign_identity.py | 24 ++++++++-------------- core/src/trezor/ui/layouts/tt.py | 20 ++++++++++++++++++ 5 files changed, 55 insertions(+), 36 deletions(-) diff --git a/core/src/apps/misc/cipher_key_value.py b/core/src/apps/misc/cipher_key_value.py index b88d5055dff..83d65a67e32 100644 --- a/core/src/apps/misc/cipher_key_value.py +++ b/core/src/apps/misc/cipher_key_value.py @@ -1,9 +1,8 @@ from trezor import wire from trezor.crypto import aes, hmac from trezor.messages.CipheredKeyValue import CipheredKeyValue -from trezor.ui.components.tt.text import Text +from trezor.ui.layouts import confirm_action, require -from apps.common.confirm import require_confirm from apps.common.keychain import get_keychain from apps.common.paths import AlwaysMatchingSchema @@ -28,9 +27,9 @@ async def cipher_key_value(ctx: Context, msg: CipherKeyValue) -> CipheredKeyValu title = "Encrypt value" else: title = "Decrypt value" - text = Text(title) - text.normal(msg.key) - await require_confirm(ctx, text) + await require( + confirm_action(ctx, "cipher_key_value", title, description=msg.key) + ) node = keychain.derive(msg.address_n) value = compute_cipher_key_value(msg, node.private_key()) diff --git a/core/src/apps/misc/get_ecdh_session_key.py b/core/src/apps/misc/get_ecdh_session_key.py index da6692797e5..06f76379cb2 100644 --- a/core/src/apps/misc/get_ecdh_session_key.py +++ b/core/src/apps/misc/get_ecdh_session_key.py @@ -1,13 +1,11 @@ from ustruct import pack, unpack -from trezor import wire +from trezor import ui, wire from trezor.crypto.hashlib import sha256 from trezor.messages.ECDHSessionKey import ECDHSessionKey -from trezor.ui.components.tt.text import Text -from trezor.utils import chunks +from trezor.ui.layouts import confirm_hex, require from apps.common import HARDENED -from apps.common.confirm import require_confirm from apps.common.keychain import get_keychain from apps.common.paths import AlwaysMatchingSchema @@ -48,11 +46,17 @@ async def get_ecdh_session_key( async def require_confirm_ecdh_session_key( ctx: wire.Context, identity: IdentityType ) -> None: - lines = chunks(serialize_identity_without_proto(identity), 18) proto = identity.proto.upper() if identity.proto else "identity" - text = Text("Decrypt %s" % proto) - text.mono(*lines) - await require_confirm(ctx, text) + await require( + confirm_hex( + ctx, + "ecdh_session_key", + "Decrypt %s" % proto, + serialize_identity_without_proto(identity), + icon=ui.ICON_DEFAULT, + icon_color=ui.ORANGE_ICON, + ) + ) def get_ecdh_path(identity: str, index: int) -> Bip32Path: diff --git a/core/src/apps/misc/get_entropy.py b/core/src/apps/misc/get_entropy.py index 11d1b6346f9..86dfa76e837 100644 --- a/core/src/apps/misc/get_entropy.py +++ b/core/src/apps/misc/get_entropy.py @@ -1,9 +1,7 @@ from trezor.crypto import random from trezor.messages import ButtonRequestType from trezor.messages.Entropy import Entropy -from trezor.ui.components.tt.text import Text - -from apps.common.confirm import require_confirm +from trezor.ui.layouts import confirm_action, require if False: from trezor.wire import Context @@ -11,10 +9,16 @@ async def get_entropy(ctx: Context, msg: GetEntropy) -> Entropy: - text = Text("Confirm entropy") - text.bold("Do you really want", "to send entropy?") - text.normal("Continue only if you", "know what you are doing!") - await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall) + await require( + confirm_action( + ctx, + "get_entropy", + "Confirm entropy", + action="Do you really want\nto send entropy?", + description="Continue only if you\nknow what you are doing!", + br_code=ButtonRequestType.ProtectCall, + ) + ) size = min(msg.size, 1024) entropy = random.bytes(size) diff --git a/core/src/apps/misc/sign_identity.py b/core/src/apps/misc/sign_identity.py index fc8945b0d8d..80f74270b7f 100644 --- a/core/src/apps/misc/sign_identity.py +++ b/core/src/apps/misc/sign_identity.py @@ -1,22 +1,19 @@ from ustruct import pack, unpack -from trezor import ui, wire +from trezor import wire from trezor.crypto.hashlib import sha256 from trezor.messages.SignedIdentity import SignedIdentity -from trezor.ui.components.tt.text import Text -from trezor.utils import chunks +from trezor.ui.layouts import confirm_sign_identity, require from apps.common import HARDENED, coininfo -from apps.common.confirm import require_confirm from apps.common.keychain import get_keychain from apps.common.paths import AlwaysMatchingSchema if False: - from typing import List, Optional, Union + from typing import Optional, Union from trezor.messages.IdentityType import IdentityType from trezor.messages.SignIdentity import SignIdentity - from trezor.ui.components.common.text import TextContent from apps.common.paths import Bip32Path @@ -86,17 +83,12 @@ async def sign_identity(ctx: wire.Context, msg: SignIdentity) -> SignedIdentity: async def require_confirm_sign_identity( ctx: wire.Context, identity: IdentityType, challenge_visual: Optional[str] ) -> None: - lines: List[TextContent] = [] - if challenge_visual: - lines.append(challenge_visual) - - lines.append(ui.MONO) - lines.extend(chunks(serialize_identity_without_proto(identity), 18)) - proto = identity.proto.upper() if identity.proto else "identity" - text = Text("Sign %s" % proto) - text.normal(*lines) - await require_confirm(ctx, text) + await require( + confirm_sign_identity( + ctx, proto, serialize_identity_without_proto(identity), challenge_visual + ) + ) def serialize_identity(identity: IdentityType) -> str: diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index cff99796fd1..019ef883065 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -29,6 +29,7 @@ from trezor import wire from trezor.messages.ButtonRequest import EnumTypeButtonRequestType + from ..components.common.text import TextContent __all__ = ( "confirm_action", @@ -36,6 +37,7 @@ "confirm_reset_device", "confirm_backup", "confirm_path_warning", + "confirm_sign_identity", "show_address", "show_error", "show_pubkey", @@ -567,3 +569,21 @@ async def confirm_modify_fee( return is_confirmed( await interact(ctx, HoldToConfirm(text), "modify_fee", ButtonRequestType.SignTx) ) + + +# TODO cleanup @ redesign +async def confirm_sign_identity( + ctx: wire.GenericContext, proto: str, identity: str, challenge_visual: Optional[str] +) -> bool: + lines: List[TextContent] = [] + if challenge_visual: + lines.append(challenge_visual) + + lines.append(ui.MONO) + lines.extend(chunks(identity, 18)) + + text = Text("Sign %s" % proto) + text.normal(*lines) + return is_confirmed( + await interact(ctx, Confirm(text), "sign_identity", ButtonRequestType.Other) + ) From 6855a73d689cc49b9ad0d88a253e58a91e15c457 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Thu, 11 Feb 2021 00:56:39 +0100 Subject: [PATCH 05/13] refactor(core): convert parts of apps.management to layouts --- core/src/apps/management/apply_settings.py | 162 ++++++++++++------ core/src/apps/management/change_pin.py | 48 ++++-- .../apps/management/recovery_device/layout.py | 26 ++- .../apps/management/reset_device/layout.py | 71 +++++--- core/src/trezor/ui/layouts/tt.py | 28 ++- tests/common.py | 8 +- .../test_msg_resetdevice_bip39_t2.py | 85 +++++++++ tests/ui_tests/fixtures.json | 27 +-- 8 files changed, 327 insertions(+), 128 deletions(-) diff --git a/core/src/apps/management/apply_settings.py b/core/src/apps/management/apply_settings.py index 4f81bdc8ac8..18f2dfa71a5 100644 --- a/core/src/apps/management/apply_settings.py +++ b/core/src/apps/management/apply_settings.py @@ -3,11 +3,10 @@ from trezor.messages import ButtonRequestType, SafetyCheckLevel from trezor.messages.Success import Success from trezor.strings import format_duration_ms -from trezor.ui.components.tt.text import Text +from trezor.ui.layouts import confirm_action, require from apps.base import reload_settings_from_storage from apps.common import safety_checks -from apps.common.confirm import require_confirm, require_hold_to_confirm if False: from trezor.messages.ApplySettings import ApplySettings, EnumTypeSafetyCheckLevel @@ -101,37 +100,62 @@ async def apply_settings(ctx: wire.Context, msg: ApplySettings): async def require_confirm_change_homescreen(ctx): - text = Text("Set homescreen", ui.ICON_CONFIG) - text.normal("Do you really want to", "change the homescreen", "image?") - await require_confirm(ctx, text, ButtonRequestType.ProtectCall) + await require( + confirm_action( + ctx, + "set_homescreen", + "Set homescreen", + description="Do you really want to change the homescreen image?", + br_code=ButtonRequestType.ProtectCall, + ) + ) async def require_confirm_change_label(ctx, label): - text = Text("Change label", ui.ICON_CONFIG) - text.normal("Do you really want to", "change the label to") - text.bold("%s?" % label) - await require_confirm(ctx, text, ButtonRequestType.ProtectCall) + await require( + confirm_action( + ctx, + "set_label", + "Change label", + description="Do you really want to change the label to {}?", + description_param=label, + br_code=ButtonRequestType.ProtectCall, + ) + ) async def require_confirm_change_passphrase(ctx, use): - text = Text("Enable passphrase" if use else "Disable passphrase", ui.ICON_CONFIG) - text.normal("Do you really want to") - text.normal("enable passphrase" if use else "disable passphrase") - text.normal("encryption?") - await require_confirm(ctx, text, ButtonRequestType.ProtectCall) + if use: + description = "Do you really want to enable passphrase encryption?" + else: + description = "Do you really want to disable passphrase encryption?" + await require( + confirm_action( + ctx, + "set_passphrase", + "Enable passphrase" if use else "Disable passphrase", + description=description, + br_code=ButtonRequestType.ProtectCall, + ) + ) async def require_confirm_change_passphrase_source( ctx, passphrase_always_on_device: bool ): - text = Text("Passphrase source", ui.ICON_CONFIG) if passphrase_always_on_device: - text.normal( - "Do you really want to", "enter passphrase always", "on the device?" - ) + description = "Do you really want to enter passphrase always on the device?" else: - text.normal("Do you want to revoke", "the passphrase on device", "setting?") - await require_confirm(ctx, text, ButtonRequestType.ProtectCall) + description = "Do you want to revoke the passphrase on device setting?" + await require( + confirm_action( + ctx, + "set_passphrase_source", + "Passphrase source", + description=description, + br_code=ButtonRequestType.ProtectCall, + ) + ) async def require_confirm_change_display_rotation(ctx, rotation): @@ -145,55 +169,85 @@ async def require_confirm_change_display_rotation(ctx, rotation): label = "west" else: raise wire.DataError("Unsupported display rotation") - text = Text("Change rotation", ui.ICON_CONFIG, new_lines=False) - text.normal("Do you really want to", "change display rotation") - text.normal("to") - text.bold("%s?" % label) - await require_confirm(ctx, text, ButtonRequestType.ProtectCall) + await require( + confirm_action( + ctx, + "set_rotation", + "Change rotation", + description="Do you really want to change display rotation to {}?", + description_param=label, + br_code=ButtonRequestType.ProtectCall, + ) + ) async def require_confirm_change_autolock_delay(ctx, delay_ms): - text = Text("Auto-lock delay", ui.ICON_CONFIG, new_lines=False) - text.normal("Do you really want to", "auto-lock your device", "after") - text.bold("{}?".format(format_duration_ms(delay_ms))) - await require_confirm(ctx, text, ButtonRequestType.ProtectCall) + await require( + confirm_action( + ctx, + "set_autolock_delay", + "Auto-lock delay", + description="Do you really want to auto-lock your device after {}?", + description_param=format_duration_ms(delay_ms), + br_code=ButtonRequestType.ProtectCall, + ) + ) async def require_confirm_safety_checks(ctx, level: EnumTypeSafetyCheckLevel) -> None: if level == SafetyCheckLevel.PromptAlways: - text = Text("Safety override", ui.ICON_CONFIG) - text.normal( - "Trezor will allow you to", - "approve some actions", - "which might be unsafe.", + await require( + confirm_action( + ctx, + "set_safety_checks", + "Safety override", + hold=True, + verb="Hold to confirm", + description="Trezor will allow you to approve some actions which might be unsafe.", + action="Are you sure?", + reverse=True, + larger_vspace=True, + br_code=ButtonRequestType.ProtectCall, + ) ) - text.br_half() - text.bold("Are you sure?") - await require_hold_to_confirm(ctx, text, ButtonRequestType.ProtectCall) elif level == SafetyCheckLevel.PromptTemporarily: - text = Text("Safety override", ui.ICON_CONFIG) - text.normal( - "Trezor will temporarily", - "allow you to approve", - "some actions which", - "might be unsafe.", + await require( + confirm_action( + ctx, + "set_safety_checks", + "Safety override", + hold=True, + verb="Hold to confirm", + description="Trezor will temporarily allow you to approve some actions which might be unsafe.", + action="Are you sure?", + reverse=True, + br_code=ButtonRequestType.ProtectCall, + ) ) - text.bold("Are you sure?") - await require_hold_to_confirm(ctx, text, ButtonRequestType.ProtectCall) elif level == SafetyCheckLevel.Strict: - text = Text("Safety checks", ui.ICON_CONFIG) - text.normal( - "Do you really want to", "enforce strict safety", "checks (recommended)?" + await require( + confirm_action( + ctx, + "set_safety_checks", + "Safety checks", + description="Do you really want to enforce strict safety checks (recommended)?", + br_code=ButtonRequestType.ProtectCall, + ) ) - await require_confirm(ctx, text, ButtonRequestType.ProtectCall) else: raise ValueError # enum value out of range async def require_confirm_experimental_features(ctx, enable: bool) -> None: if enable: - text = Text("Experimental mode", ui.ICON_CONFIG) - text.normal("Enable experimental", "features?") - text.br_half() - text.bold("Only for development", "and beta testing!") - await require_confirm(ctx, text, ButtonRequestType.ProtectCall) + await require( + confirm_action( + ctx, + "set_experimental_features", + "Experimental mode", + description="Enable experimental features?", + action="Only for development and beta testing!", + reverse=True, + br_code=ButtonRequestType.ProtectCall, + ) + ) diff --git a/core/src/apps/management/change_pin.py b/core/src/apps/management/change_pin.py index 3d52d86bfc7..c21416e267f 100644 --- a/core/src/apps/management/change_pin.py +++ b/core/src/apps/management/change_pin.py @@ -1,10 +1,8 @@ from storage.device import is_initialized -from trezor import config, ui, wire +from trezor import config, wire from trezor.messages.Success import Success -from trezor.ui.components.tt.text import Text -from trezor.ui.layouts import require, show_success +from trezor.ui.layouts import confirm_action, require, show_success -from apps.common.confirm import require_confirm from apps.common.request_pin import ( error_pin_invalid, error_pin_matches_wipe_code, @@ -63,22 +61,40 @@ def require_confirm_change_pin(ctx: wire.Context, msg: ChangePin) -> None: has_pin = config.has_pin() if msg.remove and has_pin: # removing pin - text = Text("Remove PIN", ui.ICON_CONFIG) - text.normal("Do you really want to") - text.bold("disable PIN protection?") - return require_confirm(ctx, text) + return require( + confirm_action( + ctx, + "set_pin", + "Remove PIN", + description="Do you really want to", + action="disable PIN protection?", + reverse=True, + ) + ) if not msg.remove and has_pin: # changing pin - text = Text("Change PIN", ui.ICON_CONFIG) - text.normal("Do you really want to") - text.bold("change your PIN?") - return require_confirm(ctx, text) + return require( + confirm_action( + ctx, + "set_pin", + "Change PIN", + description="Do you really want to", + action="change your PIN?", + reverse=True, + ) + ) if not msg.remove and not has_pin: # setting new pin - text = Text("Enable PIN", ui.ICON_CONFIG) - text.normal("Do you really want to") - text.bold("enable PIN protection?") - return require_confirm(ctx, text) + return require( + confirm_action( + ctx, + "set_pin", + "Enable PIN", + description="Do you really want to", + action="enable PIN protection?", + reverse=True, + ) + ) # removing non-existing PIN raise wire.ProcessError("PIN protection already disabled") diff --git a/core/src/apps/management/recovery_device/layout.py b/core/src/apps/management/recovery_device/layout.py index d8f6d91c6fe..48b7054c56f 100644 --- a/core/src/apps/management/recovery_device/layout.py +++ b/core/src/apps/management/recovery_device/layout.py @@ -5,7 +5,7 @@ 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 require, show_success, show_warning +from trezor.ui.layouts import confirm_action, require, show_success, show_warning from apps.common import button_request from apps.common.confirm import confirm, info_confirm, require_confirm @@ -23,13 +23,25 @@ async def confirm_abort(ctx: wire.GenericContext, dry_run: bool = False) -> bool: if dry_run: - text = Text("Abort seed check", ui.ICON_WIPE) - text.normal("Do you really want to", "abort the seed check?") + return await confirm_action( + ctx, + "abort_recovery", + "Abort seed check", + description="Do you really want to abort the seed check?", + icon=ui.ICON_WIPE, + br_code=ButtonRequestType.ProtectCall, + ) else: - text = Text("Abort recovery", ui.ICON_WIPE) - text.normal("Do you really want to", "abort the recovery", "process?") - text.bold("All progress will be lost.") - return await confirm(ctx, text, code=ButtonRequestType.ProtectCall) + return await confirm_action( + ctx, + "abort_recovery", + "Abort recovery", + description="Do you really want to abort the recovery process?", + action="All progress will be lost.", + reverse=True, + icon=ui.ICON_WIPE, + br_code=ButtonRequestType.ProtectCall, + ) async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int: diff --git a/core/src/apps/management/reset_device/layout.py b/core/src/apps/management/reset_device/layout.py index 0cb1e5427fa..166c3696a95 100644 --- a/core/src/apps/management/reset_device/layout.py +++ b/core/src/apps/management/reset_device/layout.py @@ -9,9 +9,15 @@ from trezor.ui.components.tt.num_input import NumInput from trezor.ui.components.tt.scroll import Paginated from trezor.ui.components.tt.text import Text -from trezor.ui.layouts import require, show_success +from trezor.ui.layouts import ( + confirm_action, + confirm_hex, + require, + show_success, + show_warning, +) -from apps.common.confirm import confirm, require_confirm, require_hold_to_confirm +from apps.common.confirm import confirm, require_hold_to_confirm if False: from trezor import loop @@ -22,11 +28,18 @@ async def show_internal_entropy(ctx, entropy: bytes): - entropy_str = ubinascii.hexlify(entropy).decode() - lines = utils.chunks(entropy_str, 16) - text = Text("Internal entropy", ui.ICON_RESET) - text.mono(*lines) - await require_confirm(ctx, text, ButtonRequestType.ResetDevice) + await require( + confirm_hex( + ctx, + "entropy", + "Internal entropy", + data=ubinascii.hexlify(entropy).decode(), + icon=ui.ICON_RESET, + icon_color=ui.ORANGE_ICON, + width=16, + br_code=ButtonRequestType.ResetDevice, + ) + ) async def _show_share_words(ctx, share_words, share_index=None, group_index=None): @@ -186,34 +199,38 @@ async def _show_confirmation_success( async def _show_confirmation_failure(ctx, share_index): if share_index is None: - text = Text("Recovery seed", ui.ICON_WRONG, ui.RED) + header = "Recovery seed" else: - text = Text("Recovery share #%s" % (share_index + 1), ui.ICON_WRONG, ui.RED) - text.bold("That is the wrong word.") - text.normal("Please check again.") - await require_confirm( - ctx, text, ButtonRequestType.ResetDevice, confirm="Check again", cancel=None + header = "Recovery share #%s" % (share_index + 1) + await require( + show_warning( + ctx, + "warning_backup_check", + header=header, + subheader="That is the wrong word.", + content="Please check again.", + button="Check again", + br_code=ButtonRequestType.ResetDevice, + ) ) async def show_backup_warning(ctx, slip39=False): - text = Text("Caution", ui.ICON_NOCOPY) if slip39: - text.normal( - "Never make a digital", - "copy of your recovery", - "shares and never upload", - "them online!", - ) + description = "Never make a digital copy of your recovery shares and never upload them online!" else: - text.normal( - "Never make a digital", - "copy of your recovery", - "seed and never upload", - "it online!", + description = "Never make a digital copy of your recovery seed and never upload\nit online!" + await require( + confirm_action( + ctx, + "backup_warning", + "Caution", + description=description, + verb="I understand", + verb_cancel=None, + icon=ui.ICON_NOCOPY, + br_code=ButtonRequestType.ResetDevice, ) - await require_confirm( - ctx, text, ButtonRequestType.ResetDevice, "I understand", cancel=None ) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index 019ef883065..d013b933f90 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -62,6 +62,7 @@ async def confirm_action( title: str, action: str = None, description: str = None, + description_param: str = None, verb: Union[str, bytes, None] = Confirm.DEFAULT_CONFIRM, verb_cancel: Union[str, bytes, None] = Confirm.DEFAULT_CANCEL, hold: bool = False, @@ -80,7 +81,9 @@ async def confirm_action( ) if reverse and description is not None: - text.normal(description) + text.format_parametrized( + description, description_param if description_param is not None else "" + ) elif action is not None: text.bold(action) @@ -92,7 +95,9 @@ async def confirm_action( if reverse and action is not None: text.bold(action) elif description is not None: - text.normal(description) + text.format_parametrized( + description, description_param if description_param is not None else "" + ) cls = HoldToConfirm if hold else Confirm return is_confirmed( @@ -203,10 +208,12 @@ def _split_address(address: str) -> Iterator[str]: return chunks(address, MONO_CHARS_PER_LINE) -def _hex_lines(hex_data: str, lines: int = TEXT_MAX_LINES) -> Iterator[str]: - if len(hex_data) >= MONO_HEX_PER_LINE * lines: - hex_data = hex_data[: (MONO_HEX_PER_LINE * lines - 3)] + "..." - return chunks(hex_data, MONO_HEX_PER_LINE) +def _hex_lines( + hex_data: str, lines: int = TEXT_MAX_LINES, width: int = MONO_HEX_PER_LINE +) -> Iterator[str]: + if len(hex_data) >= width * lines: + hex_data = hex_data[: (width * lines - 3)] + "..." + return chunks(hex_data, width) def _show_address( @@ -370,14 +377,16 @@ def show_warning( ctx: wire.GenericContext, br_type: str, content: str, + header: str = "Warning", subheader: Optional[str] = None, button: str = "Try again", + br_code: EnumTypeButtonRequestType = ButtonRequestType.Warning, ) -> Awaitable[bool]: return _show_modal( ctx, br_type=br_type, - br_code=ButtonRequestType.Warning, - header="Warning", + br_code=br_code, + header=header, subheader=subheader, content=content, button_confirm=button, @@ -450,9 +459,10 @@ async def confirm_hex( br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign + width: int = MONO_HEX_PER_LINE, ) -> bool: text = Text(title, icon, icon_color) - text.mono(*_hex_lines(data)) + text.mono(*_hex_lines(data, width=width)) return is_confirmed(await interact(ctx, Confirm(text), br_type, br_code)) diff --git a/tests/common.py b/tests/common.py index 12072691798..ada85e6f265 100644 --- a/tests/common.py +++ b/tests/common.py @@ -175,7 +175,7 @@ def input_flow(): debug.press_yes() -def read_and_confirm_mnemonic(debug, words): +def read_and_confirm_mnemonic(debug, words, choose_wrong=False): """Read a given number of mnemonic words from Trezor T screen and correctly answer confirmation questions. Return the full mnemonic. @@ -201,7 +201,11 @@ def input_flow(): # check share for _ in range(3): index = debug.read_reset_word_pos() - debug.input(mnemonic[index]) + if choose_wrong: + debug.input(mnemonic[(index + 1) % len(mnemonic)]) + return None + else: + debug.input(mnemonic[index]) return " ".join(mnemonic) diff --git a/tests/device_tests/test_msg_resetdevice_bip39_t2.py b/tests/device_tests/test_msg_resetdevice_bip39_t2.py index d79c81212cf..12b60f1872b 100644 --- a/tests/device_tests/test_msg_resetdevice_bip39_t2.py +++ b/tests/device_tests/test_msg_resetdevice_bip39_t2.py @@ -216,6 +216,91 @@ def input_flow(): assert resp.pin_protection is True assert resp.passphrase_protection is True + @pytest.mark.setup_client(uninitialized=True) + def test_reset_failed_check(self, client): + mnemonic = None + strength = 256 # 24 words + + def input_flow(): + nonlocal mnemonic + # 1. Confirm Reset + # 2. Backup your seed + # 3. Confirm warning + yield from click_through(client.debug, screens=3, code=B.ResetDevice) + + # mnemonic phrases, wrong answer + btn_code = yield + assert btn_code == B.ResetDevice + mnemonic = read_and_confirm_mnemonic( + client.debug, words=24, choose_wrong=True + ) + + # warning screen + btn_code = yield + assert btn_code == B.ResetDevice + client.debug.press_yes() + + # mnemonic phrases + btn_code = yield + assert btn_code == B.ResetDevice + mnemonic = read_and_confirm_mnemonic(client.debug, words=24) + + # confirm recovery seed check + btn_code = yield + assert btn_code == B.Success + client.debug.press_yes() + + # confirm success + btn_code = yield + assert btn_code == B.Success + client.debug.press_yes() + + os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY) + with mock.patch("os.urandom", os_urandom), client: + client.set_expected_responses( + [ + proto.ButtonRequest(code=B.ResetDevice), + proto.EntropyRequest(), + proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.Success), + proto.ButtonRequest(code=B.Success), + proto.Success, + proto.Features, + ] + ) + client.set_input_flow(input_flow) + + # PIN, passphrase, display random + device.reset( + client, + display_random=False, + strength=strength, + passphrase_protection=False, + pin_protection=False, + label="test", + language="en-US", + ) + + # generate mnemonic locally + internal_entropy = client.debug.state().reset_entropy + entropy = generate_entropy(strength, internal_entropy, EXTERNAL_ENTROPY) + expected_mnemonic = Mnemonic("english").to_mnemonic(entropy) + + # Compare that device generated proper mnemonic for given entropies + assert mnemonic == expected_mnemonic + + # Check if device is properly initialized + resp = client.call_raw(proto.Initialize()) + assert resp.initialized is True + assert resp.needs_backup is False + assert resp.pin_protection is False + assert resp.passphrase_protection is False + assert resp.backup_type is proto.BackupType.Bip39 + @pytest.mark.setup_client(uninitialized=True) def test_failed_pin(self, client): # external_entropy = b'zlutoucky kun upel divoke ody' * 2 diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 3c2799c60df..4112102dbbf 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -67,20 +67,20 @@ "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_amount_is_too_large]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_has_non_staking_path]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_with_multiple_chunks[large_transaction_to_be_-377cb9ff": "4a46dd8ac42295d853646a55ca1b85023b2235af6155be663b1de10a6c98def2", -"test_autolock.py::test_apply_auto_lock_delay": "38c720e0d29b7487060f2a0f8d256a5e5b4f735511e049c43f6ea62e560603ae", +"test_autolock.py::test_apply_auto_lock_delay": "de8ddc62320c82938920fa6b7d95cb571bd7fa766b192549971a975e24cc0b34", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[0]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[1]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[4194304]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[536871]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[9]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", -"test_autolock.py::test_apply_auto_lock_delay_valid[10]": "a751228f82166c107a8e8872919e2b010ef3079763adc473066e7a3ada36f864", -"test_autolock.py::test_apply_auto_lock_delay_valid[123]": "caf130bf5fa66fa5ac17432689046c1b6cd8b6a495bac3abef3c414d89b81e3f", -"test_autolock.py::test_apply_auto_lock_delay_valid[3601]": "b2a9a7f3e50afb04fb174627a07b939284aa0acc8b3b53af56f75a35ff1b32c9", -"test_autolock.py::test_apply_auto_lock_delay_valid[536870]": "ca2b4707227cc15089f4d6ba710706d2d5c270f19a4668c09f04f175143b202e", -"test_autolock.py::test_apply_auto_lock_delay_valid[60]": "af8d06c92fc5f9aad5685bf56e59b26ec44418a6174ff73db69803f23785802a", -"test_autolock.py::test_apply_auto_lock_delay_valid[7227]": "437cc6b0780d482a835c23f0935683c40177ae4b0ff31da4fc99eba603545ffe", -"test_autolock.py::test_autolock_cancels_ui": "bb4776bfea145528544554b11bdf13ae99f63a371e8eb0885b0a9bd5b608e027", -"test_autolock.py::test_autolock_default_value": "b9f4cd94638f5f8f4c02026b0ccaee89b42406ab63ce7fcef5c9164467de939b", +"test_autolock.py::test_apply_auto_lock_delay_valid[10]": "1c4d26c24483449997b79f385e2a6eab000dfca98bfe8f3c08e7f007370fca1c", +"test_autolock.py::test_apply_auto_lock_delay_valid[123]": "8c9f07104463543083dfb461810a5fc8727e3a2c09644b4b3e423a43970a72c7", +"test_autolock.py::test_apply_auto_lock_delay_valid[3601]": "941179a4f674b1c3d7004cc7073a29b327c70246eb853aa29f31e2e261f329e6", +"test_autolock.py::test_apply_auto_lock_delay_valid[536870]": "dd430048994310dee37453ae03dfe9c1e15c2fc2ce952dd755bf5df27730907f", +"test_autolock.py::test_apply_auto_lock_delay_valid[60]": "a0e602c8c04eae2dc6dd0768f0a627bcc0327b69d6a9645ecbc3e02f0e4c7908", +"test_autolock.py::test_apply_auto_lock_delay_valid[7227]": "f9e78f3bf23f4808d105da5ae1abc4f25ee550b7590aec0c15fcfb041eeb4413", +"test_autolock.py::test_autolock_cancels_ui": "a63bbf50bb612a87c459b27fe87b38f7b3e984b46c9d68b7604e40667072adc6", +"test_autolock.py::test_autolock_default_value": "e3e6a55dae2c8b152477080002d8b734e1bafe00f26e138294969e2dfd9c596e", "test_basic.py-test_device_id_different": "bc6acd0386b9d009e6550519917d6e08632b3badde0b0cf04c95abe5f773038a", "test_basic.py-test_device_id_same": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", "test_basic.py-test_features": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", @@ -106,11 +106,11 @@ "test_msg_applysettings.py-test_apply_homescreen_toif_fail[TOIf\\x80\\x00\\x80\\x00~\\x00\\x00\\x00-fefdb3aa": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_msg_applysettings.py-test_apply_homescreen_toif_fail[TOIf\\x90\\x00\\x90\\x00~XXXf\\x90\\x00-4f4d817c": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_msg_applysettings.py-test_apply_homescreen_toif_fail[TOIg\\x90\\x00\\x90\\x00~\\x00\\x00\\x00-63ffc926": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", -"test_msg_applysettings.py-test_apply_settings": "8f9f6013bb8a44fda279e9c7d091328fd7ccb39222a02bee701918528355083a", +"test_msg_applysettings.py-test_apply_settings": "9394ea770233a5679c6292c7afe75f4d3528f1b1576f47e6bef8ede6276eec8b", "test_msg_applysettings.py-test_apply_settings_passphrase": "40de0143b32b5d06ece43d47be27bb91499f0c2417754ddb8e9e03ff41a7f6d4", "test_msg_applysettings.py-test_apply_settings_passphrase_on_device": "3e6527e227bdde54f51bc9c417b176d0d87fdb6c40c4761368f50eb201b4beed", -"test_msg_applysettings.py-test_apply_settings_rotation": "6f0fa323dd2c82d01994273c023d3ed5e43d43c9c562664a10266f4a7f7ba4cc", -"test_msg_applysettings.py-test_experimental_features": "3127d41bd8615097295b917110ece9dd364986809288c7f958ff71d52106e346", +"test_msg_applysettings.py-test_apply_settings_rotation": "5789f4a9274b8a37af5b279296cf13a95be6e528738474a1c64d36bb0692e216", +"test_msg_applysettings.py-test_experimental_features": "388d5f0ee2e8c521fd82bf8687bd5bdba26ed34695d2a1aecb28f6a72df8df4c", "test_msg_applysettings.py-test_label_too_long": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", "test_msg_applysettings.py-test_safety_checks": "4d37de3654678b7f16643cf4ef912a0bced545e0e0526e41ea0c9b820560344e", "test_msg_authorize_coinjoin.py::test_cancel_authorization": "d8a608beb6165f5667cc44dcff6bdc17ebb4638ddd3bd09e7f0e1e75d1e21135", @@ -365,6 +365,7 @@ "test_msg_resetdevice_bip39_t2.py-test_reset_device": "5f1b6cdc46e416430df1afd114bceda57fb644108d594ce1f466460ba4917b41", "test_msg_resetdevice_bip39_t2.py-test_reset_device_192": "d304d9902accbe23af2dcf73758a2e81b00da17e89f754ca83f209bac8eb8ee1", "test_msg_resetdevice_bip39_t2.py-test_reset_device_pin": "1e4cef983f25b66931db5a8286c83f8faa3e09eb865896c7e946d2c8992e6d28", +"test_msg_resetdevice_bip39_t2.py-test_reset_failed_check": "b07b90b0c33200f431f1464750cf0934e558ae80a25ecb013c9ce652997df70c", "test_msg_resetdevice_slip39_advanced.py-test_reset_device_slip39_advanced": "92dde100ab37934b7c39a9bfff15a4e73419b4f029c377f25f0b428c6e4a005c", "test_msg_resetdevice_slip39_basic.py-test_reset_device_slip39_basic": "650ebacd885fe8c34237aceb835827472a845d8951f2ee573d94c97030603db3", "test_msg_resetdevice_slip39_basic.py-test_reset_device_slip39_basic_256": "aa7f19f34dafbd5bf205858cd8f694f3278b309a3c8837e6596759d6a1066634", @@ -586,7 +587,7 @@ "test_msg_verifymessage_segwit_native.py-test_message_verify": "3aeca0b02254b83988008b5129812a749f320add09146d189fa294f2b5c80c34", "test_msg_verifymessage_segwit_native.py-test_verify_utf": "62d12291ee0f0d4639d861ea61d55c9944c37aad24bd70dd35877e9d12a2b731", "test_msg_webauthn.py::test_add_remove": "a9cdefeb089f197427257e097d07179b23de4fcad4ee91af0191ed767f80577c", -"test_msg_wipedevice.py::test_autolock_not_retained": "b519a86b80151e5c1a4849fc5d6e8e6c8f2953c80befee8dd05f7caec254b464", +"test_msg_wipedevice.py::test_autolock_not_retained": "b07784bf0739fcc5d5877f2a1670b88e10019346c56f9864b48d5cb56944b07b", "test_msg_wipedevice.py::test_wipe_device": "bc6acd0386b9d009e6550519917d6e08632b3badde0b0cf04c95abe5f773038a", "test_multisig.py-test_15_of_15": "9d1799a199b45785ac69ae6af715c251b10aebb60f981c9b73d78e53e1a91374", "test_multisig.py-test_2_of_3": "2be92556edf4ff8eed340d535f379ee6915eae34fef25d669ce865848e7b4705", From a1629edead6e788046f2196c65cb3e042900e8de Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Fri, 12 Feb 2021 23:15:10 +0100 Subject: [PATCH 06/13] refactor(core): convert parts of apps.webauthn to layouts --- .../apps/webauthn/add_resident_credential.py | 20 ++++++++++--------- .../webauthn/list_resident_credentials.py | 18 ++++++++--------- core/src/trezor/ui/layouts/tt.py | 3 ++- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/core/src/apps/webauthn/add_resident_credential.py b/core/src/apps/webauthn/add_resident_credential.py index 08135cbe4db..822f26f0979 100644 --- a/core/src/apps/webauthn/add_resident_credential.py +++ b/core/src/apps/webauthn/add_resident_credential.py @@ -1,8 +1,8 @@ import storage.device -from trezor import ui, wire +from trezor import wire from trezor.messages.Success import Success from trezor.messages.WebAuthnAddResidentCredential import WebAuthnAddResidentCredential -from trezor.ui.components.tt.text import Text +from trezor.ui.layouts import require, show_error from apps.common.confirm import require_confirm @@ -41,14 +41,16 @@ async def add_resident_credential( try: cred = Fido2Credential.from_cred_id(bytes(msg.credential_id), None) except Exception: - text = Text("Import credential", ui.ICON_WRONG, ui.RED) - text.normal( - "The credential you are", - "trying to import does", - "not belong to this", - "authenticator.", + await require( + show_error( + ctx, + "warning_credential", + header="Import credential", + button="Close", + content="The credential you are trying to import does\nnot belong to this authenticator.", + red=True, + ) ) - await require_confirm(ctx, text, confirm=None, cancel="Close") raise wire.ActionCancelled content = ConfirmContent(ConfirmAddCredential(cred)) diff --git a/core/src/apps/webauthn/list_resident_credentials.py b/core/src/apps/webauthn/list_resident_credentials.py index 4b1576232f8..8d7fa405ce1 100644 --- a/core/src/apps/webauthn/list_resident_credentials.py +++ b/core/src/apps/webauthn/list_resident_credentials.py @@ -4,9 +4,7 @@ from trezor.messages.WebAuthnListResidentCredentials import ( WebAuthnListResidentCredentials, ) -from trezor.ui.components.tt.text import Text - -from apps.common.confirm import require_confirm +from trezor.ui.layouts import confirm_action, require from . import resident_credentials @@ -14,14 +12,14 @@ async def list_resident_credentials( ctx: wire.Context, msg: WebAuthnListResidentCredentials ) -> WebAuthnCredentials: - text = Text("List credentials") - text.normal( - "Do you want to export", - "information about the", - "resident credentials", - "stored on this device?", + await require( + confirm_action( + ctx, + "credentials_list", + title="List credentials", + description="Do you want to export information about the resident credentials stored on this device?", + ) ) - await require_confirm(ctx, text) creds = [ WebAuthnCredential( index=cred.index, diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index d013b933f90..d06b3990b1e 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -358,6 +358,7 @@ def show_error( header: str = "Error", subheader: Optional[str] = None, button: str = "Close", + red: bool = False, ) -> Awaitable[bool]: return _show_modal( ctx, @@ -369,7 +370,7 @@ def show_error( button_confirm=None, button_cancel=button, icon=ui.ICON_WRONG, - icon_color=ui.ORANGE_ICON, + icon_color=ui.RED if red else ui.ORANGE_ICON, ) From ad44a5a14dd382935452a2b8494d41ba144b2f5b Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Sat, 13 Feb 2021 01:10:10 +0100 Subject: [PATCH 07/13] refactor(core): convert apps.common.request_pin to layouts --- core/src/apps/common/request_pin.py | 28 ++++++++++++------- tests/device_tests/test_msg_changepin_t2.py | 30 ++++++++++++++++++++- tests/ui_tests/fixtures.json | 1 + 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/core/src/apps/common/request_pin.py b/core/src/apps/common/request_pin.py index 34078677455..e81e25c13c2 100644 --- a/core/src/apps/common/request_pin.py +++ b/core/src/apps/common/request_pin.py @@ -123,18 +123,26 @@ async def verify_user_pin( async def error_pin_invalid(ctx: wire.Context) -> NoReturn: - from apps.common.confirm import confirm - - text = Text("Wrong PIN", ui.ICON_WRONG, ui.RED) - text.normal("The PIN you entered is", "invalid.") - await confirm(ctx, text, confirm=None, cancel="Close") + from trezor.ui.layouts import show_error + + await show_error( + ctx, + "warning_wrong_pin", + header="Wrong PIN", + content="The PIN you entered is invalid.", + red=True, + ) raise wire.PinInvalid async def error_pin_matches_wipe_code(ctx: wire.Context) -> NoReturn: - from apps.common.confirm import confirm - - text = Text("Invalid PIN", ui.ICON_WRONG, ui.RED) - text.normal("The new PIN must be", "different from your", "wipe code.") - await confirm(ctx, text, confirm=None, cancel="Close") + from trezor.ui.layouts import show_error + + await show_error( + ctx, + "warning_invalid_new_pin", + header="Invalid PIN", + content="The new PIN must be different from your\nwipe code.", + red=True, + ) raise wire.PinInvalid diff --git a/tests/device_tests/test_msg_changepin_t2.py b/tests/device_tests/test_msg_changepin_t2.py index 723378f2a02..903d7ae5d93 100644 --- a/tests/device_tests/test_msg_changepin_t2.py +++ b/tests/device_tests/test_msg_changepin_t2.py @@ -18,7 +18,7 @@ from trezorlib import btc, device, messages from trezorlib.client import MAX_PIN_LENGTH, PASSPHRASE_TEST_PATH -from trezorlib.exceptions import Cancelled +from trezorlib.exceptions import Cancelled, TrezorFailure PIN4 = "1234" PIN60 = "789456" * 10 @@ -172,3 +172,31 @@ def input_flow(): client.init_device() assert client.features.pin_protection is True _check_pin(client, PIN4) + + +@pytest.mark.setup_client(pin=PIN4) +def test_change_invalid_current(client): + assert client.features.pin_protection is True + + # Check current PIN value + _check_pin(client, PIN4) + + # Let's set new PIN + def input_flow(): + yield # do you want to change pin? + client.debug.press_yes() + yield # enter wrong current pin + client.debug.input(PIN60) + yield + client.debug.press_no() + + with client, pytest.raises(TrezorFailure): + client.set_expected_responses([messages.ButtonRequest] * 3 + [messages.Failure]) + client.set_input_flow(input_flow) + + device.change_pin(client) + + # Check that there's still old PIN protection + client.init_device() + assert client.features.pin_protection is True + _check_pin(client, PIN4) diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 4112102dbbf..6ffa771451a 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -136,6 +136,7 @@ "test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "3f29784c14941de5fe0595780e09bd68830e3d95c981cc99e4ebd1418f875748", "test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "65d64d32e4fcc694e95e675bc2b4fbbf2d967cfc8f8fc852aeadbf58acefacf9", "test_msg_changepin_t2.py::test_change_failed": "63c1d90ce9bd9d7fdd627ce8b70b60518b373e235dcdb6713a1b5bc020e4466d", +"test_msg_changepin_t2.py::test_change_invalid_current": "25469fa710ef37444b2b99efda2527541087ae36926d6a95cc6dd3553a8effb7", "test_msg_changepin_t2.py::test_change_pin": "4cdff56add70b77cd901654fcdef6098cd38214567060c870865ee697efa6df5", "test_msg_changepin_t2.py::test_remove_pin": "64701aa15082e4d3f9639799c9d12c129dd60d1aed5f7203bfac2fd3665d1d19", "test_msg_changepin_t2.py::test_set_failed": "6ad935b038f00177fea7e7221204ca13b188ff2eb2e699b367592d1567c5bcd6", From f8874afde892a3fbe5abc92c8567ae2464557c21 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Mon, 15 Feb 2021 23:19:34 +0100 Subject: [PATCH 08/13] refactor(core): convert parts of apps.monero to layouts --- core/src/apps/monero/layout/confirms.py | 64 ++++++++++++++++++------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/core/src/apps/monero/layout/confirms.py b/core/src/apps/monero/layout/confirms.py index d72be6f055d..a4cf85e25b9 100644 --- a/core/src/apps/monero/layout/confirms.py +++ b/core/src/apps/monero/layout/confirms.py @@ -3,6 +3,7 @@ from trezor import ui, wire from trezor.messages import ButtonRequestType from trezor.ui.components.tt.text import Text +from trezor.ui.layouts import confirm_action, require from trezor.ui.popup import Popup from trezor.utils import chunks @@ -22,34 +23,63 @@ async def require_confirm_watchkey(ctx): - content = Text("Confirm export", ui.ICON_SEND, ui.GREEN) - content.normal("Do you really want to", "export watch-only", "credentials?") - await require_confirm(ctx, content, ButtonRequestType.SignTx) + await require( + confirm_action( + ctx, + "get_watchkey", + "Confirm export", + description="Do you really want to export watch-only credentials?", + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, + ) + ) async def require_confirm_keyimage_sync(ctx): - content = Text("Confirm ki sync", ui.ICON_SEND, ui.GREEN) - content.normal("Do you really want to", "sync key images?") - await require_confirm(ctx, content, ButtonRequestType.SignTx) + await require( + confirm_action( + ctx, + "key_image_sync", + "Confirm ki sync", + description="Do you really want to\nsync key images?", + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, + ) + ) async def require_confirm_live_refresh(ctx): - content = Text("Confirm refresh", ui.ICON_SEND, ui.GREEN) - content.normal("Do you really want to", "start refresh?") - await require_confirm(ctx, content, ButtonRequestType.SignTx) + await require( + confirm_action( + ctx, + "live_refresh", + "Confirm refresh", + description="Do you really want to\nstart refresh?", + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, + ) + ) async def require_confirm_tx_key(ctx, export_key=False): - content = Text("Confirm export", ui.ICON_SEND, ui.GREEN) - txt = ["Do you really want to"] if export_key: - txt.append("export tx_key?") + description = "Do you really want to export tx_key?" else: - txt.append("export tx_der") - txt.append("for tx_proof?") - - content.normal(*txt) - await require_confirm(ctx, content, ButtonRequestType.SignTx) + description = "Do you really want to export tx_der\nfor tx_proof?" + await require( + confirm_action( + ctx, + "export_tx_key", + "Confirm export", + description=description, + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, + ) + ) async def require_confirm_transaction( From 5d05e454a4bf98b82a34fbb7c52136c268e0183c Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 17 Feb 2021 13:53:33 +0100 Subject: [PATCH 09/13] refactor(core): no implicit spaces in render_text --- core/src/apps/common/signverify.py | 3 +-- core/src/apps/ethereum/layout.py | 6 ++--- .../management/recovery_device/__init__.py | 4 +-- core/src/apps/nem/layout.py | 3 +-- core/src/trezor/ui/components/common/text.py | 9 +++---- core/src/trezor/ui/layouts/tt.py | 2 +- tests/click_tests/test_autolock.py | 2 +- tests/ui_tests/fixtures.json | 26 +++++++++---------- 8 files changed, 25 insertions(+), 30 deletions(-) diff --git a/core/src/apps/common/signverify.py b/core/src/apps/common/signverify.py index 88f22dc7e2e..79cfdf37408 100644 --- a/core/src/apps/common/signverify.py +++ b/core/src/apps/common/signverify.py @@ -47,9 +47,8 @@ async def require_confirm_verify_message( ctx: wire.Context, address: str, coin: str, message: bytes ) -> None: header = "Verify {} message".format(coin) - text = Text(header, new_lines=False) + text = Text(header) text.bold("Confirm address:") - text.br() text.mono(*split_address(address)) await require_confirm(ctx, text) diff --git a/core/src/apps/ethereum/layout.py b/core/src/apps/ethereum/layout.py index 11dc5555da6..e4872d67391 100644 --- a/core/src/apps/ethereum/layout.py +++ b/core/src/apps/ethereum/layout.py @@ -20,7 +20,7 @@ async def require_confirm_tx(ctx, to_bytes, value, chain_id, token=None, tx_type to_str = "new contract?" text = Text("Confirm sending", ui.ICON_SEND, ui.GREEN, new_lines=False) text.bold(format_ethereum_amount(value, token, chain_id, tx_type)) - text.normal(ui.GREY, "to", ui.FG) + text.normal(ui.GREY, " to ", ui.FG) for to_line in split_address(to_str): text.br() text.mono(to_line) @@ -33,9 +33,9 @@ async def require_confirm_fee( ): text = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN, new_lines=False) text.bold(format_ethereum_amount(spending, token, chain_id, tx_type)) - text.normal(ui.GREY, "Gas price:", ui.FG) + text.normal(" ", ui.GREY, "Gas price:", ui.FG) text.bold(format_ethereum_amount(gas_price, None, chain_id, tx_type)) - text.normal(ui.GREY, "Maximum fee:", ui.FG) + text.normal(" ", ui.GREY, "Maximum fee:", ui.FG) text.bold(format_ethereum_amount(gas_price * gas_limit, None, chain_id, tx_type)) await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx) diff --git a/core/src/apps/management/recovery_device/__init__.py b/core/src/apps/management/recovery_device/__init__.py index 2d2c973e6f4..5be59a72d5c 100644 --- a/core/src/apps/management/recovery_device/__init__.py +++ b/core/src/apps/management/recovery_device/__init__.py @@ -99,9 +99,9 @@ async def _continue_dialog(ctx: wire.Context, msg: RecoveryDevice) -> None: text.br_half() text.normal("By continuing you agree") text.br() - text.normal("to") + text.normal("to ") text.bold("https://trezor.io/tos") else: text = Text("Seed check", ui.ICON_RECOVERY, new_lines=False) - text.normal("Do you really want to", "check the recovery", "seed?") + text.normal("Do you really want to check the recovery seed?") await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall) diff --git a/core/src/apps/nem/layout.py b/core/src/apps/nem/layout.py index 932b9e7496f..564600db226 100644 --- a/core/src/apps/nem/layout.py +++ b/core/src/apps/nem/layout.py @@ -9,9 +9,8 @@ async def require_confirm_text(ctx, action: str): - content = action.split(" ") text = Text("Confirm action", ui.ICON_SEND, ui.GREEN, new_lines=False) - text.normal(*content) + text.normal(action) await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) diff --git a/core/src/trezor/ui/components/common/text.py b/core/src/trezor/ui/components/common/text.py index 2a5dab47296..e49d6401a07 100644 --- a/core/src/trezor/ui/components/common/text.py +++ b/core/src/trezor/ui/components/common/text.py @@ -243,8 +243,6 @@ def render_text( else: fg = item - SPACE = ui.display.text_width(" ", font) - for item_index in range(item_offset, len(items)): # load current item item = items[item_index] @@ -261,7 +259,6 @@ def render_text( elif item in _FONTS: # change of font style font = item - SPACE = ui.display.text_width(" ", font) else: # change of foreground color fg = item @@ -278,7 +275,7 @@ def render_text( ): offset_y += TEXT_LINE_HEIGHT ui.display.text(INITIAL_OFFSET_X, offset_y, item, font, fg, bg) - offset_x = INITIAL_OFFSET_X + item_width + SPACE + offset_x = INITIAL_OFFSET_X + item_width continue span.reset( @@ -320,7 +317,7 @@ def render_text( offset_y += TEXT_LINE_HEIGHT elif span.width > 0: # only advance cursor if we actually rendered anything - offset_x += span.width + SPACE + offset_x += span.width if __debug__: @@ -415,7 +412,7 @@ def format_parametrized( parts = format_string.split("{}", len(params)) for i in range(len(parts)): self.content.append(font) - self.content.append(parts[i].strip(" ")) + self.content.append(parts[i]) if i < len(parts) - 1 and i < len(params): param = params[i] self.content.append(param_font) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index d06b3990b1e..7d5b97978c1 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -132,7 +132,7 @@ async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> bool: text.br_half() text.normal("By continuing you agree") text.br() - text.normal("to") + text.normal("to ") text.bold("https://trezor.io/tos") return is_confirmed( await interact( diff --git a/tests/click_tests/test_autolock.py b/tests/click_tests/test_autolock.py index 3ce4e52ee20..4e1efb75936 100644 --- a/tests/click_tests/test_autolock.py +++ b/tests/click_tests/test_autolock.py @@ -46,7 +46,7 @@ def set_autolock_delay(device_handler, delay_ms): debug.input("1234") layout = debug.wait_layout() - assert f"auto-lock your device after {delay_ms // 1000} seconds" in layout.text + assert f"auto-lock your device after {delay_ms // 1000} seconds" in layout.text debug.click(buttons.OK) layout = debug.wait_layout() diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 6ffa771451a..cdc86519b63 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -67,20 +67,20 @@ "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_amount_is_too_large]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_has_non_staking_path]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_with_multiple_chunks[large_transaction_to_be_-377cb9ff": "4a46dd8ac42295d853646a55ca1b85023b2235af6155be663b1de10a6c98def2", -"test_autolock.py::test_apply_auto_lock_delay": "de8ddc62320c82938920fa6b7d95cb571bd7fa766b192549971a975e24cc0b34", +"test_autolock.py::test_apply_auto_lock_delay": "d6b79c89a21e8979a6cede0715ea3d83a5c786ae7445dad797cf668ed6bf6180", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[0]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[1]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[4194304]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[536871]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[9]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", -"test_autolock.py::test_apply_auto_lock_delay_valid[10]": "1c4d26c24483449997b79f385e2a6eab000dfca98bfe8f3c08e7f007370fca1c", -"test_autolock.py::test_apply_auto_lock_delay_valid[123]": "8c9f07104463543083dfb461810a5fc8727e3a2c09644b4b3e423a43970a72c7", -"test_autolock.py::test_apply_auto_lock_delay_valid[3601]": "941179a4f674b1c3d7004cc7073a29b327c70246eb853aa29f31e2e261f329e6", -"test_autolock.py::test_apply_auto_lock_delay_valid[536870]": "dd430048994310dee37453ae03dfe9c1e15c2fc2ce952dd755bf5df27730907f", -"test_autolock.py::test_apply_auto_lock_delay_valid[60]": "a0e602c8c04eae2dc6dd0768f0a627bcc0327b69d6a9645ecbc3e02f0e4c7908", -"test_autolock.py::test_apply_auto_lock_delay_valid[7227]": "f9e78f3bf23f4808d105da5ae1abc4f25ee550b7590aec0c15fcfb041eeb4413", -"test_autolock.py::test_autolock_cancels_ui": "a63bbf50bb612a87c459b27fe87b38f7b3e984b46c9d68b7604e40667072adc6", -"test_autolock.py::test_autolock_default_value": "e3e6a55dae2c8b152477080002d8b734e1bafe00f26e138294969e2dfd9c596e", +"test_autolock.py::test_apply_auto_lock_delay_valid[10]": "f720426dd1f7038af5af0a96e4bcf60498aab1414f2f2828b57bfdb0e3d9a73e", +"test_autolock.py::test_apply_auto_lock_delay_valid[123]": "0362fad4d8a7aec2d0227f78f8b919a903343a3397a8442ad73bc8665a51bac8", +"test_autolock.py::test_apply_auto_lock_delay_valid[3601]": "50672aa321fbde95e976f00ae8bb329712a828af4d1121563a25a57593fc4cea", +"test_autolock.py::test_apply_auto_lock_delay_valid[536870]": "1184da463fcb4210f7fb7421332d980dad73374902b3bfc5e03e0054ff99c09b", +"test_autolock.py::test_apply_auto_lock_delay_valid[60]": "f6a45cc230422d938b1f47a7f3ffa3cbff5809b79d8f009860a28caa37b4d0bc", +"test_autolock.py::test_apply_auto_lock_delay_valid[7227]": "aec1624c900d995f114515b72a1dbcd4aea46345f0ac4bedeaf647875b063b44", +"test_autolock.py::test_autolock_cancels_ui": "cd8018fe8a79cdb701ff6468a7c43d5dd12ff1d47804320244a414061e805263", +"test_autolock.py::test_autolock_default_value": "8e4e95d6b18440bf42712c667ba3b88b55f66d02a6205bb28bb2e01b64c96377", "test_basic.py-test_device_id_different": "bc6acd0386b9d009e6550519917d6e08632b3badde0b0cf04c95abe5f773038a", "test_basic.py-test_device_id_same": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", "test_basic.py-test_features": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", @@ -106,11 +106,11 @@ "test_msg_applysettings.py-test_apply_homescreen_toif_fail[TOIf\\x80\\x00\\x80\\x00~\\x00\\x00\\x00-fefdb3aa": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_msg_applysettings.py-test_apply_homescreen_toif_fail[TOIf\\x90\\x00\\x90\\x00~XXXf\\x90\\x00-4f4d817c": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_msg_applysettings.py-test_apply_homescreen_toif_fail[TOIg\\x90\\x00\\x90\\x00~\\x00\\x00\\x00-63ffc926": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", -"test_msg_applysettings.py-test_apply_settings": "9394ea770233a5679c6292c7afe75f4d3528f1b1576f47e6bef8ede6276eec8b", +"test_msg_applysettings.py-test_apply_settings": "63f5bb37fce2d6bafee3d55bdb78eb38409605e0f603d3987465d2c4903f9ddb", "test_msg_applysettings.py-test_apply_settings_passphrase": "40de0143b32b5d06ece43d47be27bb91499f0c2417754ddb8e9e03ff41a7f6d4", "test_msg_applysettings.py-test_apply_settings_passphrase_on_device": "3e6527e227bdde54f51bc9c417b176d0d87fdb6c40c4761368f50eb201b4beed", -"test_msg_applysettings.py-test_apply_settings_rotation": "5789f4a9274b8a37af5b279296cf13a95be6e528738474a1c64d36bb0692e216", -"test_msg_applysettings.py-test_experimental_features": "388d5f0ee2e8c521fd82bf8687bd5bdba26ed34695d2a1aecb28f6a72df8df4c", +"test_msg_applysettings.py-test_apply_settings_rotation": "b4983c819399cc837b3634e1de7638b6c31c72354fe097ebd0abeebb5cadbea9", +"test_msg_applysettings.py-test_experimental_features": "e92e61f71380804323835b062e328834e9450e86d99aa7be6678edc4e971ebdf", "test_msg_applysettings.py-test_label_too_long": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", "test_msg_applysettings.py-test_safety_checks": "4d37de3654678b7f16643cf4ef912a0bced545e0e0526e41ea0c9b820560344e", "test_msg_authorize_coinjoin.py::test_cancel_authorization": "d8a608beb6165f5667cc44dcff6bdc17ebb4638ddd3bd09e7f0e1e75d1e21135", @@ -588,7 +588,7 @@ "test_msg_verifymessage_segwit_native.py-test_message_verify": "3aeca0b02254b83988008b5129812a749f320add09146d189fa294f2b5c80c34", "test_msg_verifymessage_segwit_native.py-test_verify_utf": "62d12291ee0f0d4639d861ea61d55c9944c37aad24bd70dd35877e9d12a2b731", "test_msg_webauthn.py::test_add_remove": "a9cdefeb089f197427257e097d07179b23de4fcad4ee91af0191ed767f80577c", -"test_msg_wipedevice.py::test_autolock_not_retained": "b07784bf0739fcc5d5877f2a1670b88e10019346c56f9864b48d5cb56944b07b", +"test_msg_wipedevice.py::test_autolock_not_retained": "74e2b2af49f2153886519108b4e11e250a6a5ff76d27da333066af7afc76bb5f", "test_msg_wipedevice.py::test_wipe_device": "bc6acd0386b9d009e6550519917d6e08632b3badde0b0cf04c95abe5f773038a", "test_multisig.py-test_15_of_15": "9d1799a199b45785ac69ae6af715c251b10aebb60f981c9b73d78e53e1a91374", "test_multisig.py-test_2_of_3": "2be92556edf4ff8eed340d535f379ee6915eae34fef25d669ce865848e7b4705", From c5c780d4f88c221a3cd3ce3e4e819558eaddd1c7 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 24 Feb 2021 00:55:23 +0100 Subject: [PATCH 10/13] refactor(core): convert rest of apps.bitcoin to layouts --- core/src/apps/bitcoin/authorize_coinjoin.py | 41 ++++---- core/src/apps/bitcoin/get_ownership_proof.py | 38 +++---- core/src/apps/bitcoin/sign_message.py | 5 +- core/src/apps/bitcoin/sign_tx/layout.py | 6 +- core/src/apps/bitcoin/verify_message.py | 12 ++- core/src/apps/common/layout.py | 50 +--------- core/src/apps/common/signverify.py | 27 +---- core/src/apps/ethereum/sign_message.py | 5 +- core/src/apps/ethereum/verify_message.py | 7 +- core/src/apps/lisk/sign_message.py | 5 +- core/src/apps/lisk/verify_message.py | 7 +- core/src/trezor/ui/components/tt/scroll.py | 58 ++++++++++- core/src/trezor/ui/layouts/tt.py | 100 +++++++++++++++++-- 13 files changed, 219 insertions(+), 142 deletions(-) diff --git a/core/src/apps/bitcoin/authorize_coinjoin.py b/core/src/apps/bitcoin/authorize_coinjoin.py index 2a78b2c7f5e..a978173b4f8 100644 --- a/core/src/apps/bitcoin/authorize_coinjoin.py +++ b/core/src/apps/bitcoin/authorize_coinjoin.py @@ -4,10 +4,9 @@ from trezor.messages.AuthorizeCoinJoin import AuthorizeCoinJoin from trezor.messages.Success import Success from trezor.strings import format_amount -from trezor.ui.components.tt.text import Text +from trezor.ui.layouts import confirm_action, confirm_coinjoin, require from apps.base import set_authorization -from apps.common.confirm import require_confirm, require_hold_to_confirm from apps.common.paths import validate_path from .authorization import FEE_PER_ANONYMITY_DECIMALS, CoinJoinAuthorization @@ -46,30 +45,30 @@ async def authorize_coinjoin(ctx: wire.Context, msg: AuthorizeCoinJoin) -> Succe ), ) - text = Text("Authorize CoinJoin", ui.ICON_RECOVERY) - text.normal("Do you really want to") - text.normal("take part in a CoinJoin") - text.normal("transaction at:") - text.mono(msg.coordinator) - await require_confirm(ctx, text) + await require( + confirm_action( + ctx, + "coinjoin_coordinator", + title="Authorize CoinJoin", + description="Do you really want to take part in a CoinJoin transaction at:\n{}", + description_param=msg.coordinator, + description_param_font=ui.MONO, + icon=ui.ICON_RECOVERY, + ) + ) - text = Text("Authorize CoinJoin", ui.ICON_RECOVERY) + fee_per_anonymity = None if msg.fee_per_anonymity is not None: - text.normal("Fee per anonymity set:") - text.bold( - "{} %".format( - format_amount(msg.fee_per_anonymity, FEE_PER_ANONYMITY_DECIMALS) - ) + fee_per_anonymity = format_amount( + msg.fee_per_anonymity, FEE_PER_ANONYMITY_DECIMALS ) - text.normal("Maximum total fees:") - text.bold( - format_coin_amount( - msg.max_total_fee, - coin, - msg.amount_unit, + await require( + confirm_coinjoin( + ctx, + fee_per_anonymity, + format_coin_amount(msg.max_total_fee, coin, msg.amount_unit), ) ) - await require_hold_to_confirm(ctx, text) set_authorization(CoinJoinAuthorization(msg, keychain, coin)) diff --git a/core/src/apps/bitcoin/get_ownership_proof.py b/core/src/apps/bitcoin/get_ownership_proof.py index 9bd4470dac1..5d46d1c360e 100644 --- a/core/src/apps/bitcoin/get_ownership_proof.py +++ b/core/src/apps/bitcoin/get_ownership_proof.py @@ -3,9 +3,8 @@ from trezor import ui, wire from trezor.messages.GetOwnershipProof import GetOwnershipProof from trezor.messages.OwnershipProof import OwnershipProof -from trezor.ui.components.tt.text import Text +from trezor.ui.layouts import confirm_action, confirm_hex, require -from apps.common.confirm import require_confirm from apps.common.paths import validate_path from . import addresses, common, scripts @@ -65,25 +64,28 @@ async def get_ownership_proof( # In order to set the "user confirmation" bit in the proof, the user must actually confirm. if msg.user_confirmation and not authorization: - text = Text("Proof of ownership", ui.ICON_CONFIG) - text.normal("Do you want to create a") if not msg.commitment_data: - text.normal("proof of ownership?") + await require( + confirm_action( + ctx, + "confirm_ownership_proof", + title="Proof of ownership", + description="Do you want to create a proof of ownership?", + ) + ) else: - hex_data = hexlify(msg.commitment_data).decode() - text.normal("proof of ownership for:") - if len(hex_data) > 3 * _MAX_MONO_LINE: - text.mono(hex_data[0:_MAX_MONO_LINE]) - text.mono( - hex_data[_MAX_MONO_LINE : 3 * _MAX_MONO_LINE // 2 - 1] - + "..." - + hex_data[-3 * _MAX_MONO_LINE // 2 + 2 : -_MAX_MONO_LINE] + await require( + confirm_hex( + ctx, + "confirm_ownership_proof", + title="Proof of ownership", + description="Do you want to create a proof of ownership for:", + data=hexlify(msg.commitment_data).decode(), + icon=ui.ICON_CONFIG, + icon_color=ui.ORANGE_ICON, + truncate_middle=True, ) - text.mono(hex_data[-_MAX_MONO_LINE:]) - else: - text.mono(hex_data) - - await require_confirm(ctx, text) + ) ownership_proof, signature = generate_proof( node, diff --git a/core/src/apps/bitcoin/sign_message.py b/core/src/apps/bitcoin/sign_message.py index ad842ee4471..1acb660ca76 100644 --- a/core/src/apps/bitcoin/sign_message.py +++ b/core/src/apps/bitcoin/sign_message.py @@ -2,9 +2,10 @@ from trezor.crypto.curve import secp256k1 from trezor.messages.InputScriptType import SPENDADDRESS, SPENDP2SHWITNESS, SPENDWITNESS from trezor.messages.MessageSignature import MessageSignature +from trezor.ui.layouts import confirm_signverify, require from apps.common.paths import validate_path -from apps.common.signverify import message_digest, require_confirm_sign_message +from apps.common.signverify import decode_message, message_digest from .addresses import get_address from .keychain import with_keychain @@ -25,7 +26,7 @@ async def sign_message( script_type = msg.script_type or 0 await validate_path(ctx, keychain, address_n) - await require_confirm_sign_message(ctx, coin.coin_shortcut, message) + await require(confirm_signverify(ctx, coin.coin_shortcut, decode_message(message))) node = keychain.derive(address_n) seckey = node.private_key() diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index 98043df8009..1a304dde75a 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -59,9 +59,9 @@ async def confirm_output( layout = layouts.confirm_hex( ctx, "op_return", - "OP_RETURN", - hexlify(data).decode(), - ButtonRequestType.ConfirmOutput, + title="OP_RETURN", + data=hexlify(data).decode(), + br_code=ButtonRequestType.ConfirmOutput, ) else: assert output.address is not None diff --git a/core/src/apps/bitcoin/verify_message.py b/core/src/apps/bitcoin/verify_message.py index 89b24f8f66b..cb6a55b05e2 100644 --- a/core/src/apps/bitcoin/verify_message.py +++ b/core/src/apps/bitcoin/verify_message.py @@ -2,9 +2,10 @@ from trezor.crypto.curve import secp256k1 from trezor.messages.InputScriptType import SPENDADDRESS, SPENDP2SHWITNESS, SPENDWITNESS from trezor.messages.Success import Success +from trezor.ui.layouts import confirm_signverify, require from apps.common import coins -from apps.common.signverify import message_digest, require_confirm_verify_message +from apps.common.signverify import decode_message, message_digest from .addresses import ( address_p2wpkh, @@ -62,8 +63,13 @@ async def verify_message(ctx: wire.Context, msg: VerifyMessage) -> Success: if addr != address: raise wire.ProcessError("Invalid signature") - await require_confirm_verify_message( - ctx, address_short(coin, address), coin.coin_shortcut, message + await require( + confirm_signverify( + ctx, + coin.coin_shortcut, + decode_message(message), + address=address_short(coin, address), + ) ) return Success(message="Message verified") diff --git a/core/src/apps/common/layout.py b/core/src/apps/common/layout.py index 6cd53c548dd..90901456620 100644 --- a/core/src/apps/common/layout.py +++ b/core/src/apps/common/layout.py @@ -3,8 +3,7 @@ from trezor import ui from trezor.messages import ButtonRequestType from trezor.ui.components.tt.button import ButtonDefault -from trezor.ui.components.tt.scroll import Paginated -from trezor.ui.components.tt.text import TEXT_MAX_LINES, Span, Text +from trezor.ui.components.tt.text import Text from trezor.ui.container import Container from trezor.ui.qr import Qr from trezor.utils import chunks @@ -13,7 +12,7 @@ from apps.common.confirm import confirm if False: - from typing import Iterable, Iterator, List, Union + from typing import Iterable, Iterator from trezor import wire @@ -55,48 +54,3 @@ def path_item(i: int) -> str: return "m" return "m/" + "/".join([path_item(i) for i in address_n]) - - -def paginate_text( - text: str, - header: str, - font: int = ui.NORMAL, - header_icon: str = ui.ICON_DEFAULT, - icon_color: int = ui.ORANGE_ICON, - break_words: bool = False, -) -> Union[Text, Paginated]: - span = Span(text, 0, font, break_words=break_words) - if span.count_lines() <= TEXT_MAX_LINES: - result = Text( - header, - header_icon=header_icon, - icon_color=icon_color, - new_lines=False, - ) - result.content = [font, text] - return result - - else: - pages: List[ui.Component] = [] - span.reset(text, 0, font, break_words=break_words, line_width=204) - while span.has_more_content(): - # advance to first line of the page - span.next_line() - page = Text( - header, - header_icon=header_icon, - icon_color=icon_color, - new_lines=False, - content_offset=0, - char_offset=span.start, - line_width=204, - render_page_overflow=False, - ) - page.content = [font, text] - pages.append(page) - - # roll over the remaining lines on the page - for _ in range(TEXT_MAX_LINES - 1): - span.next_line() - - return Paginated(pages) diff --git a/core/src/apps/common/signverify.py b/core/src/apps/common/signverify.py index 79cfdf37408..2b39db78ff3 100644 --- a/core/src/apps/common/signverify.py +++ b/core/src/apps/common/signverify.py @@ -1,11 +1,8 @@ from ubinascii import hexlify -from trezor import ui, utils, wire +from trezor import utils, wire from trezor.crypto.hashlib import blake256, sha256 -from trezor.ui.components.tt.text import Text -from apps.common.confirm import require_confirm -from apps.common.layout import paginate_text, split_address from apps.common.writers import write_bitcoin_varint if False: @@ -34,25 +31,3 @@ def decode_message(message: bytes) -> str: return bytes(message).decode() except UnicodeError: return "hex(%s)" % hexlify(message).decode() - - -async def require_confirm_sign_message( - ctx: wire.Context, coin: str, message: bytes -) -> None: - header = "Sign {} message".format(coin) - await require_confirm(ctx, paginate_text(decode_message(message), header)) - - -async def require_confirm_verify_message( - ctx: wire.Context, address: str, coin: str, message: bytes -) -> None: - header = "Verify {} message".format(coin) - text = Text(header) - text.bold("Confirm address:") - text.mono(*split_address(address)) - await require_confirm(ctx, text) - - await require_confirm( - ctx, - paginate_text(decode_message(message), header, font=ui.MONO), - ) diff --git a/core/src/apps/ethereum/sign_message.py b/core/src/apps/ethereum/sign_message.py index b8394e94683..7a90421a72a 100644 --- a/core/src/apps/ethereum/sign_message.py +++ b/core/src/apps/ethereum/sign_message.py @@ -1,10 +1,11 @@ from trezor.crypto.curve import secp256k1 from trezor.crypto.hashlib import sha3_256 from trezor.messages.EthereumMessageSignature import EthereumMessageSignature +from trezor.ui.layouts import confirm_signverify, require from trezor.utils import HashWriter from apps.common import paths -from apps.common.signverify import require_confirm_sign_message +from apps.common.signverify import decode_message from . import address from .keychain import PATTERNS_ADDRESS, with_keychain_from_path @@ -22,7 +23,7 @@ def message_digest(message): @with_keychain_from_path(*PATTERNS_ADDRESS) async def sign_message(ctx, msg, keychain): await paths.validate_path(ctx, keychain, msg.address_n) - await require_confirm_sign_message(ctx, "ETH", msg.message) + await require(confirm_signverify(ctx, "ETH", decode_message(msg.message))) node = keychain.derive(msg.address_n) signature = secp256k1.sign( diff --git a/core/src/apps/ethereum/verify_message.py b/core/src/apps/ethereum/verify_message.py index d177738295a..1031d150286 100644 --- a/core/src/apps/ethereum/verify_message.py +++ b/core/src/apps/ethereum/verify_message.py @@ -2,8 +2,9 @@ from trezor.crypto.curve import secp256k1 from trezor.crypto.hashlib import sha3_256 from trezor.messages.Success import Success +from trezor.ui.layouts import confirm_signverify, require -from apps.common.signverify import require_confirm_verify_message +from apps.common.signverify import decode_message from .address import address_from_bytes, bytes_from_address from .sign_message import message_digest @@ -28,6 +29,8 @@ async def verify_message(ctx, msg): address = address_from_bytes(address_bytes) - await require_confirm_verify_message(ctx, address, "ETH", msg.message) + await require( + confirm_signverify(ctx, "ETH", decode_message(msg.message), address=address) + ) return Success(message="Message verified") diff --git a/core/src/apps/lisk/sign_message.py b/core/src/apps/lisk/sign_message.py index fd747751f01..9c61e68d626 100644 --- a/core/src/apps/lisk/sign_message.py +++ b/core/src/apps/lisk/sign_message.py @@ -1,11 +1,12 @@ from trezor.crypto.curve import ed25519 from trezor.crypto.hashlib import sha256 from trezor.messages.LiskMessageSignature import LiskMessageSignature +from trezor.ui.layouts import confirm_signverify, require from trezor.utils import HashWriter from apps.common import paths from apps.common.keychain import auto_keychain -from apps.common.signverify import require_confirm_sign_message +from apps.common.signverify import decode_message from apps.common.writers import write_bitcoin_varint @@ -22,7 +23,7 @@ def message_digest(message): @auto_keychain(__name__) async def sign_message(ctx, msg, keychain): await paths.validate_path(ctx, keychain, msg.address_n) - await require_confirm_sign_message(ctx, "Lisk", msg.message) + await require(confirm_signverify(ctx, "Lisk", decode_message(msg.message))) node = keychain.derive(msg.address_n) seckey = node.private_key() diff --git a/core/src/apps/lisk/verify_message.py b/core/src/apps/lisk/verify_message.py index 4b8d1235df2..1b0af0b0993 100644 --- a/core/src/apps/lisk/verify_message.py +++ b/core/src/apps/lisk/verify_message.py @@ -1,8 +1,9 @@ from trezor import wire from trezor.crypto.curve import ed25519 from trezor.messages.Success import Success +from trezor.ui.layouts import confirm_signverify, require -from apps.common.signverify import require_confirm_verify_message +from apps.common.signverify import decode_message from .helpers import get_address_from_public_key from .sign_message import message_digest @@ -15,6 +16,8 @@ async def verify_message(ctx, msg): raise wire.ProcessError("Invalid signature") address = get_address_from_public_key(msg.public_key) - await require_confirm_verify_message(ctx, address, "Lisk", msg.message) + await require( + confirm_signverify(ctx, "Lisk", decode_message(msg.message), address=address) + ) return Success(message="Message verified") diff --git a/core/src/trezor/ui/components/tt/scroll.py b/core/src/trezor/ui/components/tt/scroll.py index 13ba7881325..28f47e70b1b 100644 --- a/core/src/trezor/ui/components/tt/scroll.py +++ b/core/src/trezor/ui/components/tt/scroll.py @@ -3,14 +3,18 @@ from trezor import loop, res, ui, utils from .button import Button, ButtonCancel, ButtonConfirm, ButtonDefault -from .confirm import CANCELLED, CONFIRMED +from .confirm import CANCELLED, CONFIRMED, Confirm from .swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe +from .text import TEXT_MAX_LINES, Span, Text if __debug__: from apps.debug import confirm_signal, swipe_signal, notify_layout_change if False: - from typing import List, Tuple + from typing import List, Tuple, Union + + +_PAGINATED_LINE_WIDTH = const(204) def render_scrollbar(pages: int, page: int) -> None: @@ -231,3 +235,53 @@ def read_content(self) -> List[str]: def create_tasks(self) -> Tuple[loop.Task, ...]: return super().create_tasks() + (confirm_signal(),) + + +def paginate_text( + text: str, + header: str, + font: int = ui.NORMAL, + header_icon: str = ui.ICON_DEFAULT, + icon_color: int = ui.ORANGE_ICON, + break_words: bool = False, +) -> Union[Confirm, Paginated]: + span = Span(text, 0, font, break_words=break_words) + if span.count_lines() <= TEXT_MAX_LINES: + result = Text( + header, + header_icon=header_icon, + icon_color=icon_color, + new_lines=False, + break_words=break_words, + ) + result.content = [font, text] + return Confirm(result) + + else: + pages: List[ui.Component] = [] + span.reset( + text, 0, font, break_words=break_words, line_width=_PAGINATED_LINE_WIDTH + ) + while span.has_more_content(): + # advance to first line of the page + span.next_line() + page = Text( + header, + header_icon=header_icon, + icon_color=icon_color, + new_lines=False, + content_offset=0, + char_offset=span.start, + line_width=_PAGINATED_LINE_WIDTH, + break_words=break_words, + render_page_overflow=False, + ) + page.content = [font, text] + pages.append(page) + + # roll over the remaining lines on the page + for _ in range(TEXT_MAX_LINES - 1): + span.next_line() + + pages[-1] = Confirm(pages[-1]) + return Paginated(pages) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index 7d5b97978c1..dbb6a829933 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -11,8 +11,8 @@ from ..components.common.confirm import is_confirmed from ..components.tt.button import ButtonCancel, ButtonDefault from ..components.tt.confirm import Confirm, HoldToConfirm -from ..components.tt.scroll import Paginated -from ..components.tt.text import Text +from ..components.tt.scroll import Paginated, paginate_text +from ..components.tt.text import Span, Text from ..constants.tt import ( MONO_CHARS_PER_LINE, MONO_HEX_PER_LINE, @@ -38,6 +38,7 @@ "confirm_backup", "confirm_path_warning", "confirm_sign_identity", + "confirm_signverify", "show_address", "show_error", "show_pubkey", @@ -53,6 +54,7 @@ "confirm_replacement", "confirm_modify_output", "confirm_modify_fee", + "confirm_coinjoin", ) @@ -63,6 +65,7 @@ async def confirm_action( action: str = None, description: str = None, description_param: str = None, + description_param_font: int = ui.BOLD, verb: Union[str, bytes, None] = Confirm.DEFAULT_CONFIRM, verb_cancel: Union[str, bytes, None] = Confirm.DEFAULT_CANCEL, hold: bool = False, @@ -82,7 +85,9 @@ async def confirm_action( if reverse and description is not None: text.format_parametrized( - description, description_param if description_param is not None else "" + description, + description_param if description_param is not None else "", + param_font=description_param_font, ) elif action is not None: text.bold(action) @@ -96,7 +101,9 @@ async def confirm_action( text.bold(action) elif description is not None: text.format_parametrized( - description, description_param if description_param is not None else "" + description, + description_param if description_param is not None else "", + param_font=description_param_font, ) cls = HoldToConfirm if hold else Confirm @@ -208,11 +215,21 @@ def _split_address(address: str) -> Iterator[str]: return chunks(address, MONO_CHARS_PER_LINE) -def _hex_lines( - hex_data: str, lines: int = TEXT_MAX_LINES, width: int = MONO_HEX_PER_LINE +def _truncate_hex( + hex_data: str, + lines: int = TEXT_MAX_LINES, + width: int = MONO_HEX_PER_LINE, + middle: bool = False, ) -> Iterator[str]: if len(hex_data) >= width * lines: - hex_data = hex_data[: (width * lines - 3)] + "..." + if middle: + hex_data = ( + hex_data[: lines * width // 2 - 1] + + "..." + + hex_data[-lines * width // 2 + 2 :] + ) + else: + hex_data = hex_data[: (width * lines - 3)] + "..." return chunks(hex_data, width) @@ -457,14 +474,29 @@ async def confirm_hex( br_type: str, title: str, data: str, + description: str = None, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign width: int = MONO_HEX_PER_LINE, + truncate_middle: bool = False, ) -> bool: - text = Text(title, icon, icon_color) - text.mono(*_hex_lines(data, width=width)) - return is_confirmed(await interact(ctx, Confirm(text), br_type, br_code)) + text = Text(title, icon, icon_color, new_lines=False) + description_lines = 0 + if description is not None: + description_lines = Span(description, 0, ui.NORMAL).count_lines() + text.normal(description) + text.br() + text.mono( + *_truncate_hex( + data, + lines=TEXT_MAX_LINES - description_lines, + width=width, + middle=truncate_middle, + ) + ) + content: ui.Layout = Confirm(text) + return is_confirmed(await interact(ctx, content, br_type, br_code)) async def confirm_total( @@ -519,7 +551,7 @@ async def confirm_replacement( ) -> bool: text = Text(description, ui.ICON_SEND, ui.GREEN) text.normal("Confirm transaction ID:") - text.mono(*_hex_lines(txid, TEXT_MAX_LINES - 1)) + text.mono(*_truncate_hex(txid, TEXT_MAX_LINES - 1)) return is_confirmed( await interact( ctx, Confirm(text), "confirm_replacement", ButtonRequestType.SignTx @@ -582,6 +614,22 @@ async def confirm_modify_fee( ) +async def confirm_coinjoin( + ctx: wire.GenericContext, fee_per_anonymity: Optional[str], total_fee: str +) -> bool: + text = Text("Authorize CoinJoin", ui.ICON_RECOVERY, new_lines=False) + if fee_per_anonymity is not None: + text.normal("Fee per anonymity set:\n") + text.bold("{} %\n".format(fee_per_anonymity)) + text.normal("Maximum total fees:\n") + text.bold(total_fee) + return is_confirmed( + await interact( + ctx, HoldToConfirm(text), "coinjoin_final", ButtonRequestType.Other + ) + ) + + # TODO cleanup @ redesign async def confirm_sign_identity( ctx: wire.GenericContext, proto: str, identity: str, challenge_visual: Optional[str] @@ -598,3 +646,33 @@ async def confirm_sign_identity( return is_confirmed( await interact(ctx, Confirm(text), "sign_identity", ButtonRequestType.Other) ) + + +async def confirm_signverify( + ctx: wire.GenericContext, coin: str, message: str, address: str = None +) -> bool: + if address: + header = "Verify {} message".format(coin) + font = ui.MONO + br_type = "verify_message" + + text = Text(header) + text.bold("Confirm address:") + text.mono(*_split_address(address)) + if not is_confirmed( + await interact(ctx, Confirm(text), br_type, ButtonRequestType.Other) + ): + return False + else: + header = "Sign {} message".format(coin) + font = ui.NORMAL + br_type = "sign_message" + + return is_confirmed( + await interact( + ctx, + paginate_text(message, header, font=font), + br_type, + ButtonRequestType.Other, + ) + ) From 681631b2de3adedea5e6c4551f0b79e221b245fd Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 10 Mar 2021 12:56:44 +0100 Subject: [PATCH 11/13] refactor(core/ui): raise exception on dialog cancel by default --- core/src/apps/base.py | 6 +- core/src/apps/binance/get_public_key.py | 4 +- core/src/apps/bitcoin/authorize_coinjoin.py | 30 ++-- core/src/apps/bitcoin/get_ownership_proof.py | 34 ++-- core/src/apps/bitcoin/get_public_key.py | 4 +- core/src/apps/bitcoin/sign_message.py | 4 +- core/src/apps/bitcoin/sign_tx/layout.py | 113 +++++------- core/src/apps/bitcoin/verify_message.py | 14 +- core/src/apps/cardano/get_public_key.py | 4 +- core/src/apps/common/paths.py | 4 +- core/src/apps/common/request_pin.py | 14 +- core/src/apps/common/sdcard.py | 52 +++--- core/src/apps/debug/load_device.py | 16 +- core/src/apps/ethereum/get_public_key.py | 4 +- core/src/apps/ethereum/sign_message.py | 4 +- core/src/apps/ethereum/verify_message.py | 6 +- core/src/apps/lisk/get_public_key.py | 4 +- core/src/apps/lisk/layout.py | 4 +- core/src/apps/lisk/sign_message.py | 4 +- core/src/apps/lisk/verify_message.py | 6 +- core/src/apps/management/apply_settings.py | 170 ++++++++---------- core/src/apps/management/change_pin.py | 52 +++--- core/src/apps/management/change_wipe_code.py | 4 +- .../management/recovery_device/homescreen.py | 8 +- .../apps/management/recovery_device/layout.py | 72 ++++---- .../apps/management/reset_device/__init__.py | 4 +- .../apps/management/reset_device/layout.py | 74 +++----- core/src/apps/management/sd_protect.py | 25 ++- core/src/apps/management/wipe_device.py | 4 +- core/src/apps/misc/cipher_key_value.py | 6 +- core/src/apps/misc/get_ecdh_session_key.py | 18 +- core/src/apps/misc/get_entropy.py | 18 +- core/src/apps/misc/sign_identity.py | 8 +- core/src/apps/monero/layout/confirms.py | 74 ++++---- core/src/apps/tezos/get_public_key.py | 4 +- .../apps/webauthn/add_resident_credential.py | 19 +- .../webauthn/list_resident_credentials.py | 14 +- .../trezor/ui/components/common/confirm.py | 10 +- core/src/trezor/ui/layouts/common.py | 6 - core/src/trezor/ui/layouts/tt.py | 160 +++++++++-------- 40 files changed, 489 insertions(+), 592 deletions(-) diff --git a/core/src/apps/base.py b/core/src/apps/base.py index e75a60951d8..42bd36023f9 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -129,12 +129,10 @@ async def handle_EndSession(ctx: wire.Context, msg: EndSession) -> Success: async def handle_Ping(ctx: wire.Context, msg: Ping) -> Success: if msg.button_protection: - from trezor.ui.layouts import require, confirm_action + from trezor.ui.layouts import confirm_action from trezor.messages.ButtonRequestType import ProtectCall - await require( - confirm_action(ctx, "ping", "Confirm", "ping", br_code=ProtectCall) - ) + await confirm_action(ctx, "ping", "Confirm", "ping", br_code=ProtectCall) return Success(message=msg.message) diff --git a/core/src/apps/binance/get_public_key.py b/core/src/apps/binance/get_public_key.py index 4c6f98c5c98..b62a99d1e0b 100644 --- a/core/src/apps/binance/get_public_key.py +++ b/core/src/apps/binance/get_public_key.py @@ -2,7 +2,7 @@ from trezor.messages.BinanceGetPublicKey import BinanceGetPublicKey from trezor.messages.BinancePublicKey import BinancePublicKey -from trezor.ui.layouts import require, show_pubkey +from trezor.ui.layouts import show_pubkey from apps.common import paths from apps.common.keychain import Keychain, auto_keychain @@ -15,6 +15,6 @@ async def get_public_key(ctx, msg: BinanceGetPublicKey, keychain: Keychain): pubkey = node.public_key() if msg.show_display: - await require(show_pubkey(ctx, hexlify(pubkey).decode())) + await show_pubkey(ctx, hexlify(pubkey).decode()) return BinancePublicKey(public_key=pubkey) diff --git a/core/src/apps/bitcoin/authorize_coinjoin.py b/core/src/apps/bitcoin/authorize_coinjoin.py index a978173b4f8..4a4ae63dca3 100644 --- a/core/src/apps/bitcoin/authorize_coinjoin.py +++ b/core/src/apps/bitcoin/authorize_coinjoin.py @@ -4,7 +4,7 @@ from trezor.messages.AuthorizeCoinJoin import AuthorizeCoinJoin from trezor.messages.Success import Success from trezor.strings import format_amount -from trezor.ui.layouts import confirm_action, confirm_coinjoin, require +from trezor.ui.layouts import confirm_action, confirm_coinjoin from apps.base import set_authorization from apps.common.paths import validate_path @@ -45,16 +45,14 @@ async def authorize_coinjoin(ctx: wire.Context, msg: AuthorizeCoinJoin) -> Succe ), ) - await require( - confirm_action( - ctx, - "coinjoin_coordinator", - title="Authorize CoinJoin", - description="Do you really want to take part in a CoinJoin transaction at:\n{}", - description_param=msg.coordinator, - description_param_font=ui.MONO, - icon=ui.ICON_RECOVERY, - ) + await confirm_action( + ctx, + "coinjoin_coordinator", + title="Authorize CoinJoin", + description="Do you really want to take part in a CoinJoin transaction at:\n{}", + description_param=msg.coordinator, + description_param_font=ui.MONO, + icon=ui.ICON_RECOVERY, ) fee_per_anonymity = None @@ -62,12 +60,10 @@ async def authorize_coinjoin(ctx: wire.Context, msg: AuthorizeCoinJoin) -> Succe fee_per_anonymity = format_amount( msg.fee_per_anonymity, FEE_PER_ANONYMITY_DECIMALS ) - await require( - confirm_coinjoin( - ctx, - fee_per_anonymity, - format_coin_amount(msg.max_total_fee, coin, msg.amount_unit), - ) + await confirm_coinjoin( + ctx, + fee_per_anonymity, + format_coin_amount(msg.max_total_fee, coin, msg.amount_unit), ) set_authorization(CoinJoinAuthorization(msg, keychain, coin)) diff --git a/core/src/apps/bitcoin/get_ownership_proof.py b/core/src/apps/bitcoin/get_ownership_proof.py index 5d46d1c360e..54b72c7b264 100644 --- a/core/src/apps/bitcoin/get_ownership_proof.py +++ b/core/src/apps/bitcoin/get_ownership_proof.py @@ -3,7 +3,7 @@ from trezor import ui, wire from trezor.messages.GetOwnershipProof import GetOwnershipProof from trezor.messages.OwnershipProof import OwnershipProof -from trezor.ui.layouts import confirm_action, confirm_hex, require +from trezor.ui.layouts import confirm_action, confirm_hex from apps.common.paths import validate_path @@ -65,26 +65,22 @@ async def get_ownership_proof( # In order to set the "user confirmation" bit in the proof, the user must actually confirm. if msg.user_confirmation and not authorization: if not msg.commitment_data: - await require( - confirm_action( - ctx, - "confirm_ownership_proof", - title="Proof of ownership", - description="Do you want to create a proof of ownership?", - ) + await confirm_action( + ctx, + "confirm_ownership_proof", + title="Proof of ownership", + description="Do you want to create a proof of ownership?", ) else: - await require( - confirm_hex( - ctx, - "confirm_ownership_proof", - title="Proof of ownership", - description="Do you want to create a proof of ownership for:", - data=hexlify(msg.commitment_data).decode(), - icon=ui.ICON_CONFIG, - icon_color=ui.ORANGE_ICON, - truncate_middle=True, - ) + await confirm_hex( + ctx, + "confirm_ownership_proof", + title="Proof of ownership", + description="Do you want to create a proof of ownership for:", + data=hexlify(msg.commitment_data).decode(), + icon=ui.ICON_CONFIG, + icon_color=ui.ORANGE_ICON, + truncate_middle=True, ) ownership_proof, signature = generate_proof( diff --git a/core/src/apps/bitcoin/get_public_key.py b/core/src/apps/bitcoin/get_public_key.py index f6d89d9fe61..07b3fcc16f6 100644 --- a/core/src/apps/bitcoin/get_public_key.py +++ b/core/src/apps/bitcoin/get_public_key.py @@ -2,7 +2,7 @@ from trezor.messages import InputScriptType from trezor.messages.HDNodeType import HDNodeType from trezor.messages.PublicKey import PublicKey -from trezor.ui.layouts import require, show_xpub +from trezor.ui.layouts import show_xpub from apps.common import coins, paths from apps.common.keychain import get_keychain @@ -59,7 +59,7 @@ async def get_public_key(ctx: wire.Context, msg: GetPublicKey) -> PublicKey: ) if msg.show_display: - await require(show_xpub(ctx, node_xpub, "XPUB", "Cancel")) + await show_xpub(ctx, node_xpub, "XPUB", "Cancel") return PublicKey( node=node_type, diff --git a/core/src/apps/bitcoin/sign_message.py b/core/src/apps/bitcoin/sign_message.py index 1acb660ca76..8eb17a1a561 100644 --- a/core/src/apps/bitcoin/sign_message.py +++ b/core/src/apps/bitcoin/sign_message.py @@ -2,7 +2,7 @@ from trezor.crypto.curve import secp256k1 from trezor.messages.InputScriptType import SPENDADDRESS, SPENDP2SHWITNESS, SPENDWITNESS from trezor.messages.MessageSignature import MessageSignature -from trezor.ui.layouts import confirm_signverify, require +from trezor.ui.layouts import confirm_signverify from apps.common.paths import validate_path from apps.common.signverify import decode_message, message_digest @@ -26,7 +26,7 @@ async def sign_message( script_type = msg.script_type or 0 await validate_path(ctx, keychain, address_n) - await require(confirm_signverify(ctx, coin.coin_shortcut, decode_message(message))) + await confirm_signverify(ctx, coin.coin_shortcut, decode_message(message)) node = keychain.derive(address_n) seckey = node.private_key() diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index 1a304dde75a..2d1be19574a 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -4,7 +4,6 @@ from trezor.messages import AmountUnit, ButtonRequestType, OutputScriptType from trezor.strings import format_amount from trezor.ui import layouts -from trezor.ui.layouts import require from .. import addresses from . import omni @@ -70,7 +69,7 @@ async def confirm_output( ctx, address_short, format_coin_amount(output.amount, coin, amount_unit) ) - await require(layout) + await layout async def confirm_decred_sstx_submission( @@ -79,20 +78,16 @@ async def confirm_decred_sstx_submission( assert output.address is not None address_short = addresses.address_short(coin, output.address) - await require( - layouts.confirm_decred_sstx_submission( - ctx, address_short, format_coin_amount(output.amount, coin, amount_unit) - ) + await layouts.confirm_decred_sstx_submission( + ctx, address_short, format_coin_amount(output.amount, coin, amount_unit) ) async def confirm_replacement(ctx: wire.Context, description: str, txid: bytes) -> None: - await require( - layouts.confirm_replacement( - ctx, - description, - hexlify(txid).decode(), - ) + await layouts.confirm_replacement( + ctx, + description, + hexlify(txid).decode(), ) @@ -106,14 +101,12 @@ async def confirm_modify_output( assert txo.address is not None address_short = addresses.address_short(coin, txo.address) amount_change = txo.amount - orig_txo.amount - await require( - layouts.confirm_modify_output( - ctx, - address_short, - amount_change, - format_coin_amount(abs(amount_change), coin, amount_unit), - format_coin_amount(txo.amount, coin, amount_unit), - ) + await layouts.confirm_modify_output( + ctx, + address_short, + amount_change, + format_coin_amount(abs(amount_change), coin, amount_unit), + format_coin_amount(txo.amount, coin, amount_unit), ) @@ -124,13 +117,11 @@ async def confirm_modify_fee( coin: CoinInfo, amount_unit: EnumTypeAmountUnit, ) -> None: - await require( - layouts.confirm_modify_fee( - ctx, - user_fee_change, - format_coin_amount(abs(user_fee_change), coin, amount_unit), - format_coin_amount(total_fee_new, coin, amount_unit), - ) + await layouts.confirm_modify_fee( + ctx, + user_fee_change, + format_coin_amount(abs(user_fee_change), coin, amount_unit), + format_coin_amount(total_fee_new, coin, amount_unit), ) @@ -141,12 +132,10 @@ async def confirm_joint_total( coin: CoinInfo, amount_unit: EnumTypeAmountUnit, ) -> None: - await require( - layouts.confirm_joint_total( - ctx, - spending_amount=format_coin_amount(spending, coin, amount_unit), - total_amount=format_coin_amount(total, coin, amount_unit), - ), + await layouts.confirm_joint_total( + ctx, + spending_amount=format_coin_amount(spending, coin, amount_unit), + total_amount=format_coin_amount(total, coin, amount_unit), ) @@ -157,12 +146,10 @@ async def confirm_total( coin: CoinInfo, amount_unit: EnumTypeAmountUnit, ) -> None: - await require( - layouts.confirm_total( - ctx, - total_amount=format_coin_amount(spending, coin, amount_unit), - fee_amount=format_coin_amount(fee, coin, amount_unit), - ), + await layouts.confirm_total( + ctx, + total_amount=format_coin_amount(spending, coin, amount_unit), + fee_amount=format_coin_amount(fee, coin, amount_unit), ) @@ -170,30 +157,26 @@ async def confirm_feeoverthreshold( ctx: wire.Context, fee: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit ) -> None: fee_amount = format_coin_amount(fee, coin, amount_unit) - await require( - layouts.confirm_metadata( - ctx, - "fee_over_threshold", - "High fee", - "The fee of\n{}is unexpectedly high.", - fee_amount, - ButtonRequestType.FeeOverThreshold, - ) + await layouts.confirm_metadata( + ctx, + "fee_over_threshold", + "High fee", + "The fee of\n{}is unexpectedly high.", + fee_amount, + ButtonRequestType.FeeOverThreshold, ) async def confirm_change_count_over_threshold( ctx: wire.Context, change_count: int ) -> None: - await require( - layouts.confirm_metadata( - ctx, - "change_count_over_threshold", - "Warning", - "There are {}\nchange-outputs.\n", - str(change_count), - ButtonRequestType.SignTx, - ) + await layouts.confirm_metadata( + ctx, + "change_count_over_threshold", + "Warning", + "There are {}\nchange-outputs.\n", + str(change_count), + ButtonRequestType.SignTx, ) @@ -213,13 +196,11 @@ async def confirm_nondefault_locktime( text = "Locktime for this\ntransaction is set to\ntimestamp:\n{}" param = str(lock_time) - await require( - layouts.confirm_metadata( - ctx, - "nondefault_locktime", - title, - text, - param, - br_code=ButtonRequestType.SignTx, - ) + await layouts.confirm_metadata( + ctx, + "nondefault_locktime", + title, + text, + param, + br_code=ButtonRequestType.SignTx, ) diff --git a/core/src/apps/bitcoin/verify_message.py b/core/src/apps/bitcoin/verify_message.py index cb6a55b05e2..4e60b48e557 100644 --- a/core/src/apps/bitcoin/verify_message.py +++ b/core/src/apps/bitcoin/verify_message.py @@ -2,7 +2,7 @@ from trezor.crypto.curve import secp256k1 from trezor.messages.InputScriptType import SPENDADDRESS, SPENDP2SHWITNESS, SPENDWITNESS from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_signverify, require +from trezor.ui.layouts import confirm_signverify from apps.common import coins from apps.common.signverify import decode_message, message_digest @@ -63,13 +63,11 @@ async def verify_message(ctx: wire.Context, msg: VerifyMessage) -> Success: if addr != address: raise wire.ProcessError("Invalid signature") - await require( - confirm_signverify( - ctx, - coin.coin_shortcut, - decode_message(message), - address=address_short(coin, address), - ) + await confirm_signverify( + ctx, + coin.coin_shortcut, + decode_message(message), + address=address_short(coin, address), ) return Success(message="Message verified") diff --git a/core/src/apps/cardano/get_public_key.py b/core/src/apps/cardano/get_public_key.py index 36573b11cd6..42ba051ce5b 100644 --- a/core/src/apps/cardano/get_public_key.py +++ b/core/src/apps/cardano/get_public_key.py @@ -3,7 +3,7 @@ from trezor import log, wire from trezor.messages.CardanoPublicKey import CardanoPublicKey from trezor.messages.HDNodeType import HDNodeType -from trezor.ui.layouts import require, show_pubkey +from trezor.ui.layouts import show_pubkey from apps.common import paths from apps.common.seed import remove_ed25519_prefix @@ -36,7 +36,7 @@ async def get_public_key( raise wire.ProcessError("Deriving public key failed") if msg.show_display: - await require(show_pubkey(ctx, hexlify(key.node.public_key).decode())) + await show_pubkey(ctx, hexlify(key.node.public_key).decode()) return key diff --git a/core/src/apps/common/paths.py b/core/src/apps/common/paths.py index ed7e93c6663..55ee31b9ddf 100644 --- a/core/src/apps/common/paths.py +++ b/core/src/apps/common/paths.py @@ -1,5 +1,5 @@ from trezor.ui.constants import MONO_CHARS_PER_LINE -from trezor.ui.layouts import confirm_path_warning, require +from trezor.ui.layouts import confirm_path_warning from . import HARDENED from .layout import address_n_to_str @@ -259,7 +259,7 @@ async def validate_path( async def show_path_warning(ctx: wire.Context, path: Bip32Path) -> None: - await require(confirm_path_warning(ctx, address_n_to_str(path))) + await confirm_path_warning(ctx, address_n_to_str(path)) def is_hardened(i: int) -> bool: diff --git a/core/src/apps/common/request_pin.py b/core/src/apps/common/request_pin.py index e81e25c13c2..bc38209b508 100644 --- a/core/src/apps/common/request_pin.py +++ b/core/src/apps/common/request_pin.py @@ -123,26 +123,28 @@ async def verify_user_pin( async def error_pin_invalid(ctx: wire.Context) -> NoReturn: - from trezor.ui.layouts import show_error + from trezor.ui.layouts import show_error_and_raise - await show_error( + await show_error_and_raise( ctx, "warning_wrong_pin", header="Wrong PIN", content="The PIN you entered is invalid.", red=True, + exc=wire.PinInvalid, ) - raise wire.PinInvalid + assert False async def error_pin_matches_wipe_code(ctx: wire.Context) -> NoReturn: - from trezor.ui.layouts import show_error + from trezor.ui.layouts import show_error_and_raise - await show_error( + await show_error_and_raise( ctx, "warning_invalid_new_pin", header="Invalid PIN", content="The new PIN must be different from your\nwipe code.", red=True, + exc=wire.PinInvalid, ) - raise wire.PinInvalid + assert False diff --git a/core/src/apps/common/sdcard.py b/core/src/apps/common/sdcard.py index e52815a3aa8..317e75b6a47 100644 --- a/core/src/apps/common/sdcard.py +++ b/core/src/apps/common/sdcard.py @@ -1,7 +1,7 @@ import storage.sd_salt from storage.sd_salt import SD_CARD_HOT_SWAPPABLE from trezor import fatfs, sdcard, ui, wire -from trezor.ui.layouts import confirm_action, show_error +from trezor.ui.layouts import confirm_action, show_error_and_raise if False: from typing import Optional @@ -11,9 +11,9 @@ class SdCardUnavailable(wire.ProcessError): pass -async def _wrong_card_dialog(ctx: wire.GenericContext) -> bool: +async def _confirm_retry_wrong_card(ctx: wire.GenericContext) -> None: if SD_CARD_HOT_SWAPPABLE: - return await confirm_action( + await confirm_action( ctx, "warning_wrong_sd", "SD card protection", @@ -23,20 +23,22 @@ async def _wrong_card_dialog(ctx: wire.GenericContext) -> bool: verb_cancel="Abort", icon=ui.ICON_WRONG, larger_vspace=True, + exc=SdCardUnavailable("Wrong SD card."), ) else: - return await show_error( + await show_error_and_raise( ctx, "warning_wrong_sd", header="SD card protection", subheader="Wrong SD card.", content="Please unplug the\ndevice and insert the correct SD card.", + exc=SdCardUnavailable("Wrong SD card."), ) -async def insert_card_dialog(ctx: wire.GenericContext) -> bool: +async def _confirm_retry_insert_card(ctx: wire.GenericContext) -> None: if SD_CARD_HOT_SWAPPABLE: - return await confirm_action( + await confirm_action( ctx, "warning_no_sd", "SD card protection", @@ -46,20 +48,22 @@ async def insert_card_dialog(ctx: wire.GenericContext) -> bool: verb_cancel="Abort", icon=ui.ICON_WRONG, larger_vspace=True, + exc=SdCardUnavailable("SD card required."), ) else: - return await show_error( + await show_error_and_raise( ctx, "warning_no_sd", header="SD card protection", subheader="SD card required.", content="Please unplug the\ndevice and insert your SD card.", + exc=SdCardUnavailable("SD card required."), ) -async def format_card_dialog(ctx: wire.GenericContext) -> bool: +async def _confirm_format_card(ctx: wire.GenericContext) -> None: # Format card? yes/no - if not await confirm_action( + await confirm_action( ctx, "warning_format_sd", "SD card error", @@ -70,11 +74,11 @@ async def format_card_dialog(ctx: wire.GenericContext) -> bool: verb="Format", verb_cancel="Cancel", larger_vspace=True, - ): - return False + exc=SdCardUnavailable("SD card not formatted."), + ) # Confirm formatting - return await confirm_action( + await confirm_action( ctx, "confirm_format_sd", "Format SD card", @@ -86,11 +90,15 @@ async def format_card_dialog(ctx: wire.GenericContext) -> bool: icon_color=ui.RED, hold=True, larger_vspace=True, + exc=SdCardUnavailable("SD card not formatted."), ) -async def sd_problem_dialog(ctx: wire.GenericContext) -> bool: - return await confirm_action( +async def confirm_retry_sd( + ctx: wire.GenericContext, + exc: wire.ProcessError = SdCardUnavailable("Error accessing SD card."), +) -> None: + await confirm_action( ctx, "warning_sd_retry", "SD card problem", @@ -100,6 +108,7 @@ async def sd_problem_dialog(ctx: wire.GenericContext) -> bool: icon_color=ui.RED, verb="Retry", verb_cancel="Abort", + exc=exc, ) @@ -116,8 +125,7 @@ async def ensure_sdcard( mounted. """ while not sdcard.is_present(): - if not await insert_card_dialog(ctx): - raise SdCardUnavailable("SD card required.") + await _confirm_retry_insert_card(ctx) if not ensure_filesystem: return @@ -134,8 +142,7 @@ async def ensure_sdcard( # no error when mounting return - if not await format_card_dialog(ctx): - raise SdCardUnavailable("SD card not formatted.") + await _confirm_format_card(ctx) # Proceed to formatting. Failure is caught by the outside OSError handler with sdcard.filesystem(mounted=False): @@ -148,8 +155,7 @@ async def ensure_sdcard( except OSError: # formatting failed, or generic I/O error (SD card power-on failed) - if not await sd_problem_dialog(ctx): - raise SdCardUnavailable("Error accessing SD card.") + await confirm_retry_sd(ctx) async def request_sd_salt( @@ -163,12 +169,10 @@ async def request_sd_salt( try: return storage.sd_salt.load_sd_salt() except (storage.sd_salt.WrongSdCard, fatfs.NoFilesystem): - if not await _wrong_card_dialog(ctx): - raise SdCardUnavailable("Wrong SD card.") + await _confirm_retry_wrong_card(ctx) except OSError: # Generic problem with loading the SD salt (hardware problem, or we could # not read the file, or there is a staged salt which cannot be committed). # In either case, there is no good way to recover. If the user clicks Retry, # we will try again. - if not await sd_problem_dialog(ctx): - raise SdCardUnavailable("Error accessing SD card.") + await confirm_retry_sd(ctx) diff --git a/core/src/apps/debug/load_device.py b/core/src/apps/debug/load_device.py index 91933033a6c..4da3e4337b6 100644 --- a/core/src/apps/debug/load_device.py +++ b/core/src/apps/debug/load_device.py @@ -4,7 +4,7 @@ from trezor.crypto import bip39, slip39 from trezor.messages import BackupType from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_action, require +from trezor.ui.layouts import confirm_action from apps.management import backup_types @@ -68,12 +68,10 @@ def _validate(msg) -> int: async def _warn(ctx: wire.Context): - await require( - confirm_action( - ctx, - "warn_loading_seed", - "Loading seed", - "Loading private seed\nis not recommended.", - "Continue only if you\nknow what you are doing!", - ) + await confirm_action( + ctx, + "warn_loading_seed", + "Loading seed", + "Loading private seed\nis not recommended.", + "Continue only if you\nknow what you are doing!", ) diff --git a/core/src/apps/ethereum/get_public_key.py b/core/src/apps/ethereum/get_public_key.py index abaeed2aeeb..909bc15023b 100644 --- a/core/src/apps/ethereum/get_public_key.py +++ b/core/src/apps/ethereum/get_public_key.py @@ -2,7 +2,7 @@ from trezor.messages.EthereumPublicKey import EthereumPublicKey from trezor.messages.HDNodeType import HDNodeType -from trezor.ui.layouts import require, show_pubkey +from trezor.ui.layouts import show_pubkey from apps.common import coins, paths @@ -30,6 +30,6 @@ async def get_public_key(ctx, msg, keychain): ) if msg.show_display: - await require(show_pubkey(ctx, hexlify(pubkey).decode())) + await show_pubkey(ctx, hexlify(pubkey).decode()) return EthereumPublicKey(node=node_type, xpub=node_xpub) diff --git a/core/src/apps/ethereum/sign_message.py b/core/src/apps/ethereum/sign_message.py index 7a90421a72a..52c81166a6d 100644 --- a/core/src/apps/ethereum/sign_message.py +++ b/core/src/apps/ethereum/sign_message.py @@ -1,7 +1,7 @@ from trezor.crypto.curve import secp256k1 from trezor.crypto.hashlib import sha3_256 from trezor.messages.EthereumMessageSignature import EthereumMessageSignature -from trezor.ui.layouts import confirm_signverify, require +from trezor.ui.layouts import confirm_signverify from trezor.utils import HashWriter from apps.common import paths @@ -23,7 +23,7 @@ def message_digest(message): @with_keychain_from_path(*PATTERNS_ADDRESS) async def sign_message(ctx, msg, keychain): await paths.validate_path(ctx, keychain, msg.address_n) - await require(confirm_signverify(ctx, "ETH", decode_message(msg.message))) + await confirm_signverify(ctx, "ETH", decode_message(msg.message)) node = keychain.derive(msg.address_n) signature = secp256k1.sign( diff --git a/core/src/apps/ethereum/verify_message.py b/core/src/apps/ethereum/verify_message.py index 1031d150286..4c3a6605802 100644 --- a/core/src/apps/ethereum/verify_message.py +++ b/core/src/apps/ethereum/verify_message.py @@ -2,7 +2,7 @@ from trezor.crypto.curve import secp256k1 from trezor.crypto.hashlib import sha3_256 from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_signverify, require +from trezor.ui.layouts import confirm_signverify from apps.common.signverify import decode_message @@ -29,8 +29,6 @@ async def verify_message(ctx, msg): address = address_from_bytes(address_bytes) - await require( - confirm_signverify(ctx, "ETH", decode_message(msg.message), address=address) - ) + await confirm_signverify(ctx, "ETH", decode_message(msg.message), address=address) return Success(message="Message verified") diff --git a/core/src/apps/lisk/get_public_key.py b/core/src/apps/lisk/get_public_key.py index 79b2dae5e5e..8fe023209d8 100644 --- a/core/src/apps/lisk/get_public_key.py +++ b/core/src/apps/lisk/get_public_key.py @@ -1,7 +1,7 @@ from ubinascii import hexlify from trezor.messages.LiskPublicKey import LiskPublicKey -from trezor.ui.layouts import require, show_pubkey +from trezor.ui.layouts import show_pubkey from apps.common import paths from apps.common.keychain import auto_keychain @@ -16,6 +16,6 @@ async def get_public_key(ctx, msg, keychain): pubkey = pubkey[1:] # skip ed25519 pubkey marker if msg.show_display: - await require(show_pubkey(ctx, hexlify(pubkey).decode())) + await show_pubkey(ctx, hexlify(pubkey).decode()) return LiskPublicKey(public_key=pubkey) diff --git a/core/src/apps/lisk/layout.py b/core/src/apps/lisk/layout.py index f3b1f3eab54..55ee1b5d5d8 100644 --- a/core/src/apps/lisk/layout.py +++ b/core/src/apps/lisk/layout.py @@ -4,7 +4,7 @@ from trezor.messages import ButtonRequestType from trezor.strings import format_amount from trezor.ui.components.tt.text import Text -from trezor.ui.layouts import require, show_pubkey +from trezor.ui.layouts import show_pubkey from trezor.utils import chunks from apps.common.confirm import require_confirm, require_hold_to_confirm @@ -36,7 +36,7 @@ async def require_confirm_vote_tx(ctx, votes): async def require_confirm_public_key(ctx, public_key): - return await require(show_pubkey(ctx, hexlify(public_key).decode())) + return await show_pubkey(ctx, hexlify(public_key).decode()) async def require_confirm_multisig(ctx, multisignature): diff --git a/core/src/apps/lisk/sign_message.py b/core/src/apps/lisk/sign_message.py index 9c61e68d626..3690cd83214 100644 --- a/core/src/apps/lisk/sign_message.py +++ b/core/src/apps/lisk/sign_message.py @@ -1,7 +1,7 @@ from trezor.crypto.curve import ed25519 from trezor.crypto.hashlib import sha256 from trezor.messages.LiskMessageSignature import LiskMessageSignature -from trezor.ui.layouts import confirm_signverify, require +from trezor.ui.layouts import confirm_signverify from trezor.utils import HashWriter from apps.common import paths @@ -23,7 +23,7 @@ def message_digest(message): @auto_keychain(__name__) async def sign_message(ctx, msg, keychain): await paths.validate_path(ctx, keychain, msg.address_n) - await require(confirm_signverify(ctx, "Lisk", decode_message(msg.message))) + await confirm_signverify(ctx, "Lisk", decode_message(msg.message)) node = keychain.derive(msg.address_n) seckey = node.private_key() diff --git a/core/src/apps/lisk/verify_message.py b/core/src/apps/lisk/verify_message.py index 1b0af0b0993..e52875e6c8b 100644 --- a/core/src/apps/lisk/verify_message.py +++ b/core/src/apps/lisk/verify_message.py @@ -1,7 +1,7 @@ from trezor import wire from trezor.crypto.curve import ed25519 from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_signverify, require +from trezor.ui.layouts import confirm_signverify from apps.common.signverify import decode_message @@ -16,8 +16,6 @@ async def verify_message(ctx, msg): raise wire.ProcessError("Invalid signature") address = get_address_from_public_key(msg.public_key) - await require( - confirm_signverify(ctx, "Lisk", decode_message(msg.message), address=address) - ) + await confirm_signverify(ctx, "Lisk", decode_message(msg.message), address=address) return Success(message="Message verified") diff --git a/core/src/apps/management/apply_settings.py b/core/src/apps/management/apply_settings.py index 18f2dfa71a5..cfef5b5dbc1 100644 --- a/core/src/apps/management/apply_settings.py +++ b/core/src/apps/management/apply_settings.py @@ -3,7 +3,7 @@ from trezor.messages import ButtonRequestType, SafetyCheckLevel from trezor.messages.Success import Success from trezor.strings import format_duration_ms -from trezor.ui.layouts import confirm_action, require +from trezor.ui.layouts import confirm_action from apps.base import reload_settings_from_storage from apps.common import safety_checks @@ -100,27 +100,23 @@ async def apply_settings(ctx: wire.Context, msg: ApplySettings): async def require_confirm_change_homescreen(ctx): - await require( - confirm_action( - ctx, - "set_homescreen", - "Set homescreen", - description="Do you really want to change the homescreen image?", - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_homescreen", + "Set homescreen", + description="Do you really want to change the homescreen image?", + br_code=ButtonRequestType.ProtectCall, ) async def require_confirm_change_label(ctx, label): - await require( - confirm_action( - ctx, - "set_label", - "Change label", - description="Do you really want to change the label to {}?", - description_param=label, - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_label", + "Change label", + description="Do you really want to change the label to {}?", + description_param=label, + br_code=ButtonRequestType.ProtectCall, ) @@ -129,14 +125,12 @@ async def require_confirm_change_passphrase(ctx, use): description = "Do you really want to enable passphrase encryption?" else: description = "Do you really want to disable passphrase encryption?" - await require( - confirm_action( - ctx, - "set_passphrase", - "Enable passphrase" if use else "Disable passphrase", - description=description, - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_passphrase", + "Enable passphrase" if use else "Disable passphrase", + description=description, + br_code=ButtonRequestType.ProtectCall, ) @@ -147,14 +141,12 @@ async def require_confirm_change_passphrase_source( description = "Do you really want to enter passphrase always on the device?" else: description = "Do you want to revoke the passphrase on device setting?" - await require( - confirm_action( - ctx, - "set_passphrase_source", - "Passphrase source", - description=description, - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_passphrase_source", + "Passphrase source", + description=description, + br_code=ButtonRequestType.ProtectCall, ) @@ -169,70 +161,60 @@ async def require_confirm_change_display_rotation(ctx, rotation): label = "west" else: raise wire.DataError("Unsupported display rotation") - await require( - confirm_action( - ctx, - "set_rotation", - "Change rotation", - description="Do you really want to change display rotation to {}?", - description_param=label, - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_rotation", + "Change rotation", + description="Do you really want to change display rotation to {}?", + description_param=label, + br_code=ButtonRequestType.ProtectCall, ) async def require_confirm_change_autolock_delay(ctx, delay_ms): - await require( - confirm_action( - ctx, - "set_autolock_delay", - "Auto-lock delay", - description="Do you really want to auto-lock your device after {}?", - description_param=format_duration_ms(delay_ms), - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_autolock_delay", + "Auto-lock delay", + description="Do you really want to auto-lock your device after {}?", + description_param=format_duration_ms(delay_ms), + br_code=ButtonRequestType.ProtectCall, ) async def require_confirm_safety_checks(ctx, level: EnumTypeSafetyCheckLevel) -> None: if level == SafetyCheckLevel.PromptAlways: - await require( - confirm_action( - ctx, - "set_safety_checks", - "Safety override", - hold=True, - verb="Hold to confirm", - description="Trezor will allow you to approve some actions which might be unsafe.", - action="Are you sure?", - reverse=True, - larger_vspace=True, - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_safety_checks", + "Safety override", + hold=True, + verb="Hold to confirm", + description="Trezor will allow you to approve some actions which might be unsafe.", + action="Are you sure?", + reverse=True, + larger_vspace=True, + br_code=ButtonRequestType.ProtectCall, ) elif level == SafetyCheckLevel.PromptTemporarily: - await require( - confirm_action( - ctx, - "set_safety_checks", - "Safety override", - hold=True, - verb="Hold to confirm", - description="Trezor will temporarily allow you to approve some actions which might be unsafe.", - action="Are you sure?", - reverse=True, - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_safety_checks", + "Safety override", + hold=True, + verb="Hold to confirm", + description="Trezor will temporarily allow you to approve some actions which might be unsafe.", + action="Are you sure?", + reverse=True, + br_code=ButtonRequestType.ProtectCall, ) elif level == SafetyCheckLevel.Strict: - await require( - confirm_action( - ctx, - "set_safety_checks", - "Safety checks", - description="Do you really want to enforce strict safety checks (recommended)?", - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_safety_checks", + "Safety checks", + description="Do you really want to enforce strict safety checks (recommended)?", + br_code=ButtonRequestType.ProtectCall, ) else: raise ValueError # enum value out of range @@ -240,14 +222,12 @@ async def require_confirm_safety_checks(ctx, level: EnumTypeSafetyCheckLevel) -> async def require_confirm_experimental_features(ctx, enable: bool) -> None: if enable: - await require( - confirm_action( - ctx, - "set_experimental_features", - "Experimental mode", - description="Enable experimental features?", - action="Only for development and beta testing!", - reverse=True, - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "set_experimental_features", + "Experimental mode", + description="Enable experimental features?", + action="Only for development and beta testing!", + reverse=True, + br_code=ButtonRequestType.ProtectCall, ) diff --git a/core/src/apps/management/change_pin.py b/core/src/apps/management/change_pin.py index c21416e267f..13842e8d380 100644 --- a/core/src/apps/management/change_pin.py +++ b/core/src/apps/management/change_pin.py @@ -1,7 +1,7 @@ from storage.device import is_initialized from trezor import config, wire from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_action, require, show_success +from trezor.ui.layouts import confirm_action, show_success from apps.common.request_pin import ( error_pin_invalid, @@ -53,7 +53,7 @@ async def change_pin(ctx: wire.Context, msg: ChangePin) -> Success: msg_screen = "You have successfully disabled PIN protection." msg_wire = "PIN removed" - await require(show_success(ctx, "success_pin", msg_screen)) + await show_success(ctx, "success_pin", msg_screen) return Success(message=msg_wire) @@ -61,39 +61,33 @@ def require_confirm_change_pin(ctx: wire.Context, msg: ChangePin) -> None: has_pin = config.has_pin() if msg.remove and has_pin: # removing pin - return require( - confirm_action( - ctx, - "set_pin", - "Remove PIN", - description="Do you really want to", - action="disable PIN protection?", - reverse=True, - ) + return confirm_action( + ctx, + "set_pin", + "Remove PIN", + description="Do you really want to", + action="disable PIN protection?", + reverse=True, ) if not msg.remove and has_pin: # changing pin - return require( - confirm_action( - ctx, - "set_pin", - "Change PIN", - description="Do you really want to", - action="change your PIN?", - reverse=True, - ) + return confirm_action( + ctx, + "set_pin", + "Change PIN", + description="Do you really want to", + action="change your PIN?", + reverse=True, ) if not msg.remove and not has_pin: # setting new pin - return require( - confirm_action( - ctx, - "set_pin", - "Enable PIN", - description="Do you really want to", - action="enable PIN protection?", - reverse=True, - ) + return confirm_action( + ctx, + "set_pin", + "Enable PIN", + description="Do you really want to", + action="enable PIN protection?", + reverse=True, ) # removing non-existing PIN diff --git a/core/src/apps/management/change_wipe_code.py b/core/src/apps/management/change_wipe_code.py index 2088e89f446..845a2f7d175 100644 --- a/core/src/apps/management/change_wipe_code.py +++ b/core/src/apps/management/change_wipe_code.py @@ -2,7 +2,7 @@ from trezor import config, ui, wire from trezor.messages.Success import Success from trezor.ui.components.tt.text import Text -from trezor.ui.layouts import require, show_success +from trezor.ui.layouts import show_success from trezor.ui.popup import Popup from apps.common.confirm import require_confirm @@ -52,7 +52,7 @@ async def change_wipe_code(ctx: wire.Context, msg: ChangeWipeCode) -> Success: msg_screen = "You have successfully disabled the wipe code." msg_wire = "Wipe code removed" - await require(show_success(ctx, "success_wipe_code", msg_screen)) + await show_success(ctx, "success_wipe_code", msg_screen) return Success(message=msg_wire) diff --git a/core/src/apps/management/recovery_device/homescreen.py b/core/src/apps/management/recovery_device/homescreen.py index da5137b4474..dcdd2d7427c 100644 --- a/core/src/apps/management/recovery_device/homescreen.py +++ b/core/src/apps/management/recovery_device/homescreen.py @@ -8,7 +8,7 @@ from trezor.errors import MnemonicError from trezor.messages import BackupType from trezor.messages.Success import Success -from trezor.ui.layouts import require, show_success +from trezor.ui.layouts import show_success from apps.common import mnemonic from apps.homescreen.homescreen import homescreen @@ -146,10 +146,8 @@ async def _finish_recovery( storage.recovery.end_progress() - await require( - show_success( - ctx, "success_recovery", "You have successfully recovered your wallet." - ) + await show_success( + ctx, "success_recovery", "You have successfully recovered your wallet." ) return Success(message="Device recovered") diff --git a/core/src/apps/management/recovery_device/layout.py b/core/src/apps/management/recovery_device/layout.py index 48b7054c56f..38c01af7eb0 100644 --- a/core/src/apps/management/recovery_device/layout.py +++ b/core/src/apps/management/recovery_device/layout.py @@ -5,7 +5,7 @@ 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, require, show_success, show_warning +from trezor.ui.layouts import confirm_action, show_success, show_warning from apps.common import button_request from apps.common.confirm import confirm, info_confirm, require_confirm @@ -21,9 +21,9 @@ from trezor.messages.ResetDevice import EnumTypeBackupType -async def confirm_abort(ctx: wire.GenericContext, dry_run: bool = False) -> bool: +async def confirm_abort(ctx: wire.GenericContext, dry_run: bool = False) -> None: if dry_run: - return await confirm_action( + await confirm_action( ctx, "abort_recovery", "Abort seed check", @@ -32,7 +32,7 @@ async def confirm_abort(ctx: wire.GenericContext, dry_run: bool = False) -> bool br_code=ButtonRequestType.ProtectCall, ) else: - return await confirm_action( + await confirm_action( ctx, "abort_recovery", "Abort recovery", @@ -146,17 +146,13 @@ async def show_dry_run_result( text = "The entered recovery\nshares are valid and\nmatch what is currently\nin the device." else: text = "The entered recovery\nseed is valid and\nmatches the one\nin the device." - await require( - show_success(ctx, "success_dry_recovery", text, button="Continue") - ) + await show_success(ctx, "success_dry_recovery", text, button="Continue") else: if is_slip39: text = "The entered recovery\nshares are valid but\ndo not match what is\ncurrently in the device." else: text = "The entered recovery\nseed is valid but does\nnot match the one\nin the device." - await require( - show_warning(ctx, "warning_dry_recovery", text, button="Continue") - ) + await show_warning(ctx, "warning_dry_recovery", text, button="Continue") async def show_dry_run_different_type(ctx: wire.GenericContext) -> None: @@ -171,50 +167,40 @@ async def show_dry_run_different_type(ctx: wire.GenericContext) -> None: async def show_invalid_mnemonic(ctx: wire.GenericContext, word_count: int) -> None: if backup_types.is_slip39_word_count(word_count): - await require( - show_warning( - ctx, - "warning_invalid_share", - "You have entered\nan invalid recovery\nshare.", - ) + await show_warning( + ctx, + "warning_invalid_share", + "You have entered\nan invalid recovery\nshare.", ) else: - await require( - show_warning( - ctx, - "warning_invalid_seed", - "You have entered\nan invalid recovery\nseed.", - ) + await show_warning( + ctx, + "warning_invalid_seed", + "You have entered\nan invalid recovery\nseed.", ) async def show_share_already_added(ctx: wire.GenericContext) -> None: - await require( - show_warning( - ctx, - "warning_known_share", - "Share already entered,\nplease enter\na different share.", - ) + await show_warning( + ctx, + "warning_known_share", + "Share already entered,\nplease enter\na different share.", ) async def show_identifier_mismatch(ctx: wire.GenericContext) -> None: - await require( - show_warning( - ctx, - "warning_mismatched_share", - "You have entered\na share from another\nShamir Backup.", - ) + await show_warning( + ctx, + "warning_mismatched_share", + "You have entered\na share from another\nShamir Backup.", ) async def show_group_threshold_reached(ctx: wire.GenericContext) -> None: - await require( - show_warning( - ctx, - "warning_group_threshold", - "Threshold of this\ngroup has been reached.\nInput share from\ndifferent group.", - ) + await show_warning( + ctx, + "warning_group_threshold", + "Threshold of this\ngroup has been reached.\nInput share from\ndifferent group.", ) @@ -288,5 +274,9 @@ async def homescreen_dialog( break # user has chosen to abort, confirm the choice dry_run = storage.recovery.is_dry_run() - if await confirm_abort(ctx, dry_run): + try: + await confirm_abort(ctx, dry_run) + except wire.ActionCancelled: + pass + else: raise RecoveryAborted diff --git a/core/src/apps/management/reset_device/__init__.py b/core/src/apps/management/reset_device/__init__.py index d8e7aafd7af..567e74e6fdc 100644 --- a/core/src/apps/management/reset_device/__init__.py +++ b/core/src/apps/management/reset_device/__init__.py @@ -6,7 +6,7 @@ from trezor.messages.EntropyAck import EntropyAck from trezor.messages.EntropyRequest import EntropyRequest from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_backup, confirm_reset_device, require +from trezor.ui.layouts import confirm_backup, confirm_reset_device from trezor.ui.loader import LoadingAnimation from .. import backup_types @@ -33,7 +33,7 @@ async def reset_device(ctx: wire.Context, msg: ResetDevice) -> Success: prompt = "Create a new wallet\nwith Super Shamir?" else: prompt = "Do you want to create\na new wallet?" - await require(confirm_reset_device(ctx, prompt)) + await confirm_reset_device(ctx, prompt) await LoadingAnimation() # wipe storage to make sure the device is in a clear state diff --git a/core/src/apps/management/reset_device/layout.py b/core/src/apps/management/reset_device/layout.py index 166c3696a95..d0a079db560 100644 --- a/core/src/apps/management/reset_device/layout.py +++ b/core/src/apps/management/reset_device/layout.py @@ -9,13 +9,7 @@ from trezor.ui.components.tt.num_input import NumInput from trezor.ui.components.tt.scroll import Paginated from trezor.ui.components.tt.text import Text -from trezor.ui.layouts import ( - confirm_action, - confirm_hex, - require, - show_success, - show_warning, -) +from trezor.ui.layouts import confirm_action, confirm_hex, show_success, show_warning from apps.common.confirm import confirm, require_hold_to_confirm @@ -28,17 +22,15 @@ async def show_internal_entropy(ctx, entropy: bytes): - await require( - confirm_hex( - ctx, - "entropy", - "Internal entropy", - data=ubinascii.hexlify(entropy).decode(), - icon=ui.ICON_RESET, - icon_color=ui.ORANGE_ICON, - width=16, - br_code=ButtonRequestType.ResetDevice, - ) + await confirm_hex( + ctx, + "entropy", + "Internal entropy", + data=ubinascii.hexlify(entropy).decode(), + icon=ui.ICON_RESET, + icon_color=ui.ORANGE_ICON, + width=16, + br_code=ButtonRequestType.ResetDevice, ) @@ -192,9 +184,7 @@ async def _show_confirmation_success( ) text = "Continue with the next\nshare." - return await require( - show_success(ctx, "success_recovery", text, subheader=subheader) - ) + return await show_success(ctx, "success_recovery", text, subheader=subheader) async def _show_confirmation_failure(ctx, share_index): @@ -202,16 +192,14 @@ async def _show_confirmation_failure(ctx, share_index): header = "Recovery seed" else: header = "Recovery share #%s" % (share_index + 1) - await require( - show_warning( - ctx, - "warning_backup_check", - header=header, - subheader="That is the wrong word.", - content="Please check again.", - button="Check again", - br_code=ButtonRequestType.ResetDevice, - ) + await show_warning( + ctx, + "warning_backup_check", + header=header, + subheader="That is the wrong word.", + content="Please check again.", + button="Check again", + br_code=ButtonRequestType.ResetDevice, ) @@ -220,25 +208,21 @@ async def show_backup_warning(ctx, slip39=False): description = "Never make a digital copy of your recovery shares and never upload them online!" else: description = "Never make a digital copy of your recovery seed and never upload\nit online!" - await require( - confirm_action( - ctx, - "backup_warning", - "Caution", - description=description, - verb="I understand", - verb_cancel=None, - icon=ui.ICON_NOCOPY, - br_code=ButtonRequestType.ResetDevice, - ) + await confirm_action( + ctx, + "backup_warning", + "Caution", + description=description, + verb="I understand", + verb_cancel=None, + icon=ui.ICON_NOCOPY, + br_code=ButtonRequestType.ResetDevice, ) async def show_backup_success(ctx): text = "Use your backup\nwhen you need to\nrecover your wallet." - await require( - show_success(ctx, "success_backup", text, subheader="Your backup is done.") - ) + await show_success(ctx, "success_backup", text, subheader="Your backup is done.") # BIP39 diff --git a/core/src/apps/management/sd_protect.py b/core/src/apps/management/sd_protect.py index a113bcae508..ea4efb6ab0b 100644 --- a/core/src/apps/management/sd_protect.py +++ b/core/src/apps/management/sd_protect.py @@ -4,14 +4,14 @@ from trezor.crypto import random from trezor.messages import SdProtectOperationType from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_action, require, show_success +from trezor.ui.layouts import confirm_action, show_success from apps.common.request_pin import ( error_pin_invalid, request_pin, request_pin_and_sd_salt, ) -from apps.common.sdcard import ensure_sdcard, sd_problem_dialog +from apps.common.sdcard import confirm_retry_sd, ensure_sdcard if False: from typing import Awaitable, Tuple @@ -33,8 +33,7 @@ async def _set_salt( try: return storage.sd_salt.set_sd_salt(salt, salt_tag, stage) except OSError: - if not await sd_problem_dialog(ctx): - raise wire.ProcessError("SD card I/O error.") + await confirm_retry_sd(ctx, exc=wire.ProcessError("SD card I/O error.")) async def sd_protect(ctx: wire.Context, msg: SdProtect) -> Success: @@ -84,8 +83,8 @@ async def sd_protect_enable(ctx: wire.Context, msg: SdProtect) -> Success: storage.device.set_sd_salt_auth_key(salt_auth_key) - await require( - show_success(ctx, "success_sd", "You have successfully enabled SD protection.") + await show_success( + ctx, "success_sd", "You have successfully enabled SD protection." ) return Success(message="SD card protection enabled") @@ -118,8 +117,8 @@ async def sd_protect_disable(ctx: wire.Context, msg: SdProtect) -> Success: # because overall SD-protection was successfully disabled. pass - await require( - show_success(ctx, "success_sd", "You have successfully disabled SD protection.") + await show_success( + ctx, "success_sd", "You have successfully disabled SD protection." ) return Success(message="SD card protection disabled") @@ -155,10 +154,8 @@ async def sd_protect_refresh(ctx: wire.Context, msg: SdProtect) -> Success: # SD-protection was successfully refreshed. pass - await require( - show_success( - ctx, "success_sd", "You have successfully refreshed SD protection." - ) + await show_success( + ctx, "success_sd", "You have successfully refreshed SD protection." ) return Success(message="SD card protection refreshed") @@ -173,6 +170,4 @@ def require_confirm_sd_protect(ctx: wire.Context, msg: SdProtect) -> Awaitable[N else: raise wire.ProcessError("Unknown operation") - return require( - confirm_action(ctx, "set_sd", "SD card protection", description=text) - ) + return confirm_action(ctx, "set_sd", "SD card protection", description=text) diff --git a/core/src/apps/management/wipe_device.py b/core/src/apps/management/wipe_device.py index 1d3e565383f..f0cfae21dce 100644 --- a/core/src/apps/management/wipe_device.py +++ b/core/src/apps/management/wipe_device.py @@ -1,12 +1,12 @@ import storage from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_wipe, require +from trezor.ui.layouts import confirm_wipe from .apply_settings import reload_settings_from_storage async def wipe_device(ctx, msg): - await require(confirm_wipe(ctx)) + await confirm_wipe(ctx) storage.wipe() reload_settings_from_storage() diff --git a/core/src/apps/misc/cipher_key_value.py b/core/src/apps/misc/cipher_key_value.py index 83d65a67e32..173f1d18f3b 100644 --- a/core/src/apps/misc/cipher_key_value.py +++ b/core/src/apps/misc/cipher_key_value.py @@ -1,7 +1,7 @@ from trezor import wire from trezor.crypto import aes, hmac from trezor.messages.CipheredKeyValue import CipheredKeyValue -from trezor.ui.layouts import confirm_action, require +from trezor.ui.layouts import confirm_action from apps.common.keychain import get_keychain from apps.common.paths import AlwaysMatchingSchema @@ -27,9 +27,7 @@ async def cipher_key_value(ctx: Context, msg: CipherKeyValue) -> CipheredKeyValu title = "Encrypt value" else: title = "Decrypt value" - await require( - confirm_action(ctx, "cipher_key_value", title, description=msg.key) - ) + await confirm_action(ctx, "cipher_key_value", title, description=msg.key) node = keychain.derive(msg.address_n) value = compute_cipher_key_value(msg, node.private_key()) diff --git a/core/src/apps/misc/get_ecdh_session_key.py b/core/src/apps/misc/get_ecdh_session_key.py index 06f76379cb2..b3a0a6885a8 100644 --- a/core/src/apps/misc/get_ecdh_session_key.py +++ b/core/src/apps/misc/get_ecdh_session_key.py @@ -3,7 +3,7 @@ from trezor import ui, wire from trezor.crypto.hashlib import sha256 from trezor.messages.ECDHSessionKey import ECDHSessionKey -from trezor.ui.layouts import confirm_hex, require +from trezor.ui.layouts import confirm_hex from apps.common import HARDENED from apps.common.keychain import get_keychain @@ -47,15 +47,13 @@ async def require_confirm_ecdh_session_key( ctx: wire.Context, identity: IdentityType ) -> None: proto = identity.proto.upper() if identity.proto else "identity" - await require( - confirm_hex( - ctx, - "ecdh_session_key", - "Decrypt %s" % proto, - serialize_identity_without_proto(identity), - icon=ui.ICON_DEFAULT, - icon_color=ui.ORANGE_ICON, - ) + await confirm_hex( + ctx, + "ecdh_session_key", + "Decrypt %s" % proto, + serialize_identity_without_proto(identity), + icon=ui.ICON_DEFAULT, + icon_color=ui.ORANGE_ICON, ) diff --git a/core/src/apps/misc/get_entropy.py b/core/src/apps/misc/get_entropy.py index 86dfa76e837..a1d8d102f26 100644 --- a/core/src/apps/misc/get_entropy.py +++ b/core/src/apps/misc/get_entropy.py @@ -1,7 +1,7 @@ from trezor.crypto import random from trezor.messages import ButtonRequestType from trezor.messages.Entropy import Entropy -from trezor.ui.layouts import confirm_action, require +from trezor.ui.layouts import confirm_action if False: from trezor.wire import Context @@ -9,15 +9,13 @@ async def get_entropy(ctx: Context, msg: GetEntropy) -> Entropy: - await require( - confirm_action( - ctx, - "get_entropy", - "Confirm entropy", - action="Do you really want\nto send entropy?", - description="Continue only if you\nknow what you are doing!", - br_code=ButtonRequestType.ProtectCall, - ) + await confirm_action( + ctx, + "get_entropy", + "Confirm entropy", + action="Do you really want\nto send entropy?", + description="Continue only if you\nknow what you are doing!", + br_code=ButtonRequestType.ProtectCall, ) size = min(msg.size, 1024) diff --git a/core/src/apps/misc/sign_identity.py b/core/src/apps/misc/sign_identity.py index 80f74270b7f..4f86afe87b7 100644 --- a/core/src/apps/misc/sign_identity.py +++ b/core/src/apps/misc/sign_identity.py @@ -3,7 +3,7 @@ from trezor import wire from trezor.crypto.hashlib import sha256 from trezor.messages.SignedIdentity import SignedIdentity -from trezor.ui.layouts import confirm_sign_identity, require +from trezor.ui.layouts import confirm_sign_identity from apps.common import HARDENED, coininfo from apps.common.keychain import get_keychain @@ -84,10 +84,8 @@ async def require_confirm_sign_identity( ctx: wire.Context, identity: IdentityType, challenge_visual: Optional[str] ) -> None: proto = identity.proto.upper() if identity.proto else "identity" - await require( - confirm_sign_identity( - ctx, proto, serialize_identity_without_proto(identity), challenge_visual - ) + await confirm_sign_identity( + ctx, proto, serialize_identity_without_proto(identity), challenge_visual ) diff --git a/core/src/apps/monero/layout/confirms.py b/core/src/apps/monero/layout/confirms.py index a4cf85e25b9..198bbf49604 100644 --- a/core/src/apps/monero/layout/confirms.py +++ b/core/src/apps/monero/layout/confirms.py @@ -3,7 +3,7 @@ from trezor import ui, wire from trezor.messages import ButtonRequestType from trezor.ui.components.tt.text import Text -from trezor.ui.layouts import confirm_action, require +from trezor.ui.layouts import confirm_action from trezor.ui.popup import Popup from trezor.utils import chunks @@ -23,44 +23,38 @@ async def require_confirm_watchkey(ctx): - await require( - confirm_action( - ctx, - "get_watchkey", - "Confirm export", - description="Do you really want to export watch-only credentials?", - icon=ui.ICON_SEND, - icon_color=ui.GREEN, - br_code=ButtonRequestType.SignTx, - ) + await confirm_action( + ctx, + "get_watchkey", + "Confirm export", + description="Do you really want to export watch-only credentials?", + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, ) async def require_confirm_keyimage_sync(ctx): - await require( - confirm_action( - ctx, - "key_image_sync", - "Confirm ki sync", - description="Do you really want to\nsync key images?", - icon=ui.ICON_SEND, - icon_color=ui.GREEN, - br_code=ButtonRequestType.SignTx, - ) + await confirm_action( + ctx, + "key_image_sync", + "Confirm ki sync", + description="Do you really want to\nsync key images?", + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, ) async def require_confirm_live_refresh(ctx): - await require( - confirm_action( - ctx, - "live_refresh", - "Confirm refresh", - description="Do you really want to\nstart refresh?", - icon=ui.ICON_SEND, - icon_color=ui.GREEN, - br_code=ButtonRequestType.SignTx, - ) + await confirm_action( + ctx, + "live_refresh", + "Confirm refresh", + description="Do you really want to\nstart refresh?", + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, ) @@ -69,16 +63,14 @@ async def require_confirm_tx_key(ctx, export_key=False): description = "Do you really want to export tx_key?" else: description = "Do you really want to export tx_der\nfor tx_proof?" - await require( - confirm_action( - ctx, - "export_tx_key", - "Confirm export", - description=description, - icon=ui.ICON_SEND, - icon_color=ui.GREEN, - br_code=ButtonRequestType.SignTx, - ) + await confirm_action( + ctx, + "export_tx_key", + "Confirm export", + description=description, + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, ) diff --git a/core/src/apps/tezos/get_public_key.py b/core/src/apps/tezos/get_public_key.py index a528c1d144b..fbd043d97b7 100644 --- a/core/src/apps/tezos/get_public_key.py +++ b/core/src/apps/tezos/get_public_key.py @@ -1,5 +1,5 @@ from trezor.messages.TezosPublicKey import TezosPublicKey -from trezor.ui.layouts import require, show_pubkey +from trezor.ui.layouts import show_pubkey from apps.common import paths, seed from apps.common.keychain import with_slip44_keychain @@ -16,6 +16,6 @@ async def get_public_key(ctx, msg, keychain): pk_prefixed = helpers.base58_encode_check(pk, prefix=helpers.TEZOS_PUBLICKEY_PREFIX) if msg.show_display: - await require(show_pubkey(ctx, pk_prefixed)) + await show_pubkey(ctx, pk_prefixed) return TezosPublicKey(public_key=pk_prefixed) diff --git a/core/src/apps/webauthn/add_resident_credential.py b/core/src/apps/webauthn/add_resident_credential.py index 822f26f0979..0bf2109a3d7 100644 --- a/core/src/apps/webauthn/add_resident_credential.py +++ b/core/src/apps/webauthn/add_resident_credential.py @@ -2,7 +2,7 @@ from trezor import wire from trezor.messages.Success import Success from trezor.messages.WebAuthnAddResidentCredential import WebAuthnAddResidentCredential -from trezor.ui.layouts import require, show_error +from trezor.ui.layouts import show_error_and_raise from apps.common.confirm import require_confirm @@ -41,17 +41,14 @@ async def add_resident_credential( try: cred = Fido2Credential.from_cred_id(bytes(msg.credential_id), None) except Exception: - await require( - show_error( - ctx, - "warning_credential", - header="Import credential", - button="Close", - content="The credential you are trying to import does\nnot belong to this authenticator.", - red=True, - ) + await show_error_and_raise( + ctx, + "warning_credential", + header="Import credential", + button="Close", + content="The credential you are trying to import does\nnot belong to this authenticator.", + red=True, ) - raise wire.ActionCancelled content = ConfirmContent(ConfirmAddCredential(cred)) await require_confirm(ctx, content) diff --git a/core/src/apps/webauthn/list_resident_credentials.py b/core/src/apps/webauthn/list_resident_credentials.py index 8d7fa405ce1..9cee7fea1da 100644 --- a/core/src/apps/webauthn/list_resident_credentials.py +++ b/core/src/apps/webauthn/list_resident_credentials.py @@ -4,7 +4,7 @@ from trezor.messages.WebAuthnListResidentCredentials import ( WebAuthnListResidentCredentials, ) -from trezor.ui.layouts import confirm_action, require +from trezor.ui.layouts import confirm_action from . import resident_credentials @@ -12,13 +12,11 @@ async def list_resident_credentials( ctx: wire.Context, msg: WebAuthnListResidentCredentials ) -> WebAuthnCredentials: - await require( - confirm_action( - ctx, - "credentials_list", - title="List credentials", - description="Do you want to export information about the resident credentials stored on this device?", - ) + await confirm_action( + ctx, + "credentials_list", + title="List credentials", + description="Do you want to export information about the resident credentials stored on this device?", ) creds = [ WebAuthnCredential( diff --git a/core/src/trezor/ui/components/common/confirm.py b/core/src/trezor/ui/components/common/confirm.py index 941d364095e..4a163ea7826 100644 --- a/core/src/trezor/ui/components/common/confirm.py +++ b/core/src/trezor/ui/components/common/confirm.py @@ -1,10 +1,10 @@ -from trezor import loop, ui +from trezor import loop, ui, wire if __debug__: from apps.debug import confirm_signal if False: - from typing import List, Tuple, Optional, Any + from typing import List, Tuple, Optional, Any, Awaitable CONFIRMED = object() CANCELLED = object() @@ -15,6 +15,12 @@ def is_confirmed(x: Any) -> bool: return x is CONFIRMED +async def raise_if_cancelled(a: Awaitable, exc: Any = wire.ActionCancelled) -> None: + result = await a + if result is CANCELLED: + raise exc + + class ConfirmBase(ui.Layout): def __init__( self, diff --git a/core/src/trezor/ui/layouts/common.py b/core/src/trezor/ui/layouts/common.py index 0b41ed59504..74c0d0ca9e5 100644 --- a/core/src/trezor/ui/layouts/common.py +++ b/core/src/trezor/ui/layouts/common.py @@ -11,12 +11,6 @@ LayoutType = Awaitable[Any] -async def require(a: Awaitable[bool]) -> None: - result = await a - if not result: - raise wire.ActionCancelled - - async def interact( ctx: wire.GenericContext, layout: LayoutType, diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index dbb6a829933..52d8b82ad4f 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -1,6 +1,6 @@ from micropython import const -from trezor import ui +from trezor import ui, wire from trezor.messages import ButtonRequestType from trezor.ui.container import Container from trezor.ui.loader import LoaderDanger @@ -8,7 +8,7 @@ from trezor.utils import chunks from ..components.common import break_path_to_lines -from ..components.common.confirm import is_confirmed +from ..components.common.confirm import is_confirmed, raise_if_cancelled from ..components.tt.button import ButtonCancel, ButtonDefault from ..components.tt.confirm import Confirm, HoldToConfirm from ..components.tt.scroll import Paginated, paginate_text @@ -24,13 +24,23 @@ from .common import interact if False: - from typing import Any, Iterator, List, Sequence, Union, Optional, Awaitable + from typing import ( + Iterator, + List, + Sequence, + Type, + Union, + Optional, + Awaitable, + NoReturn, + ) - from trezor import wire from trezor.messages.ButtonRequest import EnumTypeButtonRequestType from ..components.common.text import TextContent + ExceptionType = Union[BaseException, Type[BaseException]] + __all__ = ( "confirm_action", "confirm_wipe", @@ -40,7 +50,7 @@ "confirm_sign_identity", "confirm_signverify", "show_address", - "show_error", + "show_error_and_raise", "show_pubkey", "show_success", "show_xpub", @@ -73,9 +83,10 @@ async def confirm_action( icon_color: int = None, # TODO cleanup @ redesign reverse: bool = False, # TODO cleanup @ redesign larger_vspace: bool = False, # TODO cleanup @ redesign + exc: ExceptionType = wire.ActionCancelled, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, **kwargs: Any, -) -> bool: +) -> None: text = Text( title, icon if icon is not None else ui.ICON_DEFAULT, @@ -107,23 +118,24 @@ async def confirm_action( ) cls = HoldToConfirm if hold else Confirm - return is_confirmed( - await interact( + await raise_if_cancelled( + interact( ctx, cls(text, confirm=verb, cancel=verb_cancel), br_type, br_code, - ) + ), + exc, ) # TODO cleanup @ redesign -async def confirm_wipe(ctx: wire.GenericContext) -> bool: +async def confirm_wipe(ctx: wire.GenericContext) -> None: text = Text("Wipe device", ui.ICON_WIPE, ui.RED) text.normal("Do you really want to", "wipe the device?", "") text.bold("All data will be lost.") - return is_confirmed( - await interact( + await raise_if_cancelled( + interact( ctx, HoldToConfirm(text, confirm_style=ButtonCancel, loader_style=LoaderDanger), "wipe_device", @@ -132,7 +144,7 @@ async def confirm_wipe(ctx: wire.GenericContext) -> bool: ) -async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> bool: +async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> None: text = Text("Create new wallet", ui.ICON_RESET, new_lines=False) text.bold(prompt) text.br() @@ -141,8 +153,8 @@ async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> bool: text.br() text.normal("to ") text.bold("https://trezor.io/tos") - return is_confirmed( - await interact( + await raise_if_cancelled( + interact( ctx, Confirm(text, major_confirm=True), "setup_device", @@ -184,13 +196,13 @@ async def confirm_backup(ctx: wire.GenericContext) -> bool: return confirmed -async def confirm_path_warning(ctx: wire.GenericContext, path: str) -> bool: +async def confirm_path_warning(ctx: wire.GenericContext, path: str) -> None: text = Text("Confirm path", ui.ICON_WRONG, ui.RED) text.normal("Path") text.mono(*break_path_to_lines(path, MONO_CHARS_PER_LINE)) text.normal("is unknown.", "Are you sure?") - return is_confirmed( - await interact( + await raise_if_cancelled( + interact( ctx, Confirm(text), "path_warning", @@ -266,9 +278,9 @@ def _show_xpub(xpub: str, desc: str, cancel: str) -> Paginated: async def show_xpub( ctx: wire.GenericContext, xpub: str, desc: str, cancel: str -) -> bool: - return is_confirmed( - await interact( +) -> None: + await raise_if_cancelled( + interact( ctx, _show_xpub(xpub, desc, cancel), "show_xpub", @@ -329,7 +341,7 @@ async def show_address( def show_pubkey( ctx: wire.Context, pubkey: str, title: str = "Confirm public key" -) -> Awaitable[bool]: +) -> Awaitable[None]: return confirm_hex( ctx, br_type="show_pubkey", @@ -351,24 +363,26 @@ async def _show_modal( button_cancel: Optional[str], icon: str, icon_color: int, -) -> bool: + exc: ExceptionType = wire.ActionCancelled, +) -> None: text = Text(header, icon, icon_color, new_lines=False) if subheader: text.bold(subheader) text.br() text.br_half() text.normal(content) - return is_confirmed( - await interact( + await raise_if_cancelled( + interact( ctx, Confirm(text, confirm=button_confirm, cancel=button_cancel), br_type, br_code, - ) + ), + exc, ) -def show_error( +async def show_error_and_raise( ctx: wire.GenericContext, br_type: str, content: str, @@ -376,8 +390,9 @@ def show_error( subheader: Optional[str] = None, button: str = "Close", red: bool = False, -) -> Awaitable[bool]: - return _show_modal( + exc: ExceptionType = wire.ActionCancelled, +) -> NoReturn: + await _show_modal( ctx, br_type=br_type, br_code=ButtonRequestType.Other, @@ -388,7 +403,9 @@ def show_error( button_cancel=button, icon=ui.ICON_WRONG, icon_color=ui.RED if red else ui.ORANGE_ICON, + exc=exc, ) + raise exc def show_warning( @@ -399,7 +416,7 @@ def show_warning( subheader: Optional[str] = None, button: str = "Try again", br_code: EnumTypeButtonRequestType = ButtonRequestType.Warning, -) -> Awaitable[bool]: +) -> Awaitable[None]: return _show_modal( ctx, br_type=br_type, @@ -420,7 +437,7 @@ def show_success( content: str, subheader: Optional[str] = None, button: str = "Continue", -) -> Awaitable[bool]: +) -> Awaitable[None]: return _show_modal( ctx, br_type=br_type, @@ -439,14 +456,12 @@ async def confirm_output( ctx: wire.GenericContext, address: str, amount: str, -) -> bool: +) -> None: text = Text("Confirm sending", ui.ICON_SEND, ui.GREEN) text.normal(amount + " to") text.mono(*_split_address(address)) - return is_confirmed( - await interact( - ctx, Confirm(text), "confirm_output", ButtonRequestType.ConfirmOutput - ) + await raise_if_cancelled( + interact(ctx, Confirm(text), "confirm_output", ButtonRequestType.ConfirmOutput) ) @@ -454,13 +469,13 @@ async def confirm_decred_sstx_submission( ctx: wire.GenericContext, address: str, amount: str, -) -> bool: +) -> None: text = Text("Purchase ticket", ui.ICON_SEND, ui.GREEN) text.normal(amount) text.normal("with voting rights to") text.mono(*_split_address(address)) - return is_confirmed( - await interact( + await raise_if_cancelled( + interact( ctx, Confirm(text), "confirm_decred_sstx_submission", @@ -480,7 +495,7 @@ async def confirm_hex( icon_color: int = ui.GREEN, # TODO cleanup @ redesign width: int = MONO_HEX_PER_LINE, truncate_middle: bool = False, -) -> bool: +) -> None: text = Text(title, icon, icon_color, new_lines=False) description_lines = 0 if description is not None: @@ -496,34 +511,32 @@ async def confirm_hex( ) ) content: ui.Layout = Confirm(text) - return is_confirmed(await interact(ctx, content, br_type, br_code)) + await raise_if_cancelled(interact(ctx, content, br_type, br_code)) async def confirm_total( ctx: wire.GenericContext, total_amount: str, fee_amount: str -) -> bool: +) -> None: text = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) text.normal("Total amount:") text.bold(total_amount) text.normal("including fee:") text.bold(fee_amount) - return is_confirmed( - await interact( - ctx, HoldToConfirm(text), "confirm_total", ButtonRequestType.SignTx - ) + await raise_if_cancelled( + interact(ctx, HoldToConfirm(text), "confirm_total", ButtonRequestType.SignTx) ) async def confirm_joint_total( ctx: wire.GenericContext, spending_amount: str, total_amount: str -) -> bool: +) -> None: text = Text("Joint transaction", ui.ICON_SEND, ui.GREEN) text.normal("You are contributing:") text.bold(spending_amount) text.normal("to the total amount:") text.bold(total_amount) - return is_confirmed( - await interact( + await raise_if_cancelled( + interact( ctx, HoldToConfirm(text), "confirm_joint_total", ButtonRequestType.SignTx ) ) @@ -536,26 +549,24 @@ async def confirm_metadata( content: str, param: Optional[str] = None, br_code: EnumTypeButtonRequestType = ButtonRequestType.SignTx, -) -> bool: +) -> None: text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) text.format_parametrized(content, param if param is not None else "") text.br() text.normal("Continue?") - return is_confirmed(await interact(ctx, Confirm(text), br_type, br_code)) + await raise_if_cancelled(interact(ctx, Confirm(text), br_type, br_code)) async def confirm_replacement( ctx: wire.GenericContext, description: str, txid: str -) -> bool: +) -> None: text = Text(description, ui.ICON_SEND, ui.GREEN) text.normal("Confirm transaction ID:") text.mono(*_truncate_hex(txid, TEXT_MAX_LINES - 1)) - return is_confirmed( - await interact( - ctx, Confirm(text), "confirm_replacement", ButtonRequestType.SignTx - ) + await raise_if_cancelled( + interact(ctx, Confirm(text), "confirm_replacement", ButtonRequestType.SignTx) ) @@ -565,7 +576,7 @@ async def confirm_modify_output( sign: int, amount_change: str, amount_new: str, -) -> bool: +) -> None: page1 = Text("Modify amount", ui.ICON_SEND, ui.GREEN) page1.normal("Address:") page1.br_half() @@ -581,7 +592,7 @@ async def confirm_modify_output( page2.normal("New amount:") page2.bold(amount_new) - return is_confirmed( + await raise_if_cancelled( interact( ctx, Paginated([page1, Confirm(page2)]), @@ -596,7 +607,7 @@ async def confirm_modify_fee( sign: int, user_fee_change: str, total_fee_new: str, -) -> bool: +) -> None: text = Text("Modify fee", ui.ICON_SEND, ui.GREEN) if sign == 0: text.normal("Your fee did not change.") @@ -609,31 +620,29 @@ async def confirm_modify_fee( text.br_half() text.normal("Transaction fee:") text.bold(total_fee_new) - return is_confirmed( - await interact(ctx, HoldToConfirm(text), "modify_fee", ButtonRequestType.SignTx) + await raise_if_cancelled( + interact(ctx, HoldToConfirm(text), "modify_fee", ButtonRequestType.SignTx) ) async def confirm_coinjoin( ctx: wire.GenericContext, fee_per_anonymity: Optional[str], total_fee: str -) -> bool: +) -> None: text = Text("Authorize CoinJoin", ui.ICON_RECOVERY, new_lines=False) if fee_per_anonymity is not None: text.normal("Fee per anonymity set:\n") text.bold("{} %\n".format(fee_per_anonymity)) text.normal("Maximum total fees:\n") text.bold(total_fee) - return is_confirmed( - await interact( - ctx, HoldToConfirm(text), "coinjoin_final", ButtonRequestType.Other - ) + await raise_if_cancelled( + interact(ctx, HoldToConfirm(text), "coinjoin_final", ButtonRequestType.Other) ) # TODO cleanup @ redesign async def confirm_sign_identity( ctx: wire.GenericContext, proto: str, identity: str, challenge_visual: Optional[str] -) -> bool: +) -> None: lines: List[TextContent] = [] if challenge_visual: lines.append(challenge_visual) @@ -643,14 +652,14 @@ async def confirm_sign_identity( text = Text("Sign %s" % proto) text.normal(*lines) - return is_confirmed( - await interact(ctx, Confirm(text), "sign_identity", ButtonRequestType.Other) + await raise_if_cancelled( + interact(ctx, Confirm(text), "sign_identity", ButtonRequestType.Other) ) async def confirm_signverify( ctx: wire.GenericContext, coin: str, message: str, address: str = None -) -> bool: +) -> None: if address: header = "Verify {} message".format(coin) font = ui.MONO @@ -659,17 +668,16 @@ async def confirm_signverify( text = Text(header) text.bold("Confirm address:") text.mono(*_split_address(address)) - if not is_confirmed( - await interact(ctx, Confirm(text), br_type, ButtonRequestType.Other) - ): - return False + await raise_if_cancelled( + interact(ctx, Confirm(text), br_type, ButtonRequestType.Other) + ) else: header = "Sign {} message".format(coin) font = ui.NORMAL br_type = "sign_message" - return is_confirmed( - await interact( + await raise_if_cancelled( + interact( ctx, paginate_text(message, header, font=font), br_type, From e5cc82eaddf7380e74ecff6244afcd3d3d7bc44e Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 16 Mar 2021 13:37:34 +0100 Subject: [PATCH 12/13] refactor(core/ui): get rid of confirm_wipe --- core/src/apps/management/wipe_device.py | 19 +++++++++++++++++-- core/src/trezor/ui/layouts/tt.py | 23 +++++------------------ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/core/src/apps/management/wipe_device.py b/core/src/apps/management/wipe_device.py index f0cfae21dce..d5b2dc1893a 100644 --- a/core/src/apps/management/wipe_device.py +++ b/core/src/apps/management/wipe_device.py @@ -1,12 +1,27 @@ import storage +from trezor import ui +from trezor.messages import ButtonRequestType from trezor.messages.Success import Success -from trezor.ui.layouts import confirm_wipe +from trezor.ui.layouts import confirm_action from .apply_settings import reload_settings_from_storage async def wipe_device(ctx, msg): - await confirm_wipe(ctx) + await confirm_action( + ctx, + "confirm_wipe", + title="Wipe device", + description="Do you really want to\nwipe the device?\n", + action="All data will be lost.", + reverse=True, + verb="Hold to confirm", + hold=True, + hold_danger=True, + icon=ui.ICON_WIPE, + icon_color=ui.RED, + br_code=ButtonRequestType.WipeDevice, + ) storage.wipe() reload_settings_from_storage() diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index 52d8b82ad4f..5dbb3946023 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -43,7 +43,6 @@ __all__ = ( "confirm_action", - "confirm_wipe", "confirm_reset_device", "confirm_backup", "confirm_path_warning", @@ -79,13 +78,13 @@ async def confirm_action( verb: Union[str, bytes, None] = Confirm.DEFAULT_CONFIRM, verb_cancel: Union[str, bytes, None] = Confirm.DEFAULT_CANCEL, hold: bool = False, + hold_danger: bool = False, icon: str = None, # TODO cleanup @ redesign icon_color: int = None, # TODO cleanup @ redesign reverse: bool = False, # TODO cleanup @ redesign larger_vspace: bool = False, # TODO cleanup @ redesign exc: ExceptionType = wire.ActionCancelled, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, - **kwargs: Any, ) -> None: text = Text( title, @@ -118,10 +117,13 @@ async def confirm_action( ) cls = HoldToConfirm if hold else Confirm + kwargs = {} + if hold_danger: + kwargs = {"loader_style": LoaderDanger, "confirm_style": ButtonCancel} await raise_if_cancelled( interact( ctx, - cls(text, confirm=verb, cancel=verb_cancel), + cls(text, confirm=verb, cancel=verb_cancel, **kwargs), br_type, br_code, ), @@ -129,21 +131,6 @@ async def confirm_action( ) -# TODO cleanup @ redesign -async def confirm_wipe(ctx: wire.GenericContext) -> None: - text = Text("Wipe device", ui.ICON_WIPE, ui.RED) - text.normal("Do you really want to", "wipe the device?", "") - text.bold("All data will be lost.") - await raise_if_cancelled( - interact( - ctx, - HoldToConfirm(text, confirm_style=ButtonCancel, loader_style=LoaderDanger), - "wipe_device", - ButtonRequestType.WipeDevice, - ) - ) - - async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> None: text = Text("Create new wallet", ui.ICON_RESET, new_lines=False) text.bold(prompt) From 3b4eea1c41328c17413f23a850bc8d5a908ea2de Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 16 Mar 2021 17:38:20 +0100 Subject: [PATCH 13/13] chore(core/ui): typing: no implicit Optional --- core/src/trezor/ui/layouts/tt.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index 5dbb3946023..9e48d79e18a 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -71,16 +71,16 @@ async def confirm_action( ctx: wire.GenericContext, br_type: str, title: str, - action: str = None, - description: str = None, - description_param: str = None, + action: Optional[str] = None, + description: Optional[str] = None, + description_param: Optional[str] = None, description_param_font: int = ui.BOLD, verb: Union[str, bytes, None] = Confirm.DEFAULT_CONFIRM, verb_cancel: Union[str, bytes, None] = Confirm.DEFAULT_CANCEL, hold: bool = False, hold_danger: bool = False, - icon: str = None, # TODO cleanup @ redesign - icon_color: int = None, # TODO cleanup @ redesign + icon: Optional[str] = None, # TODO cleanup @ redesign + icon_color: Optional[int] = None, # TODO cleanup @ redesign reverse: bool = False, # TODO cleanup @ redesign larger_vspace: bool = False, # TODO cleanup @ redesign exc: ExceptionType = wire.ActionCancelled, @@ -235,7 +235,7 @@ def _truncate_hex( def _show_address( address: str, desc: str, - network: str = None, + network: Optional[str] = None, ) -> Confirm: text = Text(desc, ui.ICON_RECEIVE, ui.GREEN) if network is not None: @@ -279,10 +279,10 @@ async def show_xpub( async def show_address( ctx: wire.GenericContext, address: str, - address_qr: str = None, + address_qr: Optional[str] = None, desc: str = "Confirm address", - network: str = None, - multisig_index: int = None, + network: Optional[str] = None, + multisig_index: Optional[int] = None, xpubs: Sequence[str] = [], ) -> None: is_multisig = len(xpubs) > 0 @@ -476,7 +476,7 @@ async def confirm_hex( br_type: str, title: str, data: str, - description: str = None, + description: Optional[str] = None, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign @@ -645,7 +645,7 @@ async def confirm_sign_identity( async def confirm_signverify( - ctx: wire.GenericContext, coin: str, message: str, address: str = None + ctx: wire.GenericContext, coin: str, message: str, address: Optional[str] = None ) -> None: if address: header = "Verify {} message".format(coin)