Source code for pybgl.functions.bip39_mnemonic

from pybgl.constants import *
import time
import hashlib
from pybgl.functions.hash import sha256
from pybgl.functions.shamir import split_secret, restore_secret
from pybgl.functions.tools import int_from_bytes, get_bytes
import random
import math

[docs]def generate_entropy(strength=256, hex=True): """ Generate 128-256 bits entropy bytes string :param strength: (optional) entropy bits strength, by default is 256 bit. :param hex: (optional) return HEX encoded string result flag, by default is True. :return: HEX encoded or bytes entropy string. """ if strength not in [128, 160, 192, 224, 256]: raise ValueError('strength should be one of the following [128, 160, 192, 224, 256]') a = random.SystemRandom().randint(0, ECDSA_SEC256K1_ORDER) i = int((time.time() % 0.01 ) * 100000) h = a.to_bytes(32, byteorder="big") # more entropy from system timer and sha256 derivation while i: h = hashlib.sha256(h).digest() i -= 1 if not i and int_from_bytes(h, byteorder="big") > ECDSA_SEC256K1_ORDER: # pragma: no cover i += 1 return h[:int(strength/8)] if not hex else h[:int(strength/8)].hex()
[docs]def load_word_list(language='english', word_list_dir=None): """ Load the word list from local file. :param language: (optional) uses word list language (chinese_simplified, chinese_traditional, english, french, italian, japanese, korean, spanish), by default is english. :param word_list_dir: (optional) path to a directory containing a list of words, by default is None (use BIP39 standard list) :return: list of words. """ if not word_list_dir: # pragma: no cover word_list_dir = BIP0039_DIR path = os.path.join(word_list_dir, '.'.join((language, 'txt'))) if not os.path.exists(path): raise ValueError("word list not exist") with open(path) as f: word_list = f.read().rstrip('\n').split('\n') if len(word_list) != 2048: # pragma: no cover raise ValueError("word list invalid, should contain 2048 words") return word_list
[docs]def entropy_to_mnemonic(entropy, language='english', word_list_dir=None, word_list=None): """ Convert entropy to mnemonic words string. :param entropy: random entropy HEX encoded or bytes string. :param language: (optional) uses word list language (chinese_simplified, chinese_traditional, english, french, italian, japanese, korean, spanish), by default is english. :param word_list_dir: (optional) path to a directory containing a list of words, by default is None (use BIP39 standard list) :param word_list: (optional) already loaded word list, by default is None :return: mnemonic words string. """ entropy = get_bytes(entropy) if len(entropy) not in [16, 20, 24, 28, 32]: raise ValueError('entropy length should be one of the following: [16, 20, 24, 28, 32]') if word_list is None: word_list = load_word_list(language, word_list_dir) elif not isinstance(word_list, list) or len(word_list) != 2048: raise TypeError("invalid word list type") i = int.from_bytes(entropy, byteorder="big") # append checksum b = math.ceil(len(entropy) * 8 / 32) i = (i << b) | (sha256(entropy)[0] >> (8 - b)) return " ".join([word_list[i.__rshift__(((d - 1) * 11)) & 2047] for d in range(int((len(entropy) * 8 + 8) // 11), 0, -1)])
[docs]def mnemonic_to_entropy(mnemonic, language='english', word_list_dir=None, word_list=None, hex=True): """ Converting mnemonic words to entropy. :param mnemonic: mnemonic words string (space separated) :param language: (optional) uses word list language (chinese_simplified, chinese_traditional, english, french, italian, japanese, korean, spanish), by default is english. :param word_list_dir: (optional) path to a directory containing a list of words, by default None (use BIP39 standard list) :param word_list: (optional) already loaded word list, by default is None :param hex: (optional) return HEX encoded string result flag, by default is True. :return: HEX encoded or bytes string. """ if word_list is None: word_list = load_word_list(language, word_list_dir) elif not isinstance(word_list, list) or len(word_list) != 2048: raise TypeError("invalid word list type") mnemonic = mnemonic.split() word_count = len(mnemonic) if word_count not in [12, 15, 18, 21, 24]: raise ValueError('Number of words must be one of the following: [12, 15, 18, 21, 24]') codes = {w: c for c, w in enumerate(word_list)} entropy_int = 0 bit_size = word_count * 11 chk_sum_bit_len = word_count * 11 % 32 for w in mnemonic: entropy_int = (entropy_int << 11) | codes[w] entropy_int = entropy_int >> chk_sum_bit_len entropy = entropy_int.to_bytes((bit_size - chk_sum_bit_len) // 8, byteorder="big") return entropy if not hex else entropy.hex()
def is_mnemonic_checksum_valid(mnemonic, language='english', word_list_dir=None, word_list=None): if word_list is None: word_list = load_word_list(language, word_list_dir) elif not isinstance(word_list, list) or len(word_list) != 2048: raise TypeError("invalid word list type") mnemonic = mnemonic.split() word_count = len(mnemonic) if word_count not in [12, 15, 18, 21, 24]: raise ValueError('Number of words must be one of the following: [12, 15, 18, 21, 24]') codes = {w: c for c, w in enumerate(word_list)} entropy_int = 0 bit_size = word_count * 11 chk_sum_bit_len = word_count * 11 % 32 for w in mnemonic: entropy_int = (entropy_int << 11) | codes[w] chk_sum = entropy_int & (2 ** chk_sum_bit_len - 1) entropy_int = entropy_int >> chk_sum_bit_len entropy = entropy_int.to_bytes((bit_size - chk_sum_bit_len) // 8, byteorder="big") if (sha256(entropy)[0] >> (8 - chk_sum_bit_len)) != chk_sum: return False return True
[docs]def mnemonic_to_seed(mnemonic, passphrase="", hex=True): """ Converting mnemonic words string to seed for uses in key derivation (BIP-0032). :param mnemonic: mnemonic words string (space separated) :param passphrase: (optional) passphrase to get ability use 2FA approach for creating seed, by default is empty string. :param hex: (optional) return HEX encoded string result flag, by default is True. :return: HEX encoded or bytes string. """ if not isinstance(mnemonic, str): raise TypeError("mnemonic should be string") if not isinstance(passphrase, str): raise TypeError("mnemonic should be string") seed = hashlib.pbkdf2_hmac('sha512', mnemonic.encode(), ("mnemonic"+passphrase).encode(), 2048) return seed if not hex else seed.hex()
def __combinations(a, n): results = [] total = len(a) ** 2 for m in range(n, total): r = [] i = len(a) - 1 while i: if (m & (1 << i)) != 0: r.append(a[i]) i -= 1 if len(r) >= n: results.append(r) return results def split_mnemonic(mnemonic, threshold, total, language='english', word_list_dir=None, word_list=None): if not isinstance(mnemonic, str): raise TypeError("invalid mnemonic") entropy = mnemonic_to_entropy(mnemonic, language=language, hex=False, word_list_dir=word_list_dir, word_list=word_list) shares = split_secret(threshold, total, entropy) a = [(i, shares[i]) for i in shares] combinations = __combinations(a, threshold) for c in combinations: d = dict() for q in c: d[q[0]] = q[1] s = restore_secret(d) if s != entropy: # pragma: no cover raise Exception("split secret failed") result = dict() for share in shares: result[share] = entropy_to_mnemonic(shares[share], language=language, word_list_dir=word_list_dir, word_list=word_list) return result def combine_mnemonic(shares, language='english', word_list_dir=None, word_list=None): s = dict() for share in shares: s[share] = mnemonic_to_entropy(shares[share], language=language, hex=False, word_list_dir=word_list_dir, word_list=word_list) entropy = restore_secret(s) return entropy_to_mnemonic(entropy, language=language, word_list_dir=word_list_dir, word_list=word_list)