electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 362ca96f382afd8f8cdb31c123df930eca461f49
parent 0693403358656f1e7d5453679f125ab48d7fb644
Author: Dmitry Sorokin <asfins@gmail.com>
Date:   Sun,  5 Feb 2017 13:38:44 +0300

x509 fixes and plugins

Diffstat:
Mlib/base_wizard.py | 11++++++-----
Mlib/bitcoin.py | 8++++----
Mlib/daemon.py | 66+++++++++++++++++++++++++++++++++---------------------------------
Mlib/keystore.py | 12++++++------
Mlib/mnemonic.py | 2+-
Mlib/paymentrequest.py | 1-
Mlib/pem.py | 14+++++++-------
Mlib/plugins.py | 8+-------
Mlib/storage.py | 6------
Mlib/util.py | 4++--
Mlib/wallet.py | 2--
Mlib/x509.py | 53++++++++++++++++++++++++++++++++++-------------------
Mplugins/cosigner_pool/qt.py | 4++--
Mplugins/email_requests/qt.py | 11++++-------
Mplugins/hw_wallet/__init__.py | 2+-
Mplugins/labels/labels.py | 10++++------
Mplugins/labels/qt.py | 2+-
Mplugins/virtualkeyboard/qt.py | 10+++++-----
18 files changed, 111 insertions(+), 115 deletions(-)

