Browse Source

plugins: separate GUIs using child classes

283
ThomasV 9 years ago
parent
commit
2c0489c809
  1. 2
      gui/qt/main_window.py
  2. 11
      lib/plugins.py
  3. 2
      plugins/audio_modem.py
  4. 194
      plugins/btchipwallet.py
  5. 2
      plugins/cosigner_pool.py
  6. 2
      plugins/email_requests.py
  7. 175
      plugins/exchange_rate.py
  8. 2
      plugins/greenaddress_instant.py
  9. 498
      plugins/keepkey.py
  10. 171
      plugins/labels.py
  11. 2
      plugins/plot.py
  12. 500
      plugins/trezor.py
  13. 19
      plugins/trustedcoin.py
  14. 2
      plugins/virtualkeyboard.py

2
gui/qt/main_window.py

@ -107,7 +107,6 @@ expiration_values = [
class ElectrumWindow(QMainWindow, PrintError): class ElectrumWindow(QMainWindow, PrintError):
labelsChanged = pyqtSignal()
def __init__(self, config, network, gui_object): def __init__(self, config, network, gui_object):
QMainWindow.__init__(self) 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_ok'), self.payment_request_ok)
self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error) self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
self.labelsChanged.connect(self.update_tabs)
self.history_list.setFocus(True) self.history_list.setFocus(True)
# network callbacks # network callbacks

11
lib/plugins.py

@ -40,6 +40,7 @@ class Plugins(PrintError):
self.plugins = {} self.plugins = {}
self.network = None self.network = None
self.gui_name = gui_name
self.descriptions = plugins.descriptions self.descriptions = plugins.descriptions
for item in self.descriptions: for item in self.descriptions:
name = item['name'] name = item['name']
@ -66,7 +67,15 @@ class Plugins(PrintError):
p = imp.load_source(full_name, path) p = imp.load_source(full_name, path)
else: else:
p = __import__(full_name, fromlist=['electrum_plugins']) 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: if self.network:
self.network.add_jobs(plugin.thread_jobs()) self.network.add_jobs(plugin.thread_jobs())
self.plugins[name] = plugin self.plugins[name] = plugin

2
plugins/audio_modem.py

@ -25,7 +25,7 @@ except ImportError:
print_error('Audio MODEM is not found.') print_error('Audio MODEM is not found.')
class Plugin(BasePlugin): class QtPlugin(BasePlugin):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name) BasePlugin.__init__(self, parent, config, name)

194
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 unhexlify
from binascii import hexlify from binascii import hexlify
from struct import pack,unpack from struct import pack,unpack
@ -7,7 +5,6 @@ from sys import stderr
from time import sleep from time import sleep
import electrum import electrum
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
from electrum.account import BIP32_Account from electrum.account import BIP32_Account
from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160 from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160
from electrum.i18n import _ from electrum.i18n import _
@ -32,96 +29,6 @@ try:
except ImportError: except ImportError:
BTCHIP = False 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): class BTChipWallet(BIP32_HD_Wallet):
wallet_type = 'btchip' wallet_type = 'btchip'
@ -517,6 +424,98 @@ class BTChipWallet(BIP32_HD_Wallet):
return False, None, None return False, None, None
return True, response, response 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: class BTChipQTHandler:
def __init__(self, win): def __init__(self, win):
@ -563,6 +562,15 @@ class BTChipQTHandler:
self.d.hide() self.d.hide()
self.d = None 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: class BTChipCmdLineHandler:
def stop(self): def stop(self):

2
plugins/cosigner_pool.py

@ -79,7 +79,7 @@ class Listener(util.DaemonThread):
time.sleep(30) time.sleep(30)
class Plugin(BasePlugin): class QtPlugin(BasePlugin):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name) BasePlugin.__init__(self, parent, config, name)

2
plugins/email_requests.py

@ -101,7 +101,7 @@ class Processor(threading.Thread):
s.quit() s.quit()
class Plugin(BasePlugin): class QtPlugin(BasePlugin):
def fullname(self): def fullname(self):
return 'Email' return 'Email'

175
plugins/exchange_rate.py

@ -1,6 +1,3 @@
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from datetime import datetime from datetime import datetime
import inspect import inspect
import requests import requests
@ -17,8 +14,7 @@ from electrum.plugins import BasePlugin, hook
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError, ThreadJob, timestamp_to_datetime from electrum.util import PrintError, ThreadJob, timestamp_to_datetime
from electrum.util import format_satoshis 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 # See https://en.wikipedia.org/wiki/ISO_4217
CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0, 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 @hook
def on_new_window(self, window): def format_amount_and_units(self, btc_balance):
# Additional send and receive edit boxes rate = self.exchange_rate()
send_e = AmountEdit(self.config_ccy) return '' if rate is None else " (%s %s)" % (self.value_str(btc_balance, rate), self.ccy)
window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft)
window.amount_e.frozen.connect( @hook
lambda: send_e.setFrozen(window.amount_e.isReadOnly())) def get_fiat_status_text(self, btc_balance):
receive_e = AmountEdit(self.config_ccy) rate = self.exchange_rate()
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft) return _(" (No FX rate available)") if rate is None else "1 BTC~%s %s" % (self.value_str(COIN, rate), self.ccy)
window.fiat_send_e = send_e
window.fiat_receive_e = receive_e def get_historical_rates(self):
self.connect_fields(window, window.amount_e, send_e, window.fee_e) if self.show_history():
self.connect_fields(window, window.receive_amount_e, receive_e, None) self.exchange.get_historical_rates(self.ccy)
window.history_list.refresh_headers()
window.update_status() def requires_settings(self):
window.connect(window.app, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window)) return True
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)) def value_str(self, satoshis, rate):
window.connect(window.app, SIGNAL('refresh_headers'), window.history_list.refresh_headers) 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): def connect_fields(self, window, btc_e, fiat_e, fee_e):
@ -412,68 +462,25 @@ class Plugin(BasePlugin, ThreadJob):
combo.blockSignals(False) combo.blockSignals(False)
combo.setCurrentIndex(combo.findText(self.ccy)) 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 @hook
def historical_value_str(self, satoshis, d_t): def on_new_window(self, window):
rate = self.exchange.historical_rate(self.ccy, d_t) # Additional send and receive edit boxes
# Frequently there is no rate for today, until tomorrow :) send_e = AmountEdit(self.config_ccy)
# Use spot quotes in that case window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft)
if rate is None and (datetime.today().date() - d_t.date()).days <= 2: window.amount_e.frozen.connect(
rate = self.exchange.quotes.get(self.ccy) lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
self.history_used_spot = True receive_e = AmountEdit(self.config_ccy)
return self.value_str(satoshis, rate) window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
window.fiat_send_e = send_e
@hook window.fiat_receive_e = receive_e
def history_tab_headers(self, headers): self.connect_fields(window, window.amount_e, send_e, window.fee_e)
if self.show_history(): self.connect_fields(window, window.receive_amount_e, receive_e, None)
headers.extend(['%s '%self.ccy + _('Amount'), '%s '%self.ccy + _('Balance')]) window.history_list.refresh_headers()
window.update_status()
@hook window.connect(window.app, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window))
def history_tab_update_begin(self): window.connect(window.app, SIGNAL('new_fx_history'), lambda: self.on_fx_history(window))
self.history_used_spot = False 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)
@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 settings_widget(self, window): def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog) return EnterButton(_('Settings'), self.settings_dialog)

2
plugins/greenaddress_instant.py

@ -28,7 +28,7 @@ from electrum.i18n import _
class Plugin(BasePlugin): class QtPlugin(BasePlugin):
button_label = _("Verify GA instant") button_label = _("Verify GA instant")

498
plugins/keepkey.py

