Crockford Base32 hashing utilities for short, URL-safe identifiers, checksums, and time-sortable prefixes.
Dual package: PHP (northrook/hashes) and TypeScript (@northrook/hashes).
checksum is the low 60 bits of xxHash64 as 12 Crockford Base32 characters over encode(input) bytes (PHP Hash::encode() / TS Hash.encode()).
value / getHash() are different: they truncate xxHash64 of the raw input bytes.
encode() prefixes values with canonical type + length (no Unicode normalization), so checksum is a stable digest of the encoded form, not just the raw UTF-8 bytes.
No Unicode normalization is applied: NFC and NFD forms of the same character produce different digests.
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. For checksum, both sides hash encode(input) bytes; for value, both sides hash the raw input bytes. 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 (seefixtures/README.md)- PHP:
vendor/bin/phpunit(ParityTest,DigestTest, …) - TypeScript:
npm run test:parity(plustest: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.phpuse 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.
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 mapBrowser / 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).