From 486596b18018db1058f7b2d5bfe93cdff0ecf39d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 27 Jun 2018 19:03:34 +0200 Subject: [PATCH 1/4] trustedcoin: replace asserts --- plugins/trustedcoin/trustedcoin.py | 33 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py index 758a92064..b2b84e5b3 100644 --- a/plugins/trustedcoin/trustedcoin.py +++ b/plugins/trustedcoin/trustedcoin.py @@ -257,7 +257,8 @@ class Wallet_2fa(Multisig_Wallet): return 0 n = self.num_prepay(config) price = int(self.price_per_tx[n]) - assert price <= 100000 * n + if price > 100000 * n: + raise Exception('too high trustedcoin fee ({} for {} txns)'.format(price, n)) return price def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None, @@ -364,7 +365,8 @@ class TrustedCoinPlugin(BasePlugin): if type(wallet) != Wallet_2fa: return if wallet.billing_info is None: - assert wallet.can_sign_without_server() + if not wallet.can_sign_without_server(): + raise Exception('missing trustedcoin billing info') return None address = wallet.billing_info['billing_address'] for _type, addr, amount in tx.outputs(): @@ -390,10 +392,12 @@ class TrustedCoinPlugin(BasePlugin): self.print_error('cannot connect to TrustedCoin server: {}'.format(e)) return billing_address = make_billing_address(wallet, billing_info['billing_index']) - assert billing_address == billing_info['billing_address'] + if billing_address != billing_info['billing_address']: + raise Exception('unexpected trustedcoin billing address: expected {}, received {}' + .format(billing_address, billing_info['billing_address'])) wallet.billing_info = billing_info wallet.price_per_tx = dict(billing_info['price_per_tx']) - wallet.price_per_tx.pop(1) + wallet.price_per_tx.pop(1, None) return True def start_request_thread(self, wallet): @@ -449,7 +453,8 @@ class TrustedCoinPlugin(BasePlugin): # note: pre-2.7 2fa seeds were typically 24-25 words, however they # could probabilistically be arbitrarily shorter due to a bug. (see #3611) # the probability of it being < 20 words is about 2^(-(256+12-19*11)) = 2^(-59) - assert passphrase == '' + if passphrase != '': + raise Exception('old 2fa seed cannot have passphrase') xprv1, xpub1 = self.get_xkeys(' '.join(words[0:12]), '', "m/") xprv2, xpub2 = self.get_xkeys(' '.join(words[12:]), '', "m/") elif n==12: @@ -558,11 +563,13 @@ class TrustedCoinPlugin(BasePlugin): return _xpub3 = r['xpubkey_cosigner'] _id = r['id'] - try: - assert _id == short_id, ("user id error", _id, short_id) - assert xpub3 == _xpub3, ("xpub3 error", xpub3, _xpub3) - except Exception as e: - wizard.show_message(str(e)) + if short_id != _id: + wizard.show_message("unexpected trustedcoin short_id: expected {}, received {}" + .format(short_id, _id)) + return + if xpub3 != _xpub3: + wizard.show_message("unexpected trustedcoin xpub3: expected {}, received {}" + .format(xpub3, _xpub3)) return self.request_otp_dialog(wizard, short_id, otp_secret, xpub3) @@ -603,10 +610,8 @@ class TrustedCoinPlugin(BasePlugin): def on_reset_auth(self, wizard, short_id, seed, passphrase, xpub3): xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase) - try: - assert xpub1 == wizard.storage.get('x1/')['xpub'] - assert xpub2 == wizard.storage.get('x2/')['xpub'] - except: + if (wizard.storage.get('x1/')['xpub'] != xpub1 or + wizard.storage.get('x2/')['xpub'] != xpub2): wizard.show_message(_('Incorrect seed')) return r = server.get_challenge(short_id) From f3d2831db76b8238841c598863044f7a09e3b253 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 27 Jun 2018 20:17:59 +0200 Subject: [PATCH 2/4] trustedcoin: request billing info after resetting it --- plugins/trustedcoin/trustedcoin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py index b2b84e5b3..415c9db59 100644 --- a/plugins/trustedcoin/trustedcoin.py +++ b/plugins/trustedcoin/trustedcoin.py @@ -296,6 +296,7 @@ class Wallet_2fa(Multisig_Wallet): self.print_error("twofactor: is complete", tx.is_complete()) # reset billing_info self.billing_info = None + self.plugin.start_request_thread(self) @@ -366,6 +367,7 @@ class TrustedCoinPlugin(BasePlugin): return if wallet.billing_info is None: if not wallet.can_sign_without_server(): + self.start_request_thread(wallet) raise Exception('missing trustedcoin billing info') return None address = wallet.billing_info['billing_address'] From db91618a44826a18b1e9b7b2f2b420581f3db8a3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 27 Jun 2018 22:03:13 +0200 Subject: [PATCH 3/4] Qt: (minor) clean up "about" message --- gui/qt/main_window.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 4a5069715..aa852bb1c 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -562,9 +562,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def show_about(self): QMessageBox.about(self, "Electrum", - _("Version")+" %s" % (self.wallet.electrum_version) + "\n\n" + - _("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." + "\n\n" + - _("Uses icons from the Icons8 icon pack (icons8.com)."))) + (_("Version")+" %s" % self.wallet.electrum_version + "\n\n" + + _("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.") + "\n\n" + + _("Uses icons from the Icons8 icon pack (icons8.com)."))) def show_report_bug(self): msg = ' '.join([ From 1f6ccfb1342592d49672473e565a0ef993278ce2 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 28 Jun 2018 11:42:47 +0200 Subject: [PATCH 4/4] fast hmac on python 3.7+ --- lib/bitcoin.py | 10 +++++----- lib/crypto.py | 9 +++++++++ lib/ecc.py | 6 +++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index ad205efb8..1fe405fe2 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -31,7 +31,7 @@ from . import version from . import segwit_addr from . import constants from . import ecc -from .crypto import Hash, sha256, hash_160 +from .crypto import Hash, sha256, hash_160, hmac_oneshot ################################## transactions @@ -149,7 +149,7 @@ def add_number_to_script(i: int) -> bytes: hash_encode = lambda x: bh2u(x[::-1]) hash_decode = lambda x: bfh(x)[::-1] -hmac_sha_512 = lambda x, y: hmac.new(x, y, hashlib.sha512).digest() +hmac_sha_512 = lambda x, y: hmac_oneshot(x, y, hashlib.sha512) def is_new_seed(x, prefix=version.SEED_PREFIX): @@ -565,7 +565,7 @@ def _CKD_priv(k, c, s, is_prime): raise BitcoinException('Impossible xprv (not within curve order)') from e cK = keypair.get_public_key_bytes(compressed=True) data = bytes([0]) + k + s if is_prime else cK + s - I = hmac.new(c, data, hashlib.sha512).digest() + I = hmac_oneshot(c, data, hashlib.sha512) I_left = ecc.string_to_number(I[0:32]) k_n = (I_left + ecc.string_to_number(k)) % ecc.CURVE_ORDER if I_left >= ecc.CURVE_ORDER or k_n == 0: @@ -589,7 +589,7 @@ def CKD_pub(cK, c, n): # helper function, callable with arbitrary string. # note: 's' does not need to fit into 32 bits here! (c.f. trustedcoin billing) def _CKD_pub(cK, c, s): - I = hmac.new(c, cK + s, hashlib.sha512).digest() + I = hmac_oneshot(c, cK + s, hashlib.sha512) pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(cK) if pubkey.is_at_infinity(): raise ecc.InvalidECPointException() @@ -683,7 +683,7 @@ def xpub_from_xprv(xprv): def bip32_root(seed, xtype): - I = hmac.new(b"Bitcoin seed", seed, hashlib.sha512).digest() + I = hmac_oneshot(b"Bitcoin seed", seed, hashlib.sha512) master_k = I[0:32] master_c = I[32:] # create xprv first, as that will check if master_k is within curve order diff --git a/lib/crypto.py b/lib/crypto.py index 7020649a5..de8b6b5d7 100644 --- a/lib/crypto.py +++ b/lib/crypto.py @@ -26,6 +26,7 @@ import base64 import os import hashlib +import hmac import pyaes @@ -140,3 +141,11 @@ def hash_160(x: bytes) -> bytes: from . import ripemd md = ripemd.new(sha256(x)) return md.digest() + + +def hmac_oneshot(key: bytes, msg: bytes, digest) -> bytes: + if hasattr(hmac, 'digest'): + # requires python 3.7+; faster + return hmac.digest(key, msg, digest) + else: + return hmac.new(key, msg, digest).digest() diff --git a/lib/ecc.py b/lib/ecc.py index 585883fc2..0fd0f248b 100644 --- a/lib/ecc.py +++ b/lib/ecc.py @@ -36,7 +36,7 @@ from ecdsa.ellipticcurve import Point from ecdsa.util import string_to_number, number_to_string from .util import bfh, bh2u, assert_bytes, print_error, to_bytes, InvalidPassword, profiler -from .crypto import (Hash, aes_encrypt_with_iv, aes_decrypt_with_iv) +from .crypto import (Hash, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot) from .ecc_fast import do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1 @@ -285,7 +285,7 @@ class ECPubkey(object): ciphertext = aes_encrypt_with_iv(key_e, iv, message) ephemeral_pubkey = ephemeral.get_public_key_bytes(compressed=True) encrypted = magic + ephemeral_pubkey + ciphertext - mac = hmac.new(key_m, encrypted, hashlib.sha256).digest() + mac = hmac_oneshot(key_m, encrypted, hashlib.sha256) return base64.b64encode(encrypted + mac) @@ -424,7 +424,7 @@ class ECPrivkey(ECPubkey): ecdh_key = (ephemeral_pubkey * self.secret_scalar).get_public_key_bytes(compressed=True) key = hashlib.sha512(ecdh_key).digest() iv, key_e, key_m = key[0:16], key[16:32], key[32:] - if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest(): + if mac != hmac_oneshot(key_m, encrypted[:-32], hashlib.sha256): raise InvalidPassword() return aes_decrypt_with_iv(key_e, iv, ciphertext)