@ -7,8 +7,6 @@ import threading
import re import re
from functools import partial from functools import partial
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
import electrum import electrum
from electrum import bitcoin 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.util import print_error, print_msg
from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root 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: try:
from keepkeylib.client import types from keepkeylib.client import types
from keepkeylib.client import proto, BaseClient, ProtocolMixin from keepkeylib.client import proto, BaseClient, ProtocolMixin
from keepkeylib.qt.pinmatrix import PinMatrixWidget
from keepkeylib.transport import ConnectionError from keepkeylib.transport import ConnectionError
from keepkeylib.transport_hid import HidTransport from keepkeylib.transport_hid import HidTransport
KEEPKEY = True KEEPKEY = True
@ -47,6 +41,172 @@ def give_error(message):
raise Exception(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): class Plugin(BasePlugin):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
@ -118,64 +278,6 @@ class Plugin(BasePlugin):
self.client = None self.client = None
self.wallet = 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): def show_address(self, address):
if not self.wallet.check_proper_device(): if not self.wallet.check_proper_device():
@ -193,39 +295,6 @@ class Plugin(BasePlugin):
self.handler.stop() 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): def sign_transaction(self, tx, prev_tx, xpub_path):
self.prev_tx = prev_tx self.prev_tx = prev_tx
self.xpub_path = xpub_path self.xpub_path = xpub_path
@ -350,167 +419,110 @@ class Plugin(BasePlugin):
class KeepKeyWallet(BIP32_HD_Wallet): from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
wallet_type = 'keepkey' import PyQt4.QtCore as QtCore
root_derivation = "m/44'/0'" from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
def __init__(self, storage): from electrum_gui.qt.installwizard import InstallWizard
BIP32_HD_Wallet.__init__(self, storage) from keepkeylib.qt.pinmatrix import PinMatrixWidget
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): class QtPlugin(Plugin):
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): @hook
if not self.mpk: def load_wallet(self, wallet, window):
self.mpk = self.get_public_key("44'/0'") self.print_error("load_wallet")
return self.mpk 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): @hook
return pack('>I', x) def installwizard_load_wallet(self, wallet, window):
if type(wallet) != KeepKeyWallet:
return
self.load_wallet(wallet, window)
def add_keypairs(self, tx, keypairs, password): @hook
#do nothing - no priv keys available def installwizard_restore(self, wizard, storage):
pass 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): @hook
raise BaseException( _('Decrypt method is not implemented in KeepKey') ) def receive_menu(self, menu, addrs):
#address = public_key_to_bc_address(pubkey.decode('hex')) if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
#address_path = self.address_id(address) menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
#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): def settings_dialog(self, window):
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: try:
msg_sig = self.plugin.get_client().sign_message('Bitcoin', address_n, message) device_id = self.get_client().get_device_id()
except Exception, e: except BaseException as e:
give_error(e) window.show_message(str(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 return
if not self.check_proper_device(): get_label = lambda: self.get_client().features.label
give_error('Wrong device or password') update_label = lambda: current_label_label.setText("Label: %s" % get_label())
# previous transactions used as inputs d = QDialog()
prev_tx = {} layout = QGridLayout(d)
# path of the xpubs that are involved layout.addWidget(QLabel("KeepKey Options"),0,0)
xpub_path = {} layout.addWidget(QLabel("ID:"),1,0)
for txin in tx.inputs: layout.addWidget(QLabel(" %s" % device_id),1,1)
tx_hash = txin['prevout_hash']
ptx = self.transactions.get(tx_hash) def modify_label():
if ptx is None: response = QInputDialog().getText(None, "Set New KeepKey Label", "New KeepKey Label: (upon submission confirm on KeepKey)")
ptx = self.network.synchronous_get(('blockchain.transaction.get', [tx_hash])) if not response[1]:
ptx = Transaction(ptx) return
prev_tx[tx_hash] = ptx 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']: current_label_label = QLabel()
account_derivation = None update_label()
if not is_extended_pubkey(x_pubkey): change_label_button = QPushButton("Modify")
continue change_label_button.clicked.connect(modify_label)
xpub = x_to_xpub(x_pubkey) layout.addWidget(current_label_label,3,0)
for k, v in self.master_public_keys.items(): layout.addWidget(change_label_button,3,1)
if v == xpub: d.exec_()
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): class CmdlinePlugin(Plugin):
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: @hook
self.proper_device = False def cmdline_load_wallet(self, wallet):
else: self.wallet = wallet
self.proper_device = True self.wallet.plugin = self
if self.handler is None:
self.handler = KeepKeyCmdLineHandler()
return self.proper_device
class KeepKeyGuiMixin(object): class KeepKeyGuiMixin(object):

171
plugins/labels.py

@ -7,15 +7,6 @@ import sys
import traceback import traceback
from functools import partial 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 aes
import base64 import base64
@ -23,8 +14,6 @@ import electrum
from electrum.plugins import BasePlugin, hook from electrum.plugins import BasePlugin, hook
from electrum.i18n import _ from electrum.i18n import _
from electrum_gui.qt import HelpButton, EnterButton
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
class Plugin(BasePlugin): class Plugin(BasePlugin):
@ -32,34 +21,6 @@ class Plugin(BasePlugin):
BasePlugin.__init__(self, parent, config, name) BasePlugin.__init__(self, parent, config, name)
self.target_host = 'sync.bytesized-hosting.com:9090' self.target_host = 'sync.bytesized-hosting.com:9090'
self.wallets = {} 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): def encode(self, wallet, msg):
password, iv, wallet_id = self.wallets[wallet] password, iv, wallet_id = self.wallets[wallet]
@ -85,9 +46,6 @@ class Plugin(BasePlugin):
self.print_error("set", wallet.basename(), "nonce to", nonce) self.print_error("set", wallet.basename(), "nonce to", nonce)
wallet.storage.put("wallet_nonce", nonce, force_write) wallet.storage.put("wallet_nonce", nonce, force_write)
def requires_settings(self):
return True
@hook @hook
def set_label(self, wallet, item, label): def set_label(self, wallet, item, label):
if not wallet in self.wallets: if not wallet in self.wallets:
@ -105,47 +63,6 @@ class Plugin(BasePlugin):
# Caller will write the wallet # Caller will write the wallet
self.set_nonce(wallet, nonce + 1, force_write=False) 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): def do_request(self, method, url = "/labels", is_batch=False, data=None):
url = 'https://' + self.target_host + url url = 'https://' + self.target_host + url
kwargs = {'headers': {}} kwargs = {'headers': {}}
@ -162,8 +79,7 @@ class Plugin(BasePlugin):
raise BaseException(response["error"]) raise BaseException(response["error"])
return response return response
def push_thread(self, window): def push_thread(self, wallet):
wallet = window.wallet
wallet_id = self.wallets[wallet][2] wallet_id = self.wallets[wallet][2]
bundle = {"labels": [], bundle = {"labels": [],
"walletId": wallet_id, "walletId": wallet_id,
@ -179,14 +95,14 @@ class Plugin(BasePlugin):
'externalId': encoded_key}) 'externalId': encoded_key})
self.do_request("POST", "/labels", True, bundle) self.do_request("POST", "/labels", True, bundle)
def pull_thread(self, window, force): def pull_thread(self, wallet, force):
wallet = window.wallet
wallet_id = self.wallets[wallet][2] wallet_id = self.wallets[wallet][2]
nonce = 1 if force else self.get_nonce(wallet) - 1 nonce = 1 if force else self.get_nonce(wallet) - 1
self.print_error("asking for labels since nonce", nonce) self.print_error("asking for labels since nonce", nonce)
try: try:
response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id) )) response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id) ))
if response["labels"] is None: if response["labels"] is None:
self.print_error('no new labels')
return return
result = {} result = {}
for label in response["labels"]: for label in response["labels"]:
@ -208,9 +124,86 @@ class Plugin(BasePlugin):
wallet.labels[key] = value wallet.labels[key] = value
self.print_error("received %d labels" % len(response)) self.print_error("received %d labels" % len(response))
self.obj.emit(SIGNAL('labels:pulled'), window, # do not write to disk because we're in a daemon thread
response["nonce"] + 1) wallet.storage.put('labels', wallet.labels, False)
self.set_nonce(wallet, response["nonce"] + 1, False)
self.on_pulled(wallet)
except Exception as e: except Exception as e:
traceback.print_exc(file=sys.stderr) traceback.print_exc(file=sys.stderr)
self.print_error("could not retrieve labels") 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)

