northrook/hashes

Crockford Base32 hashing utilities for short, URL-safe identifiers, checksums, and time-sortable prefixes

Maintainers

Package info

github.com/northrook/hashes

pkg:composer/northrook/hashes

Transparency log

Statistics

Installs: 5

Dependents: 2

Suggesters: 0

Stars: 0

dev-main 2026-08-12 05:41 UTC

This package is auto-updated.

Last update: 2026-08-12 05:44:11 UTC


README

Crockford Base32 hashing utilities for short, URL-safe identifiers, checksums, and time-sortable prefixes.

Dual package: PHP (northrook/hashes) and TypeScript (@northrook/hashes).

Checksums

checksum is the low 60 bits of xxHash64 as 12 Crockford Base32 characters — same digest bytes as value / native hash('xxh64', ...).

Strings are hashed as UTF-8 bytes. PHP strings are used as-is (UTF-8 for text; arbitrary bytes for binary). TypeScript encodes strings with TextEncoder; use Uint8Array for raw bytes.

No Unicode normalization is applied: NFC and NFD forms of the same character produce different digests.

Parity

Guaranteed cross-language parity is only for digests of shared input: PHP checksum / value (and native hash('xxh32'|'xxh64', …) in fixtures) ↔ TypeScript getChecksum / getHash / getXXH32 / getXXH64. Same UTF-8/bytes in, same Crockford or hex out. Locked by shared fixtures; not the TypeScript streaming XXHash32 / XXHash64 classes (PHP has no streaming hasher API — use hash()).

crypto / fast / time / ulid match style and API (Crockford alphabet, length bounds, method shapes, seed conventions) — not bit-identical outputs. RNG and clocks differ; JS also cannot mirror PHP’s int-vs-float time seed split exactly.

  • fixtures/digest-parity.json — golden vectors (see fixtures/README.md)
  • PHP: vendor/bin/phpunit (ParityTest, DigestTest, …)
  • TypeScript: npm run test:parity (plus test:xxhash / test:time / test:fast / test:crypto / test:ulid)

Regenerate goldens from PHP’s native xxHash after intentional digest-format changes:

php bin/generate-digest-parity.php

PHP

use Northrook\Hash;

Hash::checksum('hello');   // 12-char Crockford (xxh64)
Hash::value('hello', 6);   // truncated Crockford
Hash::time();              // time-sortable prefix (int ms / float seconds)
Hash::ulid();              // time(10) + crypto(16) — 26-char ULID shape
Hash::fast();              // mt_rand Crockford (best-effort session uniqueness)
Hash::reset();             // clear fast() session map
Hash::crypto();            // CSPRNG Crockford

hash('xxh64', 'hello');    // raw hex — use native hash(), not Hash::
hash('xxh32', 'hello');

Requires ext-hash and ext-random, PHP >= 8.5.

fast uniqueness is best-effort (in-memory map + short retry budget), not absolute — tiny lengths can still warn-and-return a duplicate so runtime cannot loop. Call reset / resetFastHash from long-lived processes when the map should not grow unbounded.

TypeScript

Free functions plus a Hash facade mirroring the PHP method names:

import {getChecksum, Hash} from "@northrook/hashes";

getChecksum("hello");
Hash.checksum("hello");
Hash.xxh64("hello");
Hash.value("hello", 6);
Hash.time();               // integer seed = ms; fractional = seconds
Hash.ulid();               // time(10) + crypto(16) — 26-char ULID shape
Hash.fast();               // Math.random Crockford (best-effort session uniqueness)
Hash.reset();              // clear getFastHash session map

Browser / Electron oriented. Digests accept string (UTF-8) or Uint8Array.

XXHash32 / XXHash64 are TypeScript-only streaming hashers (used under getXXH32 / getXXH64). Prefer Uint8Array chunks when streaming: each string chunk is UTF-8-encoded independently, so splitting a surrogate pair across updates is not byte-equivalent to hashing the full string.

time / getTimeHash seed: integer → milliseconds; non-integer → seconds (floor(seed * 1000)), approximating PHP’s int/float split (style parity only — see Parity).