# Generating Wallet Address From Private Key

In this article, we learn how Bitcoin wallet addresses and their corresponding public keys are generated given a private key.

### Table of contents.

- Introduction.
- Bitcoin address formats.
- ECDSA public-key generation.
- Compressing the Public Key.
- SHA-256 and RIPEMD-160 hashing.
- The Network Byte.
- Checksum Calculations.
- Base58Check encoding.
- Summary.
- References.

### Prerequisite

Generating a Bitcoin private key

## Introduction

In the prerequisite article, we learned how to generate private keys using python. In order to generate private keys and make sure that no one else knows about them, we can opt to generate them ourselves locally. If we opt to use third parties such as *random.org* we should know that we are not the only ones who know about it.

Unlike the Ethereum blockchain where participants have accounts and account addresses, the Bitcoin blockchain uses a wallet as the interface to interact with the blockchain. Wallets have addresses that are used by other participants in transactions, for example, if a user intends to send you Bitcoins, he/she will need the recipient's wallet address. That is, the sender creates a transaction that says that a specif amount X of Bitcoins now belongs to a wallet address *W*. This information is propagated across the entire network of Bitcoin blockchain nodes to ensure that only the intended recipient can spend the *X* bitcoins.

To acquire a Bitcoin address which is the wallet address we first download the Bitcoin wallet software. The wallet will store our private key which as mentioned should be kept private, just like a password to an account. Bitcoin wallets are not only limited to mobile, web, or desktop programs rather they can only be hardware wallets.

In this article, we will learn how Bitcoin wallet addresses and corresponding public keys are generated from a private key. The process is simple, we will apply a series of conversions to a private key in order to generate its key pair, the *public-key* and a Bitcoin wallet address. We will use hashing and hashing algorithms which will make sure that we have a *trapdoor*.

## Bitcoin address formats.

Bitcoin has three common address formats. All are alphanumeric characters that are 26-35 characters long either starting with *1*, *3*, or *bc1*.

**P2PKH** are addresses starting with *1* and an example is shown below;

*1BvBMSJksi71Hya7sMkrim4GFg7xJaNVN2*

**PsSH** are addresses starting with *3*, and an example is shown below;

*3J98Jki8Kl07GhAhjueSjki8WrnqRhWNLy*

**Bech32** are address starting with *bc1*, an example is hwon below;

*bc1ghr0jkid7xfkvy5l643klotsu781f52zzwf5mdq*

## ECDSA public-key generation.

Bitcoin uses ECDSA for its cryptography because it results in much smaller signatures compared to using RSA. Also, the former algorithm is stronger compared to the latter which also means that verification is slower compared to RSA because of the level of security.

We define an *elliptic curve* using the following equation;

yÂ² = xÂ³ + ax + b. By passing the private key through the ECDSA algorithm, we get a 64-byte integer consisting of 2 32-byte integers representing points *X* and *Y* on the curve concatenated together.

The following python code does the dicussed concept;

```
def generate_public_key():
# private_key = generate_private_key()
priv_key_bytes = codecs.decode(private_key, 'hex') # convert string into byte array
key = ecdsa.SigningKey.from_string(priv_key_bytes, curve=ecdsa.SECP256k1).verifying_key
key_bytes = key.to_string()
public_key = codecs.encode(key_bytes, 'hex')
return public_key
public_key = generate_public_key()
print("Generated Public Key: ", public_key.decode())
print()
```

The private key we generate using the *generate_private_key* function in the prerequisite article is as follows;

```
6c2181e3e8a8dd271f8a074117bc4232e7250e371d2db23d9131c6659c4cc4b1
```

The above private key is of type string, we convert it into a byte array since in Python we can only use strings or bytes to store private and public keys. For this we use *codecs.decode* and we have the following byte array as a result from line 3;

```
b"l!\x81\xe3\xe8\xa8\xdd'\x1f\x8a\x07A\x17\xbcB2\xe7%\x0e7\x1d-\xb2=\x911\xc6e\x9cL\xc4\xb1"
```

We then apply the ECDSA algorithm to the above key and convert it into a string and we have the following;

```
b'\x9eC\xbe\x96l\x9a*1\xd2N\x83"\xaf\x08\xf05\xf0\xb4\x19\xda{\xb9.\xd0Q\x86\xe2kkeg\xa8\xe9"Uv\xf9\xd2\xb0z_j\x91\xbd\\\xf0\xa6\x18\xf7\xb0F\x8b\'\x98\x9d\x06-\xc1j\x97\x1c 8\xa9'
```

The above is a string of bytes.

We then use codecs to encode the above string of bytes and we have the following public key;

