Go through the signing process multiple times so that multisigs are fully signed
Goes through the entire signing process multiple times in order to fully sign multisigs that have multiple keys from this device.
This commit is contained in:
parent
cde7dfef41
commit
3b0429b7ec
@ -98,7 +98,7 @@ def signtx(args, client):
|
||||
return {'error': str(e), 'code': NOT_IMPLEMENTED}
|
||||
except IOError as e:
|
||||
return {'error':'You must provide a PSBT','code':INVALID_TX}
|
||||
except Excetion as e:
|
||||
except Exception as e:
|
||||
return {'error': str(e), 'code': BAD_ARGUMENT}
|
||||
|
||||
def getxpub(args, client):
|
||||
|
||||
@ -90,157 +90,170 @@ class TrezorClient(HardwareWalletClient):
|
||||
master_key = btc.get_public_node(self.client, [0])
|
||||
master_fp = get_xpub_fingerprint(master_key.xpub)
|
||||
|
||||
# Prepare inputs
|
||||
inputs = []
|
||||
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()
|
||||
# Do multiple passes for multisig
|
||||
passes = 1
|
||||
p = 0
|
||||
|
||||
# Set the input stuff
|
||||
txinputtype.prev_hash = ser_uint256(txin.prevout.hash)[::-1]
|
||||
txinputtype.prev_index = txin.prevout.n
|
||||
txinputtype.sequence = txin.nSequence
|
||||
while p < passes:
|
||||
# Prepare inputs
|
||||
inputs = []
|
||||
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()
|
||||
|
||||
# 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
|
||||
scriptcode = psbt_in.witness_utxo.scriptPubKey
|
||||
txinputtype.amount = psbt_in.witness_utxo.nValue
|
||||
# Set the input stuff
|
||||
txinputtype.prev_hash = ser_uint256(txin.prevout.hash)[::-1]
|
||||
txinputtype.prev_index = txin.prevout.n
|
||||
txinputtype.sequence = txin.nSequence
|
||||
|
||||
# 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
|
||||
# Detrermine spend type
|
||||
scriptcode = b''
|
||||
if psbt_in.non_witness_utxo:
|
||||
if utxo.is_p2sh:
|
||||
txinputtype.script_type = proto.InputScriptType.SPENDMULTISIG
|
||||
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:
|
||||
# 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
|
||||
txinputtype.script_type = proto.InputScriptType.SPENDWITNESS
|
||||
scriptcode = psbt_in.witness_utxo.scriptPubKey
|
||||
txinputtype.amount = psbt_in.witness_utxo.nValue
|
||||
|
||||
# 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
|
||||
# Set the script
|
||||
if psbt_in.witness_script:
|
||||
scriptcode = psbt_in.witness_script
|
||||
elif psbt_in.redeem_script:
|
||||
scriptcode = psbt_in.redeem_script
|
||||
|
||||
if not found:
|
||||
# This input is not one of ours
|
||||
ignore_input()
|
||||
continue
|
||||
def ignore_input():
|
||||
txinputtype.address_n = [0x80000000]
|
||||
inputs.append(txinputtype)
|
||||
to_ignore.append(input_num)
|
||||
|
||||
# append to inputs
|
||||
inputs.append(txinputtype)
|
||||
# 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
|
||||
|
||||
# address version byte
|
||||
if self.is_testnet:
|
||||
p2pkh_version = b'\x6f'
|
||||
p2sh_version = b'\xc4'
|
||||
bech32_hrp = 'tb'
|
||||
else:
|
||||
p2pkh_version = b'\x00'
|
||||
p2sh_version = b'\x05'
|
||||
bech32_hrp = 'bc'
|
||||
# Find key to sign with
|
||||
found = False
|
||||
our_keys = 0
|
||||
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:
|
||||
if not found:
|
||||
txinputtype.address_n = keypath[1:]
|
||||
found = True
|
||||
our_keys += 1
|
||||
|
||||
# prepare outputs
|
||||
outputs = []
|
||||
for out in tx.tx.vout:
|
||||
txoutput = proto.TxOutputType()
|
||||
txoutput.amount = out.nValue
|
||||
txoutput.script_type = proto.OutputScriptType.PAYTOADDRESS
|
||||
if out.is_p2pkh():
|
||||
txoutput.address = to_address(out.scriptPubKey[3:23], p2pkh_version)
|
||||
elif out.is_p2sh():
|
||||
txoutput.address = to_address(out.scriptPubKey[2:22], p2sh_version)
|
||||
# Determine if we need to do more passes to sign everything
|
||||
if our_keys > passes:
|
||||
passes = our_keys
|
||||
|
||||
if not found:
|
||||
# This input is not one of ours
|
||||
ignore_input()
|
||||
continue
|
||||
|
||||
# append to inputs
|
||||
inputs.append(txinputtype)
|
||||
|
||||
# address version byte
|
||||
if self.is_testnet:
|
||||
p2pkh_version = b'\x6f'
|
||||
p2sh_version = b'\xc4'
|
||||
bech32_hrp = 'tb'
|
||||
else:
|
||||
wit, ver, prog = out.is_witness()
|
||||
if wit:
|
||||
txoutput.address = bech32.encode(bech32_hrp, ver, prog)
|
||||
p2pkh_version = b'\x00'
|
||||
p2sh_version = b'\x05'
|
||||
bech32_hrp = 'bc'
|
||||
|
||||
# prepare outputs
|
||||
outputs = []
|
||||
for out in tx.tx.vout:
|
||||
txoutput = proto.TxOutputType()
|
||||
txoutput.amount = out.nValue
|
||||
txoutput.script_type = proto.OutputScriptType.PAYTOADDRESS
|
||||
if out.is_p2pkh():
|
||||
txoutput.address = to_address(out.scriptPubKey[3:23], p2pkh_version)
|
||||
elif out.is_p2sh():
|
||||
txoutput.address = to_address(out.scriptPubKey[2:22], p2sh_version)
|
||||
else:
|
||||
raise TypeError("Output is not an address")
|
||||
wit, ver, prog = out.is_witness()
|
||||
if wit:
|
||||
txoutput.address = bech32.encode(bech32_hrp, ver, prog)
|
||||
else:
|
||||
raise TypeError("Output is not an address")
|
||||
|
||||
# append to outputs
|
||||
outputs.append(txoutput)
|
||||
# append to outputs
|
||||
outputs.append(txoutput)
|
||||
|
||||
# Prepare prev txs
|
||||
prevtxs = {}
|
||||
for psbt_in in tx.inputs:
|
||||
if psbt_in.non_witness_utxo:
|
||||
prev = psbt_in.non_witness_utxo
|
||||
# Prepare prev txs
|
||||
prevtxs = {}
|
||||
for psbt_in in tx.inputs:
|
||||
if psbt_in.non_witness_utxo:
|
||||
prev = psbt_in.non_witness_utxo
|
||||
|
||||
t = proto.TransactionType()
|
||||
t.version = prev.nVersion
|
||||
t.lock_time = prev.nLockTime
|
||||
t = proto.TransactionType()
|
||||
t.version = prev.nVersion
|
||||
t.lock_time = prev.nLockTime
|
||||
|
||||
for vin in prev.vin:
|
||||
i = proto.TxInputType()
|
||||
i.prev_hash = ser_uint256(vin.prevout.hash)[::-1]
|
||||
i.prev_index = vin.prevout.n
|
||||
i.script_sig = vin.scriptSig
|
||||
i.sequence = vin.nSequence
|
||||
t.inputs.append(i)
|
||||
for vin in prev.vin:
|
||||
i = proto.TxInputType()
|
||||
i.prev_hash = ser_uint256(vin.prevout.hash)[::-1]
|
||||
i.prev_index = vin.prevout.n
|
||||
i.script_sig = vin.scriptSig
|
||||
i.sequence = vin.nSequence
|
||||
t.inputs.append(i)
|
||||
|
||||
for vout in prev.vout:
|
||||
o = proto.TxOutputBinType()
|
||||
o.amount = vout.nValue
|
||||
o.script_pubkey = vout.scriptPubKey
|
||||
t.bin_outputs.append(o)
|
||||
logging.debug(psbt_in.non_witness_utxo.hash)
|
||||
prevtxs[ser_uint256(psbt_in.non_witness_utxo.sha256)[::-1]] = t
|
||||
for vout in prev.vout:
|
||||
o = proto.TxOutputBinType()
|
||||
o.amount = vout.nValue
|
||||
o.script_pubkey = vout.scriptPubKey
|
||||
t.bin_outputs.append(o)
|
||||
logging.debug(psbt_in.non_witness_utxo.hash)
|
||||
prevtxs[ser_uint256(psbt_in.non_witness_utxo.sha256)[::-1]] = t
|
||||
|
||||
# Sign the transaction
|
||||
tx_details = proto.SignTx()
|
||||
tx_details.version = tx.tx.nVersion
|
||||
tx_details.lock_time = tx.tx.nLockTime
|
||||
if self.is_testnet:
|
||||
signed_tx = btc.sign_tx(self.client, "Testnet", inputs, outputs, tx_details, prevtxs)
|
||||
else:
|
||||
signed_tx = btc.sign_tx(self.client, "Bitcoin", inputs, outputs, tx_details, prevtxs)
|
||||
# Sign the transaction
|
||||
tx_details = proto.SignTx()
|
||||
tx_details.version = tx.tx.nVersion
|
||||
tx_details.lock_time = tx.tx.nLockTime
|
||||
if self.is_testnet:
|
||||
signed_tx = btc.sign_tx(self.client, "Testnet", inputs, outputs, tx_details, prevtxs)
|
||||
else:
|
||||
signed_tx = btc.sign_tx(self.client, "Bitcoin", inputs, outputs, tx_details, prevtxs)
|
||||
|
||||
# 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]
|
||||
if fp == master_fp and key not in psbt_in.partial_sigs:
|
||||
psbt_in.partial_sigs[pubkey] = sig + b'\x01'
|
||||
break
|
||||
# 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]
|
||||
if fp == master_fp and pubkey not in psbt_in.partial_sigs:
|
||||
psbt_in.partial_sigs[pubkey] = sig + b'\x01'
|
||||
break
|
||||
|
||||
p += 1
|
||||
|
||||
return {'psbt':tx.serialize()}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user