Skip to content

Commit b39c51a

Browse files
committed
mv "electrum seed" stuff from bitcoin.py to mnemonic.py
1 parent e7f3846 commit b39c51a

File tree

10 files changed

+120
-122
lines changed

10 files changed

+120
-122
lines changed

electrum/base_wizard.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
from . import bitcoin
3333
from . import keystore
34+
from . import mnemonic
3435
from .bip32 import is_bip32_derivation, xpub_type
3536
from .keystore import bip44_derivation, purpose48_derivation
3637
from .wallet import (Imported_Wallet, Standard_Wallet, Multisig_Wallet,
@@ -429,12 +430,12 @@ def passphrase_dialog(self, run_next, is_restoring=False):
429430
def restore_from_seed(self):
430431
self.opt_bip39 = True
431432
self.opt_ext = True
432-
is_cosigning_seed = lambda x: bitcoin.seed_type(x) in ['standard', 'segwit']
433-
test = bitcoin.is_seed if self.wallet_type == 'standard' else is_cosigning_seed
433+
is_cosigning_seed = lambda x: mnemonic.seed_type(x) in ['standard', 'segwit']
434+
test = mnemonic.is_seed if self.wallet_type == 'standard' else is_cosigning_seed
434435
self.restore_seed_dialog(run_next=self.on_restore_seed, test=test)
435436

436437
def on_restore_seed(self, seed, is_bip39, is_ext):
437-
self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed)
438+
self.seed_type = 'bip39' if is_bip39 else mnemonic.seed_type(seed)
438439
if self.seed_type == 'bip39':
439440
f = lambda passphrase: self.on_restore_bip39(seed, passphrase)
440441
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
@@ -443,7 +444,7 @@ def on_restore_seed(self, seed, is_bip39, is_ext):
443444
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
444445
elif self.seed_type == 'old':
445446
self.run('create_keystore', seed, '')
446-
elif bitcoin.is_any_2fa_seed_type(self.seed_type):
447+
elif mnemonic.is_any_2fa_seed_type(self.seed_type):
447448
self.load_2fa()
448449
self.run('on_restore_seed', seed, is_ext)
449450
else:

electrum/bitcoin.py

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -310,56 +310,6 @@ def hash_decode(x: str) -> bytes:
310310
return bfh(x)[::-1]
311311

312312

313-
################################## electrum seeds
314-
315-
316-
def is_new_seed(x: str, prefix=version.SEED_PREFIX) -> bool:
317-
from . import mnemonic
318-
x = mnemonic.normalize_text(x)
319-
s = bh2u(hmac_oneshot(b"Seed version", x.encode('utf8'), hashlib.sha512))
320-
return s.startswith(prefix)
321-
322-
323-
def is_old_seed(seed: str) -> bool:
324-
from . import old_mnemonic, mnemonic
325-
seed = mnemonic.normalize_text(seed)
326-
words = seed.split()
327-
try:
328-
# checks here are deliberately left weak for legacy reasons, see #3149
329-
old_mnemonic.mn_decode(words)
330-
uses_electrum_words = True
331-
except Exception:
332-
uses_electrum_words = False
333-
try:
334-
seed = bfh(seed)
335-
is_hex = (len(seed) == 16 or len(seed) == 32)
336-
except Exception:
337-
is_hex = False
338-
return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))
339-
340-
341-
def seed_type(x: str) -> str:
342-
if is_old_seed(x):
343-
return 'old'
344-
elif is_new_seed(x):
345-
return 'standard'
346-
elif is_new_seed(x, version.SEED_PREFIX_SW):
347-
return 'segwit'
348-
elif is_new_seed(x, version.SEED_PREFIX_2FA):
349-
return '2fa'
350-
elif is_new_seed(x, version.SEED_PREFIX_2FA_SW):
351-
return '2fa_segwit'
352-
return ''
353-
354-
355-
def is_seed(x: str) -> bool:
356-
return bool(seed_type(x))
357-
358-
359-
def is_any_2fa_seed_type(seed_type):
360-
return seed_type in ['2fa', '2fa_segwit']
361-
362-
363313
############ functions from pywallet #####################
364314

365315
def hash160_to_b58_address(h160: bytes, addrtype: int) -> str:

