Skip to content

Commit

Permalink
feat(core): implement amount_unit for AuthorizeCoinJoin and SignTx
Browse files Browse the repository at this point in the history
  • Loading branch information
prusnak committed Jan 18, 2021
1 parent eb88d69 commit b818e74
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 44 deletions.
11 changes: 9 additions & 2 deletions core/src/apps/bitcoin/authorize_coinjoin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from micropython import const

from trezor import ui
from trezor.messages import AmountUnit
from trezor.messages.AuthorizeCoinJoin import AuthorizeCoinJoin
from trezor.messages.Success import Success
from trezor.strings import format_amount
Expand All @@ -13,6 +14,7 @@
from .authorization import FEE_PER_ANONYMITY_DECIMALS, CoinJoinAuthorization
from .common import BIP32_WALLET_DEPTH
from .keychain import get_keychain_for_coin, validate_path_against_script_type
from .sign_tx.layout import format_coin_amount

if False:
from trezor import wire
Expand Down Expand Up @@ -61,9 +63,14 @@ async def authorize_coinjoin(ctx: wire.Context, msg: AuthorizeCoinJoin) -> Succe
)
)
text.normal("Maximum total fees:")
amount_unit = (
msg.amount_unit if msg.amount_unit is not None else AmountUnit.BITCOIN
)
text.bold(
"{} {}".format(
format_amount(msg.max_total_fee, coin.decimals), coin.coin_shortcut
format_coin_amount(
msg.max_total_fee,
coin,
amount_unit,
)
)
await require_hold_to_confirm(ctx, text)
Expand Down
18 changes: 12 additions & 6 deletions core/src/apps/bitcoin/sign_tx/approvers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from micropython import const

from trezor import wire
from trezor.messages import OutputScriptType
from trezor.messages import AmountUnit, OutputScriptType

from apps.common import safety_checks

Expand Down Expand Up @@ -42,6 +42,10 @@ def __init__(self, tx: SignTx, coin: CoinInfo) -> None:
self.orig_total_out = 0 # sum of original output amounts
self.orig_change_out = 0 # sum of original change output amounts

self.amount_unit = (
tx.amount_unit if tx.amount_unit is not None else AmountUnit.BITCOIN
)

async def add_internal_input(self, txi: TxInput) -> None:
self.weight.add_input(txi)
self.total_in += txi.amount
Expand Down Expand Up @@ -121,7 +125,7 @@ async def add_external_output(
"Adding new OP_RETURN outputs in replacement transactions is not supported."
)
else:
await helpers.confirm_output(txo, self.coin)
await helpers.confirm_output(txo, self.coin, self.amount_unit)

async def approve_tx(self, tx_info: TxInfo, orig_txs: List[OriginalTxInfo]) -> None:
fee = self.total_in - self.total_out
Expand All @@ -139,7 +143,7 @@ async def approve_tx(self, tx_info: TxInfo, orig_txs: List[OriginalTxInfo]) -> N
if fee > fee_threshold:
if fee > 10 * fee_threshold and safety_checks.is_strict():
raise wire.DataError("The fee is unexpectedly large")
await helpers.confirm_feeoverthreshold(fee, self.coin)
await helpers.confirm_feeoverthreshold(fee, self.coin, self.amount_unit)

if self.change_count > self.MAX_SILENT_CHANGE_COUNT:
await helpers.confirm_change_count_over_threshold(self.change_count)
Expand Down Expand Up @@ -195,7 +199,7 @@ async def approve_tx(self, tx_info: TxInfo, orig_txs: List[OriginalTxInfo]) -> N
# what it's worth, see PR #1292.
if spending > orig_spending or self.external_in == self.orig_external_in:
await helpers.confirm_modify_fee(
spending - orig_spending, fee, self.coin
spending - orig_spending, fee, self.coin, self.amount_unit
)
else:
# Standard transaction.
Expand All @@ -205,9 +209,11 @@ async def approve_tx(self, tx_info: TxInfo, orig_txs: List[OriginalTxInfo]) -> N
)

if not self.external_in:
await helpers.confirm_total(total, fee, self.coin)
await helpers.confirm_total(total, fee, self.coin, self.amount_unit)
else:
await helpers.confirm_joint_total(spending, total, self.coin)
await helpers.confirm_joint_total(
spending, total, self.coin, self.amount_unit
)


class CoinJoinApprover(Approver):
Expand Down
64 changes: 44 additions & 20 deletions core/src/apps/bitcoin/sign_tx/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

if False:
from typing import Any, Awaitable, Optional
from trezor.messages.SignTx import EnumTypeAmountUnit


# Machine instructions
Expand All @@ -44,12 +45,15 @@ def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:


class UiConfirmOutput(UiConfirm):
def __init__(self, output: TxOutput, coin: CoinInfo):
def __init__(
self, output: TxOutput, coin: CoinInfo, amount_unit: EnumTypeAmountUnit
):
self.output = output
self.coin = coin
self.amount_unit = amount_unit

def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:
return layout.confirm_output(ctx, self.output, self.coin)
return layout.confirm_output(ctx, self.output, self.coin, self.amount_unit)

__eq__ = utils.obj_eq

Expand All @@ -66,50 +70,70 @@ def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:


class UiConfirmModifyFee(UiConfirm):
def __init__(self, user_fee_change: int, total_fee_new: int, coin: CoinInfo):
def __init__(
self,
user_fee_change: int,
total_fee_new: int,
coin: CoinInfo,
amount_unit: EnumTypeAmountUnit,
):
self.user_fee_change = user_fee_change
self.total_fee_new = total_fee_new
self.coin = coin
self.amount_unit = amount_unit

def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:
return layout.confirm_modify_fee(
ctx, self.user_fee_change, self.total_fee_new, self.coin
ctx, self.user_fee_change, self.total_fee_new, self.coin, self.amount_unit
)

__eq__ = utils.obj_eq


class UiConfirmTotal(UiConfirm):
def __init__(self, spending: int, fee: int, coin: CoinInfo):
def __init__(
self, spending: int, fee: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit
):
self.spending = spending
self.fee = fee
self.coin = coin
self.amount_unit = amount_unit

def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:
return layout.confirm_total(ctx, self.spending, self.fee, self.coin)
return layout.confirm_total(
ctx, self.spending, self.fee, self.coin, self.amount_unit
)

__eq__ = utils.obj_eq


class UiConfirmJointTotal(UiConfirm):
def __init__(self, spending: int, total: int, coin: CoinInfo):
def __init__(
self, spending: int, total: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit
):
self.spending = spending
self.total = total
self.coin = coin
self.amount_unit = amount_unit

def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:
return layout.confirm_joint_total(ctx, self.spending, self.total, self.coin)
return layout.confirm_joint_total(
ctx, self.spending, self.total, self.coin, self.amount_unit
)

__eq__ = utils.obj_eq


class UiConfirmFeeOverThreshold(UiConfirm):
def __init__(self, fee: int, coin: CoinInfo):
def __init__(self, fee: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit):
self.fee = fee
self.coin = coin
self.amount_unit = amount_unit

def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:
return layout.confirm_feeoverthreshold(ctx, self.fee, self.coin)
return layout.confirm_feeoverthreshold(
ctx, self.fee, self.coin, self.amount_unit
)

__eq__ = utils.obj_eq

Expand Down Expand Up @@ -147,28 +171,28 @@ def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:
__eq__ = utils.obj_eq


def confirm_output(output: TxOutput, coin: CoinInfo) -> Awaitable[None]: # type: ignore
return (yield UiConfirmOutput(output, coin))
def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: EnumTypeAmountUnit) -> Awaitable[None]: # type: ignore
return (yield UiConfirmOutput(output, coin, amount_unit))


def confirm_replacement(description: str, txid: bytes) -> Awaitable[Any]: # type: ignore
return (yield UiConfirmReplacement(description, txid))


def confirm_modify_fee(user_fee_change: int, total_fee_new: int, coin: CoinInfo) -> Awaitable[Any]: # type: ignore
return (yield UiConfirmModifyFee(user_fee_change, total_fee_new, coin))
def confirm_modify_fee(user_fee_change: int, total_fee_new: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit) -> Awaitable[Any]: # type: ignore
return (yield UiConfirmModifyFee(user_fee_change, total_fee_new, coin, amount_unit))


def confirm_total(spending: int, fee: int, coin: CoinInfo) -> Awaitable[None]: # type: ignore
return (yield UiConfirmTotal(spending, fee, coin))
def confirm_total(spending: int, fee: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit) -> Awaitable[None]: # type: ignore
return (yield UiConfirmTotal(spending, fee, coin, amount_unit))


def confirm_joint_total(spending: int, total: int, coin: CoinInfo) -> Awaitable[Any]: # type: ignore
return (yield UiConfirmJointTotal(spending, total, coin))
def confirm_joint_total(spending: int, total: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit) -> Awaitable[Any]: # type: ignore
return (yield UiConfirmJointTotal(spending, total, coin, amount_unit))


def confirm_feeoverthreshold(fee: int, coin: CoinInfo) -> Awaitable[Any]: # type: ignore
return (yield UiConfirmFeeOverThreshold(fee, coin))
def confirm_feeoverthreshold(fee: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit) -> Awaitable[Any]: # type: ignore
return (yield UiConfirmFeeOverThreshold(fee, coin, amount_unit))


def confirm_change_count_over_threshold(change_count: int) -> Awaitable[Any]: # type: ignore
Expand Down
65 changes: 49 additions & 16 deletions core/src/apps/bitcoin/sign_tx/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ubinascii import hexlify

from trezor import ui
from trezor.messages import ButtonRequestType, OutputScriptType
from trezor.messages import AmountUnit, ButtonRequestType, OutputScriptType
from trezor.strings import format_amount
from trezor.ui.text import Text
from trezor.utils import chunks
Expand All @@ -15,15 +15,32 @@
if False:
from typing import Iterator
from trezor import wire
from trezor.messages.SignTx import EnumTypeAmountUnit
from trezor.messages.TxOutput import TxOutput

