Let's keep it simple and assume the range is 1-10. The half of the range is 5. When you randomly generate numbers you would expect that those generated numbers are well distributed within the range. That means that as more numbers you generate (let's say thousands and millions of them) the total of "lower half" and "upper half" should be around 'n' (=total of numbers generated). That's what randomness does, nothing fancy.
For example, range is 1-10 and we generate 1000 numbers. I would expect that about 500 numbers are on the lower half and 500 numbers are in the upper half. As n raises this distribution should be more clear and you should see the results clearly near n/2
Currently I'm busy with some other stuff, I will post the solution later, be patient. The solution was not library secure (I tried that, too) but it works also with the simple random library in Python. The culprit was something quite different
EDIT:Well, back again. First of all, here are two variations of my updated program. The first one is using python's "random" and the second one is using python's "secrets" library. Both programs are doing the same. I added the option for command line argument where we can specify n (how many numbers to create) and also with some detailled output information.
#!/usr/bin/python3
# generate n keys within specified range
# using python library 'random'
# by [email protected], 2022/11/20
import random
import sys
n = int(sys.argv[1])
low = 0x0000000
high = 0xFFFFFFF
#half = hex(int((high-low)/2))
half = int((high-low)/2)
def generate_private_key():
return hex(random.randrange(low,high))
counter = lowercount = uppercount = exactcount = 0
print("Range information:")
print("------------------")
print("HEX = ", hex(low),"-",hex(high))
print("DEC = ",low,"-",high)
print("Half of range in HEX: ", hex(half))
print("Half of range in DEC: ", half, "\n")
print("\nYou're about to generate ",n,"numbers in total. We expect even distribution in bottom/upper half of the range with each count of about ",int(n/2))
input("\nPress ENTER to start ...")
while counter < n:
randhex = generate_private_key()
#if randhex < half:
if int(randhex,16) < half:
lowercount += 1
#elif randhex > half:
elif int(randhex,16) > half:
uppercount += 1
#elif randhex == half:
elif int(randhex,16) == half:
exactcount += 1
print("Random number was: ", randhex, "( DEC=",int(randhex,16),") Bottom half count: ", lowercount, " Upper half count: ", uppercount, "Exact half count: ", exactcount )
counter += 1
#!/usr/bin/python3
# generate n keys within specified range
# using python library 'secrets'
# by [email protected], 2022/11/20
import sys
import secrets
n = int(sys.argv[1])
low = 0x00000000
high = 0xFFFFFFFF
half = int((high-low)/2)
def generate_private_key():
return secrets.token_hex(4)
counter = lowercount = uppercount = exactcount = 0
print("Range information:")
print("------------------")
print("HEX = ", hex(low),"-",hex(high))
print("DEC = ",low,"-",high)
print("Half of range in HEX: ", hex(half))
print("Half of range in DEC: ", half, "\n")
print("\nYou're about to generate ",n,"numbers in total. We expect even distribution in bottom/upper half of the range with each count of about ",int(n/2))
input("\nPress ENTER to start ...")
while counter < n:
randhex = generate_private_key()
if int(randhex,16) < half:
lowercount += 1
elif int(randhex,16) > half:
uppercount += 1
elif int(randhex,16) == half:
exactcount += 1
print("Random number was: ", randhex, "( DEC=",int(randhex,16),") Bottom half count: ", lowercount, " Upper half count: ", uppercount, "Exact half count: ", exactcount )
counter += 1
Now let's run the first program and generate 10 numbers...
$ ./rand_generator_using_random.py 10
Range information:
------------------
HEX = 0x0 - 0xfffffff
DEC = 0 - 268435455
Half of range in HEX: 0x7ffffff
Half of range in DEC: 134217727
You're about to generate 10 numbers in total. We expect even distribution in bottom/upper half of the range with each count of about 5
Press ENTER to start ...
Random number was: 0xa97a9bb ( DEC= 177711547 ) Bottom half count: 0 Upper half count: 1 Exact half count: 0
Random number was: 0x78d6d5 ( DEC= 7919317 ) Bottom half count: 1 Upper half count: 1 Exact half count: 0
Random number was: 0xe6d74d7 ( DEC= 242054359 ) Bottom half count: 1 Upper half count: 2 Exact half count: 0
Random number was: 0xa5df423 ( DEC= 173929507 ) Bottom half count: 1 Upper half count: 3 Exact half count: 0
Random number was: 0x215860 ( DEC= 2185312 ) Bottom half count: 2 Upper half count: 3 Exact half count: 0
Random number was: 0x4b80cdc ( DEC= 79170780 ) Bottom half count: 3 Upper half count: 3 Exact half count: 0
Random number was: 0x21b609b ( DEC= 35348635 ) Bottom half count: 4 Upper half count: 3 Exact half count: 0
Random number was: 0x26ff112 ( DEC= 40890642 ) Bottom half count: 5 Upper half count: 3 Exact half count: 0
Random number was: 0x9f3785 ( DEC= 10434437 ) Bottom half count: 6 Upper half count: 3 Exact half count: 0
Random number was: 0x197c28b ( DEC= 26722955 ) Bottom half count: 7 Upper half count: 3 Exact half count: 0
We see that 7 of those numbers fall into the lower range and 3 numbers fall into the upper range.
Let's run the same command again ...
$ ./rand_generator_using_random.py 10
[...]
Random number was: 0xf48da93 ( DEC= 256432787 ) Bottom half count: 5 Upper half count: 5 Exact half count: 0
This time it was perfectly even distributed, what a coincidence
Let's run the same command again ...
$ ./rand_generator_using_random.py 10
[...]
Random number was: 0xb2eed0c ( DEC= 187624716 ) Bottom half count: 4 Upper half count: 6 Exact half count: 0
Now let's run the program with 10K numbers a few times and compare the results...
$ ./rand_generator_using_random.py 10000
[...]
Random number was: 0xc4cb938 ( DEC= 206354744 ) Bottom half count: 5037 Upper half count: 4963 Exact half count: 0
[...]
Random number was: 0x2b80462 ( DEC= 45614178 ) Bottom half count: 5041 Upper half count: 4959 Exact half count: 0
[...]
Random number was: 0x5c0f08 ( DEC= 6033160 ) Bottom half count: 5135 Upper half count: 4865 Exact half count: 0
[...]
Random number was: 0x6230922 ( DEC= 102959394 ) Bottom half count: 4973 Upper half count: 5027 Exact half count: 0
we already see that with increasing n the even distribution yield better results.
Now let's raise n to 1M and run it a few times to compare results ...
$ ./rand_generator_using_random.py 1000000
[...]
Random number was: 0x3d38eb8 ( DEC= 64196280 ) Bottom half count: 499887 Upper half count: 500113 Exact half count: 0
[...]
Random number was: 0x8dec3e2 ( DEC= 148816866 ) Bottom half count: 500167 Upper half count: 499833 Exact half count: 0
[...]
Random number was: 0x9a6de7b ( DEC= 161930875 ) Bottom half count: 500477 Upper half count: 499523 Exact half count: 0
[...]
Random number was: 0xd049ba1 ( DEC= 218405793 ) Bottom half count: 499698 Upper half count: 500302 Exact half count: 0
This is what I call a perfect even distribution as expected.One last try, let's go ahead and raise n to 100M (hundred million) ...
$ ./rand_generator_using_random.py 100000000
[...]
Random number was: 0xdbec231 ( DEC= 230605361 ) Bottom half count: 50003376 Upper half count: 49996624 Exact half count: 0
==>
PERFECT! That is what I expected to see.
Now the question remains "What was the issue?". Well, I modified my program to calculate with decimals instead of hex. I need to dig into it and see and
understand where the coding error was with hex calculation.