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
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 errortext (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 errorBytes given as hex, encoded as kind: for keys, hashes and ids that are not text.
basex_decode(kind: str, code: str) → stror an errorThe 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 errorThe bytes code holds, as hex.
bech32_encode(hrp: str, hex_bytes: str, variant: str) → stror an errorBytes (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 errorWhat 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.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/base_codes.qwer$ curl -O https://quillolang.org/plugins/files/base_codes.qweThen, in your program, all of it or only what you call:
use base_codes.qweruse basex_encode, basex_encode_hex from base_codes // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
data-encoding = "2"bs58 = { version = "0.5", features = ["check"] }bech32 = "0.11"Example
// 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
//! 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 }))}Related
bson.qwer · 1.0.0BSON, MongoDB's binary format: documents to BSON and back as Extended JSON, and ObjectIds made and read.6 views · 6 downloadsPEM pem.qwer · 1.0.0PEM bundles read block by block (certificates, keys and requests, with their labels, headers, key algorithm and SHA-256), blocks picked out, written, and turned into DER files and back.6 views · 6 downloadsThread 0 posts
On Base codes: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.