```
b'9e43be966c9a2a31d24e8322af08f035f0b419da7bb92ed05186e26b6b6567a8e9225576f9d2b07a5f6a91bd5cf0a618f7b0468b27989d062dc16a971c2038a9'
```

*Codecs* is a python library that is used for encoding and decoding. It is commonly used to encode text into a byte string and then decode the encoded byte string text into the original text. The above results in the following random public keys after each execution.

We add then a *0x04* prefix byte. This is used in Bitcoin to distinguish between several other encodings. In this case, it denotes an uncompressed public key. We use the following simple function;

```
def convert_pub_key(public_key):
bit_public_key = b'0x04' + public_key
return bit_public_key.decode()[2:]
bit_pub_key = convert_pub_key(public_key)
print("Standard Bitcoin Pub_Key: ", bit_pub_key)
print()
```

The result is the following standard Bitcoin public key.

```
b'0x049e43be966c9a2a31d24e8322af08f035f0b419da7bb92ed05186e26b6b6567a8e9225576f9d2b07a5f6a91bd5cf0a618f7b0468b27989d062dc16a971c2038a9'
```

In the function *convert_pub_key* function, we can replace **return bit_public_key** with **return bit_public_key.decode()[2:]** line to obtain a string type;

```
049e43be966c9a2a31d24e8322af08f035f0b419da7bb92ed05186e26b6b6567a8e9225576f9d2b07a5f6a91bd5cf0a618f7b0468b27989d062dc16a971c2038a9
```

## Compressing the Public Key.

Compression is also important since it reduces the size of the key, this goes a long way in improving the speed of transactions while also maintaining security.

Remember that a public key on the elliptic curve is a coordinate (X, Y) on the curve. Therefore for each *X* there are two *Ys*. this means that we can keep *X* and the sign of *Y* and it will allow us to derive Y as needed.

The process involves adding *0x02* to *X* from the public key, if the last byte of *Y* is even otherwise we add *0x03* if the last byte is odd. In case, the last byte is the odd number *9*, therefore we have the following compressed public key.

```
b'0x039e43be966c9a2a31d24e8322af08f035f0b419da7bb92ed05186e26b6b6567a8'
```

Remember we can decode it and have the following string;

```
039e43be966c9a2a31d24e8322af08f035f0b419da7bb92ed05186e26b6b6567a8
```

To generate a compressed public key, we use the following function;

```
# compress public key
def compress_pub_key(public_key):
mid = int(len(public_key) / 2)
x = public_key[:mid]
if(int(public_key[-1:]) % 2 == 0):
x = b'0x02' + x
else:
x = b'0x03' + x
return x
compressed_pub_key = compress_pub_key(public_key)
print("Compressed Pub_Key: ", compressed_pub_key.decode()[2:])
print()
```

## SHA-256 and RIPEMD-160 hashing.

In this section, we will learn how to encrypt data using a public key. We will use hashing algorithms such as SHA-256 after which we will apply *RIPEMD-160* to the obtained result.

The process is as follows, we first decode the public key as done previously, then we hash the result using SHA-256 then apply the RIPEMD-160 hashing algorithm to the result of the previous operation.

**Encrypted Public Key = RIPEMD-160(SHA-256(Public Key))

The following is demonstrated in python code;

```
# Encrypt public key
def encrypt_pub_key(compressed_pub_key):
public_key = compressed_pub_key.decode()[2:]
public_key_bytes = codecs.decode(public_key, 'hex')
sha256_pub_key = hashlib.sha256(public_key_bytes) # pass public key through SHA-256
sha256_pub_key_digest = sha256_pub_key.digest()
ripemd160_pub_key = hashlib.new('ripemd160') # pass hashed SHA-256 through RIPEMD
ripemd160_pub_key.update(sha256_pub_key_digest)
ripemd160_pub_key_digest = ripemd160_pub_key.digest()
ripemd160_pub_key_hex = codecs.encode(ripemd160_pub_key_digest, 'hex')
return ripemd160_pub_key_hex
encrypted_pub_key = encrypt_pub_key(compressed_pub_key)
print("Encrypted Pub_Key:", encrypted_pub_key.decode())
print()
```

This results in the following *encrypted public key* bytes;

```
b'0f4d06eef6c6ad74fb912e3dc47f23474a430950'
```

## The Network Byte.

We also add a network byte to the generated address, this is because Bitcoin just like other blockchain technologies such as Ethereum has a test net and a mainnet, the former is used by developers to test features while the latter is what is used in day to day transactions on the blockchain.

For the testnet, we add **0x6f** bytes to the address, for the mainnet, we add **0x00**. In our case we are using the mainnet therefore we have the following compressed, encrypted public key prepended with the network byte;

```
b'0x000f4d06eef6c6ad74fb912e3dc47f23474a430950'
```

This is similar to the following;

```
000f4d06eef6c6ad74fb912e3dc47f23474a430950
```

The above is after decoding and splicing the resulting string;

The code for adding the network byte is shown below;

```
# adding network byte
def network_byte(encrypted_pub_key, network):
if(network == 'mainnet'):
return b'0x00' + encrypted_pub_key
elif(network == 'testnet'):
return b'0x6f' + encrypted_pub_key
else:
print('Invalid Network')
net_pubkey = network_byte(encrypted_pub_key, 'mainnet')
print("Bitcoin mainnet Addr: ", net_pubkey.decode()[2:])
print()
```

## Checksum Calculations.

A checksum is used to ensure that the data is not corrupted. We calculate the checksum by passing the address through SHA-256 twice and then using taking the preceding 4 bytes of the result. That is;

**checksum = SHA-256(SHA-256(encrypted public_key))[:8]**

Above we use *8* since 4 bytes is equivalent to 8 hex values.

The following python code does this;

```
# calculate checksum of address
def calc_addr_checksum(net_pubkey):
sha256_net_pubkey = hashlib.sha256(net_pubkey) # first SHA
sha256_net_pubkey_digest = sha256_net_pubkey.digest()
sha256_2_sha256_net_pubkey = hashlib.sha256(sha256_net_pubkey_digest) # second SHA
sha256_2_net_pubkey_digest = sha256_2_sha256_net_pubkey.digest()
sha256_2_hex = codecs.encode(sha256_2_net_pubkey_digest, 'hex')
checksum = sha256_2_hex[:8]
return checksum
checksum = calc_addr_checksum(net_pubkey)
print("Checksum: ", checksum.decode())
print()
```

In this case, we have the following checksum;

```
da0b514d
```

Now, to create a valid address, we concatenate the network public key with the generated checksum and we have the following;

```
b'0x000f4d06eef6c6ad74fb912e3dc47f23474a430950da0b514d'
```

We use the following simple function;

```
# create wallet address
def wallet_address(public_key, checksum):
return public_key + checksum
wallet_addr = wallet_address(net_pubkey, checksum)
print("Wallet Address: ", wallet_addr.decode()[2:])
print()
```

## Base58Check Encoding.

To have a valid address we first concatenate the mainnnet or testnet key and the checksum. Then convert the result into a Base58Check encoding which is what Bitcoin uses. For more on this refer to the links in the references section.

The following python code encodes the currently generate address using Base58Check encoding;

```
# Encode with Base58 encoding
def base58(address_hex):
address_hex = wallet_addr.decode()[2:]
alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
b58_string = ''
# Get the number of leading zeros
leading_zeros = len(address_hex) - len(address_hex.lstrip('0'))
# Convert hex to decimal
address_int = int(address_hex, 16)
# Append digits to the start of string
while address_int > 0:
digit = address_int % 58
digit_char = alphabet[digit]
b58_string = digit_char + b58_string
address_int //= 58
# Add '1' for each 2 leading zeros
ones = leading_zeros // 2
for _ in range(ones):
b58_string = '1' + b58_string
return b58_string
Bit_wallet_addr = base58(wallet_addr)
print("Final Bitcoin Wallet Address: ", Bit_wallet_addr)
print()
```

The result of the above function is the following compressed Bitcoin wallet address;

```
12PuQpA9eFXCRRA6T81LZXj4gixdLmh1JL
```

The following are the outputs at different stages in generating the bitcoin wallet address;

```
Generated Public Key: 9e43be966c9a2a31d24e8322af08f035f0b419da7bb92ed05186e26b6b6567a8e9225576f9d2b07a5f6a91bd5cf0a618f7b0468b27989d062dc16a971c2038a9
Standard Bitcoin Pub_Key: 049e43be966c9a2a31d24e8322af08f035f0b419da7bb92ed05186e26b6b6567a8e9225576f9d2b07a5f6a91bd5cf0a618f7b0468b27989d062dc16a971c2038a9
Compressed Pub_Key: 039e43be966c9a2a31d24e8322af08f035f0b419da7bb92ed05186e26b6b6567a8
Encrypted Pub_Key: 0f4d06eef6c6ad74fb912e3dc47f23474a430950
Bitcoin mainnet Addr: 000f4d06eef6c6ad74fb912e3dc47f23474a430950
Checksum: da0b514d
Wallet Address: 000f4d06eef6c6ad74fb912e3dc47f23474a430950da0b514d
Final Bitcoin Wallet Address: 12PuQpA9eFXCRRA6T81LZXj4gixdLmh1JL
```

## Summary

A Bitcoin wallet address is the source and destination of all transactions on the Bitcoin blockchain network.

The Ethereum blockchain uses accounts instead of wallet addresses.

