Skip to content

Commit

Permalink
Added randomness tests for entropy generation
Browse files Browse the repository at this point in the history
Fixed entropy generation of polynomial coefficients in Sharmir's secret sharing
  • Loading branch information
4tochka committed Jul 10, 2021
1 parent c009218 commit 77be7d4
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 34 deletions.
2 changes: 1 addition & 1 deletion pybtc/connector/block_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
19 changes: 18 additions & 1 deletion pybtc/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 12 additions & 25 deletions pybtc/functions/bip39_mnemonic.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down
165 changes: 165 additions & 0 deletions pybtc/functions/entropy.py
Original file line number Diff line number Diff line change
@@ -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.')
2 changes: 1 addition & 1 deletion pybtc/functions/key.py
Original file line number Diff line number Diff line change
@@ -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__

Expand Down
16 changes: 12 additions & 4 deletions pybtc/functions/shamir.py
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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)])
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
18 changes: 17 additions & 1 deletion tests/test_bip39_mnemonic_functions.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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):
Expand Down

0 comments on commit 77be7d4

Please sign in to comment.