Skip to content

C bindings for tacet, providing low-overhead timing side-channel detection.

#include <tacet.h>
// Callback for sample collection
void collect(uint64_t* baseline, uint64_t* sample, size_t count, void* ctx) {
for (size_t i = 0; i < count; i++) {
baseline[i] = measure(generate_baseline());
sample[i] = measure(generate_sample());
}
}
int main(void) {
// Configure based on attacker model
ToConfig cfg = to_config_default(AdjacentNetwork);
cfg.time_budget_secs = 30.0;
cfg.max_samples = 100000;
cfg = to_config_from_env(cfg); // CI can set TO_TIME_BUDGET_SECS, etc.
ToResult result;
to_test(&cfg, collect, NULL, &result);
switch (result.outcome) {
case Pass:
printf("No leak: P(leak)=%.1f%%\n", result.leak_probability * 100.0);
break;
case Fail:
printf("Leak: %.1f ns shift\n", result.effect.shift_ns);
break;
case Inconclusive:
printf("Inconclusive\n");
break;
case Unmeasurable:
printf("Too fast to measure\n");
break;
}
return result.outcome == Fail ? 1 : 0;
}

Terminal window
git clone https://github.com/agucova/tacet
cd tacet
cargo build --release -p tacet-c
# Headers at: crates/tacet-c/include/tacet.h
# Library at: target/release/libtacet_c.{a,so,dylib}

Start by creating a configuration for your attacker model, then customize as needed:

// Create config for your attacker model
ToConfig cfg = to_config_default(AdjacentNetwork);
// Or use convenience functions
ToConfig cfg = to_config_adjacent_network(); // 100 ns threshold
ToConfig cfg = to_config_shared_hardware(); // ~0.6 ns threshold
ToConfig cfg = to_config_remote_network(); // 50 μs threshold
ToConfig cfg = to_config_default(AdjacentNetwork);
cfg.time_budget_secs = 60.0;
cfg.max_samples = 50000;
cfg.pass_threshold = 0.01; // More confident pass
cfg.fail_threshold = 0.99; // More confident fail
cfg.seed = 12345; // Reproducibility
cfg.custom_threshold_ns = 500.0; // Override threshold

Allow CI systems to override configuration without recompiling:

ToConfig cfg = to_config_default(AdjacentNetwork);
cfg.time_budget_secs = 30.0;
cfg.max_samples = 100000;
cfg = to_config_from_env(cfg); // Merges TO_* env vars

Supported environment variables:

  • TO_TIME_BUDGET_SECS - Time budget in seconds (float)
  • TO_MAX_SAMPLES - Maximum samples per class (integer)
  • TO_PASS_THRESHOLD - Pass threshold for P(leak) (float, e.g., 0.05)
  • TO_FAIL_THRESHOLD - Fail threshold for P(leak) (float, e.g., 0.95)
  • TO_SEED - Random seed for reproducibility (integer)
  • TO_THRESHOLD_NS - Custom threshold in nanoseconds (float)
Enum ValueThresholdUse Case
SharedHardware~0.6 nsSGX, containers, hyperthreading
PostQuantum~3.3 nsPost-quantum crypto
AdjacentNetwork100 nsLAN, HTTP/2
RemoteNetwork50 μsPublic internet
Research~0Detect any difference

The to_test() function handles the full adaptive sampling loop internally.

typedef void (*ToCollectFn)(
uint64_t* baseline_out, // Fill with baseline timing samples
uint64_t* sample_out, // Fill with sample timing samples
size_t count, // Number of samples to collect per class
void* user_ctx // User context pointer
);

The callback is invoked multiple times. Each invocation should:

  1. Collect count baseline timing measurements into baseline_out
  2. Collect count sample timing measurements into sample_out
  3. Use interleaved sampling for best statistical properties
ToError to_test(
const ToConfig* config,
ToCollectFn collect_fn,
void* user_ctx,
ToResult* result_out
);

Example:

void collect(uint64_t* baseline, uint64_t* sample, size_t count, void* ctx) {
for (size_t i = 0; i < count; i++) {
// Baseline: all-zero input
uint8_t input[32] = {0};
uint64_t start = read_timer();
my_crypto_op(input, 32);
uint64_t end = read_timer();
baseline[i] = end - start;
// Sample: random input
arc4random_buf(input, 32);
start = read_timer();
my_crypto_op(input, 32);
end = read_timer();
sample[i] = end - start;
}
}
int main(void) {
ToConfig cfg = to_config_default(AdjacentNetwork);
cfg.time_budget_secs = 30.0;
cfg.max_samples = 100000;
ToResult result;
ToError err = to_test(&cfg, collect, NULL, &result);
if (err != Ok) {
fprintf(stderr, "Test failed: %d\n", err);
return 1;
}
// Handle result...
return result.outcome == Fail ? 1 : 0;
}

For more control, use the calibrate/step API:

// 1. Collect calibration samples
uint64_t baseline[5000], sample[5000];
collect_samples(baseline, sample, 5000);
// 2. Calibrate
ToError err;
ToCalibration* cal = to_calibrate(baseline, sample, 5000, &config, &err);
// 3. Create state
ToState* state = to_state_new();
// 4. Adaptive loop
double start_time = get_time();
while (1) {
uint64_t batch_b[1000], batch_s[1000];
collect_samples(batch_b, batch_s, 1000);
ToStepResult step;
to_step(cal, state, batch_b, batch_s, 1000, &config,
get_time() - start_time, &step);
if (step.has_decision) {
// step.result contains the final ToResult
break;
}
}
// 5. Clean up
to_state_free(state);
to_calibration_free(cal);

For pre-collected data:

uint64_t baseline[10000], sample[10000];
// ... collect all samples ...
ToResult result;
ToError err = to_analyze(baseline, sample, 10000, &config, &result);

typedef enum {
Pass = 0, // No leak detected
Fail = 1, // Leak confirmed
Inconclusive = 2,// Cannot decide
Unmeasurable = 3 // Operation too fast
} ToOutcome;
typedef struct {
ToOutcome outcome;
double leak_probability; // P(effect > threshold | data)
ToEffect effect; // Effect size estimate
ToMeasurementQuality quality; // Measurement precision
uint64_t samples_used; // Samples per class
double elapsed_secs; // Test duration
ToExploitability exploitability; // Exploitability assessment
ToInconclusiveReason inconclusive_reason;
double mde_shift_ns; // Minimum detectable shift
double mde_tail_ns; // Minimum detectable tail
double theta_user_ns; // User's requested threshold
double theta_eff_ns; // Effective threshold used
double theta_floor_ns; // Measurement floor
double timer_resolution_ns; // Timer resolution
ToDiagnostics diagnostics; // Detailed diagnostics
} ToResult;
typedef struct {
double shift_ns; // Uniform shift component
double tail_ns; // Tail effect component
double ci_low_ns; // 95% credible interval lower
double ci_high_ns; // 95% credible interval upper
ToEffectPattern pattern; // UniformShift, TailEffect, Mixed, Indeterminate
} ToEffect;
typedef enum {
SharedHardwareOnly = 0, // < 10 ns
Http2Multiplexing = 1, // 10-100 ns
StandardRemote = 2, // 100 ns - 10 μs
ObviousLeak = 3 // > 10 μs
} ToExploitability;
typedef enum {
Excellent = 0, // MDE < 5 ns
Good = 1, // MDE 5-20 ns
Poor = 2, // MDE 20-100 ns
TooNoisy = 3 // MDE > 100 ns
} ToMeasurementQuality;

typedef enum {
Ok = 0,
NullPointer = 1,
InvalidConfig = 2,
CalibrationFailed = 3,
AnalysisFailed = 4,
NotEnoughSamples = 5
} ToError;

#include <tacet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
// Platform-specific timer
#if defined(__x86_64__)
static inline uint64_t read_timer(void) {
uint32_t lo, hi;
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
return ((uint64_t)hi << 32) | lo;
}
#elif defined(__aarch64__)
static inline uint64_t read_timer(void) {
uint64_t val;
__asm__ volatile("mrs %0, cntvct_el0" : "=r"(val));
return val;
}
#endif
#define DO_NOT_OPTIMIZE(x) __asm__ volatile("" : : "r,m"(x) : "memory")
static uint8_t secret[32];
// Leaky comparison (for testing)
static int leaky_compare(const uint8_t* a, const uint8_t* b, size_t n) {
for (size_t i = 0; i < n; i++) {
if (a[i] != b[i]) return 0; // Early exit!
}
return 1;
}
void collect(uint64_t* baseline, uint64_t* sample, size_t count, void* ctx) {
uint8_t input[32];
for (size_t i = 0; i < count; i++) {
// Baseline: all zeros
memset(input, 0, 32);
uint64_t start = read_timer();
int result = leaky_compare(input, secret, 32);
uint64_t end = read_timer();
DO_NOT_OPTIMIZE(result);
baseline[i] = end - start;
// Sample: random
arc4random_buf(input, 32);
start = read_timer();
result = leaky_compare(input, secret, 32);
end = read_timer();
DO_NOT_OPTIMIZE(result);
sample[i] = end - start;
}
}
int main(void) {
arc4random_buf(secret, sizeof(secret));
ToConfig cfg = to_config_default(AdjacentNetwork);
cfg.time_budget_secs = 30.0;
cfg.max_samples = 100000;
cfg = to_config_from_env(cfg);
printf("Running timing test...\n");
ToResult result;
ToError err = to_test(&cfg, collect, NULL, &result);
if (err != Ok) {
fprintf(stderr, "Test failed: %d\n", err);
return 1;
}
printf("Outcome: %s\n",
result.outcome == Pass ? "PASS" :
result.outcome == Fail ? "FAIL" :
result.outcome == Inconclusive ? "INCONCLUSIVE" : "UNMEASURABLE");
printf("Leak probability: %.1f%%\n", result.leak_probability * 100.0);
printf("Effect: %.2f ns (shift) + %.2f ns (tail)\n",
result.effect.shift_ns, result.effect.tail_ns);
printf("Samples: %llu, Time: %.2fs\n",
(unsigned long long)result.samples_used, result.elapsed_secs);
return result.outcome == Fail ? 1 : 0;
}
Terminal window
cc -O3 -Wall -std=c11 -o timing_test timing_test.c \
-I/path/to/tacet/crates/tacet-c/include \
-L/path/to/tacet/target/release \
-ltacet_c -lpthread -lm