diff --git a/src/mnemonic/mnemonic.py b/src/mnemonic/mnemonic.py index caa4ffb..3613bdd 100644 --- a/src/mnemonic/mnemonic.py +++ b/src/mnemonic/mnemonic.py @@ -152,7 +152,7 @@ def generate(self, strength: int = 128) -> str: # Adapted from def to_entropy(self, words: list[str] | str) -> bytearray: if not isinstance(words, list): - words = words.split(" ") + words = self.normalize_string(words).split() if len(words) not in [12, 15, 18, 21, 24]: raise ValueError( "Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d)." @@ -210,7 +210,7 @@ def to_mnemonic(self, data: bytes) -> str: return self.delimiter.join(result) def check(self, mnemonic: str) -> bool: - mnemonic_list = self.normalize_string(mnemonic).split(" ") + mnemonic_list = self.normalize_string(mnemonic).split() # list of valid mnemonic lengths if len(mnemonic_list) not in [12, 15, 18, 21, 24]: return False @@ -241,11 +241,11 @@ def expand_word(self, prefix: str) -> str: return prefix def expand(self, mnemonic: str) -> str: - return " ".join(map(self.expand_word, mnemonic.split(" "))) + return " ".join(map(self.expand_word, mnemonic.split())) @classmethod def to_seed(cls, mnemonic: str, passphrase: str = "") -> bytes: - mnemonic = cls.normalize_string(mnemonic) + mnemonic = " ".join(cls.normalize_string(mnemonic).split()) passphrase = cls.normalize_string(passphrase) passphrase = "mnemonic" + passphrase mnemonic_bytes = mnemonic.encode("utf-8") diff --git a/tests/test_mnemonic.py b/tests/test_mnemonic.py index 641cf16..3d13c0a 100755 --- a/tests/test_mnemonic.py +++ b/tests/test_mnemonic.py @@ -141,6 +141,38 @@ def test_expand_word(self) -> None: "action", m.expand_word("acti") ) # unique prefix expanded to word in list + def test_japanese_vectors_to_entropy(self) -> None: + # the ideographic space separates words on every path, not only in + # to_mnemonic; see #110, whose fix split on self.delimiter and was + # reverted because NFKD had already turned U+3000 into a space + with open("vectors.json", "r") as f: + vectors = json.load(f) + m = Mnemonic("japanese") + for v in vectors["japanese"]: + self.assertIn("\u3000", v[1]) + entropy = bytes(m.to_entropy(v[1])) + self.assertEqual(v[0], entropy.hex()) + self.assertEqual(v[1], m.to_mnemonic(entropy)) + + def test_whitespace_runs(self) -> None: + # check(), to_entropy() and to_seed() read the same separator: any + # run of unicode whitespace, once NFKD has been applied + m = Mnemonic("english") + canonical = "abandon " * 11 + "about" + entropy = m.to_entropy(canonical) + seed = Mnemonic.to_seed(canonical) + for variant in ( + " " + canonical, + canonical + "\n", + canonical.replace("abandon about", "abandon about"), + canonical.replace(" ", "\t"), + canonical.replace(" ", "\u3000"), + canonical.replace(" ", "\u00a0"), + ): + self.assertIs(m.check(variant), True) + self.assertEqual(entropy, m.to_entropy(variant)) + self.assertEqual(seed, Mnemonic.to_seed(variant)) + def test_expand(self) -> None: m = Mnemonic("english") self.assertEqual("access", m.expand("access"))