Have trezor ignore inputs it cannot sign rather than throwing an error

Instead of erroring and failing to sign the transaction when an input
cannot be signed, just ignore that input. Ideally this would be done
using InputScriptType.EXTERNAL, but the firmware does not support this
So to achieve this, we instead fill in the input with bogus data and
let the Trezor sign it anyways. Then we just ignore the signature
that was produced.
This commit is contained in:
Andrew Chow 2019-01-02 21:33:11 -05:00
parent f2f8b5de93
commit 327f1e0122

View File

@ -16,6 +16,8 @@ import json
import logging
import os
py_enumerate = enumerate # Need to use the enumerate built-in but there's another function already named that
# Only handles up to 15 of 15
def parse_multisig(script):
# Get m
@ -89,7 +91,8 @@ class TrezorClient(HardwareWalletClient):
# Prepare inputs
inputs = []
for psbt_in, txin in zip(tx.inputs, tx.tx.vin):
to_ignore = [] # Note down which inputs whose signatures we're going to ignore
for input_num, (psbt_in, txin) in py_enumerate(list(zip(tx.inputs, tx.tx.vin))):
txinputtype = proto.TxInputType()
# Set the input stuff
@ -98,55 +101,68 @@ class TrezorClient(HardwareWalletClient):
txinputtype.sequence = txin.nSequence
# Detrermine spend type
scriptcode = b''
if psbt_in.non_witness_utxo:
utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n]
txinputtype.script_type = proto.InputScriptType.SPENDADDRESS
scriptcode = utxo.scriptPubKey
txinputtype.amount = psbt_in.non_witness_utxo.vout[txin.prevout.n].nValue
elif psbt_in.witness_utxo:
utxo = psbt_in.witness_utxo
# Check if the output is p2sh
if psbt_in.witness_utxo.is_p2sh():
txinputtype.script_type = proto.InputScriptType.SPENDP2SHWITNESS
else:
txinputtype.script_type = proto.InputScriptType.SPENDWITNESS
# Check for 1 key
if len(psbt_in.hd_keypaths) == 1:
# Is this key ours
pubkey = list(psbt_in.hd_keypaths.keys())[0]
fp = psbt_in.hd_keypaths[pubkey][0]
keypath = list(psbt_in.hd_keypaths[pubkey][1:])
if fp == master_fp:
# Set the keypath
txinputtype.address_n = keypath
# Check for multisig (more than 1 key)
elif len(psbt_in.hd_keypaths) > 1:
if psbt_in.witness_script:
valid, multisig = parse_multisig(psbt_in.witness_script)
elif psbt_in.redeem_script:
valid, multisig = parse_multisig(psbt_in.redeem_script)
else:
valid, multisig = (False, None)
if valid:
ms_pubkeys = []
for key in psbt_in.hd_keypaths.keys():
keypath = psbt_in.hd_keypaths[key]
if keypath[0] == master_fp:
txinputtype.address_n = keypath[1:]
break
# Add to txinputtype
txinputtype.multisig = multisig
else:
raise TypeError('Cannot sign transactions with arbitrary scripts')
else:
raise TypeError("All inputs must have a key for this device")
# Set the amount
if psbt_in.non_witness_utxo:
txinputtype.amount = psbt_in.non_witness_utxo.vout[txin.prevout.n].nValue
elif psbt_in.witness_utxo:
scriptcode = psbt_in.witness_utxo.scriptPubKey
txinputtype.amount = psbt_in.witness_utxo.nValue
# Set the script
if psbt_in.witness_script:
scriptcode = psbt_in.witness_script
elif psbt_in.redeem_script:
scriptcode = psbt_in.redeem_script
def ignore_input():
txinputtype.address_n = [0x80000000]
inputs.append(txinputtype)
to_ignore.append(input_num)
# Check for multisig
is_ms, multisig = parse_multisig(scriptcode)
if is_ms:
# Add to txinputtype
txinputtype.multisig = multisig
if psbt_in.non_witness_utxo:
if utxo.is_p2sh:
txinputtype.script_type = proto.InputScriptType.SPENDMULTISIG
else:
# Cannot sign bare multisig, ignore it
ignore_input()
continue
elif not is_ms and psbt_in.non_witness_utxo and not utxo.is_p2pkh:
# Cannot sign unknown spk, ignore it
ignore_input()
continue
elif not is_ms and psbt_in.witness_utxo and psbt_in.witness_script:
# Cannot sign unknown witness script, ignore it
ignore_input()
continue
# Find key to sign with
found = False
for key in psbt_in.hd_keypaths.keys():
keypath = psbt_in.hd_keypaths[key]
if keypath[0] == master_fp and key not in psbt_in.partial_sigs:
txinputtype.address_n = keypath[1:]
found = True
break
if not found:
# This input is not one of ours
ignore_input()
continue
# append to inputs
inputs.append(txinputtype)
@ -215,15 +231,15 @@ class TrezorClient(HardwareWalletClient):
else:
signed_tx = btc.sign_tx(self.client, "Bitcoin", inputs, outputs, tx_details, prevtxs)
signatures = signed_tx[0]
for psbt_in in tx.inputs:
for pubkey, sig in zip(psbt_in.hd_keypaths.keys(), signatures):
# Each input has one signature
for input_num, (psbt_in, sig) in py_enumerate(list(zip(tx.inputs, signed_tx[0]))):
if input_num in to_ignore:
continue
for pubkey in psbt_in.hd_keypaths.keys():
fp = psbt_in.hd_keypaths[pubkey][0]
keypath = psbt_in.hd_keypaths[pubkey][1:]
if fp == master_fp:
if fp == master_fp and key not in psbt_in.partial_sigs:
psbt_in.partial_sigs[pubkey] = sig + b'\x01'
break
signatures.remove(sig)
break
return {'psbt':tx.serialize()}