From 2b0ffbb1626888ccde7ff28f58845742e2782c4f Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:17:38 +0100 Subject: [PATCH 01/11] Update tools_views.py - add menu entry and views --- src/seedsigner/views/tools_views.py | 60 ++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index eabb1171c..8372c8d3f 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -12,7 +12,7 @@ from seedsigner.gui.components import FontAwesomeIconConstants, GUIConstants, SeedSignerIconConstants from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen) from seedsigner.gui.screens.tools_screens import (ToolsCalcFinalWordDoneScreen, ToolsCalcFinalWordFinalizePromptScreen, - ToolsCalcFinalWordScreen, ToolsCoinFlipEntryScreen, ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, + ToolsCalcFinalWordScreen, ToolsCoinFlipEntryScreen, ToolsDiceEntropyEntryScreen, ToolsCoinEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsAddressExplorerAddressTypeScreen) from seedsigner.helpers import embit_utils, mnemonic_generation from seedsigner.models.encode_qr import GenericStaticQrEncoder @@ -28,12 +28,13 @@ class ToolsMenuView(View): IMAGE = (" New seed", FontAwesomeIconConstants.CAMERA) DICE = ("New seed", FontAwesomeIconConstants.DICE) + COIN = ("New seed", FontAwesomeIconConstants.COINS) KEYBOARD = ("Calc 12th/24th word", FontAwesomeIconConstants.KEYBOARD) ADDRESS_EXPLORER = "Address Explorer" VERIFY_ADDRESS = "Verify address" def run(self): - button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.ADDRESS_EXPLORER, self.VERIFY_ADDRESS] + button_data = [self.IMAGE, self.DICE, self.COIN, self.KEYBOARD, self.ADDRESS_EXPLORER, self.VERIFY_ADDRESS] selected_menu_num = self.run_screen( ButtonListScreen, @@ -51,6 +52,9 @@ def run(self): elif button_data[selected_menu_num] == self.DICE: return Destination(ToolsDiceEntropyMnemonicLengthView) + elif button_data[selected_menu_num] == self.COIN: + return Destination(ToolsCoinEntropyMnemonicLengthView) + elif button_data[selected_menu_num] == self.KEYBOARD: return Destination(ToolsCalcFinalWordNumWordsView) @@ -236,6 +240,58 @@ def run(self): +"""**************************************************************************** + Coin flips Views +****************************************************************************""" +class ToolsCoinEntropyMnemonicLengthView(View): + def run(self): + TWELVE = f"12 words ({mnemonic_generation.COIN__NUM_FLIPS__12WORD} flips)" + TWENTY_FOUR = f"24 words ({mnemonic_generation.COIN__NUM_FLIPS__24WORD} flips)" + + 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(ToolsCoinEntropyEntryView, view_args=dict(total_flips=mnemonic_generation.COIN__NUM_FLIPS__12WORD)) + + elif button_data[selected_menu_num] == TWENTY_FOUR: + return Destination(ToolsCoinEntropyEntryView, view_args=dict(total_flips=mnemonic_generation.COIN__NUM_FLIPS__24WORD)) + + + +class ToolsCoinEntropyEntryView(View): + def __init__(self, total_flips: int): + super().__init__() + self.total_flips = total_flips + + + def run(self): + ret = ToolsCoinEntropyEntryScreen( + return_after_n_chars=self.total_flips, + ).display() + + if ret == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + coin_seed_phrase = mnemonic_generation.generate_mnemonic_from_coin_flips(ret) + + # Add the mnemonic as an in-memory Seed + seed = Seed(coin_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 ****************************************************************************""" From 63a7bc47c9bd16acd2c142743e4d2191f31a9489 Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:18:59 +0100 Subject: [PATCH 02/11] Update components.py - add icons (coins ion for the seed menu and T/H for head and tails) --- src/seedsigner/gui/components.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index 9193e451b..d16348732 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -89,6 +89,9 @@ class FontAwesomeIconConstants: DICE_FOUR = "\uf524" DICE_FIVE = "\uf523" DICE_SIX = "\uf526" + COINS = "\uf51e" + COIN_H = "\u0048" # H for heads + COIN_T = "\u0054" # T for tails KEYBOARD = "\uf11c" LOCK = "\uf023" MAP = "\uf279" From dbfa20cc13b9cf744008b9ae00b4a91493456e92 Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:20:36 +0100 Subject: [PATCH 03/11] Update tools_screens.py - add coin flip screen --- src/seedsigner/gui/screens/tools_screens.py | 32 +++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index 13fa33beb..8494764e5 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -164,6 +164,38 @@ def update_title(self) -> bool: +@dataclass +class ToolsCoinEntropyEntryScreen(KeyboardScreen): + def __post_init__(self): + # Override values set by the parent class + self.title = f"Coin Flip 1/{self.return_after_n_chars}" + + # Specify the keys in the keyboard + self.rows = 1 + self.cols = 2 + self.keyboard_font_name = GUIConstants.ICON_FONT_NAME__FONT_AWESOME + self.keyboard_font_size = None # Force auto-scaling to Key height + self.keys_charset = "".join([ + FontAwesomeIconConstants.COIN_T, + FontAwesomeIconConstants.COIN_H, + ]) + + # Map Key display chars to actual output values + self.keys_to_values = { + FontAwesomeIconConstants.COIN_H: "1", + FontAwesomeIconConstants.COIN_T: "0", + } + + # Now initialize the parent class + super().__post_init__() + + + def update_title(self) -> bool: + self.title = f"Coin Flip {self.cursor_position + 1}/{self.return_after_n_chars}" + return True + + + @dataclass class ToolsCalcFinalWordFinalizePromptScreen(ButtonListScreen): mnemonic_length: int = None From 5b3a1f3346a1f547028ee9252971bd3dbd622ed3 Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:21:29 +0100 Subject: [PATCH 04/11] Update mnemonic_generation.py - add number of flips constants --- src/seedsigner/helpers/mnemonic_generation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/seedsigner/helpers/mnemonic_generation.py b/src/seedsigner/helpers/mnemonic_generation.py index b3e90680e..b5f99d456 100644 --- a/src/seedsigner/helpers/mnemonic_generation.py +++ b/src/seedsigner/helpers/mnemonic_generation.py @@ -16,6 +16,8 @@ DICE__NUM_ROLLS__12WORD = 50 DICE__NUM_ROLLS__24WORD = 99 +COIN__NUM_FLIPS__12WORD = 128 +COIN__NUM_FLIPS__24WORD = 256 From 323ed7d8945f306a9dc7ac4ad6247ea5d4d9b53a Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:26:39 +0100 Subject: [PATCH 05/11] Update tools_screens.py - change layout to 3, 3 similar to the dice screen --- src/seedsigner/gui/screens/tools_screens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index 8494764e5..a08a76fb5 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -171,8 +171,8 @@ def __post_init__(self): self.title = f"Coin Flip 1/{self.return_after_n_chars}" # Specify the keys in the keyboard - self.rows = 1 - self.cols = 2 + self.rows = 3 + self.cols = 3 self.keyboard_font_name = GUIConstants.ICON_FONT_NAME__FONT_AWESOME self.keyboard_font_size = None # Force auto-scaling to Key height self.keys_charset = "".join([ From 4d5a5c3a89fa040fb948c9cb7c2240f8abada0b4 Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:30:20 +0100 Subject: [PATCH 06/11] Update tools_screens.py - change order of H and T --- src/seedsigner/gui/screens/tools_screens.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index a08a76fb5..0aee83118 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -176,8 +176,8 @@ def __post_init__(self): self.keyboard_font_name = GUIConstants.ICON_FONT_NAME__FONT_AWESOME self.keyboard_font_size = None # Force auto-scaling to Key height self.keys_charset = "".join([ - FontAwesomeIconConstants.COIN_T, FontAwesomeIconConstants.COIN_H, + FontAwesomeIconConstants.COIN_T, ]) # Map Key display chars to actual output values From 4361ee233f9450f624674b1dab96620bf5f243cc Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:43:56 +0100 Subject: [PATCH 07/11] Update tools_views.py - add missing space in menu entry --- src/seedsigner/views/tools_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 8372c8d3f..115f29132 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -28,7 +28,7 @@ class ToolsMenuView(View): IMAGE = (" New seed", FontAwesomeIconConstants.CAMERA) DICE = ("New seed", FontAwesomeIconConstants.DICE) - COIN = ("New seed", FontAwesomeIconConstants.COINS) + COIN = (" New seed", FontAwesomeIconConstants.COINS) KEYBOARD = ("Calc 12th/24th word", FontAwesomeIconConstants.KEYBOARD) ADDRESS_EXPLORER = "Address Explorer" VERIFY_ADDRESS = "Verify address" From 1fb739cf0bbd679c6a5ec40b29f7de527c79c3fb Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 04:33:13 +0100 Subject: [PATCH 08/11] Update test_mnemonic_generation.py - add pytest tests similar to the tests for dice rolls --- tests/test_mnemonic_generation.py | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/test_mnemonic_generation.py b/tests/test_mnemonic_generation.py index a1f46e85d..be2f123b3 100644 --- a/tests/test_mnemonic_generation.py +++ b/tests/test_mnemonic_generation.py @@ -30,6 +30,29 @@ def test_dice_rolls(): +def test_coin_flips(): + """ Given random coin flips, the resulting mnemonic should be valid. """ + coin_flips = "" + for i in range(0, mnemonic_generation.COIN__NUM_FLIPS__24WORD): + # Do not need truly rigorous random for this test + coin_flips += str(random.randint(0, 1)) + + mnemonic = mnemonic_generation.generate_mnemonic_from_coin_flips(coin_flips) + + assert len(mnemonic) == 24 + assert bip39.mnemonic_is_valid(" ".join(mnemonic)) + + coin_flips = "" + for i in range(0, mnemonic_generation.COIN__NUM_FLIPS__12WORD): + # Do not need truly rigorous random for this test + coin_flips += str(random.randint(0, 1)) + + mnemonic = mnemonic_generation.generate_mnemonic_from_coin_flips(coin_flips) + assert len(mnemonic) == 12 + assert bip39.mnemonic_is_valid(" ".join(mnemonic)) + + + def test_calculate_checksum_input_type(): """ Given an 11-word or 23-word mnemonic, the calculated checksum should yield a @@ -195,3 +218,59 @@ def test_50_dice_rolls(): actual = " ".join(mnemonic) assert bip39.mnemonic_is_valid(actual) assert actual == expected + + + +def test_256_coin_flips(): + """ 256 coin flips input should yield the same 24-word mnemonic as iancoleman.io/bip39 """ + # Check "Show entropy details", paste in coin flip sequence, click "Binary", select "Mnemonic Length" as "24 Words" + coin_flips = "1010101010101110110001000000001100100011000000000011000000001001110000000000000010000000000110000001110010110010000100110011001010101010101011101100010000000011001000110000000000110000000010011100000000000000100000000001100000011100101100100001001100110010" + expected = "grocery secret mountain turkey moment elbow size castle eagle water nominee general usual awesome attack daring cruise marriage debate tortoise document capital avocado engine" + + mnemonic = mnemonic_generation.generate_mnemonic_from_coin_flips(coin_flips) + actual = " ".join(mnemonic) + assert bip39.mnemonic_is_valid(actual) + assert actual == expected + + coin_flips = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + expected = "gun library main saddle doctor meat pizza bone brave output matter chef merry flag abuse puppy first rotate era tent news arrest pepper finger" + + mnemonic = mnemonic_generation.generate_mnemonic_from_coin_flips(coin_flips) + actual = " ".join(mnemonic) + assert bip39.mnemonic_is_valid(actual) + assert actual == expected + + coin_flips = "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + expected = "rural oval civil ignore moon glide any pony perfect gain stable flag fortune require roast stereo mad guitar page flat reduce give borrow leisure" + + mnemonic = mnemonic_generation.generate_mnemonic_from_coin_flips(coin_flips) + actual = " ".join(mnemonic) + assert bip39.mnemonic_is_valid(actual) + assert actual == expected + + + +def test_128_dice_rolls(): + """ 128 coin flips input should yield the same 12-word mnemonic as iancoleman.io/bip39 """ + # Check "Show entropy details", paste in coin flip sequence, click "Binary", select "Mnemonic Length" as "12 Words" + coin_flips = "10101010101011101100010000000011001000110000000000110000000010011100000000000000100000000001100000011100101100100001001100110010" + expected = "prevent style echo subway next museum palace lobster toward office shoe unfair" + mnemonic = mnemonic_generation.generate_mnemonic_from_coin_flips(coin_flips) + actual = " ".join(mnemonic) + assert bip39.mnemonic_is_valid(actual) + assert actual == expected + + coin_flips = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + expected = "earth naive tongue material rebel cotton credit quarter market peanut memory other" + mnemonic = mnemonic_generation.generate_mnemonic_from_coin_flips(coin_flips) + actual = " ".join(mnemonic) + assert bip39.mnemonic_is_valid(actual) + assert actual == expected + + coin_flips = "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + expected = "exit pulp believe feature horror vehicle home more patrol hair drink resist" + mnemonic = mnemonic_generation.generate_mnemonic_from_coin_flips(coin_flips) + actual = " ".join(mnemonic) + assert bip39.mnemonic_is_valid(actual) + assert actual == expected + From 756e8e2edf4d6defd3380e28bc1ea38511fbd62c Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 3 Nov 2024 04:44:22 +0100 Subject: [PATCH 09/11] Update test_mnemonic_generation.py - fix copy/paste error --- tests/test_mnemonic_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mnemonic_generation.py b/tests/test_mnemonic_generation.py index be2f123b3..563ca86f7 100644 --- a/tests/test_mnemonic_generation.py +++ b/tests/test_mnemonic_generation.py @@ -250,7 +250,7 @@ def test_256_coin_flips(): -def test_128_dice_rolls(): +def test_128_coin_flips(): """ 128 coin flips input should yield the same 12-word mnemonic as iancoleman.io/bip39 """ # Check "Show entropy details", paste in coin flip sequence, click "Binary", select "Mnemonic Length" as "12 Words" coin_flips = "10101010101011101100010000000011001000110000000000110000000010011100000000000000100000000001100000011100101100100001001100110010" From 101e5cdc0af4a0778f8559d5ef38794b8483103c Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 24 Nov 2024 03:29:49 +0100 Subject: [PATCH 10/11] Update components.py - delete heads/tails icon (and stick to normal letters instead of icons (as easyuxd proposed) --- src/seedsigner/gui/components.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index d16348732..2ec78c61c 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -90,8 +90,6 @@ class FontAwesomeIconConstants: DICE_FIVE = "\uf523" DICE_SIX = "\uf526" COINS = "\uf51e" - COIN_H = "\u0048" # H for heads - COIN_T = "\u0054" # T for tails KEYBOARD = "\uf11c" LOCK = "\uf023" MAP = "\uf279" From c3665086313420f083dad6355ca1d198b52a2774 Mon Sep 17 00:00:00 2001 From: BtcAutoNode <149917291+BtcAutoNode@users.noreply.github.com> Date: Sun, 24 Nov 2024 03:32:42 +0100 Subject: [PATCH 11/11] Update tools_screens.py - changed coin flip screen) to reflect changes proposed by easyuxd) --- src/seedsigner/gui/screens/tools_screens.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index 0aee83118..c4868f61c 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -171,24 +171,27 @@ def __post_init__(self): self.title = f"Coin Flip 1/{self.return_after_n_chars}" # Specify the keys in the keyboard - self.rows = 3 - self.cols = 3 - self.keyboard_font_name = GUIConstants.ICON_FONT_NAME__FONT_AWESOME - self.keyboard_font_size = None # Force auto-scaling to Key height + self.rows = 2 + self.cols = 2 + self.key_height = GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 2 + 2*GUIConstants.EDGE_PADDING self.keys_charset = "".join([ - FontAwesomeIconConstants.COIN_H, - FontAwesomeIconConstants.COIN_T, + "H", + "T", ]) # Map Key display chars to actual output values self.keys_to_values = { - FontAwesomeIconConstants.COIN_H: "1", - FontAwesomeIconConstants.COIN_T: "0", + "H": "1", + "T": "0", } # Now initialize the parent class super().__post_init__() - + + self.components.append(TextArea( + text="(H)eads: 1 / (T)ails: 0", + screen_y = self.keyboard.rect[3] + 3*GUIConstants.COMPONENT_PADDING, + )) def update_title(self) -> bool: self.title = f"Coin Flip {self.cursor_position + 1}/{self.return_after_n_chars}"