From 41ab1b7ff43db3480c6e8760a3a4ee8edb3003ee Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Wed, 25 May 2022 20:07:50 +0200 Subject: [PATCH] feat(core): Confirm source account in SignTx. --- core/src/apps/bitcoin/paths.py | 23 ++++++++++++++++++- core/src/apps/bitcoin/sign_tx/approvers.py | 24 +++++++++++++++++++- core/src/apps/bitcoin/sign_tx/bitcoin.py | 3 +++ core/src/apps/bitcoin/sign_tx/helpers.py | 12 ++++++++++ core/src/apps/bitcoin/sign_tx/layout.py | 4 ++++ core/src/apps/bitcoin/sign_tx/matchcheck.py | 6 +++++ core/src/apps/common/paths.py | 1 + core/src/trezor/ui/layouts/tt/__init__.py | 9 ++++++++ core/src/trezor/ui/layouts/tt_v2/__init__.py | 4 ++++ 9 files changed, 84 insertions(+), 2 deletions(-) diff --git a/core/src/apps/bitcoin/paths.py b/core/src/apps/bitcoin/paths.py index 0c7be8e2d..c43c1c0e9 100644 --- a/core/src/apps/bitcoin/paths.py +++ b/core/src/apps/bitcoin/paths.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from trezor.enums import InputScriptType -from apps.common.paths import PATTERN_BIP44, PathSchema +from apps.common.paths import PATTERN_BIP44, UNHARDEN_MASK, PathSchema from .common import BITCOIN_NAMES @@ -202,3 +202,24 @@ def get_schemas_for_coin( gc.collect() return [schema.copy() for schema in schemas] + + +def address_n_to_name(address_n: list[int], coin: CoinInfo) -> str | None: + patterns: list[tuple[str, str]] = [(PATTERN_BIP44, "Legacy")] + if coin.segwit: + patterns.append((PATTERN_BIP49, "Legacy SegWit")) + patterns.append((PATTERN_BIP84, "SegWit")) + + if coin.taproot: + patterns.append((PATTERN_BIP86, "Taproot")) + patterns.append((PATTERN_SLIP25, "CoinJoin")) + + for pattern, account_type in patterns: + if PathSchema.parse(pattern, coin.slip44).match(address_n): + account_number = (address_n[2] & UNHARDEN_MASK) + 1 + if len(patterns) == 1: + return f"account #{account_number}" + else: + return f"{account_type} account #{account_number}" + + return None diff --git a/core/src/apps/bitcoin/sign_tx/approvers.py b/core/src/apps/bitcoin/sign_tx/approvers.py index 377d49182..06c5bda60 100644 --- a/core/src/apps/bitcoin/sign_tx/approvers.py +++ b/core/src/apps/bitcoin/sign_tx/approvers.py @@ -6,10 +6,11 @@ from trezor.enums import OutputScriptType from trezor.ui.components.common.confirm import INFO from apps.common import safety_checks +from apps.common.paths import address_n_to_str from ..authorization import FEE_RATE_DECIMALS from ..common import input_is_external_unverified -from ..paths import validate_path_against_script_type +from ..paths import address_n_to_name, validate_path_against_script_type from . import helpers, tx_weight from .payment_request import PaymentRequestVerifier from .tx_info import OriginalTxInfo, TxInfo @@ -121,6 +122,9 @@ class Approver: def add_orig_external_output(self, txo: TxOutput) -> None: self.orig_total_out += txo.amount + async def approve_account(self, tx_info: TxInfo) -> None: + raise NotImplementedError + async def approve_orig_txids( self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo] ) -> None: @@ -214,6 +218,21 @@ class BasicApprover(Approver): result = await helpers.confirm_payment_request(msg, self.coin, self.amount_unit) self.show_payment_req_details = result is INFO + async def approve_account(self, tx_info: TxInfo) -> None: + wallet_path = tx_info.wallet_path.get_wallet_path() + if not wallet_path: + description = f"multiple {self.coin.coin_name} accounts" + else: + account_name = address_n_to_name(wallet_path + [0, 0], self.coin) + if account_name: + description = f"{self.coin.coin_name} {account_name}" + else: + description = ( + f"{self.coin.coin_name} account {address_n_to_str(wallet_path)}" + ) + + await helpers.confirm_account(description) + async def approve_orig_txids( self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo] ) -> None: @@ -379,6 +398,9 @@ class CoinJoinApprover(Approver): if msg.memos: raise wire.DataError("Memos not allowed in CoinJoin payment request.") + async def approve_account(self, tx_info: TxInfo) -> None: + pass + async def approve_orig_txids( self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo] ) -> None: diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index 2acdefefc..43b9c867a 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -54,6 +54,9 @@ class Bitcoin: # Add inputs to sig_hasher and h_tx_check and compute the sum of input amounts. await self.step1_process_inputs() + # Approve the source account. + await self.approver.approve_account(self.tx_info) + # Approve the original TXIDs in case of a replacement transaction. await self.approver.approve_orig_txids(self.tx_info, self.orig_txs) diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index 482b4113a..d32fbd66f 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -83,6 +83,14 @@ class UiConfirmPaymentRequest(UiConfirm): __eq__ = utils.obj_eq +class UiConfirmAccount(UiConfirm): + def __init__(self, description: str): + self.description = description + + def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + return layout.confirm_account(ctx, self.description) + + class UiConfirmReplacement(UiConfirm): def __init__(self, description: str, txid: bytes): self.description = description @@ -222,6 +230,10 @@ def confirm_payment_request(payment_req: TxAckPaymentRequest, coin: CoinInfo, am return (yield UiConfirmPaymentRequest(payment_req, coin, amount_unit)) +def confirm_account(description: str) -> Awaitable[Any]: # type: ignore [awaitable-is-generator] + return (yield UiConfirmAccount(description)) + + def confirm_replacement(description: str, txid: bytes) -> Awaitable[Any]: # type: ignore [awaitable-is-generator] return (yield UiConfirmReplacement(description, txid)) diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index 1db50bf76..843994092 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -123,6 +123,10 @@ async def confirm_payment_request( ) +async def confirm_account(ctx: wire.Context, description: str) -> None: + await layouts.confirm_account(ctx, description) + + async def confirm_replacement(ctx: wire.Context, description: str, txid: bytes) -> None: await layouts.confirm_replacement( ctx, diff --git a/core/src/apps/bitcoin/sign_tx/matchcheck.py b/core/src/apps/bitcoin/sign_tx/matchcheck.py index 39fa3554f..88d2497ac 100644 --- a/core/src/apps/bitcoin/sign_tx/matchcheck.py +++ b/core/src/apps/bitcoin/sign_tx/matchcheck.py @@ -91,6 +91,12 @@ class WalletPathChecker(MatchChecker): return None return txio.address_n[:-BIP32_WALLET_DEPTH] + def get_wallet_path(self) -> list[int] | None: + if isinstance(self.attribute, list): + return self.attribute + else: + return None + class MultisigFingerprintChecker(MatchChecker): def attribute_from_tx(self, txio: TxInput | TxOutput) -> Any: diff --git a/core/src/apps/common/paths.py b/core/src/apps/common/paths.py index 64218f6b1..5921dd146 100644 --- a/core/src/apps/common/paths.py +++ b/core/src/apps/common/paths.py @@ -2,6 +2,7 @@ from micropython import const from typing import TYPE_CHECKING HARDENED = const(0x8000_0000) +UNHARDEN_MASK = const(0x7FFF_FFFF) if TYPE_CHECKING: from typing import ( diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index 1b689636a..c1bc518e6 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -45,6 +45,7 @@ if TYPE_CHECKING: __all__ = ( + "confirm_account", "confirm_action", "confirm_address", "confirm_text", @@ -925,6 +926,14 @@ async def confirm_metadata( await raise_if_cancelled(interact(ctx, cls(text), br_type, br_code)) +async def confirm_account(ctx: wire.GenericContext, description: str) -> None: + text = Text("Confirm account", ui.ICON_SEND, ui.GREEN) + text.normal(f"Spend from {description}?") + await raise_if_cancelled( + interact(ctx, Confirm(text), "confirm_account", ButtonRequestType.SignTx) + ) + + async def confirm_replacement( ctx: wire.GenericContext, description: str, txid: str ) -> None: diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index c7a0fdb37..f3eccd541 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -440,6 +440,10 @@ async def confirm_metadata( raise NotImplementedError +async def confirm_account(ctx: wire.GenericContext, description: str) -> None: + raise NotImplementedError + + async def confirm_replacement( ctx: wire.GenericContext, description: str, txid: str ) -> None: