├── lib ├── tests │ ├── __init__.py │ ├── test_interface.py │ ├── test_util.py │ └── test_wallet.py ├── version.py ├── __init__.py ├── qrscanner.py ├── contacts.py ├── i18n.py ├── paymentrequest.proto ├── msqr.py ├── www │ └── index.html ├── verifier.py ├── asn1tinydecoder.py ├── websockets.py ├── plugins.py ├── mnemonic.py └── simple_config.py ├── gui ├── qt │ ├── themes │ │ ├── dark │ │ │ ├── name.cfg │ │ │ └── style.css │ │ ├── sahara │ │ │ ├── name.cfg │ │ │ └── style.css │ │ ├── cleanlook │ │ │ ├── name.cfg │ │ │ └── style.css │ │ └── README │ ├── history_widget_lite.py │ ├── qrtextedit.py │ ├── address_dialog.py │ ├── qrwindow.py │ ├── receiving_widget.py │ ├── seed_dialog.py │ ├── amountedit.py │ ├── history_widget.py │ ├── qrcodewidget.py │ ├── version_getter.py │ └── password_dialog.py ├── __init__.py └── jsonrpc.py ├── windows-pack.bat ├── electrum.icns ├── icons ├── key.png ├── copy.png ├── file.png ├── lock.png ├── seal.png ├── seed.png ├── zoom.png ├── clock1.png ├── clock2.png ├── clock3.png ├── clock4.png ├── clock5.png ├── electrum.ico ├── expired.png ├── hot_seed.png ├── network.png ├── qrcode.png ├── speaker.png ├── unlock.png ├── unpaid.png ├── cold_seed.png ├── confirmed.png ├── microphone.png ├── switchgui.png ├── electrum-xvg.png ├── preferences.png ├── trustedcoin.png ├── unconfirmed.png ├── dark_background.png ├── status_lagging.png ├── status_waiting.png ├── electrum_dark_icon.png ├── status_connected.png ├── electrum_light_icon.png └── status_disconnected.png ├── electrumlogo.png ├── .travis.yml ├── tox.ini ├── contrib ├── make_commands_list ├── build-wine │ ├── archive.patch │ ├── README │ ├── build-electrum.sh │ ├── build-electrum-git.sh │ ├── deterministic.spec │ ├── prepare-wine.sh │ └── electrum.nsi ├── make_android ├── make_download ├── make_packages └── make_locale ├── scripts ├── estimate_fee ├── get_history ├── txradar ├── merchant │ ├── merchant.conf.template │ └── merchant.readme ├── servers ├── block_headers ├── watch_address ├── bip70 ├── peers └── util.py ├── .gitignore ├── app.fil ├── MANIFEST.in ├── AUTHORS ├── electrum-xvg.desktop ├── electrum-xvg.conf.sample ├── Info.plist ├── electrum-env ├── Dockerfile ├── cx_setup.py ├── icons.qrc ├── README.rst ├── plugins ├── README ├── virtualkeyboard.py ├── __init__.py ├── greenaddress_instant.py ├── plot.py ├── audio_modem.py ├── cosigner_pool.py └── labels.py ├── setup.py ├── windows.spec ├── docs ├── cold_storage ├── android.html └── console.html ├── setup-release.py ├── README.md └── pubkeys └── ThomasV.asc /lib/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/qt/themes/dark/name.cfg: -------------------------------------------------------------------------------- 1 | Dark 2 | -------------------------------------------------------------------------------- /gui/qt/themes/sahara/name.cfg: -------------------------------------------------------------------------------- 1 | Sahara 2 | -------------------------------------------------------------------------------- /gui/qt/themes/cleanlook/name.cfg: -------------------------------------------------------------------------------- 1 | Cleanlook 2 | -------------------------------------------------------------------------------- /windows-pack.bat: -------------------------------------------------------------------------------- 1 | pyinstaller --noconfirm --ascii -w windows.spec 2 | pause -------------------------------------------------------------------------------- /electrum.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/electrum.icns -------------------------------------------------------------------------------- /icons/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/key.png -------------------------------------------------------------------------------- /icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/copy.png -------------------------------------------------------------------------------- /icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/file.png -------------------------------------------------------------------------------- /icons/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/lock.png -------------------------------------------------------------------------------- /icons/seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/seal.png -------------------------------------------------------------------------------- /icons/seed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/seed.png -------------------------------------------------------------------------------- /icons/zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/zoom.png -------------------------------------------------------------------------------- /electrumlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/electrumlogo.png -------------------------------------------------------------------------------- /icons/clock1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/clock1.png -------------------------------------------------------------------------------- /icons/clock2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/clock2.png -------------------------------------------------------------------------------- /icons/clock3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/clock3.png -------------------------------------------------------------------------------- /icons/clock4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/clock4.png -------------------------------------------------------------------------------- /icons/clock5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/clock5.png -------------------------------------------------------------------------------- /icons/electrum.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/electrum.ico -------------------------------------------------------------------------------- /icons/expired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/expired.png -------------------------------------------------------------------------------- /icons/hot_seed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/hot_seed.png -------------------------------------------------------------------------------- /icons/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/network.png -------------------------------------------------------------------------------- /icons/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/qrcode.png -------------------------------------------------------------------------------- /icons/speaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/speaker.png -------------------------------------------------------------------------------- /icons/unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/unlock.png -------------------------------------------------------------------------------- /icons/unpaid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/unpaid.png -------------------------------------------------------------------------------- /icons/cold_seed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/cold_seed.png -------------------------------------------------------------------------------- /icons/confirmed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/confirmed.png -------------------------------------------------------------------------------- /icons/microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/microphone.png -------------------------------------------------------------------------------- /icons/switchgui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/switchgui.png -------------------------------------------------------------------------------- /icons/electrum-xvg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/electrum-xvg.png -------------------------------------------------------------------------------- /icons/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/preferences.png -------------------------------------------------------------------------------- /icons/trustedcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/trustedcoin.png -------------------------------------------------------------------------------- /icons/unconfirmed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/unconfirmed.png -------------------------------------------------------------------------------- /icons/dark_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/dark_background.png -------------------------------------------------------------------------------- /icons/status_lagging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/status_lagging.png -------------------------------------------------------------------------------- /icons/status_waiting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/status_waiting.png -------------------------------------------------------------------------------- /icons/electrum_dark_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/electrum_dark_icon.png -------------------------------------------------------------------------------- /icons/status_connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/status_connected.png -------------------------------------------------------------------------------- /icons/electrum_light_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/electrum_light_icon.png -------------------------------------------------------------------------------- /icons/status_disconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vergecurrency/electrum-xvg/HEAD/icons/status_disconnected.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - "2.7" 5 | install: 6 | - pip install tox 7 | script: 8 | - tox 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27 3 | 4 | [testenv] 5 | deps= 6 | pytest 7 | coverage 8 | commands= 9 | coverage run --source=lib -m py.test -v 10 | coverage report 11 | -------------------------------------------------------------------------------- /contrib/make_commands_list: -------------------------------------------------------------------------------- 1 | cat lib/commands.py|grep register_command|sed "s/register_command(\(.*\))/\1/" |sed "s/'//g"| awk -F, '{print "|-\n|" $1 "|| " $7 "|| " $8 "|| " $4 "|| " $5 "|| " $6 }' 2 | -------------------------------------------------------------------------------- /scripts/estimate_fee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import util, json 3 | peers = util.get_peers() 4 | results = util.send_request(peers, {'method':'blockchain.estimatefee','params':[1]}) 5 | print json.dumps(results, indent=4) 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- 1 | # To create a new GUI, please add its code to this directory. 2 | # Two objects must be passed to the ElectrumGui: config and network 3 | # The Wallet object is instanciated by the GUI 4 | 5 | # Notifications about network events are sent to the GUI by using network.register_callback() 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ####-*.patch 2 | gui/icons_rc.py 3 | lib/icons_rc.py 4 | *.pyc 5 | *.swp 6 | build/ 7 | dist/ 8 | *.egg/ 9 | /electrum.py 10 | contrib/pyinstaller/ 11 | Electrum.egg-info/ 12 | gui/qt/icons_rc.py 13 | locale/ 14 | .devlocaltmp/ 15 | *_trial_temp 16 | packages 17 | env/ 18 | .tox/ 19 | *.egg-info/ -------------------------------------------------------------------------------- /contrib/build-wine/archive.patch: -------------------------------------------------------------------------------- 1 | 252a253,255 2 | > class NoZlib: 3 | > def decompress(self, data): 4 | > return data 5 | 253a257,259 6 | > def compress(self, data, lvl): 7 | > return data 8 | > 9 | 316c322 10 | < zlib = DummyZlib() 11 | --- 12 | > zlib = NoZlib() 13 | -------------------------------------------------------------------------------- /app.fil: -------------------------------------------------------------------------------- 1 | gui/gtk.py 2 | gui/qt/main_window.py 3 | gui/qt/lite_window.py 4 | gui/qt/history_widget.py 5 | gui/qt/installwizard.py 6 | gui/qt/network_dialog.py 7 | gui/qt/password_dialog.py 8 | gui/qt/util.py 9 | gui/qt/receiving_widget.py 10 | gui/qt/seed_dialog.py 11 | gui/qt/transaction_dialog.py 12 | gui/qt/version_getter.py 13 | plugins/labels.py 14 | plugins/virtualkeyboard.py 15 | -------------------------------------------------------------------------------- /scripts/get_history: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | from electrum_xvg import NetworkProxy, print_json 5 | 6 | try: 7 | addr = sys.argv[1] 8 | except Exception: 9 | print "usage: get_history " 10 | sys.exit(1) 11 | 12 | n = NetworkProxy() 13 | n.start(start_daemon=True) 14 | h = n.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0] 15 | print_json(h) 16 | 17 | -------------------------------------------------------------------------------- /lib/version.py: -------------------------------------------------------------------------------- 1 | ELECTRUM_VERSION = "2.4.2" # version of the client package 2 | PROTOCOL_VERSION = '0.10' # protocol version requested 3 | NEW_SEED_VERSION = 11 # electrum versions >= 2.0 4 | OLD_SEED_VERSION = 4 # electrum versions < 2.0 5 | 6 | 7 | # The hash of the mnemonic seed must begin with this 8 | SEED_PREFIX = '01' # Electrum standard wallet 9 | SEED_PREFIX_2FA = '101' # extended seed for two-factor authentication 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README LICENCE RELEASE-NOTES AUTHORS 2 | include electrum-xvg.conf.sample 3 | include electrum-xvg.desktop 4 | include *.py 5 | include electrum-xvg 6 | recursive-include lib *.py 7 | recursive-include gui *.py 8 | recursive-include plugins *.py 9 | recursive-include packages *.py 10 | recursive-include packages cacert.pem 11 | include app.fil 12 | include icons.qrc 13 | recursive-include icons * 14 | recursive-include scripts * 15 | recursive-include docs * 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | ThomasV - Creator and maintainer. 2 | Animazing / Tachikoma - Styled the new GUI. Mac version. 3 | Azelphur - GUI stuff. 4 | Coblee - Alternate coin support and py2app support. 5 | Deafboy - Ubuntu packages. 6 | EagleTM - Bugfixes. 7 | ErebusBat - Mac distribution. 8 | Genjix - Porting pro-mode functionality to lite-gui and worked on server 9 | Slush - Work on the server. Designed the original Stratum spec. 10 | Julian Toash (Tuxavant) - Various fixes to the client. 11 | rdymac - Website and translations. 12 | -------------------------------------------------------------------------------- /scripts/txradar: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import util, sys 3 | try: 4 | tx = sys.argv[1] 5 | except: 6 | print "usage: txradar txid" 7 | sys.exit(1) 8 | 9 | peers = util.get_peers() 10 | results = util.send_request(peers, {'method':'blockchain.transaction.get','params':[tx]}) 11 | 12 | r1 = [] 13 | r2 = [] 14 | 15 | for k, v in results.items(): 16 | (r1 if v else r2).append(k) 17 | 18 | print "Received %d answers"%len(results) 19 | print "Propagation rate: %.1f percent" % (len(r1) *100./(len(r1)+ len(r2))) 20 | 21 | -------------------------------------------------------------------------------- /electrum-xvg.desktop: -------------------------------------------------------------------------------- 1 | # If you want electrum to appear in a linux app launcher ("start menu"), install this by doing: 2 | # sudo desktop-file-install electrum-xvg.desktop 3 | 4 | [Desktop Entry] 5 | Comment=Lightweight Verge Client 6 | Exec=electrum-xvg %u 7 | GenericName[en_US]=Electrum-XVG 8 | GenericName=Electrum-XVG 9 | Icon=electrum-xvg 10 | Name[en_US]=Electrum Verge Wallet 11 | Name=Electrum Verge Wallet 12 | Categories=Network; 13 | StartupNotify=false 14 | Terminal=false 15 | Type=Application 16 | MimeType=x-scheme-handler/VERGE; 17 | 18 | -------------------------------------------------------------------------------- /scripts/merchant/merchant.conf.template: -------------------------------------------------------------------------------- 1 | [main] 2 | host = hostname of the machine where you run this program 3 | port = choose a port number 4 | password = choose a password 5 | 6 | [sqlite3] 7 | database = database filename 8 | 9 | [electrum] 10 | xpub = the master public key of your wallet 11 | wallet_path = path where the script will save the wallet 12 | 13 | [callback] 14 | received = URL where we POST json data when payment has been received 15 | expired = URL where we POST json data if payment has expired 16 | password = password sent in the json data, for authentication 17 | -------------------------------------------------------------------------------- /electrum-xvg.conf.sample: -------------------------------------------------------------------------------- 1 | # Configuration file for the verge electrum client 2 | # Settings defined here are shared across wallets 3 | # 4 | # copy this file to /etc/electrum-xvg.conf if you want read-only settings 5 | # copy it into your ~/.electrum-xvg/ directory if you want global settings 6 | # that can be rewritten by the client 7 | 8 | [client] 9 | server = electrum-verge.xyz:50002:t 10 | proxy = None 11 | gap_limit = 5 12 | # booleans use python syntax 13 | use_change = True 14 | gui = qt 15 | num_zeros = 2 16 | # default transaction fee is in Satoshis 17 | fee = 1000000 18 | winpos-qt = [799, 226, 877, 435] 19 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleURLTypes 6 | 7 | 8 | CFBundleURLName 9 | VERGE 10 | CFBundleURLSchemes 11 | 12 | VERGE 13 | 14 | 15 | 16 | LSArchitecturePriority 17 | 18 | x86_64 19 | i386 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /gui/qt/themes/README: -------------------------------------------------------------------------------- 1 | To add a new theme, simply create a new directory in the electrum data directory (either ./data or your system wide local data). 2 | 3 | Inside your directory create a file called name.cfg with the name of that theme. 4 | 5 | Create another file called style.css - this will be your CSS for the theme (see other themes for reference). 6 | 7 | Documentation on Qt's stylesheets (used by Electrum): 8 | 9 | Overview: http://qt-project.org/doc/qt-4.8/stylesheet.html 10 | Examples: http://qt-project.org/doc/qt-4.8/stylesheet-examples.html 11 | Reference manual: http://doc.qt.nokia.com/4.7-snapshot/stylesheet-reference.html 12 | 13 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | from version import ELECTRUM_VERSION 2 | from util import format_satoshis, print_msg, print_json, print_error, set_verbosity 3 | from wallet import WalletSynchronizer, WalletStorage 4 | from wallet import Wallet, Imported_Wallet 5 | from network import Network, DEFAULT_SERVERS, DEFAULT_PORTS, pick_random_server 6 | from interface import Interface 7 | from simple_config import SimpleConfig, get_config, set_config 8 | import bitcoin 9 | import account 10 | import transaction 11 | from transaction import Transaction 12 | from plugins import BasePlugin 13 | from commands import Commands, known_commands 14 | from daemon import NetworkServer 15 | from network_proxy import NetworkProxy 16 | -------------------------------------------------------------------------------- /gui/qt/themes/dark/style.css: -------------------------------------------------------------------------------- 1 | #main_window 2 | { 3 | background-image: url(:/icons/dark_background.png); 4 | } 5 | 6 | #address_input[readOnly=true], #amount_input[readOnly=true] 7 | { 8 | font: italic; 9 | color: gray; 10 | } 11 | #address_input[readOnly=false], #amount_input[readOnly=false] 12 | { 13 | font: normal; 14 | color: black; 15 | } 16 | 17 | #valid_address::indicator 18 | { 19 | width: 24px; 20 | height: 24px; 21 | } 22 | #valid_address::indicator:checked 23 | { 24 | image: url(confirmed.png); 25 | } 26 | #valid_address::indicator:unchecked 27 | { 28 | image: url(unconfirmed.png); 29 | } 30 | 31 | #balance_label 32 | { 33 | color: white; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /scripts/servers: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from electrum_xvg import SimpleConfig, set_verbosity 4 | from electrum_xvg.network import filter_protocol 5 | import time, Queue 6 | from collections import defaultdict 7 | 8 | import util, json 9 | set_verbosity(False) 10 | 11 | config = SimpleConfig() 12 | servers = filter_protocol(protocol = 't') 13 | results = util.send_request(servers, {'method':'blockchain.headers.subscribe', 'params':[]}) 14 | 15 | d = defaultdict(int) 16 | 17 | for k, r in results.items(): 18 | blocks = r.get('block_height') 19 | d[blocks] += 1 20 | 21 | 22 | 23 | for k, v in results.items(): 24 | print k, v.get('block_height') 25 | 26 | v = d.values() 27 | numblocks = d.keys()[v.index(max(v))] 28 | print "blocks:",numblocks 29 | -------------------------------------------------------------------------------- /electrum-env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script creates a virtualenv named 'env' and installs all 4 | # python dependencies before activating the env and running Electrum. 5 | # If 'env' already exists, it is activated and Electrum is started 6 | # without any installations. Additionally, the PYTHONPATH environment 7 | # variable is set properly before running Electrum. 8 | # 9 | # python-qt and its dependencies will still need to be installed with 10 | # your package manager. 11 | 12 | if [ -e ./env/bin/activate ]; then 13 | source ./env/bin/activate 14 | else 15 | virtualenv env 16 | source ./env/bin/activate 17 | python setup.py install 18 | fi 19 | 20 | export PYTHONPATH="/usr/local/lib/python2.7/site-packages:$PYTHONPATH" 21 | 22 | ./electrum-xvg "$@" 23 | 24 | deactivate 25 | -------------------------------------------------------------------------------- /scripts/block_headers: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # A simple script that connects to a server and displays block headers 4 | 5 | import time 6 | import electrum_xvg as electrum 7 | 8 | # start network 9 | c = electrum.SimpleConfig() 10 | s = electrum.daemon.get_daemon(c,True) 11 | network = electrum.NetworkProxy(s,c) 12 | network.start() 13 | 14 | # wait until connected 15 | while network.is_connecting(): 16 | time.sleep(0.1) 17 | 18 | if not network.is_connected(): 19 | print_msg("daemon is not connected") 20 | sys.exit(1) 21 | 22 | # 2. send the subscription 23 | callback = lambda response: electrum.print_json(response.get('result')) 24 | network.send([('blockchain.headers.subscribe',[])], callback) 25 | 26 | # 3. wait for results 27 | while network.is_connected(): 28 | time.sleep(1) 29 | 30 | -------------------------------------------------------------------------------- /contrib/build-wine/README: -------------------------------------------------------------------------------- 1 | These scripts can be used for cross-compilation of Windows Electrum executables from Linux/Wine. 2 | 3 | Usage: 4 | 1. Copy content of this directory to /build-wine. 5 | 2. Install Wine (version 1.4 or 1.5+ works fine, 1.4.1 has bug). 6 | 3. Run "./prepare-wine.sh", it will download all dependencies. When you'll be asked, always leave default settings and press "Next >". 7 | 4. By running "./build-electrum.sh", sources will be packed into three separate versions to dist/ directory: 8 | * Standalone compressed executable is "dist/electrum.exe" 9 | * Uncompressed binaries are in "dist/electrum". They're useful for comparsion with other builds. 10 | * NSIS-based installer of Electrum is "electrum-setup.exe" 11 | 7. Everytime you want to rebuild new version of Electrum just change the path to ZIP file in "build-electrum.sh" and re-run the script. 12 | 13 | -------------------------------------------------------------------------------- /scripts/watch_address: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import time 5 | import electrum_xvg as electrum 6 | 7 | try: 8 | addr = sys.argv[1] 9 | except Exception: 10 | print "usage: watch_address " 11 | sys.exit(1) 12 | 13 | # start network 14 | c = electrum.SimpleConfig() 15 | s = electrum.daemon.get_daemon(c,True) 16 | network = electrum.NetworkProxy(s,c) 17 | network.start() 18 | 19 | # wait until connected 20 | while network.is_connecting(): 21 | time.sleep(0.1) 22 | 23 | if not network.is_connected(): 24 | print_msg("daemon is not connected") 25 | sys.exit(1) 26 | 27 | # 2. send the subscription 28 | callback = lambda response: electrum.print_json(response.get('result')) 29 | network.send([('blockchain.address.subscribe',[addr])], callback) 30 | 31 | # 3. wait for results 32 | while network.is_connected(): 33 | time.sleep(1) 34 | 35 | -------------------------------------------------------------------------------- /gui/qt/history_widget_lite.py: -------------------------------------------------------------------------------- 1 | from PyQt4.QtGui import * 2 | from electrum_xvg.i18n import _ 3 | 4 | class HistoryWidget(QTreeWidget): 5 | 6 | def __init__(self, parent=None): 7 | QTreeWidget.__init__(self, parent) 8 | self.setColumnCount(2) 9 | self.setHeaderLabels([_("Amount"), _("To / From"), _("When")]) 10 | self.setIndentation(0) 11 | 12 | def empty(self): 13 | self.clear() 14 | 15 | def append(self, address, amount, date): 16 | if address is None: 17 | address = _("Unknown") 18 | if amount is None: 19 | amount = _("Unknown") 20 | if date is None: 21 | date = _("Unknown") 22 | item = QTreeWidgetItem([amount, address, date]) 23 | if amount.find('-') != -1: 24 | item.setForeground(0, QBrush(QColor("#BC1E1E"))) 25 | self.insertTopLevelItem(0, item) 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile assumes that pull request #10 (https://github.com/vergecurrency/electrum-xvg/pull/10) has been merged 2 | # if not, then change repo on line #17 to https://github.com/tomzuu/electrum-xvg.git 3 | 4 | FROM debian:sid 5 | 6 | LABEL maintainer "tomzuu " 7 | 8 | RUN apt-get update && apt-get install -y \ 9 | git \ 10 | pyqt4-dev-tools \ 11 | python-pip \ 12 | python-dev \ 13 | python-slowaes \ 14 | && pip install pyasn1 pyasn1-modules pbkdf2 tlslite qrcode \ 15 | && groupadd -g 1000 user && useradd -m -u 1000 -g user user \ 16 | && cd /home/user \ 17 | && git clone https://github.com/vergecurrency/electrum-xvg.git && cd electrum-xvg \ 18 | && pyrcc4 icons.qrc -o gui/qt/icons_rc.py \ 19 | && chmod +x electrum-xvg \ 20 | && python setup.py install \ 21 | && chown -R user:user /home/user \ 22 | && rm -rf /var/lib/apt/lists/* \ 23 | && apt-get purge -y --auto-remove git 24 | 25 | USER user 26 | 27 | ENTRYPOINT [ "/usr/local/bin/electrum-xvg" ] 28 | -------------------------------------------------------------------------------- /lib/tests/test_interface.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from lib import interface 4 | 5 | 6 | class TestInterface(unittest.TestCase): 7 | 8 | def test_match_host_name(self): 9 | self.assertTrue(interface._match_hostname('asd.fgh.com', 'asd.fgh.com')) 10 | self.assertFalse(interface._match_hostname('asd.fgh.com', 'asd.zxc.com')) 11 | self.assertTrue(interface._match_hostname('asd.fgh.com', '*.fgh.com')) 12 | self.assertFalse(interface._match_hostname('asd.fgh.com', '*fgh.com')) 13 | self.assertFalse(interface._match_hostname('asd.fgh.com', '*.zxc.com')) 14 | 15 | def test_check_host_name(self): 16 | self.assertFalse(interface.check_host_name(None, None)) 17 | self.assertFalse(interface.check_host_name( 18 | peercert={'subjectAltName': []}, name='')) 19 | self.assertTrue(interface.check_host_name( 20 | peercert={'subjectAltName': [('DNS', '*.bar.com')]}, 21 | name='foo.bar.com')) 22 | self.assertTrue(interface.check_host_name( 23 | peercert={'subject': [('commonName', '*.bar.com')]}, 24 | name='foo.bar.com')) 25 | -------------------------------------------------------------------------------- /scripts/bip70: -------------------------------------------------------------------------------- 1 | # create a BIP70 payment request signed with a certificate 2 | 3 | import tlslite 4 | import time 5 | import hashlib 6 | 7 | from electrum_xvg.transaction import Transaction 8 | from electrum_xvg import bitcoin 9 | from electrum_xvg import x509 10 | from electrum_xvg import paymentrequest 11 | from electrum_xvg import paymentrequest_pb2 as pb2 12 | 13 | chain_file = 'mychain.pem' 14 | cert_file = 'mycert.pem' 15 | amount = 1000000 16 | address = "DPNC2H2pYUCSebQ992GyeRTRuWw3hCTBwD" 17 | memo = "blah" 18 | out_file = "payreq" 19 | 20 | 21 | with open(chain_file, 'r') as f: 22 | chain = tlslite.X509CertChain() 23 | chain.parsePemList(f.read()) 24 | 25 | certificates = pb2.X509Certificates() 26 | certificates.certificate.extend(map(lambda x: str(x.bytes), chain.x509List)) 27 | 28 | with open(cert_file, 'r') as f: 29 | rsakey = tlslite.utils.python_rsakey.Python_RSAKey.parsePEM(f.read()) 30 | 31 | script = Transaction.pay_script('address', address).decode('hex') 32 | 33 | pr_string = paymentrequest.make_payment_request(amount, script, memo, rsakey) 34 | 35 | with open(out_file,'wb') as f: 36 | f.write(pr_string) 37 | 38 | print "Payment request was written to file '%s'"%out_file 39 | -------------------------------------------------------------------------------- /cx_setup.py: -------------------------------------------------------------------------------- 1 | from cx_Freeze import setup, Executable 2 | import os,sys 3 | 4 | # Dependencies are automatically detected, but it might need 5 | # setup file for cx_freeze to build Windows Executable 6 | # create executable using command `python cx_setup.py build` 7 | script_dir = os.path.dirname(os.path.realpath(__file__)) 8 | 9 | is_bundle = getattr(sys, 'frozen', False) 10 | is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "setup-release.py")) 11 | is_android = 'ANDROID_DATA' in os.environ 12 | 13 | if is_bundle or is_local or is_android: 14 | import imp 15 | imp.load_module('electrum_xvg', *imp.find_module('lib')) 16 | 17 | from electrum_xvg.version import ELECTRUM_VERSION 18 | buildOptions = dict(packages = ["idna","shutil","ltc_scrypt","argparse","dns","gui","dbhash","dumbdbm"], excludes = [], include_files=["lib"]) 19 | 20 | base = 'Win32GUI' if sys.platform=='win32' else None 21 | 22 | executables = [ 23 | Executable('electrum-xvg', base=base, icon = "./icons/electrum.ico") 24 | ] 25 | 26 | setup( 27 | name='Electrum XVG', 28 | version=ELECTRUM_VERSION, 29 | description = 'Electrum Wallet for Verge (XVG)', 30 | options = dict(build_exe = buildOptions), 31 | executables = executables, 32 | ) 33 | -------------------------------------------------------------------------------- /scripts/peers: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import util, json 4 | from collections import defaultdict 5 | 6 | 7 | def analyze(results): 8 | out = {} 9 | dd = {} 10 | for k, v in results.items(): 11 | height = v.get('block_height') 12 | merkle = v.get('merkle_root') 13 | utxo = v.get('utxo_root') 14 | d = dd.get(merkle, defaultdict(int)) 15 | d[utxo] += 1 16 | dd[merkle] = d 17 | refs = {} 18 | for merkle, d in dd.items(): 19 | v = d.values() 20 | m = max(v) 21 | ref = d.keys()[v.index(m)] 22 | refs[merkle] = ref, m 23 | for k, v in results.items(): 24 | height = v.get('block_height') 25 | merkle = v.get('merkle_root') 26 | utxo = v.get('utxo_root') 27 | ref_utxo, num = refs.get(merkle) 28 | if ref_utxo != utxo and num > 1: 29 | out[k] = height, merkle, utxo 30 | return out 31 | 32 | 33 | peers = util.get_peers() 34 | results = util.send_request(peers, {'method':'blockchain.headers.subscribe','params':[]}) 35 | 36 | errors = analyze(results).keys() 37 | 38 | for n,v in sorted(results.items(), key=lambda x:x[1].get('block_height')): 39 | print "%40s"%n, v.get('block_height'), v.get('utxo_root'), "error" if n in errors else "ok" 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /gui/qt/themes/cleanlook/style.css: -------------------------------------------------------------------------------- 1 | #main_window 2 | { 3 | background: qlineargradient(x1: 0, y1: 0, x2:0,y2:1, stop: 0 white , stop: 1 #E8E8E8); 4 | } 5 | 6 | MiniWindow QPushButton { 7 | color: #777; 8 | border: 1px solid #CCC; 9 | border-radius: 0px; 10 | background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 11 | stop: 0 white, stop: 1 #E6E6E6); 12 | min-height: 30px; 13 | min-width: 30px; 14 | } 15 | 16 | #send_button{ 17 | color: #FFF; 18 | border: 1px solid #3786E6; 19 | border-radius: 4px; 20 | background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 21 | stop: 0 #72B2F8, stop: 1 #3484E6); 22 | padding: 2px; 23 | width: 20px; 24 | } 25 | 26 | #send_button:disabled{ 27 | color: #D3E8FE; 28 | border: 1px solid #6DAEF7; 29 | border-radius: 4px; 30 | background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 31 | stop: 0 #A5CFFA, stop: 1 #72B2F8); 32 | } 33 | #address_input, #amount_input, #label_input 34 | { 35 | color: #000; 36 | padding: 5px; 37 | border-radius: 5px; 38 | min-height: 23px; 39 | border: 1px solid #AAA9A9; 40 | width: 200px; 41 | } 42 | 43 | #address_input[isValid=true] 44 | { 45 | color: #4D9948; 46 | } 47 | 48 | #address_input[isValid=false] 49 | { 50 | color: #CE4141; 51 | } 52 | 53 | #balance_label 54 | { 55 | color: #333; 56 | } 57 | 58 | #history 59 | { 60 | color: #888; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /scripts/merchant/merchant.readme: -------------------------------------------------------------------------------- 1 | merchant.py is a daemon that manages payments for a web server. It 2 | creates Bitcoin addresses using a master public key (so you do not 3 | leave your private keys on the server), detects when payments are 4 | received and notifies your web application. 5 | 6 | The workflow goes like this: 7 | 8 | - the server sends a request to the daemon via POST. the request 9 | contains an amount to be paid, a number of confirmations, and an 10 | expiration period in hours. 11 | 12 | - the daemon answers with a Bitcoin address, where the customer needs 13 | to send the coins. 14 | 15 | - later, the daemon will send a POST to the webserver, to notify that 16 | the payment has been received OR that the request has expired 17 | 18 | 19 | Since addresses are generated using an Electrum master public key, it 20 | is possible to visualize payments in the Electrum client; you will, 21 | however, need to manually adjust your "gap limit". 22 | 23 | In order to use this script, first edit and rename 24 | merchant.conf.template to merchant.conf 25 | 26 | to launch it, type: 27 | > python merchant.py 28 | 29 | In another terminal, you may send a request from the command line (it 30 | will send the request to the running daemon via http). For example, 31 | here is a request for 0.1 bitcoins, that requires two confirmations, 32 | and expires in 120 minutes: 33 | 34 | > python merchant.py request 0.1 2 120 35 | 36 | 37 | -------------------------------------------------------------------------------- /icons.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/electrum-xvg.png 4 | icons/clock1.png 5 | icons/clock2.png 6 | icons/clock3.png 7 | icons/clock4.png 8 | icons/clock5.png 9 | icons/confirmed.png 10 | icons/copy.png 11 | icons/expired.png 12 | icons/file.png 13 | icons/key.png 14 | icons/lock.png 15 | icons/unlock.png 16 | icons/preferences.png 17 | icons/seed.png 18 | icons/hot_seed.png 19 | icons/cold_seed.png 20 | icons/status_connected.png 21 | icons/status_disconnected.png 22 | icons/status_waiting.png 23 | icons/status_lagging.png 24 | icons/switchgui.png 25 | icons/electrum_light_icon.png 26 | icons/electrum_dark_icon.png 27 | icons/unconfirmed.png 28 | icons/unpaid.png 29 | icons/network.png 30 | icons/dark_background.png 31 | icons/qrcode.png 32 | icons/microphone.png 33 | icons/seal.png 34 | icons/speaker.png 35 | icons/trustedcoin.png 36 | icons/zoom.png 37 | 38 | 39 | -------------------------------------------------------------------------------- /contrib/make_android: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | if __name__ == '__main__': 5 | import sys, re, shutil, os, hashlib 6 | import imp 7 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 8 | os.chdir('..') 9 | 10 | v = imp.load_source('version', 'lib/version.py') 11 | version = v.ELECTRUM_VERSION 12 | 13 | if not ( os.path.exists('packages')): 14 | print "The packages directory is missing." 15 | sys.exit() 16 | 17 | target = 'dist/e4a-%s'%version 18 | os.system('rm -rf %s'%target) 19 | os.mkdir(target) 20 | shutil.copyfile('electrum-xvg', target + '/e4a.py') 21 | shutil.copyfile('scripts/authenticator.py', target + '/authenticator.py') 22 | shutil.copytree("packages",'dist/e4a-%s/packages'%version, ignore=shutil.ignore_patterns('*.pyc')) 23 | shutil.copytree("lib",'dist/e4a-%s/lib'%version, ignore=shutil.ignore_patterns('*.pyc')) 24 | # dns is not used by android app 25 | os.system('rm -rf %s/packages/dns'%target) 26 | os.mkdir(target + '/gui') 27 | shutil.copyfile('gui/android.py', target + '/gui/android.py') 28 | open(target + '/gui/__init__.py','w').close() 29 | 30 | os.chdir("dist") 31 | # create the zip file 32 | os.system( "zip -qr e4a-%s.zip e4a-%s"%(version, version) ) 33 | os.system( "rm -rf e4a-%s"%(version) ) 34 | 35 | # change filename because some 3G carriers do not allow users to download a zip file... 36 | e4a_name = "e4a-%s.zip"%version 37 | print "dist/%s"%e4a_name 38 | 39 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Electrum - lightweight Bitcoin client 2 | ===================================== 3 | 4 | :: 5 | 6 | Licence: GNU GPL v3 7 | Author: Thomas Voegtlin 8 | Language: Python 9 | Homepage: https://electrum.org/ 10 | 11 | 12 | .. image:: https://travis-ci.org/spesmilo/electrum.svg?branch=master 13 | :target: https://travis-ci.org/spesmilo/electrum 14 | :alt: Build Status 15 | 16 | 17 | 1. GETTING STARTED 18 | ------------------ 19 | 20 | To run Electrum from this directory, just do:: 21 | 22 | ./electrum 23 | 24 | If you install Electrum on your system, you can run it from any 25 | directory. 26 | 27 | If you have pip, you can do:: 28 | 29 | python setup.py sdist 30 | sudo pip install --pre dist/Electrum-2.0.tar.gz 31 | 32 | 33 | If you don't have pip, install with:: 34 | 35 | python setup.py sdist 36 | sudo python setup.py install 37 | 38 | 39 | 40 | To start Electrum from your web browser, see 41 | http://electrum.org/bitcoin_URIs.html 42 | 43 | 44 | 45 | 2. HOW OFFICIAL PACKAGES ARE CREATED 46 | ------------------------------------ 47 | 48 | On Linux/Windows:: 49 | 50 | pyrcc4 icons.qrc -o gui/qt/icons_rc.py 51 | python setup.py sdist --format=zip,gztar 52 | 53 | On Mac OS X:: 54 | 55 | # On port based installs 56 | sudo python setup-release.py py2app 57 | 58 | # On brew installs 59 | ARCHFLAGS="-arch i386 -arch x86_64" sudo python setup-release.py py2app --includes sip 60 | 61 | sudo hdiutil create -fs HFS+ -volname "Electrum" -srcfolder dist/Electrum.app dist/electrum-VERSION-macosx.dmg 62 | -------------------------------------------------------------------------------- /lib/qrscanner.py: -------------------------------------------------------------------------------- 1 | import os 2 | from i18n import _ 3 | 4 | try: 5 | import zbar 6 | except ImportError: 7 | zbar = None 8 | 9 | proc = None 10 | 11 | 12 | def scan_qr(config): 13 | global proc 14 | if not zbar: 15 | raise BaseException("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo pip install zbar'")])) 16 | if proc is None: 17 | device = config.get("video_device", "default") 18 | if device == 'default': 19 | device = '' 20 | _proc = zbar.Processor() 21 | _proc.init(video_device=device) 22 | # set global only if init did not raise an exception 23 | proc = _proc 24 | 25 | 26 | proc.visible = True 27 | while True: 28 | try: 29 | proc.process_one() 30 | except Exception: 31 | # User closed the preview window 32 | return "" 33 | for r in proc.results: 34 | if str(r.type) != 'QRCODE': 35 | continue 36 | # hiding the preview window stops the camera 37 | proc.visible = False 38 | return r.data 39 | 40 | def _find_system_cameras(): 41 | device_root = "/sys/class/video4linux" 42 | devices = {} # Name -> device 43 | if os.path.exists(device_root): 44 | for device in os.listdir(device_root): 45 | name = open(os.path.join(device_root, device, 'name')).read() 46 | name = name.strip('\n') 47 | devices[name] = os.path.join("/dev",device) 48 | return devices 49 | -------------------------------------------------------------------------------- /plugins/README: -------------------------------------------------------------------------------- 1 | Plugin rules: 2 | 3 | * The plugin system of Electrum is designed to allow the development 4 | of new features without increasing the core code of Electrum. 5 | 6 | * Electrum is written in pure python. if you want to add a feature 7 | that requires non-python libraries, then it must be submitted as a 8 | plugin. If the feature you want to add requires communication with 9 | a remote server (not an Electrum server), then it should be a 10 | plugin as well. If the feature you want to add introduces new 11 | dependencies in the code, then it should probably be a plugin. 12 | 13 | * We expect plugin developers to maintain their plugin code. However, 14 | once a plugin is merged in Electrum, we will have to maintain it 15 | too, because changes in the Electrum code often require updates in 16 | the plugin code. Therefore, plugins have to be easy to maintain. If 17 | we believe that a plugin will create too much maintenance work in 18 | the future, it will be rejected. 19 | 20 | * Plugins should be compatible with Electrum's conventions. If your 21 | plugin does not fit with Electrum's architecture, or if we believe 22 | that it will create too much maintenance work, it will not be 23 | accepted. In particular, do not duplicate existing Electrum code in 24 | your plugin. 25 | 26 | * We may decide to remove a plugin after it has been merged in 27 | Electrum. For this reason, a plugin must be easily removable, 28 | without putting at risk the user's bitcoins. If we feel that a 29 | plugin cannot be removed without threatening users who rely on it, 30 | we will not merge it. 31 | 32 | -------------------------------------------------------------------------------- /contrib/build-wine/build-electrum.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # You probably need to update only this link 4 | ELECTRUM_URL=http://electrum.bitcoin.cz/download/Electrum-1.6.1.tar.gz 5 | NAME_ROOT=electrum-1.6.1 6 | 7 | # These settings probably don't need any change 8 | export WINEPREFIX=/opt/wine-electrum 9 | PYHOME=c:/python26 10 | PYTHON="wine $PYHOME/python.exe -OO -B" 11 | 12 | # Let's begin! 13 | cd `dirname $0` 14 | set -e 15 | 16 | cd tmp 17 | 18 | # Download and unpack Electrum 19 | wget -O electrum-xvg.tgz "$ELECTRUM_URL" 20 | tar xf electrum.tgz 21 | mv Electrum-* electrum 22 | rm -rf $WINEPREFIX/drive_c/electrum-xvg 23 | cp electrum-xvg/LICENCE . 24 | mv electrum-xvg $WINEPREFIX/drive_c 25 | 26 | # Copy ZBar libraries to electrum 27 | #cp "$WINEPREFIX/drive_c/Program Files (x86)/ZBar/bin/"*.dll "$WINEPREFIX/drive_c/electrum/" 28 | 29 | cd .. 30 | 31 | rm -rf dist/$NAME_ROOT 32 | rm -f dist/$NAME_ROOT.zip 33 | rm -f dist/$NAME_ROOT.exe 34 | rm -f dist/$NAME_ROOT-setup.exe 35 | 36 | # For building standalone compressed EXE, run: 37 | $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w --onefile "C:/electrum-xvg/electrum-xvg" 38 | 39 | # For building uncompressed directory of dependencies, run: 40 | $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec 41 | 42 | # For building NSIS installer, run: 43 | wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsi 44 | #wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis 45 | 46 | cd dist 47 | mv electrum-xvg.exe $NAME_ROOT.exe 48 | mv electrum-xvg $NAME_ROOT 49 | mv electrum-xvg-setup.exe $NAME_ROOT-setup.exe 50 | zip -r $NAME_ROOT.zip $NAME_ROOT 51 | -------------------------------------------------------------------------------- /plugins/virtualkeyboard.py: -------------------------------------------------------------------------------- 1 | from PyQt4.QtGui import * 2 | from electrum_xvg.plugins import BasePlugin, hook 3 | from electrum_xvg.i18n import _ 4 | import random 5 | 6 | class Plugin(BasePlugin): 7 | 8 | @hook 9 | def init_qt(self, gui): 10 | self.gui = gui 11 | self.vkb = None 12 | self.vkb_index = 0 13 | 14 | @hook 15 | def password_dialog(self, pw, grid, pos): 16 | vkb_button = QPushButton(_("+")) 17 | vkb_button.setFixedWidth(20) 18 | vkb_button.clicked.connect(lambda: self.toggle_vkb(grid, pw)) 19 | grid.addWidget(vkb_button, pos, 2) 20 | self.kb_pos = 2 21 | self.vkb = None 22 | 23 | def toggle_vkb(self, grid, pw): 24 | if self.vkb: 25 | grid.removeItem(self.vkb) 26 | self.vkb = self.virtual_keyboard(self.vkb_index, pw) 27 | grid.addLayout(self.vkb, self.kb_pos, 0, 1, 3) 28 | self.vkb_index += 1 29 | 30 | def virtual_keyboard(self, i, pw): 31 | i = i%3 32 | if i == 0: 33 | chars = 'abcdefghijklmnopqrstuvwxyz ' 34 | elif i == 1: 35 | chars = 'ABCDEFGHIJKLMNOPQRTSUVWXYZ ' 36 | elif i == 2: 37 | chars = '1234567890!?.,;:/%&()[]{}+-' 38 | 39 | n = len(chars) 40 | s = [] 41 | for i in xrange(n): 42 | while True: 43 | k = random.randint(0,n-1) 44 | if k not in s: 45 | s.append(k) 46 | break 47 | 48 | def add_target(t): 49 | return lambda: pw.setText(str(pw.text()) + t) 50 | 51 | vbox = QVBoxLayout() 52 | grid = QGridLayout() 53 | grid.setSpacing(2) 54 | for i in range(n): 55 | l_button = QPushButton(chars[s[i]]) 56 | l_button.setFixedWidth(25) 57 | l_button.setFixedHeight(25) 58 | l_button.clicked.connect(add_target(chars[s[i]])) 59 | grid.addWidget(l_button, i/6, i%6) 60 | 61 | vbox.addLayout(grid) 62 | 63 | return vbox 64 | 65 | -------------------------------------------------------------------------------- /lib/contacts.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | import dns 4 | 5 | import bitcoin 6 | import dnssec 7 | from util import StoreDict, print_error 8 | from i18n import _ 9 | 10 | 11 | class Contacts(StoreDict): 12 | 13 | def __init__(self, config): 14 | StoreDict.__init__(self, config, 'contacts') 15 | 16 | def resolve(self, k): 17 | if bitcoin.is_address(k): 18 | return { 19 | 'address': k, 20 | 'type': 'address' 21 | } 22 | if k in self.keys(): 23 | _type, addr = self[k] 24 | if _type == 'address': 25 | return { 26 | 'address': addr, 27 | 'type': 'contact' 28 | } 29 | out = self.resolve_openalias(k) 30 | if out: 31 | address, name, validated = out 32 | return { 33 | 'address': address, 34 | 'name': name, 35 | 'type': 'openalias', 36 | 'validated': validated 37 | } 38 | raise Exception("Invalid Verge address or alias", k) 39 | 40 | def resolve_openalias(self, url): 41 | # support email-style addresses, per the OA standard 42 | url = url.replace('@', '.') 43 | try: 44 | records, validated = dnssec.query(url, dns.rdatatype.TXT) 45 | except: 46 | return 47 | prefix = 'btc' 48 | for record in records: 49 | string = record.strings[0] 50 | if string.startswith('oa1:' + prefix): 51 | address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)') 52 | name = self.find_regex(string, r'recipient_name=([^;]+)') 53 | if not name: 54 | name = address 55 | if not address: 56 | continue 57 | return address, name, validated 58 | 59 | def find_regex(self, haystack, needle): 60 | regex = re.compile(needle) 61 | try: 62 | return regex.search(haystack).groups()[0] 63 | except AttributeError: 64 | return None 65 | 66 | -------------------------------------------------------------------------------- /lib/i18n.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2012 thomasv@gitorious 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import gettext, os 20 | 21 | LOCALE_DIR = os.path.join(os.path.dirname(__file__), 'locale') 22 | language = gettext.translation('electrum', LOCALE_DIR, fallback = True) 23 | 24 | 25 | def _(x): 26 | global language 27 | return language.ugettext(x) 28 | 29 | def set_language(x): 30 | global language 31 | if x: language = gettext.translation('electrum', LOCALE_DIR, fallback = True, languages=[x]) 32 | 33 | 34 | languages = { 35 | '':_('Default'), 36 | 'ar_SA':_('Arabic'), 37 | 'cs_CZ':_('Czech'), 38 | 'da_DK':_('Danish'), 39 | 'de_DE':_('German'), 40 | 'eo_UY':_('Esperanto'), 41 | 'el_GR':_('Greek'), 42 | 'en_UK':_('English'), 43 | 'es_ES':_('Spanish'), 44 | 'fr_FR':_('French'), 45 | 'hu_HU':_('Hungarian'), 46 | 'hy_AM':_('Armenian'), 47 | 'id_ID':_('Indonesian'), 48 | 'it_IT':_('Italian'), 49 | 'ja_JP':_('Japanese'), 50 | 'ky_KG':_('Kyrgyz'), 51 | 'lv_LV':_('Latvian'), 52 | 'nl_NL':_('Dutch'), 53 | 'no_NO':_('Norwegian'), 54 | 'pl_PL':_('Polish'), 55 | 'pt_BR':_('Brasilian'), 56 | 'pt_PT':_('Portuguese'), 57 | 'ro_RO':_('Romanian'), 58 | 'ru_RU':_('Russian'), 59 | 'sk_SK':_('Slovak'), 60 | 'sl_SI':_('Slovenian'), 61 | 'ta_IN':_('Tamil'), 62 | 'th_TH':_('Thai'), 63 | 'vi_VN':_('Vietnamese'), 64 | 'zh_CN':_('Chinese') 65 | } 66 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # python setup.py sdist --format=zip,gztar 4 | 5 | from setuptools import setup 6 | import os 7 | import sys 8 | import platform 9 | import imp 10 | 11 | 12 | version = imp.load_source('version', 'lib/version.py') 13 | 14 | if sys.version_info[:3] < (2, 7, 0): 15 | sys.exit("Error: Electrum requires Python version >= 2.7.0...") 16 | 17 | 18 | 19 | data_files = [] 20 | if platform.system() in [ 'Linux', 'FreeBSD', 'DragonFly']: 21 | usr_share = os.path.join(sys.prefix, "share") 22 | data_files += [ 23 | (os.path.join(usr_share, 'applications/'), ['electrum-xvg.desktop']), 24 | (os.path.join(usr_share, 'pixmaps/'), ['icons/electrum-xvg.png']) 25 | ] 26 | 27 | 28 | setup( 29 | name="Electrum-XVG", 30 | version=version.ELECTRUM_VERSION, 31 | install_requires=[ 32 | 'slowaes>=0.1a1', 33 | 'ecdsa>=0.9', 34 | 'pbkdf2', 35 | 'requests', 36 | 'qrcode', 37 | 'ltc_scrypt', 38 | 'protobuf', 39 | 'tlslite', 40 | 'dnspython', 41 | ], 42 | package_dir={ 43 | 'electrum_xvg': 'lib', 44 | 'electrum_xvg_gui': 'gui', 45 | 'electrum_xvg_plugins': 'plugins', 46 | }, 47 | packages=['electrum_xvg','electrum_xvg_gui','electrum_xvg_gui.qt','electrum_xvg_plugins'], 48 | package_data={ 49 | 'electrum_xvg': [ 50 | 'www/index.html', 51 | 'wordlist/*.txt', 52 | 'locale/*/LC_MESSAGES/electrum.mo', 53 | ], 54 | 'electrum_xvg_gui': [ 55 | "qt/themes/cleanlook/name.cfg", 56 | "qt/themes/cleanlook/style.css", 57 | "qt/themes/sahara/name.cfg", 58 | "qt/themes/sahara/style.css", 59 | "qt/themes/dark/name.cfg", 60 | "qt/themes/dark/style.css", 61 | ] 62 | }, 63 | scripts=['electrum-xvg'], 64 | data_files=data_files, 65 | description="Lightweight Verge Wallet", 66 | author="sunerok", 67 | author_email="twitter.com/vergecurrency", 68 | license="GNU GPLv3", 69 | url="https://vergecurrency.com", 70 | long_description="""Lightweight Verge Wallet""" 71 | ) 72 | -------------------------------------------------------------------------------- /gui/qt/qrtextedit.py: -------------------------------------------------------------------------------- 1 | from electrum_xvg.i18n import _ 2 | from electrum_xvg.plugins import run_hook 3 | from PyQt4.QtGui import * 4 | from PyQt4.QtCore import * 5 | 6 | from util import ButtonsTextEdit 7 | 8 | 9 | class ShowQRTextEdit(ButtonsTextEdit): 10 | 11 | def __init__(self, text=None): 12 | ButtonsTextEdit.__init__(self, text) 13 | self.setReadOnly(1) 14 | self.addButton(":icons/qrcode.png", self.qr_show, _("Show as QR code")) 15 | run_hook('show_text_edit', self) 16 | 17 | def qr_show(self): 18 | from qrcodewidget import QRDialog 19 | try: 20 | s = str(self.toPlainText()) 21 | except: 22 | s = unicode(self.toPlainText()) 23 | QRDialog(s).exec_() 24 | 25 | def contextMenuEvent(self, e): 26 | m = self.createStandardContextMenu() 27 | m.addAction(_("Show as QR code"), self.qr_show) 28 | m.exec_(e.globalPos()) 29 | 30 | 31 | class ScanQRTextEdit(ButtonsTextEdit): 32 | 33 | def __init__(self, text=""): 34 | ButtonsTextEdit.__init__(self, text) 35 | self.setReadOnly(0) 36 | self.addButton(":icons/file.png", self.file_input, _("Read file")) 37 | self.addButton(":icons/qrcode.png", self.qr_input, _("Read QR code")) 38 | run_hook('scan_text_edit', self) 39 | 40 | def file_input(self): 41 | fileName = unicode(QFileDialog.getOpenFileName(self, 'select file')) 42 | if not fileName: 43 | return 44 | with open(fileName, "r") as f: 45 | data = f.read() 46 | self.setText(data) 47 | 48 | def qr_input(self): 49 | from electrum_xvg import qrscanner, get_config 50 | try: 51 | data = qrscanner.scan_qr(get_config()) 52 | except BaseException, e: 53 | QMessageBox.warning(self, _('Error'), _(e), _('OK')) 54 | return "" 55 | if type(data) != str: 56 | return 57 | self.setText(data) 58 | return data 59 | 60 | def contextMenuEvent(self, e): 61 | m = self.createStandardContextMenu() 62 | m.addAction(_("Read QR code"), self.qr_input) 63 | m.exec_(e.globalPos()) 64 | -------------------------------------------------------------------------------- /contrib/make_download: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | import re 4 | import hashlib 5 | import os 6 | 7 | from versions import version, version_win, version_mac 8 | from versions import download_template, download_page 9 | 10 | with open(download_template) as f: 11 | string = f.read() 12 | 13 | string = string.replace("##VERSION##", version) 14 | string = string.replace("##VERSION_WIN##", version_win) 15 | string = string.replace("##VERSION_MAC##", version_mac) 16 | 17 | files = { 18 | 'tgz': "Electrum-XVG-%s.tar.gz" % version, 19 | 'zip': "Electrum-XVG-%s.zip" % version, 20 | 'mac': "electrum-xvg-%s.dmg" % version_mac, 21 | 'win': "electrum-xvg-%s.exe" % version_win, 22 | 'win_setup': "electrum-xvg-%s-setup.exe" % version_win, 23 | 'win_portable': "electrum-xvg-%s-portable.exe" % version_win, 24 | } 25 | 26 | for k, n in files.items(): 27 | path = "binaries/%s"%n 28 | link = "https://github.com/vergecurrency/electrum-xvg/releases/download/%s"%n 29 | if not os.path.exists(path): 30 | os.system("wget -q %s -O %s" % (link, path)) 31 | if not os.path.getsize(path): 32 | os.unlink(path) 33 | string = re.sub("
(.*?)
"%k, '', string, flags=re.DOTALL + re.MULTILINE) 34 | continue 35 | sigpath = path + '.asc' 36 | siglink = link + '.asc' 37 | if not os.path.exists(sigpath): 38 | os.system("wget -q %s -O %s" % (siglink, sigpath)) 39 | if not os.path.getsize(sigpath): 40 | os.unlink(sigpath) 41 | string = re.sub("
(.*?)
"%k, '', string, flags=re.DOTALL + re.MULTILINE) 42 | continue 43 | if os.system("gpg --verify %s"%sigpath) != 0: 44 | raise BaseException(sigpath) 45 | string = string.replace("##link_%s##"%k, link) 46 | 47 | 48 | with open(download_page,'w') as f: 49 | f.write(string) 50 | 51 | 52 | # android 53 | 54 | from versions import android_template, android_page 55 | with open(android_template) as f: 56 | string = f.read() 57 | 58 | e4a_zipname = "e4a-%s.zip"%version 59 | string = string.replace("##VERSION##", version) 60 | string = string.replace("##ZIPNAME##", e4a_zipname) 61 | 62 | with open(android_page,'w') as f: 63 | f.write(string) 64 | -------------------------------------------------------------------------------- /contrib/build-wine/build-electrum-git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # You probably need to update only this link 4 | ELECTRUM_GIT_URL=git://github.com/vergecurrency/electrum-xvg.git 5 | BRANCH=master 6 | NAME_ROOT=electrum-xvg 7 | 8 | # These settings probably don't need any change 9 | export WINEPREFIX=/opt/wine-electrum 10 | PYHOME=c:/python26 11 | PYTHON="wine $PYHOME/python.exe -OO -B" 12 | 13 | # Let's begin! 14 | cd `dirname $0` 15 | set -e 16 | 17 | cd tmp 18 | 19 | if [ -d "electrum-git" ]; then 20 | # GIT repository found, update it 21 | echo "Pull" 22 | 23 | cd electrum-git 24 | git pull 25 | cd .. 26 | 27 | else 28 | # GIT repository not found, clone it 29 | echo "Clone" 30 | 31 | git clone -b $BRANCH $ELECTRUM_GIT_URL electrum-git 32 | fi 33 | 34 | cd electrum-git 35 | COMMIT_HASH=`git rev-parse HEAD | awk '{ print substr($1, 0, 11) }'` 36 | echo "Last commit: $COMMIT_HASH" 37 | cd .. 38 | 39 | 40 | rm -rf $WINEPREFIX/drive_c/electrum-xvg 41 | cp -r electrum-git $WINEPREFIX/drive_c/electrum-xvg 42 | cp electrum-git/LICENCE . 43 | 44 | # Build Qt resources 45 | wine $WINEPREFIX/drive_c/Python26/Lib/site-packages/PyQt4/pyrcc4.exe C:/electrum/icons.qrc -o C:/electrum/lib/icons_rc.py 46 | 47 | # Copy ZBar libraries to electrum 48 | #cp "$WINEPREFIX/drive_c/Program Files (x86)/ZBar/bin/"*.dll "$WINEPREFIX/drive_c/electrum-xvg/" 49 | 50 | cd .. 51 | 52 | rm -rf dist/ 53 | 54 | # For building standalone compressed EXE, run: 55 | $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w --onefile "C:/electrum-xvg/electrum-xvg" 56 | 57 | # For building uncompressed directory of dependencies, run: 58 | $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec 59 | 60 | # For building NSIS installer, run: 61 | wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum-xvg.nsi 62 | #wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum-xvg.nsis 63 | 64 | DATE=`date +"%Y%m%d"` 65 | cd dist 66 | mv electrum-xvg.exe $NAME_ROOT-$DATE-$COMMIT_HASH.exe 67 | mv electrum-xvg $NAME_ROOT-$DATE-$COMMIT_HASH 68 | mv electrum-xvg-setup.exe $NAME_ROOT-$DATE-$COMMIT_HASH-setup.exe 69 | zip -r $NAME_ROOT-$DATE-$COMMIT_HASH.zip $NAME_ROOT-$DATE-$COMMIT_HASH 70 | -------------------------------------------------------------------------------- /gui/jsonrpc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2015 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | 20 | """ 21 | jsonrpc interface for webservers. 22 | may be called from your php script. 23 | """ 24 | 25 | import socket, os 26 | from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer 27 | 28 | from electrum_xvg.wallet import WalletStorage, Wallet 29 | from electrum_xvg.commands import known_commands, Commands 30 | 31 | 32 | class ElectrumGui: 33 | 34 | def __init__(self, config, network): 35 | self.network = network 36 | self.config = config 37 | storage = WalletStorage(self.config.get_wallet_path()) 38 | if not storage.file_exists: 39 | raise BaseException("Wallet not found") 40 | self.wallet = Wallet(storage) 41 | self.cmd_runner = Commands(self.config, self.wallet, self.network) 42 | host = config.get('rpchost', 'localhost') 43 | port = config.get('rpcport', 7777) 44 | self.server = SimpleJSONRPCServer((host, port)) 45 | self.server.socket.settimeout(1) 46 | for cmdname in known_commands: 47 | self.server.register_function(getattr(self.cmd_runner, cmdname), cmdname) 48 | 49 | def main(self, url): 50 | self.wallet.start_threads(self.network) 51 | while True: 52 | try: 53 | self.server.handle_request() 54 | except socket.timeout: 55 | continue 56 | except: 57 | break 58 | self.wallet.stop_threads() 59 | -------------------------------------------------------------------------------- /windows.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | # We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports 4 | a = Analysis(['electrum-xvg', 'gui/qt/main_window.py', 'gui/qt/lite_window.py', 'gui/text.py', 5 | 'lib/util.py', 'lib/wallet.py', 'lib/simple_config.py', 6 | 'lib/bitcoin.py' 7 | ], 8 | hiddenimports=["qt","lib","gui"], 9 | pathex=['lib','gui','plugins','packages'], 10 | hookspath=None) 11 | 12 | ##### include mydir in distribution ####### 13 | def extra_datas(mydir): 14 | def rec_glob(p, files): 15 | import os 16 | import glob 17 | for d in glob.glob(p): 18 | if os.path.isfile(d): 19 | files.append(d) 20 | rec_glob("%s/*" % d, files) 21 | files = [] 22 | rec_glob("%s/*" % mydir, files) 23 | extra_datas = [] 24 | for f in files: 25 | extra_datas.append((f, f, 'DATA')) 26 | 27 | return extra_datas 28 | ########################################### 29 | 30 | # append dirs 31 | 32 | # cacert.pem 33 | # a.datas += [ ('requests/cacert.pem', 'packages/requests/cacert.pem', 'DATA') ] 34 | 35 | # Py folders that are needed because of the magic import finding 36 | a.datas += extra_datas('gui') 37 | a.datas += extra_datas('lib') 38 | a.datas += extra_datas('plugins') 39 | 40 | pyz = PYZ(a.pure) 41 | exe = EXE(pyz, 42 | a.scripts, 43 | a.binaries, 44 | a.datas, 45 | name=os.path.join('build\\pyi.win32\\electrum', 'electrum-xvg.exe'), 46 | debug=False, 47 | strip=None, 48 | upx=False, 49 | icon='icons/electrum.ico', 50 | console=False) 51 | # The console True makes an annoying black box pop up, but it does make Electrum output command line commands, with this turned off no output will be given but commands can still be used 52 | 53 | coll = COLLECT(exe, 54 | a.binaries, 55 | a.zipfiles, 56 | a.datas, 57 | strip=None, 58 | upx=False, 59 | debug=False, 60 | icon='icons/electrum.ico', 61 | console=False, 62 | name=os.path.join('dist', 'electrum-xvg')) 63 | -------------------------------------------------------------------------------- /contrib/build-wine/deterministic.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | # We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports 4 | a = Analysis(['electrum', 'gui/qt/main_window.py', 'gui/qt/lite_window.py', 'gui/text.py', 5 | 'lib/util.py', 'lib/wallet.py', 'lib/simple_config.py', 6 | 'lib/bitcoin.py' 7 | ], 8 | hiddenimports=["lib","gui"], 9 | pathex=['lib','gui','plugins','packages'], 10 | hookspath=None) 11 | 12 | ##### include mydir in distribution ####### 13 | def extra_datas(mydir): 14 | def rec_glob(p, files): 15 | import os 16 | import glob 17 | for d in glob.glob(p): 18 | if os.path.isfile(d): 19 | files.append(d) 20 | rec_glob("%s/*" % d, files) 21 | files = [] 22 | rec_glob("%s/*" % mydir, files) 23 | extra_datas = [] 24 | for f in files: 25 | extra_datas.append((f, f, 'DATA')) 26 | 27 | return extra_datas 28 | ########################################### 29 | 30 | # append dirs 31 | 32 | # cacert.pem 33 | a.datas += [ ('requests/cacert.pem', 'packages/requests/cacert.pem', 'DATA') ] 34 | 35 | # Py folders that are needed because of the magic import finding 36 | a.datas += extra_datas('gui') 37 | a.datas += extra_datas('lib') 38 | a.datas += extra_datas('plugins') 39 | 40 | pyz = PYZ(a.pure) 41 | exe = EXE(pyz, 42 | a.scripts, 43 | a.binaries, 44 | a.datas, 45 | name=os.path.join('build\\pyi.win32\\electrum', 'electrum.exe'), 46 | debug=False, 47 | strip=None, 48 | upx=False, 49 | icon='icons/electrum.ico', 50 | console=False) 51 | # The console True makes an annoying black box pop up, but it does make Electrum output command line commands, with this turned off no output will be given but commands can still be used 52 | 53 | coll = COLLECT(exe, 54 | a.binaries, 55 | a.zipfiles, 56 | a.datas, 57 | strip=None, 58 | upx=True, 59 | debug=False, 60 | icon='icons/electrum.ico', 61 | console=False, 62 | name=os.path.join('dist', 'electrum')) 63 | -------------------------------------------------------------------------------- /contrib/build-wine/prepare-wine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Please update these links carefully, some versions won't work under Wine 4 | PYTHON_URL=http://www.python.org/ftp/python/2.6.6/python-2.6.6.msi 5 | PYQT4_URL=http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.9.5/PyQt-Py2.6-x86-gpl-4.9.5-1.exe 6 | PYWIN32_URL=http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.6.exe/download 7 | PYINSTALLER_URL=http://downloads.sourceforge.net/project/pyinstaller/2.0/pyinstaller-2.0.zip 8 | NSIS_URL=http://prdownloads.sourceforge.net/nsis/nsis-2.46-setup.exe?download 9 | #ZBAR_URL=http://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download 10 | 11 | # These settings probably don't need change 12 | export WINEPREFIX=/opt/wine-electrum 13 | PYHOME=c:/python26 14 | PYTHON="wine $PYHOME/python.exe -OO -B" 15 | 16 | # Let's begin! 17 | cd `dirname $0` 18 | set -e 19 | 20 | # Clean up Wine environment 21 | echo "Cleaning $WINEPREFIX" 22 | rm -rf $WINEPREFIX/* 23 | echo "done" 24 | 25 | echo "Cleaning tmp" 26 | rm -rf tmp 27 | mkdir -p tmp 28 | echo "done" 29 | 30 | cd tmp 31 | 32 | # Install Python 33 | wget -O python.msi "$PYTHON_URL" 34 | msiexec /q /i python.msi 35 | 36 | # Install PyWin32 37 | wget -O pywin32.exe "$PYWIN32_URL" 38 | wine pywin32.exe 39 | 40 | # Install PyQt4 41 | wget -O PyQt.exe "$PYQT4_URL" 42 | wine PyQt.exe 43 | 44 | #cp -r /electrum-wine/pyinstaller $WINEPREFIX/drive_c/ 45 | # Install pyinstaller 46 | wget -O pyinstaller.zip "$PYINSTALLER_URL" 47 | unzip pyinstaller.zip 48 | mv pyinstaller-2.0 $WINEPREFIX/drive_c/pyinstaller 49 | 50 | # Patch pyinstaller's DummyZlib 51 | patch $WINEPREFIX/drive_c/pyinstaller/PyInstaller/loader/archive.py < ../archive.patch 52 | 53 | # Install ZBar 54 | #wget -q -O zbar.exe "http://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download" 55 | #wine zbar.exe 56 | 57 | # Install dependencies 58 | wget -q -O - "http://python-distribute.org/distribute_setup.py" | $PYTHON 59 | wine "$PYHOME\\Scripts\\easy_install.exe" ecdsa slowaes #zbar 60 | 61 | # Install NSIS installer 62 | wget -q -O nsis.exe "http://prdownloads.sourceforge.net/nsis/nsis-2.46-setup.exe?download" 63 | wine nsis.exe 64 | 65 | # Install UPX 66 | #wget -O upx.zip "http://upx.sourceforge.net/download/upx308w.zip" 67 | #unzip -o upx.zip 68 | #cp upx*/upx.exe . 69 | -------------------------------------------------------------------------------- /contrib/make_packages: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys, re, shutil, os, hashlib 4 | import imp 5 | import getpass 6 | 7 | if __name__ == '__main__': 8 | 9 | d = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) 10 | os.chdir(d) 11 | v = imp.load_source('version', 'lib/version.py') 12 | version = v.ELECTRUM_VERSION 13 | print "version", version 14 | 15 | # copy dependencies into 'packages' directory 16 | deps = [ 17 | 'aes', 18 | 'ecdsa', 19 | 'pbkdf2', 20 | 'requests', # note: requests-2.5.1 is needed to build with pyinstaller 21 | 'qrcode', 22 | 'google/protobuf', 23 | 'tlslite', 24 | 'dns', 25 | 'six', 26 | ] 27 | # don't use /usr/lib 28 | sys.path = ['/usr/local/lib/python2.7/dist-packages'] 29 | for module in deps: 30 | f, pathname, descr = imp.find_module(module) 31 | target = 'packages/' + module + descr[0] 32 | if os.path.exists(target): 33 | continue 34 | d = os.path.dirname(target) 35 | if d and not (os.path.exists(d)): 36 | os.makedirs(d) 37 | if descr[0]: 38 | shutil.copy(pathname, target) 39 | else: 40 | shutil.copytree(pathname, target, ignore=shutil.ignore_patterns('*.pyc')) 41 | 42 | # fix google/__init__.py needed by pyinstaller 43 | n = 'packages/google/__init__.py' 44 | if not os.path.exists(n): 45 | os.system("echo \# do not remove>%s"%n) 46 | 47 | os.system("pyrcc4 icons.qrc -o gui/qt/icons_rc.py") 48 | os.system("python setup.py sdist --format=zip,gztar") 49 | 50 | _tgz="Electrum-XVG-%s.tar.gz"%version 51 | _zip="Electrum-XVG-%s.zip"%version 52 | 53 | os.chdir("dist") 54 | password = getpass.getpass("Password:") 55 | for f in [_tgz,_zip]: 56 | os.system( "gpg --sign --armor --detach --passphrase \"%s\" %s"%(password, f) ) 57 | 58 | md5_tgz = hashlib.md5(file(_tgz, 'r').read()).digest().encode('hex') 59 | md5_zip = hashlib.md5(file(_zip, 'r').read()).digest().encode('hex') 60 | os.chdir("..") 61 | 62 | print "" 63 | print "Packages are ready:" 64 | print "dist/%s "%_tgz, md5_tgz 65 | print "dist/%s "%_zip, md5_zip 66 | print "To make a release, upload the files to the server, and update the webpages in branch gh-pages" 67 | 68 | -------------------------------------------------------------------------------- /scripts/util.py: -------------------------------------------------------------------------------- 1 | import time, electrum_xvg as electrum, Queue 2 | from electrum_xvg import Interface, SimpleConfig 3 | from electrum_xvg.network import filter_protocol, parse_servers 4 | 5 | # electrum.util.set_verbosity(1) 6 | 7 | def get_peers(): 8 | # 1. start interface and wait for connection 9 | q = Queue.Queue() 10 | interface = electrum.Interface('electrum-verge.xyz:50002:s', q) 11 | interface.start() 12 | i, r = q.get() 13 | if not interface.is_connected(): 14 | raise BaseException("not connected") 15 | # 2. get list of peers 16 | interface.send_request({'id':0, 'method':'server.peers.subscribe','params':[]}) 17 | i, r = q.get(timeout=10000) 18 | peers = parse_servers(r.get('result')) 19 | peers = filter_protocol(peers,'s') 20 | i.stop() 21 | return peers 22 | 23 | def send_request(peers, request): 24 | print "Contacting %d servers"%len(peers) 25 | # start interfaces 26 | q2 = Queue.Queue() 27 | config = SimpleConfig() 28 | interfaces = map(lambda server: Interface(server, q2, config), peers) 29 | reached_servers = [] 30 | for i in interfaces: 31 | i.start() 32 | t0 = time.time() 33 | while peers: 34 | try: 35 | i, r = q2.get(timeout=1) 36 | except: 37 | if time.time() - t0 > 10: 38 | print "timeout" 39 | break 40 | else: 41 | continue 42 | if i.server in peers: 43 | peers.remove(i.server) 44 | if i.is_connected(): 45 | reached_servers.append(i) 46 | else: 47 | print "Connection failed:", i.server 48 | 49 | print "%d servers could be reached"%len(reached_servers) 50 | 51 | results_queue = Queue.Queue() 52 | for i in reached_servers: 53 | i.send_request(request, results_queue) 54 | results = {} 55 | t0 = time.time() 56 | while reached_servers: 57 | try: 58 | i, r = results_queue.get(timeout=1) 59 | except: 60 | if time.time() - t0 > 10: 61 | break 62 | else: 63 | continue 64 | results[i.server] = r.get('result') 65 | reached_servers.remove(i) 66 | i.stop() 67 | 68 | for i in reached_servers: 69 | print i.server, "did not answer" 70 | print "%d answers"%len(results) 71 | return results 72 | -------------------------------------------------------------------------------- /contrib/make_locale: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from StringIO import StringIO 3 | import os, zipfile, pycurl 4 | import requests 5 | 6 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 7 | os.chdir('..') 8 | 9 | # Generate fresh translation template 10 | if not os.path.exists('lib/locale'): 11 | os.mkdir('lib/locale') 12 | cmd = 'xgettext -s --no-wrap -f app.fil --output=lib/locale/messages.pot' 13 | print 'Generate template' 14 | os.system(cmd) 15 | 16 | os.chdir('lib') 17 | 18 | crowdin_identifier = 'electrum-xvg' 19 | crowdin_file_name = 'electrum-client/messages.pot' 20 | locale_file_name = 'locale/messages.pot' 21 | 22 | if os.path.exists('../contrib/crowdin_api_key.txt'): 23 | crowdin_api_key = open('../contrib/crowdin_api_key.txt').read().strip() 24 | # Push to Crowdin 25 | print 'Push to Crowdin' 26 | url = ('http://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key) 27 | c = pycurl.Curl() 28 | c.setopt(c.URL, url) 29 | c.setopt(c.POST, 1) 30 | fields = [('files[' + crowdin_file_name + ']', (pycurl.FORM_FILE, locale_file_name))] 31 | c.setopt(c.HTTPPOST, fields) 32 | c.perform() 33 | # Build translations 34 | print 'Build translations' 35 | response = requests.request('GET', 'http://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key).content 36 | print response 37 | 38 | # Download & unzip 39 | print 'Download translations' 40 | zfobj = zipfile.ZipFile(StringIO(requests.request('GET', 'http://crowdin.com/download/project/' + crowdin_identifier + '.zip').content)) 41 | 42 | print 'Unzip translations' 43 | for name in zfobj.namelist(): 44 | if not name.startswith('electrum-client/locale'): 45 | continue 46 | if name.endswith('/'): 47 | if not os.path.exists(name[16:]): 48 | os.mkdir(name[16:]) 49 | else: 50 | output = open(name[16:],'w') 51 | output.write(zfobj.read(name)) 52 | output.close() 53 | 54 | # Convert .po to .mo 55 | print 'Installing' 56 | for lang in os.listdir('locale'): 57 | if lang.startswith('messages'): 58 | continue 59 | # Check LC_MESSAGES folder 60 | mo_dir = 'locale/%s/LC_MESSAGES' % lang 61 | if not os.path.exists(mo_dir): 62 | os.mkdir(mo_dir) 63 | cmd = 'msgfmt --output-file="%s/electrum.mo" "locale/%s/electrum.po"' % (mo_dir,lang) 64 | print 'Installing',lang 65 | os.system(cmd) 66 | -------------------------------------------------------------------------------- /gui/qt/themes/sahara/style.css: -------------------------------------------------------------------------------- 1 | #main_window 2 | { 3 | background: qlineargradient(x1: 0, y1: 0, x2:0,y2:1, stop: 0 white , stop: 1 #F2E3BE); 4 | } 5 | 6 | MiniWindow QPushButton { 7 | color: #C1A76D; 8 | border: 1px solid #A7811C; 9 | border-radius: 0px; 10 | background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 11 | stop: 0 white, stop: 1 #F2E3BE); 12 | min-height: 25px; 13 | min-width: 30px; 14 | } 15 | 16 | #send_button{ 17 | color: #FEEBA7; 18 | border: 1px solid #AD8B35; 19 | border-radius: 4px; 20 | background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 21 | stop: 0 #E0A035, stop: 1 #AD8B35); 22 | min-width: 80px; 23 | min-height: 23px; 24 | padding: 2px; 25 | } 26 | 27 | #send_button:disabled{ 28 | color: #FEEDD3; 29 | border: 1px solid #F7D46D; 30 | border-radius: 4px; 31 | background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 32 | stop: 0 #FAEEA5, stop: 1 #DBC050); 33 | min-width: 80px; 34 | min-height: 23px; 35 | padding: 2px; 36 | } 37 | 38 | #receive_button 39 | { 40 | color: #FEEBA7; 41 | border: 1px solid #AD8B35; 42 | border-radius: 4px; 43 | background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 44 | stop: 0 #E0A035, stop: 1 #AD8B35); 45 | min-height: 25px; 46 | min-width: 30px; 47 | } 48 | #receive_button[isActive=true] 49 | { 50 | color: #FEEBA7; 51 | background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 52 | stop: 0 #E0A035, stop: 1 #987620); 53 | } 54 | 55 | #address_input, #amount_input 56 | { 57 | color: #000; 58 | padding: 5px; 59 | border-radius: 4px; 60 | border: 1px solid #CBAE69; 61 | width: 225px; 62 | } 63 | 64 | #address_input[isValid=true] 65 | { 66 | color: #4D9948; 67 | padding: 5px; 68 | border-radius: 4px; 69 | border: 1px solid #CBAE69; 70 | width: 225px; 71 | margin-top: 4px; 72 | } 73 | 74 | #address_input[isValid=false] 75 | { 76 | color: #CE4141; 77 | padding: 5px; 78 | border-radius: 4px; 79 | border: 1px solid #CBAE69; 80 | width: 225px; 81 | margin-top: 4px; 82 | } 83 | 84 | #address_input[isValid=placeholder] 85 | { 86 | color: #DEC58D; 87 | padding: 5px; 88 | border-radius: 4px; 89 | border: 1px solid #CBAE69; 90 | width: 225px; 91 | margin-top: 4px; 92 | } 93 | #balance_label 94 | { 95 | color: #7E5907; 96 | } 97 | 98 | #history 99 | { 100 | color: #8B6914; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /lib/paymentrequest.proto: -------------------------------------------------------------------------------- 1 | // 2 | // Simple Bitcoin Payment Protocol messages 3 | // 4 | // Use fields 1000+ for extensions; 5 | // to avoid conflicts, register extensions via pull-req at 6 | // https://github.com/bitcoin/bips/bip-0070/extensions.mediawiki 7 | // 8 | 9 | package payments; 10 | option java_package = "org.bitcoin.protocols.payments"; 11 | option java_outer_classname = "Protos"; 12 | 13 | // Generalized form of "send payment to this/these bitcoin addresses" 14 | message Output { 15 | optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis 16 | required bytes script = 2; // usually one of the standard Script forms 17 | } 18 | message PaymentDetails { 19 | optional string network = 1 [default = "main"]; // "main" or "test" 20 | repeated Output outputs = 2; // Where payment should be sent 21 | required uint64 time = 3; // Timestamp; when payment request created 22 | optional uint64 expires = 4; // Timestamp; when this request should be considered invalid 23 | optional string memo = 5; // Human-readable description of request for the customer 24 | optional string payment_url = 6; // URL to send Payment and get PaymentACK 25 | optional bytes merchant_data = 7; // Arbitrary data to include in the Payment message 26 | } 27 | message PaymentRequest { 28 | optional uint32 payment_details_version = 1 [default = 1]; 29 | optional string pki_type = 2 [default = "none"]; // none / x509+sha256 / x509+sha1 30 | optional bytes pki_data = 3; // depends on pki_type 31 | required bytes serialized_payment_details = 4; // PaymentDetails 32 | optional bytes signature = 5; // pki-dependent signature 33 | } 34 | message X509Certificates { 35 | repeated bytes certificate = 1; // DER-encoded X.509 certificate chain 36 | } 37 | message Payment { 38 | optional bytes merchant_data = 1; // From PaymentDetails.merchant_data 39 | repeated bytes transactions = 2; // Signed transactions that satisfy PaymentDetails.outputs 40 | repeated Output refund_to = 3; // Where to send refunds, if a refund is necessary 41 | optional string memo = 4; // Human-readable message for the merchant 42 | } 43 | message PaymentACK { 44 | required Payment payment = 1; // Payment message that triggered this ACK 45 | optional string memo = 2; // human-readable message for customer 46 | } 47 | -------------------------------------------------------------------------------- /lib/msqr.py: -------------------------------------------------------------------------------- 1 | # from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/ 2 | 3 | def modular_sqrt(a, p): 4 | """ Find a quadratic residue (mod p) of 'a'. p 5 | must be an odd prime. 6 | 7 | Solve the congruence of the form: 8 | x^2 = a (mod p) 9 | And returns x. Note that p - x is also a root. 10 | 11 | 0 is returned is no square root exists for 12 | these a and p. 13 | 14 | The Tonelli-Shanks algorithm is used (except 15 | for some simple cases in which the solution 16 | is known from an identity). This algorithm 17 | runs in polynomial time (unless the 18 | generalized Riemann hypothesis is false). 19 | """ 20 | # Simple cases 21 | # 22 | if legendre_symbol(a, p) != 1: 23 | return 0 24 | elif a == 0: 25 | return 0 26 | elif p == 2: 27 | return p 28 | elif p % 4 == 3: 29 | return pow(a, (p + 1) / 4, p) 30 | 31 | # Partition p-1 to s * 2^e for an odd s (i.e. 32 | # reduce all the powers of 2 from p-1) 33 | # 34 | s = p - 1 35 | e = 0 36 | while s % 2 == 0: 37 | s /= 2 38 | e += 1 39 | 40 | # Find some 'n' with a legendre symbol n|p = -1. 41 | # Shouldn't take long. 42 | # 43 | n = 2 44 | while legendre_symbol(n, p) != -1: 45 | n += 1 46 | 47 | # Here be dragons! 48 | # Read the paper "Square roots from 1; 24, 51, 49 | # 10 to Dan Shanks" by Ezra Brown for more 50 | # information 51 | # 52 | 53 | # x is a guess of the square root that gets better 54 | # with each iteration. 55 | # b is the "fudge factor" - by how much we're off 56 | # with the guess. The invariant x^2 = ab (mod p) 57 | # is maintained throughout the loop. 58 | # g is used for successive powers of n to update 59 | # both a and b 60 | # r is the exponent - decreases with each update 61 | # 62 | x = pow(a, (s + 1) / 2, p) 63 | b = pow(a, s, p) 64 | g = pow(n, s, p) 65 | r = e 66 | 67 | while True: 68 | t = b 69 | m = 0 70 | for m in xrange(r): 71 | if t == 1: 72 | break 73 | t = pow(t, 2, p) 74 | 75 | if m == 0: 76 | return x 77 | 78 | gs = pow(g, 2 ** (r - m - 1), p) 79 | g = (gs * gs) % p 80 | x = (x * gs) % p 81 | b = (b * g) % p 82 | r = m 83 | 84 | def legendre_symbol(a, p): 85 | """ Compute the Legendre symbol a|p using 86 | Euler's criterion. p is a prime, a is 87 | relatively prime to p (if p divides 88 | a, then a|p = 0) 89 | 90 | Returns 1 if a has a square root modulo 91 | p, -1 otherwise. 92 | """ 93 | ls = pow(a, (p - 1) / 2, p) 94 | return -1 if ls == p - 1 else ls 95 | -------------------------------------------------------------------------------- /docs/cold_storage: -------------------------------------------------------------------------------- 1 | Here is how to sign a transaction with an offline Electrum wallet. 2 | 3 | 1. With your online (seedless) wallet, create the transaction using 'mktx': 4 | 5 | ./electrum -w seedless_wallet mktx 1Cpf9zb5Rm5Z5qmmGezn6ERxFWvwuZ6UCx 0.1 6 | { 7 | "complete": false, 8 | "hex": "01000000015a92850cc5dc7bb7c1a711c1ce0c1658596c085d49c17fce68c641cce0bdd188010000004801ff45fe197f1a7a7779f58690c3100364d7ce596bf47bb52e88e617e22940bf54a8f139194652584b0d357eb95defb8b4911b0a53118b8afecb96aedb1334e772df350901002800ffffffff02b1f0f65d000000001976a9147ea19cc36d846e2ce81762def3cb9299bed0847188ac80969800000000001976a91451e814c0f7637ba9a59bc11628337a2df6559a5088ac00000000" 9 | } 10 | 11 | Electrum returns an unsigned transaction. Note that the serialization 12 | format contains the master public key needed and key derivation, used 13 | by the offline wallet to sign the transaction. 14 | 15 | 16 | 2. Sign the transaction with your offline wallet, using 'signrawtransaction': 17 | 18 | ./electrum -w wallet_with_seed signrawtransaction 01000000015a92850cc5dc7bb7c1a711c1ce0c1658596c085d49c17fce68c641cce0bdd188010000004801ff45fe197f1a7a7779f58690c3100364d7ce596bf47bb52e88e617e22940bf54a8f139194652584b0d357eb95defb8b4911b0a53118b8afecb96aedb1334e772df350901002800ffffffff02b1f0f65d000000001976a9147ea19cc36d846e2ce81762def3cb9299bed0847188ac80969800000000001976a91451e814c0f7637ba9a59bc11628337a2df6559a5088ac00000000 19 | Password: 20 | { 21 | "complete": true, 22 | "hex": "01000000015a92850cc5dc7bb7c1a711c1ce0c1658596c085d49c17fce68c641cce0bdd188010000008b483045022100c65dd8899d4e1d12b1ebaa0ea15835f9a158343733fbe990cdfebde2164d89c802201a5a8fe737b07daf700aeecf3b6a4111c563ebc181da75b1f264883060c273da0141040beb415f075a532982fe982d01736453d4e3413566c79a39d16679474c7ab94022269b9f726edc152a89dfcf18cd3dd2a38fc5e442f24d22a51545ca42beb7b5ffffffff02b1f0f65d000000001976a9147ea19cc36d846e2ce81762def3cb9299bed0847188ac80969800000000001976a91451e814c0f7637ba9a59bc11628337a2df6559a5088ac00000000" 23 | } 24 | 25 | The result is a fully signed transaction, as indicated by the "complete" field. 26 | 27 | 28 | 3. Broadcast the transaction to the Bitcoin network, using 'sendrawtransaction': 29 | 30 | ./electrum sendrawtransaction 01000000015a92850cc5dc7bb7c1a711c1ce0c1658596c085d49c17fce68c641cce0bdd188010000008b483045022100c65dd8899d4e1d12b1ebaa0ea15835f9a158343733fbe990cdfebde2164d89c802201a5a8fe737b07daf700aeecf3b6a4111c563ebc181da75b1f264883060c273da0141040beb415f075a532982fe982d01736453d4e3413566c79a39d16679474c7ab94022269b9f726edc152a89dfcf18cd3dd2a38fc5e442f24d22a51545ca42beb7b5ffffffff02b1f0f65d000000001976a9147ea19cc36d846e2ce81762def3cb9299bed0847188ac80969800000000001976a91451e814c0f7637ba9a59bc11628337a2df6559a5088ac00000000 31 | "ef6b561232f3c507219ab7d2a79f8849e14ed7e926e77546c2d9e751905b825b" 32 | -------------------------------------------------------------------------------- /docs/android.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Electrum for Android

