Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Branch of Keiths 404 branch, Coin flip code march 5 #534

Draft
wants to merge 17 commits into
base: dev
Choose a base branch
from
Draft
67 changes: 67 additions & 0 deletions docs/dice_verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,70 @@ We double-checked in two different web tools implementing different methods for

So congratulations if the fingerprints, zpubs and addresses all match up in your example so you can be much more confident that nothing is wrong with your generated seed.

---

# Command Line Tool
_(for more advanced/python-savvy users)_

Run the exact same SeedSigner mnemonic generation code from the command line to quickly test and externally verify the results.

Create a python virtualenv (out of the scope of this doc) and install dependencies:
```bash
pip3 install embit

# Install the main project code to make it importable
pip3 install -e .
```


Then run the utility script with `-h` to view the usage instructions:
```bash
cd tools
python3 mnemonic.py -h
```

```
Verify SeedSigner's dice rolls and coin flip entropy-to-mnemonic conversion via this tool.

Compare its results against iancoleman.io/bip39 and bitcoiner.guide/seed

Usage:
# 50 dice rolls / 12-word mnemonic
python3 mnemonic.py dice 5624433434...

# 99 dice rolls / 24-word mnemonic
python3 mnemonic.py dice 6151463561...

# 50 dice rolls, entered as 0-5 / 12-word mnemonic
python3 mnemonic.py --zero-indexed-dice dice 5135535514...

# 128 coin flips / 12-word mnemonic
python3 mnemonic.py coins 1111100111...

# 256 coin flips / 24-word mnemonic
python mnemonic.py coins 0010111010...

# GENERATE 50 random dice rolls / 12-word mnemonic
python3 mnemonic.py dice rand12

# GENERATE 99 random dice rolls / 24-word mnemonic
python3 mnemonic.py dice rand24

# GENERATE 99 random dice rolls, entered as 0-5 / 24-word mnemonic
python3 mnemonic.py --zero-indexed-dice dice rand24

# GENERATE 128 random coin flips / 12-word mnemonic
python3 mnemonic.py coins rand12

# GENERATE 256 random coin flips / 24-word mnemonic
python3 mnemonic.py coins rand24
```

### How to get the same results in iancoleman.io
Always specify your expected length in the "Mnemonic Length" droplist (defaults to "Use Raw Entropy (3 words per 32 bits)").

Dice Rolls: Do NOT use the "Dice [1-6]" option; select "Base 10 [0-9]" or "Hex [0-9A-F]"

Zero-indexed dice rolls: Select "Base 6 [0-5]", "Base 10 [0-9]", or "Hex [0-9A-F]"

Coin Flips: Select "Binary [0-1]", "Base 6 [0-5]", "Base 10 [0-9]", or "Hex [0-9A-F]"
1 change: 1 addition & 0 deletions src/seedsigner/gui/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class FontAwesomeIconConstants:
CHEVRON_DOWN = "\uf078"
CIRCLE = "\uf111"
CIRCLE_CHEVRON_RIGHT = "\uf138"
COIN = "\uf51e"
DICE = "\uf522"
DICE_ONE = "\uf525"
DICE_TWO = "\uf528"
Expand Down
85 changes: 73 additions & 12 deletions src/seedsigner/helpers/mnemonic_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,42 @@
import unicodedata

from embit import bip39
from embit.bip39 import mnemonic_to_bytes, mnemonic_from_bytes
from typing import List
from seedsigner.models.settings_definition import SettingsConstants
from seedsigner.models.seed import Seed

"""
This is SeedSigner's internal mnemonic generation utility.

It can also be run as an independently-executable CLI to facilitate external
verification of SeedSigner's results for a given input entropy.

see: docs/dice_verification.md (the "Command Line Tool" section).
"""

def calculate_checksum(mnemonic: list, wordlist_language_code: str) -> List[str]:
DICE__NUM_ROLLS__12WORD = 50
DICE__NUM_ROLLS__24WORD = 99
COIN__NUM_FLIPS__12WORD = 128
COIN__NUM_FLIPS__24WORD = 256



