trezor plugin: move Qt callbacks in a handler

This commit is contained in:
ThomasV 2015-04-04 15:13:56 +02:00
parent f14c863a0a
commit 21ccb1e82d

View file

@ -6,6 +6,7 @@ from sys import stderr
from time import sleep from time import sleep
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
import unicodedata import unicodedata
import threading
import electrum import electrum
from electrum.account import BIP32_Account from electrum.account import BIP32_Account
@ -15,6 +16,7 @@ from electrum.plugins import BasePlugin, hook
from electrum.transaction import deserialize from electrum.transaction import deserialize
from electrum.wallet import BIP32_HD_Wallet from electrum.wallet import BIP32_HD_Wallet
from electrum.util import print_error from electrum.util import print_error
from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root
from electrum_gui.qt.util import * from electrum_gui.qt.util import *
@ -37,20 +39,6 @@ def give_error(message):
raise Exception(message) raise Exception(message)
def trezor_passphrase_dialog(msg):
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
d = QDialog()
d.setModal(1)
d.setLayout(make_password_dialog(d, None, msg, False))
confirmed, p, passphrase = run_password_dialog(d, None, None)
if not confirmed:
return None
if passphrase is None:
passphrase = '' # Even blank string is valid Trezor passphrase
passphrase = unicodedata.normalize('NFKD', unicode(passphrase))
return passphrase
class Plugin(BasePlugin): class Plugin(BasePlugin):
def fullname(self): def fullname(self):
@ -102,10 +90,6 @@ class Plugin(BasePlugin):
return False return False
return True return True
@hook
def init_qt(self, gui):
self.window = gui.main_window
@hook @hook
def close_wallet(self): def close_wallet(self):
print_error("trezor: clear session") print_error("trezor: clear session")
@ -115,6 +99,8 @@ class Plugin(BasePlugin):
@hook @hook
def load_wallet(self, wallet): def load_wallet(self, wallet):
self.twd = TrezorQtHandler(self.window)
self.wallet.twd = self.twd
if self.trezor_is_connected(): if self.trezor_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 Trezor device"), _('OK')) QMessageBox.information(self.window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
@ -132,9 +118,8 @@ class Plugin(BasePlugin):
return return
wallet = TrezorWallet(storage) wallet = TrezorWallet(storage)
self.wallet = wallet self.wallet = wallet
passphrase = trezor_passphrase_dialog(_("Please enter your Trezor passphrase.") + '\n' + _("Press OK if you do not use one.")) passphrase = self.twd.get_passphrase(_("Please enter your Trezor passphrase.") + '\n' + _("Press OK if you do not use one."))
if passphrase is None: if passphrase is None:
QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
return return
password = wizard.password_dialog() password = wizard.password_dialog()
wallet.add_seed(seed, password) wallet.add_seed(seed, password)
@ -145,13 +130,6 @@ class Plugin(BasePlugin):
return wallet return wallet
@hook @hook
def send_tx(self, tx):
tx.error = None
try:
self.wallet.trezor_sign(tx)
except Exception as e:
tx.error = str(e)
@hook
def receive_menu(self, menu, addrs): def receive_menu(self, menu, addrs):
if not self.wallet.is_watching_only() and self.wallet.atleast_version(1, 3) and len(addrs) == 1: if not self.wallet.is_watching_only() and self.wallet.atleast_version(1, 3) and len(addrs) == 1:
menu.addAction(_("Show on TREZOR"), lambda: self.wallet.show_address(addrs[0])) menu.addAction(_("Show on TREZOR"), lambda: self.wallet.show_address(addrs[0]))
@ -174,9 +152,9 @@ class Plugin(BasePlugin):
if not response[1]: if not response[1]:
return return
new_label = str(response[0]) new_label = str(response[0])
twd.start("Please confirm label change on Trezor") self.twd.show_message("Please confirm label change on Trezor")
status = self.wallet.get_client().apply_settings(label=new_label) status = self.wallet.get_client().apply_settings(label=new_label)
twd.stop() self.twd.stop()
update_label() update_label()
current_label_label = QLabel() current_label_label = QLabel()
@ -192,7 +170,6 @@ class Plugin(BasePlugin):
return False return False
from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root
class TrezorWallet(BIP32_HD_Wallet): class TrezorWallet(BIP32_HD_Wallet):
wallet_type = 'trezor' wallet_type = 'trezor'
@ -213,6 +190,11 @@ class TrezorWallet(BIP32_HD_Wallet):
def can_import(self): def can_import(self):
return False return False
def can_sign_xpubkey(self, x_pubkey):
xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
print "z", xpub
return xpub in self.master_public_keys.values()
def can_export(self): def can_export(self):
return False return False
@ -236,6 +218,7 @@ class TrezorWallet(BIP32_HD_Wallet):
except: except:
give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.') give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.')
self.client = QtGuiTrezorClient(self.transport) self.client = QtGuiTrezorClient(self.transport)
self.client.twd = self.twd
self.client.set_tx_api(self) self.client.set_tx_api(self)
#self.client.clear_session()# TODO Doesn't work with firmware 1.1, returns proto.Failure #self.client.clear_session()# TODO Doesn't work with firmware 1.1, returns proto.Failure
self.client.bad = False self.client.bad = False
@ -306,7 +289,7 @@ class TrezorWallet(BIP32_HD_Wallet):
#except Exception, e: #except Exception, e:
# give_error(e) # give_error(e)
#finally: #finally:
# twd.emit(SIGNAL('trezor_done')) # twd.stop()
#return str(decrypted_msg) #return str(decrypted_msg)
def show_address(self, address): def show_address(self, address):
@ -322,7 +305,7 @@ class TrezorWallet(BIP32_HD_Wallet):
except Exception, e: except Exception, e:
give_error(e) give_error(e)
finally: finally:
twd.emit(SIGNAL('trezor_done')) self.twd.stop()
def sign_message(self, address, message, password): def sign_message(self, address, message, password):
if not self.check_proper_device(): if not self.check_proper_device():
@ -337,21 +320,16 @@ class TrezorWallet(BIP32_HD_Wallet):
except Exception, e: except Exception, e:
give_error(e) give_error(e)
finally: finally:
twd.emit(SIGNAL('trezor_done')) self.twd.stop()
b64_msg_sig = b64encode(msg_sig.signature) b64_msg_sig = b64encode(msg_sig.signature)
return str(b64_msg_sig) return str(b64_msg_sig)
def sign_transaction(self, tx, password): def sign_transaction(self, tx, password):
# the tx is signed by trezor_sign, in the GUI thread
if tx.error:
raise BaseException(tx.error)
def trezor_sign(self, tx):
if tx.is_complete(): if tx.is_complete():
return return
if not self.check_proper_device(): if not self.check_proper_device():
give_error('Wrong device or password') give_error('Wrong device or password')
client = self.get_client()
inputs = self.tx_inputs(tx) inputs = self.tx_inputs(tx)
outputs = self.tx_outputs(tx) outputs = self.tx_outputs(tx)
try: try:
@ -359,16 +337,15 @@ class TrezorWallet(BIP32_HD_Wallet):
except Exception, e: except Exception, e:
give_error(e) give_error(e)
finally: finally:
twd.emit(SIGNAL('trezor_done')) self.twd.stop()
values = [i['value'] for i in tx.inputs] #values = [i['value'] for i in tx.inputs]
raw = signed_tx.encode('hex') raw = signed_tx.encode('hex')
tx.update(raw) tx.update(raw)
for i, txinput in enumerate(tx.inputs): #for i, txinput in enumerate(tx.inputs):
txinput['value'] = values[i] # txinput['value'] = values[i]
def tx_inputs(self, tx): def tx_inputs(self, tx):
inputs = [] inputs = []
for txinput in tx.inputs: for txinput in tx.inputs:
txinputtype = types.TxInputType() txinputtype = types.TxInputType()
if ('is_coinbase' in txinput and txinput['is_coinbase']): if ('is_coinbase' in txinput and txinput['is_coinbase']):
@ -442,6 +419,7 @@ class TrezorWallet(BIP32_HD_Wallet):
def get_tx(self, tx_hash): def get_tx(self, tx_hash):
tx = self.transactions[tx_hash] tx = self.transactions[tx_hash]
tx.deserialize()
return self.electrum_tx_to_txtype(tx) return self.electrum_tx_to_txtype(tx)
def check_proper_device(self): def check_proper_device(self):
@ -461,10 +439,10 @@ class TrezorWallet(BIP32_HD_Wallet):
return self.proper_device return self.proper_device
class TrezorQtGuiMixin(object): class TrezorGuiMixin(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TrezorQtGuiMixin, self).__init__(*args, **kwargs) super(TrezorGuiMixin, self).__init__(*args, **kwargs)
def callback_ButtonRequest(self, msg): def callback_ButtonRequest(self, msg):
if msg.code == 3: if msg.code == 3:
@ -477,7 +455,7 @@ class TrezorQtGuiMixin(object):
message = "Confirm address on Trezor device to continue" message = "Confirm address on Trezor device to continue"
else: else:
message = "Check Trezor device to continue" message = "Check Trezor device to continue"
twd.start(message) self.twd.show_message(message)
return proto.ButtonAck() return proto.ButtonAck()
def callback_PinMatrixRequest(self, msg): def callback_PinMatrixRequest(self, msg):
@ -489,17 +467,15 @@ class TrezorQtGuiMixin(object):
desc = 'new PIN again' desc = 'new PIN again'
else: else:
desc = 'PIN' desc = 'PIN'
pin = self.twd.get_pin("Please enter Trezor %s" % desc)
pin = self.pin_dialog(msg="Please enter Trezor %s" % desc)
if not pin: if not pin:
return proto.Cancel() return proto.Cancel()
return proto.PinMatrixAck(pin=pin) return proto.PinMatrixAck(pin=pin)
def callback_PassphraseRequest(self, req): def callback_PassphraseRequest(self, req):
msg = _("Please enter your Trezor passphrase.") msg = _("Please enter your Trezor passphrase.")
passphrase = trezor_passphrase_dialog(msg) passphrase = self.twd.get_passphrase(msg)
if passphrase is None: if passphrase is None:
QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
return proto.Cancel() return proto.Cancel()
return proto.PassphraseAck(passphrase=passphrase) return proto.PassphraseAck(passphrase=passphrase)
@ -509,47 +485,85 @@ class TrezorQtGuiMixin(object):
word = raw_input() word = raw_input()
return proto.WordAck(word=word) return proto.WordAck(word=word)
def pin_dialog(self, msg):
class TrezorQtHandler:
def __init__(self, win):
self.win = win
self.win.connect(win, SIGNAL('trezor_done'), self.dialog_stop)
self.win.connect(win, SIGNAL('message_dialog'), self.message_dialog)
self.win.connect(win, SIGNAL('pin_dialog'), self.pin_dialog)
self.win.connect(win, SIGNAL('passphrase_dialog'), self.passphrase_dialog)
self.done = threading.Event()
def stop(self):
self.win.emit(SIGNAL('trezor_done'))
def show_message(self, msg):
self.message = msg
self.win.emit(SIGNAL('message_dialog'))
def get_pin(self, msg):
self.done.clear()
self.message = msg
self.win.emit(SIGNAL('pin_dialog'))
self.done.wait()
return self.response
def get_passphrase(self, msg):
self.done.clear()
self.message = msg
self.win.emit(SIGNAL('passphrase_dialog'))
self.done.wait()
return self.passphrase
def pin_dialog(self):
d = QDialog(None) d = QDialog(None)
d.setModal(1) d.setModal(1)
d.setWindowTitle(_("Enter PIN")) d.setWindowTitle(_("Enter PIN"))
d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
matrix = PinMatrixWidget() matrix = PinMatrixWidget()
vbox = QVBoxLayout() vbox = QVBoxLayout()
vbox.addWidget(QLabel(msg)) vbox.addWidget(QLabel(self.message))
vbox.addWidget(matrix) vbox.addWidget(matrix)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
d.setLayout(vbox) d.setLayout(vbox)
if not d.exec_():
self.response = None
self.response = str(matrix.get_value())
self.done.set()
if not d.exec_(): return def passphrase_dialog(self):
return str(matrix.get_value()) from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
d = QDialog()
d.setModal(1)
d.setLayout(make_password_dialog(d, None, self.message, False))
confirmed, p, passphrase = run_password_dialog(d, None, None)
if not confirmed:
QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
self.passphrase = None
else:
if passphrase is None:
passphrase = '' # Even blank string is valid Trezor passphrase
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase))
self.done.set()
class TrezorWaitingDialog(QThread): def message_dialog(self):
def __init__(self):
QThread.__init__(self)
self.waiting = False
def start(self, message):
self.d = QDialog() self.d = QDialog()
self.d.setModal(1) self.d.setModal(1)
self.d.setWindowTitle('Please Check Trezor Device') self.d.setWindowTitle('Please Check Trezor Device')
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
self.d.connect(twd, SIGNAL('trezor_done'), self.stop)
def stop(self): def dialog_stop(self):
self.d.hide() self.d.hide()
self.waiting = False
if TREZOR: if TREZOR:
class QtGuiTrezorClient(ProtocolMixin, TrezorQtGuiMixin, BaseClient): class QtGuiTrezorClient(ProtocolMixin, TrezorGuiMixin, BaseClient):
def call_raw(self, msg): def call_raw(self, msg):
try: try:
resp = BaseClient.call_raw(self, msg) resp = BaseClient.call_raw(self, msg)
@ -558,6 +572,3 @@ if TREZOR:
raise raise
return resp return resp
twd = TrezorWaitingDialog()