WO-04 — Bech32m codec¶
Local checks passed; stopped after step 7. Claude’s independent check is pending.
Branch: wo-04-bech32m, from wo-02-shake at 148c7fd.
New golden reference/vectors frozen in 3af5f22, before C++ implementation.
No other work order’s golden files changed. No installations, pushes or merges.
Built¶
e46/include/e46/bech32m.hpp,e46/src/bech32m.cpp: static targetE46::bech32m, generic raw encode/decode and E46 version/payload framing.All ten mainnet/testnet prefixes from §2a, version symbols
q/p/z.Mandatory expected prefix on E46 decoding, rejecting wrong network/object prefixes; strict checksum, ASCII, case, length and padding validation.
Independent Python reference, frozen vectors, batch CLI, unit/differential tests, parser fuzzer and benchmark. CMake changes only integrate this WO.
Owner’s UNC formatting; warnings are errors. Library uses no SHA-2, floating point or external crypto library.
Standards: BIP-350 and BIP-173. Official vectors here are the generic Bech32m vectors. Bitcoin SegWit witness-version/program rules are not E46’s address rules.
Measured¶
Evidence: summary and source fingerprints. Command outputs and fuzz/benchmark logs are alongside it.
Check |
Result |
|---|---|
Generic BIP-350 vectors |
7 valid and 14 invalid cases passed |
Chain §2a examples |
All 10 reproduce exactly |
Python/C++ differential |
7,233 comparisons, zero mismatches, including 1,000 seeded random inputs |
Edge checks |
Empty/max-size frames, uppercase/mixed case, unknown/wrong prefixes, unsupported versions, old Bech32 checksums, tampering, excess/nonzero padding |
Exhaustive padding unit check |
All 1,024 two-word combinations checked |
Full release suite, including WO-01/02 |
8/8 passed |
Full ASan/UBSan suite |
8/8 passed, recovery disabled |
Golden regeneration |
Byte-for-byte identical; frozen file not overwritten |
Formatting |
New C++ files pass clang-format |
Fuzzing |
1,030,037 executions in 31 seconds, one worker, no errors; seeded with official vectors and E46 examples |
Benchmark |
Median 658,711 encode+decode round trips/second, 32-byte payload, one worker, five ~0.25-second samples |
Fuzzer seed: 2026100804, input cap 256 bytes. Benchmark: macOS arm64, Apple
Clang 21 release build; fuzzing uses the already-installed Homebrew LLVM 22.
Throughput includes both operations and is host/load-specific.
API boundary for review¶
encode_raw(hrp, five_bit_words) and decode_raw(text) implement generic
Bech32m. encode_e46(hrp, version, bytes) and
decode_e46(text, expected_hrp) additionally handle E46 prefixes, versions and
canonical byte conversion. Encoding emits lowercase; decoding accepts uniform
uppercase and rejects mixed case. Invalid inputs throw std::invalid_argument.
This is a framing codec, not address derivation or a consensus validator.
Payload meaning/length and whether reserved p is activated belong to WO-05.
Empty payloads and p envelopes are representable without declaring them valid
spendable addresses. Every E46 version uses Bech32m, including q; Bitcoin’s
version-zero Bech32 exception does not apply. No automatic error correction.
Re-run¶
cmake --preset release && cmake --build --preset release && ctest --preset release
cmake --preset sanitize && cmake --build --preset sanitize && ctest --preset sanitize
The CLI reads one operation per line and returns OK ... or ERR:
encode e46 0 00010203
decode e46 <encoded-string-as-hex>
raw_encode <hrp-as-hex> <five-bit-words-as-hex>
raw_decode <encoded-string-as-hex>
Use - for an empty byte field. CLI: build/release/bech32m_cli.
Python reference for fresh-input review:
e46/golden/bech32m_reference.py. The builder’s public seed is not Claude’s
unpublished-seed surprise exam. No later work order was started.