Spaces:
Running
Running
| /** | |
| * 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); | |
| }); | |
| }); | |