Skip to main content

HEXA60: Identifier-Safe Binary-to-Text Encoding Specification & Reference Implementation

License: AGPL v3 Python Version Code Style

HEXA60 is a deterministic, high-performance binary-to-text encoding scheme engineered specifically for transport safety across modern network protocols, web APIs, biometrics, and database storage.

Unlike traditional Base64 (which requires characters like +, /, and =) or Base58, HEXA60 uses an identifier-safe 60-character alphabet. It eliminates all characters requiring URL percent-encoding, regex escaping, or special handling in HTTP headers, QR codes, and SQL queries.


Key Features

  • 🛡️ Identifier-Safe Alphabet: Devoid of +, /, =, and punctuation that triggers URL encoding or header parsing errors.
  • ⚡ $\mathcal{O}(N)$ Chunked Encoding: High-throughput encode_chunked() processes 8-byte blocks into 11-character chunks in linear time ($\mathcal{O}(N)$), making it ideal for large payloads and biometrics.
  • 0️⃣ Deterministic Zero-Byte Preservation: Preserves leading zero bytes (b'\x00') by mapping them directly to '0' characters (the first character of ALPHABET).
  • 🔍 Strict Error Handling: Immediate detection of corrupted input via InvalidCharacterError (with exact character and index position) and LengthError.
  • 🔄 Automatic Tail Length Detection: decode_chunked_auto() recovers exact original byte length without requiring length metadata.
  • ✅ Pre-flight Validation: Fast is_valid() utility to check reformatting and sanitization before decoding.

Table of Contents


Alphabet Specification

HEXA60 uses a fixed 60-character alphabet (ALPHABET):

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-
  • Index 0: '0' (used for zero-padding and leading zero bytes)
  • Safe Characters: Numbers (0-9), Uppercase (A-Z), Lowercase (a-z), Underscore (_), Hyphen (-).

Installation

Install via pip (or include hexa60.py directly in your project):

pip install hexa60

Quick Start

1. Bulk Encoding & Decoding

Suitable for short tokens, hashes, UUIDs, and API keys:

from hexa60 import encode, decode

# Encoding arbitrary bytes (including leading zeros)
data = b"\x00\x00\x01\x02\x03\xff\xfe"
encoded_str = encode(data)
print(f"Encoded: {encoded_str}")

# Decoding back to bytes
decoded_bytes = decode(encoded_str)
assert decoded_bytes == data

2. High-Performance Chunked Encoding

Recommended for biometrics, payload streams, and large files ($\mathcal{O}(N)$ linear time complexity):

from hexa60 import encode_chunked, decode_chunked_auto

payload = b"BiometricTemplateData_2026_Sample_Payload"

# Chunked Encoding (8-byte chunks -> 11 characters)
wire_format = encode_chunked(payload)
print(f"Wire Format: {wire_format}")

# Automatic Decoding (infers exact byte length automatically)
restored_payload = decode_chunked_auto(wire_format)
assert restored_payload == payload

3. Input Validation & Error Handling

from hexa60 import is_valid, decode, decode_chunked_auto
from hexa60 import Base60Error, InvalidCharacterError, LengthError

user_input = "41SmmMHgyT7SbqrV1EEW_-"

if not is_valid(user_input):          # requires a str, raises TypeError otherwise
    print("Invalid HEXA60 string detected!")

try:
    decoded = decode(user_input, strict=True)     # strict=True is the default
except InvalidCharacterError as err:
    print(f"Invalid character {err.char!r} at position {err.position}")
except LengthError as err:
    print(f"Length mismatch: {err}")
except Base60Error as err:
    print(f"Other HEXA60 error: {err}")

Bulk decode() also accepts length= to left-pad with zero bytes or truncate to exactly that size; a negative length raises LengthError.

Chunk capacity

The example strings in the protocol illustration (A1XA608EF, BK0WK27GK, the HEXA60... token) are 9 or 27 characters long, i.e. they are bulk (encode) output, not encode_chunked output. Chunked output is always a multiple of 11 characters plus a valid tail, so those strings must be decoded with decode().

60**11 = 36279705600000000000 is larger than 2**64 = 18446744073709551616, so the 11-character space is slightly wider than the 8-byte space it encodes. An encode_chunked value is always below 2**64 and therefore always decodable, but roughly half of all theoretical 11-character strings cannot be represented as 8 bytes. Such an over-capacity chunk raises LengthError — it is never silently truncated to a shorter buffer.

from hexa60 import decode_chunked_auto, encode_chunked, LengthError

encode_chunked(b"\xff" * 8)          # always round-trips
decode_chunked_auto("z" * 11)        # raises LengthError (over capacity)

The same rule applies to tails: a tail whose value needs more bytes than its tail width implies is rejected rather than truncated.


Encoding Modes

Function Format Complexity Use
encode / decode Bulk (single big integer) $\mathcal{O}(N^2)$ Short tokens, salts, nonces
encode_chunked / decode_chunked 8 bytes $\rightarrow$ 11 chars $\mathcal{O}(N)$ Large payloads, biometrics

Bulk and chunked output are different formats and are not interchangeable. Do not pass encode_chunked output to decode.


Arithmetic

Beyond binary-to-text encoding, the package provides Base-60 integer arithmetic. Both modules use the same ALPHABET as the codec, so a value encoded by hexa60 is directly usable as a number.

Base60Int (object-oriented)

Base60Int is an immutable value type. It stores a Python int internally (arbitrary precision) and renders itself as Base-60 text on demand.

from base60_int import Base60Int

n = Base60Int(60)
n.to_b60()              # '10'  -- because 60 == '10' in Base-60
n + n                  # Base60Int('20')
n * n                  # Base60Int('100')

Base60Int(2) ** 10     # Base60Int('H4')  -- 1024 in decimal
int(Base60Int("10"))   # 60

It accepts int, str (Base-60 text), bytes (big-endian) or another Base60Int. Comparison, hash and the usual arithmetic operators all work:

a, b = Base60Int(60), Base60Int(61)
a < b                  # True
{a, Base60Int("10")}   # set works -- equal values hash equal

# mixed with plain int and Base-60 strings
n = Base60Int(10)
n + 5                  # 15
n + "5"                # 15

No sign. Base-60 has no room for a sign position, so a negative result is rejected rather than silently mis-encoded:

Base60Int(1) - Base60Int(5)   # raises ValueError

Pass modulus= for circular arithmetic — every result is normalised into [0, modulus) and the modulus propagates through the whole chain:

x = Base60Int(50, modulus=60)
(x + 30).to_int()             # 20

Functional API

If you only need a one-off operation on strings, base60_int also exposes digit-level helpers that avoid object overhead:

from base60_int import add_b60, mul_b60, divmod_b60

add_b60("10", "10")       # '20'
mul_b60("10", "10")       # '100'
divmod_b60("10", "3")     # ('L', '0')  -- 60 // 3, 60 % 3

The older base60_arithmetic module offers the same style of operations under its original names (base60_add, base60_multiply, ...). Both modules produce identical results; base60_int is the recommended entry point because its multiplication is $\mathcal{O}(n \cdot m)$ without the insert(0, ...) reallocation cost of the original implementation.

import base60_arithmetic as arith

arith.base60_add("10", "10")        # '20'
arith.base60_multiply("10", "10")   # '100'

Periodic fractions

base60_arithmetic.base60_fraction performs long division directly in Base-60 and returns a fractional string, detecting repeating cycles automatically:

from base60_arithmetic import base60_fraction

base60_fraction("1", "3")     # '0.L'      -- 20 + 20 + 20 ... = 0.333...
base60_fraction("1", "2")     # '0.W'      -- 30/60
base60_fraction("1", "4")     # '0.F'      -- 15/60
base60_fraction("1", "5")     # '0.C'      -- 12/60
base60_fraction("1", "6")     # '0.A'      -- 10/60
base60_fraction("1", "60")    # '0.00F'    -- 15/60^3, "60" == 60
base60_fraction("2", "3")     # '0.g'      -- 40 + 20 + 20 ... = 0.666...
base60_fraction("1", "7")     # '0.8aH'    -- cycle 8,a,H (60 == 4 mod 7)

Note the denominator is itself Base-60: base60_fraction("1", "10") divides 1 by 60 (since "10" is 1*60 + 0), not by decimal 10.

Tail handling

Leftover bytes use TAIL_CHARS, derived as C = ceil(CHUNK_BYTES * r / log2(60)):

Remaining bytes (R) 1 2 3 4 5 6 7
Chars (C) 2 3 5 6 7 9 10

The resulting residues modulo CHUNK_CHARS (11) are 0, 2, 3, 5, 6, 7, 9, 10 — all unique, so decode_chunked_auto recovers the tail length from len(text) % 11 without extra metadata. Residues 1, 4 and 8 are invalid and raise LengthError instead of being decoded.

Legacy API

Name Alias of Notes
bytes_to_base60(data) encode Bulk encoder
base60_to_bytes(text, length=) decode Strict by default
base60_to_bytes_lenient(s, length) — Skips bad chars, truncates instead of raising (lossy)

BASE60_DIGITS.py, BASE60_CORE.py, BASE60_BINAR.py and base60.py are retained for backwards compatibility and now delegate to hexa60. The chunked output is no longer the historical 3 bytes -> 5 chars layout, so legacy aliases cannot decode records stored under the old format.


Licensing

HEXA60 is published under a Dual-Licensing Model:

  1. Open-Source (GNU AGPLv3): Free for open-source applications, personal projects, and academic research under the terms of the GNU Affero General Public License v3.0.
  2. Commercial OEM License: Required for commercial entities, closed-source proprietary applications, and SaaS integrations that do not wish to be bound by AGPLv3 copyleft terms.

For commercial licensing enquiries, custom integration support, or OEM agreement details, please contact: mullerladislav20@gmail.com.

Metadata

Release files for hexa60 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hexa60 1.0.0
File Size Uploaded
hexa60-1.0.0.tar.gz 32.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hexa60 1.0.0
File Interpreter ABI Platform
hexa60-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 52.7 kB

Release files / hexa60-1.0.0.tar.gz

Download URL hexa60-1.0.0.tar.gz
Size 32.6 kB
Tags Source
SHA-256 checksum
How to use checksums
8408ef64b19b89ef92cb04cf3b13eaf7158054679d6e2365ae0e4bb278e42129
BLAKE2b-256 checksum
How to use checksums
47b9dc2cc239cd7eb6ab74167f9acf52c56d4c86a924f2f104afbdd202d83e90
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / hexa60-1.0.0-py3-none-any.whl

Download URL hexa60-1.0.0-py3-none-any.whl
Size 20.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
55c443e5b004987db2aa887ee3edb39f7b07047a0b3ba6c1546f4139bb279650
BLAKE2b-256 checksum
How to use checksums
b3f325bc8abd9f3dafc80b63aac230e7fadafa553dd1248963e5668a0a9a5589
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page