From 31b6a4b4d01803ebb60a2afcccffc126ac2cd85f Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 23 Jan 2015 16:13:43 +0100 Subject: [PATCH 01/64] update documentation link --- gui/qt/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 7e6b0e115..ca993abc2 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -371,7 +371,7 @@ class ElectrumWindow(QMainWindow): help_menu.addAction(_("&About"), self.show_about) help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org")) 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) self.setMenuBar(menubar) From ee067a47ddc1a5676da9ba456ac9c32841be13f0 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 23 Jan 2015 16:57:49 +0100 Subject: [PATCH 02/64] cosign plugin: fix listener --- plugins/cosigner_pool.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/cosigner_pool.py b/plugins/cosigner_pool.py index 63216b222..fd20b06e3 100644 --- a/plugins/cosigner_pool.py +++ b/plugins/cosigner_pool.py @@ -93,14 +93,9 @@ class Plugin(BasePlugin): def init_qt(self, gui): self.win = gui.main_window 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): self.set_enabled(True) - if self.win.wallet: - self.load_wallet(self.win.wallet) return True def is_available(self): @@ -110,6 +105,9 @@ class Plugin(BasePlugin): @hook def load_wallet(self, wallet): + if self.listener is None: + self.listener = Listener(self) + self.listener.start() self.wallet = wallet if not self.is_available(): return From 622b9d13ee609c22c0ea608e7fc6abd2b1c9708d Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 23 Jan 2015 17:06:16 +0100 Subject: [PATCH 03/64] fix: start listener after testing if available --- plugins/cosigner_pool.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/cosigner_pool.py b/plugins/cosigner_pool.py index fd20b06e3..dd851acd0 100644 --- a/plugins/cosigner_pool.py +++ b/plugins/cosigner_pool.py @@ -105,12 +105,12 @@ class Plugin(BasePlugin): @hook def load_wallet(self, wallet): - if self.listener is None: - self.listener = Listener(self) - self.listener.start() self.wallet = wallet if not self.is_available(): return + if self.listener is None: + self.listener = Listener(self) + self.listener.start() mpk = self.wallet.get_master_public_keys() self.cosigner_list = [] for key, xpub in mpk.items(): From 33f48276db0d3ca325758cfabf02bc64af54ab5b Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 23 Jan 2015 17:12:26 +0100 Subject: [PATCH 04/64] trustedcoin: check that price does not exceed predefined value --- plugins/trustedcoin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/trustedcoin.py b/plugins/trustedcoin.py index 32a040342..ae7195630 100644 --- a/plugins/trustedcoin.py +++ b/plugins/trustedcoin.py @@ -481,6 +481,7 @@ class Plugin(BasePlugin): return 0 # trustedcoin won't charge if the total inputs is lower than their fee price = int(self.price_per_tx.get(1)) + assert price <= 50000 if tx.input_value() < price: print_error("not charging for this tx") return 0 From eab2e828a3f4e49f249141527ebfcffbecbc81a0 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 23 Jan 2015 17:54:48 +0100 Subject: [PATCH 05/64] update release notes for 2.0 --- RELEASE-NOTES | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index ca3440a66..d999a2be9 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,28 +1,33 @@ # 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 - current version number (0x01) refers to the current wallet - structure (BIP44). The version number also serves as a checksum for - the seed, and it will prevent the import of seeds from incompatible - wallets. + * New seed phrase format: 8 bits of the seed phrase are used to store + a version number. The current version number (0x01) refers to the + default wallet structure. The version number also serves as a + checksum for the seed, and it will prevent the import of seeds from + 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 - needed to sign inputs. This new format is compact enough to - send transactions to cold storage using QR codes + needed to sign inputs. This allows to send partially signed + transactions using QR codes * Deterministic Multisig wallets using parallel BIP32 derivations and 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 - - Cosigner Pool: shared memory pool for partially signed transactions + - BTCChip: support for the BTCChip hardware wallet (aka HW1) + - Cosigner Pool: communication channel between cosigner wallets, to + send and receive partially signed transactions + - Audio Modem: send and receive transactions by sound - * BIP70: verification of signed payment requests - - Verification is pure python, using tlslite. - - In the GUI, payment requests are in the 'Invoices' tab. + * Support for BIP70: payment requests + - Verification of the signature chain uses tlslite. + - In the GUI, payment requests are shown in the 'Invoices' tab. * New 'Receive' tab: - create and manage payment requests, with QR Codes @@ -31,7 +36,7 @@ window that pops up if you click on the QR code * 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 several clients. It times out if no client uses if for more than 5 From 6a7d50d5c5143d7d98aa76c911766f353322197f Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 23 Jan 2015 18:04:31 +0100 Subject: [PATCH 06/64] raise trustedcoin max fee. --- plugins/trustedcoin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/trustedcoin.py b/plugins/trustedcoin.py index ae7195630..5fd7764c0 100644 --- a/plugins/trustedcoin.py +++ b/plugins/trustedcoin.py @@ -481,7 +481,7 @@ class Plugin(BasePlugin): return 0 # trustedcoin won't charge if the total inputs is lower than their fee price = int(self.price_per_tx.get(1)) - assert price <= 50000 + assert price <= 100000 if tx.input_value() < price: print_error("not charging for this tx") return 0 From a171a29afbfbc6013df92650967cdd3ad31e4a8f Mon Sep 17 00:00:00 2001 From: dabura667 Date: Sun, 25 Jan 2015 12:22:40 +0900 Subject: [PATCH 07/64] Fix the line deliminator in csv currently the csv output is `\r\r\n` where the line terminator is `\r\n` and the extra `\r` is probably being inserted from writing a dict to csv. I get around this by changing the line terminator to `\n` to output `\r\n` which will make it compatible with most spreadsheet apps. --- gui/qt/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index ca993abc2..1ff6f310f 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -2507,7 +2507,7 @@ class ElectrumWindow(QMainWindow): with open(fileName, "w+") as f: if is_csv: - transaction = csv.writer(f) + transaction = csv.writer(f, lineterminator='\n') transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"]) for line in lines: transaction.writerow(line) From 82746c20fb667fb0f8aed4aaa07a45e9e8248f8d Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 25 Jan 2015 08:14:25 +0100 Subject: [PATCH 08/64] update packaging scripts --- make_packages => contrib/make_android | 32 +++++---------------- contrib/make_packages | 40 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 25 deletions(-) rename make_packages => contrib/make_android (50%) create mode 100755 contrib/make_packages diff --git a/make_packages b/contrib/make_android similarity index 50% rename from make_packages rename to contrib/make_android index 22e01a30c..859300fb0 100755 --- a/make_packages +++ b/contrib/make_android @@ -1,23 +1,20 @@ #!/usr/bin/python -from lib.version import ELECTRUM_VERSION as version if __name__ == '__main__': 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')): 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 - - # android os.system('rm -rf dist/e4a-%s'%version) os.mkdir('dist/e4a-%s'%version) shutil.copyfile("electrum",'dist/e4a-%s/e4a.py'%version) @@ -37,21 +34,6 @@ if __name__ == '__main__': e4a_name = "e4a-%s.zip"%version e4a_name2 = e4a_name.replace(".","") 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" diff --git a/contrib/make_packages b/contrib/make_packages new file mode 100755 index 000000000..5660454fb --- /dev/null +++ b/contrib/make_packages @@ -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 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') + 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" + From 50c2efc32a4294f30dd7e6fb091cc9cc63b1b013 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 25 Jan 2015 15:23:26 +0200 Subject: [PATCH 09/64] audio_modem: update plugin to work with latest version. Loading the library is now done after creating the interface. --- plugins/audio_modem.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/audio_modem.py b/plugins/audio_modem.py index 2244edbbb..efe35a28b 100644 --- a/plugins/audio_modem.py +++ b/plugins/audio_modem.py @@ -106,10 +106,8 @@ class Plugin(BasePlugin): button.clicked.connect(handler) def _audio_interface(self): - return amodem.audio.Interface( - config=self.modem_config, - name=self.library_name - ) + interface = amodem.audio.Interface(config=self.modem_config) + return interface.load(self.library_name) def _send(self, parent, blob): def sender_thread(): From d14c03b47f5da7190fd34073a136f4fa586847e2 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 25 Jan 2015 19:51:09 +0100 Subject: [PATCH 10/64] don't use listdir in make_package script --- contrib/make_packages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/make_packages b/contrib/make_packages index 5660454fb..13ef14c53 100755 --- a/contrib/make_packages +++ b/contrib/make_packages @@ -25,7 +25,7 @@ if __name__ == '__main__': os.chdir("dist") password = getpass.getpass("Password:") - for f in os.listdir("."): + 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') From 16c72d286c119562a591dc06303dfeefa3ccfa01 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 26 Jan 2015 14:14:16 +0100 Subject: [PATCH 11/64] try to import all python dependencies from the main script --- electrum | 29 +++++++++++++++++++++++++++++ lib/bitcoin.py | 18 +++--------------- lib/paymentrequest.py | 8 +------- lib/x509.py | 18 +++--------------- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/electrum b/electrum index 82bd509fa..aaa0b2348 100755 --- a/electrum +++ b/electrum @@ -36,6 +36,35 @@ if is_local: import __builtin__ __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 +except ImportError as e: + sys.exit("Error: %s. Try 'sudo pip install '"%e.message) + +# these imports must be redeclared for pyinstaller +import pyasn1.codec +import pyasn1.codec.der +from pyasn1.codec.der import encoder, decoder +from pyasn1_modules import rfc2459 + +# test that we have the correct version of ecdsa +try: + 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() + + # load local module as electrum if __builtin__.use_local_modules: import imp diff --git a/lib/bitcoin.py b/lib/bitcoin.py index d4a265b3a..779473081 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -26,15 +26,8 @@ import hmac import version from util import print_error, InvalidPassword -try: - import ecdsa -except ImportError: - sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'") - -try: - import aes -except ImportError: - sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'") +import ecdsa +import aes ################################## transactions @@ -401,12 +394,7 @@ def is_private_key(key): ########### end pywallet functions ####################### -try: - 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.ecdsa import curve_secp256k1, generator_secp256k1 from ecdsa.curves import SECP256k1 from ecdsa.ellipticcurve import Point from ecdsa.util import string_to_number, number_to_string diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index 6d2c09c04..f1b14d61b 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -27,19 +27,13 @@ import time import traceback import urllib2 import urlparse - +import requests try: 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: - sys.exit("Error: requests does not seem to be installed. Try 'sudo pip install requests'") - - import bitcoin import util import transaction diff --git a/lib/x509.py b/lib/x509.py index bebe2d72e..41da17a96 100644 --- a/lib/x509.py +++ b/lib/x509.py @@ -20,21 +20,9 @@ from datetime import datetime import sys -try: - import pyasn1 -except ImportError: - sys.exit("Error: pyasn1 does not seem to be installed. Try 'sudo pip install pyasn1'") - -try: - import pyasn1_modules -except ImportError: - sys.exit("Error: pyasn1 does not seem to be installed. Try 'sudo pip install pyasn1-modules'") - -try: - import tlslite -except ImportError: - sys.exit("Error: tlslite does not seem to be installed. Try 'sudo pip install tlslite'") - +import pyasn1 +import pyasn1_modules +import tlslite # workaround https://github.com/trevp/tlslite/issues/15 tlslite.utils.cryptomath.pycryptoLoaded = False From 9d40fb2ea8b347e08b4b64897537e2197d4325e3 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 26 Jan 2015 16:53:59 +0100 Subject: [PATCH 12/64] detect if we are in a pyinstaller bundle --- electrum | 7 ++++--- lib/plugins.py | 4 ++-- lib/util.py | 6 +++++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/electrum b/electrum index aaa0b2348..3470e9150 100755 --- a/electrum +++ b/electrum @@ -27,7 +27,8 @@ import time import traceback -is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd() +is_bundle = getattr(sys, 'frozen', False) +is_local = not is_bundle and os.path.dirname(os.path.realpath(__file__)) == os.getcwd() is_android = 'ANDROID_DATA' in os.environ if is_local: @@ -66,7 +67,7 @@ except Exception: # load local module as electrum -if __builtin__.use_local_modules: +if is_bundle or is_local or is_android: import imp imp.load_module('electrum', *imp.find_module('lib')) imp.load_module('electrum_gui', *imp.find_module('gui')) @@ -214,7 +215,7 @@ if __name__ == '__main__': cmd = args[0] if cmd == 'gui': - init_plugins(config) + init_plugins(config, is_bundle or is_local or is_android) gui_name = config.get('gui', 'classic') if gui_name in ['lite', 'classic']: gui_name = 'qt' diff --git a/lib/plugins.py b/lib/plugins.py index d3dba3500..18b2c1395 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -6,11 +6,11 @@ from i18n import _ plugins = [] -def init_plugins(config): +def init_plugins(config, local): import imp, pkgutil, __builtin__, os global plugins - if __builtin__.use_local_modules: + if local: fp, pathname, description = imp.find_module('plugins') 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) diff --git a/lib/util.py b/lib/util.py index 27e9228b4..ec3b2a7ea 100644 --- a/lib/util.py +++ b/lib/util.py @@ -66,7 +66,11 @@ def data_dir(): if __builtin__.use_local_modules: return local_data_dir() else: - return appdata_dir() + if getattr(sys, 'frozen'): + basedir = sys._MEIPASS + return os.path.join(basedir, 'data') + else: + return appdata_dir() def usr_share_dir(): return os.path.join(sys.prefix, "share") From dda4a0fcb3671a260cd5bee61ef83b747e179678 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 26 Jan 2015 20:42:32 +0100 Subject: [PATCH 13/64] call load_wallet and close_wallet for each plugin --- gui/qt/main_window.py | 9 ++++++++- lib/plugins.py | 25 +++++++++++++++---------- plugins/trezor.py | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 1ff6f310f..99c2e1df8 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -203,6 +203,7 @@ class ElectrumWindow(QMainWindow): def close_wallet(self): self.wallet.stop_threads() + self.hide() run_hook('close_wallet') def load_wallet(self, wallet): @@ -210,13 +211,17 @@ class ElectrumWindow(QMainWindow): self.wallet = wallet self.update_wallet_format() # 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.accounts_expanded = self.wallet.storage.get('accounts_expanded',{}) self.current_account = self.wallet.storage.get("current_account", None) title = 'Electrum ' + self.wallet.electrum_version + ' - ' + os.path.basename(self.wallet.storage.path) if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only')) self.setWindowTitle( title ) + self.update_history_tab() + self.show() 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 self.notify_transactions() @@ -308,6 +313,8 @@ class ElectrumWindow(QMainWindow): QMessageBox.critical(None, "Error", _("File exists")) return + if self.wallet: + self.close_wallet() wizard = installwizard.InstallWizard(self.config, self.network, storage) wallet = wizard.run('new') if wallet: diff --git a/lib/plugins.py b/lib/plugins.py index 18b2c1395..2a6a3b2b6 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -45,16 +45,17 @@ def run_hook(name, *args): for p, f in f_list: if name == 'load_wallet': p.wallet = args[0] - if not p.is_enabled(): - continue - try: - r = f(*args) - except Exception: - print_error("Plugin error") - traceback.print_exc(file=sys.stdout) - r = False - if r: - results.append(r) + if p.is_enabled(): + try: + r = f(*args) + except Exception: + print_error("Plugin error") + traceback.print_exc(file=sys.stdout) + r = False + if r: + results.append(r) + if name == 'close_wallet': + p.wallet = None if results: assert len(results) == 1, results @@ -92,8 +93,12 @@ class BasePlugin: def init_qt(self, gui): pass + @hook def load_wallet(self, wallet): pass + @hook + def close_wallet(self): pass + #def init(self): pass def close(self): pass diff --git a/plugins/trezor.py b/plugins/trezor.py index 66a0e1bdd..ca71d1507 100644 --- a/plugins/trezor.py +++ b/plugins/trezor.py @@ -93,7 +93,7 @@ class Plugin(BasePlugin): @hook def close_wallet(self): print_error("trezor: clear session") - if self.wallet.client: + if self.wallet and self.wallet.client: self.wallet.client.clear_session() @hook From 4120678dffbf4a02df903c4dc8aa95b1eb01c333 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 27 Jan 2015 10:01:40 +0100 Subject: [PATCH 14/64] add google protobuf to packages --- MANIFEST.in | 1 + electrum | 14 ++++++++++---- lib/paymentrequest.py | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 515844d0b..a96077dcf 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,6 +7,7 @@ recursive-include lib *.py recursive-include gui *.py recursive-include plugins *.py recursive-include packages *.py +recursive-include packages cacert.pem include app.fil include icons.qrc recursive-include icons * diff --git a/electrum b/electrum index 3470e9150..62ec942c7 100755 --- a/electrum +++ b/electrum @@ -32,7 +32,7 @@ is_local = not is_bundle and os.path.dirname(os.path.realpath(__file__)) == os.g is_android = 'ANDROID_DATA' in os.environ if is_local: - sys.path.append('packages') + sys.path.insert(0,'packages') import __builtin__ __builtin__.use_local_modules = is_local or is_android @@ -49,16 +49,22 @@ try: import pyasn1_modules import tlslite import pbkdf2 + import google.protobuf except ImportError as e: sys.exit("Error: %s. Try 'sudo pip install '"%e.message) -# these imports must be redeclared for pyinstaller +# 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 -# test that we have the correct version of ecdsa + +# check that we have the correct version of ecdsa try: from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1 except Exception: @@ -79,7 +85,7 @@ from electrum.util import print_msg, print_stderr, print_json, set_verbosity, In from electrum.daemon import get_daemon from electrum.plugins import init_plugins - +#print_msg("ca_bundle", requests.utils.DEFAULT_CA_BUNDLE_PATH, os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH)) # get password routine def prompt_password(prompt, confirm=True): diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index f1b14d61b..511918730 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -31,7 +31,7 @@ import requests try: import paymentrequest_pb2 -except: +except ImportError: sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto'") import bitcoin From 3ef3bebeb37d5d89821209334eec62c7bb3063af Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 27 Jan 2015 10:12:44 +0100 Subject: [PATCH 15/64] print debug message with ca bundle status --- electrum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum b/electrum index 62ec942c7..80f7ae07a 100755 --- a/electrum +++ b/electrum @@ -81,11 +81,10 @@ if is_bundle or is_local or is_android: from electrum import util 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.plugins import init_plugins -#print_msg("ca_bundle", requests.utils.DEFAULT_CA_BUNDLE_PATH, os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH)) # get password routine def prompt_password(prompt, confirm=True): @@ -210,6 +209,7 @@ if __name__ == '__main__': set_verbosity(config_options.get('verbose')) 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: url = None From 2bf32880e85cb092e55f4c42be2aea872e4d2b0e Mon Sep 17 00:00:00 2001 From: Maran Date: Tue, 27 Jan 2015 12:18:04 +0100 Subject: [PATCH 16/64] Add dynamic package loading for Py2App osx binaries --- electrum | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/electrum b/electrum index 80f7ae07a..c4931acc9 100755 --- a/electrum +++ b/electrum @@ -26,14 +26,23 @@ import sys import time import traceback +is_local = False +is_android = False + +if sys.platform == 'darwin': + is_bundle = getattr(sys, 'frozen') == "macosx_app" + if is_bundle: + sys.path.insert(0, os.getcwd() + "/lib/python2.7/packages") +else: + is_bundle = getattr(sys, 'frozen', False) + is_local = not is_bundle and os.path.dirname(os.path.realpath(__file__)) == os.getcwd() + is_android = 'ANDROID_DATA' in os.environ + if is_local: + sys.path.append('packages') -is_bundle = getattr(sys, 'frozen', False) is_local = not is_bundle and os.path.dirname(os.path.realpath(__file__)) == os.getcwd() is_android = 'ANDROID_DATA' in os.environ -if is_local: - sys.path.insert(0,'packages') - import __builtin__ __builtin__.use_local_modules = is_local or is_android From fd8a931608d3676cf7220dc955b41b7b95dbc14b Mon Sep 17 00:00:00 2001 From: Maran Date: Tue, 27 Jan 2015 12:22:28 +0100 Subject: [PATCH 17/64] Make where() work with OSX app bundles MEIPASS is PythonInstaller specific and won't work for py2app modified: lib/util.py modified: .gitignore modified: lib/util.py modified: setup-release.py --- .gitignore | 1 + lib/util.py | 9 +++++++-- setup-release.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 87d614076..7918ea80b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ gui/qt/icons_rc.py locale/ .devlocaltmp/ *_trial_temp +packages diff --git a/lib/util.py b/lib/util.py index ec3b2a7ea..dd7e7f935 100644 --- a/lib/util.py +++ b/lib/util.py @@ -66,8 +66,13 @@ def data_dir(): if __builtin__.use_local_modules: return local_data_dir() else: - if getattr(sys, 'frozen'): - basedir = sys._MEIPASS + is_frozen = getattr(sys, 'frozen') + if is_frozen: + if is_frozen == "macosx_app": + basedir = os.path.abspath(".") + else: + basedir = sys._MEIPASS + return os.path.join(basedir, 'data') else: return appdata_dir() diff --git a/setup-release.py b/setup-release.py index 62f9a8708..1ec18dd8c 100644 --- a/setup-release.py +++ b/setup-release.py @@ -37,7 +37,7 @@ if sys.platform == 'darwin': app=[mainscript], options=dict(py2app=dict(argv_emulation=True, includes=['PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4.QtWebKit', 'PyQt4.QtNetwork', 'sip'], - packages=['lib', 'gui', 'plugins'], + packages=['lib', 'gui', 'plugins', 'packages'], iconfile='electrum.icns', plist=plist, resources=["data", "icons"])), From 56e80566f9fe1fa741c8e7c9d1d7cd2f93eca53e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 27 Jan 2015 13:50:02 +0100 Subject: [PATCH 18/64] simplify packages insertion --- electrum | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/electrum b/electrum index c4931acc9..f30607bb0 100755 --- a/electrum +++ b/electrum @@ -26,23 +26,15 @@ import sys import time import traceback -is_local = False -is_android = False - -if sys.platform == 'darwin': - is_bundle = getattr(sys, 'frozen') == "macosx_app" - if is_bundle: - sys.path.insert(0, os.getcwd() + "/lib/python2.7/packages") -else: - is_bundle = getattr(sys, 'frozen', False) - is_local = not is_bundle and os.path.dirname(os.path.realpath(__file__)) == os.getcwd() - is_android = 'ANDROID_DATA' in os.environ - if is_local: - sys.path.append('packages') - +is_bundle = getattr(sys, 'frozen', False) is_local = not is_bundle and os.path.dirname(os.path.realpath(__file__)) == os.getcwd() is_android = 'ANDROID_DATA' in os.environ +if is_local: + sys.path.insert(0, 'packages') +elif is_bundle and sys.platform=='darwin': + sys.path.insert(0, os.getcwd() + "/lib/python2.7/packages") + import __builtin__ __builtin__.use_local_modules = is_local or is_android From 4eda748506851129e4d6e12628041c3db01d6744 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 28 Jan 2015 08:24:51 +0100 Subject: [PATCH 19/64] fix #991 --- lib/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.py b/lib/util.py index dd7e7f935..175c29045 100644 --- a/lib/util.py +++ b/lib/util.py @@ -66,7 +66,7 @@ def data_dir(): if __builtin__.use_local_modules: return local_data_dir() else: - is_frozen = getattr(sys, 'frozen') + is_frozen = getattr(sys, 'frozen', False) if is_frozen: if is_frozen == "macosx_app": basedir = os.path.abspath(".") From 667bc59426c60ab5e6e640ce162738a2cfac26e2 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 29 Jan 2015 11:06:46 +0100 Subject: [PATCH 20/64] fix check_for_disable in network window --- gui/qt/network_dialog.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/gui/qt/network_dialog.py b/gui/qt/network_dialog.py index b777db29c..549c42e33 100644 --- a/gui/qt/network_dialog.py +++ b/gui/qt/network_dialog.py @@ -143,19 +143,18 @@ class NetworkDialog(QDialog): self.proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) def check_for_disable(index = False): - if self.proxy_mode.currentText() != 'NONE': - self.proxy_host.setEnabled(True) - self.proxy_port.setEnabled(True) + if self.config.is_modifiable('proxy'): + if self.proxy_mode.currentText() != 'NONE': + self.proxy_host.setEnabled(True) + self.proxy_port.setEnabled(True) + else: + self.proxy_host.setEnabled(False) + self.proxy_port.setEnabled(False) else: - self.proxy_host.setEnabled(False) - self.proxy_port.setEnabled(False) + for w in [self.proxy_host, self.proxy_port, self.proxy_mode]: w.setEnabled(False) 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_host.setText(proxy_config.get("host")) self.proxy_port.setText(proxy_config.get("port")) From 30763a655563611ac1f662be8a023b61ffc107a3 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 29 Jan 2015 11:21:17 +0100 Subject: [PATCH 21/64] fix config: serialize/deserialize proxy --- lib/interface.py | 43 +++++++++++++++++++++++-------------------- lib/network.py | 10 ++++++---- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/lib/interface.py b/lib/interface.py index 0831dd555..76685ba57 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -38,6 +38,28 @@ proxy_modes = ['socks4', 'socks5', 'http'] import util +def serialize_proxy(p): + 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): @@ -68,7 +90,7 @@ class TcpInterface(threading.Thread): self.host, self.port, self.protocol = self.server.split(':') self.port = int(self.port) 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: self.proxy_mode = proxy_modes.index(self.proxy["mode"]) + 1 socks.setdefaultproxy(self.proxy_mode, self.proxy["host"], int(self.proxy["port"])) @@ -271,25 +293,6 @@ class TcpInterface(threading.Thread): self.unanswered_requests[self.message_id] = method, params, _id, queue 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): if self.is_connected and self.protocol in 'st' and self.s: self.s.shutdown(socket.SHUT_RDWR) diff --git a/lib/network.py b/lib/network.py index c1b88ba9b..fc542a075 100644 --- a/lib/network.py +++ b/lib/network.py @@ -178,7 +178,7 @@ class Network(threading.Thread): def get_parameters(self): host, port, protocol = self.default_server.split(':') - proxy = self.proxy + proxy = interface.deserialize_proxy(self.proxy) auto_connect = self.config.get('auto_cycle', True) return host, port, protocol, proxy, auto_connect @@ -225,14 +225,16 @@ class Network(threading.Thread): threading.Thread.start(self) def set_parameters(self, host, port, protocol, proxy, auto_connect): + proxy_str = interface.serialize_proxy(proxy) 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) server = ':'.join([ host, port, protocol ]) self.config.set_key("server", server, True) - if self.proxy != proxy or self.protocol != protocol: - self.proxy = proxy + if self.proxy != proxy_str or self.protocol != protocol: + print_error('restarting network') + self.proxy = proxy_str self.protocol = protocol for i in self.interfaces.values(): i.stop() if auto_connect: From 992a634a77534c7e3cc35eac298f7235a8c731e2 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 29 Jan 2015 11:30:42 +0100 Subject: [PATCH 22/64] serialize: handle case where proxy is None --- lib/interface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/interface.py b/lib/interface.py index 76685ba57..71b4f6884 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -39,6 +39,8 @@ proxy_modes = ['socks4', 'socks5', 'http'] 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): From 8ca17a3e705d8f22723fcc8012552e0172804290 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 29 Jan 2015 11:32:58 +0100 Subject: [PATCH 23/64] print traceback is an exception occurs in network.process_request --- lib/network.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index fc542a075..ba46dd4bb 100644 --- a/lib/network.py +++ b/lib/network.py @@ -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 bitcoin import * import interface @@ -360,6 +368,7 @@ class Network(threading.Thread): out['result'] = f(*params) except BaseException as e: out['error'] = str(e) + traceback.print_exc(file=sys.stout) print_error("network error", str(e)) self.response_queue.put(out) From 3c6cfc8b70671d314e9cac41d5237abbf5ac7266 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 29 Jan 2015 13:35:19 +0100 Subject: [PATCH 24/64] disable server selector if server is passed from cmd line --- gui/qt/network_dialog.py | 15 ++++++++------- lib/network.py | 6 +++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/gui/qt/network_dialog.py b/gui/qt/network_dialog.py index 549c42e33..a52cb1c23 100644 --- a/gui/qt/network_dialog.py +++ b/gui/qt/network_dialog.py @@ -122,14 +122,15 @@ class NetworkDialog(QDialog): lambda x,y: self.server_changed(x)) 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(): - enabled = not self.autocycle_cb.isChecked() - self.server_host.setEnabled(enabled) - self.server_port.setEnabled(enabled) - self.servers_list_widget.setEnabled(enabled) + if config.is_modifiable('server'): + enabled = not self.autocycle_cb.isChecked() + self.server_host.setEnabled(enabled) + self.server_port.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) enable_set_server() diff --git a/lib/network.py b/lib/network.py index ba46dd4bb..de6ac30a7 100644 --- a/lib/network.py +++ b/lib/network.py @@ -234,11 +234,11 @@ class Network(threading.Thread): 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("proxy", proxy_str, True) self.config.set_key("protocol", protocol, True) - server = ':'.join([ host, port, protocol ]) - self.config.set_key("server", server, True) + self.config.set_key("server", server_str, True) if self.proxy != proxy_str or self.protocol != protocol: print_error('restarting network') @@ -256,7 +256,7 @@ class Network(threading.Thread): if self.server_is_lagging(): self.stop_interface() else: - self.set_server(server) + self.set_server(server_str) def switch_to_random_interface(self): From 3d2a410de0a4972a632069ecfad9b4108cb4027c Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 29 Jan 2015 14:04:04 +0100 Subject: [PATCH 25/64] launch wizard if user opens an unfinished wallet --- gui/qt/main_window.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 99c2e1df8..5f9abafc4 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -267,9 +267,31 @@ class ElectrumWindow(QMainWindow): # close current wallet self.close_wallet() - # load new wallet - wallet = Wallet(storage) - wallet.start_threads(self.network) + + # read wizard action + try: + wallet = Wallet(storage) + except BaseException as e: + QMessageBox.warning(None, _('Warning'), str(e), _('OK')) + return + action = wallet.get_action() + + # run wizard + 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) + + # load new wallet in gui self.load_wallet(wallet) From 935a9a980d1d507dc7504af38e442c57d78420d9 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 30 Jan 2015 10:19:22 +0100 Subject: [PATCH 26/64] disable auo-connect is --server option is passed. fixes #992 --- electrum | 3 ++- lib/simple_config.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/electrum b/electrum index f30607bb0..75e5e0a8c 100755 --- a/electrum +++ b/electrum @@ -206,9 +206,10 @@ if __name__ == '__main__': for k, v in config_options.items(): if v is None: config_options.pop(k) + if config_options.get('server'): + config_options['auto_cycle'] = False set_verbosity(config_options.get('verbose')) - 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") diff --git a/lib/simple_config.py b/lib/simple_config.py index 0824c04e4..b9ad42f4d 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -110,7 +110,7 @@ class SimpleConfig(object): out = None with self.lock: out = self.read_only_options.get(key) - if not out: + if out is None: out = self.user_config.get(key, default) return out From ee6a9f7249d99c206096fd65e43c6a124998293a Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 30 Jan 2015 12:34:46 +0100 Subject: [PATCH 27/64] do not include btchip plugin in package; it has too many bugs --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index a96077dcf..76dcc32e4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -15,3 +15,4 @@ recursive-include scripts * recursive-include data * recursive-include locale *.mo recursive-include docs * +recursive-exclude plugins btchipwallet.py \ No newline at end of file From 8b44dd2a9ef69439e415a5dd684b0e84aab6b163 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 30 Jan 2015 12:43:27 +0100 Subject: [PATCH 28/64] update release notes --- RELEASE-NOTES | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index d999a2be9..3f4691013 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -20,9 +20,8 @@ - TrustedCoin: two-factor authentication using 2 of 3 multisig and Google Authenticator - Trezor: support for the Trezor hardware wallet by SatoshiLabs - - BTCChip: support for the BTCChip hardware wallet (aka HW1) - - Cosigner Pool: communication channel between cosigner wallets, to - send and receive 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 * Support for BIP70: payment requests @@ -47,6 +46,8 @@ * 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 From 94c9bf3655d682bb70853629fb9b2a6d51d3cb7a Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 30 Jan 2015 13:18:00 +0100 Subject: [PATCH 29/64] define special hooks for install wizard --- lib/plugins.py | 3 ++- plugins/trustedcoin.py | 16 +++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/plugins.py b/lib/plugins.py index 2a6a3b2b6..311dea7aa 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -40,12 +40,13 @@ def hook(func): def run_hook(name, *args): + SPECIAL_HOOKS = ['add_wallet_types', 'get_wizard_action'] results = [] f_list = hooks.get(name,[]) for p, f in f_list: if name == 'load_wallet': p.wallet = args[0] - if p.is_enabled(): + if name in SPECIAL_HOOKS or p.is_enabled(): try: r = f(*args) except Exception: diff --git a/plugins/trustedcoin.py b/plugins/trustedcoin.py index 5fd7764c0..b78846ccc 100644 --- a/plugins/trustedcoin.py +++ b/plugins/trustedcoin.py @@ -344,15 +344,13 @@ class Plugin(BasePlugin): @hook 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.window.statusBar().addPermanentWidget(self.trustedcoin_button) - self.xpub = self.wallet.master_public_keys.get('x1/') - self.user_id = self.get_user_id()[1] - t = threading.Thread(target=self.request_billing_info) - t.setDaemon(True) - t.start() + self.trustedcoin_button = StatusBarButton( QIcon(":icons/trustedcoin.png"), _("Network"), self.settings_dialog) + self.window.statusBar().addPermanentWidget(self.trustedcoin_button) + self.xpub = self.wallet.master_public_keys.get('x1/') + self.user_id = self.get_user_id()[1] + t = threading.Thread(target=self.request_billing_info) + t.setDaemon(True) + t.start() @hook def close_wallet(self): From 3b9f9d5320ff9353cf4f021881817bc8425c292b Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 30 Jan 2015 13:36:20 +0100 Subject: [PATCH 30/64] show confirmation dialog before opening incomplete wallet --- gui/qt/main_window.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 5f9abafc4..110570464 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -265,8 +265,6 @@ class ElectrumWindow(QMainWindow): self.show_message("file not found "+ filename) return - # close current wallet - self.close_wallet() # read wizard action try: @@ -276,6 +274,14 @@ class ElectrumWindow(QMainWindow): 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 + + # close current wallet + self.close_wallet() + # run wizard if action is not None: import installwizard From 1f9598e1c7a06978da5e898d7485b87c83f76a18 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 30 Jan 2015 13:44:05 +0100 Subject: [PATCH 31/64] show dialog if IOError is raised reading wallet file --- gui/qt/main_window.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 110570464..f305ca093 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -259,13 +259,14 @@ class ElectrumWindow(QMainWindow): filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) ) if not filename: return - - storage = WalletStorage({'wallet_path': filename}) + try: + storage = WalletStorage({'wallet_path': filename}) + except Exception as e: + self.show_message(str(e)) + return if not storage.file_exists: - self.show_message("file not found "+ filename) + self.show_message(_("File not found") + ' ' + filename) return - - # read wizard action try: wallet = Wallet(storage) @@ -273,15 +274,12 @@ class ElectrumWindow(QMainWindow): 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 - # close current wallet self.close_wallet() - # run wizard if action is not None: import installwizard @@ -296,7 +294,6 @@ class ElectrumWindow(QMainWindow): return else: wallet.start_threads(self.network) - # load new wallet in gui self.load_wallet(wallet) From 36a5e095324248920f349335cfa32a6808943a1e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sat, 31 Jan 2015 08:35:07 +0100 Subject: [PATCH 32/64] trezor restore from seed --- plugins/trezor.py | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/plugins/trezor.py b/plugins/trezor.py index ca71d1507..44399f1f0 100644 --- a/plugins/trezor.py +++ b/plugins/trezor.py @@ -99,22 +99,31 @@ class Plugin(BasePlugin): @hook def load_wallet(self, wallet): self.wallet = wallet + if self.wallet.has_seed(): + return if self.trezor_is_connected(): if not self.wallet.check_proper_device(): QMessageBox.information(self.window, _('Error'), _("This wallet does not match your Trezor device"), _('OK')) + self.wallet.force_watching_only = True else: QMessageBox.information(self.window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode."), _('OK')) + self.wallet.force_watching_only = True @hook def installwizard_restore(self, wizard, storage): if storage.get('wallet_type') != 'trezor': return - wallet = TrezorWallet(storage) - try: - wallet.create_main_account(None) - except BaseException as e: - QMessageBox.information(None, _('Error'), str(e), _('OK')) + seed = wizard.enter_seed_dialog("Enter your Trezor seed", None, func=lambda x:True) + if not seed: 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 @hook @@ -161,6 +170,8 @@ class Plugin(BasePlugin): return False +from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root + class TrezorWallet(BIP32_HD_Wallet): wallet_type = 'trezor' root_derivation = "m/44'/0'" @@ -171,6 +182,7 @@ class TrezorWallet(BIP32_HD_Wallet): self.client = None self.mpk = None self.device_checked = False + self.force_watching_only = False def get_action(self): if not self.accounts: @@ -188,11 +200,8 @@ class TrezorWallet(BIP32_HD_Wallet): def can_change_password(self): return False - def has_seed(self): - return False - def is_watching_only(self): - return False + return self.force_watching_only def get_client(self): if not TREZOR: @@ -221,10 +230,23 @@ class TrezorWallet(BIP32_HD_Wallet): def create_main_account(self, password): self.create_account('Main account', None) #name, empty password + def mnemonic_to_seed(self, mnemonic, passphrase): + # trezor uses bip39 + import pbkdf2, hashlib, hmac + PBKDF2_ROUNDS = 2048 + mnemonic = ' '.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): - derivation = derivation.replace(self.root_name,"44'/0'/") - xpub = self.get_public_key(derivation) - return xpub, None + x = self.master_private_keys.get(root) + if x: + root_xprv = pw_decode(x, password) + xprv, xpub = bip32_private_derivation(root_xprv, root, derivation) + return xpub, xprv + else: + derivation = derivation.replace(self.root_name,"44'/0'/") + xpub = self.get_public_key(derivation) + return xpub, None def get_public_key(self, bip32_path): address_n = self.get_client().expand_path(bip32_path) From 78ce20b0b8461c84bd40d039b3346a448a9e810f Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sat, 31 Jan 2015 18:09:50 +0100 Subject: [PATCH 33/64] fix plugins initialization --- lib/plugins.py | 3 +++ plugins/trezor.py | 23 ++++++++++------------- plugins/trustedcoin.py | 8 ++------ 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/plugins.py b/lib/plugins.py index 311dea7aa..1206e865a 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -46,6 +46,9 @@ def run_hook(name, *args): for p, f in f_list: if name == 'load_wallet': p.wallet = args[0] + if name == 'init_qt': + gui = args[0] + p.window = gui.main_window if name in SPECIAL_HOOKS or p.is_enabled(): try: r = f(*args) diff --git a/plugins/trezor.py b/plugins/trezor.py index 44399f1f0..d4ebc6c7f 100644 --- a/plugins/trezor.py +++ b/plugins/trezor.py @@ -55,11 +55,13 @@ class Plugin(BasePlugin): return TREZOR def is_available(self): - if self.wallet is None: - return self._is_available - if self.wallet.storage.get('wallet_type') == 'trezor': - return True - return False + if not self._is_available: + return False + if not self.wallet: + return False + if self.wallet.storage.get('wallet_type') != 'trezor': + return False + return True def requires_settings(self): return self._requires_settings @@ -70,11 +72,9 @@ class Plugin(BasePlugin): def is_enabled(self): if not self.is_available(): return False - - if not self.wallet or self.wallet.storage.get('wallet_type') == 'trezor': - return True - - return self.wallet.storage.get('use_' + self.name) is True + if self.wallet.has_seed(): + return False + return True def enable(self): return BasePlugin.enable(self) @@ -98,9 +98,6 @@ class Plugin(BasePlugin): @hook def load_wallet(self, wallet): - self.wallet = wallet - if self.wallet.has_seed(): - return if self.trezor_is_connected(): if not self.wallet.check_proper_device(): QMessageBox.information(self.window, _('Error'), _("This wallet does not match your Trezor device"), _('OK')) diff --git a/plugins/trustedcoin.py b/plugins/trustedcoin.py index b78846ccc..2bfc132dd 100644 --- a/plugins/trustedcoin.py +++ b/plugins/trustedcoin.py @@ -223,8 +223,8 @@ class Plugin(BasePlugin): + _("For more information, visit") + " https://api.trustedcoin.com/#/electrum-help" def is_available(self): - if self.wallet is None: - return True + if not self.wallet: + return False if self.wallet.storage.get('wallet_type') == '2fa': return True return False @@ -238,10 +238,6 @@ class Plugin(BasePlugin): def is_enabled(self): if not self.is_available(): 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/'): return False return True From 820d356325ca45977819123e6486e6183e9b97d5 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sat, 31 Jan 2015 20:25:12 +0100 Subject: [PATCH 34/64] fix saving request: binary flag is needed on windows --- lib/paymentrequest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index 511918730..0c9630268 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -110,7 +110,7 @@ class PaymentRequest: self.id = bitcoin.sha256(r)[0:16].encode('hex') filename = os.path.join(self.dir_path, self.id) - with open(filename,'w') as f: + with open(filename,'wb') as f: f.write(r) return self.parse(r) @@ -125,7 +125,7 @@ class PaymentRequest: def read_file(self, key): filename = os.path.join(self.dir_path, key) - with open(filename,'r') as f: + with open(filename,'rb') as f: r = f.read() assert key == bitcoin.sha256(r)[0:16].encode('hex') From f305c01792fa56d71ea375d2be6259d74d60012e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sat, 31 Jan 2015 20:41:28 +0100 Subject: [PATCH 35/64] accept bitcoin: URIs in payto field --- gui/qt/paytoedit.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py index 924ffcb8f..b9e3c88f3 100644 --- a/gui/qt/paytoedit.py +++ b/gui/qt/paytoedit.py @@ -96,20 +96,21 @@ class PayToEdit(ScanQRTextEdit): self.errors = [] if self.is_pr: return - # filter out empty lines lines = filter( lambda x: x, self.lines()) outputs = [] total = 0 - self.payto_address = None if len(lines) == 1: + data = lines[0] + if data.startswith("bitcoin:"): + self.scan_f(data) + return try: - self.payto_address = self.parse_address(lines[0]) + self.payto_address = self.parse_address(data) except: pass - if self.payto_address: self.unlock_amount() return From 4b9385b70b819988b6d55d963773688027d6ae74 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sat, 31 Jan 2015 20:44:52 +0100 Subject: [PATCH 36/64] switch expired and unpaid icons --- icons/expired.png | Bin 27679 -> 28522 bytes icons/unpaid.png | Bin 28522 -> 27679 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/expired.png b/icons/expired.png index e0f3639df66d088a384073442d6451b0f34ecfaa..6400b8ba62b7c51ab9a7639d4239ae49475607a4 100644 GIT binary patch literal 28522 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 N<~?22GG)uq{{tA31VI1* literal 27679 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 diff --git a/icons/unpaid.png b/icons/unpaid.png index 6400b8ba62b7c51ab9a7639d4239ae49475607a4..e0f3639df66d088a384073442d6451b0f34ecfaa 100644 GIT binary patch literal 27679 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 literal 28522 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 N<~?22GG)uq{{tA31VI1* From 0da3836b14af29e7ca369e7653d1b1af481a58b0 Mon Sep 17 00:00:00 2001 From: BTChip Date: Sun, 1 Feb 2015 02:29:21 +0100 Subject: [PATCH 37/64] Catch up with modifications, along with Python API 0.1.12 closes #869 #868 #957 #871 #870 --- plugins/btchipwallet.py | 62 +++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py index 7852b3997..c45e89fc3 100644 --- a/plugins/btchipwallet.py +++ b/plugins/btchipwallet.py @@ -15,7 +15,7 @@ from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, public_key_to from electrum.i18n import _ from electrum.plugins import BasePlugin, hook from electrum.transaction import deserialize -from electrum.wallet import NewWallet +from electrum.wallet import BIP32_HD_Wallet from electrum.util import format_satoshis import hashlib @@ -52,12 +52,14 @@ class Plugin(BasePlugin): def _init(self): return BTCHIP - def is_available(self): - if self.wallet is None: - return self._is_available - if self.wallet.storage.get('wallet_type') == 'btchip': - return True - return False + def is_available(self): + if not self._is_available: + return False + if not self.wallet: + return False + if self.wallet.storage.get('wallet_type') != 'btchip': + return False + return True def set_enabled(self, enabled): self.wallet.storage.put('use_' + self.name, enabled) @@ -65,19 +67,30 @@ class Plugin(BasePlugin): def is_enabled(self): if not self.is_available(): return False - - if not self.wallet or self.wallet.storage.get('wallet_type') == 'btchip': - return True - - return self.wallet.storage.get('use_' + self.name) is True + if self.wallet.has_seed(): + return False + return True def enable(self): return BasePlugin.enable(self) + def btchip_is_connected(self): + try: + self.wallet.get_client().getFirmwareVersion() + except: + return False + return True + @hook 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 def installwizard_restore(self, wizard, storage): if storage.get('wallet_type') != 'btchip': @@ -98,16 +111,18 @@ class Plugin(BasePlugin): except Exception as e: tx.error = str(e) -class BTChipWallet(NewWallet): +class BTChipWallet(BIP32_HD_Wallet): wallet_type = 'btchip' + root_derivation = "m/44'/0'" def __init__(self, storage): - NewWallet.__init__(self, storage) + BIP32_HD_Wallet.__init__(self, storage) self.transport = None self.client = None self.mpk = None self.device_checked = False self.signing = False + self.force_watching_only = False def give_error(self, message, clear_client = False): if not self.signing: @@ -129,11 +144,8 @@ class BTChipWallet(NewWallet): def can_change_password(self): return False - def has_seed(self): - return False - def is_watching_only(self): - return False + return self.force_watching_only def get_client(self, noPin=False): if not BTCHIP: @@ -258,9 +270,6 @@ class BTChipWallet(NewWallet): def get_master_public_key(self): try: 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'") return self.mpk except Exception, e: @@ -278,6 +287,7 @@ class BTChipWallet(NewWallet): def sign_message(self, address, message, password): use2FA = False + self.signing = True self.get_client() # prompt for the PIN before displaying the dialog if necessary if not self.check_proper_device(): self.give_error('Wrong device or password') @@ -298,11 +308,15 @@ class BTChipWallet(NewWallet): self.get_client(True) signature = self.get_client().signMessageSign(pin) except Exception, e: - self.give_error(e, True) + 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) finally: if waitDialog.waiting: waitDialog.emit(SIGNAL('dongle_done')) self.client.bad = use2FA + self.signing = False # Parse the ASN.1 signature From 62fd3ad9a0cc610c5889b480f8f7472b23039a3c Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 1 Feb 2015 09:51:54 +0100 Subject: [PATCH 38/64] 2.0 beta --- lib/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/version.py b/lib/version.py index 5b98ef598..f7c23962c 100644 --- a/lib/version.py +++ b/lib/version.py @@ -1,4 +1,4 @@ -ELECTRUM_VERSION = "2.0" # version of the client package +ELECTRUM_VERSION = "2.0-beta" # version of the client package PROTOCOL_VERSION = '0.9' # protocol version requested NEW_SEED_VERSION = 11 # electrum versions >= 2.0 OLD_SEED_VERSION = 4 # electrum versions < 2.0 From 395312e70f39bdbfccd637b66c947a10ab96487c Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 1 Feb 2015 10:44:29 +0100 Subject: [PATCH 39/64] fix: add wallet types only if available --- lib/plugins.py | 2 +- lib/wallet.py | 1 - plugins/btchipwallet.py | 6 +++--- plugins/trezor.py | 3 ++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/plugins.py b/lib/plugins.py index 1206e865a..956caffa4 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -40,7 +40,7 @@ def hook(func): def run_hook(name, *args): - SPECIAL_HOOKS = ['add_wallet_types', 'get_wizard_action'] + SPECIAL_HOOKS = ['get_wizard_action'] results = [] f_list = hooks.get(name,[]) for p, f in f_list: diff --git a/lib/wallet.py b/lib/wallet.py index ef8a62cb5..9bb553666 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1637,7 +1637,6 @@ class Wallet(object): msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version raise BaseException(msg) - run_hook('add_wallet_types', wallet_types) wallet_type = storage.get('wallet_type') if wallet_type: for cat, t, name, c in wallet_types: diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py index c45e89fc3..727ddc951 100644 --- a/plugins/btchipwallet.py +++ b/plugins/btchipwallet.py @@ -45,9 +45,9 @@ class Plugin(BasePlugin): def __init__(self, gui, name): BasePlugin.__init__(self, gui, name) self._is_available = self._init() - self.wallet = None - electrum.wallet.wallet_types.append(('hardware', 'btchip', _("BTChip wallet"), BTChipWallet)) - + self.wallet = None + if self._is_available: + electrum.wallet.wallet_types.append(('hardware', 'btchip', _("BTChip wallet"), BTChipWallet)) def _init(self): return BTCHIP diff --git a/plugins/trezor.py b/plugins/trezor.py index d4ebc6c7f..1048b6e02 100644 --- a/plugins/trezor.py +++ b/plugins/trezor.py @@ -49,7 +49,8 @@ class Plugin(BasePlugin): self._is_available = self._init() self._requires_settings = True self.wallet = None - electrum.wallet.wallet_types.append(('hardware', 'trezor', _("Trezor wallet"), TrezorWallet)) + if self._is_available: + electrum.wallet.wallet_types.append(('hardware', 'trezor', _("Trezor wallet"), TrezorWallet)) def _init(self): return TREZOR From 3ae1e80ad28ddeff5f5f951bc193e71ce4ee5839 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 1 Feb 2015 13:39:00 +0100 Subject: [PATCH 40/64] add btchip again --- MANIFEST.in | 1 - 1 file changed, 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 76dcc32e4..a96077dcf 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -15,4 +15,3 @@ recursive-include scripts * recursive-include data * recursive-include locale *.mo recursive-include docs * -recursive-exclude plugins btchipwallet.py \ No newline at end of file From 1905b4743dc864d58c29b8f52f4f7aa8f1d9493e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 1 Feb 2015 22:05:19 +0100 Subject: [PATCH 41/64] fix: add missing protobuf to dependencies --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f3beb76fa..0e8f1e008 100644 --- a/setup.py +++ b/setup.py @@ -73,6 +73,7 @@ setup( 'pyasn1-modules', 'qrcode', 'SocksiPy-branch', + 'protobuf', 'tlslite' ], package_dir={ From 6050e4d549516ef68ccd1ee400d3da6a732ae176 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 2 Feb 2015 13:54:03 +0100 Subject: [PATCH 42/64] fix QRcode widget in multisig wallet wizard --- gui/qt/installwizard.py | 8 ++--- gui/qt/seed_dialog.py | 68 +++++++++++------------------------------ 2 files changed, 22 insertions(+), 54 deletions(-) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index 0b625d75f..46e4549f6 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -142,10 +142,10 @@ class InstallWizard(QDialog): def multi_mpk_dialog(self, xpub_hot, n): 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) - seed_e0.setText(xpub_hot) - seed_e0.setReadOnly(True) + #seed_e0.setText(xpub_hot) + #seed_e0.setReadOnly(True) entries = [] for i in range(n): vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, self, 'cold') @@ -308,7 +308,7 @@ class InstallWizard(QDialog): 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"))) self.set_layout(vbox) return self.exec_() diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py index c27f77050..87497f4a7 100644 --- a/gui/qt/seed_dialog.py +++ b/gui/qt/seed_dialog.py @@ -31,7 +31,7 @@ class SeedDialog(QDialog): self.setModal(1) self.setMinimumWidth(400) self.setWindowTitle('Electrum' + ' - ' + _('Seed')) - vbox = show_seed_box(seed) + vbox = show_seed_box_msg(seed) if imported_keys: vbox.addWidget(QLabel(""+_("WARNING")+": " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "

