pingql/apps/shared/feistel.ts

46 lines
1.3 KiB
TypeScript

const SECRET = process.env.FEISTEL_SECRET || "change-me";
const ROUNDS = 8;
const HALF = 20;
const MASK = (1 << HALF) - 1;
const CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
function roundFn(right: number, round: number): number {
const h = new Bun.CryptoHasher("sha256").update(SECRET + ":" + round + ":" + right).digest();
return (h[0] << 12 | h[1] << 4 | h[2] >> 4) & MASK;
}
function toBase62(n: number): string {
let s = "";
for (let i = 0; i < 7; i++) { s = CHARS[n % 62] + s; n = Math.floor(n / 62); }
return s;
}
function fromBase62(s: string): number {
let n = 0;
for (const c of s) n = n * 62 + CHARS.indexOf(c);
return n;
}
export function encodeId(val: number): string {
let left = Math.floor(val / (MASK + 1)) & MASK;
let right = val & MASK;
for (let i = 0; i < ROUNDS; i++) {
const tmp = (left ^ roundFn(right, i)) & MASK;
left = right;
right = tmp;
}
return toBase62(left * (MASK + 1) + right);
}
export function decodeId(s: string): number {
const val = fromBase62(s);
let left = Math.floor(val / (MASK + 1)) & MASK;
let right = val & MASK;
for (let i = ROUNDS - 1; i >= 0; i--) {
const tmp = (right ^ roundFn(left, i)) & MASK;
right = left;
left = tmp;
}
return left * (MASK + 1) + right;
}