From dcc38f5267846bea6d8ee8b39ec8d6d714dc06ad Mon Sep 17 00:00:00 2001 From: matejcik Date: Fri, 16 Jul 2021 11:48:24 +0200 Subject: [PATCH] refactor(core/ui): replace confirm_hex, clarify API Truncation options were removed. Subtitle distinct from description was removed. confirm_hex was replaced by confirm_blob. You should use confirm_blob when displaying data that is not human readable and can be broken at any character. Also it is now possible to pass bytes, which are automatically converted to hex. For displaying addresses, a separate confirm_address is introduced, which simply delegates to confirm_blob, but has a more limited signature. Analogously, there is confirm_text for text data (should maybe be used in many places where we currently use confirm_metadata) and a specialized confirm_amount. --- core/src/trezor/ui/layouts/tt.py | 179 ++++++++++++++++++++++++------- 1 file changed, 139 insertions(+), 40 deletions(-) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index d1ca8355cb..f34d75b4a3 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -43,6 +43,9 @@ if False: __all__ = ( "confirm_action", + "confirm_address", + "confirm_text", + "confirm_amount", "confirm_reset_device", "confirm_backup", "confirm_path_warning", @@ -56,7 +59,7 @@ __all__ = ( "show_warning", "confirm_output", "confirm_decred_sstx_submission", - "confirm_hex", + "confirm_blob", "confirm_properties", "confirm_total", "confirm_total_ethereum", @@ -356,14 +359,13 @@ async def show_address( def show_pubkey( ctx: wire.Context, pubkey: str, title: str = "Confirm public key" ) -> Awaitable[None]: - return confirm_hex( + return confirm_blob( ctx, br_type="show_pubkey", title="Confirm public key", data=pubkey, br_code=ButtonRequestType.PublicKey, icon=ui.ICON_RECEIVE, - truncate=True, # should fit? ) @@ -521,64 +523,161 @@ async def confirm_decred_sstx_submission( ) -async def confirm_hex( +async def confirm_blob( ctx: wire.GenericContext, br_type: str, title: str, - data: str, - subtitle: str | None = None, + data: bytes | str, description: str | None = None, br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign - color_description: int = ui.FG, # TODO cleanup @ redesign - width: int | None = MONO_HEX_PER_LINE, - width_paginated: int = MONO_HEX_PER_LINE - 2, - truncate: bool = False, - truncate_middle: bool = False, - truncate_ellipsis: str = "...", ) -> None: - if truncate: + """Confirm data blob. + + Applicable for public keys, signatures, hashes. In general, any kind of + data that is not human-readable, and can be wrapped at any character. + + For addresses, use `confirm_address`. + + Displays in monospace font. Paginates automatically. + If data is provided as bytes or bytearray, it is converted to hex. + """ + if isinstance(data, (bytes, bytearray)): + data_str = hexlify(data).decode() + else: + data_str = data + + span = Span() + lines = 0 + if description is not None: + span.reset(description, 0, ui.NORMAL) + lines += span.count_lines() + data_lines = (len(data_str) + MONO_HEX_PER_LINE - 1) // MONO_HEX_PER_LINE + lines += data_lines + + if lines <= TEXT_MAX_LINES: text = Text(title, icon, icon_color, new_lines=False) - description_lines = 0 - if subtitle is not None: - description_lines += Span(subtitle, 0, ui.BOLD).count_lines() - text.bold(subtitle) - text.br() if description is not None: - description_lines += Span(description, 0, ui.NORMAL).count_lines() - text.content.extend((ui.NORMAL, color_description, description, ui.FG)) + text.normal(description) text.br() - if width is not None: - text.mono( - *_truncate_hex( - data, - lines=TEXT_MAX_LINES - description_lines, - width=width, - middle=truncate_middle, - ellipsis=truncate_ellipsis, - ) - ) + + # special case: + if len(data_str) % 16 == 0: + # sanity checks: + # (a) we must not exceed MONO_HEX_PER_LINE + assert MONO_HEX_PER_LINE > 16 + # (b) we must not increase number of lines + assert (len(data_str) // 16) <= data_lines + # the above holds true for MONO_HEX_PER_LINE == 18 and TEXT_MAX_LINES == 5 + per_line = 16 + else: - text.mono(data) + per_line = MONO_HEX_PER_LINE + text.mono(ui.FG, *chunks_intersperse(data_str, per_line)) content: ui.Layout = Confirm(text) + else: para = [] - if subtitle is not None: - para.append((ui.BOLD, subtitle)) if description is not None: - assert ( - color_description == ui.FG - ) # only ethereum uses this and it truncates para.append((ui.NORMAL, description)) - if width is not None: - para.extend((ui.MONO, line) for line in chunks(data, width_paginated)) - else: - para.append((ui.MONO, data)) + para.extend((ui.MONO, line) for line in chunks(data_str, MONO_HEX_PER_LINE - 2)) content = paginate_paragraphs(para, title, icon, icon_color) await raise_if_cancelled(interact(ctx, content, br_type, br_code)) +def confirm_address( + ctx: wire.GenericContext, + title: str, + address: str, + description: str | None = "Address:", + br_type: str = "confirm_address", + br_code: ButtonRequestType = ButtonRequestType.Other, + icon: str = ui.ICON_SEND, # TODO cleanup @ redesign + icon_color: int = ui.GREEN, # TODO cleanup @ redesign +) -> Awaitable[None]: + # TODO clarify API - this should be pretty limited to support mainly confirming + # destinations and similar + return confirm_blob( + ctx, + br_type=br_type, + title=title, + data=address, + description=description, + br_code=br_code, + icon=icon, + icon_color=icon_color, + ) + + +async def confirm_text( + ctx: wire.GenericContext, + br_type: str, + title: str, + data: str, + description: str | None = None, + br_code: ButtonRequestType = ButtonRequestType.Other, + icon: str = ui.ICON_SEND, # TODO cleanup @ redesign + icon_color: int = ui.GREEN, # TODO cleanup @ redesign +) -> None: + """Confirm textual data. + + Applicable for human-readable strings, numbers, date/time values etc. + + For amounts, use `confirm_amount`. + + Displays in bold font. Paginates automatically. + """ + span = Span() + lines = 0 + if description is not None: + span.reset(description, 0, ui.NORMAL) + lines += span.count_lines() + span.reset(data, 0, ui.BOLD) + lines += span.count_lines() + + if lines <= TEXT_MAX_LINES: + text = Text(title, icon, icon_color, new_lines=False) + if description is not None: + text.normal(description) + text.br() + text.bold(data) + content: ui.Layout = Confirm(text) + + else: + para = [] + if description is not None: + para.append((ui.NORMAL, description)) + para.append((ui.BOLD, data)) + content = paginate_paragraphs(para, title, icon, icon_color) + await raise_if_cancelled(interact(ctx, content, br_type, br_code)) + + +def confirm_amount( + ctx: wire.GenericContext, + title: str, + amount: str, + description: str = "Amount:", + br_type: str = "confirm_amount", + br_code: ButtonRequestType = ButtonRequestType.Other, + icon: str = ui.ICON_SEND, # TODO cleanup @ redesign + icon_color: int = ui.GREEN, # TODO cleanup @ redesign +) -> Awaitable[None]: + """Confirm amount.""" + # TODO clarify API - this should be pretty limited to support mainly confirming + # destinations and similar + return confirm_text( + ctx, + br_type=br_type, + title=title, + data=amount, + description=description, + br_code=br_code, + icon=icon, + icon_color=icon_color, + ) + + _SCREEN_FULL_THRESHOLD = const(2)