You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
674 lines
18 KiB
674 lines
18 KiB
from micropython import const
|
|
|
|
from trezor import ui, wire
|
|
from trezor.messages import ButtonRequestType
|
|
from trezor.ui.container import Container
|
|
from trezor.ui.loader import LoaderDanger
|
|
from trezor.ui.qr import Qr
|
|
from trezor.utils import chunks
|
|
|
|
from ..components.common import break_path_to_lines
|
|
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
|
|
from ..components.tt.text import Span, Text
|
|
from ..constants.tt import (
|
|
MONO_CHARS_PER_LINE,
|
|
MONO_HEX_PER_LINE,
|
|
QR_SIZE_THRESHOLD,
|
|
QR_X,
|
|
QR_Y,
|
|
TEXT_MAX_LINES,
|
|
)
|
|
from .common import interact
|
|
|
|
if False:
|
|
from typing import (
|
|
Iterator,
|
|
List,
|
|
Sequence,
|
|
Type,
|
|
Union,
|
|
Optional,
|
|
Awaitable,
|
|
NoReturn,
|
|
)
|
|
|
|
from trezor.messages.ButtonRequest import EnumTypeButtonRequestType
|
|
|
|
from ..components.common.text import TextContent
|
|
|
|
ExceptionType = Union[BaseException, Type[BaseException]]
|
|
|
|
__all__ = (
|
|
"confirm_action",
|
|
"confirm_reset_device",
|
|
"confirm_backup",
|
|
"confirm_path_warning",
|
|
"confirm_sign_identity",
|
|
"confirm_signverify",
|
|
"show_address",
|
|
"show_error_and_raise",
|
|
"show_pubkey",
|
|
"show_success",
|
|
"show_xpub",
|
|
"show_warning",
|
|
"confirm_output",
|
|
"confirm_decred_sstx_submission",
|
|
"confirm_hex",
|
|
"confirm_total",
|
|
"confirm_joint_total",
|
|
"confirm_metadata",
|
|
"confirm_replacement",
|
|
"confirm_modify_output",
|
|
"confirm_modify_fee",
|
|
"confirm_coinjoin",
|
|
)
|
|
|
|
|
|
async def confirm_action(
|
|
ctx: wire.GenericContext,
|
|
br_type: str,
|
|
title: str,
|
|
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: 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,
|
|
br_code: EnumTypeButtonRequestType = ButtonRequestType.Other,
|
|
) -> None:
|
|
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.format_parametrized(
|
|
description,
|
|
description_param if description_param is not None else "",
|
|
param_font=description_param_font,
|
|
)
|
|
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.format_parametrized(
|
|
description,
|
|
description_param if description_param is not None else "",
|
|
param_font=description_param_font,
|
|
)
|
|
|
|
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, **kwargs),
|
|
br_type,
|
|
br_code,
|
|
),
|
|
exc,
|
|
)
|
|
|
|
|
|
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()
|
|
text.br_half()
|
|
text.normal("By continuing you agree")
|
|
text.br()
|
|
text.normal("to ")
|
|
text.bold("https://trezor.io/tos")
|
|
await raise_if_cancelled(
|
|
interact(
|
|
ctx,
|
|
Confirm(text, major_confirm=True),
|
|
"setup_device",
|
|
ButtonRequestType.ResetDevice,
|
|
)
|
|
)
|
|
|
|
|
|
# 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!")
|
|
text1.br_half()
|
|
text1.normal("You should back up your", "new wallet right now.")
|
|
|
|
text2 = Text("Warning", ui.ICON_WRONG, ui.RED)
|
|
text2.bold("Are you sure you want", "to skip the backup?")
|
|
text2.br_half()
|
|
text2.normal("You can back up your", "Trezor once, at any time.")
|
|
|
|
if is_confirmed(
|
|
await interact(
|
|
ctx,
|
|
Confirm(text1, cancel="Skip", confirm="Back up", major_confirm=True),
|
|
"backup_device",
|
|
ButtonRequestType.ResetDevice,
|
|
)
|
|
):
|
|
return True
|
|
|
|
confirmed = is_confirmed(
|
|
await interact(
|
|
ctx,
|
|
Confirm(text2, cancel="Skip", confirm="Back up", major_confirm=True),
|
|
"backup_device",
|
|
ButtonRequestType.ResetDevice,
|
|
)
|
|
)
|
|
return confirmed
|
|
|
|
|
|
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?")
|
|
await raise_if_cancelled(
|
|
interact(
|
|
ctx,
|
|
Confirm(text),
|
|
"path_warning",
|
|
ButtonRequestType.UnknownDerivationPath,
|
|
)
|
|
)
|
|
|
|
|
|
def _show_qr(
|
|
address: str,
|
|
desc: str,
|
|
cancel: str = "Address",
|
|
) -> Confirm:
|
|
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)
|
|
|
|
return Confirm(Container(qr, text), cancel=cancel, cancel_style=ButtonDefault)
|
|
|
|
|
|
def _split_address(address: str) -> Iterator[str]:
|
|
return chunks(address, MONO_CHARS_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:
|
|
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)
|
|
|
|
|
|
def _show_address(
|
|
address: str,
|
|
desc: str,
|
|
network: Optional[str] = None,
|
|
) -> Confirm:
|
|
text = Text(desc, ui.ICON_RECEIVE, ui.GREEN)
|
|
if network is not None:
|
|
text.normal("%s network" % network)
|
|
text.mono(*_split_address(address))
|
|
|
|
return Confirm(text, cancel="QR", cancel_style=ButtonDefault)
|
|
|
|
|
|
def _show_xpub(xpub: str, desc: str, cancel: str) -> Paginated:
|
|
pages: List[ui.Component] = []
|
|
for lines in chunks(list(chunks(xpub, 16)), 5):
|
|
text = Text(desc, ui.ICON_RECEIVE, ui.GREEN)
|
|
text.mono(*lines)
|
|
pages.append(text)
|
|
|
|
content = Paginated(pages)
|
|
|
|
content.pages[-1] = Confirm(
|
|
content.pages[-1],
|
|
cancel=cancel,
|
|
cancel_style=ButtonDefault,
|
|
)
|
|
|
|
return content
|
|
|
|
|
|
async def show_xpub(
|
|
ctx: wire.GenericContext, xpub: str, desc: str, cancel: str
|
|
) -> None:
|
|
await raise_if_cancelled(
|
|
interact(
|
|
ctx,
|
|
_show_xpub(xpub, desc, cancel),
|
|
"show_xpub",
|
|
ButtonRequestType.PublicKey,
|
|
)
|
|
)
|
|
|
|
|
|
async def show_address(
|
|
ctx: wire.GenericContext,
|
|
address: str,
|
|
address_qr: Optional[str] = None,
|
|
desc: str = "Confirm address",
|
|
network: Optional[str] = None,
|
|
multisig_index: Optional[int] = None,
|
|
xpubs: Sequence[str] = [],
|
|
) -> None:
|
|
is_multisig = len(xpubs) > 0
|
|
while True:
|
|
if is_confirmed(
|
|
await interact(
|
|
ctx,
|
|
_show_address(address, desc, network),
|
|
"show_address",
|
|
ButtonRequestType.Address,
|
|
)
|
|
):
|
|
break
|
|
if is_confirmed(
|
|
await interact(
|
|
ctx,
|
|
_show_qr(
|
|
address if address_qr is None else address_qr,
|
|
desc,
|
|
cancel="XPUBs" if is_multisig else "Address",
|
|
),
|
|
"show_qr",
|
|
ButtonRequestType.Address,
|
|
)
|
|
):
|
|
break
|
|
|
|
if is_multisig:
|
|
for i, xpub in enumerate(xpubs):
|
|
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 is_confirmed(
|
|
await interact(
|
|
ctx,
|
|
_show_xpub(xpub, desc=desc_xpub, cancel=cancel),
|
|
"show_xpub",
|
|
ButtonRequestType.PublicKey,
|
|
)
|
|
):
|
|
return
|
|
|
|
|
|
def show_pubkey(
|
|
ctx: wire.Context, pubkey: str, title: str = "Confirm public key"
|
|
) -> Awaitable[None]:
|
|
return confirm_hex(
|
|
ctx,
|
|
br_type="show_pubkey",
|
|
title="Confirm public key",
|
|
data=pubkey,
|
|
br_code=ButtonRequestType.PublicKey,
|
|
icon=ui.ICON_RECEIVE,
|
|
)
|
|
|
|
|
|
async def _show_modal(
|
|
ctx: wire.GenericContext,
|
|
br_type: str,
|
|
br_code: EnumTypeButtonRequestType,
|
|
header: str,
|
|
subheader: Optional[str],
|
|
content: str,
|
|
button_confirm: Optional[str],
|
|
button_cancel: Optional[str],
|
|
icon: str,
|
|
icon_color: int,
|
|
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)
|
|
await raise_if_cancelled(
|
|
interact(
|
|
ctx,
|
|
Confirm(text, confirm=button_confirm, cancel=button_cancel),
|
|
br_type,
|
|
br_code,
|
|
),
|
|
exc,
|
|
)
|
|
|
|
|
|
async def show_error_and_raise(
|
|
ctx: wire.GenericContext,
|
|
br_type: str,
|
|
content: str,
|
|
header: str = "Error",
|
|
subheader: Optional[str] = None,
|
|
button: str = "Close",
|
|
red: bool = False,
|
|
exc: ExceptionType = wire.ActionCancelled,
|
|
) -> NoReturn:
|
|
await _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.RED if red else ui.ORANGE_ICON,
|
|
exc=exc,
|
|
)
|
|
raise exc
|
|
|
|
|
|
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[None]:
|
|
return _show_modal(
|
|
ctx,
|
|
br_type=br_type,
|
|
br_code=br_code,
|
|
header=header,
|
|
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",
|
|
) -> Awaitable[None]:
|
|
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,
|
|
)
|
|
|
|
|
|
async def confirm_output(
|
|
ctx: wire.GenericContext,
|
|
address: str,
|
|
amount: str,
|
|
) -> None:
|
|
text = Text("Confirm sending", ui.ICON_SEND, ui.GREEN)
|
|
text.normal(amount + " to")
|
|
text.mono(*_split_address(address))
|
|
await raise_if_cancelled(
|
|
interact(ctx, Confirm(text), "confirm_output", ButtonRequestType.ConfirmOutput)
|
|
)
|
|
|
|
|
|
async def confirm_decred_sstx_submission(
|
|
ctx: wire.GenericContext,
|
|
address: str,
|
|
amount: str,
|
|
) -> None:
|
|
text = Text("Purchase ticket", ui.ICON_SEND, ui.GREEN)
|
|
text.normal(amount)
|
|
text.normal("with voting rights to")
|
|
text.mono(*_split_address(address))
|
|
await raise_if_cancelled(
|
|
interact(
|
|
ctx,
|
|
Confirm(text),
|
|
"confirm_decred_sstx_submission",
|
|
ButtonRequestType.ConfirmOutput,
|
|
)
|
|
)
|
|
|
|
|
|
async def confirm_hex(
|
|
ctx: wire.GenericContext,
|
|
br_type: str,
|
|
title: str,
|
|
data: str,
|
|
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
|
|
width: int = MONO_HEX_PER_LINE,
|
|
truncate_middle: bool = False,
|
|
) -> None:
|
|
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)
|
|
await raise_if_cancelled(interact(ctx, content, br_type, br_code))
|
|
|
|
|
|
async def confirm_total(
|
|
ctx: wire.GenericContext, total_amount: str, fee_amount: str
|
|
) -> 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)
|
|
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
|
|
) -> 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)
|
|
await raise_if_cancelled(
|
|
interact(
|
|
ctx, HoldToConfirm(text), "confirm_joint_total", ButtonRequestType.SignTx
|
|
)
|
|
)
|
|
|
|
|
|
async def confirm_metadata(
|
|
ctx: wire.GenericContext,
|
|
br_type: str,
|
|
title: str,
|
|
content: str,
|
|
param: Optional[str] = None,
|
|
br_code: EnumTypeButtonRequestType = ButtonRequestType.SignTx,
|
|
) -> 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?")
|
|
|
|
await raise_if_cancelled(interact(ctx, Confirm(text), br_type, br_code))
|
|
|
|
|
|
async def confirm_replacement(
|
|
ctx: wire.GenericContext, description: str, txid: str
|
|
) -> None:
|
|
text = Text(description, ui.ICON_SEND, ui.GREEN)
|
|
text.normal("Confirm transaction ID:")
|
|
text.mono(*_truncate_hex(txid, TEXT_MAX_LINES - 1))
|
|
await raise_if_cancelled(
|
|
interact(ctx, Confirm(text), "confirm_replacement", ButtonRequestType.SignTx)
|
|
)
|
|
|
|
|
|
async def confirm_modify_output(
|
|
ctx: wire.GenericContext,
|
|
address: str,
|
|
sign: int,
|
|
amount_change: str,
|
|
amount_new: str,
|
|
) -> None:
|
|
page1 = Text("Modify amount", ui.ICON_SEND, ui.GREEN)
|
|
page1.normal("Address:")
|
|
page1.br_half()
|
|
page1.mono(*_split_address(address))
|
|
|
|
page2 = Text("Modify amount", ui.ICON_SEND, ui.GREEN)
|
|
if sign < 0:
|
|
page2.normal("Decrease amount by:")
|
|
else:
|
|
page2.normal("Increase amount by:")
|
|
page2.bold(amount_change)
|
|
page2.br_half()
|
|
page2.normal("New amount:")
|
|
page2.bold(amount_new)
|
|
|
|
await raise_if_cancelled(
|
|
interact(
|
|
ctx,
|
|
Paginated([page1, Confirm(page2)]),
|
|
"modify_output",
|
|
ButtonRequestType.ConfirmOutput,
|
|
)
|
|
)
|
|
|
|
|
|
async def confirm_modify_fee(
|
|
ctx: wire.GenericContext,
|
|
sign: int,
|
|
user_fee_change: str,
|
|
total_fee_new: str,
|
|
) -> None:
|
|
text = Text("Modify fee", ui.ICON_SEND, ui.GREEN)
|
|
if sign == 0:
|
|
text.normal("Your fee did not change.")
|
|
else:
|
|
if sign < 0:
|
|
text.normal("Decrease your fee by:")
|
|
else:
|
|
text.normal("Increase your fee by:")
|
|
text.bold(user_fee_change)
|
|
text.br_half()
|
|
text.normal("Transaction fee:")
|
|
text.bold(total_fee_new)
|
|
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
|
|
) -> 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)
|
|
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]
|
|
) -> None:
|
|
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)
|
|
await raise_if_cancelled(
|
|
interact(ctx, Confirm(text), "sign_identity", ButtonRequestType.Other)
|
|
)
|
|
|
|
|
|
async def confirm_signverify(
|
|
ctx: wire.GenericContext, coin: str, message: str, address: Optional[str] = None
|
|
) -> None:
|
|
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))
|
|
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"
|
|
|
|
await raise_if_cancelled(
|
|
interact(
|
|
ctx,
|
|
paginate_text(message, header, font=font),
|
|
br_type,
|
|
ButtonRequestType.Other,
|
|
)
|
|
)
|