diff --git a/core/SConscript.firmware b/core/SConscript.firmware index d36585a89..1b9fffbe7 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -88,6 +88,7 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/sha2.c', 'vendor/trezor-crypto/sha3.c', 'vendor/trezor-crypto/shamir.c', + 'vendor/trezor-crypto/slip39.c', ] # libsecp256k1-zkp diff --git a/core/SConscript.unix b/core/SConscript.unix index 624682f64..b201997c3 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -86,6 +86,7 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/sha2.c', 'vendor/trezor-crypto/sha3.c', 'vendor/trezor-crypto/shamir.c', + 'vendor/trezor-crypto/slip39.c', ] # libsecp256k1-zkp diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-slip39.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-slip39.h new file mode 100644 index 000000000..43d2e664d --- /dev/null +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-slip39.h @@ -0,0 +1,122 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "py/obj.h" +#include "py/runtime.h" + +#include "slip39.h" + +/// package: trezorcrypto.slip39 + +/// def compute_mask(prefix: int) -> int: +/// """ +/// Calculates which buttons still can be pressed after some already were. +/// Returns a 9-bit bitmask, where each bit specifies which buttons +/// can be further pressed (there are still words in this combination). +/// LSB denotes first button. +/// +/// Example: 110000110 - second, third, eighth and ninth button still can be +/// pressed. +/// """ +STATIC mp_obj_t mod_trezorcrypto_slip39_compute_mask(mp_obj_t _prefix) { + uint16_t prefix = mp_obj_get_int(_prefix); + + if (prefix < 1 || prefix > 9999) { + mp_raise_ValueError( + "Invalid button prefix (range between 1 and 9999 is allowed)"); + } + return mp_obj_new_int_from_uint(compute_mask(prefix)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_slip39_compute_mask_obj, + mod_trezorcrypto_slip39_compute_mask); + +/// def button_sequence_to_word(prefix: int) -> str: +/// """ +/// Finds the first word that fits the given button prefix. +/// """ +STATIC mp_obj_t +mod_trezorcrypto_slip39_button_sequence_to_word(mp_obj_t _prefix) { + uint16_t prefix = mp_obj_get_int(_prefix); + + if (prefix < 1 || prefix > 9999) { + mp_raise_ValueError( + "Invalid button prefix (range between 1 and 9999 is allowed)"); + } + const char *word = button_sequence_to_word(prefix); + return mp_obj_new_str_copy(&mp_type_str, (const uint8_t *)word, strlen(word)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mod_trezorcrypto_slip39_button_sequence_to_word_obj, + mod_trezorcrypto_slip39_button_sequence_to_word); + +/// def word_index(word: str) -> int: +/// """ +/// Finds index of given word. +/// Raises ValueError if not found. +/// """ +STATIC mp_obj_t mod_trezorcrypto_slip39_word_index(mp_obj_t _word) { + mp_buffer_info_t word; + + mp_get_buffer_raise(_word, &word, MP_BUFFER_READ); + + uint16_t result = 0; + if (word_index(&result, word.buf, word.len) == false) { + mp_raise_ValueError("Invalid mnemonic word"); + } + return mp_obj_new_int_from_uint(result); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_slip39_word_index_obj, + mod_trezorcrypto_slip39_word_index); + +/// def get_word(index: int) -> str: +/// """ +/// Returns word on position 'index'. +/// """ +STATIC mp_obj_t mod_trezorcrypto_slip39_get_word(mp_obj_t _index) { + uint16_t index = mp_obj_get_int(_index); + + if (index > 1023) { + mp_raise_ValueError( + "Invalid wordlist index (range between 0 and 1024 is allowed)"); + } + + const char *word = get_word(index); + return mp_obj_new_str_copy(&mp_type_str, (const uint8_t *)word, strlen(word)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_slip39_get_word_obj, + mod_trezorcrypto_slip39_get_word); + +STATIC const mp_rom_map_elem_t mod_trezorcrypto_slip39_globals_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_slip39)}, + {MP_ROM_QSTR(MP_QSTR_compute_mask), + MP_ROM_PTR(&mod_trezorcrypto_slip39_compute_mask_obj)}, + {MP_ROM_QSTR(MP_QSTR_button_sequence_to_word), + MP_ROM_PTR(&mod_trezorcrypto_slip39_button_sequence_to_word_obj)}, + {MP_ROM_QSTR(MP_QSTR_word_index), + MP_ROM_PTR(&mod_trezorcrypto_slip39_word_index_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_word), + MP_ROM_PTR(&mod_trezorcrypto_slip39_get_word_obj)}, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_slip39_globals, + mod_trezorcrypto_slip39_globals_table); + +STATIC const mp_obj_module_t mod_trezorcrypto_slip39_module = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&mod_trezorcrypto_slip39_globals, +}; diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c index 28aaf4514..265942f15 100644 --- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c @@ -51,6 +51,7 @@ #include "modtrezorcrypto-sha3-512.h" #include "modtrezorcrypto-sha512.h" #include "modtrezorcrypto-shamir.h" +#include "modtrezorcrypto-slip39.h" STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorcrypto)}, @@ -91,6 +92,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_sha3_512), MP_ROM_PTR(&mod_trezorcrypto_Sha3_512_type)}, {MP_ROM_QSTR(MP_QSTR_shamir), MP_ROM_PTR(&mod_trezorcrypto_shamir_module)}, + {MP_ROM_QSTR(MP_QSTR_slip39), MP_ROM_PTR(&mod_trezorcrypto_slip39_module)}, }; STATIC MP_DEFINE_CONST_DICT(mp_module_trezorcrypto_globals, mp_module_trezorcrypto_globals_table); diff --git a/core/src/trezor/ui/mnemonic.py b/core/src/trezor/ui/mnemonic_bip39.py similarity index 99% rename from core/src/trezor/ui/mnemonic.py rename to core/src/trezor/ui/mnemonic_bip39.py index 3da8041d6..4c4ae0c67 100644 --- a/core/src/trezor/ui/mnemonic.py +++ b/core/src/trezor/ui/mnemonic_bip39.py @@ -90,7 +90,7 @@ class Prompt(ui.Control): self.repaint = False -class MnemonicKeyboard(ui.Layout): +class Bip39Keyboard(ui.Layout): def __init__(self, prompt): self.prompt = Prompt(prompt) diff --git a/core/src/trezor/ui/mnemonic_slip39.py b/core/src/trezor/ui/mnemonic_slip39.py new file mode 100644 index 000000000..65ad6214b --- /dev/null +++ b/core/src/trezor/ui/mnemonic_slip39.py @@ -0,0 +1,205 @@ +from trezor import io, loop, res, ui +from trezor.crypto import slip39 +from trezor.ui import display +from trezor.ui.button import Button, ButtonClear, ButtonMono, ButtonMonoConfirm + + +def check_mask(mask: int, index: int) -> bool: + return bool((1 << (index - 1)) & mask) + + +# TODO: ask UX if we want to finish sooner than 4 words +# example: 'hairy' +def _is_final(content: str) -> bool: + return len(content) > 3 + + +class KeyButton(Button): + def __init__(self, area, content, keyboard, index): + self.keyboard = keyboard + self.index = index + super().__init__(area, content) + + def on_click(self): + self.keyboard.on_key_click(self) + + +class InputButton(Button): + def __init__(self, area, content, word): + super().__init__(area, content) + self.word = word + self.pending_button = None + self.pending_index = None + self.icon = None # rendered icon + self.disable() + + def edit(self, content, word, pending_button, pending_index): + self.word = word + self.content = content + self.pending_button = pending_button + self.pending_index = pending_index + self.repaint = True + if word: + self.enable() + self.normal_style = ButtonMonoConfirm.normal + self.active_style = ButtonMonoConfirm.active + self.icon = ui.ICON_CONFIRM + else: # disabled button + self.disabled_style = ButtonMono.normal + self.disable() + self.icon = None + + def render_content(self, s, ax, ay, aw, ah): + text_style = s.text_style + fg_color = s.fg_color + bg_color = s.bg_color + + tx = ax + 24 # x-offset of the content + ty = ay + ah // 2 + 8 # y-offset of the content + + if not _is_final(self.content): + to_display = len(self.content) * "*" + if self.pending_button: + to_display = ( + to_display[:-1] + self.pending_button.content[self.pending_index] + ) + else: + to_display = self.word + + display.text(tx, ty, to_display, text_style, fg_color, bg_color) + + if self.pending_button and not _is_final(self.content): + width = display.text_width(to_display, text_style) + pw = display.text_width(self.content[-1:], text_style) + px = tx + width - pw + display.bar(px, ty + 2, pw + 1, 3, fg_color) + + if self.icon: + ix = ax + aw - 16 * 2 + iy = ty - 16 + display.icon(ix, iy, res.load(self.icon), fg_color, bg_color) + + +class Prompt(ui.Control): + def __init__(self, prompt): + self.prompt = prompt + self.repaint = True + + def on_render(self): + if self.repaint: + display.bar(0, 8, ui.WIDTH, 60, ui.BG) + display.text(20, 40, self.prompt, ui.BOLD, ui.GREY, ui.BG) + self.repaint = False + + +class Slip39Keyboard(ui.Layout): + def __init__(self, prompt): + self.prompt = Prompt(prompt) + + icon_back = res.load(ui.ICON_BACK) + self.back = Button(ui.grid(0, n_x=4, n_y=4), icon_back, ButtonClear) + self.back.on_click = self.on_back_click + + self.input = InputButton(ui.grid(1, n_x=4, n_y=4, cells_x=3), "", "") + self.input.on_click = self.on_input_click + + self.keys = [ + KeyButton(ui.grid(i + 3, n_y=4), k, self, i + 1) + for i, k in enumerate( + ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz") + ) + ] + self.pending_button = None + self.pending_index = 0 + self.button_sequence = "" + + def dispatch(self, event: int, x: int, y: int): + for btn in self.keys: + btn.dispatch(event, x, y) + if self.input.content: + self.input.dispatch(event, x, y) + self.back.dispatch(event, x, y) + else: + self.prompt.dispatch(event, x, y) + + def on_back_click(self): + # Backspace was clicked, let's delete the last character of input. + self.button_sequence = self.button_sequence[:-1] + self.edit() + + def on_input_click(self): + # Input button was clicked. If the content matches the suggested word, + # let's confirm it, otherwise just auto-complete. + result = self.input.word + if _is_final(self.input.content): + self.button_sequence = "" + self.edit() + self.on_confirm(result) + + def on_key_click(self, btn: KeyButton): + # Key button was clicked. If this button is pending, let's cycle the + # pending character in input. If not, let's just append the first + # character. + if self.pending_button is btn: + index = (self.pending_index + 1) % len(btn.content) + else: + index = 0 + self.button_sequence += str(btn.index) + self.edit(btn, index) + + def on_timeout(self): + # Timeout occurred. Let's redraw to draw asterisks. + self.edit() + + def on_confirm(self, word): + # Word was confirmed by the user. + raise ui.Result(word) + + def edit(self, button: KeyButton = None, index: int = 0): + self.pending_button = button + self.pending_index = index + + # find the completions + mask = 0 + word = "" + if _is_final(self.button_sequence): + word = slip39.button_sequence_to_word(self.button_sequence) + else: + mask = slip39.compute_mask(self.button_sequence) + + # modify the input state + self.input.edit( + self.button_sequence, word, self.pending_button, self.pending_index + ) + + # enable or disable key buttons + for btn in self.keys: + if (not _is_final(self.button_sequence) and btn is button) or check_mask( + mask, btn.index + ): + btn.enable() + else: + btn.disable() + + # invalidate the prompt if we display it next frame + if not self.input.content: + self.prompt.repaint = True + + async def handle_input(self): + touch = loop.wait(io.TOUCH) + timeout = loop.sleep(1000 * 1000 * 1) + spawn_touch = loop.spawn(touch) + spawn_timeout = loop.spawn(touch, timeout) + + while True: + if self.pending_button is not None: + spawn = spawn_timeout + else: + spawn = spawn_touch + result = await spawn + + if touch in spawn.finished: + event, x, y = result + self.dispatch(event, x, y) + else: + self.on_timeout() diff --git a/crypto/Makefile b/crypto/Makefile index 7b018482d..1c5f1672f 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -66,6 +66,7 @@ SRCS += memzero.c SRCS += shamir.c SRCS += hmac_drbg.c SRCS += rfc6979.c +SRCS += slip39.c OBJS = $(SRCS:.c=.o) diff --git a/crypto/slip39.c b/crypto/slip39.c new file mode 100644 index 000000000..9258e637f --- /dev/null +++ b/crypto/slip39.c @@ -0,0 +1,136 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "slip39.h" +#include +#include +#include "slip39_wordlist.h" + +/** + * Returns word on position `index`. + */ +const char* get_word(uint16_t index) { return wordlist[index]; } + +/** + * Finds index of given word, if found. + * Returns true on success and stores result in `index`. + */ +bool word_index(uint16_t* index, const char* word, uint8_t word_length) { + uint16_t lo = 0; + uint16_t hi = WORDS_COUNT; + uint16_t mid = 0; + + while ((hi - lo) > 1) { + mid = (hi + lo) / 2; + if (strncmp(wordlist[mid], word, word_length) > 0) { + hi = mid; + } else { + lo = mid; + } + } + if (strncmp(wordlist[lo], word, word_length) != 0) { + return false; + } + *index = lo; + return true; +} + +/** + * Calculates which buttons still can be pressed after some already were. + * Returns a 9-bit bitmask, where each bit specifies which buttons + * can be further pressed (there are still words in this combination). + * LSB denotes first button. + * + * Example: 110000110 - second, third, eighth and ninth button still can be + * pressed. + */ +uint16_t compute_mask(uint16_t prefix) { return find(prefix, false); } + +/** + * Converts sequence to word index. + */ +const char* button_sequence_to_word(uint16_t prefix) { + return wordlist[find(prefix, true)]; +} + +/** + * Computes mask if find_index is false. + * Finds the first word index that suits the prefix otherwise. + */ +uint16_t find(uint16_t prefix, bool find_index) { + uint16_t min = prefix; + uint16_t max = 0; + uint16_t for_max = 0; + uint8_t multiplier = 0; + uint8_t divider = 0; + uint16_t bitmap = 0; + uint8_t digit = 0; + uint16_t i = 0; + + max = prefix + 1; + while (min < 1000) { + min = min * 10; + max = max * 10; + multiplier++; + } + + // Four char prefix -> the mask is zero + if (!multiplier && !find_index) { + return 0; + } + for_max = min - (min % 1000) + 1000; + + // We can't use binary search because the numbers are not sorted. + // They are sorted using the words' alphabet (so we can use the index). + // Example: axle (1953), beam (1315) + // The first digit is sorted so we can loop only upto `for_max`. + while (words_button_seq[i] < for_max) { + if (words_button_seq[i] >= min && words_button_seq[i] < max) { + if (find_index) { + return i; + } + + switch (multiplier) { + case 1: + divider = 1; + break; + case 2: + divider = 10; + break; + case 3: + divider = 100; + break; + default: + divider = 1; + break; + } + + digit = (words_button_seq[i] / divider) % 10; + bitmap |= 1 << (digit - 1); + } + i++; + } + + return bitmap; +} diff --git a/crypto/slip39.h b/crypto/slip39.h new file mode 100644 index 000000000..ba314303a --- /dev/null +++ b/crypto/slip39.h @@ -0,0 +1,36 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +const char* get_word(uint16_t index); + +bool word_index(uint16_t* index, const char* word, uint8_t word_length); + +uint16_t compute_mask(uint16_t prefix); + +const char* button_sequence_to_word(uint16_t prefix); + +uint16_t find(uint16_t prefix, bool find_index); diff --git a/crypto/slip39_wordlist.h b/crypto/slip39_wordlist.h new file mode 100644 index 000000000..34fe1991d --- /dev/null +++ b/crypto/slip39_wordlist.h @@ -0,0 +1,1238 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#define WORDS_COUNT 1024 + +static const char* const wordlist[WORDS_COUNT] = { + "academic", "acid", "acne", "acquire", "acrobat", "activity", + "actress", "adapt", "adequate", "adjust", "admit", "adorn", + "adult", "advance", "advocate", "afraid", "again", "agency", + "agree", "aide", "aircraft", "airline", "airport", "ajar", + "alarm", "album", "alcohol", "alien", "alive", "alpha", + "already", "alto", "aluminum", "always", "amazing", "ambition", + "amount", "amuse", "analysis", "anatomy", "ancestor", "ancient", + "angel", "angry", "animal", "answer", "antenna", "anxiety", + "apart", "aquatic", "arcade", "arena", "argue", "armed", + "artist", "artwork", "aspect", "auction", "august", "aunt", + "average", "aviation", "avoid", "award", "away", "axis", + "axle", "beam", "beard", "beaver", "become", "bedroom", + "behavior", "being", "believe", "belong", "benefit", "best", + "beyond", "bike", "biology", "birthday", "bishop", "black", + "blanket", "blessing", "blimp", "blind", "blue", "body", + "bolt", "boring", "born", "both", "boundary", "bracelet", + "branch", "brave", "breathe", "briefing", "broken", "brother", + "browser", "bucket", "budget", "building", "bulb", "bulge", + "bumpy", "bundle", "burden", "burning", "busy", "buyer", + "cage", "calcium", "camera", "campus", "canyon", "capacity", + "capital", "capture", "carbon", "cards", "careful", "cargo", + "carpet", "carve", "category", "cause", "ceiling", "center", + "ceramic", "champion", "change", "charity", "check", "chemical", + "chest", "chew", "chubby", "cinema", "civil", "class", + "clay", "cleanup", "client", "climate", "clinic", "clock", + "clogs", "closet", "clothes", "club", "cluster", "coal", + "coastal", "coding", "column", "company", "corner", "costume", + "counter", "course", "cover", "cowboy", "cradle", "craft", + "crazy", "credit", "cricket", "criminal", "crisis", "critical", + "crowd", "crucial", "crunch", "crush", "crystal", "cubic", + "cultural", "curious", "curly", "custody", "cylinder", "daisy", + "damage", "dance", "darkness", "database", "daughter", "deadline", + "deal", "debris", "debut", "decent", "decision", "declare", + "decorate", "decrease", "deliver", "demand", "density", "deny", + "depart", "depend", "depict", "deploy", "describe", "desert", + "desire", "desktop", "destroy", "detailed", "detect", "device", + "devote", "diagnose", "dictate", "diet", "dilemma", "diminish", + "dining", "diploma", "disaster", "discuss", "disease", "dish", + "dismiss", "display", "distance", "dive", "divorce", "document", + "domain", "domestic", "dominant", "dough", "downtown", "dragon", + "dramatic", "dream", "dress", "drift", "drink", "drove", + "drug", "dryer", "duckling", "duke", "duration", "dwarf", + "dynamic", "early", "earth", "easel", "easy", "echo", + "eclipse", "ecology", "edge", "editor", "educate", "either", + "elbow", "elder", "election", "elegant", "element", "elephant", + "elevator", "elite", "else", "email", "emerald", "emission", + "emperor", "emphasis", "employer", "empty", "ending", "endless", + "endorse", "enemy", "energy", "enforce", "engage", "enjoy", + "enlarge", "entrance", "envelope", "envy", "epidemic", "episode", + "equation", "equip", "eraser", "erode", "escape", "estate", + "estimate", "evaluate", "evening", "evidence", "evil", "evoke", + "exact", "example", "exceed", "exchange", "exclude", "excuse", + "execute", "exercise", "exhaust", "exotic", "expand", "expect", + "explain", "express", "extend", "extra", "eyebrow", "facility", + "fact", "failure", "faint", "fake", "false", "family", + "famous", "fancy", "fangs", "fantasy", "fatal", "fatigue", + "favorite", "fawn", "fiber", "fiction", "filter", "finance", + "findings", "finger", "firefly", "firm", "fiscal", "fishing", + "fitness", "flame", "flash", "flavor", "flea", "flexible", + "flip", "float", "floral", "fluff", "focus", "forbid", + "force", "forecast", "forget", "formal", "fortune", "forward", + "founder", "fraction", "fragment", "frequent", "freshman", "friar", + "fridge", "friendly", "frost", "froth", "frozen", "fumes", + "funding", "furl", "fused", "galaxy", "game", "garbage", + "garden", "garlic", "gasoline", "gather", "general", "genius", + "genre", "genuine", "geology", "gesture", "glad", "glance", + "glasses", "glen", "glimpse", "goat", "golden", "graduate", + "grant", "grasp", "gravity", "gray", "greatest", "grief", + "grill", "grin", "grocery", "gross", "group", "grownup", + "grumpy", "guard", "guest", "guilt", "guitar", "gums", + "hairy", "hamster", "hand", "hanger", "harvest", "have", + "havoc", "hawk", "hazard", "headset", "health", "hearing", + "heat", "helpful", "herald", "herd", "hesitate", "hobo", + "holiday", "holy", "home", "hormone", "hospital", "hour", + "huge", "human", "humidity", "hunting", "husband", "hush", + "husky", "hybrid", "idea", "identify", "idle", "image", + "impact", "imply", "improve", "impulse", "include", "income", + "increase", "index", "indicate", "industry", "infant", "inform", + "inherit", "injury", "inmate", "insect", "inside", "install", + "intend", "intimate", "invasion", "involve", "iris", "island", + "isolate", "item", "ivory", "jacket", "jerky", "jewelry", + "join", "judicial", "juice", "jump", "junction", "junior", + "junk", "jury", "justice", "kernel", "keyboard", "kidney", + "kind", "kitchen", "knife", "knit", "laden", "ladle", + "ladybug", "lair", "lamp", "language", "large", "laser", + "laundry", "lawsuit", "leader", "leaf", "learn", "leaves", + "lecture", "legal", "legend", "legs", "lend", "length", + "level", "liberty", "library", "license", "lift", "likely", + "lilac", "lily", "lips", "liquid", "listen", "literary", + "living", "lizard", "loan", "lobe", "location", "losing", + "loud", "loyalty", "luck", "lunar", "lunch", "lungs", + "luxury", "lying", "lyrics", "machine", "magazine", "maiden", + "mailman", "main", "makeup", "making", "mama", "manager", + "mandate", "mansion", "manual", "marathon", "march", "market", + "marvel", "mason", "material", "math", "maximum", "mayor", + "meaning", "medal", "medical", "member", "memory", "mental", + "merchant", "merit", "method", "metric", "midst", "mild", + "military", "mineral", "minister", "miracle", "mixed", "mixture", + "mobile", "modern", "modify", "moisture", "moment", "morning", + "mortgage", "mother", "mountain", "mouse", "move", "much", + "mule", "multiple", "muscle", "museum", "music", "mustang", + "nail", "national", "necklace", "negative", "nervous", "network", + "news", "nuclear", "numb", "numerous", "nylon", "oasis", + "obesity", "object", "observe", "obtain", "ocean", "often", + "olympic", "omit", "oral", "orange", "orbit", "order", + "ordinary", "organize", "ounce", "oven", "overall", "owner", + "paces", "pacific", "package", "paid", "painting", "pajamas", + "pancake", "pants", "papa", "paper", "parcel", "parking", + "party", "patent", "patrol", "payment", "payroll", "peaceful", + "peanut", "peasant", "pecan", "penalty", "pencil", "percent", + "perfect", "permit", "petition", "phantom", "pharmacy", "photo", + "phrase", "physics", "pickup", "picture", "piece", "pile", + "pink", "pipeline", "pistol", "pitch", "plains", "plan", + "plastic", "platform", "playoff", "pleasure", "plot", "plunge", + "practice", "prayer", "preach", "predator", "pregnant", "premium", + "prepare", "presence", "prevent", "priest", "primary", "priority", + "prisoner", "privacy", "prize", "problem", "process", "profile", + "program", "promise", "prospect", "provide", "prune", "public", + "pulse", "pumps", "punish", "puny", "pupal", "purchase", + "purple", "python", "quantity", "quarter", "quick", "quiet", + "race", "racism", "radar", "railroad", "rainbow", "raisin", + "random", "ranked", "rapids", "raspy", "reaction", "realize", + "rebound", "rebuild", "recall", "receiver", "recover", "regret", + "regular", "reject", "relate", "remember", "remind", "remove", + "render", "repair", "repeat", "replace", "require", "rescue", + "research", "resident", "response", "result", "retailer", "retreat", + "reunion", "revenue", "review", "reward", "rhyme", "rhythm", + "rich", "rival", "river", "robin", "rocky", "romantic", + "romp", "roster", "round", "royal", "ruin", "ruler", + "rumor", "sack", "safari", "salary", "salon", "salt", + "satisfy", "satoshi", "saver", "says", "scandal", "scared", + "scatter", "scene", "scholar", "science", "scout", "scramble", + "screw", "script", "scroll", "seafood", "season", "secret", + "security", "segment", "senior", "shadow", "shaft", "shame", + "shaped", "sharp", "shelter", "sheriff", "short", "should", + "shrimp", "sidewalk", "silent", "silver", "similar", "simple", + "single", "sister", "skin", "skunk", "slap", "slavery", + "sled", "slice", "slim", "slow", "slush", "smart", + "smear", "smell", "smirk", "smith", "smoking", "smug", + "snake", "snapshot", "sniff", "society", "software", "soldier", + "solution", "soul", "source", "space", "spark", "speak", + "species", "spelling", "spend", "spew", "spider", "spill", + "spine", "spirit", "spit", "spray", "sprinkle", "square", + "squeeze", "stadium", "staff", "standard", "starting", "station", + "stay", "steady", "step", "stick", "stilt", "story", + "strategy", "strike", "style", "subject", "submit", "sugar", + "suitable", "sunlight", "superior", "surface", "surprise", "survive", + "sweater", "swimming", "swing", "switch", "symbolic", "sympathy", + "syndrome", "system", "tackle", "tactics", "tadpole", "talent", + "task", "taste", "taught", "taxi", "teacher", "teammate", + "teaspoon", "temple", "tenant", "tendency", "tension", "terminal", + "testify", "texture", "thank", "that", "theater", "theory", + "therapy", "thorn", "threaten", "thumb", "thunder", "ticket", + "tidy", "timber", "timely", "ting", "tofu", "together", + "tolerate", "total", "toxic", "tracks", "traffic", "training", + "transfer", "trash", "traveler", "treat", "trend", "trial", + "tricycle", "trip", "triumph", "trouble", "true", "trust", + "twice", "twin", "type", "typical", "ugly", "ultimate", + "umbrella", "uncover", "undergo", "unfair", "unfold", "unhappy", + "union", "universe", "unkind", "unknown", "unusual", "unwrap", + "upgrade", "upstairs", "username", "usher", "usual", "valid", + "valuable", "vampire", "vanish", "various", "vegan", "velvet", + "venture", "verdict", "verify", "very", "veteran", "vexed", + "victim", "video", "view", "vintage", "violence", "viral", + "visitor", "visual", "vitamins", "vocal", "voice", "volume", + "voter", "voting", "walnut", "warmth", "warn", "watch", + "wavy", "wealthy", "weapon", "webcam", "welcome", "welfare", + "western", "width", "wildlife", "window", "wine", "wireless", + "wisdom", "withdraw", "wits", "wolf", "woman", "work", + "worthy", "wrap", "wrist", "writing", "wrote", "year", + "yelp", "yield", "yoga", "zero", +}; + +/** + * This array contains number representations of SLIP-39 words. + * These numbers are determined how the words were entered on a + * T9 keyboard with the following layout: + * ab (1) cd (2) ef (3) + * ghij (4) klm (5) nopq (6) + * rs (7) tuv (8) wxyz (9) + * + * Each word is uniquely defined by four buttons. + */ +static const uint16_t words_button_seq[WORDS_COUNT] = { + 1212, // academic + 1242, // acid + 1263, // acne + 1268, // acquire + 1276, // acrobat + 1284, // activity + 1287, // actress + 1216, // adapt + 1236, // adequate + 1248, // adjust + 1254, // admit + 1267, // adorn + 1285, // adult + 1281, // advance + 1286, // advocate + 1371, // afraid + 1414, // again + 1436, // agency + 1473, // agree + 1423, // aide + 1472, // aircraft + 1475, // airline + 1476, // airport + 1417, // ajar + 1517, // alarm + 1518, // album + 1526, // alcohol + 1543, // alien + 1548, // alive + 1564, // alpha + 1573, // already + 1586, // alto + 1585, // aluminum + 1591, // always + 1519, // amazing + 1514, // ambition + 1568, // amount + 1587, // amuse + 1615, // analysis + 1618, // anatomy + 1623, // ancestor + 1624, // ancient + 1643, // angel + 1647, // angry + 1645, // animal + 1679, // answer + 1683, // antenna + 1694, // anxiety + 1617, // apart + 1681, // aquatic + 1721, // arcade + 1736, // arena + 1748, // argue + 1753, // armed + 1784, // artist + 1789, // artwork + 1763, // aspect + 1828, // auction + 1848, // august + 1868, // aunt + 1837, // average + 1841, // aviation + 1864, // avoid + 1917, // award + 1919, // away + 1947, // axis + 1953, // axle + 1315, // beam + 1317, // beard + 1318, // beaver + 1326, // become + 1327, // bedroom + 1341, // behavior + 1346, // being + 1354, // believe + 1356, // belong + 1363, // benefit + 1378, // best + 1396, // beyond + 1453, // bike + 1465, // biology + 1478, // birthday + 1474, // bishop + 1512, // black + 1516, // blanket + 1537, // blessing + 1545, // blimp + 1546, // blind + 1583, // blue + 1629, // body + 1658, // bolt + 1674, // boring + 1676, // born + 1684, // both + 1686, // boundary + 1712, // bracelet + 1716, // branch + 1718, // brave + 1731, // breathe + 1743, // briefing + 1765, // broken + 1768, // brother + 1769, // browser + 1825, // bucket + 1824, // budget + 1845, // building + 1851, // bulb + 1854, // bulge + 1856, // bumpy + 1862, // bundle + 1872, // burden + 1876, // burning + 1879, // busy + 1893, // buyer + 2143, // cage + 2152, // calcium + 2153, // camera + 2156, // campus + 2169, // canyon + 2161, // capacity + 2164, // capital + 2168, // capture + 2171, // carbon + 2172, // cards + 2173, // careful + 2174, // cargo + 2176, // carpet + 2178, // carve + 2183, // category + 2187, // cause + 2345, // ceiling + 2368, // center + 2371, // ceramic + 2415, // champion + 2416, // change + 2417, // charity + 2432, // check + 2435, // chemical + 2437, // chest + 2439, // chew + 2481, // chubby + 2463, // cinema + 2484, // civil + 2517, // class + 2519, // clay + 2531, // cleanup + 2543, // client + 2545, // climate + 2546, // clinic + 2562, // clock + 2564, // clogs + 2567, // closet + 2568, // clothes + 2581, // club + 2587, // cluster + 2615, // coal + 2617, // coastal + 2624, // coding + 2658, // column + 2656, // company + 2676, // corner + 2678, // costume + 2686, // counter + 2687, // course + 2683, // cover + 2691, // cowboy + 2712, // cradle + 2713, // craft + 2719, // crazy + 2732, // credit + 2742, // cricket + 2745, // criminal + 2747, // crisis + 2748, // critical + 2769, // crowd + 2782, // crucial + 2786, // crunch + 2787, // crush + 2797, // crystal + 2814, // cubic + 2858, // cultural + 2874, // curious + 2875, // curly + 2878, // custody + 2954, // cylinder + 2147, // daisy + 2151, // damage + 2162, // dance + 2175, // darkness + 2181, // database + 2184, // daughter + 2312, // deadline + 2315, // deal + 2317, // debris + 2318, // debut + 2323, // decent + 2324, // decision + 2325, // declare + 2326, // decorate + 2327, // decrease + 2354, // deliver + 2351, // demand + 2367, // density + 2369, // deny + 2361, // depart + 2363, // depend + 2364, // depict + 2365, // deploy + 2372, // describe + 2373, // desert + 2374, // desire + 2375, // desktop + 2378, // destroy + 2381, // detailed + 2383, // detect + 2384, // device + 2386, // devote + 2414, // diagnose + 2428, // dictate + 2438, // diet + 2453, // dilemma + 2454, // diminish + 2464, // dining + 2465, // diploma + 2471, // disaster + 2472, // discuss + 2473, // disease + 2474, // dish + 2475, // dismiss + 2476, // display + 2478, // distance + 2483, // dive + 2486, // divorce + 2628, // document + 2651, // domain + 2653, // domestic + 2654, // dominant + 2684, // dough + 2696, // downtown + 2714, // dragon + 2715, // dramatic + 2731, // dream + 2737, // dress + 2743, // drift + 2746, // drink + 2768, // drove + 2784, // drug + 2793, // dryer + 2825, // duckling + 2853, // duke + 2871, // duration + 2917, // dwarf + 2961, // dynamic + 3175, // early + 3178, // earth + 3173, // easel + 3179, // easy + 3246, // echo + 3254, // eclipse + 3265, // ecology + 3243, // edge + 3248, // editor + 3282, // educate + 3484, // either + 3516, // elbow + 3523, // elder + 3532, // election + 3534, // elegant + 3535, // element + 3536, // elephant + 3538, // elevator + 3548, // elite + 3573, // else + 3514, // email + 3537, // emerald + 3547, // emission + 3563, // emperor + 3564, // emphasis + 3565, // employer + 3568, // empty + 3624, // ending + 3625, // endless + 3626, // endorse + 3635, // enemy + 3637, // energy + 3636, // enforce + 3641, // engage + 3646, // enjoy + 3651, // enlarge + 3687, // entrance + 3683, // envelope + 3689, // envy + 3642, // epidemic + 3647, // episode + 3681, // equation + 3684, // equip + 3717, // eraser + 3762, // erode + 3721, // escape + 3781, // estate + 3784, // estimate + 3815, // evaluate + 3836, // evening + 3842, // evidence + 3845, // evil + 3865, // evoke + 3912, // exact + 3915, // example + 3923, // exceed + 3924, // exchange + 3925, // exclude + 3928, // excuse + 3932, // execute + 3937, // exercise + 3941, // exhaust + 3968, // exotic + 3961, // expand + 3963, // expect + 3965, // explain + 3967, // express + 3983, // extend + 3987, // extra + 3931, // eyebrow + 3124, // facility + 3128, // fact + 3145, // failure + 3146, // faint + 3153, // fake + 3157, // false + 3154, // family + 3156, // famous + 3162, // fancy + 3164, // fangs + 3168, // fantasy + 3181, // fatal + 3184, // fatigue + 3186, // favorite + 3196, // fawn + 3413, // fiber + 3428, // fiction + 3458, // filter + 3461, // finance + 3462, // findings + 3464, // finger + 3473, // firefly + 3475, // firm + 3472, // fiscal + 3474, // fishing + 3486, // fitness + 3515, // flame + 3517, // flash + 3518, // flavor + 3531, // flea + 3539, // flexible + 3546, // flip + 3561, // float + 3567, // floral + 3583, // fluff + 3628, // focus + 3671, // forbid + 3672, // force + 3673, // forecast + 3674, // forget + 3675, // formal + 3678, // fortune + 3679, // forward + 3686, // founder + 3712, // fraction + 3714, // fragment + 3736, // frequent + 3737, // freshman + 3741, // friar + 3742, // fridge + 3743, // friendly + 3767, // frost + 3768, // froth + 3769, // frozen + 3853, // fumes + 3862, // funding + 3875, // furl + 3873, // fused + 4151, // galaxy + 4153, // game + 4171, // garbage + 4172, // garden + 4175, // garlic + 4176, // gasoline + 4184, // gather + 4363, // general + 4364, // genius + 4367, // genre + 4368, // genuine + 4365, // geology + 4378, // gesture + 4512, // glad + 4516, // glance + 4517, // glasses + 4536, // glen + 4545, // glimpse + 4618, // goat + 4652, // golden + 4712, // graduate + 4716, // grant + 4717, // grasp + 4718, // gravity + 4719, // gray + 4731, // greatest + 4743, // grief + 4745, // grill + 4746, // grin + 4762, // grocery + 4767, // gross + 4768, // group + 4769, // grownup + 4785, // grumpy + 4817, // guard + 4837, // guest + 4845, // guilt + 4848, // guitar + 4857, // gums + 4147, // hairy + 4157, // hamster + 4162, // hand + 4164, // hanger + 4178, // harvest + 4183, // have + 4186, // havoc + 4195, // hawk + 4191, // hazard + 4312, // headset + 4315, // health + 4317, // hearing + 4318, // heat + 4356, // helpful + 4371, // herald + 4372, // herd + 4374, // hesitate + 4616, // hobo + 4654, // holiday + 4659, // holy + 4653, // home + 4675, // hormone + 4676, // hospital + 4687, // hour + 4843, // huge + 4851, // human + 4854, // humidity + 4868, // hunting + 4871, // husband + 4874, // hush + 4875, // husky + 4917, // hybrid + 4231, // idea + 4236, // identify + 4253, // idle + 4514, // image + 4561, // impact + 4565, // imply + 4567, // improve + 4568, // impulse + 4625, // include + 4626, // income + 4627, // increase + 4623, // index + 4624, // indicate + 4628, // industry + 4631, // infant + 4636, // inform + 4643, // inherit + 4648, // injury + 4651, // inmate + 4673, // insect + 4674, // inside + 4678, // install + 4683, // intend + 4684, // intimate + 4681, // invasion + 4686, // involve + 4747, // iris + 4751, // island + 4765, // isolate + 4835, // item + 4867, // ivory + 4125, // jacket + 4375, // jerky + 4393, // jewelry + 4646, // join + 4824, // judicial + 4842, // juice + 4856, // jump + 4862, // junction + 4864, // junior + 4865, // junk + 4879, // jury + 4878, // justice + 5376, // kernel + 5391, // keyboard + 5426, // kidney + 5462, // kind + 5482, // kitchen + 5643, // knife + 5648, // knit + 5123, // laden + 5125, // ladle + 5129, // ladybug + 5147, // lair + 5156, // lamp + 5164, // language + 5174, // large + 5173, // laser + 5186, // laundry + 5197, // lawsuit + 5312, // leader + 5313, // leaf + 5317, // learn + 5318, // leaves + 5328, // lecture + 5341, // legal + 5343, // legend + 5347, // legs + 5362, // lend + 5364, // length + 5383, // level + 5413, // liberty + 5417, // library + 5423, // license + 5438, // lift + 5453, // likely + 5451, // lilac + 5459, // lily + 5467, // lips + 5468, // liquid + 5478, // listen + 5483, // literary + 5484, // living + 5491, // lizard + 5616, // loan + 5613, // lobe + 5621, // location + 5674, // losing + 5682, // loud + 5691, // loyalty + 5825, // luck + 5861, // lunar + 5862, // lunch + 5864, // lungs + 5898, // luxury + 5946, // lying + 5974, // lyrics + 5124, // machine + 5141, // magazine + 5142, // maiden + 5145, // mailman + 5146, // main + 5153, // makeup + 5154, // making + 5151, // mama + 5161, // manager + 5162, // mandate + 5167, // mansion + 5168, // manual + 5171, // marathon + 5172, // march + 5175, // market + 5178, // marvel + 5176, // mason + 5183, // material + 5184, // math + 5194, // maximum + 5196, // mayor + 5316, // meaning + 5321, // medal + 5324, // medical + 5351, // member + 5356, // memory + 5368, // mental + 5372, // merchant + 5374, // merit + 5384, // method + 5387, // metric + 5427, // midst + 5452, // mild + 5454, // military + 5463, // mineral + 5464, // minister + 5471, // miracle + 5493, // mixed + 5498, // mixture + 5614, // mobile + 5623, // modern + 5624, // modify + 5647, // moisture + 5653, // moment + 5676, // morning + 5678, // mortgage + 5684, // mother + 5686, // mountain + 5687, // mouse + 5683, // move + 5824, // much + 5853, // mule + 5858, // multiple + 5872, // muscle + 5873, // museum + 5874, // music + 5878, // mustang + 6145, // nail + 6184, // national + 6325, // necklace + 6341, // negative + 6378, // nervous + 6389, // network + 6397, // news + 6825, // nuclear + 6851, // numb + 6853, // numerous + 6956, // nylon + 6174, // oasis + 6137, // obesity + 6143, // object + 6173, // observe + 6181, // obtain + 6231, // ocean + 6383, // often + 6595, // olympic + 6548, // omit + 6715, // oral + 6716, // orange + 6714, // orbit + 6723, // order + 6724, // ordinary + 6741, // organize + 6862, // ounce + 6836, // oven + 6837, // overall + 6963, // owner + 6123, // paces + 6124, // pacific + 6125, // package + 6142, // paid + 6146, // painting + 6141, // pajamas + 6162, // pancake + 6168, // pants + 6161, // papa + 6163, // paper + 6172, // parcel + 6175, // parking + 6178, // party + 6183, // patent + 6187, // patrol + 6195, // payment + 6197, // payroll + 6312, // peaceful + 6316, // peanut + 6317, // peasant + 6321, // pecan + 6361, // penalty + 6362, // pencil + 6372, // percent + 6373, // perfect + 6375, // permit + 6384, // petition + 6416, // phantom + 6417, // pharmacy + 6468, // photo + 6471, // phrase + 6497, // physics + 6425, // pickup + 6428, // picture + 6432, // piece + 6453, // pile + 6465, // pink + 6463, // pipeline + 6478, // pistol + 6482, // pitch + 6514, // plains + 6516, // plan + 6517, // plastic + 6518, // platform + 6519, // playoff + 6531, // pleasure + 6568, // plot + 6586, // plunge + 6712, // practice + 6719, // prayer + 6731, // preach + 6732, // predator + 6734, // pregnant + 6735, // premium + 6736, // prepare + 6737, // presence + 6738, // prevent + 6743, // priest + 6745, // primary + 6746, // priority + 6747, // prisoner + 6748, // privacy + 6749, // prize + 6761, // problem + 6762, // process + 6763, // profile + 6764, // program + 6765, // promise + 6767, // prospect + 6768, // provide + 6786, // prune + 6815, // public + 6857, // pulse + 6856, // pumps + 6864, // punish + 6869, // puny + 6861, // pupal + 6872, // purchase + 6876, // purple + 6984, // python + 6816, // quantity + 6817, // quarter + 6842, // quick + 6843, // quiet + 7123, // race + 7124, // racism + 7121, // radar + 7145, // railroad + 7146, // rainbow + 7147, // raisin + 7162, // random + 7165, // ranked + 7164, // rapids + 7176, // raspy + 7312, // reaction + 7315, // realize + 7316, // rebound + 7318, // rebuild + 7321, // recall + 7323, // receiver + 7326, // recover + 7347, // regret + 7348, // regular + 7343, // reject + 7351, // relate + 7353, // remember + 7354, // remind + 7356, // remove + 7362, // render + 7361, // repair + 7363, // repeat + 7365, // replace + 7368, // require + 7372, // rescue + 7373, // research + 7374, // resident + 7376, // response + 7378, // result + 7381, // retailer + 7387, // retreat + 7386, // reunion + 7383, // revenue + 7384, // review + 7391, // reward + 7495, // rhyme + 7498, // rhythm + 7424, // rich + 7481, // rival + 7483, // river + 7614, // robin + 7625, // rocky + 7651, // romantic + 7656, // romp + 7678, // roster + 7686, // round + 7691, // royal + 7846, // ruin + 7853, // ruler + 7856, // rumor + 7125, // sack + 7131, // safari + 7151, // salary + 7156, // salon + 7158, // salt + 7184, // satisfy + 7186, // satoshi + 7183, // saver + 7197, // says + 7216, // scandal + 7217, // scared + 7218, // scatter + 7236, // scene + 7246, // scholar + 7243, // science + 7268, // scout + 7271, // scramble + 7273, // screw + 7274, // script + 7276, // scroll + 7313, // seafood + 7317, // season + 7327, // secret + 7328, // security + 7345, // segment + 7364, // senior + 7412, // shadow + 7413, // shaft + 7415, // shame + 7416, // shaped + 7417, // sharp + 7435, // shelter + 7437, // sheriff + 7467, // short + 7468, // should + 7474, // shrimp + 7423, // sidewalk + 7453, // silent + 7458, // silver + 7454, // similar + 7456, // simple + 7464, // single + 7478, // sister + 7546, // skin + 7586, // skunk + 7516, // slap + 7518, // slavery + 7532, // sled + 7542, // slice + 7545, // slim + 7569, // slow + 7587, // slush + 7517, // smart + 7531, // smear + 7535, // smell + 7547, // smirk + 7548, // smith + 7565, // smoking + 7584, // smug + 7615, // snake + 7616, // snapshot + 7643, // sniff + 7624, // society + 7638, // software + 7652, // soldier + 7658, // solution + 7685, // soul + 7687, // source + 7612, // space + 7617, // spark + 7631, // speak + 7632, // species + 7635, // spelling + 7636, // spend + 7639, // spew + 7642, // spider + 7645, // spill + 7646, // spine + 7647, // spirit + 7648, // spit + 7671, // spray + 7674, // sprinkle + 7681, // square + 7683, // squeeze + 7812, // stadium + 7813, // staff + 7816, // standard + 7817, // starting + 7818, // station + 7819, // stay + 7831, // steady + 7836, // step + 7842, // stick + 7845, // stilt + 7867, // story + 7871, // strategy + 7874, // strike + 7895, // style + 7814, // subject + 7815, // submit + 7841, // sugar + 7848, // suitable + 7865, // sunlight + 7863, // superior + 7873, // surface + 7876, // surprise + 7878, // survive + 7931, // sweater + 7945, // swimming + 7946, // swing + 7948, // switch + 7951, // symbolic + 7956, // sympathy + 7962, // syndrome + 7978, // system + 8125, // tackle + 8128, // tactics + 8126, // tadpole + 8153, // talent + 8175, // task + 8178, // taste + 8184, // taught + 8194, // taxi + 8312, // teacher + 8315, // teammate + 8317, // teaspoon + 8356, // temple + 8361, // tenant + 8362, // tendency + 8367, // tension + 8375, // terminal + 8378, // testify + 8398, // texture + 8416, // thank + 8418, // that + 8431, // theater + 8436, // theory + 8437, // therapy + 8467, // thorn + 8473, // threaten + 8485, // thumb + 8486, // thunder + 8425, // ticket + 8429, // tidy + 8451, // timber + 8453, // timely + 8464, // ting + 8638, // tofu + 8643, // together + 8653, // tolerate + 8681, // total + 8694, // toxic + 8712, // tracks + 8713, // traffic + 8714, // training + 8716, // transfer + 8717, // trash + 8718, // traveler + 8731, // treat + 8736, // trend + 8741, // trial + 8742, // tricycle + 8746, // trip + 8748, // triumph + 8768, // trouble + 8783, // true + 8787, // trust + 8942, // twice + 8946, // twin + 8963, // type + 8964, // typical + 8459, // ugly + 8584, // ultimate + 8517, // umbrella + 8626, // uncover + 8623, // undergo + 8631, // unfair + 8636, // unfold + 8641, // unhappy + 8646, // union + 8648, // universe + 8654, // unkind + 8656, // unknown + 8687, // unusual + 8697, // unwrap + 8647, // upgrade + 8678, // upstairs + 8737, // username + 8743, // usher + 8781, // usual + 8154, // valid + 8158, // valuable + 8156, // vampire + 8164, // vanish + 8174, // various + 8341, // vegan + 8358, // velvet + 8368, // venture + 8372, // verdict + 8374, // verify + 8379, // very + 8383, // veteran + 8393, // vexed + 8428, // victim + 8423, // video + 8439, // view + 8468, // vintage + 8465, // violence + 8471, // viral + 8474, // visitor + 8478, // visual + 8481, // vitamins + 8621, // vocal + 8642, // voice + 8658, // volume + 8683, // voter + 8684, // voting + 9156, // walnut + 9175, // warmth + 9176, // warn + 9182, // watch + 9189, // wavy + 9315, // wealthy + 9316, // weapon + 9312, // webcam + 9352, // welcome + 9353, // welfare + 9378, // western + 9428, // width + 9452, // wildlife + 9462, // window + 9463, // wine + 9473, // wireless + 9472, // wisdom + 9484, // withdraw + 9487, // wits + 9653, // wolf + 9651, // woman + 9675, // work + 9678, // worthy + 9716, // wrap + 9747, // wrist + 9748, // writing + 9768, // wrote + 9317, // year + 9356, // yelp + 9435, // yield + 9641, // yoga + 9376, // zero +}; diff --git a/crypto/tests/test_check.c b/crypto/tests/test_check.c index 310640ced..2170c25da 100644 --- a/crypto/tests/test_check.c +++ b/crypto/tests/test_check.c @@ -67,6 +67,7 @@ #include "sha2.h" #include "sha3.h" #include "shamir.h" +#include "slip39.h" #if VALGRIND /* @@ -5096,6 +5097,96 @@ START_TEST(test_mnemonic_to_entropy) { } END_TEST +START_TEST(test_slip39_get_word) { + static const struct { + const int index; + const char *expected_word; + } vectors[] = {{573, "member"}, + {0, "academic"}, + {1023, "zero"}, + {245, "drove"}, + {781, "satoshi"}}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + const char *a = get_word(vectors[i].index); + ck_assert_str_eq(a, vectors[i].expected_word); + } +} +END_TEST + +START_TEST(test_slip39_word_index) { + uint16_t index; + static const struct { + const char *word; + bool expected_result; + uint16_t expected_index; + } vectors[] = {{"academic", true, 0}, + {"zero", true, 1023}, + {"drove", true, 245}, + {"satoshi", true, 781}, + {"member", true, 573}, + // 9999 value is never checked since the word is not in list + {"fakeword", false, 9999}}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + bool result = word_index(&index, vectors[i].word, sizeof(vectors[i].word)); + ck_assert_int_eq(result, vectors[i].expected_result); + if (result) { + ck_assert_int_eq(index, vectors[i].expected_index); + } + } +} +END_TEST + +START_TEST(test_slip39_compute_mask) { + static const struct { + const uint16_t prefix; + const uint16_t expected_mask; + } vectors[] = {{ + 12, + 0xFD // 011111101 + }, + { + 21, + 0xF8 // 011111000 + }, + { + 75, + 0xAD // 010101101 + }, + { + 4, + 0x1F7 // 111110111 + }, + { + 738, + 0x6D // 001101101 + }, + { + 9, + 0x6D // 001101101 + }}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + uint16_t mask = compute_mask(vectors[i].prefix); + ck_assert_int_eq(mask, vectors[i].expected_mask); + } +} +END_TEST + +START_TEST(test_slip39_sequence_to_word) { + static const struct { + const uint16_t prefix; + const char *expected_word; + } vectors[] = {{7945, "swimming"}, + {646, "photo"}, + {5, "kernel"}, + {34, "either"}, + {62, "ocean"}}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + const char *word = button_sequence_to_word(vectors[i].prefix); + ck_assert_str_eq(word, vectors[i].expected_word); + } +} +END_TEST + START_TEST(test_shamir) { #define SHAMIR_MAX_COUNT 16 static const struct { @@ -8676,6 +8767,13 @@ Suite *test_suite(void) { tcase_add_test(tc, test_mnemonic_to_entropy); suite_add_tcase(s, tc); + tc = tcase_create("slip39"); + tcase_add_test(tc, test_slip39_get_word); + tcase_add_test(tc, test_slip39_word_index); + tcase_add_test(tc, test_slip39_compute_mask); + tcase_add_test(tc, test_slip39_sequence_to_word); + suite_add_tcase(s, tc); + tc = tcase_create("shamir"); tcase_add_test(tc, test_shamir); suite_add_tcase(s, tc);