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_wallet_type': ['btchip'],
|
||||
'registers_wallet_type': ('hardware', 'btchip', _("BTChip wallet")),
|
||||
'available_for': ['qt'],
|
||||
'available_for': ['qt', 'cmdline'],
|
||||
},
|
||||
{
|
||||
'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
|
||||
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)
|
||||
|
@ -71,10 +73,20 @@ class Plugin(BasePlugin):
|
|||
return False
|
||||
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
|
||||
|
||||
|
@ -152,8 +169,8 @@ class BTChipWallet(BIP32_HD_Wallet):
|
|||
if not self.client or self.client.bad:
|
||||
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:
|
||||
|
@ -401,9 +415,8 @@ class BTChipWallet(BIP32_HD_Wallet):
|
|||
if firstTransaction:
|
||||
transactionOutput = outputData['outputData']
|
||||
if outputData['confirmationNeeded']:
|
||||
use2FA = True
|
||||
# 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 DongleWaitingDialog(QThread):
|
||||
def __init__(self):
|
||||
QThread.__init__(self)
|
||||
self.waiting = False
|
||||
class BTChipQTHandler:
|
||||
|
||||
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.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 dialog_stop(self):
|
||||
if self.d is not None:
|
||||
self.d.hide()
|
||||
self.d = None
|
||||
|
||||
class BTChipCmdLineHandler:
|
||||
|
||||
def stop(self):
|
||||
self.d.hide()
|
||||
self.waiting = False
|
||||
pass
|
||||
|
||||
if BTCHIP:
|
||||
waitDialog = DongleWaitingDialog()
|
||||
def show_message(self, msg):
|
||||
print_msg(msg)
|
||||
|
||||
# Tickle the UI a bit while waiting
|
||||
class DongleWaitQT(DongleWait):
|
||||
def __init__(self, dongle):
|
||||
self.dongle = dongle
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue