Base58 Encoding and Decoding Tutorial

by shahiN Noursalehi

You are here: Home / Tutorials / Cryptography, Blockchain, Base58

Introduction to Base58

Base58 is an encoding scheme used to represent large numbers as human-readable strings, primarily in cryptocurrency systems like Bitcoin and Ripple. It uses a 58-character alphabet (123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz), excluding ambiguous characters (0, O, I, l) to minimize errors in manual transcription. Unlike Base64, Base58 avoids non-alphanumeric characters (+, /), making it suitable for URLs, addresses, and user interfaces.

Base58 is often used with a checksum (Base58Check) to ensure data integrity, as seen in Bitcoin addresses. This tutorial focuses on raw Base58 encoding and decoding for numeric values, useful for converting large integers to compact strings.

Key Concepts

  • Alphabet: 58 characters, designed for readability and error prevention.
  • Base58Check: Adds a version byte and checksum for integrity (e.g., Bitcoin addresses).
  • Use Cases: Cryptocurrency addresses, private keys, and compact number representation.

Mathematics and Pseudo-Code for Base58

Base58 encoding requires unsigned (non-negative) numbers as input, as it is designed to represent positive integers in contexts like cryptocurrency addresses. Negative numbers are invalid because the encoding process maps remainders to a 58-character alphabet, which assumes a positive value. Implementations typically enforce this by rejecting negative inputs, ensuring consistent and meaningful output.

Base58 converts a large number into a base-58 representation, similar to how decimal uses base-10 or hexadecimal uses base-16. The process involves:

Mathematical Foundation

Let N be the input number (non-negative integer).

Encoding: Repeatedly compute d = N mod 58, append the character at index d in the alphabet, and update N = N / 58. Reverse the result to match big-endian order.

Decoding: For a string S = c_1 c_2 ... c_n, compute N = sum(index(c_i) * 58^(n-i)).


Pseudo-Code: Encoding


FUNCTION encode_base58(number: BigInteger) -> String:
    IF number < 0 THEN ERROR "Input must be non-negative"
    alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    result = empty string
    WHILE number > 0:
        remainder = number % 58
        result = alphabet[remainder] + result
        number = number / 58
    RETURN result
END

                

Pseudo-Code: Decoding


FUNCTION decode_base58(base58: String) -> BigInteger:
    alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    value = 0
    FOR each character c in base58:
        index = position of c in alphabet
        IF index = -1 THEN ERROR "Invalid Base58 character"
        value = value * 58 + index
    RETURN value
END

                

Critical Note:
This handles raw Base58 for numeric values. For binary data, additional logic is needed for Base58Check or other formats.

Java Implementation

Below is a Java implementation for Base58 encoding and decoding, taking a BigInteger as input and returning a Base58 string.

Base58 Encode/Decode


import java.math.BigInteger;
import java.util.Arrays;

public class Base58 {
    // Base58 alphabet
    private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
    private static final BigInteger BASE = BigInteger.valueOf(ALPHABET.length);
    private static final int[] INDEXES = new int[128];

    static {
        Arrays.fill(INDEXES, -1);
        for (int i = 0; i < ALPHABET.length; i++) {
            INDEXES[ALPHABET[i]] = i;
        }
    }

    // Encode a BigInteger to a Base58 string
    public static String encode(BigInteger value) {
        if (value.compareTo(BigInteger.ZERO) < 0) {
            throw new IllegalArgumentException("Input must be non-negative");
        }
        StringBuilder sb = new StringBuilder();
        while (value.compareTo(BigInteger.ZERO) > 0) {
            BigInteger[] divmod = value.divideAndRemainder(BASE);
            sb.append(ALPHABET[divmod[1].intValue()]);
            value = divmod[0];
        }
        return sb.length() > 0 ? sb.reverse().toString() : "";
    }

    // Decode a Base58 string to a BigInteger
    public static BigInteger decode(String input) {
        BigInteger value = BigInteger.ZERO;
        for (char c : input.toCharArray()) {
            int index = INDEXES[c];
            if (index == -1) {
                throw new IllegalArgumentException("Invalid Base58 character: " + c);
            }
            value = value.multiply(BASE).add(BigInteger.valueOf(index));
        }
        return value;
    }

    // Example usage
    public static void main(String[] args) {
        BigInteger value = new BigInteger("123456");
        String encoded = encode(value);
        BigInteger decoded = decode(encoded);
        System.out.println("Input: " + value); // 123456
        System.out.println("Base58: " + encoded); // LDP
        System.out.println("Decoded: " + decoded); // 123456
    }
}

                

Example Process: Encoding and Decoding 123456

Encoding (123456 to "LDP"):

  1. Input: N = 123456.
  2. Step 1: 123456 / 58 = 2128, remainder 123456 mod 58 = 48. ALPHABET[48] = P.
  3. Step 2: 2128 / 58 = 36, remainder 2128 mod 58 = 40. ALPHABET[40] = D.
  4. Step 3: 36 / 58 = 0, remainder 36 mod 58 = 36. ALPHABET[36] = L.
  5. Stop: N = 0.
  6. Result: PDL, reverse to LDP.

Decoding ("LDP" to 123456):

  1. Input: LDP. Indices: L=36, D=40, P=48.
  2. Step 1: value = 0.
  3. Step 2: For L: value = 0 * 58 + 36 = 36.
  4. Step 3: For D: value = 36 * 58 + 40 = 2088 + 40 = 2128.
  5. Step 4: For P: value = 2128 * 58 + 48 = 123424 + 48 = 123456.
  6. Result: 123456.

