baseh
Python implementation of the baseH codec. Encodes an internal integer ID as
a checksummed, optionally permuted human-readable reference code. Implements
the normative spec in ../spec/IMPLEMENTATION_CODEC.md and passes the
frozen cross-language vectors in ../vectors/.
Zero runtime dependencies. HMAC-SHA-256 comes from the standard library.
Install
pip install baseh
or from a local checkout:
pip install ./python
Or run in place without installing:
PYTHONPATH=python/src python3 -c "import baseh"
Usage
Expandable mode is the recommended default for new users. Codes start at 4 characters and grow automatically as the id sequence climbs — no migration, no re-issue, and old shorter codes keep decoding forever:
from baseh import Baseh, BasehError, baseh_expandable_v1
codec = Baseh(baseh_expandable_v1())
code = codec.encode(123456789)
print(code) # 4 characters at this namespace size; grows as ids climb
result = codec.decode(code.lower()) # case-insensitive
print(result.id) # 123456789
For constant-width needs, use a fixed-mode tier such as Medium:
from baseh import Baseh, BasehError, baseh_medium_v1
codec = Baseh(baseh_medium_v1())
code = codec.encode(123456789)
print(code) # "C8XP-8J49": fixed-length, checksummed code
result = codec.decode(code.lower()) # case-insensitive
print(result.id) # 123456789
print(result.canonical_code) # "C8XP-8J49"
print(result.corrected) # False (input differed only in case)
# Assisted correction over spoken-confusion pairs (B/D, P/T, C/G, ...):
fixed = codec.decode(
"GC8G-AZ2V",
try_correction=True,
confusion_profile="heavy",
)
print(fixed.corrected) # True
print(fixed.canonical_code) # "CC8G-AZ2V"
# Non-throwing validation for user input:
check = codec.validate("00000000")
print(check) # {"valid": False, "reason": "INVALID_CHECKSUM"}
# Live as-you-type feedback for a code entry field (spec 12.5):
live = codec.inspect("CC8G")
print(live) # InspectResult(state="typing", typed="CC8G", progress=0.5, ...)
print(codec.capacity()) # 481890304
Errors raise BasehError with a .code attribute, one of:
INVALID_PROFILE, OUT_OF_RANGE, PERMUTATION_FAILURE, INVALID_LENGTH,
INVALID_CHARACTER, INVALID_CHECKSUM, AMBIGUOUS_INPUT,
TOO_MANY_CANDIDATES, BLOCKED_CODE.
Expandable mode
Profiles carry a mode field: "expandable" or "fixed". All the frozen
tiers below are mode: "fixed" and behave exactly as before. The new
frozen tier baseh-expandable-v1 (helper baseh_expandable_v1()) is the
recommended starting point for new users.
Expandable properties:
- Codes start short and grow automatically: minimum length 4 characters
(profile field
minLength, default 4). As the id sequence climbs past each length's capacity, codes simply become one character longer — transparently, with no migration or re-issue. Old shorter codes keep decoding forever; the code's length selects the generation at decode. - The default expandable body alphabet is the 27 symbols left after the
medium visual and spoken safety strips and the
0/Ozero ban, so an issued code never emits a visual or spoken confusable. A custom alphabet containing0/Ohas those symbols silently removed during profile preparation. This composes unchanged with the existing visual/spoken safety levels, profanity modes and blocklists. - The checksum alphabet is the body alphabet plus
0(28 symbols for the default). The existing input aliasO -> 0remains, so a typed or misreadOin a checksum position resolves to0. - There is no left-padding in expandable mode (fixed mode keeps its
documented left-pad behaviour). A
0orOin a body position of presented input is simply an invalid character. - Permutation stays ON: the Feistel permutation is applied per generation (per code length), with the length mixed into the key derivation alongside the profile id. Codes within each length look random even though issuance is a sequential counter. Same caveat as today: presentation only, not encryption.
- Separators/grouping only appear once codes reach a threshold length:
profile field
separatorMinLength(the shipped tier uses 6, i.e. no hyphen until codes are 6+ characters). Below the threshold there is no separator and no grouping.
The security posture is unchanged: a code is a reference alias, never an authorization token. Expandable codes in the smallest generations are a small namespace, so rate-limit public lookups and enforce authorization after decode.
Frozen tiers (fixed mode)
Four frozen profiles, each 6 body symbols and case-insensitive, built from
the full alphanumeric set with cumulative visual and spoken strips. All are
mode: "fixed" — constant-width codes for fixed-width needs:
| Tier | Helper | Symbols | Checksums | Shape | Capacity |
|---|---|---|---|---|---|
| Minimum | baseh_minimum_v1() |
36 | none | XXX-XXX |
2,176,782,336 |
| Light | baseh_light_v1() |
31 | 2 | XXXX-XXXX |
887,503,681 |
| Medium | baseh_medium_v1() |
28 | 2 | XXXX-XXXX |
481,890,304 |
| Heavy | baseh_heavy_v1() |
26 | 2 | XXXX-XXXX |
308,915,776 |
Medium is the default fixed tier (expandable is the recommended default overall). All four keep the typed O/I/L aliases where possible, use a hyphen delimiter at the midpoint and run the default profanity blocklist.
Every plain tier permutes with the frozen published key, exported as
FROZEN_KEY_BYTES (the ASCII bytes of "baseh-frozen-key-v1", 8
feistel-v1 rounds, key id "frozen"). The key is public by design: it
makes issued codes look non-sequential but offers no secrecy, since anyone
can read it in the source. Never swap it on a live namespace; codes only
decode with the key they were issued under.
Each helper returns a freshly-built mutable profile dict on every call, so callers can load a default and modify it:
profile = baseh_medium_v1()
profile["bodyLength"] = 7
profile["grouping"] = [4, 5] # groups must sum to bodyLength + checksumLength
codec = Baseh(profile)
Permuted variants
The _p variants are identical to their tier but permute with
caller-supplied feistel-v1 key material instead of the frozen key:
from baseh import Baseh, baseh_medium_p_v1
key = bytes.fromhex("746573742d6f6e6c792d6b65792d6d6174657269616c2d30303031")
codec = Baseh(baseh_medium_p_v1(key, key_id="my-app-01")) # rounds defaults to 8
Available as baseh_minimum_p_v1, baseh_light_p_v1, baseh_medium_p_v1
and baseh_heavy_p_v1, plus baseh_expandable_p_v1 for the keyed
private-mapping expandable variant (baseh-expandable-p-v1).
Profanity safety (spec 18)
All frozen tiers run the default blocklist. Profiles accept a profanity
field to change that:
# Block specific words (substrings of the raw code) at encode time:
profile = baseh_medium_v1()
profile["profanity"] = {"mode": "blocklist", "extraWords": ["QQQQ"]}
codec = Baseh(profile)
# codec.encode(id) raises BasehError(code="BLOCKED_CODE") on a match.
# Or remove vowels from both alphabets entirely:
profile["profanity"] = {"mode": "no-vowels"}
Tests
cd python
PYTHONPATH=src python3 -m unittest discover -s tests -v
Metadata
Release files for baseh 2.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| baseh-2.0.3.tar.gz | 45.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| baseh-2.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 68.7 kB
Release files / baseh-2.0.3.tar.gz
| Download URL | baseh-2.0.3.tar.gz |
|---|---|
| Size | 45.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
54249fee17108ea22df7e3797d6f0a41338d9ab6b26855295778e55da03f4aed
|
|
BLAKE2b-256 checksum How to use checksums |
8fd1582e4471e2a86a2d1c62c093de4db7d448e616fb9997c9f86a8b366714e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logRelease files / baseh-2.0.3-py3-none-any.whl
| Download URL | baseh-2.0.3-py3-none-any.whl |
|---|---|
| Size | 23.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ac37a97cd3303f9ee03104ca6c8e1bd69b822b64f551b54ce52e054f8e8b2165
|
|
BLAKE2b-256 checksum How to use checksums |
242da27022fcb763cdddde75d375f0ab17810e7d1315c05358437e74bc619b43
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency log