merge upstream, fix setup.py conflict

This commit is contained in:
Riccardo Spagni 2015-02-04 16:03:03 +02:00
commit ab6837485f
No known key found for this signature in database
GPG Key ID: 55432DF31CCD4FCD
35 changed files with 452 additions and 317 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ gui/qt/icons_rc.py
locale/ locale/
.devlocaltmp/ .devlocaltmp/
*_trial_temp *_trial_temp
packages

View File

@ -7,6 +7,7 @@ recursive-include lib *.py
recursive-include gui *.py recursive-include gui *.py
recursive-include plugins *.py recursive-include plugins *.py
recursive-include packages *.py recursive-include packages *.py
recursive-include packages cacert.pem
include app.fil include app.fil
include icons.qrc include icons.qrc
recursive-include icons * recursive-include icons *

View File

@ -1,28 +1,32 @@
# Release 2.0 # Release 2.0
* New address derivation (BIP32 + BIP44). * New address derivation (BIP32).
* 8 bits of the seed phrase are used to store a version number. The * New seed phrase format: 8 bits of the seed phrase are used to store
current version number (0x01) refers to the current wallet a version number. The current version number (0x01) refers to the
structure (BIP44). The version number also serves as a checksum for default wallet structure. The version number also serves as a
the seed, and it will prevent the import of seeds from incompatible checksum for the seed, and it will prevent the import of seeds from
wallets. incompatible wallets.
* New serialization format for unsigned or partially signed * Compact serialization format for unsigned or partially signed
transactions, that includes the master public key and derivation transactions, that includes the master public key and derivation
needed to sign inputs. This new format is compact enough to needed to sign inputs. This allows to send partially signed
send transactions to cold storage using QR codes transactions using QR codes
* Deterministic Multisig wallets using parallel BIP32 derivations and * Deterministic Multisig wallets using parallel BIP32 derivations and
P2SH addresses (2 of 2, 2 of 3). P2SH addresses (2 of 2, 2 of 3).
* New plugins: * New plugins:
- TrustedCoin: two-factor authentication using 2 of 3 multisig and
Google Authenticator
- Trezor: support for the Trezor hardware wallet by SatoshiLabs - Trezor: support for the Trezor hardware wallet by SatoshiLabs
- Cosigner Pool: shared memory pool for partially signed transactions - Cosigner Pool: encrypted communication channel for multisig
wallets, to send and receive partially signed transactions
- Audio Modem: send and receive transactions by sound
* BIP70: verification of signed payment requests * Support for BIP70: payment requests
- Verification is pure python, using tlslite. - Verification of the signature chain uses tlslite.
- In the GUI, payment requests are in the 'Invoices' tab. - In the GUI, payment requests are shown in the 'Invoices' tab.
* New 'Receive' tab: * New 'Receive' tab:
- create and manage payment requests, with QR Codes - create and manage payment requests, with QR Codes
@ -31,7 +35,7 @@
window that pops up if you click on the QR code window that pops up if you click on the QR code
* The 'Send' tab in the Qt GUI supports transactions with multiple * The 'Send' tab in the Qt GUI supports transactions with multiple
outputs, and with OP_RETURN "message" outputs, and raw hexadecimal scripts.
* The GUI can use the daemon: "electrum -d". The daemon can serve * The GUI can use the daemon: "electrum -d". The daemon can serve
several clients. It times out if no client uses if for more than 5 several clients. It times out if no client uses if for more than 5
@ -42,8 +46,15 @@
* Wallet files are saved as JSON instead of Python. * Wallet files are saved as JSON instead of Python.
* Client supports servers with SSL certificates signed by a CA.
* Documentation is now hosted on a wiki: http://electrum.orain.org * Documentation is now hosted on a wiki: http://electrum.orain.org
* ECIES encrypt/decrypt methods, availabe in the GUI and using the
command line:
encrypt <pubkey> <message>
decrypt <pubkey> <message>
# Release 1.9.8 # Release 1.9.8

View File

@ -1,23 +1,20 @@
#!/usr/bin/python #!/usr/bin/python
from lib.version import ELECTRUM_VERSION as version
if __name__ == '__main__': if __name__ == '__main__':
import sys, re, shutil, os, hashlib import sys, re, shutil, os, hashlib
import imp
os.chdir(os.path.dirname(os.path.realpath(__file__)))
os.chdir('..')
imp.load_module('electrum', *imp.find_module('../lib'))
from electrum.version import ELECTRUM_VERSION as version
if not ( os.path.exists('packages')): if not ( os.path.exists('packages')):
print "The packages directory is missing." print "The packages directory is missing."
sys.exit() sys.exit()
# os.system("python mki18n.py")
os.system("pyrcc4 icons.qrc -o gui/qt/icons_rc.py")
os.system("python setup.py sdist --format=zip,gztar")
_tgz="Electrum-%s.tar.gz"%version
_zip="Electrum-%s.zip"%version
# android
os.system('rm -rf dist/e4a-%s'%version) os.system('rm -rf dist/e4a-%s'%version)
os.mkdir('dist/e4a-%s'%version) os.mkdir('dist/e4a-%s'%version)
shutil.copyfile("electrum",'dist/e4a-%s/e4a.py'%version) shutil.copyfile("electrum",'dist/e4a-%s/e4a.py'%version)
@ -37,21 +34,6 @@ if __name__ == '__main__':
e4a_name = "e4a-%s.zip"%version e4a_name = "e4a-%s.zip"%version
e4a_name2 = e4a_name.replace(".","") e4a_name2 = e4a_name.replace(".","")
os.system( "mv %s %s"%(e4a_name, e4a_name2) ) os.system( "mv %s %s"%(e4a_name, e4a_name2) )
print "dist/%s "%e4a_name2
import getpass
password = getpass.getpass("Password:")
for f in os.listdir("."):
os.system( "gpg --sign --armor --detach --passphrase \"%s\" %s"%(password, f) )
md5_tgz = hashlib.md5(file(_tgz, 'r').read()).digest().encode('hex')
md5_zip = hashlib.md5(file(_zip, 'r').read()).digest().encode('hex')
md5_android = hashlib.md5(file(e4a_name2, 'r').read()).digest().encode('hex')
os.chdir("..")
print ""
print "Packages are ready:"
print "dist/%s "%_tgz, md5_tgz
print "dist/%s "%_zip, md5_zip
print "dist/%s "%e4a_name2, md5_android
print "To make a release, upload the files to the server, and update the webpages in branch gh-pages"

40
contrib/make_packages Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/python
import sys, re, shutil, os, hashlib
import imp
import getpass
if __name__ == '__main__':
os.chdir(os.path.dirname(os.path.realpath(__file__)))
os.chdir('..')
imp.load_module('electrum', *imp.find_module('../lib'))
from electrum.version import ELECTRUM_VERSION as version
if not ( os.path.exists('packages')):
print "The packages directory is missing."
sys.exit()
# os.system("python mki18n.py")
os.system("pyrcc4 icons.qrc -o gui/qt/icons_rc.py")
os.system("python setup.py sdist --format=zip,gztar")
_tgz="Electrum-%s.tar.gz"%version
_zip="Electrum-%s.zip"%version
os.chdir("dist")
password = getpass.getpass("Password:")
for f in [_tgz,_zip]:
os.system( "gpg --sign --armor --detach --passphrase \"%s\" %s"%(password, f) )
md5_tgz = hashlib.md5(file(_tgz, 'r').read()).digest().encode('hex')
md5_zip = hashlib.md5(file(_zip, 'r').read()).digest().encode('hex')
os.chdir("..")
print ""
print "Packages are ready:"
print "dist/%s "%_tgz, md5_tgz
print "dist/%s "%_zip, md5_zip
print "To make a release, upload the files to the server, and update the webpages in branch gh-pages"