5 | 6 | This page explains how to install Electrum on Android devices.

7 | 8 | Please note that Electrum is not distributed as a binary package, but 9 | as python source code; this gives users the possibility to see what 10 | the code is doing, and to check that it does not contain malware. The 11 | downside is that installation is slightly more complicated than 12 | downloading an app on the Android market, but it remains very 13 | simple.

14 | 15 | It is possible to print this page on paper and to install everything from 16 | QR codes. If you encounter problems, you may find help at 17 | 18 | this link. 19 | 20 | 21 |

1. Download and install Google Scripting Layer for Android

22 | 23 | You can get 24 | it here, 25 | or by scanning the following qr code:
26 | 28 | 29 | 30 |

2. Download and install Python for Android

31 | 32 | You can get 33 | it here, 34 | or by scanning the following qr code:
35 | 36 |
37 | Once you have installed the apk, launch the Python for Android application and click 'install' 38 | 39 |

3. Download the Electrum install script

40 | 41 | Download e4a_install.py and install it in your sl4a/scripts directory. 42 | You can do it manually, or from QR code, as follows: 43 |
44 | 1. Launch SL4A.
45 | 2. Press the Menu button.
46 | 3. Tap Add.
47 | 4. Tap Scan Barcode.
48 | 5. Scan the following QRcode:
49 | 
50 | 52 |
This will install a script named e4a_install.py
53 | 54 |

4. Download and install Electrum

55 |
56 | 1. Tap e4a_install.py: it will download and install a directory named "Electrum-0.43d"
57 | 2. To launch Electrum, visit the "Electrum-0.43d" directory and tap 'electrum4a.py'
58 | 
59 | 60 | 61 | -------------------------------------------------------------------------------- /gui/qt/address_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2012 thomasv@gitorious 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import sys, time, datetime, re, threading 20 | from electrum_xvg.i18n import _, set_language 21 | from electrum_xvg.util import print_error, print_msg 22 | import os.path, json, ast, traceback 23 | import shutil 24 | import StringIO 25 | import importlib 26 | 27 | try: 28 | importlib.import_module("PyQt4") 29 | except Exception: 30 | sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'") 31 | 32 | from PyQt4.QtGui import * 33 | from PyQt4.QtCore import * 34 | import PyQt4.QtCore as QtCore 35 | 36 | 37 | from util import * 38 | from history_widget import HistoryWidget 39 | 40 | class AddressDialog(QDialog): 41 | 42 | def __init__(self, address, parent): 43 | self.address = address 44 | self.parent = parent 45 | self.config = parent.config 46 | self.wallet = parent.wallet 47 | self.app = parent.app 48 | self.saved = True 49 | 50 | QDialog.__init__(self) 51 | self.setMinimumWidth(700) 52 | self.setWindowTitle(_("Address")) 53 | self.setModal(1) 54 | vbox = QVBoxLayout() 55 | self.setLayout(vbox) 56 | 57 | vbox.addWidget(QLabel(_("Address:"))) 58 | self.addr_e = ButtonsLineEdit(self.address) 59 | self.addr_e.addCopyButton(self.app) 60 | self.addr_e.addButton(":icons/qrcode.png", self.show_qr, _("Show QR Code")) 61 | self.addr_e.setReadOnly(True) 62 | vbox.addWidget(self.addr_e) 63 | 64 | vbox.addWidget(QLabel(_("History"))) 65 | self.hw = HistoryWidget(self.parent) 66 | vbox.addWidget(self.hw) 67 | 68 | vbox.addStretch(1) 69 | vbox.addLayout(Buttons(CloseButton(self))) 70 | self.format_amount = self.parent.format_amount 71 | 72 | h = self.wallet.get_history([self.address]) 73 | self.hw.update(h) 74 | 75 | 76 | 77 | def show_qr(self): 78 | text = self.address 79 | try: 80 | self.parent.show_qrcode(text, 'Address') 81 | except Exception as e: 82 | self.show_message(str(e)) 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /docs/console.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is the documentation for the Electrum Console.
4 | 5 | 6 |
7 |
8 | Most Electrum command-line commands are also available in the console.
9 | The results are Python objects, even though they are 10 | sometimes rendered as JSON for clarity.
11 |
12 | Let us call listunspent(), to see the list of unspent outputs in the wallet: 13 |
14 | >> listunspent()
15 | [
16 |     {
17 |         "address": "12cmY5RHRgx8KkUKASDcDYRotget9FNso3", 
18 |         "index": 0, 
19 |         "raw_output_script": "76a91411bbdc6e3a27c44644d83f783ca7df3bdc2778e688ac", 
20 |         "tx_hash": "e7029df9ac8735b04e8e957d0ce73987b5c9c5e920ec4a445130cdeca654f096", 
21 |         "value": 0.01
22 |     }, 
23 |     {
24 |         "address": "1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF", 
25 |         "index": 0, 
26 |         "raw_output_script": "76a914aaf437e25805f288141bfcdc27887ee5492bd13188ac", 
27 |         "tx_hash": "b30edf57ca2a31560b5b6e8dfe567734eb9f7d3259bb334653276efe520735df", 
28 |         "value": 9.04735316
29 |     }
30 | ]
31 | 
32 | Note that the result is rendered as JSON.
33 | However, if we save it to a Python variable, it is rendered as a Python object: 34 |
35 | >> u = listunspent()
36 | >> u
37 | [{'tx_hash': u'e7029df9ac8735b04e8e957d0ce73987b5c9c5e920ec4a445130cdeca654f096', 'index': 0, 'raw_output_script': '76a91411bbdc6e3a27c44644d83f783ca7df3bdc2778e688ac', 'value': 0.01, 'address': '12cmY5RHRgx8KkUKASDcDYRotget9FNso3'}, {'tx_hash': u'b30edf57ca2a31560b5b6e8dfe567734eb9f7d3259bb334653276efe520735df', 'index': 0, 'raw_output_script': '76a914aaf437e25805f288141bfcdc27887ee5492bd13188ac', 'value': 9.04735316, 'address': '1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF'}]
38 | 
39 |
40 | This makes it possible to combine Electrum commands with Python.
41 | For example, let us pick only the addresses in the previous result: 42 |
43 | >> map(lambda x:x.get('address'), listunspent())
44 | [
45 |     "12cmY5RHRgx8KkUKASDcDYRotget9FNso3", 
46 |     "1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF"
47 | ]
48 | 
49 | Here we combine two commands, listunspent 50 | and dumpprivkeys, in order to dump the private keys of all adresses that have unspent outputs: 51 |
52 | >> dumpprivkeys( map(lambda x:x.get('address'), listunspent()) )
53 | {
54 |     "12cmY5RHRgx8KkUKASDcDYRotget9FNso3": "***************************************************", 
55 |     "1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF": "***************************************************"
56 | }
57 | 
58 | Note that dumpprivkey will ask for your password if your 59 | wallet is encrypted. 60 |
61 | The GUI methods can be accessed through the gui variable. 62 | For example, you can display a QR code from a string using 63 | gui.show_qrcode. 64 | Example: 65 |
66 | gui.show_qrcode(dumpprivkey(listunspent()[0]['address']))
67 | 
68 | 69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /contrib/build-wine/electrum.nsi: -------------------------------------------------------------------------------- 1 | ;-------------------------------- 2 | ;Include Modern UI 3 | 4 | !include "MUI2.nsh" 5 | 6 | ;-------------------------------- 7 | ;General 8 | 9 | ;Name and file 10 | Name "Electrum" 11 | OutFile "dist/electrum-setup.exe" 12 | 13 | ;Default installation folder 14 | InstallDir "$PROGRAMFILES\Electrum" 15 | 16 | ;Get installation folder from registry if available 17 | InstallDirRegKey HKCU "Software\Electrum" "" 18 | 19 | ;Request application privileges for Windows Vista 20 | RequestExecutionLevel admin 21 | 22 | ;-------------------------------- 23 | ;Variables 24 | 25 | ;-------------------------------- 26 | ;Interface Settings 27 | 28 | !define MUI_ABORTWARNING 29 | 30 | ;-------------------------------- 31 | ;Pages 32 | 33 | ;!insertmacro MUI_PAGE_LICENSE "tmp/LICENCE" 34 | ;!insertmacro MUI_PAGE_COMPONENTS 35 | !insertmacro MUI_PAGE_DIRECTORY 36 | 37 | ;Start Menu Folder Page Configuration 38 | !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" 39 | !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Electrum" 40 | !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" 41 | 42 | ;!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder 43 | 44 | !insertmacro MUI_PAGE_INSTFILES 45 | 46 | !insertmacro MUI_UNPAGE_CONFIRM 47 | !insertmacro MUI_UNPAGE_INSTFILES 48 | 49 | ;-------------------------------- 50 | ;Languages 51 | 52 | !insertmacro MUI_LANGUAGE "English" 53 | 54 | ;-------------------------------- 55 | ;Installer Sections 56 | 57 | Section 58 | 59 | SetOutPath "$INSTDIR" 60 | 61 | ;ADD YOUR OWN FILES HERE... 62 | file /r dist\electrum\*.* 63 | 64 | ;Store installation folder 65 | WriteRegStr HKCU "Software\Electrum" "" $INSTDIR 66 | 67 | ;Create uninstaller 68 | WriteUninstaller "$INSTDIR\Uninstall.exe" 69 | 70 | 71 | CreateShortCut "$DESKTOP\Electrum.lnk" "$INSTDIR\electrum.exe" "" 72 | 73 | ;create start-menu items 74 | CreateDirectory "$SMPROGRAMS\Electrum" 75 | CreateShortCut "$SMPROGRAMS\Electrum\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0 76 | CreateShortCut "$SMPROGRAMS\Electrum\Electrum.lnk" "$INSTDIR\electrum.exe" "" "$INSTDIR\electrum.exe" 0 77 | 78 | SectionEnd 79 | 80 | ;-------------------------------- 81 | ;Descriptions 82 | 83 | ;Assign language strings to sections 84 | ;!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN 85 | ; !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) 86 | ;!insertmacro MUI_FUNCTION_DESCRIPTION_END 87 | 88 | ;-------------------------------- 89 | ;Uninstaller Section 90 | 91 | Section "Uninstall" 92 | 93 | ;ADD YOUR OWN FILES HERE... 94 | RMDir /r "$INSTDIR\*.*" 95 | 96 | RMDir "$INSTDIR" 97 | 98 | Delete "$DESKTOP\Electrum.lnk" 99 | Delete "$SMPROGRAMS\Electrum\*.*" 100 | RmDir "$SMPROGRAMS\Electrum" 101 | 102 | DeleteRegKey /ifempty HKCU "Software\Electrum" 103 | 104 | SectionEnd 105 | -------------------------------------------------------------------------------- /gui/qt/qrwindow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2014 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import re 20 | import platform 21 | from decimal import Decimal 22 | from urllib import quote 23 | 24 | from PyQt4.QtGui import * 25 | from PyQt4.QtCore import * 26 | import PyQt4.QtCore as QtCore 27 | import PyQt4.QtGui as QtGui 28 | 29 | from electrum_xvg_gui.qt.qrcodewidget import QRCodeWidget 30 | from electrum_xvg.i18n import _ 31 | 32 | if platform.system() == 'Windows': 33 | MONOSPACE_FONT = 'Lucida Console' 34 | elif platform.system() == 'Darwin': 35 | MONOSPACE_FONT = 'Monaco' 36 | else: 37 | MONOSPACE_FONT = 'monospace' 38 | 39 | column_index = 4 40 | 41 | class QR_Window(QWidget): 42 | 43 | def __init__(self, win): 44 | QWidget.__init__(self) 45 | self.win = win 46 | self.setWindowTitle('Electrum - '+_('Payment Request')) 47 | self.setMinimumSize(800, 250) 48 | self.address = '' 49 | self.label = '' 50 | self.amount = 0 51 | self.setFocusPolicy(QtCore.Qt.NoFocus) 52 | 53 | main_box = QHBoxLayout() 54 | 55 | self.qrw = QRCodeWidget() 56 | main_box.addWidget(self.qrw, 1) 57 | 58 | vbox = QVBoxLayout() 59 | main_box.addLayout(vbox) 60 | 61 | self.address_label = QLabel("") 62 | #self.address_label.setFont(QFont(MONOSPACE_FONT)) 63 | vbox.addWidget(self.address_label) 64 | 65 | self.label_label = QLabel("") 66 | vbox.addWidget(self.label_label) 67 | 68 | self.amount_label = QLabel("") 69 | vbox.addWidget(self.amount_label) 70 | 71 | vbox.addStretch(1) 72 | self.setLayout(main_box) 73 | 74 | 75 | def set_content(self, address, amount, message, url): 76 | address_text = "%s" % address if address else "" 77 | self.address_label.setText(address_text) 78 | if amount: 79 | amount = self.win.format_amount(amount) 80 | amount_text = "%s %s " % (amount, self.win.base_unit()) 81 | else: 82 | amount_text = '' 83 | self.amount_label.setText(amount_text) 84 | label_text = "%s" % message if message else "" 85 | self.label_label.setText(label_text) 86 | self.qrw.setData(url) 87 | -------------------------------------------------------------------------------- /gui/qt/receiving_widget.py: -------------------------------------------------------------------------------- 1 | from PyQt4.QtGui import * 2 | from PyQt4.QtCore import * 3 | from electrum_xvg.i18n import _ 4 | 5 | class ReceivingWidget(QTreeWidget): 6 | 7 | def toggle_used(self): 8 | if self.hide_used: 9 | self.hide_used = False 10 | self.setColumnHidden(2, False) 11 | else: 12 | self.hide_used = True 13 | self.setColumnHidden(2, True) 14 | self.update_list() 15 | 16 | def edit_label(self, item, column): 17 | if column == 1 and item.isSelected(): 18 | self.editing = True 19 | item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) 20 | self.editItem(item, column) 21 | item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) 22 | self.editing = False 23 | 24 | def update_label(self, item, column): 25 | if self.editing: 26 | return 27 | else: 28 | address = str(item.text(0)) 29 | label = unicode( item.text(1) ) 30 | self.owner.actuator.g.wallet.set_label(address, label) 31 | 32 | def copy_address(self): 33 | address = self.currentItem().text(0) 34 | qApp.clipboard().setText(address) 35 | 36 | 37 | def update_list(self): 38 | return 39 | self.clear() 40 | addresses = self.owner.actuator.g.wallet.addresses(False) 41 | for address in addresses: 42 | history = self.owner.actuator.g.wallet.history.get(address,[]) 43 | 44 | used = "No" 45 | # It appears that at this moment history can either be an array with tx and block height 46 | # Or just a tx that's why this ugly code duplication is in, will fix 47 | if len(history) == 1: 48 | # This means pruned data. If that's the case the address has to been used at one point 49 | if history[0] == "*": 50 | used = "Yes" 51 | else: 52 | for tx_hash in history: 53 | tx = self.owner.actuator.g.wallet.transactions.get(tx_hash) 54 | if tx: 55 | used = "Yes" 56 | else: 57 | for tx_hash, height in history: 58 | tx = self.owner.actuator.g.wallet.transactions.get(tx_hash) 59 | if tx: 60 | used = "Yes" 61 | 62 | if(self.hide_used == True and used == "No") or self.hide_used == False: 63 | label = self.owner.actuator.g.wallet.labels.get(address,'') 64 | item = QTreeWidgetItem([address, label, used]) 65 | self.insertTopLevelItem(0, item) 66 | 67 | def __init__(self, owner=None): 68 | self.owner = owner 69 | self.editing = False 70 | 71 | QTreeWidget.__init__(self, owner) 72 | self.setColumnCount(3) 73 | self.setHeaderLabels([_("Address"), _("Label"), _("Used")]) 74 | self.setIndentation(0) 75 | 76 | self.hide_used = True 77 | self.setColumnHidden(2, True) 78 | -------------------------------------------------------------------------------- /setup-release.py: -------------------------------------------------------------------------------- 1 | """ 2 | py2app/py2exe build script for Electrum Verge 3 | 4 | Usage (Mac OS X): 5 | python setup.py py2app 6 | 7 | Usage (Windows): 8 | python setup.py py2exe 9 | """ 10 | 11 | from setuptools import setup 12 | import os 13 | import re 14 | import shutil 15 | import sys 16 | 17 | from lib.util import print_error 18 | from lib.version import ELECTRUM_VERSION as version 19 | 20 | 21 | name = "Electrum-XVG" 22 | mainscript = 'electrum-xvg' 23 | 24 | if sys.version_info[:3] < (2, 6, 0): 25 | print_error("Error: " + name + " requires Python version >= 2.6.0...") 26 | sys.exit(1) 27 | 28 | if sys.platform == 'darwin': 29 | from plistlib import Plist 30 | plist = Plist.fromFile('Info.plist') 31 | plist.update(dict(CFBundleIconFile='electrum.icns')) 32 | 33 | shutil.copy(mainscript, mainscript + '.py') 34 | mainscript += '.py' 35 | extra_options = dict( 36 | setup_requires=['py2app'], 37 | app=[mainscript], 38 | options=dict(py2app=dict(argv_emulation=False, 39 | includes=['PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4.QtWebKit', 'PyQt4.QtNetwork', 'sip'], 40 | packages=['lib', 'gui', 'plugins'], 41 | iconfile='electrum.icns', 42 | plist=plist, 43 | resources=["icons"])), 44 | ) 45 | elif sys.platform == 'win32': 46 | extra_options = dict( 47 | setup_requires=['py2exe'], 48 | app=[mainscript], 49 | ) 50 | else: 51 | extra_options = dict( 52 | # Normally unix-like platforms will use "setup.py install" 53 | # and install the main script as such 54 | scripts=[mainscript], 55 | ) 56 | 57 | setup( 58 | name=name, 59 | version=version, 60 | **extra_options 61 | ) 62 | from distutils import dir_util 63 | 64 | if sys.platform == 'darwin': 65 | # Remove the copied py file 66 | os.remove(mainscript) 67 | resource = "dist/" + name + ".app/Contents/Resources/" 68 | 69 | # Try to locate qt_menu 70 | # Let's try the port version first! 71 | if os.path.isfile("/opt/local/lib/Resources/qt_menu.nib"): 72 | qt_menu_location = "/opt/local/lib/Resources/qt_menu.nib" 73 | else: 74 | # No dice? Then let's try the brew version 75 | if os.path.exists("/usr/local/Cellar"): 76 | qt_menu_location = os.popen("find /usr/local/Cellar -name qt_menu.nib | tail -n 1").read() 77 | # no brew, check /opt/local 78 | else: 79 | qt_menu_location = os.popen("find /opt/local -name qt_menu.nib | tail -n 1").read() 80 | qt_menu_location = re.sub('\n', '', qt_menu_location) 81 | 82 | if (len(qt_menu_location) == 0): 83 | print "Sorry couldn't find your qt_menu.nib this probably won't work" 84 | else: 85 | print "Found your qib: " + qt_menu_location 86 | 87 | # Need to include a copy of qt_menu.nib 88 | shutil.copytree(qt_menu_location, resource + "qt_menu.nib") 89 | # Need to touch qt.conf to avoid loading 2 sets of Qt libraries 90 | fname = resource + "qt.conf" 91 | with file(fname, 'a'): 92 | os.utime(fname, None) 93 | -------------------------------------------------------------------------------- /gui/qt/seed_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2013 ecdsa@github 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | from PyQt4.QtGui import * 20 | from PyQt4.QtCore import * 21 | import PyQt4.QtCore as QtCore 22 | from electrum_xvg.i18n import _ 23 | from electrum_xvg import mnemonic 24 | 25 | from util import * 26 | from qrtextedit import ShowQRTextEdit, ScanQRTextEdit 27 | 28 | class SeedDialog(QDialog): 29 | def __init__(self, parent, seed, imported_keys): 30 | QDialog.__init__(self, parent) 31 | self.setModal(1) 32 | self.setMinimumWidth(400) 33 | self.setWindowTitle('Electrum' + ' - ' + _('Seed')) 34 | vbox = show_seed_box_msg(seed) 35 | if imported_keys: 36 | vbox.addWidget(QLabel(""+_("WARNING")+": " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "

")) 37 | vbox.addLayout(Buttons(CloseButton(self))) 38 | self.setLayout(vbox) 39 | 40 | 41 | def icon_filename(sid): 42 | if sid == 'cold': 43 | return ":icons/cold_seed.png" 44 | elif sid == 'hot': 45 | return ":icons/hot_seed.png" 46 | else: 47 | return ":icons/seed.png" 48 | 49 | 50 | def show_seed_box_msg(seedphrase, sid=None): 51 | msg = _("Your wallet generation seed is") + ":" 52 | vbox = show_seed_box(msg, seedphrase, sid) 53 | save_msg = _("Please save these %d words on paper (order is important).")%len(seedphrase.split()) + " " 54 | msg2 = save_msg + " " \ 55 | + _("This seed will allow you to recover your wallet in case of computer failure.") + "
" \ 56 | + ""+_("WARNING")+": " + _("Never disclose your seed. Never type it on a website.") + "

" 57 | label2 = QLabel(msg2) 58 | label2.setWordWrap(True) 59 | vbox.addWidget(label2) 60 | vbox.addStretch(1) 61 | return vbox 62 | 63 | def show_seed_box(msg, seed, sid): 64 | vbox, seed_e = enter_seed_box(msg, None, sid=sid, text=seed) 65 | return vbox 66 | 67 | def enter_seed_box(msg, window, sid=None, text=None): 68 | vbox = QVBoxLayout() 69 | logo = QLabel() 70 | logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56)) 71 | logo.setMaximumWidth(60) 72 | label = QLabel(msg) 73 | label.setWordWrap(True) 74 | if not text: 75 | seed_e = ScanQRTextEdit() 76 | seed_e.setTabChangesFocus(True) 77 | else: 78 | seed_e = ShowQRTextEdit(text=text) 79 | seed_e.setMaximumHeight(130) 80 | vbox.addWidget(label) 81 | grid = QGridLayout() 82 | grid.addWidget(logo, 0, 0) 83 | grid.addWidget(seed_e, 0, 1) 84 | vbox.addLayout(grid) 85 | return vbox, seed_e 86 | -------------------------------------------------------------------------------- /gui/qt/amountedit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4.QtCore import * 4 | from PyQt4.QtGui import * 5 | 6 | from decimal import Decimal 7 | from electrum_xvg.util import format_satoshis_plain 8 | 9 | class MyLineEdit(QLineEdit): 10 | frozen = pyqtSignal() 11 | 12 | def setFrozen(self, b): 13 | self.setReadOnly(b) 14 | self.setFrame(not b) 15 | self.frozen.emit() 16 | 17 | class AmountEdit(MyLineEdit): 18 | shortcut = pyqtSignal() 19 | 20 | def __init__(self, base_unit, is_int = False, parent=None): 21 | QLineEdit.__init__(self, parent) 22 | # This seems sufficient for hundred-BTC amounts with 8 decimals 23 | self.setFixedWidth(140) 24 | self.base_unit = base_unit 25 | self.textChanged.connect(self.numbify) 26 | self.is_int = is_int 27 | self.is_shortcut = False 28 | self.help_palette = QPalette() 29 | 30 | def decimal_point(self): 31 | return 6 32 | 33 | def numbify(self): 34 | text = unicode(self.text()).strip() 35 | if text == '!': 36 | self.shortcut.emit() 37 | return 38 | pos = self.cursorPosition() 39 | chars = '0123456789' 40 | if not self.is_int: chars +='.' 41 | s = ''.join([i for i in text if i in chars]) 42 | if not self.is_int: 43 | if '.' in s: 44 | p = s.find('.') 45 | s = s.replace('.','') 46 | s = s[:p] + '.' + s[p:p+self.decimal_point()] 47 | self.setText(s) 48 | # setText sets Modified to False. Instead we want to remember 49 | # if updates were because of user modification. 50 | self.setModified(self.hasFocus()) 51 | self.setCursorPosition(pos) 52 | 53 | def paintEvent(self, event): 54 | QLineEdit.paintEvent(self, event) 55 | if self.base_unit: 56 | panel = QStyleOptionFrameV2() 57 | self.initStyleOption(panel) 58 | textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self) 59 | textRect.adjust(2, 0, -10, 0) 60 | painter = QPainter(self) 61 | painter.setPen(self.help_palette.brush(QPalette.Disabled, QPalette.Text).color()) 62 | painter.drawText(textRect, Qt.AlignRight | Qt.AlignVCenter, self.base_unit()) 63 | 64 | def get_amount(self): 65 | try: 66 | x = int(str(self.text())) 67 | except: 68 | return None 69 | return x 70 | 71 | 72 | class BTCAmountEdit(AmountEdit): 73 | 74 | def __init__(self, decimal_point, is_int = False, parent=None): 75 | AmountEdit.__init__(self, self._base_unit, is_int, parent) 76 | self.decimal_point = decimal_point 77 | 78 | def _base_unit(self): 79 | p = self.decimal_point() 80 | assert p in [0, 3, 6] 81 | if p == 6: 82 | return 'XVG' 83 | if p == 3: 84 | return 'mXVG' 85 | if p == 0: 86 | return 'bits' 87 | raise Exception('Unknown base unit') 88 | 89 | def get_amount(self): 90 | try: 91 | x = Decimal(str(self.text())) 92 | except: 93 | return None 94 | p = pow(10, self.decimal_point()) 95 | return int( p * x ) 96 | 97 | def setAmount(self, amount): 98 | if amount is None: 99 | self.setText("") 100 | else: 101 | self.setText(format_satoshis_plain(amount, self.decimal_point())) 102 | -------------------------------------------------------------------------------- /lib/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Payment request 6 | 7 | 8 | 9 | 10 | 87 | 88 | 89 |

