Base58Check encoding in Python

Non-Sequential Base Systems

Bitcoin’s Base58

Base58 is a binary to text encoding developed for displaying the 20-byte integer representing a Bitcoin address. Base58 is the same as base 64, but without the +, /, 0, O, I, and l symbols.

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

The encoding prevents confusion between letters, especially when handwritten and across different fonts. The resulting expression is also selectable by double clicking the mouse.

19wWTEnNTWna86WmtFsTAr5 (“Hello world!”)

Geohash’s Base 32

Source: HackerNoon

Geohash has a similar 32 character alphabet, with no capital letters:

0123456789bcdefghjkmnpqrstuvwxyz

Base58Check encoding

Base58Check consists of a version number, a payload (typically 20 bytes), and a double sha256 hash of the payload.

Let’s try it out. First, pip install base58.

import base58

text = base58.b58encode_check(b'\0'+b'Hello world!')
>> b'19wWTEnNTWna86WmtFsTAr5'
payload = base58.b58decode_check(text)[1:]
>> b'Hello world!'

If the version byte is 0, the result always starts with a 1!

Base58Check size overhead

The Base58Check encoded string is about 37% larger for most payloads.

Preventing manual entry problems

Manual entry can be a problem if valid IDs are common enough to stumble upon.

I created the following perturbation functions:

  • Neighboring swap
  • Character insertion
  • Character removal
  • Character substitution
  • Repeat
  • Repeat with neighbor removal

No base58check string was invalid for 1 million trials of each function! This test is not enough for security purposes, but it is a good safety test for manual entry issues.

Generating QR Codes

19wWTEnNTWna86WmtFsTAr5 (“Hello world!”)
import base58
import pyqrcode

text = base58.b58encode_check(b'0'+b'Hello world!')
qr = pyqrcode.create(text)
qr.png('{0}.png'.format(text), scale=8)

Encrypted payloads

1R7wpD8bnXx9KnhyuKT3kVb9XY6Pd3mHtDk2gwsaiP6mFbQe3btnTHTgpErwtvnAYMY6gnHb (ciphertext of “Hello world!” encrypted with private key “password”).

Here is the code to encode ciphertext in base58:

import base58
from Crypto.Cipher import AES
from Crypto import Random

def pad(s,bs=32):
    return s+(bs-len(s)%bs)*chr(bs-len(s)%bs)

def unpad(s):
    return s[:-ord(s[len(s)-1:])]

def encrypt(key,payload):
    key = pad(key)
    payload = pad(payload)
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ciphertext = base58.b58encode_check(b'\0'+iv+cipher.encrypt(payload))
    return ciphertext

def decrypt(key,ciphertext):
    key = pad(key)
    ciphertext = base58.b58decode_check(ciphertext)
    iv = ciphertext[1:AES.block_size+1]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    payload = unpad(cipher.decrypt(ciphertext[AES.block_size+1:]))
    return payload

Now let’s run it!

key = 'password'
payload = 'Hello world!'
ciphertext = encrypt(key,payload)
>> "1R7wpD8bnXx9KnhyuKT3kVb9XY6Pd3mHtDk2gwsaiP6mFbQe3btnTHTgpErwtvnAYMY6gnHb"
payload = decrypt(key,ciphertext)
>> "Hello world!"

Each time this function is run results in different cyphertext. This is because of the initalization vector used in AES.


About the author



Hi, I'm Nathan. I'm an electrical engineer in the Los Angeles area. Keep an eye out for more content being posted soon.


Leave a Reply

Your email address will not be published. Required fields are marked *