diff --git a/core/src/apps/wallet/sign_tx/bitcoin.py b/core/src/apps/wallet/sign_tx/bitcoin.py index c32757599..22281a6e0 100644 --- a/core/src/apps/wallet/sign_tx/bitcoin.py +++ b/core/src/apps/wallet/sign_tx/bitcoin.py @@ -2,9 +2,8 @@ import gc from micropython import const from trezor import utils -from trezor.crypto import base58 from trezor.crypto.hashlib import sha256 -from trezor.messages import FailureType, InputScriptType, OutputScriptType +from trezor.messages import FailureType, InputScriptType from trezor.messages.SignTx import SignTx from trezor.messages.TransactionType import TransactionType from trezor.messages.TxInputType import TxInputType @@ -14,7 +13,7 @@ from trezor.messages.TxRequest import TxRequest from trezor.messages.TxRequestDetailsType import TxRequestDetailsType from trezor.messages.TxRequestSerializedType import TxRequestSerializedType -from apps.common import address_type, coininfo, seed +from apps.common import coininfo, seed from apps.wallet.sign_tx import ( addresses, helpers, @@ -426,6 +425,7 @@ class Bitcoin: return amount_out def check_prevtx_output(self, txo_bin: TxOutputBinType) -> None: + # Validations to perform on the UTXO when checking the previous transaction output amount. pass # TX Helpers @@ -462,51 +462,20 @@ class Bitcoin: # === def output_derive_script(self, txo: TxOutputType) -> bytes: - if txo.script_type == OutputScriptType.PAYTOOPRETURN: - return scripts.output_script_paytoopreturn(txo.op_return_data) - if txo.address_n: # change output - txo.address = self.get_address_for_change(txo) - - if self.coin.bech32_prefix and txo.address.startswith(self.coin.bech32_prefix): - # p2wpkh or p2wsh - witprog = addresses.decode_bech32_address( - self.coin.bech32_prefix, txo.address + try: + input_script_type = helpers.CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[ + txo.script_type + ] + except KeyError: + raise SigningError(FailureType.DataError, "Invalid script type") + node = self.keychain.derive(txo.address_n, self.coin.curve_name) + txo.address = addresses.get_address( + input_script_type, self.coin, node, txo.multisig ) - return scripts.output_script_native_p2wpkh_or_p2wsh(witprog) - - raw_address = self.get_raw_address(txo) - - if address_type.check(self.coin.address_type, raw_address): - # p2pkh - pubkeyhash = address_type.strip(self.coin.address_type, raw_address) - script = scripts.output_script_p2pkh(pubkeyhash) - return script - - elif address_type.check(self.coin.address_type_p2sh, raw_address): - # p2sh - scripthash = address_type.strip(self.coin.address_type_p2sh, raw_address) - script = scripts.output_script_p2sh(scripthash) - return script - - raise SigningError(FailureType.DataError, "Invalid address type") - - def get_raw_address(self, txo: TxOutputType) -> bytes: - try: - return base58.decode_check(txo.address, self.coin.b58_hash) - except ValueError: - raise SigningError(FailureType.DataError, "Invalid address") - - def get_address_for_change(self, txo: TxOutputType) -> str: - try: - input_script_type = helpers.CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[ - txo.script_type - ] - except KeyError: - raise SigningError(FailureType.DataError, "Invalid script type") - node = self.keychain.derive(txo.address_n, self.coin.curve_name) - return addresses.get_address(input_script_type, self.coin, node, txo.multisig) + + return scripts.output_derive_script(txo, self.coin) def output_is_change(self, txo: TxOutputType) -> bool: if txo.script_type not in helpers.CHANGE_OUTPUT_SCRIPT_TYPES: @@ -525,44 +494,9 @@ class Bitcoin: def input_derive_script( self, txi: TxInputType, pubkey: bytes, signature: bytes = None ) -> bytes: - if txi.script_type == InputScriptType.SPENDADDRESS: - # p2pkh or p2sh - return scripts.input_script_p2pkh_or_p2sh( - pubkey, signature, self.get_hash_type() - ) - - if txi.script_type == InputScriptType.SPENDP2SHWITNESS: - # p2wpkh or p2wsh using p2sh - - if txi.multisig: - # p2wsh in p2sh - pubkeys = multisig.multisig_get_pubkeys(txi.multisig) - witness_script_hasher = self.create_hash_writer() - scripts.write_output_script_multisig( - witness_script_hasher, pubkeys, txi.multisig.m - ) - witness_script_hash = witness_script_hasher.get_digest() - return scripts.input_script_p2wsh_in_p2sh(witness_script_hash) - - # p2wpkh in p2sh - return scripts.input_script_p2wpkh_in_p2sh( - addresses.ecdsa_hash_pubkey(pubkey, self.coin) - ) - elif txi.script_type == InputScriptType.SPENDWITNESS: - # native p2wpkh or p2wsh - return scripts.input_script_native_p2wpkh_or_p2wsh() - elif txi.script_type == InputScriptType.SPENDMULTISIG: - # p2sh multisig - signature_index = multisig.multisig_pubkey_index(txi.multisig, pubkey) - return scripts.input_script_multisig( - txi.multisig, - signature, - signature_index, - self.get_hash_type(), - self.coin, - ) - else: - raise SigningError(FailureType.ProcessError, "Invalid script type") + return scripts.input_derive_script( + txi, self.coin, self.get_hash_type(), pubkey, signature + ) def input_is_segwit(txi: TxInputType) -> bool: diff --git a/core/src/apps/wallet/sign_tx/bitcoinlike.py b/core/src/apps/wallet/sign_tx/bitcoinlike.py index d3142c1aa..992732412 100644 --- a/core/src/apps/wallet/sign_tx/bitcoinlike.py +++ b/core/src/apps/wallet/sign_tx/bitcoinlike.py @@ -1,12 +1,10 @@ import gc from micropython import const -from trezor.crypto import cashaddr from trezor.messages import FailureType, InputScriptType from trezor.messages.SignTx import SignTx from trezor.messages.TransactionType import TransactionType from trezor.messages.TxInputType import TxInputType -from trezor.messages.TxOutputType import TxOutputType from apps.wallet.sign_tx import addresses, helpers, multisig, writers from apps.wallet.sign_tx.bitcoin import Bitcoin @@ -87,22 +85,6 @@ class Bitcoinlike(Bitcoin): if not self.coin.negative_fee: super().on_negative_fee() - def get_raw_address(self, txo: TxOutputType) -> bytes: - if self.coin.cashaddr_prefix is not None and txo.address.startswith( - self.coin.cashaddr_prefix + ":" - ): - prefix, addr = txo.address.split(":") - version, data = cashaddr.decode(prefix, addr) - if version == cashaddr.ADDRESS_TYPE_P2KH: - version = self.coin.address_type - elif version == cashaddr.ADDRESS_TYPE_P2SH: - version = self.coin.address_type_p2sh - else: - raise SigningError("Unknown cashaddr address type") - return bytes([version]) + data - else: - return super().get_raw_address(txo) - def get_hash_type(self) -> int: SIGHASH_FORKID = const(0x40) hashtype = super().get_hash_type() diff --git a/core/src/apps/wallet/sign_tx/scripts.py b/core/src/apps/wallet/sign_tx/scripts.py index 260ab5340..f6d22f684 100644 --- a/core/src/apps/wallet/sign_tx/scripts.py +++ b/core/src/apps/wallet/sign_tx/scripts.py @@ -1,9 +1,20 @@ from trezor import utils +from trezor.crypto import base58, cashaddr +from trezor.crypto.hashlib import sha256 +from trezor.messages import FailureType, InputScriptType, OutputScriptType from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType +from trezor.messages.TxInputType import TxInputType +from trezor.messages.TxOutputType import TxOutputType +from apps.common import address_type from apps.common.coininfo import CoinInfo from apps.common.writers import empty_bytearray -from apps.wallet.sign_tx.multisig import multisig_get_pubkey_count, multisig_get_pubkeys +from apps.wallet.sign_tx import addresses +from apps.wallet.sign_tx.multisig import ( + multisig_get_pubkey_count, + multisig_get_pubkeys, + multisig_pubkey_index, +) from apps.wallet.sign_tx.writers import ( write_bytes_fixed, write_bytes_unchecked, @@ -12,7 +23,7 @@ from apps.wallet.sign_tx.writers import ( ) if False: - from typing import List + from typing import List, Optional from apps.wallet.sign_tx.writers import Writer @@ -20,6 +31,86 @@ class ScriptsError(ValueError): pass +def input_derive_script( + txi: TxInputType, + coin: CoinInfo, + hash_type: int, + pubkey: bytes, + signature: Optional[bytes], +) -> bytes: + if txi.script_type == InputScriptType.SPENDADDRESS: + # p2pkh or p2sh + return input_script_p2pkh_or_p2sh(pubkey, signature, hash_type) + + if txi.script_type == InputScriptType.SPENDP2SHWITNESS: + # p2wpkh or p2wsh using p2sh + + if txi.multisig: + # p2wsh in p2sh + pubkeys = multisig_get_pubkeys(txi.multisig) + witness_script_hasher = utils.HashWriter(sha256()) + write_output_script_multisig(witness_script_hasher, pubkeys, txi.multisig.m) + witness_script_hash = witness_script_hasher.get_digest() + return input_script_p2wsh_in_p2sh(witness_script_hash) + + # p2wpkh in p2sh + return input_script_p2wpkh_in_p2sh(addresses.ecdsa_hash_pubkey(pubkey, coin)) + elif txi.script_type == InputScriptType.SPENDWITNESS: + # native p2wpkh or p2wsh + return input_script_native_p2wpkh_or_p2wsh() + elif txi.script_type == InputScriptType.SPENDMULTISIG: + # p2sh multisig + signature_index = multisig_pubkey_index(txi.multisig, pubkey) + return input_script_multisig( + txi.multisig, signature, signature_index, hash_type, coin + ) + else: + raise ScriptsError(FailureType.ProcessError, "Invalid script type") + + +def output_derive_script(txo: TxOutputType, coin: CoinInfo) -> bytes: + if txo.script_type == OutputScriptType.PAYTOOPRETURN: + return output_script_paytoopreturn(txo.op_return_data) + + if coin.bech32_prefix and txo.address.startswith(coin.bech32_prefix): + # p2wpkh or p2wsh + witprog = addresses.decode_bech32_address(coin.bech32_prefix, txo.address) + return output_script_native_p2wpkh_or_p2wsh(witprog) + + if ( + not utils.BITCOIN_ONLY + and coin.cashaddr_prefix is not None + and txo.address.startswith(coin.cashaddr_prefix + ":") + ): + prefix, addr = txo.address.split(":") + version, data = cashaddr.decode(prefix, addr) + if version == cashaddr.ADDRESS_TYPE_P2KH: + version = coin.address_type + elif version == cashaddr.ADDRESS_TYPE_P2SH: + version = coin.address_type_p2sh + else: + raise ScriptsError("Unknown cashaddr address type") + raw_address = bytes([version]) + data + else: + try: + raw_address = base58.decode_check(txo.address, coin.b58_hash) + except ValueError: + raise ScriptsError(FailureType.DataError, "Invalid address") + + if address_type.check(coin.address_type, raw_address): + # p2pkh + pubkeyhash = address_type.strip(coin.address_type, raw_address) + script = output_script_p2pkh(pubkeyhash) + return script + elif address_type.check(coin.address_type_p2sh, raw_address): + # p2sh + scripthash = address_type.strip(coin.address_type_p2sh, raw_address) + script = output_script_p2sh(scripthash) + return script + + raise ScriptsError(FailureType.DataError, "Invalid address type") + + # P2PKH, P2SH # === # https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki @@ -225,7 +316,7 @@ def input_script_multisig( w = empty_bytearray(total_length) - if not coin.decred: + if utils.BITCOIN_ONLY or not coin.decred: # Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which # consumes one additional item on the stack: # https://bitcoin.org/en/developer-guide#standard-transactions @@ -242,7 +333,7 @@ def input_script_multisig( return w -def output_script_multisig(pubkeys: List[bytes], m: int, w: Writer = None) -> bytearray: +def output_script_multisig(pubkeys: List[bytes], m: int) -> bytearray: w = empty_bytearray(output_script_multisig_length(pubkeys, m)) write_output_script_multisig(w, pubkeys, m) return w diff --git a/core/tests/test_apps.wallet.segwit.bip143.native_p2wpkh.py b/core/tests/test_apps.wallet.segwit.bip143.native_p2wpkh.py index 4ed11d7f0..9cff689d1 100644 --- a/core/tests/test_apps.wallet.segwit.bip143.native_p2wpkh.py +++ b/core/tests/test_apps.wallet.segwit.bip143.native_p2wpkh.py @@ -1,6 +1,6 @@ from common import * -from apps.wallet.sign_tx import bitcoin +from apps.wallet.sign_tx.scripts import output_derive_script from apps.wallet.sign_tx.segwit_bip143 import * from apps.common import coins from trezor.messages.SignTx import SignTx @@ -61,17 +61,14 @@ class TestSegwitBip143NativeP2WPKH(unittest.TestCase): def test_outputs(self): seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '') - root = bip32.from_seed(seed, 'secp256k1') coin = coins.by_name(self.tx.coin_name) - coinsig = bitcoin.Bitcoin() - coinsig.initialize(self.tx, root, coin) bip143 = Bip143() for txo in [self.out1, self.out2]: txo_bin = TxOutputBinType() txo_bin.amount = txo.amount - txo_bin.script_pubkey = coinsig.output_derive_script(txo) + txo_bin.script_pubkey = output_derive_script(txo, coin) bip143.add_output(txo_bin) self.assertEqual(hexlify(bip143.get_outputs_hash(coin)), @@ -80,10 +77,7 @@ class TestSegwitBip143NativeP2WPKH(unittest.TestCase): def test_preimage_testdata(self): seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '') - root = bip32.from_seed(seed, 'secp256k1') coin = coins.by_name(self.tx.coin_name) - coinsig = bitcoin.Bitcoin() - coinsig.initialize(self.tx, root, coin) bip143 = Bip143() bip143.add_prevouts(self.inp1) @@ -94,7 +88,7 @@ class TestSegwitBip143NativeP2WPKH(unittest.TestCase): for txo in [self.out1, self.out2]: txo_bin = TxOutputBinType() txo_bin.amount = txo.amount - txo_bin.script_pubkey = coinsig.output_derive_script(txo) + txo_bin.script_pubkey = output_derive_script(txo, coin) bip143.add_output(txo_bin) # test data public key hash diff --git a/core/tests/test_apps.wallet.segwit.bip143.p2wpkh_in_p2sh.py b/core/tests/test_apps.wallet.segwit.bip143.p2wpkh_in_p2sh.py index f031c536f..e4cbf153f 100644 --- a/core/tests/test_apps.wallet.segwit.bip143.p2wpkh_in_p2sh.py +++ b/core/tests/test_apps.wallet.segwit.bip143.p2wpkh_in_p2sh.py @@ -1,6 +1,6 @@ from common import * -from apps.wallet.sign_tx import bitcoin +from apps.wallet.sign_tx.scripts import output_derive_script from apps.wallet.sign_tx.segwit_bip143 import * from apps.common import coins from trezor.messages.SignTx import SignTx @@ -51,17 +51,14 @@ class TestSegwitBip143(unittest.TestCase): def test_bip143_outputs(self): seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '') - root = bip32.from_seed(seed, 'secp256k1') coin = coins.by_name(self.tx.coin_name) - coinsig = bitcoin.Bitcoin() - coinsig.initialize(self.tx, root, coin) bip143 = Bip143() for txo in [self.out1, self.out2]: txo_bin = TxOutputBinType() txo_bin.amount = txo.amount - txo_bin.script_pubkey = coinsig.output_derive_script(txo) + txo_bin.script_pubkey = output_derive_script(txo, coin) bip143.add_output(txo_bin) self.assertEqual(hexlify(bip143.get_outputs_hash(coin)), @@ -70,10 +67,7 @@ class TestSegwitBip143(unittest.TestCase): def test_bip143_preimage_testdata(self): seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '') - root = bip32.from_seed(seed, 'secp256k1') coin = coins.by_name(self.tx.coin_name) - coinsig = bitcoin.Bitcoin() - coinsig.initialize(self.tx, root, coin) bip143 = Bip143() bip143.add_prevouts(self.inp1) @@ -81,7 +75,7 @@ class TestSegwitBip143(unittest.TestCase): for txo in [self.out1, self.out2]: txo_bin = TxOutputBinType() txo_bin.amount = txo.amount - txo_bin.script_pubkey = coinsig.output_derive_script(txo) + txo_bin.script_pubkey = output_derive_script(txo, coin) bip143.add_output(txo_bin) # test data public key hash diff --git a/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh.py b/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh.py index 9a0dbc62f..dfce9b138 100644 --- a/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh.py +++ b/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh.py @@ -17,7 +17,7 @@ from trezor.messages import OutputScriptType from apps.common import coins from apps.common.seed import Keychain from apps.wallet.sign_tx import helpers, bitcoin -from apps.wallet.sign_tx.common import SigningError +from apps.wallet.sign_tx.scripts import ScriptsError EMPTY_SERIALIZED = TxRequestSerializedType(serialized_tx=bytearray()) @@ -271,7 +271,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): signer = coinsig.signer(tx, keychain, coin) for request, response in chunks(messages, 2): if response is None: - with self.assertRaises(SigningError): + with self.assertRaises(ScriptsError): signer.send(request) else: self.assertEqual(signer.send(request), response) diff --git a/core/tests/test_apps.wallet.txweight.py b/core/tests/test_apps.wallet.txweight.py index cdd07b3fd..4d81a7705 100644 --- a/core/tests/test_apps.wallet.txweight.py +++ b/core/tests/test_apps.wallet.txweight.py @@ -7,7 +7,7 @@ from trezor.crypto import bip32, bip39 from apps.common import coins from apps.wallet.sign_tx.tx_weight import * -from apps.wallet.sign_tx import bitcoin +from apps.wallet.sign_tx.scripts import output_derive_script class TestCalculateTxWeight(unittest.TestCase): @@ -16,11 +16,7 @@ class TestCalculateTxWeight(unittest.TestCase): def test_p2pkh_txweight(self): coin = coins.by_name('Bitcoin') - coinsig = bitcoin.Bitcoin() - seed = bip39.seed(' '.join(['all'] * 12), '') - root = bip32.from_seed(seed, 'secp256k1') - coinsig.initialize(SignTx(), root, coin) inp1 = TxInputType(address_n=[0], # 14LmW5k4ssUrtbAB4255zdqv3b4w1TuX9e # amount=390000, @@ -38,7 +34,7 @@ class TestCalculateTxWeight(unittest.TestCase): calculator = TxWeightCalculator(1, 1) calculator.add_input(inp1) - calculator.add_output(coinsig.output_derive_script(out1)) + calculator.add_output(output_derive_script(out1, coin)) serialized_tx = '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0160cc0500000000001976a914de9b2a8da088824e8fe51debea566617d851537888ac00000000' tx_weight = len(serialized_tx) / 2 * 4 # non-segwit tx's weight is simple length*4 @@ -48,11 +44,7 @@ class TestCalculateTxWeight(unittest.TestCase): def test_p2wpkh_in_p2sh_txweight(self): coin = coins.by_name('Testnet') - coinsig = bitcoin.Bitcoin() - seed = bip39.seed(' '.join(['all'] * 12), '') - root = bip32.from_seed(seed, 'secp256k1') - coinsig.initialize(SignTx(), root, coin) inp1 = TxInputType( # 49'/1'/0'/1/0" - 2N1LGaGg836mqSQqiuUBLfcyGBhyZbremDX @@ -81,8 +73,8 @@ class TestCalculateTxWeight(unittest.TestCase): calculator = TxWeightCalculator(1, 2) calculator.add_input(inp1) - calculator.add_output(coinsig.output_derive_script(out1)) - calculator.add_output(coinsig.output_derive_script(out2)) + calculator.add_output(output_derive_script(out1, coin)) + calculator.add_output(output_derive_script(out2, coin)) self.assertEqual(calculator.get_total(), 670) # non-segwit: header, inputs, outputs, locktime 4*(4+65+67+4) = 560 @@ -92,11 +84,7 @@ class TestCalculateTxWeight(unittest.TestCase): def test_native_p2wpkh_txweight(self): coin = coins.by_name('Testnet') - coinsig = bitcoin.Bitcoin() - seed = bip39.seed(' '.join(['all'] * 12), '') - root = bip32.from_seed(seed, 'secp256k1') - coinsig.initialize(SignTx(), root, coin) inp1 = TxInputType( # 49'/1'/0'/0/0" - tb1qqzv60m9ajw8drqulta4ld4gfx0rdh82un5s65s @@ -125,8 +113,8 @@ class TestCalculateTxWeight(unittest.TestCase): calculator = TxWeightCalculator(1, 2) calculator.add_input(inp1) - calculator.add_output(coinsig.output_derive_script(out1)) - calculator.add_output(coinsig.output_derive_script(out2)) + calculator.add_output(output_derive_script(out1, coin)) + calculator.add_output(output_derive_script(out2, coin)) self.assertEqual(calculator.get_total(), 566) # non-segwit: header, inputs, outputs, locktime 4*(4+42+64+4) = 456