diff --git a/pybtc/connector/block_loader.py b/pybtc/connector/block_loader.py index 55aff6c..d7cd0f4 100644 --- a/pybtc/connector/block_loader.py +++ b/pybtc/connector/block_loader.py @@ -463,7 +463,7 @@ async def load_blocks(self, height, limit): block["txMap"].add((address, tx_pointer)) out["_address"] = address - self.coins[o] = (pointer, out["value"], address) + self.coins[o] = (pointer, out["value"], w) if self.option_analytica: tx = block["rawTx"][z] diff --git a/pybtc/constants.py b/pybtc/constants.py index a176f83..26ec383 100644 --- a/pybtc/constants.py +++ b/pybtc/constants.py @@ -105,7 +105,24 @@ TESTNET_M84_XPRIVATE_KEY_PREFIX = b'\x04\x5f\x18\xbc' TESTNET_M84_XPUBLIC_KEY_PREFIX = b'\x04\x5f\x1c\xf6' - +GAMMA_NUM_LN = 607 / 128 +GAMMA_TABLE_LN = [0.99999999999999709182, + 57.156235665862923517, + -59.597960355475491248, + 14.136097974741747174, + -0.49191381609762019978, + 0.33994649984811888699e-4, + 0.46523628927048575665e-4, + -0.98374475304879564677e-4, + 0.15808870322491248884e-3, + -0.21026444172410488319e-3, + 0.21743961811521264320e-3, + -0.16431810653676389022e-3, + 0.84418223983852743293e-4, + -0.26190838401581408670e-4, + 0.36899182659531622704e-5] +MACHEP = 1.11022302462515654042E-16 +MAXLOG = 7.09782712893383996732E2 HARDENED_KEY = 0x80000000 FIRST_HARDENED_CHILD = 0x80000000 diff --git a/pybtc/functions/bip39_mnemonic.py b/pybtc/functions/bip39_mnemonic.py index e61a3ff..7e157b8 100644 --- a/pybtc/functions/bip39_mnemonic.py +++ b/pybtc/functions/bip39_mnemonic.py @@ -1,32 +1,11 @@ from pybtc.constants import * -import time import hashlib from pybtc.functions.hash import sha256 from pybtc.functions.shamir import split_secret, restore_secret -from pybtc.functions.tools import int_from_bytes, get_bytes +from pybtc.functions.tools import get_bytes import random import math -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: # pragma: no cover - 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): @@ -267,7 +246,8 @@ def create_mnemonic_additional_share(threshold_shares, language='english', word_ -def combine_mnemonic(shares, language='english', word_list_dir=None, word_list=None): +def combine_mnemonic(shares, share_id = None, language='english', word_list_dir=None, word_list=None): + # share_id used to reconstruct upper level share embedded_index = isinstance(shares, list) s = dict() if embedded_index: @@ -284,8 +264,15 @@ def combine_mnemonic(shares, language='english', word_list_dir=None, word_list=N s[share] = mnemonic_to_entropy(shares[share], language=language, hex=False, word_list_dir=word_list_dir, word_list=word_list) entropy = restore_secret(s) - return entropy_to_mnemonic(entropy, language=language, word_list_dir=word_list_dir, - word_list=word_list) + + if share_id is None: + return entropy_to_mnemonic(entropy, language=language, word_list_dir=word_list_dir, word_list=word_list) + else: + m = entropy_to_mnemonic(entropy, language=language, word_list_dir=word_list_dir, word_list=word_list) + m = m.split()[:-1] + m.append(share_id) + return " ".join(m) + def is_mnemonic_valid(mnemonic, word_list=None): diff --git a/pybtc/functions/entropy.py b/pybtc/functions/entropy.py new file mode 100644 index 0000000..dd52eb9 --- /dev/null +++ b/pybtc/functions/entropy.py @@ -0,0 +1,165 @@ +from pybtc.constants import * +import random +import math + +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]') + c = 0 + while True: + a = random.SystemRandom().randint(0, ECDSA_SEC256K1_ORDER) + try: + randomness_test(a) + if a > ECDSA_SEC256K1_ORDER: + raise Exception("ECDSA_SEC256K1_ORDER") + break + except: + if c < 100: + c += 1 + continue + else: + raise Exception("Entropy generator filed") + h = a.to_bytes(32, byteorder="big") + return h[:int(strength/8)] if not hex else h[:int(strength/8)].hex() + +def ln_gamma(z): + if z<0: + return None + x = GAMMA_TABLE_LN[0] + i = len(GAMMA_TABLE_LN) - 1 + while i > 0: + x += GAMMA_TABLE_LN[i] / (z + i) + i -= 1 + t = z + GAMMA_NUM_LN + 0.5 + return 0.5 * math.log(2 * math.pi) + (z + 0.5) * math.log(t) - t + math.log(x) - math.log(z) + +def igam(a, x): + if x <= 0 or a <= 0: + return 0.0 + if x > 1.0 and x > a: + return 1.0 - igamc(a, x) + ax = a * math.log(x) - x - ln_gamma(a) + + if ax < -MAXLOG: + return 0.0 + ax = math.exp(ax) + r = a + c = 1.0 + ans = 1.0 + while True: + r += 1.0 + c *= x / r + ans += c + if not c / ans > MACHEP: + break + return ans * ax / a + +def igamc(a, x): + if x <= 0 or a <= 0: + return 1.0 + if x < 1.0 or x < a: + return 1.0 - igam(a, x) + big = 4.503599627370496e15 + biginv = 2.22044604925031308085e-16 + ax = a * math.log(x) - x - ln_gamma(a) + if ax < - MAXLOG: + return 0.0 + ax = math.exp(ax) + y = 1.0 - a + z = x + y + 1.0 + c = 0.0 + pkm2 = 1.0 + qkm2 = x + pkm1 = x + 1.0 + qkm1 = z * x + ans = pkm1 / qkm1 + + while True: + c += 1.0 + y += 1.0 + z += 2.0 + yc = y * c + pk = pkm1 * z - pkm2 * yc + qk = qkm1 * z - qkm2 * yc + if qk != 0: + r = pk / qk + t = abs((ans - r) / r) + ans = r + else: + t = 1.0 + + pkm2 = pkm1 + pkm1 = pk + qkm2 = qkm1 + qkm1 = qk + if abs(pk) > big: + pkm2 *= biginv + pkm1 *= biginv + qkm2 *= biginv + qkm1 *= biginv + if not t > MACHEP: + break + return ans * ax + +def randomness_test(b): + # NIST SP 800-22 randomness tests + # https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf + s = bin(b)[2:].rjust(256, '0') + + # Frequency (Monobit) Test + n = len(s) + s_0 = s.count('0') + s_1 = s.count('1') + s_obs =abs(s_1 - s_0) / math.sqrt(2 * n) + if math.erfc(s_obs) < 0.01: + raise Exception('Frequency (Monobit) Test failed.') + + # Runs Test + pi = s_1 / n + r = 2 / math.sqrt(n) + if abs(pi - 0.5) > r: + raise Exception('Runs Test failed.') + v = 1 + for i in range(n-1): + v += 0 if (s[i] == s[i + 1]) else 1 + + a = v - 2 * n * pi * (1 - pi) + q = 2 * math.sqrt(2 * n) * pi * (1 - pi); + + if math.erfc(abs(a) / q) < 0.01: + raise Exception('Runs Test failed.') + + # Test for the Longest Run of Ones in a Block + s = s[:256] + blocks = [s[i:i + 8] for i in range(0, len(s), 8)] + v = [0, 0, 0, 0] + for block in blocks: + if block == "": + continue + l = max(len(i) for i in block.split("0")) + if l < 2: + v[0] += 1 + elif l == 2: + v[1] += 1 + elif l == 3: + v[2] += 1 + else: + v[3] += 1 + + k = 3 + r = len(blocks) + pi = [0.2148, 0.3672, 0.2305, 0.1875] + x_sqrt = math.pow(v[0] - r * pi[0], 2) / (r * pi[0]) + x_sqrt += math.pow(v[1] - r * pi[1], 2) / (r * pi[1]) + x_sqrt += math.pow(v[2] - r * pi[2], 2) / (r * pi[2]) + x_sqrt += math.pow(v[3] - r * pi[3], 2) / (r * pi[3]) + + if (igamc(k / 2, x_sqrt / 2) < 0.01): + raise Exception('Test for the Longest Run of Ones in a Block failed.') diff --git a/pybtc/functions/key.py b/pybtc/functions/key.py index 685c1b9..44b953a 100644 --- a/pybtc/functions/key.py +++ b/pybtc/functions/key.py @@ -1,7 +1,7 @@ from pybtc.constants import * from pybtc.functions.encode import encode_base58, decode_base58 from pybtc.functions.hash import double_sha256 -from .bip39_mnemonic import generate_entropy +from pybtc.functions.entropy import generate_entropy bytes_from_hex = bytes.fromhex from pybtc.crypto import __secp256k1_ec_pubkey_create__ diff --git a/pybtc/functions/shamir.py b/pybtc/functions/shamir.py index 26f9a45..f283ac0 100644 --- a/pybtc/functions/shamir.py +++ b/pybtc/functions/shamir.py @@ -1,5 +1,6 @@ import random import time +from pybtc.functions.entropy import generate_entropy def _precompute_gf256_exp_log(): exp = [0 for i in range(255)] @@ -108,13 +109,20 @@ def split_secret(threshold, total, secret, index_bits=8): shares_indexes.append(q) shares[q] = b"" - + e = generate_entropy(hex=False) + e_i = 0 for b in secret: q = [b] + for i in range(threshold - 1): - a = random.SystemRandom().randint(0, 255) - i = int((time.time() % 0.0001) * 1000000) + 1 - q.append((a * i) % 255) + if e_i < len(e): + a = e[e_i] + e_i += 1 + else: + e = generate_entropy(hex=False) + a = e[0] + e_i = 1 + q.append(a) for z in shares_indexes: shares[z] += bytes([_fn(z, q)]) diff --git a/setup.py b/setup.py index 6610d8a..86e0598 100644 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ def run(self): return _build_ext.run(self) setup(name='pybtc', - version='2.3.9', + version='2.3.10', description='Python Bitcoin library', keywords='bitcoin', url='https://github.com/bitaps-com/pybtc', diff --git a/tests/test_bip39_mnemonic_functions.py b/tests/test_bip39_mnemonic_functions.py index f1d0f4a..b839414 100644 --- a/tests/test_bip39_mnemonic_functions.py +++ b/tests/test_bip39_mnemonic_functions.py @@ -1,6 +1,8 @@ import pytest -from pybtc.functions.bip39_mnemonic import generate_entropy +from pybtc.functions.entropy import generate_entropy +from pybtc.functions.entropy import igam +from pybtc.functions.entropy import igamc from pybtc.functions.bip39_mnemonic import load_word_list from pybtc.functions.bip39_mnemonic import entropy_to_mnemonic from pybtc.functions.bip39_mnemonic import mnemonic_to_entropy @@ -20,6 +22,20 @@ def test_generate_entropy(): with pytest.raises(ValueError): generate_entropy(strength=40) +def test_gam_funtions(): + q = 0.0000000000001 + assert igam(0.56133437, 7.79533309) - 0.99989958147838275959 < q + assert igam(3.80398274, 0.77658461) - 0.01162079725209424867 < q + assert igam(6.71146614, 0.39790492) - 0.00000051486912406477 < q + assert igam(5.05505886, 6.08602125) - 0.71809645160316382118 < q + assert igam(9.45603411, 4.60043366) - 0.03112942473115925396 < q + assert igamc(3.08284045, 0.79469709) - 0.95896191705843125686 < q + assert igamc(7.91061495, 9.30889249) - 0.27834295370900602462 < q + assert igamc(4.89616780, 5.75314859) - 0.30291667399717547848 < q + assert igamc(8.11261940, 4.05857957) - 0.95010562492501993148 < q + assert igamc(1.34835811, 6.64708856) - 0.00295250273836756942 < q + + def test_load_word_list(): assert len(load_word_list()) == 2048 with pytest.raises(ValueError):