merge upstream, fix setup.py conflict
This commit is contained in:
commit
ab6837485f
|
@ -13,3 +13,4 @@ gui/qt/icons_rc.py
|
||||||
locale/
|
locale/
|
||||||
.devlocaltmp/
|
.devlocaltmp/
|
||||||
*_trial_temp
|
*_trial_temp
|
||||||
|
packages
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
55
electrum
55
electrum
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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_()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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 |
BIN
icons/unpaid.png
BIN
icons/unpaid.png
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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':
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
12
lib/x509.py
12
lib/x509.py
|
@ -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
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"])),
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -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'
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue