C API
C bindings for tacet, providing low-overhead timing side-channel detection.
Quick Reference
Section titled “Quick Reference”#include <tacet.h>
// Callback for sample collectionvoid 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;}Building
Section titled “Building”git clone https://github.com/agucova/tacetcd tacetcargo build --release -p tacet-c
# Headers at: crates/tacet-c/include/tacet.h# Library at: target/release/libtacet_c.{a,so,dylib}Configuration
Section titled “Configuration”Creating a Configuration
Section titled “Creating a Configuration”Start by creating a configuration for your attacker model, then customize as needed:
// Create config for your attacker modelToConfig cfg = to_config_default(AdjacentNetwork);
// Or use convenience functionsToConfig cfg = to_config_adjacent_network(); // 100 ns thresholdToConfig cfg = to_config_shared_hardware(); // ~0.6 ns thresholdToConfig cfg = to_config_remote_network(); // 50 μs thresholdCustomizing Configuration
Section titled “Customizing Configuration”ToConfig cfg = to_config_default(AdjacentNetwork);cfg.time_budget_secs = 60.0;cfg.max_samples = 50000;cfg.pass_threshold = 0.01; // More confident passcfg.fail_threshold = 0.99; // More confident failcfg.seed = 12345; // Reproducibilitycfg.custom_threshold_ns = 500.0; // Override thresholdEnvironment Variable Override
Section titled “Environment Variable Override”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 varsSupported 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)
Attacker Models
Section titled “Attacker Models”| Enum Value | Threshold | Use Case |
|---|---|---|
SharedHardware | ~0.6 ns | SGX, containers, hyperthreading |
PostQuantum | ~3.3 ns | Post-quantum crypto |
AdjacentNetwork | 100 ns | LAN, HTTP/2 |
RemoteNetwork | 50 μs | Public internet |
Research | ~0 | Detect any difference |
Callback-Based API (Recommended)
Section titled “Callback-Based API (Recommended)”The to_test() function handles the full adaptive sampling loop internally.
Collect Callback
Section titled “Collect Callback”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:
- Collect
countbaseline timing measurements intobaseline_out - Collect
countsample timing measurements intosample_out - Use interleaved sampling for best statistical properties
Running a Test
Section titled “Running a Test”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;}Low-Level Adaptive API
Section titled “Low-Level Adaptive API”For more control, use the calibrate/step API:
// 1. Collect calibration samplesuint64_t baseline[5000], sample[5000];collect_samples(baseline, sample, 5000);
// 2. CalibrateToError err;ToCalibration* cal = to_calibrate(baseline, sample, 5000, &config, &err);
// 3. Create stateToState* state = to_state_new();
// 4. Adaptive loopdouble 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 upto_state_free(state);to_calibration_free(cal);One-Shot Analysis
Section titled “One-Shot Analysis”For pre-collected data:
uint64_t baseline[10000], sample[10000];// ... collect all samples ...
ToResult result;ToError err = to_analyze(baseline, sample, 10000, &config, &result);Result Types
Section titled “Result Types”Outcome Enum
Section titled “Outcome Enum”typedef enum { Pass = 0, // No leak detected Fail = 1, // Leak confirmed Inconclusive = 2,// Cannot decide Unmeasurable = 3 // Operation too fast} ToOutcome;Result Structure
Section titled “Result Structure”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;Effect Structure
Section titled “Effect Structure”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;Exploitability
Section titled “Exploitability”typedef enum { SharedHardwareOnly = 0, // < 10 ns Http2Multiplexing = 1, // 10-100 ns StandardRemote = 2, // 100 ns - 10 μs ObviousLeak = 3 // > 10 μs} ToExploitability;Measurement Quality
Section titled “Measurement Quality”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;Error Handling
Section titled “Error Handling”typedef enum { Ok = 0, NullPointer = 1, InvalidConfig = 2, CalibrationFailed = 3, AnalysisFailed = 4, NotEnoughSamples = 5} ToError;Complete Example
Section titled “Complete Example”#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;}Compilation
Section titled “Compilation”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