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:
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)