parent
132519123e
commit
49d6a35249
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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,
|
||||||
|
};
|
@ -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()
|
@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#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;
|
||||||
|
}
|
@ -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 <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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);
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue