commit aca8cf595679ac6fc9ba3ae9f60c38ea360f610c
parent 15f592f0220fa339b8bb709961a7c38ad0f56fff
Author: ThomasV <thomasv@gitorious>
Date: Sun, 5 Jul 2015 23:29:49 +0200
Merge branch 'master' of git://github.com/spesmilo/electrum
Diffstat:
2 files changed, 90 insertions(+), 48 deletions(-)
diff --git a/plugins/__init__.py b/plugins/__init__.py
@@ -34,7 +34,7 @@ descriptions = [
'requires': [('btchip', 'github.com/btchip/btchip-python')],
'requires_wallet_type': ['btchip'],
'registers_wallet_type': ('hardware', 'btchip', _("BTChip wallet")),
- 'available_for': ['qt'],
+ 'available_for': ['qt', 'cmdline'],
},
{
'name': 'cosigner_pool',
diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py
@@ -1,4 +1,4 @@
-from PyQt4.Qt import QApplication, QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL
+from PyQt4.Qt import QApplication, QMessageBox, QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, QThread, SIGNAL
import PyQt4.QtCore as QtCore
from binascii import unhexlify
from binascii import hexlify
@@ -16,8 +16,9 @@ from electrum.plugins import BasePlugin, hook
from electrum.transaction import deserialize
from electrum.wallet import BIP32_HD_Wallet, BIP32_Wallet
-from electrum.util import format_satoshis_plain
+from electrum.util import format_satoshis_plain, print_error, print_msg
import hashlib
+import threading
try:
from btchip.btchipComm import getDongle, DongleWait
@@ -38,6 +39,7 @@ class Plugin(BasePlugin):
BasePlugin.__init__(self, gui, name)
self._is_available = self._init()
self.wallet = None
+ self.handler = None
def constructor(self, s):
return BTChipWallet(s)
@@ -72,9 +74,19 @@ class Plugin(BasePlugin):
return True
@hook
+ def cmdline_load_wallet(self, wallet):
+ self.wallet = wallet
+ self.wallet.plugin = self
+ if self.handler is None:
+ self.handler = BTChipCmdLineHandler()
+
+ @hook
def load_wallet(self, wallet, window):
self.wallet = wallet
+ self.wallet.plugin = self
self.window = window
+ if self.handler is None:
+ self.handler = BTChipQTHandler(self.window.app)
if self.btchip_is_connected():
if not self.wallet.check_proper_device():
QMessageBox.information(self.window, _('Error'), _("This wallet does not match your BTChip device"), _('OK'))
@@ -117,6 +129,7 @@ class BTChipWallet(BIP32_HD_Wallet):
self.force_watching_only = False
def give_error(self, message, clear_client = False):
+ print_error(message)
if not self.signing:
QMessageBox.warning(QDialog(), _('Warning'), _(message), _('OK'))
else:
@@ -130,6 +143,10 @@ class BTChipWallet(BIP32_HD_Wallet):
if not self.accounts:
return 'create_accounts'
+ def can_sign_xpubkey(self, x_pubkey):
+ xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
+ return xpub in self.master_public_keys.values()
+
def can_create_accounts(self):
return False
@@ -150,10 +167,10 @@ class BTChipWallet(BIP32_HD_Wallet):
aborted = False
if not self.client or self.client.bad:
- try:
+ try:
d = getDongle(BTCHIP_DEBUG)
- d.setWaitImpl(DongleWaitQT(d))
self.client = btchip(d)
+ self.client.handler = self.plugin.handler
firmware = self.client.getFirmwareVersion()['version'].split(".")
if not checkFirmware(firmware):
d.close()
@@ -163,7 +180,6 @@ class BTChipWallet(BIP32_HD_Wallet):
aborted = True
raise e
d = getDongle(BTCHIP_DEBUG)
- d.setWaitImpl(DongleWaitQT(d))
self.client = btchip(d)
try:
self.client.getOperationMode()
@@ -174,7 +190,6 @@ class BTChipWallet(BIP32_HD_Wallet):
dialog.exec_()
# Then fetch the reference again as it was invalidated
d = getDongle(BTCHIP_DEBUG)
- d.setWaitImpl(DongleWaitQT(d))
self.client = btchip(d)
else:
raise e
@@ -237,7 +252,7 @@ class BTChipWallet(BIP32_HD_Wallet):
# 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")
+ self.plugin.handler.show_message("Computing master public key")
try:
splitPath = bip32_path.split('/')
fingerprint = 0
@@ -260,7 +275,7 @@ class BTChipWallet(BIP32_HD_Wallet):
except Exception, e:
self.give_error(e, True)
finally:
- waitDialog.emit(SIGNAL('dongle_done'))
+ self.plugin.handler.stop()
return EncodeBase58Check(xpub)
@@ -289,7 +304,7 @@ class BTChipWallet(BIP32_HD_Wallet):
if not self.check_proper_device():
self.give_error('Wrong device or password')
address_path = self.address_id(address)
- waitDialog.start("Signing Message ...")
+ self.plugin.handler.show_message("Signing message ...")
try:
info = self.get_client().signMessagePrepare(address_path, message)
pin = ""
@@ -312,8 +327,7 @@ class BTChipWallet(BIP32_HD_Wallet):
except Exception, e:
self.give_error(e, True)
finally:
- if waitDialog.waiting:
- waitDialog.emit(SIGNAL('dongle_done'))
+ self.plugin.handler.stop()
self.client.bad = use2FA
self.signing = False
@@ -337,8 +351,8 @@ class BTChipWallet(BIP32_HD_Wallet):
def sign_transaction(self, tx, password):
if tx.is_complete():
return
- if tx.error:
- raise BaseException(tx.error)
+ #if tx.error:
+ # raise BaseException(tx.error)
self.signing = True
inputs = []
inputsPaths = []
@@ -382,7 +396,7 @@ class BTChipWallet(BIP32_HD_Wallet):
if not self.check_proper_device():
self.give_error('Wrong device or password')
- waitDialog.start("Signing Transaction ...")
+ self.plugin.handler.show_message("Signing Transaction ...")
try:
# Get trusted inputs from the original transactions
for utxo in inputs:
@@ -400,10 +414,9 @@ class BTChipWallet(BIP32_HD_Wallet):
format_satoshis_plain(self.get_tx_fee(tx)), changePath, bytearray(rawTx.decode('hex')))
if firstTransaction:
transactionOutput = outputData['outputData']
- if outputData['confirmationNeeded']:
- use2FA = True
+ if outputData['confirmationNeeded']:
# TODO : handle different confirmation types. For the time being only supports keyboard 2FA
- waitDialog.emit(SIGNAL('dongle_done'))
+ self.plugin.handler.stop()
if 'keycardData' in outputData:
pin2 = ""
for keycardIndex in range(len(outputData['keycardData'])):
@@ -426,6 +439,7 @@ class BTChipWallet(BIP32_HD_Wallet):
raise Exception('Invalid PIN character')
pin = pin2
else:
+ use2FA = True
confirmed, p, pin = self.password_dialog()
if not confirmed:
raise Exception('Aborted by user')
@@ -433,7 +447,7 @@ class BTChipWallet(BIP32_HD_Wallet):
self.client.bad = True
self.device_checked = False
self.get_client(True)
- waitDialog.start("Signing ...")
+ self.plugin.handler.show_message("Signing ...")
else:
# Sign input with the provided PIN
inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex],
@@ -445,8 +459,7 @@ class BTChipWallet(BIP32_HD_Wallet):
except Exception, e:
self.give_error(e, True)
finally:
- if waitDialog.waiting:
- waitDialog.emit(SIGNAL('dongle_done'))
+ self.plugin.handler.stop()
# Reformat transaction
inputIndex = 0
@@ -464,13 +477,13 @@ class BTChipWallet(BIP32_HD_Wallet):
def check_proper_device(self):
pubKey = DecodeBase58Check(self.master_public_keys["x/0'"])[45:]
if not self.device_checked:
- waitDialog.start("Checking device")
+ self.plugin.handler.show_message("Checking device")
try:
nodeData = self.get_client().getWalletPublicKey("44'/0'/0'")
except Exception, e:
self.give_error(e, True)
finally:
- waitDialog.emit(SIGNAL('dongle_done'))
+ self.plugin.handler.stop()
pubKeyDevice = compress_public_key(nodeData['publicKey'])
self.device_checked = True
if pubKey != pubKeyDevice:
@@ -488,40 +501,69 @@ class BTChipWallet(BIP32_HD_Wallet):
"It should show itself to your computer as a keyboard and output the second factor along with a summary of the transaction it is signing into the text-editor.\r\n\r\n" \
"Check that summary and then enter the second factor code here.\r\n" \
"Before clicking OK, re-plug the device once more (unplug it and plug it again if you read the second factor code on the same computer)")
- d = QDialog()
- d.setModal(1)
- d.setLayout( make_password_dialog(d, None, msg, False) )
- return run_password_dialog(d, None, None)
+ response = self.plugin.handler.prompt_auth(msg)
+ if response is None:
+ return False, None, None
+ return True, response, response
+
+class BTChipQTHandler:
-class DongleWaitingDialog(QThread):
- def __init__(self):
- QThread.__init__(self)
- self.waiting = False
+ def __init__(self, win):
+ self.win = win
+ self.win.connect(win, SIGNAL('btchip_done'), self.dialog_stop)
+ self.win.connect(win, SIGNAL('message_dialog'), self.message_dialog)
+ self.win.connect(win, SIGNAL('auth_dialog'), self.auth_dialog)
+ self.done = threading.Event()
- def start(self, message):
+ def stop(self):
+ self.win.emit(SIGNAL('btchip_done'))
+
+ def show_message(self, msg):
+ self.message = msg
+ self.win.emit(SIGNAL('message_dialog'))
+
+ def prompt_auth(self, msg):
+ self.done.clear()
+ self.message = msg
+ self.win.emit(SIGNAL('auth_dialog'))
+ self.done.wait()
+ return self.response
+
+ def auth_dialog(self):
+ response = QInputDialog.getText(None, "BTChip Authentication", self.message, QLineEdit.Password)
+ if not response[1]:
+ self.response = None
+ else:
+ self.response = str(response[0])
+ self.done.set()
+
+ def message_dialog(self):
self.d = QDialog()
self.d.setModal(1)
- self.d.setWindowTitle('Please Wait')
+ self.d.setWindowTitle('BTChip')
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
- l = QLabel(message)
+ l = QLabel(self.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
+ def dialog_stop(self):
+ if self.d is not None:
+ self.d.hide()
+ self.d = None
-if BTCHIP:
- waitDialog = DongleWaitingDialog()
+class BTChipCmdLineHandler:
+
+ def stop(self):
+ pass
- # Tickle the UI a bit while waiting
- class DongleWaitQT(DongleWait):
- def __init__(self, dongle):
- self.dongle = dongle
+ def show_message(self, msg):
+ print_msg(msg)
- def waitFirstResponse(self, timeout):
- return self.dongle.waitFirstResponse(timeout)
+ def prompt_auth(self, msg):
+ import getpass
+ print_msg(msg)
+ response = getpass.getpass('')
+ if len(response) == 0:
+ return None
+ return response