Merge pull request #1909 from btchip/ledger-nanos

Ledger pack : Nano S support, rewrite/cleanup, P2SH
This commit is contained in:
ThomasV 2016-08-28 17:56:57 +02:00 committed by GitHub
commit 5be93bdb73
6 changed files with 317 additions and 202 deletions

View File

@ -17,6 +17,8 @@
<file>icons/keepkey.png</file> <file>icons/keepkey.png</file>
<file>icons/keepkey_unpaired.png</file> <file>icons/keepkey_unpaired.png</file>
<file>icons/key.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/lock.png</file>
<file>icons/microphone.png</file> <file>icons/microphone.png</file>
<file>icons/network.png</file> <file>icons/network.png</file>

BIN
icons/ledger.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
icons/ledger_unpaired.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -500,8 +500,11 @@ class DeviceMgr(ThreadJob, PrintError):
if product_key in self.recognised_hardware: if product_key in self.recognised_hardware:
# Older versions of hid don't provide interface_number # Older versions of hid don't provide interface_number
interface_number = d.get('interface_number', 0) 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, devices.append(Device(d['path'], interface_number,
d['serial_number'], product_key)) serial, product_key))
# Now find out what was disconnected # Now find out what was disconnected
pairs = [(dev.path, dev.id_) for dev in devices] pairs = [(dev.path, dev.id_) for dev in devices]

View File

@ -1,10 +1,10 @@
from binascii import hexlify from binascii import hexlify
from struct import unpack from struct import pack, unpack
import hashlib import hashlib
import time import time
import electrum 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.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
@ -13,9 +13,10 @@ from electrum.util import format_satoshis_plain, print_error
try: try:
from btchip.btchipComm import getDongle, DongleWait import hid
from btchip.btchipComm import HIDDongleHIDAPI, DongleWait
from btchip.btchip import btchip 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.bitcoinTransaction import bitcoinTransaction
from btchip.btchipPersoWizard import StartBTChipPersoDialog from btchip.btchipPersoWizard import StartBTChipPersoDialog
from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware
@ -25,8 +26,136 @@ try:
except ImportError: except ImportError:
BTCHIP = False 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): class Ledger_KeyStore(Hardware_KeyStore):
hw_type = 'ledger'
device = 'Ledger' device = 'Ledger'
def __init__(self, d): def __init__(self, d):
@ -35,15 +164,13 @@ class Ledger_KeyStore(Hardware_KeyStore):
# handler. The handler is per-window and preserved across # handler. The handler is per-window and preserved across
# device reconnects # device reconnects
self.force_watching_only = False self.force_watching_only = False
self.device_checked = False
self.signing = False self.signing = False
def get_client(self): def get_derivation(self):
return self.plugin.get_client() return self.derivation
def init_xpub(self): def get_client(self):
client = self.get_client() return self.plugin.get_client(self)
self.xpub = self.get_public_key(self.get_derivation())
def give_error(self, message, clear_client = False): def give_error(self, message, clear_client = False):
print_error(message) print_error(message)
@ -53,38 +180,34 @@ class Ledger_KeyStore(Hardware_KeyStore):
self.signing = False self.signing = False
if clear_client: if clear_client:
self.client = None self.client = None
self.device_checked = False
raise Exception(message) raise Exception(message)
def address_id(self, address): def address_id_stripped(self, address):
# Strip the leading "m/" # 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): 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): def sign_message(self, sequence, message, password):
use2FA = False
self.signing = True self.signing = True
# prompt for the PIN before displaying the dialog if necessary # prompt for the PIN before displaying the dialog if necessary
client = self.get_client() client = self.get_client()
if not self.check_proper_device(): address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
self.give_error('Wrong device or password')
address_path = self.address_id(address)
self.handler.show_message("Signing message ...") self.handler.show_message("Signing message ...")
try: try:
info = self.get_client().signMessagePrepare(address_path, message) info = self.get_client().signMessagePrepare(address_path, message)
pin = "" pin = ""
if info['confirmationNeeded']: if info['confirmationNeeded']:
# TODO : handle different confirmation types. For the time being only supports keyboard 2FA # TODO : handle different confirmation types. For the time being only supports keyboard 2FA
use2FA = True
confirmed, p, pin = self.password_dialog() confirmed, p, pin = self.password_dialog()
if not confirmed: if not confirmed:
raise Exception('Aborted by user') raise Exception('Aborted by user')
pin = pin.encode() pin = pin.encode()
client.bad = True #self.plugin.get_client(self, True, True)
self.device_checked = False
self.plugin.get_client(self, True, True)
signature = self.get_client().signMessageSign(pin) signature = self.get_client().signMessageSign(pin)
except BTChipException, e: except BTChipException, e:
if e.sw == 0x6a80: if e.sw == 0x6a80:
@ -95,7 +218,6 @@ class Ledger_KeyStore(Hardware_KeyStore):
self.give_error(e, True) self.give_error(e, True)
finally: finally:
self.handler.clear_dialog() self.handler.clear_dialog()
client.bad = use2FA
self.signing = False self.signing = False
# Parse the ASN.1 signature # Parse the ASN.1 signature
@ -122,7 +244,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
inputs = [] inputs = []
inputsPaths = [] inputsPaths = []
pubKeys = [] pubKeys = []
trustedInputs = [] chipInputs = []
redeemScripts = [] redeemScripts = []
signatures = [] signatures = []
preparedTrustedInputs = [] preparedTrustedInputs = []
@ -130,53 +252,91 @@ class Ledger_KeyStore(Hardware_KeyStore):
changeAmount = None changeAmount = None
output = None output = None
outputAmount = None outputAmount = None
use2FA = False p2shTransaction = False
pin = "" pin = ""
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 txinput in tx.inputs():
if ('is_coinbase' in txinput and txinput['is_coinbase']): if ('is_coinbase' in txinput and txinput['is_coinbase']):
self.give_error("Coinbase not supported") # should never happen self.give_error("Coinbase not supported") # should never happen
inputs.append([ self.transactions[txinput['prevout_hash']].raw, redeemScript = None
txinput['prevout_n'] ]) signingPos = -1
address = txinput['address'] hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], txinput['derivation'][0], txinput['derivation'][1])
inputsPaths.append(self.address_id(address)) if len(txinput['pubkeys']) > 1:
pubKeys.append(self.get_public_keys(address)) 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 # Recognize outputs - only one output and one change is authorized
if len(tx.outputs()) > 2: # should never happen if not p2shTransaction:
self.give_error("Transaction with more than 2 outputs not supported") if len(tx.outputs()) > 2: # should never happen
for type, address, amount in tx.outputs(): self.give_error("Transaction with more than 2 outputs not supported")
assert type == TYPE_ADDRESS for i, (_type, address, amount) in enumerate(tx.outputs()):
if self.is_change(address): assert _type == TYPE_ADDRESS
changePath = self.address_id(address) change, index = tx.output_info[i]
changeAmount = amount if change:
else: changePath = "%s/%d/%d" % (self.get_derivation()[2:], change, index)
if output <> None: # should never happen changeAmount = amount
self.give_error("Multiple outputs with no change not supported") else:
output = address if output <> None: # should never happen
outputAmount = amount self.give_error("Multiple outputs with no change not supported")
output = address
self.get_client() # prompt for the PIN before displaying the dialog if necessary outputAmount = amount
if not self.check_proper_device():
self.give_error('Wrong device or password')
self.handler.show_message("Signing Transaction ...") self.handler.show_message("Signing Transaction ...")
try: try:
# Get trusted inputs from the original transactions # Get trusted inputs from the original transactions
for utxo in inputs: for utxo in inputs:
txtmp = bitcoinTransaction(bytearray(utxo[0].decode('hex'))) if not p2shTransaction:
trustedInputs.append(self.get_client().getTrustedInput(txtmp, utxo[1])) txtmp = bitcoinTransaction(bytearray(utxo[0].decode('hex')))
# TODO : Support P2SH later chipInputs.append(self.get_client().getTrustedInput(txtmp, utxo[1]))
redeemScripts.append(txtmp.outputs[utxo[1]].script) 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 # Sign all inputs
firstTransaction = True firstTransaction = True
inputIndex = 0 inputIndex = 0
while inputIndex < len(inputs): while inputIndex < len(inputs):
self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,
trustedInputs, redeemScripts[inputIndex]) chipInputs, redeemScripts[inputIndex])
outputData = self.get_client().finalizeInput(output, format_satoshis_plain(outputAmount), if not p2shTransaction:
format_satoshis_plain(self.get_tx_fee(tx)), changePath, bytearray(rawTx.decode('hex'))) 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: if firstTransaction:
transactionOutput = outputData['outputData'] transactionOutput = outputData['outputData']
if outputData['confirmationNeeded']: if outputData['confirmationNeeded']:
@ -204,14 +364,11 @@ class Ledger_KeyStore(Hardware_KeyStore):
raise Exception('Invalid PIN character') raise Exception('Invalid PIN character')
pin = pin2 pin = pin2
else: else:
use2FA = True
confirmed, p, pin = self.password_dialog() confirmed, p, pin = self.password_dialog()
if not confirmed: if not confirmed:
raise Exception('Aborted by user') raise Exception('Aborted by user')
pin = pin.encode() pin = pin.encode()
client.bad = True #self.plugin.get_client(self, True, True)
self.device_checked = False
self.plugin.get_client(self, True, True)
self.handler.show_message("Signing ...") self.handler.show_message("Signing ...")
else: else:
# Sign input with the provided PIN # Sign input with the provided PIN
@ -229,35 +386,19 @@ class Ledger_KeyStore(Hardware_KeyStore):
# Reformat transaction # Reformat transaction
inputIndex = 0 inputIndex = 0
while inputIndex < len(inputs): while inputIndex < len(inputs):
# TODO : Support P2SH later if p2shTransaction:
inputScript = get_regular_input_script(signatures[inputIndex], pubKeys[inputIndex][0].decode('hex')) signaturesPack = [signatures[inputIndex]] * len(pubKeys[inputIndex])
preparedTrustedInputs.append([ trustedInputs[inputIndex]['value'], inputScript ]) 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 inputIndex = inputIndex + 1
updatedTransaction = format_transaction(transactionOutput, preparedTrustedInputs) updatedTransaction = format_transaction(transactionOutput, preparedTrustedInputs)
updatedTransaction = hexlify(updatedTransaction) updatedTransaction = hexlify(updatedTransaction)
tx.update(updatedTransaction) tx.update_signatures(updatedTransaction)
client.bad = use2FA
self.signing = False 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): def password_dialog(self, msg=None):
if not msg: if not msg:
msg = _("Do not enter your device PIN here !\r\n\r\n" \ msg = _("Do not enter your device PIN here !\r\n\r\n" \
@ -279,121 +420,77 @@ class Ledger_KeyStore(Hardware_KeyStore):
class LedgerPlugin(HW_PluginBase): class LedgerPlugin(HW_PluginBase):
libraries_available = BTCHIP libraries_available = BTCHIP
keystore_class = Ledger_KeyStore keystore_class = Ledger_KeyStore
hw_type='ledger'
client = None 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): def btchip_is_connected(self, keystore):
try: try:
self.get_client().getFirmwareVersion() self.get_client(keystore).getFirmwareVersion()
except Exception as e: except Exception as e:
self.print_error("get_client", str(e))
return False return False
return True return True
def get_client(self, force_pair=True, noPin=False): def get_btchip_device(self, device):
aborted = False ledger = False
client = self.client 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):
if not client or client.bad: ledger = True
try: dev = hid.device()
d = getDongle(BTCHIP_DEBUG) dev.open_path(device.path)
client = btchip(d) dev.set_nonblocking(True)
firmware = client.getFirmwareVersion()['version'].split(".") return HIDDongleHIDAPI(dev, ledger, BTCHIP_DEBUG)
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: def verify_btchip_pin(self):
try: pass
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 create_client(self, device, handler):
self.handler = handler
def get_public_key(self, bip32_path): client = self.get_btchip_device(device)
# bip32_path is of the form 44'/0'/1' if client <> None:
# S-L-O-W - we don't handle the fingerprint directly, so compute client = Ledger_Client(client)
# it manually from the previous node return client
# 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 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

View File

@ -3,27 +3,35 @@ import threading
from PyQt4.Qt import (QDialog, QInputDialog, QLineEdit, from PyQt4.Qt import (QDialog, QInputDialog, QLineEdit,
QVBoxLayout, QLabel, SIGNAL) QVBoxLayout, QLabel, SIGNAL)
import PyQt4.QtCore as QtCore import PyQt4.QtCore as QtCore
from electrum_gui.qt.main_window import StatusBarButton
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugins import hook from electrum.plugins import hook
from .ledger import LedgerPlugin, Ledger_KeyStore from .ledger import LedgerPlugin, Ledger_KeyStore
from ..hw_wallet.qt import QtHandlerBase from ..hw_wallet.qt import QtHandlerBase
from electrum_gui.qt.util import *
class Plugin(LedgerPlugin): class Plugin(LedgerPlugin):
icon_unpaired = ":icons/ledger_unpaired.png"
icon_paired = ":icons/ledger.png"
@hook @hook
def load_wallet(self, wallet, window): def load_wallet(self, wallet, window):
keystore = wallet.get_keystore() for keystore in wallet.get_keystores():
if type(keystore) != self.keystore_class: if type(keystore) != self.keystore_class:
return continue
keystore.handler = BTChipQTHandler(window) tooltip = self.device
if self.btchip_is_connected(keystore): cb = partial(self.show_settings_dialog, window, keystore)
if not keystore.check_proper_device(): button = StatusBarButton(QIcon(self.icon_unpaired), tooltip, cb)
window.show_error(_("This wallet does not match your Ledger device")) button.icon_paired = self.icon_paired
wallet.force_watching_only = True button.icon_unpaired = self.icon_unpaired
else: window.statusBar().addPermanentWidget(button)
window.show_error(_("Ledger device not detected.\nContinuing in watching-only mode.")) handler = BTChipQTHandler(window)
wallet.force_watching_only = True 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): def create_keystore(self, hw_type, derivation, wizard):
from electrum.keystore import hardware_keystore from electrum.keystore import hardware_keystore
@ -40,6 +48,11 @@ class Plugin(LedgerPlugin):
k = hardware_keystore(hw_type, d) k = hardware_keystore(hw_type, d)
return k return k
def create_handler(self, wizard):
return BTChipQTHandler(wizard)
def show_settings_dialog(self, window, keystore):
pass
class BTChipQTHandler(QtHandlerBase): class BTChipQTHandler(QtHandlerBase):