Merge branch 'master' of git://github.com/spesmilo/electrum
This commit is contained in:
commit
3f919e193b
6
README
6
README
|
@ -1,9 +1,9 @@
|
||||||
Electrum - lightweight Bitcoin client
|
Electrum - lightweight Bitcoin client
|
||||||
|
|
||||||
Licence: GNU GPL v3
|
Licence: GNU GPL v3
|
||||||
Author: thomasv@gitorious
|
Author: thomasv@bitcointalk.org
|
||||||
Language: Python
|
Language: Python
|
||||||
Homepage: http://electrum.ecdsa.org/
|
Homepage: http://electrum.org/
|
||||||
|
|
||||||
|
|
||||||
== INSTALL ==
|
== INSTALL ==
|
||||||
|
@ -42,4 +42,4 @@ On Mac OS X:
|
||||||
|
|
||||||
== BROWSER CONFIGURATION ==
|
== BROWSER CONFIGURATION ==
|
||||||
|
|
||||||
see http://ecdsa.org/bitcoin_URIs.html
|
See http://electrum.org/bitcoin_URIs.html
|
||||||
|
|
|
@ -1,3 +1,55 @@
|
||||||
|
# Release 1.6.2 (Not released)
|
||||||
|
|
||||||
|
== Classic GUI
|
||||||
|
* Added new version notification
|
||||||
|
|
||||||
|
# Release 1.6.1 (11-01-2013)
|
||||||
|
|
||||||
|
== Core
|
||||||
|
* It is now possible to restore a wallet from MPK (this will create a watching-only wallet)
|
||||||
|
* A switch button allows to easily switch between Lite and Classic GUI.
|
||||||
|
|
||||||
|
== Classic GUI
|
||||||
|
* Seed and MPK help dialogs were rewritten
|
||||||
|
* Point of Sale: requested amounts can be expressed in other currencies and are converted to bitcoin.
|
||||||
|
|
||||||
|
== Lite GUI
|
||||||
|
* The receiving button was removed in favor of a menu item to keep it consistent with the history toggle.
|
||||||
|
|
||||||
|
# Release 1.6.0 (07-01-2013)
|
||||||
|
|
||||||
|
== Core
|
||||||
|
* (Feature) Add support for importing, signing and verifiying compressed keys
|
||||||
|
* (Feature) Auto reconnect to random server on disconnect
|
||||||
|
* (Feature) Ultimate fallback to HTTP port 80 if TCP doesn't work on any server
|
||||||
|
* (Bug) Under rare circumstances changing password with incorrect password could damage wallet
|
||||||
|
|
||||||
|
== Lite GUI
|
||||||
|
* (Chore) Use blockchain.info for exchange rate data
|
||||||
|
* (Feature) added currency conversion for BRL, CNY, RUB
|
||||||
|
* (Feature) Saraha theme
|
||||||
|
* (Feature) csv import/export for transactions including labels
|
||||||
|
|
||||||
|
== Classic GUI
|
||||||
|
* (Chore) pruning servers now called "p", full servers "f" to avoid confusion with terms
|
||||||
|
* (Feature) Debits in history shown in red
|
||||||
|
* (Feature) csv import/export for transactions including labels
|
||||||
|
|
||||||
|
# Release 1.5.8 (02-01-2013)
|
||||||
|
|
||||||
|
== Core
|
||||||
|
* (Bug) Fix pending address balance on received coins for pruning servers
|
||||||
|
* (Bug) Fix history command line option to show output again (regression by SPV)
|
||||||
|
* (Chore) Add timeout to blockchain headers file download by HTTP
|
||||||
|
* (Feature) new option: -L, --language: default language used in GUI.
|
||||||
|
|
||||||
|
== Lite GUI
|
||||||
|
* (Bug) Sending to auto-completed contacts works again
|
||||||
|
* (Chore) Added version number to title bar
|
||||||
|
|
||||||
|
== Classic GUI
|
||||||
|
* (Feature) Language selector in options.
|
||||||
|
|
||||||
# Release 1.5.7 (18-12-2012)
|
# Release 1.5.7 (18-12-2012)
|
||||||
|
|
||||||
== Core
|
== Core
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
security:
|
||||||
|
- check that we are on the longest chain after a reorg
|
||||||
|
- check SSL cerfificates of servers
|
||||||
|
|
||||||
|
|
||||||
|
wallet, transactions :
|
||||||
|
- dust sweeping
|
||||||
|
- transactions with multiple outputs
|
||||||
|
- BIP 32
|
||||||
|
|
||||||
|
|
||||||
|
code improvements:
|
||||||
|
- qrcode and bmp patches are on github (they are incompatible with android)
|
||||||
|
|
||||||
|
|
||||||
|
android:
|
||||||
|
- kivy-based gui
|
||||||
|
|
||||||
|
|
||||||
|
protocol:
|
||||||
|
- add client authentication, to make paying servers possible
|
||||||
|
|
1
app.fil
1
app.fil
|
@ -1,2 +1,3 @@
|
||||||
lib/gui_qt.py
|
lib/gui_qt.py
|
||||||
lib/gui.py
|
lib/gui.py
|
||||||
|
lib/gui_lite.py
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
These scripts can be used for cross-compilation of Windows Electrum executables from Linux/Wine.
|
These scripts can be used for cross-compilation of Windows Electrum executables from Linux/Wine.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
1. Copy content of this directory to /electrum-wine.
|
1. Copy content of this directory to /build-wine.
|
||||||
2. Install Wine (version 1.4 or 1.5+ works fine, 1.4.1 has bug).
|
2. Install Wine (version 1.4 or 1.5+ works fine, 1.4.1 has bug).
|
||||||
3. Run "./prepare-wine.sh", it will download all dependencies. When you'll be asked, always leave default settings and press "Next >".
|
3. Run "./prepare-wine.sh", it will download all dependencies. When you'll be asked, always leave default settings and press "Next >".
|
||||||
4. By running "./build-electrum.sh", sources will be packed into three separate versions to dist/ directory:
|
4. By running "./build-electrum.sh", sources will be packed into three separate versions to dist/ directory:
|
||||||
|
|
|
@ -6,7 +6,7 @@ BRANCH=master
|
||||||
NAME_ROOT=electrum
|
NAME_ROOT=electrum
|
||||||
|
|
||||||
# These settings probably don't need any change
|
# These settings probably don't need any change
|
||||||
export WINEPREFIX=~/.wine-electrum
|
export WINEPREFIX=/opt/wine-electrum
|
||||||
PYHOME=c:/python26
|
PYHOME=c:/python26
|
||||||
PYTHON="wine $PYHOME/python.exe -OO -B"
|
PYTHON="wine $PYHOME/python.exe -OO -B"
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# You probably need to update only this link
|
# You probably need to update only this link
|
||||||
ELECTRUM_URL=https://github.com/downloads/spesmilo/electrum/Electrum-1.5.6.tar.gz
|
ELECTRUM_URL=https://github.com/downloads/spesmilo/electrum/Electrum-1.6.1.tar.gz
|
||||||
NAME_ROOT=electrum-1.5.6
|
NAME_ROOT=electrum-1.6.1
|
||||||
|
|
||||||
# These settings probably don't need any change
|
# These settings probably don't need any change
|
||||||
export WINEPREFIX=~/.wine-electrum
|
export WINEPREFIX=/opt/wine-electrum
|
||||||
PYHOME=c:/python26
|
PYHOME=c:/python26
|
||||||
PYTHON="wine $PYHOME/python.exe -OO -B"
|
PYTHON="wine $PYHOME/python.exe -OO -B"
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ NSIS_URL=http://prdownloads.sourceforge.net/nsis/nsis-2.46-setup.exe?download
|
||||||
#ZBAR_URL=http://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download
|
#ZBAR_URL=http://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download
|
||||||
|
|
||||||
# These settings probably don't need change
|
# These settings probably don't need change
|
||||||
export WINEPREFIX=~/.wine-electrum
|
export WINEPREFIX=/opt/wine-electrum
|
||||||
PYHOME=c:/python26
|
PYHOME=c:/python26
|
||||||
PYTHON="wine $PYHOME/python.exe -OO -B"
|
PYTHON="wine $PYHOME/python.exe -OO -B"
|
||||||
|
|
||||||
|
|
|
@ -9,19 +9,18 @@ MiniWindow QPushButton {
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
stop: 0 white, stop: 1 #E6E6E6);
|
stop: 0 white, stop: 1 #E6E6E6);
|
||||||
min-height: 25px;
|
min-height: 30px;
|
||||||
min-width: 30px;
|
min-width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#send_button{
|
#send_button{
|
||||||
color: #E5F2FF;
|
color: #FFF;
|
||||||
border: 1px solid #3786E6;
|
border: 1px solid #3786E6;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
stop: 0 #72B2F8, stop: 1 #3484E6);
|
stop: 0 #72B2F8, stop: 1 #3484E6);
|
||||||
min-width: 80px;
|
|
||||||
min-height: 23px;
|
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#send_button:disabled{
|
#send_button:disabled{
|
||||||
|
@ -30,71 +29,33 @@ MiniWindow QPushButton {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
stop: 0 #A5CFFA, stop: 1 #72B2F8);
|
stop: 0 #A5CFFA, stop: 1 #72B2F8);
|
||||||
min-width: 80px;
|
|
||||||
min-height: 23px;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
}
|
||||||
#receive_button
|
|
||||||
{
|
|
||||||
color: #777;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
border-radius: 0px;
|
|
||||||
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
|
||||||
stop: 0 white, stop: 1 #E6E6E6);
|
|
||||||
min-height: 25px;
|
|
||||||
min-width: 30px;
|
|
||||||
}
|
|
||||||
#receive_button[isActive=true]
|
|
||||||
{
|
|
||||||
color: #575757;
|
|
||||||
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
|
||||||
stop: 0 white, stop: 1 #D1D1D1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#address_input, #amount_input, #label_input
|
#address_input, #amount_input, #label_input
|
||||||
{
|
{
|
||||||
color: #000;
|
color: #000;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 4px;
|
border-radius: 5px;
|
||||||
|
min-height: 23px;
|
||||||
border: 1px solid #AAA9A9;
|
border: 1px solid #AAA9A9;
|
||||||
width: 225px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#address_input[isValid=true]
|
#address_input[isValid=true]
|
||||||
{
|
{
|
||||||
color: #4D9948;
|
color: #4D9948;
|
||||||
padding: 5px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #AAA9A9;
|
|
||||||
width: 225px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#address_input[isValid=false]
|
#address_input[isValid=false]
|
||||||
{
|
{
|
||||||
color: #CE4141;
|
color: #CE4141;
|
||||||
padding: 5px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #AAA9A9;
|
|
||||||
width: 225px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#address_input[isValid=placeholder]
|
|
||||||
{
|
|
||||||
color: blue;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #AAA9A9;
|
|
||||||
width: 225px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
#balance_label
|
#balance_label
|
||||||
{
|
{
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
#history::item
|
#history
|
||||||
{
|
{
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Sahara
|
|
@ -0,0 +1,102 @@
|
||||||
|
#main_window
|
||||||
|
{
|
||||||
|
background: qlineargradient(x1: 0, y1: 0, x2:0,y2:1, stop: 0 white , stop: 1 #F2E3BE);
|
||||||
|
}
|
||||||
|
|
||||||
|
MiniWindow QPushButton {
|
||||||
|
color: #C1A76D;
|
||||||
|
border: 1px solid #A7811C;
|
||||||
|
border-radius: 0px;
|
||||||
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
|
stop: 0 white, stop: 1 #F2E3BE);
|
||||||
|
min-height: 25px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#send_button{
|
||||||
|
color: #FEEBA7;
|
||||||
|
border: 1px solid #AD8B35;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
|
stop: 0 #E0A035, stop: 1 #AD8B35);
|
||||||
|
min-width: 80px;
|
||||||
|
min-height: 23px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#send_button:disabled{
|
||||||
|
color: #FEEDD3;
|
||||||
|
border: 1px solid #F7D46D;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
|
stop: 0 #FAEEA5, stop: 1 #DBC050);
|
||||||
|
min-width: 80px;
|
||||||
|
min-height: 23px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#receive_button
|
||||||
|
{
|
||||||
|
color: #FEEBA7;
|
||||||
|
border: 1px solid #AD8B35;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
|
stop: 0 #E0A035, stop: 1 #AD8B35);
|
||||||
|
min-height: 25px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
#receive_button[isActive=true]
|
||||||
|
{
|
||||||
|
color: #FEEBA7;
|
||||||
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
|
stop: 0 #E0A035, stop: 1 #987620);
|
||||||
|
}
|
||||||
|
|
||||||
|
#address_input, #amount_input
|
||||||
|
{
|
||||||
|
color: #000;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #CBAE69;
|
||||||
|
width: 225px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#address_input[isValid=true]
|
||||||
|
{
|
||||||
|
color: #4D9948;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #CBAE69;
|
||||||
|
width: 225px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#address_input[isValid=false]
|
||||||
|
{
|
||||||
|
color: #CE4141;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #CBAE69;
|
||||||
|
width: 225px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#address_input[isValid=placeholder]
|
||||||
|
{
|
||||||
|
color: #DEC58D;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #CBAE69;
|
||||||
|
width: 225px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
#balance_label
|
||||||
|
{
|
||||||
|
color: #7E5907;
|
||||||
|
}
|
||||||
|
|
||||||
|
#history
|
||||||
|
{
|
||||||
|
color: #8B6914;
|
||||||
|
}
|
||||||
|
|
75
electrum
75
electrum
|
@ -70,8 +70,8 @@ options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address
|
||||||
'signtx':"Sign an unsigned transaction created by a deseeded wallet\nSyntax: signtx <filename>",
|
'signtx':"Sign an unsigned transaction created by a deseeded wallet\nSyntax: signtx <filename>",
|
||||||
'seed':
|
'seed':
|
||||||
"Print the generation seed of your wallet.",
|
"Print the generation seed of your wallet.",
|
||||||
'import':
|
'importprivkey':
|
||||||
'Imports a key pair\nSyntax: import <address>:<privatekey>',
|
'Import a private key\nSyntax: importprivkey <privatekey>',
|
||||||
'signmessage':
|
'signmessage':
|
||||||
'Signs a message with a key\nSyntax: signmessage <address> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
|
'Signs a message with a key\nSyntax: signmessage <address> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
|
||||||
'verifymessage':
|
'verifymessage':
|
||||||
|
@ -99,13 +99,13 @@ offline_commands = [ 'password', 'mktx', 'signtx',
|
||||||
'help', 'validateaddress',
|
'help', 'validateaddress',
|
||||||
'signmessage', 'verifymessage',
|
'signmessage', 'verifymessage',
|
||||||
'eval', 'set', 'get', 'create', 'addresses',
|
'eval', 'set', 'get', 'create', 'addresses',
|
||||||
'import', 'seed',
|
'importprivkey', 'seed',
|
||||||
'deseed','reseed',
|
'deseed','reseed',
|
||||||
'freeze','unfreeze',
|
'freeze','unfreeze',
|
||||||
'prioritize','unprioritize']
|
'prioritize','unprioritize']
|
||||||
|
|
||||||
|
|
||||||
protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'import','signmessage' ]
|
protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'importprivkey','signmessage' ]
|
||||||
|
|
||||||
# get password routine
|
# get password routine
|
||||||
def prompt_password(prompt, confirm=True):
|
def prompt_password(prompt, confirm=True):
|
||||||
|
@ -138,6 +138,8 @@ def arg_parser():
|
||||||
parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
|
parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
|
||||||
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
|
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
|
||||||
parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
|
parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
|
||||||
|
parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
|
||||||
|
parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
@ -204,8 +206,10 @@ if __name__ == '__main__':
|
||||||
interface = Interface(config, True)
|
interface = Interface(config, True)
|
||||||
wallet.interface = interface
|
wallet.interface = interface
|
||||||
interface.start()
|
interface.start()
|
||||||
interface.send([('server.peers.subscribe',[])])
|
if interface.is_connected:
|
||||||
|
interface.send([('server.peers.subscribe',[])])
|
||||||
|
|
||||||
|
set_language(config.get('language'))
|
||||||
gui = gui.ElectrumGui(wallet, config)
|
gui = gui.ElectrumGui(wallet, config)
|
||||||
|
|
||||||
found = config.wallet_file_exists
|
found = config.wallet_file_exists
|
||||||
|
@ -220,8 +224,18 @@ if __name__ == '__main__':
|
||||||
wallet.init_mpk( wallet.seed )
|
wallet.init_mpk( wallet.seed )
|
||||||
else:
|
else:
|
||||||
# ask for seed and gap.
|
# ask for seed and gap.
|
||||||
if not gui.seed_dialog(): exit()
|
sg = gui.seed_dialog()
|
||||||
wallet.init_mpk( wallet.seed )
|
if not sg: exit()
|
||||||
|
seed, gap = sg
|
||||||
|
if not seed: exit()
|
||||||
|
wallet.gap_limit = gap
|
||||||
|
if len(seed) == 128:
|
||||||
|
wallet.seed = None
|
||||||
|
wallet.master_public_key = seed
|
||||||
|
else:
|
||||||
|
wallet.seed = str(seed)
|
||||||
|
wallet.init_mpk( wallet.seed )
|
||||||
|
|
||||||
|
|
||||||
# generate the first addresses, in case we are offline
|
# generate the first addresses, in case we are offline
|
||||||
if s is None or a == 'create':
|
if s is None or a == 'create':
|
||||||
|
@ -301,10 +315,14 @@ if __name__ == '__main__':
|
||||||
if not seed:
|
if not seed:
|
||||||
sys.exit("Error: No seed")
|
sys.exit("Error: No seed")
|
||||||
|
|
||||||
wallet.seed = str(seed)
|
if len(seed) == 128:
|
||||||
wallet.init_mpk( wallet.seed )
|
wallet.seed = None
|
||||||
if not options.offline:
|
wallet.master_public_key = seed
|
||||||
|
else:
|
||||||
|
wallet.seed = str(seed)
|
||||||
|
wallet.init_mpk( wallet.seed )
|
||||||
|
|
||||||
|
if not options.offline:
|
||||||
interface = Interface(config)
|
interface = Interface(config)
|
||||||
interface.start()
|
interface.start()
|
||||||
wallet.interface = interface
|
wallet.interface = interface
|
||||||
|
@ -378,24 +396,31 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
# commands needing password
|
# commands needing password
|
||||||
if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
|
if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
|
||||||
password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
|
if wallet.use_encryption and not is_temporary:
|
||||||
# check password
|
password = prompt_password('Password:', False)
|
||||||
try:
|
if not password:
|
||||||
wallet.pw_decode( wallet.seed, password)
|
print_msg("Error: Password required")
|
||||||
except:
|
exit(1)
|
||||||
print_msg("Error: This password does not decode this wallet.")
|
# check password
|
||||||
exit(1)
|
try:
|
||||||
|
seed = wallet.decode_seed(password)
|
||||||
|
except:
|
||||||
|
print_msg("Error: This password does not decode this wallet.")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
password = None
|
||||||
|
seed = wallet.seed
|
||||||
|
|
||||||
if cmd == 'import':
|
if cmd == 'importprivkey':
|
||||||
# See if they specificed a key on the cmd line, if not prompt
|
# See if they specificed a key on the cmd line, if not prompt
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
keypair = args[1]
|
sec = args[1]
|
||||||
else:
|
else:
|
||||||
keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
|
sec = prompt_password('Enter PrivateKey (will not echo):', False)
|
||||||
try:
|
try:
|
||||||
wallet.import_key(keypair,password)
|
addr = wallet.import_key(sec,password)
|
||||||
wallet.save()
|
wallet.save()
|
||||||
print_msg("Keypair imported")
|
print_msg("Keypair imported: ", addr)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
print_msg("Error: Keypair import failed: " + str(e))
|
print_msg("Error: Keypair import failed: " + str(e))
|
||||||
|
|
||||||
|
@ -410,7 +435,6 @@ if __name__ == '__main__':
|
||||||
print_msg(known_commands[cmd2])
|
print_msg(known_commands[cmd2])
|
||||||
|
|
||||||
elif cmd == 'seed':
|
elif cmd == 'seed':
|
||||||
seed = wallet.pw_decode( wallet.seed, password)
|
|
||||||
print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
|
print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
|
||||||
|
|
||||||
elif cmd == 'deseed':
|
elif cmd == 'deseed':
|
||||||
|
@ -613,11 +637,6 @@ if __name__ == '__main__':
|
||||||
print_msg(h)
|
print_msg(h)
|
||||||
|
|
||||||
elif cmd == 'password':
|
elif cmd == 'password':
|
||||||
try:
|
|
||||||
seed = wallet.pw_decode( wallet.seed, password)
|
|
||||||
except ValueError:
|
|
||||||
sys.exit("Error: Password does not decrypt this wallet.")
|
|
||||||
|
|
||||||
new_password = prompt_password('New password:')
|
new_password = prompt_password('New password:')
|
||||||
wallet.update_password(seed, password, new_password)
|
wallet.update_password(seed, password, new_password)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
<file>icons/status_connected.png</file>
|
<file>icons/status_connected.png</file>
|
||||||
<file>icons/status_disconnected.png</file>
|
<file>icons/status_disconnected.png</file>
|
||||||
<file>icons/status_waiting.png</file>
|
<file>icons/status_waiting.png</file>
|
||||||
|
<file>icons/switchgui.png</file>
|
||||||
<file>icons/unconfirmed.png</file>
|
<file>icons/unconfirmed.png</file>
|
||||||
<file>icons/network.png</file>
|
<file>icons/network.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -1,6 +1,8 @@
|
||||||
from version import ELECTRUM_VERSION
|
from version import ELECTRUM_VERSION
|
||||||
from util import format_satoshis, print_msg, print_error, set_verbosity
|
from util import format_satoshis, print_msg, print_error, set_verbosity
|
||||||
from wallet import Wallet, WalletSynchronizer
|
from i18n import set_language
|
||||||
|
from wallet import WalletSynchronizer
|
||||||
|
from wallet_factory import WalletFactory as Wallet
|
||||||
from verifier import WalletVerifier
|
from verifier import WalletVerifier
|
||||||
from interface import Interface, pick_random_server, DEFAULT_SERVERS
|
from interface import Interface, pick_random_server, DEFAULT_SERVERS
|
||||||
from simple_config import SimpleConfig
|
from simple_config import SimpleConfig
|
||||||
|
|
|
@ -43,8 +43,57 @@ Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest()
|
||||||
hash_encode = lambda x: x[::-1].encode('hex')
|
hash_encode = lambda x: x[::-1].encode('hex')
|
||||||
hash_decode = lambda x: x.decode('hex')[::-1]
|
hash_decode = lambda x: x.decode('hex')[::-1]
|
||||||
|
|
||||||
############ functions from pywallet #####################
|
|
||||||
|
|
||||||
|
# pywallet openssl private key implementation
|
||||||
|
|
||||||
|
def i2d_ECPrivateKey(pkey, compressed=False):
|
||||||
|
if compressed:
|
||||||
|
key = '3081d30201010420' + \
|
||||||
|
'%064x' % pkey.secret + \
|
||||||
|
'a081a53081a2020101302c06072a8648ce3d0101022100' + \
|
||||||
|
'%064x' % _p + \
|
||||||
|
'3006040100040107042102' + \
|
||||||
|
'%064x' % _Gx + \
|
||||||
|
'022100' + \
|
||||||
|
'%064x' % _r + \
|
||||||
|
'020101a124032200'
|
||||||
|
else:
|
||||||
|
key = '308201130201010420' + \
|
||||||
|
'%064x' % pkey.secret + \
|
||||||
|
'a081a53081a2020101302c06072a8648ce3d0101022100' + \
|
||||||
|
'%064x' % _p + \
|
||||||
|
'3006040100040107044104' + \
|
||||||
|
'%064x' % _Gx + \
|
||||||
|
'%064x' % _Gy + \
|
||||||
|
'022100' + \
|
||||||
|
'%064x' % _r + \
|
||||||
|
'020101a144034200'
|
||||||
|
|
||||||
|
return key.decode('hex') + i2o_ECPublicKey(pkey, compressed)
|
||||||
|
|
||||||
|
def i2o_ECPublicKey(pkey, compressed=False):
|
||||||
|
# public keys are 65 bytes long (520 bits)
|
||||||
|
# 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate
|
||||||
|
# 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed
|
||||||
|
# compressed keys: <sign> <x> where <sign> is 0x02 if y is even and 0x03 if y is odd
|
||||||
|
if compressed:
|
||||||
|
if pkey.pubkey.point.y() & 1:
|
||||||
|
key = '03' + '%064x' % pkey.pubkey.point.x()
|
||||||
|
else:
|
||||||
|
key = '02' + '%064x' % pkey.pubkey.point.x()
|
||||||
|
else:
|
||||||
|
key = '04' + \
|
||||||
|
'%064x' % pkey.pubkey.point.x() + \
|
||||||
|
'%064x' % pkey.pubkey.point.y()
|
||||||
|
|
||||||
|
return key.decode('hex')
|
||||||
|
|
||||||
|
# end pywallet openssl private key implementation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
############ functions from pywallet #####################
|
||||||
|
|
||||||
addrtype = 0
|
addrtype = 0
|
||||||
|
|
||||||
def hash_160(public_key):
|
def hash_160(public_key):
|
||||||
|
@ -151,17 +200,39 @@ def DecodeBase58Check(psz):
|
||||||
def PrivKeyToSecret(privkey):
|
def PrivKeyToSecret(privkey):
|
||||||
return privkey[9:9+32]
|
return privkey[9:9+32]
|
||||||
|
|
||||||
def SecretToASecret(secret):
|
def SecretToASecret(secret, compressed=False):
|
||||||
vchIn = chr(addrtype+128) + secret
|
vchIn = chr((addrtype+128)&255) + secret
|
||||||
|
if compressed: vchIn += '\01'
|
||||||
return EncodeBase58Check(vchIn)
|
return EncodeBase58Check(vchIn)
|
||||||
|
|
||||||
def ASecretToSecret(key):
|
def ASecretToSecret(key):
|
||||||
vch = DecodeBase58Check(key)
|
vch = DecodeBase58Check(key)
|
||||||
if vch and vch[0] == chr(addrtype+128):
|
if vch and vch[0] == chr((addrtype+128)&255):
|
||||||
return vch[1:]
|
return vch[1:]
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def regenerate_key(sec):
|
||||||
|
b = ASecretToSecret(sec)
|
||||||
|
if not b:
|
||||||
|
return False
|
||||||
|
b = b[0:32]
|
||||||
|
secret = int('0x' + b.encode('hex'), 16)
|
||||||
|
return EC_KEY(secret)
|
||||||
|
|
||||||
|
def GetPubKey(pkey, compressed=False):
|
||||||
|
return i2o_ECPublicKey(pkey, compressed)
|
||||||
|
|
||||||
|
def GetPrivKey(pkey, compressed=False):
|
||||||
|
return i2d_ECPrivateKey(pkey, compressed)
|
||||||
|
|
||||||
|
def GetSecret(pkey):
|
||||||
|
return ('%064x' % pkey.secret).decode('hex')
|
||||||
|
|
||||||
|
def is_compressed(sec):
|
||||||
|
b = ASecretToSecret(sec)
|
||||||
|
return len(b) == 33
|
||||||
|
|
||||||
########### end pywallet functions #######################
|
########### end pywallet functions #######################
|
||||||
|
|
||||||
# secp256k1, http://www.oid-info.com/get/1.3.132.0.10
|
# secp256k1, http://www.oid-info.com/get/1.3.132.0.10
|
||||||
|
@ -176,6 +247,13 @@ generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r )
|
||||||
oid_secp256k1 = (1,3,132,0,10)
|
oid_secp256k1 = (1,3,132,0,10)
|
||||||
SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 )
|
SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 )
|
||||||
|
|
||||||
|
class EC_KEY(object):
|
||||||
|
def __init__( self, secret ):
|
||||||
|
self.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret )
|
||||||
|
self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )
|
||||||
|
self.secret = secret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def filter(s):
|
def filter(s):
|
||||||
out = re.sub('( [^\n]*|)\n','',s)
|
out = re.sub('( [^\n]*|)\n','',s)
|
||||||
|
@ -195,7 +273,6 @@ def raw_tx( inputs, outputs, for_sig = None ):
|
||||||
sig = sig + chr(1) # hashtype
|
sig = sig + chr(1) # hashtype
|
||||||
script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig)
|
script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig)
|
||||||
script += sig.encode('hex') + ' sig\n'
|
script += sig.encode('hex') + ' sig\n'
|
||||||
pubkey = chr(4) + pubkey
|
|
||||||
script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey)
|
script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey)
|
||||||
script += pubkey.encode('hex') + ' pubkey\n'
|
script += pubkey.encode('hex') + ' pubkey\n'
|
||||||
elif for_sig==i:
|
elif for_sig==i:
|
||||||
|
|
|
@ -28,31 +28,32 @@ class Exchanger(threading.Thread):
|
||||||
self.discovery()
|
self.discovery()
|
||||||
|
|
||||||
def discovery(self):
|
def discovery(self):
|
||||||
connection = httplib.HTTPSConnection('intersango.com')
|
try:
|
||||||
connection.request("GET", "/api/ticker.php")
|
connection = httplib.HTTPSConnection('blockchain.info')
|
||||||
|
connection.request("GET", "/ticker")
|
||||||
|
except:
|
||||||
|
return
|
||||||
response = connection.getresponse()
|
response = connection.getresponse()
|
||||||
if response.reason == httplib.responses[httplib.NOT_FOUND]:
|
if response.reason == httplib.responses[httplib.NOT_FOUND]:
|
||||||
return
|
return
|
||||||
response = json.loads(response.read())
|
response = json.loads(response.read())
|
||||||
# 1 = BTC:GBP
|
|
||||||
# 2 = BTC:EUR
|
|
||||||
# 3 = BTC:USD
|
|
||||||
# 4 = BTC:PLN
|
|
||||||
quote_currencies = {}
|
quote_currencies = {}
|
||||||
try:
|
try:
|
||||||
quote_currencies["GBP"] = self._lookup_rate(response, 1)
|
for r in response:
|
||||||
quote_currencies["EUR"] = self._lookup_rate(response, 2)
|
quote_currencies[r] = self._lookup_rate(response, r)
|
||||||
quote_currencies["USD"] = self._lookup_rate(response, 3)
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.quote_currencies = quote_currencies
|
self.quote_currencies = quote_currencies
|
||||||
self.parent.emit(SIGNAL("refresh_balance()"))
|
self.parent.emit(SIGNAL("refresh_balance()"))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_currencies(self):
|
||||||
|
return [] if self.quote_currencies == None else sorted(self.quote_currencies.keys())
|
||||||
|
|
||||||
def _lookup_rate(self, response, quote_id):
|
def _lookup_rate(self, response, quote_id):
|
||||||
return decimal.Decimal(response[str(quote_id)]["last"])
|
return decimal.Decimal(str(response[str(quote_id)]["15m"]))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
exch = Exchanger(("EUR", "USD", "GBP"))
|
exch = Exchanger(("BRL", "CNY", "EUR", "GBP", "RUB", "USD"))
|
||||||
print exch.exchange(1, "EUR")
|
print exch.exchange(1, "EUR")
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ def show_seed_dialog(wallet, password, parent):
|
||||||
show_message("No seed")
|
show_message("No seed")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
seed = wallet.pw_decode( wallet.seed, password)
|
seed = wallet.decode_seed(password)
|
||||||
except:
|
except:
|
||||||
show_message("Incorrect password")
|
show_message("Incorrect password")
|
||||||
return
|
return
|
||||||
|
@ -164,10 +164,7 @@ def run_recovery_dialog(wallet):
|
||||||
show_message("no seed")
|
show_message("no seed")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
wallet.seed = seed
|
return seed, gap
|
||||||
wallet.gap_limit = gap
|
|
||||||
wallet.save()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -477,7 +474,7 @@ def change_password_dialog(wallet, parent, icon):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
seed = wallet.pw_decode( wallet.seed, password)
|
seed = wallet.decode_seed(password)
|
||||||
except:
|
except:
|
||||||
show_message("Incorrect password")
|
show_message("Incorrect password")
|
||||||
return
|
return
|
||||||
|
|
|
@ -709,7 +709,7 @@ def seed_dialog():
|
||||||
password = None
|
password = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
seed = wallet.pw_decode( wallet.seed, password)
|
seed = wallet.decode_seed(password)
|
||||||
except:
|
except:
|
||||||
modal_dialog('error','incorrect password')
|
modal_dialog('error','incorrect password')
|
||||||
return
|
return
|
||||||
|
@ -725,7 +725,7 @@ def change_password_dialog():
|
||||||
password = None
|
password = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
seed = wallet.pw_decode( wallet.seed, password)
|
seed = wallet.decode_seed(password)
|
||||||
except:
|
except:
|
||||||
modal_dialog('error','incorrect password')
|
modal_dialog('error','incorrect password')
|
||||||
return
|
return
|
||||||
|
@ -956,8 +956,10 @@ class ElectrumGui:
|
||||||
except:
|
except:
|
||||||
modal_dialog('error: could not decode this seed')
|
modal_dialog('error: could not decode this seed')
|
||||||
return
|
return
|
||||||
wallet.seed = str(seed)
|
|
||||||
return True
|
gap = 5 # default
|
||||||
|
|
||||||
|
return str(seed), gap
|
||||||
|
|
||||||
|
|
||||||
def network_dialog(self):
|
def network_dialog(self):
|
||||||
|
|
203
lib/gui_lite.py
203
lib/gui_lite.py
|
@ -31,6 +31,7 @@ import util
|
||||||
import csv
|
import csv
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from version import ELECTRUM_VERSION as electrum_version
|
||||||
from wallet import format_satoshis
|
from wallet import format_satoshis
|
||||||
import gui_qt
|
import gui_qt
|
||||||
import shutil
|
import shutil
|
||||||
|
@ -85,16 +86,62 @@ def load_theme_paths():
|
||||||
return theme_paths
|
return theme_paths
|
||||||
|
|
||||||
|
|
||||||
|
def csv_transaction(wallet):
|
||||||
|
try:
|
||||||
|
fileName = QFileDialog.getSaveFileName(QWidget(), 'Select file to export your wallet transactions to', os.path.expanduser('~/electrum-history.csv'), "*.csv")
|
||||||
|
if fileName:
|
||||||
|
with open(fileName, "w+") as csvfile:
|
||||||
|
transaction = csv.writer(csvfile)
|
||||||
|
transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
|
||||||
|
for item in wallet.get_tx_history():
|
||||||
|
tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
|
||||||
|
if confirmations:
|
||||||
|
if timestamp is not None:
|
||||||
|
try:
|
||||||
|
time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
|
||||||
|
except [RuntimeError, TypeError, NameError] as reason:
|
||||||
|
time_string = "unknown"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
time_string = "unknown"
|
||||||
|
else:
|
||||||
|
time_string = "pending"
|
||||||
|
|
||||||
|
if value is not None:
|
||||||
|
value_string = format_satoshis(value, True, wallet.num_zeros)
|
||||||
|
else:
|
||||||
|
value_string = '--'
|
||||||
|
|
||||||
|
if fee is not None:
|
||||||
|
fee_string = format_satoshis(fee, True, wallet.num_zeros)
|
||||||
|
else:
|
||||||
|
fee_string = '0'
|
||||||
|
|
||||||
|
if tx_hash:
|
||||||
|
label, is_default_label = wallet.get_label(tx_hash)
|
||||||
|
else:
|
||||||
|
label = ""
|
||||||
|
|
||||||
|
balance_string = format_satoshis(balance, False, wallet.num_zeros)
|
||||||
|
transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
|
||||||
|
QMessageBox.information(None,"CSV Export created", "Your CSV export has been successfully created.")
|
||||||
|
except (IOError, os.error), reason:
|
||||||
|
QMessageBox.critical(None,"Unable to create csv", "Electrum was unable to produce a transaction export.\n" + str(reason))
|
||||||
|
|
||||||
|
|
||||||
class ElectrumGui(QObject):
|
class ElectrumGui(QObject):
|
||||||
|
|
||||||
def __init__(self, wallet, config):
|
def __init__(self, wallet, config, expert=None):
|
||||||
super(QObject, self).__init__()
|
super(QObject, self).__init__()
|
||||||
|
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
self.config = config
|
self.config = config
|
||||||
self.check_qt_version()
|
self.check_qt_version()
|
||||||
self.app = QApplication(sys.argv)
|
self.expert = expert
|
||||||
|
if self.expert != None:
|
||||||
|
self.app = self.expert.app
|
||||||
|
else:
|
||||||
|
self.app = QApplication(sys.argv)
|
||||||
|
|
||||||
def check_qt_version(self):
|
def check_qt_version(self):
|
||||||
qtVersion = qVersion()
|
qtVersion = qVersion()
|
||||||
|
@ -121,17 +168,19 @@ class ElectrumGui(QObject):
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
self.set_url(url)
|
self.set_url(url)
|
||||||
|
|
||||||
timer = Timer()
|
if self.expert == None:
|
||||||
timer.start()
|
timer = Timer()
|
||||||
self.expert = gui_qt.ElectrumWindow(self.wallet, self.config)
|
timer.start()
|
||||||
self.expert.app = self.app
|
self.expert = gui_qt.ElectrumWindow(self.wallet, self.config)
|
||||||
self.expert.connect_slots(timer)
|
self.expert.app = self.app
|
||||||
self.expert.update_wallet()
|
self.expert.connect_slots(timer)
|
||||||
self.app.exec_()
|
self.expert.update_wallet()
|
||||||
|
self.app.exec_()
|
||||||
|
|
||||||
def expand(self):
|
def expand(self):
|
||||||
"""Hide the lite mode window and show pro-mode."""
|
"""Hide the lite mode window and show pro-mode."""
|
||||||
|
self.config.set_key('gui', 'classic', True)
|
||||||
self.mini.hide()
|
self.mini.hide()
|
||||||
self.expert.show()
|
self.expert.show()
|
||||||
|
|
||||||
|
@ -196,7 +245,7 @@ class MiniWindow(QDialog):
|
||||||
self.actuator = actuator
|
self.actuator = actuator
|
||||||
self.config = config
|
self.config = config
|
||||||
self.btc_balance = None
|
self.btc_balance = None
|
||||||
self.quote_currencies = ["EUR", "USD", "GBP"]
|
self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
|
||||||
self.actuator.set_configured_currency(self.set_quote_currency)
|
self.actuator.set_configured_currency(self.set_quote_currency)
|
||||||
self.exchanger = exchange_rate.Exchanger(self)
|
self.exchanger = exchange_rate.Exchanger(self)
|
||||||
# Needed because price discovery is done in a different thread
|
# Needed because price discovery is done in a different thread
|
||||||
|
@ -209,12 +258,12 @@ class MiniWindow(QDialog):
|
||||||
|
|
||||||
# Bitcoin address code
|
# Bitcoin address code
|
||||||
self.address_input = QLineEdit()
|
self.address_input = QLineEdit()
|
||||||
self.address_input.setPlaceholderText(_("Enter a Bitcoin address..."))
|
self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
|
||||||
self.address_input.setObjectName("address_input")
|
self.address_input.setObjectName("address_input")
|
||||||
|
|
||||||
self.address_input.setFocusPolicy(Qt.ClickFocus)
|
self.address_input.setFocusPolicy(Qt.ClickFocus)
|
||||||
|
|
||||||
self.address_input.textEdited.connect(self.address_field_changed)
|
self.address_input.textChanged.connect(self.address_field_changed)
|
||||||
resize_line_edit_width(self.address_input,
|
resize_line_edit_width(self.address_input,
|
||||||
"1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
|
"1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
|
||||||
|
|
||||||
|
@ -249,27 +298,28 @@ class MiniWindow(QDialog):
|
||||||
self.send_button.clicked.connect(self.send)
|
self.send_button.clicked.connect(self.send)
|
||||||
|
|
||||||
# Creating the receive button
|
# Creating the receive button
|
||||||
self.receive_button = QPushButton(_("&Receive"))
|
self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
|
||||||
self.receive_button.setObjectName("receive_button")
|
self.switch_button.setMaximumWidth(25)
|
||||||
self.receive_button.setDefault(True)
|
self.switch_button.setFlat(True)
|
||||||
|
self.switch_button.clicked.connect(expand_callback)
|
||||||
|
|
||||||
main_layout = QGridLayout(self)
|
main_layout = QGridLayout(self)
|
||||||
|
|
||||||
main_layout.addWidget(self.balance_label, 0, 0)
|
main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
|
||||||
main_layout.addWidget(self.receive_button, 0, 1)
|
main_layout.addWidget(self.switch_button, 0, 3)
|
||||||
|
|
||||||
main_layout.addWidget(self.address_input, 1, 0)
|
main_layout.addWidget(self.address_input, 1, 0, 1, 4)
|
||||||
|
main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
|
||||||
|
main_layout.addWidget(self.send_button, 2, 2, 1, 2)
|
||||||
|
|
||||||
main_layout.addWidget(self.amount_input, 2, 0)
|
self.send_button.setMaximumWidth(125)
|
||||||
main_layout.addWidget(self.send_button, 2, 1)
|
|
||||||
|
|
||||||
self.history_list = history_widget.HistoryWidget()
|
self.history_list = history_widget.HistoryWidget()
|
||||||
self.history_list.setObjectName("history")
|
self.history_list.setObjectName("history")
|
||||||
self.history_list.hide()
|
self.history_list.hide()
|
||||||
self.history_list.setAlternatingRowColors(True)
|
self.history_list.setAlternatingRowColors(True)
|
||||||
|
|
||||||
main_layout.addWidget(self.history_list, 3, 0, 1, 2)
|
main_layout.addWidget(self.history_list, 3, 0, 1, 4)
|
||||||
|
|
||||||
|
|
||||||
self.receiving = receiving_widget.ReceivingWidget(self)
|
self.receiving = receiving_widget.ReceivingWidget(self)
|
||||||
self.receiving.setObjectName("receiving")
|
self.receiving.setObjectName("receiving")
|
||||||
|
@ -288,6 +338,7 @@ class MiniWindow(QDialog):
|
||||||
self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
|
self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
|
||||||
self.receiving.itemChanged.connect(self.receiving.update_label)
|
self.receiving.itemChanged.connect(self.receiving.update_label)
|
||||||
|
|
||||||
|
|
||||||
# Label
|
# Label
|
||||||
extra_layout.addWidget( QLabel(_('Selecting an address will copy it to the clipboard.\nDouble clicking the label will allow you to edit it.') ),0,0)
|
extra_layout.addWidget( QLabel(_('Selecting an address will copy it to the clipboard.\nDouble clicking the label will allow you to edit it.') ),0,0)
|
||||||
|
|
||||||
|
@ -296,18 +347,15 @@ class MiniWindow(QDialog):
|
||||||
extra_layout.setColumnMinimumWidth(0,200)
|
extra_layout.setColumnMinimumWidth(0,200)
|
||||||
|
|
||||||
self.receiving_box.setLayout(extra_layout)
|
self.receiving_box.setLayout(extra_layout)
|
||||||
main_layout.addWidget(self.receiving_box,0,3,-1,3)
|
main_layout.addWidget(self.receiving_box,0,4,-1,3)
|
||||||
self.receiving_box.hide()
|
self.receiving_box.hide()
|
||||||
|
|
||||||
self.receive_button.clicked.connect(self.toggle_receiving_layout)
|
|
||||||
|
|
||||||
# Creating the menu bar
|
# Creating the menu bar
|
||||||
menubar = QMenuBar()
|
menubar = QMenuBar()
|
||||||
electrum_menu = menubar.addMenu(_("&Bitcoin"))
|
electrum_menu = menubar.addMenu(_("&Electrum"))
|
||||||
|
|
||||||
electrum_menu.addSeparator()
|
quit_option = electrum_menu.addAction(_("&Close"))
|
||||||
|
|
||||||
quit_option = electrum_menu.addAction(_("&Quit"))
|
|
||||||
quit_option.triggered.connect(self.close)
|
quit_option.triggered.connect(self.close)
|
||||||
|
|
||||||
view_menu = menubar.addMenu(_("&View"))
|
view_menu = menubar.addMenu(_("&View"))
|
||||||
|
@ -317,7 +365,7 @@ class MiniWindow(QDialog):
|
||||||
backup_wallet.triggered.connect(self.backup_wallet)
|
backup_wallet.triggered.connect(self.backup_wallet)
|
||||||
|
|
||||||
export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
|
export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
|
||||||
export_csv.triggered.connect(self.actuator.csv_transaction)
|
export_csv.triggered.connect(lambda: csv_transaction(self.actuator.wallet))
|
||||||
|
|
||||||
master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
|
master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
|
||||||
master_key.triggered.connect(self.actuator.copy_master_public_key)
|
master_key.triggered.connect(self.actuator.copy_master_public_key)
|
||||||
|
@ -343,6 +391,18 @@ class MiniWindow(QDialog):
|
||||||
theme_action.toggled.connect(delegate)
|
theme_action.toggled.connect(delegate)
|
||||||
theme_group.addAction(theme_action)
|
theme_group.addAction(theme_action)
|
||||||
view_menu.addSeparator()
|
view_menu.addSeparator()
|
||||||
|
|
||||||
|
show_receiving = view_menu.addAction(_("Show Receiving addresses"))
|
||||||
|
show_receiving.setCheckable(True)
|
||||||
|
show_receiving.toggled.connect(self.toggle_receiving_layout)
|
||||||
|
|
||||||
|
show_receiving_toggle = self.config.get("gui_show_receiving",False)
|
||||||
|
show_receiving.setChecked(show_receiving_toggle)
|
||||||
|
self.show_receiving = show_receiving
|
||||||
|
|
||||||
|
self.toggle_receiving_layout(show_receiving_toggle)
|
||||||
|
|
||||||
|
|
||||||
show_history = view_menu.addAction(_("Show History"))
|
show_history = view_menu.addAction(_("Show History"))
|
||||||
show_history.setCheckable(True)
|
show_history.setCheckable(True)
|
||||||
show_history.toggled.connect(self.show_history)
|
show_history.toggled.connect(self.show_history)
|
||||||
|
@ -377,19 +437,6 @@ class MiniWindow(QDialog):
|
||||||
self.setObjectName("main_window")
|
self.setObjectName("main_window")
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def toggle_receiving_layout(self):
|
|
||||||
if self.receiving_box.isVisible():
|
|
||||||
self.receiving_box.hide()
|
|
||||||
self.receive_button.setProperty("isActive", False)
|
|
||||||
|
|
||||||
qApp.style().unpolish(self.receive_button)
|
|
||||||
qApp.style().polish(self.receive_button)
|
|
||||||
else:
|
|
||||||
self.receiving_box.show()
|
|
||||||
self.receive_button.setProperty("isActive", 'true')
|
|
||||||
|
|
||||||
qApp.style().unpolish(self.receive_button)
|
|
||||||
qApp.style().polish(self.receive_button)
|
|
||||||
|
|
||||||
def toggle_theme(self, theme_name):
|
def toggle_theme(self, theme_name):
|
||||||
old_path = QDir.currentPath()
|
old_path = QDir.currentPath()
|
||||||
|
@ -403,6 +450,7 @@ class MiniWindow(QDialog):
|
||||||
g = self.geometry()
|
g = self.geometry()
|
||||||
self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
|
self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
|
||||||
self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
|
self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
|
||||||
|
self.config.set_key("gui_show_receiving", self.receiving_box.isVisible(),True)
|
||||||
|
|
||||||
super(MiniWindow, self).closeEvent(event)
|
super(MiniWindow, self).closeEvent(event)
|
||||||
qApp.quit()
|
qApp.quit()
|
||||||
|
@ -447,7 +495,7 @@ class MiniWindow(QDialog):
|
||||||
quote_text = "(%s)" % quote_text
|
quote_text = "(%s)" % quote_text
|
||||||
btc_balance = "%.2f" % (btc_balance / bitcoin(1))
|
btc_balance = "%.2f" % (btc_balance / bitcoin(1))
|
||||||
self.balance_label.set_balance_text(btc_balance, quote_text)
|
self.balance_label.set_balance_text(btc_balance, quote_text)
|
||||||
self.setWindowTitle("Electrum - %s BTC" % btc_balance)
|
self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
|
||||||
|
|
||||||
def amount_input_changed(self, amount_text):
|
def amount_input_changed(self, amount_text):
|
||||||
"""Update the number of bitcoins displayed."""
|
"""Update the number of bitcoins displayed."""
|
||||||
|
@ -499,6 +547,13 @@ class MiniWindow(QDialog):
|
||||||
self.send_button.setDisabled(True)
|
self.send_button.setDisabled(True)
|
||||||
|
|
||||||
def address_field_changed(self, address):
|
def address_field_changed(self, address):
|
||||||
|
# label or alias, with address in brackets
|
||||||
|
match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
|
||||||
|
address)
|
||||||
|
if match2:
|
||||||
|
address = match2.group(2)
|
||||||
|
self.address_input.setText(address)
|
||||||
|
|
||||||
if self.actuator.is_valid(address):
|
if self.actuator.is_valid(address):
|
||||||
self.check_button_status()
|
self.check_button_status()
|
||||||
self.address_input.setProperty("isValid", True)
|
self.address_input.setProperty("isValid", True)
|
||||||
|
@ -540,15 +595,21 @@ class MiniWindow(QDialog):
|
||||||
self.actuator.acceptbit(self.quote_currencies[0])
|
self.actuator.acceptbit(self.quote_currencies[0])
|
||||||
|
|
||||||
def the_website(self):
|
def the_website(self):
|
||||||
webbrowser.open("http://electrum-desktop.com")
|
webbrowser.open("http://electrum.org")
|
||||||
|
|
||||||
def show_about(self):
|
def show_about(self):
|
||||||
QMessageBox.about(self, "Electrum",
|
QMessageBox.about(self, "Electrum",
|
||||||
_("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjuction with high-performance servers that handle the most complicated parts of the Bitcoin system.\n\nSend donations to 1JwTMv4GWaPdf931N6LNPJeZBfZgZJ3zX1"))
|
_("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
|
||||||
|
|
||||||
def show_report_bug(self):
|
def show_report_bug(self):
|
||||||
QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
|
QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
|
||||||
_("Please report any bugs as issues on github: https://github.com/spesmilo/electrum/issues"))
|
_("Please report any bugs as issues on github: <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>"))
|
||||||
|
|
||||||
|
def toggle_receiving_layout(self, toggle_state):
|
||||||
|
if toggle_state:
|
||||||
|
self.receiving_box.show()
|
||||||
|
else:
|
||||||
|
self.receiving_box.hide()
|
||||||
|
|
||||||
def show_history(self, toggle_state):
|
def show_history(self, toggle_state):
|
||||||
if toggle_state:
|
if toggle_state:
|
||||||
|
@ -684,7 +745,7 @@ class ReceivePopup(QDialog):
|
||||||
|
|
||||||
class MiniActuator:
|
class MiniActuator:
|
||||||
"""Initialize the definitions relating to themes and
|
"""Initialize the definitions relating to themes and
|
||||||
sending/recieving bitcoins."""
|
sending/receiving bitcoins."""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, wallet):
|
def __init__(self, wallet):
|
||||||
|
@ -761,47 +822,6 @@ class MiniActuator:
|
||||||
w.exec_()
|
w.exec_()
|
||||||
w.destroy()
|
w.destroy()
|
||||||
|
|
||||||
def csv_transaction(self):
|
|
||||||
try:
|
|
||||||
fileName = QFileDialog.getSaveFileName(QWidget(), 'Select file to export your wallet transactions to', os.path.expanduser('~/'), "*.csv")
|
|
||||||
if fileName:
|
|
||||||
with open(fileName, "w+") as csvfile:
|
|
||||||
transaction = csv.writer(csvfile)
|
|
||||||
transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
|
|
||||||
for item in self.wallet.get_tx_history():
|
|
||||||
tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
|
|
||||||
if confirmations:
|
|
||||||
if timestamp is not None:
|
|
||||||
try:
|
|
||||||
time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
|
|
||||||
except [RuntimeError, TypeError, NameError] as reason:
|
|
||||||
time_string = "unknown"
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
time_string = "unknown"
|
|
||||||
else:
|
|
||||||
time_string = "pending"
|
|
||||||
|
|
||||||
if value is not None:
|
|
||||||
value_string = format_satoshis(value, True, self.wallet.num_zeros)
|
|
||||||
else:
|
|
||||||
value_string = '--'
|
|
||||||
|
|
||||||
if fee is not None:
|
|
||||||
fee_string = format_satoshis(fee, True, self.wallet.num_zeros)
|
|
||||||
else:
|
|
||||||
fee_string = '0'
|
|
||||||
|
|
||||||
if tx_hash:
|
|
||||||
label, is_default_label = self.wallet.get_label(tx_hash)
|
|
||||||
else:
|
|
||||||
label = ""
|
|
||||||
|
|
||||||
balance_string = format_satoshis(balance, False, self.wallet.num_zeros)
|
|
||||||
transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
|
|
||||||
QMessageBox.information(None,"CSV Export created", "Your CSV export has been succesfully created.")
|
|
||||||
except (IOError, os.error), reason:
|
|
||||||
QMessageBox.critical(None,"Unable to create csv", "Electrum was unable to produce a transaction export.\n" + str(reason))
|
|
||||||
|
|
||||||
def send(self, address, amount, parent_window):
|
def send(self, address, amount, parent_window):
|
||||||
"""Send bitcoins to the target address."""
|
"""Send bitcoins to the target address."""
|
||||||
|
@ -879,12 +899,13 @@ class MiniActuator:
|
||||||
|
|
||||||
def is_valid(self, address):
|
def is_valid(self, address):
|
||||||
"""Check if bitcoin address is valid."""
|
"""Check if bitcoin address is valid."""
|
||||||
|
|
||||||
return self.wallet.is_valid(address)
|
return self.wallet.is_valid(address)
|
||||||
|
|
||||||
def copy_master_public_key(self):
|
def copy_master_public_key(self):
|
||||||
master_pubkey = self.wallet.master_public_key
|
master_pubkey = self.wallet.master_public_key
|
||||||
qApp.clipboard().setText(master_pubkey)
|
qApp.clipboard().setText(master_pubkey)
|
||||||
QMessageBox.information(None,"Copy succesful", "Your public master key has been copied to your clipboard.")
|
QMessageBox.information(None,"Copy successful", "Your master public key has been copied to your clipboard.")
|
||||||
|
|
||||||
|
|
||||||
def acceptbit(self, currency):
|
def acceptbit(self, currency):
|
||||||
|
|
735
lib/gui_qt.py
735
lib/gui_qt.py
File diff suppressed because it is too large
Load Diff
|
@ -20,5 +20,7 @@ class HistoryWidget(QTreeWidget):
|
||||||
if date is None:
|
if date is None:
|
||||||
date = "Unknown"
|
date = "Unknown"
|
||||||
item = QTreeWidgetItem([amount, address, date])
|
item = QTreeWidgetItem([amount, address, date])
|
||||||
|
if float(amount) < 0:
|
||||||
|
item.setForeground(0, QBrush(QColor("#BC1E1E")))
|
||||||
self.insertTopLevelItem(0, item)
|
self.insertTopLevelItem(0, item)
|
||||||
|
|
||||||
|
|
36
lib/i18n.py
36
lib/i18n.py
|
@ -16,10 +16,38 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import gettext
|
import gettext, os
|
||||||
|
|
||||||
LOCALE_DIR = '/usr/share/locale'
|
if os.path.exists('./locale'):
|
||||||
#LOCALE_DIR = './locale'
|
LOCALE_DIR = './locale'
|
||||||
|
else:
|
||||||
|
LOCALE_DIR = '/usr/share/locale'
|
||||||
|
|
||||||
language = gettext.translation('electrum', LOCALE_DIR, fallback = True)
|
language = gettext.translation('electrum', LOCALE_DIR, fallback = True)
|
||||||
_ = language.ugettext
|
|
||||||
|
def _(x):
|
||||||
|
global language
|
||||||
|
return language.ugettext(x)
|
||||||
|
|
||||||
|
def set_language(x):
|
||||||
|
global language
|
||||||
|
if x: language = gettext.translation('electrum', LOCALE_DIR, fallback = True, languages=[x])
|
||||||
|
|
||||||
|
|
||||||
|
languages = {
|
||||||
|
'':_('Default'),
|
||||||
|
'br':_('Brasilian'),
|
||||||
|
'cs':_('Czech'),
|
||||||
|
'de':_('German'),
|
||||||
|
'eo':_('Esperanto'),
|
||||||
|
'en':_('English'),
|
||||||
|
'es':_('Spanish'),
|
||||||
|
'fr':_('French'),
|
||||||
|
'it':_('Italian'),
|
||||||
|
'lv':_('Latvian'),
|
||||||
|
'nl':_('Dutch'),
|
||||||
|
'ru':_('Russian'),
|
||||||
|
'sl':_('Slovenian'),
|
||||||
|
'vi':_('Vietnamese'),
|
||||||
|
'zh':_('Chinese')
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import random, socket, ast, re, ssl
|
||||||
import threading, traceback, sys, time, json, Queue
|
import threading, traceback, sys, time, json, Queue
|
||||||
|
|
||||||
from version import ELECTRUM_VERSION, PROTOCOL_VERSION
|
from version import ELECTRUM_VERSION, PROTOCOL_VERSION
|
||||||
from util import print_error
|
from util import print_error, print_msg
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 5
|
DEFAULT_TIMEOUT = 5
|
||||||
|
@ -39,6 +39,11 @@ DEFAULT_SERVERS = [
|
||||||
'ecdsa.org:50001:t'
|
'ecdsa.org:50001:t'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# add only port 80 servers here
|
||||||
|
DEFAULT_HTTP_SERVERS = [
|
||||||
|
'electrum.no-ip.org:80:h'
|
||||||
|
]
|
||||||
|
|
||||||
proxy_modes = ['socks4', 'socks5', 'http']
|
proxy_modes = ['socks4', 'socks5', 'http']
|
||||||
|
|
||||||
|
|
||||||
|
@ -174,7 +179,14 @@ class Interface(threading.Thread):
|
||||||
self.init_server(host, port, proxy, use_ssl)
|
self.init_server(host, port, proxy, use_ssl)
|
||||||
self.session_id = None
|
self.session_id = None
|
||||||
self.connection_msg = ('https' if self.use_ssl else 'http') + '://%s:%d'%( self.host, self.port )
|
self.connection_msg = ('https' if self.use_ssl else 'http') + '://%s:%d'%( self.host, self.port )
|
||||||
self.is_connected = True
|
try:
|
||||||
|
self.poll()
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.session_id:
|
||||||
|
print_error('http session:',self.session_id)
|
||||||
|
self.is_connected = True
|
||||||
|
|
||||||
def run_http(self):
|
def run_http(self):
|
||||||
self.is_connected = True
|
self.is_connected = True
|
||||||
|
@ -232,7 +244,7 @@ class Interface(threading.Thread):
|
||||||
headers['cookie'] = 'SESSION=%s'%self.session_id
|
headers['cookie'] = 'SESSION=%s'%self.session_id
|
||||||
|
|
||||||
req = urllib2.Request(self.connection_msg, data_json, headers)
|
req = urllib2.Request(self.connection_msg, data_json, headers)
|
||||||
response_stream = urllib2.urlopen(req)
|
response_stream = urllib2.urlopen(req, timeout=DEFAULT_TIMEOUT)
|
||||||
|
|
||||||
for index, cookie in enumerate(cj):
|
for index, cookie in enumerate(cj):
|
||||||
if cookie.name=='SESSION':
|
if cookie.name=='SESSION':
|
||||||
|
@ -376,6 +388,7 @@ class Interface(threading.Thread):
|
||||||
self.servers = {} # actual list from IRC
|
self.servers = {} # actual list from IRC
|
||||||
self.rtime = 0
|
self.rtime = 0
|
||||||
self.bytes_received = 0
|
self.bytes_received = 0
|
||||||
|
self.is_connected = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -383,18 +396,28 @@ class Interface(threading.Thread):
|
||||||
if self.config.get('server'):
|
if self.config.get('server'):
|
||||||
self.init_with_server(self.config)
|
self.init_with_server(self.config)
|
||||||
else:
|
else:
|
||||||
print "Using random server..."
|
if self.config.get('auto_cycle') is None:
|
||||||
servers = DEFAULT_SERVERS[:]
|
self.config.set_key('auto_cycle', True, False)
|
||||||
while servers:
|
|
||||||
server = random.choice( servers )
|
if not self.is_connected and self.config.get('auto_cycle'):
|
||||||
servers.remove(server)
|
print_msg("Using random server...")
|
||||||
|
servers_tcp = DEFAULT_SERVERS[:]
|
||||||
|
servers_http = DEFAULT_HTTP_SERVERS[:]
|
||||||
|
while servers_tcp or servers_http:
|
||||||
|
if servers_tcp:
|
||||||
|
server = random.choice( servers_tcp )
|
||||||
|
servers_tcp.remove(server)
|
||||||
|
else:
|
||||||
|
# try HTTP if we can't get a TCP connection
|
||||||
|
server = random.choice( servers_http )
|
||||||
|
servers_http.remove(server)
|
||||||
|
print server
|
||||||
self.config.set_key('server', server, False)
|
self.config.set_key('server', server, False)
|
||||||
self.init_with_server(self.config)
|
self.init_with_server(self.config)
|
||||||
if self.is_connected: break
|
if self.is_connected: break
|
||||||
|
|
||||||
if not servers:
|
if not self.is_connected:
|
||||||
print 'no server available'
|
print 'no server available'
|
||||||
self.is_connected = False
|
|
||||||
self.connect_event.set() # to finish start
|
self.connect_event.set() # to finish start
|
||||||
self.server = 'ecdsa.org:50001:t'
|
self.server = 'ecdsa.org:50001:t'
|
||||||
self.proxy = None
|
self.proxy = None
|
||||||
|
|
|
@ -94,7 +94,7 @@ a SimpleConfig instance then reads the wallet file.
|
||||||
try:
|
try:
|
||||||
out = ast.literal_eval(out)
|
out = ast.literal_eval(out)
|
||||||
except:
|
except:
|
||||||
print "type error, using default value"
|
print "type error for '%s': using default value"%key
|
||||||
out = default
|
out = default
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
|
@ -298,7 +298,8 @@ class WalletVerifier(threading.Thread):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import urllib
|
import urllib, socket
|
||||||
|
socket.setdefaulttimeout(30)
|
||||||
print_error("downloading ", self.headers_url )
|
print_error("downloading ", self.headers_url )
|
||||||
urllib.urlretrieve(self.headers_url, filename)
|
urllib.urlretrieve(self.headers_url, filename)
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
ELECTRUM_VERSION = "1.5.7" # version of the client package
|
ELECTRUM_VERSION = "1.6.1" # version of the client package
|
||||||
PROTOCOL_VERSION = '0.6' # protocol version requested
|
PROTOCOL_VERSION = '0.6' # protocol version requested
|
||||||
SEED_VERSION = 4 # bump this everytime the seed generation is modified
|
SEED_VERSION = 4 # bump this everytime the seed generation is modified
|
||||||
TRANSLATION_ID = 33853 # version of the wiki page
|
TRANSLATION_ID = 34952 # version of the wiki page
|
||||||
|
|
126
lib/wallet.py
126
lib/wallet.py
|
@ -64,13 +64,13 @@ class Wallet:
|
||||||
self.addresses = config.get('addresses', []) # receiving addresses visible for user
|
self.addresses = config.get('addresses', []) # receiving addresses visible for user
|
||||||
self.change_addresses = config.get('change_addresses', []) # addresses used as change
|
self.change_addresses = config.get('change_addresses', []) # addresses used as change
|
||||||
self.seed = config.get('seed', '') # encrypted
|
self.seed = config.get('seed', '') # encrypted
|
||||||
self.labels = config.get('labels',{}) # labels for addresses and transactions
|
self.labels = config.get('labels',{'1NmduGNyC5XejoysbuioodCN3jR3yf64xM':'Electrum donation address'})
|
||||||
self.aliases = config.get('aliases', {}) # aliases for addresses
|
self.aliases = config.get('aliases', {}) # aliases for addresses
|
||||||
self.authorities = config.get('authorities', {}) # trusted addresses
|
self.authorities = config.get('authorities', {}) # trusted addresses
|
||||||
self.frozen_addresses = config.get('frozen_addresses',[])
|
self.frozen_addresses = config.get('frozen_addresses',[])
|
||||||
self.prioritized_addresses = config.get('prioritized_addresses',[])
|
self.prioritized_addresses = config.get('prioritized_addresses',[])
|
||||||
self.receipts = config.get('receipts',{}) # signed URIs
|
self.receipts = config.get('receipts',{}) # signed URIs
|
||||||
self.addressbook = config.get('contacts', []) # outgoing addresses, for payments
|
self.addressbook = config.get('contacts', ['1NmduGNyC5XejoysbuioodCN3jR3yf64xM'])
|
||||||
self.imported_keys = config.get('imported_keys',{})
|
self.imported_keys = config.get('imported_keys',{})
|
||||||
self.history = config.get('addr_history',{}) # address -> list(txid, height)
|
self.history = config.get('addr_history',{}) # address -> list(txid, height)
|
||||||
self.transactions = config.get('transactions',{}) # txid -> deserialised
|
self.transactions = config.get('transactions',{}) # txid -> deserialised
|
||||||
|
@ -112,23 +112,33 @@ class Wallet:
|
||||||
self.interface.poke('synchronizer')
|
self.interface.poke('synchronizer')
|
||||||
while not self.is_up_to_date(): time.sleep(0.1)
|
while not self.is_up_to_date(): time.sleep(0.1)
|
||||||
|
|
||||||
def import_key(self, keypair, password):
|
def import_key(self, sec, password):
|
||||||
address, key = keypair.split(':')
|
# try password
|
||||||
if not self.is_valid(address):
|
try:
|
||||||
raise BaseException('Invalid Bitcoin address')
|
seed = self.decode_seed(password)
|
||||||
|
except:
|
||||||
|
raise BaseException("Invalid password")
|
||||||
|
|
||||||
|
# rebuild public key from private key, compressed or uncompressed
|
||||||
|
pkey = regenerate_key(sec)
|
||||||
|
if not pkey:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# figure out if private key is compressed
|
||||||
|
compressed = is_compressed(sec)
|
||||||
|
|
||||||
|
# rebuild private and public key from regenerated secret
|
||||||
|
private_key = GetPrivKey(pkey, compressed)
|
||||||
|
public_key = GetPubKey(pkey, compressed)
|
||||||
|
address = public_key_to_bc_address(public_key)
|
||||||
|
|
||||||
if address in self.all_addresses():
|
if address in self.all_addresses():
|
||||||
raise BaseException('Address already in wallet')
|
raise BaseException('Address already in wallet')
|
||||||
b = ASecretToSecret( key )
|
|
||||||
if not b:
|
# store the originally requested keypair into the imported keys table
|
||||||
raise BaseException('Unsupported key format')
|
self.imported_keys[address] = self.pw_encode(sec, password )
|
||||||
secexp = int( b.encode('hex'), 16)
|
return address
|
||||||
private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve=SECP256k1 )
|
|
||||||
# sanity check
|
|
||||||
public_key = private_key.get_verifying_key()
|
|
||||||
if not address == public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() ):
|
|
||||||
raise BaseException('Address does not match private key')
|
|
||||||
self.imported_keys[address] = self.pw_encode( key, password )
|
|
||||||
|
|
||||||
|
|
||||||
def new_seed(self, password):
|
def new_seed(self, password):
|
||||||
seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
|
seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
|
||||||
|
@ -172,19 +182,22 @@ class Wallet:
|
||||||
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) )
|
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) )
|
||||||
|
|
||||||
def get_private_key_base58(self, address, password):
|
def get_private_key_base58(self, address, password):
|
||||||
pk = self.get_private_key(address, password)
|
secexp, compressed = self.get_private_key(address, password)
|
||||||
if pk is None: return None
|
if secexp is None: return None
|
||||||
return SecretToASecret( pk )
|
pk = number_to_string( secexp, generator_secp256k1.order() )
|
||||||
|
return SecretToASecret( pk, compressed )
|
||||||
|
|
||||||
def get_private_key(self, address, password):
|
def get_private_key(self, address, password):
|
||||||
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
||||||
order = generator_secp256k1.order()
|
order = generator_secp256k1.order()
|
||||||
|
|
||||||
if address in self.imported_keys.keys():
|
if address in self.imported_keys.keys():
|
||||||
b = self.pw_decode( self.imported_keys[address], password )
|
sec = self.pw_decode( self.imported_keys[address], password )
|
||||||
if not b: return None
|
if not sec: return None, None
|
||||||
b = ASecretToSecret( b )
|
pkey = regenerate_key(sec)
|
||||||
secexp = int( b.encode('hex'), 16)
|
compressed = is_compressed(sec)
|
||||||
|
secexp = pkey.secret
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if address in self.addresses:
|
if address in self.addresses:
|
||||||
n = self.addresses.index(address)
|
n = self.addresses.index(address)
|
||||||
|
@ -194,27 +207,33 @@ class Wallet:
|
||||||
for_change = True
|
for_change = True
|
||||||
else:
|
else:
|
||||||
raise BaseException("unknown address")
|
raise BaseException("unknown address")
|
||||||
try:
|
|
||||||
seed = self.pw_decode( self.seed, password)
|
seed = self.pw_decode( self.seed, password)
|
||||||
except:
|
|
||||||
raise BaseException("Invalid password")
|
|
||||||
if not seed: return None
|
if not seed: return None
|
||||||
secexp = self.stretch_key(seed)
|
secexp = self.stretch_key(seed)
|
||||||
secexp = ( secexp + self.get_sequence(n,for_change) ) % order
|
secexp = ( secexp + self.get_sequence(n,for_change) ) % order
|
||||||
|
compressed = False
|
||||||
|
pkey = EC_KEY(secexp)
|
||||||
|
|
||||||
pk = number_to_string(secexp,order)
|
public_key = GetPubKey(pkey, compressed)
|
||||||
return pk
|
addr = public_key_to_bc_address(public_key)
|
||||||
|
if addr != address:
|
||||||
|
print_error('Invalid password with correct decoding')
|
||||||
|
raise BaseException('Invalid password')
|
||||||
|
|
||||||
|
return secexp, compressed
|
||||||
|
|
||||||
def msg_magic(self, message):
|
def msg_magic(self, message):
|
||||||
return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
|
return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
|
||||||
|
|
||||||
def sign_message(self, address, message, password):
|
def sign_message(self, address, message, password):
|
||||||
private_key = ecdsa.SigningKey.from_string( self.get_private_key(address, password), curve = SECP256k1 )
|
secexp, compressed = self.get_private_key(address, password)
|
||||||
|
private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
||||||
public_key = private_key.get_verifying_key()
|
public_key = private_key.get_verifying_key()
|
||||||
signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string )
|
signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string )
|
||||||
assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
|
assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
sig = base64.b64encode( chr(27+i) + signature )
|
sig = base64.b64encode( chr(27 + i + (4 if compressed else 0)) + signature )
|
||||||
try:
|
try:
|
||||||
self.verify_message( address, sig, message)
|
self.verify_message( address, sig, message)
|
||||||
return sig
|
return sig
|
||||||
|
@ -485,15 +504,16 @@ class Wallet:
|
||||||
h = self.history.get(address,[])
|
h = self.history.get(address,[])
|
||||||
if h == ['*']: return 0,0
|
if h == ['*']: return 0,0
|
||||||
c = u = 0
|
c = u = 0
|
||||||
received_coins = []
|
received_coins = [] # list of coins received at address
|
||||||
|
|
||||||
for tx_hash, tx_height in h:
|
for tx_hash, tx_height in h:
|
||||||
d = self.transactions.get(tx_hash)
|
d = self.transactions.get(tx_hash)
|
||||||
if not d: continue
|
if not d: continue
|
||||||
for item in d.get('outputs'):
|
for item in d.get('outputs'):
|
||||||
addr = item.get('address')
|
addr = item.get('address')
|
||||||
key = tx_hash + ':%d'%item['index']
|
if addr == address:
|
||||||
received_coins.append(key)
|
key = tx_hash + ':%d'%item['index']
|
||||||
|
received_coins.append(key)
|
||||||
|
|
||||||
for tx_hash, tx_height in h:
|
for tx_hash, tx_height in h:
|
||||||
d = self.transactions.get(tx_hash)
|
d = self.transactions.get(tx_hash)
|
||||||
|
@ -597,9 +617,13 @@ class Wallet:
|
||||||
s_inputs = []
|
s_inputs = []
|
||||||
for i in range(len(inputs)):
|
for i in range(len(inputs)):
|
||||||
addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
|
addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
|
||||||
private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
|
secexp, compressed = self.get_private_key(addr, password)
|
||||||
|
private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
||||||
public_key = private_key.get_verifying_key()
|
public_key = private_key.get_verifying_key()
|
||||||
pubkey = public_key.to_string()
|
|
||||||
|
pkey = EC_KEY(secexp)
|
||||||
|
pubkey = GetPubKey(pkey, compressed)
|
||||||
|
|
||||||
tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
|
tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
|
||||||
sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
|
sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
|
||||||
assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
|
assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
|
||||||
|
@ -616,16 +640,28 @@ class Wallet:
|
||||||
def pw_decode(self, s, password):
|
def pw_decode(self, s, password):
|
||||||
if password is not None:
|
if password is not None:
|
||||||
secret = Hash(password)
|
secret = Hash(password)
|
||||||
d = DecodeAES(secret, s)
|
try:
|
||||||
if s == self.seed:
|
d = DecodeAES(secret, s)
|
||||||
try:
|
except:
|
||||||
d.decode('hex')
|
raise BaseException('Invalid password')
|
||||||
except:
|
|
||||||
raise ValueError("Invalid password")
|
|
||||||
return d
|
return d
|
||||||
else:
|
else:
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
def decode_seed(self, password):
|
||||||
|
seed = self.pw_decode(self.seed, password)
|
||||||
|
|
||||||
|
# check decoded seed with master public key
|
||||||
|
curve = SECP256k1
|
||||||
|
secexp = self.stretch_key(seed)
|
||||||
|
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
||||||
|
master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
|
||||||
|
if master_public_key != self.master_public_key:
|
||||||
|
print_error('invalid password (mpk)')
|
||||||
|
raise BaseException('Invalid password')
|
||||||
|
|
||||||
|
return seed
|
||||||
|
|
||||||
|
|
||||||
def get_history(self, address):
|
def get_history(self, address):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
@ -699,7 +735,7 @@ class Wallet:
|
||||||
balance = c + u - balance
|
balance = c + u - balance
|
||||||
for tx in history:
|
for tx in history:
|
||||||
tx_hash = tx['tx_hash']
|
tx_hash = tx['tx_hash']
|
||||||
conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else None
|
conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
|
||||||
is_mine, value, fee = self.get_tx_value(tx_hash)
|
is_mine, value, fee = self.get_tx_value(tx_hash)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
balance += value
|
balance += value
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Electrum - lightweight Bitcoin client
|
||||||
|
# Copyright (C) 2011 thomasv@gitorious
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from wallet import Wallet
|
||||||
|
#import bitkeylib.bitkey_pb2 as proto
|
||||||
|
|
||||||
|
from version import ELECTRUM_VERSION
|
||||||
|
SEED_VERSION = 4 # Version of bitkey algorithm
|
||||||
|
|
||||||
|
class WalletBitkey(Wallet):
|
||||||
|
pass
|
|
@ -0,0 +1,11 @@
|
||||||
|
class WalletFactory(object):
|
||||||
|
def __new__(cls, config):
|
||||||
|
if config.get('bitkey', False):
|
||||||
|
# if user requested support for Bitkey device,
|
||||||
|
# import Bitkey driver
|
||||||
|
from wallet_bitkey import WalletBitkey
|
||||||
|
return WalletBitkey(config)
|
||||||
|
|
||||||
|
# Load standard wallet
|
||||||
|
from wallet import Wallet
|
||||||
|
return Wallet(config)
|
|
@ -60,7 +60,7 @@ if sys.platform == 'darwin':
|
||||||
qt_menu_location = "/opt/local/lib/Resources/qt_menu.nib"
|
qt_menu_location = "/opt/local/lib/Resources/qt_menu.nib"
|
||||||
else:
|
else:
|
||||||
# No dice? Then let's try the brew version
|
# No dice? Then let's try the brew version
|
||||||
qt_menu_location = os.popen("mdfind -name qt_menu.nib | grep Cellar | head").read()
|
qt_menu_location = os.popen("find /usr/local/Cellar -name qt_menu.nib | head").read()
|
||||||
qt_menu_location = re.sub('\n','', qt_menu_location)
|
qt_menu_location = re.sub('\n','', qt_menu_location)
|
||||||
|
|
||||||
if(len(qt_menu_location) == 0):
|
if(len(qt_menu_location) == 0):
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -30,6 +30,10 @@ data_files += [
|
||||||
"data/cleanlook/name.cfg",
|
"data/cleanlook/name.cfg",
|
||||||
"data/cleanlook/style.css"
|
"data/cleanlook/style.css"
|
||||||
]),
|
]),
|
||||||
|
(os.path.join(util.appdata_dir(), "sahara"), [
|
||||||
|
"data/sahara/name.cfg",
|
||||||
|
"data/sahara/style.css"
|
||||||
|
]),
|
||||||
(os.path.join(util.appdata_dir(), "dark"), [
|
(os.path.join(util.appdata_dir(), "dark"), [
|
||||||
"data/dark/background.png",
|
"data/dark/background.png",
|
||||||
"data/dark/name.cfg",
|
"data/dark/name.cfg",
|
||||||
|
@ -46,6 +50,8 @@ setup(name = "Electrum",
|
||||||
data_files = data_files,
|
data_files = data_files,
|
||||||
py_modules = ['electrum.version',
|
py_modules = ['electrum.version',
|
||||||
'electrum.wallet',
|
'electrum.wallet',
|
||||||
|
'electrum.wallet_bitkey',
|
||||||
|
'electrum.wallet_factory',
|
||||||
'electrum.interface',
|
'electrum.interface',
|
||||||
'electrum.gui',
|
'electrum.gui',
|
||||||
'electrum.gui_qt',
|
'electrum.gui_qt',
|
||||||
|
|
Loading…
Reference in New Issue