From 53435588a04c74e9d50359c9d6913fd030f6e9fc Mon Sep 17 00:00:00 2001 From: Chris Marslender Date: Wed, 1 Nov 2023 09:52:52 -0700 Subject: [PATCH] Update to support looking up mnemonic by just the first 4 letters of each word (#16704) Co-authored-by: Kyle Altendorf --- chia/util/keychain.py | 26 ++++++++++- tests/core/util/test_keychain.py | 20 +++++++++ tests/util/bip39_test_vectors.json | 72 ++++++++++++++++++++---------- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/chia/util/keychain.py b/chia/util/keychain.py index 98cd671bc963..f80800b477bb 100644 --- a/chia/util/keychain.py +++ b/chia/util/keychain.py @@ -87,11 +87,31 @@ def bytes_to_mnemonic(mnemonic_bytes: bytes) -> str: return " ".join(mnemonics) -def bytes_from_mnemonic(mnemonic_str: str) -> bytes: +def mnemonic_from_short_words(mnemonic_str: str) -> str: + """ + Since the first 4 letters of each word is unique (or the full word, if less than 4 characters), and its common + practice to only store the first 4 letters of each word in many offline storage solutions, also support looking + up words by the first 4 characters + """ mnemonic: List[str] = mnemonic_str.split(" ") if len(mnemonic) not in [12, 15, 18, 21, 24]: raise ValueError("Invalid mnemonic length") + four_char_dict = {word[:4]: word for word in bip39_word_list().splitlines()} + full_words: List[str] = [] + for word in mnemonic: + full_word = four_char_dict.get(word[:4]) + if full_word is None: + raise ValueError(f"{word!r} is not in the mnemonic dictionary; may be misspelled") + full_words.append(full_word) + + return " ".join(full_words) + + +def bytes_from_mnemonic(mnemonic_str: str) -> bytes: + full_mnemonic_str = mnemonic_from_short_words(mnemonic_str) + mnemonic: List[str] = full_mnemonic_str.split(" ") + word_list = {word: i for i, word in enumerate(bip39_word_list().splitlines())} bit_array = BitArray() for i in range(0, len(mnemonic)): @@ -123,6 +143,10 @@ def mnemonic_to_seed(mnemonic: str) -> bytes: """ Uses BIP39 standard to derive a seed from entropy bytes. """ + # If there are only ASCII characters (as typically expected in a seed phrase), we can check if its just shortened + # 4 letter versions of each word + if not any(ord(c) >= 128 for c in mnemonic): + mnemonic = mnemonic_from_short_words(mnemonic) salt_str: str = "mnemonic" salt = unicodedata.normalize("NFKD", salt_str).encode("utf-8") mnemonic_normalized = unicodedata.normalize("NFKD", mnemonic).encode("utf-8") diff --git a/tests/core/util/test_keychain.py b/tests/core/util/test_keychain.py index 1201a5ed936a..9598a4f0f5a0 100644 --- a/tests/core/util/test_keychain.py +++ b/tests/core/util/test_keychain.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import pathlib import random from dataclasses import replace from typing import Callable, List, Optional, Tuple @@ -8,6 +9,7 @@ from typing import Callable, List, Optional, Tuple import pytest from blspy import AugSchemeMPL, G1Element, PrivateKey +import tests from chia.simulator.keyring import TempKeyring from chia.types.blockchain_format.sized_bytes import bytes32 from chia.util.errors import ( @@ -26,6 +28,7 @@ from chia.util.keychain import ( bytes_from_mnemonic, bytes_to_mnemonic, generate_mnemonic, + mnemonic_from_short_words, mnemonic_to_seed, ) @@ -165,6 +168,23 @@ class TestKeychain: assert bytes_to_mnemonic(entropy_bytes) == mnemonic assert mnemonic_to_seed(mnemonic) == seed + def test_bip39_test_vectors_short(self): + """ + Tests that the first 4 letters of each mnemonic phrase matches as if it were the full phrase + """ + test_vectors_path = pathlib.Path(tests.__file__).parent.joinpath("util", "bip39_test_vectors.json") + with open(test_vectors_path) as f: + all_vectors = json.load(f) + + for idx, [entropy_hex, full_mnemonic, seed, short_mnemonic] in enumerate(all_vectors["english"]): + entropy_bytes = bytes.fromhex(entropy_hex) + seed = bytes.fromhex(seed) + + assert mnemonic_from_short_words(short_mnemonic) == full_mnemonic + assert bytes_from_mnemonic(short_mnemonic) == entropy_bytes + assert bytes_to_mnemonic(entropy_bytes) == full_mnemonic + assert mnemonic_to_seed(short_mnemonic) == seed + def test_utf8_nfkd(self): # Test code from trezor: # Copyright (c) 2013 Pavol Rusnak diff --git a/tests/util/bip39_test_vectors.json b/tests/util/bip39_test_vectors.json index a63548ef1c6f..646234007a82 100644 --- a/tests/util/bip39_test_vectors.json +++ b/tests/util/bip39_test_vectors.json @@ -3,122 +3,146 @@ [ "00000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", - "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4" + "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", + "aban aban aban aban aban aban aban aban aban aban aban abou" ], [ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", "legal winner thank year wave sausage worth useful legal winner thank yellow", - "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096" + "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096", + "lega winn than year wave saus wort usef lega winn than yell" ], [ "80808080808080808080808080808080", "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", - "77d6be9708c8218738934f84bbbb78a2e048ca007746cb764f0673e4b1812d176bbb173e1a291f31cf633f1d0bad7d3cf071c30e98cd0688b5bcce65ecaceb36" + "77d6be9708c8218738934f84bbbb78a2e048ca007746cb764f0673e4b1812d176bbb173e1a291f31cf633f1d0bad7d3cf071c30e98cd0688b5bcce65ecaceb36", + "lett advi cage absu amou doct acou avoi lett advi cage abov" ], [ "ffffffffffffffffffffffffffffffff", "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", - "b6a6d8921942dd9806607ebc2750416b289adea669198769f2e15ed926c3aa92bf88ece232317b4ea463e84b0fcd3b53577812ee449ccc448eb45e6f544e25b6" + "b6a6d8921942dd9806607ebc2750416b289adea669198769f2e15ed926c3aa92bf88ece232317b4ea463e84b0fcd3b53577812ee449ccc448eb45e6f544e25b6", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wron" ], [ "000000000000000000000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", - "4975bb3d1faf5308c86a30893ee903a976296609db223fd717e227da5a813a34dc1428b71c84a787fc51f3b9f9dc28e9459f48c08bd9578e9d1b170f2d7ea506" + "4975bb3d1faf5308c86a30893ee903a976296609db223fd717e227da5a813a34dc1428b71c84a787fc51f3b9f9dc28e9459f48c08bd9578e9d1b170f2d7ea506", + "aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban agen" ], [ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", - "b059400ce0f55498a5527667e77048bb482ff6daa16c37b4b9e8af70c85b3f4df588004f19812a1a027c9a51e5e94259a560268e91cd10e206451a129826e740" + "b059400ce0f55498a5527667e77048bb482ff6daa16c37b4b9e8af70c85b3f4df588004f19812a1a027c9a51e5e94259a560268e91cd10e206451a129826e740", + "lega winn than year wave saus wort usef lega winn than year wave saus wort usef lega will" ], [ "808080808080808080808080808080808080808080808080", "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", - "04d5f77103510c41d610f7f5fb3f0badc77c377090815cee808ea5d2f264fdfabf7c7ded4be6d4c6d7cdb021ba4c777b0b7e57ca8aa6de15aeb9905dba674d66" + "04d5f77103510c41d610f7f5fb3f0badc77c377090815cee808ea5d2f264fdfabf7c7ded4be6d4c6d7cdb021ba4c777b0b7e57ca8aa6de15aeb9905dba674d66", + "lett advi cage absu amou doct acou avoi lett advi cage absu amou doct acou avoi lett alwa" ], [ "ffffffffffffffffffffffffffffffffffffffffffffffff", "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", - "d2911131a6dda23ac4441d1b66e2113ec6324354523acfa20899a2dcb3087849264e91f8ec5d75355f0f617be15369ffa13c3d18c8156b97cd2618ac693f759f" + "d2911131a6dda23ac4441d1b66e2113ec6324354523acfa20899a2dcb3087849264e91f8ec5d75355f0f617be15369ffa13c3d18c8156b97cd2618ac693f759f", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when" ], [ "0000000000000000000000000000000000000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", - "408b285c123836004f4b8842c89324c1f01382450c0d439af345ba7fc49acf705489c6fc77dbd4e3dc1dd8cc6bc9f043db8ada1e243c4a0eafb290d399480840" + "408b285c123836004f4b8842c89324c1f01382450c0d439af345ba7fc49acf705489c6fc77dbd4e3dc1dd8cc6bc9f043db8ada1e243c4a0eafb290d399480840", + "aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban aban art" ], [ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", - "761914478ebf6fe16185749372e91549361af22b386de46322cf8b1ba7e92e80c4af05196f742be1e63aab603899842ddadf4e7248d8e43870a4b6ff9bf16324" + "761914478ebf6fe16185749372e91549361af22b386de46322cf8b1ba7e92e80c4af05196f742be1e63aab603899842ddadf4e7248d8e43870a4b6ff9bf16324", + "lega winn than year wave saus wort usef lega winn than year wave saus wort usef lega winn than year wave saus wort titl" ], [ "8080808080808080808080808080808080808080808080808080808080808080", "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", - "848bbe19cad445e46f35fd3d1a89463583ac2b60b5eb4cfcf955731775a5d9e17a81a71613fed83f1ae27b408478fdec2bbc75b5161d1937aa7cdf4ad686ef5f" + "848bbe19cad445e46f35fd3d1a89463583ac2b60b5eb4cfcf955731775a5d9e17a81a71613fed83f1ae27b408478fdec2bbc75b5161d1937aa7cdf4ad686ef5f", + "lett advi cage absu amou doct acou avoi lett advi cage absu amou doct acou avoi lett advi cage absu amou doct acou bles" ], [ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", - "e28a37058c7f5112ec9e16a3437cf363a2572d70b6ceb3b6965447623d620f14d06bb321a26b33ec15fcd84a3b5ddfd5520e230c924c87aaa0d559749e044fef" + "e28a37058c7f5112ec9e16a3437cf363a2572d70b6ceb3b6965447623d620f14d06bb321a26b33ec15fcd84a3b5ddfd5520e230c924c87aaa0d559749e044fef", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote" ], [ "9e885d952ad362caeb4efe34a8e91bd2", "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic", - "e2f88a043776c828063d4c3c97173944d32cf847a925b6e40b0b8bd0b4bead3ba734bdda5250d4698b310a71c9934e1a48e562315ce22bf85f89459df0e73a6c" + "e2f88a043776c828063d4c3c97173944d32cf847a925b6e40b0b8bd0b4bead3ba734bdda5250d4698b310a71c9934e1a48e562315ce22bf85f89459df0e73a6c", + "ozon dril grab fibe curt grac pudd than crui elde eigh picn" ], [ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b", "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog", - "31736b4f31612ece84404c74a5d3b938a092480eb89c11b81491f1a3657eb2fe50024610fbe814df55913a87ef741020fcf076a75a29aea0aba638126ba4c8bb" + "31736b4f31612ece84404c74a5d3b938a092480eb89c11b81491f1a3657eb2fe50024610fbe814df55913a87ef741020fcf076a75a29aea0aba638126ba4c8bb", + "grav mach nort sort syst fema filt atti volu fold club stay feat offi ecol stab narr fog" ], [ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c", "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", - "17e4b5661796eeff8904550f8572289317ece7c1cc1316469f8f4c986c1ffd7b9f4c3aeac3e1713ffc21fa33707d09d57a2ece358d72111ef7c7658e7b33f2d5" + "17e4b5661796eeff8904550f8572289317ece7c1cc1316469f8f4c986c1ffd7b9f4c3aeac3e1713ffc21fa33707d09d57a2ece358d72111ef7c7658e7b33f2d5", + "hams diag priv dutc caus dela priv meat slid todd razo book happ fanc gosp tenn mapl dile loan word shrug infl dela leng" ], [ "c0ba5a8e914111210f2bd131f3d5e08d", "scheme spot photo card baby mountain device kick cradle pact join borrow", - "00e1e39a39e23735adab690d0ffbefda6cacc063b4a5fcc605de060bd370adc54c94370d966b9a3fae5ed16bd58a02224cbefca4146a083951b70be2a2ce3dcc" + "00e1e39a39e23735adab690d0ffbefda6cacc063b4a5fcc605de060bd370adc54c94370d966b9a3fae5ed16bd58a02224cbefca4146a083951b70be2a2ce3dcc", + "sche spot phot card baby moun devi kick crad pact join borr" ], [ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3", "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave", - "e893fc34fa2a78ceaaea78c246b69257ad283fa538d88f3c4520beb618a2062b8ec4920ed1793fff6ad443523ed18c03da433004d0a1e9497e194621607bc9e2" + "e893fc34fa2a78ceaaea78c246b69257ad283fa538d88f3c4520beb618a2062b8ec4920ed1793fff6ad443523ed18c03da433004d0a1e9497e194621607bc9e2", + "horn tena knee tale spon spel gate clip puls soap slus warm silv neph swap uncl crac brav" ], [ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863", "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", - "3e066d7dee2dbf8fcd3fe240a3975658ca118a8f6f4ca81cf99104944604b05a5090a79d99e545704b914ca0397fedb82fd00fd6a72098703709c891a065ee49" + "3e066d7dee2dbf8fcd3fe240a3975658ca118a8f6f4ca81cf99104944604b05a5090a79d99e545704b914ca0397fedb82fd00fd6a72098703709c891a065ee49", + "pand eyeb bull gori call smok muff tast mesh disc soft ostr alco spee nati flas devo leve hobb quic inne driv ghos insi" ], [ "23db8160a31d3e0dca3688ed941adbf3", "cat swing flag economy stadium alone churn speed unique patch report train", - "7ea73b3a398f8a71f7dde589d972b0358d3fa8b9e91317ecc544e42752b1bb251a1926b1f4c69eec0a80c0396aa0f7df29f7d73411d3106eba539f3d584fcdf8" + "7ea73b3a398f8a71f7dde589d972b0358d3fa8b9e91317ecc544e42752b1bb251a1926b1f4c69eec0a80c0396aa0f7df29f7d73411d3106eba539f3d584fcdf8", + "cat swin flag econ stad alon chur spee uniq patc repo trai" ], [ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0", "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access", - "d0ca8861283a7124515e825b7a06de8e0ad0dd5ac7888013efe6e3c300d4745bbd2c729f3355d769d23718579e7b735999a8f0b38e22b5bff45c9085af449056" + "d0ca8861283a7124515e825b7a06de8e0ad0dd5ac7888013efe6e3c300d4745bbd2c729f3355d769d23718579e7b735999a8f0b38e22b5bff45c9085af449056", + "ligh rule cinn wrap dras word prid squi upgr then inco fata apar sust crac supp prou acce" ], [ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad", "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", - "fc795be0c3f18c50dddb34e72179dc597d64055497ecc1e69e2e56a5409651bc139aae8070d4df0ea14d8d2a518a9a00bb1cc6e92e053fe34051f6821df9164c" + "fc795be0c3f18c50dddb34e72179dc597d64055497ecc1e69e2e56a5409651bc139aae8070d4df0ea14d8d2a518a9a00bb1cc6e92e053fe34051f6821df9164c", + "all hour make firs lead exte hole alie behi guar gosp lava path outp cens muse juni mass reop famo sing adva salt refo" ], [ "f30f8c1da665478f49b001d94c5fc452", "vessel ladder alter error federal sibling chat ability sun glass valve picture", - "e2d7f9a462875c1325f44f321392edc8eaafebf1547c89d72d10b41b4ee23af3fb0ab010f39f5cbea3b3aa671161b58262b6a508bcbe2d34ee272a942534d45f" + "e2d7f9a462875c1325f44f321392edc8eaafebf1547c89d72d10b41b4ee23af3fb0ab010f39f5cbea3b3aa671161b58262b6a508bcbe2d34ee272a942534d45f", + "vess ladd alte erro fede sibl chat abil sun glas valv pict" ], [ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05", "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump", - "a555426999448df9022c3afc2ed0e4aebff3a0ac37d8a395f81412e14994efc960ed168d39a80478e0467a3d5cfc134fef2767c1d3a27f18e3afeb11bfc8e6ad" + "a555426999448df9022c3afc2ed0e4aebff3a0ac37d8a395f81412e14994efc960ed168d39a80478e0467a3d5cfc134fef2767c1d3a27f18e3afeb11bfc8e6ad", + "scis invi lock mapl supr raw rapi void cong musc digi eleg litt bris hair mang cong clum" ], [ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", - "b873212f885ccffbf4692afcb84bc2e55886de2dfa07d90f5c3c239abc31c0a6ce047e30fd8bf6a281e71389aa82d73df74c7bbfb3b06b4639a5cee775cccd3c" + "b873212f885ccffbf4692afcb84bc2e55886de2dfa07d90f5c3c239abc31c0a6ce047e30fd8bf6a281e71389aa82d73df74c7bbfb3b06b4639a5cee775cccd3c", + "void come effo suff camp surv warr heav shoo prim clut crus open amaz scre patr grou spac poin ten exis slus invo unfo" ] ] }