# base32c — Crockford Base32 with a check symbol (stdlib-only) Lane: Codeforge (null). Built 2026-10-23. Spec: https://www.crockford.com/base32.html (Douglas Crockford, 2019-03-04) — the authoritative table was fetched during this build; the quoted rules live in the `base32c.py` docstring (symbol set, I/L/O/U exclusions, i/l→1 and o→0 decode aliases, zero-extension to a multiple of 5 bits, hyphens ignored on decode, check symbol = value mod 37 over the 37-symbol alphabet `0-9 A-Z * ~ $ = U`). ## Files base32c.py codec + argparse CLI (encode, decode, encode-checked, verify, roundtrip) test_base32c.py 16-case evidence suite; exit 0 = all invariants hold, 1 = broken mutation_check.py injects 6 faults into a scratch copy; exit 0 = every fault caught crosscheck_oracle.py agreement run vs the independent PyPI package base32-crockford 0.3.0 demo_ids.py real-data demo: SHA-256 -> 128-bit hyphenated public ID + typo sweep base32c-selftest.txt captured run of test_base32c.py base32c-oracle.txt captured run of crosscheck_oracle.py base32c-demo.txt captured run of demo_ids.py ## Reproduce (each command's own exit code is the verdict) python3 test_base32c.py python3 mutation_check.py python3 demo_ids.py /root/.hermes/profiles/null/cache/scratch/b32venv/bin/python3.14 crosscheck_oracle.py ## Measured results (this build, this machine) test_base32c.py 16/16 cases pass, exit 0 [measured] round trips 105,806 values (exhaustive 0..100,000 plus 2**k-1, 2**k, 2**k+1 for k=0..600 and 4,000 seeded randoms up to 1024 bits), each checked by three independent decoders/encoders [measured] mutation_check.py 6/6 injected faults killed, control copy passes, exit 0 [measured] crosscheck_oracle.py 23,514 values agree with PyPI base32-crockford 0.3.0 on payload, checked payload, check symbol, hyphenated decode and alias handling; 18/18 junk rejections matched; 10/10 corrupted check symbols rejected by both [measured] single-symbol typos 9,808/9,808 rejected, of which 9,706/9,706 use a legal payload symbol (the interesting case) [measured] adjacent transpositions 27/27 rejected (all distinct-symbol adjacent swaps) [measured] deletions 816/837 rejected (97.5%) [measured] insertions 2,568/2,691 rejected (95.4%) [measured] demo_ids.py GPL-3 sha256 -> id 1SEB-E9EH-7P96-FGZ6-SDQX-V6JV-SAE, round-trip OK, 842/842 typos rejected, exit 0 [measured] Why substitution and adjacent-transposition detection are 100% (not a coincidence): substituting the symbol at weight 32**j by a different canonical digit changes the value by d*32**j, 1 <= |d| <= 31; 37 is prime and 32**j is a unit, and |d| < 37, so the change is never 0 mod 37. Adjacent swaps change the value by (a-b)*32**j*31 with a != b, |a-b| <= 31 — also never 0 mod 37. The suite asserts both exhaustively rather than asserting the algebra. ## Limitations — stated, not hidden * The check symbol is value-based, so edits that preserve the value are invisible: deleting a leading `0`, or inserting `0` in front, still verifies. The suite asserts this blind spot on purpose (`case_leading_zero_blind_spot_is_real`). * Single deletions/insertions elsewhere are ~95–98% caught, not 100%; measured above. * `min_length` pads with `0`, which is exactly what creates the leading-zero blind spot — use it for display widths, not for extra integrity. * Behavioural divergence from the PyPI oracle: its `decode(checksum=False)` treats a trailing check-only symbol (`* ~ $ = U`) as a payload digit, so `a=` decodes to a number. `base32c.decode` refuses check-only symbols in payload positions. In validating mode (`decode_checked` / `oracle.decode(..., checksum=True)`) both reject. Not a bug on either side — an ambiguity in the lax mode, recorded in `crosscheck_oracle.py`. ## Verdict VERDICT: Crockford Base32 codec ships with 16/16 invariant cases, 6/6 mutants killed, and 23,514-value agreement with an independent PyPI implementation; the one real limitation (leading-zero edits are invisible to a mod-37 check) is asserted in the suite, not hidden.