ttest_mnemonic.py - electrum - Electrum Bitcoin wallet
HTML git clone https://git.parazyd.org/electrum
DIR Log
DIR Files
DIR Refs
DIR Submodules
---
ttest_mnemonic.py (11996B)
---
1 from typing import NamedTuple, Optional
2
3 from electrum import keystore
4 from electrum import mnemonic
5 from electrum import old_mnemonic
6 from electrum.util import bh2u, bfh
7 from electrum.mnemonic import is_new_seed, is_old_seed, seed_type
8 from electrum.version import SEED_PREFIX_SW, SEED_PREFIX
9
10 from . import ElectrumTestCase
11 from .test_wallet_vertical import UNICODE_HORROR, UNICODE_HORROR_HEX
12
13
14 class SeedTestCase(NamedTuple):
15 words: str
16 bip32_seed: str
17 lang: Optional[str] = 'en'
18 words_hex: Optional[str] = None
19 entropy: Optional[int] = None
20 passphrase: Optional[str] = None
21 passphrase_hex: Optional[str] = None
22 seed_version: str = SEED_PREFIX
23
24
25 SEED_TEST_CASES = {
26 'english': SeedTestCase(
27 words='wild father tree among universe such mobile favorite target dynamic credit identify',
28 seed_version=SEED_PREFIX_SW,
29 bip32_seed='aac2a6302e48577ab4b46f23dbae0774e2e62c796f797d0a1b5faeb528301e3064342dafb79069e7c4c6b8c38ae11d7a973bec0d4f70626f8cc5184a8d0b0756'),
30 'english_with_passphrase': SeedTestCase(
31 words='wild father tree among universe such mobile favorite target dynamic credit identify',
32 seed_version=SEED_PREFIX_SW,
33 passphrase='Did you ever hear the tragedy of Darth Plagueis the Wise?',
34 bip32_seed='4aa29f2aeb0127efb55138ab9e7be83b36750358751906f86c662b21a1ea1370f949e6d1a12fa56d3d93cadda93038c76ac8118597364e46f5156fde6183c82f'),
35 'japanese': SeedTestCase(
36 lang='ja',
37 words='なのか ひろい しなん まなぶ つぶす さがす おしゃれ かわく おいかける けさき かいとう さたん',
38 words_hex='e381aae381aee3818b20e381b2e3828de3818420e38197e381aae3829320e381bee381aae381b5e3829920e381a4e381b5e38299e3819920e38195e3818be38299e3819920e3818ae38197e38283e3828c20e3818be3828fe3818f20e3818ae38184e3818be38191e3828b20e38191e38195e3818d20e3818be38184e381a8e3818620e38195e3819fe38293',
39 entropy=1938439226660562861250521787963972783469,
40 bip32_seed='d3eaf0e44ddae3a5769cb08a26918e8b308258bcb057bb704c6f69713245c0b35cb92c03df9c9ece5eff826091b4e74041e010b701d44d610976ce8bfb66a8ad'),
41 'japanese_with_passphrase': SeedTestCase(
42 lang='ja',
43 words='なのか ひろい しなん まなぶ つぶす さがす おしゃれ かわく おいかける けさき かいとう さたん',
44 words_hex='e381aae381aee3818b20e381b2e3828de3818420e38197e381aae3829320e381bee381aae381b5e3829920e381a4e381b5e38299e3819920e38195e3818be38299e3819920e3818ae38197e38283e3828c20e3818be3828fe3818f20e3818ae38184e3818be38191e3828b20e38191e38195e3818d20e3818be38184e381a8e3818620e38195e3819fe38293',
45 entropy=1938439226660562861250521787963972783469,
46 passphrase=UNICODE_HORROR,
47 passphrase_hex=UNICODE_HORROR_HEX,
48 bip32_seed='251ee6b45b38ba0849e8f40794540f7e2c6d9d604c31d68d3ac50c034f8b64e4bc037c5e1e985a2fed8aad23560e690b03b120daf2e84dceb1d7857dda042457'),
49 'chinese': SeedTestCase(
50 lang='zh',
51 words='眼 悲 叛 改 节 跃 衡 响 疆 股 遂 冬',
52 words_hex='e79cbc20e682b220e58f9b20e694b920e88a8220e8b78320e8a1a120e5938d20e7968620e882a120e9818220e586ac',
53 seed_version=SEED_PREFIX_SW,
54 entropy=3083737086352778425940060465574397809099,
55 bip32_seed='0b9077db7b5a50dbb6f61821e2d35e255068a5847e221138048a20e12d80b673ce306b6fe7ac174ebc6751e11b7037be6ee9f17db8040bb44f8466d519ce2abf'),
56 'chinese_with_passphrase': SeedTestCase(
57 lang='zh',
58 words='眼 悲 叛 改 节 跃 衡 响 疆 股 遂 冬',
59 words_hex='e79cbc20e682b220e58f9b20e694b920e88a8220e8b78320e8a1a120e5938d20e7968620e882a120e9818220e586ac',
60 seed_version=SEED_PREFIX_SW,
61 entropy=3083737086352778425940060465574397809099,
62 passphrase='给我一些测试向量谷歌',
63 passphrase_hex='e7bb99e68891e4b880e4ba9be6b58be8af95e59091e9878fe8b0b7e6ad8c',
64 bip32_seed='6c03dd0615cf59963620c0af6840b52e867468cc64f20a1f4c8155705738e87b8edb0fc8a6cee4085776cb3a629ff88bb1a38f37085efdbf11ce9ec5a7fa5f71'),
65 'spanish': SeedTestCase(
66 lang='es',
67 words='almíbar tibio superar vencer hacha peatón príncipe matar consejo polen vehículo odisea',
68 words_hex='616c6d69cc8162617220746962696f20737570657261722076656e63657220686163686120706561746fcc816e20707269cc816e63697065206d6174617220636f6e73656a6f20706f6c656e2076656869cc8163756c6f206f6469736561',
69 entropy=3423992296655289706780599506247192518735,
70 bip32_seed='18bffd573a960cc775bbd80ed60b7dc00bc8796a186edebe7fc7cf1f316da0fe937852a969c5c79ded8255cdf54409537a16339fbe33fb9161af793ea47faa7a'),
71 'spanish_with_passphrase': SeedTestCase(
72 lang='es',
73 words='almíbar tibio superar vencer hacha peatón príncipe matar consejo polen vehículo odisea',
74 words_hex='616c6d69cc8162617220746962696f20737570657261722076656e63657220686163686120706561746fcc816e20707269cc816e63697065206d6174617220636f6e73656a6f20706f6c656e2076656869cc8163756c6f206f6469736561',
75 entropy=3423992296655289706780599506247192518735,
76 passphrase='araña difícil solución término cárcel',
77 passphrase_hex='6172616ecc83612064696669cc8163696c20736f6c7563696fcc816e207465cc81726d696e6f206361cc817263656c',
78 bip32_seed='363dec0e575b887cfccebee4c84fca5a3a6bed9d0e099c061fa6b85020b031f8fe3636d9af187bf432d451273c625e20f24f651ada41aae2c4ea62d87e9fa44c'),
79 'spanish2': SeedTestCase(
80 lang='es',
81 words='equipo fiar auge langosta hacha calor trance cubrir carro pulmón oro áspero',
82 words_hex='65717569706f20666961722061756765206c616e676f7374612068616368612063616c6f72207472616e63652063756272697220636172726f2070756c6d6fcc816e206f726f2061cc81737065726f',
83 seed_version=SEED_PREFIX_SW,
84 entropy=448346710104003081119421156750490206837,
85 bip32_seed='001ebce6bfde5851f28a0d44aae5ae0c762b600daf3b33fc8fc630aee0d207646b6f98b18e17dfe3be0a5efe2753c7cdad95860adbbb62cecad4dedb88e02a64'),
86 'spanish3': SeedTestCase(
87 lang='es',
88 words='vidrio jabón muestra pájaro capucha eludir feliz rotar fogata pez rezar oír',
89 words_hex='76696472696f206a61626fcc816e206d756573747261207061cc816a61726f206361707563686120656c756469722066656c697a20726f74617220666f676174612070657a2072657a6172206f69cc8172',
90 seed_version=SEED_PREFIX_SW,
91 entropy=3444792611339130545499611089352232093648,
92 passphrase='¡Viva España! repiten veinte pueblos y al hablar dan fe del ánimo español... ¡Marquen arado martillo y clarín',
93 passphrase_hex='c2a1566976612045737061c3b16121207265706974656e207665696e746520707565626c6f73207920616c206861626c61722064616e2066652064656c20c3a16e696d6f2065737061c3b16f6c2e2e2e20c2a14d61727175656e20617261646f206d617274696c6c6f207920636c6172c3ad6e',
94 bip32_seed='c274665e5453c72f82b8444e293e048d700c59bf000cacfba597629d202dcf3aab1cf9c00ba8d3456b7943428541fed714d01d8a0a4028fc3a9bb33d981cb49f'),
95 }
96
97
98 class Test_NewMnemonic(ElectrumTestCase):
99
100 def test_mnemonic_to_seed_basic(self):
101 # note: not a valid electrum seed
102 seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic='foobar', passphrase='none')
103 self.assertEqual('741b72fd15effece6bfe5a26a52184f66811bd2be363190e07a42cca442b1a5bb22b3ad0eb338197287e6d314866c7fba863ac65d3f156087a5052ebc7157fce',
104 bh2u(seed))
105
106 def test_mnemonic_to_seed(self):
107 for test_name, test in SEED_TEST_CASES.items():
108 if test.words_hex is not None:
109 self.assertEqual(test.words_hex, bh2u(test.words.encode('utf8')), msg=test_name)
110 self.assertTrue(is_new_seed(test.words, prefix=test.seed_version), msg=test_name)
111 m = mnemonic.Mnemonic(lang=test.lang)
112 if test.entropy is not None:
113 self.assertEqual(test.entropy, m.mnemonic_decode(test.words), msg=test_name)
114 if test.passphrase_hex is not None:
115 self.assertEqual(test.passphrase_hex, bh2u(test.passphrase.encode('utf8')), msg=test_name)
116 seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic=test.words, passphrase=test.passphrase)
117 self.assertEqual(test.bip32_seed, bh2u(seed), msg=test_name)
118
119 def test_random_seeds(self):
120 iters = 10
121 m = mnemonic.Mnemonic(lang='en')
122 for _ in range(iters):
123 seed = m.make_seed(seed_type="standard")
124 i = m.mnemonic_decode(seed)
125 self.assertEqual(m.mnemonic_encode(i), seed)
126
127
128 class Test_OldMnemonic(ElectrumTestCase):
129
130 def test(self):
131 seed = '8edad31a95e7d59f8837667510d75a4d'
132 result = old_mnemonic.mn_encode(seed)
133 words = 'hardly point goal hallway patience key stone difference ready caught listen fact'
134 self.assertEqual(result, words.split())
135 self.assertEqual(old_mnemonic.mn_decode(result), seed)
136
137
138 class Test_BIP39Checksum(ElectrumTestCase):
139
140 def test(self):
141 mnemonic = u'gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog'
142 is_checksum_valid, is_wordlist_valid = keystore.bip39_is_checksum_valid(mnemonic)
143 self.assertTrue(is_wordlist_valid)
144 self.assertTrue(is_checksum_valid)
145
146
147 class Test_seeds(ElectrumTestCase):
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 idx, (seed_words, _type) in enumerate(self.mnemonics):
190 with self.subTest(msg=f"seed_type_subcase_{idx}", seed_words=seed_words):
191 self.assertEqual(_type, seed_type(seed_words), msg=seed_words)