2
plugins/plot.py

@ -17,7 +17,7 @@ except:
flag_matlib=False flag_matlib=False
class Plugin(BasePlugin): class QtPlugin(BasePlugin):
def is_available(self): def is_available(self):
if flag_matlib: if flag_matlib:

500
plugins/trezor.py

@ -7,8 +7,6 @@ import threading
import re import re
from functools import partial from functools import partial
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
import electrum import electrum
from electrum import bitcoin 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.util import print_error, print_msg
from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root 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: try:
from trezorlib.client import types from trezorlib.client import types
from trezorlib.client import proto, BaseClient, ProtocolMixin from trezorlib.client import proto, BaseClient, ProtocolMixin
from trezorlib.qt.pinmatrix import PinMatrixWidget
from trezorlib.transport import ConnectionError from trezorlib.transport import ConnectionError
from trezorlib.transport_hid import HidTransport from trezorlib.transport_hid import HidTransport
TREZOR = True TREZOR = True
@ -47,6 +40,166 @@ def give_error(message):
raise Exception(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): class Plugin(BasePlugin):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
@ -125,107 +278,6 @@ class Plugin(BasePlugin):
if self.handler is None: if self.handler is None:
self.handler = TrezorCmdLineHandler() 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): def sign_transaction(self, tx, prev_tx, xpub_path):
self.prev_tx = prev_tx self.prev_tx = prev_tx
self.xpub_path = xpub_path self.xpub_path = xpub_path
@ -347,169 +399,119 @@ class Plugin(BasePlugin):
return self.electrum_tx_to_txtype(tx) 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): @hook
wallet_type = 'trezor' def load_wallet(self, wallet, window):
root_derivation = "m/44'/0'" self.print_error("load_wallet")
self.wallet = wallet
def __init__(self, storage): self.wallet.plugin = self
BIP32_HD_Wallet.__init__(self, storage) self.trezor_button = StatusBarButton(QIcon(":icons/trezor.png"), _("Trezor"), partial(self.settings_dialog, window))
self.mpk = None if type(window) is ElectrumWindow:
self.device_checked = False window.statusBar().addPermanentWidget(self.trezor_button)
self.proper_device = False if self.handler is None:
self.force_watching_only = False self.handler = TrezorQtHandler(window)
try:
def get_action(self): self.get_client().ping('t')
if not self.accounts: except BaseException as e:
return 'create_accounts' 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
def can_import(self): return
return False if self.wallet.addresses() and not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
def can_sign_xpubkey(self, x_pubkey): self.wallet.force_watching_only = True
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): @hook
return pack('>I', x) def installwizard_load_wallet(self, wallet, window):
if type(wallet) != TrezorWallet:
return
self.load_wallet(wallet, window)
def add_keypairs(self, tx, keypairs, password): @hook
#do nothing - no priv keys available def installwizard_restore(self, wizard, storage):
pass 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): @hook
raise BaseException( _('Decrypt method is not implemented in Trezor') ) def receive_menu(self, menu, addrs):
#address = public_key_to_bc_address(pubkey.decode('hex')) if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
#address_path = self.address_id(address) menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
#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): def show_address(self, address):
if self.has_seed(): if not self.wallet.check_proper_device():
return BIP32_HD_Wallet.sign_message(self, address, message, password)
if not self.check_proper_device():
give_error('Wrong device or password') give_error('Wrong device or password')
try: try:
address_path = self.address_id(address) address_path = self.wallet.address_id(address)
address_n = self.plugin.get_client().expand_path(address_path) address_n = self.get_client().expand_path(address_path)
except Exception, e: except Exception, e:
give_error(e) give_error(e)
try: 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: except Exception, e:
give_error(e) give_error(e)
finally: finally:
self.plugin.handler.stop() self.handler.stop()
return msg_sig.signature
def sign_transaction(self, tx, password):
if self.has_seed(): def settings_dialog(self, window):
return BIP32_HD_Wallet.sign_transaction(self, tx, password) try:
if tx.is_complete(): device_id = self.get_client().get_device_id()
except BaseException as e:
window.show_message(str(e))
return return
if not self.check_proper_device(): get_label = lambda: self.get_client().features.label
give_error('Wrong device or password') update_label = lambda: current_label_label.setText("Label: %s" % get_label())
# previous transactions used as inputs d = QDialog()
prev_tx = {} layout = QGridLayout(d)
# path of the xpubs that are involved layout.addWidget(QLabel("Trezor Options"),0,0)
xpub_path = {} layout.addWidget(QLabel("ID:"),1,0)
for txin in tx.inputs: layout.addWidget(QLabel(" %s" % device_id),1,1)
tx_hash = txin['prevout_hash']
ptx = self.transactions.get(tx_hash) def modify_label():
if ptx is None: response = QInputDialog().getText(None, "Set New Trezor Label", "New Trezor Label: (upon submission confirm on Trezor)")
ptx = self.network.synchronous_get(('blockchain.transaction.get', [tx_hash])) if not response[1]:
ptx = Transaction(ptx) return
prev_tx[tx_hash] = ptx 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): class TrezorGuiMixin(object):

