commit 2c0489c8094b2ba9409ae0e36bfe674ce327d883
parent 175fdbcac6fc1bd5ed54ceb709200b746877935d
Author: ThomasV <thomasv@electrum.org>
Date: Mon, 23 Nov 2015 14:15:25 +0100
plugins: separate GUIs using child classes
Diffstat:
14 files changed, 808 insertions(+), 774 deletions(-)
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -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
diff --git a/lib/plugins.py b/lib/plugins.py
@@ -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
diff --git a/plugins/audio_modem.py b/plugins/audio_modem.py
@@ -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)
diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py
@@ -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):
diff --git a/plugins/cosigner_pool.py b/plugins/cosigner_pool.py
@@ -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)
diff --git a/plugins/email_requests.py b/plugins/email_requests.py
@@ -101,7 +101,7 @@ class Processor(threading.Thread):
s.quit()
-class Plugin(BasePlugin):
+class QtPlugin(BasePlugin):
def fullname(self):
return 'Email'
diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate.py
@@ -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)
diff --git a/plugins/greenaddress_instant.py b/plugins/greenaddress_instant.py
@@ -28,7 +28,7 @@ from electrum.i18n import _
-class Plugin(BasePlugin):
+class QtPlugin(BasePlugin):
button_label = _("Verify GA instant")
diff --git a/plugins/keepkey.py b/plugins/keepkey.py
@@ -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,6 +41,172 @@ def give_error(message):
raise Exception(message)
+
+
+class KeepKeyWallet(BIP32_HD_Wallet):
+ wallet_type = 'keepkey'
+ root_derivation = "m/44'/0'"
+
+ def __init__(self, storage):
+ BIP32_HD_Wallet.__init__(self, storage)
+ self.mpk = None
+ self.device_checked = False
+ self.proper_device = False
+ self.force_watching_only = False
+
+ def get_action(self):
+ if not self.accounts:
+ return 'create_accounts'
+
+ def can_import(self):
+ return False
+
+ 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_export(self):
+ return False
+
+ def can_create_accounts(self):
+ return True
+
+ def can_change_password(self):
+ return False
+
+ def is_watching_only(self):
+ return self.force_watching_only
+
+ def get_client(self):
+ return self.plugin.get_client()
+
+ def address_id(self, address):
+ account_id, (change, address_index) = self.get_address_index(address)
+ return "44'/0'/%s'/%d/%d" % (account_id, change, address_index)
+
+ def create_main_account(self, password):
+ self.create_account('Main account', None) #name, empty password
+
+ def mnemonic_to_seed(self, mnemonic, passphrase):
+ # keepkey uses bip39
+ import pbkdf2, hashlib, hmac
+ PBKDF2_ROUNDS = 2048
+ mnemonic = unicodedata.normalize('NFKD', ' '.join(mnemonic.split()))
+ passphrase = unicodedata.normalize('NFKD', passphrase)
+ return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
+
+ def derive_xkeys(self, root, derivation, password):
+ x = self.master_private_keys.get(root)
+ if x:
+ root_xprv = pw_decode(x, password)
+ xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
+ return xpub, xprv
+ else:
+ derivation = derivation.replace(self.root_name,"44'/0'/")
+ xpub = self.get_public_key(derivation)
+ return xpub, None
+
+ def get_public_key(self, bip32_path):
+ address_n = self.plugin.get_client().expand_path(bip32_path)
+ node = self.plugin.get_client().get_public_node(address_n).node
+ xpub = "0488B21E".decode('hex') + chr(node.depth) + self.i4b(node.fingerprint) + self.i4b(node.child_num) + node.chain_code + node.public_key
+ return EncodeBase58Check(xpub)
+
+ def get_master_public_key(self):
+ if not self.mpk:
+ self.mpk = self.get_public_key("44'/0'")
+ return self.mpk
+
+ def i4b(self, x):
+ return pack('>I', x)
+
+ def add_keypairs(self, tx, keypairs, password):
+ #do nothing - no priv keys available
+ pass
+
+ def decrypt_message(self, pubkey, message, password):
+ raise BaseException( _('Decrypt method is not implemented in KeepKey') )
+ #address = public_key_to_bc_address(pubkey.decode('hex'))
+ #address_path = self.address_id(address)
+ #address_n = self.get_client().expand_path(address_path)
+ #try:
+ # decrypted_msg = self.get_client().decrypt_message(address_n, b64decode(message))
+ #except Exception, e:
+ # give_error(e)
+ #finally:
+ # twd.stop()
+ #return str(decrypted_msg)
+
+ def sign_message(self, address, message, password):
+ if self.has_seed():
+ return BIP32_HD_Wallet.sign_message(self, address, message, password)
+ if not self.check_proper_device():
+ give_error('Wrong device or password')
+ try:
+ address_path = self.address_id(address)
+ address_n = self.plugin.get_client().expand_path(address_path)
+ except Exception, e:
+ give_error(e)
+ try:
+ msg_sig = self.plugin.get_client().sign_message('Bitcoin', address_n, message)
+ except Exception, e:
+ give_error(e)
+ finally:
+ self.plugin.handler.stop()
+ return msg_sig.signature
+
+ def sign_transaction(self, tx, password):
+ if self.has_seed():
+ return BIP32_HD_Wallet.sign_transaction(self, tx, password)
+ if tx.is_complete():
+ return
+ if not self.check_proper_device():
+ give_error('Wrong device or password')
+ # previous transactions used as inputs
+ prev_tx = {}
+ # path of the xpubs that are involved
+ xpub_path = {}
+ for txin in tx.inputs:
+ tx_hash = txin['prevout_hash']
+
+ ptx = self.transactions.get(tx_hash)
+ if ptx is None:
+ ptx = self.network.synchronous_get(('blockchain.transaction.get', [tx_hash]))
+ ptx = Transaction(ptx)
+ prev_tx[tx_hash] = ptx
+
+ for x_pubkey in txin['x_pubkeys']:
+ account_derivation = None
+ if not is_extended_pubkey(x_pubkey):
+ continue
+ xpub = x_to_xpub(x_pubkey)
+ for k, v in self.master_public_keys.items():
+ if v == xpub:
+ account_id = re.match("x/(\d+)'", k).group(1)
+ account_derivation = "44'/0'/%s'"%account_id
+ if account_derivation is not None:
+ xpub_path[xpub] = account_derivation
+
+ self.plugin.sign_transaction(tx, prev_tx, xpub_path)
+
+ def check_proper_device(self):
+ self.get_client().ping('t')
+ if not self.device_checked:
+ address = self.addresses(False)[0]
+ address_id = self.address_id(address)
+ 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
+
+ return self.proper_device
+
+
+
class Plugin(BasePlugin):
def __init__(self, parent, config, name):
@@ -118,64 +278,6 @@ class Plugin(BasePlugin):
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():
@@ -193,39 +295,6 @@ class Plugin(BasePlugin):
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
@@ -350,167 +419,110 @@ class Plugin(BasePlugin):
-class KeepKeyWallet(BIP32_HD_Wallet):
- wallet_type = 'keepkey'
- root_derivation = "m/44'/0'"
-
- def __init__(self, storage):
- BIP32_HD_Wallet.__init__(self, storage)
- self.mpk = None
- self.device_checked = False
- self.proper_device = False
- self.force_watching_only = False
-
- def get_action(self):
- if not self.accounts:
- return 'create_accounts'
-
- def can_import(self):
- return False
-
- 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_export(self):
- return False
-
- def can_create_accounts(self):
- return True
-
- def can_change_password(self):
- return False
-
- def is_watching_only(self):
- return self.force_watching_only
-
- def get_client(self):
- return self.plugin.get_client()
-
- def address_id(self, address):
- account_id, (change, address_index) = self.get_address_index(address)
- return "44'/0'/%s'/%d/%d" % (account_id, change, address_index)
-
- def create_main_account(self, password):
- self.create_account('Main account', None) #name, empty password
-
- def mnemonic_to_seed(self, mnemonic, passphrase):
- # keepkey uses bip39
- import pbkdf2, hashlib, hmac
- PBKDF2_ROUNDS = 2048
- mnemonic = unicodedata.normalize('NFKD', ' '.join(mnemonic.split()))
- passphrase = unicodedata.normalize('NFKD', passphrase)
- return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
+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
- def derive_xkeys(self, root, derivation, password):
- x = self.master_private_keys.get(root)
- if x:
- root_xprv = pw_decode(x, password)
- xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
- return xpub, xprv
- else:
- derivation = derivation.replace(self.root_name,"44'/0'/")
- xpub = self.get_public_key(derivation)
- return xpub, None
- def get_public_key(self, bip32_path):
- address_n = self.plugin.get_client().expand_path(bip32_path)
- node = self.plugin.get_client().get_public_node(address_n).node
- xpub = "0488B21E".decode('hex') + chr(node.depth) + self.i4b(node.fingerprint) + self.i4b(node.child_num) + node.chain_code + node.public_key
- return EncodeBase58Check(xpub)
+class QtPlugin(Plugin):
- def get_master_public_key(self):
- if not self.mpk:
- self.mpk = self.get_public_key("44'/0'")
- return self.mpk
+ @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
- def i4b(self, x):
- return pack('>I', x)
+ @hook
+ def installwizard_load_wallet(self, wallet, window):
+ if type(wallet) != KeepKeyWallet:
+ return
+ self.load_wallet(wallet, window)
- def add_keypairs(self, tx, keypairs, password):
- #do nothing - no priv keys available
- pass
+ @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
- def decrypt_message(self, pubkey, message, password):
- raise BaseException( _('Decrypt method is not implemented in KeepKey') )
- #address = public_key_to_bc_address(pubkey.decode('hex'))
- #address_path = self.address_id(address)
- #address_n = self.get_client().expand_path(address_path)
- #try:
- # decrypted_msg = self.get_client().decrypt_message(address_n, b64decode(message))
- #except Exception, e:
- # give_error(e)
- #finally:
- # twd.stop()
- #return str(decrypted_msg)
+ @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 sign_message(self, address, message, password):
- if self.has_seed():
- return BIP32_HD_Wallet.sign_message(self, address, message, password)
- if not self.check_proper_device():
- give_error('Wrong device or password')
- try:
- address_path = self.address_id(address)
- address_n = self.plugin.get_client().expand_path(address_path)
- except Exception, e:
- give_error(e)
+ def settings_dialog(self, window):
try:
- msg_sig = self.plugin.get_client().sign_message('Bitcoin', address_n, message)
- except Exception, e:
- give_error(e)
- finally:
- self.plugin.handler.stop()
- return msg_sig.signature
-
- def sign_transaction(self, tx, password):
- if self.has_seed():
- return BIP32_HD_Wallet.sign_transaction(self, tx, password)
- if tx.is_complete():
+ device_id = self.get_client().get_device_id()
+ except BaseException as e:
+ window.show_message(str(e))
return
- if not self.check_proper_device():
- give_error('Wrong device or password')
- # previous transactions used as inputs
- prev_tx = {}
- # path of the xpubs that are involved
- xpub_path = {}
- for txin in tx.inputs:
- tx_hash = txin['prevout_hash']
+ 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)
- ptx = self.transactions.get(tx_hash)
- if ptx is None:
- ptx = self.network.synchronous_get(('blockchain.transaction.get', [tx_hash]))
- ptx = Transaction(ptx)
- prev_tx[tx_hash] = ptx
+ 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()
- for x_pubkey in txin['x_pubkeys']:
- account_derivation = None
- if not is_extended_pubkey(x_pubkey):
- continue
- xpub = x_to_xpub(x_pubkey)
- for k, v in self.master_public_keys.items():
- if v == xpub:
- account_id = re.match("x/(\d+)'", k).group(1)
- account_derivation = "44'/0'/%s'"%account_id
- if account_derivation is not None:
- xpub_path[xpub] = account_derivation
+ 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_()
- self.plugin.sign_transaction(tx, prev_tx, xpub_path)
- def check_proper_device(self):
- self.get_client().ping('t')
- if not self.device_checked:
- address = self.addresses(False)[0]
- address_id = self.address_id(address)
- n = self.get_client().expand_path(address_id)
- device_address = self.get_client().get_address('Bitcoin', n)
- self.device_checked = True
+class CmdlinePlugin(Plugin):
- if device_address != address:
- self.proper_device = False
- else:
- self.proper_device = True
+ @hook
+ def cmdline_load_wallet(self, wallet):
+ self.wallet = wallet
+ self.wallet.plugin = self
+ if self.handler is None:
+ self.handler = KeepKeyCmdLineHandler()
- return self.proper_device
class KeepKeyGuiMixin(object):
diff --git a/plugins/labels.py b/plugins/labels.py
@@ -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)
+
diff --git a/plugins/plot.py b/plugins/plot.py
@@ -17,7 +17,7 @@ except:
flag_matlib=False
-class Plugin(BasePlugin):
+class QtPlugin(BasePlugin):
def is_available(self):
if flag_matlib:
diff --git a/plugins/trezor.py b/plugins/trezor.py
@@ -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,6 +40,166 @@ def give_error(message):
raise Exception(message)
+class TrezorWallet(BIP32_HD_Wallet):
+ wallet_type = 'trezor'
+ root_derivation = "m/44'/0'"
+
+ def __init__(self, storage):
+ BIP32_HD_Wallet.__init__(self, storage)
+ self.mpk = None
+ self.device_checked = False
+ self.proper_device = False
+ self.force_watching_only = False
+
+ def get_action(self):
+ if not self.accounts:
+ return 'create_accounts'
+
+ def can_import(self):
+ return False
+
+ 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_export(self):
+ return False
+
+ def can_create_accounts(self):
+ return True
+
+ def can_change_password(self):
+ return False
+
+ def is_watching_only(self):
+ return self.force_watching_only
+
+ def get_client(self):
+ return self.plugin.get_client()
+
+ def address_id(self, address):
+ account_id, (change, address_index) = self.get_address_index(address)
+ return "44'/0'/%s'/%d/%d" % (account_id, change, address_index)
+
+ def create_main_account(self, password):
+ self.create_account('Main account', None) #name, empty password
+
+ def mnemonic_to_seed(self, mnemonic, passphrase):
+ # trezor uses bip39
+ import pbkdf2, hashlib, hmac
+ PBKDF2_ROUNDS = 2048
+ mnemonic = unicodedata.normalize('NFKD', ' '.join(mnemonic.split()))
+ passphrase = unicodedata.normalize('NFKD', passphrase)
+ return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
+
+ def derive_xkeys(self, root, derivation, password):
+ x = self.master_private_keys.get(root)
+ if x:
+ root_xprv = pw_decode(x, password)
+ xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
+ return xpub, xprv
+ else:
+ derivation = derivation.replace(self.root_name,"44'/0'/")
+ xpub = self.get_public_key(derivation)
+ return xpub, None
+
+ def get_public_key(self, bip32_path):
+ address_n = self.plugin.get_client().expand_path(bip32_path)
+ node = self.plugin.get_client().get_public_node(address_n).node
+ xpub = "0488B21E".decode('hex') + chr(node.depth) + self.i4b(node.fingerprint) + self.i4b(node.child_num) + node.chain_code + node.public_key
+ return EncodeBase58Check(xpub)
+
+ def get_master_public_key(self):
+ if not self.mpk:
+ self.mpk = self.get_public_key("44'/0'")
+ return self.mpk
+
+ def i4b(self, x):
+ return pack('>I', x)
+
+ def add_keypairs(self, tx, keypairs, password):
+ #do nothing - no priv keys available
+ pass
+
+ def decrypt_message(self, pubkey, message, password):
+ raise BaseException( _('Decrypt method is not implemented in Trezor') )
+ #address = public_key_to_bc_address(pubkey.decode('hex'))
+ #address_path = self.address_id(address)
+ #address_n = self.get_client().expand_path(address_path)
+ #try:
+ # decrypted_msg = self.get_client().decrypt_message(address_n, b64decode(message))
+ #except Exception, e:
+ # give_error(e)
+ #finally:
+ # twd.stop()
+ #return str(decrypted_msg)
+
+ def sign_message(self, address, message, password):
+ if self.has_seed():
+ return BIP32_HD_Wallet.sign_message(self, address, message, password)
+ if not self.check_proper_device():
+ give_error('Wrong device or password')
+ try:
+ address_path = self.address_id(address)
+ address_n = self.plugin.get_client().expand_path(address_path)
+ except Exception, e:
+ give_error(e)
+ try:
+ msg_sig = self.plugin.get_client().sign_message('Bitcoin', address_n, message)
+ except Exception, e:
+ give_error(e)
+ finally:
+ self.plugin.handler.stop()
+ return msg_sig.signature
+
+ def sign_transaction(self, tx, password):
+ if self.has_seed():
+ return BIP32_HD_Wallet.sign_transaction(self, tx, password)
+ if tx.is_complete():
+ return
+ if not self.check_proper_device():
+ give_error('Wrong device or password')
+ # previous transactions used as inputs
+ prev_tx = {}
+ # path of the xpubs that are involved
+ xpub_path = {}
+ for txin in tx.inputs:
+ tx_hash = txin['prevout_hash']
+
+ ptx = self.transactions.get(tx_hash)
+ if ptx is None:
+ ptx = self.network.synchronous_get(('blockchain.transaction.get', [tx_hash]))
+ ptx = Transaction(ptx)
+ prev_tx[tx_hash] = ptx
+
+ for x_pubkey in txin['x_pubkeys']:
+ account_derivation = None
+ if not is_extended_pubkey(x_pubkey):
+ continue
+ xpub = x_to_xpub(x_pubkey)
+ for k, v in self.master_public_keys.items():
+ if v == xpub:
+ account_id = re.match("x/(\d+)'", k).group(1)
+ account_derivation = "44'/0'/%s'"%account_id
+ if account_derivation is not None:
+ xpub_path[xpub] = account_derivation
+
+ self.plugin.sign_transaction(tx, prev_tx, xpub_path)
+
+ def check_proper_device(self):
+ self.get_client().ping('t')
+ if not self.device_checked:
+ address = self.addresses(False)[0]
+ address_id = self.address_id(address)
+ n = self.get_client().expand_path(address_id)
+ device_address = self.get_client().get_address('Bitcoin', n)
+ self.device_checked = True
+ self.proper_device = (device_address == address)
+
+ return self.proper_device
+
+
+
class Plugin(BasePlugin):
def __init__(self, parent, config, name):
@@ -125,107 +278,6 @@ class Plugin(BasePlugin):
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
@@ -347,169 +399,119 @@ class Plugin(BasePlugin):
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):
-class TrezorWallet(BIP32_HD_Wallet):
- wallet_type = 'trezor'
- root_derivation = "m/44'/0'"
-
- def __init__(self, storage):
- BIP32_HD_Wallet.__init__(self, storage)
- self.mpk = None
- self.device_checked = False
- self.proper_device = False
- self.force_watching_only = False
-
- def get_action(self):
- if not self.accounts:
- return 'create_accounts'
-
- def can_import(self):
- return False
-
- 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_export(self):
- return False
-
- def can_create_accounts(self):
- return True
-
- def can_change_password(self):
- return False
-
- def is_watching_only(self):
- return self.force_watching_only
-
- def get_client(self):
- return self.plugin.get_client()
-
- def address_id(self, address):
- account_id, (change, address_index) = self.get_address_index(address)
- return "44'/0'/%s'/%d/%d" % (account_id, change, address_index)
-
- def create_main_account(self, password):
- self.create_account('Main account', None) #name, empty password
-
- def mnemonic_to_seed(self, mnemonic, passphrase):
- # trezor uses bip39
- import pbkdf2, hashlib, hmac
- PBKDF2_ROUNDS = 2048
- mnemonic = unicodedata.normalize('NFKD', ' '.join(mnemonic.split()))
- passphrase = unicodedata.normalize('NFKD', passphrase)
- return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
-
- def derive_xkeys(self, root, derivation, password):
- x = self.master_private_keys.get(root)
- if x:
- root_xprv = pw_decode(x, password)
- xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
- return xpub, xprv
- else:
- derivation = derivation.replace(self.root_name,"44'/0'/")
- xpub = self.get_public_key(derivation)
- return xpub, None
-
- def get_public_key(self, bip32_path):
- address_n = self.plugin.get_client().expand_path(bip32_path)
- node = self.plugin.get_client().get_public_node(address_n).node
- xpub = "0488B21E".decode('hex') + chr(node.depth) + self.i4b(node.fingerprint) + self.i4b(node.child_num) + node.chain_code + node.public_key
- return EncodeBase58Check(xpub)
-
- def get_master_public_key(self):
- if not self.mpk:
- self.mpk = self.get_public_key("44'/0'")
- return self.mpk
+ @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
- def i4b(self, x):
- return pack('>I', x)
+ @hook
+ def installwizard_load_wallet(self, wallet, window):
+ if type(wallet) != TrezorWallet:
+ return
+ self.load_wallet(wallet, window)
- def add_keypairs(self, tx, keypairs, password):
- #do nothing - no priv keys available
- pass
+ @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
- def decrypt_message(self, pubkey, message, password):
- raise BaseException( _('Decrypt method is not implemented in Trezor') )
- #address = public_key_to_bc_address(pubkey.decode('hex'))
- #address_path = self.address_id(address)
- #address_n = self.get_client().expand_path(address_path)
- #try:
- # decrypted_msg = self.get_client().decrypt_message(address_n, b64decode(message))
- #except Exception, e:
- # give_error(e)
- #finally:
- # twd.stop()
- #return str(decrypted_msg)
+ @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 sign_message(self, address, message, password):
- if self.has_seed():
- return BIP32_HD_Wallet.sign_message(self, address, message, password)
- if not self.check_proper_device():
+ def show_address(self, address):
+ if not self.wallet.check_proper_device():
give_error('Wrong device or password')
try:
- address_path = self.address_id(address)
- address_n = self.plugin.get_client().expand_path(address_path)
+ address_path = self.wallet.address_id(address)
+ address_n = self.get_client().expand_path(address_path)
except Exception, e:
give_error(e)
try:
- msg_sig = self.plugin.get_client().sign_message('Bitcoin', address_n, message)
+ self.get_client().get_address('Bitcoin', address_n, True)
except Exception, e:
give_error(e)
finally:
- self.plugin.handler.stop()
- return msg_sig.signature
+ self.handler.stop()
- def sign_transaction(self, tx, password):
- if self.has_seed():
- return BIP32_HD_Wallet.sign_transaction(self, tx, password)
- if tx.is_complete():
+
+ def settings_dialog(self, window):
+ try:
+ device_id = self.get_client().get_device_id()
+ except BaseException as e:
+ window.show_message(str(e))
return
- if not self.check_proper_device():
- give_error('Wrong device or password')
- # previous transactions used as inputs
- prev_tx = {}
- # path of the xpubs that are involved
- xpub_path = {}
- for txin in tx.inputs:
- tx_hash = txin['prevout_hash']
+ 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)
- ptx = self.transactions.get(tx_hash)
- if ptx is None:
- ptx = self.network.synchronous_get(('blockchain.transaction.get', [tx_hash]))
- ptx = Transaction(ptx)
- prev_tx[tx_hash] = ptx
+ 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_()
- for x_pubkey in txin['x_pubkeys']:
- account_derivation = None
- if not is_extended_pubkey(x_pubkey):
- continue
- xpub = x_to_xpub(x_pubkey)
- for k, v in self.master_public_keys.items():
- if v == xpub:
- account_id = re.match("x/(\d+)'", k).group(1)
- account_derivation = "44'/0'/%s'"%account_id
- if account_derivation is not None:
- xpub_path[xpub] = account_derivation
- self.plugin.sign_transaction(tx, prev_tx, xpub_path)
- def check_proper_device(self):
- self.get_client().ping('t')
- if not self.device_checked:
- address = self.addresses(False)[0]
- address_id = self.address_id(address)
- 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
- return self.proper_device
class TrezorGuiMixin(object):
diff --git a/plugins/trustedcoin.py b/plugins/trustedcoin.py
@@ -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('')
+
+
diff --git a/plugins/virtualkeyboard.py b/plugins/virtualkeyboard.py
@@ -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