a11oy / test /gates /scitt_mask_entropy.test.ts
betterwithage's picture
sync(space): complete build context — fix BUILD_ERROR (CTO)
518343a verified
Raw
History Blame
7.87 kB
/**
* scitt_mask_entropy.test.ts
*
* Vitest tests for the Lutar.DPI.SCITT.SCITTMaskEntropy gate.
*
* Tests:
* 1. 1000-input random entropy bound test: H(mask(X)) ≤ H(X)
* 2. Hash preservation edge cases
* 3. Full-mask, empty-mask, partial-mask
* 4. Receipt emission with mock signer
*
* Lean commit: c4d13795689601324fce0236351bfe0ade990a43
*/
import { describe, it, expect } from "vitest";
import {
applyMask,
shannonEntropy,
maskedEntropy,
verifySCITTMaskEntropyBound,
verifyMaskRefinementMono,
verifyHashPreservation,
emitSCITTMaskEntropyReceipt,
scittMaskEntropyGate,
type SCITTStatement,
type MaskSpec,
type StmtDist,
type Signer,
} from "../../src/gates/scitt_mask_entropy";
// ---------------------------------------------------------------------------
// Seeded LCG
// ---------------------------------------------------------------------------
function seedRandom(seed: number) {
let s = seed >>> 0;
return () => {
s = (Math.imul(1664525, s) + 1013904223) >>> 0;
return s / 0x100000000;
};
}
const mockSigner: Signer = (p: string) =>
`mock-sig::${Buffer.from(p).slice(0, 16).toString("hex")}`;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeUniformDist(K: number): StmtDist {
const statements: SCITTStatement[] = Array.from({ length: K }, (_, i) => ({
fields: [i, i + 1, i + 2],
hash: `hash-${i}`,
}));
const probs = Array(K).fill(1 / K);
return { statements, probs };
}
function makeRandomDist(K: number, rand: () => number): StmtDist {
// Generate unnormalised weights then normalise
const weights = Array.from({ length: K }, () => rand());
const total = weights.reduce((a, b) => a + b, 0);
const probs = weights.map((w) => w / total);
const statements: SCITTStatement[] = Array.from({ length: K }, (_, i) => ({
fields: [i, i * 2],
hash: `h-${i}`,
}));
return { statements, probs };
}
// ---------------------------------------------------------------------------
// 1. 1000-input random entropy bound test
// ---------------------------------------------------------------------------
describe("scitt_mask_entropy: 1000-input random entropy bound", () => {
it("H(mask(X)) ≤ H(X) for 1000 random distributions and masks", () => {
const rand = seedRandom(0x98765432);
let violations = 0;
for (let i = 0; i < 1000; i++) {
const K = Math.floor(rand() * 8) + 2; // K in [2,9]
const nFields = Math.floor(rand() * 4) + 1; // nFields in [1,4]
const dist = makeRandomDist(K, rand);
const mask: MaskSpec = {
redacted: Array.from({ length: nFields }, () => rand() > 0.5),
};
if (!verifySCITTMaskEntropyBound(mask, dist)) violations++;
}
expect(violations).toBe(0);
});
it("mask refinement monotonicity holds for 1000 random pairs", () => {
const rand = seedRandom(0x12345678);
let violations = 0;
for (let i = 0; i < 1000; i++) {
const K = Math.floor(rand() * 6) + 2;
const nFields = Math.floor(rand() * 4) + 1;
const dist = makeRandomDist(K, rand);
// mask1: random redaction
const r1 = Array.from({ length: nFields }, () => rand() > 0.5);
// mask2: superset — any field redacted in mask1 is also redacted in mask2
const r2 = r1.map((b) => b || rand() > 0.7);
const mask1: MaskSpec = { redacted: r1 };
const mask2: MaskSpec = { redacted: r2 };
if (!verifyMaskRefinementMono(mask1, mask2, dist)) violations++;
}
expect(violations).toBe(0);
});
});
// ---------------------------------------------------------------------------
// 2. Hash preservation
// ---------------------------------------------------------------------------
describe("scitt_mask_entropy: hash preservation (scitt_mask_preserves_hash)", () => {
it("applyMask preserves hash for all statements", () => {
const rand = seedRandom(0xaabbccdD);
for (let i = 0; i < 100; i++) {
const nFields = Math.floor(rand() * 5) + 1;
const stmt: SCITTStatement = {
fields: Array.from({ length: nFields }, () => Math.floor(rand() * 10)),
hash: `hash-${i}-${Math.floor(rand() * 100000)}`,
};
const mask: MaskSpec = {
redacted: Array.from({ length: nFields }, () => rand() > 0.5),
};
const masked = applyMask(mask, stmt);
expect(masked.hash).toBe(stmt.hash);
}
});
it("verifyHashPreservation returns true for all statements in a dist", () => {
const dist = makeUniformDist(5);
const mask: MaskSpec = { redacted: [true, false, true] };
expect(verifyHashPreservation(mask, dist.statements)).toBe(true);
});
});
// ---------------------------------------------------------------------------
// 3. Full-mask, empty-mask, partial-mask
// ---------------------------------------------------------------------------
describe("scitt_mask_entropy: mask variants", () => {
const dist4 = makeUniformDist(4);
const fullMask: MaskSpec = { redacted: [true, true, true] };
const emptyMask: MaskSpec = { redacted: [false, false, false] };
const partialMask: MaskSpec = { redacted: [true, false, false] };
it("full mask entropy bound holds", () => {
expect(verifySCITTMaskEntropyBound(fullMask, dist4)).toBe(true);
});
it("empty mask entropy equals original", () => {
const hOrig = shannonEntropy(dist4.probs);
const hMasked = maskedEntropy(emptyMask, dist4);
expect(Math.abs(hMasked - hOrig)).toBeLessThan(1e-10);
});
it("partial mask entropy bound holds", () => {
expect(verifySCITTMaskEntropyBound(partialMask, dist4)).toBe(true);
});
it("applyMask zeroes redacted fields", () => {
const stmt: SCITTStatement = { fields: [3, 7, 11], hash: "h0" };
const masked = applyMask(fullMask, stmt);
expect(masked.fields).toEqual([0, 0, 0]);
expect(masked.hash).toBe("h0");
});
it("applyMask leaves non-redacted fields unchanged", () => {
const stmt: SCITTStatement = { fields: [3, 7, 11], hash: "h1" };
const masked = applyMask(emptyMask, stmt);
expect(masked.fields).toEqual([3, 7, 11]);
});
it("shannonEntropy of uniform 4-dist ≈ 2 bits", () => {
const h = shannonEntropy(dist4.probs);
expect(h).toBeCloseTo(2.0, 5);
});
it("shannonEntropy of [1] = 0 (certain distribution)", () => {
expect(shannonEntropy([1])).toBeCloseTo(0, 10);
});
});
// ---------------------------------------------------------------------------
// 4. Receipt emission
// ---------------------------------------------------------------------------
describe("scitt_mask_entropy: DSSE receipt emission", () => {
const dist = makeUniformDist(4);
const mask: MaskSpec = { redacted: [false, true, false] };
it("emits receipt with correct theorem and commit SHA", () => {
const receipt = emitSCITTMaskEntropyReceipt(mask, dist, mockSigner);
expect(receipt.theorem).toBe("Lutar.DPI.SCITT.scitt_mask_entropy_bound");
expect(receipt.lean_commit_sha).toBe("c4d13795689601324fce0236351bfe0ade990a43");
expect(receipt.output).toBe(true);
expect(receipt.inputs_hash).toMatch(/^[0-9a-f]{64}$/);
expect(receipt.sig).toContain("mock-sig::");
});
it("gate returns entropyBoundHolds=true and entropies", () => {
const result = scittMaskEntropyGate(mask, dist, mockSigner);
expect(result.entropyBoundHolds).toBe(true);
expect(result.originalEntropy).toBeCloseTo(2.0, 5);
expect(result.maskedEntropy).toBeCloseTo(2.0, 5);
expect(result.receipt.output).toBe(true);
});
it("inputs_hash is deterministic", () => {
const r1 = emitSCITTMaskEntropyReceipt(mask, dist, mockSigner);
const r2 = emitSCITTMaskEntropyReceipt(mask, dist, mockSigner);
expect(r1.inputs_hash).toBe(r2.inputs_hash);
});
});