View File

@ -26,18 +26,54 @@ import sys
import time import time
import traceback import traceback
is_bundle = getattr(sys, 'frozen', False)
is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd() is_local = not is_bundle and os.path.dirname(os.path.realpath(__file__)) == os.getcwd()
is_android = 'ANDROID_DATA' in os.environ is_android = 'ANDROID_DATA' in os.environ
if is_local: if is_local:
sys.path.append('packages') sys.path.insert(0, 'packages')
elif is_bundle and sys.platform=='darwin':
sys.path.insert(0, os.getcwd() + "/lib/python2.7/packages")
import __builtin__ import __builtin__
__builtin__.use_local_modules = is_local or is_android __builtin__.use_local_modules = is_local or is_android
# pure-python dependencies need to be imported here for pyinstaller
try:
import aes
import ecdsa
import socks
import requests
import six
import qrcode
import pyasn1
import pyasn1_modules
import tlslite
import pbkdf2
import google.protobuf
except ImportError as e:
sys.exit("Error: %s. Try 'sudo pip install <module-name>'"%e.message)
# the following imports are for pyinstaller
import pyasn1.codec
import pyasn1.codec.der
from pyasn1.codec.der import encoder, decoder
from pyasn1_modules import rfc2459
from google.protobuf import descriptor
from google.protobuf import message
from google.protobuf import reflection
from google.protobuf import descriptor_pb2
# check that we have the correct version of ecdsa
try:
from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
except Exception:
sys.exit("cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa")
# load local module as electrum # load local module as electrum
if __builtin__.use_local_modules: if is_bundle or is_local or is_android:
import imp import imp
imp.load_module('electrum', *imp.find_module('lib')) imp.load_module('electrum', *imp.find_module('lib'))
imp.load_module('electrum_gui', *imp.find_module('gui')) imp.load_module('electrum_gui', *imp.find_module('gui'))
@ -45,12 +81,11 @@ if __builtin__.use_local_modules:
from electrum import util from electrum import util
from electrum import SimpleConfig, Network, Wallet, WalletStorage, NetworkProxy, Commands, known_commands, pick_random_server from electrum import SimpleConfig, Network, Wallet, WalletStorage, NetworkProxy, Commands, known_commands, pick_random_server
from electrum.util import print_msg, print_stderr, print_json, set_verbosity, InvalidPassword from electrum.util import print_msg, print_error, print_stderr, print_json, set_verbosity, InvalidPassword
from electrum.daemon import get_daemon from electrum.daemon import get_daemon
from electrum.plugins import init_plugins from electrum.plugins import init_plugins
# get password routine # get password routine
def prompt_password(prompt, confirm=True): def prompt_password(prompt, confirm=True):
import getpass import getpass
@ -170,10 +205,12 @@ if __name__ == '__main__':
for k, v in config_options.items(): for k, v in config_options.items():
if v is None: if v is None:
config_options.pop(k) config_options.pop(k)
if config_options.get('server'):
config_options['auto_cycle'] = False
set_verbosity(config_options.get('verbose')) set_verbosity(config_options.get('verbose'))
config = SimpleConfig(config_options) config = SimpleConfig(config_options)
print_error("CA bundle:", requests.utils.DEFAULT_CA_BUNDLE_PATH, "found" if os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH) else "not found")
if len(args) == 0: if len(args) == 0:
url = None url = None
@ -185,7 +222,6 @@ if __name__ == '__main__':
cmd = args[0] cmd = args[0]
if cmd == 'gui': if cmd == 'gui':
init_plugins(config)
gui_name = config.get('gui', 'classic') gui_name = config.get('gui', 'classic')
if gui_name in ['lite', 'classic']: if gui_name in ['lite', 'classic']:
gui_name = 'qt' gui_name = 'qt'
@ -196,6 +232,9 @@ if __name__ == '__main__':
sys.exit() sys.exit()
#sys.exit("Error: Unknown GUI: " + gui_name ) #sys.exit("Error: Unknown GUI: " + gui_name )
if gui_name=='qt':
init_plugins(config, is_bundle or is_local or is_android)
# network interface # network interface
if not options.offline: if not options.offline:
s = get_daemon(config, start_daemon=options.daemon) s = get_daemon(config, start_daemon=options.daemon)

24
electrum-env Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
#
# This script creates a virtualenv named 'env' and installs all
# python dependencies before activating the env and running Electrum.
# If 'env' already exists, it is activated and Electrum is started
# without any installations. Additionally, the PYTHONPATH environment
# variable is set properly before running Electrum.
#
# python-qt and its dependencies will still need to be installed with
# your package manager.
if [ -e ./env/bin/activate ]; then
source ./env/bin/activate
else
virtualenv env
source ./env/bin/activate
python setup.py install
fi
export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH
./electrum
deactivate

View File

@ -35,7 +35,7 @@ class InstallWizard(QDialog):
self.storage = storage self.storage = storage
self.setMinimumSize(575, 400) self.setMinimumSize(575, 400)
self.setMaximumSize(575, 400) self.setMaximumSize(575, 400)
self.setWindowTitle('Electrum') self.setWindowTitle('Electrum' + ' - ' + os.path.basename(self.storage.path))
self.connect(self, QtCore.SIGNAL('accept'), self.accept) self.connect(self, QtCore.SIGNAL('accept'), self.accept)
self.stack = QStackedLayout() self.stack = QStackedLayout()
self.setLayout(self.stack) self.setLayout(self.stack)
@ -142,10 +142,8 @@ class InstallWizard(QDialog):
def multi_mpk_dialog(self, xpub_hot, n): def multi_mpk_dialog(self, xpub_hot, n):
vbox = QVBoxLayout() vbox = QVBoxLayout()
vbox0, seed_e0 = seed_dialog.enter_seed_box(MSG_SHOW_MPK, self, 'hot') vbox0 = seed_dialog.show_seed_box(MSG_SHOW_MPK, xpub_hot, 'hot')
vbox.addLayout(vbox0) vbox.addLayout(vbox0)
seed_e0.setText(xpub_hot)
seed_e0.setReadOnly(True)
entries = [] entries = []
for i in range(n): for i in range(n):
vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, self, 'cold') vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, self, 'cold')
@ -308,7 +306,7 @@ class InstallWizard(QDialog):
def show_seed(self, seed, sid): def show_seed(self, seed, sid):
vbox = seed_dialog.show_seed_box(seed, sid) vbox = seed_dialog.show_seed_box_msg(seed, sid)
vbox.addLayout(ok_cancel_buttons(self, _("Next"))) vbox.addLayout(ok_cancel_buttons(self, _("Next")))
self.set_layout(vbox) self.set_layout(vbox)
return self.exec_() return self.exec_()

View File