electrum/gui/qt/seed_dialog.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
QLabel, QCompleter, QDialog)
3030

3131
from electrum.i18n import _
32-
from electrum.mnemonic import Mnemonic
32+
from electrum.mnemonic import Mnemonic, seed_type
3333
import electrum.old_mnemonic
3434

3535
from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path,
@@ -161,7 +161,6 @@ def get_seed(self):
161161
return ' '.join(text.split())
162162

163163
def on_edit(self):
164-
from electrum.bitcoin import seed_type
165164
s = self.get_seed()
166165
b = self.is_seed(s)
167166
if not self.is_bip39:

electrum/keystore.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from . import bitcoin, ecc, constants, bip32
3232
from .bitcoin import (deserialize_privkey, serialize_privkey,
33-
public_key_to_p2pkh, seed_type, is_seed)
33+
public_key_to_p2pkh)
3434
from .bip32 import (bip32_public_derivation, deserialize_xpub, CKD_pub,
3535
bip32_root, deserialize_xprv, bip32_private_derivation,
3636
bip32_private_key, bip32_derivation, BIP32_PRIME,
@@ -40,7 +40,7 @@
4040
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion)
4141
from .util import (PrintError, InvalidPassword, WalletFileException,
4242
BitcoinException, bh2u, bfh, print_error, inv_dict)
43-
from .mnemonic import Mnemonic, load_wordlist
43+
from .mnemonic import Mnemonic, load_wordlist, seed_type, is_seed
4444
from .plugin import run_hook
4545

4646

electrum/mnemonic.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030

3131
import ecdsa
3232

33-
from .util import print_error, resource_path
34-
from .bitcoin import is_old_seed, is_new_seed
33+
from .util import print_error, resource_path, bfh, bh2u
34+
from .crypto import hmac_oneshot
3535
from . import version
3636

3737
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html
@@ -181,3 +181,49 @@ def make_seed(self, seed_type='standard', num_bits=132):
181181
break
182182
print_error('%d words'%len(seed.split()))
183183
return seed
184+
185+
186+
def is_new_seed(x: str, prefix=version.SEED_PREFIX) -> bool:
187+
x = normalize_text(x)
188+
s = bh2u(hmac_oneshot(b"Seed version", x.encode('utf8'), hashlib.sha512))
189+
return s.startswith(prefix)
190+
191+
192+
def is_old_seed(seed: str) -> bool:
193+
from . import old_mnemonic
194+
seed = normalize_text(seed)
195+
words = seed.split()
196+
try:
197+
# checks here are deliberately left weak for legacy reasons, see #3149
198+
old_mnemonic.mn_decode(words)
199+
uses_electrum_words = True
200+
except Exception:
201+
uses_electrum_words = False
202+
try:
203+
seed = bfh(seed)
204+
is_hex = (len(seed) == 16 or len(seed) == 32)
205+
except Exception:
206+
is_hex = False
207+
return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))
208+
209+
210+
def seed_type(x: str) -> str:
211+
if is_old_seed(x):
212+
return 'old'
213+
elif is_new_seed(x):
214+
return 'standard'
215+
elif is_new_seed(x, version.SEED_PREFIX_SW):
216+
return 'segwit'
217+
elif is_new_seed(x, version.SEED_PREFIX_2FA):
218+
return '2fa'
219+
elif is_new_seed(x, version.SEED_PREFIX_2FA_SW):
220+
return '2fa_segwit'
221+
return ''
222+
223+
224+
def is_seed(x: str) -> bool:
225+
return bool(seed_type(x))
226+
227+
228+
def is_any_2fa_seed_type(seed_type: str) -> bool:
229+
return seed_type in ['2fa', '2fa_segwit']

electrum/old_mnemonic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,13 +1652,13 @@
16521652
"total",
16531653
"unseen",
16541654
"weapon",
1655-
"weary"
1655+
"weary",
16561656
]
16571657

1658+
n = len(words)
1659+
assert n == 1626
16581660

16591661

1660-
n = 1626
1661-
16621662
# Note about US patent no 5892470: Here each word does not represent a given digit.
16631663
# Instead, the digit represented by a word is variable, it depends on the previous word.
16641664

electrum/plugins/trustedcoin/trustedcoin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@
3636
from aiohttp import ClientResponse
3737

