From 985c01caf4ed8445b7a529a910e531f83e1dafc8 Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Thu, 8 Feb 2018 13:14:36 +0100 Subject: [PATCH] signing/multisig: change check using multisig fingerprint --- src/apps/wallet/sign_tx/multisig.py | 36 +++++++++++++++++++++++++++++ src/apps/wallet/sign_tx/signing.py | 29 ++++++++++++++++------- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/apps/wallet/sign_tx/multisig.py b/src/apps/wallet/sign_tx/multisig.py index 85f0ab8974..108b98a729 100644 --- a/src/apps/wallet/sign_tx/multisig.py +++ b/src/apps/wallet/sign_tx/multisig.py @@ -1,9 +1,45 @@ +from trezor.crypto.hashlib import sha256 from trezor.crypto import bip32 from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType from trezor.messages.HDNodePathType import HDNodePathType from apps.wallet.sign_tx.writers import * +from apps.common.hash_writer import * + + +def multisig_fingerprint(multisig: MultisigRedeemScriptType) -> bytes: + pubkeys = multisig.pubkeys + m = multisig.m + n = len(pubkeys) + + if n < 1 or n > 15: + return None + if m < 1 or m > 15: + return None + + for hd in pubkeys: + d = hd.node + if len(d.public_key) != 33: + return None + if len(d.chain_code) != 32: + return None + + # casting to bytes(), sorting on bytearray() is not supported in MicroPython + pubkeys = sorted(pubkeys, key=lambda hd: bytes(hd.node.public_key)) + + h = HashWriter(sha256) + write_uint32(h, m) + write_uint32(h, n) + for hd in pubkeys: + d = hd.node + write_uint32(h, d.depth) + write_uint32(h, d.fingerprint) + write_uint32(h, d.child_num) + write_bytes(h, d.chain_code) + write_bytes(h, d.public_key) + + return h.get_digest() def multisig_pubkey_index(multisig: MultisigRedeemScriptType, pubkey: bytes) -> int: diff --git a/src/apps/wallet/sign_tx/signing.py b/src/apps/wallet/sign_tx/signing.py index f48e4e7958..769296ca2d 100644 --- a/src/apps/wallet/sign_tx/signing.py +++ b/src/apps/wallet/sign_tx/signing.py @@ -64,6 +64,8 @@ async def check_tx_fee(tx: SignTx, root): change_out = 0 # change output amount wallet_path = [] # common prefix of input paths segwit = {} # dict of booleans stating if input is segwit + multisig_fp = bytes() # multisig fingerprint + multisig_fp_mismatch = False # flag if multisig input fingerprints are equal for i in range(tx.inputs_count): # STAGE_REQUEST_1_INPUT @@ -73,8 +75,7 @@ async def check_tx_fee(tx: SignTx, root): weight.add_input(txi) bip143.add_prevouts(txi) # all inputs are included (non-segwit as well) bip143.add_sequence(txi) - is_segwit = (txi.script_type == InputScriptType.SPENDWITNESS or - txi.script_type == InputScriptType.SPENDP2SHWITNESS) + if coin.force_bip143: is_bip143 = (txi.script_type == InputScriptType.SPENDADDRESS) if not is_bip143: @@ -86,7 +87,8 @@ async def check_tx_fee(tx: SignTx, root): segwit[i] = False segwit_in += txi.amount total_in += txi.amount - elif is_segwit: + + elif txi.script_type in [InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS]: if not coin.segwit: raise SigningError(FailureType.DataError, 'Segwit not enabled on this coin') @@ -96,10 +98,18 @@ async def check_tx_fee(tx: SignTx, root): segwit[i] = True segwit_in += txi.amount total_in += txi.amount + elif txi.script_type in [InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG]: segwit[i] = False total_in += await get_prevtx_output_value( tx_req, txi.prev_hash, txi.prev_index) + if txi.script_type == InputScriptType.SPENDMULTISIG: + fp = multisig_fingerprint(txi.multisig) + if not len(multisig_fp): + multisig_fp = fp + elif multisig_fp != fp: + multisig_fp_mismatch = True + else: raise SigningError(FailureType.DataError, 'Wrong input script type') @@ -110,7 +120,7 @@ async def check_tx_fee(tx: SignTx, root): txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) weight.add_output(txo_bin.script_pubkey) - if output_is_change(txo, wallet_path, segwit_in): + if is_change(txo, wallet_path, segwit_in, multisig_fp, multisig_fp_mismatch): if change_out != 0: raise SigningError(FailureType.ProcessError, 'Only one change output is valid') @@ -438,10 +448,6 @@ def output_derive_script(o: TxOutputType, coin: CoinType, root) -> bytes: if o.address_n: # change output if o.address: raise SigningError(FailureType.DataError, 'Address in change output') - if o.multisig: - if not check_address_n_against_pubkeys(o.multisig, o.address_n): - raise AddressError(FailureType.ProcessError, - 'address_n must match one of the address_n in the MultisigRedeemScriptType pubkeys') o.address = get_address_for_change(o, coin, root) else: if not o.address: @@ -543,3 +549,10 @@ def ecdsa_sign(node, digest: bytes) -> bytes: sig = secp256k1.sign(node.private_key(), digest) sigder = der.encode_seq((sig[1:33], sig[33:65])) return sigder + + +def is_change(txo: TxOutputType, wallet_path, segwit_in: int, multisig_fp: bytes, multisig_fp_mismatch: bool) -> bool: + if txo.multisig: + if multisig_fp_mismatch or (multisig_fp != multisig_fingerprint(txo.multisig)): + return False + return output_is_change(txo, wallet_path, segwit_in)