pyflo/pybtc/functions/bip39_mnemonic.py
2019-03-15 01:36:25 +04:00

138 lines
6.0 KiB
Python

from pybtc.constants import *
import time
import hashlib
from pybtc.functions.hash import sha256
from pybtc.functions.tools import int_from_bytes
def generate_entropy(strength=256, hex=True):
"""
Generate 128-256 bits entropy bytes string
:param int strength: entropy bits strength, by default is 256 bit.
:param boolean hex: return HEX encoded string result flag, by default 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:
i += 1
return h[:int(strength/8)] if not hex else h[:int(strength/8)].hex()
def load_word_list(language='english', word_list_dir=None):
"""
Load the word list from local file.
:param str language: (optional) uses word list language (chinese_simplified, chinese_traditional, english, french,
italian, japanese, korean, spanish), by default is english.
:param str word_list_dir: (optional) path to a directory containing a list of words,
by default None (use BIP39 standard list)
:return: list of words.
"""
if not word_list_dir:
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:
raise ValueError("word list invalid, should contain 2048 words")
return word_list
def entropy_to_mnemonic(entropy, language='english', word_list_dir=None, word_list=None):
"""
Convert entropy to mnemonic words string.
:param str,bytes entropy: random entropy HEX encoded or bytes string.
:param str language: (optional) uses word list language (chinese_simplified, chinese_traditional, english, french,
italian, japanese, korean, spanish), by default is english.
:param str word_list_dir: (optional) path to a directory containing a list of words,
by default None (use BIP39 standard list)
:param list word_list: (optional) already loaded word list, by default None
:return: mnemonic words string.
"""
if isinstance(entropy, str):
entropy = bytes.fromhex(entropy)
if not isinstance(entropy, bytes):
raise TypeError("entropy should be bytes or hex encoded string")
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
i = (i << len(entropy) * 8 // 32) | sha256(entropy)[0]
return " ".join([word_list[i.__rshift__(((d - 1) * 11)) & 2047]
for d in range(int((len(entropy) * 8 + 8) // 11), 0, -1)])
def mnemonic_to_entropy(mnemonic, language='english', word_list_dir=None,
word_list=None, hex=True):
"""
Converting mnemonic words to entropy.
:param str mnemonic: mnemonic words string (space separated)
:param str language: (optional) uses word list language (chinese_simplified, chinese_traditional, english, french,
italian, japanese, korean, spanish), by default is english.
:param str word_list_dir: (optional) path to a directory containing a list of words,
by default None (use BIP39 standard list)
:param list word_list: (optional) already loaded word list, by default None
:param boolean hex: return HEX encoded string result flag, by default True.
:return: 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]
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:
raise ValueError("invalid mnemonic checksum")
return entropy if not hex else entropy.hex()
def mnemonic_to_seed(mnemonic, passphrase="", hex=True):
"""
Converting mnemonic words string to seed for uses in key derivation (BIP-0032).
:param str mnemonic: mnemonic words string (space separated)
:param str passphrase: (optional) passphrase to get ability use 2FA approach for
creating seed, by default empty string.
:param boolean hex: return HEX encoded string result flag, by default 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()