178 lines
5.6 KiB
Python
178 lines
5.6 KiB
Python
import os
|
|
import hmac
|
|
|
|
from struct import pack
|
|
from hashlib import pbkdf2_hmac
|
|
from binascii import hexlify, unhexlify
|
|
from .constants import *
|
|
from .tools import priv2pub
|
|
from .hash import hmac_sha512, hash160, double_sha256, sha256, double_sha256
|
|
|
|
|
|
# BIP39
|
|
#
|
|
#
|
|
#
|
|
|
|
def create_passphrase(bits=256, language='english'):
|
|
if bits in [128, 160, 192, 224, 256]:
|
|
entropy = os.urandom(bits // 8)
|
|
mnemonic = create_mnemonic(entropy, language)
|
|
return ' '.join(mnemonic[::-1])
|
|
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'):
|
|
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, 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):
|
|
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 mnemonic2bytes(passphrase, language):
|
|
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 create_seed(passphrase, password=''):
|
|
return pbkdf2_hmac('sha512', password.encode(), passphrase.encode(), 2048)
|
|
|
|
|
|
|
|
# BIP32
|
|
#
|
|
#
|
|
#
|
|
|
|
# создание родительского приватного ключа
|
|
def create_master_key_hdwallet(seed):
|
|
key = b'Bitcoin seed'
|
|
intermediary = hmac_sha512(key, seed)
|
|
master_key = intermediary[:32]
|
|
chain_code = intermediary[32:]
|
|
if validate_private_key(master_key) and validate_private_key(chain_code):
|
|
return dict(version=MAINNET_PRIVATE_WALLET_VERSION,
|
|
key=master_key,
|
|
depth=0,
|
|
child=0,
|
|
finger_print=b'\x00\x00\x00\x00',
|
|
chain_code=chain_code,
|
|
is_private=True)
|
|
else:
|
|
return None
|
|
|
|
|
|
# создание дочернего приватного ключа
|
|
def create_child_key_hdwallet(key, child_idx):
|
|
if not key.get('is_private') and child_idx >= FIRST_HARDENED_CHILD:
|
|
return None
|
|
public_key = priv2pub(key['key'], True)
|
|
assert public_key is not None
|
|
seed = public_key + bytes([key['depth'] + 1])
|
|
intermediary = hmac_sha512(key['chain_code'], seed)
|
|
chain_code = intermediary[32:]
|
|
child_key = add_private_keys(intermediary[:32], key['key'])
|
|
finger_print = hash160(child_key)[:4]
|
|
if validate_private_key(child_key) and validate_private_key(chain_code):
|
|
return dict(version=MAINNET_PRIVATE_WALLET_VERSION,
|
|
key=child_key,
|
|
depth=key['depth'] + 1,
|
|
child=0,
|
|
finger_print=finger_print,
|
|
chain_code=chain_code,
|
|
is_private=True)
|
|
return None
|
|
|
|
|
|
def add_private_keys(ext_value, key):
|
|
ext_value_int = int.from_bytes(ext_value, byteorder="big")
|
|
key_int = int.from_bytes(key, byteorder="big")
|
|
ext_value_int = (ext_value_int + key_int) % MAX_INT_PRIVATE_KEY
|
|
return ext_value_int.to_bytes((ext_value_int.bit_length() + 7) // 8, byteorder="big")
|
|
|
|
|
|
## Надо удалить в будущем как дублирование. И добавить в реализации ООП как метод
|
|
def create_public_key_hdwallet(master_key):
|
|
return priv2pub(master_key, True)
|
|
|
|
|
|
def validate_private_key(key):
|
|
key_int = int.from_bytes(key, byteorder="big")
|
|
if key_int > 0 and key_int < MAX_INT_PRIVATE_KEY and len(key) == 32:
|
|
return True
|
|
return False
|
|
|
|
|
|
def validate_child_public_key(key):
|
|
return False
|
|
|
|
|
|
def serialize_key_hdwallet(key):
|
|
try:
|
|
key_bytes = key['key']
|
|
if key.get('is_private'):
|
|
key_bytes = bytes(1) + key_bytes
|
|
|
|
result = key['version']
|
|
result += pack('>B', key['depth'])
|
|
result += key['finger_print']
|
|
result += pack('>I', key['child'])
|
|
result += key['chain_code']
|
|
result += key_bytes
|
|
chk_sum = double_sha256(result)[:4]
|
|
return result + chk_sum
|
|
except:
|
|
raise Exception('Serialization error')
|
|
|
|
|
|
|