Created
May 19, 2021 16:14
-
-
Save kpe/c677167da488a93035dc3e9bc3ef7364 to your computer and use it in GitHub Desktop.
bip39-diceware-helper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import tempfile | |
import random | |
from typing import Tuple, List | |
WORDLIST_URL = "https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt" | |
WORDLIST_LOCAL = "/tmp/" | |
USE_RANDOM = False | |
# | |
# https://www.reddit.com/r/Bitcoin/comments/c82urq/how_to_manual_bip39_last_word_calculation_when/ | |
# | |
# s=s/256 | |
# h=hashlib.sha256(binascii.unhexlify('%064x' % s)).digest().encode('hex') | |
# int(('%064x' % s)[-1] + h[:2], 16) % 2048 | |
# | |
def fetch_wordlist(): | |
wordlist_path = os.path.join(tempfile.gettempdir(), WORDLIST_URL.split('/')[-1]) | |
if not os.path.isfile(wordlist_path): | |
print(f"Wordlist not found locally at: {wordlist_path}") | |
print(f"fetching wordlist from: {WORDLIST_URL}") | |
import requests | |
response = requests.get(WORDLIST_URL) | |
with open(wordlist_path, "w") as f: | |
f.write(response.content.decode("utf8")) | |
else: | |
print(f"Using local wordlist from: {wordlist_path}") | |
with open(wordlist_path, "r") as f: | |
result = f.readlines() | |
return list(map(str.strip, result)) | |
def input_coin_flip(ndx): | |
while True: | |
inp = input(f"{ndx:2d}: (H)ead or (T)ail? ").upper().strip() | |
if inp in ["H", "T"]: | |
return inp == "T" | |
def input_dice(ndx, dndx): | |
while True: | |
try: | |
inp = int(input(f"{ndx:2d}: Dice {dndx} (1-6)? ").strip()) | |
if 0 < inp < 7: | |
return inp | |
except ValueError: | |
pass | |
def input_coin_flip_random(ndx): | |
return random.randint(0, 1) | |
def input_dice_random(ndx) -> int: | |
return random.randint(1, 6) | |
if USE_RANDOM: | |
input_dice = input_dice_random | |
input_coin_flip = input_coin_flip_random | |
def diceware_to_word_ndx(coin: int, dices: List[int]): | |
return coin * 6**4 + sum((dice - 1) * (6**pos) for pos, dice in enumerate(reversed(dices))) | |
def input_word_entropy(ndx) -> Tuple[int, List[int]]: | |
while True: | |
result = input_coin_flip(ndx+1), [input_dice(ndx+1, dndx+1) for dndx in range(4)] | |
if diceware_to_word_ndx(*result) < 2048: | |
return result | |
def input_entropy(word_count=24): | |
return [input_word_entropy(ndx) for ndx in range(word_count)] | |
def bip39_from_diceware(wordlist: List[str], entropy: List[Tuple[int, List[int]]]): | |
word_ndxs = [] | |
for ndx, (coin, dices) in enumerate(entropy): | |
word_ndx = diceware_to_word_ndx(coin, dices) | |
word = wordlist[word_ndx] | |
print("{:2d}: {}{}{}{}{} : {:9s} : {}".format(ndx + 1, 'T' if coin else 'H', *dices, word, word_ndx)) | |
word_ndxs.append(word_ndx) | |
bip39_ndxs_to_mnemonic(word_ndxs) | |
def bip39_ndxs_to_mnemonic(wordlist: List[str], word_ndxs: List[int]): | |
import hashlib | |
import binascii | |
all_bits = 0 | |
for ndx, word_ndx in enumerate(word_ndxs): | |
word = wordlist[word_ndx] | |
print("{:2d}: {:9s} : {}".format(ndx+1, word, word_ndx)) | |
all_bits = (all_bits << 11) + word_ndx | |
s = all_bits | |
s = s//256 | |
h = hashlib.sha256(binascii.unhexlify('%064x' % s)).digest().hex() | |
word_ndx = int(('%064x' % s)[-1] + h[:2], 16) % 2048 | |
word_ndxs[-1] = word_ndx | |
word = wordlist[word_ndx] | |
print("{:2d}: {}{}{}{}{} : {:9s} : {}".format(24, 'x', *['x']*4, word, word_ndx)) | |
print("mnemonic: ", " ".join([wordlist[wndx] for wndx in word_ndxs])) | |
def stdin_bytes_to_mnemonic(wordlist: List[str], word_count: int): | |
""" | |
Reads the entropy from stdin as base 10 byte values (0-255). | |
cat /dev/urandom | hexdump -v -n 33 -e '/1 "%03u\n"' | |
""" | |
import sys | |
buff = 0 | |
for ndx in range(word_count*11//8): | |
line = int(sys.stdin.readline().strip()) | |
print(f"{ndx:3d}:byte: [{line}]") | |
buff = (buff << 8) + line | |
print(buff) | |
word_ndxs = [] | |
for ndx in range(word_count): | |
word_ndx = buff & (2**11 - 1) | |
buff = buff >> 11 | |
print(f"{ndx:2d}:word ndx: {word_ndx}: {wordlist[word_ndx]}") | |
word_ndxs.append(word_ndx) | |
bip39_ndxs_to_mnemonic(wordlist, word_ndxs) | |
if __name__ == "__main__": | |
bip39_wordlist = fetch_wordlist() | |
word_count = 24 | |
import sys | |
if sys.stdin.isatty(): | |
diceware_entropy = input_entropy(word_count) | |
bip39_from_diceware(bip39_wordlist, diceware_entropy) | |
else: | |
print("reading entropy as a single base-10 byte (0-255) per line from stdin...") | |
print(r' try:\n\t cat /dev/urandom | hexdump -v -n 33 -e \'/1 "%03u\n"\'') | |
stdin_bytes_to_mnemonic(bip39_wordlist, word_count) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment