1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-05 20:31:01 +00:00

refactor(core): convert apps.cardano to layouts

This commit is contained in:
Martin Milata 2021-03-10 10:52:26 +01:00 committed by matejcik
parent ffb400180d
commit ddac852acf
6 changed files with 378 additions and 391 deletions

View File

@ -1,8 +1,9 @@
from trezor import log, wire from trezor import log, wire
from trezor.messages import CardanoAddress from trezor.messages import CardanoAddress
from trezor.ui.layouts import show_address
from apps.common import paths from apps.common import paths
from apps.common.layout import address_n_to_str, show_qr from apps.common.layout import address_n_to_str
from . import seed from . import seed
from .address import derive_human_readable_address, validate_address_parameters from .address import derive_human_readable_address, validate_address_parameters
@ -10,7 +11,7 @@ from .helpers import protocol_magics, staking_use_cases
from .helpers.paths import SCHEMA_ADDRESS from .helpers.paths import SCHEMA_ADDRESS
from .helpers.utils import to_account_path from .helpers.utils import to_account_path
from .layout import ( from .layout import (
show_address, ADDRESS_TYPE_NAMES,
show_warning_address_foreign_staking_key, show_warning_address_foreign_staking_key,
show_warning_address_pointer, show_warning_address_pointer,
) )
@ -70,19 +71,15 @@ async def _display_address(
if not protocol_magics.is_mainnet(protocol_magic): if not protocol_magics.is_mainnet(protocol_magic):
network_name = protocol_magics.to_ui_string(protocol_magic) network_name = protocol_magics.to_ui_string(protocol_magic)
while True: address_n = address_n_to_str(address_parameters.address_n)
if await show_address( await show_address(
ctx, ctx,
address, address=address,
address_parameters.address_type, title="%s address" % ADDRESS_TYPE_NAMES[address_parameters.address_type],
address_parameters.address_n, network=network_name,
network=network_name, address_extra=address_n,
): title_qr=address_n,
break )
if await show_qr(
ctx, address, desc=address_n_to_str(address_parameters.address_n)
):
break
async def _show_staking_warnings( async def _show_staking_warnings(

View File

@ -1,15 +1,15 @@
import math
from ubinascii import hexlify from ubinascii import hexlify
from trezor import ui from trezor import ui
from trezor.enums import ButtonRequestType, CardanoAddressType, CardanoCertificateType from trezor.enums import ButtonRequestType, CardanoAddressType, CardanoCertificateType
from trezor.strings import format_amount from trezor.strings import format_amount
from trezor.ui.components.tt.button import ButtonDefault from trezor.ui.layouts import (
from trezor.ui.components.tt.scroll import Paginated confirm_metadata,
from trezor.ui.components.tt.text import Text confirm_output,
from trezor.utils import chunks confirm_path_warning,
confirm_properties,
)
from apps.common.confirm import confirm, require_confirm, require_hold_to_confirm
from apps.common.layout import address_n_to_str from apps.common.layout import address_n_to_str
from . import seed from . import seed
@ -55,9 +55,6 @@ CERTIFICATE_TYPE_NAMES = {
CardanoCertificateType.STAKE_POOL_REGISTRATION: "Stakepool registration", CardanoCertificateType.STAKE_POOL_REGISTRATION: "Stakepool registration",
} }
# Maximum number of characters per line in monospace font.
_MAX_MONO_LINE = 18
def format_coin_amount(amount: int) -> str: def format_coin_amount(amount: int) -> str:
return "%s %s" % (format_amount(amount, 6), "ADA") return "%s %s" % (format_amount(amount, 6), "ADA")
@ -75,73 +72,72 @@ async def confirm_sending(
) -> None: ) -> None:
await confirm_sending_token_bundle(ctx, token_bundle) await confirm_sending_token_bundle(ctx, token_bundle)
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_output(
page1.normal("Confirm sending:") ctx,
page1.bold(format_coin_amount(ada_amount))
page1.normal("to")
pages = _paginate_text(
page1,
"Confirm transaction",
ui.ICON_SEND,
to, to,
lines_per_page=4, format_coin_amount(ada_amount),
lines_used_on_first_page=3, title="Confirm transaction",
subtitle="Confirm sending:",
font_amount=ui.BOLD,
width_paginated=17,
to_str="\nto\n",
to_paginated=True,
br_code=ButtonRequestType.Other,
) )
await require_confirm(ctx, Paginated(pages))
async def confirm_sending_token_bundle( async def confirm_sending_token_bundle(
ctx: wire.Context, token_bundle: list[CardanoAssetGroupType] ctx: wire.Context, token_bundle: list[CardanoAssetGroupType]
) -> None: ) -> None:
for token_group in token_bundle: for token_group in token_bundle:
for token in token_group.tokens: for token in token_group.tokens:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page1.normal("Asset fingerprint:") ctx,
page1.bold( "confirm_token",
format_asset_fingerprint( title="Confirm transaction",
policy_id=token_group.policy_id, props=[
asset_name_bytes=token.asset_name_bytes, (
) "Asset fingerprint:",
format_asset_fingerprint(
policy_id=token_group.policy_id,
asset_name_bytes=token.asset_name_bytes,
),
),
("Amount sent:", format_amount(token.amount, 0)),
],
br_code=ButtonRequestType.Other,
) )
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page2.normal("Amount sent:")
page2.bold(format_amount(token.amount, 0))
await require_confirm(ctx, Paginated([page1, page2]))
async def show_warning_tx_output_contains_tokens(ctx: wire.Context) -> None: async def show_warning_tx_output_contains_tokens(ctx: wire.Context) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_metadata(
page1.normal("The following") ctx,
page1.normal("transaction output") "confirm_tokens",
page1.normal("contains tokens.") title="Confirm transaction",
page1.br_half() content="The following\ntransaction output\ncontains tokens.",
page1.normal("Continue?") larger_vspace=True,
br_code=ButtonRequestType.Other,
await require_confirm(ctx, page1) )
async def show_warning_path(ctx: wire.Context, path: list[int], title: str) -> None: async def show_warning_path(ctx: wire.Context, path: list[int], title: str) -> None:
page1 = Text("Confirm path", ui.ICON_WRONG, ui.RED) await confirm_path_warning(ctx, address_n_to_str(path), path_type=title)
page1.normal(title)
page1.bold(address_n_to_str(path))
page1.normal("is unknown.")
page1.normal("Are you sure?")
await require_confirm(ctx, page1)
async def show_warning_tx_no_staking_info( async def show_warning_tx_no_staking_info(
ctx: wire.Context, address_type: CardanoAddressType, amount: int ctx: wire.Context, address_type: CardanoAddressType, amount: int
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) atype = ADDRESS_TYPE_NAMES[address_type].lower()
page1.normal("Change " + ADDRESS_TYPE_NAMES[address_type].lower()) content = "Change %s address has no stake rights.\nChange amount:\n{}" % atype
page1.normal("address has no stake") await confirm_metadata(
page1.normal("rights.") ctx,
page1.normal("Change amount:") "warning_staking",
page1.bold(format_coin_amount(amount)) title="Confirm transaction",
content=content,
await require_confirm(ctx, page1) param=format_coin_amount(amount),
hide_continue=True,
br_code=ButtonRequestType.Other,
)
async def show_warning_tx_pointer_address( async def show_warning_tx_pointer_address(
@ -149,21 +145,25 @@ async def show_warning_tx_pointer_address(
pointer: CardanoBlockchainPointerType, pointer: CardanoBlockchainPointerType,
amount: int, amount: int,
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page1.normal("Change address has a") ctx,
page1.normal("pointer with staking") "warning_pointer",
page1.normal("rights.") title="Confirm transaction",
props=[
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) ("Change address has a\npointer with staking\nrights.\n\n\n", None),
page2.normal("Pointer:") (
page2.bold( "Pointer:",
"%s, %s, %s" "%s, %s, %s"
% (pointer.block_index, pointer.tx_index, pointer.certificate_index) % (
pointer.block_index,
pointer.tx_index,
pointer.certificate_index,
),
),
("Change amount:", format_coin_amount(amount)),
],
br_code=ButtonRequestType.Other,
) )
page2.normal("Change amount:")
page2.bold(format_coin_amount(amount))
await require_confirm(ctx, Paginated([page1, page2]))
async def show_warning_tx_different_staking_account( async def show_warning_tx_different_staking_account(
@ -171,18 +171,23 @@ async def show_warning_tx_different_staking_account(
staking_account_path: list[int], staking_account_path: list[int],
amount: int, amount: int,
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page1.normal("Change address staking") ctx,
page1.normal("rights do not match") "warning_differentstaking",
page1.normal("the current account.") title="Confirm transaction",
props=[
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) (
page2.normal("Staking account %s:" % format_account_number(staking_account_path)) "Change address staking rights do not match the current account.\n\n",
page2.bold(address_n_to_str(staking_account_path)) None,
page2.normal("Change amount:") ),
page2.bold(format_coin_amount(amount)) (
"Staking account %s:" % format_account_number(staking_account_path),
await require_confirm(ctx, Paginated([page1, page2])) address_n_to_str(staking_account_path),
),
("Change amount:", format_coin_amount(amount)),
],
br_code=ButtonRequestType.Other,
)
async def show_warning_tx_staking_key_hash( async def show_warning_tx_staking_key_hash(
@ -190,20 +195,19 @@ async def show_warning_tx_staking_key_hash(
staking_key_hash: bytes, staking_key_hash: bytes,
amount: int, amount: int,
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) props = [
page1.normal("Change address staking") ("Change address staking rights do not match the current account.\n\n", None),
page1.normal("rights do not match") ("Staking key hash:", hexlify(staking_key_hash).decode()),
page1.normal("the current account.") ("Change amount:", format_coin_amount(amount)),
]
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page2.normal("Staking key hash:") ctx,
page2.mono(*chunks(hexlify(staking_key_hash).decode(), 17)) "confirm_different_stakingrights",
title="Confirm transaction",
page3 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) props=props,
page3.normal("Change amount:") br_code=ButtonRequestType.Other,
page3.bold(format_coin_amount(amount)) )
await require_confirm(ctx, Paginated([page1, page2, page3]))
async def confirm_transaction( async def confirm_transaction(
@ -215,24 +219,27 @@ async def confirm_transaction(
validity_interval_start: int | None, validity_interval_start: int | None,
is_network_id_verifiable: bool, is_network_id_verifiable: bool,
) -> None: ) -> None:
pages: list[ui.Component] = [] props = [
("Transaction amount:", format_coin_amount(amount)),
("Transaction fee:", format_coin_amount(fee)),
]
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.normal("Transaction amount:")
page1.bold(format_coin_amount(amount))
page1.normal("Transaction fee:")
page1.bold(format_coin_amount(fee))
pages.append(page1)
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
if is_network_id_verifiable: if is_network_id_verifiable:
page2.normal("Network:") props.append(("Network:", protocol_magics.to_ui_string(protocol_magic)))
page2.bold(protocol_magics.to_ui_string(protocol_magic))
page2.normal("Valid since: %s" % format_optional_int(validity_interval_start))
page2.normal("TTL: %s" % format_optional_int(ttl))
pages.append(page2)
await require_hold_to_confirm(ctx, Paginated(pages)) props.append(
("Valid since: %s" % format_optional_int(validity_interval_start), None)
)
props.append(("TTL: %s" % format_optional_int(ttl), None))
await confirm_properties(
ctx,
"confirm_total",
title="Confirm transaction",
props=props,
hold=True,
br_code=ButtonRequestType.Other,
)
async def confirm_certificate( async def confirm_certificate(
@ -242,23 +249,24 @@ async def confirm_certificate(
# in this call # in this call
assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION
pages: list[ui.Component] = [] props = [
("Confirm:", CERTIFICATE_TYPE_NAMES[certificate.type]),
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) (
page1.normal("Confirm:") "for account %s:" % format_account_number(certificate.path),
page1.bold(CERTIFICATE_TYPE_NAMES[certificate.type]) address_n_to_str(to_account_path(certificate.path)),
page1.normal("for account %s:" % format_account_number(certificate.path)) ),
page1.bold(address_n_to_str(to_account_path(certificate.path))) ]
pages.append(page1)
if certificate.type == CardanoCertificateType.STAKE_DELEGATION: if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
assert certificate.pool is not None # validate_certificate assert certificate.pool is not None # validate_certificate
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) props.append(("to pool:", format_stake_pool_id(certificate.pool)))
page2.normal("to pool:")
page2.bold(format_stake_pool_id(certificate.pool))
pages.append(page2)
await require_confirm(ctx, Paginated(pages)) await confirm_properties(
ctx,
"confirm_certificate",
title="Confirm transaction",
props=props,
br_code=ButtonRequestType.Other,
)
async def confirm_stake_pool_parameters( async def confirm_stake_pool_parameters(
@ -267,25 +275,31 @@ async def confirm_stake_pool_parameters(
network_id: int, network_id: int,
protocol_magic: int, protocol_magic: int,
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.bold("Stake pool registration")
page1.normal("Pool ID:")
page1.bold(format_stake_pool_id(pool_parameters.pool_id))
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page2.normal("Pool reward account:")
page2.bold(pool_parameters.reward_account)
page3 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page3.normal("Pledge: " + format_coin_amount(pool_parameters.pledge))
page3.normal("Cost: " + format_coin_amount(pool_parameters.cost))
margin_percentage = ( margin_percentage = (
100.0 * pool_parameters.margin_numerator / pool_parameters.margin_denominator 100.0 * pool_parameters.margin_numerator / pool_parameters.margin_denominator
) )
percentage_formatted = ("%f" % margin_percentage).rstrip("0").rstrip(".") percentage_formatted = ("%f" % margin_percentage).rstrip("0").rstrip(".")
page3.normal("Margin: %s%%" % percentage_formatted) await confirm_properties(
ctx,
await require_confirm(ctx, Paginated([page1, page2, page3])) "confirm_pool_registration",
title="Confirm transaction",
props=[
(
"Stake pool registration\nPool ID:",
format_stake_pool_id(pool_parameters.pool_id),
),
("Pool reward account:", pool_parameters.reward_account),
(
"Pledge: {}\nCost: {}\nMargin: {}%".format(
format_coin_amount(pool_parameters.pledge),
format_coin_amount(pool_parameters.cost),
percentage_formatted,
),
None,
),
],
br_code=ButtonRequestType.Other,
)
async def confirm_stake_pool_owners( async def confirm_stake_pool_owners(
@ -294,56 +308,67 @@ async def confirm_stake_pool_owners(
owners: list[CardanoPoolOwnerType], owners: list[CardanoPoolOwnerType],
network_id: int, network_id: int,
) -> None: ) -> None:
pages: list[ui.Component] = [] props: list[tuple[str, str | None]] = []
for index, owner in enumerate(owners, 1): for index, owner in enumerate(owners, 1):
page = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page.normal("Pool owner #%d:" % (index))
if owner.staking_key_path: if owner.staking_key_path:
page.bold(address_n_to_str(owner.staking_key_path)) props.append(
page.normal( ("Pool owner #%d:" % index, address_n_to_str(owner.staking_key_path))
encode_human_readable_address( )
pack_reward_address_bytes( props.append(
get_public_key_hash(keychain, owner.staking_key_path), (
network_id, encode_human_readable_address(
) pack_reward_address_bytes(
get_public_key_hash(keychain, owner.staking_key_path),
network_id,
)
),
None,
) )
) )
else: else:
assert owner.staking_key_hash is not None # validate_pool_owners assert owner.staking_key_hash is not None # validate_pool_owners
page.bold( props.append(
encode_human_readable_address( (
pack_reward_address_bytes(owner.staking_key_hash, network_id) "Pool owner #%d:" % index,
encode_human_readable_address(
pack_reward_address_bytes(owner.staking_key_hash, network_id)
),
) )
) )
pages.append(page) await confirm_properties(
ctx,
await require_confirm(ctx, Paginated(pages)) "confirm_pool_owners",
title="Confirm transaction",
props=props,
br_code=ButtonRequestType.Other,
)
async def confirm_stake_pool_metadata( async def confirm_stake_pool_metadata(
ctx: wire.Context, ctx: wire.Context,
metadata: CardanoPoolMetadataType | None, metadata: CardanoPoolMetadataType | None,
) -> None: ) -> None:
if metadata is None: if metadata is None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page1.normal("Pool has no metadata") ctx,
page1.normal("(anonymous pool)") "confirm_pool_metadata",
title="Confirm transaction",
await require_confirm(ctx, page1) props=[("Pool has no metadata (anonymous pool)", None)],
br_code=ButtonRequestType.Other,
)
return return
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page1.normal("Pool metadata url:") ctx,
page1.bold(metadata.url) "confirm_pool_metadata",
title="Confirm transaction",
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) props=[
page2.normal("Pool metadata hash:") ("Pool metadata url:", metadata.url),
page2.bold(hexlify(metadata.hash).decode()) ("Pool metadata hash:", hexlify(metadata.hash).decode()),
],
await require_confirm(ctx, Paginated([page1, page2])) br_code=ButtonRequestType.Other,
)
async def confirm_transaction_network_ttl( async def confirm_transaction_network_ttl(
@ -352,36 +377,53 @@ async def confirm_transaction_network_ttl(
ttl: int | None, ttl: int | None,
validity_interval_start: int | None, validity_interval_start: int | None,
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page1.normal("Network:") ctx,
page1.bold(protocol_magics.to_ui_string(protocol_magic)) "confirm_pool_network",
page1.normal("Valid since: %s" % format_optional_int(validity_interval_start)) title="Confirm transaction",
page1.normal("TTL: %s" % format_optional_int(ttl)) props=[
("Network:", protocol_magics.to_ui_string(protocol_magic)),
await require_confirm(ctx, page1) (
"Valid since: %s" % format_optional_int(validity_interval_start),
None,
),
("TTL: %s" % format_optional_int(ttl), None),
],
br_code=ButtonRequestType.Other,
)
async def confirm_stake_pool_registration_final( async def confirm_stake_pool_registration_final(
ctx: wire.Context, ctx: wire.Context,
) -> None: ) -> None:
await confirm_metadata(
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) ctx,
page1.normal("Confirm signing the stake pool registration as an owner") "confirm_pool_final",
title="Confirm transaction",
await require_hold_to_confirm(ctx, page1) content="Confirm signing the stake pool registration as an owner",
hide_continue=True,
hold=True,
br_code=ButtonRequestType.Other,
)
async def confirm_withdrawal( async def confirm_withdrawal(
ctx: wire.Context, withdrawal: CardanoTxWithdrawalType ctx: wire.Context, withdrawal: CardanoTxWithdrawalType
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page1.normal("Confirm withdrawal") ctx,
page1.normal("for account %s:" % format_account_number(withdrawal.path)) "confirm_withdrawal",
page1.bold(address_n_to_str(to_account_path(withdrawal.path))) title="Confirm transaction",
page1.normal("Amount:") props=[
page1.bold(format_coin_amount(withdrawal.amount)) (
"Confirm withdrawal\nfor account %s:"
await require_confirm(ctx, page1) % format_account_number(withdrawal.path),
address_n_to_str(to_account_path(withdrawal.path)),
),
("Amount:", format_coin_amount(withdrawal.amount)),
],
br_code=ButtonRequestType.Other,
)
async def confirm_catalyst_registration( async def confirm_catalyst_registration(
@ -391,166 +433,98 @@ async def confirm_catalyst_registration(
reward_address: str, reward_address: str,
nonce: int, nonce: int,
) -> None: ) -> None:
pages: list[ui.Component] = [] await confirm_properties(
ctx,
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) "confirm_catalyst_registration",
page1.bold("Catalyst voting key") title="Confirm transaction",
page1.bold("registration") props=[
pages.append(page1) ("Catalyst voting key registration", None),
("Voting public key:", public_key),
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) (
page2.normal("Voting public key:") "Staking key for account %s:" % format_account_number(staking_path),
page2.bold(*chunks(public_key, 17)) address_n_to_str(staking_path),
pages.append(page2) ),
("Rewards go to:", reward_address),
page3 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) ("Nonce:", str(nonce)),
page3.normal("Staking key for") ],
page3.normal("account %s:" % format_account_number(staking_path)) br_code=ButtonRequestType.Other,
page3.bold(address_n_to_str(staking_path))
pages.append(page3)
page4 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page4.normal("Rewards go to:")
pages.extend(
_paginate_text(page4, "Confirm transaction", ui.ICON_SEND, reward_address)
) )
last_page = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
last_page.normal("Nonce: %s" % nonce)
pages.append(last_page)
await require_confirm(ctx, Paginated(pages))
async def show_auxiliary_data_hash( async def show_auxiliary_data_hash(
ctx: wire.Context, auxiliary_data_hash: bytes ctx: wire.Context, auxiliary_data_hash: bytes
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) await confirm_properties(
page1.normal("Auxiliary data hash:")
page1.bold(hexlify(auxiliary_data_hash).decode())
await require_confirm(ctx, page1)
async def show_address(
ctx: wire.Context,
address: str,
address_type: CardanoAddressType,
path: list[int],
network: str | None = None,
) -> bool:
"""
Custom show_address function is needed because cardano addresses don't
fit on a single screen.
"""
address_type_label = "%s address" % ADDRESS_TYPE_NAMES[address_type]
page1 = Text(address_type_label, ui.ICON_RECEIVE, ui.GREEN)
lines_per_page = 5
lines_used_on_first_page = 0
# assemble first page to be displayed (path + network + whatever part of the address fits)
if network is not None:
page1.normal("%s network" % network)
lines_used_on_first_page += 1
path_str = address_n_to_str(path)
page1.mono(path_str)
lines_used_on_first_page = min(
lines_used_on_first_page + math.ceil(len(path_str) / _MAX_MONO_LINE),
lines_per_page,
)
pages = _paginate_text(
page1,
address_type_label,
ui.ICON_RECEIVE,
address,
lines_per_page=lines_per_page,
lines_used_on_first_page=lines_used_on_first_page,
)
return await confirm(
ctx, ctx,
Paginated(pages), "confirm_auxiliary_data",
code=ButtonRequestType.Address, title="Confirm transaction",
cancel="QR", props=[("Auxiliary data hash:", hexlify(auxiliary_data_hash).decode())],
cancel_style=ButtonDefault, br_code=ButtonRequestType.Other,
) )
def _paginate_text(
first_page: Text,
page_desc: str,
page_icon: str,
text: str,
lines_per_page: int = 5,
lines_used_on_first_page: int = 1,
) -> list[ui.Component]:
lines = list(chunks(text, 17))
offset = lines_per_page - lines_used_on_first_page
for text_line in lines[:offset]:
first_page.bold(text_line)
pages: list[ui.Component] = [first_page]
if len(lines) > offset:
to_pages = list(chunks(lines[offset:], lines_per_page))
for page in to_pages:
t = Text(page_desc, page_icon, ui.GREEN)
for line in page:
t.bold(line)
pages.append(t)
return pages
async def show_warning_address_foreign_staking_key( async def show_warning_address_foreign_staking_key(
ctx: wire.Context, ctx: wire.Context,
account_path: list[int], account_path: list[int],
staking_account_path: list[int], staking_account_path: list[int],
staking_key_hash: bytes | None, staking_key_hash: bytes | None,
) -> None: ) -> None:
page1 = Text("Warning", ui.ICON_WRONG, ui.RED) props: list[tuple[str, str | None]] = [
page1.normal("Stake rights associated") (
page1.normal("with this address do") "Stake rights associated with this address do not match your account %s:"
page1.normal("not match your") % format_account_number(account_path),
page1.normal("account %s:" % format_account_number(account_path)) address_n_to_str(account_path),
page1.bold(address_n_to_str(account_path)) )
]
page2 = Text("Warning", ui.ICON_WRONG, ui.RED)
if staking_account_path: if staking_account_path:
page2.normal("Stake account %s:" % format_account_number(staking_account_path)) props.append(
page2.bold(address_n_to_str(staking_account_path)) (
page2.br_half() "Stake account %s:" % format_account_number(staking_account_path),
address_n_to_str(staking_account_path),
)
)
else: else:
assert staking_key_hash is not None # _validate_base_address_staking_info assert staking_key_hash is not None # _validate_base_address_staking_info
page2.normal("Staking key:") props.append(("Staking key:", hexlify(staking_key_hash).decode()))
page2.bold(hexlify(staking_key_hash).decode()) props.append(("Continue?", None))
page2.normal("Continue?")
await require_confirm(ctx, Paginated([page1, page2])) await confirm_properties(
ctx,
"warning_foreign_stakingkey",
title="Warning",
props=props,
icon=ui.ICON_WRONG,
icon_color=ui.RED,
br_code=ButtonRequestType.Other,
)
async def show_warning_tx_network_unverifiable(ctx: wire.Context) -> None: async def show_warning_tx_network_unverifiable(ctx: wire.Context) -> None:
page1 = Text("Warning", ui.ICON_SEND, ui.GREEN) await confirm_metadata(
page1.normal("Transaction has no outputs, network cannot be verified.") ctx,
page1.br_half() "warning_no_outputs",
page1.normal("Continue?") title="Warning",
content="Transaction has no outputs, network cannot be verified.",
await require_confirm(ctx, page1) larger_vspace=True,
br_code=ButtonRequestType.Other,
)
async def show_warning_address_pointer( async def show_warning_address_pointer(
ctx: wire.Context, pointer: CardanoBlockchainPointerType ctx: wire.Context, pointer: CardanoBlockchainPointerType
) -> None: ) -> None:
text = Text("Warning", ui.ICON_WRONG, ui.RED) content = "Pointer address:\nBlock: %s\nTransaction: %s\nCertificate: %s" % (
text.normal("Pointer address:") pointer.block_index,
text.normal("Block: %s" % pointer.block_index) pointer.tx_index,
text.normal("Transaction: %s" % pointer.tx_index) pointer.certificate_index,
text.normal("Certificate: %s" % pointer.certificate_index) )
text.normal("Continue?") await confirm_metadata(
await require_confirm(ctx, text) ctx,
"warning_pointer",
title="Warning",
icon=ui.ICON_WRONG,
icon_color=ui.RED,
content=content,
br_code=ButtonRequestType.Other,
)

View File

@ -1,42 +1,9 @@
from micropython import const
from trezor import ui
from trezor.enums import ButtonRequestType
from trezor.ui.components.tt.button import ButtonDefault
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 from trezor.utils import chunks
from apps.common import HARDENED from apps.common import HARDENED
from apps.common.confirm import confirm
if False: if False:
from typing import Iterable, Iterator from typing import Iterable, Iterator
from trezor import wire
async def show_qr(
ctx: wire.Context,
address: str,
desc: str = "Confirm address",
cancel: str = "Address",
) -> bool:
QR_X = const(120)
QR_Y = const(115)
QR_SIZE_THRESHOLD = const(63)
QR_COEF = const(4) if len(address) < QR_SIZE_THRESHOLD else const(3)
qr = Qr(address, QR_X, QR_Y, QR_COEF)
text = Text(desc, ui.ICON_RECEIVE, ui.GREEN)
content = Container(qr, text)
return await confirm(
ctx,
content,
code=ButtonRequestType.Address,
cancel=cancel,
cancel_style=ButtonDefault,
)
def split_address(address: str) -> Iterator[str]: def split_address(address: str) -> Iterator[str]:

View File

@ -10,7 +10,7 @@ from .swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe
from .text import TEXT_MAX_LINES, Span, Text from .text import TEXT_MAX_LINES, Span, Text
if False: if False:
from typing import Iterable, Any from typing import Callable, Iterable
from ..common.text import TextContent from ..common.text import TextContent
@ -266,7 +266,8 @@ def paginate_text(
header_icon: str = ui.ICON_DEFAULT, header_icon: str = ui.ICON_DEFAULT,
icon_color: int = ui.ORANGE_ICON, icon_color: int = ui.ORANGE_ICON,
break_words: bool = False, break_words: bool = False,
) -> Confirm | Paginated: confirm: Callable[[ui.Component], ui.Layout] = Confirm,
) -> ui.Layout:
span = Span(text, 0, font, break_words=break_words) span = Span(text, 0, font, break_words=break_words)
if span.count_lines() <= TEXT_MAX_LINES: if span.count_lines() <= TEXT_MAX_LINES:
result = Text( result = Text(
@ -277,7 +278,7 @@ def paginate_text(
break_words=break_words, break_words=break_words,
) )
result.content = [font, text] result.content = [font, text]
return Confirm(result) return confirm(result)
else: else:
pages: list[ui.Component] = [] pages: list[ui.Component] = []
@ -305,7 +306,7 @@ def paginate_text(
for _ in range(TEXT_MAX_LINES - 1): for _ in range(TEXT_MAX_LINES - 1):
span.next_line() span.next_line()
pages[-1] = Confirm(pages[-1]) pages[-1] = confirm(pages[-1])
return Paginated(pages) return Paginated(pages)
@ -315,8 +316,8 @@ def paginate_paragraphs(
header_icon: str = ui.ICON_DEFAULT, header_icon: str = ui.ICON_DEFAULT,
icon_color: int = ui.ORANGE_ICON, icon_color: int = ui.ORANGE_ICON,
break_words: bool = False, break_words: bool = False,
confirm_kwargs: Dict[str, Any] = {}, confirm: Callable[[ui.Component], ui.Layout] = Confirm,
) -> Union[Confirm, Paginated]: ) -> ui.Layout:
span = Span("", 0, ui.NORMAL, break_words=break_words) span = Span("", 0, ui.NORMAL, break_words=break_words)
lines = 0 lines = 0
content: list[TextContent] = [] content: list[TextContent] = []
@ -343,7 +344,7 @@ def paginate_paragraphs(
result.content.append("\n") result.content.append("\n")
result.content.append(font) result.content.append(font)
result.content.append(text) result.content.append(text)
return Confirm(result, **confirm_kwargs) return confirm(result)
else: else:
pages: list[ui.Component] = [] pages: list[ui.Component] = []
@ -373,5 +374,5 @@ def paginate_paragraphs(
else: else:
lines_left -= 1 lines_left -= 1
pages[-1] = Confirm(pages[-1], **confirm_kwargs) pages[-1] = confirm(pages[-1])
return Paginated(pages) return Paginated(pages)

View File

@ -25,15 +25,18 @@ from .common import interact
if False: if False:
from typing import ( from typing import (
Awaitable,
Iterator, Iterator,
NoReturn,
Optional,
Sequence, Sequence,
Tuple,
Type, Type,
Union, Union,
Awaitable,
NoReturn,
) )
ExceptionType = Union[BaseException, Type[BaseException]] ExceptionType = Union[BaseException, Type[BaseException]]
PropertyType = Tuple[str, Optional[str]]
__all__ = ( __all__ = (
@ -52,6 +55,7 @@ __all__ = (
"confirm_output", "confirm_output",
"confirm_decred_sstx_submission", "confirm_decred_sstx_submission",
"confirm_hex", "confirm_hex",
"confirm_properties",
"confirm_total", "confirm_total",
"confirm_total_ethereum", "confirm_total_ethereum",
"confirm_total_ripple", "confirm_total_ripple",
@ -182,9 +186,11 @@ async def confirm_backup(ctx: wire.GenericContext) -> bool:
return confirmed return confirmed
async def confirm_path_warning(ctx: wire.GenericContext, path: str) -> None: async def confirm_path_warning(
ctx: wire.GenericContext, path: str, path_type: str = "Path"
) -> None:
text = Text("Confirm path", ui.ICON_WRONG, ui.RED) text = Text("Confirm path", ui.ICON_WRONG, ui.RED)
text.normal("Path") text.normal(path_type)
text.mono(*break_path_to_lines(path, MONO_ADDR_PER_LINE)) text.mono(*break_path_to_lines(path, MONO_ADDR_PER_LINE))
text.normal("is unknown.", "Are you sure?") text.normal("is unknown.", "Are you sure?")
await raise_if_cancelled( await raise_if_cancelled(
@ -237,8 +243,11 @@ def _show_address(
address: str, address: str,
title: str, title: str,
network: str | None = None, network: str | None = None,
) -> Confirm | Paginated: extra: str | None = None,
) -> ui.Layout:
para = [(ui.NORMAL, "%s network" % network)] if network is not None else [] para = [(ui.NORMAL, "%s network" % network)] if network is not None else []
if extra is not None:
para.append((ui.BOLD, extra))
para.extend( para.extend(
(ui.MONO, address_line) for address_line in chunks(address, MONO_ADDR_PER_LINE) (ui.MONO, address_line) for address_line in chunks(address, MONO_ADDR_PER_LINE)
) )
@ -247,7 +256,9 @@ def _show_address(
header=title, header=title,
header_icon=ui.ICON_RECEIVE, header_icon=ui.ICON_RECEIVE,
icon_color=ui.GREEN, icon_color=ui.GREEN,
confirm_kwargs={"cancel": "QR", "cancel_style": ButtonDefault}, confirm=lambda content: Confirm(
content, cancel="QR", cancel_style=ButtonDefault
),
) )
@ -290,6 +301,8 @@ async def show_address(
network: str | None = None, network: str | None = None,
multisig_index: int | None = None, multisig_index: int | None = None,
xpubs: Sequence[str] = [], xpubs: Sequence[str] = [],
address_extra: str | None = None,
title_qr: str | None = None,
) -> None: ) -> None:
is_multisig = len(xpubs) > 0 is_multisig = len(xpubs) > 0
while True: while True:
@ -300,6 +313,7 @@ async def show_address(
address, address,
title, title,
network, network,
extra=address_extra,
), ),
"show_address", "show_address",
ButtonRequestType.Address, ButtonRequestType.Address,
@ -311,7 +325,7 @@ async def show_address(
ctx, ctx,
_show_qr( _show_qr(
address if address_qr is None else address_qr, address if address_qr is None else address_qr,
title, title if title_qr is None else title_qr,
cancel="XPUBs" if is_multisig else "Address", cancel="XPUBs" if is_multisig else "Address",
), ),
"show_qr", "show_qr",
@ -455,19 +469,29 @@ async def confirm_output(
address: str, address: str,
amount: str, amount: str,
font_amount: int = ui.NORMAL, # TODO cleanup @ redesign font_amount: int = ui.NORMAL, # TODO cleanup @ redesign
title: str = "Confirm sending",
subtitle: str | None = None, # TODO cleanup @ redesign
color_to: int = ui.FG, # TODO cleanup @ redesign color_to: int = ui.FG, # TODO cleanup @ redesign
to_str: str = " to\n", # TODO cleanup @ redesign to_str: str = " to\n", # TODO cleanup @ redesign
to_paginated: bool = False, # TODO cleanup @ redesign
width: int = MONO_ADDR_PER_LINE, width: int = MONO_ADDR_PER_LINE,
width_paginated: int = MONO_ADDR_PER_LINE - 1, width_paginated: int = MONO_ADDR_PER_LINE - 1,
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
) -> None: ) -> None:
title = "Confirm sending" header_lines = to_str.count("\n") + int(subtitle is not None)
if len(address) > (TEXT_MAX_LINES - 1) * width: if len(address) > (TEXT_MAX_LINES - header_lines) * width:
para = [(font_amount, amount)] para = []
if subtitle is not None:
para.append((ui.NORMAL, subtitle))
para.append((font_amount, amount))
if to_paginated:
para.append((ui.NORMAL, "to"))
para.extend((ui.MONO, line) for line in chunks(address, width_paginated)) para.extend((ui.MONO, line) for line in chunks(address, width_paginated))
content: ui.Layout = paginate_paragraphs(para, title, ui.ICON_SEND, ui.GREEN) content: ui.Layout = paginate_paragraphs(para, title, ui.ICON_SEND, ui.GREEN)
else: else:
text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False)
if subtitle is not None:
text.normal(subtitle, "\n")
text.content = [font_amount, amount, ui.NORMAL, color_to, to_str, ui.FG] text.content = [font_amount, amount, ui.NORMAL, color_to, to_str, ui.FG]
text.mono(*chunks_intersperse(address, width)) text.mono(*chunks_intersperse(address, width))
content = Confirm(text) content = Confirm(text)
@ -552,6 +576,28 @@ async def confirm_hex(
await raise_if_cancelled(interact(ctx, content, br_type, br_code)) await raise_if_cancelled(interact(ctx, content, br_type, br_code))
# TODO keep name and value on the same page if possible
async def confirm_properties(
ctx: wire.GenericContext,
br_type: str,
title: str,
props: Sequence[PropertyType],
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
hold: bool = False,
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
) -> None:
para = []
for p in props:
para.append((ui.NORMAL, p[0]))
if p[1] is not None:
para.append((ui.BOLD, p[1]))
content = paginate_paragraphs(
para, title, icon, icon_color, confirm=HoldToConfirm if hold else Confirm
)
await raise_if_cancelled(interact(ctx, content, br_type, br_code))
async def confirm_total( async def confirm_total(
ctx: wire.GenericContext, ctx: wire.GenericContext,
total_amount: str, total_amount: str,
@ -630,6 +676,7 @@ async def confirm_metadata(
param_font: int = ui.BOLD, param_font: int = ui.BOLD,
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
icon_color: int = ui.GREEN, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign
larger_vspace: bool = False, # TODO cleanup @ redesign
) -> None: ) -> None:
text = Text(title, icon, icon_color, new_lines=False) text = Text(title, icon, icon_color, new_lines=False)
text.format_parametrized( text.format_parametrized(
@ -638,6 +685,8 @@ async def confirm_metadata(
if not hide_continue: if not hide_continue:
text.br() text.br()
if larger_vspace:
text.br_half()
text.normal("Continue?") text.normal("Continue?")
cls = HoldToConfirm if hold else Confirm cls = HoldToConfirm if hold else Confirm

View File

@ -107,7 +107,6 @@ def test_cardano_sign_tx_with_multiple_chunks(client, parameters, result):
# If that changes, we'll need to figure out something else. # If that changes, we'll need to figure out something else.
messages.ButtonRequest(page_number=1), messages.ButtonRequest(page_number=1),
messages.ButtonRequest(page_number=2), messages.ButtonRequest(page_number=2),
messages.ButtonRequest(page_number=3),
messages.ButtonRequest(page_number=1), messages.ButtonRequest(page_number=1),
messages.ButtonRequest(page_number=2), messages.ButtonRequest(page_number=2),
] ]