From d71d22d279b57dbe1f1509b7ba37fc421b2f173a Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 10 Mar 2018 14:55:06 +0100 Subject: [PATCH 01/23] Fix Typo --- 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 27a4b0165d..ccd051e4e0 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1182,7 +1182,7 @@ def feerounding_onclick(): self.fee_adv_controls.setVisible(False) self.preview_button = EnterButton(_("Preview"), self.do_preview) - self.preview_button.setToolTip(_('Display the details of your transactions before signing it.')) + self.preview_button.setToolTip(_('Display the details of your transaction before signing it.')) self.send_button = EnterButton(_("Send"), self.do_send) self.clear_button = EnterButton(_("Clear"), self.do_clear) buttons = QHBoxLayout() From e31c2d491d4301c6ec44e977ee22f2bc81381410 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 11 Mar 2018 07:18:07 +0100 Subject: [PATCH 02/23] fix #4093 --- lib/bitcoin.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 1d7831465f..731d10f514 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -408,7 +408,10 @@ def base_decode(v, length, base): chars = __b43chars long_value = 0 for (i, c) in enumerate(v[::-1]): - long_value += chars.find(bytes([c])) * (base**i) + digit = chars.find(bytes([c])) + if digit == -1: + raise ValueError('Forbidden character {} for base {}'.format(c, base)) + long_value += digit * (base**i) result = bytearray() while long_value >= 256: div, mod = divmod(long_value, 256) @@ -428,6 +431,10 @@ def base_decode(v, length, base): return bytes(result) +class InvalidChecksum(Exception): + pass + + def EncodeBase58Check(vchIn): hash = Hash(vchIn) return base_encode(vchIn + hash[0:4], base=58) @@ -440,7 +447,7 @@ def DecodeBase58Check(psz): hash = Hash(key) cs32 = hash[0:4] if cs32 != csum: - return None + raise InvalidChecksum('expected {}, actual {}'.format(bh2u(cs32), bh2u(csum))) else: return key @@ -479,8 +486,9 @@ def deserialize_privkey(key): if ':' in key: txin_type, key = key.split(sep=':', maxsplit=1) assert txin_type in SCRIPT_TYPES - vch = DecodeBase58Check(key) - if not vch: + try: + vch = DecodeBase58Check(key) + except BaseException: neutered_privkey = str(key)[:3] + '..' + str(key)[-2:] raise BaseException("cannot deserialize", neutered_privkey) From 78692327373252714792607ae49a0daee06bae15 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sun, 11 Mar 2018 11:26:19 +0100 Subject: [PATCH 03/23] Switch labelsync to electrum.org --- plugins/labels/labels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/labels/labels.py b/plugins/labels/labels.py index 3e778d544f..0033b2d85a 100644 --- a/plugins/labels/labels.py +++ b/plugins/labels/labels.py @@ -16,7 +16,7 @@ class LabelsPlugin(BasePlugin): def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) - self.target_host = 'labels.bauerj.eu' + self.target_host = 'labels.electrum.org' self.wallets = {} def encode(self, wallet, msg): From d3065f73bf01a86dc50d902f0a118af4710e802d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 11 Mar 2018 13:28:13 +0100 Subject: [PATCH 04/23] follow-up 3c505660a6c0827b72d12bd9bb7d5756974d5285 --- plugins/digitalbitbox/digitalbitbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index a472d9f971..599c8f1c07 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -174,12 +174,12 @@ def check_device_dialog(self): self.password = None if reply['error']['code'] == 109: msg = _("Incorrect password entered.") + "\n\n" + \ - + reply['error']['message'] + "\n\n" + \ + reply['error']['message'] + "\n\n" + \ _("Enter your Digital Bitbox password:") else: # Should never occur msg = _("Unexpected error occurred.") + "\n\n" + \ - + reply['error']['message'] + "\n\n" + \ + reply['error']['message'] + "\n\n" + \ _("Enter your Digital Bitbox password:") # Initialize device if not yet initialized From a048a00594f00da0cd800b9b25a8a2cde341db1e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 12 Mar 2018 04:00:27 +0100 Subject: [PATCH 05/23] close #4102 close #3337 --- lib/storage.py | 4 +++- lib/wallet.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/storage.py b/lib/storage.py index d87a339a83..1b1079a32b 100644 --- a/lib/storage.py +++ b/lib/storage.py @@ -51,6 +51,8 @@ def multisig_type(wallet_type): '''If wallet_type is mofn multi-sig, return [m, n], otherwise return None.''' + if not wallet_type: + return None match = re.match('(\d+)of(\d+)', wallet_type) if match: match = [int(x) for x in match.group(1, 2)] @@ -417,7 +419,7 @@ def convert_wallet_type(self): d['seed'] = seed self.put(key, d) else: - raise + raise Exception('Unable to tell wallet type. Is this even a wallet file?') # remove junk self.put('master_public_key', None) self.put('master_public_keys', None) diff --git a/lib/wallet.py b/lib/wallet.py index 9f4f2baa91..48a11d4fc0 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -2301,5 +2301,5 @@ def wallet_class(wallet_type): return Multisig_Wallet if wallet_type in wallet_constructors: return wallet_constructors[wallet_type] - raise RuntimeError("Unknown wallet type: " + wallet_type) + raise RuntimeError("Unknown wallet type: " + str(wallet_type)) From 0603f9f2b4c6e23fbe9dc0bf79803a86f9aea9e5 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 10:18:09 +0100 Subject: [PATCH 06/23] fix #4108 --- lib/base_wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/base_wizard.py b/lib/base_wizard.py index 98394382a2..016fbdf522 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -164,7 +164,7 @@ def on_import(self, text): k = keystore.Imported_KeyStore({}) self.storage.put('keystore', k.dump()) w = Imported_Wallet(self.storage) - for x in text.split(): + for x in keystore.get_private_keys(text): w.import_private_key(x, None) self.keystores.append(w.keystore) else: From cf866adfe3063f3603be28216606d9448ad0da19 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 10:30:56 +0100 Subject: [PATCH 07/23] fix #4109 --- lib/wallet.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 48a11d4fc0..40ca6c736b 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1737,11 +1737,12 @@ def get_depending_transactions(self, tx_hash): def txin_value(self, txin): txid = txin['prevout_hash'] prev_n = txin['prevout_n'] - for address, d in self.txo[txid].items(): + for address, d in self.txo.get(txid, {}).items(): for n, v, cb in d: if n == prev_n: return v - raise BaseException('unknown txin value') + # may occur if wallet is not synchronized + return None def price_at_timestamp(self, txid, price_func): height, conf, timestamp = self.get_tx_height(txid) @@ -1770,6 +1771,8 @@ def coin_price(self, txid, price_func, ccy, txin_value): Acquisition price of a coin. This assumes that either all inputs are mine, or no input is mine. """ + if txin_value is None: + return Decimal('NaN') cache_key = "{}:{}:{}".format(str(txid), str(ccy), str(txin_value)) result = self.coin_price_cache.get(cache_key, None) if result is not None: From 152ec1447ced1a33a372d1684caa8be2ae1755c2 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 11:56:00 +0100 Subject: [PATCH 08/23] fix #4100: spent_outpoints does not track everything --- lib/wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wallet.py b/lib/wallet.py index 40ca6c736b..c5495de26f 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -890,7 +890,7 @@ def remove_transaction(self, tx_hash): # undo spent_outpoints that are in pruned_txo for ser, hh in list(self.pruned_txo.items()): if hh == tx_hash: - self.spent_outpoints.pop(ser) + self.spent_outpoints.pop(ser, None) self.pruned_txo.pop(ser) # add tx to pruned_txo, and undo the txi addition From c3e26a1e2b8b579c64f765d02e015df65e82934c Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 12:19:45 +0100 Subject: [PATCH 09/23] fix #4098 --- lib/wallet.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index c5495de26f..10bb77b0b9 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1628,13 +1628,14 @@ def remove_payment_request(self, addr, config): return True def get_sorted_requests(self, config): - def f(x): + def f(addr): try: - addr = x.get('address') - return self.get_address_index(addr) or addr + return self.get_address_index(addr) except: - return addr - return sorted(map(lambda x: self.get_payment_request(x, config), self.receive_requests.keys()), key=f) + return + keys = map(lambda x: (f(x), x), self.receive_requests.keys()) + sorted_keys = sorted(filter(lambda x: x[0] is not None, keys)) + return [self.get_payment_request(x[1], config) for x in sorted_keys] def get_fingerprint(self): raise NotImplementedError() From 79edd2dbf1ad98ed255d85bace7b4622a1f4560e Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Mon, 12 Mar 2018 16:58:05 +0100 Subject: [PATCH 10/23] Fix crowdin upload --- contrib/make_locale | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/contrib/make_locale b/contrib/make_locale index 2831cfeb3d..3ab4b21cf2 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -28,11 +28,11 @@ os.system(cmd) os.chdir('lib') crowdin_identifier = 'electrum' -crowdin_file_name = 'electrum-client/messages.pot' +crowdin_file_name = 'files[electrum-client/messages.pot]' locale_file_name = 'locale/messages.pot' crowdin_api_key = None -filename = '~/.crowdin_api_key' +filename = os.path.expanduser('~/.crowdin_api_key') if os.path.exists(filename): with open(filename) as f: crowdin_api_key = f.read().strip() @@ -44,13 +44,14 @@ if crowdin_api_key: # Push to Crowdin print('Push to Crowdin') url = ('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key) - with open(locale_file_name,'rb') as f: + with open(locale_file_name, 'rb') as f: files = {crowdin_file_name: f} - requests.request('POST', url, files=files) + response = requests.request('POST', url, files=files) + print("", "update-file:", "-"*20, response.text, "-"*20, sep="\n") # Build translations print('Build translations') - response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key).content - print(response) + response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key) + print("", "export:", "-" * 20, response.text, "-" * 20, sep="\n") # Download & unzip print('Download translations') From 3234917ea1356143b837183a5d6009724b1f18c4 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 18:27:36 +0100 Subject: [PATCH 11/23] release notes of version 3.1.1 --- RELEASE-NOTES | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 258efb5d2c..dff649b561 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,5 +1,19 @@ -# Release 3.1 - (March 5, 2018) +# Release 3.1.1 - (March 12, 2018) + + * fix #4031: Trezor T support + * partial fix #4060: proxy and hardware wallet can't be used together + * fix #4039: can't set address labels + * fix crash related to coinbase transactions + * MacOS: use internal graphics card + * fix openalias related crashes + * speed-up capital gains calculations + * hw wallet encryption: re-prompt for passphrase if incorrect + * other minor fixes. + + + +# Release 3.1.0 - (March 5, 2018) * Memory-pool based fee estimation. Dynamic fees can target a desired depth in the memory pool. This feature is optional, and ETA-based From 87aee100470be979bb324d186559441e5c914b6a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 12 Mar 2018 21:23:37 +0100 Subject: [PATCH 12/23] fix #4111 --- plugins/cosigner_pool/qt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/cosigner_pool/qt.py b/plugins/cosigner_pool/qt.py index 524d62b769..e00edd3ba6 100644 --- a/plugins/cosigner_pool/qt.py +++ b/plugins/cosigner_pool/qt.py @@ -173,7 +173,8 @@ def do_send(self, tx): for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue - message = bitcoin.encrypt_message(bfh(tx.raw), bh2u(K)).decode('ascii') + raw_tx_bytes = bfh(str(tx)) + message = bitcoin.encrypt_message(raw_tx_bytes, bh2u(K)).decode('ascii') try: server.put(_hash, message) except Exception as e: From a6841cbd5fb826eb12cf562eaeaf4929248a6dc8 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 13 Mar 2018 01:02:03 +0100 Subject: [PATCH 13/23] fix #4099: serialisation of txns with negative version number --- lib/bitcoin.py | 3 +++ lib/tests/test_transaction.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 731d10f514..cd277f4d70 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -144,6 +144,9 @@ def rev_hex(s): def int_to_hex(i, length=1): assert isinstance(i, int) + if i < 0: + # two's complement + i = pow(256, length) + i s = hex(i)[2:].rstrip('L') s = "0"*(2*length - len(s)) + s return rev_hex(s) diff --git a/lib/tests/test_transaction.py b/lib/tests/test_transaction.py index c92c6fff70..794099f373 100644 --- a/lib/tests/test_transaction.py +++ b/lib/tests/test_transaction.py @@ -265,6 +265,11 @@ def test_txid_regression_issue_3899(self): txid = 'f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d' self._run_naive_tests_on_tx(raw_tx, txid) + def test_txid_negative_version_num(self): + raw_tx = 'f0b47b9a01ecf5e5c3bbf2cf1f71ecdc7f708b0b222432e914b394e24aad1494a42990ddfc000000008b483045022100852744642305a99ad74354e9495bf43a1f96ded470c256cd32e129290f1fa191022030c11d294af6a61b3da6ed2c0c296251d21d113cfd71ec11126517034b0dcb70014104a0fe6e4a600f859a0932f701d3af8e0ecd4be886d91045f06a5a6b931b95873aea1df61da281ba29cadb560dad4fc047cf47b4f7f2570da4c0b810b3dfa7e500ffffffff0240420f00000000001976a9147eeacb8a9265cd68c92806611f704fc55a21e1f588ac05f00d00000000001976a914eb3bd8ccd3ba6f1570f844b59ba3e0a667024a6a88acff7f0000' + txid = 'c659729a7fea5071361c2c1a68551ca2bf77679b27086cc415adeeb03852e369' + self._run_naive_tests_on_tx(raw_tx, txid) + # these transactions are from Bitcoin Core unit tests ---> # https://github.com/bitcoin/bitcoin/blob/11376b5583a283772c82f6d32d0007cdbf5b8ef0/src/test/data/tx_valid.json From 1f1ce26211d5fc7e4ee1c52244c6dc79e68941a0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 13 Mar 2018 06:22:38 +0100 Subject: [PATCH 14/23] fix transaction dialog for p2pk input --- gui/qt/transaction_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index 8344f82b39..904828d23d 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -289,7 +289,7 @@ def format_amount(amt): cursor.insertText(prevout_hash[-8:] + ":%-4d " % prevout_n, ext) addr = x.get('address') if addr == "(pubkey)": - _addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n) + _addr = self.wallet.get_txin_address(x) if _addr: addr = _addr if addr is None: From b009c56b9db8abef998e0cdf5d2457256543cb4e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Mar 2018 10:42:56 +0100 Subject: [PATCH 15/23] kivy: save requests only with the save button --- gui/kivy/uix/screens.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py index a4cd314426..ff4bafc31a 100644 --- a/gui/kivy/uix/screens.py +++ b/gui/kivy/uix/screens.py @@ -380,7 +380,6 @@ def save_request(self): self.app.update_tab('requests') def on_amount_or_message(self): - self.save_request() Clock.schedule_once(lambda dt: self.update_qr()) def do_new(self): From e0122f8c63080a32e3484f4917ef485af09e0114 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Mar 2018 14:54:21 +0100 Subject: [PATCH 16/23] disable save button for partially signed tx --- gui/qt/transaction_dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index 904828d23d..d53997baf4 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -179,7 +179,8 @@ def show_qr(self): def sign(self): def sign_done(success): - if success: + # note: with segwit we could save partially signed tx, because they have a txid + if self.tx.is_complete(): self.prompt_if_unsaved = True self.saved = False self.save_button.setDisabled(False) From 4137ae94a0e16d2de885b98d0ebf3d23adcb81ac Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Mar 2018 15:07:19 +0100 Subject: [PATCH 17/23] flush certificate file; might fix #4059 --- lib/interface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/interface.py b/lib/interface.py index f7fe82420c..2e76d9870a 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -174,6 +174,8 @@ def get_socket(self): temporary_path = cert_path + '.temp' with open(temporary_path,"w") as f: f.write(cert) + f.flush() + os.fsync(f.fileno()) else: is_new = False From 38ec65716c98950a874074480cd9321b2b240c17 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Mar 2018 15:31:29 +0100 Subject: [PATCH 18/23] fix #4116 --- gui/qt/main_window.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index ccd051e4e0..466ef2c5d3 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -409,13 +409,12 @@ def backup_wallet(self): filename, __ = QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) if not filename: return - new_path = os.path.join(wallet_folder, filename) if new_path != path: try: shutil.copy2(path, new_path) self.show_message(_("A copy of your wallet file was created in")+" '%s'" % str(new_path), title=_("Wallet backup created")) - except (IOError, os.error) as reason: + except BaseException as reason: self.show_critical(_("Electrum was unable to copy your wallet file to the specified location.") + "\n" + str(reason), title=_("Unable to create backup")) def update_recently_visited(self, filename): From 71c5e4f6bcfcfbf8944b1a1d84693a1a65b694bd Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Tue, 13 Mar 2018 17:36:21 +0100 Subject: [PATCH 19/23] Add badge for crowdin to README --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9b18323547..8458581e52 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,9 @@ Electrum - Lightweight Bitcoin client .. image:: https://coveralls.io/repos/github/spesmilo/electrum/badge.svg?branch=master :target: https://coveralls.io/github/spesmilo/electrum?branch=master :alt: Test coverage statistics - +.. image:: https://img.shields.io/badge/help-translating-blue.svg + :target: https://crowdin.com/project/electrum + :alt: Help translating Electrum online From 7e6fba0513b6a8db0a0546e8184f7d7a66008585 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Tue, 13 Mar 2018 23:38:43 +0100 Subject: [PATCH 20/23] Make generated .app deterministic --- contrib/build-osx/make_osx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx index e5a6560494..c459503e92 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -77,6 +77,13 @@ fail "Could not install hardware wallet requirements" info "Building $PACKAGE..." python3 setup.py install --user > /dev/null || fail "Could not build $PACKAGE" +info "Faking timestamps..." +for d in ~/Library/Python/ ~/.pyenv .; do + pushd $d + find . -exec touch -t '200101220000' {} + + popd +done + info "Building binary" pyinstaller --noconfirm --ascii --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary" From 0f5cabc7f6500a3143f7ad8e71ab9cb84083ee09 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 14 Mar 2018 12:42:42 +0100 Subject: [PATCH 21/23] fix #4122 --- electrum | 7 +++++-- plugins/hw_wallet/cmdline.py | 3 +++ plugins/keepkey/clientbase.py | 3 +++ plugins/keepkey/qt_generic.py | 2 +- plugins/trezor/clientbase.py | 3 +++ plugins/trezor/qt_generic.py | 2 +- 6 files changed, 16 insertions(+), 4 deletions(-) diff --git a/electrum b/electrum index 78924631e2..783d5c13b4 100755 --- a/electrum +++ b/electrum @@ -93,7 +93,7 @@ from electrum import constants from electrum import SimpleConfig, Network from electrum.wallet import Wallet, Imported_Wallet from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption -from electrum.util import print_msg, print_stderr, json_encode, json_decode +from electrum.util import print_msg, print_stderr, json_encode, json_decode, UserCancelled from electrum.util import set_verbosity, InvalidPassword from electrum.commands import get_parser, known_commands, Commands, config_variables from electrum import daemon @@ -295,7 +295,10 @@ def get_password_for_hw_device_encrypted_storage(plugins): name, device_info = devices[0] plugin = plugins.get_plugin(name) derivation = get_derivation_used_for_hw_device_encryption() - xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler) + try: + xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler) + except UserCancelled: + sys.exit(0) password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()) return password diff --git a/plugins/hw_wallet/cmdline.py b/plugins/hw_wallet/cmdline.py index 999f82994c..6cd27a0010 100644 --- a/plugins/hw_wallet/cmdline.py +++ b/plugins/hw_wallet/cmdline.py @@ -32,6 +32,9 @@ def stop(self): def show_message(self, msg, on_cancel=None): print_msg(msg) + def show_error(self, msg): + print_msg(msg) + def update_status(self, b): print_error('trezor status', b) diff --git a/plugins/keepkey/clientbase.py b/plugins/keepkey/clientbase.py index 6b33c9d43b..b354798a24 100644 --- a/plugins/keepkey/clientbase.py +++ b/plugins/keepkey/clientbase.py @@ -50,6 +50,9 @@ def callback_PinMatrixRequest(self, msg): else: msg = _("Enter your current {} PIN:") pin = self.handler.get_pin(msg.format(self.device)) + if len(pin) > 9: + self.handler.show_error(_('The PIN cannot be longer than 9 characters.')) + pin = '' # to cancel below if not pin: return self.proto.Cancel() return self.proto.PinMatrixAck(pin=pin) diff --git a/plugins/keepkey/qt_generic.py b/plugins/keepkey/qt_generic.py index a66e8f3d23..7cdc50769f 100644 --- a/plugins/keepkey/qt_generic.py +++ b/plugins/keepkey/qt_generic.py @@ -250,7 +250,7 @@ def set_enabled(): vbox.addWidget(QLabel(msg)) vbox.addWidget(text) pin = QLineEdit() - pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}'))) + pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index 6e10d4c49d..e7f0d43c85 100644 --- a/plugins/trezor/clientbase.py +++ b/plugins/trezor/clientbase.py @@ -50,6 +50,9 @@ def callback_PinMatrixRequest(self, msg): else: msg = _("Enter your current {} PIN:") pin = self.handler.get_pin(msg.format(self.device)) + if len(pin) > 9: + self.handler.show_error(_('The PIN cannot be longer than 9 characters.')) + pin = '' # to cancel below if not pin: return self.proto.Cancel() return self.proto.PinMatrixAck(pin=pin) diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py index 808f83a6a4..032a02f168 100644 --- a/plugins/trezor/qt_generic.py +++ b/plugins/trezor/qt_generic.py @@ -251,7 +251,7 @@ def set_enabled(): vbox.addWidget(QLabel(msg)) vbox.addWidget(text) pin = QLineEdit() - pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}'))) + pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) From 5e5134b76ffec8ed9702f5661a1d16e9e52a24c8 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 14 Mar 2018 14:59:27 +0100 Subject: [PATCH 22/23] remove custom entropy option again (follow-up e0c38b3), because seeds can be extended with passphrase --- lib/commands.py | 12 ++---------- lib/mnemonic.py | 23 ++++++++--------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/lib/commands.py b/lib/commands.py index 23c35b8964..9106b1ca40 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -159,19 +159,13 @@ def setconfig(self, key, value): return True @command('') - def make_seed(self, nbits=132, entropy=1, language=None, segwit=False): + def make_seed(self, nbits=132, language=None, segwit=False): """Create a seed""" from .mnemonic import Mnemonic t = 'segwit' if segwit else 'standard' - s = Mnemonic(language).make_seed(t, nbits, custom_entropy=entropy) + s = Mnemonic(language).make_seed(t, nbits) return s - @command('') - def check_seed(self, seed, entropy=1, language=None): - """Check that a seed was generated with given entropy""" - from .mnemonic import Mnemonic - return Mnemonic(language).check_seed(seed, entropy) - @command('n') def getaddresshistory(self, address): """Return the transaction history of any address. Note: This is a @@ -697,7 +691,6 @@ def help(self): 'from_addr': ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."), 'change_addr': ("-c", "Change address. Default is a spare address, or the source address if it's not in the wallet"), 'nbits': (None, "Number of bits of entropy"), - 'entropy': (None, "Custom entropy"), 'segwit': (None, "Create segwit seed"), 'language': ("-L", "Default language for wordlist"), 'privkey': (None, "Private key. Set to '?' to get a prompt."), @@ -726,7 +719,6 @@ def help(self): 'nbits': int, 'imax': int, 'year': int, - 'entropy': int, 'tx': tx_from_str, 'pubkeys': json_loads, 'jsontx': json_loads, diff --git a/lib/mnemonic.py b/lib/mnemonic.py index 7096e20f64..846dcc74fe 100644 --- a/lib/mnemonic.py +++ b/lib/mnemonic.py @@ -157,28 +157,21 @@ def mnemonic_decode(self, seed): i = i*n + k return i - def check_seed(self, seed, custom_entropy): - assert is_new_seed(seed) - i = self.mnemonic_decode(seed) - return i % custom_entropy == 0 - - def make_seed(self, seed_type='standard', num_bits=132, custom_entropy=1): + def make_seed(self, seed_type='standard', num_bits=132): 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) - num_bits = int(math.ceil(num_bits/bpw) * bpw) - # handle custom entropy; make sure we add at least 16 bits - n_custom = int(math.ceil(math.log(custom_entropy, 2))) - n = max(16, num_bits - n_custom) - print_error("make_seed", prefix, "adding %d bits"%n) - my_entropy = 1 - while my_entropy < pow(2, n - bpw): + # rounding + n = int(math.ceil(num_bits/bpw) * bpw) + print_error("make_seed. prefix: '%s'"%prefix, "entropy: %d bits"%n) + entropy = 1 + while entropy < pow(2, n - bpw): # try again if seed would not contain enough words - my_entropy = ecdsa.util.randrange(pow(2, n)) + entropy = ecdsa.util.randrange(pow(2, n)) nonce = 0 while True: nonce += 1 - i = custom_entropy * (my_entropy + nonce) + i = entropy + nonce seed = self.mnemonic_encode(i) assert i == self.mnemonic_decode(seed) if is_old_seed(seed): From 8589c1b0bb79c3c6a8506d5ef875bd1e2bb055e6 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 14 Mar 2018 15:18:26 +0100 Subject: [PATCH 23/23] ledger: don't throw exception if user cancels signing Used to show "Exception : Invalid status 6985", which is not really user-friendly. This now mimics the behaviour with Trezor where we silently ignore cancellation (not showing any popup). --- plugins/ledger/ledger.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index 3d34bc9da4..9ab52709de 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -229,6 +229,16 @@ def give_error(self, message, clear_client = False): self.client = None raise Exception(message) + def set_and_unset_signing(func): + """Function decorator to set and unset self.signing.""" + def wrapper(self, *args, **kwargs): + try: + self.signing = True + return func(self, *args, **kwargs) + finally: + self.signing = False + return wrapper + def address_id_stripped(self, address): # Strip the leading "m/" change, index = self.get_address_index(address) @@ -239,8 +249,8 @@ def address_id_stripped(self, address): def decrypt_message(self, pubkey, message, password): raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device)) + @set_and_unset_signing def sign_message(self, sequence, message, password): - self.signing = True message = message.encode('utf8') message_hash = hashlib.sha256(message).hexdigest().upper() # prompt for the PIN before displaying the dialog if necessary @@ -259,16 +269,17 @@ def sign_message(self, sequence, message, password): except BTChipException as e: if e.sw == 0x6a80: self.give_error("Unfortunately, this message cannot be signed by the Ledger wallet. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry.") + elif e.sw == 0x6985: # cancelled by user + return b'' else: self.give_error(e, True) except UserWarning: self.handler.show_error(_('Cancelled by user')) - return '' + return b'' except Exception as e: self.give_error(e, True) finally: self.handler.finished() - self.signing = False # Parse the ASN.1 signature rLength = signature[3] r = signature[4 : 4 + rLength] @@ -281,12 +292,11 @@ def sign_message(self, sequence, message, password): # And convert it return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s - + @set_and_unset_signing def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() - self.signing = True inputs = [] inputsPaths = [] pubKeys = [] @@ -446,6 +456,12 @@ def sign_transaction(self, tx, password): except UserWarning: self.handler.show_error(_('Cancelled by user')) return + except BTChipException as e: + if e.sw == 0x6985: # cancelled by user + return + else: + traceback.print_exc(file=sys.stderr) + self.give_error(e, True) except BaseException as e: traceback.print_exc(file=sys.stdout) self.give_error(e, True) @@ -456,10 +472,9 @@ def sign_transaction(self, tx, password): signingPos = inputs[i][4] txin['signatures'][signingPos] = bh2u(signatures[i]) tx.raw = tx.serialize() - self.signing = False + @set_and_unset_signing def show_address(self, sequence, txin_type): - self.signing = True client = self.get_client() address_path = self.get_derivation()[2:] + "/%d/%d"%sequence self.handler.show_message(_("Showing address ...")) @@ -478,7 +493,6 @@ def show_address(self, sequence, txin_type): self.handler.show_error(e) finally: self.handler.finished() - self.signing = False class LedgerPlugin(HW_PluginBase): libraries_available = BTCHIP