Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
c41fe53fd2
|
@ -14,3 +14,4 @@ locale/
|
||||||
.devlocaltmp/
|
.devlocaltmp/
|
||||||
*_trial_temp
|
*_trial_temp
|
||||||
packages
|
packages
|
||||||
|
env/
|
||||||
|
|
2
electrum
2
electrum
|
@ -120,7 +120,7 @@ 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("-L", "--lang", dest="language", default=None, help="default language used in GUI")
|
||||||
parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
|
parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
|
||||||
parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
|
parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
|
||||||
parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
|
parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
|
||||||
|
|
|
@ -17,8 +17,8 @@ else
|
||||||
python setup.py install
|
python setup.py install
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH
|
export PYTHONPATH="/usr/local/lib/python2.7/site-packages:$PYTHONPATH"
|
||||||
|
|
||||||
./electrum
|
./electrum "$@"
|
||||||
|
|
||||||
deactivate
|
deactivate
|
||||||
|
|
|
@ -13,5 +13,5 @@ Categories=Network;
|
||||||
StartupNotify=false
|
StartupNotify=false
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
MimeType=x-scheme-handler/bitcoin
|
MimeType=x-scheme-handler/bitcoin;
|
||||||
|
|
||||||
|
|
|
@ -296,6 +296,8 @@ class ElectrumWindow(QMainWindow):
|
||||||
wallet.start_threads(self.network)
|
wallet.start_threads(self.network)
|
||||||
# load new wallet in gui
|
# load new wallet in gui
|
||||||
self.load_wallet(wallet)
|
self.load_wallet(wallet)
|
||||||
|
# save path
|
||||||
|
self.config.set_key('default_wallet_path', filename)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -577,7 +579,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
elif be == 'Insight.is':
|
elif be == 'Insight.is':
|
||||||
block_explorer = 'http://live.insight.is/tx/'
|
block_explorer = 'http://live.insight.is/tx/'
|
||||||
elif be == "Blocktrail.com":
|
elif be == "Blocktrail.com":
|
||||||
block_explorer = 'https://www.blocktrail.com/tx/'
|
block_explorer = 'https://www.blocktrail.com/BTC/tx/'
|
||||||
|
|
||||||
if not item: return
|
if not item: return
|
||||||
tx_hash = str(item.data(0, Qt.UserRole).toString())
|
tx_hash = str(item.data(0, Qt.UserRole).toString())
|
||||||
|
@ -1907,39 +1909,44 @@ class ElectrumWindow(QMainWindow):
|
||||||
dialog.setModal(1)
|
dialog.setModal(1)
|
||||||
dialog.setWindowTitle(_("Master Public Keys"))
|
dialog.setWindowTitle(_("Master Public Keys"))
|
||||||
|
|
||||||
main_layout = QGridLayout()
|
|
||||||
mpk_dict = self.wallet.get_master_public_keys()
|
mpk_dict = self.wallet.get_master_public_keys()
|
||||||
# filter out the empty keys (PendingAccount)
|
vbox = QVBoxLayout()
|
||||||
mpk_dict = {acc:mpk for acc,mpk in mpk_dict.items() if mpk}
|
|
||||||
|
|
||||||
# only show the combobox in case multiple accounts are available
|
# only show the combobox in case multiple accounts are available
|
||||||
if len(mpk_dict) > 1:
|
if len(mpk_dict) > 1:
|
||||||
combobox = QComboBox()
|
gb = QGroupBox(_("Master Public Keys"))
|
||||||
for name in mpk_dict:
|
vbox.addWidget(gb)
|
||||||
combobox.addItem(name)
|
group = QButtonGroup()
|
||||||
combobox.setCurrentIndex(0)
|
first_button = None
|
||||||
main_layout.addWidget(combobox, 1, 0)
|
for name in sorted(mpk_dict.keys()):
|
||||||
|
b = QRadioButton(gb)
|
||||||
|
b.setText(name)
|
||||||
|
group.addButton(b)
|
||||||
|
vbox.addWidget(b)
|
||||||
|
if not first_button:
|
||||||
|
first_button = b
|
||||||
|
|
||||||
account = unicode(combobox.currentText())
|
mpk_text = ShowQRTextEdit()
|
||||||
mpk_text = ShowQRTextEdit(text=mpk_dict[account])
|
|
||||||
mpk_text.setMaximumHeight(170)
|
mpk_text.setMaximumHeight(170)
|
||||||
mpk_text.selectAll() # for easy copying
|
vbox.addWidget(mpk_text)
|
||||||
main_layout.addWidget(mpk_text, 2, 0)
|
|
||||||
|
|
||||||
def show_mpk(account):
|
def show_mpk(b):
|
||||||
mpk = mpk_dict.get(unicode(account), "")
|
name = str(b.text())
|
||||||
|
mpk = mpk_dict.get(name, "")
|
||||||
mpk_text.setText(mpk)
|
mpk_text.setText(mpk)
|
||||||
|
mpk_text.selectAll() # for easy copying
|
||||||
|
|
||||||
combobox.currentIndexChanged[str].connect(lambda acc: show_mpk(acc))
|
group.buttonReleased.connect(show_mpk)
|
||||||
|
first_button.setChecked(True)
|
||||||
|
show_mpk(first_button)
|
||||||
|
|
||||||
|
#combobox.currentIndexChanged[str].connect(lambda acc: show_mpk(acc))
|
||||||
elif len(mpk_dict) == 1:
|
elif len(mpk_dict) == 1:
|
||||||
mpk = mpk_dict.values()[0]
|
mpk = mpk_dict.values()[0]
|
||||||
mpk_text = ShowQRTextEdit(text=mpk)
|
mpk_text = ShowQRTextEdit(text=mpk)
|
||||||
mpk_text.setMaximumHeight(170)
|
mpk_text.setMaximumHeight(170)
|
||||||
mpk_text.selectAll() # for easy copying
|
mpk_text.selectAll() # for easy copying
|
||||||
main_layout.addWidget(mpk_text, 2, 0)
|
vbox.addWidget(mpk_text)
|
||||||
|
|
||||||
vbox = QVBoxLayout()
|
|
||||||
vbox.addLayout(main_layout)
|
|
||||||
vbox.addLayout(close_button(dialog))
|
vbox.addLayout(close_button(dialog))
|
||||||
|
|
||||||
dialog.setLayout(vbox)
|
dialog.setLayout(vbox)
|
||||||
|
|
|
@ -201,9 +201,12 @@ class NetworkDialog(QDialog):
|
||||||
def change_server(self, host, protocol):
|
def change_server(self, host, protocol):
|
||||||
|
|
||||||
pp = self.servers.get(host, DEFAULT_PORTS)
|
pp = self.servers.get(host, DEFAULT_PORTS)
|
||||||
|
if protocol and protocol not in protocol_letters:
|
||||||
|
protocol = None
|
||||||
if protocol:
|
if protocol:
|
||||||
port = pp.get(protocol)
|
port = pp.get(protocol)
|
||||||
if not port: protocol = None
|
if port is None:
|
||||||
|
protocol = None
|
||||||
|
|
||||||
if not protocol:
|
if not protocol:
|
||||||
if 's' in pp.keys():
|
if 's' in pp.keys():
|
||||||
|
@ -217,15 +220,6 @@ class NetworkDialog(QDialog):
|
||||||
self.server_port.setText( port )
|
self.server_port.setText( port )
|
||||||
self.server_protocol.setCurrentIndex(protocol_letters.index(protocol))
|
self.server_protocol.setCurrentIndex(protocol_letters.index(protocol))
|
||||||
|
|
||||||
if not self.servers: return
|
|
||||||
for p in protocol_letters:
|
|
||||||
i = protocol_letters.index(p)
|
|
||||||
j = self.server_protocol.model().index(i,0)
|
|
||||||
#if p not in pp.keys(): # and self.interface.is_connected:
|
|
||||||
# self.server_protocol.model().setData(j, QVariant(0), Qt.UserRole-1)
|
|
||||||
#else:
|
|
||||||
# self.server_protocol.model().setData(j, QVariant(33), Qt.UserRole-1)
|
|
||||||
|
|
||||||
|
|
||||||
def do_exec(self):
|
def do_exec(self):
|
||||||
|
|
||||||
|
|
|
@ -368,7 +368,7 @@ class Network(threading.Thread):
|
||||||
out['result'] = f(*params)
|
out['result'] = f(*params)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
out['error'] = str(e)
|
out['error'] = str(e)
|
||||||
traceback.print_exc(file=sys.stout)
|
traceback.print_exc(file=sys.stdout)
|
||||||
print_error("network error", str(e))
|
print_error("network error", str(e))
|
||||||
|
|
||||||
self.response_queue.put(out)
|
self.response_queue.put(out)
|
||||||
|
|
|
@ -43,42 +43,8 @@ import x509
|
||||||
REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
|
REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
|
||||||
ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
|
ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
|
||||||
|
|
||||||
|
|
||||||
ca_list = {}
|
|
||||||
ca_path = requests.certs.where()
|
ca_path = requests.certs.where()
|
||||||
|
ca_list = x509.load_certificates(ca_path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def load_certificates():
|
|
||||||
try:
|
|
||||||
ca_f = open(ca_path, 'r')
|
|
||||||
except Exception:
|
|
||||||
print "ERROR: Could not open %s"%ca_path
|
|
||||||
print "ca-bundle.crt file should be placed in ~/.electrum/ca/ca-bundle.crt"
|
|
||||||
print "Documentation on how to download or create the file here: http://curl.haxx.se/docs/caextract.html"
|
|
||||||
print "Payment will continue with manual verification."
|
|
||||||
return False
|
|
||||||
c = ""
|
|
||||||
for line in ca_f:
|
|
||||||
if line == "-----BEGIN CERTIFICATE-----\n":
|
|
||||||
c = line
|
|
||||||
else:
|
|
||||||
c += line
|
|
||||||
if line == "-----END CERTIFICATE-----\n":
|
|
||||||
x = x509.X509()
|
|
||||||
try:
|
|
||||||
x.parse(c)
|
|
||||||
except Exception as e:
|
|
||||||
util.print_error("cannot parse cert:", e)
|
|
||||||
continue
|
|
||||||
ca_list[x.getFingerprint()] = x
|
|
||||||
ca_f.close()
|
|
||||||
util.print_error("%d certificates"%len(ca_list))
|
|
||||||
return True
|
|
||||||
|
|
||||||
load_certificates()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentRequest:
|
class PaymentRequest:
|
||||||
|
@ -190,8 +156,13 @@ class PaymentRequest:
|
||||||
verify = pubkey.hashAndVerify(sig, data)
|
verify = pubkey.hashAndVerify(sig, data)
|
||||||
elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA256:
|
elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA256:
|
||||||
hashBytes = bytearray(hashlib.sha256(data).digest())
|
hashBytes = bytearray(hashlib.sha256(data).digest())
|
||||||
prefixBytes = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20])
|
verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA256 + hashBytes)
|
||||||
verify = pubkey.verify(sig, prefixBytes + hashBytes)
|
elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA384:
|
||||||
|
hashBytes = bytearray(hashlib.sha384(data).digest())
|
||||||
|
verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA384 + hashBytes)
|
||||||
|
elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA512:
|
||||||
|
hashBytes = bytearray(hashlib.sha512(data).digest())
|
||||||
|
verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA512 + hashBytes)
|
||||||
else:
|
else:
|
||||||
self.error = "Algorithm not supported"
|
self.error = "Algorithm not supported"
|
||||||
util.print_error(self.error, algo.getComponentByName('algorithm'))
|
util.print_error(self.error, algo.getComponentByName('algorithm'))
|
||||||
|
@ -226,8 +197,7 @@ class PaymentRequest:
|
||||||
|
|
||||||
if paymntreq.pki_type == "x509+sha256":
|
if paymntreq.pki_type == "x509+sha256":
|
||||||
hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
|
hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
|
||||||
prefixBytes = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20])
|
verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes)
|
||||||
verify = pubkey0.verify(sigBytes, prefixBytes + hashBytes)
|
|
||||||
elif paymntreq.pki_type == "x509+sha1":
|
elif paymntreq.pki_type == "x509+sha1":
|
||||||
verify = pubkey0.hashAndVerify(sigBytes, msgBytes)
|
verify = pubkey0.hashAndVerify(sigBytes, msgBytes)
|
||||||
else:
|
else:
|
||||||
|
@ -321,7 +291,6 @@ class PaymentRequest:
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
util.set_verbosity(True)
|
util.set_verbosity(True)
|
||||||
load_certificates()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
uri = sys.argv[1]
|
uri = sys.argv[1]
|
||||||
|
|
|
@ -40,7 +40,7 @@ def hook(func):
|
||||||
|
|
||||||
|
|
||||||
def run_hook(name, *args):
|
def run_hook(name, *args):
|
||||||
SPECIAL_HOOKS = ['get_wizard_action']
|
SPECIAL_HOOKS = ['get_wizard_action','installwizard_restore']
|
||||||
results = []
|
results = []
|
||||||
f_list = hooks.get(name,[])
|
f_list = hooks.get(name,[])
|
||||||
for p, f in f_list:
|
for p, f in f_list:
|
||||||
|
@ -71,6 +71,7 @@ class BasePlugin:
|
||||||
def __init__(self, config, name):
|
def __init__(self, config, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.wallet = None
|
||||||
# add self to hooks
|
# add self to hooks
|
||||||
for k in dir(self):
|
for k in dir(self):
|
||||||
if k in hook_names:
|
if k in hook_names:
|
||||||
|
|
|
@ -67,7 +67,7 @@ class WalletStorage(object):
|
||||||
|
|
||||||
# path in config file
|
# path in config file
|
||||||
path = config.get('default_wallet_path')
|
path = config.get('default_wallet_path')
|
||||||
if path:
|
if path and os.path.exists(path):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
# default path
|
# default path
|
||||||
|
@ -1494,7 +1494,7 @@ class Wallet_2of2(BIP32_Wallet, Mnemonic):
|
||||||
def get_master_public_keys(self):
|
def get_master_public_keys(self):
|
||||||
xpub1 = self.master_public_keys.get("x1/")
|
xpub1 = self.master_public_keys.get("x1/")
|
||||||
xpub2 = self.master_public_keys.get("x2/")
|
xpub2 = self.master_public_keys.get("x2/")
|
||||||
return {'x1':xpub1, 'x2':xpub2}
|
return { 'Self':xpub1, 'Cosigner':xpub2 }
|
||||||
|
|
||||||
def get_action(self):
|
def get_action(self):
|
||||||
xpub1 = self.master_public_keys.get("x1/")
|
xpub1 = self.master_public_keys.get("x1/")
|
||||||
|
|
29
lib/x509.py
29
lib/x509.py
|
@ -23,6 +23,7 @@ import sys
|
||||||
import pyasn1
|
import pyasn1
|
||||||
import pyasn1_modules
|
import pyasn1_modules
|
||||||
import tlslite
|
import tlslite
|
||||||
|
import util
|
||||||
|
|
||||||
# workaround https://github.com/trevp/tlslite/issues/15
|
# workaround https://github.com/trevp/tlslite/issues/15
|
||||||
tlslite.utils.cryptomath.pycryptoLoaded = False
|
tlslite.utils.cryptomath.pycryptoLoaded = False
|
||||||
|
@ -41,8 +42,18 @@ from pyasn1_modules.rfc2459 import id_at_organizationalUnitName as OU_NAME
|
||||||
from pyasn1_modules.rfc2459 import id_ce_basicConstraints, BasicConstraints
|
from pyasn1_modules.rfc2459 import id_ce_basicConstraints, BasicConstraints
|
||||||
XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
|
XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
|
||||||
SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')
|
SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')
|
||||||
|
|
||||||
|
# algo OIDs
|
||||||
ALGO_RSA_SHA1 = ObjectIdentifier('1.2.840.113549.1.1.5')
|
ALGO_RSA_SHA1 = ObjectIdentifier('1.2.840.113549.1.1.5')
|
||||||
ALGO_RSA_SHA256 = ObjectIdentifier('1.2.840.113549.1.1.11')
|
ALGO_RSA_SHA256 = ObjectIdentifier('1.2.840.113549.1.1.11')
|
||||||
|
ALGO_RSA_SHA384 = ObjectIdentifier('1.2.840.113549.1.1.12')
|
||||||
|
ALGO_RSA_SHA512 = ObjectIdentifier('1.2.840.113549.1.1.13')
|
||||||
|
|
||||||
|
# prefixes, see http://stackoverflow.com/questions/3713774/c-sharp-how-to-calculate-asn-1-der-encoding-of-a-particular-hash-algorithm
|
||||||
|
PREFIX_RSA_SHA256 = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20])
|
||||||
|
PREFIX_RSA_SHA384 = bytearray([0x30,0x41,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x02,0x05,0x00,0x04,0x30])
|
||||||
|
PREFIX_RSA_SHA512 = bytearray([0x30,0x51,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x03,0x05,0x00,0x04,0x40])
|
||||||
|
|
||||||
|
|
||||||
class CertificateError(Exception):
|
class CertificateError(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -214,3 +225,21 @@ class X509(tlslite.X509):
|
||||||
|
|
||||||
class X509CertChain(tlslite.X509CertChain):
|
class X509CertChain(tlslite.X509CertChain):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def load_certificates(ca_path):
|
||||||
|
ca_list = {}
|
||||||
|
with open(ca_path, 'r') as f:
|
||||||
|
s = f.read()
|
||||||
|
bList = tlslite.utils.pem.dePemList(s, "CERTIFICATE")
|
||||||
|
for b in bList:
|
||||||
|
x = X509()
|
||||||
|
try:
|
||||||
|
x.parseBinary(b)
|
||||||
|
except Exception as e:
|
||||||
|
util.print_error("cannot parse cert:", e)
|
||||||
|
continue
|
||||||
|
ca_list[x.getFingerprint()] = x
|
||||||
|
return ca_list
|
||||||
|
|
|
@ -15,8 +15,7 @@ import platform
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import amodem.audio
|
import amodem.audio
|
||||||
import amodem.recv
|
import amodem.main
|
||||||
import amodem.send
|
|
||||||
import amodem.config
|
import amodem.config
|
||||||
print_error('Audio MODEM is available.')
|
print_error('Audio MODEM is available.')
|
||||||
amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr))
|
amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr))
|
||||||
|
@ -115,7 +114,7 @@ class Plugin(BasePlugin):
|
||||||
with self._audio_interface() as interface:
|
with self._audio_interface() as interface:
|
||||||
src = BytesIO(blob)
|
src = BytesIO(blob)
|
||||||
dst = interface.player()
|
dst = interface.player()
|
||||||
amodem.send.main(config=self.modem_config, src=src, dst=dst)
|
amodem.main.send(config=self.modem_config, src=src, dst=dst)
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
@ -132,7 +131,7 @@ class Plugin(BasePlugin):
|
||||||
with self._audio_interface() as interface:
|
with self._audio_interface() as interface:
|
||||||
src = interface.recorder()
|
src = interface.recorder()
|
||||||
dst = BytesIO()
|
dst = BytesIO()
|
||||||
amodem.recv.main(config=self.modem_config, src=src, dst=dst)
|
amodem.main.recv(config=self.modem_config, src=src, dst=dst)
|
||||||
return dst.getvalue()
|
return dst.getvalue()
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
|
@ -17,6 +17,7 @@ from electrum_gui.qt.amountedit import AmountEdit
|
||||||
|
|
||||||
EXCHANGES = ["BitcoinAverage",
|
EXCHANGES = ["BitcoinAverage",
|
||||||
"BitcoinVenezuela",
|
"BitcoinVenezuela",
|
||||||
|
"BTCParalelo",
|
||||||
"Bitcurex",
|
"Bitcurex",
|
||||||
"Bitmarket",
|
"Bitmarket",
|
||||||
"BitPay",
|
"BitPay",
|
||||||
|
@ -64,6 +65,26 @@ class Exchanger(threading.Thread):
|
||||||
raise
|
raise
|
||||||
return json_resp
|
return json_resp
|
||||||
|
|
||||||
|
def get_json_insecure(self, site, get_string):
|
||||||
|
""" get_json_insecure shouldn't be used in production releases
|
||||||
|
It doesn't use SSL, and so prices could be manipulated by a middle man
|
||||||
|
This should be used ONLY when developing plugins when you don't have a
|
||||||
|
SSL certificate that validates against HTTPSConnection
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
connection = httplib.HTTPConnection(site)
|
||||||
|
connection.request("GET", get_string, headers={"User-Agent":"Electrum"})
|
||||||
|
except Exception:
|
||||||
|
raise
|
||||||
|
resp = connection.getresponse()
|
||||||
|
if resp.reason == httplib.responses[httplib.NOT_FOUND]:
|
||||||
|
raise
|
||||||
|
try:
|
||||||
|
json_resp = json.loads(resp.read())
|
||||||
|
except Exception:
|
||||||
|
raise
|
||||||
|
return json_resp
|
||||||
|
|
||||||
|
|
||||||
def exchange(self, btc_amount, quote_currency):
|
def exchange(self, btc_amount, quote_currency):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
@ -82,6 +103,7 @@ class Exchanger(threading.Thread):
|
||||||
update_rates = {
|
update_rates = {
|
||||||
"BitcoinAverage": self.update_ba,
|
"BitcoinAverage": self.update_ba,
|
||||||
"BitcoinVenezuela": self.update_bv,
|
"BitcoinVenezuela": self.update_bv,
|
||||||
|
"BTCParalelo": self.update_bpl,
|
||||||
"Bitcurex": self.update_bx,
|
"Bitcurex": self.update_bx,
|
||||||
"Bitmarket": self.update_bm,
|
"Bitmarket": self.update_bm,
|
||||||
"BitPay": self.update_bp,
|
"BitPay": self.update_bp,
|
||||||
|
@ -110,6 +132,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_cd(self):
|
def update_cd(self):
|
||||||
try:
|
try:
|
||||||
resp_currencies = self.get_json('api.coindesk.com', "/v1/bpi/supported-currencies.json")
|
resp_currencies = self.get_json('api.coindesk.com', "/v1/bpi/supported-currencies.json")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing coindesk")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -138,6 +163,9 @@ class Exchanger(threading.Thread):
|
||||||
try:
|
try:
|
||||||
resp_rate = self.get_json('api.itbit.com', "/v1/markets/XBT" + str(current_cur) + "/ticker")
|
resp_rate = self.get_json('api.itbit.com', "/v1/markets/XBT" + str(current_cur) + "/ticker")
|
||||||
quote_currencies[str(current_cur)] = decimal.Decimal(str(resp_rate["lastPrice"]))
|
quote_currencies[str(current_cur)] = decimal.Decimal(str(resp_rate["lastPrice"]))
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing itbit")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
@ -147,6 +175,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_wd(self):
|
def update_wd(self):
|
||||||
try:
|
try:
|
||||||
winkresp = self.get_json('winkdex.com', "/api/v0/price")
|
winkresp = self.get_json('winkdex.com', "/api/v0/price")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing winkdex")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {"USD": 0.0}
|
quote_currencies = {"USD": 0.0}
|
||||||
|
@ -162,6 +193,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_cv(self):
|
def update_cv(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('www.cavirtex.com', "/api/CAD/ticker.json")
|
jsonresp = self.get_json('www.cavirtex.com', "/api/CAD/ticker.json")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing cavirtex")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {"CAD": 0.0}
|
quote_currencies = {"CAD": 0.0}
|
||||||
|
@ -177,6 +211,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_bm(self):
|
def update_bm(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('www.bitmarket.pl', "/json/BTCPLN/ticker.json")
|
jsonresp = self.get_json('www.bitmarket.pl', "/json/BTCPLN/ticker.json")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing bitmarket")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {"PLN": 0.0}
|
quote_currencies = {"PLN": 0.0}
|
||||||
|
@ -192,6 +229,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_bx(self):
|
def update_bx(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('pln.bitcurex.com', "/data/ticker.json")
|
jsonresp = self.get_json('pln.bitcurex.com', "/data/ticker.json")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing bitcurex")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {"PLN": 0.0}
|
quote_currencies = {"PLN": 0.0}
|
||||||
|
@ -207,6 +247,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_CNY(self):
|
def update_CNY(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('data.btcchina.com', "/data/ticker")
|
jsonresp = self.get_json('data.btcchina.com', "/data/ticker")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing btcchina")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {"CNY": 0.0}
|
quote_currencies = {"CNY": 0.0}
|
||||||
|
@ -222,6 +265,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_bp(self):
|
def update_bp(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('bitpay.com', "/api/rates")
|
jsonresp = self.get_json('bitpay.com', "/api/rates")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing bitpay")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {}
|
quote_currencies = {}
|
||||||
|
@ -237,6 +283,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_cb(self):
|
def update_cb(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('coinbase.com', "/api/v1/currencies/exchange_rates")
|
jsonresp = self.get_json('coinbase.com', "/api/v1/currencies/exchange_rates")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing coinbase")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -255,6 +304,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_bc(self):
|
def update_bc(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('blockchain.info', "/ticker")
|
jsonresp = self.get_json('blockchain.info', "/ticker")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing blockchain")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {}
|
quote_currencies = {}
|
||||||
|
@ -270,6 +322,9 @@ class Exchanger(threading.Thread):
|
||||||
def update_lb(self):
|
def update_lb(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('localbitcoins.com', "/bitcoinaverage/ticker-all-currencies/")
|
jsonresp = self.get_json('localbitcoins.com', "/bitcoinaverage/ticker-all-currencies/")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing localbitcoins")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {}
|
quote_currencies = {}
|
||||||
|
@ -285,23 +340,52 @@ class Exchanger(threading.Thread):
|
||||||
|
|
||||||
def update_bv(self):
|
def update_bv(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('api.bitcoinvenezuela.com', "/")
|
jsonresp = self.get_json_insecure('api.bitcoinvenezuela.com', "/")
|
||||||
|
print("**WARNING**: update_bv is using an insecure connection, shouldn't be used on production")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing bitcoinvenezuela")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
quote_currencies = {}
|
quote_currencies = {}
|
||||||
try:
|
try:
|
||||||
for r in jsonresp["BTC"]:
|
for r in jsonresp["BTC"]:
|
||||||
quote_currencies[r] = Decimal(jsonresp["BTC"][r])
|
quote_currencies[r] = Decimal(jsonresp["BTC"][r])
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.quote_currencies = quote_currencies
|
self.quote_currencies = quote_currencies
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
print ("KeyError")
|
||||||
self.parent.set_currencies(quote_currencies)
|
self.parent.set_currencies(quote_currencies)
|
||||||
|
|
||||||
|
|
||||||
|
def update_bpl(self):
|
||||||
|
try:
|
||||||
|
jsonresp = self.get_json_insecure('btcparalelo.com', "/api/price")
|
||||||
|
print("**WARNING**: update_bpl is using an insecure connection, shouldn't be used on production")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing btcparalelo")
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
quote_currencies = {}
|
||||||
|
try:
|
||||||
|
quote_currencies = {"VEF": Decimal(jsonresp["price"])}
|
||||||
|
with self.lock:
|
||||||
|
self.quote_currencies = quote_currencies
|
||||||
|
except KeyError:
|
||||||
|
print ("KeyError")
|
||||||
|
self.parent.set_currencies(quote_currencies)
|
||||||
|
|
||||||
def update_ba(self):
|
def update_ba(self):
|
||||||
try:
|
try:
|
||||||
jsonresp = self.get_json('api.bitcoinaverage.com', "/ticker/global/all")
|
jsonresp = self.get_json('api.bitcoinaverage.com', "/ticker/global/all")
|
||||||
|
except SSLError:
|
||||||
|
print("SSL Error when accesing bitcoinaverage")
|
||||||
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
quote_currencies = {}
|
quote_currencies = {}
|
||||||
|
@ -411,7 +495,6 @@ class Plugin(BasePlugin):
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def load_wallet(self, wallet):
|
def load_wallet(self, wallet):
|
||||||
self.wallet = wallet
|
|
||||||
tx_list = {}
|
tx_list = {}
|
||||||
for item in self.wallet.get_tx_history(self.wallet.storage.get("current_account", None)):
|
for item in self.wallet.get_tx_history(self.wallet.storage.get("current_account", None)):
|
||||||
tx_hash, conf, is_mine, value, fee, balance, timestamp = item
|
tx_hash, conf, is_mine, value, fee, balance, timestamp = item
|
||||||
|
@ -471,6 +554,8 @@ class Plugin(BasePlugin):
|
||||||
return
|
return
|
||||||
if not self.resp_hist:
|
if not self.resp_hist:
|
||||||
return
|
return
|
||||||
|
if not self.wallet:
|
||||||
|
return
|
||||||
|
|
||||||
self.win.is_edit = True
|
self.win.is_edit = True
|
||||||
self.win.history_list.setColumnCount(6)
|
self.win.history_list.setColumnCount(6)
|
||||||
|
|
|
@ -96,6 +96,7 @@ class Plugin(BasePlugin):
|
||||||
print_error("trezor: clear session")
|
print_error("trezor: clear session")
|
||||||
if self.wallet and self.wallet.client:
|
if self.wallet and self.wallet.client:
|
||||||
self.wallet.client.clear_session()
|
self.wallet.client.clear_session()
|
||||||
|
self.wallet.client.transport.close()
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def load_wallet(self, wallet):
|
def load_wallet(self, wallet):
|
||||||
|
@ -131,6 +132,10 @@ class Plugin(BasePlugin):
|
||||||
self.wallet.trezor_sign(tx)
|
self.wallet.trezor_sign(tx)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tx.error = str(e)
|
tx.error = str(e)
|
||||||
|
@hook
|
||||||
|
def receive_menu(self, menu, addrs):
|
||||||
|
if not self.wallet.is_watching_only() and self.wallet.atleast_version(1, 3) and len(addrs) == 1:
|
||||||
|
menu.addAction(_("Show on TREZOR"), lambda: self.wallet.show_address(addrs[0]))
|
||||||
|
|
||||||
def settings_widget(self, window):
|
def settings_widget(self, window):
|
||||||
return EnterButton(_('Settings'), self.settings_dialog)
|
return EnterButton(_('Settings'), self.settings_dialog)
|
||||||
|
@ -212,15 +217,22 @@ class TrezorWallet(BIP32_HD_Wallet):
|
||||||
except:
|
except:
|
||||||
give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.')
|
give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.')
|
||||||
self.client = QtGuiTrezorClient(self.transport)
|
self.client = QtGuiTrezorClient(self.transport)
|
||||||
if (self.client.features.major_version == 1 and self.client.features.minor_version < 2) or (self.client.features.major_version == 1 and self.client.features.minor_version == 2 and self.client.features.patch_version < 1):
|
|
||||||
give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com')
|
|
||||||
self.client.set_tx_api(self)
|
self.client.set_tx_api(self)
|
||||||
#self.client.clear_session()# TODO Doesn't work with firmware 1.1, returns proto.Failure
|
#self.client.clear_session()# TODO Doesn't work with firmware 1.1, returns proto.Failure
|
||||||
self.client.bad = False
|
self.client.bad = False
|
||||||
self.device_checked = False
|
self.device_checked = False
|
||||||
self.proper_device = False
|
self.proper_device = False
|
||||||
|
if not self.atleast_version(1, 2, 1):
|
||||||
|
give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com')
|
||||||
return self.client
|
return self.client
|
||||||
|
|
||||||
|
def compare_version(self, major, minor=0, patch=0):
|
||||||
|
features = self.get_client().features
|
||||||
|
return cmp([features.major_version, features.minor_version, features.patch_version], [major, minor, patch])
|
||||||
|
|
||||||
|
def atleast_version(self, major, minor=0, patch=0):
|
||||||
|
return self.compare_version(major, minor, patch) >= 0
|
||||||
|
|
||||||
def address_id(self, address):
|
def address_id(self, address):
|
||||||
account_id, (change, address_index) = self.get_address_index(address)
|
account_id, (change, address_index) = self.get_address_index(address)
|
||||||
return "44'/0'/%s'/%d/%d" % (account_id, change, address_index)
|
return "44'/0'/%s'/%d/%d" % (account_id, change, address_index)
|
||||||
|
@ -277,6 +289,21 @@ class TrezorWallet(BIP32_HD_Wallet):
|
||||||
# twd.emit(SIGNAL('trezor_done'))
|
# twd.emit(SIGNAL('trezor_done'))
|
||||||
#return str(decrypted_msg)
|
#return str(decrypted_msg)
|
||||||
|
|
||||||
|
def show_address(self, address):
|
||||||
|
if not self.check_proper_device():
|
||||||
|
give_error('Wrong device or password')
|
||||||
|
try:
|
||||||
|
address_path = self.address_id(address)
|
||||||
|
address_n = self.get_client().expand_path(address_path)
|
||||||
|
except Exception, e:
|
||||||
|
give_error(e)
|
||||||
|
try:
|
||||||
|
self.get_client().get_address('Bitcoin', address_n, True)
|
||||||
|
except Exception, e:
|
||||||
|
give_error(e)
|
||||||
|
finally:
|
||||||
|
twd.emit(SIGNAL('trezor_done'))
|
||||||
|
|
||||||
def sign_message(self, address, message, password):
|
def sign_message(self, address, message, password):
|
||||||
if not self.check_proper_device():
|
if not self.check_proper_device():
|
||||||
give_error('Wrong device or password')
|
give_error('Wrong device or password')
|
||||||
|
@ -426,6 +453,8 @@ class TrezorQtGuiMixin(object):
|
||||||
message = "Confirm transaction fee on Trezor device to continue"
|
message = "Confirm transaction fee on Trezor device to continue"
|
||||||
elif msg.code == 7:
|
elif msg.code == 7:
|
||||||
message = "Confirm message to sign on Trezor device to continue"
|
message = "Confirm message to sign on Trezor device to continue"
|
||||||
|
elif msg.code == 10:
|
||||||
|
message = "Confirm address on Trezor device to continue"
|
||||||
else:
|
else:
|
||||||
message = "Check Trezor device to continue"
|
message = "Check Trezor device to continue"
|
||||||
twd.start(message)
|
twd.start(message)
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
# create a BIP70 payment request signed with a certificate
|
||||||
|
|
||||||
|
import tlslite
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from electrum import paymentrequest_pb2 as pb2
|
||||||
|
from electrum.transaction import Transaction
|
||||||
|
from electrum import bitcoin
|
||||||
|
from electrum import x509
|
||||||
|
|
||||||
|
|
||||||
|
chain_file = 'mychain.pem'
|
||||||
|
cert_file = 'mycert.pem'
|
||||||
|
amount = 1000000
|
||||||
|
address = "18U5kpCAU4s8weFF8Ps5n8HAfpdUjDVF64"
|
||||||
|
memo = "blah"
|
||||||
|
out_file = "payreq"
|
||||||
|
|
||||||
|
|
||||||
|
with open(chain_file, 'r') as f:
|
||||||
|
chain = tlslite.X509CertChain()
|
||||||
|
chain.parsePemList(f.read())
|
||||||
|
|
||||||
|
certificates = pb2.X509Certificates()
|
||||||
|
certificates.certificate.extend(map(lambda x: str(x.bytes), chain.x509List))
|
||||||
|
|
||||||
|
with open(cert_file, 'r') as f:
|
||||||
|
rsakey = tlslite.utils.python_rsakey.Python_RSAKey.parsePEM(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
def make_payment_request(amount, script, memo):
|
||||||
|
"""Generates a http PaymentRequest object"""
|
||||||
|
pd = pb2.PaymentDetails()
|
||||||
|
pd.outputs.add(amount=amount, script=script)
|
||||||
|
now = int(time.time())
|
||||||
|
pd.time = now
|
||||||
|
pd.expires = now + 15*60
|
||||||
|
pd.memo = memo
|
||||||
|
pd.payment_url = 'http://payment_ack.url'
|
||||||
|
pr = pb2.PaymentRequest()
|
||||||
|
pr.serialized_payment_details = pd.SerializeToString()
|
||||||
|
pr.pki_type = 'x509+sha256'
|
||||||
|
pr.pki_data = certificates.SerializeToString()
|
||||||
|
pr.signature = ''
|
||||||
|
msgBytes = bytearray(pr.SerializeToString())
|
||||||
|
hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
|
||||||
|
sig = rsakey.sign(x509.PREFIX_RSA_SHA256 + hashBytes)
|
||||||
|
pr.signature = bytes(sig)
|
||||||
|
return pr.SerializeToString()
|
||||||
|
|
||||||
|
|
||||||
|
script = Transaction.pay_script('address', address).decode('hex')
|
||||||
|
|
||||||
|
pr_string = make_payment_request(amount, script, memo)
|
||||||
|
with open(out_file,'wb') as f:
|
||||||
|
f.write(pr_string)
|
||||||
|
|
||||||
|
print "Payment request was written to file '%s'"%out_file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import util, json
|
||||||
|
peers = util.get_peers()
|
||||||
|
results = util.send_request(peers, {'method':'blockchain.estimatefee','params':[1]})
|
||||||
|
print json.dumps(results, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue