moonbase

moonbase

Every base encoding for MoonBit — base2 through base85, bech32 and multibase — with one shape and the same five faces.

$ moon add moonbitstack/moonbase
13
encodings, one API shape
5
faces each: encode, decode, bytes, lossy
RFC 4648
…and 9285, 1924, BIP 173/350
0
dependencies between packages

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.

@base2

One bit a character

Eight characters a byte, no padding, no waiting. The multibase 0 prefix.

@base8

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.

@base10

Decimal

The bytes as one big-endian integer. The only base whose output a human reads without a legend.

@base16

Hexadecimal

Two characters per byte, so leading zeros survive. A colon form writes certificate fingerprints.

@base32

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.

@base36

0–9a–z

Case-insensitive, alphanumeric. Short, human-facing ids. Has an integer mode too.

@base45

RFC 9285

Two bytes in three characters a QR code's alphanumeric mode can hold, so the data fits in a smaller symbol.

@base58

No look-alikes

0, O, I and l are left out so strings survive being read aloud. Bitcoin, Flickr and Ripple alphabets, plus base58check.

@base62

0–9A–Za–z

Fully URL-safe. The usual choice for shortened links and opaque public ids.

@base64

RFC 4648 base64

Standard +/ and a URL-safe -_ kind for tokens and file names.

@base85

Four bytes, five characters

Adobe's Ascii85, ZeroMQ's Z85 (RFC 32) and the IPv6 spelling of RFC 1924 — three alphabets, one width.

@bech32

BIP 173, BIP 350

A readable prefix, five-bit groups and a BCH checksum that makes a typo visible anywhere — including in the prefix.

@multibase

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.

Note

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

PackageSpecificationNotes
@base2multibase 0One bit a character; eight characters a byte.
@base8multibase 7Three bits a character — a bit packing, not an octal of a number.
@base10multibase 9Bytes as one decimal integer; a leading zero byte stays a leading 0.
@base16RFC 4648 §8Byte-aligned, so leading zeros survive; encode_colons writes fingerprints.
@base32§6, §7kind=Hex keeps byte order under sorting; Crockford, Z.
@base36multibase k/KNumbers and identifiers.
@base45RFC 9285QR alphanumeric mode; two bytes in three characters, no padding.
@base58multibase zNo 0, O, I or l; Flickr, Ripple, and encode_check.
@base62—Digits, upper, lower; short identifiers.
@base64§4, §5kind=Url for tokens and file names; decode_any reads either alphabet.
@base85Adobe, RFC 32, RFC 1924kind=Ascii85, Z85, Ipv6 — four bytes in five characters.
@bech32BIP 173, BIP 350kind=Bech32m for witness version 1 and up; the two never overlap.
@multibasemultiformatsA 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.