I wanted to learn how to create a Bitcoin wallet in code. I used as reference this guide which has code examples in JavaScript.
I wrote my implementation in Python. Here is the resulting code:
#!/usr/bin/env python
'''
Creates BitCoin Wallet complaint credentials:
- Public Key
- Private Key
- Private Key (Wallet Import Format)
'''
import hashlib
import base58
import ecdsa
from ecdsa.keys import SigningKey
from utilitybelt import dev_random_entropy
from binascii import hexlify, unhexlify
def random_secret_exponent(curve_order):
while True:
random_hex = hexlify(dev_random_entropy(32))
random_int = int(random_hex, 16)
if random_int >= 1 and random_int < curve_order:
return random_int
def generate_private_key():
curve = ecdsa.curves.SECP256k1
se = random_secret_exponent(curve.order)
from_secret_exponent = ecdsa.keys.SigningKey.from_secret_exponent
return hexlify(from_secret_exponent(se, curve, hashlib.sha256).to_string())
def generate_public_key(private_key_hex):
hash160 = ripe_hash(private_key_hex)
public_key_and_version_hex = b"04" + hash160
checksum = double_hash(public_key_and_version_hex)[:4]
return base58.b58encode(public_key_and_version_hex + checksum)
def ripe_hash(key):
ret = hashlib.new('ripemd160')
ret.update(hashlib.sha256(key).digest())
return ret.digest()
def double_hash(key):
return hashlib.sha256(hashlib.sha256(key).digest()).digest()
def generate_private_key_wif(private_key_hex):
private_key_and_version = b"80" + private_key_hex
checksum = double_hash(private_key_and_version)[:8]
hashed = private_key_and_version + checksum
return base58.b58encode(private_key_and_version + checksum)
def main():
private_key_hex = generate_private_key()
public_key_hex = generate_public_key(private_key_hex)
private_key_wif_hex = generate_private_key_wif(private_key_hex)
print("Private Key: " + private_key_hex)
print("Public Key: " + public_key_hex)
print("Private Key (WIF Format): " + private_key_wif_hex)
if __name__ == '__main__':
main()
How to run:
Private Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Public Key: 2Uc973e5adf5867512dd7a21638810bd53422
Private Key (WIF Format): xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I have my concerns about the correctness of the generated keys, as well as for the private key in WIF format. In theory it should be a short string.
-
7\$\begingroup\$ BTW: I hope that those private keys aren't ACTUAL private keys. Those should NEVER, EVER be posted in a location even as remotely public as a code review site. \$\endgroup\$Nzall– Nzall2018年01月15日 14:44:10 +00:00Commented Jan 15, 2018 at 14:44
-
\$\begingroup\$ @Nzail OK, I take notice. I replaced all the keys, either public and private, with hexadecimal random strings of the same size. \$\endgroup\$Diego Pino– Diego Pino2018年01月15日 17:13:52 +00:00Commented Jan 15, 2018 at 17:13
-
5\$\begingroup\$ Replacing them is not enough. For all purposes, you should now view the original keys as compromised and must NEVER EVER EVER use them again for bitcoin storage. Doing so runs high risk of losing the bitcoins stored in them. I already flagged your question to remove the editing history, but that isn't enough. \$\endgroup\$Nzall– Nzall2018年01月15日 17:26:41 +00:00Commented Jan 15, 2018 at 17:26
-
1\$\begingroup\$ @Nzall I just added your good observation to my answer to make others aware of this. I hope you don't mind ;) \$\endgroup\$Grajdeanu Alex– Grajdeanu Alex2018年01月15日 19:13:22 +00:00Commented Jan 15, 2018 at 19:13
4 Answers 4
IMPORTANT!!!
Not a Python tip, but rather a MUST when it comes to sensitive information (quote from @nzall's comment):
Those private keys should NEVER, EVER be posted in a location even as remotely public as a code review site. More, replacing them is not enough. For all purposes, you should now view the original keys as compromised and must NEVER EVER EVER use them again for bitcoin storage. Doing so runs high risk of losing the bitcoins stored in them
I just made this tip a bit more seeable for everybody.
Another three small observations, in addition to what @Graipher said.
In Python you can "chain" comparison operations which just means they are "and"ed together.
if random_int >= 1 and random_int < curve_order:
return random_int
In your case, it'd be like this:
if 1 <= random_int < curve_order:
return random_int
Read more about it here
More, I'd recommend you use .format()
when it comes to printing:
print("Private Key: " + private_key_hex)
print("Public Key: " + public_key_hex)
print("Private Key (WIF Format): " + private_key_wif_hex)
Could be:
print("Private Key: {}".format(private_key_hex))
print("Public Key: {}".format(public_key_hex))
print("Private Key (WIF Format): {}".format(private_key_wif_hex))
You should also have two blank lines between your functions (instead of a single one)
This:
# ...
from binascii import hexlify, unhexlify
def random_secret_exponent(curve_order):
while True:
random_hex = hexlify(dev_random_entropy(32))
random_int = int(random_hex, 16)
if random_int >= 1 and random_int < curve_order:
return random_int
def generate_private_key():
curve = ecdsa.curves.SECP256k1
se = random_secret_exponent(curve.order)
from_secret_exponent = ecdsa.keys.SigningKey.from_secret_exponent
return hexlify(from_secret_exponent(se, curve, hashlib.sha256).to_string())
# ...
Should be:
# ...
from binascii import hexlify, unhexlify
def random_secret_exponent(curve_order):
while True:
random_hex = hexlify(dev_random_entropy(32))
random_int = int(random_hex, 16)
if random_int >= 1 and random_int < curve_order:
return random_int
def generate_private_key():
curve = ecdsa.curves.SECP256k1
se = random_secret_exponent(curve.order)
from_secret_exponent = ecdsa.keys.SigningKey.from_secret_exponent
return hexlify(from_secret_exponent(se, curve, hashlib.sha256).to_string())
# ...
For more details / suggestions / improvements regarding your code layout, you could take a look at PEP8, Python's official style guide.
-
\$\begingroup\$ @MrGrj Ah, makes sense, :) \$\endgroup\$2018年01月15日 12:00:30 +00:00Commented Jan 15, 2018 at 12:00
Three very small observations:
In your function
generate_private_key_wif
, you never usehashed
, you re-compute it for the return value.In a nice self-contained module like this one, you should definitely add
docstring
s to your functions. Have a look at PEP257 to see how they are defined.You currently do
from ecdsa.keys import SigningKey
, but never use it. You also assignfrom_secret_exponent = ecdsa.keys.SigningKey.from_secret_exponent
in yourgenerate_private_key
function. Instead dofrom_secret_exponent = SigningKey.from_secret_exponent
or, probably better, split that line across two lines like this:
def generate_private_key():
curve = ecdsa.curves.SECP256k1
se = random_secret_exponent(curve.order)
key = SigningKey.from_secret_exponent(se, curve, hashlib.sha256)
return hexlify(key.to_string())
You have an error in WIF generation, you need to decode it from hex and take only 4 bytes for checksum, not 8:
def generate_private_key_wif(private_key_hex):
private_key_and_version = b"80" + private_key_hex
private_key_and_version = codecs.decode(private_key_and_version, 'hex')
checksum = double_hash(private_key_and_version)[:4]
hashed = private_key_and_version + checksum
return base58.b58encode(hashed)
The generate_public_key
function's parameter is private key.
I find the public key is generated by hashing private key in your code.
Is it not wrong? I think the public key is generated by ecdsa.SECP256k1 using private key.
#!/usr/bin/python
import ecdsa
from binascii import hexlify, unhexlify
secret = unhexlify('18E14A_the_private_key')
order = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).curve.generator.order()
p = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).verifying_key.pubkey.point
x_str = ecdsa.util.number_to_string(p.x(), order)
y_str = ecdsa.util.number_to_string(p.y(), order)
compressed = hexlify(chr(2 + (p.y() & 1)) + x_str).decode('ascii')
uncompressed = hexlify(chr(4) + x_str + y_str).decode('ascii')
print(compressed)
print(uncompressed)
-
\$\begingroup\$ Are you sure this should be an answer, and not a comment? What exactly are you suggesting here? \$\endgroup\$Daniel– Daniel2018年06月09日 11:15:42 +00:00Commented Jun 9, 2018 at 11:15
-
\$\begingroup\$ @Coal_ Hi, He want generate a address. But there is an error in the code. I point it out. \$\endgroup\$Victor Choy– Victor Choy2018年06月09日 12:16:36 +00:00Commented Jun 9, 2018 at 12:16