3838
from electrum import ecc, constants, keystore, version, bip32, bitcoin
39-
from electrum.bitcoin import TYPE_ADDRESS, is_new_seed, seed_type, is_any_2fa_seed_type
39+
from electrum.bitcoin import TYPE_ADDRESS
4040
from electrum.bip32 import (deserialize_xpub, deserialize_xprv, bip32_private_key, CKD_pub,
4141
serialize_xpub, bip32_root, bip32_private_derivation, xpub_type)
4242
from electrum.crypto import sha256
4343
from electrum.transaction import TxOutput
44-
from electrum.mnemonic import Mnemonic
44+
from electrum.mnemonic import Mnemonic, seed_type, is_any_2fa_seed_type
4545
from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
4646
from electrum.i18n import _
4747
from electrum.plugin import BasePlugin, hook

electrum/tests/test_bitcoin.py

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import sys
33

44
from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key,
5-
is_address, is_private_key, is_new_seed, is_old_seed,
5+
is_address, is_private_key,
66
var_int, _op_push, address_to_script,
77
deserialize_privkey, serialize_privkey, is_segwit_address,
88
is_b58_address, address_to_scripthash, is_minikey,
9-
is_compressed_privkey, seed_type, EncodeBase58Check,
9+
is_compressed_privkey, EncodeBase58Check,
1010
script_num_to_hex, push_script, add_number_to_script, int_to_hex,
1111
opcodes)
1212
from electrum.bip32 import (bip32_root, bip32_public_derivation, bip32_private_derivation,
@@ -719,49 +719,3 @@ def test_is_compressed_privkey(self):
719719
for priv_details in self.priv_pub_addr:
720720
self.assertEqual(priv_details['compressed'],
721721
is_compressed_privkey(priv_details['priv']))
722-
723-
724-
class Test_seeds(SequentialTestCase):
725-
""" Test old and new seeds. """
726-
727-
mnemonics = {
728-
('cell dumb heartbeat north boom tease ship baby bright kingdom rare squeeze', 'old'),
729-
('cell dumb heartbeat north boom tease ' * 4, 'old'),
730-
('cell dumb heartbeat north boom tease ship baby bright kingdom rare badword', ''),
731-
('cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE', 'old'),
732-
(' cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE ', 'old'),
733-
# below seed is actually 'invalid old' as it maps to 33 hex chars
734-
('hurry idiot prefer sunset mention mist jaw inhale impossible kingdom rare squeeze', 'old'),
735-
('cram swing cover prefer miss modify ritual silly deliver chunk behind inform able', 'standard'),
736-
('cram swing cover prefer miss modify ritual silly deliver chunk behind inform', ''),
737-
('ostrich security deer aunt climb inner alpha arm mutual marble solid task', 'standard'),
738-
('OSTRICH SECURITY DEER AUNT CLIMB INNER ALPHA ARM MUTUAL MARBLE SOLID TASK', 'standard'),
739-
(' oStRiCh sEcUrItY DeEr aUnT ClImB InNeR AlPhA ArM MuTuAl mArBlE SoLiD TaSk ', 'standard'),
740-
('x8', 'standard'),
741-
('science dawn member doll dutch real can brick knife deny drive list', '2fa'),
742-
('science dawn member doll dutch real ca brick knife deny drive list', ''),
743-
(' sCience dawn member doll Dutch rEAl can brick knife deny drive lisT', '2fa'),
744-
('frost pig brisk excite novel report camera enlist axis nation novel desert', 'segwit'),
745-
(' fRoSt pig brisk excIte novel rePort CamEra enlist axis nation nOVeL dEsert ', 'segwit'),
746-
('9dk', 'segwit'),
747-
}
748-
749-
def test_new_seed(self):
750-
seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform able"
751-
self.assertTrue(is_new_seed(seed))
752-
753-
seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform"
754-
self.assertFalse(is_new_seed(seed))
755-
756-
def test_old_seed(self):
757-
self.assertTrue(is_old_seed(" ".join(["like"] * 12)))
758-
self.assertFalse(is_old_seed(" ".join(["like"] * 18)))
759-
self.assertTrue(is_old_seed(" ".join(["like"] * 24)))
760-
self.assertFalse(is_old_seed("not a seed"))
761-
762-
self.assertTrue(is_old_seed("0123456789ABCDEF" * 2))
763-
self.assertTrue(is_old_seed("0123456789ABCDEF" * 4))
764-
765-
def test_seed_type(self):
766-
for seed_words, _type in self.mnemonics:
767-
self.assertEqual(_type, seed_type(seed_words), msg=seed_words)

electrum/tests/test_mnemonic.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from electrum import mnemonic
55
from electrum import old_mnemonic
66
from electrum.util import bh2u, bfh
7-
from electrum.bitcoin import is_new_seed
7+
from electrum.mnemonic import is_new_seed, is_old_seed, seed_type
88
from electrum.version import SEED_PREFIX_SW, SEED_PREFIX
99

1010
from . import SequentialTestCase
@@ -134,10 +134,57 @@ def test(self):
134134
self.assertEqual(result, words.split())
135135
self.assertEqual(old_mnemonic.mn_decode(result), seed)
136136

137+
137138
class Test_BIP39Checksum(SequentialTestCase):
138139

139140
def test(self):
140141
mnemonic = u'gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog'
141142
is_checksum_valid, is_wordlist_valid = keystore.bip39_is_checksum_valid(mnemonic)
142143
self.assertTrue(is_wordlist_valid)
143144
self.assertTrue(is_checksum_valid)
145+
146+
147+
class Test_seeds(SequentialTestCase):
148+
""" Test old and new seeds. """
149+
150+
mnemonics = {
151+
('cell dumb heartbeat north boom tease ship baby bright kingdom rare squeeze', 'old'),
152+
('cell dumb heartbeat north boom tease ' * 4, 'old'),
153+
('cell dumb heartbeat north boom tease ship baby bright kingdom rare badword', ''),
154+
('cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE', 'old'),
155+
(' cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE ', 'old'),
156+
# below seed is actually 'invalid old' as it maps to 33 hex chars
157+
('hurry idiot prefer sunset mention mist jaw inhale impossible kingdom rare squeeze', 'old'),
158+
('cram swing cover prefer miss modify ritual silly deliver chunk behind inform able', 'standard'),
159+
('cram swing cover prefer miss modify ritual silly deliver chunk behind inform', ''),
160+
('ostrich security deer aunt climb inner alpha arm mutual marble solid task', 'standard'),
161+
('OSTRICH SECURITY DEER AUNT CLIMB INNER ALPHA ARM MUTUAL MARBLE SOLID TASK', 'standard'),
162+
(' oStRiCh sEcUrItY DeEr aUnT ClImB InNeR AlPhA ArM MuTuAl mArBlE SoLiD TaSk ', 'standard'),
163+
('x8', 'standard'),
164+
('science dawn member doll dutch real can brick knife deny drive list', '2fa'),
165+
('science dawn member doll dutch real ca brick knife deny drive list', ''),
166+
(' sCience dawn member doll Dutch rEAl can brick knife deny drive lisT', '2fa'),
167+
('frost pig brisk excite novel report camera enlist axis nation novel desert', 'segwit'),
168+
(' fRoSt pig brisk excIte novel rePort CamEra enlist axis nation nOVeL dEsert ', 'segwit'),
169+
('9dk', 'segwit'),
170+
}
171+
172+
def test_new_seed(self):
173+
seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform able"
174+
self.assertTrue(is_new_seed(seed))
175+
176+
seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform"
177+
self.assertFalse(is_new_seed(seed))
178+
179+
def test_old_seed(self):
180+
self.assertTrue(is_old_seed(" ".join(["like"] * 12)))
181+
self.assertFalse(is_old_seed(" ".join(["like"] * 18)))
182+
self.assertTrue(is_old_seed(" ".join(["like"] * 24)))
183+
self.assertFalse(is_old_seed("not a seed"))
184+
185+
self.assertTrue(is_old_seed("0123456789ABCDEF" * 2))
186+
self.assertTrue(is_old_seed("0123456789ABCDEF" * 4))
187+
188+
def test_seed_type(self):
189+
for seed_words, _type in self.mnemonics:
190+
self.assertEqual(_type, seed_type(seed_words), msg=seed_words)

0 commit comments

Comments
 (0)