1 Commits

Author SHA1 Message Date
f6e4ac8aeb test(crypto): stop letting the scheduler answer for the code
Some checks failed
Test / test (push) Has been cancelled
Third flake found in the same hunt. `timing variance stays bounded across
mismatch positions` took two single measurements of 50 000 iterations each and
compared their ratio, which is one GC pause away from a false alarm — it failed
about one run in fourteen on a loaded machine.

Two things were wrong with the measurement, not the property:

The first loop through that code pays for JIT compilation the second one does
not, so whichever side ran first was biased upward. The old comment admitted it
— "allow 2x variance for JIT/noise" — which is a tolerance covering a
measurement artefact rather than the thing under test. There is a warm-up now.

And a single pair has no defence against a scheduler hiccup. Five interleaved
pairs compared by median throw the pause out instead of the property.

The threshold is unchanged at 3, deliberately. An early-exit compare takes
~256x longer to reach a mismatch in the last byte than the first, on every
round; nothing here weakens what the test catches.

Verified that it still catches what it is for: with `constantTimeEqual`
temporarily replaced by an early-exit loop the test fails, and passes again
with the real one restored. A security test nobody has watched fail is a
security test nobody knows works.

A security test that cries wolf under load is worse than none — it teaches
people to re-run until green, and then a real regression looks like the noise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014489bKUtUEY1Zgs9xN9mt7
2026-09-08 16:42:37 +02:00

View File

@@ -59,26 +59,40 @@ describe('Cryptographic Hardening', () => {
const mismatchAtEnd = new Uint8Array(target);
mismatchAtEnd[len - 1] ^= 0xff;
// Measure many iterations to get a stable signal
const iterations = 50000;
const iterations = 20000;
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();
for (let i = 0; i < iterations; i++) {
crypto.constantTimeEqual(target, mismatchAtStart);
// Warm up before measuring anything. The first loop through this code
// pays for JIT compilation that the second one does not, which biased
// 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();
for (let i = 0; i < iterations; i++) {
crypto.constantTimeEqual(target, mismatchAtEnd);
}
const timeEnd = performance.now() - start2;
// 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);
// The threshold is unchanged and deliberately so: an early-exit compare
// would take ~256x longer to reach a mismatch in the last byte than the
// first, on every single round. Nothing about sampling weakens what this
// catches — it only stops the scheduler from answering for the code.
expect(median).toBeLessThan(3);
});
});