electrum

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

commit b39c51adf7ef9d56bd45b1c30a86d4d415ef7940
parent e7f38467d7a592ab5288e29200cbace65e87834e
Author: SomberNight <somber.night@protonmail.com>
Date:   Fri, 22 Feb 2019 18:01:54 +0100

mv "electrum seed" stuff from bitcoin.py to mnemonic.py

Diffstat:
Melectrum/base_wizard.py | 9+++++----
Melectrum/bitcoin.py | 50--------------------------------------------------
Melectrum/gui/qt/seed_dialog.py | 3+--
Melectrum/keystore.py | 4++--
Melectrum/mnemonic.py | 50++++++++++++++++++++++++++++++++++++++++++++++++--
Melectrum/old_mnemonic.py | 6+++---
Melectrum/plugins/trustedcoin/trustedcoin.py | 4++--
Melectrum/tests/test_bitcoin.py | 50++------------------------------------------------
Melectrum/tests/test_mnemonic.py | 49++++++++++++++++++++++++++++++++++++++++++++++++-
Melectrum/tests/test_wallet_vertical.py | 17+++++++++--------
10 files changed, 120 insertions(+), 122 deletions(-)

diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py @@ -31,6 +31,7 @@ from typing import List, TYPE_CHECKING, Tuple, NamedTuple, Any from . import bitcoin from . import keystore +from . import mnemonic from .bip32 import is_bip32_derivation, xpub_type from .keystore import bip44_derivation, purpose48_derivation from .wallet import (Imported_Wallet, Standard_Wallet, Multisig_Wallet, @@ -429,12 +430,12 @@ class BaseWizard(object): def restore_from_seed(self): self.opt_bip39 = True self.opt_ext = True - is_cosigning_seed = lambda x: bitcoin.seed_type(x) in ['standard', 'segwit'] - test = bitcoin.is_seed if self.wallet_type == 'standard' else is_cosigning_seed + is_cosigning_seed = lambda x: mnemonic.seed_type(x) in ['standard', 'segwit'] + test = mnemonic.is_seed if self.wallet_type == 'standard' else is_cosigning_seed self.restore_seed_dialog(run_next=self.on_restore_seed, test=test) def on_restore_seed(self, seed, is_bip39, is_ext): - self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed) + self.seed_type = 'bip39' if is_bip39 else mnemonic.seed_type(seed) if self.seed_type == 'bip39': f = lambda passphrase: self.on_restore_bip39(seed, passphrase) self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('') @@ -443,7 +444,7 @@ class BaseWizard(object): 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 bitcoin.is_any_2fa_seed_type(self.seed_type): + elif mnemonic.is_any_2fa_seed_type(self.seed_type): self.load_2fa() self.run('on_restore_seed', seed, is_ext) else: diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py @@ -310,56 +310,6 @@ def hash_decode(x: str) -> bytes: return bfh(x)[::-1] -################################## electrum seeds - - -def is_new_seed(x: str, prefix=version.SEED_PREFIX) -> bool: - from . import mnemonic - x = mnemonic.normalize_text(x) - s = bh2u(hmac_oneshot(b"Seed version", x.encode('utf8'), hashlib.sha512)) - return s.startswith(prefix) - - -def is_old_seed(seed: str) -> bool: - from . import old_mnemonic, mnemonic - seed = mnemonic.normalize_text(seed) - words = seed.split() - try: - # checks here are deliberately left weak for legacy reasons, see #3149 - old_mnemonic.mn_decode(words) - uses_electrum_words = True - except Exception: - uses_electrum_words = False - try: - seed = bfh(seed) - is_hex = (len(seed) == 16 or len(seed) == 32) - except Exception: - is_hex = False - return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24)) - - -def seed_type(x: str) -> str: - if is_old_seed(x): - return 'old' - elif is_new_seed(x): - return 'standard' - elif is_new_seed(x, version.SEED_PREFIX_SW): - return 'segwit' - elif is_new_seed(x, version.SEED_PREFIX_2FA): - return '2fa' - elif is_new_seed(x, version.SEED_PREFIX_2FA_SW): - return '2fa_segwit' - return '' - - -def is_seed(x: str) -> bool: - return bool(seed_type(x)) - - -def is_any_2fa_seed_type(seed_type): - return seed_type in ['2fa', '2fa_segwit'] - - ############ functions from pywallet ##################### def hash160_to_b58_address(h160: bytes, addrtype: int) -> str: diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py @@ -29,7 +29,7 @@ from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit, QLabel, QCompleter, QDialog) from electrum.i18n import _ -from electrum.mnemonic import Mnemonic +from electrum.mnemonic import Mnemonic, seed_type import electrum.old_mnemonic from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path, @@ -161,7 +161,6 @@ class SeedLayout(QVBoxLayout): return ' '.join(text.split()) def on_edit(self): - from electrum.bitcoin import seed_type s = self.get_seed() b = self.is_seed(s) if not self.is_bip39: diff --git a/electrum/keystore.py b/electrum/keystore.py @@ -30,7 +30,7 @@ from typing import Tuple from . import bitcoin, ecc, constants, bip32 from .bitcoin import (deserialize_privkey, serialize_privkey, - public_key_to_p2pkh, seed_type, is_seed) + public_key_to_p2pkh) from .bip32 import (bip32_public_derivation, deserialize_xpub, CKD_pub, bip32_root, deserialize_xprv, bip32_private_derivation, bip32_private_key, bip32_derivation, BIP32_PRIME, @@ -40,7 +40,7 @@ from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATE SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion) from .util import (PrintError, InvalidPassword, WalletFileException, BitcoinException, bh2u, bfh, print_error, inv_dict) -from .mnemonic import Mnemonic, load_wordlist +from .mnemonic import Mnemonic, load_wordlist, seed_type, is_seed from .plugin import run_hook diff --git a/electrum/mnemonic.py b/electrum/mnemonic.py @@ -30,8 +30,8 @@ import string import ecdsa -from .util import print_error, resource_path -from .bitcoin import is_old_seed, is_new_seed +from .util import print_error, resource_path, bfh, bh2u +from .crypto import hmac_oneshot from . import version # http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html @@ -181,3 +181,49 @@ class Mnemonic(object): break print_error('%d words'%len(seed.split())) return seed + + +def is_new_seed(x: str, prefix=version.SEED_PREFIX) -> bool: + x = normalize_text(x) + s = bh2u(hmac_oneshot(b"Seed version", x.encode('utf8'), hashlib.sha512)) + return s.startswith(prefix) + + +def is_old_seed(seed: str) -> bool: + from . import old_mnemonic + seed = normalize_text(seed) + words = seed.split() + try: + # checks here are deliberately left weak for legacy reasons, see #3149 + old_mnemonic.mn_decode(words) + uses_electrum_words = True + except Exception: + uses_electrum_words = False + try: + seed = bfh(seed) + is_hex = (len(seed) == 16 or len(seed) == 32) + except Exception: + is_hex = False + return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24)) + + +def seed_type(x: str) -> str: + if is_old_seed(x): + return 'old' + elif is_new_seed(x): + return 'standard' + elif is_new_seed(x, version.SEED_PREFIX_SW): + return 'segwit' + elif is_new_seed(x, version.SEED_PREFIX_2FA): + return '2fa' + elif is_new_seed(x, version.SEED_PREFIX_2FA_SW): + return '2fa_segwit' + return '' + + +def is_seed(x: str) -> bool: + return bool(seed_type(x)) + + +def is_any_2fa_seed_type(seed_type: str) -> bool: + return seed_type in ['2fa', '2fa_segwit'] diff --git a/electrum/old_mnemonic.py b/electrum/old_mnemonic.py @@ -1652,13 +1652,13 @@ words = [ "total", "unseen", "weapon", -"weary" +"weary", ] +n = len(words) +assert n == 1626 -n = 1626 - # Note about US patent no 5892470: Here each word does not represent a given digit. # Instead, the digit represented by a word is variable, it depends on the previous word. diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py @@ -36,12 +36,12 @@ from urllib.parse import quote from aiohttp import ClientResponse from electrum import ecc, constants, keystore, version, bip32, bitcoin -from electrum.bitcoin import TYPE_ADDRESS, is_new_seed, seed_type, is_any_2fa_seed_type +from electrum.bitcoin import TYPE_ADDRESS from electrum.bip32 import (deserialize_xpub, deserialize_xprv, bip32_private_key, CKD_pub, serialize_xpub, bip32_root, bip32_private_derivation, xpub_type) from electrum.crypto import sha256 from electrum.transaction import TxOutput -from electrum.mnemonic import Mnemonic +from electrum.mnemonic import Mnemonic, seed_type, is_any_2fa_seed_type from electrum.wallet import Multisig_Wallet, Deterministic_Wallet from electrum.i18n import _ from electrum.plugin import BasePlugin, hook diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py @@ -2,11 +2,11 @@ import base64 import sys from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key, - is_address, is_private_key, is_new_seed, is_old_seed, + is_address, is_private_key, var_int, _op_push, address_to_script, deserialize_privkey, serialize_privkey, is_segwit_address, is_b58_address, address_to_scripthash, is_minikey, - is_compressed_privkey, seed_type, EncodeBase58Check, + is_compressed_privkey, EncodeBase58Check, script_num_to_hex, push_script, add_number_to_script, int_to_hex, opcodes) from electrum.bip32 import (bip32_root, bip32_public_derivation, bip32_private_derivation, @@ -719,49 +719,3 @@ class Test_keyImport(SequentialTestCase): for priv_details in self.priv_pub_addr: self.assertEqual(priv_details['compressed'], is_compressed_privkey(priv_details['priv'])) - - -class Test_seeds(SequentialTestCase): - """ Test old and new seeds. """ - - mnemonics = { - ('cell dumb heartbeat north boom tease ship baby bright kingdom rare squeeze', 'old'), - ('cell dumb heartbeat north boom tease ' * 4, 'old'), - ('cell dumb heartbeat north boom tease ship baby bright kingdom rare badword', ''), - ('cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE', 'old'), - (' cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE ', 'old'), - # below seed is actually 'invalid old' as it maps to 33 hex chars - ('hurry idiot prefer sunset mention mist jaw inhale impossible kingdom rare squeeze', 'old'), - ('cram swing cover prefer miss modify ritual silly deliver chunk behind inform able', 'standard'), - ('cram swing cover prefer miss modify ritual silly deliver chunk behind inform', ''), - ('ostrich security deer aunt climb inner alpha arm mutual marble solid task', 'standard'), - ('OSTRICH SECURITY DEER AUNT CLIMB INNER ALPHA ARM MUTUAL MARBLE SOLID TASK', 'standard'), - (' oStRiCh sEcUrItY DeEr aUnT ClImB InNeR AlPhA ArM MuTuAl mArBlE SoLiD TaSk ', 'standard'), - ('x8', 'standard'), - ('science dawn member doll dutch real can brick knife deny drive list', '2fa'), - ('science dawn member doll dutch real ca brick knife deny drive list', ''), - (' sCience dawn member doll Dutch rEAl can brick knife deny drive lisT', '2fa'), - ('frost pig brisk excite novel report camera enlist axis nation novel desert', 'segwit'), - (' fRoSt pig brisk excIte novel rePort CamEra enlist axis nation nOVeL dEsert ', 'segwit'), - ('9dk', 'segwit'), - } - - def test_new_seed(self): - seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform able" - self.assertTrue(is_new_seed(seed)) - - seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform" - self.assertFalse(is_new_seed(seed)) - - def test_old_seed(self): - self.assertTrue(is_old_seed(" ".join(["like"] * 12))) - self.assertFalse(is_old_seed(" ".join(["like"] * 18))) - self.assertTrue(is_old_seed(" ".join(["like"] * 24))) - self.assertFalse(is_old_seed("not a seed")) - - self.assertTrue(is_old_seed("0123456789ABCDEF" * 2)) - self.assertTrue(is_old_seed("0123456789ABCDEF" * 4)) - - def test_seed_type(self): - for seed_words, _type in self.mnemonics: - self.assertEqual(_type, seed_type(seed_words), msg=seed_words) diff --git a/electrum/tests/test_mnemonic.py b/electrum/tests/test_mnemonic.py @@ -4,7 +4,7 @@ from electrum import keystore from electrum import mnemonic from electrum import old_mnemonic from electrum.util import bh2u, bfh -from electrum.bitcoin import is_new_seed +from electrum.mnemonic import is_new_seed, is_old_seed, seed_type from electrum.version import SEED_PREFIX_SW, SEED_PREFIX from . import SequentialTestCase @@ -134,6 +134,7 @@ class Test_OldMnemonic(SequentialTestCase): self.assertEqual(result, words.split()) self.assertEqual(old_mnemonic.mn_decode(result), seed) + class Test_BIP39Checksum(SequentialTestCase): def test(self): @@ -141,3 +142,49 @@ class Test_BIP39Checksum(SequentialTestCase): is_checksum_valid, is_wordlist_valid = keystore.bip39_is_checksum_valid(mnemonic) self.assertTrue(is_wordlist_valid) self.assertTrue(is_checksum_valid) + + +class Test_seeds(SequentialTestCase): + """ Test old and new seeds. """ + + mnemonics = { + ('cell dumb heartbeat north boom tease ship baby bright kingdom rare squeeze', 'old'), + ('cell dumb heartbeat north boom tease ' * 4, 'old'), + ('cell dumb heartbeat north boom tease ship baby bright kingdom rare badword', ''), + ('cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE', 'old'), + (' cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE ', 'old'), + # below seed is actually 'invalid old' as it maps to 33 hex chars + ('hurry idiot prefer sunset mention mist jaw inhale impossible kingdom rare squeeze', 'old'), + ('cram swing cover prefer miss modify ritual silly deliver chunk behind inform able', 'standard'), + ('cram swing cover prefer miss modify ritual silly deliver chunk behind inform', ''), + ('ostrich security deer aunt climb inner alpha arm mutual marble solid task', 'standard'), + ('OSTRICH SECURITY DEER AUNT CLIMB INNER ALPHA ARM MUTUAL MARBLE SOLID TASK', 'standard'), + (' oStRiCh sEcUrItY DeEr aUnT ClImB InNeR AlPhA ArM MuTuAl mArBlE SoLiD TaSk ', 'standard'), + ('x8', 'standard'), + ('science dawn member doll dutch real can brick knife deny drive list', '2fa'), + ('science dawn member doll dutch real ca brick knife deny drive list', ''), + (' sCience dawn member doll Dutch rEAl can brick knife deny drive lisT', '2fa'), + ('frost pig brisk excite novel report camera enlist axis nation novel desert', 'segwit'), + (' fRoSt pig brisk excIte novel rePort CamEra enlist axis nation nOVeL dEsert ', 'segwit'), + ('9dk', 'segwit'), + } + + def test_new_seed(self): + seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform able" + self.assertTrue(is_new_seed(seed)) + + seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform" + self.assertFalse(is_new_seed(seed)) + + def test_old_seed(self): + self.assertTrue(is_old_seed(" ".join(["like"] * 12))) + self.assertFalse(is_old_seed(" ".join(["like"] * 18))) + self.assertTrue(is_old_seed(" ".join(["like"] * 24))) + self.assertFalse(is_old_seed("not a seed")) + + self.assertTrue(is_old_seed("0123456789ABCDEF" * 2)) + self.assertTrue(is_old_seed("0123456789ABCDEF" * 4)) + + def test_seed_type(self): + for seed_words, _type in self.mnemonics: + self.assertEqual(_type, seed_type(seed_words), msg=seed_words) diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py @@ -11,6 +11,7 @@ from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCON from electrum.wallet import sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet from electrum.util import bfh, bh2u from electrum.transaction import TxOutput +from electrum.mnemonic import seed_type from electrum.plugins.trustedcoin import trustedcoin @@ -80,7 +81,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_seed_standard(self, mock_write): seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song' - self.assertEqual(bitcoin.seed_type(seed_words), 'standard') + self.assertEqual(seed_type(seed_words), 'standard') ks = keystore.from_seed(seed_words, '', False) @@ -100,7 +101,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_seed_segwit(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') + self.assertEqual(seed_type(seed_words), 'segwit') ks = keystore.from_seed(seed_words, '', False) @@ -120,7 +121,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @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') + self.assertEqual(seed_type(seed_words), 'segwit') ks = keystore.from_seed(seed_words, UNICODE_HORROR, False) @@ -140,7 +141,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @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') + self.assertEqual(seed_type(seed_words), 'old') ks = keystore.from_seed(seed_words, '', False) @@ -159,7 +160,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_seed_2fa_legacy(self, mock_write): seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove' - self.assertEqual(bitcoin.seed_type(seed_words), '2fa') + self.assertEqual(seed_type(seed_words), '2fa') xprv1, xpub1, xprv2, xpub2 = trustedcoin.TrustedCoinPlugin.xkeys_from_seed(seed_words, '') @@ -194,7 +195,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_seed_2fa_segwit(self, mock_write): seed_words = 'universe topic remind silver february ranch shine worth innocent cattle enhance wise' - self.assertEqual(bitcoin.seed_type(seed_words), '2fa_segwit') + self.assertEqual(seed_type(seed_words), '2fa_segwit') xprv1, xpub1, xprv2, xpub2 = trustedcoin.TrustedCoinPlugin.xkeys_from_seed(seed_words, '') @@ -306,7 +307,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_multisig_seed_standard(self, mock_write): seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure' - self.assertEqual(bitcoin.seed_type(seed_words), 'standard') + self.assertEqual(seed_type(seed_words), 'standard') ks1 = keystore.from_seed(seed_words, '', True) WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1) @@ -329,7 +330,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_multisig_seed_segwit(self, mock_write): seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun' - self.assertEqual(bitcoin.seed_type(seed_words), 'segwit') + self.assertEqual(seed_type(seed_words), 'segwit') ks1 = keystore.from_seed(seed_words, '', True) WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1)