plugins: separate GUIs using child classes
This commit is contained in:
parent
175fdbcac6
commit
2c0489c809
|
@ -107,7 +107,6 @@ expiration_values = [
|
|||
|
||||
|
||||
class ElectrumWindow(QMainWindow, PrintError):
|
||||
labelsChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, config, network, gui_object):
|
||||
QMainWindow.__init__(self)
|
||||
|
@ -157,7 +156,6 @@ class ElectrumWindow(QMainWindow, PrintError):
|
|||
|
||||
self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
|
||||
self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
|
||||
self.labelsChanged.connect(self.update_tabs)
|
||||
self.history_list.setFocus(True)
|
||||
|
||||
# network callbacks
|
||||
|
|
|
@ -40,6 +40,7 @@ class Plugins(PrintError):
|
|||
|
||||
self.plugins = {}
|
||||
self.network = None
|
||||
self.gui_name = gui_name
|
||||
self.descriptions = plugins.descriptions
|
||||
for item in self.descriptions:
|
||||
name = item['name']
|
||||
|
@ -66,7 +67,15 @@ class Plugins(PrintError):
|
|||
p = imp.load_source(full_name, path)
|
||||
else:
|
||||
p = __import__(full_name, fromlist=['electrum_plugins'])
|
||||
plugin = p.Plugin(self, config, name)
|
||||
|
||||
if self.gui_name == 'qt':
|
||||
klass = p.QtPlugin
|
||||
elif self.gui_name == 'cmdline':
|
||||
klass = p.CmdlinePlugin
|
||||
else:
|
||||
return
|
||||
|
||||
plugin = klass(self, config, name)
|
||||
if self.network:
|
||||
self.network.add_jobs(plugin.thread_jobs())
|
||||
self.plugins[name] = plugin
|
||||
|
|
|
@ -25,7 +25,7 @@ except ImportError:
|
|||
print_error('Audio MODEM is not found.')
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
class QtPlugin(BasePlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
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
|
||||
from struct import pack,unpack
|
||||
|
@ -7,7 +5,6 @@ from sys import stderr
|
|||
from time import sleep
|
||||
|
||||
import electrum
|
||||
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
|
||||
from electrum.account import BIP32_Account
|
||||
from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160
|
||||
from electrum.i18n import _
|
||||
|
@ -32,96 +29,6 @@ try:
|
|||
except ImportError:
|
||||
BTCHIP = False
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
self._is_available = self._init()
|
||||
self.wallet = None
|
||||
self.handler = None
|
||||
|
||||
def constructor(self, s):
|
||||
return BTChipWallet(s)
|
||||
|
||||
def _init(self):
|
||||
return BTCHIP
|
||||
|
||||
def is_available(self):
|
||||
if not self._is_available:
|
||||
return False
|
||||
if not self.wallet:
|
||||
return False
|
||||
if self.wallet.storage.get('wallet_type') != 'btchip':
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
self.wallet.storage.put('use_' + self.name, enabled)
|
||||
|
||||
def is_enabled(self):
|
||||
if not self.is_available():
|
||||
return False
|
||||
if self.wallet.has_seed():
|
||||
return False
|
||||
return True
|
||||
|
||||
def btchip_is_connected(self):
|
||||
try:
|
||||
self.wallet.get_client().getFirmwareVersion()
|
||||
except:
|
||||
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
|
||||
if self.handler is None:
|
||||
self.handler = BTChipQTHandler(window)
|
||||
if self.btchip_is_connected():
|
||||
if not self.wallet.check_proper_device():
|
||||
QMessageBox.information(window, _('Error'), _("This wallet does not match your Ledger device"), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
else:
|
||||
QMessageBox.information(window, _('Error'), _("Ledger device not detected.\nContinuing in watching-only mode."), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
|
||||
@hook
|
||||
def close_wallet(self):
|
||||
self.wallet = None
|
||||
|
||||
@hook
|
||||
def installwizard_load_wallet(self, wallet, window):
|
||||
if type(wallet) != BTChipWallet:
|
||||
return
|
||||
self.load_wallet(wallet, window)
|
||||
|
||||
@hook
|
||||
def installwizard_restore(self, wizard, storage):
|
||||
if storage.get('wallet_type') != 'btchip':
|
||||
return
|
||||
wallet = BTChipWallet(storage)
|
||||
try:
|
||||
wallet.create_main_account(None)
|
||||
except BaseException as e:
|
||||
QMessageBox.information(None, _('Error'), str(e), _('OK'))
|
||||
return
|
||||
return wallet
|
||||
|
||||
@hook
|
||||
def sign_tx(self, window, tx):
|
||||
tx.error = None
|
||||
try:
|
||||
self.wallet.sign_transaction(tx, None)
|
||||
except Exception as e:
|
||||
tx.error = str(e)
|
||||
|
||||
class BTChipWallet(BIP32_HD_Wallet):
|
||||
wallet_type = 'btchip'
|
||||
|
@ -517,6 +424,98 @@ class BTChipWallet(BIP32_HD_Wallet):
|
|||
return False, None, None
|
||||
return True, response, response
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
self._is_available = self._init()
|
||||
self.wallet = None
|
||||
self.handler = None
|
||||
|
||||
def constructor(self, s):
|
||||
return BTChipWallet(s)
|
||||
|
||||
def _init(self):
|
||||
return BTCHIP
|
||||
|
||||
def is_available(self):
|
||||
if not self._is_available:
|
||||
return False
|
||||
if not self.wallet:
|
||||
return False
|
||||
if self.wallet.storage.get('wallet_type') != 'btchip':
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
self.wallet.storage.put('use_' + self.name, enabled)
|
||||
|
||||
def is_enabled(self):
|
||||
if not self.is_available():
|
||||
return False
|
||||
if self.wallet.has_seed():
|
||||
return False
|
||||
return True
|
||||
|
||||
def btchip_is_connected(self):
|
||||
try:
|
||||
self.wallet.get_client().getFirmwareVersion()
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
@hook
|
||||
def close_wallet(self):
|
||||
self.wallet = None
|
||||
|
||||
@hook
|
||||
def installwizard_load_wallet(self, wallet, window):
|
||||
if type(wallet) != BTChipWallet:
|
||||
return
|
||||
self.load_wallet(wallet, window)
|
||||
|
||||
@hook
|
||||
def installwizard_restore(self, wizard, storage):
|
||||
if storage.get('wallet_type') != 'btchip':
|
||||
return
|
||||
wallet = BTChipWallet(storage)
|
||||
try:
|
||||
wallet.create_main_account(None)
|
||||
except BaseException as e:
|
||||
QMessageBox.information(None, _('Error'), str(e), _('OK'))
|
||||
return
|
||||
return wallet
|
||||
|
||||
@hook
|
||||
def sign_tx(self, window, tx):
|
||||
tx.error = None
|
||||
try:
|
||||
self.wallet.sign_transaction(tx, None)
|
||||
except Exception as e:
|
||||
tx.error = str(e)
|
||||
|
||||
from PyQt4.Qt import QApplication, QMessageBox, QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, QThread, SIGNAL
|
||||
import PyQt4.QtCore as QtCore
|
||||
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
|
||||
|
||||
class QtPlugin(Plugin):
|
||||
|
||||
@hook
|
||||
def load_wallet(self, wallet, window):
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
if self.handler is None:
|
||||
self.handler = BTChipQTHandler(window)
|
||||
if self.btchip_is_connected():
|
||||
if not self.wallet.check_proper_device():
|
||||
QMessageBox.information(window, _('Error'), _("This wallet does not match your Ledger device"), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
else:
|
||||
QMessageBox.information(window, _('Error'), _("Ledger device not detected.\nContinuing in watching-only mode."), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
|
||||
|
||||
class BTChipQTHandler:
|
||||
|
||||
def __init__(self, win):
|
||||
|
@ -563,6 +562,15 @@ class BTChipQTHandler:
|
|||
self.d.hide()
|
||||
self.d = None
|
||||
|
||||
class CmdlinePlugin(Plugin):
|
||||
@hook
|
||||
def cmdline_load_wallet(self, wallet):
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
if self.handler is None:
|
||||
self.handler = BTChipCmdLineHandler()
|
||||
|
||||
|
||||
class BTChipCmdLineHandler:
|
||||
|
||||
def stop(self):
|
||||
|
|
|
@ -79,7 +79,7 @@ class Listener(util.DaemonThread):
|
|||
time.sleep(30)
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
class QtPlugin(BasePlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
|
|
|
@ -101,7 +101,7 @@ class Processor(threading.Thread):
|
|||
s.quit()
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
class QtPlugin(BasePlugin):
|
||||
|
||||
def fullname(self):
|
||||
return 'Email'
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
|
||||
from datetime import datetime
|
||||
import inspect
|
||||
import requests
|
||||
|
@ -17,8 +14,7 @@ from electrum.plugins import BasePlugin, hook
|
|||
from electrum.i18n import _
|
||||
from electrum.util import PrintError, ThreadJob, timestamp_to_datetime
|
||||
from electrum.util import format_satoshis
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.amountedit import AmountEdit
|
||||
|
||||
|
||||
# See https://en.wikipedia.org/wiki/ISO_4217
|
||||
CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0,
|
||||
|
@ -301,25 +297,79 @@ class Plugin(BasePlugin, ThreadJob):
|
|||
|
||||
|
||||
|
||||
def exchange_rate(self):
|
||||
'''Returns None, or the exchange rate as a Decimal'''
|
||||
rate = self.exchange.quotes.get(self.ccy)
|
||||
if rate:
|
||||
return Decimal(rate)
|
||||
|
||||
@hook
|
||||
def on_new_window(self, window):
|
||||
# Additional send and receive edit boxes
|
||||
send_e = AmountEdit(self.config_ccy)
|
||||
window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft)
|
||||
window.amount_e.frozen.connect(
|
||||
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
|
||||
receive_e = AmountEdit(self.config_ccy)
|
||||
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
|
||||
window.fiat_send_e = send_e
|
||||
window.fiat_receive_e = receive_e
|
||||
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
|
||||
self.connect_fields(window, window.receive_amount_e, receive_e, None)
|
||||
window.history_list.refresh_headers()
|
||||
window.update_status()
|
||||
window.connect(window.app, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window))
|
||||
window.connect(window.app, SIGNAL('new_fx_history'), lambda: self.on_fx_history(window))
|
||||
window.connect(window.app, SIGNAL('close_fx_plugin'), lambda: self.restore_window(window))
|
||||
window.connect(window.app, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
|
||||
def format_amount_and_units(self, btc_balance):
|
||||
rate = self.exchange_rate()
|
||||
return '' if rate is None else " (%s %s)" % (self.value_str(btc_balance, rate), self.ccy)
|
||||
|
||||
@hook
|
||||
def get_fiat_status_text(self, btc_balance):
|
||||
rate = self.exchange_rate()
|
||||
return _(" (No FX rate available)") if rate is None else "1 BTC~%s %s" % (self.value_str(COIN, rate), self.ccy)
|
||||
|
||||
def get_historical_rates(self):
|
||||
if self.show_history():
|
||||
self.exchange.get_historical_rates(self.ccy)
|
||||
|
||||
def requires_settings(self):
|
||||
return True
|
||||
|
||||
def value_str(self, satoshis, rate):
|
||||
if satoshis is None: # Can happen with incomplete history
|
||||
return _("Unknown")
|
||||
if rate:
|
||||
value = Decimal(satoshis) / COIN * Decimal(rate)
|
||||
return "%s" % (self.ccy_amount_str(value, True))
|
||||
return _("No data")
|
||||
|
||||
@hook
|
||||
def historical_value_str(self, satoshis, d_t):
|
||||
rate = self.exchange.historical_rate(self.ccy, d_t)
|
||||
# Frequently there is no rate for today, until tomorrow :)
|
||||
# Use spot quotes in that case
|
||||
if rate is None and (datetime.today().date() - d_t.date()).days <= 2:
|
||||
rate = self.exchange.quotes.get(self.ccy)
|
||||
self.history_used_spot = True
|
||||
return self.value_str(satoshis, rate)
|
||||
|
||||
@hook
|
||||
def history_tab_headers(self, headers):
|
||||
if self.show_history():
|
||||
headers.extend(['%s '%self.ccy + _('Amount'), '%s '%self.ccy + _('Balance')])
|
||||
|
||||
@hook
|
||||
def history_tab_update_begin(self):
|
||||
self.history_used_spot = False
|
||||
|
||||
@hook
|
||||
def history_tab_update(self, tx, entry):
|
||||
if not self.show_history():
|
||||
return
|
||||
tx_hash, conf, value, timestamp, balance = tx
|
||||
if conf <= 0:
|
||||
date = datetime.today()
|
||||
else:
|
||||
date = timestamp_to_datetime(timestamp)
|
||||
for amount in [value, balance]:
|
||||
text = self.historical_value_str(amount, date)
|
||||
entry.append(text)
|
||||
|
||||
|
||||
|
||||
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.amountedit import AmountEdit
|
||||
|
||||
|
||||
class QtPlugin(Plugin):
|
||||
|
||||
|
||||
def connect_fields(self, window, btc_e, fiat_e, fee_e):
|
||||
|
@ -412,68 +462,25 @@ class Plugin(BasePlugin, ThreadJob):
|
|||
combo.blockSignals(False)
|
||||
combo.setCurrentIndex(combo.findText(self.ccy))
|
||||
|
||||
def exchange_rate(self):
|
||||
'''Returns None, or the exchange rate as a Decimal'''
|
||||
rate = self.exchange.quotes.get(self.ccy)
|
||||
if rate:
|
||||
return Decimal(rate)
|
||||
|
||||
@hook
|
||||
def format_amount_and_units(self, btc_balance):
|
||||
rate = self.exchange_rate()
|
||||
return '' if rate is None else " (%s %s)" % (self.value_str(btc_balance, rate), self.ccy)
|
||||
|
||||
@hook
|
||||
def get_fiat_status_text(self, btc_balance):
|
||||
rate = self.exchange_rate()
|
||||
return _(" (No FX rate available)") if rate is None else "1 BTC~%s %s" % (self.value_str(COIN, rate), self.ccy)
|
||||
|
||||
def get_historical_rates(self):
|
||||
if self.show_history():
|
||||
self.exchange.get_historical_rates(self.ccy)
|
||||
|
||||
def requires_settings(self):
|
||||
return True
|
||||
|
||||
def value_str(self, satoshis, rate):
|
||||
if satoshis is None: # Can happen with incomplete history
|
||||
return _("Unknown")
|
||||
if rate:
|
||||
value = Decimal(satoshis) / COIN * Decimal(rate)
|
||||
return "%s" % (self.ccy_amount_str(value, True))
|
||||
return _("No data")
|
||||
|
||||
@hook
|
||||
def historical_value_str(self, satoshis, d_t):
|
||||
rate = self.exchange.historical_rate(self.ccy, d_t)
|
||||
# Frequently there is no rate for today, until tomorrow :)
|
||||
# Use spot quotes in that case
|
||||
if rate is None and (datetime.today().date() - d_t.date()).days <= 2:
|
||||
rate = self.exchange.quotes.get(self.ccy)
|
||||
self.history_used_spot = True
|
||||
return self.value_str(satoshis, rate)
|
||||
|
||||
@hook
|
||||
def history_tab_headers(self, headers):
|
||||
if self.show_history():
|
||||
headers.extend(['%s '%self.ccy + _('Amount'), '%s '%self.ccy + _('Balance')])
|
||||
|
||||
@hook
|
||||
def history_tab_update_begin(self):
|
||||
self.history_used_spot = False
|
||||
|
||||
@hook
|
||||
def history_tab_update(self, tx, entry):
|
||||
if not self.show_history():
|
||||
return
|
||||
tx_hash, conf, value, timestamp, balance = tx
|
||||
if conf <= 0:
|
||||
date = datetime.today()
|
||||
else:
|
||||
date = timestamp_to_datetime(timestamp)
|
||||
for amount in [value, balance]:
|
||||
text = self.historical_value_str(amount, date)
|
||||
entry.append(text)
|
||||
def on_new_window(self, window):
|
||||
# Additional send and receive edit boxes
|
||||
send_e = AmountEdit(self.config_ccy)
|
||||
window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft)
|
||||
window.amount_e.frozen.connect(
|
||||
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
|
||||
receive_e = AmountEdit(self.config_ccy)
|
||||
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
|
||||
window.fiat_send_e = send_e
|
||||
window.fiat_receive_e = receive_e
|
||||
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
|
||||
self.connect_fields(window, window.receive_amount_e, receive_e, None)
|
||||
window.history_list.refresh_headers()
|
||||
window.update_status()
|
||||
window.connect(window.app, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window))
|
||||
window.connect(window.app, SIGNAL('new_fx_history'), lambda: self.on_fx_history(window))
|
||||
window.connect(window.app, SIGNAL('close_fx_plugin'), lambda: self.restore_window(window))
|
||||
window.connect(window.app, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
|
||||
|
||||
def settings_widget(self, window):
|
||||
return EnterButton(_('Settings'), self.settings_dialog)
|
||||
|
|
|
@ -28,7 +28,7 @@ from electrum.i18n import _
|
|||
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
class QtPlugin(BasePlugin):
|
||||
|
||||
button_label = _("Verify GA instant")
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@ import threading
|
|||
import re
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
|
||||
import PyQt4.QtCore as QtCore
|
||||
|
||||
import electrum
|
||||
from electrum import bitcoin
|
||||
|
@ -22,14 +20,10 @@ from electrum.wallet import BIP32_HD_Wallet
|
|||
from electrum.util import print_error, print_msg
|
||||
from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root
|
||||
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
|
||||
from electrum_gui.qt.installwizard import InstallWizard
|
||||
|
||||
try:
|
||||
from keepkeylib.client import types
|
||||
from keepkeylib.client import proto, BaseClient, ProtocolMixin
|
||||
from keepkeylib.qt.pinmatrix import PinMatrixWidget
|
||||
from keepkeylib.transport import ConnectionError
|
||||
from keepkeylib.transport_hid import HidTransport
|
||||
KEEPKEY = True
|
||||
|
@ -47,307 +41,6 @@ def give_error(message):
|
|||
raise Exception(message)
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
self._is_available = self._init()
|
||||
self.wallet = None
|
||||
self.handler = None
|
||||
self.client = None
|
||||
self.transport = None
|
||||
|
||||
def constructor(self, s):
|
||||
return KeepKeyWallet(s)
|
||||
|
||||
def _init(self):
|
||||
return KEEPKEY
|
||||
|
||||
def is_available(self):
|
||||
if not self._is_available:
|
||||
return False
|
||||
if not self.wallet:
|
||||
return False
|
||||
if self.wallet.storage.get('wallet_type') != 'keepkey':
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
self.wallet.storage.put('use_' + self.name, enabled)
|
||||
|
||||
def is_enabled(self):
|
||||
if not self.is_available():
|
||||
return False
|
||||
if self.wallet.has_seed():
|
||||
return False
|
||||
return True
|
||||
|
||||
def compare_version(self, major, minor=0, patch=0):
|
||||
features = self.get_client().features
|
||||
v = [features.major_version, features.minor_version, features.patch_version]
|
||||
self.print_error('firmware version', v)
|
||||
return cmp(v, [major, minor, patch])
|
||||
|
||||
def atleast_version(self, major, minor=0, patch=0):
|
||||
return self.compare_version(major, minor, patch) >= 0
|
||||
|
||||
def get_client(self):
|
||||
if not KEEPKEY:
|
||||
give_error('please install github.com/keepkey/python-keepkey')
|
||||
|
||||
if not self.client or self.client.bad:
|
||||
d = HidTransport.enumerate()
|
||||
if not d:
|
||||
give_error('Could not connect to your KeepKey. Please verify the cable is connected and that no other app is using it.')
|
||||
self.transport = HidTransport(d[0])
|
||||
self.client = QtGuiKeepKeyClient(self.transport)
|
||||
self.client.handler = self.handler
|
||||
self.client.set_tx_api(self)
|
||||
self.client.bad = False
|
||||
if not self.atleast_version(1, 0, 0):
|
||||
self.client = None
|
||||
give_error('Outdated KeepKey firmware. Please update the firmware from https://www.keepkey.com')
|
||||
return self.client
|
||||
|
||||
@hook
|
||||
def close_wallet(self):
|
||||
print_error("keepkey: clear session")
|
||||
if self.client:
|
||||
self.client.clear_session()
|
||||
self.client.transport.close()
|
||||
self.client = None
|
||||
self.wallet = None
|
||||
|
||||
@hook
|
||||
def cmdline_load_wallet(self, wallet):
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
if self.handler is None:
|
||||
self.handler = KeepKeyCmdLineHandler()
|
||||
|
||||
@hook
|
||||
def load_wallet(self, wallet, window):
|
||||
self.print_error("load_wallet")
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
self.keepkey_button = StatusBarButton(QIcon(":icons/keepkey.png"), _("KeepKey"), partial(self.settings_dialog, window))
|
||||
if type(window) is ElectrumWindow:
|
||||
window.statusBar().addPermanentWidget(self.keepkey_button)
|
||||
if self.handler is None:
|
||||
self.handler = KeepKeyQtHandler(window)
|
||||
try:
|
||||
self.get_client().ping('t')
|
||||
except BaseException as e:
|
||||
QMessageBox.information(window, _('Error'), _("KeepKey device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
return
|
||||
if self.wallet.addresses() and not self.wallet.check_proper_device():
|
||||
QMessageBox.information(window, _('Error'), _("This wallet does not match your KeepKey device"), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
|
||||
@hook
|
||||
def installwizard_load_wallet(self, wallet, window):
|
||||
if type(wallet) != KeepKeyWallet:
|
||||
return
|
||||
self.load_wallet(wallet, window)
|
||||
|
||||
@hook
|
||||
def installwizard_restore(self, wizard, storage):
|
||||
if storage.get('wallet_type') != 'keepkey':
|
||||
return
|
||||
seed = wizard.enter_seed_dialog("Enter your KeepKey seed", None, func=lambda x:True)
|
||||
if not seed:
|
||||
return
|
||||
wallet = KeepKeyWallet(storage)
|
||||
self.wallet = wallet
|
||||
handler = KeepKeyQtHandler(wizard)
|
||||
passphrase = handler.get_passphrase(_("Please enter your KeepKey passphrase.") + '\n' + _("Press OK if you do not use one."))
|
||||
if passphrase is None:
|
||||
return
|
||||
password = wizard.password_dialog()
|
||||
wallet.add_seed(seed, password)
|
||||
wallet.add_cosigner_seed(seed, 'x/', password, passphrase)
|
||||
wallet.create_main_account(password)
|
||||
# disable keepkey plugin
|
||||
self.set_enabled(False)
|
||||
return wallet
|
||||
|
||||
@hook
|
||||
def receive_menu(self, menu, addrs):
|
||||
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
|
||||
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
|
||||
|
||||
def show_address(self, address):
|
||||
if not self.wallet.check_proper_device():
|
||||
give_error('Wrong device or password')
|
||||
try:
|
||||
address_path = self.wallet.address_id(address)
|
||||
address_n = self.get_client().expand_path(address_path)
|
||||
except Exception, e:
|
||||
give_error(e)
|
||||
try:
|
||||
self.get_client().get_address('Bitcoin', address_n, True)
|
||||
except Exception, e:
|
||||
give_error(e)
|
||||
finally:
|
||||
self.handler.stop()
|
||||
|
||||
|
||||
def settings_dialog(self, window):
|
||||
try:
|
||||
device_id = self.get_client().get_device_id()
|
||||
except BaseException as e:
|
||||
window.show_message(str(e))
|
||||
return
|
||||
get_label = lambda: self.get_client().features.label
|
||||
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
|
||||
d = QDialog()
|
||||
layout = QGridLayout(d)
|
||||
layout.addWidget(QLabel("KeepKey Options"),0,0)
|
||||
layout.addWidget(QLabel("ID:"),1,0)
|
||||
layout.addWidget(QLabel(" %s" % device_id),1,1)
|
||||
|
||||
def modify_label():
|
||||
response = QInputDialog().getText(None, "Set New KeepKey Label", "New KeepKey Label: (upon submission confirm on KeepKey)")
|
||||
if not response[1]:
|
||||
return
|
||||
new_label = str(response[0])
|
||||
self.handler.show_message("Please confirm label change on KeepKey")
|
||||
status = self.get_client().apply_settings(label=new_label)
|
||||
self.handler.stop()
|
||||
update_label()
|
||||
|
||||
current_label_label = QLabel()
|
||||
update_label()
|
||||
change_label_button = QPushButton("Modify")
|
||||
change_label_button.clicked.connect(modify_label)
|
||||
layout.addWidget(current_label_label,3,0)
|
||||
layout.addWidget(change_label_button,3,1)
|
||||
d.exec_()
|
||||
|
||||
|
||||
def sign_transaction(self, tx, prev_tx, xpub_path):
|
||||
self.prev_tx = prev_tx
|
||||
self.xpub_path = xpub_path
|
||||
client = self.get_client()
|
||||
inputs = self.tx_inputs(tx, True)
|
||||
outputs = self.tx_outputs(tx)
|
||||
try:
|
||||
signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1]
|
||||
except Exception, e:
|
||||
self.handler.stop()
|
||||
give_error(e)
|
||||
|
||||
self.handler.stop()
|
||||
|
||||
raw = signed_tx.encode('hex')
|
||||
tx.update_signatures(raw)
|
||||
|
||||
|
||||
def tx_inputs(self, tx, for_sig=False):
|
||||
inputs = []
|
||||
for txin in tx.inputs:
|
||||
txinputtype = types.TxInputType()
|
||||
if txin.get('is_coinbase'):
|
||||
prev_hash = "\0"*32
|
||||
prev_index = 0xffffffff # signed int -1
|
||||
else:
|
||||
if for_sig:
|
||||
x_pubkeys = txin['x_pubkeys']
|
||||
if len(x_pubkeys) == 1:
|
||||
x_pubkey = x_pubkeys[0]
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
xpub_n = self.get_client().expand_path(self.xpub_path[xpub])
|
||||
txinputtype.address_n.extend(xpub_n + s)
|
||||
else:
|
||||
def f(x_pubkey):
|
||||
if is_extended_pubkey(x_pubkey):
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
else:
|
||||
xpub = xpub_from_pubkey(x_pubkey.decode('hex'))
|
||||
s = []
|
||||
node = ckd_public.deserialize(xpub)
|
||||
return types.HDNodePathType(node=node, address_n=s)
|
||||
pubkeys = map(f, x_pubkeys)
|
||||
multisig = types.MultisigRedeemScriptType(
|
||||
pubkeys=pubkeys,
|
||||
signatures=map(lambda x: x.decode('hex') if x else '', txin.get('signatures')),
|
||||
m=txin.get('num_sig'),
|
||||
)
|
||||
txinputtype = types.TxInputType(
|
||||
script_type=types.SPENDMULTISIG,
|
||||
multisig= multisig
|
||||
)
|
||||
# find which key is mine
|
||||
for x_pubkey in x_pubkeys:
|
||||
if is_extended_pubkey(x_pubkey):
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
if xpub in self.xpub_path:
|
||||
xpub_n = self.get_client().expand_path(self.xpub_path[xpub])
|
||||
txinputtype.address_n.extend(xpub_n + s)
|
||||
break
|
||||
|
||||
prev_hash = unhexlify(txin['prevout_hash'])
|
||||
prev_index = txin['prevout_n']
|
||||
|
||||
txinputtype.prev_hash = prev_hash
|
||||
txinputtype.prev_index = prev_index
|
||||
|
||||
if 'scriptSig' in txin:
|
||||
script_sig = txin['scriptSig'].decode('hex')
|
||||
txinputtype.script_sig = script_sig
|
||||
|
||||
if 'sequence' in txin:
|
||||
sequence = txin['sequence']
|
||||
txinputtype.sequence = sequence
|
||||
|
||||
inputs.append(txinputtype)
|
||||
|
||||
return inputs
|
||||
|
||||
def tx_outputs(self, tx):
|
||||
outputs = []
|
||||
|
||||
for type, address, amount in tx.outputs:
|
||||
assert type == 'address'
|
||||
txoutputtype = types.TxOutputType()
|
||||
if self.wallet.is_change(address):
|
||||
address_path = self.wallet.address_id(address)
|
||||
address_n = self.get_client().expand_path(address_path)
|
||||
txoutputtype.address_n.extend(address_n)
|
||||
else:
|
||||
txoutputtype.address = address
|
||||
txoutputtype.amount = amount
|
||||
addrtype, hash_160 = bc_address_to_hash_160(address)
|
||||
if addrtype == 0:
|
||||
txoutputtype.script_type = types.PAYTOADDRESS
|
||||
elif addrtype == 5:
|
||||
txoutputtype.script_type = types.PAYTOSCRIPTHASH
|
||||
else:
|
||||
raise BaseException('addrtype')
|
||||
outputs.append(txoutputtype)
|
||||
|
||||
return outputs
|
||||
|
||||
def electrum_tx_to_txtype(self, tx):
|
||||
t = types.TransactionType()
|
||||
d = deserialize(tx.raw)
|
||||
t.version = d['version']
|
||||
t.lock_time = d['lockTime']
|
||||
inputs = self.tx_inputs(tx)
|
||||
t.inputs.extend(inputs)
|
||||
for vout in d['outputs']:
|
||||
o = t.bin_outputs.add()
|
||||
o.amount = vout['value']
|
||||
o.script_pubkey = vout['scriptPubKey'].decode('hex')
|
||||
return t
|
||||
|
||||
def get_tx(self, tx_hash):
|
||||
tx = self.prev_tx[tx_hash]
|
||||
tx.deserialize()
|
||||
return self.electrum_tx_to_txtype(tx)
|
||||
|
||||
|
||||
|
||||
|
||||
class KeepKeyWallet(BIP32_HD_Wallet):
|
||||
|
@ -513,6 +206,325 @@ class KeepKeyWallet(BIP32_HD_Wallet):
|
|||
return self.proper_device
|
||||
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
self._is_available = self._init()
|
||||
self.wallet = None
|
||||
self.handler = None
|
||||
self.client = None
|
||||
self.transport = None
|
||||
|
||||
def constructor(self, s):
|
||||
return KeepKeyWallet(s)
|
||||
|
||||
def _init(self):
|
||||
return KEEPKEY
|
||||
|
||||
def is_available(self):
|
||||
if not self._is_available:
|
||||
return False
|
||||
if not self.wallet:
|
||||
return False
|
||||
if self.wallet.storage.get('wallet_type') != 'keepkey':
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
self.wallet.storage.put('use_' + self.name, enabled)
|
||||
|
||||
def is_enabled(self):
|
||||
if not self.is_available():
|
||||
return False
|
||||
if self.wallet.has_seed():
|
||||
return False
|
||||
return True
|
||||
|
||||
def compare_version(self, major, minor=0, patch=0):
|
||||
features = self.get_client().features
|
||||
v = [features.major_version, features.minor_version, features.patch_version]
|
||||
self.print_error('firmware version', v)
|
||||
return cmp(v, [major, minor, patch])
|
||||
|
||||
def atleast_version(self, major, minor=0, patch=0):
|
||||
return self.compare_version(major, minor, patch) >= 0
|
||||
|
||||
def get_client(self):
|
||||
if not KEEPKEY:
|
||||
give_error('please install github.com/keepkey/python-keepkey')
|
||||
|
||||
if not self.client or self.client.bad:
|
||||
d = HidTransport.enumerate()
|
||||
if not d:
|
||||
give_error('Could not connect to your KeepKey. Please verify the cable is connected and that no other app is using it.')
|
||||
self.transport = HidTransport(d[0])
|
||||
self.client = QtGuiKeepKeyClient(self.transport)
|
||||
self.client.handler = self.handler
|
||||
self.client.set_tx_api(self)
|
||||
self.client.bad = False
|
||||
if not self.atleast_version(1, 0, 0):
|
||||
self.client = None
|
||||
give_error('Outdated KeepKey firmware. Please update the firmware from https://www.keepkey.com')
|
||||
return self.client
|
||||
|
||||
@hook
|
||||
def close_wallet(self):
|
||||
print_error("keepkey: clear session")
|
||||
if self.client:
|
||||
self.client.clear_session()
|
||||
self.client.transport.close()
|
||||
self.client = None
|
||||
self.wallet = None
|
||||
|
||||
|
||||
def show_address(self, address):
|
||||
if not self.wallet.check_proper_device():
|
||||
give_error('Wrong device or password')
|
||||
try:
|
||||
address_path = self.wallet.address_id(address)
|
||||
address_n = self.get_client().expand_path(address_path)
|
||||
except Exception, e:
|
||||
give_error(e)
|
||||
try:
|
||||
self.get_client().get_address('Bitcoin', address_n, True)
|
||||
except Exception, e:
|
||||
give_error(e)
|
||||
finally:
|
||||
self.handler.stop()
|
||||
|
||||
|
||||
def sign_transaction(self, tx, prev_tx, xpub_path):
|
||||
self.prev_tx = prev_tx
|
||||
self.xpub_path = xpub_path
|
||||
client = self.get_client()
|
||||
inputs = self.tx_inputs(tx, True)
|
||||
outputs = self.tx_outputs(tx)
|
||||
try:
|
||||
signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1]
|
||||
except Exception, e:
|
||||
self.handler.stop()
|
||||
give_error(e)
|
||||
|
||||
self.handler.stop()
|
||||
|
||||
raw = signed_tx.encode('hex')
|
||||
tx.update_signatures(raw)
|
||||
|
||||
|
||||
def tx_inputs(self, tx, for_sig=False):
|
||||
inputs = []
|
||||
for txin in tx.inputs:
|
||||
txinputtype = types.TxInputType()
|
||||
if txin.get('is_coinbase'):
|
||||
prev_hash = "\0"*32
|
||||
prev_index = 0xffffffff # signed int -1
|
||||
else:
|
||||
if for_sig:
|
||||
x_pubkeys = txin['x_pubkeys']
|
||||
if len(x_pubkeys) == 1:
|
||||
x_pubkey = x_pubkeys[0]
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
xpub_n = self.get_client().expand_path(self.xpub_path[xpub])
|
||||
txinputtype.address_n.extend(xpub_n + s)
|
||||
else:
|
||||
def f(x_pubkey):
|
||||
if is_extended_pubkey(x_pubkey):
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
else:
|
||||
xpub = xpub_from_pubkey(x_pubkey.decode('hex'))
|
||||
s = []
|
||||
node = ckd_public.deserialize(xpub)
|
||||
return types.HDNodePathType(node=node, address_n=s)
|
||||
pubkeys = map(f, x_pubkeys)
|
||||
multisig = types.MultisigRedeemScriptType(
|
||||
pubkeys=pubkeys,
|
||||
signatures=map(lambda x: x.decode('hex') if x else '', txin.get('signatures')),
|
||||
m=txin.get('num_sig'),
|
||||
)
|
||||
txinputtype = types.TxInputType(
|
||||
script_type=types.SPENDMULTISIG,
|
||||
multisig= multisig
|
||||
)
|
||||
# find which key is mine
|
||||
for x_pubkey in x_pubkeys:
|
||||
if is_extended_pubkey(x_pubkey):
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
if xpub in self.xpub_path:
|
||||
xpub_n = self.get_client().expand_path(self.xpub_path[xpub])
|
||||
txinputtype.address_n.extend(xpub_n + s)
|
||||
break
|
||||
|
||||
prev_hash = unhexlify(txin['prevout_hash'])
|
||||
prev_index = txin['prevout_n']
|
||||
|
||||
txinputtype.prev_hash = prev_hash
|
||||
txinputtype.prev_index = prev_index
|
||||
|
||||
if 'scriptSig' in txin:
|
||||
script_sig = txin['scriptSig'].decode('hex')
|
||||
txinputtype.script_sig = script_sig
|
||||
|
||||
if 'sequence' in txin:
|
||||
sequence = txin['sequence']
|
||||
txinputtype.sequence = sequence
|
||||
|
||||
inputs.append(txinputtype)
|
||||
|
||||
return inputs
|
||||
|
||||
def tx_outputs(self, tx):
|
||||
outputs = []
|
||||
|
||||
for type, address, amount in tx.outputs:
|
||||
assert type == 'address'
|
||||
txoutputtype = types.TxOutputType()
|
||||
if self.wallet.is_change(address):
|
||||
address_path = self.wallet.address_id(address)
|
||||
address_n = self.get_client().expand_path(address_path)
|
||||
txoutputtype.address_n.extend(address_n)
|
||||
else:
|
||||
txoutputtype.address = address
|
||||
txoutputtype.amount = amount
|
||||
addrtype, hash_160 = bc_address_to_hash_160(address)
|
||||
if addrtype == 0:
|
||||
txoutputtype.script_type = types.PAYTOADDRESS
|
||||
elif addrtype == 5:
|
||||
txoutputtype.script_type = types.PAYTOSCRIPTHASH
|
||||
else:
|
||||
raise BaseException('addrtype')
|
||||
outputs.append(txoutputtype)
|
||||
|
||||
return outputs
|
||||
|
||||
def electrum_tx_to_txtype(self, tx):
|
||||
t = types.TransactionType()
|
||||
d = deserialize(tx.raw)
|
||||
t.version = d['version']
|
||||
t.lock_time = d['lockTime']
|
||||
inputs = self.tx_inputs(tx)
|
||||
t.inputs.extend(inputs)
|
||||
for vout in d['outputs']:
|
||||
o = t.bin_outputs.add()
|
||||
o.amount = vout['value']
|
||||
o.script_pubkey = vout['scriptPubKey'].decode('hex')
|
||||
return t
|
||||
|
||||
def get_tx(self, tx_hash):
|
||||
tx = self.prev_tx[tx_hash]
|
||||
tx.deserialize()
|
||||
return self.electrum_tx_to_txtype(tx)
|
||||
|
||||
|
||||
|
||||
|
||||
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
|
||||
import PyQt4.QtCore as QtCore
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
|
||||
from electrum_gui.qt.installwizard import InstallWizard
|
||||
from keepkeylib.qt.pinmatrix import PinMatrixWidget
|
||||
|
||||
|
||||
class QtPlugin(Plugin):
|
||||
|
||||
@hook
|
||||
def load_wallet(self, wallet, window):
|
||||
self.print_error("load_wallet")
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
self.keepkey_button = StatusBarButton(QIcon(":icons/keepkey.png"), _("KeepKey"), partial(self.settings_dialog, window))
|
||||
if type(window) is ElectrumWindow:
|
||||
window.statusBar().addPermanentWidget(self.keepkey_button)
|
||||
if self.handler is None:
|
||||
self.handler = KeepKeyQtHandler(window)
|
||||
try:
|
||||
self.get_client().ping('t')
|
||||
except BaseException as e:
|
||||
QMessageBox.information(window, _('Error'), _("KeepKey device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
return
|
||||
if self.wallet.addresses() and not self.wallet.check_proper_device():
|
||||
QMessageBox.information(window, _('Error'), _("This wallet does not match your KeepKey device"), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
|
||||
@hook
|
||||
def installwizard_load_wallet(self, wallet, window):
|
||||
if type(wallet) != KeepKeyWallet:
|
||||
return
|
||||
self.load_wallet(wallet, window)
|
||||
|
||||
@hook
|
||||
def installwizard_restore(self, wizard, storage):
|
||||
if storage.get('wallet_type') != 'keepkey':
|
||||
return
|
||||
seed = wizard.enter_seed_dialog("Enter your KeepKey seed", None, func=lambda x:True)
|
||||
if not seed:
|
||||
return
|
||||
wallet = KeepKeyWallet(storage)
|
||||
self.wallet = wallet
|
||||
handler = KeepKeyQtHandler(wizard)
|
||||
passphrase = handler.get_passphrase(_("Please enter your KeepKey passphrase.") + '\n' + _("Press OK if you do not use one."))
|
||||
if passphrase is None:
|
||||
return
|
||||
password = wizard.password_dialog()
|
||||
wallet.add_seed(seed, password)
|
||||
wallet.add_cosigner_seed(seed, 'x/', password, passphrase)
|
||||
wallet.create_main_account(password)
|
||||
# disable keepkey plugin
|
||||
self.set_enabled(False)
|
||||
return wallet
|
||||
|
||||
@hook
|
||||
def receive_menu(self, menu, addrs):
|
||||
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
|
||||
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
|
||||
|
||||
def settings_dialog(self, window):
|
||||
try:
|
||||
device_id = self.get_client().get_device_id()
|
||||
except BaseException as e:
|
||||
window.show_message(str(e))
|
||||
return
|
||||
get_label = lambda: self.get_client().features.label
|
||||
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
|
||||
d = QDialog()
|
||||
layout = QGridLayout(d)
|
||||
layout.addWidget(QLabel("KeepKey Options"),0,0)
|
||||
layout.addWidget(QLabel("ID:"),1,0)
|
||||
layout.addWidget(QLabel(" %s" % device_id),1,1)
|
||||
|
||||
def modify_label():
|
||||
response = QInputDialog().getText(None, "Set New KeepKey Label", "New KeepKey Label: (upon submission confirm on KeepKey)")
|
||||
if not response[1]:
|
||||
return
|
||||
new_label = str(response[0])
|
||||
self.handler.show_message("Please confirm label change on KeepKey")
|
||||
status = self.get_client().apply_settings(label=new_label)
|
||||
self.handler.stop()
|
||||
update_label()
|
||||
|
||||
current_label_label = QLabel()
|
||||
update_label()
|
||||
change_label_button = QPushButton("Modify")
|
||||
change_label_button.clicked.connect(modify_label)
|
||||
layout.addWidget(current_label_label,3,0)
|
||||
layout.addWidget(change_label_button,3,1)
|
||||
d.exec_()
|
||||
|
||||
|
||||
class CmdlinePlugin(Plugin):
|
||||
|
||||
@hook
|
||||
def cmdline_load_wallet(self, wallet):
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
if self.handler is None:
|
||||
self.handler = KeepKeyCmdLineHandler()
|
||||
|
||||
|
||||
|
||||
class KeepKeyGuiMixin(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -7,15 +7,6 @@ import sys
|
|||
import traceback
|
||||
from functools import partial
|
||||
|
||||
try:
|
||||
import PyQt4
|
||||
except Exception:
|
||||
sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
|
||||
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
import PyQt4.QtCore as QtCore
|
||||
import PyQt4.QtGui as QtGui
|
||||
import aes
|
||||
import base64
|
||||
|
||||
|
@ -23,8 +14,6 @@ import electrum
|
|||
from electrum.plugins import BasePlugin, hook
|
||||
from electrum.i18n import _
|
||||
|
||||
from electrum_gui.qt import HelpButton, EnterButton
|
||||
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
|
@ -32,34 +21,6 @@ class Plugin(BasePlugin):
|
|||
BasePlugin.__init__(self, parent, config, name)
|
||||
self.target_host = 'sync.bytesized-hosting.com:9090'
|
||||
self.wallets = {}
|
||||
self.obj = QObject()
|
||||
self.obj.connect(self.obj, SIGNAL('labels:pulled'), self.on_pulled)
|
||||
|
||||
@hook
|
||||
def on_new_window(self, window):
|
||||
wallet = window.wallet
|
||||
nonce = self.get_nonce(wallet)
|
||||
self.print_error("wallet", wallet.basename(), "nonce is", nonce)
|
||||
mpk = ''.join(sorted(wallet.get_master_public_keys().values()))
|
||||
if not mpk:
|
||||
return
|
||||
|
||||
password = hashlib.sha1(mpk).digest().encode('hex')[:32]
|
||||
iv = hashlib.sha256(password).digest()[:16]
|
||||
wallet_id = hashlib.sha256(mpk).digest().encode('hex')
|
||||
self.wallets[wallet] = (password, iv, wallet_id)
|
||||
|
||||
# If there is an auth token we can try to actually start syncing
|
||||
t = threading.Thread(target=self.pull_thread, args=(window, False))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
@hook
|
||||
def on_close_window(self, window):
|
||||
self.wallets.pop(window.wallet)
|
||||
|
||||
def version(self):
|
||||
return "0.0.1"
|
||||
|
||||
def encode(self, wallet, msg):
|
||||
password, iv, wallet_id = self.wallets[wallet]
|
||||
|
@ -85,9 +46,6 @@ class Plugin(BasePlugin):
|
|||
self.print_error("set", wallet.basename(), "nonce to", nonce)
|
||||
wallet.storage.put("wallet_nonce", nonce, force_write)
|
||||
|
||||
def requires_settings(self):
|
||||
return True
|
||||
|
||||
@hook
|
||||
def set_label(self, wallet, item, label):
|
||||
if not wallet in self.wallets:
|
||||
|
@ -105,47 +63,6 @@ class Plugin(BasePlugin):
|
|||
# Caller will write the wallet
|
||||
self.set_nonce(wallet, nonce + 1, force_write=False)
|
||||
|
||||
def settings_widget(self, window):
|
||||
return EnterButton(_('Settings'),
|
||||
partial(self.settings_dialog, window))
|
||||
|
||||
def settings_dialog(self, window):
|
||||
print "window:", window
|
||||
d = QDialog(window)
|
||||
vbox = QVBoxLayout(d)
|
||||
layout = QGridLayout()
|
||||
vbox.addLayout(layout)
|
||||
|
||||
layout.addWidget(QLabel("Label sync options: "),2,0)
|
||||
|
||||
self.upload = ThreadedButton("Force upload",
|
||||
partial(self.push_thread, window),
|
||||
self.done_processing)
|
||||
layout.addWidget(self.upload, 2, 1)
|
||||
|
||||
self.download = ThreadedButton("Force download",
|
||||
partial(self.pull_thread, window, True),
|
||||
self.done_processing)
|
||||
layout.addWidget(self.download, 2, 2)
|
||||
|
||||
self.accept = OkButton(d, _("Done"))
|
||||
vbox.addLayout(Buttons(CancelButton(d), self.accept))
|
||||
|
||||
if d.exec_():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def on_pulled(self, window, nonce):
|
||||
wallet = window.wallet
|
||||
wallet.storage.put('labels', wallet.labels, False)
|
||||
self.set_nonce(wallet, nonce)
|
||||
window.labelsChanged.emit()
|
||||
|
||||
def done_processing(self):
|
||||
QMessageBox.information(None, _("Labels synchronised"),
|
||||
_("Your labels have been synchronised."))
|
||||
|
||||
def do_request(self, method, url = "/labels", is_batch=False, data=None):
|
||||
url = 'https://' + self.target_host + url
|
||||
kwargs = {'headers': {}}
|
||||
|
@ -162,8 +79,7 @@ class Plugin(BasePlugin):
|
|||
raise BaseException(response["error"])
|
||||
return response
|
||||
|
||||
def push_thread(self, window):
|
||||
wallet = window.wallet
|
||||
def push_thread(self, wallet):
|
||||
wallet_id = self.wallets[wallet][2]
|
||||
bundle = {"labels": [],
|
||||
"walletId": wallet_id,
|
||||
|
@ -179,14 +95,14 @@ class Plugin(BasePlugin):
|
|||
'externalId': encoded_key})
|
||||
self.do_request("POST", "/labels", True, bundle)
|
||||
|
||||
def pull_thread(self, window, force):
|
||||
wallet = window.wallet
|
||||
def pull_thread(self, wallet, force):
|
||||
wallet_id = self.wallets[wallet][2]
|
||||
nonce = 1 if force else self.get_nonce(wallet) - 1
|
||||
self.print_error("asking for labels since nonce", nonce)
|
||||
try:
|
||||
response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id) ))
|
||||
if response["labels"] is None:
|
||||
self.print_error('no new labels')
|
||||
return
|
||||
result = {}
|
||||
for label in response["labels"]:
|
||||
|
@ -208,9 +124,86 @@ class Plugin(BasePlugin):
|
|||
wallet.labels[key] = value
|
||||
|
||||
self.print_error("received %d labels" % len(response))
|
||||
self.obj.emit(SIGNAL('labels:pulled'), window,
|
||||
response["nonce"] + 1)
|
||||
# do not write to disk because we're in a daemon thread
|
||||
wallet.storage.put('labels', wallet.labels, False)
|
||||
self.set_nonce(wallet, response["nonce"] + 1, False)
|
||||
self.on_pulled(wallet)
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
self.print_error("could not retrieve labels")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
import PyQt4.QtCore as QtCore
|
||||
import PyQt4.QtGui as QtGui
|
||||
from electrum_gui.qt import HelpButton, EnterButton
|
||||
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
|
||||
|
||||
class QtPlugin(Plugin):
|
||||
|
||||
def __init__(self, *args):
|
||||
Plugin.__init__(self, *args)
|
||||
self.obj = QObject()
|
||||
|
||||
def requires_settings(self):
|
||||
return True
|
||||
|
||||
def settings_widget(self, window):
|
||||
return EnterButton(_('Settings'),
|
||||
partial(self.settings_dialog, window))
|
||||
|
||||
def settings_dialog(self, window):
|
||||
d = QDialog(window)
|
||||
vbox = QVBoxLayout(d)
|
||||
layout = QGridLayout()
|
||||
vbox.addLayout(layout)
|
||||
layout.addWidget(QLabel("Label sync options: "), 2, 0)
|
||||
self.upload = ThreadedButton("Force upload",
|
||||
partial(self.push_thread, window.wallet),
|
||||
self.done_processing)
|
||||
layout.addWidget(self.upload, 2, 1)
|
||||
self.download = ThreadedButton("Force download",
|
||||
partial(self.pull_thread, window.wallet, True),
|
||||
self.done_processing)
|
||||
layout.addWidget(self.download, 2, 2)
|
||||
self.accept = OkButton(d, _("Done"))
|
||||
vbox.addLayout(Buttons(CancelButton(d), self.accept))
|
||||
if d.exec_():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def on_pulled(self, wallet):
|
||||
self.obj.emit(SIGNAL('labels_changed'), wallet)
|
||||
|
||||
def done_processing(self):
|
||||
QMessageBox.information(None, _("Labels synchronised"),
|
||||
_("Your labels have been synchronised."))
|
||||
|
||||
@hook
|
||||
def on_new_window(self, window):
|
||||
window.connect(window.app, SIGNAL('labels_changed'), window.update_tabs)
|
||||
wallet = window.wallet
|
||||
nonce = self.get_nonce(wallet)
|
||||
self.print_error("wallet", wallet.basename(), "nonce is", nonce)
|
||||
mpk = ''.join(sorted(wallet.get_master_public_keys().values()))
|
||||
if not mpk:
|
||||
return
|
||||
password = hashlib.sha1(mpk).digest().encode('hex')[:32]
|
||||
iv = hashlib.sha256(password).digest()[:16]
|
||||
wallet_id = hashlib.sha256(mpk).digest().encode('hex')
|
||||
self.wallets[wallet] = (password, iv, wallet_id)
|
||||
# If there is an auth token we can try to actually start syncing
|
||||
t = threading.Thread(target=self.pull_thread, args=(wallet, False))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
@hook
|
||||
def on_close_window(self, window):
|
||||
self.wallets.pop(window.wallet)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ except:
|
|||
flag_matlib=False
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
class QtPlugin(BasePlugin):
|
||||
|
||||
def is_available(self):
|
||||
if flag_matlib:
|
||||
|
|
|
@ -7,8 +7,6 @@ import threading
|
|||
import re
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
|
||||
import PyQt4.QtCore as QtCore
|
||||
|
||||
import electrum
|
||||
from electrum import bitcoin
|
||||
|
@ -22,14 +20,9 @@ from electrum.wallet import BIP32_HD_Wallet
|
|||
from electrum.util import print_error, print_msg
|
||||
from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root
|
||||
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
|
||||
from electrum_gui.qt.installwizard import InstallWizard
|
||||
|
||||
try:
|
||||
from trezorlib.client import types
|
||||
from trezorlib.client import proto, BaseClient, ProtocolMixin
|
||||
from trezorlib.qt.pinmatrix import PinMatrixWidget
|
||||
from trezorlib.transport import ConnectionError
|
||||
from trezorlib.transport_hid import HidTransport
|
||||
TREZOR = True
|
||||
|
@ -47,308 +40,6 @@ def give_error(message):
|
|||
raise Exception(message)
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
self._is_available = self._init()
|
||||
self.wallet = None
|
||||
self.handler = None
|
||||
self.client = None
|
||||
self.transport = None
|
||||
|
||||
def constructor(self, s):
|
||||
return TrezorWallet(s)
|
||||
|
||||
def _init(self):
|
||||
return TREZOR
|
||||
|
||||
def is_available(self):
|
||||
if not self._is_available:
|
||||
return False
|
||||
if not self.wallet:
|
||||
return False
|
||||
if self.wallet.storage.get('wallet_type') != 'trezor':
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
self.wallet.storage.put('use_' + self.name, enabled)
|
||||
|
||||
def is_enabled(self):
|
||||
if not self.is_available():
|
||||
return False
|
||||
if self.wallet.has_seed():
|
||||
return False
|
||||
return True
|
||||
|
||||
def compare_version(self, major, minor=0, patch=0):
|
||||
features = self.get_client().features
|
||||
v = [features.major_version, features.minor_version, features.patch_version]
|
||||
self.print_error('firmware version', v)
|
||||
return cmp(v, [major, minor, patch])
|
||||
|
||||
def atleast_version(self, major, minor=0, patch=0):
|
||||
return self.compare_version(major, minor, patch) >= 0
|
||||
|
||||
def get_client(self):
|
||||
if not TREZOR:
|
||||
give_error('please install github.com/trezor/python-trezor')
|
||||
|
||||
if not self.client or self.client.bad:
|
||||
d = HidTransport.enumerate()
|
||||
if not d:
|
||||
give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.')
|
||||
self.transport = HidTransport(d[0])
|
||||
self.client = QtGuiTrezorClient(self.transport)
|
||||
self.client.handler = self.handler
|
||||
self.client.set_tx_api(self)
|
||||
self.client.bad = False
|
||||
if not self.atleast_version(1, 2, 1):
|
||||
self.client = None
|
||||
give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com')
|
||||
return self.client
|
||||
|
||||
@hook
|
||||
def close_wallet(self):
|
||||
print_error("trezor: clear session")
|
||||
if self.client:
|
||||
self.client.clear_session()
|
||||
self.client.transport.close()
|
||||
self.client = None
|
||||
self.wallet = None
|
||||
|
||||
@hook
|
||||
def cmdline_load_wallet(self, wallet):
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
if self.handler is None:
|
||||
self.handler = TrezorCmdLineHandler()
|
||||
|
||||
@hook
|
||||
def load_wallet(self, wallet, window):
|
||||
self.print_error("load_wallet")
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
self.trezor_button = StatusBarButton(QIcon(":icons/trezor.png"), _("Trezor"), partial(self.settings_dialog, window))
|
||||
if type(window) is ElectrumWindow:
|
||||
window.statusBar().addPermanentWidget(self.trezor_button)
|
||||
if self.handler is None:
|
||||
self.handler = TrezorQtHandler(window)
|
||||
try:
|
||||
self.get_client().ping('t')
|
||||
except BaseException as e:
|
||||
QMessageBox.information(window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
return
|
||||
if self.wallet.addresses() and not self.wallet.check_proper_device():
|
||||
QMessageBox.information(window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
|
||||
@hook
|
||||
def installwizard_load_wallet(self, wallet, window):
|
||||
if type(wallet) != TrezorWallet:
|
||||
return
|
||||
self.load_wallet(wallet, window)
|
||||
|
||||
@hook
|
||||
def installwizard_restore(self, wizard, storage):
|
||||
if storage.get('wallet_type') != 'trezor':
|
||||
return
|
||||
seed = wizard.enter_seed_dialog("Enter your Trezor seed", None, func=lambda x:True)
|
||||
if not seed:
|
||||
return
|
||||
wallet = TrezorWallet(storage)
|
||||
self.wallet = wallet
|
||||
handler = TrezorQtHandler(wizard)
|
||||
passphrase = handler.get_passphrase(_("Please enter your Trezor passphrase.") + '\n' + _("Press OK if you do not use one."))
|
||||
if passphrase is None:
|
||||
return
|
||||
password = wizard.password_dialog()
|
||||
wallet.add_seed(seed, password)
|
||||
wallet.add_cosigner_seed(seed, 'x/', password, passphrase)
|
||||
wallet.create_main_account(password)
|
||||
# disable trezor plugin
|
||||
self.set_enabled(False)
|
||||
return wallet
|
||||
|
||||
@hook
|
||||
def receive_menu(self, menu, addrs):
|
||||
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
|
||||
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
|
||||
|
||||
def show_address(self, address):
|
||||
if not self.wallet.check_proper_device():
|
||||
give_error('Wrong device or password')
|
||||
try:
|
||||
address_path = self.wallet.address_id(address)
|
||||
address_n = self.get_client().expand_path(address_path)
|
||||
except Exception, e:
|
||||
give_error(e)
|
||||
try:
|
||||
self.get_client().get_address('Bitcoin', address_n, True)
|
||||
except Exception, e:
|
||||
give_error(e)
|
||||
finally:
|
||||
self.handler.stop()
|
||||
|
||||
|
||||
def settings_dialog(self, window):
|
||||
try:
|
||||
device_id = self.get_client().get_device_id()
|
||||
except BaseException as e:
|
||||
window.show_message(str(e))
|
||||
return
|
||||
get_label = lambda: self.get_client().features.label
|
||||
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
|
||||
d = QDialog()
|
||||
layout = QGridLayout(d)
|
||||
layout.addWidget(QLabel("Trezor Options"),0,0)
|
||||
layout.addWidget(QLabel("ID:"),1,0)
|
||||
layout.addWidget(QLabel(" %s" % device_id),1,1)
|
||||
|
||||
def modify_label():
|
||||
response = QInputDialog().getText(None, "Set New Trezor Label", "New Trezor Label: (upon submission confirm on Trezor)")
|
||||
if not response[1]:
|
||||
return
|
||||
new_label = str(response[0])
|
||||
self.handler.show_message("Please confirm label change on Trezor")
|
||||
status = self.get_client().apply_settings(label=new_label)
|
||||
self.handler.stop()
|
||||
update_label()
|
||||
|
||||
current_label_label = QLabel()
|
||||
update_label()
|
||||
change_label_button = QPushButton("Modify")
|
||||
change_label_button.clicked.connect(modify_label)
|
||||
layout.addWidget(current_label_label,3,0)
|
||||
layout.addWidget(change_label_button,3,1)
|
||||
d.exec_()
|
||||
|
||||
|
||||
def sign_transaction(self, tx, prev_tx, xpub_path):
|
||||
self.prev_tx = prev_tx
|
||||
self.xpub_path = xpub_path
|
||||
client = self.get_client()
|
||||
inputs = self.tx_inputs(tx, True)
|
||||
outputs = self.tx_outputs(tx)
|
||||
#try:
|
||||
signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1]
|
||||
#except Exception, e:
|
||||
# give_error(e)
|
||||
#finally:
|
||||
self.handler.stop()
|
||||
|
||||
raw = signed_tx.encode('hex')
|
||||
tx.update_signatures(raw)
|
||||
|
||||
|
||||
def tx_inputs(self, tx, for_sig=False):
|
||||
inputs = []
|
||||
for txin in tx.inputs:
|
||||
txinputtype = types.TxInputType()
|
||||
if txin.get('is_coinbase'):
|
||||
prev_hash = "\0"*32
|
||||
prev_index = 0xffffffff # signed int -1
|
||||
else:
|
||||
if for_sig:
|
||||
x_pubkeys = txin['x_pubkeys']
|
||||
if len(x_pubkeys) == 1:
|
||||
x_pubkey = x_pubkeys[0]
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
xpub_n = self.get_client().expand_path(self.xpub_path[xpub])
|
||||
txinputtype.address_n.extend(xpub_n + s)
|
||||
else:
|
||||
def f(x_pubkey):
|
||||
if is_extended_pubkey(x_pubkey):
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
else:
|
||||
xpub = xpub_from_pubkey(x_pubkey.decode('hex'))
|
||||
s = []
|
||||
node = ckd_public.deserialize(xpub)
|
||||
return types.HDNodePathType(node=node, address_n=s)
|
||||
pubkeys = map(f, x_pubkeys)
|
||||
multisig = types.MultisigRedeemScriptType(
|
||||
pubkeys=pubkeys,
|
||||
signatures=map(lambda x: x.decode('hex') if x else '', txin.get('signatures')),
|
||||
m=txin.get('num_sig'),
|
||||
)
|
||||
txinputtype = types.TxInputType(
|
||||
script_type=types.SPENDMULTISIG,
|
||||
multisig= multisig
|
||||
)
|
||||
# find which key is mine
|
||||
for x_pubkey in x_pubkeys:
|
||||
if is_extended_pubkey(x_pubkey):
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
if xpub in self.xpub_path:
|
||||
xpub_n = self.get_client().expand_path(self.xpub_path[xpub])
|
||||
txinputtype.address_n.extend(xpub_n + s)
|
||||
break
|
||||
|
||||
prev_hash = unhexlify(txin['prevout_hash'])
|
||||
prev_index = txin['prevout_n']
|
||||
|
||||
txinputtype.prev_hash = prev_hash
|
||||
txinputtype.prev_index = prev_index
|
||||
|
||||
if 'scriptSig' in txin:
|
||||
script_sig = txin['scriptSig'].decode('hex')
|
||||
txinputtype.script_sig = script_sig
|
||||
|
||||
if 'sequence' in txin:
|
||||
sequence = txin['sequence']
|
||||
txinputtype.sequence = sequence
|
||||
|
||||
inputs.append(txinputtype)
|
||||
|
||||
return inputs
|
||||
|
||||
def tx_outputs(self, tx):
|
||||
outputs = []
|
||||
|
||||
for type, address, amount in tx.outputs:
|
||||
assert type == 'address'
|
||||
txoutputtype = types.TxOutputType()
|
||||
if self.wallet.is_change(address):
|
||||
address_path = self.wallet.address_id(address)
|
||||
address_n = self.get_client().expand_path(address_path)
|
||||
txoutputtype.address_n.extend(address_n)
|
||||
else:
|
||||
txoutputtype.address = address
|
||||
txoutputtype.amount = amount
|
||||
addrtype, hash_160 = bc_address_to_hash_160(address)
|
||||
if addrtype == 0:
|
||||
txoutputtype.script_type = types.PAYTOADDRESS
|
||||
elif addrtype == 5:
|
||||
txoutputtype.script_type = types.PAYTOSCRIPTHASH
|
||||
else:
|
||||
raise BaseException('addrtype')
|
||||
outputs.append(txoutputtype)
|
||||
|
||||
return outputs
|
||||
|
||||
def electrum_tx_to_txtype(self, tx):
|
||||
t = types.TransactionType()
|
||||
d = deserialize(tx.raw)
|
||||
t.version = d['version']
|
||||
t.lock_time = d['lockTime']
|
||||
inputs = self.tx_inputs(tx)
|
||||
t.inputs.extend(inputs)
|
||||
for vout in d['outputs']:
|
||||
o = t.bin_outputs.add()
|
||||
o.amount = vout['value']
|
||||
o.script_pubkey = vout['scriptPubKey'].decode('hex')
|
||||
return t
|
||||
|
||||
def get_tx(self, tx_hash):
|
||||
tx = self.prev_tx[tx_hash]
|
||||
tx.deserialize()
|
||||
return self.electrum_tx_to_txtype(tx)
|
||||
|
||||
|
||||
|
||||
|
||||
class TrezorWallet(BIP32_HD_Wallet):
|
||||
wallet_type = 'trezor'
|
||||
root_derivation = "m/44'/0'"
|
||||
|
@ -503,15 +194,326 @@ class TrezorWallet(BIP32_HD_Wallet):
|
|||
n = self.get_client().expand_path(address_id)
|
||||
device_address = self.get_client().get_address('Bitcoin', n)
|
||||
self.device_checked = True
|
||||
|
||||
if device_address != address:
|
||||
self.proper_device = False
|
||||
else:
|
||||
self.proper_device = True
|
||||
self.proper_device = (device_address == address)
|
||||
|
||||
return self.proper_device
|
||||
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
self._is_available = self._init()
|
||||
self.wallet = None
|
||||
self.handler = None
|
||||
self.client = None
|
||||
self.transport = None
|
||||
|
||||
def constructor(self, s):
|
||||
return TrezorWallet(s)
|
||||
|
||||
def _init(self):
|
||||
return TREZOR
|
||||
|
||||
def is_available(self):
|
||||
if not self._is_available:
|
||||
return False
|
||||
if not self.wallet:
|
||||
return False
|
||||
if self.wallet.storage.get('wallet_type') != 'trezor':
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
self.wallet.storage.put('use_' + self.name, enabled)
|
||||
|
||||
def is_enabled(self):
|
||||
if not self.is_available():
|
||||
return False
|
||||
if self.wallet.has_seed():
|
||||
return False
|
||||
return True
|
||||
|
||||
def compare_version(self, major, minor=0, patch=0):
|
||||
features = self.get_client().features
|
||||
v = [features.major_version, features.minor_version, features.patch_version]
|
||||
self.print_error('firmware version', v)
|
||||
return cmp(v, [major, minor, patch])
|
||||
|
||||
def atleast_version(self, major, minor=0, patch=0):
|
||||
return self.compare_version(major, minor, patch) >= 0
|
||||
|
||||
def get_client(self):
|
||||
if not TREZOR:
|
||||
give_error('please install github.com/trezor/python-trezor')
|
||||
|
||||
if not self.client or self.client.bad:
|
||||
d = HidTransport.enumerate()
|
||||
if not d:
|
||||
give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.')
|
||||
self.transport = HidTransport(d[0])
|
||||
self.client = QtGuiTrezorClient(self.transport)
|
||||
self.client.handler = self.handler
|
||||
self.client.set_tx_api(self)
|
||||
self.client.bad = False
|
||||
if not self.atleast_version(1, 2, 1):
|
||||
self.client = None
|
||||
give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com')
|
||||
return self.client
|
||||
|
||||
@hook
|
||||
def close_wallet(self):
|
||||
print_error("trezor: clear session")
|
||||
if self.client:
|
||||
self.client.clear_session()
|
||||
self.client.transport.close()
|
||||
self.client = None
|
||||
self.wallet = None
|
||||
|
||||
@hook
|
||||
def cmdline_load_wallet(self, wallet):
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
if self.handler is None:
|
||||
self.handler = TrezorCmdLineHandler()
|
||||
|
||||
def sign_transaction(self, tx, prev_tx, xpub_path):
|
||||
self.prev_tx = prev_tx
|
||||
self.xpub_path = xpub_path
|
||||
client = self.get_client()
|
||||
inputs = self.tx_inputs(tx, True)
|
||||
outputs = self.tx_outputs(tx)
|
||||
#try:
|
||||
signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1]
|
||||
#except Exception, e:
|
||||
# give_error(e)
|
||||
#finally:
|
||||
self.handler.stop()
|
||||
|
||||
raw = signed_tx.encode('hex')
|
||||
tx.update_signatures(raw)
|
||||
|
||||
|
||||
def tx_inputs(self, tx, for_sig=False):
|
||||
inputs = []
|
||||
for txin in tx.inputs:
|
||||
txinputtype = types.TxInputType()
|
||||
if txin.get('is_coinbase'):
|
||||
prev_hash = "\0"*32
|
||||
prev_index = 0xffffffff # signed int -1
|
||||
else:
|
||||
if for_sig:
|
||||
x_pubkeys = txin['x_pubkeys']
|
||||
if len(x_pubkeys) == 1:
|
||||
x_pubkey = x_pubkeys[0]
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
xpub_n = self.get_client().expand_path(self.xpub_path[xpub])
|
||||
txinputtype.address_n.extend(xpub_n + s)
|
||||
else:
|
||||
def f(x_pubkey):
|
||||
if is_extended_pubkey(x_pubkey):
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
else:
|
||||
xpub = xpub_from_pubkey(x_pubkey.decode('hex'))
|
||||
s = []
|
||||
node = ckd_public.deserialize(xpub)
|
||||
return types.HDNodePathType(node=node, address_n=s)
|
||||
pubkeys = map(f, x_pubkeys)
|
||||
multisig = types.MultisigRedeemScriptType(
|
||||
pubkeys=pubkeys,
|
||||
signatures=map(lambda x: x.decode('hex') if x else '', txin.get('signatures')),
|
||||
m=txin.get('num_sig'),
|
||||
)
|
||||
txinputtype = types.TxInputType(
|
||||
script_type=types.SPENDMULTISIG,
|
||||
multisig= multisig
|
||||
)
|
||||
# find which key is mine
|
||||
for x_pubkey in x_pubkeys:
|
||||
if is_extended_pubkey(x_pubkey):
|
||||
xpub, s = BIP32_Account.parse_xpubkey(x_pubkey)
|
||||
if xpub in self.xpub_path:
|
||||
xpub_n = self.get_client().expand_path(self.xpub_path[xpub])
|
||||
txinputtype.address_n.extend(xpub_n + s)
|
||||
break
|
||||
|
||||
prev_hash = unhexlify(txin['prevout_hash'])
|
||||
prev_index = txin['prevout_n']
|
||||
|
||||
txinputtype.prev_hash = prev_hash
|
||||
txinputtype.prev_index = prev_index
|
||||
|
||||
if 'scriptSig' in txin:
|
||||
script_sig = txin['scriptSig'].decode('hex')
|
||||
txinputtype.script_sig = script_sig
|
||||
|
||||
if 'sequence' in txin:
|
||||
sequence = txin['sequence']
|
||||
txinputtype.sequence = sequence
|
||||
|
||||
inputs.append(txinputtype)
|
||||
|
||||
return inputs
|
||||
|
||||
def tx_outputs(self, tx):
|
||||
outputs = []
|
||||
|
||||
for type, address, amount in tx.outputs:
|
||||
assert type == 'address'
|
||||
txoutputtype = types.TxOutputType()
|
||||
if self.wallet.is_change(address):
|
||||
address_path = self.wallet.address_id(address)
|
||||
address_n = self.get_client().expand_path(address_path)
|
||||
txoutputtype.address_n.extend(address_n)
|
||||
else:
|
||||
txoutputtype.address = address
|
||||
txoutputtype.amount = amount
|
||||
addrtype, hash_160 = bc_address_to_hash_160(address)
|
||||
if addrtype == 0:
|
||||
txoutputtype.script_type = types.PAYTOADDRESS
|
||||
elif addrtype == 5:
|
||||
txoutputtype.script_type = types.PAYTOSCRIPTHASH
|
||||
else:
|
||||
raise BaseException('addrtype')
|
||||
outputs.append(txoutputtype)
|
||||
|
||||
return outputs
|
||||
|
||||
def electrum_tx_to_txtype(self, tx):
|
||||
t = types.TransactionType()
|
||||
d = deserialize(tx.raw)
|
||||
t.version = d['version']
|
||||
t.lock_time = d['lockTime']
|
||||
inputs = self.tx_inputs(tx)
|
||||
t.inputs.extend(inputs)
|
||||
for vout in d['outputs']:
|
||||
o = t.bin_outputs.add()
|
||||
o.amount = vout['value']
|
||||
o.script_pubkey = vout['scriptPubKey'].decode('hex')
|
||||
return t
|
||||
|
||||
def get_tx(self, tx_hash):
|
||||
tx = self.prev_tx[tx_hash]
|
||||
tx.deserialize()
|
||||
return self.electrum_tx_to_txtype(tx)
|
||||
|
||||
|
||||
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
|
||||
import PyQt4.QtCore as QtCore
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
|
||||
from electrum_gui.qt.installwizard import InstallWizard
|
||||
from trezorlib.qt.pinmatrix import PinMatrixWidget
|
||||
|
||||
class QtPlugin(Plugin):
|
||||
|
||||
@hook
|
||||
def load_wallet(self, wallet, window):
|
||||
self.print_error("load_wallet")
|
||||
self.wallet = wallet
|
||||
self.wallet.plugin = self
|
||||
self.trezor_button = StatusBarButton(QIcon(":icons/trezor.png"), _("Trezor"), partial(self.settings_dialog, window))
|
||||
if type(window) is ElectrumWindow:
|
||||
window.statusBar().addPermanentWidget(self.trezor_button)
|
||||
if self.handler is None:
|
||||
self.handler = TrezorQtHandler(window)
|
||||
try:
|
||||
self.get_client().ping('t')
|
||||
except BaseException as e:
|
||||
QMessageBox.information(window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
return
|
||||
if self.wallet.addresses() and not self.wallet.check_proper_device():
|
||||
QMessageBox.information(window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
|
||||
self.wallet.force_watching_only = True
|
||||
|
||||
@hook
|
||||
def installwizard_load_wallet(self, wallet, window):
|
||||
if type(wallet) != TrezorWallet:
|
||||
return
|
||||
self.load_wallet(wallet, window)
|
||||
|
||||
@hook
|
||||
def installwizard_restore(self, wizard, storage):
|
||||
if storage.get('wallet_type') != 'trezor':
|
||||
return
|
||||
seed = wizard.enter_seed_dialog("Enter your Trezor seed", None, func=lambda x:True)
|
||||
if not seed:
|
||||
return
|
||||
wallet = TrezorWallet(storage)
|
||||
self.wallet = wallet
|
||||
handler = TrezorQtHandler(wizard)
|
||||
passphrase = handler.get_passphrase(_("Please enter your Trezor passphrase.") + '\n' + _("Press OK if you do not use one."))
|
||||
if passphrase is None:
|
||||
return
|
||||
password = wizard.password_dialog()
|
||||
wallet.add_seed(seed, password)
|
||||
wallet.add_cosigner_seed(seed, 'x/', password, passphrase)
|
||||
wallet.create_main_account(password)
|
||||
# disable trezor plugin
|
||||
self.set_enabled(False)
|
||||
return wallet
|
||||
|
||||
@hook
|
||||
def receive_menu(self, menu, addrs):
|
||||
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
|
||||
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
|
||||
|
||||
def show_address(self, address):
|
||||
if not self.wallet.check_proper_device():
|
||||
give_error('Wrong device or password')
|
||||
try:
|
||||
address_path = self.wallet.address_id(address)
|
||||
address_n = self.get_client().expand_path(address_path)
|
||||
except Exception, e:
|
||||
give_error(e)
|
||||
try:
|
||||
self.get_client().get_address('Bitcoin', address_n, True)
|
||||
except Exception, e:
|
||||
give_error(e)
|
||||
finally:
|
||||
self.handler.stop()
|
||||
|
||||
|
||||
def settings_dialog(self, window):
|
||||
try:
|
||||
device_id = self.get_client().get_device_id()
|
||||
except BaseException as e:
|
||||
window.show_message(str(e))
|
||||
return
|
||||
get_label = lambda: self.get_client().features.label
|
||||
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
|
||||
d = QDialog()
|
||||
layout = QGridLayout(d)
|
||||
layout.addWidget(QLabel("Trezor Options"),0,0)
|
||||
layout.addWidget(QLabel("ID:"),1,0)
|
||||
layout.addWidget(QLabel(" %s" % device_id),1,1)
|
||||
|
||||
def modify_label():
|
||||
response = QInputDialog().getText(None, "Set New Trezor Label", "New Trezor Label: (upon submission confirm on Trezor)")
|
||||
if not response[1]:
|
||||
return
|
||||
new_label = str(response[0])
|
||||
self.handler.show_message("Please confirm label change on Trezor")
|
||||
status = self.get_client().apply_settings(label=new_label)
|
||||
self.handler.stop()
|
||||
update_label()
|
||||
|
||||
current_label_label = QLabel()
|
||||
update_label()
|
||||
change_label_button = QPushButton("Modify")
|
||||
change_label_button.clicked.connect(modify_label)
|
||||
layout.addWidget(current_label_label,3,0)
|
||||
layout.addWidget(change_label_button,3,1)
|
||||
d.exec_()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TrezorGuiMixin(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -27,9 +27,6 @@ from urlparse import urljoin
|
|||
from urllib import quote
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
|
||||
import electrum
|
||||
from electrum import bitcoin
|
||||
from electrum.bitcoin import *
|
||||
|
@ -39,10 +36,6 @@ from electrum.wallet import Multisig_Wallet, BIP32_Wallet
|
|||
from electrum.i18n import _
|
||||
from electrum.plugins import BasePlugin, run_hook, hook
|
||||
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.qrcodewidget import QRCodeWidget
|
||||
from electrum_gui.qt.amountedit import AmountEdit
|
||||
from electrum_gui.qt.main_window import StatusBarButton
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
|
@ -460,6 +453,16 @@ class Plugin(BasePlugin):
|
|||
wallet.add_master_public_key('x3/', xpub3)
|
||||
return True
|
||||
|
||||
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.qrcodewidget import QRCodeWidget
|
||||
from electrum_gui.qt.amountedit import AmountEdit
|
||||
from electrum_gui.qt.main_window import StatusBarButton
|
||||
|
||||
class QtPlugin(Plugin):
|
||||
|
||||
def auth_dialog(self, window):
|
||||
d = QDialog(window)
|
||||
d.setModal(1)
|
||||
|
@ -674,3 +677,5 @@ class Plugin(BasePlugin):
|
|||
except:
|
||||
QMessageBox.information(window, _('Message'), _('Incorrect password'), _('OK'))
|
||||
pw.setText('')
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from electrum.plugins import BasePlugin, hook
|
|||
from electrum.i18n import _
|
||||
import random
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
class QtPlugin(BasePlugin):
|
||||
|
||||
vkb = None
|
||||
vkb_index = 0
|
||||
|
|
Loading…
Reference in New Issue