From f00df7023c774c9f6973e895409bb7c775aecdff Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 4 Feb 2015 18:38:38 +0200 Subject: [PATCH 01/19] audio_modem: update plugin to work with latest version. send() and recv() API are now part of amodem.main module. --- plugins/audio_modem.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/audio_modem.py b/plugins/audio_modem.py index efe35a28..a6f1f35d 100644 --- a/plugins/audio_modem.py +++ b/plugins/audio_modem.py @@ -15,8 +15,7 @@ import platform try: import amodem.audio - import amodem.recv - import amodem.send + import amodem.main import amodem.config print_error('Audio MODEM is available.') amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr)) @@ -115,7 +114,7 @@ class Plugin(BasePlugin): with self._audio_interface() as interface: src = BytesIO(blob) 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: traceback.print_exc() @@ -132,7 +131,7 @@ class Plugin(BasePlugin): with self._audio_interface() as interface: src = interface.recorder() 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() except Exception: traceback.print_exc() From 99715fe07ae12cf8c6793e2351fb86cd7ccfb8b6 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Thu, 5 Feb 2015 13:02:44 +0100 Subject: [PATCH 02/19] changed blocktrail baseurl to include network 'BTC' to avoid redirects --- gui/qt/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 2ea39a15..02aa1db2 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -577,7 +577,7 @@ class ElectrumWindow(QMainWindow): elif be == 'Insight.is': block_explorer = 'http://live.insight.is/tx/' elif be == "Blocktrail.com": - block_explorer = 'https://www.blocktrail.com/tx/' + block_explorer = 'https://www.blocktrail.com/BTC/tx/' if not item: return tx_hash = str(item.data(0, Qt.UserRole).toString()) From 76cbafe754940481fac862d6620ebb1c01d31b37 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 5 Feb 2015 13:29:18 +0100 Subject: [PATCH 03/19] fix Master Public Keys dialog --- gui/qt/main_window.py | 43 ++++++++++++++++++++++++------------------- lib/wallet.py | 2 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 2ea39a15..45c7a311 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1907,39 +1907,44 @@ class ElectrumWindow(QMainWindow): dialog.setModal(1) dialog.setWindowTitle(_("Master Public Keys")) - main_layout = QGridLayout() mpk_dict = self.wallet.get_master_public_keys() - # filter out the empty keys (PendingAccount) - mpk_dict = {acc:mpk for acc,mpk in mpk_dict.items() if mpk} - + vbox = QVBoxLayout() # only show the combobox in case multiple accounts are available if len(mpk_dict) > 1: - combobox = QComboBox() - for name in mpk_dict: - combobox.addItem(name) - combobox.setCurrentIndex(0) - main_layout.addWidget(combobox, 1, 0) + gb = QGroupBox(_("Master Public Keys")) + vbox.addWidget(gb) + group = QButtonGroup() + first_button = None + 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(text=mpk_dict[account]) + mpk_text = ShowQRTextEdit() mpk_text.setMaximumHeight(170) - mpk_text.selectAll() # for easy copying - main_layout.addWidget(mpk_text, 2, 0) + vbox.addWidget(mpk_text) - def show_mpk(account): - mpk = mpk_dict.get(unicode(account), "") + def show_mpk(b): + name = str(b.text()) + mpk = mpk_dict.get(name, "") 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: mpk = mpk_dict.values()[0] mpk_text = ShowQRTextEdit(text=mpk) mpk_text.setMaximumHeight(170) mpk_text.selectAll() # for easy copying - main_layout.addWidget(mpk_text, 2, 0) + vbox.addWidget(mpk_text, 2, 0) - vbox = QVBoxLayout() - vbox.addLayout(main_layout) vbox.addLayout(close_button(dialog)) dialog.setLayout(vbox) diff --git a/lib/wallet.py b/lib/wallet.py index c9c10aee..00ee6c25 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1494,7 +1494,7 @@ class Wallet_2of2(BIP32_Wallet, Mnemonic): def get_master_public_keys(self): xpub1 = self.master_public_keys.get("x1/") xpub2 = self.master_public_keys.get("x2/") - return {'x1':xpub1, 'x2':xpub2} + return { 'Self':xpub1, 'Cosigner':xpub2 } def get_action(self): xpub1 = self.master_public_keys.get("x1/") From 4bf019e9a617d3081b8ed1a5cb62ffc4f735e48a Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 5 Feb 2015 13:31:09 +0100 Subject: [PATCH 04/19] fix --- gui/qt/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 45c7a311..75d046e2 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1943,7 +1943,7 @@ class ElectrumWindow(QMainWindow): mpk_text = ShowQRTextEdit(text=mpk) mpk_text.setMaximumHeight(170) mpk_text.selectAll() # for easy copying - vbox.addWidget(mpk_text, 2, 0) + vbox.addWidget(mpk_text) vbox.addLayout(close_button(dialog)) From 271a27fc83121e206631ebac95490973f6e96adb Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 6 Feb 2015 07:20:09 +0100 Subject: [PATCH 05/19] fix installwizard_restore --- lib/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins.py b/lib/plugins.py index 956caffa..9a1b3b9f 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -40,7 +40,7 @@ def hook(func): def run_hook(name, *args): - SPECIAL_HOOKS = ['get_wizard_action'] + SPECIAL_HOOKS = ['get_wizard_action','installwizard_restore'] results = [] f_list = hooks.get(name,[]) for p, f in f_list: From 7b0903d81c37bca3bc7e78c2e61442d868c1bf49 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 6 Feb 2015 07:37:09 +0100 Subject: [PATCH 06/19] fix #1010 and cleanup --- gui/qt/network_dialog.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/gui/qt/network_dialog.py b/gui/qt/network_dialog.py index a52cb1c2..98f0f3f0 100644 --- a/gui/qt/network_dialog.py +++ b/gui/qt/network_dialog.py @@ -201,9 +201,12 @@ class NetworkDialog(QDialog): def change_server(self, host, protocol): pp = self.servers.get(host, DEFAULT_PORTS) + if protocol and protocol not in protocol_letters: + protocol = None if protocol: port = pp.get(protocol) - if not port: protocol = None + if port is None: + protocol = None if not protocol: if 's' in pp.keys(): @@ -217,15 +220,6 @@ class NetworkDialog(QDialog): self.server_port.setText( port ) 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): From 1601d58819cf1af4ecbc6392db0dac75804d4bad Mon Sep 17 00:00:00 2001 From: m0mchil Date: Sun, 8 Feb 2015 13:28:10 +0200 Subject: [PATCH 07/19] Confirm address on TREZOR --- plugins/trezor.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/trezor.py b/plugins/trezor.py index 1048b6e0..94fe892c 100644 --- a/plugins/trezor.py +++ b/plugins/trezor.py @@ -131,6 +131,10 @@ class Plugin(BasePlugin): self.wallet.trezor_sign(tx) except Exception as e: tx.error = str(e) + @hook + def receive_menu(self, menu, addrs): + if not self.wallet.is_watching_only() and len(addrs) == 1: + menu.addAction(_("Show on TREZOR"), lambda: self.wallet.show_address(addrs[0])) def settings_widget(self, window): return EnterButton(_('Settings'), self.settings_dialog) @@ -277,6 +281,21 @@ class TrezorWallet(BIP32_HD_Wallet): # twd.emit(SIGNAL('trezor_done')) #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): if not self.check_proper_device(): give_error('Wrong device or password') @@ -426,6 +445,8 @@ class TrezorQtGuiMixin(object): message = "Confirm transaction fee on Trezor device to continue" elif msg.code == 7: message = "Confirm message to sign on Trezor device to continue" + elif msg.code == 10: + message = "Confirm address on Trezor device to continue" else: message = "Check Trezor device to continue" twd.start(message) From 3e1078c79b8f45efd3365b43440a18f2b5dcfe08 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 8 Feb 2015 20:04:42 +0100 Subject: [PATCH 08/19] add support for rsa+sha384 and rsa+sha512 in paymentrequests --- lib/paymentrequest.py | 12 ++++++++---- lib/x509.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index 0c963026..eeab5215 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -190,8 +190,13 @@ class PaymentRequest: verify = pubkey.hashAndVerify(sig, data) elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA256: 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, prefixBytes + hashBytes) + verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA256 + 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: self.error = "Algorithm not supported" util.print_error(self.error, algo.getComponentByName('algorithm')) @@ -226,8 +231,7 @@ class PaymentRequest: if paymntreq.pki_type == "x509+sha256": 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, prefixBytes + hashBytes) + verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes) elif paymntreq.pki_type == "x509+sha1": verify = pubkey0.hashAndVerify(sigBytes, msgBytes) else: diff --git a/lib/x509.py b/lib/x509.py index 41da17a9..b2865bb5 100644 --- a/lib/x509.py +++ b/lib/x509.py @@ -41,8 +41,18 @@ from pyasn1_modules.rfc2459 import id_at_organizationalUnitName as OU_NAME from pyasn1_modules.rfc2459 import id_ce_basicConstraints, BasicConstraints XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5') 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_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): pass From 64677facf209b7e1b670bf8c78332eb773460286 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 9 Feb 2015 17:08:06 +0100 Subject: [PATCH 09/19] fix #1013 --- electrum.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum.desktop b/electrum.desktop index 1b0c5ab3..20a0b1ee 100644 --- a/electrum.desktop +++ b/electrum.desktop @@ -13,5 +13,5 @@ Categories=Network; StartupNotify=false Terminal=false Type=Application -MimeType=x-scheme-handler/bitcoin +MimeType=x-scheme-handler/bitcoin; From 03a88743a3fae6b16c7ba8c5b924f1575203ffec Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 11 Feb 2015 21:05:33 +0100 Subject: [PATCH 10/19] fix typo: stdout --- lib/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index de6ac30a..e9624877 100644 --- a/lib/network.py +++ b/lib/network.py @@ -368,7 +368,7 @@ class Network(threading.Thread): out['result'] = f(*params) except BaseException as e: out['error'] = str(e) - traceback.print_exc(file=sys.stout) + traceback.print_exc(file=sys.stdout) print_error("network error", str(e)) self.response_queue.put(out) From 80446aca9ce37a9fd4e79b2629541eefd261ef02 Mon Sep 17 00:00:00 2001 From: Andy Weidenbaum Date: Wed, 11 Feb 2015 21:53:22 -0800 Subject: [PATCH 11/19] s/defaut/default/ --- electrum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum b/electrum index 0d5d7635..90536aea 100755 --- a/electrum +++ b/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("-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("-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("-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") From ae8c382a4d6011fedf0a19d14451cc6d7f834e50 Mon Sep 17 00:00:00 2001 From: m0mchil Date: Sat, 14 Feb 2015 14:13:59 +0200 Subject: [PATCH 12/19] better trezor version checks --- plugins/trezor.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/trezor.py b/plugins/trezor.py index 94fe892c..bd8c6273 100644 --- a/plugins/trezor.py +++ b/plugins/trezor.py @@ -133,7 +133,7 @@ class Plugin(BasePlugin): tx.error = str(e) @hook def receive_menu(self, menu, addrs): - if not self.wallet.is_watching_only() and len(addrs) == 1: + 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): @@ -216,15 +216,22 @@ class TrezorWallet(BIP32_HD_Wallet): except: 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) - 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.clear_session()# TODO Doesn't work with firmware 1.1, returns proto.Failure self.client.bad = False self.device_checked = 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 + 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): account_id, (change, address_index) = self.get_address_index(address) return "44'/0'/%s'/%d/%d" % (account_id, change, address_index) From beaa932c97f0d822264da024b08f90614f4a4ffd Mon Sep 17 00:00:00 2001 From: m0mchil Date: Sat, 14 Feb 2015 22:42:50 +0200 Subject: [PATCH 13/19] close Trezor USB transport on wallet close --- plugins/trezor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/trezor.py b/plugins/trezor.py index 94fe892c..2b08d029 100644 --- a/plugins/trezor.py +++ b/plugins/trezor.py @@ -96,6 +96,7 @@ class Plugin(BasePlugin): print_error("trezor: clear session") if self.wallet and self.wallet.client: self.wallet.client.clear_session() + self.wallet.client.transport.close() @hook def load_wallet(self, wallet): From 8a8dea73904d9064434d5a144478d92163e7a09f Mon Sep 17 00:00:00 2001 From: Kevin Cooper Date: Fri, 13 Feb 2015 18:28:34 -0700 Subject: [PATCH 14/19] added env/ to gitignore and fixed an issue with electrum-env to pass command line arguments correctly --- .gitignore | 1 + electrum-env | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7918ea80..1a10a8c9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ locale/ .devlocaltmp/ *_trial_temp packages +env/ diff --git a/electrum-env b/electrum-env index 0e2cc70f..0c9c1d7c 100755 --- a/electrum-env +++ b/electrum-env @@ -17,8 +17,8 @@ else python setup.py install fi -export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH +export PYTHONPATH="/usr/local/lib/python2.7/site-packages:$PYTHONPATH" -./electrum +./electrum "$@" deactivate From 58d2e90fa5c9e1c19f3c1726219b4ca1f672a56a Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 15 Feb 2015 21:27:11 +0100 Subject: [PATCH 15/19] parse PEM list using tlslite --- lib/paymentrequest.py | 37 +------------------------------------ lib/x509.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index eeab5215..df5dfd62 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -43,42 +43,8 @@ import x509 REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'} ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'} - -ca_list = {} ca_path = requests.certs.where() - - - - -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() - +ca_list = x509.load_certificates(ca_path) class PaymentRequest: @@ -325,7 +291,6 @@ class PaymentRequest: if __name__ == "__main__": util.set_verbosity(True) - load_certificates() try: uri = sys.argv[1] diff --git a/lib/x509.py b/lib/x509.py index b2865bb5..5b06533f 100644 --- a/lib/x509.py +++ b/lib/x509.py @@ -23,6 +23,7 @@ import sys import pyasn1 import pyasn1_modules import tlslite +import util # workaround https://github.com/trevp/tlslite/issues/15 tlslite.utils.cryptomath.pycryptoLoaded = False @@ -224,3 +225,21 @@ class X509(tlslite.X509): class X509CertChain(tlslite.X509CertChain): 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 From 69ee0bd0de551991a8048f1f7cccc4432b1284b5 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 16 Feb 2015 14:05:04 +0100 Subject: [PATCH 16/19] new scripts: bip70 and estimate_fee --- scripts/bip70 | 59 ++++++++++++++++++++++++++++++++++++++++++++ scripts/estimate_fee | 9 +++++++ 2 files changed, 68 insertions(+) create mode 100755 scripts/bip70 create mode 100755 scripts/estimate_fee diff --git a/scripts/bip70 b/scripts/bip70 new file mode 100755 index 00000000..f7563eb9 --- /dev/null +++ b/scripts/bip70 @@ -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 diff --git a/scripts/estimate_fee b/scripts/estimate_fee new file mode 100755 index 00000000..aa8a2f05 --- /dev/null +++ b/scripts/estimate_fee @@ -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) + + + + From ea4bf4e91f1ba4442fdfcc031727c4c81908d5c1 Mon Sep 17 00:00:00 2001 From: John Miguel Villar Zavatti Date: Mon, 16 Feb 2015 15:24:26 -0430 Subject: [PATCH 17/19] * Added BTCParalelo price index for Venezuelan Bolivar VEF * Fixed all of the update_* functions on the exchange_rate.py plugin to indicate when there are SSL errors, there was a horrorous try-except-pass block that was drowning all the exceptions * Added get_json_insecure to allow exchange rates over HTTP Signed-off-by: John Miguel Villar Zavatti --- plugins/exchange_rate.py | 90 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate.py index 12a4df65..d2e6a133 100644 --- a/plugins/exchange_rate.py +++ b/plugins/exchange_rate.py @@ -17,6 +17,7 @@ from electrum_gui.qt.amountedit import AmountEdit EXCHANGES = ["BitcoinAverage", "BitcoinVenezuela", + "BTCParalelo", "Bitcurex", "Bitmarket", "BitPay", @@ -63,6 +64,26 @@ class Exchanger(threading.Thread): except Exception: raise 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): @@ -82,6 +103,7 @@ class Exchanger(threading.Thread): update_rates = { "BitcoinAverage": self.update_ba, "BitcoinVenezuela": self.update_bv, + "BTCParalelo": self.update_bpl, "Bitcurex": self.update_bx, "Bitmarket": self.update_bm, "BitPay": self.update_bp, @@ -110,6 +132,9 @@ class Exchanger(threading.Thread): def update_cd(self): try: resp_currencies = self.get_json('api.coindesk.com', "/v1/bpi/supported-currencies.json") + except SSLError: + print("SSL Error when accesing coindesk") + return except Exception: return @@ -138,6 +163,9 @@ class Exchanger(threading.Thread): try: 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"])) + except SSLError: + print("SSL Error when accesing itbit") + return except Exception: return with self.lock: @@ -147,6 +175,9 @@ class Exchanger(threading.Thread): def update_wd(self): try: winkresp = self.get_json('winkdex.com', "/api/v0/price") + except SSLError: + print("SSL Error when accesing winkdex") + return except Exception: return quote_currencies = {"USD": 0.0} @@ -162,6 +193,9 @@ class Exchanger(threading.Thread): def update_cv(self): try: jsonresp = self.get_json('www.cavirtex.com', "/api/CAD/ticker.json") + except SSLError: + print("SSL Error when accesing cavirtex") + return except Exception: return quote_currencies = {"CAD": 0.0} @@ -177,6 +211,9 @@ class Exchanger(threading.Thread): def update_bm(self): try: jsonresp = self.get_json('www.bitmarket.pl', "/json/BTCPLN/ticker.json") + except SSLError: + print("SSL Error when accesing bitmarket") + return except Exception: return quote_currencies = {"PLN": 0.0} @@ -192,6 +229,9 @@ class Exchanger(threading.Thread): def update_bx(self): try: jsonresp = self.get_json('pln.bitcurex.com', "/data/ticker.json") + except SSLError: + print("SSL Error when accesing bitcurex") + return except Exception: return quote_currencies = {"PLN": 0.0} @@ -207,6 +247,9 @@ class Exchanger(threading.Thread): def update_CNY(self): try: jsonresp = self.get_json('data.btcchina.com', "/data/ticker") + except SSLError: + print("SSL Error when accesing btcchina") + return except Exception: return quote_currencies = {"CNY": 0.0} @@ -222,6 +265,9 @@ class Exchanger(threading.Thread): def update_bp(self): try: jsonresp = self.get_json('bitpay.com', "/api/rates") + except SSLError: + print("SSL Error when accesing bitpay") + return except Exception: return quote_currencies = {} @@ -237,6 +283,9 @@ class Exchanger(threading.Thread): def update_cb(self): try: jsonresp = self.get_json('coinbase.com', "/api/v1/currencies/exchange_rates") + except SSLError: + print("SSL Error when accesing coinbase") + return except Exception: return @@ -255,6 +304,9 @@ class Exchanger(threading.Thread): def update_bc(self): try: jsonresp = self.get_json('blockchain.info', "/ticker") + except SSLError: + print("SSL Error when accesing blockchain") + return except Exception: return quote_currencies = {} @@ -270,6 +322,9 @@ class Exchanger(threading.Thread): def update_lb(self): try: jsonresp = self.get_json('localbitcoins.com', "/bitcoinaverage/ticker-all-currencies/") + except SSLError: + print("SSL Error when accesing localbitcoins") + return except Exception: return quote_currencies = {} @@ -285,23 +340,52 @@ class Exchanger(threading.Thread): def update_bv(self): 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: return + quote_currencies = {} try: for r in jsonresp["BTC"]: quote_currencies[r] = Decimal(jsonresp["BTC"][r]) + with self.lock: self.quote_currencies = quote_currencies except KeyError: - pass + print ("KeyError") 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): try: jsonresp = self.get_json('api.bitcoinaverage.com', "/ticker/global/all") + except SSLError: + print("SSL Error when accesing bitcoinaverage") + return except Exception: return quote_currencies = {} From 0934976f3ab1b39fddc65892f809b42b9c6ff89f Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 17 Feb 2015 10:30:10 +0100 Subject: [PATCH 18/19] fix #1011 --- gui/qt/main_window.py | 2 ++ lib/wallet.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 6d4b330e..fef18b5b 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -296,6 +296,8 @@ class ElectrumWindow(QMainWindow): wallet.start_threads(self.network) # load new wallet in gui self.load_wallet(wallet) + # save path + self.config.set_key('default_wallet_path', filename) diff --git a/lib/wallet.py b/lib/wallet.py index 00ee6c25..3264845f 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -67,7 +67,7 @@ class WalletStorage(object): # path in config file path = config.get('default_wallet_path') - if path: + if path and os.path.exists(path): return path # default path From f302c9064991bc44ae8ce1a0f790f550ded196e3 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 17 Feb 2015 11:39:06 +0100 Subject: [PATCH 19/19] set self.wallet to None in plugin constructor --- lib/plugins.py | 1 + plugins/exchange_rate.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/plugins.py b/lib/plugins.py index 9a1b3b9f..cb860019 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -71,6 +71,7 @@ class BasePlugin: def __init__(self, config, name): self.name = name self.config = config + self.wallet = None # add self to hooks for k in dir(self): if k in hook_names: diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate.py index d2e6a133..1afdd465 100644 --- a/plugins/exchange_rate.py +++ b/plugins/exchange_rate.py @@ -495,7 +495,6 @@ class Plugin(BasePlugin): @hook def load_wallet(self, wallet): - self.wallet = wallet tx_list = {} 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 @@ -555,6 +554,8 @@ class Plugin(BasePlugin): return if not self.resp_hist: return + if not self.wallet: + return self.win.is_edit = True self.win.history_list.setColumnCount(6)