Merge branch 'master' of git://github.com/spesmilo/electrum
This commit is contained in:
commit
aca8cf5956
|
@ -34,7 +34,7 @@ descriptions = [
|
||||||
'requires': [('btchip', 'github.com/btchip/btchip-python')],
|
'requires': [('btchip', 'github.com/btchip/btchip-python')],
|
||||||
'requires_wallet_type': ['btchip'],
|
'requires_wallet_type': ['btchip'],
|
||||||
'registers_wallet_type': ('hardware', 'btchip', _("BTChip wallet")),
|
'registers_wallet_type': ('hardware', 'btchip', _("BTChip wallet")),
|
||||||
'available_for': ['qt'],
|
'available_for': ['qt', 'cmdline'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'cosigner_pool',
|
'name': 'cosigner_pool',
|
||||||
|
|
|
@ -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
|
import PyQt4.QtCore as QtCore
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
|
@ -16,8 +16,9 @@ from electrum.plugins import BasePlugin, hook
|
||||||
from electrum.transaction import deserialize
|
from electrum.transaction import deserialize
|
||||||
from electrum.wallet import BIP32_HD_Wallet, BIP32_Wallet
|
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 hashlib
|
||||||
|
import threading
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from btchip.btchipComm import getDongle, DongleWait
|
from btchip.btchipComm import getDongle, DongleWait
|
||||||
|
@ -38,6 +39,7 @@ class Plugin(BasePlugin):
|
||||||
BasePlugin.__init__(self, gui, name)
|
BasePlugin.__init__(self, gui, name)
|
||||||
self._is_available = self._init()
|
self._is_available = self._init()
|
||||||
self.wallet = None
|
self.wallet = None
|
||||||
|
self.handler = None
|
||||||
|
|
||||||
def constructor(self, s):
|
def constructor(self, s):
|
||||||
return BTChipWallet(s)
|
return BTChipWallet(s)
|
||||||
|
@ -71,10 +73,20 @@ class Plugin(BasePlugin):
|
||||||
return False
|
return False
|
||||||
return True
|
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
|
@hook
|
||||||
def load_wallet(self, wallet, window):
|
def load_wallet(self, wallet, window):
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
|
self.wallet.plugin = self
|
||||||
self.window = window
|
self.window = window
|
||||||
|
if self.handler is None:
|
||||||
|
self.handler = BTChipQTHandler(self.window.app)
|
||||||
if self.btchip_is_connected():
|
if self.btchip_is_connected():
|
||||||
if not self.wallet.check_proper_device():
|
if not self.wallet.check_proper_device():
|
||||||
QMessageBox.information(self.window, _('Error'), _("This wallet does not match your BTChip device"), _('OK'))
|
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
|
self.force_watching_only = False
|
||||||
|
|
||||||
def give_error(self, message, clear_client = False):
|
def give_error(self, message, clear_client = False):
|
||||||
|
print_error(message)
|
||||||
if not self.signing:
|
if not self.signing:
|
||||||
QMessageBox.warning(QDialog(), _('Warning'), _(message), _('OK'))
|
QMessageBox.warning(QDialog(), _('Warning'), _(message), _('OK'))
|
||||||
else:
|
else:
|
||||||
|
@ -130,6 +143,10 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
if not self.accounts:
|
if not self.accounts:
|
||||||
return 'create_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):
|
def can_create_accounts(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -152,8 +169,8 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
if not self.client or self.client.bad:
|
if not self.client or self.client.bad:
|
||||||
try:
|
try:
|
||||||
d = getDongle(BTCHIP_DEBUG)
|
d = getDongle(BTCHIP_DEBUG)
|
||||||
d.setWaitImpl(DongleWaitQT(d))
|
|
||||||
self.client = btchip(d)
|
self.client = btchip(d)
|
||||||
|
self.client.handler = self.plugin.handler
|
||||||
firmware = self.client.getFirmwareVersion()['version'].split(".")
|
firmware = self.client.getFirmwareVersion()['version'].split(".")
|
||||||
if not checkFirmware(firmware):
|
if not checkFirmware(firmware):
|
||||||
d.close()
|
d.close()
|
||||||
|
@ -163,7 +180,6 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
aborted = True
|
aborted = True
|
||||||
raise e
|
raise e
|
||||||
d = getDongle(BTCHIP_DEBUG)
|
d = getDongle(BTCHIP_DEBUG)
|
||||||
d.setWaitImpl(DongleWaitQT(d))
|
|
||||||
self.client = btchip(d)
|
self.client = btchip(d)
|
||||||
try:
|
try:
|
||||||
self.client.getOperationMode()
|
self.client.getOperationMode()
|
||||||
|
@ -174,7 +190,6 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
dialog.exec_()
|
dialog.exec_()
|
||||||
# Then fetch the reference again as it was invalidated
|
# Then fetch the reference again as it was invalidated
|
||||||
d = getDongle(BTCHIP_DEBUG)
|
d = getDongle(BTCHIP_DEBUG)
|
||||||
d.setWaitImpl(DongleWaitQT(d))
|
|
||||||
self.client = btchip(d)
|
self.client = btchip(d)
|
||||||
else:
|
else:
|
||||||
raise e
|
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
|
# 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
|
# This only happens once so it's bearable
|
||||||
self.get_client() # prompt for the PIN before displaying the dialog if necessary
|
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:
|
try:
|
||||||
splitPath = bip32_path.split('/')
|
splitPath = bip32_path.split('/')
|
||||||
fingerprint = 0
|
fingerprint = 0
|
||||||
|
@ -260,7 +275,7 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.give_error(e, True)
|
self.give_error(e, True)
|
||||||
finally:
|
finally:
|
||||||
waitDialog.emit(SIGNAL('dongle_done'))
|
self.plugin.handler.stop()
|
||||||
|
|
||||||
return EncodeBase58Check(xpub)
|
return EncodeBase58Check(xpub)
|
||||||
|
|
||||||
|
@ -289,7 +304,7 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
if not self.check_proper_device():
|
if not self.check_proper_device():
|
||||||
self.give_error('Wrong device or password')
|
self.give_error('Wrong device or password')
|
||||||
address_path = self.address_id(address)
|
address_path = self.address_id(address)
|
||||||
waitDialog.start("Signing Message ...")
|
self.plugin.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 = ""
|
||||||
|
@ -312,8 +327,7 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.give_error(e, True)
|
self.give_error(e, True)
|
||||||
finally:
|
finally:
|
||||||
if waitDialog.waiting:
|
self.plugin.handler.stop()
|
||||||
waitDialog.emit(SIGNAL('dongle_done'))
|
|
||||||
self.client.bad = use2FA
|
self.client.bad = use2FA
|
||||||
self.signing = False
|
self.signing = False
|
||||||
|
|
||||||
|
@ -337,8 +351,8 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
def sign_transaction(self, tx, password):
|
def sign_transaction(self, tx, password):
|
||||||
if tx.is_complete():
|
if tx.is_complete():
|
||||||
return
|
return
|
||||||
if tx.error:
|
#if tx.error:
|
||||||
raise BaseException(tx.error)
|
# raise BaseException(tx.error)
|
||||||
self.signing = True
|
self.signing = True
|
||||||
inputs = []
|
inputs = []
|
||||||
inputsPaths = []
|
inputsPaths = []
|
||||||
|
@ -382,7 +396,7 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
if not self.check_proper_device():
|
if not self.check_proper_device():
|
||||||
self.give_error('Wrong device or password')
|
self.give_error('Wrong device or password')
|
||||||
|
|
||||||
waitDialog.start("Signing Transaction ...")
|
self.plugin.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:
|
||||||
|
@ -401,9 +415,8 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
if firstTransaction:
|
if firstTransaction:
|
||||||
transactionOutput = outputData['outputData']
|
transactionOutput = outputData['outputData']
|
||||||
if outputData['confirmationNeeded']:
|
if outputData['confirmationNeeded']:
|
||||||
use2FA = True
|
|
||||||
# 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
|
||||||
waitDialog.emit(SIGNAL('dongle_done'))
|
self.plugin.handler.stop()
|
||||||
if 'keycardData' in outputData:
|
if 'keycardData' in outputData:
|
||||||
pin2 = ""
|
pin2 = ""
|
||||||
for keycardIndex in range(len(outputData['keycardData'])):
|
for keycardIndex in range(len(outputData['keycardData'])):
|
||||||
|
@ -426,6 +439,7 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
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')
|
||||||
|
@ -433,7 +447,7 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
self.client.bad = True
|
self.client.bad = True
|
||||||
self.device_checked = False
|
self.device_checked = False
|
||||||
self.get_client(True)
|
self.get_client(True)
|
||||||
waitDialog.start("Signing ...")
|
self.plugin.handler.show_message("Signing ...")
|
||||||
else:
|
else:
|
||||||
# Sign input with the provided PIN
|
# Sign input with the provided PIN
|
||||||
inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex],
|
inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex],
|
||||||
|
@ -445,8 +459,7 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.give_error(e, True)
|
self.give_error(e, True)
|
||||||
finally:
|
finally:
|
||||||
if waitDialog.waiting:
|
self.plugin.handler.stop()
|
||||||
waitDialog.emit(SIGNAL('dongle_done'))
|
|
||||||
|
|
||||||
# Reformat transaction
|
# Reformat transaction
|
||||||
inputIndex = 0
|
inputIndex = 0
|
||||||
|
@ -464,13 +477,13 @@ class BTChipWallet(BIP32_HD_Wallet):
|
||||||
def check_proper_device(self):
|
def check_proper_device(self):
|
||||||
pubKey = DecodeBase58Check(self.master_public_keys["x/0'"])[45:]
|
pubKey = DecodeBase58Check(self.master_public_keys["x/0'"])[45:]
|
||||||
if not self.device_checked:
|
if not self.device_checked:
|
||||||
waitDialog.start("Checking device")
|
self.plugin.handler.show_message("Checking device")
|
||||||
try:
|
try:
|
||||||
nodeData = self.get_client().getWalletPublicKey("44'/0'/0'")
|
nodeData = self.get_client().getWalletPublicKey("44'/0'/0'")
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.give_error(e, True)
|
self.give_error(e, True)
|
||||||
finally:
|
finally:
|
||||||
waitDialog.emit(SIGNAL('dongle_done'))
|
self.plugin.handler.stop()
|
||||||
pubKeyDevice = compress_public_key(nodeData['publicKey'])
|
pubKeyDevice = compress_public_key(nodeData['publicKey'])
|
||||||
self.device_checked = True
|
self.device_checked = True
|
||||||
if pubKey != pubKeyDevice:
|
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" \
|
"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" \
|
"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)")
|
"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()
|
response = self.plugin.handler.prompt_auth(msg)
|
||||||
d.setModal(1)
|
if response is None:
|
||||||
d.setLayout( make_password_dialog(d, None, msg, False) )
|
return False, None, None
|
||||||
return run_password_dialog(d, None, None)
|
return True, response, response
|
||||||
|
|
||||||
class DongleWaitingDialog(QThread):
|
class BTChipQTHandler:
|
||||||
def __init__(self):
|
|
||||||
QThread.__init__(self)
|
|
||||||
self.waiting = False
|
|
||||||
|
|
||||||
def start(self, message):
|
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 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 = QDialog()
|
||||||
self.d.setModal(1)
|
self.d.setModal(1)
|
||||||
self.d.setWindowTitle('Please Wait')
|
self.d.setWindowTitle('BTChip')
|
||||||
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
|
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
|
||||||
l = QLabel(message)
|
l = QLabel(self.message)
|
||||||
vbox = QVBoxLayout(self.d)
|
vbox = QVBoxLayout(self.d)
|
||||||
vbox.addWidget(l)
|
vbox.addWidget(l)
|
||||||
self.d.show()
|
self.d.show()
|
||||||
if not self.waiting:
|
|
||||||
self.waiting = True
|
def dialog_stop(self):
|
||||||
self.d.connect(waitDialog, SIGNAL('dongle_done'), self.stop)
|
if self.d is not None:
|
||||||
|
self.d.hide()
|
||||||
|
self.d = None
|
||||||
|
|
||||||
|
class BTChipCmdLineHandler:
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.d.hide()
|
pass
|
||||||
self.waiting = False
|
|
||||||
|
|
||||||
if BTCHIP:
|
def show_message(self, msg):
|
||||||
waitDialog = DongleWaitingDialog()
|
print_msg(msg)
|
||||||
|
|
||||||
# Tickle the UI a bit while waiting
|
def prompt_auth(self, msg):
|
||||||
class DongleWaitQT(DongleWait):
|
import getpass
|
||||||
def __init__(self, dongle):
|
print_msg(msg)
|
||||||
self.dongle = dongle
|
response = getpass.getpass('')
|
||||||
|
if len(response) == 0:
|
||||||
def waitFirstResponse(self, timeout):
|
return None
|
||||||
return self.dongle.waitFirstResponse(timeout)
|
return response
|
||||||
|
|
Loading…
Reference in New Issue