")) vbox.addLayout(close_button(self)) @@ -47,71 +47,39 @@ def icon_filename(sid): return ":icons/seed.png" - - -def show_seed_box(seed, sid=None): - - 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.") + "

" - warning_msg = ""+_("WARNING")+": " + _("Never disclose your seed. Never type it on a website.") + "

" - - if sid is None: - msg = _("Your wallet generation seed is") - msg2 = save_msg + " " \ - + _("This seed will allow you to recover your wallet in case of computer failure.") + "
" \ - + warning_msg - - 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) - +def show_seed_box_msg(seedphrase, sid=None): + msg = _("Your wallet generation seed is") + ":" + vbox = show_seed_box(msg, seedphrase, sid) + save_msg = _("Please save these %d words on paper (order is important).")%len(seedphrase.split()) + " " + msg2 = save_msg + " " \ + + _("This seed will allow you to recover your wallet in case of computer failure.") + "
" \ + + ""+_("WARNING")+": " + _("Never disclose your seed. Never type it on a website.") + "

" label2 = QLabel(msg2) 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.addStretch(1) - 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() logo = QLabel() logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56)) logo.setMaximumWidth(60) - label = QLabel(msg) label.setWordWrap(True) - - seed_e = ScanQRTextEdit(win=window) - seed_e.setMaximumHeight(100) - seed_e.setTabChangesFocus(True) - + if not text: + seed_e = ScanQRTextEdit(win=window) + seed_e.setTabChangesFocus(True) + else: + seed_e = ShowQRTextEdit(text=text) + seed_e.setMaximumHeight(130) vbox.addWidget(label) - grid = QGridLayout() grid.addWidget(logo, 0, 0) grid.addWidget(seed_e, 0, 1) - vbox.addLayout(grid) return vbox, seed_e From 97c57996b8d2398785b2a77aa188c236125301da Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 2 Feb 2015 15:12:27 +0100 Subject: [PATCH 43/64] cleanup --- gui/qt/installwizard.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index 46e4549f6..b0a51cf23 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -144,8 +144,6 @@ class InstallWizard(QDialog): vbox = QVBoxLayout() vbox0 = seed_dialog.show_seed_box(MSG_SHOW_MPK, xpub_hot, 'hot') vbox.addLayout(vbox0) - #seed_e0.setText(xpub_hot) - #seed_e0.setReadOnly(True) entries = [] for i in range(n): vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, self, 'cold') From 6bb4a554e39db25799a8d113336acb8e3638047a Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 2 Feb 2015 15:15:44 +0100 Subject: [PATCH 44/64] suggest pip install instead of apt-get --- lib/qrscanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/qrscanner.py b/lib/qrscanner.py index 479dc8e8c..38cc1f3d0 100644 --- a/lib/qrscanner.py +++ b/lib/qrscanner.py @@ -12,7 +12,7 @@ proc = None def scan_qr(config): global proc 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: device = config.get("video_device", "default") if device == 'default': From 0b674eb35de78d81877276654cc877aab625df9e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 2 Feb 2015 16:06:29 +0100 Subject: [PATCH 45/64] fix can_sign: detect if OldWallet is used --- lib/wallet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/wallet.py b/lib/wallet.py index 9bb553666..1cb685931 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1048,9 +1048,11 @@ class Abstract_Wallet(object): addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex')) return self.is_mine(addr) elif x_pubkey[0:2] == 'ff': + if isinstance(self, OldWallet): return False xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey) return xpub in [ self.master_public_keys[k] for k in self.master_private_keys.keys() ] elif x_pubkey[0:2] == 'fe': + if not isinstance(self, OldWallet): return False xpub, sequence = OldAccount.parse_xpubkey(x_pubkey) return xpub == self.get_master_public_key() elif x_pubkey[0:2] == 'fd': From e770ec3a613b8eb4291d0da5aaa10f013197cc49 Mon Sep 17 00:00:00 2001 From: pooler Date: Mon, 2 Feb 2015 17:49:11 +0100 Subject: [PATCH 46/64] Retrieve version information using HTTPS --- gui/qt/version_getter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/version_getter.py b/gui/qt/version_getter.py index f60a60919..52d52fbb9 100644 --- a/gui/qt/version_getter.py +++ b/gui/qt/version_getter.py @@ -33,7 +33,7 @@ class VersionGetter(threading.Thread): def run(self): try: - con = httplib.HTTPConnection('electrum.org', 80, timeout=5) + con = httplib.HTTPSConnection('electrum.org', timeout=5) con.request("GET", "/version") res = con.getresponse() except socket.error as msg: From 50c6a2fcbd366111c74c50f6b63b96bc9023f138 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 2 Feb 2015 18:38:12 +0100 Subject: [PATCH 47/64] show wallet name in wizard --- gui/qt/installwizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index b0a51cf23..2d8291780 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -35,7 +35,7 @@ class InstallWizard(QDialog): self.storage = storage self.setMinimumSize(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.stack = QStackedLayout() self.setLayout(self.stack) From 25b82992f0c3157cbd97668bcc0f71da01d4ed98 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 2 Feb 2015 19:09:17 +0100 Subject: [PATCH 48/64] add encrypt/decrypt to RELEASE_NOTES --- RELEASE-NOTES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 3f4691013..9780db805 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -50,6 +50,11 @@ * 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 + decrypt + # Release 1.9.8 From 27382c210e83704c350b99ec1c9952590e4ea1bd Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 2 Feb 2015 19:17:08 +0100 Subject: [PATCH 49/64] fix messages for i18n --- gui/qt/main_window.py | 2 +- gui/qt/transaction_dialog.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index f305ca093..2ea39a154 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1310,7 +1310,7 @@ class ElectrumWindow(QMainWindow): if not request_url: if 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): self.wallet.addressbook.append(address) self.wallet.set_label(address, label) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index 0754dbdec..866057f95 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -156,7 +156,7 @@ class TxDialog(QDialog): self.broadcast_button.show() else: 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 self.broadcast_button.hide() tx_hash = 'unknown' From 4f2d75142fb154545828d471b478b7712aac9d42 Mon Sep 17 00:00:00 2001 From: Kevin Cooper Date: Mon, 2 Feb 2015 12:53:36 -0700 Subject: [PATCH 50/64] Added electrum-env to use a virtualenv for python dependencies --- electrum-env | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 electrum-env diff --git a/electrum-env b/electrum-env new file mode 100755 index 000000000..f7debc8b7 --- /dev/null +++ b/electrum-env @@ -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 + pip install slowaes 'ecdsa>=0.9' pbkdf2 requests pyasn1 pyasn1-modules qrcode SocksiPy-branch protobuf tlslite +fi + +export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH + +./electrum + +deactivate From 059d381ba8cfed0b4c00cb9b4ec1ff13293de74c Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 08:01:01 +0100 Subject: [PATCH 51/64] backport padding fix to AES module --- lib/bitcoin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 779473081..65771b8e8 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -51,6 +51,8 @@ def strip_PKCS7_padding(s): raise ValueError("Invalid PKCS7 padding") return s[:-numpads] +# backport padding fix to AES module +aes.strip_PKCS7_padding = strip_PKCS7_padding def aes_encrypt_with_iv(key, iv, data): mode = aes.AESModeOfOperation.modeOfOperation["CBC"] From ca01e1d7be2e157534b1e179556f95723a1c75f1 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 12:20:03 +0100 Subject: [PATCH 52/64] handle 1.9.8 bug that created seed_v6 wallets --- lib/wallet.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 1cb685931..5e4e140e9 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1634,9 +1634,19 @@ class Wallet(object): 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]: - 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]: - 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) wallet_type = storage.get('wallet_type') From b6c1b22c3536e5e1a36d9d7219511c9ada3ba780 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 12:29:04 +0100 Subject: [PATCH 53/64] use sys.exit --- electrum | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/electrum b/electrum index 75e5e0a8c..5fb3c91fa 100755 --- a/electrum +++ b/electrum @@ -69,8 +69,7 @@ from google.protobuf import descriptor_pb2 try: 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() + sys.exit("cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa") # load local module as electrum From f52760ef4e5112d3e9d60b5223ad1971da073a81 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 12:51:27 +0100 Subject: [PATCH 54/64] add try.. except around version comparison --- gui/qt/version_getter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gui/qt/version_getter.py b/gui/qt/version_getter.py index 52d52fbb9..d37bda317 100644 --- a/gui/qt/version_getter.py +++ b/gui/qt/version_getter.py @@ -75,7 +75,10 @@ class UpdateLabel(QLabel): def compare_versions(self, version1, version2): def normalize(v): return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")] - return cmp(normalize(version1), normalize(version2)) + try: + return cmp(normalize(version1), normalize(version2)) + except: + return 0 def ignore_this_version(self): self.setText("") From 2f7b8ce86490729ba3d158f311f8245009d8beab Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 13:14:35 +0100 Subject: [PATCH 55/64] fix wallet.can_sign: use isinstance with BIP32 class --- lib/wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wallet.py b/lib/wallet.py index 5e4e140e9..4c0d6a7e1 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1048,7 +1048,7 @@ class Abstract_Wallet(object): addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex')) return self.is_mine(addr) elif x_pubkey[0:2] == 'ff': - if isinstance(self, OldWallet): return False + if not isinstance(self, BIP32_Wallet): return False xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey) return xpub in [ self.master_public_keys[k] for k in self.master_private_keys.keys() ] elif x_pubkey[0:2] == 'fe': From 6717460ee640dbfc11b8ee14be7b3ef80e19ad8d Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 14:20:35 +0100 Subject: [PATCH 56/64] version 2.0-beta2 --- lib/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/version.py b/lib/version.py index f7c23962c..c0f912aa7 100644 --- a/lib/version.py +++ b/lib/version.py @@ -1,4 +1,4 @@ -ELECTRUM_VERSION = "2.0-beta" # version of the client package +ELECTRUM_VERSION = "2.0-beta2" # version of the client package PROTOCOL_VERSION = '0.9' # protocol version requested NEW_SEED_VERSION = 11 # electrum versions >= 2.0 OLD_SEED_VERSION = 4 # electrum versions < 2.0 From 4d90e2e1f773c44c27632a786be95b62ae0ad894 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 15:13:01 +0100 Subject: [PATCH 57/64] add version number for aes requirement, because it's a prerelease --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0e8f1e008..c778e4505 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ setup( name="Electrum", version=version.ELECTRUM_VERSION, install_requires=[ - 'slowaes', + 'slowaes>=0.1a1', 'ecdsa>=0.9', 'pbkdf2', 'requests', From 22fdedf7a616409c9170df4098a228385fa52787 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 15:15:27 +0100 Subject: [PATCH 58/64] normalize version number --- lib/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/version.py b/lib/version.py index c0f912aa7..beaeb8d3f 100644 --- a/lib/version.py +++ b/lib/version.py @@ -1,4 +1,4 @@ -ELECTRUM_VERSION = "2.0-beta2" # version of the client package +ELECTRUM_VERSION = "2.0b2" # version of the client package PROTOCOL_VERSION = '0.9' # protocol version requested NEW_SEED_VERSION = 11 # electrum versions >= 2.0 OLD_SEED_VERSION = 4 # electrum versions < 2.0 From 58146407f7fc337c3614dc8d03eb8c6b4e1145e3 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 16:09:39 +0100 Subject: [PATCH 59/64] fix: gap_limit --- lib/account.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/account.py b/lib/account.py index 3e765d08d..9d90c1455 100644 --- a/lib/account.py +++ b/lib/account.py @@ -76,7 +76,7 @@ class Account(object): return None 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: addresses = self.get_addresses(for_change) if len(addresses) < limit: @@ -175,14 +175,11 @@ class ImportedAccount(Account): class OldAccount(Account): """ Privatekey(type,n) = Master_private_key + H(n|S|type) """ - gap_limit = 5 - gap_limit_for_change = 3 def __init__(self, v): Account.__init__(self, v) self.mpk = v['mpk'].decode('hex') - @classmethod def mpk_from_seed(klass, seed): curve = SECP256k1 @@ -274,8 +271,6 @@ class OldAccount(Account): class BIP32_Account(Account): - gap_limit = 20 - gap_limit_for_change = 3 def __init__(self, v): Account.__init__(self, v) From 036147db444af55ab83cffb895c41d1a4fe3a385 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 16:18:42 +0100 Subject: [PATCH 60/64] fix: change_gap_limit --- lib/wallet.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 4c0d6a7e1..a2b2693f7 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1142,17 +1142,15 @@ class Deterministic_Wallet(Abstract_Wallet): if value >= self.gap_limit: self.gap_limit = value self.storage.put('gap_limit', self.gap_limit, True) - #self.interface.poke('synchronizer') return True elif value >= self.min_acceptable_gap(): for key, account in self.accounts.items(): - addresses = account[0] + addresses = account.get_addresses(False) k = self.num_unused_trailing_addresses(addresses) n = len(addresses) - k + value - addresses = addresses[0:n] - self.accounts[key][0] = addresses - + account.receiving_pubkeys = account.receiving_pubkeys[0:n] + account.receiving_addresses = account.receiving_addresses[0:n] self.gap_limit = value self.storage.put('gap_limit', self.gap_limit, True) self.save_accounts() From 8982a463a4ceaecfd328c4f3751ca77b19b8cd17 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Feb 2015 16:26:00 +0100 Subject: [PATCH 61/64] allow change_gap_limit in bip32 wallets --- lib/wallet.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index a2b2693f7..c9c10aee7 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1264,12 +1264,12 @@ class Deterministic_Wallet(Abstract_Wallet): class BIP32_Wallet(Deterministic_Wallet): # abstract class, bip32 logic root_name = 'x/' - gap_limit = 20 def __init__(self, storage): Deterministic_Wallet.__init__(self, storage) self.master_public_keys = storage.get('master_public_keys', {}) self.master_private_keys = storage.get('master_private_keys', {}) + self.gap_limit = storage.get('gap_limit', 20) def is_watching_only(self): return not bool(self.master_private_keys) @@ -1539,7 +1539,6 @@ class Wallet_2of3(Wallet_2of2): class OldWallet(Deterministic_Wallet): wallet_type = 'old' - gap_limit = 5 def __init__(self, storage): Deterministic_Wallet.__init__(self, storage) From 55c88a1748eae2ff83c57b0461f79e2bb51521cc Mon Sep 17 00:00:00 2001 From: Kevin Cooper Date: Wed, 4 Feb 2015 02:19:10 -0700 Subject: [PATCH 62/64] Switched to using setup.py to install dependencies --- electrum-env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum-env b/electrum-env index f7debc8b7..0e2cc70f1 100755 --- a/electrum-env +++ b/electrum-env @@ -14,7 +14,7 @@ if [ -e ./env/bin/activate ]; then else virtualenv env source ./env/bin/activate - pip install slowaes 'ecdsa>=0.9' pbkdf2 requests pyasn1 pyasn1-modules qrcode SocksiPy-branch protobuf tlslite + python setup.py install fi export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH From f928c565c48b8c36233825607cf35ac4e946b1aa Mon Sep 17 00:00:00 2001 From: Kevin Cooper Date: Wed, 4 Feb 2015 03:47:16 -0700 Subject: [PATCH 63/64] fixed setup.py so it doesnt break when trying to install pyasn1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0e8f1e008..956742f65 100644 --- a/setup.py +++ b/setup.py @@ -69,8 +69,8 @@ setup( 'ecdsa>=0.9', 'pbkdf2', 'requests', - 'pyasn1', 'pyasn1-modules', + 'pyasn1', 'qrcode', 'SocksiPy-branch', 'protobuf', From 19d7a81d702baf8a42fd9a5d567036a7c07293ff Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 4 Feb 2015 12:05:46 +0100 Subject: [PATCH 64/64] restrict plugins to qt for now --- electrum | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/electrum b/electrum index 5fb3c91fa..0d5d7635e 100755 --- a/electrum +++ b/electrum @@ -222,7 +222,6 @@ if __name__ == '__main__': cmd = args[0] if cmd == 'gui': - init_plugins(config, is_bundle or is_local or is_android) gui_name = config.get('gui', 'classic') if gui_name in ['lite', 'classic']: gui_name = 'qt' @@ -233,6 +232,9 @@ if __name__ == '__main__': sys.exit() #sys.exit("Error: Unknown GUI: " + gui_name ) + if gui_name=='qt': + init_plugins(config, is_bundle or is_local or is_android) + # network interface if not options.offline: s = get_daemon(config, start_daemon=options.daemon)