Spaces:
Running
Running
File size: 7,869 Bytes
518343a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | /**
* 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);
});
});
|