Okay, so you may have heard of the fact that Bitcoin is using a signature algorithm which contains a pair of public key and private key in verifying the identity of the owners. Simply speaking, the public key is just the account name of your wallet and private key the password. At first glance, you may think that the public key is referring to your Bitcoin address, but in fact that is not the case. This article is a step-by-step guide to getting the exact Bitcoin address by looking at its private key.
Importing Dependencies
In this study, we will need to make use of the below Python libraries.
import hashlib
import base58
import codecs
import ecdsa
Converting Private Key to Address
Step 0
First of all, we will have to start with a private ECDSA key (basically any series of 32 bytes) and this will be our private key. In this example, we will use the same key from the Bitcoin Wiki.
Input
private_key = "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725"private_key
Output
'18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725'
Step 1
Next we will have to generate an uncompressed public key from the private key that we have
Input
# Hex decoding the private key to bytes using codecs library
private_key_bytes = codecs.decode(private_key, 'hex')# Generating a public key in bytes using SECP256k1 & ecdsa library
public_key_raw = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
public_key_bytes = public_key_raw.to_string()# Hex encoding the public key from bytes
public_key_hex = codecs.encode(public_key_bytes, 'hex')# Bitcoin public key begins with bytes 0x04 so we have to add the bytes at the start
public_key = (b'04' + public_key_hex).decode("utf-8")public_key
Output
'0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6'
Step 2
Next we have to compressed our uncompressed public key
Input
# Checking if the last byte is odd or even
if (ord(bytearray.fromhex(public_key[-2:])) % 2 == 0):
public_key_compressed = '02'
else:
public_key_compressed = '03'
# Add bytes 0x02 to the X of the key if even or 0x03 if odd
public_key_compressed += public_key[2:66]public_key_compressed
Output
'0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352'
Step 3
Perform SHA-256 hashing on the compressed public key
Input
# Converting to bytearray for SHA-256 hashing
hex_str = bytearray.fromhex(public_key_compressed)
sha = hashlib.sha256()
sha.update(hex_str)sha.hexdigest() # .hexdigest() is hex ASCII
Output
'0b7c28c9b7290c98d7438e70b3d3f7c848fbd7d1dc194ff83f4f7cc9b1378e98'
Step 4
Perform RIPMED-160 hashing on the result of SHA-256
Input
rip = hashlib.new('ripemd160')
rip.update(sha.digest())
key_hash = rip.hexdigest()key_hash # Hash160
Output
'f54a5851e9372b87810a8e60cdd2e7cfd80b6e31'
Step 5
Add version byte in front of RIPEMD-160 hash (0x00 for Main Network)
Input
modified_key_hash = "00" + key_hashmodified_key_hash
Output
'00f54a5851e9372b87810a8e60cdd2e7cfd80b6e31'
Step 6
Perform SHA-256 hash on the extended RIPEMD-160 result (Below steps are called Base58Check encoding)
Input
sha = hashlib.sha256()
hex_str = bytearray.fromhex(modified_key_hash)
sha.update(hex_str)sha.hexdigest()
Output
'ad3c854da227c7e99c4abfad4ea41d71311160df2e415e713318c70d67c6b41c'
Step 7
Perform SHA-256 hash on the result of the previous SHA-256 hash
Input
sha_2 = hashlib.sha256()
sha_2.update(sha.digest())sha_2.hexdigest()
Output
'c7f18fe8fcbed6396741e58ad259b5cb16b7fd7f041904147ba1dcffabf747fd'
Step 8
Take the first 4 bytes of the second SHA-256 hash, this is the address checksum
Input
checksum = sha_2.hexdigest()[:8]checksum
Output
'c7f18fe8'
Step 9
Add the 4 checksum bytes from stage 8 at the end of extended RIPEMD-160 hash from stage 5, this is the 25-byte binary Bitcoin Address
Input
byte_25_address = modified_key_hash + checksumbyte_25_address
Output
'00f54a5851e9372b87810a8e60cdd2e7cfd80b6e31c7f18fe8'
Step 10 (Final Result)
Convert the result from a byte string into a base58 string using Base58Check encoding, this is the most commonly used Bitcoin Address format
Input
address = base58.b58encode(bytes(bytearray.fromhex(byte_25_address))).decode('utf-8')address
Output
'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs'
Alternative Ways in Converting Hash160 (key hash) into base58
Making use of b58.encode_check from base58 library directly at Step 6
Input
key_bytes = codecs.decode(modified_key_hash, 'hex')
address = base58.b58encode_check(key_bytes).decode('utf-8')address
Output
'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs'
Reference
(Full code & notes available on my Github)
Bitcoin Wiki
Github
Medium (freeCodeCamp)
freeCodeCamp
stackoverflow