URI: 
       tmnemonic: implement Wordlist class - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit a0b096dcb2292c2826f7beae173c529d335142f0
   DIR parent e1dcdde272530193040f8cf52697d2f75d0028a1
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Sat, 29 Feb 2020 00:20:11 +0100
       
       mnemonic: implement Wordlist class
       
       Wordlist subclasses 'tuple' so it can be transparently used.
       'in' and '.index()' are fast.
       Use Wordlist in bip39_is_checksum_valid, which makes that faster.
       
       Diffstat:
         M electrum/gui/kivy/uix/dialogs/inst… |       2 +-
         M electrum/gui/qt/seed_dialog.py      |       4 ++--
         M electrum/keystore.py                |       4 ++--
         M electrum/mnemonic.py                |      67 ++++++++++++++++++++-----------
         M electrum/old_mnemonic.py            |      17 ++++++++++-------
       
       5 files changed, 59 insertions(+), 35 deletions(-)
       ---
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/installwizard.py b/electrum/gui/kivy/uix/dialogs/installwizard.py
       t@@ -859,7 +859,7 @@ class RestoreSeedDialog(WizardDialog):
                super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
                self._test = kwargs['test']
                from electrum.mnemonic import Mnemonic
       -        from electrum.old_mnemonic import words as old_wordlist
       +        from electrum.old_mnemonic import wordlist as old_wordlist
                self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
                self.ids.text_input_seed.text = test_seed if is_test else ''
                self.message = _('Please type your seed phrase using the virtual keyboard.')
   DIR diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py
       t@@ -30,7 +30,7 @@ from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit,
        
        from electrum.i18n import _
        from electrum.mnemonic import Mnemonic, seed_type
       -import electrum.old_mnemonic
       +from electrum import old_mnemonic
        
        from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path,
                           EnterButton, CloseButton, WindowModalDialog, ColorScheme)
       t@@ -150,7 +150,7 @@ class SeedLayout(QVBoxLayout):
        
            def initialize_completer(self):
                bip39_english_list = Mnemonic('en').wordlist
       -        old_list = electrum.old_mnemonic.words
       +        old_list = old_mnemonic.wordlist
                only_old_list = set(old_list) - set(bip39_english_list)
                self.wordlist = list(bip39_english_list) + list(only_old_list)  # concat both lists
                self.wordlist.sort()
   DIR diff --git a/electrum/keystore.py b/electrum/keystore.py
       t@@ -42,7 +42,7 @@ from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATE
                             SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion, hash_160)
        from .util import (InvalidPassword, WalletFileException,
                           BitcoinException, bh2u, bfh, inv_dict, is_hex_str)
       -from .mnemonic import Mnemonic, load_wordlist, seed_type, is_seed
       +from .mnemonic import Mnemonic, Wordlist, seed_type, is_seed
        from .plugin import run_hook
        from .logging import Logger
        
       t@@ -811,7 +811,7 @@ def bip39_is_checksum_valid(mnemonic: str) -> Tuple[bool, bool]:
            """
            words = [ normalize('NFKD', word) for word in mnemonic.split() ]
            words_len = len(words)
       -    wordlist = load_wordlist("english.txt")
       +    wordlist = Wordlist.from_file("english.txt")
            n = len(wordlist)
            i = 0
            words.reverse()
   DIR diff --git a/electrum/mnemonic.py b/electrum/mnemonic.py
       t@@ -27,6 +27,8 @@ import math
        import hashlib
        import unicodedata
        import string
       +from typing import Sequence, Dict
       +from types import MappingProxyType
        
        from .util import resource_path, bfh, bh2u, randrange
        from .crypto import hmac_oneshot
       t@@ -88,28 +90,48 @@ def normalize_text(seed: str) -> str:
            return seed
        
        
       -_WORDLIST_CACHE = {}
       +_WORDLIST_CACHE = {}  # type: Dict[str, Wordlist]
        
        
       -def load_wordlist(filename) -> tuple:
       -    path = resource_path('wordlist', filename)
       -    if path not in _WORDLIST_CACHE:
       -        with open(path, 'r', encoding='utf-8') as f:
       -            s = f.read().strip()
       -        s = unicodedata.normalize('NFKD', s)
       -        lines = s.split('\n')
       -        wordlist = []
       -        for line in lines:
       -            line = line.split('#')[0]
       -            line = line.strip(' \r')
       -            assert ' ' not in line
       -            if line:
       -                wordlist.append(line)
       +class Wordlist(tuple):
        
       -        # wordlists shouldn't be mutated, but just in case,
       -        # convert it to a tuple
       -        _WORDLIST_CACHE[path] = tuple(wordlist)
       -    return _WORDLIST_CACHE[path]
       +    def __init__(self, words: Sequence[str]):
       +        super().__init__()
       +        index_from_word = {w: i for i, w in enumerate(words)}
       +        self._index_from_word = MappingProxyType(index_from_word)  # no mutation
       +
       +    def index(self, word, start=None, stop=None) -> int:
       +        try:
       +            return self._index_from_word[word]
       +        except KeyError as e:
       +            raise ValueError from e
       +
       +    def __contains__(self, word) -> bool:
       +        try:
       +            self.index(word)
       +        except ValueError:
       +            return False
       +        else:
       +            return True
       +
       +    @classmethod
       +    def from_file(cls, filename) -> 'Wordlist':
       +        path = resource_path('wordlist', filename)
       +        if path not in _WORDLIST_CACHE:
       +            with open(path, 'r', encoding='utf-8') as f:
       +                s = f.read().strip()
       +            s = unicodedata.normalize('NFKD', s)
       +            lines = s.split('\n')
       +            words = []
       +            for line in lines:
       +                line = line.split('#')[0]
       +                line = line.strip(' \r')
       +                assert ' ' not in line
       +                if line:
       +                    words.append(line)
       +
       +            _WORDLIST_CACHE[path] = Wordlist(words)
       +        return _WORDLIST_CACHE[path]
        
        
        filenames = {
       t@@ -130,8 +152,7 @@ class Mnemonic(Logger):
                lang = lang or 'en'
                self.logger.info(f'language {lang}')
                filename = filenames.get(lang[0:2], 'english.txt')
       -        self.wordlist = load_wordlist(filename)
       -        self.wordlist_indexes = {w: i for i, w in enumerate(self.wordlist)}
       +        self.wordlist = Wordlist.from_file(filename)
                self.logger.info(f"wordlist has {len(self.wordlist)} words")
        
            @classmethod
       t@@ -162,11 +183,11 @@ class Mnemonic(Logger):
                i = 0
                while words:
                    w = words.pop()
       -            k = self.wordlist_indexes[w]
       +            k = self.wordlist.index(w)
                    i = i*n + k
                return i
        
       -    def make_seed(self, seed_type=None, *, num_bits=132):
       +    def make_seed(self, seed_type=None, *, num_bits=132) -> str:
                if seed_type is None:
                    seed_type = 'segwit'
                prefix = version.seed_prefix(seed_type)
   DIR diff --git a/electrum/old_mnemonic.py b/electrum/old_mnemonic.py
       t@@ -23,10 +23,12 @@
        # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        # SOFTWARE.
        
       +from .mnemonic import Wordlist
       +
        
        # list of words from http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry
        
       -words = (
       +_words = (
        "like",
        "just",
        "love",
       t@@ -1654,9 +1656,10 @@ words = (
        "weapon",
        "weary",
        )
       -_words_indexes = {w: i for i, w in enumerate(words)}
        
       -n = len(words)
       +wordlist = Wordlist(_words)
       +
       +n = len(wordlist)
        assert n == 1626
        
        
       t@@ -1672,7 +1675,7 @@ def mn_encode( message ):
                w1 = (x%n)
                w2 = ((x//n) + w1)%n
                w3 = ((x//n//n) + w2)%n
       -        out += [ words[w1], words[w2], words[w3] ]
       +        out += [ wordlist[w1], wordlist[w2], wordlist[w3] ]
            return out
        
        
       t@@ -1680,9 +1683,9 @@ def mn_decode( wlist ):
            out = ''
            for i in range(len(wlist)//3):
                word1, word2, word3 = wlist[3*i:3*i+3]
       -        w1 =  _words_indexes[word1]
       -        w2 = (_words_indexes[word2]) % n
       -        w3 = (_words_indexes[word3]) % n
       +        w1 =  wordlist.index(word1)
       +        w2 = (wordlist.index(word2)) % n
       +        w3 = (wordlist.index(word3)) % n
                x = w1 +n*((w2-w1)%n) +n*n*((w3-w2)%n)
                out += '%08x'%x
            return out