refactor(core): convert rest of apps.bitcoin to layouts

pull/1533/head
Martin Milata 3 years ago
parent b1e38fe382
commit 2b6ea25712

@ -4,10 +4,9 @@ from trezor import ui
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))

@ -3,9 +3,8 @@ from ubinascii import hexlify
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,

@ -2,9 +2,10 @@ from trezor import wire
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()

@ -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

@ -2,9 +2,10 @@ from trezor import wire
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")

@ -3,8 +3,7 @@ from micropython import const
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 import HARDENED
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 address_n_to_str(address_n: Iterable[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)

@ -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),
)

@ -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(

@ -2,8 +2,9 @@ from trezor import wire
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")

@ -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()

@ -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")

@ -3,14 +3,18 @@ from micropython import const
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 @@ class PaginatedWithButtons(ui.Layout):
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)

@ -11,8 +11,8 @@ from ..components.common import break_path_to_lines
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 @@ __all__ = (
"confirm_backup",
"confirm_path_warning",
"confirm_sign_identity",
"confirm_signverify",
"show_address",
"show_error",
"show_pubkey",
@ -53,6 +54,7 @@ __all__ = (
"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,
)
)

Loading…
Cancel
Save