ltmarx / core /keygen.ts
harelcain's picture
Upload 37 files
f2f99a3 verified
/**
* Key derivation and PRNG for watermark embedding
*
* Uses a simple but effective approach: derive deterministic
* pseudo-random sequences from a secret key using a seeded PRNG.
* For browser compatibility, we use a pure-JS implementation.
*/
/**
* Simple hash function (djb2 variant) for string to 32-bit seed
*/
function hashString(str: string): number {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
}
return hash >>> 0;
}
/**
* Mix two 32-bit values (MurmurHash3 finalizer)
*/
function mix(h: number): number {
h = ((h >>> 16) ^ h) * 0x45d9f3b | 0;
h = ((h >>> 16) ^ h) * 0x45d9f3b | 0;
h = (h >>> 16) ^ h;
return h >>> 0;
}
/**
* Seeded PRNG (xorshift128)
* Produces deterministic sequences from a seed
*/
export class SeededPRNG {
private s0: number;
private s1: number;
private s2: number;
private s3: number;
constructor(seed: number) {
// Initialize state from seed using splitmix32
this.s0 = seed | 0;
this.s1 = mix(seed + 1);
this.s2 = mix(seed + 2);
this.s3 = mix(seed + 3);
// Warm up
for (let i = 0; i < 20; i++) this.next();
}
/** Get next random 32-bit unsigned integer */
next(): number {
let t = this.s3;
const s = this.s0;
this.s3 = this.s2;
this.s2 = this.s1;
this.s1 = s;
t ^= t << 11;
t ^= t >>> 8;
this.s0 = t ^ s ^ (s >>> 19);
return this.s0 >>> 0;
}
/** Get random float in [0, 1) */
nextFloat(): number {
return this.next() / 0x100000000;
}
/** Get random float in [min, max) */
nextRange(min: number, max: number): number {
return min + this.nextFloat() * (max - min);
}
/** Get random integer in [0, max) */
nextInt(max: number): number {
return (this.next() % max) >>> 0;
}
}
/**
* Derive a seed from key + purpose string
*/
export function deriveSeed(key: string, purpose: string): number {
const combined = key + ':' + purpose;
return mix(hashString(combined));
}
/**
* Generate dither values for DM-QIM embedding
* Returns array of dither values in [-delta/2, +delta/2]
*/
export function generateDithers(key: string, count: number, delta: number): Float64Array {
const prng = new SeededPRNG(deriveSeed(key, 'dither'));
const dithers = new Float64Array(count);
for (let i = 0; i < count; i++) {
dithers[i] = prng.nextRange(-delta / 2, delta / 2);
}
return dithers;
}
/**
* Generate a permutation (Fisher-Yates) for bit interleaving
*/
export function generatePermutation(key: string, length: number): Uint32Array {
const prng = new SeededPRNG(deriveSeed(key, 'permutation'));
const perm = new Uint32Array(length);
for (let i = 0; i < length; i++) perm[i] = i;
for (let i = length - 1; i > 0; i--) {
const j = prng.nextInt(i + 1);
const tmp = perm[i];
perm[i] = perm[j];
perm[j] = tmp;
}
return perm;
}
/**
* Generate inverse permutation
*/
export function generateInversePermutation(key: string, length: number): Uint32Array {
const perm = generatePermutation(key, length);
const inv = new Uint32Array(length);
for (let i = 0; i < length; i++) {
inv[perm[i]] = i;
}
return inv;
}