moonbase

How the encoders work

Pick an encoding, type something, and watch the bytes turn into characters. The four mechanisms below are the whole library — every one of the thirteen builds on one of them.

Try it

Four mechanisms

An encoding answers one question: how do I write this byte, or this run of bits, using only characters from a small alphabet? moonbase answers it in exactly four ways, and which one an encoding uses decides everything about how it handles a leading zero, a short tail, and a string you can stream.

base2 · base8 · base16 · base32 · base64

Bit packing

Read the bytes as one long bit string, then cut it into fixed-width groups — 1, 3, 4, 5 or 6 bits. Each group is an index into the alphabet. The trailing group is shifted up and zero-filled.

base10 · base36 · base58 · base62

Repeated division

Read the bytes as one big integer, then divide by the base again and again. Each remainder is a digit, read off from the last division to the first.

base45 · base85

One number a group

Two or four bytes at a time become one number, written in base 45 or 85 with a fixed number of digits. Short, local, and cheap to decode on a small device.

base256emoji · bech32

Substitution and checksum

base256emoji is a 256-entry table, one character a byte, with no arithmetic at all. bech32 reads five bits a group, then appends a BCH checksum over the prefix and the data.

Bit packing

base16 takes 4 bits at a time, so one byte is exactly two characters and a leading 0x00 stays "00". base32 takes 5 bits, base64 takes 6, base2 takes one and base8 takes three. The leftover bits of the last group are shifted up and padded with zeros; base32 and base64 then pad the output with = to a full group, and base2 and base8 cannot, because their groups divide a byte exactly.

This is why a base64 string of b"moonbase" is bW9vbmJhc2U= — eleven characters of payload, one = of padding. The live demo shows the finished strings; this page shows the groups.

Because the group never crosses a byte boundary in base2, base8 and base16, those three are the ones a stream can encode as it reads: nothing has to wait for the next chunk.

Repeated division

base10, base36, base58 and base62 read the whole byte string as one big-endian integer. To write it in base N, divide by N and keep the remainder; repeat on the quotient until it reaches zero. The remainders, reversed, are the digits.

Leading zero bytes are special: an integer has no leading zeros, so the encoder counts them first and writes that many zero digits up front. That is why b"\x00\x00\x01" in base58 is 112 — two leading 1s from the zeros, then the 1 itself.

This is also why these four have no streaming face: the first digit depends on the last byte, so there is nothing to hand back until the input ends. The encode_int functions take the same path on a UInt64, and encode_big on a BigInt, skipping the byte step entirely.

One number a group

base45 and base85 sit between the two: they do not read the whole payload as one integer, and they do not cut it into bits. They take a fixed number of bytes at a time and write that group's value in a larger base.

base45 takes two bytes — a number below 65536 — and writes three base-45 digits, least significant first. Three digits of base 45 are more than a QR code's alphanumeric mode needs, which is the point: the characters are all ones that mode can hold, so the payload fits in a smaller symbol. There is no padding; an odd final byte is two digits.

base85 takes four bytes and writes five base-85 digits, most significant first. Adobe's Ascii85 also lets a zero group be written as a single z, and shortens the last group to n + 1 characters; ZeroMQ's Z85 refuses a tail that is not a whole group, because its frame is four bytes or nothing.

A checksum

bech32 is the one encoding here that carries its own integrity check. The payload is regrouped from eight bits to five, the human-readable prefix is expanded into its high bits and low bits, and a BCH code over both produces six more characters. A single typo anywhere — including in the prefix, which says which network an address belongs to — fails that code and is reported at the index where the reader stopped believing.

BIP 173 and BIP 350 differ only in one constant of that code: witness version 0 uses Bech32, every later version uses Bech32m, and no string is valid under both. That is a rule about addresses, not about bech32 — the library will happily write either, which is why the choice is a named argument.