// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// multibase, and the packages it is assembled from.
///
/// The point of multibase is that a string carries the name of its own encoding,
/// so the reader does not have to be told. This example writes one payload every
/// way the table allows, then reads all of them back without being told which is
/// which.
fn main raise {
let raw = b"\x00yes mani !"
// The same bytes, twelve ways. The first character is the encoding; the rest
// is that encoding's own output.
let bases = [
@multibase.Base2,
@multibase.Base8,
@multibase.Base10,
@multibase.Base16,
@multibase.Base32,
@multibase.Base36,
@multibase.Base45,
@multibase.Base58Btc,
@multibase.Base58Flickr,
@multibase.Base64,
@multibase.Base64Url,
]
for base in bases {
let text = @multibase.encode(raw[:], base~)
println(pad(@multibase.name(base), 14) + text)
}
// Nothing above said which encoding it used, and nothing below says either:
// the reader asks the string.
println("")
for base in bases {
let text = @multibase.encode(raw[:], base~)
let found = @multibase.detect(text).unwrap()
let back = @multibase.decode(text)
let ok = back == raw && found == base
println(
pad(@multibase.name(base), 14) +
"reads back as " +
@multibase.name(found) +
" " +
(if ok { "ok" } else { "WRONG" }),
)
}
// The base256emoji entry is the one where a character is not a byte: one
// character per byte value, and some of them are outside the basic plane.
println("")
println(@multibase.encode(b"hi!"[:], base=@multibase.Base256Emoji))
// Underneath, the encodings are ordinary packages. base58check is what a
// Bitcoin address is: the payload, then four bytes of double SHA-256, then the
// whole thing in base58.
println("")
let key = @base16.decode("00010966776006953D5567439E5E39F86A0D273BEE")
println(@base58.encode_check(key[:]))
// bech32 puts the prefix inside the checksum, so a typo in the prefix is as
// visible as a typo anywhere else. The prefix says which network an address
// belongs to, which is why it cannot be dropped.
//
// A segregated witness address is a witness version as one five-bit group and
// then the program, which is why the words are built here rather than passed
// as bytes: the version is not part of the program.
let program = @base16.decode("751e76e8199196d454941c45d1b3a323f1433bd6")
let address = witness(0, program[:])
for hrp in ["bc", "tb"] {
println(@bech32.encode_words(hrp, address[:]))
}
// bech32m is the same code with a different constant, and the two do not
// overlap: a version-0 output is bech32, every later version is bech32m. A
// taproot output is version 1 with a 32-byte program, so it is spelled with
// the other constant — and the same call with `Bech32` would produce a string
// no wallet accepts.
let taproot = @base16.decode(
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
)
println(
@bech32.encode_words("bc", witness(1, taproot[:])[:], kind=@bech32.Bech32m),
)
// The same address, spelled in upper case, decodes to the same bytes — a
// bech32 string is one case throughout, and the checksum is over the lower one.
let upper = upper_of(@bech32.encode_words("bc", address[:]))
println("")
println(upper)
// Read at the words layer: the data part of an address is a version and a
// program, which is not a whole number of bytes, so `decode` — the byte face —
// would refuse it. That is a property of addresses, not of bech32.
let parts = @bech32.decode_words(upper)
println(
parts.hrp + " " + @base16.encode(@bech32.bytes_of(parts.words[1:])[:]),
)
}
///|
/// A witness program, as the groups bech32 carries: the version alone, then the
/// program regrouped from eight bits to five.
fn witness(version : Int32, program : BytesView) -> Bytes {
let data : Array[Byte] = [version.to_byte()]
let words = @bech32.words_of(program)
for i in 0.. String {
let out = StringBuilder()
for i in 0..= 97 && code <= 122 {
(code - 32).unsafe_to_char()
} else {
text[i].unsafe_to_char()
},
)
}
out.to_string()
}
///|
/// Left-align a label so a column of them lines up.
fn pad(text : String, width : Int32) -> String {
let out = StringBuilder()
out.write_string(text)
for _ in 0..<(width - text.length()) {
out.write_char(' ')
}
out.to_string()
}