URI: 
       tMerge pull request #1909 from btchip/ledger-nanos - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 5be93bdb738dfb12e8fcb02572592b55814d8f88
   DIR parent 357ea62303633e6514378c4ebe231fab911a614b
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sun, 28 Aug 2016 17:56:57 +0200
       
       Merge pull request #1909 from btchip/ledger-nanos
       
       Ledger pack : Nano S support, rewrite/cleanup, P2SH
       Diffstat:
         M icons.qrc                           |       2 ++
         A icons/ledger.png                    |       0 
         A icons/ledger_unpaired.png           |       0 
         M lib/plugins.py                      |       5 ++++-
         M plugins/ledger/ledger.py            |     487 ++++++++++++++++++-------------
         M plugins/ledger/qt.py                |      35 +++++++++++++++++++++----------
       
       6 files changed, 322 insertions(+), 207 deletions(-)
       ---
   DIR diff --git a/icons.qrc b/icons.qrc
       t@@ -17,6 +17,8 @@
            <file>icons/keepkey.png</file>
            <file>icons/keepkey_unpaired.png</file>
            <file>icons/key.png</file>
       +    <file>icons/ledger.png</file>
       +    <file>icons/ledger_unpaired.png</file>
            <file>icons/lock.png</file>
            <file>icons/microphone.png</file>
            <file>icons/network.png</file>
   DIR diff --git a/icons/ledger.png b/icons/ledger.png
       Binary files differ.
   DIR diff --git a/icons/ledger_unpaired.png b/icons/ledger_unpaired.png
       Binary files differ.
   DIR diff --git a/lib/plugins.py b/lib/plugins.py
       t@@ -500,8 +500,11 @@ class DeviceMgr(ThreadJob, PrintError):
                    if product_key in self.recognised_hardware:
                        # Older versions of hid don't provide interface_number
                        interface_number = d.get('interface_number', 0)
       +                serial = d['serial_number']
       +                if len(serial) == 0:
       +                    serial = d['path']
                        devices.append(Device(d['path'], interface_number,
       -                                      d['serial_number'], product_key))
       +                                      serial, product_key))
        
                # Now find out what was disconnected
                pairs = [(dev.path, dev.id_) for dev in devices]
   DIR diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py
       t@@ -1,10 +1,10 @@
        from binascii import hexlify
       -from struct import unpack
       +from struct import pack, unpack
        import hashlib
        import time
        
        import electrum
       -from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, TYPE_ADDRESS
       +from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, TYPE_ADDRESS, int_to_hex, var_int
        from electrum.i18n import _
        from electrum.plugins import BasePlugin, hook
        from electrum.keystore import Hardware_KeyStore
       t@@ -13,9 +13,10 @@ from electrum.util import format_satoshis_plain, print_error
        
        
        try:
       -    from btchip.btchipComm import getDongle, DongleWait
       +    import hid
       +    from btchip.btchipComm import HIDDongleHIDAPI, DongleWait
            from btchip.btchip import btchip
       -    from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script
       +    from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script, get_p2sh_input_script
            from btchip.bitcoinTransaction import bitcoinTransaction
            from btchip.btchipPersoWizard import StartBTChipPersoDialog
            from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware
       t@@ -25,8 +26,136 @@ try:
        except ImportError:
            BTCHIP = False
        
       +class Ledger_Client():
       +    def __init__(self, hidDevice):
       +        self.dongleObject = btchip(hidDevice)
       +        self.preflightDone = False
       +
       +    def is_pairable(self):
       +        return True
       +
       +    def close(self):
       +        self.dongleObject.dongle.close()
       +
       +    def timeout(self, cutoff):
       +        pass
       +
       +    def is_initialized(self):
       +        return True
       +
       +    def label(self):
       +        return ""
       +
       +    def i4b(self, x):
       +        return pack('>I', x)        
       +
       +    def get_xpub(self, bip32_path):
       +        self.checkDevice()
       +        # bip32_path is of the form 44'/0'/1'
       +        # 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
       +        #self.handler.show_message("Computing master public key")
       +        try:
       +            splitPath = bip32_path.split('/')
       +            if splitPath[0] == 'm':
       +                splitPath = splitPath[1:]
       +                bip32_path = bip32_path[2:]
       +            fingerprint = 0
       +            if len(splitPath) > 1:
       +                prevPath = "/".join(splitPath[0:len(splitPath) - 1])
       +                nodeData = self.dongleObject.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.dongleObject.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:
       +            #self.give_error(e, True)
       +            return None
       +        finally:
       +            #self.handler.clear_dialog()
       +            pass
       +
       +        return EncodeBase58Check(xpub)
       +
       +    def has_detached_pin_support(self, client):
       +        try:
       +            client.getVerifyPinRemainingAttempts()
       +            return True
       +        except BTChipException, e:
       +            if e.sw == 0x6d00:
       +                return False
       +            raise e
       +
       +    def is_pin_validated(self, client):
       +        try:
       +            # Invalid SET OPERATION MODE to verify the PIN status
       +            client.dongle.exchange(bytearray([0xe0, 0x26, 0x00, 0x00, 0x01, 0xAB]))
       +        except BTChipException, e:
       +            if (e.sw == 0x6982):
       +                return False
       +            if (e.sw == 0x6A80):
       +                return True
       +            raise e
       +
       +    def perform_hw1_preflight(self):
       +        try:
       +            firmware = self.dongleObject.getFirmwareVersion()['version'].split(".")
       +            if not checkFirmware(firmware):
       +                self.dongleObject.close()
       +                raise Exception("HW1 firmware version too old. Please update at https://www.ledgerwallet.com")
       +            try:
       +                self.dongleObject.getOperationMode()
       +            except BTChipException, e:
       +                if (e.sw == 0x6985):
       +                    self.dongleObject.close()
       +                    dialog = StartBTChipPersoDialog()
       +                    dialog.exec_()
       +                    # Acquire the new client on the next run
       +                else:
       +                    raise e
       +            if self.has_detached_pin_support(self.dongleObject) and not self.is_pin_validated(self.dongleObject) and (self.handler <> None):
       +                remaining_attempts = self.dongleObject.getVerifyPinRemainingAttempts()
       +                if remaining_attempts <> 1:
       +                    msg = "Enter your Ledger PIN - remaining attempts : " + str(remaining_attempts)
       +                else:
       +                    msg = "Enter your Ledger 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:
       +                    raise Exception('Aborted by user - please unplug the dongle and plug it again before retrying')
       +                pin = pin.encode()
       +                self.dongleObject.verifyPin(pin)
       +        except BTChipException, e:
       +            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
       +
       +    def checkDevice(self):
       +        if not self.preflightDone:
       +            self.perform_hw1_preflight()
       +            self.preflightDone = True
       +
       +    def password_dialog(self, msg=None):
       +        response = self.handler.get_word(msg)
       +        if response is None:
       +            return False, None, None
       +        return True, response, response
       +
        
        class Ledger_KeyStore(Hardware_KeyStore):
       +    hw_type = 'ledger'
            device = 'Ledger'
        
            def __init__(self, d):
       t@@ -35,16 +164,14 @@ class Ledger_KeyStore(Hardware_KeyStore):
                # handler.  The handler is per-window and preserved across
                # device reconnects
                self.force_watching_only = False
       -        self.device_checked = False
                self.signing = False
        
       -    def get_client(self):
       -        return self.plugin.get_client()
       -
       -    def init_xpub(self):
       -        client = self.get_client()
       -        self.xpub = self.get_public_key(self.get_derivation())
       +    def get_derivation(self):
       +        return self.derivation        
        
       +    def get_client(self):
       +        return self.plugin.get_client(self)
       +    
            def give_error(self, message, clear_client = False):
                print_error(message)
                if not self.signing:
       t@@ -53,38 +180,34 @@ class Ledger_KeyStore(Hardware_KeyStore):
                    self.signing = False
                if clear_client:
                    self.client = None
       -            self.device_checked = False
                raise Exception(message)
        
       -    def address_id(self, address):
       +    def address_id_stripped(self, address):
                # Strip the leading "m/"
       -        return BIP32_HW_Wallet.address_id(self, address)[2:]
       +        change, index = self.get_address_index(address)
       +        derivation = self.derivation
       +        address_path = "%s/%d/%d"%(derivation, change, index)
       +        return address_path[2:]
        
            def decrypt_message(self, pubkey, message, password):
       -        self.give_error("Not supported")
       +        raise RuntimeError(_('Encryption and decryption are currently not supported for %s') % self.device)
        
       -    def sign_message(self, address, message, password):
       -        use2FA = False
       +    def sign_message(self, sequence, message, password):
                self.signing = True
                # prompt for the PIN before displaying the dialog if necessary
                client = self.get_client()
       -        if not self.check_proper_device():
       -            self.give_error('Wrong device or password')
       -        address_path = self.address_id(address)
       +        address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
                self.handler.show_message("Signing message ...")
                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:
                            raise Exception('Aborted by user')
                        pin = pin.encode()
       -                client.bad = True
       -                self.device_checked = False
       -                self.plugin.get_client(self, True, True)
       +                #self.plugin.get_client(self, True, True)
                    signature = self.get_client().signMessageSign(pin)
                except BTChipException, e:
                    if e.sw == 0x6a80:
       t@@ -95,7 +218,6 @@ class Ledger_KeyStore(Hardware_KeyStore):
                    self.give_error(e, True)
                finally:
                    self.handler.clear_dialog()
       -        client.bad = use2FA
                self.signing = False
        
                # Parse the ASN.1 signature
       t@@ -122,7 +244,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
                inputs = []
                inputsPaths = []
                pubKeys = []
       -        trustedInputs = []
       +        chipInputs = []
                redeemScripts = []
                signatures = []
                preparedTrustedInputs = []
       t@@ -130,53 +252,91 @@ class Ledger_KeyStore(Hardware_KeyStore):
                changeAmount = None
                output = None
                outputAmount = None
       -        use2FA = False
       +        p2shTransaction = False
                pin = ""
       +        self.get_client() # prompt for the PIN before displaying the dialog if necessary
                rawTx = tx.serialize()
                # Fetch inputs of the transaction to sign
                for txinput in tx.inputs():
                    if ('is_coinbase' in txinput and txinput['is_coinbase']):
                        self.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))
       +            redeemScript = None
       +            signingPos = -1
       +            hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], txinput['derivation'][0], txinput['derivation'][1])            
       +            if len(txinput['pubkeys']) > 1:
       +                p2shTransaction = True            
       +            if 'redeemScript' in txinput:
       +                redeemScript = txinput['redeemScript']
       +            if p2shTransaction:
       +                chipPublicKey = compress_public_key(self.get_client().getWalletPublicKey(hwAddress)['publicKey'])
       +                for currentIndex, key in enumerate(txinput['pubkeys']):
       +                    if chipPublicKey == key.decode('hex'):
       +                        signingPos = currentIndex
       +                        break
       +                if signingPos == -1:
       +                    self.give_error("No matching key for multisignature input") # should never happen
       +
       +            inputs.append([ txinput['prev_tx'].raw,
       +                             txinput['prevout_n'], redeemScript, txinput['prevout_hash'], signingPos ])
       +            inputsPaths.append(hwAddress)
       +            pubKeys.append(txinput['pubkeys'])
       +       
       +        # Sanity check
       +        if p2shTransaction:
       +            for txinput in tx.inputs():
       +                if len(txinput['pubkeys']) < 2:
       +                    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():
       +                output_type, addr, amount = output
       +                txOutput += int_to_hex(amount, 8)                       
       +                script = tx.pay_script(output_type, addr)
       +                txOutput += var_int(len(script)/2)
       +                txOutput += script
       +            txOutput = txOutput.decode('hex')
        
                # Recognize outputs - only one output and one change is authorized
       -        if len(tx.outputs()) > 2: # should never happen
       -            self.give_error("Transaction with more than 2 outputs not supported")
       -        for type, address, amount in tx.outputs():
       -            assert type == TYPE_ADDRESS
       -            if self.is_change(address):
       -                changePath = self.address_id(address)
       -                changeAmount = amount
       -            else:
       -                if output <> None: # should never happen
       -                    self.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
       -        if not self.check_proper_device():
       -            self.give_error('Wrong device or password')
       -
       +        if not p2shTransaction:
       +            if len(tx.outputs()) > 2: # should never happen
       +                self.give_error("Transaction with more than 2 outputs not supported")
       +            for i, (_type, address, amount) in enumerate(tx.outputs()):
       +                assert _type == TYPE_ADDRESS
       +                change, index = tx.output_info[i]            
       +                if change:
       +                    changePath = "%s/%d/%d" % (self.get_derivation()[2:], change, index)
       +                    changeAmount = amount
       +                else:
       +                    if output <> None: # should never happen
       +                        self.give_error("Multiple outputs with no change not supported")
       +                    output = address
       +                    outputAmount = amount
       +        
                self.handler.show_message("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)
       +            for utxo in inputs:                
       +                if not p2shTransaction:
       +                    txtmp = bitcoinTransaction(bytearray(utxo[0].decode('hex')))             
       +                    chipInputs.append(self.get_client().getTrustedInput(txtmp, utxo[1]))
       +                    redeemScripts.append(txtmp.outputs[utxo[1]].script)                    
       +                else:
       +                    tmp = utxo[3].decode('hex')[::-1].encode('hex')
       +                    tmp += int_to_hex(utxo[1], 4)
       +                    chipInputs.append({ 'value' : tmp.decode('hex') })                                        
       +                    redeemScripts.append(bytearray(utxo[2].decode('hex')))
       +
                    # 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_plain(outputAmount),
       -                format_satoshis_plain(self.get_tx_fee(tx)), changePath, bytearray(rawTx.decode('hex')))
       +                chipInputs, redeemScripts[inputIndex])
       +                if not p2shTransaction:
       +                    outputData = self.get_client().finalizeInput(output, format_satoshis_plain(outputAmount),
       +                        format_satoshis_plain(tx.get_fee()), changePath, bytearray(rawTx.decode('hex')))
       +                else:
       +                    outputData = self.get_client().finalizeInputFull(txOutput)
       +                    outputData['outputData'] = txOutput
                        if firstTransaction:
                            transactionOutput = outputData['outputData']
                        if outputData['confirmationNeeded']:
       t@@ -204,14 +364,11 @@ class Ledger_KeyStore(Hardware_KeyStore):
                                        raise Exception('Invalid PIN character')
                                pin = pin2
                            else:
       -                        use2FA = True
                                confirmed, p, pin = self.password_dialog()
                                if not confirmed:
                                    raise Exception('Aborted by user')
                                pin = pin.encode()
       -                        client.bad = True
       -                        self.device_checked = False
       -                        self.plugin.get_client(self, True, True)
       +                        #self.plugin.get_client(self, True, True)
                            self.handler.show_message("Signing ...")
                        else:
                            # Sign input with the provided PIN
       t@@ -229,35 +386,19 @@ class Ledger_KeyStore(Hardware_KeyStore):
                # 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 ])
       +            if p2shTransaction:
       +                signaturesPack = [signatures[inputIndex]] * len(pubKeys[inputIndex])
       +                inputScript = get_p2sh_input_script(redeemScripts[inputIndex], signaturesPack)
       +                preparedTrustedInputs.append([ ("\x00" * 4) + chipInputs[inputIndex]['value'], inputScript ])                
       +            else:
       +                inputScript = get_regular_input_script(signatures[inputIndex], pubKeys[inputIndex][0].decode('hex'))
       +                preparedTrustedInputs.append([ chipInputs[inputIndex]['value'], inputScript ])
                    inputIndex = inputIndex + 1
                updatedTransaction = format_transaction(transactionOutput, preparedTrustedInputs)
                updatedTransaction = hexlify(updatedTransaction)
       -        tx.update(updatedTransaction)
       -        client.bad = use2FA
       +        tx.update_signatures(updatedTransaction)
                self.signing = False
        
       -    def check_proper_device(self):
       -        pubKey = DecodeBase58Check(self.xpub)[45:]
       -        if not self.device_checked:
       -            self.handler.show_message("Checking device")
       -            try:
       -                nodeData = self.get_client().getWalletPublicKey("44'/0'/0'")
       -            except Exception, e:
       -                self.give_error(e, True)
       -            finally:
       -                self.handler.clear_dialog()
       -            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 = _("Do not enter your device PIN here !\r\n\r\n" \
       t@@ -279,121 +420,77 @@ class Ledger_KeyStore(Hardware_KeyStore):
        class LedgerPlugin(HW_PluginBase):
            libraries_available = BTCHIP
            keystore_class = Ledger_KeyStore
       -    hw_type='ledger'
            client = None
       +    DEVICE_IDS = [ 
       +                   (0x2581, 0x1807), # HW.1 legacy btchip
       +                   (0x2581, 0x2b7c), # HW.1 transitional production
       +                   (0x2581, 0x3b7c), # HW.1 ledger production
       +                   (0x2581, 0x4b7c), # HW.1 ledger test
       +                   (0x2c97, 0x0000), # Blue
       +                   (0x2c97, 0x0001)  # Nano-S
       +                 ]
       +
       +    def __init__(self, parent, config, name):
       +        HW_PluginBase.__init__(self, parent, config, name)
       +        if self.libraries_available:
       +            self.device_manager().register_devices(self.DEVICE_IDS)
        
            def btchip_is_connected(self, keystore):
                try:
       -            self.get_client().getFirmwareVersion()
       +            self.get_client(keystore).getFirmwareVersion()
                except Exception as e:
       -            self.print_error("get_client", str(e))
                    return False
                return True
        
       -    def get_client(self, force_pair=True, noPin=False):
       -        aborted = False
       -        client = self.client
       -        if not client or client.bad:
       -            try:
       -                d = getDongle(BTCHIP_DEBUG)
       -                client = btchip(d)
       -                firmware = client.getFirmwareVersion()['version'].split(".")
       -                if not checkFirmware(firmware):
       -                    d.close()
       -                    try:
       -                        updateFirmware()
       -                    except Exception, e:
       -                        aborted = True
       -                        raise e
       -                    d = getDongle(BTCHIP_DEBUG)
       -                    client = btchip(d)
       -                try:
       -                    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(BTCHIP_DEBUG)
       -                        client = btchip(d)
       -                    else:
       -                        raise e
       -                if not noPin:
       -                    # Immediately prompts for the PIN
       -                    remaining_attempts = client.getVerifyPinRemainingAttempts()
       -                    if remaining_attempts <> 1:
       -                        msg = "Enter your Ledger PIN - remaining attempts : " + str(remaining_attempts)
       -                    else:
       -                        msg = "Enter your Ledger PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped."
       -                    confirmed, p, pin = wallet.password_dialog(msg)
       -                    if not confirmed:
       -                        aborted = True
       -                        raise Exception('Aborted by user - please unplug the dongle and plug it again before retrying')
       -                    pin = pin.encode()
       -                    client.verifyPin(pin)
       -
       -            except BTChipException, e:
       -                try:
       -                    client.dongle.close()
       -                except:
       -                    pass
       -                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:
       -                try:
       -                    client.dongle.close()
       -                except:
       -                    pass
       -                client = None
       -                if not aborted:
       -                    raise Exception("Could not connect to your Ledger wallet. Please verify access permissions, PIN, or unplug the dongle and plug it again")
       -                else:
       -                    raise e
       -            client.bad = False
       -            self.device_checked = False
       -            self.proper_device = False
       -            self.client = client
       -
       -        return self.client
       -
       -    def get_public_key(self, bip32_path):
       -        # bip32_path is of the form 44'/0'/1'
       -        # 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
       -        self.handler.show_message("Computing master public key")
       -        try:
       -            splitPath = bip32_path.split('/')
       -            if splitPath[0] == 'm':
       -                splitPath = splitPath[1:]
       -                bip32_path = bip32_path[2:]
       -            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:
       -            self.give_error(e, True)
       -        finally:
       -            self.handler.clear_dialog()
       -
       -        return EncodeBase58Check(xpub)
       -
       +    def get_btchip_device(self, device):
       +        ledger = False
       +        if (device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c) or (device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c) or (device.product_key[0] == 0x2c97):
       +           ledger = True        
       +        dev = hid.device()
       +        dev.open_path(device.path)
       +        dev.set_nonblocking(True)
       +        return HIDDongleHIDAPI(dev, ledger, BTCHIP_DEBUG)
       +
       +    def verify_btchip_pin(self):
       +        pass
       +        
       +    def create_client(self, device, handler):
       +        self.handler = handler
       +
       +        client = self.get_btchip_device(device)
       +        if client <> None:
       +            client = Ledger_Client(client)
       +        return client
       +
       +    def setup_device(self, device_info, wizard):        
       +        devmgr = self.device_manager()
       +        device_id = device_info.device.id_
       +        client = devmgr.client_by_id(device_id)
       +        #client.handler = wizard
       +        client.handler = self.create_handler(wizard)
       +        client.get_xpub('m')
       +
       +    def get_xpub(self, device_id, derivation, wizard):
       +        devmgr = self.device_manager()
       +        client = devmgr.client_by_id(device_id)
       +        #client.handler = wizard
       +        client.handler = self.create_handler(wizard)
       +        client.checkDevice()
       +        xpub = client.get_xpub(derivation)
       +        return xpub
       +
       +    def get_client(self, keystore, force_pair=True):
       +        # All client interaction should not be in the main GUI thread
       +        #assert self.main_thread != threading.current_thread()
       +        devmgr = self.device_manager()
       +        handler = keystore.handler
       +        handler = keystore.handler
       +        with devmgr.hid_lock:
       +            client = devmgr.client_for_keystore(self, handler, keystore, force_pair)        
       +        # returns the client for a given keystore. can use xpub
       +        #if client:
       +        #    client.used()
       +        if client <> None:
       +            client.checkDevice()
       +            client = client.dongleObject            
       +        return client
   DIR diff --git a/plugins/ledger/qt.py b/plugins/ledger/qt.py
       t@@ -3,27 +3,35 @@ import threading
        from PyQt4.Qt import (QDialog, QInputDialog, QLineEdit,
                              QVBoxLayout, QLabel, SIGNAL)
        import PyQt4.QtCore as QtCore
       +from electrum_gui.qt.main_window import StatusBarButton
        
        from electrum.i18n import _
        from electrum.plugins import hook
        from .ledger import LedgerPlugin, Ledger_KeyStore
        from ..hw_wallet.qt import QtHandlerBase
       +from electrum_gui.qt.util import *
        
        class Plugin(LedgerPlugin):
       +    icon_unpaired = ":icons/ledger_unpaired.png"
       +    icon_paired = ":icons/ledger.png"
        
            @hook
            def load_wallet(self, wallet, window):
       -        keystore = wallet.get_keystore()
       -        if type(keystore) != self.keystore_class:
       -            return
       -        keystore.handler = BTChipQTHandler(window)
       -        if self.btchip_is_connected(keystore):
       -            if not keystore.check_proper_device():
       -                window.show_error(_("This wallet does not match your Ledger device"))
       -                wallet.force_watching_only = True
       -        else:
       -            window.show_error(_("Ledger device not detected.\nContinuing in watching-only mode."))
       -            wallet.force_watching_only = True
       +        for keystore in wallet.get_keystores():
       +            if type(keystore) != self.keystore_class:
       +                continue
       +            tooltip = self.device
       +            cb = partial(self.show_settings_dialog, window, keystore)
       +            button = StatusBarButton(QIcon(self.icon_unpaired), tooltip, cb)
       +            button.icon_paired = self.icon_paired
       +            button.icon_unpaired = self.icon_unpaired
       +            window.statusBar().addPermanentWidget(button)
       +            handler = BTChipQTHandler(window)
       +            handler.button = button
       +            keystore.handler = handler
       +            keystore.thread = TaskThread(window, window.on_error)
       +            # Trigger a pairing
       +            keystore.thread.add(partial(self.get_client, keystore))
        
            def create_keystore(self, hw_type, derivation, wizard):
                from electrum.keystore import hardware_keystore
       t@@ -40,6 +48,11 @@ class Plugin(LedgerPlugin):
                k = hardware_keystore(hw_type, d)
                return k
        
       +    def create_handler(self, wizard):
       +        return BTChipQTHandler(wizard)        
       +
       +    def show_settings_dialog(self, window, keystore):
       +        pass
        
        class BTChipQTHandler(QtHandlerBase):