ledger plugin: parse xpubkey instead of using txin['derivation']; always use client.finalizeInputFull

This commit is contained in:
ThomasV 2016-09-22 10:25:03 +02:00
parent 03c66bb5f9
commit 5f038a4157

View file

@ -2,12 +2,14 @@ from binascii import hexlify
from struct import pack, unpack from struct import pack, unpack
import hashlib import hashlib
import time import time
import sys
import traceback
import electrum import electrum
from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, TYPE_ADDRESS, int_to_hex, var_int from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, TYPE_ADDRESS, int_to_hex, var_int
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugins import BasePlugin, hook from electrum.plugins import BasePlugin, hook
from electrum.keystore import Hardware_KeyStore from electrum.keystore import Hardware_KeyStore, parse_xpubkey
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from electrum.util import format_satoshis_plain, print_error from electrum.util import format_satoshis_plain, print_error
@ -257,43 +259,45 @@ class Ledger_KeyStore(Hardware_KeyStore):
self.get_client() # prompt for the PIN before displaying the dialog if necessary self.get_client() # prompt for the PIN before displaying the dialog if necessary
rawTx = tx.serialize() rawTx = tx.serialize()
# Fetch inputs of the transaction to sign # Fetch inputs of the transaction to sign
for txinput in tx.inputs(): for txin in tx.inputs():
if ('is_coinbase' in txinput and txinput['is_coinbase']): if txin.get('is_coinbase'):
self.give_error("Coinbase not supported") # should never happen self.give_error("Coinbase not supported") # should never happen
redeemScript = None redeemScript = None
signingPos = -1 signingPos = -1
hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], txinput['derivation'][0], txinput['derivation'][1]) xpub, s = parse_xpubkey(txin['x_pubkeys'][0])
if len(txinput['pubkeys']) > 1: hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1])
if len(txin['pubkeys']) > 1:
p2shTransaction = True p2shTransaction = True
if 'redeemScript' in txinput: if 'redeemScript' in txin:
redeemScript = txinput['redeemScript'] redeemScript = txin['redeemScript']
if p2shTransaction: if p2shTransaction:
chipPublicKey = compress_public_key(self.get_client().getWalletPublicKey(hwAddress)['publicKey']) chipPublicKey = compress_public_key(self.get_client().getWalletPublicKey(hwAddress)['publicKey'])
for currentIndex, key in enumerate(txinput['pubkeys']): for currentIndex, key in enumerate(txin['pubkeys']):
if chipPublicKey == key.decode('hex'): if chipPublicKey == key.decode('hex'):
signingPos = currentIndex signingPos = currentIndex
break break
if signingPos == -1: if signingPos == -1:
self.give_error("No matching key for multisignature input") # should never happen self.give_error("No matching key for multisignature input") # should never happen
inputs.append([ txinput['prev_tx'].raw, inputs.append([txin['prev_tx'].raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos ])
txinput['prevout_n'], redeemScript, txinput['prevout_hash'], signingPos ])
inputsPaths.append(hwAddress) inputsPaths.append(hwAddress)
pubKeys.append(txinput['pubkeys']) pubKeys.append(txin['pubkeys'])
# Sanity check # Sanity check
if p2shTransaction: if p2shTransaction:
for txinput in tx.inputs(): for txinput in tx.inputs():
if len(txinput['pubkeys']) < 2: if len(txinput['pubkeys']) < 2:
self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen
txOutput = var_int(len(tx.outputs()))
for output in tx.outputs(): txOutput = var_int(len(tx.outputs()))
output_type, addr, amount = output for txout in tx.outputs():
txOutput += int_to_hex(amount, 8) output_type, addr, amount = txout
script = tx.pay_script(output_type, addr) txOutput += int_to_hex(amount, 8)
txOutput += var_int(len(script)/2) script = tx.pay_script(output_type, addr)
txOutput += script txOutput += var_int(len(script)/2)
txOutput = txOutput.decode('hex') txOutput += script
txOutput = txOutput.decode('hex')
# Recognize outputs - only one output and one change is authorized # Recognize outputs - only one output and one change is authorized
if not p2shTransaction: if not p2shTransaction:
@ -306,8 +310,6 @@ class Ledger_KeyStore(Hardware_KeyStore):
changePath = "%s/%d/%d" % (self.get_derivation()[2:], change, index) changePath = "%s/%d/%d" % (self.get_derivation()[2:], change, index)
changeAmount = amount changeAmount = amount
else: else:
if output <> None: # should never happen
self.give_error("Multiple outputs with no change not supported")
output = address output = address
outputAmount = amount outputAmount = amount
@ -330,13 +332,9 @@ class Ledger_KeyStore(Hardware_KeyStore):
inputIndex = 0 inputIndex = 0
while inputIndex < len(inputs): while inputIndex < len(inputs):
self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,
chipInputs, redeemScripts[inputIndex]) chipInputs, redeemScripts[inputIndex])
if not p2shTransaction: outputData = self.get_client().finalizeInputFull(txOutput)
outputData = self.get_client().finalizeInput(output, format_satoshis_plain(outputAmount), outputData['outputData'] = txOutput
format_satoshis_plain(tx.get_fee()), changePath, bytearray(rawTx.decode('hex')))
else:
outputData = self.get_client().finalizeInputFull(txOutput)
outputData['outputData'] = txOutput
if firstTransaction: if firstTransaction:
transactionOutput = outputData['outputData'] transactionOutput = outputData['outputData']
if outputData['confirmationNeeded']: if outputData['confirmationNeeded']:
@ -378,7 +376,8 @@ class Ledger_KeyStore(Hardware_KeyStore):
signatures.append(inputSignature) signatures.append(inputSignature)
inputIndex = inputIndex + 1 inputIndex = inputIndex + 1
firstTransaction = False firstTransaction = False
except Exception, e: except BaseException as e:
traceback.print_exc(file=sys.stdout)
self.give_error(e, True) self.give_error(e, True)
finally: finally:
self.handler.clear_dialog() self.handler.clear_dialog()