/** * Nightly dudect-style timing driver. For every native impl that is not a * `PrimitiveNotImplemented` stub, sample its runtime across fixed vs. random * inputs and flag t-statistics above the threshold as potential leaks. * * In Phase 0 every native is a stub — the driver probes each, catches * `PrimitiveNotImplemented`, skips, and exits clean. The wiring fires the * moment a real native lands. */ import { dudect } from "../universal/crypto/crypto-utils/src/testing/dudect/dudect.ts"; /** Stable code emitted by `PrimitiveNotImplemented`. */ const NOT_IMPLEMENTED_CODE = "CRYPTO.UTILS.PRIMITIVE_NOT_IMPLEMENTED"; const MEASUREMENTS = Number.parseInt( process.env.CRYPTO_TIMING_MEASUREMENTS ?? "20000", 10 ); const T_THRESHOLD = Number.parseFloat( process.env.CRYPTO_TIMING_T_THRESHOLD ?? "4.5" ); function randomBytes(len: number): Uint8Array { const buf = new Uint8Array(len); crypto.getRandomValues(buf); return buf; } async function probeAndRun( id: string, category: "hash", sampleFixed: () => unknown, sampleRandom: () => unknown, pickFn: (mod: Record) => (input: unknown) => unknown ): Promise { const nativePath = `../universal/crypto/crypto-${category}/src/primitives/${id}/impl/${id}.native.ts`; const mod = (await import(nativePath)) as Record; const fn = pickFn(mod); try { fn(sampleFixed()); } catch (err) { if ( typeof err === "object" && err !== null && (err as { code?: unknown }).code === NOT_IMPLEMENTED_CODE ) { console.log( `[timing] ${id}: native is PrimitiveNotImplemented, skipping` ); return; } throw err; } const result = dudect({ fn, sampleFixed, sampleRandom, measurements: MEASUREMENTS, tThreshold: T_THRESHOLD, }); console.log( `[timing] ${id}: t=${result.tStatistic.toFixed(3)} (threshold ${result.tThreshold}) → ${ result.constantTime ? "OK" : "LEAK" }` ); if (!result.constantTime) process.exit(1); } const HASH_ONE_SHOT_IDS = ["sha256", "sha384", "sha512"] as const; async function main(): Promise { for (const id of HASH_ONE_SHOT_IDS) { await probeAndRun( id, "hash", () => new Uint8Array(64), // class A: all zeros, fixed length () => randomBytes(64), // class B: random bytes, same length (mod) => mod[id] as (input: unknown) => unknown ); } console.log("[timing] done"); } if (import.meta.main) { await main(); }