From 8e918b5f3528332a2c587d3a3b32dc061e80e28b Mon Sep 17 00:00:00 2001 From: BTChip Date: Sun, 24 Aug 2014 19:44:26 +0200 Subject: [PATCH 1/7] Add BTChip wallet plugin --- plugins/btchipwallet.py | 450 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 plugins/btchipwallet.py diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py new file mode 100644 index 00000000..a51a300a --- /dev/null +++ b/plugins/btchipwallet.py @@ -0,0 +1,450 @@ +from PyQt4.Qt import QApplication, QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL +import PyQt4.QtCore as QtCore +from binascii import unhexlify +from binascii import hexlify +from struct import pack,unpack +from sys import stderr +from time import sleep +from base64 import b64encode, b64decode + +from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog +from electrum_gui.qt.util import ok_cancel_buttons +from electrum.account import BIP32_Account +from electrum.bitcoin import EC_KEY, EncodeBase58Check, DecodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160 +from electrum.i18n import _ +from electrum.plugins import BasePlugin +from electrum.transaction import deserialize +from electrum.wallet import NewWallet + +from lib.util import format_satoshis +import hashlib + +try: + from usb.core import USBError + from btchip.btchipComm import getDongle, DongleWait + from btchip.btchip import btchip + from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script + from btchip.bitcoinTransaction import bitcoinTransaction + BTCHIP = True +except ImportError: + BTCHIP = False + +def log(msg): + stderr.write("%s\n" % msg) + stderr.flush() + +def give_error(message): + QMessageBox.warning(QDialog(), _('Warning'), _(message), _('OK')) + raise Exception(message) + +class Plugin(BasePlugin): + + def fullname(self): return 'BTChip Wallet' + + def description(self): return 'Provides support for BTChip hardware wallet\n\nRequires github.com/btchip/btchip-python' + + def __init__(self, gui, name): + BasePlugin.__init__(self, gui, name) + self._is_available = self._init() + self.wallet = None + + def _init(self): + return BTCHIP + + def is_available(self): + #if self.wallet is None: + # return self._is_available + #if self.wallet.storage.get('wallet_type') == 'btchip': + # return True + #return False + return self._is_available + + def set_enabled(self, enabled): + self.wallet.storage.put('use_' + self.name, enabled) + + def is_enabled(self): + if not self.is_available(): + return False + + if not self.wallet or self.wallet.storage.get('wallet_type') == 'btchip': + return True + + return self.wallet.storage.get('use_' + self.name) is True + + def enable(self): + return BasePlugin.enable(self) + + def load_wallet(self, wallet): + self.wallet = wallet + + def add_wallet_types(self, wallet_types): + wallet_types.append(('btchip', _("BTChip wallet"), BTChipWallet)) + + def installwizard_restore(self, wizard, storage): + wallet = BTChipWallet(storage) + try: + wallet.create_main_account(None) + except BaseException as e: + QMessageBox.information(None, _('Error'), str(e), _('OK')) + return + return wallet + + def send_tx(self, tx): + try: + self.wallet.sign_transaction(tx, None, None) + except Exception as e: + tx.error = str(e) + + +class BTChipWallet(NewWallet): + wallet_type = 'btchip' + + def __init__(self, storage): + NewWallet.__init__(self, storage) + self.transport = None + self.client = None + self.mpk = None + self.device_checked = False + + def get_action(self): + if not self.accounts: + return 'create_accounts' + + def can_create_accounts(self): + return True + + def can_change_password(self): + return False + + def has_seed(self): + return False + + def is_watching_only(self): + return False + + def get_client(self, noPin=False): + if not BTCHIP: + give_error('please install github.com/btchip/btchip-python') + + aborted = False + if not self.client or self.client.bad: + try: + d = getDongle(True) + d.setWaitImpl(DongleWaitQT(d)) + self.client = btchip(d) + firmware = self.client.getFirmwareVersion()['version'].split(".") + if int(firmware[0]) <> 1 or int(firmware[1]) <> 4: + aborted = True + give_error("Unsupported firmware version") + if int(firmware[2]) < 8: + aborted = True + give_error("Please update your firmware - 1.4.8 or higher is necessary") + if not noPin: + # Immediately prompts for the PIN + confirmed, p, pin = self.password_dialog("Enter your BTChip PIN") + if not confirmed: + aborted = True + give_error('Aborted by user') + pin = pin.encode() + self.client.verifyPin(pin) + except Exception, e: + if not aborted: + give_error("Could not connect to your BTChip dongle. Please verify access permissions or PIN") + else: + raise e + self.client.bad = False + self.device_checked = False + self.proper_device = False + return self.client + + def address_id(self, address): + account_id, (change, address_index) = self.get_address_index(address) + # FIXME review + return "44'/0'/%s'/%d/%d" % (account_id, change, address_index) + + def create_main_account(self, password): + self.create_account('Main account', None) #name, empty password + + def derive_xkeys(self, root, derivation, password): + # FIXME review + derivation = derivation.replace(self.root_name,"44'/0'/") + xpub = self.get_public_key(derivation) + return xpub, None + + def get_public_key(self, bip32_path): + # S-L-O-W - we don't handle the fingerprint directly, so compute it manually from the previous node + # This only happens once so it's bearable + self.get_client() # prompt for the PIN before displaying the dialog if necessary + waitDialog.start("Computing master public key") + try: + splitPath = bip32_path.split('/') + fingerprint = 0 + if len(splitPath) > 1: + prevPath = "/".join(splitPath[0:len(splitPath) - 1]) + nodeData = self.get_client().getWalletPublicKey(prevPath) + publicKey = compress_public_key(nodeData['publicKey']) + h = hashlib.new('ripemd160') + h.update(hashlib.sha256(publicKey).digest()) + fingerprint = unpack(">I", h.digest()[0:4])[0] + nodeData = self.get_client().getWalletPublicKey(bip32_path) + publicKey = compress_public_key(nodeData['publicKey']) + depth = len(splitPath) + lastChild = splitPath[len(splitPath) - 1].split('\'') + if len(lastChild) == 1: + childnum = int(lastChild[0]) + else: + childnum = 0x80000000 | int(lastChild[0]) + xpub = "0488B21E".decode('hex') + chr(depth) + self.i4b(fingerprint) + self.i4b(childnum) + str(nodeData['chainCode']) + str(publicKey) + except Exception, e: + give_error(e) + finally: + waitDialog.emit(SIGNAL('dongle_done')) + + return EncodeBase58Check(xpub) + + def get_master_public_key(self): + if not self.mpk: + self.mpk = self.get_public_key("44'/0'") + return self.mpk + + def i4b(self, x): + return pack('>I', x) + + def add_keypairs(self, tx, keypairs, password): + #do nothing - no priv keys available + pass + + def decrypt_message(self, pubkey, message, password): + give_error("Not supported") + + def sign_message(self, address, message, password): + address_path = self.address_id(address) + waitDialog.start("Signing Message ...") + aborted = False + try: + info = self.get_client().signMessagePrepare(address_path, message) + pin = "" + if info['confirmationNeeded']: + # TODO : handle different confirmation types. For the time being only supports keyboard 2FA + confirmed, p, pin = self.password_dialog() + if not confirmed: + aborted = True + give_error('Aborted by user') + pin = pin.encode() + self.client.bad = True + self.get_client(True) + signature = self.get_client().signMessageSign(pin) + except Exception, e: + if not aborted: + give_error(e) + else: + raise e + finally: + if waitDialog.waiting: + waitDialog.emit(SIGNAL('dongle_done')) + + # Parse the ASN.1 signature + + rLength = signature[3] + r = signature[4 : 4 + rLength] + sLength = signature[4 + rLength + 1] + s = signature[4 + rLength + 2:] + if rLength == 33: + r = r[1:] + if sLength == 33: + s = s[1:] + r = str(r) + s = str(s) + + # And convert it + + for i in range(4): + sig = b64encode( chr(27 + i + 4) + r + s) + try: + EC_KEY.verify_message(address, sig, message) + return sig + except Exception: + continue + else: + raise Exception("error: cannot sign message") + + def choose_tx_inputs( self, amount, fixed_fee, num_outputs, domain = None, coins = None ): + # Overloaded to get the fee, as BTChip recomputes the change amount + inputs, total, fee = super(BTChipWallet, self).choose_tx_inputs(amount, fixed_fee, num_outputs, domain, coins) + self.lastFee = fee + return inputs, total, fee + + def sign_transaction(self, tx, keypairs, password): + if tx.error or tx.is_complete(): + return + inputs = [] + inputsPaths = [] + pubKeys = [] + trustedInputs = [] + redeemScripts = [] + signatures = [] + preparedTrustedInputs = [] + changePath = None + changeAmount = None + output = None + outputAmount = None + use2FA = False + aborted = False + # Fetch inputs of the transaction to sign + for txinput in tx.inputs: + if ('is_coinbase' in txinput and txinput['is_coinbase']): + give_error("Coinbase not supported") # should never happen + inputs.append([ self.transactions[txinput['prevout_hash']].raw, + txinput['prevout_n'] ]) + address = txinput['address'] + inputsPaths.append(self.address_id(address)) + pubKeys.append(self.get_public_keys(address)) + + # Recognize outputs - only one output and one change is authorized + if len(tx.outputs) > 2: # should never happen + give_error("Transaction with more than 2 outputs not supported") + for type, address, amount in tx.outputs: + assert type == 'address' + if self.is_change(address): + changePath = self.address_id(address) + changeAmount = amount + else: + if output <> None: # should never happen + give_error("Multiple outputs with no change not supported") + output = address + outputAmount = amount + + self.get_client() # prompt for the PIN before displaying the dialog if necessary + # S-L-O-W. Make it better + if not self.check_proper_device(): + give_error('Wrong device or password') + + waitDialog.start("Signing Transaction ...") + try: + # Get trusted inputs from the original transactions + for utxo in inputs: + txtmp = bitcoinTransaction(bytearray(utxo[0].decode('hex'))) + trustedInputs.append(self.get_client().getTrustedInput(txtmp, utxo[1])) + # TODO : Support P2SH later + redeemScripts.append(txtmp.outputs[utxo[1]].script) + # Sign all inputs + firstTransaction = True + inputIndex = 0 + while inputIndex < len(inputs): + self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, + trustedInputs, redeemScripts[inputIndex]) + outputData = self.get_client().finalizeInput(output, format_satoshis(outputAmount), + format_satoshis(self.lastFee), changePath) + if firstTransaction: + transactionOutput = outputData['outputData'] + if outputData['confirmationNeeded']: + use2FA = True + # TODO : handle different confirmation types. For the time being only supports keyboard 2FA + waitDialog.emit(SIGNAL('dongle_done')) + confirmed, p, pin = self.password_dialog() + if not confirmed: + aborted = True + give_error('Aborted by user') + pin = pin.encode() + self.client.bad = True + self.get_client(True) + waitDialog.start("Signing ...") + else: + # Sign input with the provided PIN + signatures.append(self.get_client().untrustedHashSign(inputsPaths[inputIndex], + pin)) + inputIndex = inputIndex + 1 + firstTransaction = False + except Exception, e: + if not aborted: + give_error(e) + else: + raise e + finally: + if waitDialog.waiting: + waitDialog.emit(SIGNAL('dongle_done')) + + # Reformat transaction + inputIndex = 0 + while inputIndex < len(inputs): + # TODO : Support P2SH later + inputScript = get_regular_input_script(signatures[inputIndex], pubKeys[inputIndex][0].decode('hex')) + preparedTrustedInputs.append([ trustedInputs[inputIndex]['value'], inputScript ]) + inputIndex = inputIndex + 1 + updatedTransaction = format_transaction(transactionOutput, preparedTrustedInputs) + updatedTransaction = hexlify(updatedTransaction) + tx.update(updatedTransaction) + self.client.bad = use2FA + + def check_proper_device(self): + pubKey = DecodeBase58Check(self.master_public_keys["x/0'"])[45:] + if not self.device_checked: + waitDialog.start("Checking device") + try: + nodeData = self.get_client().getWalletPublicKey("44'/0'/0'") + except Exception, e: + give_error(e) + finally: + waitDialog.emit(SIGNAL('dongle_done')) + pubKeyDevice = compress_public_key(nodeData['publicKey']) + self.device_checked = True + if pubKey != pubKeyDevice: + self.proper_device = False + else: + self.proper_device = True + + return self.proper_device + + def password_dialog(self, msg=None): + if not msg: + msg = _("Disconnect your BTChip, read the unique second factor PIN, reconnect it and enter the unique second factor PIN") + + d = QDialog() + d.setModal(1) + d.setLayout( make_password_dialog(d, None, msg, False) ) + return run_password_dialog(d, None, None) + +class DongleWaitingDialog(QThread): + def __init__(self): + QThread.__init__(self) + self.waiting = False + + def start(self, message): + self.d = QDialog() + self.d.setModal(1) + self.d.setWindowTitle('Please Wait') + self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) + l = QLabel(message) + vbox = QVBoxLayout(self.d) + vbox.addWidget(l) + self.d.show() + if not self.waiting: + self.waiting = True + self.d.connect(waitDialog, SIGNAL('dongle_done'), self.stop) + + def stop(self): + self.d.hide() + self.waiting = False + +if BTCHIP: + waitDialog = DongleWaitingDialog() + +# Tickle the UI a bit while waiting +class DongleWaitQT(DongleWait): + def __init__(self, dongle): + self.dongle = dongle + + def waitFirstResponse(self, timeout): + customTimeout = 0 + while customTimeout < timeout: + try: + response = self.dongle.waitFirstResponse(200) + return response + except USBError, e: + if e.backend_error_code == -7: + QApplication.processEvents() + customTimeout = customTimeout + 100 + pass + else: + raise e + raise e From 8a47c8082768f9efa1026383deab8a8f0acb2404 Mon Sep 17 00:00:00 2001 From: BTChip Date: Sun, 24 Aug 2014 20:19:23 +0200 Subject: [PATCH 2/7] Already optimized --- plugins/btchipwallet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py index a51a300a..44defd3e 100644 --- a/plugins/btchipwallet.py +++ b/plugins/btchipwallet.py @@ -315,7 +315,6 @@ class BTChipWallet(NewWallet): outputAmount = amount self.get_client() # prompt for the PIN before displaying the dialog if necessary - # S-L-O-W. Make it better if not self.check_proper_device(): give_error('Wrong device or password') From 32937310380294aa6bec3e199f6208cfe82bafe1 Mon Sep 17 00:00:00 2001 From: BTChip Date: Mon, 25 Aug 2014 00:18:38 +0200 Subject: [PATCH 3/7] Do not crash if no change is sent --- plugins/btchipwallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py index 44defd3e..c9ac18d0 100644 --- a/plugins/btchipwallet.py +++ b/plugins/btchipwallet.py @@ -284,7 +284,7 @@ class BTChipWallet(NewWallet): redeemScripts = [] signatures = [] preparedTrustedInputs = [] - changePath = None + changePath = "" changeAmount = None output = None outputAmount = None From 7354f01c8e55c21ffb8c759996160cd4111a7d16 Mon Sep 17 00:00:00 2001 From: BTChip Date: Mon, 25 Aug 2014 17:25:02 +0200 Subject: [PATCH 4/7] lib.util->electrum.util --- plugins/btchipwallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py index c9ac18d0..c4c08d60 100644 --- a/plugins/btchipwallet.py +++ b/plugins/btchipwallet.py @@ -16,7 +16,7 @@ from electrum.plugins import BasePlugin from electrum.transaction import deserialize from electrum.wallet import NewWallet -from lib.util import format_satoshis +from electrum.util import format_satoshis import hashlib try: From 5873004af445a80c942fcb2002883339e834213a Mon Sep 17 00:00:00 2001 From: BTChip Date: Tue, 26 Aug 2014 13:04:38 +0200 Subject: [PATCH 5/7] Verify proper device before signing --- plugins/btchipwallet.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py index c4c08d60..e2a7c204 100644 --- a/plugins/btchipwallet.py +++ b/plugins/btchipwallet.py @@ -218,6 +218,9 @@ class BTChipWallet(NewWallet): give_error("Not supported") def sign_message(self, address, message, password): + self.get_client() # prompt for the PIN before displaying the dialog if necessary + if not self.check_proper_device(): + give_error('Wrong device or password') address_path = self.address_id(address) waitDialog.start("Signing Message ...") aborted = False From af7cc78075054ebc88ef741285b43c97b7cb9a6a Mon Sep 17 00:00:00 2001 From: BTChip Date: Wed, 27 Aug 2014 23:19:14 +0200 Subject: [PATCH 6/7] Add creation wizard, prepare for 1.4.9 --- plugins/btchipwallet.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py index e2a7c204..56c1a51e 100644 --- a/plugins/btchipwallet.py +++ b/plugins/btchipwallet.py @@ -25,6 +25,8 @@ try: from btchip.btchip import btchip from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script from btchip.bitcoinTransaction import bitcoinTransaction + from btchip.btchipPersoWizard import StartBTChipPersoDialog + from btchip.btchipException import BTChipException BTCHIP = True except ImportError: BTCHIP = False @@ -139,6 +141,19 @@ class BTChipWallet(NewWallet): if int(firmware[2]) < 8: aborted = True give_error("Please update your firmware - 1.4.8 or higher is necessary") + try: + self.client.getOperationMode() + except BTChipException,e: + if (e.sw == 0x6985): + d.close() + dialog = StartBTChipPersoDialog() + dialog.exec_() + # Then fetch the reference again as it was invalidated + d = getDongle(True) + d.setWaitImpl(DongleWaitQT(d)) + self.client = btchip(d) + else: + raise e if not noPin: # Immediately prompts for the PIN confirmed, p, pin = self.password_dialog("Enter your BTChip PIN") @@ -148,6 +163,7 @@ class BTChipWallet(NewWallet): pin = pin.encode() self.client.verifyPin(pin) except Exception, e: + self.client = None if not aborted: give_error("Could not connect to your BTChip dongle. Please verify access permissions or PIN") else: @@ -159,14 +175,12 @@ class BTChipWallet(NewWallet): def address_id(self, address): account_id, (change, address_index) = self.get_address_index(address) - # FIXME review return "44'/0'/%s'/%d/%d" % (account_id, change, address_index) def create_main_account(self, password): self.create_account('Main account', None) #name, empty password def derive_xkeys(self, root, derivation, password): - # FIXME review derivation = derivation.replace(self.root_name,"44'/0'/") xpub = self.get_public_key(derivation) return xpub, None @@ -245,6 +259,7 @@ class BTChipWallet(NewWallet): finally: if waitDialog.waiting: waitDialog.emit(SIGNAL('dongle_done')) + self.client.bad = True # Parse the ASN.1 signature @@ -261,6 +276,9 @@ class BTChipWallet(NewWallet): # And convert it + #Optimization for 1.4.9+ + #return b64encode(chr(27 + 4 + (signature[0] & 0x01)) + r + s) + for i in range(4): sig = b64encode( chr(27 + i + 4) + r + s) try: @@ -268,8 +286,10 @@ class BTChipWallet(NewWallet): return sig except Exception: continue - else: - raise Exception("error: cannot sign message") + else: + raise Exception("error: cannot sign message") + return b64encode(chr(27 + 4 + (signature[0] & 0x01)) + r + s) + def choose_tx_inputs( self, amount, fixed_fee, num_outputs, domain = None, coins = None ): # Overloaded to get the fee, as BTChip recomputes the change amount @@ -293,6 +313,7 @@ class BTChipWallet(NewWallet): outputAmount = None use2FA = False aborted = False + pin = "" # Fetch inputs of the transaction to sign for txinput in tx.inputs: if ('is_coinbase' in txinput and txinput['is_coinbase']): @@ -353,8 +374,10 @@ class BTChipWallet(NewWallet): waitDialog.start("Signing ...") else: # Sign input with the provided PIN - signatures.append(self.get_client().untrustedHashSign(inputsPaths[inputIndex], - pin)) + inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], + pin) + inputSignature[0] = 0x30 # force for 1.4.9+ + signatures.append(inputSignature) inputIndex = inputIndex + 1 firstTransaction = False except Exception, e: From 0507017c851fe969ecad5fece38302defe8a5a7c Mon Sep 17 00:00:00 2001 From: BTChip Date: Fri, 29 Aug 2014 00:38:16 +0200 Subject: [PATCH 7/7] 1.4.9 support, better error management, fix 2fa check --- plugins/btchipwallet.py | 80 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py index 56c1a51e..5b23866c 100644 --- a/plugins/btchipwallet.py +++ b/plugins/btchipwallet.py @@ -10,7 +10,7 @@ from base64 import b64encode, b64decode from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog from electrum_gui.qt.util import ok_cancel_buttons from electrum.account import BIP32_Account -from electrum.bitcoin import EC_KEY, EncodeBase58Check, DecodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160 +from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160 from electrum.i18n import _ from electrum.plugins import BasePlugin from electrum.transaction import deserialize @@ -137,13 +137,13 @@ class BTChipWallet(NewWallet): firmware = self.client.getFirmwareVersion()['version'].split(".") if int(firmware[0]) <> 1 or int(firmware[1]) <> 4: aborted = True - give_error("Unsupported firmware version") - if int(firmware[2]) < 8: + raise Exception("Unsupported firmware version") + if int(firmware[2]) < 9: aborted = True - give_error("Please update your firmware - 1.4.8 or higher is necessary") + raise Exception("Please update your firmware - 1.4.9 or higher is necessary") try: self.client.getOperationMode() - except BTChipException,e: + except BTChipException, e: if (e.sw == 0x6985): d.close() dialog = StartBTChipPersoDialog() @@ -154,18 +154,39 @@ class BTChipWallet(NewWallet): self.client = btchip(d) else: raise e - if not noPin: + if not noPin: # Immediately prompts for the PIN - confirmed, p, pin = self.password_dialog("Enter your BTChip PIN") + remaining_attempts = self.client.getVerifyPinRemainingAttempts() + if remaining_attempts <> 1: + msg = "Enter your BTChip PIN - remaining attempts : " + str(remaining_attempts) + else: + msg = "Enter your BTChip PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped." + confirmed, p, pin = self.password_dialog(msg) if not confirmed: aborted = True - give_error('Aborted by user') - pin = pin.encode() + raise Exception('Aborted by user - please unplug the dongle and plug it again before retrying') + pin = pin.encode() self.client.verifyPin(pin) + + except BTChipException, e: + try: + self.client.dongle.close() + except: + pass + self.client = None + if (e.sw == 0x6faa): + raise Exception("Dongle is temporarily locked - please unplug it and replug it again") + if ((e.sw & 0xFFF0) == 0x63c0): + raise Exception("Invalid PIN - please unplug the dongle and plug it again before retrying") + raise e except Exception, e: - self.client = None + try: + self.client.dongle.close() + except: + pass + self.client = None if not aborted: - give_error("Could not connect to your BTChip dongle. Please verify access permissions or PIN") + raise Exception("Could not connect to your BTChip dongle. Please verify access permissions or PIN") else: raise e self.client.bad = False @@ -232,34 +253,31 @@ class BTChipWallet(NewWallet): give_error("Not supported") def sign_message(self, address, message, password): + use2FA = False self.get_client() # prompt for the PIN before displaying the dialog if necessary if not self.check_proper_device(): give_error('Wrong device or password') address_path = self.address_id(address) waitDialog.start("Signing Message ...") - aborted = False try: info = self.get_client().signMessagePrepare(address_path, message) pin = "" if info['confirmationNeeded']: # TODO : handle different confirmation types. For the time being only supports keyboard 2FA + use2FA = True confirmed, p, pin = self.password_dialog() if not confirmed: - aborted = True - give_error('Aborted by user') + raise Exception('Aborted by user') pin = pin.encode() self.client.bad = True self.get_client(True) signature = self.get_client().signMessageSign(pin) except Exception, e: - if not aborted: - give_error(e) - else: - raise e + give_error(e) finally: if waitDialog.waiting: waitDialog.emit(SIGNAL('dongle_done')) - self.client.bad = True + self.client.bad = use2FA # Parse the ASN.1 signature @@ -276,20 +294,7 @@ class BTChipWallet(NewWallet): # And convert it - #Optimization for 1.4.9+ - #return b64encode(chr(27 + 4 + (signature[0] & 0x01)) + r + s) - - for i in range(4): - sig = b64encode( chr(27 + i + 4) + r + s) - try: - EC_KEY.verify_message(address, sig, message) - return sig - except Exception: - continue - else: - raise Exception("error: cannot sign message") - return b64encode(chr(27 + 4 + (signature[0] & 0x01)) + r + s) - + return b64encode(chr(27 + 4 + (signature[0] & 0x01)) + r + s) def choose_tx_inputs( self, amount, fixed_fee, num_outputs, domain = None, coins = None ): # Overloaded to get the fee, as BTChip recomputes the change amount @@ -312,7 +317,6 @@ class BTChipWallet(NewWallet): output = None outputAmount = None use2FA = False - aborted = False pin = "" # Fetch inputs of the transaction to sign for txinput in tx.inputs: @@ -366,8 +370,7 @@ class BTChipWallet(NewWallet): waitDialog.emit(SIGNAL('dongle_done')) confirmed, p, pin = self.password_dialog() if not confirmed: - aborted = True - give_error('Aborted by user') + raise Exception('Aborted by user') pin = pin.encode() self.client.bad = True self.get_client(True) @@ -381,10 +384,7 @@ class BTChipWallet(NewWallet): inputIndex = inputIndex + 1 firstTransaction = False except Exception, e: - if not aborted: - give_error(e) - else: - raise e + give_error(e) finally: if waitDialog.waiting: waitDialog.emit(SIGNAL('dongle_done'))