/
mnemonic.py
131 lines (93 loc) · 4.1 KB
/
mnemonic.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/env python3
# Copyright (C) 2017-2020 The btclib developers
#
# This file is part of btclib. It is subject to the license terms in the
# LICENSE file found in the top-level directory of this distribution.
#
# No part of btclib including this file, may be copied, modified, propagated,
# or distributed except according to the terms contained in the LICENSE file.
"""Entropy conversion from/to mnemonic word-list sentence.
Entropy must be represented as binary 0/1 string.
Warning: these functions are not meant for end-users which are
better served by the bip39 and electrum module functions.
"""
from os import path
from typing import List
from .utils import ensure_is_power_of_two
WordList = List[str]
class WordLists:
"""Class for word-lists to be used in entropy/mnemonic conversions.
Word-lists are from:
* *en*: https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt
* *it*: https://github.com/bitcoin/bips/blob/master/bip-0039/italian.txt
More word-lists can be added using the load_lang method.
Word-lists are loaded only if needed and read only once from disk.
"""
def __init__(self) -> None:
path_to_filename = path.join(path.dirname(__file__), "data")
self.language_files = {
"en": path.join(path_to_filename, "english.txt"),
"it": path.join(path_to_filename, "italian.txt"),
}
self.languages = list(self.language_files)
# create dictionaries where each language has empty word-list
wordlists: List[List[str]] = [[] for _ in self.languages]
self._wordlist = dict(zip(self.languages, wordlists))
zeros = len(self.languages) * [0]
self._bits_per_word = dict(zip(self.languages, zeros))
self._language_length = dict(zip(self.languages, zeros))
def load_lang(self, lang: str, filename: str = None) -> None:
"""Load/add a language word-list if not loaded/added yet.
The language file has to be provided for adding new languages
beyond those already provided.
"""
# a new language, unknown before
if lang not in self.languages:
if filename is None:
raise ValueError(f"Missing file for language '{lang}'")
else:
# initialize the new language
self.languages.append(lang)
self.language_files[lang] = filename
self._wordlist[lang] = []
self._bits_per_word[lang] = 0
self._language_length[lang] = 0
# language has not been loaded yet
if self._language_length[lang] == 0:
with open(self.language_files[lang], "r") as f:
lines = f.readlines()
nwords = len(lines)
ensure_is_power_of_two(nwords, "invalid wordlist length")
self._language_length[lang] = nwords
# clean up and normalization are missing, but removal of \n
self._wordlist[lang] = [line[:-1] for line in lines]
def wordlist(self, lang: str) -> WordList:
"""Return the language word-list."""
self.load_lang(lang)
return self._wordlist[lang]
def language_length(self, lang: str) -> int:
"""Return the number of words in the language word-list."""
self.load_lang(lang)
return self._language_length[lang]
# singleton
_wordlists = WordLists()
Mnemonic = str
def _mnemonic_from_indexes(indexes: List[int], lang: str) -> Mnemonic:
"""Return the mnemonic from a list of word-list indexes.
Return the mnemonic from a list of integer indexes into
a given language word-list.
"""
words = []
wordlist = _wordlists.wordlist(lang)
for index in indexes:
word = wordlist[index]
words.append(word)
return " ".join(words)
def _indexes_from_mnemonic(mnemonic: Mnemonic, lang: str) -> List[int]:
"""Return the word-list indexes for a given mnemonic.
Return the list of integer indexes into a language word-list
for a given mnemonic.
"""
words = mnemonic.split()
wordlist = _wordlists.wordlist(lang)
return [wordlist.index(w) for w in words]