Data formats

Base codes

Base32 (RFC 4648, Crockford, z-base-32), Base58 and Base58Check, and Bech32/Bech32m encoded and decoded.

Version 1.0.0 · 6 functions · 3 crates

#encoding#ids#bitcoin#codes

Functions

What use base_codes.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use basex_encode, basex_encode_hex from base_codes (Quillo 0.6).

basex_encode(kind: str, text: str) → stror an error

text (its UTF-8 bytes) encoded as kind: "base32" (RFC 4648, padded with =), "base32_nopad" (as TOTP secrets are), "base32hex", "crockford" (no I, L, O or U: for codes people read and type), "zbase32", "base58" (Bitcoin's alphabet, no 0, O, I or l) or "base58check" (with a 4-byte checksum).

basex_encode_hex(kind: str, hex_bytes: str) → stror an error

Bytes given as hex, encoded as kind: for keys, hashes and ids that are not text.

basex_decode(kind: str, code: str) → stror an error

The text code holds, decoded as kind. Base32 is read in any case; Crockford also with dashes and I, L, O typed for 1, 1, 0; base58check's checksum is checked. An error when the bytes are not text: use basex_decode_hex.

basex_decode_hex(kind: str, code: str) → stror an error

The bytes code holds, as hex.

bech32_encode(hrp: str, hex_bytes: str, variant: str) → stror an error

Bytes (hex) as Bech32 (variant "bech32") or Bech32m ("bech32m") with the human-readable part hrp (npub, lnbc, age): lower case, with a checksum that catches typos. At most 1023 characters are made.

bech32_decode(code: str) → anyor an error

What a Bech32 or Bech32m string holds: { hrp, hex, variant }, and segwit { version, program } when it is a valid Bitcoin SegWit address (bc1..., tb1...), else null. An error for a wrong checksum: a character mistyped.

Get it

$ quillo install base_codes               # base_codes.qwer into this folder$ quillo install base_codes --example     # and its example, base_codes.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/base_codes.qwer$ curl -O https://quillolang.org/plugins/files/base_codes.qwe

Then, in your program, all of it or only what you call:

main.qwe
use base_codes.qweruse basex_encode, basex_encode_hex from base_codes      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
data-encoding = "2"bs58 = { version = "0.5", features = ["check"] }bech32 = "0.11"

Example

base_codes.qwe
// Codes for people to read and type, keys in Base58, and addresses in Bech32.////   quillo run base_codes.qwe      then open http://localhost:8080/samplesuse "base_codes.qwer"route GET /samples = {    base32: basex_encode("base32", "hello")?,    crockford: basex_encode("crockford", "hello")?,    zbase32: basex_encode("zbase32", "hello")?,    base58: basex_encode("base58", "hello")?,    base58check: basex_encode_hex("base58check", "00f54a5851e9372b87810a8e60cdd2e7cfd80b6e31")?,    typed_badly: basex_decode("crockford", "d1JPRV-3F")?,}// A voucher code from a number, readable over the phone.route GET /voucher ? { id: int = 4521 } {    let code = basex_encode("crockford", `QS-${id}`)?    { code, back: basex_decode("crockford", lower(code))? }}route GET /bech32 {    let npub = bech32_encode("npub", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", "bech32")?    {        npub,        back: bech32_decode(npub)?.hex,        segwit: bech32_decode("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")?.segwit,        typo: bech32_decode("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5").error,    }}
$ quillo run base_codes.qwe$ curl 'localhost:8080/samples'$ curl 'localhost:8080/bech32'$ curl 'localhost:8080/voucher'

Source

base_codes.qwer
//! Base32 (RFC 4648, hex, Crockford, z-base-32), Base58 (and Base58Check) and Bech32/Bech32m encoded and decoded: short ids, TOTP secrets, Bitcoin and Nostr addresses, codes people read aloud.//! version: 1.0.0//!//! [dependencies]//! data-encoding = "2"//! bs58 = { version = "0.5", features = ["check"] }//! bech32 = "0.11"use data_encoding::{Encoding, Specification, BASE32, BASE32HEX_NOPAD, BASE32_NOPAD};use serde_json::{json, Value};use std::sync::OnceLock;const KINDS: &str = "base32, base32_nopad, base32hex, crockford, zbase32, base58, base58check";/// Inputs up to 1 MB are taken.const MAX: usize = 1024 * 1024;fn crockford() -> &'static Encoding {    static E: OnceLock<Encoding> = OnceLock::new();    E.get_or_init(|| {        let mut s = Specification::new();        s.symbols.push_str("0123456789ABCDEFGHJKMNPQRSTVWXYZ");        // Read as people write it: lower case, and I, L, O for 1, 1, 0.        s.translate.from.push_str("abcdefghjkmnpqrstvwxyzIiLlOo");        s.translate.to.push_str("ABCDEFGHJKMNPQRSTVWXYZ111100");        s.ignore.push('-');        s.encoding().expect("Crockford spec")    })}fn zbase32() -> &'static Encoding {    static E: OnceLock<Encoding> = OnceLock::new();    E.get_or_init(|| {        let mut s = Specification::new();        s.symbols.push_str("ybndrfg8ejkmcpqxot1uwisza345h769");        s.encoding().expect("z-base-32 spec")    })}fn encode(kind: &str, bytes: &[u8]) -> Result<String, String> {    if bytes.len() > MAX {        return Err("inputs up to 1 MB".into());    }    Ok(match kind {        "base32" => BASE32.encode(bytes),        "base32_nopad" => BASE32_NOPAD.encode(bytes),        "base32hex" => BASE32HEX_NOPAD.encode(bytes),        "crockford" => crockford().encode(bytes),        "zbase32" => zbase32().encode(bytes),        "base58" => bs58::encode(bytes).into_string(),        "base58check" => bs58::encode(bytes).with_check().into_string(),        other => return Err(format!("unknown kind `{}`: {}", other, KINDS)),    })}fn decode(kind: &str, code: &str) -> Result<Vec<u8>, String> {    let c = code.trim();    if c.len() > MAX * 2 {        return Err("inputs up to 1 MB".into());    }    let bad = |e: String| format!("not {}: {}", kind, e);    match kind {        "base32" | "base32_nopad" => {            let up = c.to_uppercase();            let e = if up.ends_with('=') { &BASE32 } else { &BASE32_NOPAD };            e.decode(up.as_bytes()).map_err(|e| bad(e.to_string()))        }        "base32hex" => BASE32HEX_NOPAD.decode(c.to_uppercase().trim_end_matches('=').as_bytes()).map_err(|e| bad(e.to_string())),        "crockford" => crockford().decode(c.as_bytes()).map_err(|e| bad(e.to_string())),        "zbase32" => zbase32().decode(c.to_lowercase().as_bytes()).map_err(|e| bad(e.to_string())),        "base58" => bs58::decode(c).into_vec().map_err(|e| bad(e.to_string())),        "base58check" => bs58::decode(c).with_check(None).into_vec().map_err(|e| bad(e.to_string())),        other => Err(format!("unknown kind `{}`: {}", other, KINDS)),    }}fn hex(b: &[u8]) -> String {    b.iter().map(|x| format!("{:02x}", x)).collect()}fn unhex(s: &str) -> Result<Vec<u8>, String> {    let s = s.trim();    if s.len() % 2 != 0 || !s.chars().all(|c| c.is_ascii_hexdigit()) {        return Err(format!("`{}` is not hex", s.chars().take(40).collect::<String>()));    }    Ok((0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap_or(0)).collect())}/// `text` (its UTF-8 bytes) encoded as `kind`: "base32" (RFC 4648, padded with `=`), "base32_nopad" (as TOTP secrets are),/// "base32hex", "crockford" (no I, L, O or U: for codes people read and type), "zbase32", "base58" (Bitcoin's alphabet, no/// 0, O, I or l) or "base58check" (with a 4-byte checksum).pub fn basex_encode(kind: &str, text: &str) -> Result<String, String> {    encode(kind, text.as_bytes())}/// Bytes given as hex, encoded as `kind`: for keys, hashes and ids that are not text.pub fn basex_encode_hex(kind: &str, hex_bytes: &str) -> Result<String, String> {    encode(kind, &unhex(hex_bytes)?)}/// The text `code` holds, decoded as `kind`. Base32 is read in any case; Crockford also with dashes and I, L, O typed for/// 1, 1, 0; base58check's checksum is checked. An error when the bytes are not text: use basex_decode_hex.pub fn basex_decode(kind: &str, code: &str) -> Result<String, String> {    String::from_utf8(decode(kind, code)?).map_err(|_| "the bytes are not text: use basex_decode_hex".to_string())}/// The bytes `code` holds, as hex.pub fn basex_decode_hex(kind: &str, code: &str) -> Result<String, String> {    Ok(hex(&decode(kind, code)?))}/// Bytes (hex) as Bech32 (`variant` "bech32") or Bech32m ("bech32m") with the human-readable part `hrp` (`npub`, `lnbc`,/// `age`): lower case, with a checksum that catches typos. At most 1023 characters are made.pub fn bech32_encode(hrp: &str, hex_bytes: &str, variant: &str) -> Result<String, String> {    let h = bech32::Hrp::parse(hrp).map_err(|e| format!("`{}` is not a human-readable part: {}", hrp, e))?;    let data = unhex(hex_bytes)?;    match variant {        "" | "bech32" => bech32::encode::<bech32::Bech32>(h, &data).map_err(|e| e.to_string()),        "bech32m" => bech32::encode::<bech32::Bech32m>(h, &data).map_err(|e| e.to_string()),        other => Err(format!("variant is bech32 or bech32m, not `{}`", other)),    }}/// What a Bech32 or Bech32m string holds: `{ hrp, hex, variant }`, and `segwit` `{ version, program }` when it is a valid/// Bitcoin SegWit address (`bc1...`, `tb1...`), else null. An error for a wrong checksum: a character mistyped.pub fn bech32_decode(code: &str) -> Result<Value, String> {    let c = code.trim();    let (hrp, data) = bech32::decode(c).map_err(|e| format!("not Bech32: {}", e))?;    let variant = if bech32::primitives::decode::CheckedHrpstring::new::<bech32::Bech32>(c).is_ok() { "bech32" } else { "bech32m" };    let segwit = bech32::segwit::decode(c).ok().map(|(_, v, program)| json!({ "version": v.to_u8(), "program": hex(&program) }));    Ok(json!({ "hrp": hrp.to_string(), "hex": hex(&data), "variant": variant, "segwit": segwit }))}
Thread
88Useful? Like it, or add to its thread below.

Thread 0 posts

On Base codes: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.