baseh

Ruby port of the baseH (Human Reference Code) codec. Encodes integer IDs as fixed-length, checksummed, human-friendly reference codes with an opt-in reversible feistel-v1 permutation and profanity safety. The normative spec is spec/IMPLEMENTATION_CODEC.md in the monorepo root.

Install

# Gemfile
gem "baseh", path: "ruby"

or

gem build baseh.gemspec
gem install ./baseh-1.0.0.gem

Zero runtime dependencies. Only openssl and json from the standard library are used.

Frozen tiers

Four frozen tiers ship with the gem, built from the full alphanumeric set with cumulative visual and spoken strips. All four encode 6 body symbols, are case-insensitive and run the default profanity blocklist.

Tier Helper Body symbols Checksum Format Capacity
Minimum Baseh.baseh_minimum_v1 36 none XXX-XXX 2,176,782,336
Light Baseh.baseh_light_v1 31 1 plain 887,503,681
Medium Baseh.baseh_medium_v1 28 1 plain 481,890,304
Heavy Baseh.baseh_heavy_v1 26 1 plain 308,915,776

Medium is the default. Minimum keeps the full alphabet and uses a hyphen delimiter; the rest have no separator. Each tier keeps the typed O/I/L aliases where possible and adds spoken-confusion aliases for the stripped symbols.

Every helper returns a freshly built mutable profile hash on each call, so callers can load a default and modify it before constructing a codec.

Usage

require "baseh"

codec = Baseh::Baseh.new(Baseh.baseh_medium_v1)

code = codec.encode(id: 123_456)           # => raw fixed-width code

result = codec.decode(code)
result.id                                  # => 123456
result.canonical_code                      # => canonical form
result.corrected                           # => true when input needed correction

codec.capacity                             # => 481890304

check = codec.validate("0000000")
check.valid                                # => false
check.reason                               # => "INVALID_CHECKSUM"

# Spoken-confusion correction
result = codec.decode("TB14QDF", try_correction: true, confusion_profile: :light)

Permutation (opt-in)

The -p variants opt a tier into the reversible feistel-v1 permutation. key_bytes: is required; keep the key in a secret manager and never change it for a live profile:

profile = Baseh.baseh_medium_p_v1(
  key_bytes: File.binread("path/to/key.bin"),
  key_id: "prod-01"                        # optional, defaults to "default"
)
codec = Baseh::Baseh.new(profile)

rounds: is also accepted (default 8). The -p profile is identical to its plain tier apart from the permutation; its profile id gains a -p segment, for example baseh-medium-p-v1.

Profanity safety (spec 18)

Profiles accept an optional profanity: object:

# mode "no-vowels": vowels are stripped from both alphabets at construction
# and can never appear in issued codes.
profanity: { mode: "no-vowels" }

# mode "blocklist": encode raises BLOCKED_CODE when the raw code contains an
# entry. words replaces the default list, extra_words appends to it.
profanity: { mode: "blocklist", words: ["ZZZZ"], extra_words: ["QQQQ"] }

The frozen tiers run the default blocklist out of the box.

All failures raise Baseh::BasehError with a #code from the spec: INVALID_PROFILE, OUT_OF_RANGE, PERMUTATION_FAILURE, INVALID_LENGTH, INVALID_CHARACTER, INVALID_CHECKSUM, AMBIGUOUS_INPUT, TOO_MANY_CANDIDATES and BLOCKED_CODE. validate never raises on user input.

Ruby Integer is arbitrary precision, so every capacity and ID operation is exact at any size.

Tests

rake            # or: ruby -Ilib -Itest -e 'Dir["test/test_*.rb"].each { |f| require "./#{f}" }'
SLOW=1 rake     # includes the 10k sequential round trip and bijection checks

The vector tests load ../vectors/vectors.json and ../vectors/feistel-vectors.json from the monorepo root and assert every entry. Running the suite from a different directory layout requires those files at that relative path.