To increase the level of security and anonymity, it is recommended to generate a new wallet address after each transaction.

Following is the full algorithm in python code:

```
## Part of iq.opengenus.org
import codecs
import ecdsa
import secrets
import hashlib
# generate bitcoin private key
def generate_private_key():
bits = secrets.randbits(256)
bits_hex = hex(bits)
return bits_hex[2:]
private_key = "6c2181e3e8a8dd271f8a074117bc4232e7250e371d2db23d9131c6659c4cc4b1"
# return 64-byte key, 2 32-byte representing x,y coordinates of elliptic curve
def generate_public_key():
# private_key = generate_private_key()
priv_key_bytes = codecs.decode(private_key, 'hex') # convert string into byte array
key = ecdsa.SigningKey.from_string(priv_key_bytes, curve=ecdsa.SECP256k1).verifying_key
key_bytes = key.to_string()
public_key = codecs.encode(key_bytes, 'hex')
return public_key
public_key = generate_public_key()
print("Generated Public Key: ", public_key.decode())
print()
def convert_pub_key(public_key):
bit_public_key = b'0x04' + public_key
return bit_public_key.decode()[2:]
bit_pub_key = convert_pub_key(public_key)
print("Standard Bitcoin Pub_Key: ", bit_pub_key)
print()
# compress public key
def compress_pub_key(public_key):
mid = int(len(public_key) / 2)
x = public_key[:mid]
if(int(public_key[-1:]) % 2 == 0):
x = b'0x02' + x
else:
x = b'0x03' + x
return x
compressed_pub_key = compress_pub_key(public_key)
print("Compressed Pub_Key: ", compressed_pub_key.decode()[2:])
print()
# Encrypt public key
def encrypt_pub_key(compressed_pub_key):
public_key = compressed_pub_key.decode()[2:]
public_key_bytes = codecs.decode(public_key, 'hex')
sha256_pub_key = hashlib.sha256(public_key_bytes) # pass public key through SHA-256
sha256_pub_key_digest = sha256_pub_key.digest()
ripemd160_pub_key = hashlib.new('ripemd160') # pass hashed SHA-256 through RIPEMD
ripemd160_pub_key.update(sha256_pub_key_digest)
ripemd160_pub_key_digest = ripemd160_pub_key.digest()
ripemd160_pub_key_hex = codecs.encode(ripemd160_pub_key_digest, 'hex')
return ripemd160_pub_key_hex
encrypted_pub_key = encrypt_pub_key(compressed_pub_key)
print("Encrypted Pub_Key:", encrypted_pub_key.decode())
print()
# adding network byte
def network_byte(encrypted_pub_key, network):
if(network == 'mainnet'):
return b'0x00' + encrypted_pub_key
elif(network == 'testnet'):
return b'0x6f' + encrypted_pub_key
else:
print('Invalid Network')
net_pubkey = network_byte(encrypted_pub_key, 'mainnet')
print("Bitcoin mainnet Addr: ", net_pubkey.decode()[2:])
print()
# calculate checksum of address
def calc_addr_checksum(net_pubkey):
sha256_net_pubkey = hashlib.sha256(net_pubkey) # first SHA
sha256_net_pubkey_digest = sha256_net_pubkey.digest()
sha256_2_sha256_net_pubkey = hashlib.sha256(sha256_net_pubkey_digest) # second SHA
sha256_2_net_pubkey_digest = sha256_2_sha256_net_pubkey.digest()
sha256_2_hex = codecs.encode(sha256_2_net_pubkey_digest, 'hex')
checksum = sha256_2_hex[:8]
return checksum
checksum = calc_addr_checksum(net_pubkey)
print("Checksum: ", checksum.decode())
print()
# create wallet address
def wallet_address(public_key, checksum):
# checksum = b'512f43c4'
return public_key + checksum
wallet_addr = wallet_address(net_pubkey, checksum)
print("Wallet Address: ", wallet_addr.decode()[2:])
print()
# Ecnode with Base58 encoding
def base58(address_hex):
address_hex = wallet_addr.decode()[2:]
alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
b58_string = ''
# Get the number of leading zeros
leading_zeros = len(address_hex) - len(address_hex.lstrip('0'))
# Convert hex to decimal
address_int = int(address_hex, 16)
# Append digits to the start of string
while address_int > 0:
digit = address_int % 58
digit_char = alphabet[digit]
b58_string = digit_char + b58_string
address_int //= 58
# Add '1' for each 2 leading zeros
ones = leading_zeros // 2
for _ in range(ones):
b58_string = '1' + b58_string
return b58_string
Bit_wallet_addr = base58(wallet_addr)
print("Final Bitcoin Wallet Address: ", Bit_wallet_addr)
print()
```