12- and 24-word dice roll seed creation

This commit is contained in:
kdmukai
2022-03-23 00:29:10 -05:00
parent 34e477b063
commit dac78b3a35
5 changed files with 221 additions and 7 deletions
+3
View File
@@ -180,6 +180,9 @@ class Fonts(Singleton):
if font_name not in cls.fonts:
cls.fonts[font_name] = {}
if font_name in [GUIConstants.ICON_FONT_NAME__FONT_AWESOME, GUIConstants.ICON_FONT_NAME__SEEDSIGNER]:
file_extension = "otf"
if size not in cls.fonts[font_name]:
try:
cls.fonts[font_name][size] = ImageFont.truetype(os.path.join(cls.font_path, f"{font_name}.{file_extension}"), size)
+3 -1
View File
@@ -162,6 +162,8 @@ class Keyboard:
def __init__(self,
draw: ImageDraw,
charset="1234567890abcdefghijklmnopqrstuvwxyz",
font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME,
font_size=24,
selected_char="a",
rows=4,
cols=10,
@@ -179,7 +181,7 @@ class Keyboard:
self.rows = rows
self.cols = cols
self.rect = rect
self.font = Fonts.get_font(GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, 24)
self.font = Fonts.get_font(font_name, font_size)
self.auto_wrap = auto_wrap
self.background_color = GUIConstants.BUTTON_BACKGROUND_COLOR
+155 -4
View File
@@ -1,11 +1,10 @@
import time
from dataclasses import dataclass
from PIL.Image import Image
from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay
from seedsigner.hardware.camera import Camera
from seedsigner.gui.components import Fonts, GUIConstants, IconTextLine, SeedSignerCustomIconConstants, TextArea
from seedsigner.gui.components import FontAwesomeIconConstants, Fonts, GUIConstants, IconTextLine, SeedSignerCustomIconConstants, TextArea
from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, BaseScreen, ButtonListScreen
from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, BaseScreen, BaseTopNavScreen, ButtonListScreen
from seedsigner.hardware.buttons import HardwareButtonsConstants
@@ -82,6 +81,7 @@ class ToolsImageEntropyLivePreviewScreen(BaseScreen):
return preview_images
@dataclass
class ToolsImageEntropyFinalImageScreen(BaseScreen):
final_image: Image = None
@@ -110,6 +110,157 @@ class ToolsImageEntropyFinalImageScreen(BaseScreen):
@dataclass
class ToolsDiceEntropyEntryScreen(BaseTopNavScreen):
total_rolls: int = None
def __post_init__(self):
self.title = f"Dice Roll 1/{self.total_rolls}"
super().__post_init__()
self.dice_rolls = ""
# Set up the keyboard params
keyboard_width = self.canvas_width - 2*GUIConstants.EDGE_PADDING
text_entry_display_y = self.top_nav.height
text_entry_display_height = 30
keyboard_start_y = text_entry_display_y + text_entry_display_height + GUIConstants.COMPONENT_PADDING
rows = 3
button_height = int((self.canvas_height - GUIConstants.EDGE_PADDING - text_entry_display_y - text_entry_display_height - GUIConstants.COMPONENT_PADDING - (rows - 1) * 2) / rows)
self.keyboard_digits = Keyboard(
draw=self.renderer.draw,
charset="".join([
FontAwesomeIconConstants.DICE_ONE,
FontAwesomeIconConstants.DICE_TWO,
FontAwesomeIconConstants.DICE_THREE,
FontAwesomeIconConstants.DICE_FOUR,
FontAwesomeIconConstants.DICE_FIVE,
FontAwesomeIconConstants.DICE_SIX,
]),
font_name=GUIConstants.ICON_FONT_NAME__FONT_AWESOME,
font_size=button_height - GUIConstants.COMPONENT_PADDING,
rows=rows,
cols=3,
rect=(
GUIConstants.EDGE_PADDING,
keyboard_start_y,
GUIConstants.EDGE_PADDING + keyboard_width,
keyboard_start_y + rows * button_height + (rows - 1) * 2
),
auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT],
render_now=False
)
self.keyboard_digits.set_selected_key(selected_letter=FontAwesomeIconConstants.DICE_ONE)
self.text_entry_display = TextEntryDisplay(
canvas=self.renderer.canvas,
rect=(
GUIConstants.EDGE_PADDING,
text_entry_display_y,
self.canvas_width - GUIConstants.EDGE_PADDING,
text_entry_display_y + text_entry_display_height
),
cursor_mode=TextEntryDisplay.CURSOR_MODE__BAR,
is_centered=False,
)
def _render(self):
super()._render()
self.keyboard_digits.render_keys()
self.text_entry_display.render()
self.renderer.show_image()
def _run(self):
cursor_position = len(self.dice_rolls)
# Start the interactive update loop
while True:
input = self.hw_inputs.wait_for(
HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN + [HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY3],
check_release=True,
release_keys=[HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY3]
)
# Check possible exit condition
if self.top_nav.is_selected and input == HardwareButtonsConstants.KEY_PRESS:
return RET_CODE__BACK_BUTTON
# Process normal input
if input in [HardwareButtonsConstants.KEY_UP, HardwareButtonsConstants.KEY_DOWN] and self.top_nav.is_selected:
# We're navigating off the previous button
self.top_nav.is_selected = False
self.top_nav.render_buttons()
# Override the actual input w/an ENTER signal for the Keyboard
if input == HardwareButtonsConstants.KEY_DOWN:
input = Keyboard.ENTER_TOP
else:
input = Keyboard.ENTER_BOTTOM
elif input in [HardwareButtonsConstants.KEY_LEFT, HardwareButtonsConstants.KEY_RIGHT] and self.top_nav.is_selected:
# ignore
continue
ret_val = self.keyboard_digits.update_from_input(input)
# Now process the result from the keyboard
if ret_val in Keyboard.EXIT_DIRECTIONS:
self.top_nav.is_selected = True
self.top_nav.render_buttons()
elif ret_val in Keyboard.ADDITIONAL_KEYS and input == HardwareButtonsConstants.KEY_PRESS:
if ret_val == Keyboard.KEY_BACKSPACE["code"]:
if len(self.dice_rolls) > 0:
self.dice_rolls = self.dice_rolls[:-1]
cursor_position -= 1
elif input == HardwareButtonsConstants.KEY_PRESS and ret_val not in Keyboard.ADDITIONAL_KEYS:
# User has locked in the current letter
if ret_val == FontAwesomeIconConstants.DICE_ONE:
ret_val = "1"
elif ret_val == FontAwesomeIconConstants.DICE_TWO:
ret_val = "2"
elif ret_val == FontAwesomeIconConstants.DICE_THREE:
ret_val = "3"
elif ret_val == FontAwesomeIconConstants.DICE_FOUR:
ret_val = "4"
elif ret_val == FontAwesomeIconConstants.DICE_FIVE:
ret_val = "5"
elif ret_val == FontAwesomeIconConstants.DICE_SIX:
ret_val = "6"
self.dice_rolls += ret_val
cursor_position += 1
if cursor_position == self.total_rolls:
return self.dice_rolls
# Render a new TextArea over the TopNav title bar
TextArea(
text=f"Dice Roll {cursor_position + 1}/{self.total_rolls}",
font_name=GUIConstants.TOP_NAV_TITLE_FONT_NAME,
font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE,
height=self.top_nav.height,
).render()
self.top_nav.render_buttons()
elif input in HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN:
# Live joystick movement; haven't locked this new letter in yet.
# Leave current spot blank for now. Only update the active keyboard keys
# when a selection has been locked in (KEY_PRESS) or removed ("del").
pass
# Render the text entry display and cursor block
self.text_entry_display.render(self.dice_rolls)
self.renderer.show_image()
@dataclass
class ToolsCalcFinalWordShowFinalWordScreen(ButtonListScreen):
final_word: str = None
@@ -43,6 +43,10 @@ def generate_mnemonic_from_bytes(entropy_bytes) -> List[str]:
def generate_mnemonic_from_dice(roll_data: str) -> List[str]:
entropy_bytes = hashlib.sha256(roll_data.encode()).digest()
if len(roll_data) == 50:
# 12-word mnemonic; only use 128bits / 16 bytes
entropy_bytes = entropy_bytes[:16]
# Return as a list
return bip39.mnemonic_from_bytes(entropy_bytes).split()
+56 -2
View File
@@ -9,7 +9,7 @@ from seedsigner.gui.screens.screen import LargeButtonScreen
from seedsigner.hardware.camera import Camera
from seedsigner.gui.components import FontAwesomeIconConstants
from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen)
from seedsigner.gui.screens.tools_screens import ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordShowFinalWordScreen
from seedsigner.gui.screens.tools_screens import ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordShowFinalWordScreen
from seedsigner.helpers import mnemonic_generation
from seedsigner.models.seed import Seed
from seedsigner.models.settings_definition import SettingsConstants
@@ -39,7 +39,7 @@ class ToolsMenuView(View):
return Destination(ToolsImageEntropyLivePreviewView)
elif button_data[selected_menu_num] == DICE:
return Destination(NotYetImplementedView)
return Destination(ToolsDiceEntropyMnemonicLengthView)
elif button_data[selected_menu_num] == KEYBOARD:
return Destination(ToolsCalcFinalWordNumWordsView)
@@ -166,6 +166,60 @@ class ToolsImageEntropyMnemonicLengthView(View):
"""****************************************************************************
Dice rolls Views
****************************************************************************"""
class ToolsDiceEntropyMnemonicLengthView(View):
def run(self):
TWELVE = "12 words (50 rolls)"
TWENTY_FOUR = "24 words (99 rolls)"
button_data = [TWELVE, TWENTY_FOUR]
selected_menu_num = ButtonListScreen(
title="Mnemonic Length",
is_bottom_list=True,
is_button_text_centered=True,
button_data=button_data,
).display()
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
elif button_data[selected_menu_num] == TWELVE:
return Destination(ToolsDiceEntropyEntryView, view_args=dict(total_rolls=50))
elif button_data[selected_menu_num] == TWENTY_FOUR:
return Destination(ToolsDiceEntropyEntryView, view_args=dict(total_rolls=99))
class ToolsDiceEntropyEntryView(View):
def __init__(self, total_rolls: int):
super().__init__()
self.total_rolls = total_rolls
def run(self):
ret = ToolsDiceEntropyEntryScreen(
total_rolls=self.total_rolls,
).display()
if ret == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
print(ret)
dice_seed_phrase = mnemonic_generation.generate_mnemonic_from_dice(ret)
print(dice_seed_phrase)
# Add the mnemonic as an in-memory Seed
seed = Seed(dice_seed_phrase, wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE))
self.controller.storage.set_pending_seed(seed)
# Cannot return BACK to this View
return Destination(SeedWordsWarningView, view_args={"seed_num": None}, clear_history=True)
"""****************************************************************************
Calc final word Views
****************************************************************************"""