90 |

91 |

92 |

93 |
94 | Pay with Bitcoin 95 |
96 |
97 |
98 |

99 |
100 |
101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /lib/tests/test_util.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from lib.util import format_satoshis, parse_URI 3 | 4 | class TestUtil(unittest.TestCase): 5 | 6 | def test_format_satoshis(self): 7 | result = format_satoshis(1234) 8 | expected = "0.00001234" 9 | self.assertEqual(expected, result) 10 | 11 | def test_format_satoshis_diff_positive(self): 12 | result = format_satoshis(1234, is_diff=True) 13 | expected = "+0.00001234" 14 | self.assertEqual(expected, result) 15 | 16 | def test_format_satoshis_diff_negative(self): 17 | result = format_satoshis(-1234, is_diff=True) 18 | expected = "-0.00001234" 19 | self.assertEqual(expected, result) 20 | 21 | def _do_test_parse_URI(self, uri, expected): 22 | result = parse_URI(uri) 23 | self.assertEqual(expected, result) 24 | 25 | def test_parse_URI_address(self): 26 | self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 27 | {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma'}) 28 | 29 | def test_parse_URI_only_address(self): 30 | self._do_test_parse_URI('15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 31 | {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma'}) 32 | 33 | 34 | def test_parse_URI_address_label(self): 35 | self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?label=electrum%20test', 36 | {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'label': 'electrum test'}) 37 | 38 | def test_parse_URI_address_message(self): 39 | self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?message=electrum%20test', 40 | {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'message': 'electrum test', 'memo': 'electrum test'}) 41 | 42 | def test_parse_URI_address_amount(self): 43 | self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003', 44 | {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'amount': 30000}) 45 | 46 | def test_parse_URI_address_request_url(self): 47 | self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?r=http://domain.tld/page?h%3D2a8628fc2fbe', 48 | {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'r': 'http://domain.tld/page?h=2a8628fc2fbe'}) 49 | 50 | def test_parse_URI_ignore_args(self): 51 | self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?test=test', 52 | {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'test': 'test'}) 53 | 54 | def test_parse_URI_multiple_args(self): 55 | self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.00004&label=electrum-test&message=electrum%20test&test=none&r=http://domain.tld/page', 56 | {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'amount': 4000, 'label': 'electrum-test', 'message': u'electrum test', 'memo': u'electrum test', 'r': 'http://domain.tld/page', 'test': 'none'}) 57 | 58 | def test_parse_URI_no_address_request_url(self): 59 | self._do_test_parse_URI('bitcoin:?r=http://domain.tld/page?h%3D2a8628fc2fbe', 60 | {'r': 'http://domain.tld/page?h=2a8628fc2fbe'}) 61 | 62 | def test_parse_URI_invalid_address(self): 63 | self.assertRaises(AssertionError, parse_URI, 'bitcoin:invalidaddress') 64 | 65 | def test_parse_URI_invalid(self): 66 | self.assertRaises(AssertionError, parse_URI, 'notbitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma') 67 | 68 | def test_parse_URI_parameter_polution(self): 69 | self.assertRaises(Exception, parse_URI, 'bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003&label=test&amount=30.0') 70 | 71 | -------------------------------------------------------------------------------- /lib/verifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2012 thomasv@ecdsa.org 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | 20 | import threading 21 | import Queue 22 | 23 | 24 | import util 25 | from bitcoin import * 26 | 27 | 28 | class SPV(util.DaemonThread): 29 | """ Simple Payment Verification """ 30 | 31 | def __init__(self, network, wallet): 32 | util.DaemonThread.__init__(self) 33 | self.wallet = wallet 34 | self.network = network 35 | self.merkle_roots = {} # hashed by me 36 | self.queue = Queue.Queue() 37 | 38 | def run(self): 39 | requested_merkle = set() 40 | while self.is_running(): 41 | unverified = self.wallet.get_unverified_txs() 42 | for (tx_hash, tx_height) in unverified: 43 | if self.merkle_roots.get(tx_hash) is None and tx_hash not in requested_merkle: 44 | if self.network.send([ ('blockchain.transaction.get_merkle',[tx_hash, tx_height]) ], self.queue.put): 45 | self.print_error('requesting merkle', tx_hash) 46 | requested_merkle.add(tx_hash) 47 | try: 48 | r = self.queue.get(timeout=0.1) 49 | except Queue.Empty: 50 | continue 51 | if not r: 52 | continue 53 | 54 | if r.get('error'): 55 | self.print_error('Verifier received an error:', r) 56 | continue 57 | 58 | # 3. handle response 59 | method = r['method'] 60 | params = r['params'] 61 | result = r['result'] 62 | 63 | if method == 'blockchain.transaction.get_merkle': 64 | tx_hash = params[0] 65 | self.verify_merkle(tx_hash, result) 66 | 67 | self.print_error("stopped") 68 | 69 | 70 | def verify_merkle(self, tx_hash, result): 71 | tx_height = result.get('block_height') 72 | pos = result.get('pos') 73 | merkle_root = self.hash_merkle_root(result['merkle'], tx_hash, pos) 74 | header = self.network.get_header(tx_height) 75 | if not header: return 76 | if header.get('merkle_root') != merkle_root: 77 | self.print_error("merkle verification failed for", tx_hash) 78 | return 79 | 80 | # we passed all the tests 81 | self.merkle_roots[tx_hash] = merkle_root 82 | self.print_error("verified %s" % tx_hash) 83 | self.wallet.add_verified_tx(tx_hash, (tx_height, header.get('timestamp'), pos)) 84 | 85 | 86 | def hash_merkle_root(self, merkle_s, target_hash, pos): 87 | h = hash_decode(target_hash) 88 | for i in range(len(merkle_s)): 89 | item = merkle_s[i] 90 | h = Hash( hash_decode(item) + h ) if ((pos >> i) & 1) else Hash( h + hash_decode(item) ) 91 | return hash_encode(h) 92 | 93 | 94 | def undo_verifications(self, height): 95 | tx_hashes = self.wallet.undo_verifications(height) 96 | for tx_hash in tx_hashes: 97 | self.print_error("redoing", tx_hash) 98 | self.merkle_roots.pop(tx_hash, None) 99 | -------------------------------------------------------------------------------- /lib/tests/test_wallet.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import tempfile 3 | import sys 4 | import unittest 5 | import os 6 | import json 7 | 8 | from StringIO import StringIO 9 | from lib.wallet import WalletStorage, NewWallet 10 | 11 | 12 | class FakeSynchronizer(object): 13 | 14 | def __init__(self): 15 | self.store = [] 16 | 17 | def add(self, address): 18 | self.store.append(address) 19 | 20 | 21 | class WalletTestCase(unittest.TestCase): 22 | 23 | def setUp(self): 24 | super(WalletTestCase, self).setUp() 25 | self.user_dir = tempfile.mkdtemp() 26 | 27 | self.wallet_path = os.path.join(self.user_dir, "somewallet") 28 | 29 | self._saved_stdout = sys.stdout 30 | self._stdout_buffer = StringIO() 31 | sys.stdout = self._stdout_buffer 32 | 33 | def tearDown(self): 34 | super(WalletTestCase, self).tearDown() 35 | shutil.rmtree(self.user_dir) 36 | # Restore the "real" stdout 37 | sys.stdout = self._saved_stdout 38 | 39 | 40 | class TestWalletStorage(WalletTestCase): 41 | 42 | def test_read_dictionnary_from_file(self): 43 | 44 | some_dict = {"a":"b", "c":"d"} 45 | contents = repr(some_dict) 46 | with open(self.wallet_path, "w") as f: 47 | contents = f.write(contents) 48 | 49 | storage = WalletStorage(self.wallet_path) 50 | self.assertEqual("b", storage.get("a")) 51 | self.assertEqual("d", storage.get("c")) 52 | 53 | def test_write_dictionnary_to_file(self): 54 | 55 | storage = WalletStorage(self.wallet_path) 56 | 57 | some_dict = {"a":"b", "c":"d"} 58 | storage.data = some_dict 59 | 60 | storage.write() 61 | 62 | contents = "" 63 | with open(self.wallet_path, "r") as f: 64 | contents = f.read() 65 | self.assertEqual(some_dict, json.loads(contents)) 66 | 67 | 68 | class TestNewWallet(WalletTestCase): 69 | 70 | seed_text = "travel nowhere air position hill peace suffer parent beautiful rise blood power home crumble teach" 71 | password = "secret" 72 | 73 | first_account_name = "account1" 74 | 75 | import_private_key = "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW" 76 | import_key_address = "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma" 77 | 78 | def setUp(self): 79 | super(TestNewWallet, self).setUp() 80 | self.storage = WalletStorage(self.wallet_path) 81 | self.wallet = NewWallet(self.storage) 82 | # This cannot be constructed by electrum at random, it should be safe 83 | # from eventual collisions. 84 | self.wallet.add_seed(self.seed_text, self.password) 85 | self.wallet.create_master_keys(self.password) 86 | self.wallet.create_main_account(self.password) 87 | 88 | def test_wallet_with_seed_is_not_watching_only(self): 89 | self.assertFalse(self.wallet.is_watching_only()) 90 | 91 | def test_wallet_without_seed_is_watching_only(self): 92 | # We need a new storage , since the default storage was already seeded 93 | # in setUp() 94 | new_dir = tempfile.mkdtemp() 95 | storage = WalletStorage(os.path.join(new_dir, "somewallet")) 96 | wallet = NewWallet(storage) 97 | self.assertTrue(wallet.is_watching_only()) 98 | shutil.rmtree(new_dir) # Don't leave useless stuff in /tmp 99 | 100 | def test_new_wallet_is_deterministic(self): 101 | self.assertTrue(self.wallet.is_deterministic()) 102 | 103 | def test_get_seed_returns_correct_seed(self): 104 | self.assertEqual(self.wallet.get_seed(self.password), self.seed_text) 105 | 106 | def test_update_password(self): 107 | new_password = "secret2" 108 | self.wallet.update_password(self.password, new_password) 109 | self.wallet.get_seed(new_password) 110 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2015 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | from electrum_xvg.i18n import _ 20 | 21 | descriptions = [ 22 | { 23 | 'name': 'audio_modem', 24 | 'fullname': _('Audio MODEM'), 25 | 'description': ('Provides support for air-gapped transaction signing.\n\n' 26 | 'Requires http://github.com/romanz/amodem/'), 27 | 'requires': ['amodem'], 28 | 'GUI': ['qt'] 29 | }, 30 | { 31 | 'name': 'btchipwallet', 32 | 'fullname': _('BTChip Wallet'), 33 | 'description': _('Provides support for BTChip hardware wallet') + '\n\n' + _('Requires github.com/btchip/btchip-python'), 34 | 'requires': ['btchip'], 35 | 'requires_wallet_type': ['btchip'], 36 | 'registers_wallet_type': True 37 | }, 38 | { 39 | 'name': 'cosigner_pool', 40 | 'fullname': _('Cosigner Pool'), 41 | 'description': ' '.join([ 42 | _("This plugin facilitates the use of multi-signatures wallets."), 43 | _("It sends and receives partially signed transactions from/to your cosigner wallet."), 44 | _("Transactions are encrypted and stored on a remote server.") 45 | ]), 46 | 'GUI': ['qt'], 47 | 'requires_wallet_type': ['2of2', '2of3'] 48 | }, 49 | { 50 | 'name': 'exchange_rate', 51 | 'fullname': _("Exchange rates"), 52 | 'description': """exchange rates, retrieved from BTC-e and other market exchanges""" 53 | }, 54 | { 55 | 'name': 'labels', 56 | 'fullname': _('LabelSync'), 57 | 'description': '%s\n\n%s' % (_("The new and improved LabelSync plugin. This can sync your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server."), _("The label sync's server software is open-source as well and can be found on github.com/maran/electrum-sync-server")) 58 | }, 59 | { 60 | 'name': 'openalias', 61 | 'fullname': 'OpenAlias', 62 | 'description': 'Allow for payments to OpenAlias addresses.\nRequires dnspython', 63 | 'requires': ['dns'] 64 | }, 65 | { 66 | 'name': 'plot', 67 | 'fullname': 'Plot History', 68 | 'description': '\n'.join([ 69 | _("Ability to plot transaction history in graphical mode."), 70 | _("Warning: Requires matplotlib library.") 71 | ]), 72 | 'requires': ['matplotlib'], 73 | 'GUI': ['qt'] 74 | }, 75 | { 76 | 'name':'trezor', 77 | 'fullname': 'Trezor Wallet', 78 | 'description': 'Provides support for Trezor hardware wallet\n\nRequires github.com/trezor/python-trezor', 79 | 'GUI': ['qt'], 80 | 'requires': ['trezorlib'], 81 | 'requires_wallet_type': ['trezor'], 82 | 'registers_wallet_type': True 83 | }, 84 | { 85 | 'name': 'virtualkeyboard', 86 | 'fullname': 'Virtual Keyboard', 87 | 'description': '%s\n%s' % (_("Add an optional virtual keyboard to the password dialog."), _("Warning: do not use this if it makes you pick a weaker password.")), 88 | } 89 | ] 90 | -------------------------------------------------------------------------------- /gui/qt/history_widget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2015 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | 20 | import webbrowser 21 | 22 | from util import * 23 | from electrum_xvg.i18n import _ 24 | from electrum_xvg.util import block_explorer_URL, format_satoshis, format_time 25 | from electrum_xvg.plugins import run_hook 26 | 27 | 28 | class HistoryWidget(MyTreeWidget): 29 | 30 | def __init__(self, parent=None): 31 | MyTreeWidget.__init__(self, parent, self.create_menu, [ '', _('Date'), _('Description') , _('Amount'), _('Balance')], 2) 32 | self.config = self.parent.config 33 | self.setSortingEnabled(False) 34 | 35 | def update(self, h): 36 | self.wallet = self.parent.wallet 37 | item = self.currentItem() 38 | current_tx = item.data(0, Qt.UserRole).toString() if item else None 39 | self.clear() 40 | for item in h: 41 | tx_hash, conf, value, timestamp, balance = item 42 | time_str = _("unknown") 43 | if conf is None and timestamp is None: 44 | continue # skip history in offline mode 45 | if conf > 0: 46 | time_str = format_time(timestamp) 47 | if conf == -1: 48 | time_str = 'unverified' 49 | icon = QIcon(":icons/unconfirmed.png") 50 | elif conf == 0: 51 | time_str = 'pending' 52 | icon = QIcon(":icons/unconfirmed.png") 53 | elif conf < 6: 54 | icon = QIcon(":icons/clock%d.png"%conf) 55 | else: 56 | icon = QIcon(":icons/confirmed.png") 57 | v_str = self.parent.format_amount(value, True, whitespaces=True) 58 | balance_str = self.parent.format_amount(balance, whitespaces=True) 59 | label, is_default_label = self.wallet.get_label(tx_hash) 60 | item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] ) 61 | item.setFont(2, QFont(MONOSPACE_FONT)) 62 | item.setFont(3, QFont(MONOSPACE_FONT)) 63 | item.setFont(4, QFont(MONOSPACE_FONT)) 64 | if value < 0: 65 | item.setForeground(3, QBrush(QColor("#BC1E1E"))) 66 | if tx_hash: 67 | item.setData(0, Qt.UserRole, tx_hash) 68 | if is_default_label: 69 | item.setForeground(2, QBrush(QColor('grey'))) 70 | item.setIcon(0, icon) 71 | self.insertTopLevelItem(0, item) 72 | if current_tx == tx_hash: 73 | self.setCurrentItem(item) 74 | 75 | run_hook('history_tab_update') 76 | 77 | 78 | def create_menu(self, position): 79 | self.selectedIndexes() 80 | item = self.currentItem() 81 | if not item: 82 | return 83 | tx_hash = str(item.data(0, Qt.UserRole).toString()) 84 | if not tx_hash: 85 | return 86 | tx_URL = block_explorer_URL(self.config, 'tx', tx_hash) 87 | if not tx_URL: 88 | return 89 | menu = QMenu() 90 | menu.addAction(_("Copy ID to Clipboard"), lambda: self.parent.app.clipboard().setText(tx_hash)) 91 | menu.addAction(_("Details"), lambda: self.parent.show_transaction(self.wallet.transactions.get(tx_hash))) 92 | menu.addAction(_("Edit description"), lambda: self.edit_label(item)) 93 | menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL)) 94 | menu.exec_(self.viewport().mapToGlobal(position)) 95 | 96 | -------------------------------------------------------------------------------- /gui/qt/qrcodewidget.py: -------------------------------------------------------------------------------- 1 | from PyQt4.QtGui import * 2 | from PyQt4.QtCore import * 3 | import PyQt4.QtCore as QtCore 4 | import PyQt4.QtGui as QtGui 5 | 6 | import os 7 | import qrcode 8 | 9 | import electrum_xvg 10 | from electrum_xvg import bmp 11 | from electrum_xvg.i18n import _ 12 | 13 | 14 | class QRCodeWidget(QWidget): 15 | 16 | def __init__(self, data = None, fixedSize=False): 17 | QWidget.__init__(self) 18 | self.data = None 19 | self.qr = None 20 | self.fixedSize=fixedSize 21 | if fixedSize: 22 | self.setFixedSize(fixedSize, fixedSize) 23 | self.setData(data) 24 | 25 | 26 | def setData(self, data): 27 | if self.data != data: 28 | self.data = data 29 | if self.data: 30 | self.qr = qrcode.QRCode() 31 | self.qr.add_data(self.data) 32 | if not self.fixedSize: 33 | k = len(self.qr.get_matrix()) 34 | self.setMinimumSize(k*5,k*5) 35 | else: 36 | self.qr = None 37 | 38 | self.update() 39 | 40 | 41 | def paintEvent(self, e): 42 | if not self.data: 43 | return 44 | 45 | black = QColor(0, 0, 0, 255) 46 | white = QColor(255, 255, 255, 255) 47 | 48 | if not self.qr: 49 | qp = QtGui.QPainter() 50 | qp.begin(self) 51 | qp.setBrush(white) 52 | qp.setPen(white) 53 | r = qp.viewport() 54 | qp.drawRect(0, 0, r.width(), r.height()) 55 | qp.end() 56 | return 57 | 58 | matrix = self.qr.get_matrix() 59 | k = len(matrix) 60 | qp = QtGui.QPainter() 61 | qp.begin(self) 62 | r = qp.viewport() 63 | 64 | margin = 10 65 | framesize = min(r.width(), r.height()) 66 | boxsize = int( (framesize - 2*margin)/k ) 67 | size = k*boxsize 68 | left = (r.width() - size)/2 69 | top = (r.height() - size)/2 70 | 71 | # Make a white margin around the QR in case of dark theme use 72 | qp.setBrush(white) 73 | qp.setPen(white) 74 | qp.drawRect(left-margin, top-margin, size+(margin*2), size+(margin*2)) 75 | qp.setBrush(black) 76 | qp.setPen(black) 77 | 78 | for r in range(k): 79 | for c in range(k): 80 | if matrix[r][c]: 81 | qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1) 82 | qp.end() 83 | 84 | 85 | 86 | class QRDialog(QDialog): 87 | 88 | def __init__(self, data, parent=None, title = "", show_text=False): 89 | QDialog.__init__(self, parent) 90 | 91 | d = self 92 | d.setWindowTitle(title) 93 | vbox = QVBoxLayout() 94 | qrw = QRCodeWidget(data) 95 | vbox.addWidget(qrw, 1) 96 | if show_text: 97 | text = QTextEdit() 98 | text.setText(data) 99 | text.setReadOnly(True) 100 | vbox.addWidget(text) 101 | hbox = QHBoxLayout() 102 | hbox.addStretch(1) 103 | 104 | config = electrum_xvg.get_config() 105 | if config: 106 | filename = os.path.join(config.path, "qrcode.bmp") 107 | 108 | def print_qr(): 109 | bmp.save_qrcode(qrw.qr, filename) 110 | QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK')) 111 | 112 | def copy_to_clipboard(): 113 | bmp.save_qrcode(qrw.qr, filename) 114 | QApplication.clipboard().setImage(QImage(filename)) 115 | QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK')) 116 | 117 | b = QPushButton(_("Copy")) 118 | hbox.addWidget(b) 119 | b.clicked.connect(copy_to_clipboard) 120 | 121 | b = QPushButton(_("Save")) 122 | hbox.addWidget(b) 123 | b.clicked.connect(print_qr) 124 | 125 | b = QPushButton(_("Close")) 126 | hbox.addWidget(b) 127 | b.clicked.connect(d.accept) 128 | b.setDefault(True) 129 | 130 | vbox.addLayout(hbox) 131 | d.setLayout(vbox) 132 | -------------------------------------------------------------------------------- /plugins/greenaddress_instant.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2014 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import urllib 20 | import json 21 | import sys 22 | import requests 23 | 24 | from PyQt4.QtGui import QMessageBox, QApplication, QPushButton 25 | 26 | from electrum.account import BIP32_Account 27 | from electrum import bitcoin, util 28 | from electrum import transaction 29 | from electrum.plugins import BasePlugin, hook 30 | from electrum.i18n import _ 31 | from electrum.bitcoin import regenerate_key 32 | 33 | 34 | 35 | class Plugin(BasePlugin): 36 | 37 | button_label = _("Verify GA instant") 38 | 39 | @hook 40 | def init_qt(self, gui): 41 | self.win = gui.main_window 42 | 43 | @hook 44 | def transaction_dialog(self, d): 45 | self.wallet = d.wallet 46 | self.verify_button = b = QPushButton(self.button_label) 47 | b.clicked.connect(lambda: self.do_verify(d.tx)) 48 | d.buttons.insert(0, b) 49 | self.transaction_dialog_update(d) 50 | 51 | def get_my_addr(self, tx): 52 | """Returns the address for given tx which can be used to request 53 | instant confirmation verification from GreenAddress""" 54 | 55 | for addr, _ in tx.get_outputs(): 56 | if self.wallet.is_mine(addr): 57 | return addr 58 | return None 59 | 60 | @hook 61 | def transaction_dialog_update(self, d): 62 | if d.tx.is_complete() and self.get_my_addr(d.tx): 63 | self.verify_button.show() 64 | else: 65 | self.verify_button.hide() 66 | 67 | def do_verify(self, tx): 68 | # 1. get the password and sign the verification request 69 | password = None 70 | if self.wallet.use_encryption: 71 | msg = _('GreenAddress requires your signature to verify that transaction is instant.\n' 72 | 'Please enter your password to sign a verification request.') 73 | password = self.win.password_dialog(msg) 74 | if not password: 75 | return 76 | try: 77 | self.verify_button.setText(_('Verifying...')) 78 | QApplication.processEvents() # update the button label 79 | 80 | addr = self.get_my_addr(tx) 81 | message = "Please verify if %s is GreenAddress instant confirmed" % tx.hash() 82 | sig = self.wallet.sign_message(addr, message, password) 83 | sig = base64.b64encode(sig) 84 | 85 | # 2. send the request 86 | response = requests.request("GET", ("https://greenaddress.it/verify/?signature=%s&txhash=%s" % (urllib.quote(sig), tx.hash())), 87 | headers = {'User-Agent': 'Electrum'}) 88 | response = response.json() 89 | 90 | # 3. display the result 91 | if response.get('verified'): 92 | QMessageBox.information(None, _('Verification successful!'), 93 | _('%s is covered by GreenAddress instant confirmation') % (tx.hash()), _('OK')) 94 | else: 95 | QMessageBox.critical(None, _('Verification failed!'), 96 | _('%s is not covered by GreenAddress instant confirmation') % (tx.hash()), _('OK')) 97 | except BaseException as e: 98 | import traceback 99 | traceback.print_exc(file=sys.stdout) 100 | QMessageBox.information(None, _('Error'), str(e), _('OK')) 101 | finally: 102 | self.verify_button.setText(self.button_label) 103 | -------------------------------------------------------------------------------- /lib/asn1tinydecoder.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 2 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program; if not, write to the Free Software 13 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 14 | # MA 02110-1301, USA. 15 | 16 | 17 | # This is a simple and fast ASN1 decoder without external libraries. 18 | # 19 | # In order to browse through the ASN1 structure you need only 3 20 | # functions allowing you to navigate: 21 | # asn1_node_root(...), asn1_node_next(...) and asn1_node_first_child(...) 22 | # 23 | ####################### BEGIN ASN1 DECODER ############################ 24 | 25 | # Author: Jens Getreu, 8.11.2014 26 | 27 | ##### NAVIGATE 28 | 29 | # The following 4 functions are all you need to parse an ASN1 structure 30 | 31 | # gets the first ASN1 structure in der 32 | def asn1_node_root(der): 33 | return asn1_read_length(der,0) 34 | 35 | # gets the next ASN1 structure following (ixs,ixf,ixl) 36 | def asn1_node_next(der, (ixs,ixf,ixl)): 37 | return asn1_read_length(der,ixl+1) 38 | 39 | # opens the container (ixs,ixf,ixl) and returns the first ASN1 inside 40 | def asn1_node_first_child(der, (ixs,ixf,ixl)): 41 | if ord(der[ixs]) & 0x20 != 0x20: 42 | raise ValueError('Error: can only open constructed types. ' 43 | +'Found type: 0x'+der[ixs].encode("hex")) 44 | return asn1_read_length(der,ixf) 45 | 46 | # is true if one ASN1 chunk is inside another chunk. 47 | def asn1_node_is_child_of((ixs,ixf,ixl), (jxs,jxf,jxl)): 48 | return ( (ixf <= jxs ) and (jxl <= ixl) ) or \ 49 | ( (jxf <= ixs ) and (ixl <= jxl) ) 50 | 51 | ##### END NAVIGATE 52 | 53 | 54 | 55 | ##### ACCESS PRIMITIVES 56 | 57 | # get content and verify type byte 58 | def asn1_get_value_of_type(der,(ixs,ixf,ixl),asn1_type): 59 | asn1_type_table = { 60 | 'BOOLEAN': 0x01, 'INTEGER': 0x02, 61 | 'BIT STRING': 0x03, 'OCTET STRING': 0x04, 62 | 'NULL': 0x05, 'OBJECT IDENTIFIER': 0x06, 63 | 'SEQUENCE': 0x70, 'SET': 0x71, 64 | 'PrintableString': 0x13, 'IA5String': 0x16, 65 | 'UTCTime': 0x17, 'ENUMERATED': 0x0A, 66 | 'UTF8String': 0x0C, 'PrintableString': 0x13, 67 | } 68 | if asn1_type_table[asn1_type] != ord(der[ixs]): 69 | raise ValueError('Error: Expected type was: '+ 70 | hex(asn1_type_table[asn1_type])+ 71 | ' Found: 0x'+der[ixs].encode('hex')) 72 | return der[ixf:ixl+1] 73 | 74 | # get value 75 | def asn1_get_value(der,(ixs,ixf,ixl)): 76 | return der[ixf:ixl+1] 77 | 78 | # get type+length+value 79 | def asn1_get_all(der,(ixs,ixf,ixl)): 80 | return der[ixs:ixl+1] 81 | 82 | ##### END ACCESS PRIMITIVES 83 | 84 | 85 | 86 | ##### HELPER FUNCTIONS 87 | 88 | # converter 89 | def bitstr_to_bytestr(bitstr): 90 | if bitstr[0] != '\x00': 91 | raise ValueError('Error: only 00 padded bitstr can be converted to bytestr!') 92 | return bitstr[1:] 93 | 94 | # converter 95 | def bytestr_to_int(s): 96 | # converts bytestring to integer 97 | i = 0 98 | for char in s: 99 | i <<= 8 100 | i |= ord(char) 101 | return i 102 | 103 | # ix points to the first byte of the asn1 structure 104 | # Returns first byte pointer, first content byte pointer and last. 105 | def asn1_read_length(der,ix): 106 | first= ord(der[ix+1]) 107 | if (ord(der[ix+1]) & 0x80) == 0: 108 | length = first 109 | ix_first_content_byte = ix+2 110 | ix_last_content_byte = ix_first_content_byte + length -1 111 | else: 112 | lengthbytes = first & 0x7F 113 | length = bytestr_to_int(der[ix+2:ix+2+lengthbytes]) 114 | ix_first_content_byte = ix+2+lengthbytes 115 | ix_last_content_byte = ix_first_content_byte + length -1 116 | return (ix,ix_first_content_byte,ix_last_content_byte) 117 | 118 | ##### END HELPER FUNCTIONS 119 | 120 | 121 | ####################### END ASN1 DECODER ############################ 122 | 123 | 124 | -------------------------------------------------------------------------------- /plugins/plot.py: -------------------------------------------------------------------------------- 1 | from PyQt4.QtGui import * 2 | from electrum_xvg.plugins import BasePlugin, hook 3 | from electrum_xvg.i18n import _ 4 | 5 | 6 | import datetime 7 | from electrum_xvg.util import format_satoshis 8 | 9 | 10 | try: 11 | import matplotlib.pyplot as plt 12 | import matplotlib.dates as md 13 | from matplotlib.patches import Ellipse 14 | from matplotlib.offsetbox import AnchoredOffsetbox, TextArea, DrawingArea, HPacker 15 | flag_matlib=True 16 | except: 17 | flag_matlib=False 18 | 19 | 20 | 21 | 22 | 23 | class Plugin(BasePlugin): 24 | 25 | def is_available(self): 26 | if flag_matlib: 27 | return True 28 | else: 29 | return False 30 | 31 | @hook 32 | def init_qt(self, gui): 33 | self.win = gui.main_window 34 | 35 | @hook 36 | def export_history_dialog(self, d,hbox): 37 | self.wallet = d.wallet 38 | history = self.wallet.get_history() 39 | if len(history) > 0: 40 | b = QPushButton(_("Preview plot")) 41 | hbox.addWidget(b) 42 | b.clicked.connect(lambda: self.do_plot(self.wallet, history)) 43 | else: 44 | b = QPushButton(_("No history to plot")) 45 | hbox.addWidget(b) 46 | 47 | 48 | def do_plot(self, wallet, history): 49 | balance_Val=[] 50 | fee_val=[] 51 | value_val=[] 52 | datenums=[] 53 | unknown_trans = 0 54 | pending_trans = 0 55 | counter_trans = 0 56 | balance = 0 57 | for item in history: 58 | tx_hash, confirmations, value, timestamp = item 59 | balance += value 60 | if confirmations: 61 | if timestamp is not None: 62 | try: 63 | datenums.append(md.date2num(datetime.datetime.fromtimestamp(timestamp))) 64 | balance_string = format_satoshis(balance, False) 65 | balance_Val.append(float((format_satoshis(balance,False)))*1000.0) 66 | except [RuntimeError, TypeError, NameError] as reason: 67 | unknown_trans += 1 68 | pass 69 | else: 70 | unknown_trans += 1 71 | else: 72 | pending_trans += 1 73 | 74 | value_string = format_satoshis(value, True) 75 | value_val.append(float(value_string)*1000.0) 76 | 77 | if tx_hash: 78 | label, is_default_label = wallet.get_label(tx_hash) 79 | label = label.encode('utf-8') 80 | else: 81 | label = "" 82 | 83 | 84 | f, axarr = plt.subplots(2, sharex=True) 85 | 86 | plt.subplots_adjust(bottom=0.2) 87 | plt.xticks( rotation=25 ) 88 | ax=plt.gca() 89 | x=19 90 | test11="Unknown transactions = "+str(unknown_trans)+" Pending transactions = "+str(pending_trans)+" ." 91 | box1 = TextArea(" Test : Number of pending transactions", textprops=dict(color="k")) 92 | box1.set_text(test11) 93 | 94 | 95 | box = HPacker(children=[box1], 96 | align="center", 97 | pad=0.1, sep=15) 98 | 99 | anchored_box = AnchoredOffsetbox(loc=3, 100 | child=box, pad=0.5, 101 | frameon=True, 102 | bbox_to_anchor=(0.5, 1.02), 103 | bbox_transform=ax.transAxes, 104 | borderpad=0.5, 105 | ) 106 | 107 | 108 | ax.add_artist(anchored_box) 109 | 110 | 111 | plt.ylabel('mXVG') 112 | plt.xlabel('Dates') 113 | xfmt = md.DateFormatter('%Y-%m-%d') 114 | ax.xaxis.set_major_formatter(xfmt) 115 | 116 | 117 | axarr[0].plot(datenums,balance_Val,marker='o',linestyle='-',color='blue',label='Balance') 118 | axarr[0].legend(loc='upper left') 119 | axarr[0].set_title('History Transactions') 120 | 121 | 122 | xfmt = md.DateFormatter('%Y-%m-%d') 123 | ax.xaxis.set_major_formatter(xfmt) 124 | axarr[1].plot(datenums,value_val,marker='o',linestyle='-',color='green',label='Value') 125 | 126 | 127 | axarr[1].legend(loc='upper left') 128 | # plt.annotate('unknown transaction = %d \n pending transactions = %d' %(unknown_trans,pending_trans),xy=(0.7,0.05),xycoords='axes fraction',size=12) 129 | plt.show() 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Electrum-XVG - Lightweight Verge client 2 | ------------------------------------------------ 3 | ![Electrum-XVG](https://raw.githubusercontent.com/vergecurrency/electrum-xvg/master/electrumlogo.png) 4 | 5 | [![Build Status](https://travis-ci.org/vergecurrency/electrum-xvg.svg?branch=master)](https://travis-ci.org/vergecurrency/electrum-xvg) 6 | 7 | 8 | Licence: GNU GPL v3 9 | 10 | _Authors: Sunerok, Bitspill, Whit3water & CryptoRekt_ 11 | 12 | ## Language: 13 | Python 14 | 15 | ## Homepage: 16 | https://Vergecurrency.com/ 17 | 18 | 19 | 20 | # Warning 21 | 22 | Don't forget to copy your randomly generated seedphrase, this will act as your private key. They are not stored on our servers, so please don't lose them! 23 | 24 | 25 | 26 | # Installation 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ## Linux: 35 | 36 | ``` 37 | sudo apt-get install git pyqt4-dev-tools python-pip python-dev python-slowaes python-pip 38 | ``` 39 | 40 | ``` 41 | sudo pip install pyasn1 pyasn1-modules pbkdf2 tlslite qrcode 42 | ``` 43 | 44 | ``` 45 | git clone https://github.com/vergecurrency/electrum-xvg-tor && cd electrum-xvg-tor 46 | ``` 47 | 48 | ``` 49 | pyrcc4 icons.qrc -o gui/qt/icons_rc.py 50 | ``` 51 | 52 | ``` 53 | sudo python setup.py install 54 | ``` 55 | 56 | ``` 57 | chmod +x ./electrum-xvg 58 | ``` 59 | 60 | ## To run Electrum from this directory, just do: 61 | 62 | ``` 63 | ./electrum-xvg 64 | ``` 65 | 66 | ## To start Electrum from your web browser, see: 67 | 68 | http://electrum-verge.xyz/Verge_URIs.html 69 | 70 | 71 | ## To update your copy of the electrum client: 72 | 73 | ``` 74 | cd electrum-xvg 75 | ``` 76 | ``` 77 | git pull 78 | ``` 79 | ``` 80 | sudo python setup.py install 81 | ``` 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ## Windows 90 | 91 | 92 | ##### 1.) Download this repo as a zip and extract it to where you would like it to run from: 93 | https://github.com/vergecurrency/electrum-xvg/archive/master.zip 94 | 95 | ##### 2.) Download and install python 2.7 for windows here: 96 | https://www.python.org/ftp/python/2.7.10/python-2.7.10.msi 97 | 98 | ##### 3.) Download and install Microsoft Visual C++ Compiler for Python 2.7 here: 99 | https://www.microsoft.com/en-us/download/details.aspx?id=44266 100 | 101 | ##### 4.) Download and install python qt4: 102 | http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe 103 | 104 | ##### 5.) Launch MS Visual Studio command prompt (32 or 64 bit) 105 | 106 | ##### 6.) cd into the directory electrum-xvg-tor-master and execute the following: 107 | 108 | ``` 109 | pyrcc4 icons.qrc -o gui/qt/icons_rc.py 110 | ``` 111 | 112 | ``` 113 | python -m pip install --upgrade pip 114 | ``` 115 | 116 | ``` 117 | python -m pip install pyasn1 pyasn1-modules pbkdf2 tlslite qrcode ecdsa ltc_scrypt 118 | ``` 119 | 120 | ``` 121 | python setup.py install 122 | ``` 123 | 124 | ``` 125 | python electrum-xvg 126 | ``` 127 | 128 | 129 | 130 | 131 | 132 | 133 | ## How Official Packages Are Created. 134 | 135 | ``` 136 | python mki18n.py 137 | 138 | pyrcc4 icons.qrc -o gui/qt/icons_rc.py 139 | 140 | python setup.py sdist --format=zip,gztar 141 | ``` 142 | ### On Mac OS X: 143 | 144 | #### On port based installs 145 | ``` 146 | sudo python setup-release.py py2app 147 | ``` 148 | #### On brew installs 149 | ``` 150 | ARCHFLAGS="-arch i386 -arch x86_64" sudo python setup-release.py py2app --includes sip 151 | 152 | sudo hdiutil create -fs HFS+ -volname "Electrum-XVG" -srcfolder dist/Electrum-XVG.app dist/electrum-xvg-VERSION-macosx.dmg 153 | ``` 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | ## Alternate official build method 162 | 163 | On Linux: 164 | 165 | ```python setup.py sdist --format=gztar``` 166 | 167 | On Windows: 168 | 169 | ```export VERSION=2.0.0``` 170 | 171 | ```pyinstaller windows.spec``` 172 | 173 | ```zip -r dist/verge-electrum-$VERSION-win.zip dist/verge-electrum.exe``` 174 | 175 | On Mac OS X: 176 | 177 | ```export VERSION=2.0.0``` 178 | 179 | ```pyinstaller macosx.spec``` 180 | 181 | ```sudo hdiutil create -fs HFS+ -volname "Verge Electrum" -srcfolder "dist/VERGE Electrum.app" dist/VERGE-electrum-$VERSION-mac.dmg``` 182 | 183 | 184 | 185 | # Verge Electrum Server List 186 | 187 | - electrum-verge.xyz 188 | - electrum-xvg.stream 189 | 190 | [![Visit our IRC Chat!](https://kiwiirc.com/buttons/irc.freenode.net/VERGE.png)](https://kiwiirc.com/client/irc.freenode.net/?nick=xvg|?&theme=cli#VERGE) 191 | -------------------------------------------------------------------------------- /plugins/audio_modem.py: -------------------------------------------------------------------------------- 1 | from electrum_xvg.plugins import BasePlugin, hook 2 | from electrum_xvg_gui.qt.util import WaitingDialog, EnterButton 3 | from electrum_xvg.util import print_msg, print_error 4 | from electrum_xvg.i18n import _ 5 | 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | import traceback 10 | import zlib 11 | import json 12 | from io import BytesIO 13 | import sys 14 | import platform 15 | 16 | try: 17 | import amodem.audio 18 | import amodem.main 19 | import amodem.config 20 | print_error('Audio MODEM is available.') 21 | amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr)) 22 | amodem.log.setLevel(amodem.logging.INFO) 23 | except ImportError: 24 | amodem = None 25 | print_error('Audio MODEM is not found.') 26 | 27 | 28 | class Plugin(BasePlugin): 29 | 30 | def __init__(self, config, name): 31 | BasePlugin.__init__(self, config, name) 32 | if self.is_available(): 33 | self.modem_config = amodem.config.slowest() 34 | self.library_name = { 35 | 'Linux': 'libportaudio.so' 36 | }[platform.system()] 37 | 38 | def is_available(self): 39 | return amodem is not None 40 | 41 | def requires_settings(self): 42 | return True 43 | 44 | def settings_widget(self, window): 45 | return EnterButton(_('Settings'), self.settings_dialog) 46 | 47 | def settings_dialog(self): 48 | d = QDialog() 49 | d.setWindowTitle("Settings") 50 | 51 | layout = QGridLayout(d) 52 | layout.addWidget(QLabel(_('Bit rate [kbps]: ')), 0, 0) 53 | 54 | bitrates = list(sorted(amodem.config.bitrates.keys())) 55 | 56 | def _index_changed(index): 57 | bitrate = bitrates[index] 58 | self.modem_config = amodem.config.bitrates[bitrate] 59 | 60 | combo = QComboBox() 61 | combo.addItems(map(str, bitrates)) 62 | combo.currentIndexChanged.connect(_index_changed) 63 | layout.addWidget(combo, 0, 1) 64 | 65 | ok_button = QPushButton(_("OK")) 66 | ok_button.clicked.connect(d.accept) 67 | layout.addWidget(ok_button, 1, 1) 68 | 69 | return bool(d.exec_()) 70 | 71 | @hook 72 | def transaction_dialog(self, dialog): 73 | b = QPushButton() 74 | b.setIcon(QIcon(":icons/speaker.png")) 75 | 76 | def handler(): 77 | blob = json.dumps(dialog.tx.as_dict()) 78 | self.sender = self._send(parent=dialog, blob=blob) 79 | self.sender.start() 80 | b.clicked.connect(handler) 81 | dialog.buttons.insert(0, b) 82 | 83 | @hook 84 | def scan_text_edit(self, parent): 85 | def handler(): 86 | self.receiver = self._recv(parent=parent) 87 | self.receiver.start() 88 | parent.addButton(':icons/microphone.png', handler, _("Read from microphone")) 89 | 90 | @hook 91 | def show_text_edit(self, parent): 92 | def handler(): 93 | blob = str(parent.toPlainText()) 94 | self.sender = self._send(parent=parent, blob=blob) 95 | self.sender.start() 96 | parent.addButton(':icons/speaker.png', handler, _("Send to speaker")) 97 | 98 | def _audio_interface(self): 99 | interface = amodem.audio.Interface(config=self.modem_config) 100 | return interface.load(self.library_name) 101 | 102 | def _send(self, parent, blob): 103 | def sender_thread(): 104 | try: 105 | with self._audio_interface() as interface: 106 | src = BytesIO(blob) 107 | dst = interface.player() 108 | amodem.main.send(config=self.modem_config, src=src, dst=dst) 109 | except Exception: 110 | traceback.print_exc() 111 | 112 | print_msg('Sending:', repr(blob)) 113 | blob = zlib.compress(blob) 114 | 115 | kbps = self.modem_config.modem_bps / 1e3 116 | msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps) 117 | return WaitingDialog(parent=parent, message=msg, run_task=sender_thread) 118 | 119 | def _recv(self, parent): 120 | def receiver_thread(): 121 | try: 122 | with self._audio_interface() as interface: 123 | src = interface.recorder() 124 | dst = BytesIO() 125 | amodem.main.recv(config=self.modem_config, src=src, dst=dst) 126 | return dst.getvalue() 127 | except Exception: 128 | traceback.print_exc() 129 | 130 | def on_success(blob): 131 | if blob: 132 | blob = zlib.decompress(blob) 133 | print_msg('Received:', repr(blob)) 134 | parent.setText(blob) 135 | 136 | kbps = self.modem_config.modem_bps / 1e3 137 | msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps) 138 | return WaitingDialog(parent=parent, message=msg, 139 | run_task=receiver_thread, on_success=on_success) 140 | 141 | 142 | -------------------------------------------------------------------------------- /gui/qt/version_getter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2012 thomasv@gitorious 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import threading, re, socket 20 | import webbrowser 21 | import requests 22 | 23 | from PyQt4.QtGui import * 24 | from PyQt4.QtCore import * 25 | import PyQt4.QtCore as QtCore 26 | 27 | from electrum_xvg.i18n import _ 28 | from electrum_xvg import ELECTRUM_VERSION, print_error 29 | 30 | class VersionGetter(threading.Thread): 31 | 32 | def __init__(self, label): 33 | threading.Thread.__init__(self) 34 | self.label = label 35 | self.daemon = True 36 | 37 | def run(self): 38 | try: 39 | res = requests.request("GET", "http://electrum-verge.xyz/version") 40 | except: 41 | print_error("Could not retrieve version information") 42 | return 43 | 44 | if res.status_code == 200: 45 | latest_version = res.text 46 | latest_version = latest_version.replace("\n","") 47 | if(re.match('^\d+(\.\d+)*$', latest_version)): 48 | self.label.callback(latest_version) 49 | 50 | class UpdateLabel(QLabel): 51 | def __init__(self, config, sb): 52 | QLabel.__init__(self) 53 | self.new_version = False 54 | self.sb = sb 55 | self.config = config 56 | self.current_version = ELECTRUM_VERSION 57 | self.connect(self, QtCore.SIGNAL('new_electrum_version'), self.new_electrum_version) 58 | # prevent HTTP leaks if a proxy is set 59 | if self.config.get('proxy'): 60 | return 61 | VersionGetter(self).start() 62 | 63 | def callback(self, version): 64 | self.latest_version = version 65 | if(self.compare_versions(self.latest_version, self.current_version) == 1): 66 | latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION) 67 | if(self.compare_versions(self.latest_version, latest_seen) == 1): 68 | self.new_version = True 69 | self.emit(QtCore.SIGNAL('new_electrum_version')) 70 | 71 | def new_electrum_version(self): 72 | if self.new_version: 73 | self.setText(_("New version available") + ": " + self.latest_version) 74 | self.sb.insertPermanentWidget(1, self) 75 | 76 | def compare_versions(self, version1, version2): 77 | def normalize(v): 78 | return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")] 79 | try: 80 | return cmp(normalize(version1), normalize(version2)) 81 | except: 82 | return 0 83 | 84 | def ignore_this_version(self): 85 | self.setText("") 86 | self.config.set_key("last_seen_version", self.latest_version, True) 87 | QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again.")) 88 | self.dialog.done(0) 89 | 90 | def ignore_all_version(self): 91 | self.setText("") 92 | self.config.set_key("last_seen_version", "9.9.9", True) 93 | QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown.")) 94 | self.dialog.done(0) 95 | 96 | def open_website(self): 97 | webbrowser.open("http://electrum-verge.xyz/download.html") 98 | self.dialog.done(0) 99 | 100 | def mouseReleaseEvent(self, event): 101 | dialog = QDialog(self) 102 | dialog.setWindowTitle(_('Electrum-XVG update')) 103 | dialog.setModal(1) 104 | 105 | main_layout = QGridLayout() 106 | main_layout.addWidget(QLabel(_("A new version of Electrum-XVG is available:")+" " + self.latest_version), 0,0,1,3) 107 | 108 | ignore_version = QPushButton(_("Ignore this version")) 109 | ignore_version.clicked.connect(self.ignore_this_version) 110 | 111 | ignore_all_versions = QPushButton(_("Ignore all versions")) 112 | ignore_all_versions.clicked.connect(self.ignore_all_version) 113 | 114 | open_website = QPushButton(_("Goto download page")) 115 | open_website.clicked.connect(self.open_website) 116 | 117 | main_layout.addWidget(ignore_version, 1, 0) 118 | main_layout.addWidget(ignore_all_versions, 1, 1) 119 | main_layout.addWidget(open_website, 1, 2) 120 | 121 | dialog.setLayout(main_layout) 122 | 123 | self.dialog = dialog 124 | 125 | if not dialog.exec_(): return 126 | -------------------------------------------------------------------------------- /lib/websockets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2015 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import threading, Queue, os, json, time 20 | from collections import defaultdict 21 | try: 22 | from SimpleWebSocketServer import WebSocket, SimpleSSLWebSocketServer 23 | except ImportError: 24 | print "install SimpleWebSocketServer" 25 | sys.exit() 26 | 27 | import util, daemon 28 | 29 | request_queue = Queue.Queue() 30 | 31 | class ElectrumWebSocket(WebSocket): 32 | 33 | def handleMessage(self): 34 | assert self.data[0:3] == 'id:' 35 | print "message received", self.data 36 | request_id = self.data[3:] 37 | request_queue.put((self, request_id)) 38 | 39 | def handleConnected(self): 40 | util.print_error("connected", self.address) 41 | 42 | def handleClose(self): 43 | util.print_error("closed", self.address) 44 | 45 | 46 | 47 | class WsClientThread(daemon.ClientThread): 48 | 49 | def __init__(self, config, server): 50 | util.DaemonThread.__init__(self) 51 | self.server = server 52 | self.config = config 53 | self.response_queue = Queue.Queue() 54 | self.server.add_client(self) 55 | self.subscriptions = defaultdict(list) 56 | self.sub_ws = defaultdict(list) 57 | self.counter = 0 58 | 59 | def make_request(self, request_id): 60 | # read json file 61 | rdir = self.config.get('requests_dir') 62 | n = os.path.join(rdir, request_id + '.json') 63 | with open(n) as f: 64 | s = f.read() 65 | d = json.loads(s) 66 | addr = d.get('address') 67 | amount = d.get('amount') 68 | return addr, amount 69 | 70 | def reading_thread(self): 71 | while self.is_running(): 72 | try: 73 | ws, request_id = request_queue.get() 74 | except Queue.Empty: 75 | continue 76 | try: 77 | addr, amount = self.make_request(request_id) 78 | except: 79 | continue 80 | method = 'blockchain.address.subscribe' 81 | params = [addr] 82 | request = {'method':method, 'params':params, 'id':self.counter} 83 | self.subscriptions[method].append(params) 84 | self.sub_ws[self.counter] = ws, amount, request 85 | self.counter += 1 86 | self.server.send_request(self, request) 87 | 88 | def run(self): 89 | threading.Thread(target=self.reading_thread).start() 90 | while self.is_running(): 91 | try: 92 | r = self.response_queue.get(timeout=0.1) 93 | except Queue.Empty: 94 | continue 95 | id = r.get('id') 96 | if id is None: 97 | method = r.get('method') 98 | params = r.get('params') 99 | else: 100 | ws, amount, rr = self.sub_ws[id] 101 | method = rr.get('method') 102 | params = rr.get('params') 103 | 104 | result = r.get('result') 105 | 106 | if method == 'blockchain.address.subscribe': 107 | util.print_error('response', r) 108 | if result is not None: 109 | request = {'method':'blockchain.address.get_balance', 'params':params, 'id':self.counter} 110 | self.server.send_request(self, request) 111 | self.sub_ws[self.counter] = ws, amount, request 112 | self.counter += 1 113 | 114 | if r.get('method') == 'blockchain.address.get_balance': 115 | util.print_error('response', r) 116 | if not ws.closed: 117 | if sum(result.values()) >=amount: 118 | ws.sendMessage(unicode('paid')) 119 | 120 | self.server.remove_client(self) 121 | 122 | 123 | class WebSocketServer(threading.Thread): 124 | 125 | def __init__(self, config, ns): 126 | threading.Thread.__init__(self) 127 | self.config = config 128 | self.net_server = ns 129 | self.daemon = True 130 | 131 | def run(self): 132 | t = WsClientThread(self.config, self.net_server) 133 | t.start() 134 | 135 | host = self.config.get('websocket_server') 136 | port = self.config.get('websocket_port', 9999) 137 | certfile = self.config.get('ssl_chain') 138 | keyfile = self.config.get('ssl_privkey') 139 | self.server = SimpleSSLWebSocketServer(host, port, ElectrumWebSocket, certfile, keyfile) 140 | self.server.serveforever() 141 | 142 | 143 | -------------------------------------------------------------------------------- /pubkeys/ThomasV.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1.4.11 (GNU/Linux) 3 | 4 | mQINBE34z9wBEACT31iv9i8Jx/6MhywWmytSGWojS7aJwGiH/wlHQcjeleGnW8HF 5 | Z8R73ICgvpcWM2mfx0R/YIzRIbbT+E2PJ+iTw0BTGU7irRKrdLXReH130K3bDg05 6 | +DaYFf0qY/t/e4WDXRVnr8L28hRQ4/9SnvgNcUBzd0IDOUiicZvhkIm6TikL+xSr 7 | 5Gcn/PaJFS1VpbWklXaLfvci9l4fINL3vMyLiV/75b1laSP5LPEvbfd7W9T6HeCX 8 | 63epTHmGBmB4ycGqkwOgq6NxxaLHxRWlfylRXRWpI/9B66x8vOUd70jjjyqG+mhQ 9 | +1+qfydeSW3R6Dr2vzDyDrBXbdVMTL2VFXqNG03FYcv191H7zJgPlJGyaO4IZxj+ 10 | +O8LaoJuFqAr8/+NX4K4UfWPvcrJ2i+eUkbkDJHo4GQK712/DtSLAA+YGeIF9HAn 11 | zKvaMkZDMwY8z3gBSE/jMV2IcONvpUUOFPQgTmCvlJZAFTPeLTDv+HX8GfhmjAJY 12 | T5rTcvyPEkoq9fWhQiFp5HRpYrD36yLVrpznh2Mx7B1Iy8Rq/7avadwVn87C6scJ 13 | ouPu+0PF3IeVmYfCScbfxtx1FaEczm8wGBlaB/jkDEhx0RR8PYKKTIEM7T2LH2p6 14 | s/+Ei4V7mqkcveF/DPnScMPBprJwuoGNFdx2qKmgCKLycWlSnwec+hdyTwARAQAB 15 | tBlUaG9tYXNWIDx0aG9tYXN2MUBnbXguZGU+iQI4BBMBAgAiBQJN+M/cAhsDBgsJ 16 | CAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRAr1YJLf5Rw5hlhD/9T4I/sBCleS9nH 17 | njTJqcOnG28c9C3CRYIizjEui/pKmXz9fB1N9QrCaruPUQx2UacDVCl6dKxac+7s 18 | s3/a6lsjaRn0/2OM/sCVLScyxNPNPQs2b6jkodSNPIM8zv51g+flhwtfrO6h6B4j 19 | IhZgSjFdvqtZd5jaly9rA0uMX045CC4K6HGnq8n4F2p31z0L0LaHBf5EcsCM0MMp 20 | QVkY0aUrNg9uVMGXBHn3osHnOtQaODqcIbpa/OG+Tlt6pVOiDJ7i8TkpQKT7sOaM 21 | VdL//TEoDIOC7qVCN82q2q/gtiBXbziaERVs/eU0O52aX5qUhXu3VIjXTp/riRim 22 | R/f9BPB1dgDZbF2aPZ/rJm26v82ft7gP1Sf52E9MrAaZATTfI0/TUHXeBzN93EA9 23 | xb6/ENAMTX74u+NjlynWPD+hl64eBzJ2ionZF1bJFTgBkMfRYnhllvleCjcq9YfX 24 | md5HKCwtxfygBIujUQSwyUzn0f5DbVCJ7/B19bKdvHGSSBgBEjxqXWQskm2wc0In 25 | ww63goZAGDQliKhIT8xnwOBbLkqSobq4tD9zpQyxvMA2rhy7/gfFRp7TTak7MZHf 26 | lTJ37S5LvcWHm/ccWUZDUN7akoEDc+m6jX3uIEPMD3PQvcHhWv0amco3zDr1qb/+ 27 | rXM7TJKd7DPX0E2dRzKu6aYRMTbklbkCDQRN+M/cARAAvktgmJflLg8bz3VMyKF6 28 | OHkpWBzfr9HOBZgmQTborznm35Z0BrqykcpHVGalNOydpNdoL9s3Elmr7S+L0YqX 29 | D3i3AS567S8KeKKenLoAV7J294KL6p2vldDiHHUNjXWSe0YEvbAw62tnigB4Wee0 30 | dhwAxhowFjmnyB3dZVHZ2Ai+k5x7NAEvCGgwec3oD4kRDbtkdyiXAM72Jz63hgC2 31 | XnnYM/XEptxPgPUkPQnpboVtQkSU5dF5KM0YK5ItllOwLMyDQ/pEfBZQq0O9Eqsr 32 | 8zc2H8vYSxbmYBCdimzpr6HJWRV32QuyiC0c8TjG/fenNsMHliP7bOp2NhSo784N 33 | +Q/4eM1G/KO4cvpbduNToWe4lBdfiWWySWziGUoM9rBrdy5aex1p+i4Bk7FbAGUb 34 | KxFnsFoc4URM6UB5Q3URh8WtyRx+RjNNfs0MDRL31pfvfTP9z1eueoznY470utIV 35 | Gdhn4rczzPYUW+j39m2qpMMfT+gf5rLQLC2jBKi9lSiWMBDzOaaLwK08MUeDiytp 36 | 7xjFbh962ftC0/qs5VHBALI/XYvO0LuhgY55y1Tkb0aRUO5s1bjxW57HT+6k/1E5 37 | GMo3xq0GuhJyaiZHmFKHGaB0kHLgj9MORUVtmr+FE9JMAeCdyIQLrtNiGzPNc/ed 38 | Ofn8CHTAIYJ3cd+I1fobsx0AEQEAAYkCHwQYAQIACQUCTfjP3AIbDAAKCRAr1YJL 39 | f5Rw5olED/90sdEGN4CQ1tGxjkY1NZRnNevdULm82qnCk/2srhBMCamXeJTkX7Cy 40 | AwbvyJLZc5X6KNkDwq+KyGV61Amg0zFLU9TiwDhjfiFhvrWm0ez4+bA2lx2CMBf6 41 | YKH3FDTzXobIhpe3G3FpwklbcrTt+ooiQmMhI87JMtJ37CXUu1ETZ8Drukyakwcb 42 | y35E+rV2n97eqnovuNzpfdT5ufabu7ZyARVu4CdJooagxIyex3vUu1/Vvr8PC9nP 43 | 8cG20SByBKY+V4dRXIEPgsOtyNMMrvN0QzDtv9w8Ge7oKZNyiAUSq8Hif1ih2j7i 44 | KPsCd1RPPgmPWny8WWce+FGDzYwN8ZQ8n66MGNthdE6z5YCZoJDxOCNnd0AU0Ws6 45 | oBti7pCVjbF+7u+P6u5zoCt2YGs6WwkWY2zEVEl+B1S6DkkUhQ3vsZuOKweNZ6bx 46 | xjezO7ISR7wfamLzTcIC5W2Rqg2Y0MewQGJ5+3fhmjRX4D+rbCSLHdL2/JGM4qqE 47 | atbfSrGdXpkV7G55a47eo5DRlTn9MgfSR+3afjn9F7F4Gr8innbMTW8S9ORCYEJ8 48 | IZmapO771GjyqyN5zFU3HJU+XCGWKavKJD509olho5J+LAQQyHYzJXlzq3qo32fq 49 | MV2IIs6WaYdkUDzEOh2yh9ASaW/4j/HIRnudrqfFullW48LcHQkW1pkBDQRQ9YM4 50 | AQgAxlafMFTJNL4lE0dxQcwn3RhlC2HMoihTXRpSsTkcq3ViRev4OSbmD1NDVwkS 51 | e4al+IaKToiA/qMRO6f7z/4v0pugesWTKvGyfj4idofGap6Lq4C/LZAKpQ1fsy2p 52 | 2vl3kD7D4ST3gEQEA7ZeUIUG71ptInTMndc7lnbeDlSGJ4Se8OzMNAnVJgW5E3NT 53 | 9tyfL8Kossrh6ldyYppDSS5uL8lhhYoOhJYETG1eWFnIuNTkx98fKpFhxXQdYdDg 54 | z/9vGwcKVVV6pzm66fy7K/WkEj+VboxiC/3DnXpf+8Q9g3Oakvx12GYCVJOsdlpC 55 | 5juOzUqVuq6J3t1tgPNoJCkcFwARAQABtB9BbmltYXppbmcgPGFuaW1hemluZ0Bn 56 | bWFpbC5jb20+iQE4BBMBAgAiBQJQ9YM4AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIe 57 | AQIXgAAKCRAiRTAEaVUG/eYvCACEqNjb2VvGZZ+Ir7qOZej4qyK80iJXnN12v8Zp 58 | IKx03wVU91mHhqeeDZBHKYGqw9ao7eWRRe8BMZ/VixKxpbCJD5noDmY8dpp92cgf 59 | mOctHCXhjzO9mg9Gd42gYvobb9+xjG9plesju9NHRtcd78EtWGMkS4EeqVcJUcPt 60 | JfuQf+cx7CnUb6UXLk6Zq4PK7YE8WTrEcjkZ+nJOsO5VG56N/tVzAUxU21X+P0sp 61 | jFN1pfXq7tmoGEX5Ad6bl3fAzGJUs2YolFkx1emzrAdDdNNbCbZhX+ipjNcRfEP3 62 | qjJVnr0trHdFxjlhULuERsgCaje4dWd3Mptu/FlQjIWMyZ/0iQIcBBABAgAGBQJR 63 | 2qHTAAoJELlvIwCtEcvusaIQAI9QpGtQKCA5j3FOSJTyz8LGKMny5pAG3KXjSmQO 64 | mH+KZveot7Sl2y4AsUq3o0pq2lSGGaDRhbZgMfq6cHWbQ1shVhpTJA4Y0WNa0Op5 65 | ZDvArT1P2628mSXItieu1O4VLCbfjuYGn7XJRtkXAutQUM9xHtBZfCSkhOVdiqjg 66 | 9ltQUwk2K56WOWHTAfKkmpUfLcWWkqpAN8aUqyMCdOBB09bskACYmnENMpx6XFP8 67 | X1QZ849x3NJkcQUyOTc55PDQB5XmOTkomV+WCW95r8nHVd+Jhl552spPUvB6LVrO 68 | ZC6TENk6VzomSuP5SQZafqt5LI/tNaqKk60V4oyPus/UEUusuwJEljtD2lfVIrgE 69 | R+WwVeas809qvb7WExJKBh49SMqFu9Jdk8cn0C0AVTXGKfOuPk0CZsYlSFVBDErC 70 | e1Z6In8yL4Ac/P3etjzkB+875p1gO1b2A/D593V1The7EjsaifmC8XDpI6YKiPHn 71 | 9CYsqWtMOU4Lo6Sf0lVqXj+NdEpSuMPiApsXN7IUf6XSX1YYERkunvAOBgh5wx4P 72 | JLCyfKqFJj5TliY3X42pM0A+b7wFdCsFkNJrvXGtwtzY4r2ElmXYh83kBOYV6lVf 73 | MJYIbIRSjnJvNb3Qf4bGsuYbYYpUki/ba5pzXWDaaWR3NG1ej2KWKR1mg9TaVkEm 74 | yHoluQENBFD1gzgBCADM3jfDYxUQedxYnfyGoEMcUGXg9WPXJ/UunyfpxKGVjD0P 75 | olQonLR94JqpvnmdKjq6kk8ZF4MiIe1IrtpcI5u55ngSt9vJNnotXt26adCNqDIk 76 | oVQHocW18eSYdvZ5Tw0vFEv8XoY+lp1HqI0dql1TkgLSu/le7WksooDqWK//uX3K 77 | vv/GfnfjWlafezFJqB+S1TVNowkfqsiDNO6l9huFcfoVOLY3O/SpeptXqruv7B8J 78 | 5JjCLBryx2Rvj6vtcByGcLdad/TIRb6G/QdkRamxbYoU2pd+g9DQUDHZafT7SrUG 79 | UkEeYVrJlhvpte/78rse2bcJ00W0fqf2nRcen7E3ABEBAAGJAR8EGAECAAkFAlD1 80 | gzgCGwwACgkQIkUwBGlVBv2gEQf/aWH5AeKw3uDrpa1FwbrE8Thiptcj42Wl73jq 81 | kJA/P9lcMbu7WBTFTCwymLb8aZq1HW+qM/bPF605DCFu2TNxZoTUIcbHxI/YOlBi 82 | Zwl3H/mcPcG6pszjTQrqXzecDzraBnfihVEeXVZd/gbFKCBHAHD0eQHT/YG6g0m6 83 | w8rpPPo9Qn4i1QEF7nd7tq5LfBlm0yfy8yrRdVDNF92JxO38YutQw4F+gNRCX7LN 84 | 2hY9pcJm9/GKdONViopNmWREKckPK13mBvFpdMEhGGjDw3nRB0Q6CAGEbIUR4JWW 85 | Ag/NCxe3N9ZKBixs1VoqN3qyYFGFMIctUFJbXVS/uT3FclJZtQ== 86 | =U0io 87 | -----END PGP PUBLIC KEY BLOCK----- 88 | -------------------------------------------------------------------------------- /lib/plugins.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2015 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import traceback 20 | import sys 21 | import os 22 | import imp 23 | import pkgutil 24 | 25 | from util import * 26 | from i18n import _ 27 | from util import print_error, profiler 28 | 29 | plugins = {} 30 | descriptions = [] 31 | loader = None 32 | 33 | def is_available(name, w): 34 | for d in descriptions: 35 | if d.get('name') == name: 36 | break 37 | else: 38 | return False 39 | deps = d.get('requires', []) 40 | for dep, s in deps: 41 | try: 42 | __import__(dep) 43 | except ImportError: 44 | return False 45 | wallet_types = d.get('requires_wallet_type') 46 | if wallet_types: 47 | if w.wallet_type not in wallet_types: 48 | return False 49 | return True 50 | 51 | 52 | @profiler 53 | def init_plugins(config, is_local, gui_name): 54 | global plugins, descriptions, loader 55 | if is_local: 56 | fp, pathname, description = imp.find_module('plugins') 57 | electrum_plugins = imp.load_module('electrum_xvg_plugins', fp, pathname, description) 58 | loader = lambda name: imp.load_source('electrum_xvg_plugins.' + name, os.path.join(pathname, name + '.py')) 59 | else: 60 | electrum_plugins = __import__('electrum_xvg_plugins') 61 | loader = lambda name: __import__('electrum_xvg_plugins.' + name, fromlist=['electrum_xvg_plugins']) 62 | 63 | def constructor(name, storage): 64 | if plugins.get(name) is None: 65 | try: 66 | print_error(_("Loading plugin by constructor:"), name) 67 | p = loader(name) 68 | plugins[name] = p.Plugin(config, name) 69 | except: 70 | print_msg(_("Error: cannot initialize plugin"), name) 71 | return 72 | return plugins[name].constructor(storage) 73 | 74 | def register_wallet_type(name, x, constructor): 75 | import wallet 76 | x += (lambda storage: constructor(name, storage),) 77 | wallet.wallet_types.append(x) 78 | 79 | descriptions = electrum_plugins.descriptions 80 | for item in descriptions: 81 | name = item['name'] 82 | if gui_name not in item.get('available_for', []): 83 | continue 84 | x = item.get('registers_wallet_type') 85 | if x: 86 | register_wallet_type(name, x, constructor) 87 | if not config.get('use_' + name): 88 | continue 89 | try: 90 | p = loader(name) 91 | plugins[name] = p.Plugin(config, name) 92 | except Exception: 93 | print_msg(_("Error: cannot initialize plugin"), name) 94 | traceback.print_exc(file=sys.stdout) 95 | 96 | 97 | hook_names = set() 98 | hooks = {} 99 | 100 | def hook(func): 101 | hook_names.add(func.func_name) 102 | return func 103 | 104 | def run_hook(name, *args): 105 | return _run_hook(name, False, *args) 106 | 107 | def always_hook(name, *args): 108 | return _run_hook(name, True, *args) 109 | 110 | def _run_hook(name, always, *args): 111 | results = [] 112 | f_list = hooks.get(name, []) 113 | for p, f in f_list: 114 | if name == 'load_wallet': 115 | p.wallet = args[0] 116 | if name == 'init_qt': 117 | gui = args[0] 118 | p.window = gui.main_window 119 | if always or p.is_enabled(): 120 | try: 121 | r = f(*args) 122 | except Exception: 123 | print_error("Plugin error") 124 | traceback.print_exc(file=sys.stdout) 125 | r = False 126 | if r: 127 | results.append(r) 128 | if name == 'close_wallet': 129 | p.wallet = None 130 | 131 | if results: 132 | assert len(results) == 1, results 133 | return results[0] 134 | 135 | 136 | class BasePlugin: 137 | 138 | def __init__(self, config, name): 139 | self.name = name 140 | self.config = config 141 | self.wallet = None 142 | # add self to hooks 143 | for k in dir(self): 144 | if k in hook_names: 145 | l = hooks.get(k, []) 146 | l.append((self, getattr(self, k))) 147 | hooks[k] = l 148 | 149 | def close(self): 150 | # remove self from hooks 151 | for k in dir(self): 152 | if k in hook_names: 153 | l = hooks.get(k, []) 154 | l.remove((self, getattr(self, k))) 155 | hooks[k] = l 156 | 157 | def print_error(self, *msg): 158 | print_error("[%s]"%self.name, *msg) 159 | 160 | def requires_settings(self): 161 | return False 162 | 163 | def enable(self): 164 | self.set_enabled(True) 165 | return True 166 | 167 | def disable(self): 168 | self.set_enabled(False) 169 | return True 170 | 171 | def init_qt(self, gui): pass 172 | 173 | @hook 174 | def load_wallet(self, wallet, window): pass 175 | 176 | @hook 177 | def close_wallet(self): pass 178 | 179 | #def init(self): pass 180 | 181 | def is_enabled(self): 182 | return self.is_available() and self.config.get('use_'+self.name) is True 183 | 184 | def is_available(self): 185 | return True 186 | 187 | def set_enabled(self, enabled): 188 | self.config.set_key('use_'+self.name, enabled, True) 189 | 190 | def settings_dialog(self): 191 | pass 192 | -------------------------------------------------------------------------------- /lib/mnemonic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2014 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import os 20 | import hmac 21 | import math 22 | import hashlib 23 | import unicodedata 24 | import string 25 | 26 | import ecdsa 27 | import pbkdf2 28 | 29 | from util import print_error 30 | from bitcoin import is_old_seed, is_new_seed 31 | import version 32 | import i18n 33 | 34 | # http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html 35 | CJK_INTERVALS = [ 36 | (0x4E00, 0x9FFF, 'CJK Unified Ideographs'), 37 | (0x3400, 0x4DBF, 'CJK Unified Ideographs Extension A'), 38 | (0x20000, 0x2A6DF, 'CJK Unified Ideographs Extension B'), 39 | (0x2A700, 0x2B73F, 'CJK Unified Ideographs Extension C'), 40 | (0x2B740, 0x2B81F, 'CJK Unified Ideographs Extension D'), 41 | (0xF900, 0xFAFF, 'CJK Compatibility Ideographs'), 42 | (0x2F800, 0x2FA1D, 'CJK Compatibility Ideographs Supplement'), 43 | (0x3190, 0x319F , 'Kanbun'), 44 | (0x2E80, 0x2EFF, 'CJK Radicals Supplement'), 45 | (0x2F00, 0x2FDF, 'CJK Radicals'), 46 | (0x31C0, 0x31EF, 'CJK Strokes'), 47 | (0x2FF0, 0x2FFF, 'Ideographic Description Characters'), 48 | (0xE0100, 0xE01EF, 'Variation Selectors Supplement'), 49 | (0x3100, 0x312F, 'Bopomofo'), 50 | (0x31A0, 0x31BF, 'Bopomofo Extended'), 51 | (0xFF00, 0xFFEF, 'Halfwidth and Fullwidth Forms'), 52 | (0x3040, 0x309F, 'Hiragana'), 53 | (0x30A0, 0x30FF, 'Katakana'), 54 | (0x31F0, 0x31FF, 'Katakana Phonetic Extensions'), 55 | (0x1B000, 0x1B0FF, 'Kana Supplement'), 56 | (0xAC00, 0xD7AF, 'Hangul Syllables'), 57 | (0x1100, 0x11FF, 'Hangul Jamo'), 58 | (0xA960, 0xA97F, 'Hangul Jamo Extended A'), 59 | (0xD7B0, 0xD7FF, 'Hangul Jamo Extended B'), 60 | (0x3130, 0x318F, 'Hangul Compatibility Jamo'), 61 | (0xA4D0, 0xA4FF, 'Lisu'), 62 | (0x16F00, 0x16F9F, 'Miao'), 63 | (0xA000, 0xA48F, 'Yi Syllables'), 64 | (0xA490, 0xA4CF, 'Yi Radicals'), 65 | ] 66 | 67 | def is_CJK(c): 68 | n = ord(c) 69 | for imin,imax,name in CJK_INTERVALS: 70 | if n>=imin and n<=imax: return True 71 | return False 72 | 73 | 74 | def prepare_seed(seed): 75 | # normalize 76 | seed = unicodedata.normalize('NFKD', unicode(seed)) 77 | # lower 78 | seed = seed.lower() 79 | # remove accents 80 | seed = u''.join([c for c in seed if not unicodedata.combining(c)]) 81 | # normalize whitespaces 82 | seed = u' '.join(seed.split()) 83 | # remove whitespaces between CJK 84 | seed = u''.join([seed[i] for i in range(len(seed)) if not (seed[i] in string.whitespace and is_CJK(seed[i-1]) and is_CJK(seed[i+1]))]) 85 | return seed 86 | 87 | 88 | filenames = { 89 | 'en':'english.txt', 90 | 'es':'spanish.txt', 91 | 'ja':'japanese.txt', 92 | 'pt':'portuguese.txt', 93 | } 94 | 95 | 96 | 97 | class Mnemonic(object): 98 | # Seed derivation no longer follows BIP39 99 | # Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum 100 | 101 | def __init__(self, lang=None): 102 | if lang in [None, '']: 103 | lang = i18n.language.info().get('language', 'en') 104 | print_error('language', lang) 105 | filename = filenames.get(lang[0:2], 'english.txt') 106 | path = os.path.join(os.path.dirname(__file__), 'wordlist', filename) 107 | s = open(path,'r').read().strip() 108 | s = unicodedata.normalize('NFKD', s.decode('utf8')) 109 | lines = s.split('\n') 110 | self.wordlist = [] 111 | for line in lines: 112 | line = line.split('#')[0] 113 | line = line.strip(' \r') 114 | assert ' ' not in line 115 | if line: 116 | self.wordlist.append(line) 117 | print_error("wordlist has %d words"%len(self.wordlist)) 118 | 119 | @classmethod 120 | def mnemonic_to_seed(self, mnemonic, passphrase): 121 | PBKDF2_ROUNDS = 2048 122 | mnemonic = prepare_seed(mnemonic) 123 | return pbkdf2.PBKDF2(mnemonic, 'electrum' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64) 124 | 125 | def mnemonic_encode(self, i): 126 | n = len(self.wordlist) 127 | words = [] 128 | while i: 129 | x = i%n 130 | i = i/n 131 | words.append(self.wordlist[x]) 132 | return ' '.join(words) 133 | 134 | def mnemonic_decode(self, seed): 135 | n = len(self.wordlist) 136 | words = seed.split() 137 | i = 0 138 | while words: 139 | w = words.pop() 140 | k = self.wordlist.index(w) 141 | i = i*n + k 142 | return i 143 | 144 | def check_seed(self, seed, custom_entropy): 145 | assert is_new_seed(seed) 146 | i = self.mnemonic_decode(seed) 147 | return i % custom_entropy == 0 148 | 149 | def make_seed(self, num_bits=128, prefix=version.SEED_PREFIX, custom_entropy=1): 150 | n = int(math.ceil(math.log(custom_entropy,2))) 151 | # bits of entropy used by the prefix 152 | k = len(prefix)*4 153 | # we add at least 16 bits 154 | n_added = max(16, k + num_bits - n) 155 | print_error("make_seed", prefix, "adding %d bits"%n_added) 156 | my_entropy = ecdsa.util.randrange( pow(2, n_added) ) 157 | nonce = 0 158 | while True: 159 | nonce += 1 160 | i = custom_entropy * (my_entropy + nonce) 161 | seed = self.mnemonic_encode(i) 162 | 163 | try: 164 | assert i == self.mnemonic_decode(seed) 165 | except AssertionError: 166 | # If assertion fails, generate new seed 167 | continue 168 | 169 | if is_old_seed(seed): 170 | continue 171 | if is_new_seed(seed, prefix): 172 | break 173 | print_error('%d words'%len(seed.split())) 174 | return seed 175 | -------------------------------------------------------------------------------- /plugins/cosigner_pool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2014 Thomas Voegtlin 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import socket 20 | import threading 21 | import time 22 | import xmlrpclib 23 | 24 | from PyQt4.QtGui import * 25 | from PyQt4.QtCore import * 26 | 27 | from electrum_xvg import bitcoin, util 28 | from electrum_xvg import transaction 29 | from electrum_xvg.plugins import BasePlugin, hook 30 | from electrum_xvg.i18n import _ 31 | 32 | from electrum_xvg_gui.qt import transaction_dialog 33 | 34 | import sys 35 | import traceback 36 | 37 | 38 | PORT = 12344 39 | HOST = 'ecdsa.net' 40 | server = xmlrpclib.ServerProxy('http://%s:%d'%(HOST,PORT), allow_none=True) 41 | 42 | 43 | class Listener(threading.Thread): 44 | 45 | def __init__(self, parent): 46 | threading.Thread.__init__(self) 47 | self.daemon = True 48 | self.parent = parent 49 | self.keyname = None 50 | self.keyhash = None 51 | self.is_running = False 52 | self.message = None 53 | 54 | def set_key(self, keyname, keyhash): 55 | self.keyname = keyname 56 | self.keyhash = keyhash 57 | 58 | def clear(self): 59 | server.delete(self.keyhash) 60 | self.message = None 61 | 62 | 63 | def run(self): 64 | self.is_running = True 65 | while self.is_running: 66 | if not self.keyhash: 67 | time.sleep(2) 68 | continue 69 | if not self.message: 70 | try: 71 | self.message = server.get(self.keyhash) 72 | except Exception as e: 73 | util.print_error("cannot contact cosigner pool") 74 | time.sleep(30) 75 | continue 76 | if self.message: 77 | self.parent.win.emit(SIGNAL("cosigner:receive")) 78 | # poll every 30 seconds 79 | time.sleep(30) 80 | 81 | 82 | class Plugin(BasePlugin): 83 | 84 | wallet = None 85 | listener = None 86 | 87 | @hook 88 | def init_qt(self, gui): 89 | self.win = gui.main_window 90 | self.win.connect(self.win, SIGNAL('cosigner:receive'), self.on_receive) 91 | 92 | def is_available(self): 93 | if self.wallet is None: 94 | return True 95 | return self.wallet.wallet_type in ['2of2', '2of3'] 96 | 97 | @hook 98 | def load_wallet(self, wallet): 99 | self.wallet = wallet 100 | if not self.is_available(): 101 | return 102 | if self.listener is None: 103 | self.listener = Listener(self) 104 | self.listener.start() 105 | self.cosigner_list = [] 106 | for key, xpub in self.wallet.master_public_keys.items(): 107 | K = bitcoin.deserialize_xkey(xpub)[-1].encode('hex') 108 | _hash = bitcoin.Hash(K).encode('hex') 109 | if self.wallet.master_private_keys.get(key): 110 | self.listener.set_key(key, _hash) 111 | else: 112 | self.cosigner_list.append((xpub, K, _hash)) 113 | 114 | @hook 115 | def transaction_dialog(self, d): 116 | self.send_button = b = QPushButton(_("Send to cosigner")) 117 | b.clicked.connect(lambda: self.do_send(d.tx)) 118 | d.buttons.insert(2, b) 119 | self.transaction_dialog_update(d) 120 | 121 | @hook 122 | def transaction_dialog_update(self, d): 123 | if d.tx.is_complete(): 124 | self.send_button.hide() 125 | return 126 | for xpub, K, _hash in self.cosigner_list: 127 | if self.cosigner_can_sign(d.tx, xpub): 128 | self.send_button.show() 129 | break 130 | else: 131 | self.send_button.hide() 132 | 133 | def cosigner_can_sign(self, tx, cosigner_xpub): 134 | from electrum_xvg.transaction import x_to_xpub 135 | xpub_set = set([]) 136 | for txin in tx.inputs: 137 | for x_pubkey in txin['x_pubkeys']: 138 | xpub = x_to_xpub(x_pubkey) 139 | if xpub: 140 | xpub_set.add(xpub) 141 | 142 | return cosigner_xpub in xpub_set 143 | 144 | def do_send(self, tx): 145 | for xpub, K, _hash in self.cosigner_list: 146 | if not self.cosigner_can_sign(tx, xpub): 147 | continue 148 | message = bitcoin.encrypt_message(tx.raw, K) 149 | try: 150 | server.put(_hash, message) 151 | except Exception as e: 152 | self.win.show_message(str(e)) 153 | return 154 | self.win.show_message("Your transaction was sent to the cosigning pool.\nOpen your cosigner wallet to retrieve it.") 155 | 156 | def on_receive(self): 157 | if self.wallet.use_encryption: 158 | password = self.win.password_dialog('An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.') 159 | if not password: 160 | return 161 | else: 162 | password = None 163 | if not self.win.question(_("An encrypted transaction was retrieved from cosigning pool.\nDo you want to open it now?")): 164 | return 165 | 166 | message = self.listener.message 167 | key = self.listener.keyname 168 | xprv = self.wallet.get_master_private_key(key, password) 169 | if not xprv: 170 | return 171 | try: 172 | k = bitcoin.deserialize_xkey(xprv)[-1].encode('hex') 173 | EC = bitcoin.EC_KEY(k.decode('hex')) 174 | message = EC.decrypt_message(message) 175 | except Exception as e: 176 | traceback.print_exc(file=sys.stdout) 177 | self.win.show_message(str(e)) 178 | return 179 | 180 | self.listener.clear() 181 | tx = transaction.Transaction(message) 182 | d = transaction_dialog.TxDialog(tx, self.win) 183 | d.saved = False 184 | d.exec_() 185 | -------------------------------------------------------------------------------- /gui/qt/password_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2013 ecdsa@github 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | from PyQt4.QtGui import * 20 | from PyQt4.QtCore import * 21 | from electrum_xvg.i18n import _ 22 | from util import * 23 | import re 24 | import math 25 | 26 | 27 | 28 | def make_password_dialog(self, wallet, msg, new_pass=True): 29 | 30 | self.pw = QLineEdit() 31 | self.pw.setEchoMode(2) 32 | self.new_pw = QLineEdit() 33 | self.new_pw.setEchoMode(2) 34 | self.conf_pw = QLineEdit() 35 | self.conf_pw.setEchoMode(2) 36 | 37 | vbox = QVBoxLayout() 38 | label = QLabel(msg) 39 | label.setWordWrap(True) 40 | 41 | grid = QGridLayout() 42 | grid.setSpacing(8) 43 | grid.setColumnMinimumWidth(0, 70) 44 | grid.setColumnStretch(1,1) 45 | 46 | logo = QLabel() 47 | lockfile = ":icons/lock.png" if wallet and wallet.use_encryption else ":icons/unlock.png" 48 | logo.setPixmap(QPixmap(lockfile).scaledToWidth(36)) 49 | logo.setAlignment(Qt.AlignCenter) 50 | 51 | grid.addWidget(logo, 0, 0) 52 | grid.addWidget(label, 0, 1, 1, 2) 53 | vbox.addLayout(grid) 54 | 55 | grid = QGridLayout() 56 | grid.setSpacing(8) 57 | grid.setColumnMinimumWidth(0, 250) 58 | grid.setColumnStretch(1,1) 59 | 60 | if wallet and wallet.use_encryption: 61 | grid.addWidget(QLabel(_('Password')), 0, 0) 62 | grid.addWidget(self.pw, 0, 1) 63 | 64 | grid.addWidget(QLabel(_('New Password') if new_pass else _('Password')), 1, 0) 65 | grid.addWidget(self.new_pw, 1, 1) 66 | 67 | grid.addWidget(QLabel(_('Confirm Password')), 2, 0) 68 | grid.addWidget(self.conf_pw, 2, 1) 69 | vbox.addLayout(grid) 70 | 71 | #Password Strength Label 72 | self.pw_strength = QLabel() 73 | grid.addWidget(self.pw_strength, 3, 0, 1, 2) 74 | self.new_pw.textChanged.connect(lambda: update_password_strength(self.pw_strength, self.new_pw.text())) 75 | 76 | vbox.addStretch(1) 77 | vbox.addLayout(Buttons(CancelButton(self), OkButton(self))) 78 | return vbox 79 | 80 | 81 | def run_password_dialog(self, wallet, parent): 82 | 83 | if wallet and wallet.is_watching_only(): 84 | QMessageBox.information(parent, _('Error'), _('This is a watching-only wallet'), _('OK')) 85 | return False, None, None 86 | 87 | if not self.exec_(): 88 | return False, None, None 89 | 90 | password = unicode(self.pw.text()) if wallet and wallet.use_encryption else None 91 | new_password = unicode(self.new_pw.text()) 92 | new_password2 = unicode(self.conf_pw.text()) 93 | 94 | if new_password != new_password2: 95 | QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK')) 96 | # Retry 97 | return run_password_dialog(self, wallet, parent) 98 | 99 | if not new_password: 100 | new_password = None 101 | 102 | return True, password, new_password 103 | 104 | def check_password_strength(password): 105 | 106 | ''' 107 | Check the strength of the password entered by the user and return back the same 108 | :param password: password entered by user in New Password 109 | :return: password strength Weak or Medium or Strong 110 | ''' 111 | password = unicode(password) 112 | n = math.log(len(set(password))) 113 | num = re.search("[0-9]", password) is not None and re.match("^[0-9]*$", password) is None 114 | caps = password != password.upper() and password != password.lower() 115 | extra = re.match("^[a-zA-Z0-9]*$", password) is None 116 | score = len(password)*( n + caps + num + extra)/20 117 | password_strength = {0:"Weak",1:"Medium",2:"Strong",3:"Very Strong"} 118 | return password_strength[min(3, int(score))] 119 | 120 | 121 | def update_password_strength(pw_strength_label,password): 122 | 123 | ''' 124 | call the function check_password_strength and update the label pw_strength interactively as the user is typing the password 125 | :param pw_strength_label: the label pw_strength 126 | :param password: password entered in New Password text box 127 | :return: None 128 | ''' 129 | if password: 130 | colors = {"Weak":"Red","Medium":"Blue","Strong":"Green", "Very Strong":"Green"} 131 | strength = check_password_strength(password) 132 | label = _("Password Strength")+ ": "+"" + strength + "" 133 | else: 134 | label = "" 135 | pw_strength_label.setText(label) 136 | 137 | 138 | 139 | class PasswordDialog(QDialog): 140 | 141 | def __init__(self, wallet, parent): 142 | QDialog.__init__(self, parent) 143 | self.setModal(1) 144 | self.wallet = wallet 145 | self.parent = parent 146 | self.setWindowTitle(_("Set Password")) 147 | msg = (_('Your wallet is encrypted. Use this dialog to change your password.') + ' '\ 148 | +_('To disable wallet encryption, enter an empty new password.')) \ 149 | if wallet.use_encryption else _('Your wallet keys are not encrypted') 150 | self.setLayout(make_password_dialog(self, wallet, msg)) 151 | 152 | 153 | def run(self): 154 | ok, password, new_password = run_password_dialog(self, self.wallet, self.parent) 155 | if not ok: 156 | return 157 | 158 | try: 159 | self.wallet.check_password(password) 160 | except BaseException as e: 161 | QMessageBox.warning(self.parent, _('Error'), str(e), _('OK')) 162 | return False, None, None 163 | 164 | try: 165 | self.wallet.update_password(password, new_password) 166 | except: 167 | import traceback, sys 168 | traceback.print_exc(file=sys.stdout) 169 | QMessageBox.warning(self.parent, _('Error'), _('Failed to update password'), _('OK')) 170 | return 171 | 172 | if new_password: 173 | QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK')) 174 | else: 175 | QMessageBox.information(self.parent, _('Success'), _('This wallet is not encrypted'), _('OK')) 176 | -------------------------------------------------------------------------------- /lib/simple_config.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import json 3 | import threading 4 | import os 5 | 6 | from copy import deepcopy 7 | from util import user_dir, print_error, print_msg, print_stderr 8 | 9 | SYSTEM_CONFIG_PATH = "/etc/electrum-xvg.conf" 10 | 11 | config = None 12 | 13 | 14 | def get_config(): 15 | global config 16 | return config 17 | 18 | 19 | def set_config(c): 20 | global config 21 | config = c 22 | 23 | 24 | class SimpleConfig(object): 25 | """ 26 | The SimpleConfig class is responsible for handling operations involving 27 | configuration files. 28 | 29 | There are 3 different sources of possible configuration values: 30 | 1. Command line options. 31 | 2. User configuration (in the user's config directory) 32 | 3. System configuration (in /etc/) 33 | They are taken in order (1. overrides config options set in 2., that 34 | override config set in 3.) 35 | """ 36 | def __init__(self, options={}, read_system_config_function=None, 37 | read_user_config_function=None, read_user_dir_function=None): 38 | 39 | # This lock needs to be acquired for updating and reading the config in 40 | # a thread-safe way. 41 | self.lock = threading.RLock() 42 | 43 | # The following two functions are there for dependency injection when 44 | # testing. 45 | if read_system_config_function is None: 46 | read_system_config_function = read_system_config 47 | if read_user_config_function is None: 48 | read_user_config_function = read_user_config 49 | if read_user_dir_function is None: 50 | self.user_dir = user_dir 51 | else: 52 | self.user_dir = read_user_dir_function 53 | 54 | # The command line options 55 | self.cmdline_options = deepcopy(options) 56 | 57 | # Portable wallets don't use a system config 58 | if self.cmdline_options.get('portable', False): 59 | self.system_config = {} 60 | else: 61 | self.system_config = read_system_config_function() 62 | 63 | # Set self.path and read the user config 64 | self.user_config = {} # for self.get in electrum_path() 65 | self.path = self.electrum_path() 66 | self.user_config = read_user_config_function(self.path) 67 | # Upgrade obsolete keys 68 | self.fixup_keys({'auto_cycle': 'auto_connect'}) 69 | # Make a singleton instance of 'self' 70 | set_config(self) 71 | 72 | def electrum_path(self): 73 | # Read electrum_path from command line / system configuration 74 | # Otherwise use the user's default data directory. 75 | path = self.get('electrum_path') 76 | if path is None: 77 | path = self.user_dir() 78 | 79 | # Make directory if it does not yet exist. 80 | if not os.path.exists(path): 81 | os.mkdir(path) 82 | 83 | print_error("electrum directory", path) 84 | return path 85 | 86 | def fixup_config_keys(self, config, keypairs): 87 | updated = False 88 | for old_key, new_key in keypairs.iteritems(): 89 | if old_key in config: 90 | if not new_key in config: 91 | config[new_key] = config[old_key] 92 | del config[old_key] 93 | updated = True 94 | return updated 95 | 96 | def fixup_keys(self, keypairs): 97 | '''Migrate old key names to new ones''' 98 | self.fixup_config_keys(self.cmdline_options, keypairs) 99 | self.fixup_config_keys(self.system_config, keypairs) 100 | if self.fixup_config_keys(self.user_config, keypairs): 101 | self.save_user_config() 102 | 103 | def set_key(self, key, value, save = True): 104 | if not self.is_modifiable(key): 105 | print_stderr("Warning: not changing config key '%s' set on the command line" % key) 106 | return 107 | 108 | with self.lock: 109 | self.user_config[key] = value 110 | if save: 111 | self.save_user_config() 112 | return 113 | 114 | def get(self, key, default=None): 115 | with self.lock: 116 | out = self.cmdline_options.get(key) 117 | if out is None: 118 | out = self.user_config.get(key) 119 | if out is None: 120 | out = self.system_config.get(key, default) 121 | return out 122 | 123 | def is_modifiable(self, key): 124 | return not key in self.cmdline_options 125 | 126 | def save_user_config(self): 127 | if not self.path: 128 | return 129 | path = os.path.join(self.path, "config") 130 | s = json.dumps(self.user_config, indent=4, sort_keys=True) 131 | f = open(path, "w") 132 | f.write(s) 133 | f.close() 134 | if self.get('gui') != 'android': 135 | import stat 136 | os.chmod(path, stat.S_IREAD | stat.S_IWRITE) 137 | 138 | def get_wallet_path(self): 139 | """Set the path of the wallet.""" 140 | 141 | # command line -w option 142 | path = self.get('wallet_path') 143 | if path: 144 | return path 145 | 146 | # path in config file 147 | path = self.get('default_wallet_path') 148 | if path and os.path.exists(path): 149 | return path 150 | 151 | # default path 152 | dirpath = os.path.join(self.path, "wallets") 153 | if not os.path.exists(dirpath): 154 | os.mkdir(dirpath) 155 | 156 | new_path = os.path.join(self.path, "wallets", "default_wallet") 157 | 158 | # default path in pre 1.9 versions 159 | old_path = os.path.join(self.path, "electrum-xvg.dat") 160 | if os.path.exists(old_path) and not os.path.exists(new_path): 161 | os.rename(old_path, new_path) 162 | 163 | return new_path 164 | 165 | 166 | 167 | def read_system_config(path=SYSTEM_CONFIG_PATH): 168 | """Parse and return the system config settings in /etc/electrum-xvg.conf.""" 169 | result = {} 170 | if os.path.exists(path): 171 | try: 172 | import ConfigParser 173 | except ImportError: 174 | print "cannot parse electrum-xvg.conf. please install ConfigParser" 175 | return 176 | 177 | p = ConfigParser.ConfigParser() 178 | try: 179 | p.read(path) 180 | for k, v in p.items('client'): 181 | result[k] = v 182 | except (ConfigParser.NoSectionError, ConfigParser.MissingSectionHeaderError): 183 | pass 184 | 185 | return result 186 | 187 | def read_user_config(path): 188 | """Parse and store the user config settings in electrum-xvg.conf into user_config[].""" 189 | if not path: 190 | return {} 191 | config_path = os.path.join(path, "config") 192 | try: 193 | with open(config_path, "r") as f: 194 | data = f.read() 195 | except IOError: 196 | print_msg("Error: Cannot read config file.") 197 | return {} 198 | try: 199 | result = json.loads(data) 200 | except: 201 | try: 202 | result = ast.literal_eval(data) 203 | except: 204 | print_msg("Error: Cannot read config file.") 205 | return {} 206 | if not type(result) is dict: 207 | return {} 208 | return result 209 | -------------------------------------------------------------------------------- /plugins/labels.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import requests 3 | import threading 4 | import hashlib 5 | import json 6 | import importlib 7 | 8 | try: 9 | importlib.import_module("PyQt4") 10 | except Exception: 11 | sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'") 12 | 13 | from PyQt4.QtGui import * 14 | from PyQt4.QtCore import * 15 | import PyQt4.QtCore as QtCore 16 | import PyQt4.QtGui as QtGui 17 | import aes 18 | import base64 19 | 20 | import electrum_xvg as electrum 21 | from electrum_xvg.plugins import BasePlugin, hook 22 | from electrum_xvg.i18n import _ 23 | 24 | from electrum_xvg_gui.qt import HelpButton, EnterButton 25 | from electrum_xvg_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton 26 | 27 | class Plugin(BasePlugin): 28 | 29 | target_host = 'sync.bytesized-hosting.com:9090' 30 | encode_password = None 31 | 32 | def version(self): 33 | return "0.0.1" 34 | 35 | def encode(self, message): 36 | encrypted = electrum.bitcoin.aes_encrypt_with_iv(self.encode_password, self.iv, message.encode('utf8')) 37 | encoded_message = base64.b64encode(encrypted) 38 | return encoded_message 39 | 40 | def decode(self, message): 41 | decoded_message = electrum.bitcoin.aes_decrypt_with_iv(self.encode_password, self.iv, base64.b64decode(message)).decode('utf8') 42 | return decoded_message 43 | 44 | def set_nonce(self, nonce): 45 | self.print_error("Set nonce to", nonce) 46 | self.wallet.storage.put("wallet_nonce", nonce, True) 47 | self.wallet_nonce = nonce 48 | 49 | @hook 50 | def init_qt(self, gui): 51 | self.window = gui.main_window 52 | self.window.connect(self.window, SIGNAL('labels:pulled'), self.on_pulled) 53 | 54 | @hook 55 | def load_wallet(self, wallet): 56 | self.wallet = wallet 57 | 58 | self.wallet_nonce = self.wallet.storage.get("wallet_nonce") 59 | self.print_error("Wallet nonce is", self.wallet_nonce) 60 | if self.wallet_nonce is None: 61 | self.set_nonce(1) 62 | 63 | mpk = ''.join(sorted(self.wallet.get_master_public_keys().values())) 64 | self.encode_password = hashlib.sha1(mpk).digest().encode('hex')[:32] 65 | self.iv = hashlib.sha256(self.encode_password).digest()[:16] 66 | self.wallet_id = hashlib.sha256(mpk).digest().encode('hex') 67 | 68 | addresses = [] 69 | for account in self.wallet.accounts.values(): 70 | for address in account.get_addresses(0): 71 | addresses.append(address) 72 | 73 | self.addresses = addresses 74 | 75 | # If there is an auth token we can try to actually start syncing 76 | def do_pull_thread(): 77 | try: 78 | self.pull_thread() 79 | except Exception as e: 80 | self.print_error("could not retrieve labels:", e) 81 | t = threading.Thread(target=do_pull_thread) 82 | t.setDaemon(True) 83 | t.start() 84 | 85 | 86 | def is_available(self): 87 | return True 88 | 89 | def requires_settings(self): 90 | return True 91 | 92 | @hook 93 | def set_label(self, item,label, changed): 94 | if self.encode_password is None: 95 | return 96 | if not changed: 97 | return 98 | bundle = {"walletId": self.wallet_id, "walletNonce": self.wallet.storage.get("wallet_nonce"), "externalId": self.encode(item), "encryptedLabel": self.encode(label)} 99 | t = threading.Thread(target=self.do_request, args=["POST", "/label", False, bundle]) 100 | t.setDaemon(True) 101 | t.start() 102 | self.set_nonce(self.wallet.storage.get("wallet_nonce") + 1) 103 | 104 | def settings_widget(self, window): 105 | return EnterButton(_('Settings'), self.settings_dialog) 106 | 107 | def settings_dialog(self): 108 | d = QDialog() 109 | vbox = QVBoxLayout(d) 110 | layout = QGridLayout() 111 | vbox.addLayout(layout) 112 | 113 | layout.addWidget(QLabel("Label sync options: "),2,0) 114 | 115 | self.upload = ThreadedButton("Force upload", self.push_thread, self.done_processing) 116 | layout.addWidget(self.upload, 2, 1) 117 | 118 | self.download = ThreadedButton("Force download", lambda: self.pull_thread(True), self.done_processing) 119 | layout.addWidget(self.download, 2, 2) 120 | 121 | self.accept = OkButton(d, _("Done")) 122 | vbox.addLayout(Buttons(CancelButton(d), self.accept)) 123 | 124 | if d.exec_(): 125 | return True 126 | else: 127 | return False 128 | 129 | def on_pulled(self): 130 | wallet = self.wallet 131 | wallet.storage.put('labels', wallet.labels, True) 132 | self.window.labelsChanged.emit() 133 | 134 | def done_processing(self): 135 | QMessageBox.information(None, _("Labels synchronised"), _("Your labels have been synchronised.")) 136 | 137 | def do_request(self, method, url = "/labels", is_batch=False, data=None): 138 | url = 'https://' + self.target_host + url 139 | kwargs = {'headers': {}} 140 | if method == 'GET' and data: 141 | kwargs['params'] = data 142 | elif method == 'POST' and data: 143 | kwargs['data'] = json.dumps(data) 144 | kwargs['headers']['Content-Type'] = 'application/json' 145 | response = requests.request(method, url, **kwargs) 146 | if response.status_code != 200: 147 | raise BaseException(response.status_code, response.text) 148 | response = response.json() 149 | if "error" in response: 150 | raise BaseException(response["error"]) 151 | return response 152 | 153 | def push_thread(self): 154 | bundle = {"labels": [], "walletId": self.wallet_id, "walletNonce": self.wallet_nonce} 155 | for key, value in self.wallet.labels.iteritems(): 156 | try: 157 | encoded_key = self.encode(key) 158 | encoded_value = self.encode(value) 159 | except: 160 | self.print_error('cannot encode', repr(key), repr(value)) 161 | continue 162 | bundle["labels"].append({'encryptedLabel': encoded_value, 'externalId': encoded_key}) 163 | self.do_request("POST", "/labels", True, bundle) 164 | 165 | def pull_thread(self, force = False): 166 | wallet_nonce = 1 if force else self.wallet_nonce - 1 167 | self.print_error("Asking for labels since nonce", wallet_nonce) 168 | response = self.do_request("GET", ("/labels/since/%d/for/%s" % (wallet_nonce, self.wallet_id) )) 169 | result = {} 170 | if not response["labels"] is None: 171 | for label in response["labels"]: 172 | try: 173 | key = self.decode(label["externalId"]) 174 | value = self.decode(label["encryptedLabel"]) 175 | except: 176 | continue 177 | try: 178 | json.dumps(key) 179 | json.dumps(value) 180 | except: 181 | self.print_error('error: no json', key) 182 | continue 183 | result[key] = value 184 | 185 | wallet = self.wallet 186 | if not wallet: 187 | return 188 | for key, value in result.items(): 189 | if force or not wallet.labels.get(key): 190 | wallet.labels[key] = value 191 | 192 | self.window.emit(SIGNAL('labels:pulled')) 193 | self.set_nonce(response["nonce"] + 1) 194 | self.print_error("received %d labels"%len(response)) 195 | --------------------------------------------------------------------------------