mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-05 23:18:25 +00:00
12- and 24-word dice roll seed creation
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
****************************************************************************"""
|
||||
|
||||
Reference in New Issue
Block a user