diff --git a/core/src/apps/wallet/sign_tx/bitcoinlike.py b/core/src/apps/wallet/sign_tx/bitcoinlike.py index 5ad0271e1..c9f60693c 100644 --- a/core/src/apps/wallet/sign_tx/bitcoinlike.py +++ b/core/src/apps/wallet/sign_tx/bitcoinlike.py @@ -80,16 +80,11 @@ class Bitcoinlike(signing.Bitcoin): txi_sign.script_sig = self.input_derive_script( txi_sign, key_sign_pub, signature ) - w_txi_sign = writers.empty_bytearray( - 5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4 - ) 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.tx_ser.signature_index = i_sign - self.tx_ser.signature = signature - self.tx_ser.serialized_tx = w_txi_sign - self.tx_req.serialized = self.tx_ser + self.write_sign_tx_header(self.serialized_tx, True in self.segwit.values()) + writers.write_tx_input(self.serialized_tx, txi_sign) + self.tx_req.serialized.signature_index = i_sign + self.tx_req.serialized.signature = signature def on_negative_fee(self) -> None: # some coins require negative fees for reward TX diff --git a/core/src/apps/wallet/sign_tx/decred.py b/core/src/apps/wallet/sign_tx/decred.py index 15f1c3451..d35022657 100644 --- a/core/src/apps/wallet/sign_tx/decred.py +++ b/core/src/apps/wallet/sign_tx/decred.py @@ -78,16 +78,10 @@ class Decred(Bitcoin): await super().process_input(i, txi) # Decred serializes inputs early. - 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) + self.write_sign_tx_header(self.serialized_tx, False) - self.write_tx_input(w_txi, txi) - - self.tx_ser.signature_index = None - self.tx_ser.signature = None - self.tx_ser.serialized_tx = w_txi - self.tx_req.serialized = self.tx_ser + self.write_tx_input(self.serialized_tx, txi) async def confirm_output( self, i: int, txo: TxOutputType, txo_bin: TxOutputBinType @@ -99,17 +93,11 @@ class Decred(Bitcoin): ) txo_bin.decred_script_version = txo.decred_script_version - w_txo_bin = writers.empty_bytearray(4 + 8 + 2 + 4 + len(txo_bin.script_pubkey)) if i == 0: # serializing first output => prepend outputs count - writers.write_varint(w_txo_bin, self.tx.outputs_count) + writers.write_varint(self.serialized_tx, self.tx.outputs_count) self.hash143.add_output_count(self.tx) - writers.write_tx_output(w_txo_bin, txo_bin) - - self.tx_ser.signature_index = None - self.tx_ser.signature = None - self.tx_ser.serialized_tx = w_txo_bin - self.tx_req.serialized = self.tx_ser + writers.write_tx_output(self.serialized_tx, txo_bin) await super().confirm_output(i, txo, txo_bin) @@ -169,21 +157,15 @@ class Decred(Bitcoin): txi_sign, key_sign_pub, signature ) - max_witness_size = 8 + 4 + 4 + 4 + len(txi_sign.script_sig) if i_sign == 0: - w_txi_sign = writers.empty_bytearray(4 + 4 + 4 + max_witness_size) - writers.write_uint32(w_txi_sign, self.tx.lock_time) - writers.write_uint32(w_txi_sign, self.tx.expiry) - writers.write_varint(w_txi_sign, self.tx.inputs_count) - else: - w_txi_sign = writers.empty_bytearray(max_witness_size) + writers.write_uint32(self.serialized_tx, self.tx.lock_time) + writers.write_uint32(self.serialized_tx, self.tx.expiry) + writers.write_varint(self.serialized_tx, self.tx.inputs_count) - writers.write_tx_input_decred_witness(w_txi_sign, txi_sign) + writers.write_tx_input_decred_witness(self.serialized_tx, txi_sign) - self.tx_ser.signature_index = i_sign - self.tx_ser.signature = signature - self.tx_ser.serialized_tx = w_txi_sign - self.tx_req.serialized = self.tx_ser + self.tx_req.serialized.signature_index = i_sign + self.tx_req.serialized.signature = signature async def step5_serialize_outputs(self) -> None: pass diff --git a/core/src/apps/wallet/sign_tx/helpers.py b/core/src/apps/wallet/sign_tx/helpers.py index b154b5468..95950a265 100644 --- a/core/src/apps/wallet/sign_tx/helpers.py +++ b/core/src/apps/wallet/sign_tx/helpers.py @@ -117,7 +117,9 @@ def request_tx_meta(tx_req: TxRequest, coin: CoinInfo, tx_hash: bytes = None) -> tx_req.details.tx_hash = tx_hash tx_req.details.request_index = None ack = yield tx_req - tx_req.serialized = None + tx_req.serialized.signature = None + tx_req.serialized.signature_index = None + tx_req.serialized.serialized_tx[:] = bytes() gc.collect() return sanitize_tx_meta(ack.tx, coin) @@ -131,7 +133,9 @@ def request_tx_extra_data( # type: ignore tx_req.details.tx_hash = tx_hash tx_req.details.request_index = None ack = yield tx_req - tx_req.serialized = None + tx_req.serialized.signature = None + tx_req.serialized.signature_index = None + tx_req.serialized.serialized_tx[:] = bytes() tx_req.details.extra_data_offset = None tx_req.details.extra_data_len = None gc.collect() @@ -143,7 +147,9 @@ def request_tx_input(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes = tx_req.details.request_index = i tx_req.details.tx_hash = tx_hash ack = yield tx_req - tx_req.serialized = None + tx_req.serialized.signature = None + tx_req.serialized.signature_index = None + tx_req.serialized.serialized_tx[:] = bytes() gc.collect() return sanitize_tx_input(ack.tx, coin) @@ -153,7 +159,9 @@ def request_tx_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes tx_req.details.request_index = i tx_req.details.tx_hash = tx_hash ack = yield tx_req - tx_req.serialized = None + tx_req.serialized.signature = None + tx_req.serialized.signature_index = None + tx_req.serialized.serialized_tx[:] = bytes() gc.collect() if tx_hash is None: return sanitize_tx_output(ack.tx, coin) @@ -165,7 +173,9 @@ def request_tx_finish(tx_req: TxRequest) -> Awaitable[Any]: # type: ignore tx_req.request_type = TXFINISHED tx_req.details = None yield tx_req - tx_req.serialized = None + tx_req.serialized.signature = None + tx_req.serialized.signature_index = None + tx_req.serialized.serialized_tx[:] = bytes() gc.collect() diff --git a/core/src/apps/wallet/sign_tx/signing.py b/core/src/apps/wallet/sign_tx/signing.py index af51d8d0b..59f39307d 100644 --- a/core/src/apps/wallet/sign_tx/signing.py +++ b/core/src/apps/wallet/sign_tx/signing.py @@ -40,6 +40,9 @@ _BIP32_CHANGE_CHAIN = const(1) # use and still allow to quickly brute-force the correct bip32 path _BIP32_MAX_LAST_ELEMENT = const(1000000) +# the number of bytes to preallocate for serialized transaction chunks +_MAX_SERIALIZED_CHUNK_SIZE = const(2048) + class SigningError(ValueError): pass @@ -98,15 +101,19 @@ class Bitcoin: # dict of booleans stating if input is segwit self.segwit = {} # type: Dict[int, bool] + # amounts self.total_in = 0 # sum of input amounts self.bip143_in = 0 # sum of segwit input amounts self.total_out = 0 # sum of output amounts self.change_out = 0 # change output amount self.weight = tx_weight.TxWeightCalculator(tx.inputs_count, tx.outputs_count) + # transaction and signature serialization + self.serialized_tx = writers.empty_bytearray(_MAX_SERIALIZED_CHUNK_SIZE) self.tx_req = TxRequest() self.tx_req.details = TxRequestDetailsType() - self.tx_ser = TxRequestSerializedType() + self.tx_req.serialized = TxRequestSerializedType() + self.tx_req.serialized.serialized_tx = self.serialized_tx # h_confirmed is used to make sure that the inputs and outputs streamed for # confirmation in Steps 1 and 2 are the same as the ones streamed for signing @@ -180,11 +187,10 @@ class Bitcoin: await self.sign_segwit_input(i) elif any_segwit: # add empty witness for non-segwit inputs - self.tx_ser.serialized_tx.append(0) - self.tx_req.serialized = self.tx_ser + self.serialized_tx.append(0) async def step7_finish(self) -> None: - self.write_sign_tx_footer(self.tx_ser.serialized_tx) + self.write_sign_tx_footer(self.serialized_tx) await helpers.request_tx_finish(self.tx_req) async def process_input(self, i: int, txi: TxInputType) -> None: @@ -259,16 +265,9 @@ class Bitcoin: key_sign_pub = key_sign.public_key() txi_sign.script_sig = self.input_derive_script(txi_sign, key_sign_pub) - w_txi = writers.empty_bytearray( - 7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4 - ) if i_sign == 0: # serializing first input => prepend headers - self.write_sign_tx_header(w_txi, True) - self.write_tx_input(w_txi, txi_sign) - self.tx_ser.signature_index = None - self.tx_ser.signature = None - self.tx_ser.serialized_tx = w_txi - self.tx_req.serialized = self.tx_ser + self.write_sign_tx_header(self.serialized_tx, True) + self.write_tx_input(self.serialized_tx, txi_sign) async def sign_segwit_input(self, i: int) -> None: # STAGE_REQUEST_SEGWIT_WITNESS @@ -297,18 +296,16 @@ class Bitcoin: if txi.multisig: # find out place of our signature based on the pubkey signature_index = multisig.multisig_pubkey_index(txi.multisig, key_sign_pub) - witness = scripts.witness_p2wsh( + self.serialized_tx[:] = scripts.witness_p2wsh( txi.multisig, signature, signature_index, self.get_hash_type() ) else: - witness = scripts.witness_p2wpkh( + self.serialized_tx[:] = scripts.witness_p2wpkh( signature, key_sign_pub, self.get_hash_type() ) - self.tx_ser.signature_index = i - self.tx_ser.signature = signature - self.tx_ser.serialized_tx = witness - self.tx_req.serialized = self.tx_ser + self.tx_req.serialized.signature_index = i + self.tx_req.serialized.signature = signature async def sign_nonsegwit_input(self, i_sign: int) -> None: # hash of what we are signing with this input @@ -381,17 +378,12 @@ class Bitcoin: txi_sign.script_sig = self.input_derive_script( txi_sign, key_sign_pub, signature ) - w_txi_sign = writers.empty_bytearray( - 5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4 - ) if i_sign == 0: # serializing first input => prepend headers - self.write_sign_tx_header(w_txi_sign, True in self.segwit.values()) - self.write_tx_input(w_txi_sign, txi_sign) + self.write_sign_tx_header(self.serialized_tx, True in self.segwit.values()) + self.write_tx_input(self.serialized_tx, txi_sign) - self.tx_ser.signature_index = i_sign - self.tx_ser.signature = signature - self.tx_ser.serialized_tx = w_txi_sign - self.tx_req.serialized = self.tx_ser + self.tx_req.serialized.signature_index = i_sign + self.tx_req.serialized.signature = signature async def serialize_output(self, i: int) -> None: # STAGE_REQUEST_5_OUTPUT @@ -400,15 +392,9 @@ class Bitcoin: txo_bin.amount = txo.amount txo_bin.script_pubkey = self.output_derive_script(txo) - w_txo_bin = writers.empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4) if i == 0: # serializing first output => prepend outputs count - writers.write_varint(w_txo_bin, self.tx.outputs_count) - writers.write_tx_output(w_txo_bin, txo_bin) - - self.tx_ser.signature_index = None - self.tx_ser.signature = None - self.tx_ser.serialized_tx = w_txo_bin - self.tx_req.serialized = self.tx_ser + writers.write_varint(self.serialized_tx, self.tx.outputs_count) + writers.write_tx_output(self.serialized_tx, txo_bin) async def get_prevtx_output_value(self, prev_hash: bytes, prev_index: int) -> int: amount_out = 0 # output amount