Browse Source

Merge branch 'master' into btchip

283
BTChip 10 years ago
parent
commit
11961ae811
  1. 47
      gui/qt/installwizard.py
  2. 2
      gui/qt/main_window.py
  3. 8
      gui/qt/util.py
  4. 1
      icons.qrc
  5. BIN
      icons/trustedcoin.png
  6. 3
      lib/bitcoin.py
  7. 2
      lib/commands.py
  8. 1
      lib/plugins.py
  9. 2
      lib/tests/test_bitcoin.py
  10. 29
      lib/tests/test_wallet.py
  11. 10
      lib/wallet.py
  12. 101
      plugins/cosigner_pool.py
  13. 58
      plugins/trezor.py

47
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

2
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))

8
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):

1
icons.qrc

@ -25,5 +25,6 @@
<file>icons/network.png</file>
<file>icons/dark_background.png</file>
<file>icons/qrcode.png</file>
<file>icons/trustedcoin.png</file>
</qresource>
</RCC>

BIN
icons/trustedcoin.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

3
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:]

2
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):

1
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)

2
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:]

29
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.

10
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):

101
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)

58
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)

Loading…
Cancel
Save