Skip to content

This document covers the JavaScript/TypeScript bindings for tacet via NAPI-RS.

Terminal window
bun add @tacet/js
import { TimingOracle, AttackerModel } from '@tacet/js';
import crypto from 'crypto';
const result = TimingOracle
.forAttacker(AttackerModel.AdjacentNetwork)
.timeBudget(30_000) // 30 seconds
.test(
{
baseline: () => Buffer.alloc(32, 0),
sample: () => crypto.randomBytes(32),
},
(input) => myCryptoFunction(input)
);
if (result.isFail()) {
console.error(`Timing leak: ${result.exploitabilityString()}`);
process.exit(1);
}
// Or use assertion style
result.assertNoLeak(); // throws TimingLeakError if Fail

TimingOracle.forAttacker(model: AttackerModel)

Section titled “TimingOracle.forAttacker(model: AttackerModel)”

Create a TimingOracle for a specific attacker model.

enum AttackerModel {
SharedHardware, // 0.6 ns threshold - SGX, containers
PostQuantum, // 3.3 ns threshold - PQ crypto
AdjacentNetwork, // 100 ns threshold - LAN, HTTP/2
RemoteNetwork, // 50 us threshold - Internet
Research, // ~0 threshold - Detect any difference
}
TimingOracle
.forAttacker(AttackerModel.AdjacentNetwork)
.timeBudget(30_000) // Time budget in ms (default: 30,000)
.maxSamples(100_000) // Max samples per class (default: 100,000)
.customThreshold(500) // Custom threshold in ns
.passThreshold(0.05) // Pass if P(leak) < this
.failThreshold(0.95) // Fail if P(leak) > this
.seed(12345) // Random seed
.showProgress(true) // Print progress to stderr
.test(inputs, operation);

Run the timing test.

interface InputPair<T> {
baseline: () => T; // Generate baseline input (typically all zeros)
sample: () => T; // Generate sample input (typically random)
}

The test() method returns a TimingTestResult with helper methods:

result.isPass() // No leak detected
result.isFail() // Leak detected
result.isInconclusive() // Could not decide
result.isUnmeasurable() // Operation too fast
result.isConclusive() // Pass or Fail
result.outcomeString() // "Pass", "Fail", etc.
result.exploitabilityString() // "SharedHardwareOnly", "Http2Multiplexing", etc.
result.qualityString() // "Excellent", "Good", "Poor", "TooNoisy"
result.leakProbabilityPercent() // "12.3%"
result.toString() // Human-readable summary
result.assertNoLeak(); // throws TimingLeakError if Fail

import {
TimingOracleError,
TimingLeakError,
CalibrationError,
InsufficientSamplesError,
} from '@tacet/js';
try {
result.assertNoLeak();
} catch (e) {
if (e instanceof TimingLeakError) {
console.error(`Leak: ${e.result.exploitabilityString()}`);
}
}

For advanced use cases, you can use the low-level functions directly.

Collect timing samples with automatic batch K detection.

import { collectSamples } from '@tacet/js';
const samples = collectSamples(
10_000,
() => new Uint8Array(32).fill(0),
() => crypto.getRandomValues(new Uint8Array(32)),
(input) => myOperation(input)
);

Run complete analysis on pre-collected timing data.

import { analyze, calibrateTimer, configAdjacentNetwork } from '@tacet/js';
const result = analyze(
BigInt64Array.from(baselineSamples),
BigInt64Array.from(sampleSamples),
configAdjacentNetwork(),
calibrateTimer().frequencyHz
);
import {
calibrateSamples,
adaptiveStepBatch,
AdaptiveState,
collectBatches,
} from '@tacet/js';
const calibration = calibrateSamples(calBaseline, calSample, config, timer.frequencyHz);
const state = new AdaptiveState();
for (const batch of collectBatches(1000, baselineGen, sampleGen, operation)) {
const step = adaptiveStepBatch(calibration, state, batch.baseline, batch.sample, config, elapsed);
if (step.isDecision) break;
}

enum Outcome {
Pass, // No leak detected
Fail, // Leak confirmed
Inconclusive, // Cannot decide
Unmeasurable, // Operation too fast
}
enum Exploitability {
SharedHardwareOnly, // < 10 ns
Http2Multiplexing, // 10-100 ns
StandardRemote, // 100 ns - 10 us
ObviousLeak, // > 10 us
}

  • x86_64: High resolution (~0.3-0.5 ns) via rdtsc
  • Apple Silicon: Lower resolution (~42 ns) via cntvct_el0

On Apple Silicon, the library automatically uses adaptive batching to compensate.