bip39 refactoring
This commit is contained in:
parent
3dee268133
commit
e9add2530d
@ -3,7 +3,7 @@ import random
|
||||
import os
|
||||
|
||||
ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
BIP0039_DIR = os.path.normpath(os.path.join(ROOT_DIR, 'bip-0039'))
|
||||
BIP0039_DIR = os.path.normpath(os.path.join(ROOT_DIR, 'bip39_word_list'))
|
||||
|
||||
MAX_AMOUNT = 2100000000000000
|
||||
SIGHASH_ALL = 0x00000001
|
||||
|
||||
@ -4,4 +4,5 @@ from .script import *
|
||||
from .tools import *
|
||||
from .hash import *
|
||||
from .block import *
|
||||
from .encode import *
|
||||
from .encode import *
|
||||
from .bip39_mnemonic import *
|
||||
157
pybtc/functions/bip39_mnemonic.py
Normal file
157
pybtc/functions/bip39_mnemonic.py
Normal file
@ -0,0 +1,157 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
from secp256k1 import ffi
|
||||
parentPath = os.path.abspath("../..")
|
||||
if parentPath not in sys.path:
|
||||
sys.path.insert(0, parentPath)
|
||||
|
||||
from pybtc.constants import *
|
||||
from .hash import *
|
||||
from hashlib import pbkdf2_hmac
|
||||
|
||||
|
||||
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, MAX_INT_PRIVATE_KEY)
|
||||
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") > MAX_INT_PRIVATE_KEY:
|
||||
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 wordl ist type")
|
||||
|
||||
# checksum
|
||||
mask = 0b10000000
|
||||
data_int = int.from_bytes(entropy, byteorder="big")
|
||||
data_bit_len = len(entropy) * 8 // 32
|
||||
fbyte_hash = sha256(entropy)[0]
|
||||
|
||||
while data_bit_len:
|
||||
data_bit_len -= 1
|
||||
data_int = (data_int << 1) | 1 if fbyte_hash & mask else data_int << 1
|
||||
mask = mask >> 1
|
||||
|
||||
mnemonic = []
|
||||
while data_int:
|
||||
mnemonic.append(word_list[data_int & 0b11111111111])
|
||||
data_int = data_int >> 11
|
||||
|
||||
return " ".join(mnemonic[::-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")
|
||||
fb = sha256(entropy)[0]
|
||||
assert (fb >> (8 - chk_sum_bit_len)) == chk_sum
|
||||
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 = pbkdf2_hmac('sha512', mnemonic.encode(), ("mnemonic"+passphrase).encode(), 2048)
|
||||
return seed if not hex else seed.hex()
|
||||
@ -11,6 +11,7 @@ from pybtc.constants import *
|
||||
from .hash import *
|
||||
from .encode import *
|
||||
from .hash import *
|
||||
from .bip39_mnemonic import generate_entropy
|
||||
|
||||
|
||||
def create_private_key(compressed=True, testnet=False, wif=True, hex=False):
|
||||
@ -27,20 +28,11 @@ def create_private_key(compressed=True, testnet=False, wif=True, hex=False):
|
||||
raw bytes string in case wif and hex flags set to False.
|
||||
|
||||
"""
|
||||
a = random.SystemRandom().randint(0, MAX_INT_PRIVATE_KEY)
|
||||
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") > MAX_INT_PRIVATE_KEY:
|
||||
i += 1
|
||||
if wif:
|
||||
return private_key_to_wif(h, compressed=compressed, testnet=testnet)
|
||||
return private_key_to_wif(generate_entropy(hex=False), compressed=compressed, testnet=testnet)
|
||||
elif hex:
|
||||
return h.hex()
|
||||
return h
|
||||
return generate_entropy()
|
||||
return generate_entropy(hex=False)
|
||||
|
||||
|
||||
def private_key_to_wif(h, compressed=True, testnet=False):
|
||||
|
||||
@ -146,8 +146,7 @@ def read_var_int(stream):
|
||||
:return: bytes.
|
||||
"""
|
||||
l = stream.read(1)
|
||||
bytes_length = get_var_int_len(l)
|
||||
return b"%s%s" % (l, stream.read(bytes_length - 1))
|
||||
return b"".join((l, stream.read(get_var_int_len(l) - 1)))
|
||||
|
||||
|
||||
def read_var_list(stream, data_type):
|
||||
|
||||
@ -402,122 +402,3 @@ def deserialize_xkey(encode_key):
|
||||
return None
|
||||
|
||||
|
||||
|
||||
# Mnemonic code for generating deterministic keys
|
||||
# BIP-0039
|
||||
|
||||
def create_passphrase(bits=256, language='english'):
|
||||
"""
|
||||
Creating the passphrase.
|
||||
|
||||
:param int bits: size of entropy is 128-256 bits, by default is 256.
|
||||
:param str language: uses wordlist language (chinese_simplified, chinese_traditional, english, french, italian, japanese, korean, spanish), by default is english.
|
||||
:return: string is passphrase.
|
||||
"""
|
||||
if bits in [128, 160, 192, 224, 256]:
|
||||
entropy = os.urandom(bits // 8)
|
||||
mnemonic = create_mnemonic(entropy, language)
|
||||
return ' '.join(mnemonic)
|
||||
else:
|
||||
raise ValueError('Strength should be one of the following [128, 160, 192, 224, 256], but it is not (%d).' % bits)
|
||||
|
||||
|
||||
def create_mnemonic(entropy, language='english'):
|
||||
"""
|
||||
Generating the mnemonic.
|
||||
|
||||
:param bytes entropy: random entropy bytes.
|
||||
:param str language: uses wordlist language (chinese_simplified, chinese_traditional, english, french, italian, japanese, korean, spanish), by default is english.
|
||||
:return: list of words.
|
||||
"""
|
||||
mnemonic = []
|
||||
wordlist = create_wordlist(language)
|
||||
entropy_int = int.from_bytes(entropy, byteorder="big")
|
||||
entropy_bit_len = len(entropy) * 8
|
||||
chk_sum_bit_len = entropy_bit_len // 32
|
||||
fbyte_hash = sha256(entropy)[0]
|
||||
entropy_int = add_checksum_ent(entropy)
|
||||
while entropy_int:
|
||||
mnemonic.append(wordlist[entropy_int & 0b11111111111])
|
||||
entropy_int = entropy_int >> 11
|
||||
return mnemonic[::-1]
|
||||
|
||||
|
||||
def create_wordlist(language='english', wordlist_dir=None):
|
||||
"""
|
||||
Creating the wordlist.
|
||||
|
||||
:param str language: uses wordlist language (chinese_simplified, chinese_traditional, english, french, italian, japanese, korean, spanish), by default is english.
|
||||
:param str wordlist_dir: path to a file containing a list of words.
|
||||
:return: list of words.
|
||||
"""
|
||||
if not wordlist_dir:
|
||||
wordlist_dir = BIP0039_DIR
|
||||
f = None
|
||||
path = os.path.join(wordlist_dir, '.'.join((language, 'txt')))
|
||||
assert os.path.exists(path)
|
||||
f = open(path)
|
||||
content = f.read().rstrip('\n')
|
||||
assert content
|
||||
f.close()
|
||||
return content.split('\n')
|
||||
|
||||
|
||||
def add_checksum_ent(data):
|
||||
"""
|
||||
Adding a checksum of a entropy to a entropy.
|
||||
|
||||
:param bytes data: random entropy bytes.
|
||||
:return: bytes string.
|
||||
"""
|
||||
mask = 0b10000000
|
||||
data_int = int.from_bytes(data, byteorder="big")
|
||||
data_bit_len = len(data) * 8 // 32
|
||||
fbyte_hash = sha256(data)[0]
|
||||
while data_bit_len:
|
||||
data_bit_len -= 1
|
||||
data_int = (data_int << 1) | 1 if fbyte_hash & mask else data_int << 1
|
||||
mask = mask >> 1
|
||||
return data_int
|
||||
|
||||
|
||||
def mnemonic_to_entropy(passphrase, language):
|
||||
"""
|
||||
Converting passphrase to entropy.
|
||||
|
||||
:param str passphrase: key passphrase.
|
||||
:param str language: uses wordlist language.
|
||||
:return: bytes string.
|
||||
"""
|
||||
mnemonic = passphrase.split()
|
||||
if len(mnemonic) in [12, 15, 18, 21, 24]:
|
||||
wordlist = create_wordlist(language)
|
||||
codes = dict()
|
||||
for code, word in enumerate(wordlist):
|
||||
codes[word] = code
|
||||
word_count = len(mnemonic)
|
||||
entropy_int = None
|
||||
bit_size = word_count * 11
|
||||
chk_sum_bit_len = word_count * 11 % 32
|
||||
for word in mnemonic:
|
||||
entropy_int = (entropy_int << 11) | codes[word] if entropy_int else codes[word]
|
||||
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")
|
||||
fb = sha256(entropy)[0]
|
||||
assert (fb >> (8 - chk_sum_bit_len)) & chk_sum
|
||||
return entropy
|
||||
else:
|
||||
raise ValueError('Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d).' % len(mnemonic))
|
||||
|
||||
|
||||
def mnemonic_to_seed(passphrase, password):
|
||||
"""
|
||||
Converting passphrase to seed for uses in key derivation (BIP-0032).
|
||||
|
||||
:param str passphrase: key passphrase.
|
||||
:param str password: password for key passphrase.
|
||||
:return: bytes string.
|
||||
"""
|
||||
return pbkdf2_hmac('sha512', passphrase.encode(), (passphrase + password).encode(), 2048)
|
||||
|
||||
|
||||
2
setup.py
2
setup.py
@ -16,6 +16,6 @@ setup(name='pybtc',
|
||||
install_requires=[ 'secp256k1'],
|
||||
include_package_data=True,
|
||||
package_data={
|
||||
'pybtc': ['bip-0039/*.txt'],
|
||||
'pybtc': ['bip39_word_list/*.txt'],
|
||||
},
|
||||
zip_safe=False)
|
||||
|
||||
@ -5,5 +5,5 @@ import test
|
||||
testLoad = unittest.TestLoader()
|
||||
suites = testLoad.loadTestsFromModule(test)
|
||||
|
||||
runner = unittest.TextTestRunner(verbosity=2)
|
||||
runner = unittest.TextTestRunner(verbosity=3)
|
||||
runner.run(suites)
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
from .hash_functions import *
|
||||
from .integer import *
|
||||
from .address_functions import *
|
||||
from .address_class import *
|
||||
from .ecdsa import *
|
||||
from .transaction_deserialize import *
|
||||
from .transaction_constructor import *
|
||||
from .sighash import *
|
||||
from .block import *
|
||||
# from .hash_functions import *
|
||||
# from .integer import *
|
||||
# from .address_functions import *
|
||||
# from .address_class import *
|
||||
# from .ecdsa import *
|
||||
# from .transaction_deserialize import *
|
||||
# from .transaction_constructor import *
|
||||
# from .sighash import *
|
||||
# from .block import *
|
||||
from .mnemonic import *
|
||||
|
||||
# from .script_deserialize import *
|
||||
# from .create_transaction import *
|
||||
|
||||
@ -609,6 +609,10 @@ class BlockDeserializeTests(unittest.TestCase):
|
||||
"f = open('./test/raw_block.txt');"
|
||||
"fc = f.readline();"
|
||||
"pybtc.Block(fc[:-1], format='decoded')")
|
||||
cProfile.run("import pybtc;"
|
||||
"f = open('./test/raw_block.txt');"
|
||||
"fc = f.readline();"
|
||||
"pybtc.Block(fc[:-1], format='raw')")
|
||||
# print(">>>",block.bits)
|
||||
# print(">>>",block.hash)
|
||||
# print(">>>",block.timestamp)
|
||||
|
||||
27
tests/test/mnemonic.py
Normal file
27
tests/test/mnemonic.py
Normal file
@ -0,0 +1,27 @@
|
||||
import unittest
|
||||
import os, sys
|
||||
parentPath = os.path.abspath("..")
|
||||
if parentPath not in sys.path:
|
||||
sys.path.insert(0, parentPath)
|
||||
|
||||
from pybtc import *
|
||||
|
||||
|
||||
class BlockDeserializeTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
print("\nTesting Block class deserialization:\n")
|
||||
|
||||
def test_mnemonic_functions(self):
|
||||
mnemonic = 'young crime force door joy subject situate hen pen sweet brisk snake nephew sauce ' \
|
||||
'point skate life truly hockey scout assault lab impulse boss'
|
||||
entropy = "ff46716c20b789aff26b59a27b74716699457f29d650815d2db1e0a0d8f81c88"
|
||||
seed = "a870edd6272a4f0962a7595612d96645f683a3378fd9b067340eb11ebef45cb3d28fb64678cadc43969846" \
|
||||
"0a3d48bd57b2ae562b6d2b3c9fb5462d21e474191c"
|
||||
self.assertEqual(entropy_to_mnemonic(entropy), mnemonic)
|
||||
|
||||
self.assertEqual(mnemonic_to_entropy(mnemonic), entropy)
|
||||
self.assertEqual(mnemonic_to_seed(mnemonic), seed)
|
||||
|
||||
print(generate_entropy())
|
||||
print(generate_entropy(128))
|
||||
Loading…
Reference in New Issue
Block a user