mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-24 06:11:06 +00:00
core/wallet: check inputs and outputs right after receiving them
This commit is contained in:
parent
0903159d9b
commit
f0a39df75d
@ -1,6 +1,6 @@
|
||||
import gc
|
||||
|
||||
from trezor.messages import InputScriptType
|
||||
from trezor.messages import FailureType, InputScriptType, OutputScriptType
|
||||
from trezor.messages.RequestType import (
|
||||
TXEXTRADATA,
|
||||
TXFINISHED,
|
||||
@ -16,8 +16,31 @@ from trezor.messages.TxOutputType import TxOutputType
|
||||
from trezor.messages.TxRequest import TxRequest
|
||||
from trezor.utils import obj_eq
|
||||
|
||||
from .signing import SigningError
|
||||
|
||||
from apps.common.coininfo import CoinInfo
|
||||
|
||||
CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES = {
|
||||
OutputScriptType.PAYTOADDRESS: InputScriptType.SPENDADDRESS,
|
||||
OutputScriptType.PAYTOMULTISIG: InputScriptType.SPENDMULTISIG,
|
||||
OutputScriptType.PAYTOWITNESS: InputScriptType.SPENDWITNESS,
|
||||
OutputScriptType.PAYTOP2SHWITNESS: InputScriptType.SPENDP2SHWITNESS,
|
||||
}
|
||||
CHANGE_INPUT_SCRIPT_TYPES = tuple(CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES.values())
|
||||
CHANGE_OUTPUT_SCRIPT_TYPES = tuple(CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES.keys())
|
||||
|
||||
MULTISIG_INPUT_SCRIPT_TYPES = (
|
||||
InputScriptType.SPENDMULTISIG,
|
||||
InputScriptType.SPENDP2SHWITNESS,
|
||||
InputScriptType.SPENDWITNESS,
|
||||
)
|
||||
MULTISIG_OUTPUT_SCRIPT_TYPES = (
|
||||
OutputScriptType.PAYTOMULTISIG,
|
||||
OutputScriptType.PAYTOP2SHWITNESS,
|
||||
OutputScriptType.PAYTOWITNESS,
|
||||
)
|
||||
|
||||
|
||||
# Machine instructions
|
||||
# ===
|
||||
|
||||
@ -107,17 +130,17 @@ def request_tx_extra_data(
|
||||
return ack.tx.extra_data
|
||||
|
||||
|
||||
def request_tx_input(tx_req: TxRequest, i: int, tx_hash: bytes = None):
|
||||
def request_tx_input(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes = None):
|
||||
tx_req.request_type = TXINPUT
|
||||
tx_req.details.request_index = i
|
||||
tx_req.details.tx_hash = tx_hash
|
||||
ack = yield tx_req
|
||||
tx_req.serialized = None
|
||||
gc.collect()
|
||||
return sanitize_tx_input(ack.tx)
|
||||
return sanitize_tx_input(ack.tx, coin)
|
||||
|
||||
|
||||
def request_tx_output(tx_req: TxRequest, i: int, tx_hash: bytes = None):
|
||||
def request_tx_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes = None):
|
||||
tx_req.request_type = TXOUTPUT
|
||||
tx_req.details.request_index = i
|
||||
tx_req.details.tx_hash = tx_hash
|
||||
@ -127,7 +150,7 @@ def request_tx_output(tx_req: TxRequest, i: int, tx_hash: bytes = None):
|
||||
if tx_hash is None:
|
||||
return sanitize_tx_output(ack.tx)
|
||||
else:
|
||||
return sanitize_tx_binoutput(ack.tx)
|
||||
return sanitize_tx_binoutput(ack.tx, coin)
|
||||
|
||||
|
||||
def request_tx_finish(tx_req: TxRequest):
|
||||
@ -166,18 +189,74 @@ def sanitize_tx_meta(tx: TransactionType) -> TransactionType:
|
||||
return tx
|
||||
|
||||
|
||||
def sanitize_tx_input(tx: TransactionType) -> TxInputType:
|
||||
def sanitize_tx_input(tx: TransactionType, coin: CoinInfo) -> TxInputType:
|
||||
txi = tx.inputs[0]
|
||||
if txi.script_type is None:
|
||||
txi.script_type = InputScriptType.SPENDADDRESS
|
||||
if txi.sequence is None:
|
||||
txi.sequence = 0xFFFFFFFF
|
||||
if txi.multisig and txi.script_type not in MULTISIG_INPUT_SCRIPT_TYPES:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "Multisig field provided but not expected.",
|
||||
)
|
||||
if txi.address_n and txi.script_type not in CHANGE_INPUT_SCRIPT_TYPES:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "Input's address_n provided but not expected.",
|
||||
)
|
||||
if txi.decred_script_version or txi.decred_script_version and not coin.decred:
|
||||
raise SigningError(
|
||||
FailureType.DataError,
|
||||
"Decred details provided but Decred coin not specified.",
|
||||
)
|
||||
if (txi.prev_block_hash_bip115 or txi.prev_block_height_bip115) and not coin.bip115:
|
||||
raise SigningError(
|
||||
FailureType.DataError,
|
||||
"BIP-115 details provided, but the specified coin is unaware of BIP-115.",
|
||||
)
|
||||
return txi
|
||||
|
||||
|
||||
def sanitize_tx_output(tx: TransactionType) -> TxOutputType:
|
||||
return tx.outputs[0]
|
||||
txo = tx.outputs[0]
|
||||
if txo.multisig and txo.script_type not in MULTISIG_OUTPUT_SCRIPT_TYPES:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "Multisig field provided but not expected.",
|
||||
)
|
||||
if txo.address_n and txo.script_type not in CHANGE_OUTPUT_SCRIPT_TYPES:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "Output's address_n provided but not expected.",
|
||||
)
|
||||
if txo.script_type == OutputScriptType.PAYTOOPRETURN:
|
||||
# op_return output
|
||||
if txo.amount != 0:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "OP_RETURN output with non-zero amount"
|
||||
)
|
||||
if txo.address or txo.address_n or txo.multisig:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "OP_RETURN output with address or multisig"
|
||||
)
|
||||
else:
|
||||
if txo.op_return_data:
|
||||
raise SigningError(
|
||||
FailureType.DataError,
|
||||
"OP RETURN data provided but not OP RETURN script type.",
|
||||
)
|
||||
if txo.address_n and txo.address:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "Both address and address_n provided."
|
||||
)
|
||||
if not txo.address_n and not txo.address:
|
||||
raise SigningError(FailureType.DataError, "Missing address")
|
||||
|
||||
return txo
|
||||
|
||||
|
||||
def sanitize_tx_binoutput(tx: TransactionType) -> TxOutputBinType:
|
||||
return tx.bin_outputs[0]
|
||||
def sanitize_tx_binoutput(tx: TransactionType, coin: CoinInfo) -> TxOutputBinType:
|
||||
txo_bin = tx.bin_outputs[0]
|
||||
if txo_bin.decred_script_version and not coin.decred:
|
||||
raise SigningError(
|
||||
FailureType.DataError,
|
||||
"Decred details provided but Decred coin not specified.",
|
||||
)
|
||||
return txo_bin
|
||||
|
@ -99,7 +99,7 @@ async def check_tx_fee(tx: SignTx, keychain: seed.Keychain):
|
||||
for i in range(tx.inputs_count):
|
||||
progress.advance()
|
||||
# STAGE_REQUEST_1_INPUT
|
||||
txi = await helpers.request_tx_input(tx_req, i)
|
||||
txi = await helpers.request_tx_input(tx_req, i, coin)
|
||||
wallet_path = input_extract_wallet_path(txi, wallet_path)
|
||||
writers.write_tx_input_check(h_first, txi)
|
||||
weight.add_input(txi)
|
||||
@ -162,7 +162,7 @@ async def check_tx_fee(tx: SignTx, keychain: seed.Keychain):
|
||||
|
||||
for o in range(tx.outputs_count):
|
||||
# STAGE_REQUEST_3_OUTPUT
|
||||
txo = await helpers.request_tx_output(tx_req, o)
|
||||
txo = await helpers.request_tx_output(tx_req, o, coin)
|
||||
txo_bin.amount = txo.amount
|
||||
txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)
|
||||
weight.add_output(txo_bin.script_pubkey)
|
||||
@ -255,7 +255,7 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
|
||||
|
||||
if segwit[i_sign]:
|
||||
# STAGE_REQUEST_SEGWIT_INPUT
|
||||
txi_sign = await helpers.request_tx_input(tx_req, i_sign)
|
||||
txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin)
|
||||
|
||||
if not input_is_segwit(txi_sign):
|
||||
raise SigningError(
|
||||
@ -280,7 +280,7 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
|
||||
|
||||
elif coin.force_bip143 or (not utils.BITCOIN_ONLY and tx.overwintered):
|
||||
# STAGE_REQUEST_SEGWIT_INPUT
|
||||
txi_sign = await helpers.request_tx_input(tx_req, i_sign)
|
||||
txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin)
|
||||
input_check_wallet_path(txi_sign, wallet_path)
|
||||
|
||||
is_bip143 = (
|
||||
@ -327,7 +327,7 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
|
||||
tx_req.serialized = tx_ser
|
||||
|
||||
elif not utils.BITCOIN_ONLY and coin.decred:
|
||||
txi_sign = await helpers.request_tx_input(tx_req, i_sign)
|
||||
txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin)
|
||||
|
||||
input_check_wallet_path(txi_sign, wallet_path)
|
||||
|
||||
@ -414,7 +414,7 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
|
||||
|
||||
for i in range(tx.inputs_count):
|
||||
# STAGE_REQUEST_4_INPUT
|
||||
txi = await helpers.request_tx_input(tx_req, i)
|
||||
txi = await helpers.request_tx_input(tx_req, i, coin)
|
||||
input_check_wallet_path(txi, wallet_path)
|
||||
writers.write_tx_input_check(h_second, txi)
|
||||
if i == i_sign:
|
||||
@ -449,7 +449,7 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
|
||||
|
||||
for o in range(tx.outputs_count):
|
||||
# STAGE_REQUEST_4_OUTPUT
|
||||
txo = await helpers.request_tx_output(tx_req, o)
|
||||
txo = await helpers.request_tx_output(tx_req, o, coin)
|
||||
txo_bin.amount = txo.amount
|
||||
txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)
|
||||
writers.write_tx_output(h_second, txo_bin)
|
||||
@ -500,7 +500,7 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
|
||||
for o in range(tx.outputs_count):
|
||||
progress.advance()
|
||||
# STAGE_REQUEST_5_OUTPUT
|
||||
txo = await helpers.request_tx_output(tx_req, o)
|
||||
txo = await helpers.request_tx_output(tx_req, o, coin)
|
||||
txo_bin.amount = txo.amount
|
||||
txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)
|
||||
|
||||
@ -522,7 +522,7 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
|
||||
progress.advance()
|
||||
if segwit[i]:
|
||||
# STAGE_REQUEST_SEGWIT_WITNESS
|
||||
txi = await helpers.request_tx_input(tx_req, i)
|
||||
txi = await helpers.request_tx_input(tx_req, i, coin)
|
||||
input_check_wallet_path(txi, wallet_path)
|
||||
|
||||
if not input_is_segwit(txi) or txi.amount > authorized_in:
|
||||
@ -615,7 +615,7 @@ async def get_prevtx_output_value(
|
||||
|
||||
for i in range(tx.inputs_cnt):
|
||||
# STAGE_REQUEST_2_PREV_INPUT
|
||||
txi = await helpers.request_tx_input(tx_req, i, prev_hash)
|
||||
txi = await helpers.request_tx_input(tx_req, i, coin, prev_hash)
|
||||
if not utils.BITCOIN_ONLY and coin.decred:
|
||||
writers.write_tx_input_decred(txh, txi)
|
||||
else:
|
||||
@ -625,7 +625,7 @@ async def get_prevtx_output_value(
|
||||
|
||||
for o in range(tx.outputs_cnt):
|
||||
# STAGE_REQUEST_2_PREV_OUTPUT
|
||||
txo_bin = await helpers.request_tx_output(tx_req, o, prev_hash)
|
||||
txo_bin = await helpers.request_tx_output(tx_req, o, coin, prev_hash)
|
||||
writers.write_tx_output(txh, txo_bin)
|
||||
if o == prev_index:
|
||||
total_out += txo_bin.amount
|
||||
@ -701,25 +701,11 @@ def output_derive_script(
|
||||
) -> bytes:
|
||||
|
||||
if o.script_type == OutputScriptType.PAYTOOPRETURN:
|
||||
# op_return output
|
||||
if o.amount != 0:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "OP_RETURN output with non-zero amount"
|
||||
)
|
||||
if o.address or o.address_n or o.multisig:
|
||||
raise SigningError(
|
||||
FailureType.DataError, "OP_RETURN output with address or multisig"
|
||||
)
|
||||
return scripts.output_script_paytoopreturn(o.op_return_data)
|
||||
|
||||
if o.address_n:
|
||||
# change output
|
||||
if o.address:
|
||||
raise SigningError(FailureType.DataError, "Address in change output")
|
||||
o.address = get_address_for_change(o, coin, keychain)
|
||||
else:
|
||||
if not o.address:
|
||||
raise SigningError(FailureType.DataError, "Missing address")
|
||||
|
||||
if coin.bech32_prefix and o.address.startswith(coin.bech32_prefix):
|
||||
# p2wpkh or p2wsh
|
||||
@ -770,15 +756,9 @@ def output_derive_script(
|
||||
def get_address_for_change(
|
||||
o: TxOutputType, coin: coininfo.CoinInfo, keychain: seed.Keychain
|
||||
):
|
||||
if o.script_type == OutputScriptType.PAYTOADDRESS:
|
||||
input_script_type = InputScriptType.SPENDADDRESS
|
||||
elif o.script_type == OutputScriptType.PAYTOMULTISIG:
|
||||
input_script_type = InputScriptType.SPENDMULTISIG
|
||||
elif o.script_type == OutputScriptType.PAYTOWITNESS:
|
||||
input_script_type = InputScriptType.SPENDWITNESS
|
||||
elif o.script_type == OutputScriptType.PAYTOP2SHWITNESS:
|
||||
input_script_type = InputScriptType.SPENDP2SHWITNESS
|
||||
else:
|
||||
try:
|
||||
input_script_type = helpers.CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[o.script_type]
|
||||
except KeyError:
|
||||
raise SigningError(FailureType.DataError, "Invalid script type")
|
||||
node = keychain.derive(o.address_n, coin.curve_name)
|
||||
return addresses.get_address(input_script_type, coin, node, o.multisig)
|
||||
@ -790,12 +770,7 @@ def output_is_change(
|
||||
segwit_in: int,
|
||||
multifp: multisig.MultisigFingerprint,
|
||||
) -> bool:
|
||||
if o.script_type not in (
|
||||
OutputScriptType.PAYTOADDRESS,
|
||||
OutputScriptType.PAYTOMULTISIG,
|
||||
OutputScriptType.PAYTOWITNESS,
|
||||
OutputScriptType.PAYTOP2SHWITNESS,
|
||||
):
|
||||
if o.script_type not in helpers.CHANGE_OUTPUT_SCRIPT_TYPES:
|
||||
return False
|
||||
if o.multisig and not multifp.matches(o.multisig):
|
||||
return False
|
||||
|
@ -241,4 +241,6 @@ class TestOpReturn:
|
||||
"OP_RETURN output with address or multisig"
|
||||
)
|
||||
else:
|
||||
assert exc.value.args[1] == "OP_RETURN output with address or multisig"
|
||||
assert (
|
||||
exc.value.args[1] == "Output's address_n provided but not expected."
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user