19
plugins/trustedcoin.py

@ -27,9 +27,6 @@ from urlparse import urljoin
from urllib import quote from urllib import quote
from functools import partial from functools import partial
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import electrum import electrum
from electrum import bitcoin from electrum import bitcoin
from electrum.bitcoin import * from electrum.bitcoin import *
@ -39,10 +36,6 @@ from electrum.wallet import Multisig_Wallet, BIP32_Wallet
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugins import BasePlugin, run_hook, hook 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 from decimal import Decimal
@ -460,6 +453,16 @@ class Plugin(BasePlugin):
wallet.add_master_public_key('x3/', xpub3) wallet.add_master_public_key('x3/', xpub3)
return True 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): def auth_dialog(self, window):
d = QDialog(window) d = QDialog(window)
d.setModal(1) d.setModal(1)
@ -674,3 +677,5 @@ class Plugin(BasePlugin):
except: except:
QMessageBox.information(window, _('Message'), _('Incorrect password'), _('OK')) QMessageBox.information(window, _('Message'), _('Incorrect password'), _('OK'))
pw.setText('') pw.setText('')

2
plugins/virtualkeyboard.py

@ -3,7 +3,7 @@ from electrum.plugins import BasePlugin, hook
from electrum.i18n import _ from electrum.i18n import _
import random import random
class Plugin(BasePlugin): class QtPlugin(BasePlugin):
vkb = None vkb = None
vkb_index = 0 vkb_index = 0

Loading…
Cancel
Save