C# Implementation

Below is a C# implementation for Base58 encoding and decoding, matching the Java version with BigInteger input and Base58 string output.

Base58 Encode/Decode


using System;
using System.Numerics;
using System.Text;

public class Base58
{
    private const string Base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

    // Encode a BigInteger to a Base58 string
    public static string Encode(BigInteger value)
    {
        if (value < 0)
            throw new ArgumentException("Input must be non-negative");

        StringBuilder result = new StringBuilder();
        while (value > 0)
        {
            int index = (int)(value % 58);
            result.Insert(0, Base58Chars[index]);
            value /= 58;
        }
        return result.ToString();
    }

    // Decode a Base58 string to a BigInteger
    public static BigInteger Decode(string base58)
    {
        BigInteger value = BigInteger.Zero;
        foreach (char c in base58)
        {
            int index = Base58Chars.IndexOf(c);
            if (index == -1)
                throw new ArgumentException("Invalid Base58 character: " + c);
            value = value * 58 + index;
        }
        return value;
    }

    // Example usage
    public static void Main()
    {
        BigInteger value = new BigInteger(123456);
        string encoded = Encode(value);
        BigInteger decoded = Decode(encoded);
        Console.WriteLine($"Input: {value}"); // 123456
        Console.WriteLine($"Base58: {encoded}"); // LDP
        Console.WriteLine($"Decoded: {decoded}"); // 123456
    }
}

                

Example Process: Encoding and Decoding 123456

Encoding (123456 to "LDP"):

  1. Input: value = 123456.
  2. Step 1: 123456 mod 58 = 48, 123456 / 58 = 2128. Base58Chars[48] = P.
  3. Step 2: 2128 mod 58 = 40, 2128 / 58 = 36. Base58Chars[40] = D.
  4. Step 3: 36 mod 58 = 36, 36 / 58 = 0. Base58Chars[36] = L.
  5. Stop: value = 0.
  6. Result: Insert in reverse, yielding LDP.

Decoding ("LDP" to 123456):

  1. Input: LDP. Indices: L=36, D=40, P=48.
  2. Step 1: value = 0.
  3. Step 2: For L: value = 0 * 58 + 36 = 36.
  4. Step 3: For D: value = 36 * 58 + 40 = 2088 + 40 = 2128.
  5. Step 4: For P: value = 2128 * 58 + 48 = 123424 + 48 = 123456.
  6. Result: 123456.

Converting Hex to BigInteger in C#

When preparing data for Base58 encoding in C#, you may need to convert a hex string to a BigInteger. This requires careful handling due to BigInteger’s two’s-complement interpretation.

Logic and Behavior

Hex to Bytes: Convert the hex string to a byte array (e.g., "1E240"{ 0x1E, 0x24, 0x0 }).

BigInteger Constructor: Interprets the byte array as a little-endian two’s-complement number:

  • If the first byte ≥ 0x80 (e.g., 0xFF), it’s negative unless a 0 byte is appended.
  • Example: { 0xFF }-1 (negative) without 0, 255 (positive) with 0.

Ensuring Positive: Append a 0 byte to guarantee a positive BigInteger:


BigInteger value = new BigInteger(data.Concat(new byte[] { 0 }).ToArray());

                

Why Always Add 0? It’s simpler than checking data[0] >= 0x80, covers all cases (empty arrays, all byte values), and aligns with Base58’s unsigned number requirement. The overhead is negligible.


Example Conversion:


public static BigInteger HexToBigInteger(string hex)
{
    byte[] data = FromHexString(hex);
    return new BigInteger(data.Concat(new byte[] { 0 }).ToArray()); // Ensure positive
}

private static byte[] FromHexString(string hex)
{
    if (string.IsNullOrEmpty(hex))
        throw new ArgumentException("Hex string cannot be null or empty.");

    hex = hex.Trim();
    if (hex.Length % 2 != 0)
        throw new ArgumentException($"Hex string length must be even, got {hex.Length} characters.");

    var numberChars = hex.Length;
    var hexAsBytes = new byte[numberChars / 2];
    for (var i = 0; i < numberChars; i += 2)
        hexAsBytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);

    return hexAsBytes;
}

                

Usage:


string hex = "1E240"; // 123456 in decimal
BigInteger value = HexToBigInteger(hex); // 123456
string base58 = Base58.Encode(value); // LDP

                

Example Process: Hex to BigInteger ("1E240" to 123456)

Conversion:

  1. Input: Hex string 1E240.
  2. Step 1: Parse hex to bytes:
    • 1E0x1E (30).
    • 240x24 (36).
    • 00x00 (0).
    • Result: { 0x1E, 0x24, 0x00 }.
  3. Step 2: Append 0 byte to ensure positive: { 0x1E, 0x24, 0x00, 0x00 }.
  4. Step 3: Create BigInteger (little-endian):
    • Interpret as big-endian hex 1E240.
    • Compute: 30 * 256^2 + 36 * 256^1 + 0 * 256^0 = 1966080 + 9216 + 0 = 123456.
  5. Result: BigInteger = 123456.

Critical Note:
Appending a 0 byte ensures compatibility with Java’s positive BigInteger and Base58’s unsigned number requirement.

Additional Resources


Acknowledgments

Special thanks to Grok, for its assistance in developing this tutorial on Base58 encoding and decoding.