mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 12:28:09 +00:00
chore(core): decrease bitcoin size by 1740 bytes
This commit is contained in:
parent
45b4b609db
commit
55bb61d404
@ -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:]
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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
|
||||
),
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.")
|
||||
|
@ -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)
|
||||
|
@ -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_*
|
||||
|
@ -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,
|
||||
|
@ -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."
|
||||
)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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])
|
||||
|
@ -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().
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,12 +1,27 @@
|
||||
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 trezor.wire import DataError
|
||||
|
||||
from .common import OP_0, OP_1, SigHashType, ecdsa_hash_pubkey
|
||||
from .scripts import (
|
||||
if TYPE_CHECKING:
|
||||
from typing import Sequence
|
||||
from apps.common.coininfo import CoinInfo
|
||||
from .common import SigHashType
|
||||
|
||||
|
||||
class SignatureVerifier:
|
||||
def __init__(
|
||||
self,
|
||||
script_pubkey: bytes,
|
||||
script_sig: bytes | None,
|
||||
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,
|
||||
@ -19,21 +34,8 @@ from .scripts import (
|
||||
parse_witness_p2wpkh,
|
||||
write_input_script_p2wpkh_in_p2sh,
|
||||
write_input_script_p2wsh_in_p2sh,
|
||||
)
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Sequence
|
||||
from apps.common.coininfo import CoinInfo
|
||||
|
||||
|
||||
class SignatureVerifier:
|
||||
def __init__(
|
||||
self,
|
||||
script_pubkey: bytes,
|
||||
script_sig: bytes | None,
|
||||
witness: bytes | None,
|
||||
coin: CoinInfo,
|
||||
):
|
||||
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
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 = [
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user