diff --git a/core/src/apps/bitcoin/addresses.py b/core/src/apps/bitcoin/addresses.py index ffa564463d..70ced13d1f 100644 --- a/core/src/apps/bitcoin/addresses.py +++ b/core/src/apps/bitcoin/addresses.py @@ -1,22 +1,20 @@ from typing import TYPE_CHECKING -from trezor import wire -from trezor.crypto import base58, cashaddr -from trezor.crypto.curve import bip340 +from trezor.crypto import base58 from trezor.crypto.hashlib import sha256 -from trezor.enums import InputScriptType from trezor.utils import HashWriter +from trezor.wire import ProcessError from apps.common import address_type -from apps.common.coininfo import CoinInfo from .common import ecdsa_hash_pubkey, encode_bech32_address -from .multisig import multisig_get_pubkeys, multisig_pubkey_index from .scripts import output_script_native_segwit, write_output_script_multisig if TYPE_CHECKING: from trezor.messages import MultisigRedeemScriptType from trezor.crypto import bip32 + from apps.common.coininfo import CoinInfo + from trezor.enums import InputScriptType def get_address( @@ -25,22 +23,27 @@ def get_address( node: bip32.HDNode, multisig: MultisigRedeemScriptType | None = None, ) -> str: + from trezor.enums import InputScriptType + from .multisig import multisig_get_pubkeys, multisig_pubkey_index + + node_public_key = node.public_key() # result_cache + if multisig: # Ensure that our public key is included in the multisig. - multisig_pubkey_index(multisig, node.public_key()) + multisig_pubkey_index(multisig, node_public_key) if script_type in (InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG): if multisig: # p2sh multisig if coin.address_type_p2sh is None: - raise wire.ProcessError("Multisig not enabled on this coin") + raise ProcessError("Multisig not enabled on this coin") pubkeys = multisig_get_pubkeys(multisig) - address = address_multisig_p2sh(pubkeys, multisig.m, coin) + address = _address_multisig_p2sh(pubkeys, multisig.m, coin) if coin.cashaddr_prefix is not None: address = address_to_cashaddr(address, coin) return address if script_type == InputScriptType.SPENDMULTISIG: - raise wire.ProcessError("Multisig details required") + raise ProcessError("Multisig details required") # p2pkh address = node.address(coin.address_type) @@ -50,63 +53,65 @@ def get_address( elif script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or native p2wsh if not coin.segwit or not coin.bech32_prefix: - raise wire.ProcessError("Segwit not enabled on this coin") + raise ProcessError("Segwit not enabled on this coin") # native p2wsh multisig if multisig is not None: pubkeys = multisig_get_pubkeys(multisig) - return address_multisig_p2wsh(pubkeys, multisig.m, coin.bech32_prefix) + return _address_multisig_p2wsh(pubkeys, multisig.m, coin.bech32_prefix) # native p2wpkh - return address_p2wpkh(node.public_key(), coin) + return address_p2wpkh(node_public_key, coin) elif script_type == InputScriptType.SPENDTAPROOT: # taproot if not coin.taproot or not coin.bech32_prefix: - raise wire.ProcessError("Taproot not enabled on this coin") + raise ProcessError("Taproot not enabled on this coin") if multisig is not None: - raise wire.ProcessError("Multisig not supported for taproot") + raise ProcessError("Multisig not supported for taproot") - return address_p2tr(node.public_key(), coin) + return _address_p2tr(node_public_key, coin) elif ( script_type == InputScriptType.SPENDP2SHWITNESS ): # p2wpkh or p2wsh nested in p2sh if not coin.segwit or coin.address_type_p2sh is None: - raise wire.ProcessError("Segwit not enabled on this coin") + raise ProcessError("Segwit not enabled on this coin") # p2wsh multisig nested in p2sh if multisig is not None: pubkeys = multisig_get_pubkeys(multisig) - return address_multisig_p2wsh_in_p2sh(pubkeys, multisig.m, coin) + return _address_multisig_p2wsh_in_p2sh(pubkeys, multisig.m, coin) # p2wpkh nested in p2sh - return address_p2wpkh_in_p2sh(node.public_key(), coin) + return address_p2wpkh_in_p2sh(node_public_key, coin) else: - raise wire.ProcessError("Invalid script type") + raise ProcessError("Invalid script type") -def address_multisig_p2sh(pubkeys: list[bytes], m: int, coin: CoinInfo) -> str: +def _address_multisig_p2sh(pubkeys: list[bytes], m: int, coin: CoinInfo) -> str: if coin.address_type_p2sh is None: - raise wire.ProcessError("Multisig not enabled on this coin") + raise ProcessError("Multisig not enabled on this coin") redeem_script = HashWriter(coin.script_hash()) write_output_script_multisig(redeem_script, pubkeys, m) return address_p2sh(redeem_script.get_digest(), coin) -def address_multisig_p2wsh_in_p2sh(pubkeys: list[bytes], m: int, coin: CoinInfo) -> str: +def _address_multisig_p2wsh_in_p2sh( + pubkeys: list[bytes], m: int, coin: CoinInfo +) -> str: if coin.address_type_p2sh is None: - raise wire.ProcessError("Multisig not enabled on this coin") + raise ProcessError("Multisig not enabled on this coin") witness_script_h = HashWriter(sha256()) write_output_script_multisig(witness_script_h, pubkeys, m) - return address_p2wsh_in_p2sh(witness_script_h.get_digest(), coin) + return _address_p2wsh_in_p2sh(witness_script_h.get_digest(), coin) -def address_multisig_p2wsh(pubkeys: list[bytes], m: int, hrp: str) -> str: +def _address_multisig_p2wsh(pubkeys: list[bytes], m: int, hrp: str) -> str: if not hrp: - raise wire.ProcessError("Multisig not enabled on this coin") + raise ProcessError("Multisig not enabled on this coin") witness_script_h = HashWriter(sha256()) write_output_script_multisig(witness_script_h, pubkeys, m) - return address_p2wsh(witness_script_h.get_digest(), hrp) + return _address_p2wsh(witness_script_h.get_digest(), hrp) def address_pkh(pubkey: bytes, coin: CoinInfo) -> str: @@ -126,7 +131,7 @@ def address_p2wpkh_in_p2sh(pubkey: bytes, coin: CoinInfo) -> str: return address_p2sh(redeem_script_hash, coin) -def address_p2wsh_in_p2sh(witness_script_hash: bytes, coin: CoinInfo) -> str: +def _address_p2wsh_in_p2sh(witness_script_hash: bytes, coin: CoinInfo) -> str: redeem_script = output_script_native_segwit(0, witness_script_hash) redeem_script_hash = coin.script_hash(redeem_script).digest() return address_p2sh(redeem_script_hash, coin) @@ -138,17 +143,21 @@ def address_p2wpkh(pubkey: bytes, coin: CoinInfo) -> str: return encode_bech32_address(coin.bech32_prefix, 0, pubkeyhash) -def address_p2wsh(witness_script_hash: bytes, hrp: str) -> str: +def _address_p2wsh(witness_script_hash: bytes, hrp: str) -> str: return encode_bech32_address(hrp, 0, witness_script_hash) -def address_p2tr(pubkey: bytes, coin: CoinInfo) -> str: +def _address_p2tr(pubkey: bytes, coin: CoinInfo) -> str: + from trezor.crypto.curve import bip340 + assert coin.bech32_prefix is not None output_pubkey = bip340.tweak_public_key(pubkey[1:]) return encode_bech32_address(coin.bech32_prefix, 1, output_pubkey) def address_to_cashaddr(address: str, coin: CoinInfo) -> str: + from trezor.crypto import cashaddr + assert coin.cashaddr_prefix is not None raw = base58.decode_check(address, coin.b58_hash) version, data = raw[0], raw[1:] diff --git a/core/src/apps/bitcoin/authorization.py b/core/src/apps/bitcoin/authorization.py index 7678c159c5..79e015de5d 100644 --- a/core/src/apps/bitcoin/authorization.py +++ b/core/src/apps/bitcoin/authorization.py @@ -1,16 +1,11 @@ from micropython import const from typing import TYPE_CHECKING -from trezor import utils, wire -from trezor.messages import AuthorizeCoinJoin - -from apps.common import authorization - from .common import BIP32_WALLET_DEPTH -from .writers import write_bytes_prefixed if TYPE_CHECKING: from trezor.messages import ( + AuthorizeCoinJoin, GetOwnershipProof, SignTx, TxInput, @@ -27,14 +22,19 @@ class CoinJoinAuthorization: self.params = params def check_get_ownership_proof(self, msg: GetOwnershipProof) -> bool: + from trezor import utils + from .writers import write_bytes_prefixed + + params = self.params # local_cache_attribute + # Check whether the current params matches the parameters of the request. - coordinator = utils.empty_bytearray(1 + len(self.params.coordinator.encode())) - write_bytes_prefixed(coordinator, self.params.coordinator.encode()) + coordinator = utils.empty_bytearray(1 + len(params.coordinator.encode())) + write_bytes_prefixed(coordinator, params.coordinator.encode()) return ( len(msg.address_n) >= BIP32_WALLET_DEPTH - and msg.address_n[:-BIP32_WALLET_DEPTH] == self.params.address_n - and msg.coin_name == self.params.coin_name - and msg.script_type == self.params.script_type + and msg.address_n[:-BIP32_WALLET_DEPTH] == params.address_n + and msg.coin_name == params.coin_name + and msg.script_type == params.script_type and msg.commitment_data.startswith(bytes(coordinator)) ) @@ -48,15 +48,22 @@ class CoinJoinAuthorization: ) def approve_sign_tx(self, msg: SignTx) -> bool: - if self.params.max_rounds < 1 or msg.coin_name != self.params.coin_name: + from apps.common import authorization + + params = self.params # local_cache_attribute + + if params.max_rounds < 1 or msg.coin_name != params.coin_name: return False - self.params.max_rounds -= 1 - authorization.set(self.params) + params.max_rounds -= 1 + authorization.set(params) return True def from_cached_message(auth_msg: MessageType) -> CoinJoinAuthorization: + from trezor import wire + from trezor.messages import AuthorizeCoinJoin + if not AuthorizeCoinJoin.is_type_of(auth_msg): raise wire.ProcessError("Appropriate params was not found") diff --git a/core/src/apps/bitcoin/authorize_coinjoin.py b/core/src/apps/bitcoin/authorize_coinjoin.py index 0b71a05181..b3666dd9e1 100644 --- a/core/src/apps/bitcoin/authorize_coinjoin.py +++ b/core/src/apps/bitcoin/authorize_coinjoin.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: from trezor.messages import AuthorizeCoinJoin, Success from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain - from trezor import wire + from trezor.wire import Context _MAX_COORDINATOR_LEN = const(36) _MAX_ROUNDS = const(500) @@ -17,41 +17,44 @@ _MAX_COORDINATOR_FEE_RATE = 5 * pow(10, FEE_RATE_DECIMALS) # 5 % @with_keychain async def authorize_coinjoin( - ctx: wire.Context, msg: AuthorizeCoinJoin, keychain: Keychain, coin: CoinInfo + ctx: Context, msg: AuthorizeCoinJoin, keychain: Keychain, coin: CoinInfo ) -> Success: - from trezor import wire from trezor.enums import ButtonRequestType from trezor.messages import Success from trezor.ui.layouts import confirm_coinjoin, confirm_metadata + from trezor.wire import DataError from apps.common import authorization, safety_checks from apps.common.keychain import FORBIDDEN_KEY_PATH from apps.common.paths import SLIP25_PURPOSE, validate_path - from .keychain import validate_path_against_script_type from .common import BIP32_WALLET_DEPTH, format_fee_rate + from .keychain import validate_path_against_script_type + + safety_checks_is_strict = safety_checks.is_strict() # result_cache + address_n = msg.address_n # local_cache_attribute if len(msg.coordinator) > _MAX_COORDINATOR_LEN or not all( 32 <= ord(x) <= 126 for x in msg.coordinator ): - raise wire.DataError("Invalid coordinator name.") + raise DataError("Invalid coordinator name.") - if msg.max_rounds > _MAX_ROUNDS and safety_checks.is_strict(): - raise wire.DataError("The number of rounds is unexpectedly large.") + if msg.max_rounds > _MAX_ROUNDS and safety_checks_is_strict: + raise DataError("The number of rounds is unexpectedly large.") if ( msg.max_coordinator_fee_rate > _MAX_COORDINATOR_FEE_RATE - and safety_checks.is_strict() + and safety_checks_is_strict ): - raise wire.DataError("The coordination fee rate is unexpectedly large.") + raise DataError("The coordination fee rate is unexpectedly large.") - if msg.max_fee_per_kvbyte > 10 * coin.maxfee_kb and safety_checks.is_strict(): - raise wire.DataError("The fee per vbyte is unexpectedly large.") + if msg.max_fee_per_kvbyte > 10 * coin.maxfee_kb and safety_checks_is_strict: + raise DataError("The fee per vbyte is unexpectedly large.") - if not msg.address_n: - raise wire.DataError("Empty path not allowed.") + if not address_n: + raise DataError("Empty path not allowed.") - if msg.address_n[0] != SLIP25_PURPOSE and safety_checks.is_strict(): + if address_n[0] != SLIP25_PURPOSE and safety_checks_is_strict: raise FORBIDDEN_KEY_PATH max_fee_per_vbyte = format_fee_rate( @@ -65,7 +68,7 @@ async def authorize_coinjoin( ctx, keychain, validation_path, - msg.address_n[0] == SLIP25_PURPOSE, + address_n[0] == SLIP25_PURPOSE, validate_path_against_script_type( coin, address_n=validation_path, script_type=msg.script_type ), diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index 19d4fd4a93..b70dda1012 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -2,17 +2,16 @@ from micropython import const from typing import TYPE_CHECKING from trezor import wire -from trezor.crypto import bech32, bip32, der -from trezor.crypto.curve import bip340, secp256k1 -from trezor.crypto.hashlib import sha256 +from trezor.crypto import bech32 +from trezor.crypto.curve import bip340 from trezor.enums import InputScriptType, OutputScriptType -from trezor.strings import format_amount -from trezor.utils import HashWriter, ensure if TYPE_CHECKING: from enum import IntEnum from apps.common.coininfo import CoinInfo from trezor.messages import TxInput + from trezor.utils import HashWriter + from trezor.crypto import bip32 else: IntEnum = object @@ -21,7 +20,11 @@ BITCOIN_NAMES = ("Bitcoin", "Regtest", "Testnet") class SigHashType(IntEnum): - """Enumeration type listing the supported signature hash types.""" + """Enumeration type listing the supported signature hash types. + + Class constants defined below don't need to be used in the code. + They are a list of all allowed incoming sighash types. + """ # Signature hash type with the same semantics as SIGHASH_ALL, but instead # of having to include the byte in the signature, it is implied. @@ -37,8 +40,6 @@ class SigHashType(IntEnum): # Signature hash type with the same semantics as SIGHASH_ALL. Used in some # Bitcoin-like altcoins for replay protection. - # NOTE: this seems to be unused, but when deleted, it breaks some tests - # (test_send_bch_external_presigned and test_send_btg_external_presigned) SIGHASH_ALL_FORKID = 0x41 @classmethod @@ -100,6 +101,9 @@ NONSEGWIT_INPUT_SCRIPT_TYPES = ( def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes: + from trezor.crypto import der + from trezor.crypto.curve import secp256k1 + sig = secp256k1.sign(node.private_key(), digest) sigder = der.encode_seq((sig[1:33], sig[33:65])) return sigder @@ -112,6 +116,8 @@ def bip340_sign(node: bip32.HDNode, digest: bytes) -> bytes: def ecdsa_hash_pubkey(pubkey: bytes, coin: CoinInfo) -> bytes: + from trezor.utils import ensure + if pubkey[0] == 0x04: ensure(len(pubkey) == 65) # uncompressed format elif pubkey[0] == 0x00: @@ -178,6 +184,9 @@ def input_is_external_unverified(txi: TxInput) -> bool: def tagged_hashwriter(tag: bytes) -> HashWriter: + from trezor.crypto.hashlib import sha256 + from trezor.utils import HashWriter + tag_digest = sha256(tag).digest() ctx = sha256(tag_digest) ctx.update(tag_digest) @@ -187,6 +196,8 @@ def tagged_hashwriter(tag: bytes) -> HashWriter: def format_fee_rate( fee_rate: float, coin: CoinInfo, include_shortcut: bool = False ) -> str: + from trezor.strings import format_amount + # Use format_amount to get correct thousands separator -- micropython's built-in # formatting doesn't add thousands sep to floating point numbers. # We multiply by 100 to get a fixed-point integer with two decimal places, diff --git a/core/src/apps/bitcoin/get_address.py b/core/src/apps/bitcoin/get_address.py index 7c7f9dd965..3feeeceac6 100644 --- a/core/src/apps/bitcoin/get_address.py +++ b/core/src/apps/bitcoin/get_address.py @@ -1,19 +1,9 @@ from typing import TYPE_CHECKING -from trezor.crypto import bip32 -from trezor.enums import InputScriptType -from trezor.messages import Address -from trezor.ui.layouts import show_address - -from apps.common.address_mac import get_address_mac -from apps.common.paths import address_n_to_str, validate_path - -from . import addresses -from .keychain import validate_path_against_script_type, with_keychain -from .multisig import multisig_pubkey_index +from .keychain import with_keychain if TYPE_CHECKING: - from trezor.messages import GetAddress, HDNodeType + from trezor.messages import GetAddress, HDNodeType, Address from trezor import wire from apps.common.keychain import Keychain from apps.common.coininfo import CoinInfo @@ -22,6 +12,8 @@ if TYPE_CHECKING: def _get_xpubs( coin: CoinInfo, xpub_magic: int, pubnodes: list[HDNodeType] ) -> list[str]: + from trezor.crypto import bip32 + result = [] for pubnode in pubnodes: node = bip32.HDNode( @@ -41,22 +33,37 @@ def _get_xpubs( async def get_address( ctx: wire.Context, msg: GetAddress, keychain: Keychain, coin: CoinInfo ) -> Address: + from trezor.enums import InputScriptType + from trezor.messages import Address + from trezor.ui.layouts import show_address + + from apps.common.address_mac import get_address_mac + from apps.common.paths import address_n_to_str, validate_path + + from . import addresses + from .keychain import validate_path_against_script_type + from .multisig import multisig_pubkey_index + + multisig = msg.multisig # local_cache_attribute + address_n = msg.address_n # local_cache_attribute + script_type = msg.script_type # local_cache_attribute + if msg.show_display: # skip soft-validation for silent calls await validate_path( ctx, keychain, - msg.address_n, + address_n, validate_path_against_script_type(coin, msg), ) - node = keychain.derive(msg.address_n) + node = keychain.derive(address_n) - address = addresses.get_address(msg.script_type, coin, node, msg.multisig) + address = addresses.get_address(script_type, coin, node, multisig) address_short = addresses.address_short(coin, address) address_case_sensitive = True - if coin.segwit and msg.script_type in ( + if coin.segwit and script_type in ( InputScriptType.SPENDWITNESS, InputScriptType.SPENDTAPROOT, ): @@ -66,15 +73,15 @@ async def get_address( mac: bytes | None = None multisig_xpub_magic = coin.xpub_magic - if msg.multisig: + if multisig: if coin.segwit and not msg.ignore_xpub_magic: if ( - msg.script_type == InputScriptType.SPENDWITNESS + script_type == InputScriptType.SPENDWITNESS and coin.xpub_magic_multisig_segwit_native is not None ): multisig_xpub_magic = coin.xpub_magic_multisig_segwit_native elif ( - msg.script_type == InputScriptType.SPENDP2SHWITNESS + script_type == InputScriptType.SPENDP2SHWITNESS and coin.xpub_magic_multisig_segwit_p2sh is not None ): multisig_xpub_magic = coin.xpub_magic_multisig_segwit_p2sh @@ -82,33 +89,33 @@ async def get_address( # Attach a MAC for single-sig addresses, but only if the path is standard # or if the user explicitly confirms a non-standard path. if msg.show_display or ( - keychain.is_in_keychain(msg.address_n) + keychain.is_in_keychain(address_n) and validate_path_against_script_type(coin, msg) ): mac = get_address_mac(address, coin.slip44, keychain) if msg.show_display: - if msg.multisig: - if msg.multisig.nodes: - pubnodes = msg.multisig.nodes + if multisig: + if multisig.nodes: + pubnodes = multisig.nodes else: - pubnodes = [hd.node for hd in msg.multisig.pubkeys] - multisig_index = multisig_pubkey_index(msg.multisig, node.public_key()) + pubnodes = [hd.node for hd in multisig.pubkeys] + multisig_index = multisig_pubkey_index(multisig, node.public_key()) - title = f"Multisig {msg.multisig.m} of {len(pubnodes)}" + title = f"Multisig {multisig.m} of {len(pubnodes)}" await show_address( ctx, - address=address_short, + address_short, case_sensitive=address_case_sensitive, title=title, multisig_index=multisig_index, xpubs=_get_xpubs(coin, multisig_xpub_magic, pubnodes), ) else: - title = address_n_to_str(msg.address_n) + title = address_n_to_str(address_n) await show_address( ctx, - address=address_short, + address_short, address_qr=address, case_sensitive=address_case_sensitive, title=title, diff --git a/core/src/apps/bitcoin/get_ownership_id.py b/core/src/apps/bitcoin/get_ownership_id.py index 4d34c27626..fde2d71f55 100644 --- a/core/src/apps/bitcoin/get_ownership_id.py +++ b/core/src/apps/bitcoin/get_ownership_id.py @@ -1,25 +1,30 @@ from typing import TYPE_CHECKING -from trezor import wire -from trezor.enums import InputScriptType -from trezor.messages import OwnershipId - -from apps.common.paths import validate_path - -from . import addresses, common, scripts -from .keychain import validate_path_against_script_type, with_keychain -from .ownership import get_identifier +from .keychain import with_keychain if TYPE_CHECKING: - from trezor.messages import GetOwnershipId + from trezor.messages import GetOwnershipId, OwnershipId + from trezor.wire import Context from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain @with_keychain async def get_ownership_id( - ctx: wire.Context, msg: GetOwnershipId, keychain: Keychain, coin: CoinInfo + ctx: Context, msg: GetOwnershipId, keychain: Keychain, coin: CoinInfo ) -> OwnershipId: + from trezor.wire import DataError + from trezor.enums import InputScriptType + from trezor.messages import OwnershipId + + from apps.common.paths import validate_path + + from . import addresses, common, scripts + from .keychain import validate_path_against_script_type + from .ownership import get_identifier + + script_type = msg.script_type # local_cache_attribute + await validate_path( ctx, keychain, @@ -27,17 +32,17 @@ async def get_ownership_id( validate_path_against_script_type(coin, msg), ) - if msg.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES: - raise wire.DataError("Invalid script type") + if script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES: + raise DataError("Invalid script type") - if msg.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit: - raise wire.DataError("Segwit not enabled on this coin") + if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit: + raise DataError("Segwit not enabled on this coin") - if msg.script_type == InputScriptType.SPENDTAPROOT and not coin.taproot: - raise wire.DataError("Taproot not enabled on this coin") + if script_type == InputScriptType.SPENDTAPROOT and not coin.taproot: + raise DataError("Taproot not enabled on this coin") node = keychain.derive(msg.address_n) - address = addresses.get_address(msg.script_type, coin, node, msg.multisig) + address = addresses.get_address(script_type, coin, node, msg.multisig) script_pubkey = scripts.output_derive_script(address, coin) ownership_id = get_identifier(script_pubkey, keychain) diff --git a/core/src/apps/bitcoin/get_ownership_proof.py b/core/src/apps/bitcoin/get_ownership_proof.py index 20edadfd46..7845b611d0 100644 --- a/core/src/apps/bitcoin/get_ownership_proof.py +++ b/core/src/apps/bitcoin/get_ownership_proof.py @@ -1,18 +1,10 @@ from typing import TYPE_CHECKING -from trezor import ui, wire -from trezor.enums import InputScriptType -from trezor.messages import OwnershipProof -from trezor.ui.layouts import confirm_action, confirm_blob - -from apps.common.paths import validate_path - -from . import addresses, common, scripts -from .keychain import validate_path_against_script_type, with_keychain -from .ownership import generate_proof, get_identifier +from .keychain import with_keychain if TYPE_CHECKING: - from trezor.messages import GetOwnershipProof + from trezor.messages import GetOwnershipProof, OwnershipProof + from trezor.wire import Context from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain from .authorization import CoinJoinAuthorization @@ -20,15 +12,30 @@ if TYPE_CHECKING: @with_keychain async def get_ownership_proof( - ctx: wire.Context, + ctx: Context, msg: GetOwnershipProof, keychain: Keychain, coin: CoinInfo, authorization: CoinJoinAuthorization | None = None, ) -> OwnershipProof: + from trezor import ui + from trezor.wire import DataError, ProcessError + from trezor.enums import InputScriptType + from trezor.messages import OwnershipProof + from trezor.ui.layouts import confirm_action, confirm_blob + + from apps.common.paths import validate_path + + from . import addresses, common, scripts + from .keychain import validate_path_against_script_type + from .ownership import generate_proof, get_identifier + + script_type = msg.script_type # local_cache_attribute + ownership_ids = msg.ownership_ids # local_cache_attribute + if authorization: if not authorization.check_get_ownership_proof(msg): - raise wire.ProcessError("Unauthorized operation") + raise ProcessError("Unauthorized operation") else: await validate_path( ctx, @@ -37,57 +44,57 @@ async def get_ownership_proof( validate_path_against_script_type(coin, msg), ) - if msg.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES: - raise wire.DataError("Invalid script type") + if script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES: + raise DataError("Invalid script type") - if msg.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit: - raise wire.DataError("Segwit not enabled on this coin") + if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit: + raise DataError("Segwit not enabled on this coin") - if msg.script_type == InputScriptType.SPENDTAPROOT and not coin.taproot: - raise wire.DataError("Taproot not enabled on this coin") + if script_type == InputScriptType.SPENDTAPROOT and not coin.taproot: + raise DataError("Taproot not enabled on this coin") node = keychain.derive(msg.address_n) - address = addresses.get_address(msg.script_type, coin, node, msg.multisig) + address = addresses.get_address(script_type, coin, node, msg.multisig) script_pubkey = scripts.output_derive_script(address, coin) ownership_id = get_identifier(script_pubkey, keychain) # If the scriptPubKey is multisig, then the caller has to provide # ownership IDs, otherwise providing an ID is optional. if msg.multisig: - if ownership_id not in msg.ownership_ids: - raise wire.DataError("Missing ownership identifier") - elif msg.ownership_ids: - if msg.ownership_ids != [ownership_id]: - raise wire.DataError("Invalid ownership identifier") + if ownership_id not in ownership_ids: + raise DataError("Missing ownership identifier") + elif ownership_ids: + if ownership_ids != [ownership_id]: + raise DataError("Invalid ownership identifier") else: - msg.ownership_ids = [ownership_id] + ownership_ids = [ownership_id] # In order to set the "user confirmation" bit in the proof, the user must actually confirm. if msg.user_confirmation and not authorization: await confirm_action( ctx, "confirm_ownership_proof", - title="Proof of ownership", + "Proof of ownership", description="Do you want to create a proof of ownership?", ) if msg.commitment_data: await confirm_blob( ctx, "confirm_ownership_proof", - title="Proof of ownership", - description="Commitment data:", - data=msg.commitment_data, + "Proof of ownership", + msg.commitment_data, + "Commitment data:", icon=ui.ICON_CONFIG, icon_color=ui.ORANGE_ICON, ) ownership_proof, signature = generate_proof( node, - msg.script_type, + script_type, msg.multisig, coin, msg.user_confirmation, - msg.ownership_ids, + ownership_ids, script_pubkey, msg.commitment_data, ) diff --git a/core/src/apps/bitcoin/get_public_key.py b/core/src/apps/bitcoin/get_public_key.py index bb7acbf892..000e7fc78d 100644 --- a/core/src/apps/bitcoin/get_public_key.py +++ b/core/src/apps/bitcoin/get_public_key.py @@ -1,37 +1,41 @@ from typing import TYPE_CHECKING -from trezor import wire -from trezor.enums import InputScriptType -from trezor.messages import HDNodeType, PublicKey, UnlockPath - -from apps.common import coininfo, paths -from apps.common.keychain import FORBIDDEN_KEY_PATH, get_keychain - if TYPE_CHECKING: - from trezor.messages import GetPublicKey + from trezor.messages import GetPublicKey, PublicKey from trezor.protobuf import MessageType + from trezor.wire import Context async def get_public_key( - ctx: wire.Context, msg: GetPublicKey, auth_msg: MessageType | None = None + ctx: Context, msg: GetPublicKey, auth_msg: MessageType | None = None ) -> PublicKey: + from trezor import wire + from trezor.enums import InputScriptType + from trezor.messages import HDNodeType, PublicKey, UnlockPath + + from apps.common import coininfo, paths + from apps.common.keychain import FORBIDDEN_KEY_PATH, get_keychain + coin_name = msg.coin_name or "Bitcoin" script_type = msg.script_type or InputScriptType.SPENDADDRESS coin = coininfo.by_name(coin_name) curve_name = msg.ecdsa_curve_name or coin.curve_name + address_n = msg.address_n # local_cache_attribute + ignore_xpub_magic = msg.ignore_xpub_magic # local_cache_attribute + xpub_magic = coin.xpub_magic # local_cache_attribute - if msg.address_n and msg.address_n[0] == paths.SLIP25_PURPOSE: + if address_n and address_n[0] == paths.SLIP25_PURPOSE: # UnlockPath is required to access SLIP25 paths. if not UnlockPath.is_type_of(auth_msg): raise FORBIDDEN_KEY_PATH # Verify that the desired path lies in the unlocked subtree. - if auth_msg.address_n != msg.address_n[: len(auth_msg.address_n)]: + if auth_msg.address_n != address_n[: len(auth_msg.address_n)]: raise FORBIDDEN_KEY_PATH keychain = await get_keychain(ctx, curve_name, [paths.AlwaysMatchingSchema]) - node = keychain.derive(msg.address_n) + node = keychain.derive(address_n) if ( script_type @@ -40,26 +44,26 @@ async def get_public_key( InputScriptType.SPENDMULTISIG, InputScriptType.SPENDTAPROOT, ) - and coin.xpub_magic is not None + and xpub_magic is not None ): - node_xpub = node.serialize_public(coin.xpub_magic) + node_xpub = node.serialize_public(xpub_magic) elif ( coin.segwit and script_type == InputScriptType.SPENDP2SHWITNESS - and (msg.ignore_xpub_magic or coin.xpub_magic_segwit_p2sh is not None) + and (ignore_xpub_magic or coin.xpub_magic_segwit_p2sh is not None) ): assert coin.xpub_magic_segwit_p2sh is not None node_xpub = node.serialize_public( - coin.xpub_magic if msg.ignore_xpub_magic else coin.xpub_magic_segwit_p2sh + xpub_magic if ignore_xpub_magic else coin.xpub_magic_segwit_p2sh ) elif ( coin.segwit and script_type == InputScriptType.SPENDWITNESS - and (msg.ignore_xpub_magic or coin.xpub_magic_segwit_native is not None) + and (ignore_xpub_magic or coin.xpub_magic_segwit_native is not None) ): assert coin.xpub_magic_segwit_native is not None node_xpub = node.serialize_public( - coin.xpub_magic if msg.ignore_xpub_magic else coin.xpub_magic_segwit_native + xpub_magic if ignore_xpub_magic else coin.xpub_magic_segwit_native ) else: raise wire.DataError("Invalid combination of coin and script_type") diff --git a/core/src/apps/bitcoin/keychain.py b/core/src/apps/bitcoin/keychain.py index 3bf2d1180e..e00bcd1407 100644 --- a/core/src/apps/bitcoin/keychain.py +++ b/core/src/apps/bitcoin/keychain.py @@ -1,13 +1,8 @@ -import gc from micropython import const from typing import TYPE_CHECKING -from trezor import wire -from trezor.enums import InputScriptType -from trezor.messages import AuthorizeCoinJoin, GetOwnershipProof, SignTx, UnlockPath +from trezor.messages import AuthorizeCoinJoin -from apps.common import coininfo -from apps.common.keychain import get_keychain from apps.common.paths import PATTERN_BIP44, PathSchema from . import authorization @@ -18,17 +13,22 @@ if TYPE_CHECKING: from typing_extensions import Protocol from trezor.protobuf import MessageType + from trezor.wire import Context + from trezor.enums import InputScriptType from trezor.messages import ( GetAddress, GetOwnershipId, GetPublicKey, SignMessage, VerifyMessage, + GetOwnershipProof, + SignTx, ) from apps.common.keychain import Keychain, MsgOut, Handler from apps.common.paths import Bip32Path + from apps.common import coininfo BitcoinMessage = ( AuthorizeCoinJoin @@ -99,7 +99,11 @@ def validate_path_against_script_type( script_type: InputScriptType | None = None, multisig: bool = False, ) -> bool: + from trezor.enums import InputScriptType + patterns = [] + append = patterns.append # local_cache_attribute + slip44 = coin.slip44 # local_cache_attribute if msg is not None: assert address_n is None and script_type is None @@ -111,58 +115,60 @@ def validate_path_against_script_type( assert address_n is not None and script_type is not None if script_type == InputScriptType.SPENDADDRESS and not multisig: - patterns.append(PATTERN_BIP44) - if coin.slip44 == _SLIP44_BITCOIN: - patterns.append(PATTERN_GREENADDRESS_A) - patterns.append(PATTERN_GREENADDRESS_B) + append(PATTERN_BIP44) + if slip44 == _SLIP44_BITCOIN: + append(PATTERN_GREENADDRESS_A) + append(PATTERN_GREENADDRESS_B) elif ( script_type in (InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG) and multisig ): - patterns.append(PATTERN_BIP48_RAW) - if coin.slip44 == _SLIP44_BITCOIN or ( - coin.fork_id is not None and coin.slip44 != _SLIP44_TESTNET + append(PATTERN_BIP48_RAW) + if slip44 == _SLIP44_BITCOIN or ( + coin.fork_id is not None and slip44 != _SLIP44_TESTNET ): - patterns.append(PATTERN_BIP45) - if coin.slip44 == _SLIP44_BITCOIN: - patterns.append(PATTERN_GREENADDRESS_A) - patterns.append(PATTERN_GREENADDRESS_B) + append(PATTERN_BIP45) + if slip44 == _SLIP44_BITCOIN: + append(PATTERN_GREENADDRESS_A) + append(PATTERN_GREENADDRESS_B) if coin.coin_name in BITCOIN_NAMES: - patterns.append(PATTERN_UNCHAINED_HARDENED) - patterns.append(PATTERN_UNCHAINED_UNHARDENED) - patterns.append(PATTERN_UNCHAINED_DEPRECATED) + append(PATTERN_UNCHAINED_HARDENED) + append(PATTERN_UNCHAINED_UNHARDENED) + append(PATTERN_UNCHAINED_DEPRECATED) elif coin.segwit and script_type == InputScriptType.SPENDP2SHWITNESS: - patterns.append(PATTERN_BIP49) + append(PATTERN_BIP49) if multisig: - patterns.append(PATTERN_BIP48_P2SHSEGWIT) - if coin.slip44 == _SLIP44_BITCOIN: - patterns.append(PATTERN_GREENADDRESS_A) - patterns.append(PATTERN_GREENADDRESS_B) + append(PATTERN_BIP48_P2SHSEGWIT) + if slip44 == _SLIP44_BITCOIN: + append(PATTERN_GREENADDRESS_A) + append(PATTERN_GREENADDRESS_B) if coin.coin_name in BITCOIN_NAMES: - patterns.append(PATTERN_CASA) + append(PATTERN_CASA) elif coin.segwit and script_type == InputScriptType.SPENDWITNESS: - patterns.append(PATTERN_BIP84) + append(PATTERN_BIP84) if multisig: - patterns.append(PATTERN_BIP48_SEGWIT) - if coin.slip44 == _SLIP44_BITCOIN: - patterns.append(PATTERN_GREENADDRESS_A) - patterns.append(PATTERN_GREENADDRESS_B) + append(PATTERN_BIP48_SEGWIT) + if slip44 == _SLIP44_BITCOIN: + append(PATTERN_GREENADDRESS_A) + append(PATTERN_GREENADDRESS_B) elif coin.taproot and script_type == InputScriptType.SPENDTAPROOT: - patterns.append(PATTERN_BIP86) - patterns.append(PATTERN_SLIP25_TAPROOT) + append(PATTERN_BIP86) + append(PATTERN_SLIP25_TAPROOT) return any( PathSchema.parse(pattern, coin.slip44).match(address_n) for pattern in patterns ) -def get_schemas_for_coin( +def _get_schemas_for_coin( coin: coininfo.CoinInfo, unlock_schemas: Iterable[PathSchema] = () ) -> Iterable[PathSchema]: + import gc + # basic patterns patterns = [ PATTERN_BIP44, @@ -237,7 +243,10 @@ def get_schemas_from_patterns( return schemas -def get_coin_by_name(coin_name: str | None) -> coininfo.CoinInfo: +def _get_coin_by_name(coin_name: str | None) -> coininfo.CoinInfo: + from apps.common import coininfo + from trezor import wire + if coin_name is None: coin_name = "Bitcoin" @@ -247,12 +256,14 @@ def get_coin_by_name(coin_name: str | None) -> coininfo.CoinInfo: raise wire.DataError("Unsupported coin type") -async def get_keychain_for_coin( - ctx: wire.Context, +async def _get_keychain_for_coin( + ctx: Context, coin: coininfo.CoinInfo, unlock_schemas: Iterable[PathSchema] = (), ) -> Keychain: - schemas = get_schemas_for_coin(coin, unlock_schemas) + from apps.common.keychain import get_keychain + + schemas = _get_schemas_for_coin(coin, unlock_schemas) slip21_namespaces = [[b"SLIP-0019"], [b"SLIP-0024"]] keychain = await get_keychain(ctx, coin.curve_name, schemas, slip21_namespaces) return keychain @@ -265,6 +276,7 @@ def _get_unlock_schemas( Provides additional keychain schemas that are unlocked by the particular combination of `msg` and `auth_msg`. """ + from trezor.messages import GetOwnershipProof, SignTx, UnlockPath if AuthorizeCoinJoin.is_type_of(msg): # When processing the AuthorizeCoinJoin message, validate_path() always @@ -298,13 +310,13 @@ def _get_unlock_schemas( def with_keychain(func: HandlerWithCoinInfo[MsgOut]) -> Handler[MsgIn, MsgOut]: async def wrapper( - ctx: wire.Context, + ctx: Context, msg: MsgIn, auth_msg: MessageType | None = None, ) -> MsgOut: - coin = get_coin_by_name(msg.coin_name) + coin = _get_coin_by_name(msg.coin_name) unlock_schemas = _get_unlock_schemas(msg, auth_msg, coin) - keychain = await get_keychain_for_coin(ctx, coin, unlock_schemas) + keychain = await _get_keychain_for_coin(ctx, coin, unlock_schemas) if AuthorizeCoinJoin.is_type_of(auth_msg): auth_obj = authorization.from_cached_message(auth_msg) return await func(ctx, msg, keychain, coin, auth_obj) diff --git a/core/src/apps/bitcoin/multisig.py b/core/src/apps/bitcoin/multisig.py index 17eab0be70..2bd0f5b30d 100644 --- a/core/src/apps/bitcoin/multisig.py +++ b/core/src/apps/bitcoin/multisig.py @@ -1,19 +1,17 @@ from typing import TYPE_CHECKING -from trezor import wire -from trezor.crypto import bip32 -from trezor.crypto.hashlib import sha256 -from trezor.utils import HashWriter - -from apps.common import paths - -from .writers import write_bytes_fixed, write_uint32 +from trezor.wire import DataError if TYPE_CHECKING: from trezor.messages import HDNodeType, MultisigRedeemScriptType + from apps.common import paths def multisig_fingerprint(multisig: MultisigRedeemScriptType) -> bytes: + from trezor.crypto.hashlib import sha256 + from trezor.utils import HashWriter + from .writers import write_bytes_fixed, write_uint32 + if multisig.nodes: pubnodes = multisig.nodes else: @@ -22,11 +20,11 @@ def multisig_fingerprint(multisig: MultisigRedeemScriptType) -> bytes: n = len(pubnodes) if n < 1 or n > 15 or m < 1 or m > 15: - raise wire.DataError("Invalid multisig parameters") + raise DataError("Invalid multisig parameters") for d in pubnodes: if len(d.public_key) != 33 or len(d.chain_code) != 32: - raise wire.DataError("Invalid multisig parameters") + raise DataError("Invalid multisig parameters") # casting to bytes(), sorting on bytearray() is not supported in MicroPython pubnodes = sorted(pubnodes, key=lambda n: bytes(n.public_key)) @@ -45,11 +43,13 @@ def multisig_fingerprint(multisig: MultisigRedeemScriptType) -> bytes: def validate_multisig(multisig: MultisigRedeemScriptType) -> None: + from apps.common import paths + if any(paths.is_hardened(n) for n in multisig.address_n): - raise wire.DataError("Cannot perform hardened derivation from XPUB") + raise DataError("Cannot perform hardened derivation from XPUB") for hd in multisig.pubkeys: if any(paths.is_hardened(n) for n in hd.address_n): - raise wire.DataError("Cannot perform hardened derivation from XPUB") + raise DataError("Cannot perform hardened derivation from XPUB") def multisig_pubkey_index(multisig: MultisigRedeemScriptType, pubkey: bytes) -> int: @@ -62,10 +62,12 @@ def multisig_pubkey_index(multisig: MultisigRedeemScriptType, pubkey: bytes) -> for i, hd in enumerate(multisig.pubkeys): if multisig_get_pubkey(hd.node, hd.address_n) == pubkey: return i - raise wire.DataError("Pubkey not found in multisig script") + raise DataError("Pubkey not found in multisig script") def multisig_get_pubkey(n: HDNodeType, p: paths.Bip32Path) -> bytes: + from trezor.crypto import bip32 + node = bip32.HDNode( depth=n.depth, fingerprint=n.fingerprint, diff --git a/core/src/apps/bitcoin/ownership.py b/core/src/apps/bitcoin/ownership.py index 0c414f98fd..da72993ddc 100644 --- a/core/src/apps/bitcoin/ownership.py +++ b/core/src/apps/bitcoin/ownership.py @@ -1,28 +1,22 @@ from micropython import const from typing import TYPE_CHECKING -from trezor import utils, wire -from trezor.crypto import bip32, hmac +from trezor import utils from trezor.crypto.hashlib import sha256 -from trezor.enums import InputScriptType from trezor.utils import HashWriter +from trezor.wire import DataError -from apps.bitcoin.writers import ( - write_bytes_fixed, - write_bytes_prefixed, - write_compact_size, - write_uint8, -) -from apps.common.keychain import Keychain +from apps.bitcoin.writers import write_bytes_prefixed from apps.common.readers import read_compact_size -from . import common -from .scripts import read_bip322_signature_proof, write_bip322_signature_proof -from .verification import SignatureVerifier +from .scripts import read_bip322_signature_proof if TYPE_CHECKING: from trezor.messages import MultisigRedeemScriptType + from trezor.enums import InputScriptType from apps.common.coininfo import CoinInfo + from trezor.crypto import bip32 + from apps.common.keychain import Keychain # This module implements the SLIP-0019 proof of ownership format, see # https://github.com/satoshilabs/slips/blob/master/slip-0019.md. @@ -44,6 +38,15 @@ def generate_proof( script_pubkey: bytes, commitment_data: bytes, ) -> tuple[bytes, bytes]: + from trezor.enums import InputScriptType + from apps.bitcoin.writers import ( + write_bytes_fixed, + write_compact_size, + write_uint8, + ) + from .scripts import write_bip322_signature_proof + from . import common + flags = 0 if user_confirmed: flags |= _FLAG_USER_CONFIRMED @@ -69,7 +72,7 @@ def generate_proof( elif script_type == InputScriptType.SPENDTAPROOT: signature = common.bip340_sign(node, sighash.get_digest()) else: - raise wire.DataError("Unsupported script type.") + raise DataError("Unsupported script type.") public_key = node.public_key() write_bip322_signature_proof( proof, script_type, multisig, coin, public_key, signature @@ -85,14 +88,16 @@ def verify_nonownership( keychain: Keychain, coin: CoinInfo, ) -> bool: + from .verification import SignatureVerifier + try: r = utils.BufferReader(proof) if r.read_memoryview(4) != _VERSION_MAGIC: - raise wire.DataError("Unknown format of proof of ownership") + raise DataError("Unknown format of proof of ownership") flags = r.get() if flags & 0b1111_1110: - raise wire.DataError("Unknown flags in proof of ownership") + raise DataError("Unknown flags in proof of ownership") # Determine whether our ownership ID appears in the proof. id_count = read_compact_size(r) @@ -119,7 +124,7 @@ def verify_nonownership( verifier = SignatureVerifier(script_pubkey, script_sig, witness, coin) verifier.verify(sighash.get_digest()) except (ValueError, EOFError): - raise wire.DataError("Invalid proof of ownership") + raise DataError("Invalid proof of ownership") return not_owned @@ -128,11 +133,11 @@ def read_scriptsig_witness(ownership_proof: bytes) -> tuple[memoryview, memoryvi try: r = utils.BufferReader(ownership_proof) if r.read_memoryview(4) != _VERSION_MAGIC: - raise wire.DataError("Unknown format of proof of ownership") + raise DataError("Unknown format of proof of ownership") flags = r.get() if flags & 0b1111_1110: - raise wire.DataError("Unknown flags in proof of ownership") + raise DataError("Unknown flags in proof of ownership") # Skip ownership IDs. id_count = read_compact_size(r) @@ -141,10 +146,12 @@ def read_scriptsig_witness(ownership_proof: bytes) -> tuple[memoryview, memoryvi return read_bip322_signature_proof(r) except (ValueError, EOFError): - raise wire.DataError("Invalid proof of ownership") + raise DataError("Invalid proof of ownership") def get_identifier(script_pubkey: bytes, keychain: Keychain) -> bytes: + from trezor.crypto import hmac + # k = Key(m/"SLIP-0019"/"Ownership identification key") node = keychain.derive_slip21(_OWNERSHIP_ID_KEY_PATH) diff --git a/core/src/apps/bitcoin/readers.py b/core/src/apps/bitcoin/readers.py index aeb1d7e4e6..ac67c2d371 100644 --- a/core/src/apps/bitcoin/readers.py +++ b/core/src/apps/bitcoin/readers.py @@ -1,27 +1,32 @@ -from trezor.utils import BufferReader +from typing import TYPE_CHECKING -from apps.common.readers import read_compact_size +if TYPE_CHECKING: + from trezor.utils import BufferReader def read_memoryview_prefixed(r: BufferReader) -> memoryview: + from apps.common.readers import read_compact_size + n = read_compact_size(r) return r.read_memoryview(n) def read_op_push(r: BufferReader) -> int: - prefix = r.get() + get = r.get # local_cache_attribute + + prefix = get() if prefix < 0x4C: n = prefix elif prefix == 0x4C: - n = r.get() + n = get() elif prefix == 0x4D: - n = r.get() - n += r.get() << 8 + n = get() + n += get() << 8 elif prefix == 0x4E: - n = r.get() - n += r.get() << 8 - n += r.get() << 16 - n += r.get() << 24 + n = get() + n += get() << 8 + n += get() << 16 + n += get() << 24 else: raise ValueError return n diff --git a/core/src/apps/bitcoin/scripts.py b/core/src/apps/bitcoin/scripts.py index a314494dcc..c9193ef077 100644 --- a/core/src/apps/bitcoin/scripts.py +++ b/core/src/apps/bitcoin/scripts.py @@ -1,24 +1,18 @@ from typing import TYPE_CHECKING -from trezor import utils, wire -from trezor.crypto import base58, cashaddr -from trezor.crypto.hashlib import sha256 +from trezor import utils from trezor.enums import InputScriptType +from trezor.utils import BufferReader, empty_bytearray +from trezor.wire import DataError -from apps.common import address_type from apps.common.readers import read_compact_size from apps.common.writers import write_compact_size from . import common from .common import SigHashType -from .multisig import ( - multisig_get_pubkey_count, - multisig_get_pubkeys, - multisig_pubkey_index, -) +from .multisig import multisig_get_pubkeys, multisig_pubkey_index from .readers import read_memoryview_prefixed, read_op_push from .writers import ( - op_push_length, write_bytes_fixed, write_bytes_prefixed, write_bytes_unchecked, @@ -44,10 +38,15 @@ def write_input_script_prefixed( pubkey: bytes, signature: bytes, ) -> None: - if script_type == InputScriptType.SPENDADDRESS: + from trezor.crypto.hashlib import sha256 + from trezor import wire + + IST = InputScriptType # local_cache_global + + if script_type == IST.SPENDADDRESS: # p2pkh or p2sh write_input_script_p2pkh_or_p2sh_prefixed(w, pubkey, signature, sighash_type) - elif script_type == InputScriptType.SPENDP2SHWITNESS: + elif script_type == IST.SPENDP2SHWITNESS: # p2wpkh or p2wsh using p2sh if multisig is not None: @@ -63,15 +62,15 @@ def write_input_script_prefixed( write_input_script_p2wpkh_in_p2sh( w, common.ecdsa_hash_pubkey(pubkey, coin), prefixed=True ) - elif script_type in (InputScriptType.SPENDWITNESS, InputScriptType.SPENDTAPROOT): + elif script_type in (IST.SPENDWITNESS, IST.SPENDTAPROOT): # native p2wpkh or p2wsh or p2tr - script_sig = input_script_native_segwit() + script_sig = _input_script_native_segwit() write_bytes_prefixed(w, script_sig) - elif script_type == InputScriptType.SPENDMULTISIG: + elif script_type == IST.SPENDMULTISIG: # p2sh multisig - assert multisig is not None # checked in sanitize_tx_input + assert multisig is not None # checked in _sanitize_tx_input signature_index = multisig_pubkey_index(multisig, pubkey) - write_input_script_multisig_prefixed( + _write_input_script_multisig_prefixed( w, multisig, signature, signature_index, sighash_type, coin ) else: @@ -79,6 +78,9 @@ def write_input_script_prefixed( def output_derive_script(address: str, coin: CoinInfo) -> bytes: + from trezor.crypto import base58, cashaddr + from apps.common import address_type + if coin.bech32_prefix and address.startswith(coin.bech32_prefix): # p2wpkh or p2wsh or p2tr witver, witprog = common.decode_bech32_address(coin.bech32_prefix, address) @@ -96,13 +98,13 @@ def output_derive_script(address: str, coin: CoinInfo) -> bytes: elif version == cashaddr.ADDRESS_TYPE_P2SH: version = coin.address_type_p2sh else: - raise wire.DataError("Unknown cashaddr address type") + raise DataError("Unknown cashaddr address type") raw_address = bytes([version]) + data else: try: raw_address = base58.decode_check(address, coin.b58_hash) except ValueError: - raise wire.DataError("Invalid address") + raise DataError("Invalid address") if address_type.check(coin.address_type, raw_address): # p2pkh @@ -115,7 +117,7 @@ def output_derive_script(address: str, coin: CoinInfo) -> bytes: script = output_script_p2sh(scripthash) return script - raise wire.DataError("Invalid address type") + raise DataError("Invalid address type") # see https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification @@ -144,7 +146,7 @@ def write_bip143_script_code_prefixed( w, common.ecdsa_hash_pubkey(public_keys[0], coin), prefixed=True ) else: - raise wire.DataError("Unknown input script type for bip143 script code") + raise DataError("Unknown input script type for bip143 script code") # P2PKH, P2SH @@ -164,7 +166,7 @@ def parse_input_script_p2pkh( script_sig: bytes, ) -> tuple[memoryview, memoryview, SigHashType]: try: - r = utils.BufferReader(script_sig) + r = BufferReader(script_sig) n = read_op_push(r) signature = r.read_memoryview(n - 1) sighash_type = SigHashType.from_int(r.get()) @@ -174,7 +176,7 @@ def parse_input_script_p2pkh( if len(pubkey) != n: raise ValueError except (ValueError, EOFError): - raise wire.DataError("Invalid scriptSig.") + raise DataError("Invalid scriptSig.") return pubkey, signature, sighash_type @@ -182,18 +184,20 @@ def parse_input_script_p2pkh( def write_output_script_p2pkh( w: Writer, pubkeyhash: bytes, prefixed: bool = False ) -> None: + append = w.append # local_cache_attribute + if prefixed: write_compact_size(w, 25) - w.append(0x76) # OP_DUP - w.append(0xA9) # OP_HASH160 - w.append(0x14) # OP_DATA_20 + append(0x76) # OP_DUP + append(0xA9) # OP_HASH160 + append(0x14) # OP_DATA_20 write_bytes_fixed(w, pubkeyhash, 20) - w.append(0x88) # OP_EQUALVERIFY - w.append(0xAC) # OP_CHECKSIG + append(0x88) # OP_EQUALVERIFY + append(0xAC) # OP_CHECKSIG def output_script_p2pkh(pubkeyhash: bytes) -> bytearray: - s = utils.empty_bytearray(25) + s = empty_bytearray(25) write_output_script_p2pkh(s, pubkeyhash) return s @@ -225,7 +229,7 @@ def output_script_p2sh(scripthash: bytes) -> bytearray: # https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#script-validation-rules -def input_script_native_segwit() -> bytearray: +def _input_script_native_segwit() -> bytearray: # Completely replaced by the witness and therefore empty. return bytearray(0) @@ -238,7 +242,7 @@ def output_script_native_segwit(witver: int, witprog: bytes) -> bytearray: length = len(witprog) utils.ensure((length == 20 and witver == 0) or length == 32) - w = utils.empty_bytearray(2 + length) + w = empty_bytearray(2 + length) w.append(witver + 0x50 if witver else 0) # witness version byte (OP_witver) w.append(length) # witness program length is 20 (P2WPKH) or 32 (P2WSH, P2TR) bytes write_bytes_fixed(w, witprog, length) @@ -248,7 +252,7 @@ def output_script_native_segwit(witver: int, witprog: bytes) -> bytearray: def parse_output_script_p2tr(script_pubkey: bytes) -> memoryview: # 51 20 <32-byte-taproot-output-key> try: - r = utils.BufferReader(script_pubkey) + r = BufferReader(script_pubkey) if r.get() != common.OP_1: # P2TR should be SegWit version 1 @@ -262,7 +266,7 @@ def parse_output_script_p2tr(script_pubkey: bytes) -> memoryview: if r.remaining_count(): raise ValueError except (ValueError, EOFError): - raise wire.DataError("Invalid scriptPubKey.") + raise DataError("Invalid scriptPubKey.") return pubkey @@ -325,7 +329,7 @@ def write_witness_p2wpkh( def parse_witness_p2wpkh(witness: bytes) -> tuple[memoryview, memoryview, SigHashType]: try: - r = utils.BufferReader(witness) + r = BufferReader(witness) if r.get() != 2: # num of stack items, in P2WPKH it's always 2 @@ -339,7 +343,7 @@ def parse_witness_p2wpkh(witness: bytes) -> tuple[memoryview, memoryview, SigHas if r.remaining_count(): raise ValueError except (ValueError, EOFError): - raise wire.DataError("Invalid witness.") + raise DataError("Invalid witness.") return pubkey, signature, sighash_type @@ -351,6 +355,8 @@ def write_witness_multisig( signature_index: int, sighash_type: SigHashType, ) -> None: + from .multisig import multisig_get_pubkey_count + # get other signatures, stretch with empty bytes to the number of the pubkeys signatures = multisig.signatures + [b""] * ( multisig_get_pubkey_count(multisig) - len(multisig.signatures) @@ -358,7 +364,7 @@ def write_witness_multisig( # fill in our signature if signatures[signature_index]: - raise wire.DataError("Invalid multisig parameters") + raise DataError("Invalid multisig parameters") signatures[signature_index] = signature # witness program + signatures + redeem script @@ -383,7 +389,7 @@ def parse_witness_multisig( witness: bytes, ) -> tuple[memoryview, list[tuple[memoryview, SigHashType]]]: try: - r = utils.BufferReader(witness) + r = BufferReader(witness) # Get number of witness stack items. item_count = read_compact_size(r) @@ -403,7 +409,7 @@ def parse_witness_multisig( if r.remaining_count(): raise ValueError except (ValueError, EOFError): - raise wire.DataError("Invalid witness.") + raise DataError("Invalid witness.") return script, signatures @@ -420,7 +426,7 @@ def write_witness_p2tr(w: Writer, signature: bytes, sighash_type: SigHashType) - def parse_witness_p2tr(witness: bytes) -> tuple[memoryview, SigHashType]: try: - r = utils.BufferReader(witness) + r = BufferReader(witness) if r.get() != 1: # Number of stack items. # Only Taproot key path spending without annex is supported. @@ -439,7 +445,7 @@ def parse_witness_p2tr(witness: bytes) -> tuple[memoryview, SigHashType]: if r.remaining_count(): raise ValueError except (ValueError, EOFError): - raise wire.DataError("Invalid witness.") + raise DataError("Invalid witness.") return signature, sighash_type @@ -450,7 +456,7 @@ def parse_witness_p2tr(witness: bytes) -> tuple[memoryview, SigHashType]: # Used either as P2SH, P2WSH, or P2WSH nested in P2SH. -def write_input_script_multisig_prefixed( +def _write_input_script_multisig_prefixed( w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, @@ -458,9 +464,11 @@ def write_input_script_multisig_prefixed( sighash_type: SigHashType, coin: CoinInfo, ) -> None: + from .writers import op_push_length + signatures = multisig.signatures # other signatures if len(signatures[signature_index]) > 0: - raise wire.DataError("Invalid multisig parameters") + raise DataError("Invalid multisig parameters") signatures[signature_index] = signature # our signature # length of the redeem script @@ -493,7 +501,7 @@ def parse_input_script_multisig( script_sig: bytes, ) -> tuple[memoryview, list[tuple[memoryview, SigHashType]]]: try: - r = utils.BufferReader(script_sig) + r = BufferReader(script_sig) # Skip over OP_FALSE, which is due to the old OP_CHECKMULTISIG bug. if r.get() != 0: @@ -511,13 +519,13 @@ def parse_input_script_multisig( if len(script) != n: raise ValueError except (ValueError, EOFError): - raise wire.DataError("Invalid scriptSig.") + raise DataError("Invalid scriptSig.") return script, signatures def output_script_multisig(pubkeys: list[bytes], m: int) -> bytearray: - w = utils.empty_bytearray(output_script_multisig_length(pubkeys, m)) + w = empty_bytearray(output_script_multisig_length(pubkeys, m)) write_output_script_multisig(w, pubkeys, m) return w @@ -530,10 +538,10 @@ def write_output_script_multisig( ) -> None: n = len(pubkeys) if n < 1 or n > 15 or m < 1 or m > 15 or m > n: - raise wire.DataError("Invalid multisig parameters") + raise DataError("Invalid multisig parameters") for pubkey in pubkeys: if len(pubkey) != 33: - raise wire.DataError("Invalid multisig parameters") + raise DataError("Invalid multisig parameters") if prefixed: write_compact_size(w, output_script_multisig_length(pubkeys, m)) @@ -551,7 +559,7 @@ def output_script_multisig_length(pubkeys: Sequence[bytes | memoryview], m: int) def parse_output_script_multisig(script: bytes) -> tuple[list[memoryview], int]: try: - r = utils.BufferReader(script) + r = BufferReader(script) threshold = r.get() - 0x50 pubkey_count = script[-2] - 0x50 @@ -577,7 +585,7 @@ def parse_output_script_multisig(script: bytes) -> tuple[list[memoryview], int]: raise ValueError except (ValueError, IndexError, EOFError): - raise wire.DataError("Invalid multisig script") + raise DataError("Invalid multisig script") return public_keys, threshold @@ -587,7 +595,7 @@ def parse_output_script_multisig(script: bytes) -> tuple[list[memoryview], int]: def output_script_paytoopreturn(data: bytes) -> bytearray: - w = utils.empty_bytearray(1 + 5 + len(data)) + w = empty_bytearray(1 + 5 + len(data)) w.append(0x6A) # OP_RETURN write_op_push(w, len(data)) w.extend(data) @@ -627,7 +635,7 @@ def write_bip322_signature_proof( w.append(0x00) -def read_bip322_signature_proof(r: utils.BufferReader) -> tuple[memoryview, memoryview]: +def read_bip322_signature_proof(r: BufferReader) -> tuple[memoryview, memoryview]: script_sig = read_memoryview_prefixed(r) witness = r.read_memoryview() return script_sig, witness diff --git a/core/src/apps/bitcoin/scripts_decred.py b/core/src/apps/bitcoin/scripts_decred.py index 0ed6961fab..35c99de6b7 100644 --- a/core/src/apps/bitcoin/scripts_decred.py +++ b/core/src/apps/bitcoin/scripts_decred.py @@ -1,27 +1,25 @@ from typing import TYPE_CHECKING -from trezor import utils, wire +from trezor import utils from trezor.crypto import base58 from trezor.crypto.base58 import blake256d_32 -from trezor.enums import InputScriptType - -from apps.common.writers import write_bytes_fixed, write_uint64_le +from trezor.wire import DataError from . import scripts -from .common import SigHashType -from .multisig import multisig_get_pubkeys, multisig_pubkey_index from .scripts import ( # noqa: F401 output_script_paytoopreturn, write_output_script_multisig, write_output_script_p2pkh, ) -from .writers import op_push_length, write_compact_size, write_op_push +from .writers import write_compact_size if TYPE_CHECKING: from trezor.messages import MultisigRedeemScriptType + from trezor.enums import InputScriptType from apps.common.coininfo import CoinInfo + from .common import SigHashType from .writers import Writer @@ -34,6 +32,10 @@ def write_input_script_prefixed( pubkey: bytes, signature: bytes, ) -> None: + from trezor import wire + from trezor.enums import InputScriptType + from .multisig import multisig_pubkey_index + if script_type == InputScriptType.SPENDADDRESS: # p2pkh or p2sh scripts.write_input_script_p2pkh_or_p2sh_prefixed( @@ -41,16 +43,16 @@ def write_input_script_prefixed( ) elif script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig - assert multisig is not None # checked in sanitize_tx_input + assert multisig is not None # checked in _sanitize_tx_input signature_index = multisig_pubkey_index(multisig, pubkey) - write_input_script_multisig_prefixed( + _write_input_script_multisig_prefixed( w, multisig, signature, signature_index, sighash_type, coin ) else: raise wire.ProcessError("Invalid script type") -def write_input_script_multisig_prefixed( +def _write_input_script_multisig_prefixed( w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, @@ -58,9 +60,12 @@ def write_input_script_multisig_prefixed( sighash_type: SigHashType, coin: CoinInfo, ) -> None: + from .multisig import multisig_get_pubkeys + from .writers import op_push_length, write_op_push + signatures = multisig.signatures # other signatures if len(signatures[signature_index]) > 0: - raise wire.DataError("Invalid multisig parameters") + raise DataError("Invalid multisig parameters") signatures[signature_index] = signature # our signature # length of the redeem script @@ -89,7 +94,7 @@ def output_script_sstxsubmissionpkh(addr: str) -> bytearray: try: raw_address = base58.decode_check(addr, blake256d_32) except ValueError: - raise wire.DataError("Invalid address") + raise DataError("Invalid address") w = utils.empty_bytearray(26) w.append(0xBA) # OP_SSTX @@ -102,7 +107,7 @@ def output_script_sstxchange(addr: str) -> bytearray: try: raw_address = base58.decode_check(addr, blake256d_32) except ValueError: - raise wire.DataError("Invalid address") + raise DataError("Invalid address") w = utils.empty_bytearray(26) w.append(0xBD) # OP_SSTXCHANGE @@ -128,6 +133,8 @@ def write_output_script_ssgen_prefixed(w: Writer, pkh: bytes) -> None: # Stake commitment OPRETURN. def sstxcommitment_pkh(pkh: bytes, amount: int) -> bytes: + from apps.common.writers import write_bytes_fixed, write_uint64_le + w = utils.empty_bytearray(30) write_bytes_fixed(w, pkh, 20) write_uint64_le(w, amount) diff --git a/core/src/apps/bitcoin/sign_message.py b/core/src/apps/bitcoin/sign_message.py index 8bf7f92b40..c20f22a9b6 100644 --- a/core/src/apps/bitcoin/sign_message.py +++ b/core/src/apps/bitcoin/sign_message.py @@ -1,19 +1,10 @@ from typing import TYPE_CHECKING -from trezor import wire -from trezor.crypto.curve import secp256k1 -from trezor.enums import InputScriptType -from trezor.messages import MessageSignature -from trezor.ui.layouts import confirm_signverify - -from apps.common.paths import validate_path -from apps.common.signverify import decode_message, message_digest - -from .addresses import address_short, get_address -from .keychain import validate_path_against_script_type, with_keychain +from .keychain import with_keychain if TYPE_CHECKING: - from trezor.messages import SignMessage + from trezor.messages import SignMessage, MessageSignature + from trezor.wire import Context from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain @@ -21,8 +12,20 @@ if TYPE_CHECKING: @with_keychain async def sign_message( - ctx: wire.Context, msg: SignMessage, keychain: Keychain, coin: CoinInfo + ctx: Context, msg: SignMessage, keychain: Keychain, coin: CoinInfo ) -> MessageSignature: + from trezor import wire + from trezor.crypto.curve import secp256k1 + from trezor.enums import InputScriptType + from trezor.messages import MessageSignature + from trezor.ui.layouts import confirm_signverify + + from apps.common.paths import validate_path + from apps.common.signverify import decode_message, message_digest + + from .addresses import address_short, get_address + from .keychain import validate_path_against_script_type + message = msg.message address_n = msg.address_n script_type = msg.script_type or InputScriptType.SPENDADDRESS diff --git a/core/src/apps/bitcoin/sign_tx/__init__.py b/core/src/apps/bitcoin/sign_tx/__init__.py index e7d4aa14f1..88b601f780 100644 --- a/core/src/apps/bitcoin/sign_tx/__init__.py +++ b/core/src/apps/bitcoin/sign_tx/__init__.py @@ -1,12 +1,8 @@ from typing import TYPE_CHECKING -from trezor import utils, wire -from trezor.enums import RequestType -from trezor.messages import TxRequest +from trezor import utils -from ..common import BITCOIN_NAMES from ..keychain import with_keychain -from . import approvers, bitcoin, helpers, progress if not utils.BITCOIN_ONLY: from . import bitcoinlike, decred, zcash_v4 @@ -15,6 +11,7 @@ if not utils.BITCOIN_ONLY: if TYPE_CHECKING: from typing import Protocol + from trezor.wire import Context from trezor.messages import ( SignTx, TxAckInput, @@ -23,11 +20,13 @@ if TYPE_CHECKING: TxAckPrevInput, TxAckPrevOutput, TxAckPrevExtraData, + TxRequest, ) from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain + from . import approvers from ..authorization import CoinJoinAuthorization TxAckType = ( @@ -55,12 +54,18 @@ if TYPE_CHECKING: @with_keychain async def sign_tx( - ctx: wire.Context, + ctx: Context, msg: SignTx, keychain: Keychain, coin: CoinInfo, authorization: CoinJoinAuthorization | None = None, ) -> TxRequest: + from trezor.enums import RequestType + from trezor.messages import TxRequest + + from ..common import BITCOIN_NAMES + from . import approvers, bitcoin, helpers, progress + approver: approvers.Approver | None = None if authorization: approver = approvers.CoinJoinApprover(msg, coin, authorization) diff --git a/core/src/apps/bitcoin/sign_tx/approvers.py b/core/src/apps/bitcoin/sign_tx/approvers.py index 54010680ce..866f9f0d73 100644 --- a/core/src/apps/bitcoin/sign_tx/approvers.py +++ b/core/src/apps/bitcoin/sign_tx/approvers.py @@ -1,23 +1,19 @@ from micropython import const from typing import TYPE_CHECKING -from trezor import wire from trezor.crypto.curve import bip340, secp256k1 from trezor.crypto.hashlib import sha256 -from trezor.enums import OutputScriptType -from trezor.ui.components.common.confirm import INFO from trezor.utils import HashWriter +from trezor.wire import DataError, ProcessError from apps.common import safety_checks from .. import writers -from ..authorization import FEE_RATE_DECIMALS from ..common import input_is_external_unverified from ..keychain import validate_path_against_script_type from . import helpers, tx_weight -from .payment_request import PaymentRequestVerifier from .sig_hasher import BitcoinSigHasher -from .tx_info import OriginalTxInfo, TxInfo +from .tx_info import OriginalTxInfo if TYPE_CHECKING: from trezor.crypto import bip32 @@ -27,6 +23,8 @@ if TYPE_CHECKING: from apps.common.keychain import Keychain from ..authorization import CoinJoinAuthorization + from .tx_info import TxInfo + from .payment_request import PaymentRequestVerifier # An Approver object computes the transaction totals and either prompts the user @@ -79,7 +77,7 @@ class Approver: if input_is_external_unverified(txi): self.has_unverified_external_input = True if safety_checks.is_strict(): - raise wire.ProcessError("Unverifiable external input.") + raise ProcessError("Unverifiable external input.") else: self.external_in += txi.amount if txi.orig_hash: @@ -92,6 +90,8 @@ class Approver: async def add_payment_request( self, msg: TxAckPaymentRequest, keychain: Keychain ) -> None: + from .payment_request import PaymentRequestVerifier + self.finish_payment_request() self.payment_req_verifier = PaymentRequestVerifier(msg, self.coin, keychain) @@ -135,7 +135,7 @@ class Approver: class BasicApprover(Approver): # the maximum number of change-outputs allowed without user confirmation - MAX_SILENT_CHANGE_COUNT = const(2) + MAX_SILENT_CHANGE_COUNT = 2 def __init__(self, tx: SignTx, coin: CoinInfo) -> None: super().__init__(tx, coin) @@ -158,7 +158,7 @@ class BasicApprover(Approver): not validate_path_against_script_type(self.coin, txi) and not self.foreign_address_confirmed ): - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") def add_change_output(self, txo: TxOutput, script_pubkey: bytes) -> None: super().add_change_output(txo, script_pubkey) @@ -170,6 +170,8 @@ class BasicApprover(Approver): script_pubkey: bytes, orig_txo: TxOutput | None = None, ) -> None: + from trezor.enums import OutputScriptType + await super().add_external_output(txo, script_pubkey, orig_txo) if orig_txo: @@ -180,7 +182,7 @@ class BasicApprover(Approver): if self.is_payjoin(): # In case of PayJoin the above could be used to increase other external # outputs, which would create too much UI complexity. - raise wire.ProcessError( + raise ProcessError( "Reducing original output amounts is not supported." ) await helpers.confirm_modify_output( @@ -191,7 +193,7 @@ class BasicApprover(Approver): # confirmation, because approve_tx() together with the branch above ensures that # the increase is paid by external inputs. if not self.is_payjoin(): - raise wire.ProcessError( + raise ProcessError( "Increasing original output amounts is not supported." ) @@ -199,7 +201,7 @@ class BasicApprover(Approver): # Skip output confirmation for replacement transactions, # but don't allow adding new OP_RETURN outputs. if txo.script_type == OutputScriptType.PAYTOOPRETURN and not orig_txo: - raise wire.ProcessError( + raise ProcessError( "Adding new OP_RETURN outputs in replacement transactions is not supported." ) elif txo.payment_req_index is None or self.show_payment_req_details: @@ -210,9 +212,11 @@ class BasicApprover(Approver): async def add_payment_request( self, msg: TxAckPaymentRequest, keychain: Keychain ) -> None: + from trezor.ui.components.common.confirm import INFO + await super().add_payment_request(msg, keychain) if msg.amount is None: - raise wire.DataError("Missing payment request amount.") + raise DataError("Missing payment request amount.") result = await helpers.confirm_payment_request(msg, self.coin, self.amount_unit) self.show_payment_req_details = result is INFO @@ -238,6 +242,11 @@ class BasicApprover(Approver): await helpers.confirm_replacement(description, orig.orig_hash) async def approve_tx(self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo]) -> None: + from trezor.wire import NotEnoughFunds + + coin = self.coin # local_cache_attribute + amount_unit = self.amount_unit # local_cache_attribute + await super().approve_tx(tx_info, orig_txs) if self.has_unverified_external_input: @@ -246,21 +255,21 @@ class BasicApprover(Approver): fee = self.total_in - self.total_out # some coins require negative fees for reward TX - if fee < 0 and not self.coin.negative_fee: - raise wire.NotEnoughFunds("Not enough funds") + if fee < 0 and not coin.negative_fee: + raise NotEnoughFunds("Not enough funds") total = self.total_in - self.change_out spending = total - self.external_in tx_size_vB = self.weight.get_virtual_size() fee_rate = fee / tx_size_vB # fee_threshold = (coin.maxfee per byte * tx size) - fee_threshold = (self.coin.maxfee_kb / 1000) * tx_size_vB + fee_threshold = (coin.maxfee_kb / 1000) * tx_size_vB # fee > (coin.maxfee per byte * tx size) 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, self.amount_unit) + raise DataError("The fee is unexpectedly large") + await helpers.confirm_feeoverthreshold(fee, coin, amount_unit) if self.change_count > self.MAX_SILENT_CHANGE_COUNT: await helpers.confirm_change_count_over_threshold(self.change_count) @@ -273,7 +282,7 @@ class BasicApprover(Approver): orig_fee = self.orig_total_in - self.orig_total_out if fee < 0 or orig_fee < 0: - raise wire.ProcessError( + raise ProcessError( "Negative fees not supported in transaction replacement." ) @@ -283,14 +292,14 @@ class BasicApprover(Approver): # not increase by more than the fee difference (so additional funds # can only go towards the fee, which is confirmed by the user). if spending - orig_spending > fee - orig_fee: - raise wire.ProcessError("Invalid replacement transaction.") + raise ProcessError("Invalid replacement transaction.") # Replacement transactions must not change the effective nLockTime. lock_time = 0 if tx_info.lock_time_disabled() else tx_info.tx.lock_time for orig in orig_txs: orig_lock_time = 0 if orig.lock_time_disabled() else orig.tx.lock_time if lock_time != orig_lock_time: - raise wire.ProcessError( + raise ProcessError( "Original transactions must have same effective nLockTime as replacement transaction." ) @@ -299,14 +308,14 @@ class BasicApprover(Approver): # coming entirely from the user's own funds and from decreases of external outputs. # We consider the decreases as belonging to the user. await helpers.confirm_modify_fee( - fee - orig_fee, fee, fee_rate, self.coin, self.amount_unit + fee - orig_fee, fee, fee_rate, coin, amount_unit ) elif spending > orig_spending: # PayJoin and user is spending more: Show the increase in the user's contribution # to the fee, ignoring any contribution from external inputs. Decreasing of # external outputs is not allowed in PayJoin, so there is no need to handle those. await helpers.confirm_modify_fee( - spending - orig_spending, fee, fee_rate, self.coin, self.amount_unit + spending - orig_spending, fee, fee_rate, coin, amount_unit ) else: # PayJoin and user is not spending more: When new external inputs are involved and @@ -321,13 +330,9 @@ class BasicApprover(Approver): ) if not self.external_in: - await helpers.confirm_total( - total, fee, fee_rate, self.coin, self.amount_unit - ) + await helpers.confirm_total(total, fee, fee_rate, coin, amount_unit) else: - await helpers.confirm_joint_total( - spending, total, self.coin, self.amount_unit - ) + await helpers.confirm_joint_total(spending, total, coin, amount_unit) class CoinJoinApprover(Approver): @@ -336,7 +341,7 @@ class CoinJoinApprover(Approver): MIN_REGISTRABLE_OUTPUT_AMOUNT = const(5000) # Largest possible weight of an output supported by Trezor (P2TR or P2WSH). - MAX_OUTPUT_WEIGHT = const(4 * (8 + 1 + 1 + 1 + 32)) + MAX_OUTPUT_WEIGHT = 4 * (8 + 1 + 1 + 1 + 32) # Masks for the signable and no_fee bits in coinjoin_flags. COINJOIN_FLAGS_SIGNABLE = const(0x01) @@ -356,7 +361,7 @@ class CoinJoinApprover(Approver): super().__init__(tx, coin) if not tx.coinjoin_request: - raise wire.DataError("Missing CoinJoin request.") + raise DataError("Missing CoinJoin request.") self.request = tx.coinjoin_request self.authorization = authorization @@ -384,7 +389,7 @@ class CoinJoinApprover(Approver): async def add_internal_input(self, txi: TxInput, node: bip32.HDNode) -> None: self.our_weight.add_input(txi) if not self.authorization.check_sign_tx_input(txi, self.coin): - raise wire.ProcessError("Unauthorized path") + raise ProcessError("Unauthorized path") # Compute the masking bit for the signable bit in coinjoin flags. internal_private_key = node.private_key() @@ -400,7 +405,7 @@ class CoinJoinApprover(Approver): # Ensure that the input can be signed. if bool(txi.coinjoin_flags & self.COINJOIN_FLAGS_SIGNABLE) ^ mask != 1: - raise wire.ProcessError("Unauthorized input") + raise ProcessError("Unauthorized input") # Add to coordination_fee_base, except for remixes and small inputs which are # not charged a coordination fee. @@ -416,7 +421,7 @@ class CoinJoinApprover(Approver): # in multiple signatures schemes (ECDSA and Schnorr) and we want to be sure that the user # went through a warning screen before we sign the input. if not self.authorization.check_sign_tx_input(txi, self.coin): - raise wire.ProcessError("Unauthorized path") + raise ProcessError("Unauthorized path") def add_external_input(self, txi: TxInput) -> None: super().add_external_input(txi) @@ -425,7 +430,7 @@ class CoinJoinApprover(Approver): # is not critical for security, we are just being cautious, because # CoinJoin is automated and this is not a very legitimate use-case. if input_is_external_unverified(txi): - raise wire.ProcessError("Unverifiable external input.") + raise ProcessError("Unverifiable external input.") def add_change_output(self, txo: TxOutput, script_pubkey: bytes) -> None: super().add_change_output(txo, script_pubkey) @@ -438,7 +443,7 @@ class CoinJoinApprover(Approver): def _verify_coinjoin_request(self, tx_info: TxInfo): if not isinstance(tx_info.sig_hasher, BitcoinSigHasher): - raise wire.ProcessError("Unexpected signature hasher.") + raise ProcessError("Unexpected signature hasher.") # Finish hashing the CoinJoin request. writers.write_bytes_fixed( @@ -464,10 +469,12 @@ class CoinJoinApprover(Approver): ) async def approve_tx(self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo]) -> None: + from ..authorization import FEE_RATE_DECIMALS + await super().approve_tx(tx_info, orig_txs) if not self._verify_coinjoin_request(tx_info): - raise wire.DataError("Invalid signature in CoinJoin request.") + raise DataError("Invalid signature in CoinJoin request.") # The mining fee of the transaction as a whole. mining_fee = self.total_in - self.total_out @@ -512,13 +519,13 @@ class CoinJoinApprover(Approver): + our_max_mining_fee + min_allowed_output_amount_plus_fee ): - raise wire.ProcessError("Total fee over threshold.") + raise ProcessError("Total fee over threshold.") if not self.authorization.approve_sign_tx(tx_info.tx): - raise wire.ProcessError("Exceeded number of CoinJoin rounds.") + raise ProcessError("Exceeded number of CoinJoin rounds.") def _add_output(self, txo: TxOutput, script_pubkey: bytes) -> None: super()._add_output(txo, script_pubkey) if txo.payment_req_index: - raise wire.DataError("Unexpected payment request.") + raise DataError("Unexpected payment request.") diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index fda7275963..36b4e862de 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -1,28 +1,21 @@ from micropython import const from typing import TYPE_CHECKING -from trezor import wire from trezor.crypto.hashlib import sha256 -from trezor.enums import InputScriptType, OutputScriptType -from trezor.messages import TxRequest, TxRequestDetailsType, TxRequestSerializedType -from trezor.utils import HashWriter, empty_bytearray, ensure +from trezor.enums import InputScriptType +from trezor.utils import HashWriter, empty_bytearray +from trezor.wire import DataError, ProcessError from apps.common.writers import write_compact_size from .. import addresses, common, multisig, scripts, writers -from ..common import ( - SigHashType, - bip340_sign, - ecdsa_sign, - input_is_external, - input_is_segwit, -) +from ..common import SigHashType, ecdsa_sign, input_is_external from ..ownership import verify_nonownership from ..verification import SignatureVerifier -from . import approvers, helpers +from . import helpers +from .helpers import request_tx_input, request_tx_output from .progress import progress -from .sig_hasher import BitcoinSigHasher -from .tx_info import OriginalTxInfo, TxInfo +from .tx_info import OriginalTxInfo if TYPE_CHECKING: from typing import Sequence @@ -41,7 +34,10 @@ if TYPE_CHECKING: from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain + from . import approvers from .sig_hasher import SigHasher + from .tx_info import TxInfo + from ..writers import Writer # the number of bytes to preallocate for serialized transaction chunks @@ -101,6 +97,14 @@ class Bitcoin: coin: CoinInfo, approver: approvers.Approver | None, ) -> None: + from trezor.messages import ( + TxRequest, + TxRequestDetailsType, + TxRequestSerializedType, + ) + from . import approvers + from .tx_info import TxInfo + global _SERIALIZED_TX_BUFFER self.tx_info = TxInfo(self, helpers.sanitize_sign_tx(tx, coin)) @@ -151,15 +155,20 @@ class Bitcoin: return HashWriter(sha256()) def create_sig_hasher(self, tx: SignTx | PrevTx) -> SigHasher: + from .sig_hasher import BitcoinSigHasher + return BitcoinSigHasher() async def step1_process_inputs(self) -> None: + from ..common import input_is_segwit + + tx_info = self.tx_info # local_cache_attribute h_presigned_inputs_check = HashWriter(sha256()) - for i in range(self.tx_info.tx.inputs_count): + for i in range(tx_info.tx.inputs_count): # STAGE_REQUEST_1_INPUT in legacy progress.advance() - txi = await helpers.request_tx_input(self.tx_req, i, self.coin) + txi = await request_tx_input(self.tx_req, i, self.coin) if txi.script_type not in ( InputScriptType.SPENDTAPROOT, InputScriptType.EXTERNAL, @@ -186,14 +195,14 @@ class Bitcoin: if txi.orig_hash: await self.process_original_input(txi, script_pubkey) - self.tx_info.h_inputs_check = self.tx_info.get_tx_check_digest() + tx_info.h_inputs_check = tx_info.get_tx_check_digest() self.h_presigned_inputs = h_presigned_inputs_check.get_digest() # Finalize original inputs. for orig in self.orig_txs: orig.h_inputs_check = orig.get_tx_check_digest() if orig.index != orig.tx.inputs_count: - raise wire.ProcessError("Removal of original inputs is not supported.") + raise ProcessError("Removal of original inputs is not supported.") orig.index = 0 # Reset counter for outputs. @@ -201,7 +210,7 @@ class Bitcoin: for i in range(self.tx_info.tx.outputs_count): # STAGE_REQUEST_2_OUTPUT in legacy progress.advance() - txo = await helpers.request_tx_output(self.tx_req, i, self.coin) + txo = await request_tx_output(self.tx_req, i, self.coin) script_pubkey = self.output_derive_script(txo) orig_txo: TxOutput | None = None if txo.orig_hash: @@ -228,7 +237,7 @@ class Bitcoin: for i in range(self.tx_info.tx.inputs_count): if i in self.presigned: progress.advance() - txi = await helpers.request_tx_input(self.tx_req, i, self.coin) + txi = await request_tx_input(self.tx_req, i, self.coin) writers.write_tx_input_check(h_check, txi) # txi.script_pubkey checked in sanitize_tx_input @@ -247,24 +256,24 @@ class Bitcoin: # multiple rounds of the attack. expected_digest = self.tx_info.h_inputs_check for i in range(self.tx_info.tx.inputs_count): - txi = await helpers.request_tx_input(self.tx_req, i, self.coin) + txi = await request_tx_input(self.tx_req, i, self.coin) writers.write_tx_input_check(h_check, txi) prev_amount, script_pubkey = await self.get_prevtx_output( txi.prev_hash, txi.prev_index ) if prev_amount != txi.amount: - raise wire.DataError("Invalid amount specified") + raise DataError("Invalid amount specified") if script_pubkey != self.input_derive_script(txi): - raise wire.DataError("Input does not match scriptPubKey") + raise DataError("Input does not match scriptPubKey") if i in self.presigned: await self.verify_presigned_external_input(i, txi, script_pubkey) # check that the inputs were the same as those streamed for approval if h_check.get_digest() != expected_digest: - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") # verify the signature of one SIGHASH_ALL input in each original transaction await self.verify_original_txs() @@ -306,9 +315,7 @@ class Bitcoin: if self.serialize: if i in self.presigned: progress.advance() - txi = await helpers.request_tx_input( - self.tx_req, i, self.coin - ) + txi = await request_tx_input(self.tx_req, i, self.coin) self.serialized_tx.extend(txi.witness or b"\0") else: self.serialized_tx.append(0) @@ -329,7 +336,7 @@ class Bitcoin: async def process_internal_input(self, txi: TxInput, node: bip32.HDNode) -> None: if txi.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES: - raise wire.DataError("Wrong input script type") + raise DataError("Wrong input script type") await self.approver.add_internal_input(txi, node) @@ -346,33 +353,32 @@ class Bitcoin: self.keychain, self.coin, ): - raise wire.DataError("Invalid external input") + raise DataError("Invalid external input") async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None: - assert txi.orig_hash is not None - assert txi.orig_index is not None + orig_hash = txi.orig_hash # local_cache_attribute + orig_index = txi.orig_index # local_cache_attribute + + assert orig_hash is not None + assert orig_index is not None for orig in self.orig_txs: - if orig.orig_hash == txi.orig_hash: + if orig.orig_hash == orig_hash: break else: - orig_meta = await helpers.request_tx_meta( - self.tx_req, self.coin, txi.orig_hash - ) - orig = OriginalTxInfo(self, orig_meta, txi.orig_hash) + orig_meta = await helpers.request_tx_meta(self.tx_req, self.coin, orig_hash) + orig = OriginalTxInfo(self, orig_meta, orig_hash) self.orig_txs.append(orig) - if txi.orig_index >= orig.tx.inputs_count: - raise wire.ProcessError("Not enough inputs in original transaction.") + if orig_index >= orig.tx.inputs_count: + raise ProcessError("Not enough inputs in original transaction.") - if orig.index != txi.orig_index: - raise wire.ProcessError( + if orig.index != orig_index: + raise ProcessError( "Rearranging or removal of original inputs is not supported." ) - orig_txi = await helpers.request_tx_input( - self.tx_req, txi.orig_index, self.coin, txi.orig_hash - ) + orig_txi = await request_tx_input(self.tx_req, orig_index, self.coin, orig_hash) # Verify that the original input matches: # @@ -390,7 +396,7 @@ class Bitcoin: or orig_txi.script_type != txi.script_type or self.input_derive_script(orig_txi) != script_pubkey ): - raise wire.ProcessError("Original input does not match current input.") + raise ProcessError("Original input does not match current input.") orig.add_input(orig_txi, script_pubkey) orig.index += 1 @@ -399,9 +405,7 @@ class Bitcoin: self, orig: OriginalTxInfo, orig_hash: bytes, last_index: int ) -> None: while orig.index < last_index: - txo = await helpers.request_tx_output( - self.tx_req, orig.index, self.coin, orig_hash - ) + txo = await request_tx_output(self.tx_req, orig.index, self.coin, orig_hash) orig.add_output(txo, self.output_derive_script(txo)) if orig.output_is_change(txo): @@ -409,7 +413,7 @@ class Bitcoin: self.approver.add_orig_change_output(txo) else: # Removal of external outputs requires prompting the user. Not implemented. - raise wire.ProcessError( + raise ProcessError( "Removal of original external outputs is not supported." ) @@ -418,35 +422,36 @@ class Bitcoin: async def get_original_output( self, txo: TxOutput, script_pubkey: bytes ) -> TxOutput: - assert txo.orig_hash is not None - assert txo.orig_index is not None + orig_hash = txo.orig_hash # local_cache_attribute + orig_index = txo.orig_index # local_cache_attribute + + assert orig_hash is not None + assert orig_index is not None for orig in self.orig_txs: - if orig.orig_hash == txo.orig_hash: + if orig.orig_hash == orig_hash: break else: - raise wire.ProcessError("Unknown original transaction.") + raise ProcessError("Unknown original transaction.") - if txo.orig_index >= orig.tx.outputs_count: - raise wire.ProcessError("Not enough outputs in original transaction.") + if orig_index >= orig.tx.outputs_count: + raise ProcessError("Not enough outputs in original transaction.") - if orig.index > txo.orig_index: - raise wire.ProcessError("Rearranging of original outputs is not supported.") + if orig.index > orig_index: + raise ProcessError("Rearranging of original outputs is not supported.") # First fetch any removed original outputs which precede the one we want. - await self.fetch_removed_original_outputs(orig, txo.orig_hash, txo.orig_index) + await self.fetch_removed_original_outputs(orig, orig_hash, orig_index) - orig_txo = await helpers.request_tx_output( - self.tx_req, orig.index, self.coin, txo.orig_hash + orig_txo = await request_tx_output( + self.tx_req, orig.index, self.coin, orig_hash ) if script_pubkey != self.output_derive_script(orig_txo): - raise wire.ProcessError("Not an original output.") + raise ProcessError("Not an original output.") if self.tx_info.output_is_change(txo) and not orig.output_is_change(orig_txo): - raise wire.ProcessError( - "Original output is missing change-output parameters." - ) + raise ProcessError("Original output is missing change-output parameters.") orig.add_output(orig_txo, script_pubkey) @@ -466,9 +471,7 @@ class Bitcoin: for i in range(orig.tx.inputs_count): progress.advance() - txi = await helpers.request_tx_input( - self.tx_req, i, self.coin, orig.orig_hash - ) + txi = await request_tx_input(self.tx_req, i, self.coin, orig.orig_hash) writers.write_tx_input_check(h_check, txi) script_pubkey = self.input_derive_script(txi) verifier = SignatureVerifier( @@ -489,7 +492,7 @@ class Bitcoin: # check that the inputs were the same as those streamed for approval if h_check.get_digest() != orig.h_inputs_check: - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") async def approve_output( self, @@ -497,23 +500,24 @@ class Bitcoin: script_pubkey: bytes, orig_txo: TxOutput | None, ) -> None: - if txo.payment_req_index != self.payment_req_index: - if txo.payment_req_index is None: + payment_req_index = txo.payment_req_index # local_cache_attribute + approver = self.approver # local_cache_attribute + + if payment_req_index != self.payment_req_index: + if payment_req_index is None: self.approver.finish_payment_request() else: tx_ack_payment_req = await helpers.request_payment_req( - self.tx_req, txo.payment_req_index + self.tx_req, payment_req_index ) - await self.approver.add_payment_request( - tx_ack_payment_req, self.keychain - ) - self.payment_req_index = txo.payment_req_index + await approver.add_payment_request(tx_ack_payment_req, self.keychain) + self.payment_req_index = payment_req_index if self.tx_info.output_is_change(txo): # Output is change and does not need approval. - self.approver.add_change_output(txo, script_pubkey) + approver.add_change_output(txo, script_pubkey) else: - await self.approver.add_external_output(txo, script_pubkey, orig_txo) + await approver.add_external_output(txo, script_pubkey, orig_txo) self.tx_info.add_output(txo, script_pubkey) @@ -568,18 +572,18 @@ class Bitcoin: verifier.verify(tx_digest) async def serialize_external_input(self, i: int) -> None: - txi = await helpers.request_tx_input(self.tx_req, i, self.coin) + txi = await request_tx_input(self.tx_req, i, self.coin) if not input_is_external(txi): - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") self.write_tx_input(self.serialized_tx, txi, txi.script_sig or bytes()) async def serialize_segwit_input(self, i: int) -> None: # STAGE_REQUEST_SEGWIT_INPUT in legacy - txi = await helpers.request_tx_input(self.tx_req, i, self.coin) + txi = await request_tx_input(self.tx_req, i, self.coin) if txi.script_type not in common.SEGWIT_INPUT_SCRIPT_TYPES: - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") self.tx_info.check_input(txi) if txi.script_type == InputScriptType.SPENDP2SHWITNESS: @@ -595,7 +599,7 @@ class Bitcoin: if self.taproot_only: # Prevents an attacker from bypassing prev tx checking by providing a different # script type than the one that was provided during the confirmation phase. - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") node = self.keychain.derive(txi.address_n) public_key = node.public_key() @@ -621,6 +625,8 @@ class Bitcoin: return public_key, signature def sign_taproot_input(self, i: int, txi: TxInput) -> bytes: + from ..common import bip340_sign + sigmsg_digest = self.tx_info.sig_hasher.hash341( i, self.tx_info.tx, @@ -632,11 +638,11 @@ class Bitcoin: async def sign_segwit_input(self, i: int) -> None: # STAGE_REQUEST_SEGWIT_WITNESS in legacy - txi = await helpers.request_tx_input(self.tx_req, i, self.coin) + txi = await request_tx_input(self.tx_req, i, self.coin) self.tx_info.check_input(txi) self.approver.check_internal_input(txi) if txi.script_type not in common.SEGWIT_INPUT_SCRIPT_TYPES: - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") if txi.script_type == InputScriptType.SPENDTAPROOT: signature = self.sign_taproot_input(i, txi) @@ -675,6 +681,9 @@ class Bitcoin: tx_info: TxInfo | OriginalTxInfo, script_pubkey: bytes | None = None, ) -> tuple[bytes, TxInput, bip32.HDNode | None]: + tx = tx_info.tx # local_cache_attribute + coin = self.coin # local_cache_attribute + tx_hash = tx_info.orig_hash if isinstance(tx_info, OriginalTxInfo) else None # the transaction digest which gets signed for this input @@ -682,15 +691,15 @@ class Bitcoin: # should come out the same as h_tx_check, checked before signing the digest h_check = HashWriter(sha256()) - self.write_tx_header(h_sign, tx_info.tx, witness_marker=False) - write_compact_size(h_sign, tx_info.tx.inputs_count) + self.write_tx_header(h_sign, tx, witness_marker=False) + write_compact_size(h_sign, tx.inputs_count) txi_sign = None node = None - for i in range(tx_info.tx.inputs_count): + for i in range(tx.inputs_count): # STAGE_REQUEST_4_INPUT in legacy progress.advance() - txi = await helpers.request_tx_input(self.tx_req, i, self.coin, tx_hash) + txi = await request_tx_input(self.tx_req, i, coin, tx_hash) writers.write_tx_input_check(h_check, txi) # Only the previous UTXO's scriptPubKey is included in h_sign. if i == index: @@ -699,54 +708,55 @@ class Bitcoin: self.tx_info.check_input(txi) node = self.keychain.derive(txi.address_n) key_sign_pub = node.public_key() - if txi.multisig: + txi_multisig = txi.multisig # local_cache_attribute + if txi_multisig: # Sanity check to ensure we are signing with a key that is included in the multisig. - multisig.multisig_pubkey_index(txi.multisig, key_sign_pub) + multisig.multisig_pubkey_index(txi_multisig, key_sign_pub) if txi.script_type == InputScriptType.SPENDMULTISIG: - assert txi.multisig is not None # checked in sanitize_tx_input + assert txi_multisig is not None # checked in _sanitize_tx_input script_pubkey = scripts.output_script_multisig( - multisig.multisig_get_pubkeys(txi.multisig), - txi.multisig.m, + multisig.multisig_get_pubkeys(txi_multisig), + txi_multisig.m, ) elif txi.script_type == InputScriptType.SPENDADDRESS: script_pubkey = scripts.output_script_p2pkh( - addresses.ecdsa_hash_pubkey(key_sign_pub, self.coin) + addresses.ecdsa_hash_pubkey(key_sign_pub, coin) ) else: - raise wire.ProcessError("Unknown transaction type") + raise ProcessError("Unknown transaction type") self.write_tx_input(h_sign, txi, script_pubkey) else: self.write_tx_input(h_sign, txi, bytes()) if txi_sign is None: - raise RuntimeError # index >= tx_info.tx.inputs_count + raise RuntimeError # index >= tx_info_tx.inputs_count - write_compact_size(h_sign, tx_info.tx.outputs_count) + write_compact_size(h_sign, tx.outputs_count) - for i in range(tx_info.tx.outputs_count): + for i in range(tx.outputs_count): # STAGE_REQUEST_4_OUTPUT in legacy progress.advance() - txo = await helpers.request_tx_output(self.tx_req, i, self.coin, tx_hash) + txo = await request_tx_output(self.tx_req, i, coin, tx_hash) script_pubkey = self.output_derive_script(txo) self.write_tx_output(h_check, txo, script_pubkey) self.write_tx_output(h_sign, txo, script_pubkey) - writers.write_uint32(h_sign, tx_info.tx.lock_time) + writers.write_uint32(h_sign, tx.lock_time) writers.write_uint32(h_sign, self.get_hash_type(txi_sign)) # check that the inputs were the same as those streamed for approval if tx_info.get_tx_check_digest() != h_check.get_digest(): - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") - tx_digest = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double) + tx_digest = writers.get_tx_hash(h_sign, coin.sign_hash_double) return tx_digest, txi_sign, node async def sign_nonsegwit_input(self, i: int) -> None: if self.taproot_only: # Prevents an attacker from bypassing prev tx checking by providing a different # script type than the one that was provided during the confirmation phase. - raise wire.ProcessError("Transaction has changed during signing") + raise ProcessError("Transaction has changed during signing") tx_digest, txi, node = await self.get_legacy_tx_digest(i, self.tx_info) assert node is not None @@ -763,21 +773,23 @@ class Bitcoin: async def serialize_output(self, i: int) -> None: # STAGE_REQUEST_5_OUTPUT in legacy - txo = await helpers.request_tx_output(self.tx_req, i, self.coin) + txo = await request_tx_output(self.tx_req, i, self.coin) script_pubkey = self.output_derive_script(txo) self.write_tx_output(self.serialized_tx, txo, script_pubkey) async def get_prevtx_output( self, prev_hash: bytes, prev_index: int ) -> tuple[int, bytes]: + coin = self.coin # local_cache_attribute + amount_out = 0 # output amount # STAGE_REQUEST_3_PREV_META in legacy - tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash) + tx = await helpers.request_tx_meta(self.tx_req, coin, prev_hash) progress.init_prev_tx(tx.inputs_count, tx.outputs_count) if tx.outputs_count <= prev_index: - raise wire.ProcessError("Not enough outputs in previous transaction.") + raise ProcessError("Not enough outputs in previous transaction.") txh = self.create_hash_writer() @@ -788,9 +800,7 @@ class Bitcoin: for i in range(tx.inputs_count): # STAGE_REQUEST_3_PREV_INPUT in legacy progress.advance_prev_tx() - txi = await helpers.request_tx_prev_input( - self.tx_req, i, self.coin, prev_hash - ) + txi = await helpers.request_tx_prev_input(self.tx_req, i, coin, prev_hash) self.write_tx_input(txh, txi, txi.script_sig) write_compact_size(txh, tx.outputs_count) @@ -800,7 +810,7 @@ class Bitcoin: # STAGE_REQUEST_3_PREV_OUTPUT in legacy progress.advance_prev_tx() txo_bin = await helpers.request_tx_prev_output( - self.tx_req, i, self.coin, prev_hash + self.tx_req, i, coin, prev_hash ) self.write_tx_output(txh, txo_bin, txo_bin.script_pubkey) if i == prev_index: @@ -812,11 +822,8 @@ class Bitcoin: await self.write_prev_tx_footer(txh, tx, prev_hash) - if ( - writers.get_tx_hash(txh, double=self.coin.sign_hash_double, reverse=True) - != prev_hash - ): - raise wire.ProcessError("Encountered invalid prev_hash") + if writers.get_tx_hash(txh, coin.sign_hash_double, True) != prev_hash: + raise ProcessError("Encountered invalid prev_hash") return amount_out, script_pubkey @@ -843,7 +850,7 @@ class Bitcoin: def write_tx_input_derived( self, - w: writers.Writer, + w: Writer, txi: TxInput, pubkey: bytes, signature: bytes, @@ -863,7 +870,7 @@ class Bitcoin: @staticmethod def write_tx_input( - w: writers.Writer, + w: Writer, txi: TxInput | PrevInput, script: bytes, ) -> None: @@ -871,7 +878,7 @@ class Bitcoin: @staticmethod def write_tx_output( - w: writers.Writer, + w: Writer, txo: TxOutput | PrevOutput, script_pubkey: bytes, ) -> None: @@ -879,7 +886,7 @@ class Bitcoin: def write_tx_header( self, - w: writers.Writer, + w: Writer, tx: SignTx | PrevTx, witness_marker: bool, ) -> None: @@ -888,21 +895,25 @@ class Bitcoin: write_compact_size(w, 0x00) # segwit witness marker write_compact_size(w, 0x01) # segwit witness flag - def write_tx_footer(self, w: writers.Writer, tx: SignTx | PrevTx) -> None: + def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None: writers.write_uint32(w, tx.lock_time) async def write_prev_tx_footer( - self, w: writers.Writer, tx: PrevTx, prev_hash: bytes + self, w: Writer, tx: PrevTx, prev_hash: bytes ) -> None: self.write_tx_footer(w, tx) def set_serialized_signature(self, index: int, signature: bytes) -> None: - # Only one signature per TxRequest can be serialized. - assert self.tx_req.serialized is not None - ensure(self.tx_req.serialized.signature is None) + from trezor.utils import ensure - self.tx_req.serialized.signature_index = index - self.tx_req.serialized.signature = signature + serialized = self.tx_req.serialized # local_cache_attribute + + # Only one signature per TxRequest can be serialized. + assert serialized is not None + ensure(serialized.signature is None) + + serialized.signature_index = index + serialized.signature = signature # scriptPubKey derivation # === @@ -911,7 +922,7 @@ class Bitcoin: self, txi: TxInput, node: bip32.HDNode | None = None ) -> bytes: if input_is_external(txi): - assert txi.script_pubkey is not None # checked in sanitize_tx_input + assert txi.script_pubkey is not None # checked in _sanitize_tx_input return txi.script_pubkey if node is None: @@ -921,8 +932,10 @@ class Bitcoin: return scripts.output_derive_script(address, self.coin) def output_derive_script(self, txo: TxOutput) -> bytes: + from trezor.enums import OutputScriptType + if txo.script_type == OutputScriptType.PAYTOOPRETURN: - assert txo.op_return_data is not None # checked in sanitize_tx_output + assert txo.op_return_data is not None # checked in _sanitize_tx_output return scripts.output_script_paytoopreturn(txo.op_return_data) if txo.address_n: @@ -932,12 +945,12 @@ class Bitcoin: txo.script_type ] except KeyError: - raise wire.DataError("Invalid script type") + raise DataError("Invalid script type") node = self.keychain.derive(txo.address_n) txo.address = addresses.get_address( input_script_type, self.coin, node, txo.multisig ) - assert txo.address is not None # checked in sanitize_tx_output + assert txo.address is not None # checked in _sanitize_tx_output return scripts.output_derive_script(txo.address, self.coin) diff --git a/core/src/apps/bitcoin/sign_tx/bitcoinlike.py b/core/src/apps/bitcoin/sign_tx/bitcoinlike.py index 5e4f62de59..8ab3377bed 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoinlike.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoinlike.py @@ -1,11 +1,6 @@ from typing import TYPE_CHECKING -from trezor import wire - -from apps.common.writers import write_compact_size - -from .. import multisig, writers -from ..common import NONSEGWIT_INPUT_SCRIPT_TYPES, SigHashType +from .. import writers from . import helpers from .bitcoin import Bitcoin @@ -17,6 +12,10 @@ if TYPE_CHECKING: class Bitcoinlike(Bitcoin): async def sign_nonsegwit_bip143_input(self, i_sign: int) -> None: + from trezor import wire + from .. import multisig + from ..common import NONSEGWIT_INPUT_SCRIPT_TYPES + txi = await helpers.request_tx_input(self.tx_req, i_sign, self.coin) self.tx_info.check_input(txi) self.approver.check_internal_input(txi) @@ -64,6 +63,8 @@ class Bitcoinlike(Bitcoin): ) def get_hash_type(self, txi: TxInput) -> int: + from ..common import SigHashType + hashtype = super().get_hash_type(txi) if self.coin.fork_id is not None: hashtype |= (self.coin.fork_id << 8) | SigHashType.SIGHASH_FORKID @@ -75,6 +76,8 @@ class Bitcoinlike(Bitcoin): tx: SignTx | PrevTx, witness_marker: bool, ) -> None: + from apps.common.writers import write_compact_size + writers.write_uint32(w, tx.version) # nVersion if self.coin.timestamp: assert tx.timestamp is not None # checked in sanitize_* diff --git a/core/src/apps/bitcoin/sign_tx/decred.py b/core/src/apps/bitcoin/sign_tx/decred.py index b0fe7f81b3..8054d6ce83 100644 --- a/core/src/apps/bitcoin/sign_tx/decred.py +++ b/core/src/apps/bitcoin/sign_tx/decred.py @@ -1,18 +1,18 @@ from micropython import const from typing import TYPE_CHECKING -from trezor import wire from trezor.crypto.hashlib import blake256 -from trezor.enums import DecredStakingSpendType, InputScriptType -from trezor.messages import PrevOutput -from trezor.utils import HashWriter, ensure +from trezor.enums import InputScriptType +from trezor.utils import HashWriter +from trezor.wire import DataError, ProcessError from apps.bitcoin.sign_tx.tx_weight import TxWeightCalculator from apps.common.writers import write_compact_size -from .. import multisig, scripts_decred, writers -from ..common import SigHashType, ecdsa_hash_pubkey, ecdsa_sign -from . import approvers, helpers +from .. import scripts_decred, writers +from ..common import ecdsa_hash_pubkey +from ..writers import write_uint32 +from . import helpers from .approvers import BasicApprover from .bitcoin import Bitcoin from .progress import progress @@ -35,12 +35,16 @@ if TYPE_CHECKING: TxOutput, PrevTx, PrevInput, + PrevOutput, ) from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain from .sig_hasher import SigHasher + from . import approvers + from ..common import SigHashType + from ..writers import Writer # Decred input size (without script): 32 prevhash, 4 idx, 1 Decred tree, 4 sequence @@ -144,6 +148,8 @@ class Decred(Bitcoin): coin: CoinInfo, approver: approvers.Approver | None, ) -> None: + from trezor.utils import ensure + ensure(coin.decred) self.h_prefix = HashWriter(blake256()) @@ -151,16 +157,14 @@ class Decred(Bitcoin): approver = DecredApprover(tx, coin) super().__init__(tx, keychain, coin, approver) - if self.serialize: - self.write_tx_header( - self.serialized_tx, self.tx_info.tx, witness_marker=True - ) - write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count) + tx = self.tx_info.tx # local_cache_attribute - writers.write_uint32( - self.h_prefix, self.tx_info.tx.version | _DECRED_SERIALIZE_NO_WITNESS - ) - write_compact_size(self.h_prefix, self.tx_info.tx.inputs_count) + if self.serialize: + self.write_tx_header(self.serialized_tx, tx, witness_marker=True) + write_compact_size(self.serialized_tx, tx.inputs_count) + + write_uint32(self.h_prefix, tx.version | _DECRED_SERIALIZE_NO_WITNESS) + write_compact_size(self.h_prefix, tx.inputs_count) def create_hash_writer(self) -> HashWriter: return HashWriter(blake256()) @@ -169,18 +173,20 @@ class Decred(Bitcoin): return DecredSigHasher(self.h_prefix) async def step2_approve_outputs(self) -> None: - write_compact_size(self.h_prefix, self.tx_info.tx.outputs_count) - if self.serialize: - write_compact_size(self.serialized_tx, self.tx_info.tx.outputs_count) + tx = self.tx_info.tx # local_cache_attribute - if self.tx_info.tx.decred_staking_ticket: + write_compact_size(self.h_prefix, tx.outputs_count) + if self.serialize: + write_compact_size(self.serialized_tx, tx.outputs_count) + + if tx.decred_staking_ticket: await self.approve_staking_ticket() else: await super().step2_approve_outputs() - self.write_tx_footer(self.h_prefix, self.tx_info.tx) + self.write_tx_footer(self.h_prefix, tx) if self.serialize: - self.write_tx_footer(self.serialized_tx, self.tx_info.tx) + self.write_tx_footer(self.serialized_tx, tx) async def process_internal_input(self, txi: TxInput, node: bip32.HDNode) -> None: await super().process_internal_input(txi, node) @@ -190,10 +196,10 @@ class Decred(Bitcoin): self.write_tx_input(self.serialized_tx, txi, bytes()) async def process_external_input(self, txi: TxInput) -> None: - raise wire.DataError("External inputs not supported") + raise DataError("External inputs not supported") async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None: - raise wire.DataError("Replacement transactions not supported") + raise DataError("Replacement transactions not supported") async def approve_output( self, @@ -206,15 +212,23 @@ class Decred(Bitcoin): self.write_tx_output(self.serialized_tx, txo, script_pubkey) async def step4_serialize_inputs(self) -> None: + from trezor.enums import DecredStakingSpendType + from ..common import SigHashType, ecdsa_sign + from .progress import progress + from .. import multisig + + inputs_count = self.tx_info.tx.inputs_count # local_cache_attribute + coin = self.coin # local_cache_attribute + if self.serialize: - write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count) + write_compact_size(self.serialized_tx, inputs_count) prefix_hash = self.h_prefix.get_digest() - for i_sign in range(self.tx_info.tx.inputs_count): + for i_sign in range(inputs_count): progress.advance() - txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, self.coin) + txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, coin) self.tx_info.check_input(txi_sign) @@ -222,20 +236,20 @@ class Decred(Bitcoin): key_sign_pub = key_sign.public_key() h_witness = self.create_hash_writer() - writers.write_uint32( + write_uint32( h_witness, self.tx_info.tx.version | _DECRED_SERIALIZE_WITNESS_SIGNING ) - write_compact_size(h_witness, self.tx_info.tx.inputs_count) + write_compact_size(h_witness, inputs_count) - for ii in range(self.tx_info.tx.inputs_count): + for ii in range(inputs_count): if ii == i_sign: if txi_sign.decred_staking_spend == DecredStakingSpendType.SSRTX: scripts_decred.write_output_script_ssrtx_prefixed( - h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin) + h_witness, ecdsa_hash_pubkey(key_sign_pub, coin) ) elif txi_sign.decred_staking_spend == DecredStakingSpendType.SSGen: scripts_decred.write_output_script_ssgen_prefixed( - h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin) + h_witness, ecdsa_hash_pubkey(key_sign_pub, coin) ) elif txi_sign.script_type == InputScriptType.SPENDMULTISIG: assert txi_sign.multisig is not None @@ -248,24 +262,24 @@ class Decred(Bitcoin): elif txi_sign.script_type == InputScriptType.SPENDADDRESS: scripts_decred.write_output_script_p2pkh( h_witness, - ecdsa_hash_pubkey(key_sign_pub, self.coin), + ecdsa_hash_pubkey(key_sign_pub, coin), prefixed=True, ) else: - raise wire.DataError("Unsupported input script type") + raise DataError("Unsupported input script type") else: write_compact_size(h_witness, 0) witness_hash = writers.get_tx_hash( - h_witness, double=self.coin.sign_hash_double, reverse=False + h_witness, double=coin.sign_hash_double, reverse=False ) h_sign = self.create_hash_writer() - writers.write_uint32(h_sign, SigHashType.SIGHASH_ALL) + write_uint32(h_sign, SigHashType.SIGHASH_ALL) writers.write_bytes_fixed(h_sign, prefix_hash, writers.TX_HASH_SIZE) writers.write_bytes_fixed(h_sign, witness_hash, writers.TX_HASH_SIZE) - sig_hash = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double) + sig_hash = writers.get_tx_hash(h_sign, double=coin.sign_hash_double) signature = ecdsa_sign(key_sign, sig_hash) # serialize input with correct signature @@ -289,29 +303,31 @@ class Decred(Bitcoin): def check_prevtx_output(self, txo_bin: PrevOutput) -> None: if txo_bin.decred_script_version != 0: - raise wire.ProcessError("Cannot use utxo that has script_version != 0") + raise ProcessError("Cannot use utxo that has script_version != 0") @staticmethod def write_tx_input( - w: writers.Writer, + w: Writer, txi: TxInput | PrevInput, script: bytes, ) -> None: writers.write_bytes_reversed(w, txi.prev_hash, writers.TX_HASH_SIZE) - writers.write_uint32(w, txi.prev_index or 0) + write_uint32(w, txi.prev_index or 0) writers.write_uint8(w, txi.decred_tree or 0) - writers.write_uint32(w, txi.sequence) + write_uint32(w, txi.sequence) @staticmethod def write_tx_output( - w: writers.Writer, + w: Writer, txo: TxOutput | PrevOutput, script_pubkey: bytes, ) -> None: + from trezor.messages import PrevOutput + writers.write_uint64(w, txo.amount) if PrevOutput.is_type_of(txo): if txo.decred_script_version is None: - raise wire.DataError("Script version must be provided") + raise DataError("Script version must be provided") writers.write_uint16(w, txo.decred_script_version) else: writers.write_uint16(w, _DECRED_SCRIPT_VERSION) @@ -319,7 +335,7 @@ class Decred(Bitcoin): def process_sstx_commitment_owned(self, txo: TxOutput) -> bytearray: if not self.tx_info.output_is_change(txo): - raise wire.DataError("Invalid sstxcommitment path.") + raise DataError("Invalid sstxcommitment path.") node = self.keychain.derive(txo.address_n) pkh = ecdsa_hash_pubkey(node.public_key(), self.coin) op_return_data = scripts_decred.sstxcommitment_pkh(pkh, txo.amount) @@ -327,30 +343,33 @@ class Decred(Bitcoin): return scripts_decred.output_script_paytoopreturn(op_return_data) async def approve_staking_ticket(self) -> None: - assert isinstance(self.approver, DecredApprover) + approver = self.approver # local_cache_attribute + tx_info = self.tx_info # local_cache_attribute - if self.tx_info.tx.outputs_count != 3: - raise wire.DataError("Ticket has wrong number of outputs.") + assert isinstance(approver, DecredApprover) + + if tx_info.tx.outputs_count != 3: + raise DataError("Ticket has wrong number of outputs.") # SSTX submission progress.advance() txo = await helpers.request_tx_output(self.tx_req, 0, self.coin) if txo.address is None: - raise wire.DataError("Missing address.") + raise DataError("Missing address.") script_pubkey = scripts_decred.output_script_sstxsubmissionpkh(txo.address) - await self.approver.add_decred_sstx_submission(txo, script_pubkey) - self.tx_info.add_output(txo, script_pubkey) + await approver.add_decred_sstx_submission(txo, script_pubkey) + tx_info.add_output(txo, script_pubkey) if self.serialize: self.write_tx_output(self.serialized_tx, txo, script_pubkey) # SSTX commitment progress.advance() txo = await helpers.request_tx_output(self.tx_req, 1, self.coin) - if txo.amount != self.approver.total_in: - raise wire.DataError("Wrong sstxcommitment amount.") + if txo.amount != approver.total_in: + raise DataError("Wrong sstxcommitment amount.") script_pubkey = self.process_sstx_commitment_owned(txo) - self.approver.add_change_output(txo, script_pubkey) - self.tx_info.add_output(txo, script_pubkey) + approver.add_change_output(txo, script_pubkey) + tx_info.add_output(txo, script_pubkey) if self.serialize: self.write_tx_output(self.serialized_tx, txo, script_pubkey) @@ -358,23 +377,23 @@ class Decred(Bitcoin): progress.advance() txo = await helpers.request_tx_output(self.tx_req, 2, self.coin) if txo.address is None: - raise wire.DataError("Missing address.") + raise DataError("Missing address.") script_pubkey = scripts_decred.output_script_sstxchange(txo.address) # Using change addresses is no longer common practice. Inputs are split # beforehand and should be exact. SSTX change should pay zero amount to # a zeroed hash. if txo.amount != 0: - raise wire.DataError("Only value of 0 allowed for sstx change.") + raise DataError("Only value of 0 allowed for sstx change.") if script_pubkey != OUTPUT_SCRIPT_NULL_SSTXCHANGE: - raise wire.DataError("Only zeroed addresses accepted for sstx change.") - self.approver.add_change_output(txo, script_pubkey) - self.tx_info.add_output(txo, script_pubkey) + raise DataError("Only zeroed addresses accepted for sstx change.") + approver.add_change_output(txo, script_pubkey) + tx_info.add_output(txo, script_pubkey) if self.serialize: self.write_tx_output(self.serialized_tx, txo, script_pubkey) def write_tx_header( self, - w: writers.Writer, + w: Writer, tx: SignTx | PrevTx, witness_marker: bool, ) -> None: @@ -385,19 +404,19 @@ class Decred(Bitcoin): else: version = tx.version | _DECRED_SERIALIZE_NO_WITNESS - writers.write_uint32(w, version) + write_uint32(w, version) - def write_tx_footer(self, w: writers.Writer, tx: SignTx | PrevTx) -> None: + def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None: assert tx.expiry is not None # checked in sanitize_* - writers.write_uint32(w, tx.lock_time) - writers.write_uint32(w, tx.expiry) + write_uint32(w, tx.lock_time) + write_uint32(w, tx.expiry) def write_tx_input_witness( - self, w: writers.Writer, txi: TxInput, pubkey: bytes, signature: bytes + self, w: Writer, txi: TxInput, pubkey: bytes, signature: bytes ) -> None: writers.write_uint64(w, txi.amount) - writers.write_uint32(w, 0) # block height fraud proof - writers.write_uint32(w, 0xFFFF_FFFF) # block index fraud proof + write_uint32(w, 0) # block height fraud proof + write_uint32(w, 0xFFFF_FFFF) # block index fraud proof scripts_decred.write_input_script_prefixed( w, txi.script_type, diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index ebd79230f0..4601e22af0 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -1,19 +1,8 @@ from typing import TYPE_CHECKING -from trezor import utils, wire -from trezor.enums import InputScriptType, OutputScriptType, RequestType -from trezor.messages import ( - TxAckInput, - TxAckOutput, - TxAckPaymentRequest, - TxAckPrevExtraData, - TxAckPrevInput, - TxAckPrevMeta, - TxAckPrevOutput, -) - -from apps.common import paths -from apps.common.coininfo import CoinInfo +from trezor import utils +from trezor.enums import RequestType +from trezor.wire import DataError from .. import common from ..writers import TX_HASH_SIZE @@ -22,6 +11,7 @@ from . import layout if TYPE_CHECKING: from typing import Any, Awaitable from trezor.enums import AmountUnit + from trezor.wire import Context from trezor.messages import ( PrevInput, @@ -31,14 +21,16 @@ if TYPE_CHECKING: TxInput, TxOutput, TxRequest, + TxAckPaymentRequest, ) + from apps.common.coininfo import CoinInfo # Machine instructions # === class UiConfirm: - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: raise NotImplementedError __eq__ = utils.obj_eq @@ -50,7 +42,7 @@ class UiConfirmOutput(UiConfirm): self.coin = coin self.amount_unit = amount_unit - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_output(ctx, self.output, self.coin, self.amount_unit) @@ -60,7 +52,7 @@ class UiConfirmDecredSSTXSubmission(UiConfirm): self.coin = coin self.amount_unit = amount_unit - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_decred_sstx_submission( ctx, self.output, self.coin, self.amount_unit ) @@ -77,7 +69,7 @@ class UiConfirmPaymentRequest(UiConfirm): self.amount_unit = amount_unit self.coin = coin - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_payment_request( ctx, self.payment_req, self.coin, self.amount_unit ) @@ -90,7 +82,7 @@ class UiConfirmReplacement(UiConfirm): self.description = description self.txid = txid - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_replacement(ctx, self.description, self.txid) @@ -107,7 +99,7 @@ class UiConfirmModifyOutput(UiConfirm): self.coin = coin self.amount_unit = amount_unit - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_modify_output( ctx, self.txo, self.orig_txo, self.coin, self.amount_unit ) @@ -128,7 +120,7 @@ class UiConfirmModifyFee(UiConfirm): self.coin = coin self.amount_unit = amount_unit - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_modify_fee( ctx, self.user_fee_change, @@ -154,7 +146,7 @@ class UiConfirmTotal(UiConfirm): self.coin = coin self.amount_unit = amount_unit - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_total( ctx, self.spending, self.fee, self.fee_rate, self.coin, self.amount_unit ) @@ -169,7 +161,7 @@ class UiConfirmJointTotal(UiConfirm): self.coin = coin self.amount_unit = amount_unit - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_joint_total( ctx, self.spending, self.total, self.coin, self.amount_unit ) @@ -181,7 +173,7 @@ class UiConfirmFeeOverThreshold(UiConfirm): self.coin = coin self.amount_unit = amount_unit - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_feeoverthreshold( ctx, self.fee, self.coin, self.amount_unit ) @@ -191,12 +183,12 @@ class UiConfirmChangeCountOverThreshold(UiConfirm): def __init__(self, change_count: int): self.change_count = change_count - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_change_count_over_threshold(ctx, self.change_count) class UiConfirmUnverifiedExternalInput(UiConfirm): - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_unverified_external_input(ctx) @@ -204,7 +196,9 @@ class UiConfirmForeignAddress(UiConfirm): def __init__(self, address_n: list): self.address_n = address_n - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: + from apps.common import paths + return paths.show_path_warning(ctx, self.address_n) @@ -213,7 +207,7 @@ class UiConfirmNonDefaultLocktime(UiConfirm): self.lock_time = lock_time self.lock_time_disabled = lock_time_disabled - def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]: + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: return layout.confirm_nondefault_locktime( ctx, self.lock_time, self.lock_time_disabled ) @@ -276,28 +270,36 @@ def confirm_nondefault_locktime(lock_time: int, lock_time_disabled: bool) -> Awa def request_tx_meta(tx_req: TxRequest, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[PrevTx]: # type: ignore [awaitable-is-generator] + from trezor.messages import TxAckPrevMeta + assert tx_req.details is not None tx_req.request_type = RequestType.TXMETA tx_req.details.tx_hash = tx_hash ack = yield TxAckPrevMeta, tx_req _clear_tx_request(tx_req) - return sanitize_tx_meta(ack.tx, coin) + return _sanitize_tx_meta(ack.tx, coin) def request_tx_extra_data( tx_req: TxRequest, offset: int, size: int, tx_hash: bytes | None = None ) -> Awaitable[bytearray]: # type: ignore [awaitable-is-generator] - assert tx_req.details is not None + from trezor.messages import TxAckPrevExtraData + + details = tx_req.details # local_cache_attribute + + assert details is not None tx_req.request_type = RequestType.TXEXTRADATA - tx_req.details.extra_data_offset = offset - tx_req.details.extra_data_len = size - tx_req.details.tx_hash = tx_hash + details.extra_data_offset = offset + details.extra_data_len = size + details.tx_hash = tx_hash ack = yield TxAckPrevExtraData, tx_req _clear_tx_request(tx_req) return ack.tx.extra_data_chunk def request_tx_input(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[TxInput]: # type: ignore [awaitable-is-generator] + from trezor.messages import TxAckInput + assert tx_req.details is not None if tx_hash: tx_req.request_type = RequestType.TXORIGINPUT @@ -307,20 +309,24 @@ def request_tx_input(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | tx_req.details.request_index = i ack = yield TxAckInput, tx_req _clear_tx_request(tx_req) - return sanitize_tx_input(ack.tx.input, coin) + return _sanitize_tx_input(ack.tx.input, coin) def request_tx_prev_input(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[PrevInput]: # type: ignore [awaitable-is-generator] + from trezor.messages import TxAckPrevInput + assert tx_req.details is not None tx_req.request_type = RequestType.TXINPUT tx_req.details.request_index = i tx_req.details.tx_hash = tx_hash ack = yield TxAckPrevInput, tx_req _clear_tx_request(tx_req) - return sanitize_tx_prev_input(ack.tx.input, coin) + return _sanitize_tx_prev_input(ack.tx.input, coin) def request_tx_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[TxOutput]: # type: ignore [awaitable-is-generator] + from trezor.messages import TxAckOutput + assert tx_req.details is not None if tx_hash: tx_req.request_type = RequestType.TXORIGOUTPUT @@ -330,10 +336,12 @@ def request_tx_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes tx_req.details.request_index = i ack = yield TxAckOutput, tx_req _clear_tx_request(tx_req) - return sanitize_tx_output(ack.tx.output, coin) + return _sanitize_tx_output(ack.tx.output, coin) def request_tx_prev_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[PrevOutput]: # type: ignore [awaitable-is-generator] + from trezor.messages import TxAckPrevOutput + assert tx_req.details is not None tx_req.request_type = RequestType.TXOUTPUT tx_req.details.request_index = i @@ -345,12 +353,14 @@ def request_tx_prev_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: b def request_payment_req(tx_req: TxRequest, i: int) -> Awaitable[TxAckPaymentRequest]: # type: ignore [awaitable-is-generator] + from trezor.messages import TxAckPaymentRequest + assert tx_req.details is not None tx_req.request_type = RequestType.TXPAYMENTREQ tx_req.details.request_index = i ack = yield TxAckPaymentRequest, tx_req _clear_tx_request(tx_req) - return sanitize_payment_req(ack) + return _sanitize_payment_req(ack) def request_tx_finish(tx_req: TxRequest) -> Awaitable[None]: # type: ignore [awaitable-is-generator] @@ -360,19 +370,22 @@ def request_tx_finish(tx_req: TxRequest) -> Awaitable[None]: # type: ignore [aw def _clear_tx_request(tx_req: TxRequest) -> None: - assert tx_req.details is not None - assert tx_req.serialized is not None - assert tx_req.serialized.serialized_tx is not None + details = tx_req.details # local_cache_attribute + serialized = tx_req.serialized # local_cache_attribute + + assert details is not None + assert serialized is not None + assert serialized.serialized_tx is not None tx_req.request_type = None - tx_req.details.request_index = None - tx_req.details.tx_hash = None - tx_req.details.extra_data_len = None - tx_req.details.extra_data_offset = None - tx_req.serialized.signature = None - tx_req.serialized.signature_index = None + details.request_index = None + details.tx_hash = None + details.extra_data_len = None + details.extra_data_offset = None + serialized.signature = None + serialized.signature_index = None # typechecker thinks serialized_tx is `bytes`, which is immutable # we know that it is `bytearray` in reality - tx_req.serialized.serialized_tx[:] = bytes() # type: ignore ["__setitem__" method not defined on type "bytes"] + serialized.serialized_tx[:] = bytes() # type: ignore ["__setitem__" method not defined on type "bytes"] # Data sanitizers @@ -383,149 +396,158 @@ def sanitize_sign_tx(tx: SignTx, coin: CoinInfo) -> SignTx: if coin.decred or coin.overwintered: tx.expiry = tx.expiry if tx.expiry is not None else 0 elif tx.expiry: - raise wire.DataError("Expiry not enabled on this coin.") + raise DataError("Expiry not enabled on this coin.") if coin.timestamp and not tx.timestamp: - raise wire.DataError("Timestamp must be set.") + raise DataError("Timestamp must be set.") elif not coin.timestamp and tx.timestamp: - raise wire.DataError("Timestamp not enabled on this coin.") + raise DataError("Timestamp not enabled on this coin.") if coin.overwintered: if tx.version_group_id is None: - raise wire.DataError("Version group ID must be set.") + raise DataError("Version group ID must be set.") if tx.branch_id is None: - raise wire.DataError("Branch ID must be set.") + raise DataError("Branch ID must be set.") elif not coin.overwintered: if tx.version_group_id is not None: - raise wire.DataError("Version group ID not enabled on this coin.") + raise DataError("Version group ID not enabled on this coin.") if tx.branch_id is not None: - raise wire.DataError("Branch ID not enabled on this coin.") + raise DataError("Branch ID not enabled on this coin.") return tx -def sanitize_tx_meta(tx: PrevTx, coin: CoinInfo) -> PrevTx: +def _sanitize_tx_meta(tx: PrevTx, coin: CoinInfo) -> PrevTx: if not coin.extra_data and tx.extra_data_len: - raise wire.DataError("Extra data not enabled on this coin.") + raise DataError("Extra data not enabled on this coin.") if coin.decred or coin.overwintered: tx.expiry = tx.expiry if tx.expiry is not None else 0 elif tx.expiry: - raise wire.DataError("Expiry not enabled on this coin.") + raise DataError("Expiry not enabled on this coin.") if coin.timestamp and not tx.timestamp: - raise wire.DataError("Timestamp must be set.") + raise DataError("Timestamp must be set.") elif not coin.timestamp and tx.timestamp: - raise wire.DataError("Timestamp not enabled on this coin.") + raise DataError("Timestamp not enabled on this coin.") elif not coin.overwintered: if tx.version_group_id is not None: - raise wire.DataError("Version group ID not enabled on this coin.") + raise DataError("Version group ID not enabled on this coin.") if tx.branch_id is not None: - raise wire.DataError("Branch ID not enabled on this coin.") + raise DataError("Branch ID not enabled on this coin.") return tx -def sanitize_tx_input(txi: TxInput, coin: CoinInfo) -> TxInput: +def _sanitize_tx_input(txi: TxInput, coin: CoinInfo) -> TxInput: + from trezor.enums import InputScriptType + from trezor.wire import DataError # local_cache_global + + script_type = txi.script_type # local_cache_attribute + if len(txi.prev_hash) != TX_HASH_SIZE: - raise wire.DataError("Provided prev_hash is invalid.") + raise DataError("Provided prev_hash is invalid.") - if txi.multisig and txi.script_type not in common.MULTISIG_INPUT_SCRIPT_TYPES: - raise wire.DataError("Multisig field provided but not expected.") + if txi.multisig and script_type not in common.MULTISIG_INPUT_SCRIPT_TYPES: + raise DataError("Multisig field provided but not expected.") - if not txi.multisig and txi.script_type == InputScriptType.SPENDMULTISIG: - raise wire.DataError("Multisig details required.") + if not txi.multisig and script_type == InputScriptType.SPENDMULTISIG: + raise DataError("Multisig details required.") - if txi.script_type in common.INTERNAL_INPUT_SCRIPT_TYPES: + if script_type in common.INTERNAL_INPUT_SCRIPT_TYPES: if not txi.address_n: - raise wire.DataError("Missing address_n field.") + raise DataError("Missing address_n field.") if txi.script_pubkey: - raise wire.DataError("Input's script_pubkey provided but not expected.") + raise DataError("Input's script_pubkey provided but not expected.") else: if txi.address_n: - raise wire.DataError("Input's address_n provided but not expected.") + raise DataError("Input's address_n provided but not expected.") if not txi.script_pubkey: - raise wire.DataError("Missing script_pubkey field.") + raise DataError("Missing script_pubkey field.") if not coin.decred and txi.decred_tree is not None: - raise wire.DataError("Decred details provided but Decred coin not specified.") + raise DataError("Decred details provided but Decred coin not specified.") - if txi.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES or txi.witness is not None: + if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES or txi.witness is not None: if not coin.segwit: - raise wire.DataError("Segwit not enabled on this coin.") + raise DataError("Segwit not enabled on this coin.") - if txi.script_type == InputScriptType.SPENDTAPROOT and not coin.taproot: - raise wire.DataError("Taproot not enabled on this coin") + if script_type == InputScriptType.SPENDTAPROOT and not coin.taproot: + raise DataError("Taproot not enabled on this coin") if txi.commitment_data and not txi.ownership_proof: - raise wire.DataError("commitment_data field provided but not expected.") + raise DataError("commitment_data field provided but not expected.") if txi.orig_hash and txi.orig_index is None: - raise wire.DataError("Missing orig_index field.") + raise DataError("Missing orig_index field.") return txi -def sanitize_tx_prev_input(txi: PrevInput, coin: CoinInfo) -> PrevInput: +def _sanitize_tx_prev_input(txi: PrevInput, coin: CoinInfo) -> PrevInput: if len(txi.prev_hash) != TX_HASH_SIZE: - raise wire.DataError("Provided prev_hash is invalid.") + raise DataError("Provided prev_hash is invalid.") if not coin.decred and txi.decred_tree is not None: - raise wire.DataError("Decred details provided but Decred coin not specified.") + raise DataError("Decred details provided but Decred coin not specified.") return txi -def sanitize_tx_output(txo: TxOutput, coin: CoinInfo) -> TxOutput: - if txo.multisig and txo.script_type not in common.MULTISIG_OUTPUT_SCRIPT_TYPES: - raise wire.DataError("Multisig field provided but not expected.") +def _sanitize_tx_output(txo: TxOutput, coin: CoinInfo) -> TxOutput: + from trezor.enums import OutputScriptType + from trezor.wire import DataError # local_cache_global - if not txo.multisig and txo.script_type == OutputScriptType.PAYTOMULTISIG: - raise wire.DataError("Multisig details required.") + script_type = txo.script_type # local_cache_attribute + address_n = txo.address_n # local_cache_attribute - if txo.address_n and txo.script_type not in common.CHANGE_OUTPUT_SCRIPT_TYPES: - raise wire.DataError("Output's address_n provided but not expected.") + if txo.multisig and script_type not in common.MULTISIG_OUTPUT_SCRIPT_TYPES: + raise DataError("Multisig field provided but not expected.") + + if not txo.multisig and script_type == OutputScriptType.PAYTOMULTISIG: + raise DataError("Multisig details required.") + + if address_n and script_type not in common.CHANGE_OUTPUT_SCRIPT_TYPES: + raise DataError("Output's address_n provided but not expected.") if txo.amount is None: - raise wire.DataError("Missing amount field.") + raise DataError("Missing amount field.") - if txo.script_type in common.SEGWIT_OUTPUT_SCRIPT_TYPES: + if script_type in common.SEGWIT_OUTPUT_SCRIPT_TYPES: if not coin.segwit: - raise wire.DataError("Segwit not enabled on this coin.") + raise DataError("Segwit not enabled on this coin.") - if txo.script_type == OutputScriptType.PAYTOTAPROOT and not coin.taproot: - raise wire.DataError("Taproot not enabled on this coin") + if script_type == OutputScriptType.PAYTOTAPROOT and not coin.taproot: + raise DataError("Taproot not enabled on this coin") - if txo.script_type == OutputScriptType.PAYTOOPRETURN: + if script_type == OutputScriptType.PAYTOOPRETURN: # op_return output if txo.op_return_data is None: - raise wire.DataError("OP_RETURN output without op_return_data") + raise DataError("OP_RETURN output without op_return_data") if txo.amount != 0: - raise wire.DataError("OP_RETURN output with non-zero amount") - if txo.address or txo.address_n or txo.multisig: - raise wire.DataError("OP_RETURN output with address or multisig") + raise DataError("OP_RETURN output with non-zero amount") + if txo.address or address_n or txo.multisig: + raise DataError("OP_RETURN output with address or multisig") else: if txo.op_return_data: - raise wire.DataError( - "OP RETURN data provided but not OP RETURN script type." - ) - if txo.address_n and txo.address: - raise wire.DataError("Both address and address_n provided.") - if not txo.address_n and not txo.address: - raise wire.DataError("Missing address") + raise DataError("OP RETURN data provided but not OP RETURN script type.") + if address_n and txo.address: + raise DataError("Both address and address_n provided.") + if not address_n and not txo.address: + raise DataError("Missing address") if txo.orig_hash and txo.orig_index is None: - raise wire.DataError("Missing orig_index field.") + raise DataError("Missing orig_index field.") return txo -def sanitize_payment_req(payment_req: TxAckPaymentRequest) -> TxAckPaymentRequest: +def _sanitize_payment_req(payment_req: TxAckPaymentRequest) -> TxAckPaymentRequest: for memo in payment_req.memos: if (memo.text_memo, memo.refund_memo, memo.coin_purchase_memo).count(None) != 2: - raise wire.DataError( + raise DataError( "Exactly one memo type must be specified in each PaymentRequestMemo." ) diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index be0110f3c0..820df55ce1 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -1,15 +1,14 @@ from micropython import const from typing import TYPE_CHECKING -from ubinascii import hexlify -from trezor import ui, utils, wire -from trezor.enums import AmountUnit, ButtonRequestType, OutputScriptType -from trezor.strings import format_amount, format_timestamp +from trezor import utils +from trezor.enums import ButtonRequestType +from trezor.strings import format_amount from trezor.ui import layouts +from trezor.ui.layouts import confirm_metadata from .. import addresses from ..common import format_fee_rate -from . import omni if not utils.BITCOIN_ONLY: from trezor.ui.layouts import altcoin @@ -20,6 +19,8 @@ if TYPE_CHECKING: from trezor.messages import TxAckPaymentRequest, TxOutput from trezor.ui.layouts import LayoutType + from trezor.enums import AmountUnit + from trezor.wire import Context from apps.common.coininfo import CoinInfo @@ -27,6 +28,8 @@ _LOCKTIME_TIMESTAMP_MIN_VALUE = const(500_000_000) def format_coin_amount(amount: int, coin: CoinInfo, amount_unit: AmountUnit) -> str: + from trezor.enums import AmountUnit + decimals, shortcut = coin.decimals, coin.coin_shortcut if amount_unit == AmountUnit.SATOSHI: decimals = 0 @@ -44,14 +47,18 @@ def format_coin_amount(amount: int, coin: CoinInfo, amount_unit: AmountUnit) -> async def confirm_output( - ctx: wire.Context, output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit + ctx: Context, output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit ) -> None: + from trezor import ui + from . import omni + from trezor.enums import OutputScriptType + if output.script_type == OutputScriptType.PAYTOOPRETURN: data = output.op_return_data assert data is not None if omni.is_valid(data): # OMNI transaction - layout: LayoutType = layouts.confirm_metadata( + layout: LayoutType = confirm_metadata( ctx, "omni_transaction", "OMNI transaction", @@ -63,8 +70,8 @@ async def confirm_output( layout = layouts.confirm_blob( ctx, "op_return", - title="OP_RETURN", - data=data, + "OP_RETURN", + data, br_code=ButtonRequestType.ConfirmOutput, ) else: @@ -89,7 +96,7 @@ async def confirm_output( async def confirm_decred_sstx_submission( - ctx: wire.Context, output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit + ctx: Context, output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit ) -> None: assert output.address is not None address_short = addresses.address_short(coin, output.address) @@ -100,11 +107,13 @@ async def confirm_decred_sstx_submission( async def confirm_payment_request( - ctx: wire.Context, + ctx: Context, msg: TxAckPaymentRequest, coin: CoinInfo, amount_unit: AmountUnit, ) -> Any: + from trezor import wire + memo_texts = [] for m in msg.memos: if m.text_memo is not None: @@ -126,7 +135,9 @@ async def confirm_payment_request( ) -async def confirm_replacement(ctx: wire.Context, description: str, txid: bytes) -> None: +async def confirm_replacement(ctx: Context, description: str, txid: bytes) -> None: + from ubinascii import hexlify + await layouts.confirm_replacement( ctx, description, @@ -135,7 +146,7 @@ async def confirm_replacement(ctx: wire.Context, description: str, txid: bytes) async def confirm_modify_output( - ctx: wire.Context, + ctx: Context, txo: TxOutput, orig_txo: TxOutput, coin: CoinInfo, @@ -154,7 +165,7 @@ async def confirm_modify_output( async def confirm_modify_fee( - ctx: wire.Context, + ctx: Context, user_fee_change: int, total_fee_new: int, fee_rate: float, @@ -171,7 +182,7 @@ async def confirm_modify_fee( async def confirm_joint_total( - ctx: wire.Context, + ctx: Context, spending: int, total: int, coin: CoinInfo, @@ -185,7 +196,7 @@ async def confirm_joint_total( async def confirm_total( - ctx: wire.Context, + ctx: Context, spending: int, fee: int, fee_rate: float, @@ -194,17 +205,17 @@ async def confirm_total( ) -> None: await layouts.confirm_total( ctx, - total_amount=format_coin_amount(spending, coin, amount_unit), - fee_amount=format_coin_amount(fee, coin, amount_unit), + format_coin_amount(spending, coin, amount_unit), + format_coin_amount(fee, coin, amount_unit), fee_rate_amount=format_fee_rate(fee_rate, coin) if fee_rate >= 0 else None, ) async def confirm_feeoverthreshold( - ctx: wire.Context, fee: int, coin: CoinInfo, amount_unit: AmountUnit + ctx: Context, fee: int, coin: CoinInfo, amount_unit: AmountUnit ) -> None: fee_amount = format_coin_amount(fee, coin, amount_unit) - await layouts.confirm_metadata( + await confirm_metadata( ctx, "fee_over_threshold", "High fee", @@ -214,10 +225,8 @@ async def confirm_feeoverthreshold( ) -async def confirm_change_count_over_threshold( - ctx: wire.Context, change_count: int -) -> None: - await layouts.confirm_metadata( +async def confirm_change_count_over_threshold(ctx: Context, change_count: int) -> None: + await confirm_metadata( ctx, "change_count_over_threshold", "Warning", @@ -227,8 +236,8 @@ async def confirm_change_count_over_threshold( ) -async def confirm_unverified_external_input(ctx: wire.Context) -> None: - await layouts.confirm_metadata( +async def confirm_unverified_external_input(ctx: Context) -> None: + await confirm_metadata( ctx, "unverified_external_input", "Warning", @@ -238,8 +247,10 @@ async def confirm_unverified_external_input(ctx: wire.Context) -> None: async def confirm_nondefault_locktime( - ctx: wire.Context, lock_time: int, lock_time_disabled: bool + ctx: Context, lock_time: int, lock_time_disabled: bool ) -> None: + from trezor.strings import format_timestamp + if lock_time_disabled: title = "Warning" text = "Locktime is set but will\nhave no effect.\n" @@ -253,7 +264,7 @@ async def confirm_nondefault_locktime( text = "Locktime for this\ntransaction is set to:\n{}" param = format_timestamp(lock_time) - await layouts.confirm_metadata( + await confirm_metadata( ctx, "nondefault_locktime", title, diff --git a/core/src/apps/bitcoin/sign_tx/matchcheck.py b/core/src/apps/bitcoin/sign_tx/matchcheck.py index 39fa3554fd..d0b6ebf60c 100644 --- a/core/src/apps/bitcoin/sign_tx/matchcheck.py +++ b/core/src/apps/bitcoin/sign_tx/matchcheck.py @@ -1,11 +1,5 @@ from typing import TYPE_CHECKING -from trezor import wire -from trezor.utils import ensure - -from .. import multisig -from ..common import BIP32_WALLET_DEPTH - if TYPE_CHECKING: from typing import Any, Generic, TypeVar @@ -54,6 +48,8 @@ class MatchChecker(Generic[T]): raise NotImplementedError def add_input(self, txi: TxInput) -> None: + from trezor.utils import ensure + ensure(not self.read_only) if self.attribute is self.MISMATCH: @@ -68,6 +64,8 @@ class MatchChecker(Generic[T]): self.attribute = self.MISMATCH def check_input(self, txi: TxInput) -> None: + from trezor import wire + if self.attribute is self.MISMATCH: return # There was already a mismatch when adding inputs, ignore it now. @@ -87,6 +85,8 @@ class MatchChecker(Generic[T]): class WalletPathChecker(MatchChecker): def attribute_from_tx(self, txio: TxInput | TxOutput) -> Any: + from ..common import BIP32_WALLET_DEPTH + if len(txio.address_n) < BIP32_WALLET_DEPTH: return None return txio.address_n[:-BIP32_WALLET_DEPTH] @@ -94,6 +94,8 @@ class WalletPathChecker(MatchChecker): class MultisigFingerprintChecker(MatchChecker): def attribute_from_tx(self, txio: TxInput | TxOutput) -> Any: + from .. import multisig + if not txio.multisig: return None return multisig.multisig_fingerprint(txio.multisig) diff --git a/core/src/apps/bitcoin/sign_tx/omni.py b/core/src/apps/bitcoin/sign_tx/omni.py index a128dd7e7a..870bb3bdcd 100644 --- a/core/src/apps/bitcoin/sign_tx/omni.py +++ b/core/src/apps/bitcoin/sign_tx/omni.py @@ -1,7 +1,4 @@ from micropython import const -from ustruct import unpack - -from trezor.strings import format_amount _OMNI_DECIMALS = const(8) @@ -18,6 +15,9 @@ def is_valid(data: bytes) -> bool: def parse(data: bytes) -> str: + from ustruct import unpack + from trezor.strings import format_amount + if not is_valid(data): raise ValueError # tried to parse data that fails validation tx_version, tx_type = unpack(">HH", data[4:8]) diff --git a/core/src/apps/bitcoin/sign_tx/payment_request.py b/core/src/apps/bitcoin/sign_tx/payment_request.py index 4935d87634..d0c69bf02d 100644 --- a/core/src/apps/bitcoin/sign_tx/payment_request.py +++ b/core/src/apps/bitcoin/sign_tx/payment_request.py @@ -1,20 +1,14 @@ from micropython import const from typing import TYPE_CHECKING -from storage import cache -from trezor import wire -from trezor.crypto.curve import secp256k1 -from trezor.crypto.hashlib import sha256 -from trezor.utils import HashWriter - -from apps.common import coininfo -from apps.common.address_mac import check_address_mac -from apps.common.keychain import Keychain +from trezor.wire import DataError from .. import writers if TYPE_CHECKING: from trezor.messages import TxAckPaymentRequest, TxOutput + from apps.common import coininfo + from apps.common.keychain import Keychain _MEMO_TYPE_TEXT = const(1) _MEMO_TYPE_REFUND = const(2) @@ -31,6 +25,12 @@ class PaymentRequestVerifier: def __init__( self, msg: TxAckPaymentRequest, coin: coininfo.CoinInfo, keychain: Keychain ) -> None: + from storage import cache + from trezor.crypto.hashlib import sha256 + from trezor.utils import HashWriter + from apps.common.address_mac import check_address_mac + from .. import writers # pylint: disable=import-outside-toplevel + self.h_outputs = HashWriter(sha256()) self.amount = 0 self.expected_amount = msg.amount @@ -40,12 +40,12 @@ class PaymentRequestVerifier: if msg.nonce: nonce = bytes(msg.nonce) if cache.get(cache.APP_COMMON_NONCE) != nonce: - raise wire.DataError("Invalid nonce in payment request.") + raise DataError("Invalid nonce in payment request.") cache.delete(cache.APP_COMMON_NONCE) else: nonce = b"" if msg.memos: - wire.DataError("Missing nonce in payment request.") + DataError("Missing nonce in payment request.") writers.write_bytes_fixed(self.h_pr, b"SL\x00\x24", 4) writers.write_bytes_prefixed(self.h_pr, nonce) @@ -73,8 +73,10 @@ class PaymentRequestVerifier: writers.write_uint32(self.h_pr, coin.slip44) def verify(self) -> None: + from trezor.crypto.curve import secp256k1 + if self.expected_amount is not None and self.amount != self.expected_amount: - raise wire.DataError("Invalid amount in payment request.") + raise DataError("Invalid amount in payment request.") hash_outputs = writers.get_tx_hash(self.h_outputs) writers.write_bytes_fixed(self.h_pr, hash_outputs, 32) @@ -82,7 +84,7 @@ class PaymentRequestVerifier: if not secp256k1.verify( self.PUBLIC_KEY, self.signature, self.h_pr.get_digest() ): - raise wire.DataError("Invalid signature in payment request.") + raise DataError("Invalid signature in payment request.") def _add_output(self, txo: TxOutput) -> None: # For change outputs txo.address filled in by output_derive_script(). diff --git a/core/src/apps/bitcoin/sign_tx/sig_hasher.py b/core/src/apps/bitcoin/sign_tx/sig_hasher.py index 5a41704e14..167d8e0e16 100644 --- a/core/src/apps/bitcoin/sign_tx/sig_hasher.py +++ b/core/src/apps/bitcoin/sign_tx/sig_hasher.py @@ -1,17 +1,18 @@ from typing import TYPE_CHECKING -from trezor.crypto.hashlib import sha256 -from trezor.utils import HashWriter - -from apps.common import coininfo - -from .. import scripts, writers -from ..common import tagged_hashwriter +from ..writers import ( + TX_HASH_SIZE, + write_bytes_fixed, + write_bytes_reversed, + write_uint32, + write_uint64, +) if TYPE_CHECKING: from typing import Protocol, Sequence from ..common import SigHashType from trezor.messages import PrevTx, SignTx, TxInput, TxOutput + from apps.common import coininfo class SigHasher(Protocol): def add_input(self, txi: TxInput, script_pubkey: bytes) -> None: @@ -50,6 +51,9 @@ if TYPE_CHECKING: # BIP-0143 hash class BitcoinSigHasher: def __init__(self) -> None: + from trezor.crypto.hashlib import sha256 + from trezor.utils import HashWriter + self.h_prevouts = HashWriter(sha256()) self.h_amounts = HashWriter(sha256()) self.h_scriptpubkeys = HashWriter(sha256()) @@ -57,16 +61,18 @@ class BitcoinSigHasher: self.h_outputs = HashWriter(sha256()) def add_input(self, txi: TxInput, script_pubkey: bytes) -> None: - writers.write_bytes_reversed( - self.h_prevouts, txi.prev_hash, writers.TX_HASH_SIZE - ) - writers.write_uint32(self.h_prevouts, txi.prev_index) - writers.write_uint64(self.h_amounts, txi.amount) - writers.write_bytes_prefixed(self.h_scriptpubkeys, script_pubkey) - writers.write_uint32(self.h_sequences, txi.sequence) + from ..writers import write_bytes_prefixed + + write_bytes_reversed(self.h_prevouts, txi.prev_hash, TX_HASH_SIZE) + write_uint32(self.h_prevouts, txi.prev_index) + write_uint64(self.h_amounts, txi.amount) + write_bytes_prefixed(self.h_scriptpubkeys, script_pubkey) + write_uint32(self.h_sequences, txi.sequence) def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None: - writers.write_tx_output(self.h_outputs, txo, script_pubkey) + from ..writers import write_tx_output + + write_tx_output(self.h_outputs, txo, script_pubkey) def hash143( self, @@ -77,26 +83,27 @@ class BitcoinSigHasher: coin: coininfo.CoinInfo, hash_type: int, ) -> bytes: + from trezor.crypto.hashlib import sha256 + from trezor.utils import HashWriter + from .. import scripts + from ..writers import get_tx_hash + h_preimage = HashWriter(sha256()) # nVersion - writers.write_uint32(h_preimage, tx.version) + write_uint32(h_preimage, tx.version) # hashPrevouts - prevouts_hash = writers.get_tx_hash( - self.h_prevouts, double=coin.sign_hash_double - ) - writers.write_bytes_fixed(h_preimage, prevouts_hash, writers.TX_HASH_SIZE) + prevouts_hash = get_tx_hash(self.h_prevouts, double=coin.sign_hash_double) + write_bytes_fixed(h_preimage, prevouts_hash, TX_HASH_SIZE) # hashSequence - sequence_hash = writers.get_tx_hash( - self.h_sequences, double=coin.sign_hash_double - ) - writers.write_bytes_fixed(h_preimage, sequence_hash, writers.TX_HASH_SIZE) + sequence_hash = get_tx_hash(self.h_sequences, double=coin.sign_hash_double) + write_bytes_fixed(h_preimage, sequence_hash, TX_HASH_SIZE) # outpoint - writers.write_bytes_reversed(h_preimage, txi.prev_hash, writers.TX_HASH_SIZE) - writers.write_uint32(h_preimage, txi.prev_index) + write_bytes_reversed(h_preimage, txi.prev_hash, TX_HASH_SIZE) + write_uint32(h_preimage, txi.prev_index) # scriptCode scripts.write_bip143_script_code_prefixed( @@ -104,22 +111,22 @@ class BitcoinSigHasher: ) # amount - writers.write_uint64(h_preimage, txi.amount) + write_uint64(h_preimage, txi.amount) # nSequence - writers.write_uint32(h_preimage, txi.sequence) + write_uint32(h_preimage, txi.sequence) # hashOutputs - outputs_hash = writers.get_tx_hash(self.h_outputs, double=coin.sign_hash_double) - writers.write_bytes_fixed(h_preimage, outputs_hash, writers.TX_HASH_SIZE) + outputs_hash = get_tx_hash(self.h_outputs, double=coin.sign_hash_double) + write_bytes_fixed(h_preimage, outputs_hash, TX_HASH_SIZE) # nLockTime - writers.write_uint32(h_preimage, tx.lock_time) + write_uint32(h_preimage, tx.lock_time) # nHashType - writers.write_uint32(h_preimage, hash_type) + write_uint32(h_preimage, hash_type) - return writers.get_tx_hash(h_preimage, double=coin.sign_hash_double) + return get_tx_hash(h_preimage, double=coin.sign_hash_double) def hash341( self, @@ -127,50 +134,43 @@ class BitcoinSigHasher: tx: SignTx | PrevTx, sighash_type: SigHashType, ) -> bytes: + from ..common import tagged_hashwriter + from ..writers import write_uint8 + h_sigmsg = tagged_hashwriter(b"TapSighash") # sighash epoch 0 - writers.write_uint8(h_sigmsg, 0) + write_uint8(h_sigmsg, 0) # nHashType - writers.write_uint8(h_sigmsg, sighash_type & 0xFF) + write_uint8(h_sigmsg, sighash_type & 0xFF) # nVersion - writers.write_uint32(h_sigmsg, tx.version) + write_uint32(h_sigmsg, tx.version) # nLockTime - writers.write_uint32(h_sigmsg, tx.lock_time) + write_uint32(h_sigmsg, tx.lock_time) # sha_prevouts - writers.write_bytes_fixed( - h_sigmsg, self.h_prevouts.get_digest(), writers.TX_HASH_SIZE - ) + write_bytes_fixed(h_sigmsg, self.h_prevouts.get_digest(), TX_HASH_SIZE) # sha_amounts - writers.write_bytes_fixed( - h_sigmsg, self.h_amounts.get_digest(), writers.TX_HASH_SIZE - ) + write_bytes_fixed(h_sigmsg, self.h_amounts.get_digest(), TX_HASH_SIZE) # sha_scriptpubkeys - writers.write_bytes_fixed( - h_sigmsg, self.h_scriptpubkeys.get_digest(), writers.TX_HASH_SIZE - ) + write_bytes_fixed(h_sigmsg, self.h_scriptpubkeys.get_digest(), TX_HASH_SIZE) # sha_sequences - writers.write_bytes_fixed( - h_sigmsg, self.h_sequences.get_digest(), writers.TX_HASH_SIZE - ) + write_bytes_fixed(h_sigmsg, self.h_sequences.get_digest(), TX_HASH_SIZE) # sha_outputs - writers.write_bytes_fixed( - h_sigmsg, self.h_outputs.get_digest(), writers.TX_HASH_SIZE - ) + write_bytes_fixed(h_sigmsg, self.h_outputs.get_digest(), TX_HASH_SIZE) # spend_type 0 (no tapscript message extension, no annex) - writers.write_uint8(h_sigmsg, 0) + write_uint8(h_sigmsg, 0) # input_index - writers.write_uint32(h_sigmsg, i) + write_uint32(h_sigmsg, i) return h_sigmsg.get_digest() diff --git a/core/src/apps/bitcoin/sign_tx/tx_info.py b/core/src/apps/bitcoin/sign_tx/tx_info.py index e91620e837..65cf2cd3e2 100644 --- a/core/src/apps/bitcoin/sign_tx/tx_info.py +++ b/core/src/apps/bitcoin/sign_tx/tx_info.py @@ -1,13 +1,7 @@ from micropython import const from typing import TYPE_CHECKING -from trezor import wire -from trezor.crypto.hashlib import sha256 -from trezor.utils import HashWriter - from .. import common, writers -from ..common import BIP32_WALLET_DEPTH, input_is_external -from .matchcheck import MultisigFingerprintChecker, WalletPathChecker if TYPE_CHECKING: from typing import Protocol @@ -17,6 +11,7 @@ if TYPE_CHECKING: TxInput, TxOutput, ) + from trezor.utils import HashWriter from .sig_hasher import SigHasher from apps.common.coininfo import CoinInfo @@ -61,6 +56,10 @@ _MAX_BIP125_RBF_SEQUENCE = const(0xFFFF_FFFD) class TxInfoBase: def __init__(self, signer: Signer, tx: SignTx | PrevTx) -> None: + from trezor.crypto.hashlib import sha256 + from trezor.utils import HashWriter + from .matchcheck import MultisigFingerprintChecker, WalletPathChecker + # Checksum of multisig inputs, used to validate change-output. self.multisig_fingerprint = MultisigFingerprintChecker() @@ -89,7 +88,7 @@ class TxInfoBase: writers.write_tx_input_check(self.h_tx_check, txi) self.min_sequence = min(self.min_sequence, txi.sequence) - if not input_is_external(txi): + if not common.input_is_external(txi): self.wallet_path.add_input(txi) self.multisig_fingerprint.add_input(txi) @@ -108,7 +107,7 @@ class TxInfoBase: return False return ( self.wallet_path.output_matches(txo) - and len(txo.address_n) >= BIP32_WALLET_DEPTH + and len(txo.address_n) >= common.BIP32_WALLET_DEPTH and txo.address_n[-2] <= _BIP32_CHANGE_CHAIN and txo.address_n[-1] <= _BIP32_MAX_LAST_ELEMENT and txo.amount > 0 @@ -163,6 +162,8 @@ class OriginalTxInfo(TxInfoBase): writers.write_tx_output(self.h_tx, txo, script_pubkey) async def finalize_tx_hash(self) -> None: + from trezor import wire + await self.signer.write_prev_tx_footer(self.h_tx, self.tx, self.orig_hash) if self.orig_hash != writers.get_tx_hash( self.h_tx, double=self.signer.coin.sign_hash_double, reverse=True diff --git a/core/src/apps/bitcoin/sign_tx/tx_weight.py b/core/src/apps/bitcoin/sign_tx/tx_weight.py index bd4df97f82..a1b44dbd6d 100644 --- a/core/src/apps/bitcoin/sign_tx/tx_weight.py +++ b/core/src/apps/bitcoin/sign_tx/tx_weight.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING from trezor import wire from trezor.enums import InputScriptType -from .. import common, ownership +from .. import common if TYPE_CHECKING: from trezor.messages import TxInput @@ -50,28 +50,32 @@ class TxWeightCalculator: @classmethod def input_script_size(cls, i: TxInput) -> int: - script_type = i.script_type + script_type = i.script_type # local_cache_attribute + script_pubkey = i.script_pubkey # local_cache_attribute + multisig = i.multisig # local_cache_attribute + IST = InputScriptType # local_cache_global + if common.input_is_external_unverified(i): - assert i.script_pubkey is not None # checked in sanitize_tx_input + assert script_pubkey is not None # checked in _sanitize_tx_input # Guess the script type from the scriptPubKey. - if i.script_pubkey[0] == 0x76: # OP_DUP (P2PKH) - script_type = InputScriptType.SPENDADDRESS - elif i.script_pubkey[0] == 0xA9: # OP_HASH_160 (P2SH) + if script_pubkey[0] == 0x76: # OP_DUP (P2PKH) + script_type = IST.SPENDADDRESS + elif script_pubkey[0] == 0xA9: # OP_HASH_160 (P2SH) # Probably nested P2WPKH. - script_type = InputScriptType.SPENDP2SHWITNESS - elif i.script_pubkey[0] == 0x00: # SegWit v0 (probably P2WPKH) - script_type = InputScriptType.SPENDWITNESS - elif i.script_pubkey[0] == 0x51: # SegWit v1 (P2TR) - script_type = InputScriptType.SPENDTAPROOT + script_type = IST.SPENDP2SHWITNESS + elif script_pubkey[0] == 0x00: # SegWit v0 (probably P2WPKH) + script_type = IST.SPENDWITNESS + elif script_pubkey[0] == 0x51: # SegWit v1 (P2TR) + script_type = IST.SPENDTAPROOT else: # Unknown script type. pass - if i.multisig: - if script_type == InputScriptType.SPENDTAPROOT: + if multisig: + if script_type == IST.SPENDTAPROOT: raise wire.ProcessError("Multisig not supported for taproot") - n = len(i.multisig.nodes) if i.multisig.nodes else len(i.multisig.pubkeys) + n = len(multisig.nodes) if multisig.nodes else len(multisig.pubkeys) multisig_script_size = _TXSIZE_MULTISIGSCRIPT + n * (1 + _TXSIZE_PUBKEY) if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: multisig_script_size += cls.compact_size_len(multisig_script_size) @@ -80,25 +84,29 @@ class TxWeightCalculator: return ( 1 # the OP_FALSE bug in multisig - + i.multisig.m * (1 + _TXSIZE_DER_SIGNATURE) + + multisig.m * (1 + _TXSIZE_DER_SIGNATURE) + multisig_script_size ) - elif script_type == InputScriptType.SPENDTAPROOT: + elif script_type == IST.SPENDTAPROOT: return 1 + _TXSIZE_SCHNORR_SIGNATURE else: return 1 + _TXSIZE_DER_SIGNATURE + 1 + _TXSIZE_PUBKEY def add_input(self, i: TxInput) -> None: + from .. import ownership + + script_type = i.script_type # local_cache_attribute + self.inputs_count += 1 self.counter += 4 * _TXSIZE_INPUT input_script_size = self.input_script_size(i) - if i.script_type in common.NONSEGWIT_INPUT_SCRIPT_TYPES: + if script_type in common.NONSEGWIT_INPUT_SCRIPT_TYPES: input_script_size += self.compact_size_len(input_script_size) self.counter += 4 * input_script_size - elif i.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: + elif script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: self.segwit_inputs_count += 1 - if i.script_type == InputScriptType.SPENDP2SHWITNESS: + if script_type == InputScriptType.SPENDP2SHWITNESS: # add script_sig size if i.multisig: self.counter += 4 * (2 + _TXSIZE_WITNESSSCRIPT) @@ -107,7 +115,7 @@ class TxWeightCalculator: else: self.counter += 4 # empty script_sig (1 byte) self.counter += 1 + input_script_size # discounted witness - elif i.script_type == InputScriptType.EXTERNAL: + elif script_type == InputScriptType.EXTERNAL: if i.ownership_proof: script_sig, witness = ownership.read_scriptsig_witness( i.ownership_proof diff --git a/core/src/apps/bitcoin/sign_tx/zcash_v4.py b/core/src/apps/bitcoin/sign_tx/zcash_v4.py index ce683f104b..d7b3e2bc6e 100644 --- a/core/src/apps/bitcoin/sign_tx/zcash_v4.py +++ b/core/src/apps/bitcoin/sign_tx/zcash_v4.py @@ -1,32 +1,19 @@ -import ustruct as struct from micropython import const from typing import TYPE_CHECKING -from trezor import wire from trezor.crypto.hashlib import blake2b -from trezor.utils import HashWriter, ensure +from trezor.utils import HashWriter +from trezor.wire import DataError -from apps.common.coininfo import CoinInfo -from apps.common.keychain import Keychain -from apps.common.writers import write_compact_size - -from ..scripts import write_bip143_script_code_prefixed -from ..writers import ( - TX_HASH_SIZE, - get_tx_hash, - write_bytes_fixed, - write_bytes_reversed, - write_tx_output, - write_uint32, - write_uint64, -) -from . import approvers, helpers +from ..writers import TX_HASH_SIZE, write_bytes_reversed, write_uint32, write_uint64 from .bitcoinlike import Bitcoinlike if TYPE_CHECKING: from trezor.messages import PrevTx, SignTx, TxInput, TxOutput + from apps.common.coininfo import CoinInfo + from apps.common.keychain import Keychain + from . import approvers from typing import Sequence - from apps.common import coininfo from .sig_hasher import SigHasher from .tx_info import OriginalTxInfo, TxInfo from ..common import SigHashType @@ -47,6 +34,8 @@ class Zip243SigHasher: write_uint32(self.h_sequence, txi.sequence) def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None: + from ..writers import write_tx_output + write_tx_output(self.h_outputs, txo, script_pubkey) def hash143( @@ -55,9 +44,13 @@ class Zip243SigHasher: public_keys: Sequence[bytes | memoryview], threshold: int, tx: SignTx | PrevTx, - coin: coininfo.CoinInfo, + coin: CoinInfo, hash_type: int, ) -> bytes: + import ustruct as struct + from ..scripts import write_bip143_script_code_prefixed + from ..writers import get_tx_hash, write_bytes_fixed + h_preimage = HashWriter( blake2b( outlen=32, @@ -129,23 +122,30 @@ class ZcashV4(Bitcoinlike): coin: CoinInfo, approver: approvers.Approver | None, ) -> None: + from trezor.utils import ensure + ensure(coin.overwintered) super().__init__(tx, keychain, coin, approver) if tx.version != 4: - raise wire.DataError("Unsupported transaction version.") + raise DataError("Unsupported transaction version.") def create_sig_hasher(self, tx: SignTx | PrevTx) -> SigHasher: return Zip243SigHasher() async def step7_finish(self) -> None: - if self.serialize: - self.write_tx_footer(self.serialized_tx, self.tx_info.tx) + from apps.common.writers import write_compact_size + from . import helpers - write_uint64(self.serialized_tx, 0) # valueBalance - write_compact_size(self.serialized_tx, 0) # nShieldedSpend - write_compact_size(self.serialized_tx, 0) # nShieldedOutput - write_compact_size(self.serialized_tx, 0) # nJoinSplit + serialized_tx = self.serialized_tx # local_cache_attribute + + if self.serialize: + self.write_tx_footer(serialized_tx, self.tx_info.tx) + + write_uint64(serialized_tx, 0) # valueBalance + write_compact_size(serialized_tx, 0) # nShieldedSpend + write_compact_size(serialized_tx, 0) # nShieldedOutput + write_compact_size(serialized_tx, 0) # nJoinSplit await helpers.request_tx_finish(self.tx_req) @@ -179,7 +179,7 @@ class ZcashV4(Bitcoinlike): write_uint32(w, tx.version) else: if tx.version_group_id is None: - raise wire.DataError("Version group ID is missing") + raise DataError("Version group ID is missing") # nVersion | fOverwintered write_uint32(w, tx.version | _OVERWINTERED) write_uint32(w, tx.version_group_id) # nVersionGroupId diff --git a/core/src/apps/bitcoin/verification.py b/core/src/apps/bitcoin/verification.py index aec36c15d2..2598c33abb 100644 --- a/core/src/apps/bitcoin/verification.py +++ b/core/src/apps/bitcoin/verification.py @@ -1,29 +1,11 @@ from typing import TYPE_CHECKING -from trezor import utils, wire -from trezor.crypto import der -from trezor.crypto.curve import bip340, secp256k1 -from trezor.crypto.hashlib import sha256 - -from .common import OP_0, OP_1, SigHashType, ecdsa_hash_pubkey -from .scripts import ( - output_script_native_segwit, - output_script_p2pkh, - output_script_p2sh, - parse_input_script_multisig, - parse_input_script_p2pkh, - parse_output_script_multisig, - parse_output_script_p2tr, - parse_witness_multisig, - parse_witness_p2tr, - parse_witness_p2wpkh, - write_input_script_p2wpkh_in_p2sh, - write_input_script_p2wsh_in_p2sh, -) +from trezor.wire import DataError if TYPE_CHECKING: from typing import Sequence from apps.common.coininfo import CoinInfo + from .common import SigHashType class SignatureVerifier: @@ -34,6 +16,26 @@ class SignatureVerifier: witness: bytes | None, coin: CoinInfo, ): + from trezor import utils + from trezor.wire import DataError # local_cache_global + from trezor.crypto.hashlib import sha256 + + from .common import OP_0, OP_1, SigHashType, ecdsa_hash_pubkey + from .scripts import ( + output_script_native_segwit, + output_script_p2pkh, + output_script_p2sh, + parse_input_script_multisig, + parse_input_script_p2pkh, + parse_output_script_multisig, + parse_output_script_p2tr, + parse_witness_multisig, + parse_witness_p2tr, + parse_witness_p2wpkh, + write_input_script_p2wpkh_in_p2sh, + write_input_script_p2wsh_in_p2sh, + ) + self.threshold = 1 self.public_keys: list[memoryview] = [] self.signatures: list[tuple[memoryview, SigHashType]] = [] @@ -41,27 +43,27 @@ class SignatureVerifier: if not script_sig: if not witness: - raise wire.DataError("Signature data not provided") + raise DataError("Signature data not provided") if len(script_pubkey) == 22: # P2WPKH public_key, signature, hash_type = parse_witness_p2wpkh(witness) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if output_script_native_segwit(0, pubkey_hash) != script_pubkey: - raise wire.DataError("Invalid public key hash") + raise DataError("Invalid public key hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_pubkey) == 34 and script_pubkey[0] == OP_0: # P2WSH script, self.signatures = parse_witness_multisig(witness) script_hash = sha256(script).digest() if output_script_native_segwit(0, script_hash) != script_pubkey: - raise wire.DataError("Invalid script hash") + raise DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig(script) elif len(script_pubkey) == 34 and script_pubkey[0] == OP_1: # P2TR self.is_taproot = True self.public_keys = [parse_output_script_p2tr(script_pubkey)] self.signatures = [parse_witness_p2tr(witness)] else: - raise wire.DataError("Unsupported signature script") + raise DataError("Unsupported signature script") elif witness and witness != b"\x00": if len(script_sig) == 23: # P2WPKH nested in BIP16 P2SH public_key, signature, hash_type = parse_witness_p2wpkh(witness) @@ -69,10 +71,10 @@ class SignatureVerifier: w = utils.empty_bytearray(23) write_input_script_p2wpkh_in_p2sh(w, pubkey_hash) if w != script_sig: - raise wire.DataError("Invalid public key hash") + raise DataError("Invalid public key hash") script_hash = coin.script_hash(script_sig[1:]).digest() if output_script_p2sh(script_hash) != script_pubkey: - raise wire.DataError("Invalid script hash") + raise DataError("Invalid script hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_sig) == 35: # P2WSH nested in BIP16 P2SH @@ -81,36 +83,36 @@ class SignatureVerifier: w = utils.empty_bytearray(35) write_input_script_p2wsh_in_p2sh(w, script_hash) if w != script_sig: - raise wire.DataError("Invalid script hash") + raise DataError("Invalid script hash") script_hash = coin.script_hash(script_sig[1:]).digest() if output_script_p2sh(script_hash) != script_pubkey: - raise wire.DataError("Invalid script hash") + raise DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig(script) else: - raise wire.DataError("Unsupported signature script") + raise DataError("Unsupported signature script") else: if len(script_pubkey) == 25: # P2PKH public_key, signature, hash_type = parse_input_script_p2pkh(script_sig) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if output_script_p2pkh(pubkey_hash) != script_pubkey: - raise wire.DataError("Invalid public key hash") + raise DataError("Invalid public key hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_pubkey) == 23: # P2SH script, self.signatures = parse_input_script_multisig(script_sig) script_hash = coin.script_hash(script).digest() if output_script_p2sh(script_hash) != script_pubkey: - raise wire.DataError("Invalid script hash") + raise DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig(script) else: - raise wire.DataError("Unsupported signature script") + raise DataError("Unsupported signature script") if self.threshold != len(self.signatures): - raise wire.DataError("Invalid signature") + raise DataError("Invalid signature") def ensure_hash_type(self, sighash_types: Sequence[SigHashType]) -> None: if any(h not in sighash_types for _, h in self.signatures): - raise wire.DataError("Unsupported sighash type") + raise DataError("Unsupported sighash type") def verify(self, digest: bytes) -> None: # It is up to the caller to ensure that the digest is appropriate for @@ -125,21 +127,27 @@ class SignatureVerifier: self.verify_ecdsa(digest) def verify_bip340(self, digest: bytes) -> None: + from trezor.crypto.curve import bip340 + if not bip340.verify(self.public_keys[0], self.signatures[0][0], digest): - raise wire.DataError("Invalid signature") + raise DataError("Invalid signature") def verify_ecdsa(self, digest: bytes) -> None: + from trezor.crypto.curve import secp256k1 + try: i = 0 for der_signature, _ in self.signatures: - signature = decode_der_signature(der_signature) + signature = _decode_der_signature(der_signature) while not secp256k1.verify(self.public_keys[i], signature, digest): i += 1 except Exception: - raise wire.DataError("Invalid signature") + raise DataError("Invalid signature") -def decode_der_signature(der_signature: memoryview) -> bytearray: +def _decode_der_signature(der_signature: memoryview) -> bytearray: + from trezor.crypto import der + seq = der.decode_seq(der_signature) if len(seq) != 2 or any(len(i) > 32 for i in seq): raise ValueError diff --git a/core/src/apps/bitcoin/verify_message.py b/core/src/apps/bitcoin/verify_message.py index 0b946d88e6..d73ab537ab 100644 --- a/core/src/apps/bitcoin/verify_message.py +++ b/core/src/apps/bitcoin/verify_message.py @@ -1,30 +1,20 @@ from typing import TYPE_CHECKING -from trezor import utils, wire -from trezor.crypto import base58 -from trezor.crypto.curve import secp256k1 -from trezor.enums import InputScriptType -from trezor.messages import Success -from trezor.ui.layouts import confirm_signverify, show_success - -from apps.common import address_type, coins -from apps.common.signverify import decode_message, message_digest - -from . import common -from .addresses import ( - address_p2wpkh, - address_p2wpkh_in_p2sh, - address_pkh, - address_short, - address_to_cashaddr, -) - if TYPE_CHECKING: from apps.common.coininfo import CoinInfo - from trezor.messages import VerifyMessage + from trezor.messages import VerifyMessage, Success + from trezor.wire import Context + from trezor.enums import InputScriptType -def address_to_script_type(address: str, coin: CoinInfo) -> InputScriptType: +def _address_to_script_type(address: str, coin: CoinInfo) -> InputScriptType: + from trezor.crypto import base58 + from trezor.wire import DataError + from trezor.enums import InputScriptType + from trezor import utils + from apps.common import address_type + from . import common + # Determines the script type from a non-multisig address. if coin.bech32_prefix and address.startswith(coin.bech32_prefix): @@ -34,7 +24,7 @@ def address_to_script_type(address: str, coin: CoinInfo) -> InputScriptType: elif witver == 1: return InputScriptType.SPENDTAPROOT else: - raise wire.DataError("Invalid address") + raise DataError("Invalid address") if ( not utils.BITCOIN_ONLY @@ -46,7 +36,7 @@ def address_to_script_type(address: str, coin: CoinInfo) -> InputScriptType: try: raw_address = base58.decode_check(address, coin.b58_hash) except ValueError: - raise wire.DataError("Invalid address") + raise DataError("Invalid address") if address_type.check(coin.address_type, raw_address): # p2pkh @@ -55,10 +45,28 @@ def address_to_script_type(address: str, coin: CoinInfo) -> InputScriptType: # p2sh return InputScriptType.SPENDP2SHWITNESS - raise wire.DataError("Invalid address") + raise DataError("Invalid address") -async def verify_message(ctx: wire.Context, msg: VerifyMessage) -> Success: +async def verify_message(ctx: Context, msg: VerifyMessage) -> Success: + from trezor import utils + from trezor.wire import ProcessError + from trezor.crypto.curve import secp256k1 + from trezor.enums import InputScriptType + from trezor.messages import Success + from trezor.ui.layouts import confirm_signverify, show_success + + from apps.common import coins + from apps.common.signverify import decode_message, message_digest + + from .addresses import ( + address_p2wpkh, + address_p2wpkh_in_p2sh, + address_pkh, + address_short, + address_to_cashaddr, + ) + message = msg.message address = msg.address signature = msg.signature @@ -67,7 +75,7 @@ async def verify_message(ctx: wire.Context, msg: VerifyMessage) -> Success: digest = message_digest(coin, message) - script_type = address_to_script_type(address, coin) + script_type = _address_to_script_type(address, coin) recid = signature[0] if 27 <= recid <= 34: # p2pkh or no script type provided @@ -79,12 +87,12 @@ async def verify_message(ctx: wire.Context, msg: VerifyMessage) -> Success: # native segwit signature = bytes([signature[0] - 8]) + signature[1:] else: - raise wire.ProcessError("Invalid signature") + raise ProcessError("Invalid signature") pubkey = secp256k1.verify_recover(signature, digest) if not pubkey: - raise wire.ProcessError("Invalid signature") + raise ProcessError("Invalid signature") if script_type == InputScriptType.SPENDADDRESS: addr = address_pkh(pubkey, coin) @@ -95,16 +103,16 @@ async def verify_message(ctx: wire.Context, msg: VerifyMessage) -> Success: elif script_type == InputScriptType.SPENDWITNESS: addr = address_p2wpkh(pubkey, coin) else: - raise wire.ProcessError("Invalid signature") + raise ProcessError("Invalid signature") if addr != address: - raise wire.ProcessError("Invalid signature") + raise ProcessError("Invalid signature") await confirm_signverify( ctx, coin.coin_shortcut, decode_message(message), - address=address_short(coin, address), + address_short(coin, address), verify=True, ) diff --git a/core/src/apps/bitcoin/writers.py b/core/src/apps/bitcoin/writers.py index e0680ae248..de76e10468 100644 --- a/core/src/apps/bitcoin/writers.py +++ b/core/src/apps/bitcoin/writers.py @@ -1,7 +1,6 @@ from micropython import const from typing import TYPE_CHECKING -from trezor.crypto.hashlib import sha256 from trezor.utils import ensure from apps.common.writers import ( # noqa: F401 @@ -72,22 +71,24 @@ def write_tx_output(w: Writer, o: TxOutput | PrevOutput, script_pubkey: bytes) - def write_op_push(w: Writer, n: int) -> None: + append = w.append # local_cache_attribute + ensure(0 <= n <= 0xFFFF_FFFF) if n < 0x4C: - w.append(n & 0xFF) + append(n & 0xFF) elif n < 0x100: - w.append(0x4C) - w.append(n & 0xFF) + append(0x4C) + append(n & 0xFF) elif n < 0x1_0000: - w.append(0x4D) - w.append(n & 0xFF) - w.append((n >> 8) & 0xFF) + append(0x4D) + append(n & 0xFF) + append((n >> 8) & 0xFF) else: - w.append(0x4E) - w.append(n & 0xFF) - w.append((n >> 8) & 0xFF) - w.append((n >> 16) & 0xFF) - w.append((n >> 24) & 0xFF) + append(0x4E) + append(n & 0xFF) + append((n >> 8) & 0xFF) + append((n >> 16) & 0xFF) + append((n >> 24) & 0xFF) def op_push_length(n: int) -> int: @@ -103,6 +104,8 @@ def op_push_length(n: int) -> int: def get_tx_hash(w: HashWriter, double: bool = False, reverse: bool = False) -> bytes: + from trezor.crypto.hashlib import sha256 + d = w.get_digest() if double: d = sha256(d).digest() diff --git a/core/tests/test_apps.bitcoin.address.py b/core/tests/test_apps.bitcoin.address.py index 0919029248..dc9fd9c5d5 100644 --- a/core/tests/test_apps.bitcoin.address.py +++ b/core/tests/test_apps.bitcoin.address.py @@ -1,11 +1,16 @@ from common import * from trezor.crypto import bip32, bip39 +from trezor import wire from trezor.messages import GetAddress +from trezor.enums import InputScriptType from trezor.utils import HashWriter from apps.common import coins -from apps.bitcoin import scripts from apps.bitcoin.addresses import * +from apps.bitcoin.addresses import ( + _address_p2wsh, _address_p2wsh_in_p2sh, + _address_multisig_p2wsh_in_p2sh, _address_multisig_p2sh +) from apps.bitcoin.keychain import validate_path_against_script_type from apps.bitcoin.writers import * @@ -73,7 +78,7 @@ class TestAddress(unittest.TestCase): h = HashWriter(sha256()) write_bytes_unchecked(h, script) - address = address_p2wsh( + address = _address_p2wsh( h.get_digest(), coin.bech32_prefix ) @@ -83,7 +88,7 @@ class TestAddress(unittest.TestCase): coin = coins.by_name('Bitcoin') # test data from Mastering Bitcoin - address = address_p2wsh_in_p2sh( + address = _address_p2wsh_in_p2sh( unhexlify('9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73'), coin ) @@ -99,7 +104,7 @@ class TestAddress(unittest.TestCase): # unhexlify('046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187'), # unhexlify('0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83'), # ] - # address = address_multisig_p2sh(pubkeys, 2, coin.address_type_p2sh) + # address = _address_multisig_p2sh(pubkeys, 2, coin.address_type_p2sh) # self.assertEqual(address, '347N1Thc213QqfYCz3PZkjoJpNv5b14kBd') coin = coins.by_name('Bitcoin') @@ -107,12 +112,12 @@ class TestAddress(unittest.TestCase): unhexlify('02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f'), unhexlify('02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8'), ] - address = address_multisig_p2sh(pubkeys, 2, coin) + address = _address_multisig_p2sh(pubkeys, 2, coin) self.assertEqual(address, '39bgKC7RFbpoCRbtD5KEdkYKtNyhpsNa3Z') for invalid_m in (-1, 0, len(pubkeys) + 1, 16): with self.assertRaises(wire.DataError): - address_multisig_p2sh(pubkeys, invalid_m, coin) + _address_multisig_p2sh(pubkeys, invalid_m, coin) def test_multisig_address_p2wsh_in_p2sh(self): # test data from @@ -123,7 +128,7 @@ class TestAddress(unittest.TestCase): unhexlify('0320ce424c6d61f352ccfea60d209651672cfb03b2dc77d1d64d3ba519aec756ae'), ] - address = address_multisig_p2wsh_in_p2sh(pubkeys, 2, coin) + address = _address_multisig_p2wsh_in_p2sh(pubkeys, 2, coin) self.assertEqual(address, '2MsZ2fpGKUydzY62v6trPHR8eCx5JTy1Dpa') # def test_multisig_address_p2wsh(self): diff --git a/core/tests/test_apps.bitcoin.keychain.py b/core/tests/test_apps.bitcoin.keychain.py index 2156bd24bc..2e0375759e 100644 --- a/core/tests/test_apps.bitcoin.keychain.py +++ b/core/tests/test_apps.bitcoin.keychain.py @@ -4,7 +4,7 @@ from trezor import wire from trezor.crypto import bip39 from apps.common.paths import HARDENED -from apps.bitcoin.keychain import get_coin_by_name, get_keychain_for_coin +from apps.bitcoin.keychain import _get_coin_by_name, _get_keychain_for_coin class TestBitcoinKeychain(unittest.TestCase): @@ -14,8 +14,8 @@ class TestBitcoinKeychain(unittest.TestCase): cache.set(cache.APP_COMMON_SEED, seed) def test_bitcoin(self): - coin = get_coin_by_name("Bitcoin") - keychain = await_result(get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) + coin = _get_coin_by_name("Bitcoin") + keychain = await_result(_get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) self.assertEqual(coin.coin_name, "Bitcoin") valid_addresses = ( @@ -45,8 +45,8 @@ class TestBitcoinKeychain(unittest.TestCase): self.assertRaises(wire.DataError, keychain.derive, addr) def test_testnet(self): - coin = get_coin_by_name("Testnet") - keychain = await_result(get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) + coin = _get_coin_by_name("Testnet") + keychain = await_result(_get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) self.assertEqual(coin.coin_name, "Testnet") valid_addresses = ( @@ -76,14 +76,14 @@ class TestBitcoinKeychain(unittest.TestCase): self.assertRaises(wire.DataError, keychain.derive, addr) def test_unspecified(self): - coin = get_coin_by_name(None) - keychain = await_result(get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) + coin = _get_coin_by_name(None) + keychain = await_result(_get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) self.assertEqual(coin.coin_name, "Bitcoin") keychain.derive([H_(44), H_(0), H_(0), 0, 0]) def test_unknown(self): with self.assertRaises(wire.DataError): - get_coin_by_name("MadeUpCoin2020") + _get_coin_by_name("MadeUpCoin2020") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @@ -94,8 +94,8 @@ class TestAltcoinKeychains(unittest.TestCase): cache.set(cache.APP_COMMON_SEED, seed) def test_bcash(self): - coin = get_coin_by_name("Bcash") - keychain = await_result(get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) + coin = _get_coin_by_name("Bcash") + keychain = await_result(_get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) self.assertEqual(coin.coin_name, "Bcash") self.assertFalse(coin.segwit) @@ -131,8 +131,8 @@ class TestAltcoinKeychains(unittest.TestCase): self.assertRaises(wire.DataError, keychain.derive, addr) def test_litecoin(self): - coin = get_coin_by_name("Litecoin") - keychain = await_result(get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) + coin = _get_coin_by_name("Litecoin") + keychain = await_result(_get_keychain_for_coin(wire.DUMMY_CONTEXT, coin)) self.assertEqual(coin.coin_name, "Litecoin") self.assertTrue(coin.segwit) diff --git a/core/tests/test_apps.bitcoin.ownership_proof.py b/core/tests/test_apps.bitcoin.ownership_proof.py index 9079ff40d9..ef02f621cc 100644 --- a/core/tests/test_apps.bitcoin.ownership_proof.py +++ b/core/tests/test_apps.bitcoin.ownership_proof.py @@ -8,7 +8,7 @@ from apps.common import coins from apps.common.keychain import Keychain from apps.common.paths import HARDENED, AlwaysMatchingSchema from apps.bitcoin import ownership, scripts -from apps.bitcoin.addresses import address_p2tr, address_p2wpkh, address_p2wpkh_in_p2sh, address_multisig_p2wsh, address_multisig_p2wsh_in_p2sh, address_multisig_p2sh +from apps.bitcoin.addresses import _address_p2tr, address_p2wpkh, address_p2wpkh_in_p2sh, _address_multisig_p2wsh, _address_multisig_p2wsh_in_p2sh, _address_multisig_p2sh from apps.bitcoin.multisig import multisig_get_pubkeys @@ -77,7 +77,7 @@ class TestOwnershipProof(unittest.TestCase): commitment_data = b"" node = keychain.derive([86 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 0]) - address = address_p2tr(node.public_key(), coin) + address = _address_p2tr(node.public_key(), coin) script_pubkey = scripts.output_derive_script(address, coin) ownership_id = ownership.get_identifier(script_pubkey, keychain) self.assertEqual(ownership_id, unhexlify("dc18066224b9e30e306303436dc18ab881c7266c13790350a3fe415e438135ec")) @@ -177,7 +177,7 @@ class TestOwnershipProof(unittest.TestCase): ) pubkeys = multisig_get_pubkeys(multisig) - address = address_multisig_p2wsh(pubkeys, multisig.m, coin.bech32_prefix) + address = _address_multisig_p2wsh(pubkeys, multisig.m, coin.bech32_prefix) script_pubkey = scripts.output_derive_script(address, coin) ownership_ids = [ownership.get_identifier(script_pubkey, keychain) for keychain in keychains] self.assertEqual(ownership_ids[0], unhexlify("309c4ffec5c228cc836b51d572c0a730dbabd39df9f01862502ac9eabcdeb94a")) @@ -238,7 +238,7 @@ class TestOwnershipProof(unittest.TestCase): ) pubkeys = multisig_get_pubkeys(multisig) - address = address_multisig_p2wsh_in_p2sh(pubkeys, multisig.m, coin) + address = _address_multisig_p2wsh_in_p2sh(pubkeys, multisig.m, coin) script_pubkey = scripts.output_derive_script(address, coin) ownership_id = ownership.get_identifier(script_pubkey, keychain) ownership_ids = [b'\x00' * 32, b'\x01' * 32, b'\x02' * 32, ownership_id] @@ -312,7 +312,7 @@ class TestOwnershipProof(unittest.TestCase): ) pubkeys = multisig_get_pubkeys(multisig) - address = address_multisig_p2sh(pubkeys, multisig.m, coin) + address = _address_multisig_p2sh(pubkeys, multisig.m, coin) script_pubkey = scripts.output_derive_script(address, coin) ownership_id = ownership.get_identifier(script_pubkey, keychain) ownership_ids = [b'\x00' * 32, ownership_id] diff --git a/core/tests/test_apps.bitcoin.segwit.bip143.native_p2wpkh.py b/core/tests/test_apps.bitcoin.segwit.bip143.native_p2wpkh.py index aac99441ad..fcf4d54f44 100644 --- a/core/tests/test_apps.bitcoin.segwit.bip143.native_p2wpkh.py +++ b/core/tests/test_apps.bitcoin.segwit.bip143.native_p2wpkh.py @@ -2,7 +2,7 @@ from common import * from apps.bitcoin.common import SigHashType from apps.bitcoin.scripts import output_derive_script -from apps.bitcoin.sign_tx.bitcoin import BitcoinSigHasher +from apps.bitcoin.sign_tx.sig_hasher import BitcoinSigHasher from apps.bitcoin.writers import get_tx_hash from apps.common import coins from apps.common.keychain import Keychain diff --git a/core/tests/test_apps.bitcoin.segwit.bip143.p2wpkh_in_p2sh.py b/core/tests/test_apps.bitcoin.segwit.bip143.p2wpkh_in_p2sh.py index 29c556a328..024fdcd296 100644 --- a/core/tests/test_apps.bitcoin.segwit.bip143.p2wpkh_in_p2sh.py +++ b/core/tests/test_apps.bitcoin.segwit.bip143.p2wpkh_in_p2sh.py @@ -2,7 +2,7 @@ from common import * from apps.bitcoin.common import SigHashType from apps.bitcoin.scripts import output_derive_script -from apps.bitcoin.sign_tx.bitcoin import BitcoinSigHasher +from apps.bitcoin.sign_tx.sig_hasher import BitcoinSigHasher from apps.bitcoin.writers import get_tx_hash from apps.common import coins from apps.common.keychain import Keychain diff --git a/core/tests/test_apps.bitcoin.segwit.bip341.p2tr.py b/core/tests/test_apps.bitcoin.segwit.bip341.p2tr.py index a468fa4a00..14352bcef3 100644 --- a/core/tests/test_apps.bitcoin.segwit.bip341.p2tr.py +++ b/core/tests/test_apps.bitcoin.segwit.bip341.p2tr.py @@ -1,15 +1,12 @@ from common import * from apps.bitcoin.common import SigHashType -from apps.bitcoin.scripts import output_derive_script -from apps.bitcoin.sign_tx.bitcoin import BitcoinSigHasher +from apps.bitcoin.sign_tx.sig_hasher import BitcoinSigHasher from apps.bitcoin.writers import get_tx_hash from trezor.messages import SignTx from trezor.messages import TxInput -from trezor.messages import TxOutput from trezor.messages import PrevOutput from trezor.enums import InputScriptType -from trezor.enums import OutputScriptType VECTORS = [ diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py index 41c71d8fa2..fef7c997aa 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py @@ -28,7 +28,7 @@ from trezor import wire from apps.common import coins from apps.common.keychain import Keychain -from apps.bitcoin.keychain import get_schemas_for_coin +from apps.bitcoin.keychain import _get_schemas_for_coin from apps.bitcoin.sign_tx import helpers, bitcoin @@ -160,7 +160,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoin.Bitcoin(tx, keychain, coin, None).signer() for request, response in chunks(messages, 2): @@ -292,7 +292,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoin.Bitcoin(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2): @@ -352,7 +352,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): None ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoin.Bitcoin(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2): diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py index 3c034c9428..a10d6c482e 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py @@ -27,7 +27,7 @@ from trezor.enums import OutputScriptType from apps.common import coins from apps.common.keychain import Keychain -from apps.bitcoin.keychain import get_schemas_for_coin +from apps.bitcoin.keychain import _get_schemas_for_coin from apps.bitcoin.sign_tx import bitcoinlike, helpers @@ -161,7 +161,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2): @@ -293,7 +293,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2): diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py index cb19026598..d643cd1284 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py @@ -28,7 +28,7 @@ from trezor import wire from apps.common import coins from apps.common.keychain import Keychain -from apps.bitcoin.keychain import get_schemas_for_coin +from apps.bitcoin.keychain import _get_schemas_for_coin from apps.bitcoin.sign_tx import bitcoin, helpers @@ -157,7 +157,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoin.Bitcoin(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2): @@ -296,7 +296,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoin.Bitcoin(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2): @@ -405,7 +405,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoin.Bitcoin(tx, keychain, coin, None).signer() i = 0 diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py index 1e4e697124..90e0849055 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py @@ -27,7 +27,7 @@ from trezor.enums import OutputScriptType from apps.common import coins from apps.common.keychain import Keychain -from apps.bitcoin.keychain import get_schemas_for_coin +from apps.bitcoin.keychain import _get_schemas_for_coin from apps.bitcoin.sign_tx import bitcoinlike, helpers @@ -161,7 +161,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2): @@ -300,7 +300,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): )), ] - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2): diff --git a/core/tests/test_apps.bitcoin.signtx.py b/core/tests/test_apps.bitcoin.signtx.py index e824842512..34a46dc9de 100644 --- a/core/tests/test_apps.bitcoin.signtx.py +++ b/core/tests/test_apps.bitcoin.signtx.py @@ -26,7 +26,7 @@ from trezor.enums import OutputScriptType from apps.common import coins from apps.common.keychain import Keychain -from apps.bitcoin.keychain import get_schemas_for_coin +from apps.bitcoin.keychain import _get_schemas_for_coin from apps.bitcoin.sign_tx import bitcoin, helpers @@ -212,7 +212,7 @@ class TestSignTx(unittest.TestCase): " ".join(["all"] * 12), "", ) - ns = get_schemas_for_coin(coin_bitcoin) + ns = _get_schemas_for_coin(coin_bitcoin) keychain = Keychain(seed, coin_bitcoin.curve_name, ns) signer = bitcoin.Bitcoin(tx, keychain, coin_bitcoin, None).signer() diff --git a/core/tests/test_apps.bitcoin.signtx_decred.py b/core/tests/test_apps.bitcoin.signtx_decred.py index 80e613d3de..080ec5d4fb 100644 --- a/core/tests/test_apps.bitcoin.signtx_decred.py +++ b/core/tests/test_apps.bitcoin.signtx_decred.py @@ -26,7 +26,7 @@ from trezor.enums import OutputScriptType from apps.common import coins from apps.common.keychain import Keychain -from apps.bitcoin.keychain import get_schemas_for_coin +from apps.bitcoin.keychain import _get_schemas_for_coin from apps.bitcoin.sign_tx import decred, helpers @@ -194,7 +194,7 @@ class TestSignTxDecred(unittest.TestCase): " ".join(["all"] * 12), "", ) - ns = get_schemas_for_coin(coin_decred) + ns = _get_schemas_for_coin(coin_decred) keychain = Keychain(seed, coin_decred.curve_name, ns) signer = decred.Decred(tx, keychain, coin_decred, None).signer() @@ -376,7 +376,7 @@ class TestSignTxDecred(unittest.TestCase): " ".join(["all"] * 12), "", ) - ns = get_schemas_for_coin(coin_decred) + ns = _get_schemas_for_coin(coin_decred) keychain = Keychain(seed, coin_decred.curve_name, ns) signer = decred.Decred(tx, keychain, coin_decred, None).signer() diff --git a/core/tests/test_apps.bitcoin.signtx_grs.py b/core/tests/test_apps.bitcoin.signtx_grs.py index f4d80846cf..553cfd387e 100644 --- a/core/tests/test_apps.bitcoin.signtx_grs.py +++ b/core/tests/test_apps.bitcoin.signtx_grs.py @@ -26,7 +26,7 @@ from trezor.enums import OutputScriptType from apps.common import coins from apps.common.keychain import Keychain -from apps.bitcoin.keychain import get_schemas_for_coin +from apps.bitcoin.keychain import _get_schemas_for_coin from apps.bitcoin.sign_tx import bitcoinlike, helpers @@ -104,7 +104,7 @@ class TestSignTx_GRS(unittest.TestCase): ] seed = bip39.seed(' '.join(['all'] * 12), '') - ns = get_schemas_for_coin(coin) + ns = _get_schemas_for_coin(coin) keychain = Keychain(seed, coin.curve_name, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin, None).signer() for request, expected_response in chunks(messages, 2):