def calculate_checksum(mnemonic: list | str, wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> list[str]:
"""
Provide 12- or 24-word mnemonic, returns complete mnemonic w/checksum as a list.

Mnemonic may be a list of words or a string of words separated by spaces or commas.

If 11- or 23-words are provided, append word `0000` to end of list as temp final
word.
"""
from seedsigner.models.seed import Seed
if type(mnemonic) == str:
import re
# split on commas or spaces
mnemonic = re.findall(r'[^,\s]+', mnemonic)

if len(mnemonic) in [11, 23]:
mnemonic.append(Seed.get_wordlist(wordlist_language_code)[0])
temp_final_word = Seed.get_wordlist(wordlist_language_code)[0]
mnemonic.append(temp_final_word)

if len(mnemonic) not in [12, 24]:
raise Exception("Pass in a 12- or 24-word mnemonic")
Expand All @@ -37,28 +58,68 @@ def calculate_checksum(mnemonic: list, wordlist_language_code: str) -> List[str]



def generate_mnemonic_from_bytes(entropy_bytes) -> List[str]:
return bip39.mnemonic_from_bytes(entropy_bytes).split()
def generate_mnemonic_from_bytes(entropy_bytes, wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> list[str]:
return bip39.mnemonic_from_bytes(entropy_bytes, wordlist=Seed.get_wordlist(wordlist_language_code)).split()



def generate_mnemonic_from_dice(roll_data: str, wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> list[str]:
"""
Takes a string of 50 or 99 dice rolls and returns a 12- or 24-word mnemonic.

def generate_mnemonic_from_dice(roll_data: str) -> List[str]:
Uses the iancoleman.io/bip39 and bitcoiner.guide/seed "Base 10" or "Hex" mode approach:
* dice rolls are treated as string data.
* hashed via SHA256.

Important note: This method is NOT compatible with iancoleman's "Dice" mode.
"""
entropy_bytes = hashlib.sha256(roll_data.encode()).digest()

if len(roll_data) == 50:
if len(roll_data) == DICE__NUM_ROLLS__12WORD:
# 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()
return bip39.mnemonic_from_bytes(entropy_bytes, wordlist=Seed.get_wordlist(wordlist_language_code)).split()



def generate_mnemonic_from_coin_flips(coin_flips: str, wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> list[str]:
"""
Takes a string of 128 or 256 0s and 1s and returns a 12- or 24-word mnemonic.

Uses the iancoleman.io/bip39 and bitcoiner.guide/seed "Binary" mode approach:
* binary digit stream is treated as string data.
* hashed via SHA256.
"""
entropy_bytes = hashlib.sha256(coin_flips.encode()).digest()

if len(coin_flips) == 128:
# 12-word mnemonic; only use 128bits / 16 bytes
entropy_bytes = entropy_bytes[:16]

# Return as a list
return bip39.mnemonic_from_bytes(entropy_bytes, wordlist=Seed.get_wordlist(wordlist_language_code)).split()



def get_partial_final_word(coin_flips: str, wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> str:
""" Look up the partial final word for the given coin flips.
7 coin flips: 0101010 + **** where the final 4 bits will be replaced with the checksum
3 coin flips: 010 + ******** where the final 8 bits will be replaced with the checksum
"""
binary_string = coin_flips + "0" * (11 - len(coin_flips))
wordlist_index = int(binary_string, 2)

return Seed.get_wordlist(wordlist_language_code)[wordlist_index]



# Note: This currently isn't being used since we're now chaining hashed bytes for the
# image-based entropy and aren't just ingesting a single image.
def generate_mnemonic_from_image(image) -> List[str]:
def generate_mnemonic_from_image(image, wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> list[str]:
import hashlib
hash = hashlib.sha256(image.tobytes())

# Return as a list
return bip39.mnemonic_from_bytes(hash.digest()).split()
return bip39.mnemonic_from_bytes(hash.digest(), wordlist=Seed.get_wordlist(wordlist_language_code)).split()
68 changes: 63 additions & 5 deletions src/seedsigner/views/tools_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
class ToolsMenuView(View):
IMAGE = (" New seed", FontAwesomeIconConstants.CAMERA)
DICE = ("New seed", FontAwesomeIconConstants.DICE)
COIN = ("New seed", FontAwesomeIconConstants.COIN)
KEYBOARD = ("Calc 12th/24th word", FontAwesomeIconConstants.KEYBOARD)
EXPLORER = "Address Explorer"
ADDRESS = "Verify address"

def run(self):
button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.EXPLORER, self.ADDRESS]
button_data = [self.IMAGE, self.DICE, self.COIN, self.KEYBOARD, self.EXPLORER, self.ADDRESS]

selected_menu_num = self.run_screen(
ButtonListScreen,
Expand All @@ -50,6 +51,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)

Expand Down Expand Up @@ -188,8 +192,8 @@ def run(self):
****************************************************************************"""
class ToolsDiceEntropyMnemonicLengthView(View):
def run(self):
TWELVE = "12 words (50 rolls)"
TWENTY_FOUR = "24 words (99 rolls)"
TWELVE = f"12 words ({mnemonic_generation.DICE__NUM_ROLLS__12WORD} rolls)"
TWENTY_FOUR = f"24 words ({mnemonic_generation.DICE__NUM_ROLLS__24WORD} rolls)"

button_data = [TWELVE, TWENTY_FOUR]
selected_menu_num = ButtonListScreen(
Expand All @@ -203,10 +207,10 @@ def run(self):
return Destination(BackStackView)

elif button_data[selected_menu_num] == TWELVE:
return Destination(ToolsDiceEntropyEntryView, view_args=dict(total_rolls=50))
return Destination(ToolsDiceEntropyEntryView, view_args=dict(total_rolls=mnemonic_generation.DICE__NUM_ROLLS__12WORD))

elif button_data[selected_menu_num] == TWENTY_FOUR:
return Destination(ToolsDiceEntropyEntryView, view_args=dict(total_rolls=99))
return Destination(ToolsDiceEntropyEntryView, view_args=dict(total_rolls=mnemonic_generation.DICE__NUM_ROLLS__24WORD))



Expand Down Expand Up @@ -234,6 +238,60 @@ def run(self):
return Destination(SeedWordsWarningView, view_args={"seed_num": None}, clear_history=True)


"""****************************************************************************
Coin Flip 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 = ToolsCoinFlipEntryScreen(
return_after_n_chars=self.total_flips,
).display()

if ret == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)

print(f"Coin Flips: {ret}")
coin_seed_phrase = mnemonic_generation.generate_mnemonic_from_coin_flips(ret)
print(f"""Mnemonic: "{coin_seed_phrase}" """)

# 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
Expand Down
Loading
Loading