from apps.common.coininfo import CoinInfo

_LOCKTIME_TIMESTAMP_MIN_VALUE = const(500_000_000)


def format_coin_amount(amount: int, coin: CoinInfo) -> str:
return "%s %s" % (format_amount(amount, coin.decimals), coin.coin_shortcut)
def format_coin_amount(
amount: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit
) -> str:
decimals, shortcut = coin.decimals, coin.coin_shortcut
if amount_unit == AmountUnit.SATOSHI:
amount *= 1_0000_0000
decimals -= 8
shortcut = "sat"
elif amount_unit == AmountUnit.MICROBITCOIN:
amount *= 1_000_000
decimals -= 6
shortcut = "u" + shortcut
elif amount_unit == AmountUnit.MILLIBITCOIN:
amount *= 1_000
decimals -= 3
shortcut = "m" + shortcut
# we don't need to do anything for AmountUnit.BITCOIN
return "%s %s" % (format_amount(amount, decimals), shortcut)


def split_address(address: str) -> Iterator[str]:
Expand All @@ -34,7 +51,9 @@ def split_op_return(data: str) -> Iterator[str]:
return chunks(data, 18)


async def confirm_output(ctx: wire.Context, output: TxOutput, coin: CoinInfo) -> None:
async def confirm_output(
ctx: wire.Context, output: TxOutput, coin: CoinInfo, amount_unit: EnumTypeAmountUnit
) -> None:
if output.script_type == OutputScriptType.PAYTOOPRETURN:
data = output.op_return_data
assert data is not None
Expand All @@ -54,7 +73,7 @@ async def confirm_output(ctx: wire.Context, output: TxOutput, coin: CoinInfo) ->
assert address is not None
address_short = addresses.address_short(coin, address)
text = Text("Confirm sending", ui.ICON_SEND, ui.GREEN)
text.normal(format_coin_amount(output.amount, coin) + " to")
text.normal(format_coin_amount(output.amount, coin, amount_unit) + " to")
text.mono(*split_address(address_short))
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)

Expand All @@ -70,7 +89,11 @@ async def confirm_replacement(ctx: wire.Context, description: str, txid: bytes)


async def confirm_modify_fee(
ctx: wire.Context, user_fee_change: int, total_fee_new: int, coin: CoinInfo
ctx: wire.Context,
user_fee_change: int,
total_fee_new: int,
coin: CoinInfo,
amount_unit: EnumTypeAmountUnit,
) -> None:
text = Text("Fee modification", ui.ICON_SEND, ui.GREEN)
if user_fee_change == 0:
Expand All @@ -80,39 +103,49 @@ async def confirm_modify_fee(
text.normal("Decrease your fee by:")
else:
text.normal("Increase your fee by:")
text.bold(format_coin_amount(abs(user_fee_change), coin))
text.bold(format_coin_amount(abs(user_fee_change), coin, amount_unit))
text.br_half()
text.normal("Transaction fee:")
text.bold(format_coin_amount(total_fee_new, coin))
text.bold(format_coin_amount(total_fee_new, coin, amount_unit))
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx)


async def confirm_joint_total(
ctx: wire.Context, spending: int, total: int, coin: CoinInfo
ctx: wire.Context,
spending: int,
total: int,
coin: CoinInfo,
amount_unit: EnumTypeAmountUnit,
) -> None:
text = Text("Joint transaction", ui.ICON_SEND, ui.GREEN)
text.normal("You are contributing:")
text.bold(format_coin_amount(spending, coin))
text.bold(format_coin_amount(spending, coin, amount_unit))
text.normal("to the total amount:")
text.bold(format_coin_amount(total, coin))
text.bold(format_coin_amount(total, coin, amount_unit))
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx)


async def confirm_total(
ctx: wire.Context, spending: int, fee: int, coin: CoinInfo
ctx: wire.Context,
spending: int,
fee: int,
coin: CoinInfo,
amount_unit: EnumTypeAmountUnit,
) -> None:
text = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
text.normal("Total amount:")
text.bold(format_coin_amount(spending, coin))
text.bold(format_coin_amount(spending, coin, amount_unit))
text.normal("including fee:")
text.bold(format_coin_amount(fee, coin))
text.bold(format_coin_amount(fee, coin, amount_unit))
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx)


async def confirm_feeoverthreshold(ctx: wire.Context, fee: int, coin: CoinInfo) -> None:
async def confirm_feeoverthreshold(
ctx: wire.Context, fee: int, coin: CoinInfo, amount_unit: EnumTypeAmountUnit
) -> None:
text = Text("High fee", ui.ICON_SEND, ui.GREEN)
text.normal("The fee of")
text.bold(format_coin_amount(fee, coin))
text.bold(format_coin_amount(fee, coin, amount_unit))
text.normal("is unexpectedly high.", "Continue?")
await require_confirm(ctx, text, ButtonRequestType.FeeOverThreshold)

Expand Down

0 comments on commit b818e74

Please sign in to comment.