bip39 refactoring
This commit is contained in:
parent
3dee268133
commit
e9add2530d
@ -3,7 +3,7 @@ import random
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
|
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
|
MAX_AMOUNT = 2100000000000000
|
||||||
SIGHASH_ALL = 0x00000001
|
SIGHASH_ALL = 0x00000001
|
||||||
|
|||||||
@ -5,3 +5,4 @@ from .tools import *
|
|||||||
from .hash import *
|
from .hash import *
|
||||||
from .block 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 .hash import *
|
||||||
from .encode import *
|
from .encode import *
|
||||||
from .hash import *
|
from .hash import *
|
||||||
|
from .bip39_mnemonic import generate_entropy
|
||||||
|
|
||||||
|
|
||||||
def create_private_key(compressed=True, testnet=False, wif=True, hex=False):
|
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.
|
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:
|
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:
|
elif hex:
|
||||||
return h.hex()
|
return generate_entropy()
|
||||||
return h
|
return generate_entropy(hex=False)
|
||||||
|
|
||||||
|
|
||||||
def private_key_to_wif(h, compressed=True, testnet=False):
|
def private_key_to_wif(h, compressed=True, testnet=False):
|
||||||
|
|||||||
@ -146,8 +146,7 @@ def read_var_int(stream):
|
|||||||
:return: bytes.
|
:return: bytes.
|
||||||
"""
|
"""
|
||||||
l = stream.read(1)
|
l = stream.read(1)
|
||||||
bytes_length = get_var_int_len(l)
|
return b"".join((l, stream.read(get_var_int_len(l) - 1)))
|
||||||
return b"%s%s" % (l, stream.read(bytes_length - 1))
|
|
||||||
|
|
||||||
|
|
||||||
def read_var_list(stream, data_type):
|
def read_var_list(stream, data_type):
|
||||||
|
|||||||
@ -402,122 +402,3 @@ def deserialize_xkey(encode_key):
|
|||||||
return None
|
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'],
|
install_requires=[ 'secp256k1'],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
package_data={
|
package_data={
|
||||||
'pybtc': ['bip-0039/*.txt'],
|
'pybtc': ['bip39_word_list/*.txt'],
|
||||||
},
|
},
|
||||||
zip_safe=False)
|
zip_safe=False)
|
||||||
|
|||||||
@ -5,5 +5,5 @@ import test
|
|||||||
testLoad = unittest.TestLoader()
|
testLoad = unittest.TestLoader()
|
||||||
suites = testLoad.loadTestsFromModule(test)
|
suites = testLoad.loadTestsFromModule(test)
|
||||||
|
|
||||||
runner = unittest.TextTestRunner(verbosity=2)
|
runner = unittest.TextTestRunner(verbosity=3)
|
||||||
runner.run(suites)
|
runner.run(suites)
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
from .hash_functions import *
|
# from .hash_functions import *
|
||||||
from .integer import *
|
# from .integer import *
|
||||||
from .address_functions import *
|
# from .address_functions import *
|
||||||
from .address_class import *
|
# from .address_class import *
|
||||||
from .ecdsa import *
|
# from .ecdsa import *
|
||||||
from .transaction_deserialize import *
|
# from .transaction_deserialize import *
|
||||||
from .transaction_constructor import *
|
# from .transaction_constructor import *
|
||||||
from .sighash import *
|
# from .sighash import *
|
||||||
from .block import *
|
# from .block import *
|
||||||
|
from .mnemonic import *
|
||||||
|
|
||||||
# from .script_deserialize import *
|
# from .script_deserialize import *
|
||||||
# from .create_transaction import *
|
# from .create_transaction import *
|
||||||
|
|||||||
@ -609,6 +609,10 @@ class BlockDeserializeTests(unittest.TestCase):
|
|||||||
"f = open('./test/raw_block.txt');"
|
"f = open('./test/raw_block.txt');"
|
||||||
"fc = f.readline();"
|
"fc = f.readline();"
|
||||||
"pybtc.Block(fc[:-1], format='decoded')")
|
"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.bits)
|
||||||
# print(">>>",block.hash)
|
# print(">>>",block.hash)
|
||||||
# print(">>>",block.timestamp)
|
# 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