moonbase
Every base encoding for MoonBit — base2 through base85, bech32 and multibase — with one shape and the same five faces.
moon add moonbitstack/moonbase
The thirteen encodings
Every package exposes the same five functions in the same order, so reading one is reading all of them. Variants are a named argument, not a different function name.
One bit a character
Eight characters a byte, no padding, no waiting. The multibase 0 prefix.
Three bits a character
Eight characters to three bytes. A bit packing, not an octal of a number — the difference is a leading zero byte.
Decimal
The bytes as one big-endian integer. The only base whose output a human reads without a legend.
Hexadecimal
Two characters per byte, so leading zeros survive. A colon form writes certificate fingerprints.
RFC 4648 base32
The A-Z2-7 alphabet, a hex kind that sorts like the bytes it encodes, Crockford and z-base-32 for people.
0–9a–z
Case-insensitive, alphanumeric. Short, human-facing ids. Has an integer mode too.
RFC 9285
Two bytes in three characters a QR code's alphanumeric mode can hold, so the data fits in a smaller symbol.
No look-alikes
0, O, I and l are left out so strings survive being read aloud. Bitcoin, Flickr and Ripple alphabets, plus base58check.
0–9A–Za–z
Fully URL-safe. The usual choice for shortened links and opaque public ids.
RFC 4648 base64
Standard +/ and a URL-safe -_ kind for tokens and file names.
Four bytes, five characters
Adobe's Ascii85, ZeroMQ's Z85 (RFC 32) and the IPv6 spelling of RFC 1924 — three alphabets, one width.
BIP 173, BIP 350
A readable prefix, five-bit groups and a BCH checksum that makes a typo visible anywhere — including in the prefix.
Self-identifying
One character in front of any of the above, from 0 for base2 to a rocket for base256emoji.
Live demo
Type anything. Each encoding is computed in your browser with the same algorithm the library ships — byte for byte.
Try it live
Type any text — every base's encoding of it appears at once.
The demo runs moonbase itself, built to JavaScript, so every row is what the library writes. base10, base36, base58 and base62 read the bytes as one big integer; base2, base8, base16, base32 and base64 pack bits; base45 and base85 turn a group of bytes into a number first; base256emoji is a 256-entry table; bech32 packs five bits and appends a BCH checksum, which is why its row gives up on a long line — BIP 173 caps the whole string at 90 characters.
What it gives you
One shape
All thirteen packages share the five faces: encode, encode_bytes,
decode, decode_bytes and decode_lossy. Import only
what you use — an app that needs base64 does not link base58's big-integer core.
// the ordinary case
@base64.encode(data[:]) // "bW9vbmJhc2U="
@base64.encode(data[:], kind=Url, padding=false) // "bW9vbmJhc2U"
// a wire format that already holds bytes — no round trip through String
@base64.encode_bytes(data[:])Streaming
Every bit-packing package has a sink() and a source(). Feed
them whatever chunks the transport hands you; the bytes that come out are the bytes one
call would have produced.
let sink = @base64.sink()
sink.write(b"moon"[:])
sink.write(b"base"[:])
sink.finish() // "bW9vbmJhc2U="Honest errors
A bad input names where it broke, instead of a silent None. The four
failures are Bad, Truncated, Padding and
Checksum, each carrying the offending index.
try @base64.decode("Zm*vYg==") catch {
failure => match failure {
Bad(at~, char~) => // at = 2, char = '*'
Truncated(at~) => // input ends mid-group
Padding(at~) => // '=' where it cannot be
Checksum(at~) => // a carried checksum did not match
}
} noraise { _ => println("decoded") }Self-identifying strings
multibase writes the encoding's name in front of the string;
bech32 folds the prefix into the checksum. Either way the reader does not
have to be told which encoding it was handed.
@multibase.encode(data[:], base=Base58Btc) // "zKJfFLz7PRba"
@multibase.decode("zKJfFLz7PRba") // b"moonbase"
@bech32.encode("moonbase", data[:]) // "moonbase1d4hk7mnzv9ek2s84pra"Custom alphabets
The positional core is public, so any base-N system is two lines. Alphabet::new
fails on a bad alphabet (it often comes from configuration); Alphabet::of is
the same thing for literals in your own source.
let crockford = @moonbase.Alphabet::new(
"0123456789ABCDEFGHJKMNPQRSTVWXYZ",
)
@moonbase.encode(data[:], crockford)Which one to pick
| Package | Specification | Notes |
|---|---|---|
@base2 | multibase 0 | One bit a character; eight characters a byte. |
@base8 | multibase 7 | Three bits a character — a bit packing, not an octal of a number. |
@base10 | multibase 9 | Bytes as one decimal integer; a leading zero byte stays a leading 0. |
@base16 | RFC 4648 §8 | Byte-aligned, so leading zeros survive; encode_colons writes fingerprints. |
@base32 | §6, §7 | kind=Hex keeps byte order under sorting; Crockford, Z. |
@base36 | multibase k/K | Numbers and identifiers. |
@base45 | RFC 9285 | QR alphanumeric mode; two bytes in three characters, no padding. |
@base58 | multibase z | No 0, O, I or l; Flickr, Ripple, and encode_check. |
@base62 | — | Digits, upper, lower; short identifiers. |
@base64 | §4, §5 | kind=Url for tokens and file names; decode_any reads either alphabet. |
@base85 | Adobe, RFC 32, RFC 1924 | kind=Ascii85, Z85, Ipv6 — four bytes in five characters. |
@bech32 | BIP 173, BIP 350 | kind=Bech32m for witness version 1 and up; the two never overlap. |
@multibase | multiformats | A one-character prefix in front of any of the above. |
Read the Guide to start encoding, or browse the API Reference for every type and function.