bijector 
Renamed from
inthashin v4. The algorithm and parameter format are unchanged. See Migration frominthash.
bijector is a reversible integer bijection for Javascript and Typescript. It maps an integer space onto itself with
a one-to-one correspondence, so every input has exactly one output and vice versa. Under the hood it uses Knuthβs
multiplicative method over a modular ring (prime, modular inverse, and an xor mask), giving you a fast, lossless,
deterministic encode / decode pair.
Unlike a regular hash (one-way) or a random generator, bijector is mathematically invertible β the inverse is
guaranteed to exist because the transformation is a bijection on [0, 2^bits).
Common applications: auto-increment ID obfuscation, URL shorteners, license keys, coupon codes, A/B bucketing. See Use cases for concrete examples.
Installation
Node.js
npm install bijectorDeno
import { Bijector } from "@denostack/bijector";Usage
Generating parameters
Run the CLI to generate random parameters for your bijector:
# Node.js:
npx bijector
# Deno:
deno run jsr:@denostack/bijector/cli
# Bun
bunx bijector
# Output:
# {
# "bits": 53,
# "prime": "6456111708547433",
# "inverse": "3688000043513561",
# "xor": "969402349590075"
# }Creating and using a Bijector
const bijector = new Bijector({
bits: 53, // Javascript, Number.MAX_SAFE_INTEGER
prime: "6456111708547433", // Random Prime
inverse: "3688000043513561", // Modular Inverse
xor: "969402349590075", // Random n-bit xor mask
});
const encoded = bijector.encode(100); // result: 6432533451586367
const decoded = bijector.decode(encoded); // result: 100
// You can obfuscate predictable numbers like 'Auto Increment'!
bijector.encode(0); // 969402349590075
bijector.encode(1); // 6085136369434450
bijector.encode(2); // 4132187376469225
bijector.encode(3); // 2180123214014976
bijector.encode(Number.MAX_SAFE_INTEGER - 3); // 2024647471942759
bijector.encode(Number.MAX_SAFE_INTEGER - 2); // 6827076040726014
bijector.encode(Number.MAX_SAFE_INTEGER - 1); // 4875011878271765
bijector.encode(Number.MAX_SAFE_INTEGER); // 2922062885306540bijector also supports string and bigint values:
// String input and output
const encoded = bijector.encode("100"); // "6432533451586367"
const decoded = bijector.decode(encoded); // "100"// BigInt input and output
const encoded = bijector.encode(100n); // 6432533451586367n
const decoded = bijector.decode(encoded); // 100nMath-style aliases: forward / inverse
In addition to encode / decode, you can use the mathematical pair forward / inverse. They are exact aliases of
the same methods β pick whichever reads better in context.
bijector.forward(100); // same as bijector.encode(100)
bijector.inverse(bijector.forward(100)); // 100- Use
encode/decodefor codec-style flows (ID β public code). - Use
forward/inversewhen you are thinking about the underlying bijection as a math operation.
Bit width and the number safety boundary
The default bits: 53 covers JavaScriptβs safe integer range (Number.MAX_SAFE_INTEGER === 2^53 - 1). Arbitrary bit
widths are supported β pass -b<n> to the CLI to generate wider parameters:
npx bijector -b32 # 32-bit range
npx bijector -b64 # 64-bit range (bigint / string only)
npx bijector -b128 # 128-bit range (bigint / string only)Input type rules:
bits range |
number input |
bigint input |
string input |
|---|---|---|---|
bits <= 53 |
β safe | β | β |
bits > 53 |
β throws | β | β |
bits > 53 with a number input would silently corrupt values (the encoded output can exceed MAX_SAFE_INTEGER and
get rounded to the nearest float). To prevent data loss, encode / decode throw a TypeError in that case β use
bigint or string instead. See the MySQL bigint(20) example for a 64-bit setup.
Range: all inputs must be in [0, 2^bits - 1] (both ends inclusive). Negative or out-of-range inputs throw
RangeError β the bijection is defined only on that interval.
Use cases
Obfuscate auto-increment IDs
Expose /users/6432533451586367 instead of /users/100. Hides your user base size, prevents ID enumeration, and keeps
the column numeric β no string codes in your database.
// Serializing: internal id β public id
response.publicId = bijector.encode(user.id); // 6432533451586367
// Routing: public id β internal id
app.get("/users/:id", (req, res) => {
const userId = bijector.decode(Number(req.params.id));
return db.users.findById(userId);
});For MySQL bigint(20) (64-bit) columns, generate 64-bit parameters with -b64 and use bigint inputs to cover the
full range:
npx bijector -b64const bijector = new Bijector({
bits: 64,
prime: "16131139598801670337",
inverse: "14287487925114175297",
xor: "8502035541264656686",
});
bijector.encode(12345n); // bigint in, bigint out β safe beyond Number.MAX_SAFE_INTEGERLicense / serial key generation
Turn a sequential license counter into a random-looking key. Customers cannot guess adjacent keys, but your server can always decode back to the issued sequence number for lookup.
const encoded = bijector.encode(nextLicenseNumber);
const key = encoded.toString(36).toUpperCase(); // "1KVXZ9ZPQ8M"
// Verify on redemption
const seq = bijector.decode(parseInt(key, 36));
await db.licenses.findBy({ sequence: seq });URL shorteners
Generate deterministic public short codes from internal row IDs. Because the mapping is a bijection, there are no collisions and you donβt need a separate lookup table for reverse mapping.
// Create: row id β short code
const shortCode = bijector.encode(urlRow.id).toString(36); // "1kvxz9"
// Resolve: short code β row id
const rowId = bijector.decode(parseInt(shortCode, 36));Coupon / voucher codes
Issue non-guessable codes that decode back to the issuing record. You never need to store the code itself β the issued sequence number is recoverable from the code, so validation is a single primary-key lookup.
// Issue (encode campaign + serial into one integer)
const payload = campaignId * 1_000_000 + serial;
const code = bijector.encode(payload).toString(36).toUpperCase();
// Redeem
const decoded = bijector.decode(parseInt(userInput, 36));
const campaign = Math.floor(decoded / 1_000_000);
const issuedSerial = decoded % 1_000_000;A/B bucketing & deterministic shuffles
A bijection over an integer range is exactly a permutation of that range. That makes bijector useful for stable,
reversible assignment β A/B experiment buckets, load-balancing hash rings, or deterministic shuffles without storing a
mapping table.
// Stable bucket assignment. Same user always lands in the same bucket,
// distribution looks uniform, and you can recover the user from the bucket key.
const bucket = Number(bijector.encode(BigInt(userId)) % 100n);Lightweight format-preserving transformation
When you need a reversible integer-in, integer-out transformation but not cryptographic security β for example, anonymizing order numbers in logs or exports while keeping the column type intact. For real security (regulated data, adversarial threat model), use AES-GCM or format-preserving encryption (FF1/FF3) instead.
β οΈ
bijectoris obfuscation, not encryption. Given enough (input, output) pairs an attacker can recover the parameters. Do not use it as a security primitive.
FAQ
How is this different from hashids / sqids?
hashids and sqids encode one or more integers into a string of an alphabet. bijector stays in the integer
domain β input and output are both integers (or their string/bigint representations). That makes it:
- Faster (pure arithmetic, no alphabet lookup)
- Smaller in output range (same bit-width as the input)
- A drop-in for numeric columns without string conversion
If you need short alphanumeric codes, use sqids. If you need a reversible integer β integer mapping, use bijector.
How is this different from a regular hash (MurmurHash / FNV / SHA)?
Those are one-way hash functions: many inputs can collide to the same output, and you cannot recover the input.
bijector is a bijection: every output decodes back to exactly one input.
Is it cryptographically secure?
No. It is deterministic obfuscation designed to hide sequential IDs from casual observers. Do not rely on it against a motivated attacker β use a proper authenticated encryption scheme (AES-GCM, ChaCha20-Poly1305) or format-preserving encryption (FF1/FF3) when you need real security.
Can I use it for anything other than ID obfuscation?
Yes. Any problem that needs a reversible, deterministic permutation over an integer range fits β license keys, URL shortener codes, coupon codes, deterministic shuffles, A/B bucketing.
Migration from inthash
bijector is a direct rename of inthash (v3 and below). The core algorithm,
parameter format, and encoded outputs are 100% compatible β any prime / inverse / xor / bits quadruple
generated by inthash will produce identical results under bijector. You only need to rename the imports and the
class.
Before (inthash v3) |
After (bijector v4) |
|---|---|
npm install inthash |
npm install bijector |
import { Hasher } from "inthash" |
import { Bijector } from "bijector" |
new Hasher(options) |
new Bijector(options) |
HasherOptions |
BijectorOptions |
Hasher.generate() |
Bijector.generate() |
hasher.encode(n) / .decode(n) |
bijector.encode(n) / .decode(n) (unchanged) |
npx inthash |
npx bijector |
The inthash package on npm has been deprecated and will continue to install with a notice directing you here.