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.
trezor-firmware/core/src/apps/monero/layout/confirms.py

197 lines
6.4 KiB

from ubinascii import hexlify
from trezor import ui, wire
from trezor.messages import ButtonRequestType
from trezor.ui.popup import Popup
from trezor.ui.text import Text
from trezor.utils import chunks
from apps.common.confirm import require_confirm, require_hold_to_confirm
from apps.monero.layout import common
DUMMY_PAYMENT_ID = b"\x00" * 8
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?")
return await require_confirm(ctx, content, 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?")
return await require_confirm(ctx, content, 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?")
return await require_confirm(ctx, content, 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?")
else:
txt.append("export tx_der")
txt.append("for tx_proof?")
content.normal(*txt)
return await require_confirm(ctx, content, ButtonRequestType.SignTx)
async def require_confirm_transaction(ctx, state, tsx_data, network_type):
"""
Ask for confirmation from user.
"""
from apps.monero.xmr.addresses import get_change_addr_idx
outputs = tsx_data.outputs
change_idx = get_change_addr_idx(outputs, tsx_data.change_dts)
has_integrated = bool(tsx_data.integrated_indices)
has_payment = bool(tsx_data.payment_id)
for idx, dst in enumerate(outputs):
is_change = change_idx is not None and idx == change_idx
if is_change:
continue # Change output does not need confirmation
is_dummy = change_idx is None and dst.amount == 0 and len(outputs) == 2
if is_dummy:
continue # Dummy output does not need confirmation
if has_integrated and idx in tsx_data.integrated_indices:
cur_payment = tsx_data.payment_id
else:
cur_payment = None
await _require_confirm_output(ctx, dst, network_type, cur_payment)
if has_payment and not has_integrated and tsx_data.payment_id != DUMMY_PAYMENT_ID:
await _require_confirm_payment_id(ctx, tsx_data.payment_id)
await _require_confirm_fee(ctx, tsx_data.fee)
await transaction_step(state, 0)
async def _require_confirm_output(ctx, dst, network_type, payment_id):
"""
Single transaction destination confirmation
"""
from apps.monero.xmr.addresses import encode_addr
from apps.monero.xmr.networks import net_version
version = net_version(network_type, dst.is_subaddress, payment_id is not None)
addr = encode_addr(
version, dst.addr.spend_public_key, dst.addr.view_public_key, payment_id
)
text_addr = common.split_address(addr.decode())
text_amount = common.format_amount(dst.amount)
if not await common.naive_pagination(
ctx,
[ui.BOLD, text_amount, ui.MONO] + list(text_addr),
"Confirm send",
ui.ICON_SEND,
ui.GREEN,
4,
):
raise wire.ActionCancelled("Cancelled")
async def _require_confirm_payment_id(ctx, payment_id):
if not await common.naive_pagination(
ctx,
[ui.MONO] + list(chunks(hexlify((payment_id)), 16)),
"Payment ID",
ui.ICON_SEND,
ui.GREEN,
):
raise wire.ActionCancelled("Cancelled")
async def _require_confirm_fee(ctx, fee):
content = Text("Confirm fee", ui.ICON_SEND, ui.GREEN)
content.bold(common.format_amount(fee))
await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
class TransactionStep(ui.Control):
def __init__(self, state, info):
self.state = state
self.info = info
def on_render(self):
state = self.state
info = self.info
ui.header("Signing transaction", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE)
p = 1000 * state.progress_cur // state.progress_total
ui.display.loader(p, False, -4, ui.WHITE, ui.BG)
ui.display.text_center(ui.WIDTH // 2, 210, info[0], ui.NORMAL, ui.FG, ui.BG)
if len(info) > 1:
ui.display.text_center(ui.WIDTH // 2, 235, info[1], ui.NORMAL, ui.FG, ui.BG)
class KeyImageSyncStep(ui.Control):
def __init__(self, current, total_num):
self.current = current
self.total_num = total_num
def on_render(self):
current = self.current
total_num = self.total_num
ui.header("Syncing", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE)
p = (1000 * (current + 1) // total_num) if total_num > 0 else 0
ui.display.loader(p, False, 18, ui.WHITE, ui.BG)
class LiveRefreshStep(ui.Control):
def __init__(self, current):
self.current = current
def on_render(self):
current = self.current
ui.header("Refreshing", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE)
p = (1000 * current // 8) % 1000
ui.display.loader(p, True, 18, ui.WHITE, ui.BG)
ui.display.text_center(
ui.WIDTH // 2, 145, "%d" % current, ui.NORMAL, ui.FG, ui.BG
)
async def transaction_step(state, step, sub_step=None):
if step == 0:
info = ["Signing..."]
elif step == 100:
info = ["Processing inputs", "%d/%d" % (sub_step + 1, state.input_count)]
elif step == 200:
info = ["Sorting..."]
elif step == 300:
info = ["Hashing inputs", "%d/%d" % (sub_step + 1, state.input_count)]
elif step == 350:
info = ["Processing..."]
elif step == 400:
info = ["Processing outputs", "%d/%d" % (sub_step + 1, state.output_count)]
elif step == 500:
info = ["Postprocessing..."]
elif step == 600:
info = ["Signing inputs", "%d/%d" % (sub_step + 1, state.input_count)]
else:
info = ["Processing..."]
state.progress_cur += 1
await Popup(TransactionStep(state, info))
async def keyimage_sync_step(ctx, current, total_num):
if current is None:
return
await Popup(KeyImageSyncStep(current, total_num))
async def live_refresh_step(ctx, current):
if current is None:
return
await Popup(LiveRefreshStep(current))