diff --git a/core/src/apps/wallet/sign_tx/decred.py b/core/src/apps/wallet/sign_tx/decred.py index 6885c97b2..baa427e0a 100644 --- a/core/src/apps/wallet/sign_tx/decred.py +++ b/core/src/apps/wallet/sign_tx/decred.py @@ -4,11 +4,12 @@ from micropython import const from trezor.crypto.hashlib import blake256 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.TxOutputBinType import TxOutputBinType from trezor.messages.TxOutputType import TxOutputType from trezor.messages.TxRequestSerializedType import TxRequestSerializedType -from trezor.utils import HashWriter +from trezor.utils import HashWriter, ensure from apps.common import coininfo, seed from apps.wallet.sign_tx import addresses, helpers, multisig, progress, scripts, writers @@ -20,6 +21,9 @@ DECRED_SERIALIZE_WITNESS_SIGNING = const(3 << 16) DECRED_SIGHASHALL = const(1) +if False: + from typing import Union + class DecredPrefixHasher: """ @@ -57,15 +61,19 @@ class Decred(Bitcoin): def initialize( self, tx: SignTx, keychain: seed.Keychain, coin: coininfo.CoinInfo ) -> None: + ensure(coin.decred) super().initialize(tx, keychain, coin) # This is required because the last serialized output obtained in - # `check_fee` will only be sent to the client in `sign_tx` + # phase 1 will only be sent to the client in phase 2 self.last_output_bytes = None # type: bytearray def init_hash143(self) -> None: self.hash143 = DecredPrefixHasher(self.tx) # pseudo BIP-0143 prefix hashing + def create_hash_writer(self) -> HashWriter: + return HashWriter(blake256()) + async def phase1(self) -> None: await super().phase1() self.hash143.add_locktime_expiry(self.tx) @@ -75,7 +83,7 @@ class Decred(Bitcoin): w_txi = writers.empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash)) if i == 0: # serializing first input => prepend headers self.write_sign_tx_header(w_txi, False) - writers.write_tx_input_decred(w_txi, txi) + self.write_tx_input(w_txi, txi) self.tx_req.serialized = TxRequestSerializedType(None, None, w_txi) async def phase1_confirm_output( @@ -127,7 +135,7 @@ class Decred(Bitcoin): else: raise SigningError("Unsupported input script type") - h_witness = HashWriter(blake256()) + h_witness = self.create_hash_writer() writers.write_uint32( h_witness, self.tx.version | DECRED_SERIALIZE_WITNESS_SIGNING ) @@ -143,7 +151,7 @@ class Decred(Bitcoin): h_witness, double=self.coin.sign_hash_double, reverse=False ) - h_sign = HashWriter(blake256()) + h_sign = self.create_hash_writer() writers.write_uint32(h_sign, DECRED_SIGHASHALL) writers.write_bytes_fixed(h_sign, prefix_hash, writers.TX_HASH_SIZE) writers.write_bytes_fixed(h_sign, witness_hash, writers.TX_HASH_SIZE) @@ -175,54 +183,30 @@ class Decred(Bitcoin): await helpers.request_tx_finish(self.tx_req) - async def get_prevtx_output_value(self, prev_hash: bytes, prev_index: int) -> int: - total_out = 0 # sum of output amounts - - # STAGE_REQUEST_2_PREV_META - tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash) - - if tx.outputs_cnt <= prev_index: - raise SigningError( - FailureType.ProcessError, "Not enough outputs in previous transaction." - ) - - txh = HashWriter(blake256()) - writers.write_uint32(txh, tx.version | DECRED_SERIALIZE_NO_WITNESS) - writers.write_varint(txh, tx.inputs_cnt) - - for i in range(tx.inputs_cnt): - # STAGE_REQUEST_2_PREV_INPUT - txi = await helpers.request_tx_input(self.tx_req, i, self.coin, prev_hash) - writers.write_tx_input_decred(txh, txi) - - writers.write_varint(txh, tx.outputs_cnt) - - for o in range(tx.outputs_cnt): - # STAGE_REQUEST_2_PREV_OUTPUT - txo_bin = await helpers.request_tx_output( - self.tx_req, o, self.coin, prev_hash - ) - writers.write_tx_output(txh, txo_bin) - if o == prev_index: - total_out += txo_bin.amount - if ( - txo_bin.decred_script_version is not None - and txo_bin.decred_script_version != 0 - ): - raise SigningError( - FailureType.ProcessError, - "Cannot use utxo that has script_version != 0", - ) - - writers.write_uint32(txh, tx.lock_time) - writers.write_uint32(txh, tx.expiry) - + def check_prevtx_output(self, txo_bin: TxOutputBinType) -> None: if ( - writers.get_tx_hash(txh, double=self.coin.sign_hash_double, reverse=True) - != prev_hash + txo_bin.decred_script_version is not None + and txo_bin.decred_script_version != 0 ): raise SigningError( - FailureType.ProcessError, "Encountered invalid prev_hash" + FailureType.ProcessError, + "Cannot use utxo that has script_version != 0", ) - return total_out + def write_tx_input(self, w: writers.Writer, i: TxInputType) -> None: + writers.write_tx_input_decred(w, i) + + def write_sign_tx_header(self, w: writers.Writer, has_segwit: bool) -> None: + writers.write_uint32(w, self.tx.version) # nVersion + writers.write_varint(w, self.tx.inputs_count) + + def write_tx_header( + self, w: writers.Writer, tx: Union[SignTx, TransactionType], has_segwit: bool + ) -> None: + writers.write_uint32(w, tx.version | DECRED_SERIALIZE_NO_WITNESS) + + async def write_prev_tx_footer( + self, w: writers.Writer, tx: TransactionType, prev_hash: bytes + ) -> None: + writers.write_uint32(w, tx.lock_time) + writers.write_uint32(w, tx.expiry) diff --git a/core/src/apps/wallet/sign_tx/signing.py b/core/src/apps/wallet/sign_tx/signing.py index 4f36b9421..7794b2343 100644 --- a/core/src/apps/wallet/sign_tx/signing.py +++ b/core/src/apps/wallet/sign_tx/signing.py @@ -98,13 +98,16 @@ class Bitcoin: # h_first is used to make sure the inputs and outputs streamed in Phase 1 # are the same as in Phase 2 when signing legacy inputs. it is thus not required to fully hash the # tx, as the SignTx info is streamed only once - self.h_first = utils.HashWriter(sha256()) # not a real tx hash + self.h_first = self.create_hash_writer() # not a real tx hash self.init_hash143() def init_hash143(self) -> None: self.hash143 = segwit_bip143.Bip143() # BIP-0143 transaction hashing + def create_hash_writer(self) -> utils.HashWriter: + return utils.HashWriter(sha256()) + async def phase1(self) -> None: weight = tx_weight.TxWeightCalculator( self.tx.inputs_count, self.tx.outputs_count @@ -267,7 +270,7 @@ class Bitcoin: ) if i_sign == 0: # serializing first input => prepend headers self.write_sign_tx_header(w_txi, True) - writers.write_tx_input(w_txi, txi_sign) + self.write_tx_input(w_txi, txi_sign) self.tx_req.serialized = TxRequestSerializedType(serialized_tx=w_txi) async def phase2_sign_segwit_input(self, i: int) -> Tuple[bytearray, bytes]: @@ -308,9 +311,9 @@ class Bitcoin: async def phase2_sign_nonsegwit_input(self, i_sign: int) -> None: # hash of what we are signing with this input - h_sign = utils.HashWriter(sha256()) + h_sign = self.create_hash_writer() # same as h_first, checked before signing the digest - h_second = utils.HashWriter(sha256()) + h_second = self.create_hash_writer() self.write_sign_tx_header(h_sign, has_segwit=False) @@ -341,7 +344,7 @@ class Bitcoin: ) else: txi.script_sig = bytes() - writers.write_tx_input(h_sign, txi) + self.write_tx_input(h_sign, txi) writers.write_varint(h_sign, self.tx.outputs_count) @@ -382,7 +385,7 @@ class Bitcoin: ) if i_sign == 0: # serializing first input => prepend headers self.write_sign_tx_header(w_txi_sign, True in self.segwit.values()) - writers.write_tx_input(w_txi_sign, txi_sign) + self.write_tx_input(w_txi_sign, txi_sign) self.tx_req.serialized = TxRequestSerializedType(i_sign, signature, w_txi_sign) async def phase2_serialize_output(self, i: int) -> bytearray: @@ -410,7 +413,7 @@ class Bitcoin: FailureType.ProcessError, "Not enough outputs in previous transaction." ) - txh = utils.HashWriter(sha256()) + txh = self.create_hash_writer() # TODO set has_segwit correctly self.write_tx_header(txh, tx, has_segwit=False) @@ -419,7 +422,7 @@ class Bitcoin: for i in range(tx.inputs_cnt): # STAGE_REQUEST_2_PREV_INPUT txi = await helpers.request_tx_input(self.tx_req, i, self.coin, prev_hash) - writers.write_tx_input(txh, txi) + self.write_tx_input(txh, txi) writers.write_varint(txh, tx.outputs_cnt) @@ -431,6 +434,7 @@ class Bitcoin: writers.write_tx_output(txh, txo_bin) if o == prev_index: amount_out = txo_bin.amount + self.check_prevtx_output(txo_bin) await self.write_prev_tx_footer(txh, tx, prev_hash) @@ -444,6 +448,9 @@ class Bitcoin: return amount_out + def check_prevtx_output(self, txo_bin: TxOutputBinType) -> None: + pass + # TX Helpers # === @@ -451,6 +458,9 @@ class Bitcoin: SIGHASH_ALL = const(0x01) return SIGHASH_ALL + def write_tx_input(self, w: writers.Writer, i: TxInputType) -> None: + writers.write_tx_input(w, i) + def write_sign_tx_header(self, w: writers.Writer, has_segwit: bool) -> None: self.write_tx_header(w, self.tx, has_segwit) writers.write_varint(w, self.tx.inputs_count) @@ -551,7 +561,7 @@ class Bitcoin: if i.multisig: # p2wsh in p2sh pubkeys = multisig.multisig_get_pubkeys(i.multisig) - witness_script_hasher = utils.HashWriter(sha256()) + witness_script_hasher = self.create_hash_writer() scripts.write_output_script_multisig( witness_script_hasher, pubkeys, i.multisig.m )