diff --git a/.idea/electrum.iml b/.idea/electrum.iml
index 1a53fc30325..93b1287efff 100644
--- a/.idea/electrum.iml
+++ b/.idea/electrum.iml
@@ -5,7 +5,6 @@
-
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 98a50070fcb..00000000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/README.rst b/README.rst
index e30c4f9e39f..a1c60bcd999 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,7 @@
BTCP Electrum - Lightweight Bitcoin Private Client
==========================================
-Current Release (P!1.1.0): https://github.com/BTCPrivate/electrum-btcp/releases/tag/P!1.1.0
+Current Release (P!1.1.1): https://github.com/BTCPrivate/electrum-btcp/releases/tag/P!1.1.1
Originally forked from **spesmilo/electrum**: https://github.com/spesmilo/electrum
diff --git a/contrib/build-wine/electrum.nsi b/contrib/build-wine/electrum.nsi
index 1e927349ba8..318f9913baa 100644
--- a/contrib/build-wine/electrum.nsi
+++ b/contrib/build-wine/electrum.nsi
@@ -52,7 +52,7 @@
Caption "${PRODUCT_NAME}"
;Adds the Product Version on top of the Version Tab in the Properties of the file.
- VIProductVersion 1.1.0.0
+ VIProductVersion 1.1.1.0
;VIAddVersionKey - Adds a field in the Version Tab of the File Properties. This can either be a field provided by the system or a user defined field.
VIAddVersionKey ProductName "${PRODUCT_NAME} Installer"
diff --git a/lib/version.py b/lib/version.py
index 34d60a5f9be..f3e67ff22fe 100644
--- a/lib/version.py
+++ b/lib/version.py
@@ -1,5 +1,5 @@
# version of the client package
-ELECTRUM_VERSION = 'P!1.1.0'
+ELECTRUM_VERSION = 'P!1.1.1'
# protocol version requested
PROTOCOL_VERSION = '1.1'
diff --git a/plugins/hw_wallet/cmdline.py b/plugins/hw_wallet/cmdline.py
index 999f82994cf..6cd27a00108 100644
--- a/plugins/hw_wallet/cmdline.py
+++ b/plugins/hw_wallet/cmdline.py
@@ -32,6 +32,9 @@ def stop(self):
def show_message(self, msg, on_cancel=None):
print_msg(msg)
+ def show_error(self, msg):
+ print_msg(msg)
+
def update_status(self, b):
print_error('trezor status', b)
diff --git a/plugins/ledger/auth2fa.py b/plugins/ledger/auth2fa.py
index add619a82fd..e6966855c6a 100644
--- a/plugins/ledger/auth2fa.py
+++ b/plugins/ledger/auth2fa.py
@@ -1,32 +1,41 @@
+import os
+import hashlib
+import logging
+import json
+import copy
from binascii import hexlify, unhexlify
+import websocket
+
from PyQt5.Qt import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel
import PyQt5.QtCore as QtCore
from PyQt5.QtWidgets import *
+from btchip.btchip import *
+
from electrum.i18n import _
from electrum_gui.qt.util import *
from electrum.util import print_msg
-
-import os, hashlib, websocket, logging, json, copy
+from electrum import constants, bitcoin
from electrum_gui.qt.qrcodewidget import QRCodeWidget
-from btchip.btchip import *
DEBUG = False
helpTxt = [_("Your Ledger Wallet wants to tell you a one-time PIN code.
" \
- "For best security you should unplug your device, open a text editor on another computer, " \
- "put your cursor into it, and plug your device into that computer. " \
- "It will output a summary of the transaction being signed and a one-time PIN.
" \
- "Verify the transaction summary and type the PIN code here.
" \
- "Before pressing enter, plug the device back into this computer.
" ),
- _("Verify the address below.
Type the character from your security card corresponding to the BOLD character."),
- _("Waiting for authentication on your mobile phone"),
- _("Transaction accepted by mobile phone. Waiting for confirmation."),
- _("Click Pair button to begin pairing a mobile phone."),
- _("Scan this QR code with your LedgerWallet phone app to pair it with this Ledger device.
"
- "To complete pairing you will need your security card to answer a challenge." )
- ]
+ "For best security you should unplug your device, open a text editor on another computer, " \
+ "put your cursor into it, and plug your device into that computer. " \
+ "It will output a summary of the transaction being signed and a one-time PIN.
" \
+ "Verify the transaction summary and type the PIN code here.
" \
+ "Before pressing enter, plug the device back into this computer.
"),
+ _(
+ "Verify the address below.
Type the character from your security card corresponding to the BOLD character."),
+ _("Waiting for authentication on your mobile phone"),
+ _("Transaction accepted by mobile phone. Waiting for confirmation."),
+ _("Click Pair button to begin pairing a mobile phone."),
+ _("Scan this QR code with your Ledger Wallet phone app to pair it with this Ledger device.
"
+ "To complete pairing you will need your security card to answer a challenge.")
+ ]
+
class LedgerAuthDialog(QDialog):
def __init__(self, handler, data):
@@ -37,20 +46,20 @@ def __init__(self, handler, data):
self.handler = handler
self.txdata = data
self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else ''
- self.setMinimumWidth(600)
+ self.setMinimumWidth(650)
self.setWindowTitle(_("Ledger Wallet Authentication"))
self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg)
self.dongle = self.handler.win.wallet.get_keystore().get_client().dongle
self.ws = None
self.pin = ''
-
+
self.devmode = self.getDevice2FAMode()
if self.devmode == 0x11 or self.txdata['confirmationType'] == 1:
self.cfg['mode'] = 0
-
+
vbox = QVBoxLayout()
self.setLayout(vbox)
-
+
def on_change_mode(idx):
if idx < 2 and self.ws:
self.ws.stop()
@@ -62,14 +71,16 @@ def on_change_mode(idx):
self.handler.win.wallet.get_keystore().cfg = self.cfg
self.handler.win.wallet.save_keystore()
self.update_dlg()
+
def add_pairing():
self.do_pairing()
+
def return_pin():
- self.pin = self.pintxt.text() if self.txdata['confirmationType'] == 1 else self.cardtxt.text()
+ self.pin = self.pintxt.text() if self.txdata['confirmationType'] == 1 else self.cardtxt.text()
if self.cfg['mode'] == 1:
- self.pin = ''.join(chr(int(str(i),16)) for i in self.pin)
+ self.pin = ''.join(chr(int(str(i), 16)) for i in self.pin)
self.accept()
-
+
self.modebox = QWidget()
modelayout = QHBoxLayout()
self.modebox.setLayout(modelayout)
@@ -82,16 +93,16 @@ def return_pin():
modelayout.addStretch(1)
self.modebox.setMaximumHeight(50)
vbox.addWidget(self.modebox)
-
+
self.populate_modes()
self.modes.currentIndexChanged.connect(on_change_mode)
self.addPair.clicked.connect(add_pairing)
-
+
self.helpmsg = QTextEdit()
self.helpmsg.setStyleSheet("QTextEdit { background-color: lightgray; }")
self.helpmsg.setReadOnly(True)
vbox.addWidget(self.helpmsg)
-
+
self.pinbox = QWidget()
pinlayout = QHBoxLayout()
self.pinbox.setLayout(pinlayout)
@@ -105,26 +116,35 @@ def return_pin():
pinlayout.addStretch(1)
self.pinbox.setVisible(self.cfg['mode'] == 0)
vbox.addWidget(self.pinbox)
-
+
self.cardbox = QWidget()
card = QVBoxLayout()
self.cardbox.setLayout(card)
self.addrtext = QTextEdit()
- self.addrtext.setStyleSheet("QTextEdit { color:blue; background-color:lightgray; padding:15px 10px; border:none; font-size:20pt; }")
+ self.addrtext.setStyleSheet(
+ "QTextEdit { color:blue; background-color:lightgray; padding:15px 10px; border:none; font-size:20pt; font-family:monospace; }")
self.addrtext.setReadOnly(True)
- self.addrtext.setMaximumHeight(120)
+ self.addrtext.setMaximumHeight(130)
card.addWidget(self.addrtext)
-
+
def pin_changed(s):
if len(s) < len(self.idxs):
i = self.idxs[len(s)]
addr = self.txdata['address']
- addr = addr[:i] + '' + addr[i:i+1] + '' + addr[i+1:]
- self.addrtext.setHtml(str(addr))
+ if not constants.net.TESTNET:
+ text = addr[:i] + '' + addr[i:i + 1] + '' + addr[i + 1:]
+ else:
+ # pin needs to be created from mainnet address
+ addr_mainnet = bitcoin.script_to_address(bitcoin.address_to_script(addr),
+ net=constants.BitcoinMainnet)
+ addr_mainnet = addr_mainnet[:i] + '' + addr_mainnet[i:i + 1] + '' + addr_mainnet[
+ i + 1:]
+ text = str(addr) + '\n' + str(addr_mainnet)
+ self.addrtext.setHtml(str(text))
else:
self.addrtext.setHtml(_("Press Enter"))
-
- pin_changed('')
+
+ pin_changed('')
cardpin = QHBoxLayout()
cardpin.addWidget(QLabel(_("Enter PIN:")))
self.cardtxt = QLineEdit()
@@ -138,7 +158,7 @@ def pin_changed(s):
card.addLayout(cardpin)
self.cardbox.setVisible(self.cfg['mode'] == 1)
vbox.addWidget(self.cardbox)
-
+
self.pairbox = QWidget()
pairlayout = QVBoxLayout()
self.pairbox.setLayout(pairlayout)
@@ -151,22 +171,24 @@ def pin_changed(s):
self.pairbox.setVisible(False)
vbox.addWidget(self.pairbox)
self.update_dlg()
-
+
if self.cfg['mode'] > 1 and not self.ws:
self.req_validation()
-
+
def populate_modes(self):
self.modes.blockSignals(True)
self.modes.clear()
- self.modes.addItem(_("Summary Text PIN (requires dongle replugging)") if self.txdata['confirmationType'] == 1 else _("Summary Text PIN is Disabled"))
+ self.modes.addItem(
+ _("Summary Text PIN (requires dongle replugging)") if self.txdata['confirmationType'] == 1 else _(
+ "Summary Text PIN is Disabled"))
if self.txdata['confirmationType'] > 1:
self.modes.addItem(_("Security Card Challenge"))
if not self.cfg['pair']:
- self.modes.addItem(_("Mobile - Not paired"))
+ self.modes.addItem(_("Mobile - Not paired"))
else:
self.modes.addItem(_("Mobile - {}").format(self.cfg['pair'][1]))
self.modes.blockSignals(False)
-
+
def update_dlg(self):
self.modes.setCurrentIndex(self.cfg['mode'])
self.modebox.setVisible(True)
@@ -179,8 +201,8 @@ def update_dlg(self):
self.pinbox.setVisible(self.cfg['mode'] == 0)
self.cardbox.setVisible(self.cfg['mode'] == 1)
self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True)
- self.setMaximumHeight(200)
-
+ self.setMaximumHeight(400)
+
def do_pairing(self):
rng = os.urandom(16)
pairID = (hexlify(rng) + hexlify(hashlib.sha256(rng).digest()[0:1])).decode('utf-8')
@@ -190,22 +212,22 @@ def do_pairing(self):
self.pinbox.setVisible(False)
self.cardbox.setVisible(False)
self.pairbox.setVisible(True)
- self.pairqr.setMinimumSize(300,300)
+ self.pairqr.setMinimumSize(300, 300)
if self.ws:
self.ws.stop()
self.ws = LedgerWebSocket(self, pairID)
self.ws.pairing_done.connect(self.pairing_done)
- self.ws.start()
-
+ self.ws.start()
+
def pairing_done(self, data):
if data is not None:
- self.cfg['pair'] = [ data['pairid'], data['name'], data['platform'] ]
+ self.cfg['pair'] = [data['pairid'], data['name'], data['platform']]
self.cfg['mode'] = 2
self.handler.win.wallet.get_keystore().cfg = self.cfg
self.handler.win.wallet.save_keystore()
self.pin = 'paired'
self.accept()
-
+
def req_validation(self):
if self.cfg['pair'] and 'secureScreenData' in self.txdata:
if self.ws:
@@ -213,23 +235,23 @@ def req_validation(self):
self.ws = LedgerWebSocket(self, self.cfg['pair'][0], self.txdata)
self.ws.req_updated.connect(self.req_updated)
self.ws.start()
-
+
def req_updated(self, pin):
if pin == 'accepted':
self.helpmsg.setText(helpTxt[3])
else:
self.pin = str(pin)
self.accept()
-
+
def getDevice2FAMode(self):
- apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode
+ apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode
try:
- mode = self.dongle.exchange( bytearray(apdu) )
+ mode = self.dongle.exchange(bytearray(apdu))
return mode
except BTChipException as e:
debug_msg('Device getMode Failed')
return 0x11
-
+
def closeEvent(self, evnt):
debug_msg("CLOSE - Stop WS")
if self.ws:
@@ -238,28 +260,31 @@ def closeEvent(self, evnt):
evnt.ignore()
self.update_dlg()
+
class LedgerWebSocket(QThread):
pairing_done = pyqtSignal(object)
req_updated = pyqtSignal(str)
-
+
def __init__(self, dlg, pairID, txdata=None):
QThread.__init__(self)
self.stopping = False
self.pairID = pairID
- self.txreq = '{"type":"request","second_factor_data":"' + hexlify(txdata['secureScreenData']).decode('utf-8') + '"}' if txdata else None
+ self.txreq = '{"type":"request","second_factor_data":"' + hexlify(txdata['secureScreenData']).decode(
+ 'utf-8') + '"}' if txdata else None
self.dlg = dlg
self.dongle = self.dlg.dongle
self.data = None
-
- #websocket.enableTrace(True)
+
+ # websocket.enableTrace(True)
logging.basicConfig(level=logging.INFO)
- self.ws = websocket.WebSocketApp('wss://ws.ledgerwallet.com/2fa/channels',
- on_message = self.on_message, on_error = self.on_error,
- on_close = self.on_close, on_open = self.on_open)
-
+ self.ws = websocket.WebSocketApp('wss://ws.ledgerwallet.com/2fa/channels',
+ on_message=self.on_message, on_error=self.on_error,
+ on_close=self.on_close, on_open=self.on_open)
+
def run(self):
while not self.stopping:
self.ws.run_forever()
+
def stop(self):
debug_msg("WS: Stopping")
self.stopping = True
@@ -269,33 +294,33 @@ def on_message(self, ws, msg):
data = json.loads(msg)
if data['type'] == 'identify':
debug_msg('Identify')
- apdu = [0xe0, 0x12, 0x01, 0x00, 0x41] # init pairing
+ apdu = [0xe0, 0x12, 0x01, 0x00, 0x41] # init pairing
apdu.extend(unhexlify(data['public_key']))
try:
- challenge = self.dongle.exchange( bytearray(apdu) )
- ws.send( '{"type":"challenge","data":"%s" }' % hexlify(challenge).decode('utf-8') )
+ challenge = self.dongle.exchange(bytearray(apdu))
+ ws.send('{"type":"challenge","data":"%s" }' % hexlify(challenge).decode('utf-8'))
self.data = data
except BTChipException as e:
debug_msg('Identify Failed')
-
+
if data['type'] == 'challenge':
debug_msg('Challenge')
- apdu = [0xe0, 0x12, 0x02, 0x00, 0x10] # confirm pairing
+ apdu = [0xe0, 0x12, 0x02, 0x00, 0x10] # confirm pairing
apdu.extend(unhexlify(data['data']))
try:
- self.dongle.exchange( bytearray(apdu) )
+ self.dongle.exchange(bytearray(apdu))
debug_msg('Pairing Successful')
- ws.send( '{"type":"pairing","is_successful":"true"}' )
+ ws.send('{"type":"pairing","is_successful":"true"}')
self.data['pairid'] = self.pairID
self.pairing_done.emit(self.data)
except BTChipException as e:
debug_msg('Pairing Failed')
- ws.send( '{"type":"pairing","is_successful":"false"}' )
+ ws.send('{"type":"pairing","is_successful":"false"}')
self.pairing_done.emit(None)
- ws.send( '{"type":"disconnect"}' )
+ ws.send('{"type":"disconnect"}')
self.stopping = True
ws.close()
-
+
if data['type'] == 'accept':
debug_msg('Accepted')
self.req_updated.emit('accepted')
@@ -305,44 +330,40 @@ def on_message(self, ws, msg):
self.txreq = None
self.stopping = True
ws.close()
-
+
if data['type'] == 'repeat':
debug_msg('Repeat')
if self.txreq:
- ws.send( self.txreq )
+ ws.send(self.txreq)
debug_msg("Req Sent", self.txreq)
if data['type'] == 'connect':
debug_msg('Connected')
if self.txreq:
- ws.send( self.txreq )
+ ws.send(self.txreq)
debug_msg("Req Sent", self.txreq)
if data['type'] == 'disconnect':
debug_msg('Disconnected')
ws.close()
-
+
def on_error(self, ws, error):
message = getattr(error, 'strerror', '')
if not message:
message = getattr(error, 'message', '')
debug_msg("WS: %s" % message)
-
+
def on_close(self, ws):
debug_msg("WS: ### socket closed ###")
-
+
def on_open(self, ws):
debug_msg("WS: ### socket open ###")
debug_msg("Joining with pairing ID", self.pairID)
- ws.send( '{"type":"join","room":"%s"}' % self.pairID )
- ws.send( '{"type":"repeat"}' )
+ ws.send('{"type":"join","room":"%s"}' % self.pairID)
+ ws.send('{"type":"repeat"}')
if self.txreq:
- ws.send( self.txreq )
+ ws.send(self.txreq)
debug_msg("Req Sent", self.txreq)
+
def debug_msg(*args):
if DEBUG:
- print_msg(*args)
-
-
-
-
-
+ print_msg(*args)
diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py
index a32173e2fb1..913009766d7 100644
--- a/plugins/ledger/ledger.py
+++ b/plugins/ledger/ledger.py
@@ -57,6 +57,13 @@ def label(self):
def i4b(self, x):
return pack('>I', x)
+ def has_usable_connection_with_device(self):
+ try:
+ self.dongleObject.getFirmwareVersion()
+ except BaseException:
+ return False
+ return True
+
def test_pin_unlocked(func):
"""Function decorator to test the Ledger for being unlocked, and if not,
raise a human-readable exception.
@@ -164,14 +171,11 @@ def perform_hw1_preflight(self):
raise Exception('Aborted by user - please unplug the dongle and plug it again before retrying')
pin = pin.encode()
self.dongleObject.verifyPin(pin)
- p
- #self.dongleObject.setAlternateCoinVersion(0x13, 0x25, 0x13, 0xAF)
apdu = [ 0xE0, 0x14, 0x01, 0x00, 0x17, 0x13, 0x25, 0x13, 0xAF, 0x01, 0x08]
apdu.extend("BPrivate".encode())
apdu.append(0x04)
apdu.extend("BTCP".encode())
self.dongleObject.dongle.exchange(bytearray(apdu))
- #self.dongleObject.setAlternateCoinVersion(4901, 5039)
except BTChipException as e:
if (e.sw == 0x6faa):
raise Exception("Dongle is temporarily locked - please unplug it and replug it again")
@@ -188,8 +192,9 @@ def checkDevice(self):
try:
self.perform_hw1_preflight()
except BTChipException as e:
- if (e.sw == 0x6d00):
- raise BaseException("Device not in Bitcoin Private mode")
+ if (e.sw == 0x6d00 or e.sw == 0x6700):
+ print(e.sw)
+ raise Exception(_("Device not in Bitcoin Private mode")) from e
raise e
self.preflightDone = True
@@ -237,6 +242,16 @@ def give_error(self, message, clear_client = False):
self.client = None
raise Exception(message)
+ def set_and_unset_signing(func):
+ """Function decorator to set and unset self.signing."""
+ def wrapper(self, *args, **kwargs):
+ try:
+ self.signing = True
+ return func(self, *args, **kwargs)
+ finally:
+ self.signing = False
+ return wrapper
+
def address_id_stripped(self, address):
# Strip the leading "m/"
change, index = self.get_address_index(address)
@@ -247,8 +262,8 @@ def address_id_stripped(self, address):
def decrypt_message(self, pubkey, message, password):
raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
+ @set_and_unset_signing
def sign_message(self, sequence, message, password):
- self.signing = True
message = message.encode('utf8')
message_hash = hashlib.sha256(message).hexdigest().upper()
# prompt for the PIN before displaying the dialog if necessary
@@ -267,16 +282,17 @@ def sign_message(self, sequence, message, password):
except BTChipException as e:
if e.sw == 0x6a80:
self.give_error("Unfortunately, this message cannot be signed by the Ledger wallet. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry.")
+ elif e.sw == 0x6985: # cancelled by user
+ return b''
else:
self.give_error(e, True)
except UserWarning:
self.handler.show_error(_('Cancelled by user'))
- return ''
+ return b''
except Exception as e:
self.give_error(e, True)
finally:
self.handler.finished()
- self.signing = False
# Parse the ASN.1 signature
rLength = signature[3]
r = signature[4 : 4 + rLength]
@@ -289,12 +305,11 @@ def sign_message(self, sequence, message, password):
# And convert it
return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s
-
+ @set_and_unset_signing
def sign_transaction(self, tx, password):
if tx.is_complete():
return
client = self.get_client()
- self.signing = True
inputs = []
inputsPaths = []
pubKeys = []
@@ -341,6 +356,9 @@ def sign_transaction(self, tx, password):
self.give_error("No matching x_key for sign_transaction") # should never happen
redeemScript = Transaction.get_preimage_script(txin)
+ if txin.get('prev_tx') is None: # and not Transaction.is_segwit_input(txin):
+ # note: offline signing does not work atm even with segwit inputs for ledger
+ raise Exception(_('Offline signing with {} is not supported.').format(self.device))
inputs.append([txin['prev_tx'].raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos, txin.get('sequence', 0xffffffff - 1) ])
inputsPaths.append(hwAddress)
pubKeys.append(pubkeys)
@@ -368,7 +386,8 @@ def sign_transaction(self, tx, password):
for _type, address, amount in tx.outputs():
assert _type == TYPE_ADDRESS
info = tx.output_info.get(address)
- if (info is not None) and (len(tx.outputs()) != 1):
+ if (info is not None) and len(tx.outputs()) > 1 \
+ and info[0][0] == 1: # "is on 'change' branch"
index, xpubs, m = info
changePath = self.get_derivation()[2:] + "/%d/%d"%index
changeAmount = amount
@@ -408,7 +427,12 @@ def sign_transaction(self, tx, password):
if segwitTransaction:
self.get_client().startUntrustedTransaction(True, inputIndex,
chipInputs, redeemScripts[inputIndex])
- outputData = self.get_client().finalizeInputFull(txOutput)
+ if changePath:
+ # we don't set meaningful outputAddress, amount and fees
+ # as we only care about the alternateEncoding==True branch
+ outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx))
+ else:
+ outputData = self.get_client().finalizeInputFull(txOutput)
outputData['outputData'] = txOutput
transactionOutput = outputData['outputData']
if outputData['confirmationNeeded']:
@@ -423,7 +447,7 @@ def sign_transaction(self, tx, password):
singleInput = [ chipInputs[inputIndex] ]
self.get_client().startUntrustedTransaction(False, 0,
singleInput, redeemScripts[inputIndex])
- inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
+ inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime, sighashType=0x41)
inputSignature[0] = 0x30 # force for 1.4.9+
signatures.append(inputSignature)
inputIndex = inputIndex + 1
@@ -431,7 +455,12 @@ def sign_transaction(self, tx, password):
while inputIndex < len(inputs):
self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,
chipInputs, redeemScripts[inputIndex])
- outputData = self.get_client().finalizeInputFull(txOutput)
+ if changePath:
+ # we don't set meaningful outputAddress, amount and fees
+ # as we only care about the alternateEncoding==True branch
+ outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx))
+ else:
+ outputData = self.get_client().finalizeInputFull(txOutput)
outputData['outputData'] = txOutput
if firstTransaction:
transactionOutput = outputData['outputData']
@@ -454,6 +483,12 @@ def sign_transaction(self, tx, password):
except UserWarning:
self.handler.show_error(_('Cancelled by user'))
return
+ except BTChipException as e:
+ if e.sw == 0x6985: # cancelled by user
+ return
+ else:
+ traceback.print_exc(file=sys.stderr)
+ self.give_error(e, True)
except BaseException as e:
traceback.print_exc(file=sys.stdout)
self.give_error(e, True)
@@ -464,10 +499,9 @@ def sign_transaction(self, tx, password):
signingPos = inputs[i][4]
txin['signatures'][signingPos] = bh2u(signatures[i])
tx.raw = tx.serialize()
- self.signing = False
+ @set_and_unset_signing
def show_address(self, sequence, txin_type):
- self.signing = True
client = self.get_client()
address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
self.handler.show_message(_("Showing address ..."))
@@ -486,7 +520,6 @@ def show_address(self, sequence, txin_type):
self.handler.show_error(e)
finally:
self.handler.finished()
- self.signing = False
class LedgerPlugin(HW_PluginBase):
libraries_available = BTCHIP
@@ -507,17 +540,17 @@ def __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(keystore).getFirmwareVersion()
- except Exception as e:
- return False
- return True
-
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
+ if device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c:
+ ledger = True
+ if device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c:
+ ledger = True
+ if device.product_key[0] == 0x2c97:
+ if device.interface_number == 0 or device.usage_page == 0xffa0:
+ ledger = True
+ else:
+ return None # non-compatible interface of a Nano S or Blue
dev = hid.device()
dev.open_path(device.path)
dev.set_nonblocking(True)
@@ -549,7 +582,6 @@ def get_xpub(self, device_id, derivation, xtype, wizard):
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
with devmgr.hid_lock:
diff --git a/plugins/ledger/qt.py b/plugins/ledger/qt.py
index cfdd7c383a0..5880acbb2af 100644
--- a/plugins/ledger/qt.py
+++ b/plugins/ledger/qt.py
@@ -1,15 +1,13 @@
-import threading
-
-from PyQt5.Qt import QInputDialog, QLineEdit, QVBoxLayout, QLabel
+# from btchip.btchipPersoWizard import StartBTChipPersoDialog
from electrum.i18n import _
from electrum.plugins import hook
from electrum.wallet import Standard_Wallet
+from electrum_gui.qt.util import *
+
from .ledger import LedgerPlugin
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
-from electrum_gui.qt.util import *
-#from btchip.btchipPersoWizard import StartBTChipPersoDialog
class Plugin(LedgerPlugin, QtPluginBase):
icon_unpaired = ":icons/ledger_unpaired.png"
@@ -26,8 +24,10 @@ def receive_menu(self, menu, addrs, wallet):
if type(keystore) == self.keystore_class and len(addrs) == 1:
def show_address():
keystore.thread.add(partial(self.show_address, wallet, addrs[0]))
+
menu.addAction(_("Show on Ledger"), show_address)
+
class Ledger_Handler(QtHandlerBase):
setup_signal = pyqtSignal()
auth_signal = pyqtSignal(object)
@@ -38,13 +38,14 @@ def __init__(self, win):
self.auth_signal.connect(self.auth_dialog)
def word_dialog(self, msg):
- response = QInputDialog.getText(self.top_level_window(), "Ledger Wallet Authentication", msg, QLineEdit.Password)
+ response = QInputDialog.getText(self.top_level_window(), "Ledger Wallet Authentication", msg,
+ QLineEdit.Password)
if not response[1]:
self.word = None
else:
self.word = str(response[0])
self.done.set()
-
+
def message_dialog(self, msg):
self.clear_dialog()
self.dialog = dialog = WindowModalDialog(self.top_level_window(), _("Ledger Status"))
@@ -63,25 +64,21 @@ def auth_dialog(self, data):
dialog.exec_()
self.word = dialog.pin
self.done.set()
-
+
def get_auth(self, data):
self.done.clear()
self.auth_signal.emit(data)
self.done.wait()
return self.word
-
+
def get_setup(self):
self.done.clear()
self.setup_signal.emit()
self.done.wait()
- return
-
+ return
+
def setup_dialog(self):
+ self.show_error(_('Initialization of Ledger HW devices is currently disabled.'))
+ return
dialog = StartBTChipPersoDialog()
dialog.exec_()
-
-
-
-
-
-