commit
44e164e6bb
@ -7,6 +7,17 @@ Base function primitives implemented in functional programming paradigm.
|
||||
|
||||
|
||||
|
||||
Mnemonic
|
||||
============
|
||||
|
||||
.. autofunction:: pybtc.create_passphrase
|
||||
.. autofunction:: pybtc.create_mnemonic
|
||||
.. autofunction:: pybtc.create_wordlist
|
||||
.. autofunction:: pybtc.add_checksum_ent
|
||||
.. autofunction:: pybtc.mnemonic_to_entropy
|
||||
.. autofunction:: pybtc.mnemonic_to_seed
|
||||
|
||||
|
||||
Private keys
|
||||
============
|
||||
|
||||
@ -31,7 +42,6 @@ Public keys
|
||||
.. autofunction:: pybtc.private_to_public_key
|
||||
.. autofunction:: pybtc.is_public_key_valid
|
||||
|
||||
|
||||
Addresses
|
||||
=========
|
||||
|
||||
@ -43,6 +53,26 @@ Addresses
|
||||
.. autofunction:: pybtc.is_address_valid
|
||||
|
||||
|
||||
HD Wallets
|
||||
=========
|
||||
|
||||
.. autofunction:: pybtc.create_xmaster_key
|
||||
.. autofunction:: pybtc.create_xpublic_key
|
||||
.. autofunction:: pybtc.derive_xkey
|
||||
.. autofunction:: pybtc.xprivate_to_xpublic_key
|
||||
.. autofunction:: pybtc.xkey_to_private_key
|
||||
.. autofunction:: pybtc.xkey_to_public_key
|
||||
.. autofunction:: pybtc.create_child_privkey
|
||||
.. autofunction:: pybtc.create_child_pubkey
|
||||
.. autofunction:: pybtc.create_expanded_key
|
||||
.. autofunction:: pybtc.create_expanded_hard_key
|
||||
.. autofunction:: pybtc.is_xprivate_key_valid
|
||||
.. autofunction:: pybtc.is_xpublic_key_valid
|
||||
.. autofunction:: pybtc.is_validate_path_level
|
||||
.. autofunction:: pybtc.serialize_xkey
|
||||
.. autofunction:: pybtc.deserialize_xkey
|
||||
|
||||
|
||||
Script
|
||||
======
|
||||
|
||||
|
||||
@ -4,5 +4,7 @@ from .consensus import *
|
||||
from .transaction import *
|
||||
from .block import *
|
||||
from .address import *
|
||||
from .hdwallet import *
|
||||
from .hash import *
|
||||
|
||||
version = "2.0.1"
|
||||
|
||||
2048
pybtc/bip-0039/chinese_simplified.txt
Normal file
2048
pybtc/bip-0039/chinese_simplified.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
pybtc/bip-0039/chinese_traditional.txt
Normal file
2048
pybtc/bip-0039/chinese_traditional.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
pybtc/bip-0039/english.txt
Normal file
2048
pybtc/bip-0039/english.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
pybtc/bip-0039/french.txt
Normal file
2048
pybtc/bip-0039/french.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
pybtc/bip-0039/italian.txt
Normal file
2048
pybtc/bip-0039/italian.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
pybtc/bip-0039/japanese.txt
Normal file
2048
pybtc/bip-0039/japanese.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
pybtc/bip-0039/korean.txt
Normal file
2048
pybtc/bip-0039/korean.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
pybtc/bip-0039/spanish.txt
Normal file
2048
pybtc/bip-0039/spanish.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -671,3 +671,15 @@ class BlockTemplate():
|
||||
block += t["data"]
|
||||
return double_sha256(header,1), block
|
||||
|
||||
def build_orphan(self, hash, ntime):
|
||||
self.previous_block_hash = hexlify(reverse_hash(s2rh(hash))).decode()
|
||||
self.time = hexlify(ntime.to_bytes(4, "big")).decode()
|
||||
self.height += 1
|
||||
self.transactions = list()
|
||||
self.txid_list = list()
|
||||
self.scan_tx_list()
|
||||
self.coinbase_tx = self.create_coinbase_transaction()
|
||||
self.coinb1, self.coinb2 = self.split_coinbase()
|
||||
self.target = bits2target(self.bits)
|
||||
self.difficulty = target2difficulty(self.target)
|
||||
self.merkle_branches = [hexlify(i).decode() for i in merkle_branches([self.coinbase_tx.hash, ] + self.txid_list)]
|
||||
@ -1,5 +1,9 @@
|
||||
from secp256k1 import lib as secp256k1
|
||||
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'))
|
||||
|
||||
MAX_AMOUNT = 2100000000000000
|
||||
SIGHASH_ALL = 0x00000001
|
||||
@ -75,3 +79,12 @@ SCRIPT_TYPES = {"P2PKH": 0,
|
||||
}
|
||||
|
||||
|
||||
# CONSTANTS hierarchical deterministic wallets (HD Wallets)
|
||||
MAINNET_PRIVATE_WALLET_VERSION = b'\x04\x88\xAD\xE4'
|
||||
MAINNET_PUBLIC_WALLET_VERSION = b'\x04\x88\xB2\x1E'
|
||||
TESTNET_PRIVATE_WALLET_VERSION = b'\x04\x35\x83\x94'
|
||||
TESTNET_PUBLIC_WALLET_VERSION = b'\x04\x35\x87\xCF'
|
||||
FIRST_HARDENED_CHILD = 0x80000000
|
||||
PATH_LEVEL_BIP0044 = [0x8000002C, 0x80000000, 0x80000000, 0, 0]
|
||||
TESTNET_PATH_LEVEL_BIP0044 = [0x8000002C, 0x80000001, 0x80000000, 0, 0]
|
||||
|
||||
|
||||
522
pybtc/hdwallet.py
Normal file
522
pybtc/hdwallet.py
Normal file
@ -0,0 +1,522 @@
|
||||
import os
|
||||
import hmac
|
||||
|
||||
from secp256k1 import ffi
|
||||
from struct import pack, unpack
|
||||
from hashlib import pbkdf2_hmac
|
||||
from binascii import hexlify, unhexlify
|
||||
from .constants import *
|
||||
from .tools import private_to_public_key, is_public_key_valid, encode_base58, decode_base58, private_key_to_wif
|
||||
from .hash import hmac_sha512, hash160, double_sha256, sha256, double_sha256
|
||||
|
||||
|
||||
|
||||
# Hierarchical Deterministic Wallets (HD Wallets)
|
||||
# BIP-0032/0044
|
||||
|
||||
def create_xmaster_key(seed, testnet=False):
|
||||
"""
|
||||
Creating master private key from seed
|
||||
|
||||
:param bytes seed: cryptographically secure seed.
|
||||
:param bool testnet: if True, the check will be executed for TESTNET wallets.
|
||||
:return: extended private key (xprivate key) in dict format (fields: version, key, chain_code, depth, child, finger_print, is_private).
|
||||
"""
|
||||
if testnet:
|
||||
version = TESTNET_PRIVATE_WALLET_VERSION
|
||||
else:
|
||||
version = MAINNET_PRIVATE_WALLET_VERSION
|
||||
key = b'Bitcoin seed'
|
||||
intermediary = hmac_sha512(key, seed)
|
||||
mkey = intermediary[:32]
|
||||
chain_code = intermediary[32:]
|
||||
if is_xprivate_key_valid(mkey) and is_xprivate_key_valid(chain_code):
|
||||
return dict(version=version,
|
||||
key=mkey,
|
||||
depth=0,
|
||||
child=0,
|
||||
finger_print=b'\x00\x00\x00\x00',
|
||||
chain_code=chain_code,
|
||||
is_private=True)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def create_xpublic_key(key):
|
||||
"""
|
||||
Creating parent xpublic key from xprivate key
|
||||
|
||||
:param dict key: xprivate key.
|
||||
:return: extended public key (xpublic key) in dict format (fields: version, key, chain_code, depth, child, finger_print, is_private).
|
||||
"""
|
||||
if key['is_private']:
|
||||
if key['version'] == TESTNET_PRIVATE_WALLET_VERSION:
|
||||
version = TESTNET_PUBLIC_WALLET_VERSION
|
||||
else:
|
||||
version = MAINNET_PUBLIC_WALLET_VERSION
|
||||
pubkey = private_to_public_key(key['key'], hex=False)
|
||||
return dict(version=version,
|
||||
key=pubkey,
|
||||
depth=key['depth'],
|
||||
child=key['child'],
|
||||
finger_print=key['finger_print'],
|
||||
chain_code=key['chain_code'],
|
||||
is_private=False)
|
||||
return None
|
||||
|
||||
|
||||
def derive_xkey(seed, *path_level, bip44=True, testnet=True, wif=True):
|
||||
"""
|
||||
Key derivation
|
||||
|
||||
:param bytes seed: cryptographically secure seed.
|
||||
:param list path_level: list of levels in BIP32 path. For BIP-0044 of 5 levels. For bip44 is True can be None or empty list.
|
||||
:param bool bip44: define specification BIP-0044, by default True.
|
||||
:param bool testnet: if True, the derivation will be executed for TESTNET wallets.
|
||||
:param bool wif: define xkey wallet import format, by default True.
|
||||
:return: string (serialized xprivate key).
|
||||
"""
|
||||
if not bip44:
|
||||
if not len(path_level):
|
||||
raise TypeError("not specified path levels")
|
||||
mkey = create_xmaster_key(seed, testnet)
|
||||
xkey = create_child_privkey(mkey, path_level[0])
|
||||
for idx in path_level[1:]:
|
||||
xkey = create_child_privkey(xkey, idx)
|
||||
# сериализация и кодирование ключа
|
||||
if wif:
|
||||
result = encode_base58(serialize_xkey(xkey))
|
||||
else:
|
||||
result = serialize_xkey(xkey)
|
||||
return result
|
||||
else:
|
||||
if not is_validate_path_level(path_level, testnet):
|
||||
raise TypeError("path level does not match BIP-0044 - https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki")
|
||||
elif not len(path_level):
|
||||
if testnet:
|
||||
path_level = TESTNET_PATH_LEVEL_BIP0044
|
||||
else:
|
||||
path_level = PATH_LEVEL_BIP0044
|
||||
mkey = create_xmaster_key(seed, testnet)
|
||||
xkey = create_child_privkey(mkey, path_level[0])
|
||||
for idx in path_level[1:]:
|
||||
xkey = create_child_privkey(xkey, idx)
|
||||
# сериализация и кодирование ключа
|
||||
if wif:
|
||||
result = encode_base58(serialize_xkey(xkey))
|
||||
else:
|
||||
result = serialize_xkey(xkey)
|
||||
return result
|
||||
|
||||
|
||||
def xprivate_to_xpublic_key(xprv, encode_b58=True):
|
||||
"""
|
||||
Get xpublic key from xprivate key
|
||||
|
||||
:param str xprv: extended private in base58 format (serialized).
|
||||
:param bool wif: define return format (encoded base58 or bytes string), by default True is encode base58.
|
||||
:return: string (serialized xpublic key).
|
||||
"""
|
||||
if is_xprivate_key_valid(xprv):
|
||||
xprivkey = deserialize_xkey(xprv)
|
||||
xpubkey = create_xpublic_key(xprivkey)
|
||||
if encode_b58:
|
||||
return encode_base58(serialize_xkey(xpubkey))
|
||||
return serialize_xkey(xpubkey)
|
||||
else:
|
||||
raise TypeError("Private key must be serialized according to BIP-0032 - " \
|
||||
"https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format")
|
||||
|
||||
|
||||
# получение из расширенного приватного ключа обычный приватный ключ
|
||||
def xkey_to_private_key(xkey, wif=True, hex=False):
|
||||
"""
|
||||
Get private key from xprivate key
|
||||
|
||||
:param str xkey: extended private key in base58 format (serialized).
|
||||
:param bool wif: define xkey return, by default wallet import format. If wif up then hex ignore.
|
||||
:param bool hex: define xkey return format (hex or bytes string).
|
||||
:return: string (wif or hex) or bytes string.
|
||||
"""
|
||||
if is_xprivate_key_valid(xkey):
|
||||
xprivkey = deserialize_xkey(xkey)
|
||||
privkey = xprivkey['key']
|
||||
if xprivkey['version'] in TESTNET_PRIVATE_WALLET_VERSION:
|
||||
testnet = True
|
||||
else:
|
||||
testnet = False
|
||||
if wif:
|
||||
return private_key_to_wif(privkey, testnet=testnet)
|
||||
elif hex:
|
||||
return hexlify(privkey).decode()
|
||||
return privkey
|
||||
else:
|
||||
raise TypeError("Private key must be serialized according to BIP-0032 - " \
|
||||
"https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format")
|
||||
|
||||
|
||||
# получение из расширенного приватного/публичного ключа обычный публичный ключ
|
||||
def xkey_to_public_key(xkey, hex=False):
|
||||
"""
|
||||
Get public key from xprivate/xpublic key
|
||||
|
||||
:param str xkey: extended private or extended public key in base58 format (serialized).
|
||||
:param bool hex: define xkey return format (hex or bytes string).
|
||||
:return: string or bytes string.
|
||||
"""
|
||||
if is_xprivate_key_valid(xkey):
|
||||
xkey = xprivate_to_xpublic_key(xkey)
|
||||
if is_xpublic_key_valid(xkey):
|
||||
xpubkey = deserialize_xkey(xkey)
|
||||
pubkey = xpubkey['key']
|
||||
if xpubkey['version'] in TESTNET_PUBLIC_WALLET_VERSION:
|
||||
testnet = True
|
||||
else:
|
||||
testnet = False
|
||||
if hex:
|
||||
return hexlify(pubkey).decode()
|
||||
return pubkey
|
||||
else:
|
||||
raise TypeError("Private or public key must be serialized according to BIP-0032 - " \
|
||||
"https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format")
|
||||
|
||||
|
||||
# Создание дочернего приватного ключа
|
||||
def create_child_privkey(key, child_idx):
|
||||
"""
|
||||
Get child xprivate key from parent xprivate key
|
||||
|
||||
:param dict key: extended private key in dict format.
|
||||
:param int8 child_idx: chidl index.
|
||||
:return: dict (xprivate key).
|
||||
"""
|
||||
if key['is_private']:
|
||||
if child_idx < FIRST_HARDENED_CHILD:
|
||||
expanded_privkey = create_expanded_key(key, child_idx)
|
||||
else:
|
||||
expanded_privkey = create_expanded_hard_key(key, child_idx)
|
||||
if expanded_privkey:
|
||||
child_chain_code = expanded_privkey[32:]
|
||||
child_privkey = add_private_keys(expanded_privkey[:32], key['key'])
|
||||
if is_xprivate_key_valid(child_privkey):
|
||||
finger_print = hash160(private_to_public_key(key['key'], hex=False))[:4]
|
||||
return dict(version=key['version'],
|
||||
key=child_privkey,
|
||||
depth=key['depth'] + 1,
|
||||
child=child_idx,
|
||||
finger_print=finger_print,
|
||||
chain_code=child_chain_code,
|
||||
is_private=True)
|
||||
return None
|
||||
|
||||
|
||||
# создание дочернего публичного ключа
|
||||
def create_child_pubkey(key, child_idx):
|
||||
"""
|
||||
Get child xpublic key from parent xpublic key
|
||||
|
||||
:param dict key: extended public key in dict format.
|
||||
:param int8 child_idx: child index.
|
||||
:return: dict (xpublic key).
|
||||
"""
|
||||
if not key['is_private']:
|
||||
expanded_pubkey = create_expanded_key(key, child_idx)
|
||||
if expanded_pubkey:
|
||||
child_chain_code = expanded_pubkey[32:]
|
||||
ext_value = private_to_public_key(expanded_pubkey[:32], hex=False)
|
||||
child_pubkey = add_public_keys(ext_value, key['key'])
|
||||
if is_xpublic_key_valid(child_pubkey):
|
||||
finger_print = hash160(key['key'])[:4]
|
||||
return dict(version=key['version'],
|
||||
key=child_pubkey,
|
||||
depth=key['depth'] + 1,
|
||||
child=child_idx,
|
||||
finger_print=finger_print,
|
||||
chain_code=child_chain_code,
|
||||
is_private=False)
|
||||
return None
|
||||
|
||||
|
||||
# Создание расширенного приватного/публичного ключа
|
||||
def create_expanded_key(key, child_idx):
|
||||
"""
|
||||
Get intermediary expanded key from parent xprivate/xpublic key
|
||||
|
||||
:param dict key: extended private or public key in dict format.
|
||||
:param int8 child_idx: child index.
|
||||
:return: bytes string.
|
||||
"""
|
||||
if isinstance(key, dict):
|
||||
if not key.get('is_private') and child_idx < FIRST_HARDENED_CHILD:
|
||||
seed = key['key'] + pack('I', child_idx)
|
||||
return hmac_sha512(key['chain_code'], seed)
|
||||
elif key.get('is_private') and child_idx < FIRST_HARDENED_CHILD:
|
||||
public_key = private_to_public_key(key['key'], hex=False)
|
||||
seed = public_key + pack('I', child_idx)
|
||||
return hmac_sha512(key['chain_code'], seed)
|
||||
return None
|
||||
|
||||
|
||||
# Создание усиленного расширенного приватного ключа
|
||||
def create_expanded_hard_key(key, child_idx):
|
||||
"""
|
||||
Get intermediary hardened key from parent xprivate key
|
||||
|
||||
:param dict key: extended private key in dict format.
|
||||
:param int8 child_idx: child index.
|
||||
:return: bytes string.
|
||||
"""
|
||||
if isinstance(key, dict):
|
||||
if key.get('is_private') and child_idx >= FIRST_HARDENED_CHILD:
|
||||
seed = bytes([0]) + key['key'] + pack('I', child_idx)
|
||||
return hmac_sha512(key['chain_code'], seed)
|
||||
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 add_public_keys(ext_value, key):
|
||||
pubkey_ptr = ffi.new('secp256k1_pubkey *')
|
||||
if not secp256k1.secp256k1_ec_pubkey_parse(ECDSA_CONTEXT_VERIFY, pubkey_ptr, ext_value, len(ext_value)):
|
||||
raise TypeError("public key format error")
|
||||
if secp256k1.secp256k1_ec_pubkey_tweak_add(ECDSA_CONTEXT_ALL, pubkey_ptr, key):
|
||||
pubkey = ffi.new('char [%d]' % 33)
|
||||
outlen = ffi.new('size_t *', 33)
|
||||
if secp256k1.secp256k1_ec_pubkey_serialize(ECDSA_CONTEXT_VERIFY, pubkey, outlen, pubkey_ptr, EC_COMPRESSED):
|
||||
return bytes(ffi.buffer(pubkey, 33))
|
||||
return None
|
||||
|
||||
|
||||
def is_xpublic_key_valid(key):
|
||||
"""
|
||||
Check extended public key is valid according to BIP-0032.
|
||||
|
||||
:param key: extended public key in BASE58 or bytes string format.
|
||||
:return: boolean.
|
||||
"""
|
||||
if isinstance(key, str):
|
||||
if not key[:4] in ['xpub', 'tpub']:
|
||||
return False
|
||||
elif len(key) != 111:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_xprivate_key_valid(key):
|
||||
"""
|
||||
Check the extended private key is valid according to BIP-0032.
|
||||
|
||||
:param key: extended private key in BASE58 or bytes string format.
|
||||
:return: boolean.
|
||||
"""
|
||||
if isinstance(key, bytes):
|
||||
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
|
||||
elif isinstance(key, str):
|
||||
if len(key) == 111 and key[:4] in ['xprv', 'tprv']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_validate_path_level(path_level, testnet):
|
||||
"""
|
||||
Check path level is valid according to BIP-0044.
|
||||
|
||||
:param list path_level: list of 5 levels in BIP32 path.
|
||||
:param testnet: if True, the check will be executed for TESTNET wallets.
|
||||
:return: boolean.
|
||||
"""
|
||||
if not len(path_level):
|
||||
return True
|
||||
elif len(path_level) == 5:
|
||||
if path_level[0] != 0x8000002C:
|
||||
return False
|
||||
elif testnet and path_level[1] != 0x80000001:
|
||||
return False
|
||||
elif not testnet and path_level[1] != 0x80000000:
|
||||
return False
|
||||
elif path_level[2] < 0x80000000:
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def serialize_xkey(key):
|
||||
"""
|
||||
Serialization of extended keys.
|
||||
|
||||
:param dict key: extended private or public key in Dict format.
|
||||
:return: bytes string.
|
||||
"""
|
||||
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')
|
||||
|
||||
|
||||
def deserialize_xkey(encode_key):
|
||||
"""
|
||||
Deserialization of extended keys.
|
||||
|
||||
:param str key: extended private or public key in base58 format.
|
||||
:return: bytes string.
|
||||
"""
|
||||
raw_key = decode_base58(encode_key)
|
||||
decode_key = dict()
|
||||
if raw_key[:4] in [MAINNET_PUBLIC_WALLET_VERSION, MAINNET_PRIVATE_WALLET_VERSION]:
|
||||
decode_key['version'] = raw_key[:4]
|
||||
decode_key['depth'] = unpack('B', raw_key[4:5])[0]
|
||||
decode_key['finger_print'] = raw_key[5:9]
|
||||
decode_key['child'] = unpack('I', raw_key[9:13])[0]
|
||||
decode_key['chain_code'] = raw_key[13:45]
|
||||
if decode_key['version'] in [MAINNET_PRIVATE_WALLET_VERSION]:
|
||||
decode_key['is_private'] = True
|
||||
decode_key['key'] = raw_key[46:78]
|
||||
else:
|
||||
decode_key['is_private'] = False
|
||||
decode_key['key'] = raw_key[45:78]
|
||||
chk_sum = raw_key[78:]
|
||||
if chk_sum != double_sha256(raw_key[:-4])[:4]:
|
||||
raise TypeError("key checksum does not match")
|
||||
if decode_key:
|
||||
return decode_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)
|
||||
|
||||
@ -158,7 +158,7 @@ def is_public_key_valid(key):
|
||||
key = unhexlify(key)
|
||||
if len(key) < 33:
|
||||
return False
|
||||
if key[0] == 0x04 and len(key) != 65:
|
||||
elif key[0] == 0x04 and len(key) != 65:
|
||||
return False
|
||||
elif key[0] == 0x02 or key[0] == 0x03:
|
||||
if len(key) != 33:
|
||||
|
||||
5
setup.py
5
setup.py
@ -15,4 +15,7 @@ setup(name='pybtc',
|
||||
packages=find_packages(),
|
||||
install_requires=[ 'secp256k1'],
|
||||
include_package_data=True,
|
||||
zip_safe=False)
|
||||
package_data={
|
||||
'pybtc': ['bip-0039/*.txt'],
|
||||
},
|
||||
zip_safe=False)
|
||||
|
||||
73
tests/bip0032_fixtures.py
Normal file
73
tests/bip0032_fixtures.py
Normal file
@ -0,0 +1,73 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fail_key1():
|
||||
return b'\x00\x00\x00\x00'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fail_key2():
|
||||
return b'\x97\x8bq\xc6\xd8\xfew\xe5\xfa\xad\xdc\xc6\xc5\x91\xbd\xfb'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def good_key():
|
||||
return b'B\xa8\xe9v>y\xe2\x82\x10\x80\xc2\xa91\x10E\xe0XJ\xe6\xc7\x18\x9eE~\xa0^\xd1\x820\xe7\x18\x0c'
|
||||
|
||||
@pytest.fixture
|
||||
def master_key_hdwallet_mnet():
|
||||
return dict(version=b'\x04\x88\xad\xe4',
|
||||
key=b"Y\x9e'\xe00or'\xacD\x9c(l\x99\x0fxB\x03\xbd/]|+\xfd\xe89K!\x93\x0bN\x9b",
|
||||
depth=0,
|
||||
child=0,
|
||||
finger_print=b'\x00\x00\x00\x00',
|
||||
chain_code=b'B\xa8\xe9v>y\xe2\x82\x10\x80\xc2\xa91\x10E\xe0XJ\xe6\xc7\x18\x9eE~\xa0^\xd1\x820\xe7\x18\x0c',
|
||||
is_private=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def public_key_hdwallet_mnet():
|
||||
return dict(version=b'\x04\x88\xB2\x1E',
|
||||
key=b"\x03F\xcd\x96\xd7-\xc4Q\xee\xfc\xadc\n\xe4\xd2Xe\x02\x99(\x0f\xf5\x1c'\x16\xab\xd0\x05_\xb4:8\xfa",
|
||||
depth=0,
|
||||
child=0,
|
||||
finger_print=b'\x00\x00\x00\x00',
|
||||
chain_code=b'B\xa8\xe9v>y\xe2\x82\x10\x80\xc2\xa91\x10E\xe0XJ\xe6\xc7\x18\x9eE~\xa0^\xd1\x820\xe7\x18\x0c',
|
||||
is_private=False)
|
||||
|
||||
@pytest.fixture
|
||||
def master_key_hdwallet_tnet():
|
||||
return dict(version=b'\x04\x35\x83\x94',
|
||||
key=b"Y\x9e'\xe00or'\xacD\x9c(l\x99\x0fxB\x03\xbd/]|+\xfd\xe89K!\x93\x0bN\x9b",
|
||||
depth=0,
|
||||
child=0,
|
||||
finger_print=b'\x00\x00\x00\x00',
|
||||
chain_code=b'B\xa8\xe9v>y\xe2\x82\x10\x80\xc2\xa91\x10E\xe0XJ\xe6\xc7\x18\x9eE~\xa0^\xd1\x820\xe7\x18\x0c',
|
||||
is_private=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def public_key_hdwallet_tnet():
|
||||
return dict(version=b'\x04\x35\x87\xCF',
|
||||
key=b"\x03F\xcd\x96\xd7-\xc4Q\xee\xfc\xadc\n\xe4\xd2Xe\x02\x99(\x0f\xf5\x1c'\x16\xab\xd0\x05_\xb4:8\xfa",
|
||||
depth=0,
|
||||
child=0,
|
||||
finger_print=b'\x00\x00\x00\x00',
|
||||
chain_code=b'B\xa8\xe9v>y\xe2\x82\x10\x80\xc2\xa91\x10E\xe0XJ\xe6\xc7\x18\x9eE~\xa0^\xd1\x820\xe7\x18\x0c',
|
||||
is_private=False)
|
||||
|
||||
@pytest.fixture
|
||||
def privkey_hdwallet_base58():
|
||||
return 'xprv9s21ZrQH143K2irFFw4cdtV8EicuR6Y5P2WqMpbLWhnZUADeKUi52Jh8Pzt8K9RqHanNsrVXf6VhNXQv2ypWxsTSWB8UsqjxkGPxHcjyXNC'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pubkey_hdwallet_base58():
|
||||
return 'xpub661MyMwAqRbcFCviMxbd12RrnkTPpZFvkFSSACzx53KYLxYns22Ka71cFHiMLQz3NaPYeN7EcdDUwH5QTWeS56jc2DzAzuKU2cfwp5cvyoR'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bad_key_hdwallet_base58():
|
||||
return 'xpeb661MyMwAq1ecFCviMxbd12RrnkTPpZFvkFSSACzx53KYLxYns22Ka71cFHiMLQz3NaPYeN7EcdDUwH5QTWeS56jc2DzAzuKU2cfwp5cvyoR'
|
||||
|
||||
74
tests/bip0039_fixtures.py
Normal file
74
tests/bip0039_fixtures.py
Normal file
@ -0,0 +1,74 @@
|
||||
import pytest
|
||||
import random
|
||||
|
||||
|
||||
#@pytest.fixture
|
||||
#def gen_entropy(bit_size):
|
||||
#rnd = random.systemRandom(123456)
|
||||
#return rnd.randint(0, 255)
|
||||
|
||||
@pytest.fixture
|
||||
def mnemonic_128():
|
||||
return ['nurse', 'fortune', 'immune', 'rapid', 'trash',
|
||||
'very', 'turkey', 'romance', 'short', 'clutch', 'hunt', 'wait']
|
||||
|
||||
@pytest.fixture
|
||||
def mnemonic_160():
|
||||
return ['mail', 'paddle', 'wine', 'fox', 'various', 'absent',
|
||||
'manage', 'divert', 'awful', 'push', 'mystery',
|
||||
'mule', 'arrest', 'lawsuit', 'orient']
|
||||
|
||||
@pytest.fixture
|
||||
def mnemonic_192():
|
||||
return ['craft', 'first', 'champion', 'border', 'rely',
|
||||
'dance', 'tag', 'voyage', 'category', 'orbit',
|
||||
'hungry', 'caught', 'occur', 'wonder', 'history',
|
||||
'jacket', 'first', 'plunge']
|
||||
|
||||
@pytest.fixture
|
||||
def mnemonic_224():
|
||||
return ['liberty', 'family', 'lobster', 'omit', 'glide',
|
||||
'vague', 'market', 'cancel', 'exotic', 'jazz',
|
||||
'sausage', 'elite', 'tuition', 'grief', 'typical',
|
||||
'hobby', 'local', 'impact', 'leopard', 'basic', 'obscure']
|
||||
|
||||
@pytest.fixture
|
||||
def mnemonic_256():
|
||||
return ['neck', 'adjust', 'town', 'ticket', 'sunset', 'pulse',
|
||||
'space', 'dolphin', 'farm', 'absent', 'cat', 'adult',
|
||||
'erupt', 'student', 'globe', 'tooth', 'tackle', 'group',
|
||||
'sponsor', 'dice', 'add', 'maid', 'illegal', 'major']
|
||||
|
||||
@pytest.fixture
|
||||
def entropy_128():
|
||||
return b'\x97\x8bq\xc6\xd8\xfew\xe5\xfa\xad\xdc\xc6\xc5\x91\xbd\xfb'
|
||||
|
||||
@pytest.fixture
|
||||
def entropy_160():
|
||||
return b'\x863\xdb\xee./\x18\x01a\xb9\xfe\x10\xb5\xd6I\xc8\xa0\xc6\xfcg'
|
||||
|
||||
@pytest.fixture
|
||||
def entropy_192():
|
||||
return b'2\n\xec\x98\x0c\xebVn\xb7O\xb0$3}\xbd\x129\x8d\xfa\x1b\x0b\xb8Wt'
|
||||
|
||||
@pytest.fixture
|
||||
def entropy_224():
|
||||
return b'\x80\xeaR\x0c\xcd61\xe1b\t\tP\x0e\xee\xfe\xa4\x0e\xa2\xcd:\xfbb\x83N:\x01\t\x89'
|
||||
|
||||
@pytest.fixture
|
||||
def entropy_256():
|
||||
return b'\x93\xa0s\x99p\xdd\x99[4\x12\x06S \x14\x8f\x01\xe4\xcf\xae\xd8\xd7&\xdd\x0c\xdfI\x9e\xb03\x0c\x1cD'
|
||||
|
||||
|
||||
@pytest.yield_fixture
|
||||
def wordlist():
|
||||
f = None
|
||||
def select_wordlist(filename):
|
||||
nonlocal f
|
||||
assert f is None
|
||||
f = open(filename)
|
||||
return f
|
||||
yield select_wordlist
|
||||
if f is not None:
|
||||
f.close()
|
||||
|
||||
6
tests/conftest.py
Normal file
6
tests/conftest.py
Normal file
@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
pytest_plugins = ['bip0032_fixtures', 'bip0039_fixtures']
|
||||
|
||||
180
tests/test_bip0032.py
Normal file
180
tests/test_bip0032.py
Normal file
@ -0,0 +1,180 @@
|
||||
import os
|
||||
import random
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
from binascii import hexlify, unhexlify
|
||||
from pybtc.hdwallet import *
|
||||
from pybtc.tools import encode_base58, decode_base58
|
||||
|
||||
|
||||
|
||||
def test_create_master_key_hdwallet(mnemonic_256):
|
||||
passphrase = ' '.join(mnemonic_256)
|
||||
seed = mnemonic_to_seed(passphrase, 'P@ssw0rd')
|
||||
assert seed is not None
|
||||
assert len(seed) == 64
|
||||
master_key = create_xmaster_key(seed)
|
||||
assert master_key is not None
|
||||
assert isinstance(master_key, dict)
|
||||
assert master_key.get('version') is not None
|
||||
assert master_key.get('key') is not None
|
||||
assert master_key.get('depth') is not None
|
||||
assert master_key.get('child') is not None
|
||||
assert master_key.get('finger_print') is not None
|
||||
assert master_key.get('chain_code') is not None
|
||||
assert master_key.get('is_private') is not None
|
||||
assert master_key['is_private']
|
||||
|
||||
|
||||
def test_create_public_key_hdwallet(master_key_hdwallet_mnet):
|
||||
public_key = create_xpublic_key(master_key_hdwallet_mnet)
|
||||
assert public_key is not None
|
||||
assert len(public_key['key']) == 33
|
||||
|
||||
|
||||
def test_validate_private_key(fail_key1, fail_key2, good_key):
|
||||
assert not is_xprivate_key_valid(fail_key1)
|
||||
assert not is_xprivate_key_valid(fail_key2)
|
||||
assert is_xprivate_key_valid(good_key)
|
||||
|
||||
|
||||
def test_create_expanded_key(master_key_hdwallet_mnet, public_key_hdwallet_mnet):
|
||||
result = create_expanded_key(b'asdasdasd', 0)
|
||||
assert result is None
|
||||
result = create_expanded_key(master_key_hdwallet_mnet, 0x80000000)
|
||||
assert result is None
|
||||
result = create_expanded_key(master_key_hdwallet_mnet, 0)
|
||||
assert result is not None
|
||||
assert len(result) == 64
|
||||
result = create_expanded_key(public_key_hdwallet_mnet, 0)
|
||||
assert result is not None
|
||||
assert len(result) == 64
|
||||
|
||||
|
||||
def test_create_expanded_hard_key(master_key_hdwallet_mnet, public_key_hdwallet_mnet):
|
||||
result = create_expanded_hard_key(master_key_hdwallet_mnet, 0)
|
||||
assert result is None
|
||||
result = create_expanded_hard_key(master_key_hdwallet_mnet, 0x80000000)
|
||||
assert result is not None
|
||||
assert len(result) == 64
|
||||
|
||||
|
||||
def test_create_child_privkey(master_key_hdwallet_mnet, public_key_hdwallet_mnet):
|
||||
result = create_child_privkey(public_key_hdwallet_mnet, 0)
|
||||
assert result is None
|
||||
result = create_child_privkey(master_key_hdwallet_mnet, 0)
|
||||
assert result is not None
|
||||
assert isinstance(result, dict)
|
||||
assert result.get('is_private')
|
||||
|
||||
|
||||
def test_create_child_pubkey(master_key_hdwallet_mnet, public_key_hdwallet_mnet):
|
||||
result = create_child_pubkey(master_key_hdwallet_mnet, 0)
|
||||
assert result is None
|
||||
result = create_child_pubkey(public_key_hdwallet_mnet, 0)
|
||||
assert result is not None
|
||||
assert isinstance(result, dict)
|
||||
assert not result.get('is_private')
|
||||
|
||||
|
||||
def test_serialize_key_hdwallet(master_key_hdwallet_mnet, public_key_hdwallet_tnet):
|
||||
serialize_mkey = serialize_xkey(master_key_hdwallet_mnet)
|
||||
assert serialize_mkey is not None
|
||||
assert isinstance(serialize_mkey, bytes)
|
||||
assert len(serialize_mkey[:-4]) == 78
|
||||
ser_encode = encode_base58(serialize_mkey)
|
||||
assert ser_encode[:4] in ['xprv', 'tprv']
|
||||
|
||||
serialize_pkey = serialize_xkey(public_key_hdwallet_tnet)
|
||||
assert serialize_pkey is not None
|
||||
assert isinstance(serialize_pkey, bytes)
|
||||
assert len(serialize_pkey[:-4]) == 78
|
||||
ser_encode = encode_base58(serialize_pkey)
|
||||
assert ser_encode[:4] in ['xpub', 'tpub']
|
||||
|
||||
|
||||
def test_deserialize_key(privkey_hdwallet_base58, pubkey_hdwallet_base58, bad_key_hdwallet_base58):
|
||||
#десериализация приватного ключа
|
||||
privkey = deserialize_xkey(privkey_hdwallet_base58)
|
||||
assert privkey is not None
|
||||
assert isinstance(privkey, dict)
|
||||
assert privkey['is_private']
|
||||
#десериализация публичного ключа
|
||||
pubkey = deserialize_xkey(pubkey_hdwallet_base58)
|
||||
assert pubkey is not None
|
||||
assert isinstance(pubkey, dict)
|
||||
assert not pubkey['is_private']
|
||||
#десериализация некорретного ключа
|
||||
pubkey = deserialize_xkey(bad_key_hdwallet_base58)
|
||||
assert pubkey is None
|
||||
|
||||
|
||||
def test_derive_xkey(mnemonic_256):
|
||||
passphrase = ' '.join(mnemonic_256)
|
||||
seed = mnemonic_to_seed(passphrase, 'P@ssw0rd')
|
||||
params = [0x8000002C, 0x80000001, 0x80000000, 0, 0]
|
||||
result = derive_xkey(seed, *params, bip44=True, testnet=True, wif=True)
|
||||
assert result is not None
|
||||
assert isinstance(result, str)
|
||||
assert result[:4] in 'tprv'
|
||||
|
||||
|
||||
def test_xprivate_to_xpublic_key(privkey_hdwallet_base58):
|
||||
xpubkey = xprivate_to_xpublic_key(privkey_hdwallet_base58)
|
||||
assert xpubkey is not None
|
||||
assert isinstance(xpubkey, str)
|
||||
assert len(xpubkey) == 111
|
||||
assert xpubkey[:4] in ['xpub', 'tpub']
|
||||
xpubkey = xprivate_to_xpublic_key(privkey_hdwallet_base58, False)
|
||||
assert xpubkey is not None
|
||||
assert isinstance(xpubkey, bytes)
|
||||
|
||||
|
||||
def test_xkey_to_private_key(privkey_hdwallet_base58):
|
||||
privkey = xkey_to_private_key(privkey_hdwallet_base58, True, False)
|
||||
assert privkey is not None
|
||||
assert isinstance(privkey, str)
|
||||
privkey = xkey_to_private_key(privkey_hdwallet_base58, False, True)
|
||||
assert privkey is not None
|
||||
assert isinstance(privkey, str)
|
||||
privkey = xkey_to_private_key(privkey_hdwallet_base58, False, False)
|
||||
assert privkey is not None
|
||||
assert isinstance(privkey, bytes)
|
||||
|
||||
|
||||
def test_xkey_to_public_key(privkey_hdwallet_base58, pubkey_hdwallet_base58):
|
||||
# from xpubkey to pubkey
|
||||
pubkey = xkey_to_public_key(pubkey_hdwallet_base58, True)
|
||||
assert pubkey is not None
|
||||
assert isinstance(pubkey, str)
|
||||
pubkey = xkey_to_public_key(pubkey_hdwallet_base58, False)
|
||||
assert pubkey is not None
|
||||
assert isinstance(pubkey, bytes)
|
||||
# from xprivkey to pubkey
|
||||
pubkey = xkey_to_public_key(privkey_hdwallet_base58, True)
|
||||
assert pubkey is not None
|
||||
assert isinstance(pubkey, str)
|
||||
pubkey = xkey_to_public_key(privkey_hdwallet_base58, False)
|
||||
assert pubkey is not None
|
||||
assert isinstance(pubkey, bytes)
|
||||
|
||||
|
||||
def test_validate_path_level():
|
||||
params = [0x8000002C, 0x80000001, 0x80000000, 0, 0]
|
||||
testnet = True
|
||||
assert is_validate_path_level(params, testnet)
|
||||
testnet = False
|
||||
assert not is_validate_path_level(params, testnet)
|
||||
params = [0, 0x80000001, 0x80000000, 0, 0]
|
||||
testnet = True
|
||||
assert not is_validate_path_level(params, testnet)
|
||||
params = [0x8000002C, 0x80000001, 0, 0, 0]
|
||||
testnet = True
|
||||
assert not is_validate_path_level(params, testnet)
|
||||
params = [0x8000002C, 0x80000001, 0, 0]
|
||||
testnet = True
|
||||
assert not is_validate_path_level(params, testnet)
|
||||
params = []
|
||||
assert is_validate_path_level(params, testnet)
|
||||
|
||||
119
tests/test_bip0039.py
Normal file
119
tests/test_bip0039.py
Normal file
@ -0,0 +1,119 @@
|
||||
import os
|
||||
import random
|
||||
import hashlib
|
||||
import hmac
|
||||
from binascii import hexlify, unhexlify
|
||||
from pybtc.hdwallet import *
|
||||
|
||||
|
||||
|
||||
def test_recovery_from_passphrase_12(entropy_128, mnemonic_128):
|
||||
passphrase = ' '.join(mnemonic_128)
|
||||
entropy = mnemonic_to_entropy(passphrase, 'english')
|
||||
assert entropy == entropy_128
|
||||
|
||||
|
||||
def test_recovery_from_passphrase_15(entropy_160, mnemonic_160):
|
||||
passphrase = ' '.join(mnemonic_160)
|
||||
entropy = mnemonic_to_entropy(passphrase, 'english')
|
||||
assert entropy == entropy_160
|
||||
|
||||
|
||||
def test_recovery_from_passphrase_18(entropy_192, mnemonic_192):
|
||||
passphrase = ' '.join(mnemonic_192)
|
||||
entropy = mnemonic_to_entropy(passphrase, 'english')
|
||||
assert entropy == entropy_192
|
||||
|
||||
|
||||
def test_recovery_from_passphrase_21(entropy_224, mnemonic_224):
|
||||
passphrase = ' '.join(mnemonic_224)
|
||||
entropy = mnemonic_to_entropy(passphrase, 'english')
|
||||
assert entropy == entropy_224
|
||||
|
||||
|
||||
def test_recovery_from_passphrase_24(entropy_256, mnemonic_256):
|
||||
passphrase = ' '.join(mnemonic_256)
|
||||
entropy = mnemonic_to_entropy(passphrase, 'english')
|
||||
assert entropy == entropy_256
|
||||
|
||||
|
||||
def test_create_mnemonic(entropy_128, entropy_160, entropy_192, entropy_224, entropy_256):
|
||||
mnemonic = create_mnemonic(entropy_128, 'english')
|
||||
assert len(mnemonic) == 12
|
||||
|
||||
mnemonic = create_mnemonic(entropy_160, 'english')
|
||||
assert len(mnemonic) == 15
|
||||
|
||||
mnemonic = create_mnemonic(entropy_192, 'english')
|
||||
assert len(mnemonic) == 18
|
||||
|
||||
mnemonic = create_mnemonic(entropy_224, 'english')
|
||||
assert len(mnemonic) == 21
|
||||
|
||||
mnemonic = create_mnemonic(entropy_256, 'english')
|
||||
assert len(mnemonic) == 24
|
||||
|
||||
|
||||
def test_create_wordlist():
|
||||
wordlist_en = create_wordlist('english')
|
||||
wordlist_fr = create_wordlist('french')
|
||||
wordlist_it = create_wordlist('italian')
|
||||
wordlist_sp = create_wordlist('spanish')
|
||||
assert 'abandon' in wordlist_en
|
||||
assert 'abaisser' in wordlist_fr
|
||||
assert 'abaco' in wordlist_it
|
||||
assert 'ábaco' in wordlist_sp
|
||||
|
||||
|
||||
def test_create_seed(mnemonic_256):
|
||||
passphrase = ' '.join(mnemonic_256)
|
||||
seed = mnemonic_to_seed(passphrase, 'P@ssw0rd')
|
||||
assert seed is not None
|
||||
assert len(seed) == 64
|
||||
|
||||
|
||||
def test_create_passphrase():
|
||||
passphrase = create_passphrase(128, 'english')
|
||||
assert len(passphrase.split()) == 12
|
||||
|
||||
passphrase = create_passphrase(160, 'english')
|
||||
assert len(passphrase.split()) == 15
|
||||
|
||||
passphrase = create_passphrase(192, 'english')
|
||||
assert len(passphrase.split()) == 18
|
||||
|
||||
passphrase = create_passphrase(224, 'english')
|
||||
assert len(passphrase.split()) == 21
|
||||
|
||||
passphrase = create_passphrase(256, 'english')
|
||||
assert len(passphrase.split()) == 24
|
||||
|
||||
|
||||
def test_add_checksum_ent(entropy_128, entropy_160, entropy_192, entropy_224, entropy_256):
|
||||
ent_add_chksum = add_checksum_ent(entropy_128)
|
||||
ent_hash = hashlib.sha256(entropy_128).hexdigest()
|
||||
fb = unhexlify(ent_hash)[0]
|
||||
assert (fb >> 4) & ent_add_chksum
|
||||
|
||||
ent_add_chksum = add_checksum_ent(entropy_160)
|
||||
ent_hash = hashlib.sha256(entropy_160).hexdigest()
|
||||
fb = unhexlify(ent_hash)[0]
|
||||
assert (fb >> 3) & ent_add_chksum
|
||||
|
||||
ent_add_chksum = add_checksum_ent(entropy_192)
|
||||
ent_hash = hashlib.sha256(entropy_192).hexdigest()
|
||||
fb = unhexlify(ent_hash)[0]
|
||||
assert (fb >> 2) & ent_add_chksum
|
||||
|
||||
ent_add_chksum = add_checksum_ent(entropy_224)
|
||||
ent_hash = hashlib.sha256(entropy_224).hexdigest()
|
||||
fb = unhexlify(ent_hash)[0]
|
||||
assert (fb >> 1) & ent_add_chksum
|
||||
|
||||
ent_add_chksum = add_checksum_ent(entropy_256)
|
||||
ent_hash = hashlib.sha256(entropy_256).hexdigest()
|
||||
fb = unhexlify(ent_hash)[0]
|
||||
assert fb & ent_add_chksum
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user