Compare commits
1 Commits
baddf5f56e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f6e4ac8aeb |
@@ -59,26 +59,40 @@ describe('Cryptographic Hardening', () => {
|
|||||||
const mismatchAtEnd = new Uint8Array(target);
|
const mismatchAtEnd = new Uint8Array(target);
|
||||||
mismatchAtEnd[len - 1] ^= 0xff;
|
mismatchAtEnd[len - 1] ^= 0xff;
|
||||||
|
|
||||||
// Measure many iterations to get a stable signal
|
const iterations = 20000;
|
||||||
const iterations = 50000;
|
const measure = (other: Uint8Array): number => {
|
||||||
|
const t0 = performance.now();
|
||||||
|
for (let i = 0; i < iterations; i++) crypto.constantTimeEqual(target, other);
|
||||||
|
return performance.now() - t0;
|
||||||
|
};
|
||||||
|
|
||||||
const start1 = performance.now();
|
// Warm up before measuring anything. The first loop through this code
|
||||||
for (let i = 0; i < iterations; i++) {
|
// pays for JIT compilation that the second one does not, which biased
|
||||||
crypto.constantTimeEqual(target, mismatchAtStart);
|
// whichever side ran first and is why the old version had to "allow 2x
|
||||||
|
// for JIT/noise" — a tolerance covering a measurement artefact rather
|
||||||
|
// than the property under test.
|
||||||
|
measure(mismatchAtStart);
|
||||||
|
measure(mismatchAtEnd);
|
||||||
|
|
||||||
|
// Paired, interleaved samples, compared by median. A single pair is one
|
||||||
|
// GC pause away from a false alarm: this test failed roughly one run in
|
||||||
|
// fourteen on a loaded machine on 08.09.2026, and a security test that
|
||||||
|
// cries wolf under load is a security test people learn to re-run until
|
||||||
|
// it passes. The median throws out the pause instead of the property.
|
||||||
|
const ratios: number[] = [];
|
||||||
|
for (let round = 0; round < 5; round++) {
|
||||||
|
const a = measure(mismatchAtStart);
|
||||||
|
const b = measure(mismatchAtEnd);
|
||||||
|
ratios.push(Math.max(a, b) / Math.min(a, b));
|
||||||
}
|
}
|
||||||
const timeStart = performance.now() - start1;
|
ratios.sort((x, y) => x - y);
|
||||||
|
const median = ratios[Math.floor(ratios.length / 2)]!;
|
||||||
|
|
||||||
const start2 = performance.now();
|
// The threshold is unchanged and deliberately so: an early-exit compare
|
||||||
for (let i = 0; i < iterations; i++) {
|
// would take ~256x longer to reach a mismatch in the last byte than the
|
||||||
crypto.constantTimeEqual(target, mismatchAtEnd);
|
// first, on every single round. Nothing about sampling weakens what this
|
||||||
}
|
// catches — it only stops the scheduler from answering for the code.
|
||||||
const timeEnd = performance.now() - start2;
|
expect(median).toBeLessThan(3);
|
||||||
|
|
||||||
// With constant-time comparison, these should be very close.
|
|
||||||
// Non-constant-time would show timeEnd >> timeStart (early exit vs full scan).
|
|
||||||
// Allow 2x variance for JIT/noise, but it should never be 10x.
|
|
||||||
const ratio = Math.max(timeStart, timeEnd) / Math.min(timeStart, timeEnd);
|
|
||||||
expect(ratio).toBeLessThan(3);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user