Seed Generator for Bitcoin Cash / Electron Cash v1.1
I have worked more on the Seed Generator script, I realized generating a seed by itself is useless. It should be an offline seed generator to be used for generating Bitcoin Cash wallets on a Cold Storage computer, therefore we need the Master Public Key too.
The idea is that you generate the Seed, write it down, put it in a Safe, and then send the Master Public Key somehow to an Online Computer, and generate there a Watch-Only Wallet from there, thereby not exposing the private keys.
Thus I have added the Public Key script there too, however now the code became complex, so I haven’t tested it thoroughly yet, therefore I am not releasing it just yet. However later I will release it under MIT license for free, just like the last version.
I will even contact the Electron Cash developer, see if he would like to use it for his project, and of course review it for bugs as well.
The script works like this (simple & elegant):
Just run the seed_gen.py file from the console, it's a python3 script, and enter the entropy of the seed you want, minimum 132 bit recommended. In Electrum/Electron-Cash you have 2048 words, and thus each word gives 11 bits of entropy, therefore after every 11 bits the seed gets longer by 1 word. A 132 bit seed will be 12 words (on average).
Now unfortunately I can only support English seeds, since all those Chinese characters and complicating with Unicode encoding makes it more complicated and prone to errors.
I am still not sure whether I did it correctly, I am a medium python developer, still learning.
Obviously the Seed corresponds to the Master Public key, I have checked this in Electron-Cash, so huge errors like that I don’t think I have made.
Although I am still not sure about the encoding, there are many unnecessary byte<>hex transformations in the code, so I could have made a mistake there.
Improvements
I have redesigned the random number generator! Yes, however I was very careful, and mathematically verified the entropy.
def make_seed(self, num_bits=132):
prefix = SEED_PREFIX
# ecdsa rng starts from 1 not 0, therefore 2^num_bits has only (2^num_bits - 1) outcomes
# so we must add +1 there to have num_bits of Shannon Entropy
my_entropy = ecdsa.util.randrange(pow(2, num_bits) + 1)
nonce = 0
while True:
i = (my_entropy + nonce)
# print(i)
seed = self.mnemonic_encode(i)
assert i == self.mnemonic_decode(seed) # check if seed is correct format, otherwise it gives an error
if self.is_new_seed(seed, prefix): # only the new seed format is supported from Electrum 2.0 and later versions, all Electrum Cash wallets
break
nonce += 1 # no need to increase at the beginning, try the original too, and if it's not good, then add +1 to it until it becomes good
xpubkey=self.bip32_root(seed)
return seed,xpubkey
This is the new Seed Generator function, I have removed the Custom Seed nonsense, you can just set the bit exponent, no need to add nonsense there. And cleaned up the RNG.
Actually the RNG starts from 1 not zero, so we have to add +1 there otherwise we lose 1 outcome. I think Electrum/Electron Cash does this wrongly, so I will contact the dev to see whether this has to be fixed.
This is because they have a lot of unnecessary variables there that complicate stuff, I just made it simple.
So my_entropy = ecdsa.util.randrange(pow(2, num_bits) + 1)
in my opinion is the correct, secure way to generate entropy.
How do I know this? Because I wrote a quick analysis tool:
# cryptographic evaluation test
import math,ecdsa,random
cycles=1000000
ecdsaarray=[]
uniformarray=[]
diff_ecdsaarray=[]
diff_uniformarray=[]
for i in range(cycles):
ecdsaarray.append(ecdsa.util.randrange(2 + 1) -1) # the -1 makes it between [0,1] instead [1,2]
uniformarray.append( round(random.uniform(0, 1), 0) ) # discrete unif random numbers between [0,1]
if(i>0):
diff_ecdsaarray.append ( abs(ecdsaarray [i-1]-ecdsaarray [i]) )
diff_uniformarray.append( abs(uniformarray[i-1]-uniformarray[i]) )
ecdsaaverage = sum(ecdsaarray) / float(len(ecdsaarray))
uniformaverage = sum(uniformarray) / float(len(uniformarray))
diff_ecdsaarray = sum(diff_ecdsaarray ) / float(len(diff_ecdsaarray ))
diff_uniformarray = sum(diff_uniformarray) / float(len(diff_uniformarray))
outcome1=ecdsaaverage
outcome2=(1-ecdsaaverage)
shannonecdsa = - ( outcome1 * math.log(outcome1,2) + outcome2 * math.log(outcome2,2) )
outcome1=uniformaverage
outcome2=(1-uniformaverage)
shannonuniform= - ( outcome1 * math.log(outcome1,2) + outcome2 * math.log(outcome2,2) )
print(shannonecdsa)
print(shannonuniform)
print()
print(abs(diff_ecdsaarray))
print(abs(diff_uniformarray))
With a sample size of 1 million (or higher) we create an ECDSA (discrete) distribution and a Discrete Uniform Distribution between [0,1].
We calculate the Shannon Entropy of both distributions, which should converge towards 1, and indeed it’s very close to it. We only lose 0.5 bits of entropy after 2,000,000 bits generated, therefore it’s a good quality PRNG.
ECDSA_entropy = 0.9999997522921327 bits
Uniform_entropy= 0.999997780761674 bits
Now there could still be autocorrelation between the numbers if the PRNG is crappy, thus we look at the differences between the values, it should converge towards 0.5.
ECDSA_diff = 0.4993374993374993
Uniform_diff= 0.4999824999825
Thus we can conclude that:
- A) The ECDSA script , and my suggestion generates secure PRNG with sufficient entropy (0.5 bits lost after every 2 mil bits)
- B) The ECDSA function is a Discrete Uniform Probability Distribution
Finally I have moved the nonce += 1
at the end of the loop, there is no need to call it at the beginning since then we exclude the original value. We only increase the nonce if the original value is not good, which should be since for standard wallets, we hardly have incorrect values at start.
Then just added the xpubkey function call, which has a lot of dependent functions which I am still checking.
Unfortunately we will need the pbkdf2.py
file too for this to work. It’s kind of annoying the seed is encrypted 2048 times with this function, probably a key extension technique to protect against brute forcing. So that is good, but now we are dependent on this file, as it is too big for it to be merged in 1 file so this file has to be in the same folder as the generator file.
Fortunately we don’t need the bitcoin.py
file, I have merged it into the main file. We could have referenced it, but the bitcoin.py file is very messy, it has a lot of references from version.py util.py networks.py keystore.py base_wizard.py
and more, so we might as well just use the Electron-Cash wallet then, but the point of this project is to strip down the code to the bare minimum, make it simple and as short code as possible to have a paper wallet generator basically.
So the dependencies currently are the following:
- The
ecdsa
folder from here, in the same directory - english.txt in the same directory
- pbkdf2.py in the same directory (this file is in the packages folder or from here)
- And the seed_gen.py main script which is the main script you will run.
Like this:
It works well now, with no errors, but I am still testing it, later I will publish a download link. Stay tuned!
Disclaimer: This software is licensed under MIT license. It’s experimental, use it at your own risk, I shall not be responsible for any losses that may occur by using the software. The software is not endorsed by neither the Electrum nor the Electron-Cash team.
Sources:
https://pixabay.com
https://www.pexels.com
Wow respect! I just started programming web applications and I can understand that this is really a hard job! Good luck! :)
Thanks, I am getting good in Python now, been learning in the past 1 year.
I figured it's very useful to know how to program, when we are talking about cryptocurrencies, so that I can customize a lot of things.
I was even working on a trading bot, but paused that project for a while, been too busy with other stuff.
Yes you are totally right! I also think that it is very useful to understand progresses and how things in general work!