core/wallet: check inputs and outputs right after receiving them

release/2020-04
Tomas Susanka 4 years ago
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]
def sanitize_tx_binoutput(tx: TransactionType) -> TxOutputBinType:
return tx.bin_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, 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…
Cancel
Save