From eb28998f98101adc7f00f5aed8505349ad5c22c8 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Wed, 8 Jul 2020 18:42:19 +0200 Subject: [PATCH] core/bitcoin: Support multiple change-outputs. --- core/CHANGELOG.md | 2 ++ core/src/apps/bitcoin/sign_tx/__init__.py | 7 +++++++ core/src/apps/bitcoin/sign_tx/bitcoin.py | 11 +++++++++-- core/src/apps/bitcoin/sign_tx/helpers.py | 11 +++++++++++ core/src/apps/bitcoin/sign_tx/layout.py | 14 ++++++++++++++ 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 6e3a78fe51..b1be7a4a1d 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -14,6 +14,7 @@ _Most likely to be released on July 1st._ - Dedicated `initialized` field in storage. - Support EXTERNAL transaction inputs with a SLIP-0019 proof of ownership. [#1052] - Support pre-signed EXTERNAL transaction inputs. +- Support multiple change-outputs. [#1098] ### Changed - `Features.pin_cached` renamed to `unlocked`. @@ -168,3 +169,4 @@ Version 2.0.5 [Mar 2018] [#1056]: https://github.com/trezor/trezor-firmware/issues/1056 [#1067]: https://github.com/trezor/trezor-firmware/issues/1067 [#1074]: https://github.com/trezor/trezor-firmware/issues/1074 +[#1098]: https://github.com/trezor/trezor-firmware/issues/1098 diff --git a/core/src/apps/bitcoin/sign_tx/__init__.py b/core/src/apps/bitcoin/sign_tx/__init__.py index e39c349e29..f0c0b99bbc 100644 --- a/core/src/apps/bitcoin/sign_tx/__init__.py +++ b/core/src/apps/bitcoin/sign_tx/__init__.py @@ -67,6 +67,13 @@ async def sign_tx( res = await layout.confirm_feeoverthreshold(ctx, req.fee, req.coin) utils.unimport_end(mods) progress.report_init() + elif isinstance(req, helpers.UiConfirmChangeCountOverThreshold): + mods = utils.unimport_begin() + res = await layout.confirm_change_count_over_threshold( + ctx, req.change_count + ) + utils.unimport_end(mods) + progress.report_init() elif isinstance(req, helpers.UiConfirmNonDefaultLocktime): mods = utils.unimport_begin() res = await layout.confirm_nondefault_locktime(ctx, req.lock_time) diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index d83b551193..6334a53cc7 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -38,6 +38,9 @@ _BIP32_MAX_LAST_ELEMENT = const(1000000) # the number of bytes to preallocate for serialized transaction chunks _MAX_SERIALIZED_CHUNK_SIZE = const(2048) +# the maximum number of change-outputs allowed without user confirmation +_MAX_SILENT_CHANGE_COUNT = const(2) + class Bitcoin: async def signer(self) -> None: @@ -92,6 +95,7 @@ class Bitcoin: self.external_in = 0 # sum of external input amounts self.total_out = 0 # sum of output amounts self.change_out = 0 # change output amount + self.change_count = 0 # the number of change-outputs self.weight = tx_weight.TxWeightCalculator(tx.inputs_count, tx.outputs_count) # transaction and signature serialization @@ -154,6 +158,8 @@ class Bitcoin: # fee > (coin.maxfee per byte * tx size) if fee > (self.coin.maxfee_kb / 1000) * (self.weight.get_total() / 4): await helpers.confirm_feeoverthreshold(fee, self.coin) + if self.change_count > _MAX_SILENT_CHANGE_COUNT: + await helpers.confirm_change_count_over_threshold(self.change_count) if self.tx.lock_time > 0: await helpers.confirm_nondefault_locktime(self.tx.lock_time) if not self.external: @@ -268,9 +274,10 @@ class Bitcoin: self.external_in += txi.amount async def confirm_output(self, txo: TxOutputType, script_pubkey: bytes) -> None: - if self.change_out == 0 and self.output_is_change(txo): + if self.output_is_change(txo): # output is change and does not need confirmation - self.change_out = txo.amount + self.change_out += txo.amount + self.change_count += 1 else: await helpers.confirm_output(txo, self.coin) diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index d6987d342a..0848d62eae 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -63,6 +63,13 @@ class UiConfirmFeeOverThreshold: __eq__ = utils.obj_eq +class UiConfirmChangeCountOverThreshold: + def __init__(self, change_count: int): + self.change_count = change_count + + __eq__ = utils.obj_eq + + class UiConfirmForeignAddress: def __init__(self, address_n: list): self.address_n = address_n @@ -93,6 +100,10 @@ def confirm_feeoverthreshold(fee: int, coin: CoinInfo) -> Awaitable[Any]: # typ return (yield UiConfirmFeeOverThreshold(fee, coin)) +def confirm_change_count_over_threshold(change_count: int) -> Awaitable[Any]: # type: ignore + return (yield UiConfirmChangeCountOverThreshold(change_count)) + + def confirm_foreign_address(address_n: list) -> Awaitable[Any]: # type: ignore return (yield UiConfirmForeignAddress(address_n)) diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index ffb9c1d6f0..27cf62d929 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -99,6 +99,20 @@ async def confirm_feeoverthreshold( await require_confirm(ctx, text, ButtonRequestType.FeeOverThreshold) +async def confirm_change_count_over_threshold( + ctx: wire.Context, change_count: int +) -> None: + from trezor.ui.text import Text + from apps.common.confirm import require_confirm + + text = Text("Warning", ui.ICON_SEND, ui.GREEN) + text.normal("There are {}".format(change_count)) + text.normal("change-outputs.") + text.br_half() + text.normal("Continue?") + await require_confirm(ctx, text, ButtonRequestType.SignTx) + + async def confirm_nondefault_locktime(ctx: wire.Context, lock_time: int) -> None: from trezor.ui.text import Text from apps.common.confirm import require_confirm