@ -203,6 +203,7 @@ class ElectrumWindow(QMainWindow):
def close_wallet(self): def close_wallet(self):
self.wallet.stop_threads() self.wallet.stop_threads()
self.hide()
run_hook('close_wallet') run_hook('close_wallet')
def load_wallet(self, wallet): def load_wallet(self, wallet):
@ -210,13 +211,17 @@ class ElectrumWindow(QMainWindow):
self.wallet = wallet self.wallet = wallet
self.update_wallet_format() self.update_wallet_format()
# address used to create a dummy transaction and estimate transaction fee # address used to create a dummy transaction and estimate transaction fee
self.dummy_address = self.wallet.addresses(False)[0] a = self.wallet.addresses(False)
self.dummy_address = a[0] if a else None
self.invoices = self.wallet.storage.get('invoices', {}) self.invoices = self.wallet.storage.get('invoices', {})
self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{}) self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
self.current_account = self.wallet.storage.get("current_account", None) self.current_account = self.wallet.storage.get("current_account", None)
title = 'Electrum ' + self.wallet.electrum_version + ' - ' + os.path.basename(self.wallet.storage.path) title = 'Electrum ' + self.wallet.electrum_version + ' - ' + os.path.basename(self.wallet.storage.path)
if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only')) if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
self.setWindowTitle( title ) self.setWindowTitle( title )
self.update_history_tab()
self.show()
self.update_wallet() self.update_wallet()
# Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
self.notify_transactions() self.notify_transactions()
@ -254,17 +259,42 @@ class ElectrumWindow(QMainWindow):
filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) ) filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
if not filename: if not filename:
return return
try:
storage = WalletStorage({'wallet_path': filename}) storage = WalletStorage({'wallet_path': filename})
if not storage.file_exists: except Exception as e:
self.show_message("file not found "+ filename) self.show_message(str(e))
return
if not storage.file_exists:
self.show_message(_("File not found") + ' ' + filename)
return
# read wizard action
try:
wallet = Wallet(storage)
except BaseException as e:
QMessageBox.warning(None, _('Warning'), str(e), _('OK'))
return
action = wallet.get_action()
# ask for confirmation
if action is not None:
if not self.question(_("This file contains an incompletely created wallet.\nDo you want to complete its creation now?")):
return return
# close current wallet # close current wallet
self.close_wallet() self.close_wallet()
# load new wallet # run wizard
wallet = Wallet(storage) if action is not None:
import installwizard
wizard = installwizard.InstallWizard(self.config, self.network, storage)
try:
wallet = wizard.run(action)
except BaseException as e:
traceback.print_exc(file=sys.stdout)
QMessageBox.information(None, _('Error'), str(e), _('OK'))
return
if not wallet:
return
else:
wallet.start_threads(self.network) wallet.start_threads(self.network)
# load new wallet in gui
self.load_wallet(wallet) self.load_wallet(wallet)
@ -308,6 +338,8 @@ class ElectrumWindow(QMainWindow):
QMessageBox.critical(None, "Error", _("File exists")) QMessageBox.critical(None, "Error", _("File exists"))
return return
if self.wallet:
self.close_wallet()
wizard = installwizard.InstallWizard(self.config, self.network, storage) wizard = installwizard.InstallWizard(self.config, self.network, storage)
wallet = wizard.run('new') wallet = wizard.run('new')
if wallet: if wallet:
@ -371,7 +403,7 @@ class ElectrumWindow(QMainWindow):
help_menu.addAction(_("&About"), self.show_about) help_menu.addAction(_("&About"), self.show_about)
help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org")) help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
help_menu.addSeparator() help_menu.addSeparator()
help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents) help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.orain.org/")).setShortcut(QKeySequence.HelpContents)
help_menu.addAction(_("&Report Bug"), self.show_report_bug) help_menu.addAction(_("&Report Bug"), self.show_report_bug)
self.setMenuBar(menubar) self.setMenuBar(menubar)
@ -1278,7 +1310,7 @@ class ElectrumWindow(QMainWindow):
if not request_url: if not request_url:
if label: if label:
if self.wallet.labels.get(address) != label: if self.wallet.labels.get(address) != label:
if self.question(_('Save label "%s" for address %s ?'%(label,address))): if self.question(_('Save label "%(label)s" for address %(address)s ?'%{'label':label,'address':address})):
if address not in self.wallet.addressbook and not self.wallet.is_mine(address): if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
self.wallet.addressbook.append(address) self.wallet.addressbook.append(address)
self.wallet.set_label(address, label) self.wallet.set_label(address, label)
@ -2507,7 +2539,7 @@ class ElectrumWindow(QMainWindow):
with open(fileName, "w+") as f: with open(fileName, "w+") as f:
if is_csv: if is_csv:
transaction = csv.writer(f) transaction = csv.writer(f, lineterminator='\n')
transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"]) transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
for line in lines: for line in lines:
transaction.writerow(line) transaction.writerow(line)

View File

@ -122,14 +122,15 @@ class NetworkDialog(QDialog):
lambda x,y: self.server_changed(x)) lambda x,y: self.server_changed(x))
grid.addWidget(self.servers_list_widget, 1, 1, 1, 3) grid.addWidget(self.servers_list_widget, 1, 1, 1, 3)
if not config.is_modifiable('server'):
for w in [self.server_host, self.server_port, self.server_protocol, self.servers_list_widget]: w.setEnabled(False)
def enable_set_server(): def enable_set_server():
if config.is_modifiable('server'):
enabled = not self.autocycle_cb.isChecked() enabled = not self.autocycle_cb.isChecked()
self.server_host.setEnabled(enabled) self.server_host.setEnabled(enabled)
self.server_port.setEnabled(enabled) self.server_port.setEnabled(enabled)
self.servers_list_widget.setEnabled(enabled) self.servers_list_widget.setEnabled(enabled)
else:
for w in [self.autocycle_cb, self.server_host, self.server_port, self.server_protocol, self.servers_list_widget]:
w.setEnabled(False)
self.autocycle_cb.clicked.connect(enable_set_server) self.autocycle_cb.clicked.connect(enable_set_server)
enable_set_server() enable_set_server()
@ -143,19 +144,18 @@ class NetworkDialog(QDialog):
self.proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) self.proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
def check_for_disable(index = False): def check_for_disable(index = False):
if self.config.is_modifiable('proxy'):
if self.proxy_mode.currentText() != 'NONE': if self.proxy_mode.currentText() != 'NONE':
self.proxy_host.setEnabled(True) self.proxy_host.setEnabled(True)
self.proxy_port.setEnabled(True) self.proxy_port.setEnabled(True)
else: else:
self.proxy_host.setEnabled(False) self.proxy_host.setEnabled(False)
self.proxy_port.setEnabled(False) self.proxy_port.setEnabled(False)
else:
for w in [self.proxy_host, self.proxy_port, self.proxy_mode]: w.setEnabled(False)
check_for_disable() check_for_disable()
self.proxy_mode.connect(self.proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable) self.proxy_mode.connect(self.proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
if not self.config.is_modifiable('proxy'):
for w in [self.proxy_host, self.proxy_port, self.proxy_mode]: w.setEnabled(False)
self.proxy_mode.setCurrentIndex(self.proxy_mode.findText(str(proxy_config.get("mode").upper()))) self.proxy_mode.setCurrentIndex(self.proxy_mode.findText(str(proxy_config.get("mode").upper())))
self.proxy_host.setText(proxy_config.get("host")) self.proxy_host.setText(proxy_config.get("host"))
self.proxy_port.setText(proxy_config.get("port")) self.proxy_port.setText(proxy_config.get("port"))

View File

@ -96,20 +96,21 @@ class PayToEdit(ScanQRTextEdit):
self.errors = [] self.errors = []
if self.is_pr: if self.is_pr:
return return
# filter out empty lines # filter out empty lines
lines = filter( lambda x: x, self.lines()) lines = filter( lambda x: x, self.lines())
outputs = [] outputs = []
total = 0 total = 0
self.payto_address = None self.payto_address = None
if len(lines) == 1: if len(lines) == 1:
data = lines[0]
if data.startswith("bitcoin:"):
self.scan_f(data)
return
try: try:
self.payto_address = self.parse_address(lines[0]) self.payto_address = self.parse_address(data)
except: except:
pass pass
if self.payto_address: if self.payto_address:
self.unlock_amount() self.unlock_amount()
return return

View File

@ -31,7 +31,7 @@ class SeedDialog(QDialog):
self.setModal(1) self.setModal(1)
self.setMinimumWidth(400) self.setMinimumWidth(400)
self.setWindowTitle('Electrum' + ' - ' + _('Seed')) self.setWindowTitle('Electrum' + ' - ' + _('Seed'))
vbox = show_seed_box(seed) vbox = show_seed_box_msg(seed)
if imported_keys: if imported_keys:
vbox.addWidget(QLabel("<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>")) vbox.addWidget(QLabel("<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"))
vbox.addLayout(close_button(self)) vbox.addLayout(close_button(self))
@ -47,71 +47,39 @@ def icon_filename(sid):
return ":icons/seed.png" return ":icons/seed.png"
def show_seed_box_msg(seedphrase, sid=None):
msg = _("Your wallet generation seed is") + ":"
def show_seed_box(seed, sid=None): vbox = show_seed_box(msg, seedphrase, sid)
save_msg = _("Please save these %d words on paper (order is important).")%len(seedphrase.split()) + " "
save_msg = _("Please save these %d words on paper (order is important).")%len(seed.split()) + " "
qr_msg = _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>"
warning_msg = "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
if sid is None:
msg = _("Your wallet generation seed is")
msg2 = save_msg + " " \ msg2 = save_msg + " " \
+ _("This seed will allow you to recover your wallet in case of computer failure.") + "<br/>" \ + _("This seed will allow you to recover your wallet in case of computer failure.") + "<br/>" \
+ warning_msg + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
elif sid == 'cold':
msg = _("Your cold storage seed is")
msg2 = save_msg + " " \
+ _("This seed will be permanently deleted from your wallet file. Make sure you have saved it before you press 'next'") + " " \
elif sid == 'hot':
msg = _("Your hot seed is")
msg2 = save_msg + " " \
+ _("If you ever need to recover your wallet from seed, you will need both this seed and your cold seed.") + " " \
label1 = QLabel(msg+ ":")
seed_text = ShowQRTextEdit(text=seed)
seed_text.setMaximumHeight(130)
label2 = QLabel(msg2) label2 = QLabel(msg2)
label2.setWordWrap(True) label2.setWordWrap(True)
logo = QLabel()
logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56))
logo.setMaximumWidth(60)
grid = QGridLayout()
grid.addWidget(logo, 0, 0)
grid.addWidget(label1, 0, 1)
grid.addWidget(seed_text, 1, 0, 1, 2)
vbox = QVBoxLayout()
vbox.addLayout(grid)
vbox.addWidget(label2) vbox.addWidget(label2)
vbox.addStretch(1) vbox.addStretch(1)
return vbox return vbox
def show_seed_box(msg, seed, sid):
vbox, seed_e = enter_seed_box(msg, None, sid=sid, text=seed)
return vbox
def enter_seed_box(msg, window, sid=None): def enter_seed_box(msg, window, sid=None, text=None):
vbox = QVBoxLayout() vbox = QVBoxLayout()
logo = QLabel() logo = QLabel()
logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56)) logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56))
logo.setMaximumWidth(60) logo.setMaximumWidth(60)
label = QLabel(msg) label = QLabel(msg)
label.setWordWrap(True) label.setWordWrap(True)
if not text:
seed_e = ScanQRTextEdit(win=window) seed_e = ScanQRTextEdit(win=window)
seed_e.setMaximumHeight(100)
seed_e.setTabChangesFocus(True) seed_e.setTabChangesFocus(True)
else:
seed_e = ShowQRTextEdit(text=text)
seed_e.setMaximumHeight(130)
vbox.addWidget(label) vbox.addWidget(label)
grid = QGridLayout() grid = QGridLayout()
grid.addWidget(logo, 0, 0) grid.addWidget(logo, 0, 0)
grid.addWidget(seed_e, 0, 1) grid.addWidget(seed_e, 0, 1)
vbox.addLayout(grid) vbox.addLayout(grid)
return vbox, seed_e return vbox, seed_e

