Initial Commit
This commit is contained in:
87
scripts/crypto-timing.ts
Normal file
87
scripts/crypto-timing.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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<string, unknown>) => (input: unknown) => unknown
|
||||
): Promise<void> {
|
||||
const nativePath = `../universal/crypto/crypto-${category}/src/primitives/${id}/impl/${id}.native.ts`;
|
||||
const mod = (await import(nativePath)) as Record<string, unknown>;
|
||||
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<void> {
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user