You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
6.8 KiB
189 lines
6.8 KiB
from micropython import const
|
|
|
|
from trezor import wire
|
|
from trezor.crypto.hashlib import sha256
|
|
from trezor.utils import HashWriter
|
|
|
|
from .. import common, writers
|
|
from ..common import BIP32_WALLET_DEPTH, input_is_external
|
|
from .matchcheck import MultisigFingerprintChecker, WalletPathChecker
|
|
|
|
if False:
|
|
from typing import Optional, Protocol, Union
|
|
from trezor.messages.SignTx import SignTx
|
|
from trezor.messages.PrevTx import PrevTx
|
|
from trezor.messages.TxInput import TxInput
|
|
from trezor.messages.TxOutput import TxOutput
|
|
from trezor.messages.PrevInput import PrevInput
|
|
from trezor.messages.PrevOutput import PrevOutput
|
|
from .hash143 import Hash143
|
|
|
|
from apps.common.coininfo import CoinInfo
|
|
|
|
class Signer(Protocol):
|
|
coin = ... # type: CoinInfo
|
|
|
|
def create_hash_writer(self) -> HashWriter:
|
|
...
|
|
|
|
def create_hash143(self) -> Hash143:
|
|
...
|
|
|
|
def write_tx_header(
|
|
self,
|
|
w: writers.Writer,
|
|
tx: Union[SignTx, PrevTx],
|
|
witness_marker: bool,
|
|
) -> None:
|
|
...
|
|
|
|
@staticmethod
|
|
def write_tx_input(
|
|
w: writers.Writer,
|
|
txi: Union[TxInput, PrevInput],
|
|
script: bytes,
|
|
) -> None:
|
|
...
|
|
|
|
@staticmethod
|
|
def write_tx_output(
|
|
w: writers.Writer,
|
|
txo: Union[TxOutput, PrevOutput],
|
|
script_pubkey: bytes,
|
|
) -> None:
|
|
...
|
|
|
|
async def write_prev_tx_footer(
|
|
self, w: writers.Writer, tx: PrevTx, prev_hash: bytes
|
|
) -> None:
|
|
...
|
|
|
|
|
|
# The chain id used for change.
|
|
_BIP32_CHANGE_CHAIN = const(1)
|
|
|
|
# The maximum allowed change address. This should be large enough for normal
|
|
# use and still allow to quickly brute-force the correct BIP32 path.
|
|
_BIP32_MAX_LAST_ELEMENT = const(1_000_000)
|
|
|
|
# Setting nSequence to this value for every input in a transaction disables nLockTime.
|
|
_SEQUENCE_FINAL = const(0xFFFF_FFFF)
|
|
|
|
# Setting nSequence to a value greater than this for every input in a transaction
|
|
# disables replace-by-fee opt-in.
|
|
_MAX_BIP125_RBF_SEQUENCE = const(0xFFFF_FFFD)
|
|
|
|
|
|
class TxInfoBase:
|
|
def __init__(self, signer: Signer) -> None:
|
|
# Checksum of multisig inputs, used to validate change-output.
|
|
self.multisig_fingerprint = MultisigFingerprintChecker()
|
|
|
|
# Common prefix of input paths, used to validate change-output.
|
|
self.wallet_path = WalletPathChecker()
|
|
|
|
# h_tx_check is used to make sure that the inputs and outputs streamed in
|
|
# different steps are the same every time, e.g. the ones streamed for approval
|
|
# in Steps 1 and 2 and the ones streamed for signing legacy inputs in Step 4.
|
|
self.h_tx_check = HashWriter(sha256()) # not a real tx hash
|
|
|
|
# BIP-0143 transaction hashing.
|
|
self.hash143 = signer.create_hash143()
|
|
|
|
# The minimum nSequence of all inputs.
|
|
self.min_sequence = _SEQUENCE_FINAL
|
|
|
|
def add_input(self, txi: TxInput) -> None:
|
|
self.hash143.add_input(txi) # all inputs are included (non-segwit as well)
|
|
writers.write_tx_input_check(self.h_tx_check, txi)
|
|
self.min_sequence = min(self.min_sequence, txi.sequence)
|
|
|
|
if not input_is_external(txi):
|
|
self.wallet_path.add_input(txi)
|
|
self.multisig_fingerprint.add_input(txi)
|
|
|
|
def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None:
|
|
self.hash143.add_output(txo, script_pubkey)
|
|
writers.write_tx_output(self.h_tx_check, txo, script_pubkey)
|
|
|
|
def check_input(self, txi: TxInput) -> None:
|
|
self.wallet_path.check_input(txi)
|
|
self.multisig_fingerprint.check_input(txi)
|
|
|
|
def output_is_change(self, txo: TxOutput) -> bool:
|
|
if txo.script_type not in common.CHANGE_OUTPUT_SCRIPT_TYPES:
|
|
return False
|
|
if txo.multisig and not self.multisig_fingerprint.output_matches(txo):
|
|
return False
|
|
return (
|
|
self.wallet_path.output_matches(txo)
|
|
and len(txo.address_n) >= BIP32_WALLET_DEPTH
|
|
and txo.address_n[-2] <= _BIP32_CHANGE_CHAIN
|
|
and txo.address_n[-1] <= _BIP32_MAX_LAST_ELEMENT
|
|
and txo.amount > 0
|
|
)
|
|
|
|
def lock_time_disabled(self) -> bool:
|
|
return self.min_sequence == _SEQUENCE_FINAL
|
|
|
|
def rbf_disabled(self) -> bool:
|
|
return self.min_sequence > _MAX_BIP125_RBF_SEQUENCE
|
|
|
|
def get_tx_check_digest(self) -> bytes:
|
|
return self.h_tx_check.get_digest()
|
|
|
|
|
|
# Used to keep track of the transaction currently being signed.
|
|
class TxInfo(TxInfoBase):
|
|
def __init__(self, signer: Signer, tx: SignTx) -> None:
|
|
super().__init__(signer)
|
|
self.tx = tx
|
|
|
|
|
|
# Used to keep track of any original transactions which are being replaced by the current transaction.
|
|
class OriginalTxInfo(TxInfoBase):
|
|
def __init__(self, signer: Signer, tx: PrevTx, orig_hash: bytes) -> None:
|
|
super().__init__(signer)
|
|
self.tx = tx
|
|
self.signer = signer
|
|
self.orig_hash = orig_hash
|
|
|
|
# Index of the next input or output to be added by add_input or add_output. Signer uses this
|
|
# value to check that original transaction inputs and outputs are streamed in order, and to
|
|
# check whether any have been skipped. Incrementing and resetting this variable is the
|
|
# responsibility of the signer class.
|
|
self.index = 0
|
|
|
|
# Transaction hasher to compute the TXID.
|
|
self.h_tx = signer.create_hash_writer()
|
|
signer.write_tx_header(self.h_tx, tx, witness_marker=False)
|
|
writers.write_bitcoin_varint(self.h_tx, tx.inputs_count)
|
|
|
|
# The input which will be used for verification and its index in the original transaction.
|
|
self.verification_input = None # type: Optional[TxInput]
|
|
self.verification_index = None # type: Optional[int]
|
|
|
|
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())
|
|
|
|
# For verification use the first original input that specifies address_n.
|
|
if not self.verification_input and txi.address_n:
|
|
self.verification_input = txi
|
|
self.verification_index = self.index
|
|
|
|
def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None:
|
|
super().add_output(txo, script_pubkey)
|
|
|
|
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)
|
|
|
|
async def finalize_tx_hash(self) -> None:
|
|
await self.signer.write_prev_tx_footer(self.h_tx, self.tx, self.orig_hash)
|
|
if self.orig_hash != writers.get_tx_hash(
|
|
self.h_tx, double=self.signer.coin.sign_hash_double, reverse=True
|
|
):
|
|
raise wire.ProcessError("Invalid original TXID.")
|