diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
index 039000a15..250552ef1 100644
--- a/gui/qt/installwizard.py
+++ b/gui/qt/installwizard.py
@@ -33,6 +33,7 @@ class InstallWizard(QDialog):
self.network = network
self.storage = storage
self.setMinimumSize(575, 400)
+ self.setMaximumSize(575, 400)
self.setWindowTitle('Electrum')
self.connect(self, QtCore.SIGNAL('accept'), self.accept)
@@ -313,16 +314,19 @@ class InstallWizard(QDialog):
return None
- def question(self, msg, icon=None):
+ def question(self, msg, yes_label=_('OK'), no_label=_('Cancel'), icon=None):
vbox = QVBoxLayout()
self.set_layout(vbox)
if icon:
logo = QLabel()
logo.setPixmap(icon)
vbox.addWidget(logo)
- vbox.addWidget(QLabel(msg))
+
+ label = QLabel(msg)
+ label.setWordWrap(True)
+ vbox.addWidget(label)
vbox.addStretch(1)
- vbox.addLayout(ok_cancel_buttons(self, _('OK')))
+ vbox.addLayout(ok_cancel_buttons(self, yes_label, no_label))
if not self.exec_():
return None
return True
@@ -343,29 +347,6 @@ class InstallWizard(QDialog):
return run_password_dialog(self, None, self)[2]
- def create_cold_seed(self, wallet):
- from electrum.bitcoin import mnemonic_to_seed, bip32_root
- msg = _('You are about to generate the cold storage seed of your wallet.') + '\n' \
- + _('For safety, you should do this on an offline computer.')
- icon = QPixmap( ':icons/cold_seed.png').scaledToWidth(56)
- if not self.question(msg, icon):
- return
-
- cold_seed = wallet.make_seed()
- if not self.show_seed(cold_seed, 'cold'):
- return
- if not self.verify_seed(cold_seed, 'cold'):
- return
-
- hex_seed = mnemonic_to_seed(cold_seed,'').encode('hex')
- xpriv, xpub = bip32_root(hex_seed)
- wallet.add_master_public_key('cold/', xpub)
-
- msg = _('Your master public key was saved in your wallet file.') + '\n'\
- + _('Your cold seed must be stored on paper; it is not in the wallet file.')+ '\n\n' \
- + _('This program is about to close itself.') + '\n'\
- + _('You will need to reopen your wallet on an online computer, in order to complete the creation of your wallet')
- self.show_message(msg)
@@ -429,14 +410,13 @@ class InstallWizard(QDialog):
return
self.waiting_dialog(wallet.synchronize)
- elif action == 'create_cold_seed':
- self.create_cold_seed(wallet)
- return
-
else:
- r = run_hook('install_wizard_action', self, wallet, action)
- if not r:
- raise BaseException('unknown wizard action', action)
+ f = run_hook('get_wizard_action', self, wallet, action)
+ if not f:
+ raise BaseException('unknown wizard action', action)
+ r = f(wallet, self)
+ if not r:
+ return
# next action
action = wallet.get_action()
@@ -558,6 +538,7 @@ class InstallWizard(QDialog):
wallet.create_main_account(password)
else:
+ self.storage.put('wallet_type', t)
wallet = run_hook('installwizard_restore', self, self.storage)
if not wallet:
return
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
index 57fb12836..a3795997d 100644
--- a/gui/qt/main_window.py
+++ b/gui/qt/main_window.py
@@ -1988,6 +1988,7 @@ class ElectrumWindow(QMainWindow):
decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
message_e.setText(decrypted)
except Exception as e:
+ traceback.print_exc(file=sys.stdout)
self.show_message(str(e))
@@ -1998,6 +1999,7 @@ class ElectrumWindow(QMainWindow):
encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
encrypted_e.setText(encrypted)
except Exception as e:
+ traceback.print_exc(file=sys.stdout)
self.show_message(str(e))
diff --git a/gui/qt/util.py b/gui/qt/util.py
index c021e8b38..4998498d2 100644
--- a/gui/qt/util.py
+++ b/gui/qt/util.py
@@ -94,10 +94,10 @@ def close_button(dialog, label=_("Close") ):
b.setDefault(True)
return hbox
-def ok_cancel_buttons2(dialog, ok_label=_("OK") ):
+def ok_cancel_buttons2(dialog, ok_label=_("OK"), cancel_label=_('Cancel')):
hbox = QHBoxLayout()
hbox.addStretch(1)
- b = QPushButton(_("Cancel"))
+ b = QPushButton(cancel_label)
hbox.addWidget(b)
b.clicked.connect(dialog.reject)
b = QPushButton(ok_label)
@@ -106,8 +106,8 @@ def ok_cancel_buttons2(dialog, ok_label=_("OK") ):
b.setDefault(True)
return hbox, b
-def ok_cancel_buttons(dialog, ok_label=_("OK") ):
- hbox, b = ok_cancel_buttons2(dialog, ok_label)
+def ok_cancel_buttons(dialog, ok_label=_("OK"), cancel_label=_('Cancel')):
+ hbox, b = ok_cancel_buttons2(dialog, ok_label, cancel_label)
return hbox
def line_dialog(parent, title, label, ok_label, default=None):
diff --git a/icons.qrc b/icons.qrc
index 5dce17bc8..97bae3b2e 100644
--- a/icons.qrc
+++ b/icons.qrc
@@ -25,5 +25,6 @@
icons/network.png
icons/dark_background.png
icons/qrcode.png
+ icons/trustedcoin.png
diff --git a/icons/trustedcoin.png b/icons/trustedcoin.png
new file mode 100644
index 000000000..2153866a6
Binary files /dev/null and b/icons/trustedcoin.png differ
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
index 39b339966..002dcdc29 100644
--- a/lib/bitcoin.py
+++ b/lib/bitcoin.py
@@ -706,10 +706,9 @@ def xpub_from_xprv(xprv, testnet=False):
return EncodeBase58Check(xpub)
-def bip32_root(mnemonic_seed, testnet=False):
+def bip32_root(seed, testnet=False):
import hmac
header_pub, header_priv = _get_headers(testnet)
- seed = mnemonic_to_seed(mnemonic_seed,'')
I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest()
master_k = I[0:32]
master_c = I[32:]
diff --git a/lib/commands.py b/lib/commands.py
index 4f9df0fc7..fc6dc855d 100644
--- a/lib/commands.py
+++ b/lib/commands.py
@@ -320,7 +320,7 @@ class Commands:
label, is_default_label = self.wallet.get_label(tx_hash)
- out.append({'txid':tx_hash, 'date':"%16s"%time_str, 'label':label, 'value':format_satoshis(value)})
+ out.append({'txid':tx_hash, 'date':"%16s"%time_str, 'label':label, 'value':format_satoshis(value), 'confirmations':conf})
return out
def setlabel(self, key, label):
diff --git a/lib/plugins.py b/lib/plugins.py
index feea2fcf0..46c1c3969 100644
--- a/lib/plugins.py
+++ b/lib/plugins.py
@@ -50,6 +50,7 @@ def run_hook(name, *args):
except Exception:
print_error("Plugin error")
traceback.print_exc(file=sys.stdout)
+ r = False
if r:
results.append(r)
diff --git a/lib/tests/test_bitcoin.py b/lib/tests/test_bitcoin.py
index e2227170f..c61df592e 100644
--- a/lib/tests/test_bitcoin.py
+++ b/lib/tests/test_bitcoin.py
@@ -68,7 +68,7 @@ class Test_bitcoin(unittest.TestCase):
assert xprv == "tprv8jTo9vZtZTSiTo6BBDjeQDgLEaipWPsrhYsQpZYoqqJNPKbCyDewkHJZhkoSHiWYCUf1Gm4TFzQxcG4D6s1J9Hsn4whDK7QYyHHokJeUuac"
def _do_test_bip32(self, seed, sequence, testnet):
- xprv, xpub = bip32_root(seed, testnet)
+ xprv, xpub = bip32_root(seed.decode('hex'), testnet)
assert sequence[0:2] == "m/"
path = 'm'
sequence = sequence[2:]
diff --git a/lib/tests/test_wallet.py b/lib/tests/test_wallet.py
index 49d22caf2..e537c9a06 100644
--- a/lib/tests/test_wallet.py
+++ b/lib/tests/test_wallet.py
@@ -3,6 +3,7 @@ import tempfile
import sys
import unittest
import os
+import json
from StringIO import StringIO
from lib.wallet import WalletStorage, NewWallet
@@ -97,20 +98,15 @@ class TestWalletStorage(WalletTestCase):
contents = ""
with open(path, "r") as f:
contents = f.read()
- self.assertEqual(repr(some_dict), contents)
+ self.assertEqual(some_dict, json.loads(contents))
class TestNewWallet(WalletTestCase):
- seed_text = "The seed will sprout and grow up tall."
+ seed_text = "travel nowhere air position hill peace suffer parent beautiful rise blood power home crumble teach"
password = "secret"
- master_xpub = "xpub661MyMwAqRbcGEop5Rnp68oX1ikeFNVMtx1utwXZGRKMmeXVxwBM5UzkwU9nGB1EofZekLDRfi1w5F9P7Vac3PEuWdWHr2gHLW8vp5YyKJ1"
- master_xpriv = "xprv9s21ZrQH143K3kjLyQFoizrnTgv9qumWXj6K6Z7wi5nNtrCMRPs6XggH6Bbgz9CUgPJnZnV74yUdRSr8qWVELr9QQTgU5aNL33ViMyD9nhs"
-
first_account_name = "account1"
- first_account_first_address = "1Jv9pLCJ4Sqr7aDYLGX5QhET4ps5qRcB9V"
- first_account_second_address = "14n9EsZsgTTc4eC4TxeP1ccP8bXgwxPMmL"
import_private_key = "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW"
import_key_address = "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma"
@@ -131,7 +127,8 @@ class TestNewWallet(WalletTestCase):
# in setUp()
new_dir = tempfile.mkdtemp()
config = FakeConfig(new_dir)
- wallet = NewWallet(config)
+ storage = WalletStorage(config)
+ wallet = NewWallet(storage)
self.assertTrue(wallet.is_watching_only())
shutil.rmtree(new_dir) # Don't leave useless stuff in /tmp
@@ -142,22 +139,6 @@ class TestNewWallet(WalletTestCase):
self.assertEqual(self.wallet.get_seed(self.password), self.seed_text)
self.assertEqual(0, len(self.wallet.addresses()))
- def test_add_account(self):
- self.wallet.create_account(self.first_account_name, self.password)
- self.assertEqual(1, len(self.wallet.addresses()))
- self.assertIn(self.first_account_first_address,
- self.wallet.addresses())
-
- def test_add_account_add_address(self):
- self.wallet.create_account(self.first_account_name, self.password)
- self.wallet.synchronizer = FakeSynchronizer()
-
- self.wallet.create_new_address()
- self.assertEqual(2, len(self.wallet.addresses()))
- self.assertIn(self.first_account_first_address,
- self.wallet.addresses())
- self.assertIn(self.first_account_second_address,
- self.wallet.addresses())
def test_key_import(self):
# Wallets have no imported keys by default.
diff --git a/lib/wallet.py b/lib/wallet.py
index 09830457f..5bb75839e 100644
--- a/lib/wallet.py
+++ b/lib/wallet.py
@@ -1385,7 +1385,7 @@ class BIP39_Wallet(BIP32_Wallet):
def create_master_keys(self, password):
seed = self.get_seed(password)
- xprv, xpub = bip32_root(seed)
+ xprv, xpub = bip32_root(mnemonic_to_seed(seed,''))
xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
self.add_master_public_key(self.root_name, xpub)
self.add_master_private_key(self.root_name, xprv, password)
@@ -1459,11 +1459,17 @@ class Wallet_2of2(BIP39_Wallet):
def add_cosigner_seed(self, seed, name, password):
# we don't store the seed, only the master xpriv
- xprv, xpub = bip32_root(seed)
+ xprv, xpub = bip32_root(mnemonic_to_seed(seed,''))
xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
self.add_master_public_key(name, xpub)
self.add_master_private_key(name, xprv, password)
+ def add_cosigner_xpub(self, seed, name):
+ # store only master xpub
+ xprv, xpub = bip32_root(mnemonic_to_seed(seed,''))
+ xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
+ self.add_master_public_key(name, xpub)
+
class Wallet_2of3(Wallet_2of2):
diff --git a/plugins/cosigner_pool.py b/plugins/cosigner_pool.py
index a8e097644..69f1b0a4a 100644
--- a/plugins/cosigner_pool.py
+++ b/plugins/cosigner_pool.py
@@ -45,42 +45,36 @@ class Listener(threading.Thread):
threading.Thread.__init__(self)
self.daemon = True
self.parent = parent
- self.key = None
+ self.keyname = None
+ self.keyhash = None
self.is_running = False
self.message = None
- self.delete = False
-
- def set_key(self, key):
- self.key = key
+
+ def set_key(self, keyname, keyhash):
+ self.keyname = keyname
+ self.keyhash = keyhash
def clear(self):
- self.delete = True
+ server.delete(self.keyhash)
+ self.message = None
+
def run(self):
self.is_running = True
while self.is_running:
-
- if not self.key:
+ if not self.keyhash:
time.sleep(2)
continue
-
if not self.message:
try:
- self.message = server.get(self.key)
+ self.message = server.get(self.keyhash)
except Exception as e:
util.print_error("cannot contact cosigner pool")
time.sleep(30)
continue
-
if self.message:
self.parent.win.emit(SIGNAL("cosigner:receive"))
- else:
- if self.delete:
- # save it to disk
- server.delete(self.key)
- self.message = None
- self.delete = False
-
+ # poll every 30 seconds
time.sleep(30)
@@ -109,21 +103,25 @@ class Plugin(BasePlugin):
self.load_wallet(self.win.wallet)
return True
+ def is_available(self):
+ if self.wallet is None:
+ return True
+ return self.wallet.wallet_type in ['2of2', '2of3']
+
def load_wallet(self, wallet):
self.wallet = wallet
+ if not self.is_available():
+ return
mpk = self.wallet.get_master_public_keys()
-
- self.cold = mpk.get('x2')
- if self.cold:
- self.cold_K = bitcoin.deserialize_xkey(self.cold)[-1].encode('hex')
- self.cold_hash = bitcoin.Hash(self.cold_K).encode('hex')
-
- self.hot = mpk.get('x1')
- if self.hot:
- self.hot_K = bitcoin.deserialize_xkey(self.hot)[-1].encode('hex')
- self.hot_hash = bitcoin.Hash(self.hot_K).encode('hex')
- self.listener.set_key(self.hot_hash)
-
+ self.cosigner_list = []
+ for key, xpub in mpk.items():
+ keyname = key + '/' # fixme
+ K = bitcoin.deserialize_xkey(xpub)[-1].encode('hex')
+ _hash = bitcoin.Hash(K).encode('hex')
+ if self.wallet.master_private_keys.get(keyname):
+ self.listener.set_key(keyname, _hash)
+ else:
+ self.cosigner_list.append((xpub, K, _hash))
def transaction_dialog(self, d):
self.send_button = b = QPushButton(_("Send to cosigner"))
@@ -131,18 +129,18 @@ class Plugin(BasePlugin):
d.buttons.insertWidget(2, b)
self.transaction_dialog_update(d)
-
def transaction_dialog_update(self, d):
if d.tx.is_complete():
self.send_button.hide()
return
- if self.cosigner_can_sign(d.tx):
- self.send_button.show()
+ for xpub, K, _hash in self.cosigner_list:
+ if self.cosigner_can_sign(d.tx, xpub):
+ self.send_button.show()
+ break
else:
self.send_button.hide()
-
- def cosigner_can_sign(self, tx):
+ def cosigner_can_sign(self, tx, cosigner_xpub):
from electrum.transaction import x_to_xpub
xpub_set = set([])
for txin in tx.inputs:
@@ -151,24 +149,21 @@ class Plugin(BasePlugin):
if xpub:
xpub_set.add(xpub)
- return self.cold in xpub_set
-
+ return cosigner_xpub in xpub_set
def do_send(self, tx):
- if not self.cosigner_can_sign(tx):
- return
-
- message = bitcoin.encrypt_message(tx.raw, self.cold_K)
-
- try:
- server.put(self.cold_hash, message)
- self.win.show_message("Your transaction was sent to the cosigning pool.\nOpen your cosigner wallet to retrieve it.")
- except Exception as e:
- self.win.show_message(str(e))
-
+ for xpub, K, _hash in self.cosigner_list:
+ if not self.cosigner_can_sign(tx, xpub):
+ continue
+ message = bitcoin.encrypt_message(tx.raw, K)
+ try:
+ server.put(_hash, message)
+ except Exception as e:
+ self.win.show_message(str(e))
+ return
+ self.win.show_message("Your transaction was sent to the cosigning pool.\nOpen your cosigner wallet to retrieve it.")
def on_receive(self):
-
if self.wallet.use_encryption:
password = self.win.password_dialog('An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.')
if not password:
@@ -177,11 +172,12 @@ class Plugin(BasePlugin):
password = None
message = self.listener.message
- xpriv = self.wallet.get_master_private_key('x1/', password)
- if not xpriv:
+ key = self.listener.keyname
+ xprv = self.wallet.get_master_private_key(key, password)
+ if not xprv:
return
try:
- k = bitcoin.deserialize_xkey(xpriv)[-1].encode('hex')
+ k = bitcoin.deserialize_xkey(xprv)[-1].encode('hex')
EC = bitcoin.EC_KEY(k.decode('hex'))
message = EC.decrypt_message(message)
except Exception as e:
@@ -190,7 +186,6 @@ class Plugin(BasePlugin):
return
self.listener.clear()
-
tx = transaction.Transaction.deserialize(message)
self.win.show_transaction(tx)
diff --git a/plugins/trezor.py b/plugins/trezor.py
index 0ebfb6dc9..79bb85e0d 100644
--- a/plugins/trezor.py
+++ b/plugins/trezor.py
@@ -1,4 +1,4 @@
-from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL
+from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from binascii import unhexlify
from struct import pack
@@ -7,7 +7,7 @@ from time import sleep
from base64 import b64encode, b64decode
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
-from electrum_gui.qt.util import ok_cancel_buttons
+from electrum_gui.qt.util import ok_cancel_buttons, EnterButton
from electrum.account import BIP32_Account
from electrum.bitcoin import EncodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160
from electrum.i18n import _
@@ -43,6 +43,7 @@ class Plugin(BasePlugin):
def __init__(self, gui, name):
BasePlugin.__init__(self, gui, name)
self._is_available = self._init()
+ self._requires_settings = True
self.wallet = None
def _init(self):
@@ -55,6 +56,9 @@ class Plugin(BasePlugin):
return True
return False
+ def requires_settings(self):
+ return self._requires_settings
+
def set_enabled(self, enabled):
self.wallet.storage.put('use_' + self.name, enabled)
@@ -77,6 +81,8 @@ class Plugin(BasePlugin):
wallet_types.append(('trezor', _("Trezor wallet"), TrezorWallet))
def installwizard_restore(self, wizard, storage):
+ if storage.get('wallet_type') != 'trezor':
+ return
wallet = TrezorWallet(storage)
try:
wallet.create_main_account(None)
@@ -91,6 +97,41 @@ class Plugin(BasePlugin):
except Exception as e:
tx.error = str(e)
+ def settings_widget(self, window):
+ return EnterButton(_('Settings'), self.settings_dialog)
+
+ def settings_dialog(self):
+ get_label = lambda: self.wallet.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" % self.wallet.get_client().get_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])
+ twd.start("Please confirm label change on Trezor")
+ status = self.wallet.get_client().apply_settings(label=new_label)
+ twd.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)
+
+ if d.exec_():
+ return True
+ else:
+ return False
+
class TrezorWallet(NewWallet):
wallet_type = 'trezor'
@@ -138,7 +179,7 @@ class TrezorWallet(NewWallet):
def address_id(self, address):
account_id, (change, address_index) = self.get_address_index(address)
- return "%s/%d/%d" % (account_id, change, address_index)
+ 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
@@ -167,12 +208,9 @@ class TrezorWallet(NewWallet):
pass
def decrypt_message(self, pubkey, message, password):
- try:
- address = public_key_to_bc_address(pubkey.decode('hex'))
- address_path = self.address_id(address)
- address_n = self.get_client().expand_path(address_path)
- except Exception, e:
- raise e
+ 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:
@@ -182,6 +220,8 @@ class TrezorWallet(NewWallet):
return str(decrypted_msg)
def 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.get_client().expand_path(address_path)