@ac-bench/measure-constant-time 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +31 -0
- package/dist/_collect.d.ts +50 -0
- package/dist/_collect.d.ts.map +1 -0
- package/dist/_collect.js +62 -0
- package/dist/_collect.js.map +1 -0
- package/dist/_dudect.d.ts +55 -0
- package/dist/_dudect.d.ts.map +1 -0
- package/dist/_dudect.js +48 -0
- package/dist/_dudect.js.map +1 -0
- package/dist/_run-case.d.ts +17 -0
- package/dist/_run-case.d.ts.map +1 -0
- package/dist/_run-case.js +62 -0
- package/dist/_run-case.js.map +1 -0
- package/dist/_statistics.d.ts +8 -0
- package/dist/_statistics.d.ts.map +1 -0
- package/dist/_statistics.js +2 -0
- package/dist/_statistics.js.map +1 -0
- package/dist/_types.d.ts +43 -0
- package/dist/_types.d.ts.map +1 -0
- package/dist/_types.js +20 -0
- package/dist/_types.js.map +1 -0
- package/dist/_warnings.d.ts +24 -0
- package/dist/_warnings.d.ts.map +1 -0
- package/dist/_warnings.js +35 -0
- package/dist/_warnings.js.map +1 -0
- package/dist/decl.d.ts +7 -0
- package/dist/decl.d.ts.map +1 -0
- package/dist/decl.js +22 -0
- package/dist/decl.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +14 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +2 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +105 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Anthony Champagne and contributors
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @ac-bench/measure-constant-time
|
|
2
|
+
|
|
3
|
+
Detects whether an operation's runtime depends on its input — the timing leak
|
|
4
|
+
that lets an attacker recover a secret without ever guessing it whole.
|
|
5
|
+
|
|
6
|
+
This is the dudect approach: run two closures under a randomly interleaved
|
|
7
|
+
schedule, crop the distribution tails to discard scheduler noise, and report
|
|
8
|
+
Welch's t-statistic. A large statistic means the two inputs are
|
|
9
|
+
distinguishable by timing alone.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { constantTimeCase, constantTimeCondition } from "@ac-bench/measure-constant-time";
|
|
13
|
+
|
|
14
|
+
constantTimeCondition("tag comparison", () => {
|
|
15
|
+
constantTimeCase(
|
|
16
|
+
"constantTimeBytesIsEqual",
|
|
17
|
+
() => compare(correctTag, correctTag),
|
|
18
|
+
() => compare(correctTag, wrongFirstByte),
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The two closures are the classes being told apart. A large statistic means an
|
|
24
|
+
attacker could distinguish them by timing alone.
|
|
25
|
+
|
|
26
|
+
A passing result is evidence, not proof: it shows no leak was detected at this
|
|
27
|
+
sample size, on this machine, against this compiler's output.
|
|
28
|
+
|
|
29
|
+
Host-bound for `hrtime.bigint()`'s nanosecond resolution.
|
|
30
|
+
|
|
31
|
+
Run suites with the `measure` CLI from `@ac-bench/cli`.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { MaybeAsyncCallableNoArgs, RandomFn } from "@ac-kit/core";
|
|
2
|
+
/** Two classes of raw, per-execution timing samples, in nanoseconds. */
|
|
3
|
+
export interface InterleavedTimings {
|
|
4
|
+
classA: number[];
|
|
5
|
+
classB: number[];
|
|
6
|
+
}
|
|
7
|
+
/** Options for {@link collectInterleavedTimings}. */
|
|
8
|
+
export type CollectOptions = {
|
|
9
|
+
/** Executions of each closure to measure. Default `5000`. */
|
|
10
|
+
samples?: number;
|
|
11
|
+
/** Unmeasured executions of each closure before timing starts. Default `200`. */
|
|
12
|
+
warmup?: number;
|
|
13
|
+
/**
|
|
14
|
+
* High-resolution clock, in nanoseconds. A real run passes `hrtime.bigint`; a
|
|
15
|
+
* test injects a scripted clock instead of timing the host it runs on.
|
|
16
|
+
*/
|
|
17
|
+
now: () => bigint;
|
|
18
|
+
/**
|
|
19
|
+
* Source of randomness for the shuffle that decides execution order. Fixed by
|
|
20
|
+
* the caller's choice of seed, so a run that finds no leak is reproducible
|
|
21
|
+
* rather than "passed this time".
|
|
22
|
+
*/
|
|
23
|
+
random: RandomFn;
|
|
24
|
+
signal?: AbortSignal | null;
|
|
25
|
+
beforeMeasure?: MaybeAsyncCallableNoArgs;
|
|
26
|
+
afterMeasure?: MaybeAsyncCallableNoArgs;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Times `classA` and `classB` in a randomly interleaved order and returns the
|
|
30
|
+
* raw per-execution durations for each, in nanoseconds.
|
|
31
|
+
*
|
|
32
|
+
* Randomising the order — rather than running all of `classA` and then all of
|
|
33
|
+
* `classB` — is the difference between this and an ordinary benchmark run: a
|
|
34
|
+
* systematic drift over the run (CPU frequency scaling, thermal throttling,
|
|
35
|
+
* background load) would otherwise land almost entirely on whichever class ran
|
|
36
|
+
* second, and get mistaken for the class itself being slower.
|
|
37
|
+
*
|
|
38
|
+
* Both closures must be synchronous. An `await` inside the timed region would
|
|
39
|
+
* add microtask-queue scheduling jitter on the order of microseconds, which
|
|
40
|
+
* swamps the nanosecond-to-low-microsecond differences this is built to
|
|
41
|
+
* detect.
|
|
42
|
+
*
|
|
43
|
+
* @param classA First closure to time.
|
|
44
|
+
* @param classB Second closure to time.
|
|
45
|
+
* @param options Sampling policy, clock and randomness.
|
|
46
|
+
* @returns The two classes' raw timings, in the order they were collected
|
|
47
|
+
* within each class (not the randomised execution order).
|
|
48
|
+
*/
|
|
49
|
+
export declare function collectInterleavedTimings(classA: () => void, classB: () => void, options: CollectOptions): Promise<InterleavedTimings>;
|
|
50
|
+
//# sourceMappingURL=_collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_collect.d.ts","sourceRoot":"","sources":["../src/_collect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAElE,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,MAAM,cAAc,GAAG;IAC5B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,GAAG,EAAE,MAAM,MAAM,CAAC;IAElB;;;;OAIG;IACH,MAAM,EAAE,QAAQ,CAAC;IAEjB,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAE5B,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,YAAY,CAAC,EAAE,wBAAwB,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,yBAAyB,CAC9C,MAAM,EAAE,MAAM,IAAI,EAClB,MAAM,EAAE,MAAM,IAAI,EAClB,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,kBAAkB,CAAC,CA8C7B"}
|
package/dist/_collect.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { shuffle } from "@ac-kit/algo";
|
|
2
|
+
/**
|
|
3
|
+
* Times `classA` and `classB` in a randomly interleaved order and returns the
|
|
4
|
+
* raw per-execution durations for each, in nanoseconds.
|
|
5
|
+
*
|
|
6
|
+
* Randomising the order — rather than running all of `classA` and then all of
|
|
7
|
+
* `classB` — is the difference between this and an ordinary benchmark run: a
|
|
8
|
+
* systematic drift over the run (CPU frequency scaling, thermal throttling,
|
|
9
|
+
* background load) would otherwise land almost entirely on whichever class ran
|
|
10
|
+
* second, and get mistaken for the class itself being slower.
|
|
11
|
+
*
|
|
12
|
+
* Both closures must be synchronous. An `await` inside the timed region would
|
|
13
|
+
* add microtask-queue scheduling jitter on the order of microseconds, which
|
|
14
|
+
* swamps the nanosecond-to-low-microsecond differences this is built to
|
|
15
|
+
* detect.
|
|
16
|
+
*
|
|
17
|
+
* @param classA First closure to time.
|
|
18
|
+
* @param classB Second closure to time.
|
|
19
|
+
* @param options Sampling policy, clock and randomness.
|
|
20
|
+
* @returns The two classes' raw timings, in the order they were collected
|
|
21
|
+
* within each class (not the randomised execution order).
|
|
22
|
+
*/
|
|
23
|
+
export async function collectInterleavedTimings(classA, classB, options) {
|
|
24
|
+
options.signal?.throwIfAborted();
|
|
25
|
+
const samples = options.samples ?? 5000;
|
|
26
|
+
const warmup = options.warmup ?? 200;
|
|
27
|
+
for (let index = 0; index < warmup; index++) {
|
|
28
|
+
options.signal?.throwIfAborted();
|
|
29
|
+
await options.beforeMeasure?.();
|
|
30
|
+
classA();
|
|
31
|
+
classB();
|
|
32
|
+
await options.afterMeasure?.();
|
|
33
|
+
}
|
|
34
|
+
// A flat, shuffled sequence of labels decides execution order up front,
|
|
35
|
+
// rather than shuffling per pair — a per-pair coin flip still lets both
|
|
36
|
+
// classes fall into short local runs that a slow drift can bias.
|
|
37
|
+
const order = Array.from({ length: 2 * samples });
|
|
38
|
+
for (let index = 0; index < samples; index++) {
|
|
39
|
+
order[2 * index] = 0;
|
|
40
|
+
order[2 * index + 1] = 1;
|
|
41
|
+
}
|
|
42
|
+
const shuffledOrder = shuffle(order, { rand: options.random });
|
|
43
|
+
const classATimings = [];
|
|
44
|
+
const classBTimings = [];
|
|
45
|
+
for (const label of shuffledOrder) {
|
|
46
|
+
options.signal?.throwIfAborted();
|
|
47
|
+
await options.beforeMeasure?.();
|
|
48
|
+
if (label === 0) {
|
|
49
|
+
const started = options.now();
|
|
50
|
+
classA();
|
|
51
|
+
classATimings.push(Number(options.now() - started));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const started = options.now();
|
|
55
|
+
classB();
|
|
56
|
+
classBTimings.push(Number(options.now() - started));
|
|
57
|
+
}
|
|
58
|
+
await options.afterMeasure?.();
|
|
59
|
+
}
|
|
60
|
+
return { classA: classATimings, classB: classBTimings };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=_collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_collect.js","sourceRoot":"","sources":["../src/_collect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAoCvC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC9C,MAAkB,EAClB,MAAkB,EAClB,OAAuB;IAEvB,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IAEjC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC;IAErC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7C,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;QAEjC,MAAM,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;QAChC,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,CAAC;QACT,MAAM,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;IAChC,CAAC;IAED,wEAAwE;IACxE,wEAAwE;IACxE,iEAAiE;IACjE,MAAM,KAAK,GAAc,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC;IAC7D,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9C,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACrB,KAAK,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE/D,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,aAAa,GAAa,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;QAEjC,MAAM,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;QAChC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YAC9B,MAAM,EAAE,CAAC;YACT,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACP,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YAC9B,MAAM,EAAE,CAAC;YACT,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,MAAM,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CollectOptions } from "./_collect.js";
|
|
2
|
+
/** `|t|` above which dudect-style tools conventionally report a leak. */
|
|
3
|
+
export declare const DEFAULT_LEAK_THRESHOLD = 4.5;
|
|
4
|
+
/** Default seed for {@link CollectOptions.random}, reproducible across runs. */
|
|
5
|
+
export declare const DEFAULT_SEED = 1592590337;
|
|
6
|
+
/** Options for {@link runDudectCheck}, extending the collection policy. */
|
|
7
|
+
export type DudectOptions = CollectOptions & {
|
|
8
|
+
/**
|
|
9
|
+
* Upper-tail quantile beyond which a sample is dropped before the statistic
|
|
10
|
+
* is computed. Default `0.995`.
|
|
11
|
+
*
|
|
12
|
+
* Real hardware occasionally delays an execution far past its typical cost —
|
|
13
|
+
* an interrupt, a GC pause, a context switch — and those delays land on both
|
|
14
|
+
* classes equally often, so they dilute a real signal rather than create a
|
|
15
|
+
* false one. Cropping only the slow tail, symmetrically for both classes,
|
|
16
|
+
* removes that noise without touching the difference this is looking for.
|
|
17
|
+
*/
|
|
18
|
+
cropQuantile?: number;
|
|
19
|
+
/** `|t|` above which {@link DudectResult.leakDetected} is `true`. */
|
|
20
|
+
leakThreshold?: number;
|
|
21
|
+
};
|
|
22
|
+
export type DudectResult = {
|
|
23
|
+
/** Welch's t-statistic on the (cropped) timing samples. */
|
|
24
|
+
t: number;
|
|
25
|
+
/** Welch–Satterthwaite degrees of freedom for `t`. */
|
|
26
|
+
degreesOfFreedom: number;
|
|
27
|
+
/** Samples collected per class, before cropping. */
|
|
28
|
+
samplesPerClass: number;
|
|
29
|
+
/** `true` when `|t|` exceeds the threshold. */
|
|
30
|
+
leakDetected: boolean;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A dudect-style constant-time check: times `classA` and `classB` under a
|
|
34
|
+
* randomly interleaved schedule (see {@link collectInterleavedTimings}) and
|
|
35
|
+
* reports whether their timing distributions are distinguishable.
|
|
36
|
+
*
|
|
37
|
+
* This only ever answers "is a timing difference detectable in this many
|
|
38
|
+
* samples", never "why" — a positive result still needs a human to find the
|
|
39
|
+
* secret-dependent branch or memory access it came from, and a negative result
|
|
40
|
+
* only means none was found at this sample count, not that none exists.
|
|
41
|
+
* Increase {@link CollectOptions.samples} to raise the harness's power to
|
|
42
|
+
* detect a smaller leak.
|
|
43
|
+
*
|
|
44
|
+
* The classic dudect design compares one **fixed** secret input, called
|
|
45
|
+
* repeatedly, against a **new random** secret input generated for every call of
|
|
46
|
+
* the other class — pass closures built that way, not two closures over the
|
|
47
|
+
* same fixed input or the test has no power to detect anything.
|
|
48
|
+
*
|
|
49
|
+
* @param classA First closure to time.
|
|
50
|
+
* @param classB Second closure to time.
|
|
51
|
+
* @param options Sampling, cropping and threshold policy.
|
|
52
|
+
* @returns The statistic and its leak verdict.
|
|
53
|
+
*/
|
|
54
|
+
export declare function runDudectCheck(classA: () => void, classB: () => void, options: DudectOptions): Promise<DudectResult>;
|
|
55
|
+
//# sourceMappingURL=_dudect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dudect.d.ts","sourceRoot":"","sources":["../src/_dudect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAA6B,MAAM,eAAe,CAAC;AAE1E,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,gFAAgF;AAChF,eAAO,MAAM,YAAY,aAAc,CAAC;AAExC,2EAA2E;AAC3E,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG;IAC5C;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAC1B,2DAA2D;IAC3D,CAAC,EAAE,MAAM,CAAC;IAEV,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,CAAC;IAEzB,oDAAoD;IACpD,eAAe,EAAE,MAAM,CAAC;IAExB,+CAA+C;IAC/C,YAAY,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,cAAc,CACnC,MAAM,EAAE,MAAM,IAAI,EAClB,MAAM,EAAE,MAAM,IAAI,EAClB,OAAO,EAAE,aAAa,GACpB,OAAO,CAAC,YAAY,CAAC,CAqBvB"}
|
package/dist/_dudect.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { quantile, welchTStatistic } from "@ac-kit/math-stats";
|
|
2
|
+
import { collectInterleavedTimings } from "./_collect.js";
|
|
3
|
+
/** `|t|` above which dudect-style tools conventionally report a leak. */
|
|
4
|
+
export const DEFAULT_LEAK_THRESHOLD = 4.5;
|
|
5
|
+
/** Default seed for {@link CollectOptions.random}, reproducible across runs. */
|
|
6
|
+
export const DEFAULT_SEED = 1592590337;
|
|
7
|
+
/**
|
|
8
|
+
* A dudect-style constant-time check: times `classA` and `classB` under a
|
|
9
|
+
* randomly interleaved schedule (see {@link collectInterleavedTimings}) and
|
|
10
|
+
* reports whether their timing distributions are distinguishable.
|
|
11
|
+
*
|
|
12
|
+
* This only ever answers "is a timing difference detectable in this many
|
|
13
|
+
* samples", never "why" — a positive result still needs a human to find the
|
|
14
|
+
* secret-dependent branch or memory access it came from, and a negative result
|
|
15
|
+
* only means none was found at this sample count, not that none exists.
|
|
16
|
+
* Increase {@link CollectOptions.samples} to raise the harness's power to
|
|
17
|
+
* detect a smaller leak.
|
|
18
|
+
*
|
|
19
|
+
* The classic dudect design compares one **fixed** secret input, called
|
|
20
|
+
* repeatedly, against a **new random** secret input generated for every call of
|
|
21
|
+
* the other class — pass closures built that way, not two closures over the
|
|
22
|
+
* same fixed input or the test has no power to detect anything.
|
|
23
|
+
*
|
|
24
|
+
* @param classA First closure to time.
|
|
25
|
+
* @param classB Second closure to time.
|
|
26
|
+
* @param options Sampling, cropping and threshold policy.
|
|
27
|
+
* @returns The statistic and its leak verdict.
|
|
28
|
+
*/
|
|
29
|
+
export async function runDudectCheck(classA, classB, options) {
|
|
30
|
+
const cropQuantile = options?.cropQuantile ?? 0.995;
|
|
31
|
+
const leakThreshold = options?.leakThreshold ?? DEFAULT_LEAK_THRESHOLD;
|
|
32
|
+
const { classA: rawA, classB: rawB } = await collectInterleavedTimings(classA, classB, options);
|
|
33
|
+
const croppedA = cropUpperTail_(rawA, cropQuantile);
|
|
34
|
+
const croppedB = cropUpperTail_(rawB, cropQuantile);
|
|
35
|
+
const { t, degreesOfFreedom } = welchTStatistic(croppedA, croppedB);
|
|
36
|
+
return {
|
|
37
|
+
t,
|
|
38
|
+
degreesOfFreedom,
|
|
39
|
+
samplesPerClass: rawA.length,
|
|
40
|
+
leakDetected: Math.abs(t) > leakThreshold,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function cropUpperTail_(samples, q) {
|
|
44
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
45
|
+
const limit = quantile(sorted, q);
|
|
46
|
+
return sorted.filter((value) => value <= limit);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=_dudect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dudect.js","sourceRoot":"","sources":["../src/_dudect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAkB,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE1E,yEAAyE;AACzE,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C,gFAAgF;AAChF,MAAM,CAAC,MAAM,YAAY,GAAG,UAAW,CAAC;AAkCxC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,MAAkB,EAClB,MAAkB,EAClB,OAAsB;IAEtB,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,KAAK,CAAC;IACpD,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,sBAAsB,CAAC;IAEvE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,yBAAyB,CACrE,MAAM,EACN,MAAM,EACN,OAAO,CACP,CAAC;IAEF,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAEpD,MAAM,EAAE,CAAC,EAAE,gBAAgB,EAAE,GAAG,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAEpE,OAAO;QACN,CAAC;QACD,gBAAgB;QAChB,eAAe,EAAE,IAAI,CAAC,MAAM;QAC5B,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,aAAa;KACzC,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,OAA0B,EAAE,CAAS;IAC5D,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAElC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BenchCaseRunContext } from "@ac-bench/core/runner";
|
|
2
|
+
import { CallableNoArgs } from "@ac-kit/core";
|
|
3
|
+
import { ConstantTimeCaseOptions, ConstantTimeConditionOptions } from "./options.js";
|
|
4
|
+
export type ConstantTimeRunCaseOptions = ConstantTimeCaseOptions & {
|
|
5
|
+
conditionOptions?: ConstantTimeConditionOptions;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Run one constant-time check.
|
|
9
|
+
*
|
|
10
|
+
* @param title Case name, carried into the reported result.
|
|
11
|
+
* @param classA First closure to time.
|
|
12
|
+
* @param classB Second closure to time.
|
|
13
|
+
* @param context Run context, used to report the result and check for abort.
|
|
14
|
+
* @param options Sampling, cropping and threshold policy.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runConstantTimeCase(title: string, classA: CallableNoArgs, classB: CallableNoArgs, context: BenchCaseRunContext, options?: ConstantTimeRunCaseOptions): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=_run-case.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_run-case.d.ts","sourceRoot":"","sources":["../src/_run-case.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAe,MAAM,cAAc,CAAC;AAU3D,OAAO,EACN,uBAAuB,EACvB,4BAA4B,EAC5B,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,0BAA0B,GAAG,uBAAuB,GAAG;IAClE,gBAAgB,CAAC,EAAE,4BAA4B,CAAC;CAChD,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACxC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,0BAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CA4Df"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { hrtime } from "node:process";
|
|
2
|
+
import { formatError } from "@ac-kit/core";
|
|
3
|
+
import { mulberry32 } from "@ac-kit/math-random";
|
|
4
|
+
import { DEFAULT_LEAK_THRESHOLD, DEFAULT_SEED, runDudectCheck, } from "./_dudect.js";
|
|
5
|
+
import { detectConstantTimeWarnings_ } from "./_warnings.js";
|
|
6
|
+
/**
|
|
7
|
+
* Run one constant-time check.
|
|
8
|
+
*
|
|
9
|
+
* @param title Case name, carried into the reported result.
|
|
10
|
+
* @param classA First closure to time.
|
|
11
|
+
* @param classB Second closure to time.
|
|
12
|
+
* @param context Run context, used to report the result and check for abort.
|
|
13
|
+
* @param options Sampling, cropping and threshold policy.
|
|
14
|
+
*/
|
|
15
|
+
export async function runConstantTimeCase(title, classA, classB, context, options) {
|
|
16
|
+
context.signal.throwIfAborted();
|
|
17
|
+
const tags = options?.tags ?? {};
|
|
18
|
+
const leakThreshold = options?.conditionOptions?.dudect?.leakThreshold ?? DEFAULT_LEAK_THRESHOLD;
|
|
19
|
+
const beforeMeasure = options?.beforeEach
|
|
20
|
+
? () => options.beforeEach?.(context.signal)
|
|
21
|
+
: undefined;
|
|
22
|
+
const afterMeasure = options?.afterEach
|
|
23
|
+
? () => options.afterEach?.(context.signal)
|
|
24
|
+
: undefined;
|
|
25
|
+
try {
|
|
26
|
+
await options?.setup?.(context.signal);
|
|
27
|
+
const dudectResult = await runDudectCheck(classA, classB, {
|
|
28
|
+
...options?.conditionOptions?.dudect,
|
|
29
|
+
random: options?.conditionOptions?.dudect?.random ?? mulberry32(DEFAULT_SEED),
|
|
30
|
+
now: hrtime.bigint,
|
|
31
|
+
signal: context.signal,
|
|
32
|
+
beforeMeasure,
|
|
33
|
+
afterMeasure,
|
|
34
|
+
});
|
|
35
|
+
await options?.teardown?.(context.signal);
|
|
36
|
+
context.setCaseResult({
|
|
37
|
+
title,
|
|
38
|
+
tags,
|
|
39
|
+
dudectResult,
|
|
40
|
+
warnings: detectConstantTimeWarnings_(dudectResult, leakThreshold),
|
|
41
|
+
failureMessage: null,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
let failureMessage;
|
|
46
|
+
try {
|
|
47
|
+
await options?.teardown?.(context.signal);
|
|
48
|
+
failureMessage = formatError(error);
|
|
49
|
+
}
|
|
50
|
+
catch (tearDownError) {
|
|
51
|
+
failureMessage = formatError(new SuppressedError(tearDownError, error, "teardown failed after case failure"));
|
|
52
|
+
}
|
|
53
|
+
context.setCaseResult({
|
|
54
|
+
title,
|
|
55
|
+
tags,
|
|
56
|
+
dudectResult: null,
|
|
57
|
+
warnings: [],
|
|
58
|
+
failureMessage,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=_run-case.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_run-case.js","sourceRoot":"","sources":["../src/_run-case.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAkB,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EACN,sBAAsB,EACtB,YAAY,EACZ,cAAc,GACd,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAU7D;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,KAAa,EACb,MAAsB,EACtB,MAAsB,EACtB,OAA4B,EAC5B,OAAoC;IAEpC,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IAEhC,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;IACjC,MAAM,aAAa,GAClB,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,aAAa,IAAI,sBAAsB,CAAC;IAE5E,MAAM,aAAa,GAAG,OAAO,EAAE,UAAU;QACxC,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;QAC5C,CAAC,CAAC,SAAS,CAAC;IACb,MAAM,YAAY,GAAG,OAAO,EAAE,SAAS;QACtC,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;QAC3C,CAAC,CAAC,SAAS,CAAC;IAEb,IAAI,CAAC;QACJ,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEvC,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE;YACzD,GAAG,OAAO,EAAE,gBAAgB,EAAE,MAAM;YACpC,MAAM,EACL,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,IAAI,UAAU,CAAC,YAAY,CAAC;YACtE,GAAG,EAAE,MAAM,CAAC,MAAM;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,aAAa;YACb,YAAY;SACZ,CAAC,CAAC;QAEH,MAAM,OAAO,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAE1C,OAAO,CAAC,aAAa,CAAC;YACrB,KAAK;YACL,IAAI;YACJ,YAAY;YACZ,QAAQ,EAAE,2BAA2B,CAAC,YAAY,EAAE,aAAa,CAAC;YAClE,cAAc,EAAE,IAAI;SACa,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,cAAsB,CAAC;QAC3B,IAAI,CAAC;YACJ,MAAM,OAAO,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAE1C,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,aAAa,EAAE,CAAC;YACxB,cAAc,GAAG,WAAW,CAC3B,IAAI,eAAe,CAClB,aAAa,EACb,KAAK,EACL,oCAAoC,CACpC,CACD,CAAC;QACH,CAAC;QAED,OAAO,CAAC,aAAa,CAAC;YACrB,KAAK;YACL,IAAI;YACJ,YAAY,EAAE,IAAI;YAClB,QAAQ,EAAE,EAAE;YACZ,cAAc;SACmB,CAAC,CAAC;IACrC,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DudectResult } from "./_dudect.js";
|
|
2
|
+
/**
|
|
3
|
+
* Constant-time check statistics — an alias for {@link DudectResult}, named to
|
|
4
|
+
* match its sibling measures' `_statistics.ts` modules (duration, jitter),
|
|
5
|
+
* which the plugin contract (§4.6) expects one of per measure.
|
|
6
|
+
*/
|
|
7
|
+
export type ConstantTimeStatistics = DudectResult;
|
|
8
|
+
//# sourceMappingURL=_statistics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_statistics.d.ts","sourceRoot":"","sources":["../src/_statistics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_statistics.js","sourceRoot":"","sources":["../src/_statistics.ts"],"names":[],"mappings":""}
|
package/dist/_types.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
export declare const constantTimeCaseResultSchema: z.ZodMiniObject<{
|
|
3
|
+
title: z.ZodMiniString<string>;
|
|
4
|
+
dudectResult: z.ZodMiniNullable<z.ZodMiniObject<{
|
|
5
|
+
t: z.ZodMiniNumber<number>;
|
|
6
|
+
degreesOfFreedom: z.ZodMiniNumber<number>;
|
|
7
|
+
samplesPerClass: z.ZodMiniNumber<number>;
|
|
8
|
+
leakDetected: z.ZodMiniBoolean<boolean>;
|
|
9
|
+
}, z.core.$strip>>;
|
|
10
|
+
tags: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>;
|
|
11
|
+
warnings: z.ZodMiniArray<z.ZodMiniObject<{
|
|
12
|
+
kind: z.ZodMiniEnum<{
|
|
13
|
+
"leak-detected": "leak-detected";
|
|
14
|
+
"too-few-samples": "too-few-samples";
|
|
15
|
+
}>;
|
|
16
|
+
message: z.ZodMiniString<string>;
|
|
17
|
+
}, z.core.$strip>>;
|
|
18
|
+
failureMessage: z.ZodMiniNullable<z.ZodMiniString<string>>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
export type ConstantTimeCaseResult = z.infer<typeof constantTimeCaseResultSchema>;
|
|
21
|
+
export declare const constantTimeConditionResultSchema: z.ZodMiniObject<{
|
|
22
|
+
title: z.ZodMiniString<string>;
|
|
23
|
+
results: z.ZodMiniArray<z.ZodMiniObject<{
|
|
24
|
+
title: z.ZodMiniString<string>;
|
|
25
|
+
dudectResult: z.ZodMiniNullable<z.ZodMiniObject<{
|
|
26
|
+
t: z.ZodMiniNumber<number>;
|
|
27
|
+
degreesOfFreedom: z.ZodMiniNumber<number>;
|
|
28
|
+
samplesPerClass: z.ZodMiniNumber<number>;
|
|
29
|
+
leakDetected: z.ZodMiniBoolean<boolean>;
|
|
30
|
+
}, z.core.$strip>>;
|
|
31
|
+
tags: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>;
|
|
32
|
+
warnings: z.ZodMiniArray<z.ZodMiniObject<{
|
|
33
|
+
kind: z.ZodMiniEnum<{
|
|
34
|
+
"leak-detected": "leak-detected";
|
|
35
|
+
"too-few-samples": "too-few-samples";
|
|
36
|
+
}>;
|
|
37
|
+
message: z.ZodMiniString<string>;
|
|
38
|
+
}, z.core.$strip>>;
|
|
39
|
+
failureMessage: z.ZodMiniNullable<z.ZodMiniString<string>>;
|
|
40
|
+
}, z.core.$strip>>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
export type ConstantTimeConditionResult = z.infer<typeof constantTimeConditionResultSchema>;
|
|
43
|
+
//# sourceMappingURL=_types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_types.d.ts","sourceRoot":"","sources":["../src/_types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAW9B,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;iBAMvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,4BAA4B,CACnC,CAAC;AAEF,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;iBAG5C,CAAC;AACH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAChD,OAAO,iCAAiC,CACxC,CAAC"}
|
package/dist/_types.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
import { constantTimeWarningSchema } from "./_warnings.js";
|
|
3
|
+
const dudectResultSchema = z.object({
|
|
4
|
+
t: z.number(),
|
|
5
|
+
degreesOfFreedom: z.number(),
|
|
6
|
+
samplesPerClass: z.number(),
|
|
7
|
+
leakDetected: z.boolean(),
|
|
8
|
+
});
|
|
9
|
+
export const constantTimeCaseResultSchema = z.object({
|
|
10
|
+
title: z.string(),
|
|
11
|
+
dudectResult: z.nullable(dudectResultSchema),
|
|
12
|
+
tags: z.record(z.string(), z.string()),
|
|
13
|
+
warnings: z.array(constantTimeWarningSchema),
|
|
14
|
+
failureMessage: z.nullable(z.string()),
|
|
15
|
+
});
|
|
16
|
+
export const constantTimeConditionResultSchema = z.object({
|
|
17
|
+
title: z.string(),
|
|
18
|
+
results: z.array(constantTimeCaseResultSchema),
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=_types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_types.js","sourceRoot":"","sources":["../src/_types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAE9B,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE;IACb,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC5B,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE;IAC3B,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE;CACzB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAC5C,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IACtC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,yBAAyB,CAAC;IAC5C,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CACtC,CAAC,CAAC;AAKH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC,MAAM,CAAC;IACzD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,4BAA4B,CAAC;CAC9C,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
import { ConstantTimeStatistics } from "./_statistics.js";
|
|
3
|
+
/** Categories of quality problem detected in a constant-time result. */
|
|
4
|
+
export declare const constantTimeWarningKindSchema: z.ZodMiniEnum<{
|
|
5
|
+
"leak-detected": "leak-detected";
|
|
6
|
+
"too-few-samples": "too-few-samples";
|
|
7
|
+
}>;
|
|
8
|
+
export type ConstantTimeWarningKind = z.infer<typeof constantTimeWarningKindSchema>;
|
|
9
|
+
/** A quality problem that should be shown alongside the numbers. */
|
|
10
|
+
export declare const constantTimeWarningSchema: z.ZodMiniObject<{
|
|
11
|
+
kind: z.ZodMiniEnum<{
|
|
12
|
+
"leak-detected": "leak-detected";
|
|
13
|
+
"too-few-samples": "too-few-samples";
|
|
14
|
+
}>;
|
|
15
|
+
message: z.ZodMiniString<string>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export type ConstantTimeWarning = z.infer<typeof constantTimeWarningSchema>;
|
|
18
|
+
/**
|
|
19
|
+
* Surfaces conditions that make a constant-time verdict untrustworthy, or
|
|
20
|
+
* restates a positive verdict as a warning worth calling out alongside the
|
|
21
|
+
* numbers.
|
|
22
|
+
*/
|
|
23
|
+
export declare function detectConstantTimeWarnings_(statistics: ConstantTimeStatistics, leakThreshold: number): ConstantTimeWarning[];
|
|
24
|
+
//# sourceMappingURL=_warnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_warnings.d.ts","sourceRoot":"","sources":["../src/_warnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAE9B,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAK1D,wEAAwE;AACxE,eAAO,MAAM,6BAA6B;;;EAGxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC5C,OAAO,6BAA6B,CACpC,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,yBAAyB;;;;;;iBAGpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;GAIG;AACH,wBAAgB,2BAA2B,CAC1C,UAAU,EAAE,sBAAsB,EAClC,aAAa,EAAE,MAAM,GACnB,mBAAmB,EAAE,CAkBvB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
/** Below this, cropping has too few samples per class to be meaningful. */
|
|
3
|
+
const MIN_SAMPLES_PER_CLASS = 1000;
|
|
4
|
+
/** Categories of quality problem detected in a constant-time result. */
|
|
5
|
+
export const constantTimeWarningKindSchema = z.enum([
|
|
6
|
+
"leak-detected",
|
|
7
|
+
"too-few-samples",
|
|
8
|
+
]);
|
|
9
|
+
/** A quality problem that should be shown alongside the numbers. */
|
|
10
|
+
export const constantTimeWarningSchema = z.object({
|
|
11
|
+
kind: constantTimeWarningKindSchema,
|
|
12
|
+
message: z.string(),
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Surfaces conditions that make a constant-time verdict untrustworthy, or
|
|
16
|
+
* restates a positive verdict as a warning worth calling out alongside the
|
|
17
|
+
* numbers.
|
|
18
|
+
*/
|
|
19
|
+
export function detectConstantTimeWarnings_(statistics, leakThreshold) {
|
|
20
|
+
const warnings = [];
|
|
21
|
+
if (statistics.samplesPerClass < MIN_SAMPLES_PER_CLASS) {
|
|
22
|
+
warnings.push({
|
|
23
|
+
kind: "too-few-samples",
|
|
24
|
+
message: `only ${statistics.samplesPerClass} samples per class collected; increase samples to raise the harness's power to detect a smaller leak`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (statistics.leakDetected) {
|
|
28
|
+
warnings.push({
|
|
29
|
+
kind: "leak-detected",
|
|
30
|
+
message: `timing difference detected (|t|=${Math.abs(statistics.t).toFixed(2)} > ${leakThreshold}); the two classes are statistically distinguishable`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return warnings;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=_warnings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_warnings.js","sourceRoot":"","sources":["../src/_warnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAI9B,2EAA2E;AAC3E,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC,wEAAwE;AACxE,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,IAAI,CAAC;IACnD,eAAe;IACf,iBAAiB;CACjB,CAAC,CAAC;AAKH,oEAAoE;AACpE,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,IAAI,EAAE,6BAA6B;IACnC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;CACnB,CAAC,CAAC;AAGH;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAC1C,UAAkC,EAClC,aAAqB;IAErB,MAAM,QAAQ,GAA0B,EAAE,CAAC;IAE3C,IAAI,UAAU,CAAC,eAAe,GAAG,qBAAqB,EAAE,CAAC;QACxD,QAAQ,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,QAAQ,UAAU,CAAC,eAAe,sGAAsG;SACjJ,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,mCAAmC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,aAAa,sDAAsD;SACtJ,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC"}
|
package/dist/decl.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CallableNoArgs, MaybeAsyncCallableNoArgs } from "@ac-kit/core";
|
|
2
|
+
import { ConstantTimeCaseOptions, ConstantTimeConditionOptions } from "./options.js";
|
|
3
|
+
export declare function constantTimeCondition(title: string, fn: MaybeAsyncCallableNoArgs): void;
|
|
4
|
+
export declare function constantTimeCondition(title: string, options: ConstantTimeConditionOptions, fn: MaybeAsyncCallableNoArgs): void;
|
|
5
|
+
export declare function constantTimeCase(name: string, classA: CallableNoArgs, classB: CallableNoArgs): void;
|
|
6
|
+
export declare function constantTimeCase(name: string, options: ConstantTimeCaseOptions, classA: CallableNoArgs, classB: CallableNoArgs): void;
|
|
7
|
+
//# sourceMappingURL=decl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decl.d.ts","sourceRoot":"","sources":["../src/decl.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAGxE,OAAO,EACN,uBAAuB,EACvB,4BAA4B,EAC5B,MAAM,cAAc,CAAC;AAItB,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,wBAAwB,GAC1B,IAAI,CAAC;AACR,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,4BAA4B,EACrC,EAAE,EAAE,wBAAwB,GAC1B,IAAI,CAAC;AAgBR,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,cAAc,GACpB,IAAI,CAAC;AACR,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,uBAAuB,EAChC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,cAAc,GACpB,IAAI,CAAC"}
|
package/dist/decl.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { registerCondition, registerCase } from "@ac-bench/core/runner";
|
|
2
|
+
import { runConstantTimeCase } from "./_run-case.js";
|
|
3
|
+
const MEASURE_TYPE_ = "constant-time";
|
|
4
|
+
export function constantTimeCondition(title, optionsOrFn, fn) {
|
|
5
|
+
const [options, callback] = typeof optionsOrFn === "function"
|
|
6
|
+
? [undefined, optionsOrFn]
|
|
7
|
+
: [optionsOrFn, fn];
|
|
8
|
+
registerCondition(MEASURE_TYPE_, title, callback, {
|
|
9
|
+
additionalRunArgs: [options],
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function constantTimeCase(name, optionsOrClassA, classAOrClassB, maybeClassB) {
|
|
13
|
+
const isOptionsForm = typeof optionsOrClassA !== "function";
|
|
14
|
+
const options = isOptionsForm ? optionsOrClassA : undefined;
|
|
15
|
+
const classA = isOptionsForm ? classAOrClassB : optionsOrClassA;
|
|
16
|
+
const classB = isOptionsForm ? maybeClassB : classAOrClassB;
|
|
17
|
+
registerCase(MEASURE_TYPE_, name, (context, conditionOptions) => runConstantTimeCase(name, classA, classB, context, {
|
|
18
|
+
...options,
|
|
19
|
+
conditionOptions,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=decl.js.map
|
package/dist/decl.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decl.js","sourceRoot":"","sources":["../src/decl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAMrD,MAAM,aAAa,GAAG,eAAe,CAAC;AAWtC,MAAM,UAAU,qBAAqB,CACpC,KAAa,EACb,WAAoE,EACpE,EAA6B;IAE7B,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,GACxB,OAAO,WAAW,KAAK,UAAU;QAChC,CAAC,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC;QAC1B,CAAC,CAAC,CAAC,WAAW,EAAE,EAAG,CAAC,CAAC;IAEvB,iBAAiB,CAAC,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE;QACjD,iBAAiB,EAAE,CAAC,OAAO,CAAC;KAC5B,CAAC,CAAC;AACJ,CAAC;AAaD,MAAM,UAAU,gBAAgB,CAC/B,IAAY,EACZ,eAAyD,EACzD,cAA0C,EAC1C,WAA4B;IAE5B,MAAM,aAAa,GAAG,OAAO,eAAe,KAAK,UAAU,CAAC;IAC5D,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5D,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,cAAe,CAAC,CAAC,CAAC,eAAe,CAAC;IACjE,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,WAAY,CAAC,CAAC,CAAC,cAAe,CAAC;IAE9D,YAAY,CACX,aAAa,EACb,IAAI,EACJ,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAC7B,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;QAClD,GAAG,OAAO;QACV,gBAAgB;KAChB,CAAC,CACH,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,QAAQ,EACR,SAAS,EACT,SAAS,EACT,UAAU,GACV,MAAM,uBAAuB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,QAAQ,EACR,SAAS,EACT,SAAS,EACT,UAAU,GACV,MAAM,uBAAuB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CaseCommonOptions } from "@ac-bench/core/runner";
|
|
2
|
+
import { RandomFn } from "@ac-kit/core";
|
|
3
|
+
import { DudectOptions } from "./_dudect.js";
|
|
4
|
+
export type ConstantTimeConditionOptions = {
|
|
5
|
+
dudect?: Omit<DudectOptions, "beforeMeasure" | "afterMeasure" | "signal" | "now" | "random"> & {
|
|
6
|
+
/**
|
|
7
|
+
* Source of randomness for the execution-order shuffle. Defaults to a fixed
|
|
8
|
+
* seed.
|
|
9
|
+
*/
|
|
10
|
+
random?: RandomFn;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export type ConstantTimeCaseOptions = CaseCommonOptions;
|
|
14
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,MAAM,4BAA4B,GAAG;IAC1C,MAAM,CAAC,EAAE,IAAI,CACZ,aAAa,EACb,eAAe,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAC9D,GAAG;QACH;;;WAGG;QACH,MAAM,CAAC,EAAE,QAAQ,CAAC;KAClB,CAAC;CACF,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":""}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":""}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { defineMeasurePlugin, MeasureResultValidationError, } from "@ac-bench/core/plugin";
|
|
2
|
+
import { DIMENSIONLESS } from "@ac-kit/model-dataset";
|
|
3
|
+
import * as z from "zod/mini";
|
|
4
|
+
import { constantTimeCaseResultSchema, constantTimeConditionResultSchema, } from "./_types.js";
|
|
5
|
+
const MEASURE_ID = "constant-time";
|
|
6
|
+
function parseCaseResult_(value) {
|
|
7
|
+
const result = constantTimeCaseResultSchema.safeParse(value);
|
|
8
|
+
if (!result.success) {
|
|
9
|
+
throw new MeasureResultValidationError(MEASURE_ID, z.prettifyError(result.error));
|
|
10
|
+
}
|
|
11
|
+
return result.data;
|
|
12
|
+
}
|
|
13
|
+
function parseConditionResult_(value) {
|
|
14
|
+
const result = constantTimeConditionResultSchema.safeParse(value);
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
throw new MeasureResultValidationError(MEASURE_ID, z.prettifyError(result.error));
|
|
17
|
+
}
|
|
18
|
+
return result.data;
|
|
19
|
+
}
|
|
20
|
+
const FIELDS = [
|
|
21
|
+
{ name: "case", kind: "nominal", title: "case" },
|
|
22
|
+
{
|
|
23
|
+
name: "t",
|
|
24
|
+
kind: "quantitative",
|
|
25
|
+
title: "t",
|
|
26
|
+
// Thresholded, not minimised: no direction is the honest answer here.
|
|
27
|
+
unit: DIMENSIONLESS,
|
|
28
|
+
format: { minimumFractionDigits: 3, maximumFractionDigits: 3 },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "degreesOfFreedom",
|
|
32
|
+
kind: "quantitative",
|
|
33
|
+
title: "df",
|
|
34
|
+
format: { minimumFractionDigits: 1, maximumFractionDigits: 1 },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "samplesPerClass",
|
|
38
|
+
kind: "quantitative",
|
|
39
|
+
title: "n/class",
|
|
40
|
+
format: { maximumFractionDigits: 0 },
|
|
41
|
+
},
|
|
42
|
+
{ name: "verdict", kind: "nominal", title: "verdict" },
|
|
43
|
+
];
|
|
44
|
+
function caseRow_(result) {
|
|
45
|
+
const stats = result.dudectResult;
|
|
46
|
+
if (!stats) {
|
|
47
|
+
return [
|
|
48
|
+
result.title,
|
|
49
|
+
null,
|
|
50
|
+
null,
|
|
51
|
+
null,
|
|
52
|
+
`SKIPPED: ${result.failureMessage ?? "unknown"}`,
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
return [
|
|
56
|
+
result.title,
|
|
57
|
+
stats.t,
|
|
58
|
+
stats.degreesOfFreedom,
|
|
59
|
+
stats.samplesPerClass,
|
|
60
|
+
stats.leakDetected ? "⚠ leak detected" : "ok",
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
export default defineMeasurePlugin({
|
|
64
|
+
id: "constant-time",
|
|
65
|
+
label: "Constant-time",
|
|
66
|
+
parseCaseResult: parseCaseResult_,
|
|
67
|
+
parseConditionResult: parseConditionResult_,
|
|
68
|
+
fields: FIELDS,
|
|
69
|
+
caseRow: caseRow_,
|
|
70
|
+
// No `comparison`: a t-statistic is thresholded against a critical value,
|
|
71
|
+
// not ordered against the other cases, so there is nothing to escalate on.
|
|
72
|
+
warnings: (result) => {
|
|
73
|
+
const diagnostics = [];
|
|
74
|
+
if (result.failureMessage) {
|
|
75
|
+
diagnostics.push({
|
|
76
|
+
severity: "error",
|
|
77
|
+
code: "case-failed",
|
|
78
|
+
message: result.failureMessage,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
for (const warning of result.warnings) {
|
|
82
|
+
diagnostics.push({
|
|
83
|
+
severity: "warning",
|
|
84
|
+
code: warning.kind,
|
|
85
|
+
message: warning.message,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return diagnostics;
|
|
89
|
+
},
|
|
90
|
+
// dudect interleaves its own two classes inside one case; a second
|
|
91
|
+
// interleaving layer above it (round-robin across cases) adds drift it
|
|
92
|
+
// cannot cancel.
|
|
93
|
+
scheduling: { rounds: "one", grouping: "condition" },
|
|
94
|
+
// A leak verdict is not an average: one process finding a leak is a leak,
|
|
95
|
+
// whatever the others saw. The most significant result stands for the case.
|
|
96
|
+
pool: (executions) => executions.reduce((worst, execution) => Math.abs(execution.result.dudectResult?.t ?? 0) >
|
|
97
|
+
Math.abs(worst.result.dudectResult?.t ?? 0)
|
|
98
|
+
? execution
|
|
99
|
+
: worst).result,
|
|
100
|
+
toJsonCase: (result) => result,
|
|
101
|
+
toJsonCondition: (result) => ({
|
|
102
|
+
constantTimeConditions: [result],
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ac-bench/measure-constant-time",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "dudect-style constant-time measure for @ac-bench: times two closures under a randomly interleaved schedule and reports Welch's t-statistic.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"benchmark",
|
|
7
|
+
"constant-time",
|
|
8
|
+
"dudect",
|
|
9
|
+
"side-channel",
|
|
10
|
+
"timing"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/anthochamp/node-essentials/tree/main/bench/measure-constant-time#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/anthochamp/node-essentials/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "BSD-3-Clause",
|
|
17
|
+
"author": "Anthony Champagne <dev@anthonychampagne.fr>",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/anthochamp/node-essentials.git",
|
|
21
|
+
"directory": "bench/measure-constant-time"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./dist/index.js",
|
|
31
|
+
"./plugin": "./dist/plugin.js"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"main": "./dist/index.js",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": "./dist/index.js",
|
|
37
|
+
"./plugin": "./dist/plugin.js"
|
|
38
|
+
},
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"compile": "tsc -p ./tsconfig.build.json --outDir dist",
|
|
43
|
+
"compile:check": "tsc -noEmit -p ./tsconfig.json",
|
|
44
|
+
"clean": "rimraf dist",
|
|
45
|
+
"build": "yarn clean && yarn compile",
|
|
46
|
+
"test": "yarn run -T vitest run",
|
|
47
|
+
"prepack": "yarn build"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@ac-bench/core": "^0.1.0",
|
|
51
|
+
"@ac-kit/algo": "^0.1.0",
|
|
52
|
+
"@ac-kit/app-report": "^0.1.0",
|
|
53
|
+
"@ac-kit/core": "^0.1.0",
|
|
54
|
+
"@ac-kit/math-random": "^0.1.0",
|
|
55
|
+
"@ac-kit/math-stats": "^0.1.0",
|
|
56
|
+
"@ac-kit/model-dataset": "^0.1.0",
|
|
57
|
+
"zod": "^4.5.4"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@ac-kit/tsconfig": "0.1.0",
|
|
61
|
+
"@ac-kit/typedoc-config": "0.1.0",
|
|
62
|
+
"@ac-kit/vitest-config": "0.1.0",
|
|
63
|
+
"@types/node": "26.5.1",
|
|
64
|
+
"@typescript/native": "typescript@7.0.2",
|
|
65
|
+
"rimraf": "6.1.3",
|
|
66
|
+
"typedoc": "0.28.20",
|
|
67
|
+
"typescript": "@typescript/typescript6@6.0.2",
|
|
68
|
+
"vite": "8.3.0",
|
|
69
|
+
"vitest": "5.0.0"
|
|
70
|
+
},
|
|
71
|
+
"engines": {
|
|
72
|
+
"node": ">=24"
|
|
73
|
+
}
|
|
74
|
+
}
|