text-watermark-microscope / tests /unit /hashing.test.ts
roomnumber103's picture
Add LLM Text Watermark Microscope
c126239 verified
Raw
History Blame Contribute Delete
3.07 kB
import { describe, it, expect } from 'vitest';
import {
HASH_KEY,
kirchenbauerSeed,
isGreenToken,
greenUniform,
textsealR,
keyFromString,
ksemstampSeed,
} from '../../src/lib/utils/hashing';
describe('kirchenbauerSeed', () => {
it('matches the reference formula salt * prev mod 2^64-1', () => {
expect(kirchenbauerSeed(1)).toBe(15485863n);
expect(kirchenbauerSeed(1000)).toBe(15485863000n);
const big = 2n ** 64n - 1n;
expect(kirchenbauerSeed(123456)).toBe((HASH_KEY * 123456n) % big);
});
});
describe('green partition', () => {
it('is deterministic', () => {
const seed = kirchenbauerSeed(777);
for (let v = 0; v < 50; v++) {
expect(isGreenToken(seed, v, 0.25)).toBe(isGreenToken(seed, v, 0.25));
}
});
it('has approximately gamma green fraction over the vocab', () => {
const seed = kirchenbauerSeed(42);
const V = 50000;
const gamma = 0.25;
let green = 0;
for (let v = 0; v < V; v++) if (isGreenToken(seed, v, gamma)) green++;
const frac = green / V;
// Binomial std ~ sqrt(g(1-g)/V) ~ 0.0019; allow 5 sigma
expect(Math.abs(frac - gamma)).toBeLessThan(0.01);
});
it('partitions differ across seeds (context-dependence)', () => {
const s1 = kirchenbauerSeed(10);
const s2 = kirchenbauerSeed(11);
let diff = 0;
for (let v = 0; v < 2000; v++) {
if (isGreenToken(s1, v, 0.25) !== isGreenToken(s2, v, 0.25)) diff++;
}
expect(diff).toBeGreaterThan(200);
});
it('greenUniform in [0,1)', () => {
const seed = kirchenbauerSeed(5);
for (let v = 0; v < 100; v++) {
const u = greenUniform(seed, v);
expect(u).toBeGreaterThanOrEqual(0);
expect(u).toBeLessThan(1);
}
});
});
describe('textsealR', () => {
const key = keyFromString('secret');
it('is deterministic per (key, context, token)', () => {
expect(textsealR(key, [1, 2, 3], 42)).toBe(textsealR(key, [1, 2, 3], 42));
});
it('changes with context', () => {
expect(textsealR(key, [1, 2, 3], 42)).not.toBe(textsealR(key, [1, 2, 4], 42));
});
it('changes with key', () => {
const key2 = keyFromString('other');
expect(textsealR(key, [1, 2, 3], 42)).not.toBe(textsealR(key2, [1, 2, 3], 42));
});
it('is uniform-ish: mean near 0.5', () => {
let sum = 0;
const n = 20000;
for (let v = 0; v < n; v++) sum += textsealR(key, [9, 8, 7], v);
expect(Math.abs(sum / n - 0.5)).toBeLessThan(0.01);
});
it('stays strictly inside (0,1)', () => {
for (let v = 0; v < 1000; v++) {
const r = textsealR(key, [], v);
expect(r).toBeGreaterThan(0);
expect(r).toBeLessThan(1);
}
});
});
describe('keyFromString / ksemstampSeed', () => {
it('derives stable distinct keys', () => {
expect(keyFromString('a')).toBe(keyFromString('a'));
expect(keyFromString('a')).not.toBe(keyFromString('b'));
});
it('ksemstamp seed differs per cluster and avoids zero for cluster 0', () => {
expect(ksemstampSeed(0)).not.toBe(0n);
expect(ksemstampSeed(0)).not.toBe(ksemstampSeed(1));
});
});