electrum

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

commit 0186f09c271a8b5485b53ec0e16fd98b9591b929
parent 27b36486df456f46712a2a4dd275b703f53570f7
Author: ThomasV <thomasv@electrum.org>
Date:   Wed, 18 Jul 2018 18:54:59 +0200

Merge pull request #4567 from SomberNight/issue_4566

bip39 passphrases with multiple spaces
Diffstat:
Melectrum/base_wizard.py | 11+++++++----
Melectrum/gui/qt/installwizard.py | 17+++++++++++++++--
Melectrum/tests/test_wallet_vertical.py | 45++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 66 insertions(+), 7 deletions(-)

diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py @@ -348,7 +348,7 @@ class BaseWizard(object): k = hardware_keystore(d) self.on_keystore(k) - def passphrase_dialog(self, run_next): + def passphrase_dialog(self, run_next, is_restoring=False): title = _('Seed extension') message = '\n'.join([ _('You may extend your seed with custom words.'), @@ -358,7 +358,10 @@ class BaseWizard(object): _('Note that this is NOT your encryption password.'), _('If you do not know what this is, leave this field empty.'), ]) - self.line_dialog(title=title, message=message, warning=warning, default='', test=lambda x:True, run_next=run_next) + warn_issue4566 = is_restoring and self.seed_type == 'bip39' + self.line_dialog(title=title, message=message, warning=warning, + default='', test=lambda x:True, run_next=run_next, + warn_issue4566=warn_issue4566) def restore_from_seed(self): self.opt_bip39 = True @@ -371,10 +374,10 @@ class BaseWizard(object): self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed) if self.seed_type == 'bip39': f = lambda passphrase: self.on_restore_bip39(seed, passphrase) - self.passphrase_dialog(run_next=f) if is_ext else f('') + self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('') elif self.seed_type in ['standard', 'segwit']: f = lambda passphrase: self.run('create_keystore', seed, passphrase) - self.passphrase_dialog(run_next=f) if is_ext else f('') + self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('') elif self.seed_type == 'old': self.run('create_keystore', seed, '') elif self.seed_type == '2fa': diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py @@ -32,6 +32,12 @@ WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\ 'p2wpkh-p2sh:KxZcY47uGp9a... \t-> 3NhNeZQXF...\n' + 'p2wpkh:KxZcY47uGp9a... \t-> bc1q3fjfk...') # note: full key is KxZcY47uGp9aVQAb6VVvuBs8SwHKgkSR2DbZUzjDzXf2N2GPhG9n +MSG_PASSPHRASE_WARN_ISSUE4566 = _("Warning") + ": "\ + + _("You have multiple consecutive whitespaces or leading/trailing " + "whitespaces in your passphrase.") + " " \ + + _("This is discouraged.") + " " \ + + _("Due to a bug, old versions of Electrum will NOT be creating the " + "same wallet as newer versions or other software.") class CosignWidget(QWidget): @@ -550,17 +556,24 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): @wizard_dialog def line_dialog(self, run_next, title, message, default, test, warning='', - presets=()): + presets=(), warn_issue4566=False): vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) + if warn_issue4566: + text_whitespace_normalised = ' '.join(text.split()) + warn_issue4566_label.setVisible(text != text_whitespace_normalised) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) + warn_issue4566_label = WWLabel(MSG_PASSPHRASE_WARN_ISSUE4566) + warn_issue4566_label.setVisible(False) + vbox.addWidget(warn_issue4566_label) + for preset in presets: button = QPushButton(preset[0]) button.clicked.connect(lambda __, text=preset[1]: line.setText(text)) @@ -570,7 +583,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): vbox.addLayout(hbox) self.exec_layout(vbox, title, next_enabled=test(default)) - return ' '.join(line.text().split()) + return line.text() @wizard_dialog def show_xpub_dialog(self, xpub, run_next): diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py @@ -18,6 +18,11 @@ from . import SequentialTestCase from .test_bitcoin import needs_test_with_all_ecc_implementations +_UNICODE_HORROR_HEX = 'e282bf20f09f988020f09f98882020202020e3818620e38191e3819fe381be20e3828fe3828b2077cda2cda2cd9d68cda16fcda2cda120ccb8cda26bccb5cd9f6eccb4cd98c7ab77ccb8cc9b73cd9820cc80cc8177cd98cda2e1b8a9ccb561d289cca1cda27420cca7cc9568cc816fccb572cd8fccb5726f7273cca120ccb6cda1cda06cc4afccb665cd9fcd9f20ccb6cd9d696ecda220cd8f74cc9568ccb7cca1cd9f6520cd9fcd9f64cc9b61cd9c72cc95cda16bcca2cca820cda168ccb465cd8f61ccb7cca2cca17274cc81cd8f20ccb4ccb7cda0c3b2ccb5ccb666ccb82075cca7cd986ec3adcc9bcd9c63cda2cd8f6fccb7cd8f64ccb8cda265cca1cd9d3fcd9e' +UNICODE_HORROR = bfh(_UNICODE_HORROR_HEX).decode('utf-8') +# '₿ 😀 😈 う けたま わる w͢͢͝h͡o͢͡ ̸͢k̵͟n̴͘ǫw̸̛s͘ ̀́w͘͢ḩ̵a҉̡͢t ̧̕h́o̵r͏̵rors̡ ̶͡͠lį̶e͟͟ ̶͝in͢ ͏t̕h̷̡͟e ͟͟d̛a͜r̕͡k̢̨ ͡h̴e͏a̷̢̡rt́͏ ̴̷͠ò̵̶f̸ u̧͘ní̛͜c͢͏o̷͏d̸͢e̡͝?͞' + + class WalletIntegrityHelper: gap_limit = 1 # make tests run faster @@ -68,7 +73,6 @@ class WalletIntegrityHelper: return w -# TODO passphrase/seed_extension class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @needs_test_with_all_ecc_implementations @@ -113,6 +117,26 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @needs_test_with_all_ecc_implementations @mock.patch.object(storage.WalletStorage, '_write') + def test_electrum_seed_segwit_passphrase(self, mock_write): + seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver' + self.assertEqual(bitcoin.seed_type(seed_words), 'segwit') + + ks = keystore.from_seed(seed_words, UNICODE_HORROR, False) + + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks) + self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore)) + + self.assertEqual(ks.xprv, 'zprvAZDmEQiCLUcZXPfrBXoksCD2R6RMAzAre7SUyBotibisy9c7vGhLYvHaP3d9rYU12DKAWdZfscPNA7qEPgTkCDqX5sE93ryAJAQvkDbfLxU') + self.assertEqual(ks.xpub, 'zpub6nD7dvF6ArArjskKHZLmEL9ky8FqaSti1LN5maDWGwFrqwwGTp1b6ic4EHwciFNaYDmCXcQYxXSiF9BjcLCMPcaYkVN2nQD6QjYQ8vpSR3Z') + + w = WalletIntegrityHelper.create_standard_wallet(ks) + self.assertEqual(w.txin_type, 'p2wpkh') + + self.assertEqual(w.get_receiving_addresses()[0], 'bc1qx94dutas7ysn2my645cyttujrms5d9p57f6aam') + self.assertEqual(w.get_change_addresses()[0], 'bc1qcywwsy87sdp8vz5rfjh3sxdv6rt95kujdqq38g') + + @needs_test_with_all_ecc_implementations + @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_seed_old(self, mock_write): seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over' self.assertEqual(bitcoin.seed_type(seed_words), 'old') @@ -185,6 +209,25 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @needs_test_with_all_ecc_implementations @mock.patch.object(storage.WalletStorage, '_write') + def test_bip39_seed_bip44_standard_passphrase(self, mock_write): + seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' + self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True)) + + ks = keystore.from_bip39_seed(seed_words, UNICODE_HORROR, "m/44'/0'/0'") + + self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore)) + + self.assertEqual(ks.xprv, 'xprv9z8izheguGnLopSqkY7GcGFrP2Gu6rzBvvHo6uB9B8DWJhsows6WDZAsbBTaP3ncP2AVbTQphyEQkahrB9s1L7ihZtfz5WGQPMbXwsUtSik') + self.assertEqual(ks.xpub, 'xpub6D85QDBajeLe2JXJrZeGyQCaw47PWKi3J9DPuHakjTkVBWCxVQQkmMVMSSfnw39tj9FntbozpRtb1AJ8ubjeVSBhyK4M5mzdvsXZzKPwodT') + + w = WalletIntegrityHelper.create_standard_wallet(ks) + self.assertEqual(w.txin_type, 'p2pkh') + + self.assertEqual(w.get_receiving_addresses()[0], '1F88g2naBMhDB7pYFttPWGQgryba3hPevM') + self.assertEqual(w.get_change_addresses()[0], '1H4QD1rg2zQJ4UjuAVJr5eW1fEM8WMqyxh') + + @needs_test_with_all_ecc_implementations + @mock.patch.object(storage.WalletStorage, '_write') def test_bip39_seed_bip49_p2sh_segwit(self, mock_write): seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))