diff --git a/lib/base_wizard.py b/lib/base_wizard.py @@ -189,7 +189,7 @@ class BaseWizard(object): except: devmgr.print_error("error", name) continue - devices += map(lambda x: (name, x), u) + devices += list(map(lambda x: (name, x), u)) if not devices: msg = ''.join([ _('No hardware device detected.') + '\n', @@ -235,7 +235,7 @@ class BaseWizard(object): self.line_dialog(run_next=f, title=_('Derivation'), message=message, default=default, test=bitcoin.is_bip32_derivation) def on_hw_derivation(self, name, device_info, derivation): - from keystore import hardware_keystore + from .keystore import hardware_keystore xpub = self.plugin.get_xpub(device_info.device.id_, derivation, self) if xpub is None: self.show_error('Cannot read xpub from device') @@ -286,7 +286,7 @@ class BaseWizard(object): self.load_2fa() self.run('on_restore_seed', seed, is_ext) else: - raise BaseException('Unknown seed type', seed_type) + raise BaseException('Unknown seed type', self.seed_type) def on_restore_bip39(self, seed, passphrase): f = lambda x: self.run('on_bip44', seed, passphrase, str(x)) @@ -334,7 +334,8 @@ class BaseWizard(object): k.update_password(None, password) if self.wallet_type == 'standard': self.storage.put('seed_type', self.seed_type) - self.storage.put('keystore', k.dump()) + keys = self.keystores[0].dump() + self.storage.put('keystore', keys) self.wallet = Standard_Wallet(self.storage) self.run('create_addresses') elif self.wallet_type == 'multisig': @@ -355,7 +356,7 @@ class BaseWizard(object): self.on_keystore(k) def create_seed(self): - import mnemonic + from . import mnemonic self.seed_type = 'segwit' if bitcoin.TESTNET and self.config.get('segwit') else 'standard' seed = mnemonic.Mnemonic('en').make_seed(self.seed_type) self.opt_bip39 = False diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -29,7 +29,7 @@ import re import hmac import os -from lib.util import bfh, bh2u +from lib.util import bfh, bh2u, to_string from . import version from .util import print_error, InvalidPassword, assert_bytes, to_bytes @@ -150,7 +150,7 @@ def DecodeAES(secret, e): def pw_encode(s, password): if password: secret = Hash(password) - return EncodeAES(secret, s.encode("utf8")) + return EncodeAES(secret, to_bytes(s, "utf8")) else: return s @@ -158,7 +158,7 @@ def pw_decode(s, password): if password is not None: secret = Hash(password) try: - d = DecodeAES(secret, s).decode("utf8") + d = to_string(DecodeAES(secret, s), "utf8") except Exception: raise InvalidPassword() return d @@ -213,7 +213,7 @@ def Hash(x): hash_encode = lambda x: bh2u(x[::-1]) hash_decode = lambda x: bfh(x)[::-1] -hmac_sha_512 = lambda x, y: _bytes(hmac.new(x, y, hashlib.sha512).digest()) +hmac_sha_512 = lambda x, y: hmac.new(x, y, hashlib.sha512).digest() def is_new_seed(x, prefix=version.SEED_PREFIX): diff --git a/lib/daemon.py b/lib/daemon.py @@ -34,8 +34,8 @@ import sys import time # from jsonrpc import JSONRPCResponseManager -import jsonrpclib -from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler +# import jsonrpclib +# from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler from .version import ELECTRUM_VERSION from .network import Network @@ -96,17 +96,17 @@ def get_server(config): time.sleep(1.0) -class RequestHandler(SimpleJSONRPCRequestHandler): - - def do_OPTIONS(self): - self.send_response(200) - self.end_headers() - - def end_headers(self): - self.send_header("Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept") - self.send_header("Access-Control-Allow-Origin", "*") - SimpleJSONRPCRequestHandler.end_headers(self) +# class RequestHandler(SimpleJSONRPCRequestHandler): +# +# def do_OPTIONS(self): +# self.send_response(200) +# self.end_headers() +# +# def end_headers(self): +# self.send_header("Access-Control-Allow-Headers", +# "Origin, X-Requested-With, Content-Type, Accept") +# self.send_header("Access-Control-Allow-Origin", "*") +# SimpleJSONRPCRequestHandler.end_headers(self) class Daemon(DaemonThread): @@ -132,24 +132,24 @@ class Daemon(DaemonThread): def init_server(self, config, fd): host = config.get('rpchost', '127.0.0.1') port = config.get('rpcport', 0) - try: - server = SimpleJSONRPCServer((host, port), logRequests=False, - requestHandler=RequestHandler) - except Exception as e: - self.print_error('Warning: cannot initialize RPC server on host', host, e) - self.server = None - os.close(fd) - return - os.write(fd, bytes(repr((server.socket.getsockname(), time.time())), 'utf8')) - os.close(fd) - server.timeout = 0.1 - for cmdname in known_commands: - server.register_function(getattr(self.cmd_runner, cmdname), cmdname) - server.register_function(self.run_cmdline, 'run_cmdline') - server.register_function(self.ping, 'ping') - server.register_function(self.run_daemon, 'daemon') - server.register_function(self.run_gui, 'gui') - self.server = server + # try: + # server = SimpleJSONRPCServer((host, port), logRequests=False, requestHandler=RequestHandler) + # except Exception as e: + # self.print_error('Warning: cannot initialize RPC server on host', host, e) + # self.server = None + # os.close(fd) + # return + # os.write(fd, bytes(repr((server.socket.getsockname(), time.time())), 'utf8')) + # os.close(fd) + # server.timeout = 0.1 + # for cmdname in known_commands: + # server.register_function(getattr(self.cmd_runner, cmdname), cmdname) + # server.register_function(self.run_cmdline, 'run_cmdline') + # server.register_function(self.ping, 'ping') + # server.register_function(self.run_daemon, 'daemon') + # server.register_function(self.run_gui, 'gui') + # self.server = server + self.server = None def ping(self): return True @@ -260,9 +260,9 @@ class Daemon(DaemonThread): # arguments passed to function args = map(lambda x: config.get(x), cmd.params) # decode json arguments - args = map(json_decode, args) + args = [json_decode(i) for i in args] # options - args += map(lambda x: (config_options.get(x) if x in ['password', 'new_password'] else config.get(x)), cmd.options) + args += list(map(lambda x: (config_options.get(x) if x in ['password', 'new_password'] else config.get(x)), cmd.options)) cmd_runner = Commands(config, wallet, self.network) func = getattr(cmd_runner, cmd.name) result = func(*args) diff --git a/lib/keystore.py b/lib/keystore.py @@ -149,7 +149,7 @@ class Imported_KeyStore(Software_KeyStore): except Exception: raise BaseException('Invalid private key') # allow overwrite - self.keypairs[pubkey] = pw_encode(sec, password) + self.keypairs[pubkey] = pw_encode(sec, password).decode('ascii') return pubkey def delete_imported_key(self, key): @@ -179,7 +179,7 @@ class Imported_KeyStore(Software_KeyStore): new_password = None for k, v in self.keypairs.items(): b = pw_decode(v, old_password) - c = pw_encode(b, new_password) + c = pw_encode(b, new_password).decode('ascii') self.keypairs[k] = c @@ -309,13 +309,13 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub): new_password = None if self.has_seed(): decoded = self.get_seed(old_password) - self.seed = pw_encode(decoded, new_password) + self.seed = pw_encode(decoded, new_password).decode('ascii') if self.passphrase: decoded = self.get_passphrase(old_password) - self.passphrase = pw_encode(decoded, new_password) + self.passphrase = pw_encode(decoded, new_password).decode('ascii') if self.xprv is not None: b = pw_decode(self.xprv, old_password) - self.xprv = pw_encode(b, new_password) + self.xprv = pw_encode(b, new_password).decode('ascii') def is_watching_only(self): return self.xprv is None @@ -478,7 +478,7 @@ class Old_KeyStore(Deterministic_KeyStore): new_password = None if self.has_seed(): decoded = self.get_hex_seed(old_password) - self.seed = pw_encode(decoded, new_password) + self.seed = pw_encode(decoded, new_password).decode('ascii') class Hardware_KeyStore(KeyStore, Xpub): diff --git a/lib/mnemonic.py b/lib/mnemonic.py @@ -173,7 +173,7 @@ class Mnemonic(object): return i % custom_entropy == 0 def make_seed(self, seed_type='standard', num_bits=132, custom_entropy=1): - import version + from . import version prefix = version.seed_prefix(seed_type) # increase num_bits in order to obtain a uniform distibution for the last word bpw = math.log(len(self.wordlist), 2) diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py @@ -345,7 +345,6 @@ def sign_request_with_alias(pr, alias, alias_privkey): pr.signature = ec_key.sign_message(message, compressed, address) - def verify_cert_chain(chain): """ Verify a chain of certificates. The last certificate is the CA""" load_ca_list() diff --git a/lib/pem.py b/lib/pem.py @@ -127,12 +127,12 @@ def pem(b, name): -----END CERTIFICATE----- """ s1 = b2a_base64(b)[:-1] # remove terminating \n - s2 = "" + s2 = b"" while s1: - s2 += s1[:64] + "\n" + s2 += s1[:64] + b"\n" s1 = s1[64:] - s = ("-----BEGIN %s-----\n" % name) + s2 + \ - ("-----END %s-----\n" % name) + s = ("-----BEGIN %s-----\n" % name).encode('ascii') + s2 + \ + ("-----END %s-----\n" % name).encode('ascii') return s def pemSniff(inStr, name): @@ -152,8 +152,8 @@ def parse_private_key(s): raise SyntaxError("Not a PEM private key file") -def _parsePKCS8(bytes): - s = ASN1_Node(str(bytes)) +def _parsePKCS8(_bytes): + s = ASN1_Node(_bytes) root = s.root() version_node = s.first_child(root) version = bytestr_to_int(s.get_value_of_type(version_node, 'INTEGER')) @@ -192,5 +192,5 @@ def _parseASN1PrivateKey(s): dP = s.next_node(q) dQ = s.next_node(dP) qInv = s.next_node(dQ) - return map(lambda x: bytesToNumber(s.get_value_of_type(x, 'INTEGER')), [n, e, d, p, q, dP, dQ, qInv]) + return list(map(lambda x: bytesToNumber(s.get_value_of_type(x, 'INTEGER')), [n, e, d, p, q, dP, dQ, qInv])) diff --git a/lib/plugins.py b/lib/plugins.py @@ -22,12 +22,6 @@ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import six from collections import namedtuple import traceback import sys @@ -192,7 +186,7 @@ class Plugins(DaemonThread): def hook(func): - hook_names.add(func.func_name) + hook_names.add(func.__name__) return func def run_hook(name, *args): diff --git a/lib/storage.py b/lib/storage.py @@ -22,12 +22,6 @@ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import six import os import ast import threading diff --git a/lib/util.py b/lib/util.py @@ -586,7 +586,8 @@ def parse_URI(uri, on_pr=None): request = pr.PaymentRequest(s) else: request = pr.get_payment_request(r) - on_pr(request) + if on_pr: + on_pr(request) t = threading.Thread(target=get_payment_request_thread) t.setDaemon(True) t.start() @@ -698,7 +699,6 @@ class SocketPipe: self._send(out) def send_all(self, requests): - print(requests) out = b''.join(map(lambda x: (json.dumps(x) + '\n').encode('utf8'), requests)) self._send(out) diff --git a/lib/wallet.py b/lib/wallet.py @@ -1537,8 +1537,6 @@ class Deterministic_Wallet(Abstract_Wallet): return self.get_master_public_key() - - class Simple_Wallet(Abstract_Wallet): """ Wallet with a single pubkey per address """ diff --git a/lib/x509.py b/lib/x509.py @@ -63,6 +63,7 @@ ASN1_TYPES = { 'PrintableString' : 0x13, 'IA5String' : 0x16, 'UTCTime' : 0x17, + 'GeneralizedTime' : 0x18, 'ENUMERATED' : 0x0A, 'UTF8String' : 0x0C, } @@ -75,7 +76,7 @@ class CertificateError(Exception): # helper functions def bitstr_to_bytestr(s): if s[0] != 0x00: - raise BaseException('no padding') + raise TypeError('no padding') return s[1:] @@ -117,7 +118,7 @@ class ASN1_Node(bytes): def get_node(self, ix): # return index of first byte, first content byte and last byte. first = self[ix + 1] - if (self[ix + 1] & 0x80) == 0: + if (first & 0x80) == 0: length = first ixf = ix + 2 ixl = ixf + length - 1 @@ -138,7 +139,7 @@ class ASN1_Node(bytes): def first_child(self, node): ixs, ixf, ixl = node if self[ixs] & 0x20 != 0x20: - raise BaseException('Can only open constructed types.', hex(self[ixs])) + raise TypeError('Can only open constructed types.', hex(self[ixs])) return self.get_node(ixf) def is_child_of(node1, node2): @@ -155,7 +156,7 @@ class ASN1_Node(bytes): # verify type byte and return content ixs, ixf, ixl = node if ASN1_TYPES[asn1_type] != self[ixs]: - raise BaseException('Wrong type:', hex(self[ixs]), hex(ASN1_TYPES[asn1_type])) + raise TypeError('Wrong type:', hex(self[ixs]), hex(ASN1_TYPES[asn1_type])) return self[ixf:ixl + 1] def get_value(self, node): @@ -217,9 +218,15 @@ class X509(object): # validity validity = der.next_node(issuer) ii = der.first_child(validity) - self.notBefore = der.get_value_of_type(ii, 'UTCTime') + try: + self.notBefore = der.get_value_of_type(ii, 'UTCTime') + except TypeError: + self.notBefore = der.get_value_of_type(ii, 'GeneralizedTime')[2:] # strip year ii = der.next_node(ii) - self.notAfter = der.get_value_of_type(ii, 'UTCTime') + try: + self.notAfter = der.get_value_of_type(ii, 'UTCTime') + except TypeError: + self.notAfter = der.get_value_of_type(ii, 'GeneralizedTime')[2:] # strip year # subject subject = der.next_node(validity) @@ -229,17 +236,22 @@ class X509(object): ii = der.first_child(public_key_algo) self.public_key_algo = decode_OID(der.get_value_of_type(ii, 'OBJECT IDENTIFIER')) - # pubkey modulus and exponent - subject_public_key = der.next_node(public_key_algo) - spk = der.get_value_of_type(subject_public_key, 'BIT STRING') - spk = ASN1_Node(bitstr_to_bytestr(spk)) - r = spk.root() - modulus = spk.first_child(r) - exponent = spk.next_node(modulus) - rsa_n = spk.get_value_of_type(modulus, 'INTEGER') - rsa_e = spk.get_value_of_type(exponent, 'INTEGER') - self.modulus = ecdsa.util.string_to_number(rsa_n) - self.exponent = ecdsa.util.string_to_number(rsa_e) + if self.public_key_algo != '1.2.840.10045.2.1': # for non EC public key + # pubkey modulus and exponent + subject_public_key = der.next_node(public_key_algo) + spk = der.get_value_of_type(subject_public_key, 'BIT STRING') + spk = ASN1_Node(bitstr_to_bytestr(spk)) + r = spk.root() + modulus = spk.first_child(r) + exponent = spk.next_node(modulus) + rsa_n = spk.get_value_of_type(modulus, 'INTEGER') + rsa_e = spk.get_value_of_type(exponent, 'INTEGER') + self.modulus = ecdsa.util.string_to_number(rsa_n) + self.exponent = ecdsa.util.string_to_number(rsa_e) + else: + subject_public_key = der.next_node(public_key_algo) + spk = der.get_value_of_type(subject_public_key, 'BIT STRING') + self.ec_public_key = spk # extensions self.CA = False @@ -308,14 +320,17 @@ def load_certificates(ca_path): from . import pem ca_list = {} ca_keyID = {} - with open(ca_path, 'rb') as f: - s = f.read().decode('ascii') + # ca_path = '/tmp/tmp.txt' + with open(ca_path, 'r') as f: + s = f.read() bList = pem.dePemList(s, "CERTIFICATE") for b in bList: try: x = X509(b) x.check_date() except BaseException as e: + # with open('/tmp/tmp.txt', 'w') as f: + # f.write(pem.pem(b, 'CERTIFICATE').decode('ascii')) util.print_error("cert error:", e) continue diff --git a/plugins/cosigner_pool/qt.py b/plugins/cosigner_pool/qt.py @@ -26,7 +26,7 @@ import socket import threading import time -import xmlrpclib +from xmlrpc.client import ServerProxy from PyQt4.QtGui import * from PyQt4.QtCore import * @@ -45,7 +45,7 @@ import traceback PORT = 12344 HOST = 'cosigner.electrum.org' -server = xmlrpclib.ServerProxy('http://%s:%d'%(HOST,PORT), allow_none=True) +server = ServerProxy('http://%s:%d'%(HOST,PORT), allow_none=True) class Listener(util.DaemonThread): diff --git a/plugins/email_requests/qt.py b/plugins/email_requests/qt.py @@ -23,8 +23,6 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from __future__ import absolute_import - import time import threading import base64 @@ -33,9 +31,9 @@ from functools import partial import smtplib import imaplib import email -from email.MIMEMultipart import MIMEMultipart -from email.MIMEBase import MIMEBase -from email import Encoders +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.encoders import encode_base64 from PyQt4.QtGui import * from PyQt4.QtCore import * @@ -49,7 +47,6 @@ from electrum_gui.qt.util import EnterButton, Buttons, CloseButton from electrum_gui.qt.util import OkButton, WindowModalDialog - class Processor(threading.Thread): polling_interval = 5*60 @@ -96,7 +93,7 @@ class Processor(threading.Thread): msg['From'] = self.username part = MIMEBase('application', "bitcoin-paymentrequest") part.set_payload(payment_request) - Encoders.encode_base64(part) + encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="payreq.btc"') msg.attach(part) s = smtplib.SMTP_SSL(self.imap_server, timeout=2) diff --git a/plugins/hw_wallet/__init__.py b/plugins/hw_wallet/__init__.py @@ -1 +1 @@ -from plugin import HW_PluginBase +from .plugin import HW_PluginBase diff --git a/plugins/labels/labels.py b/plugins/labels/labels.py @@ -12,8 +12,6 @@ from electrum.plugins import BasePlugin, hook from electrum.i18n import _ - - class LabelsPlugin(BasePlugin): def __init__(self, parent, config, name): @@ -83,7 +81,7 @@ class LabelsPlugin(BasePlugin): bundle = {"labels": [], "walletId": wallet_id, "walletNonce": self.get_nonce(wallet)} - for key, value in wallet.labels.iteritems(): + for key, value in wallet.labels.items(): try: encoded_key = self.encode(wallet, key) encoded_value = self.encode(wallet, value) @@ -135,12 +133,12 @@ class LabelsPlugin(BasePlugin): def start_wallet(self, wallet): nonce = self.get_nonce(wallet) self.print_error("wallet", wallet.basename(), "nonce is", nonce) - mpk = wallet.get_fingerprint() + mpk = wallet.get_fingerprint().encode('ascii') if not mpk: return - password = hashlib.sha1(mpk).digest().encode('hex')[:32] + password = hashlib.sha1(mpk).hexdigest()[:32].encode('ascii') iv = hashlib.sha256(password).digest()[:16] - wallet_id = hashlib.sha256(mpk).digest().encode('hex') + wallet_id = hashlib.sha256(mpk).hexdigest() self.wallets[wallet] = (password, iv, wallet_id) # If there is an auth token we can try to actually start syncing t = threading.Thread(target=self.pull_thread, args=(wallet, False)) diff --git a/plugins/labels/qt.py b/plugins/labels/qt.py @@ -9,7 +9,7 @@ from electrum_gui.qt import EnterButton from electrum_gui.qt.util import ThreadedButton, Buttons from electrum_gui.qt.util import WindowModalDialog, OkButton -from labels import LabelsPlugin +from .labels import LabelsPlugin class Plugin(LabelsPlugin): diff --git a/plugins/virtualkeyboard/qt.py b/plugins/virtualkeyboard/qt.py @@ -3,8 +3,8 @@ from electrum.plugins import BasePlugin, hook from electrum.i18n import _ import random -class Plugin(BasePlugin): +class Plugin(BasePlugin): vkb = None vkb_index = 0 @@ -25,7 +25,7 @@ class Plugin(BasePlugin): self.vkb_index += 1 def virtual_keyboard(self, i, pw): - i = i%3 + i = i % 3 if i == 0: chars = 'abcdefghijklmnopqrstuvwxyz ' elif i == 1: @@ -35,9 +35,9 @@ class Plugin(BasePlugin): n = len(chars) s = [] - for i in xrange(n): + for i in range(n): while True: - k = random.randint(0,n-1) + k = random.randint(0, n - 1) if k not in s: s.append(k) break @@ -53,7 +53,7 @@ class Plugin(BasePlugin): l_button.setFixedWidth(25) l_button.setFixedHeight(25) l_button.clicked.connect(add_target(chars[s[i]])) - grid.addWidget(l_button, i/6, i%6) + grid.addWidget(l_button, i // 6, i % 6) vbox.addLayout(grid)