mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 06:18:07 +00:00
core/sign_tx: Refactor to use template method.
This commit is contained in:
parent
95fad83024
commit
514f2ac649
@ -7,7 +7,6 @@ from trezor.messages.SignTx import SignTx
|
|||||||
from trezor.messages.TransactionType import TransactionType
|
from trezor.messages.TransactionType import TransactionType
|
||||||
from trezor.messages.TxInputType import TxInputType
|
from trezor.messages.TxInputType import TxInputType
|
||||||
from trezor.messages.TxOutputType import TxOutputType
|
from trezor.messages.TxOutputType import TxOutputType
|
||||||
from trezor.messages.TxRequestSerializedType import TxRequestSerializedType
|
|
||||||
|
|
||||||
from apps.wallet.sign_tx import addresses, helpers, multisig, signing, writers
|
from apps.wallet.sign_tx import addresses, helpers, multisig, signing, writers
|
||||||
|
|
||||||
@ -16,20 +15,20 @@ if False:
|
|||||||
|
|
||||||
|
|
||||||
class Bitcoinlike(signing.Bitcoin):
|
class Bitcoinlike(signing.Bitcoin):
|
||||||
async def phase1_process_segwit_input(self, i: int, txi: TxInputType) -> None:
|
async def process_segwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
if not self.coin.segwit:
|
if not self.coin.segwit:
|
||||||
raise signing.SigningError(
|
raise signing.SigningError(
|
||||||
FailureType.DataError, "Segwit not enabled on this coin"
|
FailureType.DataError, "Segwit not enabled on this coin"
|
||||||
)
|
)
|
||||||
await super().phase1_process_segwit_input(i, txi)
|
await super().process_segwit_input(i, txi)
|
||||||
|
|
||||||
async def phase1_process_nonsegwit_input(self, i: int, txi: TxInputType) -> None:
|
async def process_nonsegwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
if self.coin.force_bip143:
|
if self.coin.force_bip143:
|
||||||
await self.phase1_process_bip143_input(i, txi)
|
await self.process_bip143_input(i, txi)
|
||||||
else:
|
else:
|
||||||
await super().phase1_process_nonsegwit_input(i, txi)
|
await super().process_nonsegwit_input(i, txi)
|
||||||
|
|
||||||
async def phase1_process_bip143_input(self, i: int, txi: TxInputType) -> None:
|
async def process_bip143_input(self, i: int, txi: TxInputType) -> None:
|
||||||
if not txi.amount:
|
if not txi.amount:
|
||||||
raise signing.SigningError(
|
raise signing.SigningError(
|
||||||
FailureType.DataError, "Expected input with amount"
|
FailureType.DataError, "Expected input with amount"
|
||||||
@ -38,13 +37,13 @@ class Bitcoinlike(signing.Bitcoin):
|
|||||||
self.bip143_in += txi.amount
|
self.bip143_in += txi.amount
|
||||||
self.total_in += txi.amount
|
self.total_in += txi.amount
|
||||||
|
|
||||||
async def phase2_sign_nonsegwit_input(self, i_sign: int) -> None:
|
async def sign_nonsegwit_input(self, i_sign: int) -> None:
|
||||||
if self.coin.force_bip143:
|
if self.coin.force_bip143:
|
||||||
await self.phase2_sign_bip143_input(i_sign)
|
await self.sign_bip143_input(i_sign)
|
||||||
else:
|
else:
|
||||||
await super().phase2_sign_nonsegwit_input(i_sign)
|
await super().sign_nonsegwit_input(i_sign)
|
||||||
|
|
||||||
async def phase2_sign_bip143_input(self, i_sign: int) -> None:
|
async def sign_bip143_input(self, i_sign: int) -> None:
|
||||||
# STAGE_REQUEST_SEGWIT_INPUT
|
# STAGE_REQUEST_SEGWIT_INPUT
|
||||||
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, self.coin)
|
||||||
self.input_check_wallet_path(txi_sign)
|
self.input_check_wallet_path(txi_sign)
|
||||||
@ -87,7 +86,10 @@ class Bitcoinlike(signing.Bitcoin):
|
|||||||
if i_sign == 0: # serializing first input => prepend headers
|
if i_sign == 0: # serializing first input => prepend headers
|
||||||
self.write_sign_tx_header(w_txi_sign, True in self.segwit.values())
|
self.write_sign_tx_header(w_txi_sign, True in self.segwit.values())
|
||||||
writers.write_tx_input(w_txi_sign, txi_sign)
|
writers.write_tx_input(w_txi_sign, txi_sign)
|
||||||
self.tx_req.serialized = TxRequestSerializedType(i_sign, signature, w_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
|
||||||
|
|
||||||
def on_negative_fee(self) -> None:
|
def on_negative_fee(self) -> None:
|
||||||
# some coins require negative fees for reward TX
|
# some coins require negative fees for reward TX
|
||||||
|
@ -8,7 +8,6 @@ from trezor.messages.TransactionType import TransactionType
|
|||||||
from trezor.messages.TxInputType import TxInputType
|
from trezor.messages.TxInputType import TxInputType
|
||||||
from trezor.messages.TxOutputBinType import TxOutputBinType
|
from trezor.messages.TxOutputBinType import TxOutputBinType
|
||||||
from trezor.messages.TxOutputType import TxOutputType
|
from trezor.messages.TxOutputType import TxOutputType
|
||||||
from trezor.messages.TxRequestSerializedType import TxRequestSerializedType
|
|
||||||
from trezor.utils import HashWriter, ensure
|
from trezor.utils import HashWriter, ensure
|
||||||
|
|
||||||
from apps.common import coininfo, seed
|
from apps.common import coininfo, seed
|
||||||
@ -66,7 +65,7 @@ class Decred(Bitcoin):
|
|||||||
super().initialize(tx, keychain, coin)
|
super().initialize(tx, keychain, coin)
|
||||||
|
|
||||||
# This is required because the last serialized output obtained in
|
# This is required because the last serialized output obtained in
|
||||||
# phase 1 will only be sent to the client in phase 2
|
# step 2 will only be sent to the client in step 4
|
||||||
self.last_output_bytes = None # type: bytearray
|
self.last_output_bytes = None # type: bytearray
|
||||||
|
|
||||||
def init_hash143(self) -> None:
|
def init_hash143(self) -> None:
|
||||||
@ -75,19 +74,26 @@ class Decred(Bitcoin):
|
|||||||
def create_hash_writer(self) -> HashWriter:
|
def create_hash_writer(self) -> HashWriter:
|
||||||
return HashWriter(blake256())
|
return HashWriter(blake256())
|
||||||
|
|
||||||
async def phase1(self) -> None:
|
async def step2_confirm_outputs(self) -> None:
|
||||||
await super().phase1()
|
await super().step2_confirm_outputs()
|
||||||
self.hash143.add_locktime_expiry(self.tx)
|
self.hash143.add_locktime_expiry(self.tx)
|
||||||
|
|
||||||
async def phase1_process_input(self, i: int, txi: TxInputType) -> None:
|
async def process_input(self, i: int, txi: TxInputType) -> None:
|
||||||
await super().phase1_process_input(i, txi)
|
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))
|
w_txi = writers.empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash))
|
||||||
if i == 0: # serializing first input => prepend headers
|
if i == 0: # serializing first input => prepend headers
|
||||||
self.write_sign_tx_header(w_txi, False)
|
self.write_sign_tx_header(w_txi, False)
|
||||||
self.write_tx_input(w_txi, txi)
|
|
||||||
self.tx_req.serialized = TxRequestSerializedType(None, None, w_txi)
|
|
||||||
|
|
||||||
async def phase1_confirm_output(
|
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
|
||||||
|
|
||||||
|
async def confirm_output(
|
||||||
self, i: int, txo: TxOutputType, txo_bin: TxOutputBinType
|
self, i: int, txo: TxOutputType, txo_bin: TxOutputBinType
|
||||||
) -> None:
|
) -> None:
|
||||||
if txo.decred_script_version is not None and txo.decred_script_version != 0:
|
if txo.decred_script_version is not None and txo.decred_script_version != 0:
|
||||||
@ -103,12 +109,17 @@ class Decred(Bitcoin):
|
|||||||
self.hash143.add_output_count(self.tx)
|
self.hash143.add_output_count(self.tx)
|
||||||
|
|
||||||
writers.write_tx_output(w_txo_bin, txo_bin)
|
writers.write_tx_output(w_txo_bin, txo_bin)
|
||||||
self.tx_req.serialized = TxRequestSerializedType(serialized_tx=w_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
|
||||||
|
|
||||||
self.last_output_bytes = w_txo_bin
|
self.last_output_bytes = w_txo_bin
|
||||||
|
|
||||||
await super().phase1_confirm_output(i, txo, txo_bin)
|
await super().confirm_output(i, txo, txo_bin)
|
||||||
|
|
||||||
async def phase2(self) -> None:
|
async def step4_serialize_inputs(self) -> None:
|
||||||
self.tx_req.serialized = None
|
self.tx_req.serialized = None
|
||||||
|
|
||||||
prefix_hash = self.hash143.get_prefix_hash()
|
prefix_hash = self.hash143.get_prefix_hash()
|
||||||
@ -178,11 +189,20 @@ class Decred(Bitcoin):
|
|||||||
writers.write_varint(w_txi_sign, self.tx.inputs_count)
|
writers.write_varint(w_txi_sign, self.tx.inputs_count)
|
||||||
|
|
||||||
writers.write_tx_input_decred_witness(w_txi_sign, txi_sign)
|
writers.write_tx_input_decred_witness(w_txi_sign, txi_sign)
|
||||||
self.tx_req.serialized = TxRequestSerializedType(
|
|
||||||
i_sign, signature, w_txi_sign
|
|
||||||
)
|
|
||||||
|
|
||||||
await helpers.request_tx_finish(self.tx_req)
|
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
|
||||||
|
|
||||||
|
async def step5_serialize_outputs(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def step6_sign_segwit_inputs(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write_sign_tx_footer(self, w: writers.Writer) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
def check_prevtx_output(self, txo_bin: TxOutputBinType) -> None:
|
def check_prevtx_output(self, txo_bin: TxOutputBinType) -> None:
|
||||||
if (
|
if (
|
||||||
|
@ -28,7 +28,7 @@ from apps.wallet.sign_tx import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Dict, List, Optional, Tuple, Union
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
# the number of bip32 levels used in a wallet (chain and address)
|
# the number of bip32 levels used in a wallet (chain and address)
|
||||||
_BIP32_WALLET_DEPTH = const(2)
|
_BIP32_WALLET_DEPTH = const(2)
|
||||||
@ -60,16 +60,27 @@ class Bitcoin:
|
|||||||
|
|
||||||
progress.init(self.tx.inputs_count, self.tx.outputs_count)
|
progress.init(self.tx.inputs_count, self.tx.outputs_count)
|
||||||
|
|
||||||
# Phase 1
|
# Add inputs to hash143 and h_confirmed and compute the sum of input amounts.
|
||||||
# - check inputs, previous transactions, and outputs
|
await self.step1_process_inputs()
|
||||||
# - ask for confirmations
|
|
||||||
# - check fee
|
|
||||||
await self.phase1()
|
|
||||||
|
|
||||||
# Phase 2
|
# Add outputs to hash143 and h_confirmed, check previous transaction output
|
||||||
# - sign inputs
|
# amounts, confirm outputs and compute sum of output amounts.
|
||||||
# - check that nothing changed
|
await self.step2_confirm_outputs()
|
||||||
await self.phase2()
|
|
||||||
|
# Check fee, confirm lock_time and total.
|
||||||
|
await self.step3_confirm_tran()
|
||||||
|
|
||||||
|
# Check that inputs are unchanged. Serialize inputs and sign the non-segwit ones.
|
||||||
|
await self.step4_serialize_inputs()
|
||||||
|
|
||||||
|
# Serialize outputs.
|
||||||
|
await self.step5_serialize_outputs()
|
||||||
|
|
||||||
|
# Sign segwit inputs and serialize witness data.
|
||||||
|
await self.step6_sign_segwit_inputs()
|
||||||
|
|
||||||
|
# Write footer and send remaining data.
|
||||||
|
await self.step7_finish()
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
self, tx: SignTx, keychain: seed.Keychain, coin: coininfo.CoinInfo
|
self, tx: SignTx, keychain: seed.Keychain, coin: coininfo.CoinInfo
|
||||||
@ -78,27 +89,29 @@ class Bitcoin:
|
|||||||
self.tx = helpers.sanitize_sign_tx(tx, self.coin)
|
self.tx = helpers.sanitize_sign_tx(tx, self.coin)
|
||||||
self.keychain = keychain
|
self.keychain = keychain
|
||||||
|
|
||||||
self.multisig_fp = (
|
# checksum of multisig inputs, used to validate change-output
|
||||||
multisig.MultisigFingerprint()
|
self.multisig_fp = multisig.MultisigFingerprint()
|
||||||
) # control checksum of multisig inputs
|
|
||||||
self.wallet_path = (
|
# common prefix of input paths, used to validate change-output
|
||||||
[]
|
self.wallet_path = [] # type: Optional[List[int]]
|
||||||
) # type: Optional[List[int]] # common prefix of input paths
|
|
||||||
self.bip143_in = 0 # sum of segwit input amounts
|
# dict of booleans stating if input is segwit
|
||||||
self.segwit = (
|
self.segwit = {} # type: Dict[int, bool]
|
||||||
{}
|
|
||||||
) # type: Dict[int, bool] # dict of booleans stating if input is segwit
|
|
||||||
self.total_in = 0 # sum of input 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.total_out = 0 # sum of output amounts
|
||||||
self.change_out = 0 # change output amount
|
self.change_out = 0 # change output amount
|
||||||
|
self.weight = tx_weight.TxWeightCalculator(tx.inputs_count, tx.outputs_count)
|
||||||
|
|
||||||
self.tx_req = TxRequest()
|
self.tx_req = TxRequest()
|
||||||
self.tx_req.details = TxRequestDetailsType()
|
self.tx_req.details = TxRequestDetailsType()
|
||||||
|
self.tx_ser = TxRequestSerializedType()
|
||||||
|
|
||||||
# h_first is used to make sure the inputs and outputs streamed in Phase 1
|
# h_confirmed is used to make sure that the inputs and outputs streamed for
|
||||||
# are the same as in Phase 2 when signing legacy inputs. it is thus not required to fully hash the
|
# confirmation in Steps 1 and 2 are the same as the ones streamed for signing
|
||||||
# tx, as the SignTx info is streamed only once
|
# legacy inputs in Step 4.
|
||||||
self.h_first = self.create_hash_writer() # not a real tx hash
|
self.h_confirmed = self.create_hash_writer() # not a real tx hash
|
||||||
|
|
||||||
self.init_hash143()
|
self.init_hash143()
|
||||||
|
|
||||||
@ -108,36 +121,32 @@ class Bitcoin:
|
|||||||
def create_hash_writer(self) -> utils.HashWriter:
|
def create_hash_writer(self) -> utils.HashWriter:
|
||||||
return utils.HashWriter(sha256())
|
return utils.HashWriter(sha256())
|
||||||
|
|
||||||
async def phase1(self) -> None:
|
async def step1_process_inputs(self) -> None:
|
||||||
weight = tx_weight.TxWeightCalculator(
|
|
||||||
self.tx.inputs_count, self.tx.outputs_count
|
|
||||||
)
|
|
||||||
|
|
||||||
# compute sum of input amounts (total_in)
|
|
||||||
# add inputs to hash143 and h_first
|
|
||||||
for i in range(self.tx.inputs_count):
|
for i in range(self.tx.inputs_count):
|
||||||
# STAGE_REQUEST_1_INPUT
|
# STAGE_REQUEST_1_INPUT
|
||||||
progress.advance()
|
progress.advance()
|
||||||
txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
|
txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
|
||||||
weight.add_input(txi)
|
self.weight.add_input(txi)
|
||||||
await self.phase1_process_input(i, txi)
|
await self.process_input(i, txi)
|
||||||
|
|
||||||
|
async def step2_confirm_outputs(self) -> None:
|
||||||
txo_bin = TxOutputBinType()
|
txo_bin = TxOutputBinType()
|
||||||
for i in range(self.tx.outputs_count):
|
for i in range(self.tx.outputs_count):
|
||||||
# STAGE_REQUEST_3_OUTPUT
|
# STAGE_REQUEST_3_OUTPUT
|
||||||
txo = await helpers.request_tx_output(self.tx_req, i, self.coin)
|
txo = await helpers.request_tx_output(self.tx_req, i, self.coin)
|
||||||
txo_bin.amount = txo.amount
|
txo_bin.amount = txo.amount
|
||||||
txo_bin.script_pubkey = self.output_derive_script(txo)
|
txo_bin.script_pubkey = self.output_derive_script(txo)
|
||||||
weight.add_output(txo_bin.script_pubkey)
|
self.weight.add_output(txo_bin.script_pubkey)
|
||||||
await self.phase1_confirm_output(i, txo, txo_bin)
|
await self.confirm_output(i, txo, txo_bin)
|
||||||
|
|
||||||
|
async def step3_confirm_tran(self) -> None:
|
||||||
fee = self.total_in - self.total_out
|
fee = self.total_in - self.total_out
|
||||||
|
|
||||||
if fee < 0:
|
if fee < 0:
|
||||||
self.on_negative_fee()
|
self.on_negative_fee()
|
||||||
|
|
||||||
# fee > (coin.maxfee per byte * tx size)
|
# fee > (coin.maxfee per byte * tx size)
|
||||||
if fee > (self.coin.maxfee_kb / 1000) * (weight.get_total() / 4):
|
if fee > (self.coin.maxfee_kb / 1000) * (self.weight.get_total() / 4):
|
||||||
if not await helpers.confirm_feeoverthreshold(fee, self.coin):
|
if not await helpers.confirm_feeoverthreshold(fee, self.coin):
|
||||||
raise SigningError(FailureType.ActionCancelled, "Signing cancelled")
|
raise SigningError(FailureType.ActionCancelled, "Signing cancelled")
|
||||||
|
|
||||||
@ -150,9 +159,43 @@ class Bitcoin:
|
|||||||
):
|
):
|
||||||
raise SigningError(FailureType.ActionCancelled, "Total cancelled")
|
raise SigningError(FailureType.ActionCancelled, "Total cancelled")
|
||||||
|
|
||||||
async def phase1_process_input(self, i: int, txi: TxInputType) -> None:
|
async def step4_serialize_inputs(self) -> None:
|
||||||
|
self.tx_req.serialized = None
|
||||||
|
|
||||||
|
for i in range(self.tx.inputs_count):
|
||||||
|
progress.advance()
|
||||||
|
if self.segwit[i]:
|
||||||
|
await self.serialize_segwit_input(i)
|
||||||
|
else:
|
||||||
|
await self.sign_nonsegwit_input(i)
|
||||||
|
|
||||||
|
async def step5_serialize_outputs(self) -> None:
|
||||||
|
for i in range(self.tx.outputs_count):
|
||||||
|
progress.advance()
|
||||||
|
await self.serialize_output(i)
|
||||||
|
|
||||||
|
async def step6_sign_segwit_inputs(self) -> None:
|
||||||
|
any_segwit = True in self.segwit.values()
|
||||||
|
for i in range(self.tx.inputs_count):
|
||||||
|
progress.advance()
|
||||||
|
if self.segwit[i]:
|
||||||
|
await self.sign_segwit_input(i)
|
||||||
|
elif any_segwit:
|
||||||
|
# TODO what if a non-segwit input follows after a segwit input?
|
||||||
|
self.tx_ser.serialized_tx += bytearray(
|
||||||
|
1
|
||||||
|
) # empty witness for non-segwit inputs
|
||||||
|
self.tx_ser.signature_index = None
|
||||||
|
self.tx_ser.signature = None
|
||||||
|
self.tx_req.serialized = self.tx_ser
|
||||||
|
|
||||||
|
async def step7_finish(self) -> None:
|
||||||
|
self.write_sign_tx_footer(self.tx_ser.serialized_tx)
|
||||||
|
await helpers.request_tx_finish(self.tx_req)
|
||||||
|
|
||||||
|
async def process_input(self, i: int, txi: TxInputType) -> None:
|
||||||
self.input_extract_wallet_path(txi)
|
self.input_extract_wallet_path(txi)
|
||||||
writers.write_tx_input_check(self.h_first, txi)
|
writers.write_tx_input_check(self.h_confirmed, txi)
|
||||||
self.hash143.add_prevouts(txi) # all inputs are included (non-segwit as well)
|
self.hash143.add_prevouts(txi) # all inputs are included (non-segwit as well)
|
||||||
self.hash143.add_sequence(txi)
|
self.hash143.add_sequence(txi)
|
||||||
|
|
||||||
@ -168,29 +211,29 @@ class Bitcoin:
|
|||||||
InputScriptType.SPENDWITNESS,
|
InputScriptType.SPENDWITNESS,
|
||||||
InputScriptType.SPENDP2SHWITNESS,
|
InputScriptType.SPENDP2SHWITNESS,
|
||||||
):
|
):
|
||||||
await self.phase1_process_segwit_input(i, txi)
|
await self.process_segwit_input(i, txi)
|
||||||
elif txi.script_type in (
|
elif txi.script_type in (
|
||||||
InputScriptType.SPENDADDRESS,
|
InputScriptType.SPENDADDRESS,
|
||||||
InputScriptType.SPENDMULTISIG,
|
InputScriptType.SPENDMULTISIG,
|
||||||
):
|
):
|
||||||
await self.phase1_process_nonsegwit_input(i, txi)
|
await self.process_nonsegwit_input(i, txi)
|
||||||
else:
|
else:
|
||||||
raise SigningError(FailureType.DataError, "Wrong input script type")
|
raise SigningError(FailureType.DataError, "Wrong input script type")
|
||||||
|
|
||||||
async def phase1_process_segwit_input(self, i: int, txi: TxInputType) -> None:
|
async def process_segwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
if not txi.amount:
|
if not txi.amount:
|
||||||
raise SigningError(FailureType.DataError, "Segwit input without amount")
|
raise SigningError(FailureType.DataError, "Segwit input without amount")
|
||||||
self.segwit[i] = True
|
self.segwit[i] = True
|
||||||
self.bip143_in += txi.amount
|
self.bip143_in += txi.amount
|
||||||
self.total_in += txi.amount
|
self.total_in += txi.amount
|
||||||
|
|
||||||
async def phase1_process_nonsegwit_input(self, i: int, txi: TxInputType) -> None:
|
async def process_nonsegwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
self.segwit[i] = False
|
self.segwit[i] = False
|
||||||
self.total_in += await self.get_prevtx_output_value(
|
self.total_in += await self.get_prevtx_output_value(
|
||||||
txi.prev_hash, txi.prev_index
|
txi.prev_hash, txi.prev_index
|
||||||
)
|
)
|
||||||
|
|
||||||
async def phase1_confirm_output(
|
async def confirm_output(
|
||||||
self, i: int, txo: TxOutputType, txo_bin: TxOutputBinType
|
self, i: int, txo: TxOutputType, txo_bin: TxOutputBinType
|
||||||
) -> None:
|
) -> None:
|
||||||
if self.change_out == 0 and self.output_is_change(txo):
|
if self.change_out == 0 and self.output_is_change(txo):
|
||||||
@ -199,57 +242,14 @@ class Bitcoin:
|
|||||||
elif not await helpers.confirm_output(txo, self.coin):
|
elif not await helpers.confirm_output(txo, self.coin):
|
||||||
raise SigningError(FailureType.ActionCancelled, "Output cancelled")
|
raise SigningError(FailureType.ActionCancelled, "Output cancelled")
|
||||||
|
|
||||||
writers.write_tx_output(self.h_first, txo_bin)
|
writers.write_tx_output(self.h_confirmed, txo_bin)
|
||||||
self.hash143.add_output(txo_bin)
|
self.hash143.add_output(txo_bin)
|
||||||
self.total_out += txo_bin.amount
|
self.total_out += txo_bin.amount
|
||||||
|
|
||||||
def on_negative_fee(self) -> None:
|
def on_negative_fee(self) -> None:
|
||||||
raise SigningError(FailureType.NotEnoughFunds, "Not enough funds")
|
raise SigningError(FailureType.NotEnoughFunds, "Not enough funds")
|
||||||
|
|
||||||
async def phase2(self) -> None:
|
async def serialize_segwit_input(self, i_sign: int) -> None:
|
||||||
self.tx_req.serialized = None
|
|
||||||
|
|
||||||
# Serialize inputs and sign non-segwit inputs.
|
|
||||||
for i in range(self.tx.inputs_count):
|
|
||||||
progress.advance()
|
|
||||||
if self.segwit[i]:
|
|
||||||
await self.phase2_serialize_segwit_input(i)
|
|
||||||
else:
|
|
||||||
await self.phase2_sign_nonsegwit_input(i)
|
|
||||||
|
|
||||||
# Serialize outputs.
|
|
||||||
tx_ser = TxRequestSerializedType()
|
|
||||||
for i in range(self.tx.outputs_count):
|
|
||||||
# STAGE_REQUEST_5_OUTPUT
|
|
||||||
progress.advance()
|
|
||||||
tx_ser.serialized_tx = await self.phase2_serialize_output(i)
|
|
||||||
self.tx_req.serialized = tx_ser
|
|
||||||
|
|
||||||
# Sign segwit inputs.
|
|
||||||
any_segwit = True in self.segwit.values()
|
|
||||||
for i in range(self.tx.inputs_count):
|
|
||||||
progress.advance()
|
|
||||||
if self.segwit[i]:
|
|
||||||
# STAGE_REQUEST_SEGWIT_WITNESS
|
|
||||||
witness, signature = await self.phase2_sign_segwit_input(i)
|
|
||||||
tx_ser.serialized_tx = witness
|
|
||||||
tx_ser.signature_index = i
|
|
||||||
tx_ser.signature = signature
|
|
||||||
elif any_segwit:
|
|
||||||
# TODO what if a non-segwit input follows after a segwit input?
|
|
||||||
tx_ser.serialized_tx += bytearray(
|
|
||||||
1
|
|
||||||
) # empty witness for non-segwit inputs
|
|
||||||
tx_ser.signature_index = None
|
|
||||||
tx_ser.signature = None
|
|
||||||
|
|
||||||
self.tx_req.serialized = tx_ser
|
|
||||||
|
|
||||||
self.write_sign_tx_footer(tx_ser.serialized_tx)
|
|
||||||
|
|
||||||
await helpers.request_tx_finish(self.tx_req)
|
|
||||||
|
|
||||||
async def phase2_serialize_segwit_input(self, i_sign: int) -> None:
|
|
||||||
# STAGE_REQUEST_SEGWIT_INPUT
|
# STAGE_REQUEST_SEGWIT_INPUT
|
||||||
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, self.coin)
|
||||||
|
|
||||||
@ -271,9 +271,13 @@ class Bitcoin:
|
|||||||
if i_sign == 0: # serializing first input => prepend headers
|
if i_sign == 0: # serializing first input => prepend headers
|
||||||
self.write_sign_tx_header(w_txi, True)
|
self.write_sign_tx_header(w_txi, True)
|
||||||
self.write_tx_input(w_txi, txi_sign)
|
self.write_tx_input(w_txi, txi_sign)
|
||||||
self.tx_req.serialized = TxRequestSerializedType(serialized_tx=w_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
|
||||||
|
|
||||||
async def phase2_sign_segwit_input(self, i: int) -> Tuple[bytearray, bytes]:
|
async def sign_segwit_input(self, i: int) -> None:
|
||||||
|
# STAGE_REQUEST_SEGWIT_WITNESS
|
||||||
txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
|
txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
|
||||||
|
|
||||||
self.input_check_wallet_path(txi)
|
self.input_check_wallet_path(txi)
|
||||||
@ -307,13 +311,16 @@ class Bitcoin:
|
|||||||
signature, key_sign_pub, self.get_hash_type()
|
signature, key_sign_pub, self.get_hash_type()
|
||||||
)
|
)
|
||||||
|
|
||||||
return witness, signature
|
self.tx_ser.signature_index = i
|
||||||
|
self.tx_ser.signature = signature
|
||||||
|
self.tx_ser.serialized_tx = witness
|
||||||
|
self.tx_req.serialized = self.tx_ser
|
||||||
|
|
||||||
async def phase2_sign_nonsegwit_input(self, i_sign: int) -> None:
|
async def sign_nonsegwit_input(self, i_sign: int) -> None:
|
||||||
# hash of what we are signing with this input
|
# hash of what we are signing with this input
|
||||||
h_sign = self.create_hash_writer()
|
h_sign = self.create_hash_writer()
|
||||||
# same as h_first, checked before signing the digest
|
# should come out the same as h_confirmed, checked before signing the digest
|
||||||
h_second = self.create_hash_writer()
|
h_check = self.create_hash_writer()
|
||||||
|
|
||||||
self.write_sign_tx_header(h_sign, has_segwit=False)
|
self.write_sign_tx_header(h_sign, has_segwit=False)
|
||||||
|
|
||||||
@ -321,7 +328,7 @@ class Bitcoin:
|
|||||||
# STAGE_REQUEST_4_INPUT
|
# STAGE_REQUEST_4_INPUT
|
||||||
txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
|
txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
|
||||||
self.input_check_wallet_path(txi)
|
self.input_check_wallet_path(txi)
|
||||||
writers.write_tx_input_check(h_second, txi)
|
writers.write_tx_input_check(h_check, txi)
|
||||||
if i == i_sign:
|
if i == i_sign:
|
||||||
txi_sign = txi
|
txi_sign = txi
|
||||||
self.input_check_multisig_fingerprint(txi_sign)
|
self.input_check_multisig_fingerprint(txi_sign)
|
||||||
@ -354,14 +361,14 @@ class Bitcoin:
|
|||||||
txo = await helpers.request_tx_output(self.tx_req, i, self.coin)
|
txo = await helpers.request_tx_output(self.tx_req, i, self.coin)
|
||||||
txo_bin.amount = txo.amount
|
txo_bin.amount = txo.amount
|
||||||
txo_bin.script_pubkey = self.output_derive_script(txo)
|
txo_bin.script_pubkey = self.output_derive_script(txo)
|
||||||
writers.write_tx_output(h_second, txo_bin)
|
writers.write_tx_output(h_check, txo_bin)
|
||||||
writers.write_tx_output(h_sign, txo_bin)
|
writers.write_tx_output(h_sign, txo_bin)
|
||||||
|
|
||||||
writers.write_uint32(h_sign, self.tx.lock_time)
|
writers.write_uint32(h_sign, self.tx.lock_time)
|
||||||
writers.write_uint32(h_sign, self.get_hash_type())
|
writers.write_uint32(h_sign, self.get_hash_type())
|
||||||
|
|
||||||
# check the control digests
|
# check the control digests
|
||||||
if writers.get_tx_hash(self.h_first, False) != writers.get_tx_hash(h_second):
|
if self.h_confirmed.get_digest() != h_check.get_digest():
|
||||||
raise SigningError(
|
raise SigningError(
|
||||||
FailureType.ProcessError, "Transaction has changed during signing"
|
FailureType.ProcessError, "Transaction has changed during signing"
|
||||||
)
|
)
|
||||||
@ -375,7 +382,7 @@ class Bitcoin:
|
|||||||
key_sign, writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double)
|
key_sign, writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double)
|
||||||
)
|
)
|
||||||
|
|
||||||
# serialize input wittx_reqh correct signature
|
# serialize input with correct signature
|
||||||
gc.collect()
|
gc.collect()
|
||||||
txi_sign.script_sig = self.input_derive_script(
|
txi_sign.script_sig = self.input_derive_script(
|
||||||
txi_sign, key_sign_pub, signature
|
txi_sign, key_sign_pub, signature
|
||||||
@ -386,24 +393,31 @@ class Bitcoin:
|
|||||||
if i_sign == 0: # serializing first input => prepend headers
|
if i_sign == 0: # serializing first input => prepend headers
|
||||||
self.write_sign_tx_header(w_txi_sign, True in self.segwit.values())
|
self.write_sign_tx_header(w_txi_sign, True in self.segwit.values())
|
||||||
self.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:
|
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
|
||||||
|
|
||||||
|
async def serialize_output(self, i: int) -> None:
|
||||||
|
# STAGE_REQUEST_5_OUTPUT
|
||||||
txo = await helpers.request_tx_output(self.tx_req, i, self.coin)
|
txo = await helpers.request_tx_output(self.tx_req, i, self.coin)
|
||||||
txo_bin = TxOutputBinType()
|
txo_bin = TxOutputBinType()
|
||||||
txo_bin.amount = txo.amount
|
txo_bin.amount = txo.amount
|
||||||
txo_bin.script_pubkey = self.output_derive_script(txo)
|
txo_bin.script_pubkey = self.output_derive_script(txo)
|
||||||
|
|
||||||
# serialize output
|
|
||||||
w_txo_bin = writers.empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4)
|
w_txo_bin = writers.empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4)
|
||||||
if i == 0: # serializing first output => prepend outputs count
|
if i == 0: # serializing first output => prepend outputs count
|
||||||
writers.write_varint(w_txo_bin, self.tx.outputs_count)
|
writers.write_varint(w_txo_bin, self.tx.outputs_count)
|
||||||
writers.write_tx_output(w_txo_bin, txo_bin)
|
writers.write_tx_output(w_txo_bin, txo_bin)
|
||||||
|
|
||||||
return w_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
|
||||||
|
|
||||||
async def get_prevtx_output_value(self, prev_hash: bytes, prev_index: int) -> int:
|
async def get_prevtx_output_value(self, prev_hash: bytes, prev_index: int) -> int:
|
||||||
amount_out = 0 # sum of output amounts
|
amount_out = 0 # output amount
|
||||||
|
|
||||||
# STAGE_REQUEST_2_PREV_META
|
# STAGE_REQUEST_2_PREV_META
|
||||||
tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash)
|
tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash)
|
||||||
@ -597,7 +611,7 @@ class Bitcoin:
|
|||||||
|
|
||||||
def input_check_wallet_path(self, txi: TxInputType) -> None:
|
def input_check_wallet_path(self, txi: TxInputType) -> None:
|
||||||
if self.wallet_path is None:
|
if self.wallet_path is None:
|
||||||
return # there was a mismatch in Phase 1, ignore it now
|
return # there was a mismatch in Step 1, ignore it now
|
||||||
address_n = txi.address_n[:-_BIP32_WALLET_DEPTH]
|
address_n = txi.address_n[:-_BIP32_WALLET_DEPTH]
|
||||||
if self.wallet_path != address_n:
|
if self.wallet_path != address_n:
|
||||||
raise SigningError(
|
raise SigningError(
|
||||||
@ -606,7 +620,7 @@ class Bitcoin:
|
|||||||
|
|
||||||
def input_check_multisig_fingerprint(self, txi: TxInputType) -> None:
|
def input_check_multisig_fingerprint(self, txi: TxInputType) -> None:
|
||||||
if self.multisig_fp.mismatch is False:
|
if self.multisig_fp.mismatch is False:
|
||||||
# All inputs in Phase 1 had matching multisig fingerprints, allowing a multisig change-output.
|
# All inputs in Step 1 had matching multisig fingerprints, allowing a multisig change-output.
|
||||||
if not txi.multisig or not self.multisig_fp.matches(txi.multisig):
|
if not txi.multisig or not self.multisig_fp.matches(txi.multisig):
|
||||||
# This input no longer has a matching multisig fingerprint.
|
# This input no longer has a matching multisig fingerprint.
|
||||||
raise SigningError(
|
raise SigningError(
|
||||||
|
@ -201,11 +201,11 @@ class Overwintered(Bitcoinlike):
|
|||||||
"Unsupported version for overwintered transaction",
|
"Unsupported version for overwintered transaction",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def phase1_process_nonsegwit_input(self, i: int, txi: TxInputType) -> None:
|
async def process_nonsegwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
await self.phase1_process_bip143_input(i, txi)
|
await self.process_bip143_input(i, txi)
|
||||||
|
|
||||||
async def phase2_sign_nonsegwit_input(self, i_sign: int) -> None:
|
async def sign_nonsegwit_input(self, i_sign: int) -> None:
|
||||||
await self.phase2_sign_bip143_input(i_sign)
|
await self.sign_bip143_input(i_sign)
|
||||||
|
|
||||||
def write_tx_header(
|
def write_tx_header(
|
||||||
self, w: Writer, tx: Union[SignTx, TransactionType], has_segwit: bool
|
self, w: Writer, tx: Union[SignTx, TransactionType], has_segwit: bool
|
||||||
@ -215,7 +215,7 @@ class Overwintered(Bitcoinlike):
|
|||||||
write_uint32(w, tx.version_group_id) # nVersionGroupId
|
write_uint32(w, tx.version_group_id) # nVersionGroupId
|
||||||
|
|
||||||
def write_sign_tx_footer(self, w: Writer) -> None:
|
def write_sign_tx_footer(self, w: Writer) -> None:
|
||||||
super().write_sign_tx_footer(w)
|
write_uint32(w, self.tx.lock_time)
|
||||||
|
|
||||||
if self.tx.version == 3:
|
if self.tx.version == 3:
|
||||||
write_uint32(w, self.tx.expiry) # expiryHeight
|
write_uint32(w, self.tx.expiry) # expiryHeight
|
||||||
|
Loading…
Reference in New Issue
Block a user