From 27e6f35f78887ee2018a42dede76ec8e6aeaa9e5 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Mon, 22 Mar 2021 18:56:28 +0100 Subject: [PATCH] refactor(core/bitcoin): Change scripts to use writer semantics. --- core/.changelog.d/1581.changed | 1 + core/src/apps/bitcoin/scripts.py | 185 ++++++++++--------- core/src/apps/bitcoin/scripts_decred.py | 82 ++++---- core/src/apps/bitcoin/sign_tx/bitcoin.py | 57 +++--- core/src/apps/bitcoin/sign_tx/bitcoinlike.py | 3 +- core/src/apps/bitcoin/sign_tx/decred.py | 63 ++++--- core/src/apps/bitcoin/sign_tx/hash143.py | 5 +- core/src/apps/bitcoin/sign_tx/tx_info.py | 22 +-- core/src/apps/bitcoin/sign_tx/zcash.py | 22 +-- core/src/apps/bitcoin/verification.py | 14 +- core/src/apps/bitcoin/writers.py | 12 ++ 11 files changed, 219 insertions(+), 247 deletions(-) create mode 100644 core/.changelog.d/1581.changed diff --git a/core/.changelog.d/1581.changed b/core/.changelog.d/1581.changed new file mode 100644 index 0000000000..754651675e --- /dev/null +++ b/core/.changelog.d/1581.changed @@ -0,0 +1 @@ +Memory optimization of BTC signing and CBOR decoding. diff --git a/core/src/apps/bitcoin/scripts.py b/core/src/apps/bitcoin/scripts.py index 7b25e42c86..31b3af61b7 100644 --- a/core/src/apps/bitcoin/scripts.py +++ b/core/src/apps/bitcoin/scripts.py @@ -15,6 +15,7 @@ from .multisig import ( ) from .readers import read_bytes_prefixed, read_op_push from .writers import ( + op_push_length, write_bytes_fixed, write_bytes_prefixed, write_bytes_unchecked, @@ -29,40 +30,44 @@ if False: from .writers import Writer -def input_derive_script( +def write_input_script_prefixed( + w: Writer, script_type: InputScriptType, multisig: MultisigRedeemScriptType | None, coin: CoinInfo, hash_type: int, pubkey: bytes, signature: bytes, -) -> bytes: +) -> None: if script_type == InputScriptType.SPENDADDRESS: # p2pkh or p2sh - return input_script_p2pkh_or_p2sh(pubkey, signature, hash_type) - - if script_type == InputScriptType.SPENDP2SHWITNESS: + write_input_script_p2pkh_or_p2sh_prefixed(w, pubkey, signature, hash_type) + elif script_type == InputScriptType.SPENDP2SHWITNESS: # p2wpkh or p2wsh using p2sh if multisig is not None: # p2wsh in p2sh pubkeys = multisig_get_pubkeys(multisig) - witness_script_hasher = utils.HashWriter(sha256()) - write_output_script_multisig(witness_script_hasher, pubkeys, 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(common.ecdsa_hash_pubkey(pubkey, coin)) + witness_script_h = utils.HashWriter(sha256()) + write_output_script_multisig(witness_script_h, pubkeys, multisig.m) + write_input_script_p2wsh_in_p2sh( + w, witness_script_h.get_digest(), prefixed=True + ) + else: + # p2wpkh in p2sh + write_input_script_p2wpkh_in_p2sh( + w, common.ecdsa_hash_pubkey(pubkey, coin), prefixed=True + ) elif script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or p2wsh - return input_script_native_p2wpkh_or_p2wsh() + script_sig = input_script_native_p2wpkh_or_p2wsh() + write_bytes_prefixed(w, script_sig) elif script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig assert multisig is not None # checked in sanitize_tx_input signature_index = multisig_pubkey_index(multisig, pubkey) - return input_script_multisig( - multisig, signature, signature_index, hash_type, coin + write_input_script_multisig_prefixed( + w, multisig, signature, signature_index, hash_type, coin ) else: raise wire.ProcessError("Invalid script type") @@ -110,11 +115,12 @@ def output_derive_script(address: str, coin: CoinInfo) -> bytes: # see https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification # item 5 for details -def bip143_derive_script_code( - txi: TxInput, public_keys: list[bytes], threshold: int, coin: CoinInfo -) -> bytearray: +def write_bip143_script_code_prefixed( + w: Writer, txi: TxInput, public_keys: list[bytes], threshold: int, coin: CoinInfo +) -> None: if len(public_keys) > 1: - return output_script_multisig(public_keys, threshold) + write_output_script_multisig(w, public_keys, threshold, prefixed=True) + return p2pkh = ( txi.script_type == InputScriptType.SPENDWITNESS @@ -122,11 +128,13 @@ def bip143_derive_script_code( or txi.script_type == InputScriptType.SPENDADDRESS or txi.script_type == InputScriptType.EXTERNAL ) + if p2pkh: # for p2wpkh in p2sh or native p2wpkh # the scriptCode is a classic p2pkh - return output_script_p2pkh(common.ecdsa_hash_pubkey(public_keys[0], coin)) - + write_output_script_p2pkh( + w, common.ecdsa_hash_pubkey(public_keys[0], coin), prefixed=True + ) else: raise wire.DataError("Unknown input script type for bip143 script code") @@ -136,13 +144,12 @@ def bip143_derive_script_code( # https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki -def input_script_p2pkh_or_p2sh( - pubkey: bytes, signature: bytes, hash_type: int -) -> bytearray: - w = utils.empty_bytearray(5 + len(signature) + 1 + 5 + len(pubkey)) +def write_input_script_p2pkh_or_p2sh_prefixed( + w: Writer, pubkey: bytes, signature: bytes, hash_type: int +) -> None: + write_bitcoin_varint(w, 1 + len(signature) + 1 + 1 + len(pubkey)) append_signature(w, signature, hash_type) append_pubkey(w, pubkey) - return w def parse_input_script_p2pkh(script_sig: bytes) -> tuple[bytes, bytes, int]: @@ -162,15 +169,22 @@ def parse_input_script_p2pkh(script_sig: bytes) -> tuple[bytes, bytes, int]: return pubkey, signature, hash_type +def write_output_script_p2pkh( + w: Writer, pubkeyhash: bytes, prefixed: bool = False +) -> None: + if prefixed: + write_bitcoin_varint(w, 25) + w.append(0x76) # OP_DUP + w.append(0xA9) # OP_HASH160 + w.append(0x14) # OP_DATA_20 + write_bytes_fixed(w, pubkeyhash, 20) + w.append(0x88) # OP_EQUALVERIFY + w.append(0xAC) # OP_CHECKSIG + + def output_script_p2pkh(pubkeyhash: bytes) -> bytearray: - utils.ensure(len(pubkeyhash) == 20) - s = bytearray(25) - s[0] = 0x76 # OP_DUP - s[1] = 0xA9 # OP_HASH_160 - s[2] = 0x14 # pushing 20 bytes - s[3:23] = pubkeyhash - s[23] = 0x88 # OP_EQUALVERIFY - s[24] = 0xAC # OP_CHECKSIG + s = utils.empty_bytearray(25) + write_output_script_p2pkh(s, pubkeyhash) return s @@ -224,17 +238,18 @@ def output_script_native_p2wpkh_or_p2wsh(witprog: bytes) -> bytearray: # Uses normal P2SH output scripts. -def input_script_p2wpkh_in_p2sh(pubkeyhash: bytes) -> bytearray: +def write_input_script_p2wpkh_in_p2sh( + w: Writer, pubkeyhash: bytes, prefixed: bool = False +) -> None: # 16 00 14 # Signature is moved to the witness. - utils.ensure(len(pubkeyhash) == 20) + if prefixed: + write_bitcoin_varint(w, 23) - w = utils.empty_bytearray(3 + len(pubkeyhash)) w.append(0x16) # length of the data w.append(0x00) # witness version byte w.append(0x14) # P2WPKH witness program (pub key hash length) write_bytes_fixed(w, pubkeyhash, 20) # pub key hash - return w # SegWit: P2WSH nested in P2SH @@ -245,31 +260,30 @@ def input_script_p2wpkh_in_p2sh(pubkeyhash: bytes) -> bytearray: # Uses normal P2SH output scripts. -def input_script_p2wsh_in_p2sh(script_hash: bytes) -> bytearray: +def write_input_script_p2wsh_in_p2sh( + w: Writer, script_hash: bytes, prefixed: bool = False +) -> None: # 22 00 20 # Signature is moved to the witness. + if prefixed: + write_bitcoin_varint(w, 35) - if len(script_hash) != 32: - raise wire.DataError("Redeem script hash should be 32 bytes long") - - w = utils.empty_bytearray(3 + len(script_hash)) w.append(0x22) # length of the data w.append(0x00) # witness version byte w.append(0x20) # P2WSH witness program (redeem script hash length) write_bytes_fixed(w, script_hash, 32) - return w # SegWit: Witness getters # === -def witness_p2wpkh(signature: bytes, pubkey: bytes, hash_type: int) -> bytearray: - w = utils.empty_bytearray(1 + 5 + len(signature) + 1 + 5 + len(pubkey)) +def write_witness_p2wpkh( + w: Writer, signature: bytes, pubkey: bytes, hash_type: int +) -> None: write_bitcoin_varint(w, 0x02) # num of segwit items, in P2WPKH it's always 2 write_signature_prefixed(w, signature, hash_type) write_bytes_prefixed(w, pubkey) - return w def parse_witness_p2wpkh(witness: bytes) -> tuple[bytes, bytes, int]: @@ -293,53 +307,39 @@ def parse_witness_p2wpkh(witness: bytes) -> tuple[bytes, bytes, int]: return pubkey, signature, hash_type -def witness_multisig( +def write_witness_multisig( + w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, hash_type: int, -) -> bytearray: +) -> None: # 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) ) + # fill in our signature if signatures[signature_index]: raise wire.DataError("Invalid multisig parameters") signatures[signature_index] = signature - # filter empty - signatures = [s for s in signatures if s] - # witness program + signatures + redeem script - num_of_witness_items = 1 + len(signatures) + 1 - - # length of the redeem script - pubkeys = multisig_get_pubkeys(multisig) - redeem_script_length = output_script_multisig_length(pubkeys, multisig.m) - - # length of the result - total_length = 1 + 1 # number of items, OP_FALSE - for s in signatures: - total_length += 1 + len(s) + 1 # length, signature, hash_type - total_length += 1 + redeem_script_length # length, script - - w = utils.empty_bytearray(total_length) - + num_of_witness_items = 1 + sum(1 for s in signatures if s) + 1 write_bitcoin_varint(w, num_of_witness_items) + # 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 write_bitcoin_varint(w, 0) for s in signatures: - write_signature_prefixed(w, s, hash_type) # size of the witness included + if s: + write_signature_prefixed(w, s, hash_type) # size of the witness included # redeem script - write_bitcoin_varint(w, redeem_script_length) - write_output_script_multisig(w, pubkeys, multisig.m) - - return w + pubkeys = multisig_get_pubkeys(multisig) + write_output_script_multisig(w, pubkeys, multisig.m, prefixed=True) def parse_witness_multisig(witness: bytes) -> tuple[bytes, list[tuple[bytes, int]]]: @@ -375,13 +375,14 @@ def parse_witness_multisig(witness: bytes) -> tuple[bytes, list[tuple[bytes, int # Used either as P2SH, P2WSH, or P2WSH nested in P2SH. -def input_script_multisig( +def write_input_script_multisig_prefixed( + w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, hash_type: int, coin: CoinInfo, -) -> bytearray: +) -> None: signatures = multisig.signatures # other signatures if len(signatures[signature_index]) > 0: raise wire.DataError("Invalid multisig parameters") @@ -394,10 +395,10 @@ def input_script_multisig( # length of the result total_length = 1 # OP_FALSE for s in signatures: - total_length += 1 + len(s) + 1 # length, signature, hash_type - total_length += 1 + redeem_script_length # length, script - - w = utils.empty_bytearray(total_length) + if s: + total_length += 1 + len(s) + 1 # length, signature, hash_type + total_length += op_push_length(redeem_script_length) + redeem_script_length + write_bitcoin_varint(w, total_length) # Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which # consumes one additional item on the stack: @@ -405,15 +406,13 @@ def input_script_multisig( w.append(0x00) for s in signatures: - if len(s): + if s: append_signature(w, s, hash_type) # redeem script write_op_push(w, redeem_script_length) write_output_script_multisig(w, pubkeys, multisig.m) - return w - def parse_input_script_multisig( script_sig: bytes, @@ -448,7 +447,12 @@ def output_script_multisig(pubkeys: list[bytes], m: int) -> bytearray: return w -def write_output_script_multisig(w: Writer, pubkeys: list[bytes], m: int) -> None: +def write_output_script_multisig( + w: Writer, + pubkeys: list[bytes], + m: int, + prefixed: bool = False, +) -> 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") @@ -456,6 +460,9 @@ def write_output_script_multisig(w: Writer, pubkeys: list[bytes], m: int) -> Non if len(pubkey) != 33: raise wire.DataError("Invalid multisig parameters") + if prefixed: + write_bitcoin_varint(w, output_script_multisig_length(pubkeys, m)) + w.append(0x50 + m) # numbers 1 to 16 are pushed as 0x50 + value for p in pubkeys: append_pubkey(w, p) @@ -525,24 +532,22 @@ def write_bip322_signature_proof( public_key: bytes, signature: bytes, ) -> None: - script_sig = input_derive_script( - script_type, multisig, coin, common.SIGHASH_ALL, public_key, signature + write_input_script_prefixed( + w, script_type, multisig, coin, common.SIGHASH_ALL, public_key, signature ) + if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: if multisig: # find the place of our signature based on the public key signature_index = multisig_pubkey_index(multisig, public_key) - witness = witness_multisig( - multisig, signature, signature_index, common.SIGHASH_ALL + write_witness_multisig( + w, multisig, signature, signature_index, common.SIGHASH_ALL ) else: - witness = witness_p2wpkh(signature, public_key, common.SIGHASH_ALL) + write_witness_p2wpkh(w, signature, public_key, common.SIGHASH_ALL) else: # Zero entries in witness stack. - witness = bytearray(b"\x00") - - write_bytes_prefixed(w, script_sig) - w.extend(witness) + w.append(0x00) def read_bip322_signature_proof(r: utils.BufferReader) -> tuple[bytes, bytes]: diff --git a/core/src/apps/bitcoin/scripts_decred.py b/core/src/apps/bitcoin/scripts_decred.py index 6befe1c54e..18d41a798e 100644 --- a/core/src/apps/bitcoin/scripts_decred.py +++ b/core/src/apps/bitcoin/scripts_decred.py @@ -8,47 +8,53 @@ from apps.common.writers import write_bytes_fixed, write_uint64_le from . import scripts from .multisig import multisig_get_pubkeys, multisig_pubkey_index from .scripts import ( # noqa: F401 - output_script_multisig, - output_script_p2pkh, output_script_paytoopreturn, + write_output_script_multisig, + write_output_script_p2pkh, ) -from .writers import write_op_push +from .writers import op_push_length, write_bitcoin_varint, write_op_push if False: from trezor.messages import MultisigRedeemScriptType from apps.common.coininfo import CoinInfo + from .writers import Writer -def input_derive_script( + +def write_input_script_prefixed( + w: Writer, script_type: InputScriptType, multisig: MultisigRedeemScriptType | None, coin: CoinInfo, hash_type: int, pubkey: bytes, signature: bytes, -) -> bytes: +) -> None: if script_type == InputScriptType.SPENDADDRESS: # p2pkh or p2sh - return scripts.input_script_p2pkh_or_p2sh(pubkey, signature, hash_type) + scripts.write_input_script_p2pkh_or_p2sh_prefixed( + w, pubkey, signature, hash_type + ) elif script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig assert multisig is not None # checked in sanitize_tx_input signature_index = multisig_pubkey_index(multisig, pubkey) - return input_script_multisig( - multisig, signature, signature_index, hash_type, coin + return write_input_script_multisig_prefixed( + w, multisig, signature, signature_index, hash_type, coin ) else: raise wire.ProcessError("Invalid script type") -def input_script_multisig( +def write_input_script_multisig_prefixed( + w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, hash_type: int, coin: CoinInfo, -) -> bytearray: +) -> None: signatures = multisig.signatures # other signatures if len(signatures[signature_index]) > 0: raise wire.DataError("Invalid multisig parameters") @@ -61,21 +67,19 @@ def input_script_multisig( # length of the result total_length = 0 for s in signatures: - total_length += 1 + len(s) + 1 # length, signature, hash_type - total_length += 1 + redeem_script_length # length, script - - w = utils.empty_bytearray(total_length) + if s: + total_length += 1 + len(s) + 1 # length, signature, hash_type + total_length += op_push_length(redeem_script_length) + redeem_script_length + write_bitcoin_varint(w, total_length) for s in signatures: - if len(s): + if s: scripts.append_signature(w, s, hash_type) # redeem script write_op_push(w, redeem_script_length) scripts.write_output_script_multisig(w, pubkeys, multisig.m) - return w - # A ticket purchase submission for an address hash. def output_script_sstxsubmissionpkh(addr: str) -> bytearray: @@ -86,12 +90,7 @@ def output_script_sstxsubmissionpkh(addr: str) -> bytearray: w = utils.empty_bytearray(26) w.append(0xBA) # OP_SSTX - w.append(0x76) # OP_DUP - w.append(0xA9) # OP_HASH160 - w.append(0x14) # OP_DATA_20 - write_bytes_fixed(w, raw_address[2:], 20) - w.append(0x88) # OP_EQUALVERIFY - w.append(0xAC) # OP_CHECKSIG + scripts.write_output_script_p2pkh(w, raw_address[2:]) return w @@ -104,44 +103,27 @@ def output_script_sstxchange(addr: str) -> bytearray: w = utils.empty_bytearray(26) w.append(0xBD) # OP_SSTXCHANGE - w.append(0x76) # OP_DUP - w.append(0xA9) # OP_HASH160 - w.append(0x14) # OP_DATA_20 - write_bytes_fixed(w, raw_address[2:], 20) - w.append(0x88) # OP_EQUALVERIFY - w.append(0xAC) # OP_CHECKSIG + scripts.write_output_script_p2pkh(w, raw_address[2:]) return w # Spend from a stake revocation. -def output_script_ssrtx(pkh: bytes) -> bytearray: +def write_output_script_ssrtx_prefixed(w: Writer, pkh: bytes) -> None: utils.ensure(len(pkh) == 20) - s = bytearray(26) - s[0] = 0xBC # OP_SSRTX - s[1] = 0x76 # OP_DUP - s[2] = 0xA9 # OP_HASH160 - s[3] = 0x14 # OP_DATA_20 - s[4:24] = pkh - s[24] = 0x88 # OP_EQUALVERIFY - s[25] = 0xAC # OP_CHECKSIG - return s + write_bitcoin_varint(w, 26) + w.append(0xBC) # OP_SSRTX + scripts.write_output_script_p2pkh(w, pkh) # Spend from a stake generation. -def output_script_ssgen(pkh: bytes) -> bytearray: +def write_output_script_ssgen_prefixed(w: Writer, pkh: bytes) -> None: utils.ensure(len(pkh) == 20) - s = bytearray(26) - s[0] = 0xBB # OP_SSGEN - s[1] = 0x76 # OP_DUP - s[2] = 0xA9 # OP_HASH160 - s[3] = 0x14 # OP_DATA_20 - s[4:24] = pkh - s[24] = 0x88 # OP_EQUALVERIFY - s[25] = 0xAC # OP_CHECKSIG - return s + write_bitcoin_varint(w, 26) + w.append(0xBB) # OP_SSGEN + scripts.write_output_script_p2pkh(w, pkh) -# Retrieve pkh bytes from a stake commitment OPRETURN. +# Stake commitment OPRETURN. def sstxcommitment_pkh(pkh: bytes, amount: int) -> bytes: w = utils.empty_bytearray(30) write_bytes_fixed(w, pkh, 20) diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index dab237cd12..10a4e11569 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -459,8 +459,7 @@ class Bitcoin: node = self.keychain.derive(txi.address_n) key_sign_pub = node.public_key() - script_sig = self.input_derive_script(txi, key_sign_pub, b"") - self.write_tx_input(self.serialized_tx, txi, script_sig) + self.write_tx_input_derived(self.serialized_tx, txi, key_sign_pub, b"") def sign_bip143_input(self, txi: TxInput) -> tuple[bytes, bytes]: self.tx_info.check_input(txi) @@ -500,14 +499,16 @@ class Bitcoin: if txi.multisig: # find out place of our signature based on the pubkey signature_index = multisig.multisig_pubkey_index(txi.multisig, public_key) - self.serialized_tx.extend( - scripts.witness_multisig( - txi.multisig, signature, signature_index, self.get_hash_type(txi) - ) + scripts.write_witness_multisig( + self.serialized_tx, + txi.multisig, + signature, + signature_index, + self.get_hash_type(txi), ) else: - self.serialized_tx.extend( - scripts.witness_p2wpkh(signature, public_key, self.get_hash_type(txi)) + scripts.write_witness_p2wpkh( + self.serialized_tx, signature, public_key, self.get_hash_type(txi) ) async def get_legacy_tx_digest( @@ -585,8 +586,9 @@ class Bitcoin: signature = ecdsa_sign(node, tx_digest) # serialize input with correct signature - script_sig = self.input_derive_script(txi, node.public_key(), signature) - self.write_tx_input(self.serialized_tx, txi, script_sig) + self.write_tx_input_derived( + self.serialized_tx, txi, node.public_key(), signature + ) self.set_serialized_signature(i, signature) async def serialize_output(self, i: int) -> None: @@ -659,6 +661,26 @@ class Bitcoin: # the fork ID value. return self.get_sighash_type(txi) & 0xFF + def write_tx_input_derived( + self, + w: writers.Writer, + txi: TxInput, + pubkey: bytes, + signature: bytes, + ) -> None: + writers.write_bytes_reversed(w, txi.prev_hash, writers.TX_HASH_SIZE) + writers.write_uint32(w, txi.prev_index) + scripts.write_input_script_prefixed( + w, + txi.script_type, + txi.multisig, + self.coin, + self.get_hash_type(txi), + pubkey, + signature, + ) + writers.write_uint32(w, txi.sequence) + @staticmethod def write_tx_input( w: writers.Writer, @@ -726,18 +748,3 @@ class Bitcoin: assert txo.address is not None # checked in sanitize_tx_output return scripts.output_derive_script(txo.address, self.coin) - - # Tx Inputs - # === - - def input_derive_script( - self, txi: TxInput, pubkey: bytes, signature: bytes - ) -> bytes: - return scripts.input_derive_script( - txi.script_type, - txi.multisig, - self.coin, - self.get_hash_type(txi), - pubkey, - signature, - ) diff --git a/core/src/apps/bitcoin/sign_tx/bitcoinlike.py b/core/src/apps/bitcoin/sign_tx/bitcoinlike.py index 9f10a9c700..eeaf643386 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoinlike.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoinlike.py @@ -29,8 +29,7 @@ class Bitcoinlike(Bitcoin): multisig.multisig_pubkey_index(txi.multisig, public_key) # serialize input with correct signature - script_sig = self.input_derive_script(txi, public_key, signature) - self.write_tx_input(self.serialized_tx, txi, script_sig) + self.write_tx_input_derived(self.serialized_tx, txi, public_key, signature) self.set_serialized_signature(i_sign, signature) async def sign_nonsegwit_input(self, i_sign: int) -> None: diff --git a/core/src/apps/bitcoin/sign_tx/decred.py b/core/src/apps/bitcoin/sign_tx/decred.py index 2751b63d7a..f768e0ad95 100644 --- a/core/src/apps/bitcoin/sign_tx/decred.py +++ b/core/src/apps/bitcoin/sign_tx/decred.py @@ -147,27 +147,6 @@ class Decred(Bitcoin): key_sign = self.keychain.derive(txi_sign.address_n) key_sign_pub = key_sign.public_key() - if txi_sign.decred_staking_spend == DecredStakingSpendType.SSRTX: - prev_pkscript = scripts_decred.output_script_ssrtx( - ecdsa_hash_pubkey(key_sign_pub, self.coin) - ) - elif txi_sign.decred_staking_spend == DecredStakingSpendType.SSGen: - prev_pkscript = scripts_decred.output_script_ssgen( - ecdsa_hash_pubkey(key_sign_pub, self.coin) - ) - elif txi_sign.script_type == InputScriptType.SPENDMULTISIG: - assert txi_sign.multisig is not None - prev_pkscript = scripts_decred.output_script_multisig( - multisig.multisig_get_pubkeys(txi_sign.multisig), - txi_sign.multisig.m, - ) - elif txi_sign.script_type == InputScriptType.SPENDADDRESS: - prev_pkscript = scripts_decred.output_script_p2pkh( - ecdsa_hash_pubkey(key_sign_pub, self.coin) - ) - else: - raise wire.DataError("Unsupported input script type") - h_witness = self.create_hash_writer() writers.write_uint32( h_witness, self.tx_info.tx.version | DECRED_SERIALIZE_WITNESS_SIGNING @@ -176,7 +155,30 @@ class Decred(Bitcoin): for ii in range(self.tx_info.tx.inputs_count): if ii == i_sign: - writers.write_bytes_prefixed(h_witness, prev_pkscript) + 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) + ) + 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) + ) + elif txi_sign.script_type == InputScriptType.SPENDMULTISIG: + assert txi_sign.multisig is not None + scripts_decred.write_output_script_multisig( + h_witness, + multisig.multisig_get_pubkeys(txi_sign.multisig), + txi_sign.multisig.m, + prefixed=True, + ) + elif txi_sign.script_type == InputScriptType.SPENDADDRESS: + scripts_decred.write_output_script_p2pkh( + h_witness, + ecdsa_hash_pubkey(key_sign_pub, self.coin), + prefixed=True, + ) + else: + raise wire.DataError("Unsupported input script type") else: write_bitcoin_varint(h_witness, 0) @@ -193,8 +195,9 @@ class Decred(Bitcoin): signature = ecdsa_sign(key_sign, sig_hash) # serialize input with correct signature - script_sig = self.input_derive_script(txi_sign, key_sign_pub, signature) - self.write_tx_input_witness(self.serialized_tx, txi_sign, script_sig) + self.write_tx_input_witness( + self.serialized_tx, txi_sign, key_sign_pub, signature + ) self.set_serialized_signature(i_sign, signature) async def step5_serialize_outputs(self) -> None: @@ -306,17 +309,13 @@ class Decred(Bitcoin): writers.write_uint32(w, tx.expiry) def write_tx_input_witness( - self, w: writers.Writer, i: TxInput, script_sig: bytes + self, w: writers.Writer, txi: TxInput, pubkey: bytes, signature: bytes ) -> None: - writers.write_uint64(w, i.amount) + 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 - writers.write_bytes_prefixed(w, script_sig) - - def input_derive_script( - self, txi: TxInput, pubkey: bytes, signature: bytes - ) -> bytes: - return scripts_decred.input_derive_script( + scripts_decred.write_input_script_prefixed( + w, txi.script_type, txi.multisig, self.coin, diff --git a/core/src/apps/bitcoin/sign_tx/hash143.py b/core/src/apps/bitcoin/sign_tx/hash143.py index 70c0dba1f5..fe309272c5 100644 --- a/core/src/apps/bitcoin/sign_tx/hash143.py +++ b/core/src/apps/bitcoin/sign_tx/hash143.py @@ -76,10 +76,9 @@ class Bip143Hash: writers.write_uint32(h_preimage, txi.prev_index) # scriptCode - script_code = scripts.bip143_derive_script_code( - txi, public_keys, threshold, coin + scripts.write_bip143_script_code_prefixed( + h_preimage, txi, public_keys, threshold, coin ) - writers.write_bytes_prefixed(h_preimage, script_code) # amount writers.write_uint64(h_preimage, txi.amount) diff --git a/core/src/apps/bitcoin/sign_tx/tx_info.py b/core/src/apps/bitcoin/sign_tx/tx_info.py index 5ab229625a..5ee8b590f0 100644 --- a/core/src/apps/bitcoin/sign_tx/tx_info.py +++ b/core/src/apps/bitcoin/sign_tx/tx_info.py @@ -11,8 +11,6 @@ from .matchcheck import MultisigFingerprintChecker, WalletPathChecker if False: from typing import Protocol from trezor.messages import ( - PrevInput, - PrevOutput, PrevTx, SignTx, TxInput, @@ -39,22 +37,6 @@ if False: ) -> None: ... - @staticmethod - def write_tx_input( - w: writers.Writer, - txi: TxInput | PrevInput, - script: bytes, - ) -> None: - ... - - @staticmethod - def write_tx_output( - w: writers.Writer, - txo: TxOutput | PrevOutput, - script_pubkey: bytes, - ) -> None: - ... - async def write_prev_tx_footer( self, w: writers.Writer, tx: PrevTx, prev_hash: bytes ) -> None: @@ -167,7 +149,7 @@ class OriginalTxInfo(TxInfoBase): def add_input(self, txi: TxInput) -> None: super().add_input(txi) - self.signer.write_tx_input(self.h_tx, txi, txi.script_sig or bytes()) + writers.write_tx_input(self.h_tx, txi, txi.script_sig or bytes()) # For verification use the first original input that specifies address_n. if not self.verification_input and txi.address_n: @@ -180,7 +162,7 @@ class OriginalTxInfo(TxInfoBase): if self.index == 0: writers.write_bitcoin_varint(self.h_tx, self.tx.outputs_count) - self.signer.write_tx_output(self.h_tx, txo, script_pubkey) + writers.write_tx_output(self.h_tx, txo, script_pubkey) async def finalize_tx_hash(self) -> None: await self.signer.write_prev_tx_footer(self.h_tx, self.tx, self.orig_hash) diff --git a/core/src/apps/bitcoin/sign_tx/zcash.py b/core/src/apps/bitcoin/sign_tx/zcash.py index 295d3fc170..f886a1405d 100644 --- a/core/src/apps/bitcoin/sign_tx/zcash.py +++ b/core/src/apps/bitcoin/sign_tx/zcash.py @@ -3,7 +3,6 @@ from micropython import const from trezor import wire from trezor.crypto.hashlib import blake2b -from trezor.enums import InputScriptType from trezor.messages import PrevTx, SignTx, TxInput, TxOutput from trezor.utils import HashWriter, ensure @@ -11,13 +10,11 @@ from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain from apps.common.writers import write_bitcoin_varint -from ..common import ecdsa_hash_pubkey -from ..scripts import output_script_multisig, output_script_p2pkh +from ..scripts import write_bip143_script_code_prefixed from ..writers import ( TX_HASH_SIZE, get_tx_hash, write_bytes_fixed, - write_bytes_prefixed, write_bytes_reversed, write_tx_output, write_uint32, @@ -97,8 +94,7 @@ class Zip243Hash: write_bytes_reversed(h_preimage, txi.prev_hash, TX_HASH_SIZE) write_uint32(h_preimage, txi.prev_index) # 13b. scriptCode - script_code = derive_script_code(txi, public_keys, threshold, coin) - write_bytes_prefixed(h_preimage, script_code) + write_bip143_script_code_prefixed(h_preimage, txi, public_keys, threshold, coin) # 13c. value write_uint64(h_preimage, txi.amount) # 13d. nSequence @@ -174,17 +170,3 @@ class Zcashlike(Bitcoinlike): write_uint32(w, tx.lock_time) if tx.version >= 3: write_uint32(w, tx.expiry) # expiryHeight - - -def derive_script_code( - txi: TxInput, public_keys: list[bytes], threshold: int, coin: CoinInfo -) -> bytearray: - if len(public_keys) > 1: - return output_script_multisig(public_keys, threshold) - - p2pkh = txi.script_type in (InputScriptType.SPENDADDRESS, InputScriptType.EXTERNAL) - if p2pkh: - return output_script_p2pkh(ecdsa_hash_pubkey(public_keys[0], coin)) - - else: - raise wire.DataError("Unknown input script type for zip143 script code") diff --git a/core/src/apps/bitcoin/verification.py b/core/src/apps/bitcoin/verification.py index 856e790772..c00f9e45fb 100644 --- a/core/src/apps/bitcoin/verification.py +++ b/core/src/apps/bitcoin/verification.py @@ -1,12 +1,10 @@ -from trezor import wire +from trezor import utils, wire from trezor.crypto import der from trezor.crypto.curve import secp256k1 from trezor.crypto.hashlib import sha256 from .common import ecdsa_hash_pubkey from .scripts import ( - input_script_p2wpkh_in_p2sh, - input_script_p2wsh_in_p2sh, output_script_native_p2wpkh_or_p2wsh, output_script_p2pkh, output_script_p2sh, @@ -15,6 +13,8 @@ from .scripts import ( parse_output_script_multisig, parse_witness_multisig, parse_witness_p2wpkh, + write_input_script_p2wpkh_in_p2sh, + write_input_script_p2wsh_in_p2sh, ) if False: @@ -56,7 +56,9 @@ class SignatureVerifier: if len(script_sig) == 23: # P2WPKH nested in BIP16 P2SH public_key, signature, hash_type = parse_witness_p2wpkh(witness) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) - if input_script_p2wpkh_in_p2sh(pubkey_hash) != script_sig: + 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") script_hash = coin.script_hash(script_sig[1:]) if output_script_p2sh(script_hash) != script_pubkey: @@ -66,7 +68,9 @@ class SignatureVerifier: elif len(script_sig) == 35: # P2WSH nested in BIP16 P2SH script, self.signatures = parse_witness_multisig(witness) script_hash = sha256(script).digest() - if input_script_p2wsh_in_p2sh(script_hash) != script_sig: + w = utils.empty_bytearray(35) + write_input_script_p2wsh_in_p2sh(w, script_hash) + if w != script_sig: raise wire.DataError("Invalid script hash") script_hash = coin.script_hash(script_sig[1:]) if output_script_p2sh(script_hash) != script_pubkey: diff --git a/core/src/apps/bitcoin/writers.py b/core/src/apps/bitcoin/writers.py index 226939f68a..6c618293c9 100644 --- a/core/src/apps/bitcoin/writers.py +++ b/core/src/apps/bitcoin/writers.py @@ -79,6 +79,18 @@ def write_op_push(w: Writer, n: int) -> None: w.append((n >> 24) & 0xFF) +def op_push_length(n: int) -> int: + ensure(n >= 0 and n <= 0xFFFF_FFFF) + if n < 0x4C: + return 1 + elif n < 0xFF: + return 2 + elif n < 0xFFFF: + return 3 + else: + return 4 + + def get_tx_hash(w: HashWriter, double: bool = False, reverse: bool = False) -> bytes: d = w.get_digest() if double: