feat(core): Confirm source account in SignTx.

Andrew Kozlik 2 years ago
parent 22b992a018
commit 41ab1b7ff4

@ -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

@ -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:

@ -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)

@ -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))

@ -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,

@ -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:

@ -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 (

@ -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:

@ -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:

Loading…
Cancel
Save