merge upstream, fix setup.py conflict
This commit is contained in:
commit
ab6837485f
|
@ -13,3 +13,4 @@ gui/qt/icons_rc.py
|
|||
locale/
|
||||
.devlocaltmp/
|
||||
*_trial_temp
|
||||
packages
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
# 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
|
||||
- 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
|
||||
- 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 +35,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
|
||||
|
@ -42,8 +46,15 @@
|
|||
|
||||
* 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
|
||||
|
||||
* ECIES encrypt/decrypt methods, availabe in the GUI and using the
|
||||
command line:
|
||||
encrypt <pubkey> <message>
|
||||
decrypt <pubkey> <message>
|
||||
|
||||
|
||||
|
||||
# Release 1.9.8
|
||||
|
|
|
@ -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"
|
||||
|
|
@ -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 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:
|
||||
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__
|
||||
__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
|
||||
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'))
|
||||
|
@ -45,12 +81,11 @@ if __builtin__.use_local_modules:
|
|||
|
||||
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
|
||||
|
||||
|
||||
|
||||
# get password routine
|
||||
def prompt_password(prompt, confirm=True):
|
||||
import getpass
|
||||
|
@ -170,10 +205,12 @@ 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")
|
||||
|
||||
if len(args) == 0:
|
||||
url = None
|
||||
|
@ -185,7 +222,6 @@ if __name__ == '__main__':
|
|||
cmd = args[0]
|
||||
|
||||
if cmd == 'gui':
|
||||
init_plugins(config)
|
||||
gui_name = config.get('gui', 'classic')
|
||||
if gui_name in ['lite', 'classic']:
|
||||
gui_name = 'qt'
|
||||
|
@ -196,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)
|
||||
|
|
|
@ -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.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)
|
||||
|
@ -142,10 +142,8 @@ 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)
|
||||
entries = []
|
||||
for i in range(n):
|
||||
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):
|
||||
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_()
|
||||
|
|
|
@ -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()
|
||||
|
@ -254,17 +259,42 @@ class ElectrumWindow(QMainWindow):
|
|||
filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
|
||||
if not filename:
|
||||
return
|
||||
|
||||
storage = WalletStorage({'wallet_path': filename})
|
||||
if not storage.file_exists:
|
||||
self.show_message("file not found "+ 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)
|
||||
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
|
||||
# close current wallet
|
||||
self.close_wallet()
|
||||
# load new wallet
|
||||
wallet = Wallet(storage)
|
||||
wallet.start_threads(self.network)
|
||||
# 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)
|
||||
|
||||
|
||||
|
@ -308,6 +338,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:
|
||||
|
@ -371,7 +403,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)
|
||||
|
@ -1278,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)
|
||||
|
@ -2507,7 +2539,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)
|
||||
|
|
|
@ -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()
|
||||
|
@ -143,19 +144,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"))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"))
|
||||
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.") + "<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 + " " \
|
||||
+ _("This seed will allow you to recover your wallet in case of computer failure.") + "<br/>" \
|
||||
+ 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.") + "<br/>" \
|
||||
+ "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
|
||||
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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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:
|
||||
|
@ -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("")
|
||||
|
|
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
|
||||
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -58,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"]
|
||||
|
@ -401,12 +396,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
|
||||
|
|
|
@ -38,6 +38,30 @@ 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):
|
||||
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 +92,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 +295,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)
|
||||
|
|
|
@ -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
|
||||
|
@ -178,7 +186,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 +233,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)
|
||||
server_str = ':'.join([ host, port, protocol ])
|
||||
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)
|
||||
self.config.set_key("server", server_str, 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:
|
||||
|
@ -246,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):
|
||||
|
@ -358,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)
|
||||
|
|
|
@ -27,18 +27,12 @@ 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'")
|
||||
|
||||
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 util
|
||||
|
@ -116,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)
|
||||
|
@ -131,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')
|
||||
|
|
|
@ -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)
|
||||
|
@ -40,21 +40,26 @@ def hook(func):
|
|||
|
||||
|
||||
def run_hook(name, *args):
|
||||
SPECIAL_HOOKS = ['get_wizard_action']
|
||||
results = []
|
||||
f_list = hooks.get(name,[])
|
||||
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 name == 'init_qt':
|
||||
gui = args[0]
|
||||
p.window = gui.main_window
|
||||
if name in SPECIAL_HOOKS or 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 +97,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
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
11
lib/util.py
11
lib/util.py
|
@ -66,7 +66,16 @@ def data_dir():
|
|||
if __builtin__.use_local_modules:
|
||||
return local_data_dir()
|
||||
else:
|
||||
return appdata_dir()
|
||||
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:
|
||||
return appdata_dir()
|
||||
|
||||
def usr_share_dir():
|
||||
return os.path.join(sys.prefix, "share")
|
||||
|
|
|
@ -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
|
||||
NEW_SEED_VERSION = 11 # 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'))
|
||||
return self.is_mine(addr)
|
||||
elif x_pubkey[0:2] == 'ff':
|
||||
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':
|
||||
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':
|
||||
|
@ -1140,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()
|
||||
|
@ -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)
|
||||
|
@ -1632,12 +1631,21 @@ 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)
|
||||
|
||||
run_hook('add_wallet_types', wallet_types)
|
||||
wallet_type = storage.get('wallet_type')
|
||||
if wallet_type:
|
||||
for cat, t, name, c in wallet_types:
|
||||
|
|
18
lib/x509.py
18
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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
@ -45,19 +45,21 @@ 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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
@ -113,6 +108,9 @@ class Plugin(BasePlugin):
|
|||
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():
|
||||
|
|
|
@ -49,17 +49,20 @@ 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
|
||||
|
||||
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 +73,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)
|
||||
|
@ -93,28 +94,34 @@ 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
|
||||
def load_wallet(self, wallet):
|
||||
self.wallet = wallet
|
||||
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 +168,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 +180,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 +198,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 +228,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)
|
||||
|
|
|
@ -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>"
|
||||
|
||||
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
|
||||
|
@ -344,15 +340,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):
|
||||
|
@ -481,6 +475,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 <= 100000
|
||||
if tx.input_value() < price:
|
||||
print_error("not charging for this tx")
|
||||
return 0
|
||||
|
|
|
@ -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"])),
|
||||
|
|
Loading…
Reference in New Issue