From Private Key to Bitcoin Address

kootsZhin
3 min readJul 18, 2021

--

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'

--

--