View File

@ -156,7 +156,7 @@ class TxDialog(QDialog):
self.broadcast_button.show() self.broadcast_button.show()
else: else:
s, r = self.tx.signature_count() s, r = self.tx.signature_count()
status = _("Unsigned") if s == 0 else _('Partially signed (%d/%d)'%(s,r)) status = _("Unsigned") if s == 0 else _('Partially signed') + ' (%d/%d)'%(s,r)
time_str = None time_str = None
self.broadcast_button.hide() self.broadcast_button.hide()
tx_hash = 'unknown' tx_hash = 'unknown'

View File

@ -33,7 +33,7 @@ class VersionGetter(threading.Thread):
def run(self): def run(self):
try: try:
con = httplib.HTTPConnection('electrum.org', 80, timeout=5) con = httplib.HTTPSConnection('electrum.org', timeout=5)
con.request("GET", "/version") con.request("GET", "/version")
res = con.getresponse() res = con.getresponse()
except socket.error as msg: except socket.error as msg:
@ -75,7 +75,10 @@ class UpdateLabel(QLabel):
def compare_versions(self, version1, version2): def compare_versions(self, version1, version2):
def normalize(v): def normalize(v):
return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")] return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
try:
return cmp(normalize(version1), normalize(version2)) return cmp(normalize(version1), normalize(version2))
except:
return 0
def ignore_this_version(self): def ignore_this_version(self):
self.setText("") self.setText("")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -76,7 +76,7 @@ class Account(object):
return None return None
def synchronize_sequence(self, wallet, for_change): def synchronize_sequence(self, wallet, for_change):
limit = self.gap_limit_for_change if for_change else self.gap_limit limit = wallet.gap_limit_for_change if for_change else wallet.gap_limit
while True: while True:
addresses = self.get_addresses(for_change) addresses = self.get_addresses(for_change)
if len(addresses) < limit: if len(addresses) < limit:
@ -175,14 +175,11 @@ class ImportedAccount(Account):
class OldAccount(Account): class OldAccount(Account):
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """ """ Privatekey(type,n) = Master_private_key + H(n|S|type) """
gap_limit = 5
gap_limit_for_change = 3
def __init__(self, v): def __init__(self, v):
Account.__init__(self, v) Account.__init__(self, v)
self.mpk = v['mpk'].decode('hex') self.mpk = v['mpk'].decode('hex')
@classmethod @classmethod
def mpk_from_seed(klass, seed): def mpk_from_seed(klass, seed):
curve = SECP256k1 curve = SECP256k1
@ -274,8 +271,6 @@ class OldAccount(Account):
class BIP32_Account(Account): class BIP32_Account(Account):
gap_limit = 20
gap_limit_for_change = 3
def __init__(self, v): def __init__(self, v):
Account.__init__(self, v) Account.__init__(self, v)

View File

@ -26,15 +26,8 @@ import hmac
import version import version
from util import print_error, InvalidPassword from util import print_error, InvalidPassword
try:
import ecdsa import ecdsa
except ImportError:
sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
try:
import aes import aes
except ImportError:
sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
################################## transactions ################################## transactions
@ -58,6 +51,8 @@ def strip_PKCS7_padding(s):
raise ValueError("Invalid PKCS7 padding") raise ValueError("Invalid PKCS7 padding")
return s[:-numpads] return s[:-numpads]
# backport padding fix to AES module
aes.strip_PKCS7_padding = strip_PKCS7_padding
def aes_encrypt_with_iv(key, iv, data): def aes_encrypt_with_iv(key, iv, data):
mode = aes.AESModeOfOperation.modeOfOperation["CBC"] mode = aes.AESModeOfOperation.modeOfOperation["CBC"]
@ -401,12 +396,7 @@ def is_private_key(key):
########### end pywallet functions ####################### ########### end pywallet functions #######################
try:
from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1 from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
except Exception:
print "cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa"
exit()
from ecdsa.curves import SECP256k1 from ecdsa.curves import SECP256k1
from ecdsa.ellipticcurve import Point from ecdsa.ellipticcurve import Point
from ecdsa.util import string_to_number, number_to_string from ecdsa.util import string_to_number, number_to_string

View File

@ -38,6 +38,30 @@ proxy_modes = ['socks4', 'socks5', 'http']
import util import util
def serialize_proxy(p):
if type(p) != dict:
return None
return ':'.join([p.get('mode'),p.get('host'), p.get('port')])
def deserialize_proxy(s):
if type(s) != str:
return None
if s.lower() == 'none':
return None
proxy = { "mode":"socks5", "host":"localhost" }
args = s.split(':')
n = 0
if proxy_modes.count(args[n]) == 1:
proxy["mode"] = args[n]
n += 1
if len(args) > n:
proxy["host"] = args[n]
n += 1
if len(args) > n:
proxy["port"] = args[n]
else:
proxy["port"] = "8080" if proxy["mode"] == "http" else "1080"
return proxy
def Interface(server, config = None): def Interface(server, config = None):
@ -68,7 +92,7 @@ class TcpInterface(threading.Thread):
self.host, self.port, self.protocol = self.server.split(':') self.host, self.port, self.protocol = self.server.split(':')
self.port = int(self.port) self.port = int(self.port)
self.use_ssl = (self.protocol == 's') self.use_ssl = (self.protocol == 's')
self.proxy = self.parse_proxy_options(self.config.get('proxy')) self.proxy = deserialize_proxy(self.config.get('proxy'))
if self.proxy: if self.proxy:
self.proxy_mode = proxy_modes.index(self.proxy["mode"]) + 1 self.proxy_mode = proxy_modes.index(self.proxy["mode"]) + 1
socks.setdefaultproxy(self.proxy_mode, self.proxy["host"], int(self.proxy["port"])) socks.setdefaultproxy(self.proxy_mode, self.proxy["host"], int(self.proxy["port"]))
@ -271,25 +295,6 @@ class TcpInterface(threading.Thread):
self.unanswered_requests[self.message_id] = method, params, _id, queue self.unanswered_requests[self.message_id] = method, params, _id, queue
self.message_id += 1 self.message_id += 1
def parse_proxy_options(self, s):
if type(s) == type({}): return s # fixme: type should be fixed
if type(s) != type(""): return None
if s.lower() == 'none': return None
proxy = { "mode":"socks5", "host":"localhost" }
args = s.split(':')
n = 0
if proxy_modes.count(args[n]) == 1:
proxy["mode"] = args[n]
n += 1
if len(args) > n:
proxy["host"] = args[n]
n += 1
if len(args) > n:
proxy["port"] = args[n]
else:
proxy["port"] = "8080" if proxy["mode"] == "http" else "1080"
return proxy
def stop(self): def stop(self):
if self.is_connected and self.protocol in 'st' and self.s: if self.is_connected and self.protocol in 'st' and self.s:
self.s.shutdown(socket.SHUT_RDWR) self.s.shutdown(socket.SHUT_RDWR)

View File

@ -1,4 +1,12 @@
import threading, time, Queue, os, sys, shutil, random import threading
import time
import Queue
import os
import sys
import random
import traceback
from util import user_dir, appdata_dir, print_error, print_msg from util import user_dir, appdata_dir, print_error, print_msg
from bitcoin import * from bitcoin import *
import interface import interface
@ -178,7 +186,7 @@ class Network(threading.Thread):
def get_parameters(self): def get_parameters(self):
host, port, protocol = self.default_server.split(':') host, port, protocol = self.default_server.split(':')
proxy = self.proxy proxy = interface.deserialize_proxy(self.proxy)
auto_connect = self.config.get('auto_cycle', True) auto_connect = self.config.get('auto_cycle', True)
return host, port, protocol, proxy, auto_connect return host, port, protocol, proxy, auto_connect
@ -225,14 +233,16 @@ class Network(threading.Thread):
threading.Thread.start(self) threading.Thread.start(self)
def set_parameters(self, host, port, protocol, proxy, auto_connect): def set_parameters(self, host, port, protocol, proxy, auto_connect):
proxy_str = interface.serialize_proxy(proxy)
server_str = ':'.join([ host, port, protocol ])
self.config.set_key('auto_cycle', auto_connect, True) self.config.set_key('auto_cycle', auto_connect, True)
self.config.set_key("proxy", proxy, True) self.config.set_key("proxy", proxy_str, True)
self.config.set_key("protocol", protocol, True) self.config.set_key("protocol", protocol, True)
server = ':'.join([ host, port, protocol ]) self.config.set_key("server", server_str, True)
self.config.set_key("server", server, True)
if self.proxy != proxy or self.protocol != protocol: if self.proxy != proxy_str or self.protocol != protocol:
self.proxy = proxy print_error('restarting network')
self.proxy = proxy_str
self.protocol = protocol self.protocol = protocol
for i in self.interfaces.values(): i.stop() for i in self.interfaces.values(): i.stop()
if auto_connect: if auto_connect:
@ -246,7 +256,7 @@ class Network(threading.Thread):
if self.server_is_lagging(): if self.server_is_lagging():
self.stop_interface() self.stop_interface()
else: else:
self.set_server(server) self.set_server(server_str)
def switch_to_random_interface(self): def switch_to_random_interface(self):
@ -358,6 +368,7 @@ class Network(threading.Thread):
out['result'] = f(*params) out['result'] = f(*params)
except BaseException as e: except BaseException as e:
out['error'] = str(e) out['error'] = str(e)
traceback.print_exc(file=sys.stout)
print_error("network error", str(e)) print_error("network error", str(e))
self.response_queue.put(out) self.response_queue.put(out)

View File

@ -27,18 +27,12 @@ import time
import traceback import traceback
import urllib2 import urllib2
import urlparse import urlparse
import requests
try: try:
import paymentrequest_pb2 import paymentrequest_pb2
except:
sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto'")
try:
import requests
except ImportError: except ImportError:
sys.exit("Error: requests does not seem to be installed. Try 'sudo pip install requests'") sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto'")
import bitcoin import bitcoin
import util import util
@ -116,7 +110,7 @@ class PaymentRequest:
self.id = bitcoin.sha256(r)[0:16].encode('hex') self.id = bitcoin.sha256(r)[0:16].encode('hex')
filename = os.path.join(self.dir_path, self.id) filename = os.path.join(self.dir_path, self.id)
with open(filename,'w') as f: with open(filename,'wb') as f:
f.write(r) f.write(r)
return self.parse(r) return self.parse(r)
@ -131,7 +125,7 @@ class PaymentRequest:
def read_file(self, key): def read_file(self, key):
filename = os.path.join(self.dir_path, key) filename = os.path.join(self.dir_path, key)
with open(filename,'r') as f: with open(filename,'rb') as f:
r = f.read() r = f.read()
assert key == bitcoin.sha256(r)[0:16].encode('hex') assert key == bitcoin.sha256(r)[0:16].encode('hex')

View File

@ -6,11 +6,11 @@ from i18n import _
plugins = [] plugins = []
def init_plugins(config): def init_plugins(config, local):
import imp, pkgutil, __builtin__, os import imp, pkgutil, __builtin__, os
global plugins global plugins
if __builtin__.use_local_modules: if local:
fp, pathname, description = imp.find_module('plugins') fp, pathname, description = imp.find_module('plugins')
plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])] plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names) plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
@ -40,13 +40,16 @@ def hook(func):
def run_hook(name, *args): def run_hook(name, *args):
SPECIAL_HOOKS = ['get_wizard_action']
results = [] results = []
f_list = hooks.get(name,[]) f_list = hooks.get(name,[])
for p, f in f_list: for p, f in f_list:
if name == 'load_wallet': if name == 'load_wallet':
p.wallet = args[0] p.wallet = args[0]
if not p.is_enabled(): if name == 'init_qt':
continue gui = args[0]
p.window = gui.main_window
if name in SPECIAL_HOOKS or p.is_enabled():
try: try:
r = f(*args) r = f(*args)
except Exception: except Exception:
@ -55,6 +58,8 @@ def run_hook(name, *args):
r = False r = False
if r: if r:
results.append(r) results.append(r)
if name == 'close_wallet':
p.wallet = None
if results: if results:
assert len(results) == 1, results assert len(results) == 1, results
@ -92,8 +97,12 @@ class BasePlugin:
def init_qt(self, gui): pass def init_qt(self, gui): pass
@hook
def load_wallet(self, wallet): pass def load_wallet(self, wallet): pass
@hook
def close_wallet(self): pass
#def init(self): pass #def init(self): pass
def close(self): pass def close(self): pass

View File

@ -12,7 +12,7 @@ proc = None
def scan_qr(config): def scan_qr(config):
global proc global proc
if not zbar: if not zbar:
raise BaseException("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo apt-get install python-zbar'")])) raise BaseException("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo pip install zbar'")]))
if proc is None: if proc is None:
device = config.get("video_device", "default") device = config.get("video_device", "default")
if device == 'default': if device == 'default':

View File

@ -110,7 +110,7 @@ class SimpleConfig(object):
out = None out = None
with self.lock: with self.lock:
out = self.read_only_options.get(key) out = self.read_only_options.get(key)
if not out: if out is None:
out = self.user_config.get(key, default) out = self.user_config.get(key, default)
return out return out

View File

@ -65,6 +65,15 @@ def data_dir():
import __builtin__ import __builtin__
if __builtin__.use_local_modules: if __builtin__.use_local_modules:
return local_data_dir() return local_data_dir()
else:
is_frozen = getattr(sys, 'frozen', False)
if is_frozen:
if is_frozen == "macosx_app":
basedir = os.path.abspath(".")
else:
basedir = sys._MEIPASS
return os.path.join(basedir, 'data')
else: else:
return appdata_dir() return appdata_dir()

View File

@ -1,4 +1,4 @@
ELECTRUM_VERSION = "2.0" # version of the client package ELECTRUM_VERSION = "2.0b2" # version of the client package
PROTOCOL_VERSION = '0.9' # protocol version requested PROTOCOL_VERSION = '0.9' # protocol version requested
NEW_SEED_VERSION = 11 # electrum versions >= 2.0 NEW_SEED_VERSION = 11 # electrum versions >= 2.0
OLD_SEED_VERSION = 4 # electrum versions < 2.0 OLD_SEED_VERSION = 4 # electrum versions < 2.0

View File

@ -1048,9 +1048,11 @@ class Abstract_Wallet(object):
addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex')) addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex'))
return self.is_mine(addr) return self.is_mine(addr)
elif x_pubkey[0:2] == 'ff': elif x_pubkey[0:2] == 'ff':
if not isinstance(self, BIP32_Wallet): return False
xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey) xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
return xpub in [ self.master_public_keys[k] for k in self.master_private_keys.keys() ] return xpub in [ self.master_public_keys[k] for k in self.master_private_keys.keys() ]
elif x_pubkey[0:2] == 'fe': elif x_pubkey[0:2] == 'fe':
if not isinstance(self, OldWallet): return False
xpub, sequence = OldAccount.parse_xpubkey(x_pubkey) xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
return xpub == self.get_master_public_key() return xpub == self.get_master_public_key()
elif x_pubkey[0:2] == 'fd': elif x_pubkey[0:2] == 'fd':
@ -1140,17 +1142,15 @@ class Deterministic_Wallet(Abstract_Wallet):
if value >= self.gap_limit: if value >= self.gap_limit:
self.gap_limit = value self.gap_limit = value
self.storage.put('gap_limit', self.gap_limit, True) self.storage.put('gap_limit', self.gap_limit, True)
#self.interface.poke('synchronizer')
return True return True
elif value >= self.min_acceptable_gap(): elif value >= self.min_acceptable_gap():
for key, account in self.accounts.items(): for key, account in self.accounts.items():
addresses = account[0] addresses = account.get_addresses(False)
k = self.num_unused_trailing_addresses(addresses) k = self.num_unused_trailing_addresses(addresses)
n = len(addresses) - k + value n = len(addresses) - k + value
addresses = addresses[0:n] account.receiving_pubkeys = account.receiving_pubkeys[0:n]
self.accounts[key][0] = addresses account.receiving_addresses = account.receiving_addresses[0:n]
self.gap_limit = value self.gap_limit = value
self.storage.put('gap_limit', self.gap_limit, True) self.storage.put('gap_limit', self.gap_limit, True)
self.save_accounts() self.save_accounts()
@ -1264,12 +1264,12 @@ class Deterministic_Wallet(Abstract_Wallet):
class BIP32_Wallet(Deterministic_Wallet): class BIP32_Wallet(Deterministic_Wallet):
# abstract class, bip32 logic # abstract class, bip32 logic
root_name = 'x/' root_name = 'x/'
gap_limit = 20
def __init__(self, storage): def __init__(self, storage):
Deterministic_Wallet.__init__(self, storage) Deterministic_Wallet.__init__(self, storage)
self.master_public_keys = storage.get('master_public_keys', {}) self.master_public_keys = storage.get('master_public_keys', {})
self.master_private_keys = storage.get('master_private_keys', {}) self.master_private_keys = storage.get('master_private_keys', {})
self.gap_limit = storage.get('gap_limit', 20)
def is_watching_only(self): def is_watching_only(self):
return not bool(self.master_private_keys) return not bool(self.master_private_keys)
@ -1539,7 +1539,6 @@ class Wallet_2of3(Wallet_2of2):
class OldWallet(Deterministic_Wallet): class OldWallet(Deterministic_Wallet):
wallet_type = 'old' wallet_type = 'old'
gap_limit = 5
def __init__(self, storage): def __init__(self, storage):
Deterministic_Wallet.__init__(self, storage) Deterministic_Wallet.__init__(self, storage)
@ -1632,12 +1631,21 @@ class Wallet(object):
seed_version = OLD_SEED_VERSION if len(storage.get('master_public_key','')) == 128 else NEW_SEED_VERSION seed_version = OLD_SEED_VERSION if len(storage.get('master_public_key','')) == 128 else NEW_SEED_VERSION
if seed_version not in [OLD_SEED_VERSION, NEW_SEED_VERSION]: if seed_version not in [OLD_SEED_VERSION, NEW_SEED_VERSION]:
msg = "This wallet seed is not supported anymore." msg = "Your wallet has an unsupported seed version."
msg += '\n\nWallet file: %s' % os.path.abspath(storage.path)
if seed_version in [5, 7, 8, 9, 10]: if seed_version in [5, 7, 8, 9, 10]:
msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version msg += "\n\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
if seed_version == 6:
# version 1.9.8 created v6 wallets when an incorrect seed was entered in the restore dialog
msg += '\n\nThis file was created because of a bug in version 1.9.8.'
if storage.get('master_public_keys') is None and storage.get('master_private_keys') is None and storage.get('imported_keys') is None:
# pbkdf2 was not included with the binaries, and wallet creation aborted.
msg += "\nIt does not contain any keys, and can safely be removed."
else:
# creation was complete if electrum was run from source
msg += "\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet."
raise BaseException(msg) raise BaseException(msg)
run_hook('add_wallet_types', wallet_types)
wallet_type = storage.get('wallet_type') wallet_type = storage.get('wallet_type')
if wallet_type: if wallet_type:
for cat, t, name, c in wallet_types: for cat, t, name, c in wallet_types:

View File

@ -20,21 +20,9 @@
from datetime import datetime from datetime import datetime
import sys import sys
try:
import pyasn1 import pyasn1
except ImportError:
sys.exit("Error: pyasn1 does not seem to be installed. Try 'sudo pip install pyasn1'")
try:
import pyasn1_modules import pyasn1_modules
except ImportError:
sys.exit("Error: pyasn1 does not seem to be installed. Try 'sudo pip install pyasn1-modules'")
try:
import tlslite import tlslite
except ImportError:
sys.exit("Error: tlslite does not seem to be installed. Try 'sudo pip install tlslite'")
# workaround https://github.com/trevp/tlslite/issues/15 # workaround https://github.com/trevp/tlslite/issues/15
tlslite.utils.cryptomath.pycryptoLoaded = False tlslite.utils.cryptomath.pycryptoLoaded = False

View File

@ -106,10 +106,8 @@ class Plugin(BasePlugin):
button.clicked.connect(handler) button.clicked.connect(handler)
def _audio_interface(self): def _audio_interface(self):
return amodem.audio.Interface( interface = amodem.audio.Interface(config=self.modem_config)
config=self.modem_config, return interface.load(self.library_name)
name=self.library_name
)
def _send(self, parent, blob): def _send(self, parent, blob):
def sender_thread(): def sender_thread():

View File

@ -15,7 +15,7 @@ from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, public_key_to
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugins import BasePlugin, hook from electrum.plugins import BasePlugin, hook
from electrum.transaction import deserialize from electrum.transaction import deserialize
from electrum.wallet import NewWallet from electrum.wallet import BIP32_HD_Wallet
from electrum.util import format_satoshis from electrum.util import format_satoshis
import hashlib import hashlib
@ -46,18 +46,20 @@ class Plugin(BasePlugin):
BasePlugin.__init__(self, gui, name) BasePlugin.__init__(self, gui, name)
self._is_available = self._init() self._is_available = self._init()
self.wallet = None self.wallet = None
if self._is_available:
electrum.wallet.wallet_types.append(('hardware', 'btchip', _("BTChip wallet"), BTChipWallet)) electrum.wallet.wallet_types.append(('hardware', 'btchip', _("BTChip wallet"), BTChipWallet))
def _init(self): def _init(self):
return BTCHIP return BTCHIP
def is_available(self): def is_available(self):
if self.wallet is None: if not self._is_available:
return self._is_available
if self.wallet.storage.get('wallet_type') == 'btchip':
return True
return False 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): def set_enabled(self, enabled):
self.wallet.storage.put('use_' + self.name, enabled) self.wallet.storage.put('use_' + self.name, enabled)
@ -65,18 +67,29 @@ class Plugin(BasePlugin):
def is_enabled(self): def is_enabled(self):
if not self.is_available(): if not self.is_available():
return False return False
if self.wallet.has_seed():
if not self.wallet or self.wallet.storage.get('wallet_type') == 'btchip': return False
return True return True
return self.wallet.storage.get('use_' + self.name) is True
def enable(self): def enable(self):
return BasePlugin.enable(self) return BasePlugin.enable(self)
def btchip_is_connected(self):
try:
self.wallet.get_client().getFirmwareVersion()
except:
return False
return True
@hook @hook
def load_wallet(self, wallet): def load_wallet(self, wallet):
self.wallet = wallet if self.btchip_is_connected():
if not self.wallet.check_proper_device():
QMessageBox.information(self.window, _('Error'), _("This wallet does not match your BTChip device"), _('OK'))
self.wallet.force_watching_only = True
else:
QMessageBox.information(self.window, _('Error'), _("BTChip device not detected.\nContinuing in watching-only mode."), _('OK'))
self.wallet.force_watching_only = True
@hook @hook
def installwizard_restore(self, wizard, storage): def installwizard_restore(self, wizard, storage):
@ -98,16 +111,18 @@ class Plugin(BasePlugin):
except Exception as e: except Exception as e:
tx.error = str(e) tx.error = str(e)
class BTChipWallet(NewWallet): class BTChipWallet(BIP32_HD_Wallet):
wallet_type = 'btchip' wallet_type = 'btchip'
root_derivation = "m/44'/0'"
def __init__(self, storage): def __init__(self, storage):
NewWallet.__init__(self, storage) BIP32_HD_Wallet.__init__(self, storage)
self.transport = None self.transport = None
self.client = None self.client = None
self.mpk = None self.mpk = None
self.device_checked = False self.device_checked = False
self.signing = False self.signing = False
self.force_watching_only = False
def give_error(self, message, clear_client = False): def give_error(self, message, clear_client = False):
if not self.signing: if not self.signing:
@ -129,11 +144,8 @@ class BTChipWallet(NewWallet):
def can_change_password(self): def can_change_password(self):
return False return False
def has_seed(self):
return False
def is_watching_only(self): def is_watching_only(self):
return False return self.force_watching_only
def get_client(self, noPin=False): def get_client(self, noPin=False):
if not BTCHIP: if not BTCHIP:
@ -258,9 +270,6 @@ class BTChipWallet(NewWallet):
def get_master_public_key(self): def get_master_public_key(self):
try: try:
if not self.mpk: if not self.mpk:
self.get_client() # prompt for the PIN if necessary
if not self.check_proper_device():
self.give_error('Wrong device or password')
self.mpk = self.get_public_key("44'/0'") self.mpk = self.get_public_key("44'/0'")
return self.mpk return self.mpk
except Exception, e: except Exception, e:
@ -278,6 +287,7 @@ class BTChipWallet(NewWallet):
def sign_message(self, address, message, password): def sign_message(self, address, message, password):
use2FA = False use2FA = False
self.signing = True
self.get_client() # prompt for the PIN before displaying the dialog if necessary self.get_client() # prompt for the PIN before displaying the dialog if necessary
if not self.check_proper_device(): if not self.check_proper_device():
self.give_error('Wrong device or password') self.give_error('Wrong device or password')
@ -298,11 +308,15 @@ class BTChipWallet(NewWallet):
self.get_client(True) self.get_client(True)
signature = self.get_client().signMessageSign(pin) signature = self.get_client().signMessageSign(pin)
except Exception, e: except Exception, e:
if e.sw == 0x6a80:
self.give_error("Unfortunately, this message cannot be signed by BTChip. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry.")
else:
self.give_error(e, True) self.give_error(e, True)
finally: finally:
if waitDialog.waiting: if waitDialog.waiting:
waitDialog.emit(SIGNAL('dongle_done')) waitDialog.emit(SIGNAL('dongle_done'))
self.client.bad = use2FA self.client.bad = use2FA
self.signing = False
# Parse the ASN.1 signature # Parse the ASN.1 signature

View File

@ -93,14 +93,9 @@ class Plugin(BasePlugin):
def init_qt(self, gui): def init_qt(self, gui):
self.win = gui.main_window self.win = gui.main_window
self.win.connect(self.win, SIGNAL('cosigner:receive'), self.on_receive) self.win.connect(self.win, SIGNAL('cosigner:receive'), self.on_receive)
if self.listener is None:
self.listener = Listener(self)
self.listener.start()
def enable(self): def enable(self):
self.set_enabled(True) self.set_enabled(True)
if self.win.wallet:
self.load_wallet(self.win.wallet)
return True return True
def is_available(self): def is_available(self):
@ -113,6 +108,9 @@ class Plugin(BasePlugin):
self.wallet = wallet self.wallet = wallet
if not self.is_available(): if not self.is_available():
return return
if self.listener is None:
self.listener = Listener(self)
self.listener.start()
mpk = self.wallet.get_master_public_keys() mpk = self.wallet.get_master_public_keys()
self.cosigner_list = [] self.cosigner_list = []
for key, xpub in mpk.items(): for key, xpub in mpk.items():

View File

@ -49,17 +49,20 @@ class Plugin(BasePlugin):
self._is_available = self._init() self._is_available = self._init()
self._requires_settings = True self._requires_settings = True
self.wallet = None self.wallet = None
if self._is_available:
electrum.wallet.wallet_types.append(('hardware', 'trezor', _("Trezor wallet"), TrezorWallet)) electrum.wallet.wallet_types.append(('hardware', 'trezor', _("Trezor wallet"), TrezorWallet))
def _init(self): def _init(self):
return TREZOR return TREZOR
def is_available(self): def is_available(self):
if self.wallet is None: if not self._is_available:
return self._is_available
if self.wallet.storage.get('wallet_type') == 'trezor':
return True
return False return False
if not self.wallet:
return False
if self.wallet.storage.get('wallet_type') != 'trezor':
return False
return True
def requires_settings(self): def requires_settings(self):
return self._requires_settings return self._requires_settings
@ -70,12 +73,10 @@ class Plugin(BasePlugin):
def is_enabled(self): def is_enabled(self):
if not self.is_available(): if not self.is_available():
return False return False
if self.wallet.has_seed():
if not self.wallet or self.wallet.storage.get('wallet_type') == 'trezor': return False
return True return True
return self.wallet.storage.get('use_' + self.name) is True
def enable(self): def enable(self):
return BasePlugin.enable(self) return BasePlugin.enable(self)
@ -93,28 +94,34 @@ class Plugin(BasePlugin):
@hook @hook
def close_wallet(self): def close_wallet(self):
print_error("trezor: clear session") print_error("trezor: clear session")
if self.wallet.client: if self.wallet and self.wallet.client:
self.wallet.client.clear_session() self.wallet.client.clear_session()
@hook @hook
def load_wallet(self, wallet): def load_wallet(self, wallet):
self.wallet = wallet
if self.trezor_is_connected(): if self.trezor_is_connected():
if not self.wallet.check_proper_device(): if not self.wallet.check_proper_device():
QMessageBox.information(self.window, _('Error'), _("This wallet does not match your Trezor device"), _('OK')) QMessageBox.information(self.window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
self.wallet.force_watching_only = True
else: else:
QMessageBox.information(self.window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode."), _('OK')) QMessageBox.information(self.window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode."), _('OK'))
self.wallet.force_watching_only = True
@hook @hook
def installwizard_restore(self, wizard, storage): def installwizard_restore(self, wizard, storage):
if storage.get('wallet_type') != 'trezor': if storage.get('wallet_type') != 'trezor':
return return
wallet = TrezorWallet(storage) seed = wizard.enter_seed_dialog("Enter your Trezor seed", None, func=lambda x:True)
try: if not seed:
wallet.create_main_account(None)
except BaseException as e:
QMessageBox.information(None, _('Error'), str(e), _('OK'))
return return
wallet = TrezorWallet(storage)
self.wallet = wallet
password = wizard.password_dialog()
wallet.add_seed(seed, password)
wallet.add_cosigner_seed(' '.join(seed.split()), 'x/', password)
wallet.create_main_account(password)
# disable trezor plugin
self.set_enabled(False)
return wallet return wallet
@hook @hook
@ -161,6 +168,8 @@ class Plugin(BasePlugin):
return False return False
from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root
class TrezorWallet(BIP32_HD_Wallet): class TrezorWallet(BIP32_HD_Wallet):
wallet_type = 'trezor' wallet_type = 'trezor'
root_derivation = "m/44'/0'" root_derivation = "m/44'/0'"
@ -171,6 +180,7 @@ class TrezorWallet(BIP32_HD_Wallet):
self.client = None self.client = None
self.mpk = None self.mpk = None
self.device_checked = False self.device_checked = False
self.force_watching_only = False
def get_action(self): def get_action(self):
if not self.accounts: if not self.accounts:
@ -188,11 +198,8 @@ class TrezorWallet(BIP32_HD_Wallet):
def can_change_password(self): def can_change_password(self):
return False return False
def has_seed(self):
return False
def is_watching_only(self): def is_watching_only(self):
return False return self.force_watching_only
def get_client(self): def get_client(self):
if not TREZOR: if not TREZOR:
@ -221,7 +228,20 @@ class TrezorWallet(BIP32_HD_Wallet):
def create_main_account(self, password): def create_main_account(self, password):
self.create_account('Main account', None) #name, empty 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 = ' '.join(mnemonic.split())
return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
def derive_xkeys(self, root, derivation, password): 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'/") derivation = derivation.replace(self.root_name,"44'/0'/")
xpub = self.get_public_key(derivation) xpub = self.get_public_key(derivation)
return xpub, None return xpub, None

View File

@ -223,8 +223,8 @@ class Plugin(BasePlugin):
+ _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" + _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
def is_available(self): def is_available(self):
if self.wallet is None: if not self.wallet:
return True return False
if self.wallet.storage.get('wallet_type') == '2fa': if self.wallet.storage.get('wallet_type') == '2fa':
return True return True
return False return False
@ -238,10 +238,6 @@ class Plugin(BasePlugin):
def is_enabled(self): def is_enabled(self):
if not self.is_available(): if not self.is_available():
return False return False
if not self.wallet:
return True
if self.wallet.storage.get('wallet_type') != '2fa':
return False
if self.wallet.master_private_keys.get('x2/'): if self.wallet.master_private_keys.get('x2/'):
return False return False
return True return True
@ -344,8 +340,6 @@ class Plugin(BasePlugin):
@hook @hook
def load_wallet(self, wallet): def load_wallet(self, wallet):
self.wallet = wallet
if self.is_enabled():
self.trustedcoin_button = StatusBarButton( QIcon(":icons/trustedcoin.png"), _("Network"), self.settings_dialog) self.trustedcoin_button = StatusBarButton( QIcon(":icons/trustedcoin.png"), _("Network"), self.settings_dialog)
self.window.statusBar().addPermanentWidget(self.trustedcoin_button) self.window.statusBar().addPermanentWidget(self.trustedcoin_button)
self.xpub = self.wallet.master_public_keys.get('x1/') self.xpub = self.wallet.master_public_keys.get('x1/')
@ -481,6 +475,7 @@ class Plugin(BasePlugin):
return 0 return 0
# trustedcoin won't charge if the total inputs is lower than their fee # trustedcoin won't charge if the total inputs is lower than their fee
price = int(self.price_per_tx.get(1)) price = int(self.price_per_tx.get(1))
assert price <= 100000
if tx.input_value() < price: if tx.input_value() < price:
print_error("not charging for this tx") print_error("not charging for this tx")
return 0 return 0

View File

@ -37,7 +37,7 @@ if sys.platform == 'darwin':
app=[mainscript], app=[mainscript],
options=dict(py2app=dict(argv_emulation=True, options=dict(py2app=dict(argv_emulation=True,
includes=['PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4.QtWebKit', 'PyQt4.QtNetwork', 'sip'], includes=['PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4.QtWebKit', 'PyQt4.QtNetwork', 'sip'],
packages=['lib', 'gui', 'plugins'], packages=['lib', 'gui', 'plugins', 'packages'],
iconfile='electrum.icns', iconfile='electrum.icns',
plist=plist, plist=plist,
resources=["data", "icons"])), resources=["data", "icons"])),

View File

@ -65,14 +65,15 @@ setup(
name="Electrum", name="Electrum",
version=version.ELECTRUM_VERSION, version=version.ELECTRUM_VERSION,
install_requires=[ install_requires=[
'slowaes', 'slowaes>=0.1a1',
'ecdsa>=0.9', 'ecdsa>=0.9',
'pbkdf2', 'pbkdf2',
'requests', 'requests',
'pyasn1',
'pyasn1-modules', 'pyasn1-modules',
'pyasn1',
'qrcode', 'qrcode',
'SocksiPy-branch', 'SocksiPy-branch',
'protobuf',
'tlslite', 'tlslite',
'dnspython' 'dnspython'
], ],