Quick Start
This guide shows you how to write and run your first timing test. You’ve seen the basic pattern on the landing page; now we’ll build a complete, runnable test.
Add the dependencies
Section titled “Add the dependencies”cargo add tacet --devcargo add rand --dev # For random input generationcargo add subtle --dev # For the constant-time comparison examplebun add @tacet/js --dev# Or with npmnpm install --save-dev @tacet/jsWith pkg-config (recommended):
cmake_minimum_required(VERSION 3.14)project(my_project)
find_package(PkgConfig REQUIRED)pkg_check_modules(TACET REQUIRED tacet)
add_executable(my_test test.c)target_include_directories(my_test PRIVATE ${TACET_INCLUDE_DIRS})target_link_libraries(my_test PRIVATE ${TACET_LIBRARIES})Direct compilation:
cc test.c $(pkg-config --cflags --libs tacet) -o testManual linking (without pkg-config):
# macOStarget_link_libraries(my_test PRIVATE /usr/local/lib/libtacet_c.a "-framework Security" "-framework CoreFoundation")
# Linuxtarget_link_libraries(my_test PRIVATE /usr/local/lib/libtacet_c.a pthread dl m)With pkg-config (recommended):
cmake_minimum_required(VERSION 3.14)project(my_project)
# C++20 required for std::spanset(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(PkgConfig REQUIRED)pkg_check_modules(TACET REQUIRED tacet)
add_executable(my_test test.cpp)target_include_directories(my_test PRIVATE ${TACET_INCLUDE_DIRS})target_link_libraries(my_test PRIVATE ${TACET_LIBRARIES})Direct compilation:
c++ -std=c++20 test.cpp $(pkg-config --cflags --libs tacet) -o testgo get github.com/tacet-labs/tacetWrite a test
Section titled “Write a test”Create a test file (e.g., tests/timing.rs):
use tacet::{TimingOracle, AttackerModel, helpers::InputPair};use std::time::Duration;
#[test]fn test_constant_time_compare() { let secret = [0u8; 32];
// Baseline matches secret (slow path if leaky) // Sample is random (fast path if leaky) let inputs = InputPair::new( || [0u8; 32], || rand::random::<[u8; 32]>(), );
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork) .time_budget(Duration::from_secs(30)) .test(inputs, |input| { // Test your comparison function constant_time_eq(&secret, &input); });
// Display prints nicely formatted output with colors println!("{outcome}");
// Assert the test passed (panics on Fail, warns on Inconclusive/Unmeasurable) assert!(outcome.passed(), "Timing leak detected");}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { // Your constant-time comparison implementation subtle::ConstantTimeEq::ct_eq(a, b).into()}Create a test file (e.g., test/timing.test.ts):
import { TimingOracle, AttackerModel } from '@tacet/js';import { expect, test } from 'bun:test';
test('constant-time comparison', async () => { const secret = new Uint8Array(32).fill(0);
const outcome = await TimingOracle .forAttacker(AttackerModel.AdjacentNetwork) .timeBudget(30_000) // milliseconds .test({ // Baseline matches secret (slow path if leaky) baseline: () => new Uint8Array(32).fill(0), // Sample is random (fast path if leaky) sample: () => crypto.getRandomValues(new Uint8Array(32)), }, (input) => { // Test your comparison function constantTimeEq(secret, input); });
// Display prints formatted output console.log(outcome.toString());
// Assert the test passed expect(outcome.passed()).toBe(true);});
function constantTimeEq(a: Uint8Array, b: Uint8Array): boolean { // Your constant-time comparison implementation let result = 0; for (let i = 0; i < a.length; i++) { result |= a[i] ^ b[i]; } return result === 0;}Create a test file (e.g., test_timing.c):
#include <tacet/tacet.h>#include <string.h>#include <stdlib.h>#include <assert.h>
static uint8_t secret[32] = {0};
// Generator callback: creates test inputsvoid generate_inputs(to_class_t cls, void *buf, size_t size, void *ctx) { uint8_t *data = (uint8_t *)buf; if (cls == TO_CLASS_BASELINE) { // Baseline matches secret (slow path if leaky) memset(data, 0, size); } else { // Sample is random (fast path if leaky) for (size_t i = 0; i < size; i++) { data[i] = rand() & 0xFF; } }}
// Operation to testvoid test_operation(const void *input, size_t size, void *ctx) { constant_time_eq(secret, (const uint8_t *)input, size);}
int constant_time_eq(const uint8_t *a, const uint8_t *b, size_t len) { // Your constant-time comparison implementation uint8_t result = 0; for (size_t i = 0; i < len; i++) { result |= a[i] ^ b[i]; } return result == 0;}
int main(void) { to_config_t config = { .attacker_model = TO_ATTACKER_ADJACENT_NETWORK, .time_budget_ms = 30000, };
to_result_t result; to_test(&config, generate_inputs, test_operation, NULL, 32, &result);
// Print formatted output to_result_print(&result);
// Check result assert(to_result_passed(&result) && "Timing leak detected");
to_result_free(&result); return 0;}Create a test file (e.g., test_timing.cpp):
#include <tacet/tacet.hpp>#include <cstring>#include <random>#include <cassert>
static uint8_t secret[32] = {0};
bool constant_time_eq(const uint8_t* a, const uint8_t* b, size_t len) { // Your constant-time comparison implementation uint8_t result = 0; for (size_t i = 0; i < len; i++) { result |= a[i] ^ b[i]; } return result == 0;}
int main() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 255);
auto outcome = tacet::Oracle::forAttacker(tacet::AttackerModel::AdjacentNetwork) .timeBudget(std::chrono::seconds(30)) .test<std::array<uint8_t, 32>>( // Generator lambda [&](tacet::Class cls) { std::array<uint8_t, 32> data; if (cls == tacet::Class::Baseline) { // Baseline matches secret (slow path if leaky) data.fill(0); } else { // Sample is random (fast path if leaky) for (auto& byte : data) { byte = dis(gen); } } return data; }, // Operation lambda [](const std::array<uint8_t, 32>& input) { constant_time_eq(secret, input.data(), 32); } );
// Print formatted output std::cout << outcome << std::endl;
// Check result assert(outcome.passed() && "Timing leak detected"); return 0;}Create a test file (e.g., timing_test.go):
package mypackage
import ( "crypto/rand" "testing" "time"
"github.com/tacet-labs/tacet")
var secret = make([]byte, 32)
func TestConstantTimeCompare(t *testing.T) { // Generator function: creates test inputs generator := func(cls tacet.Class) []byte { data := make([]byte, 32) if cls == tacet.Baseline { // Baseline matches secret (slow path if leaky) // data is already zeros } else { // Sample is random (fast path if leaky) rand.Read(data) } return data }
// Operation to test operation := func(input []byte) { constantTimeEq(secret, input) }
outcome := tacet.Test( generator, operation, 32, // input size tacet.WithAttackerModel(tacet.AdjacentNetwork), tacet.WithTimeBudget(30*time.Second), )
// Print formatted output t.Log(outcome.String())
// Assert the test passed if !outcome.Passed() { t.Fatal("Timing leak detected") }}
func constantTimeEq(a, b []byte) bool { // Your constant-time comparison implementation var result byte for i := range a { result |= a[i] ^ b[i] } return result == 0}Run the test
Section titled “Run the test”# Rustcargo test --test timing -- --test-threads=1 --nocapture
# JavaScriptbun test --concurrent=1 test/timing.test.ts
# C (compile and run)gcc test_timing.c -ltacet -o test_timing && ./test_timing
# C++ (compile and run)g++ test_timing.cpp -ltacet -std=c++17 -o test_timing && ./test_timing
# Gogo test -v -p 1 ./...You should see output like:
[test_constant_time_compare]tacet──────────────────────────────────────────────────────────────
Samples: 6000 per class Quality: Good
✓ No timing leak detected
Probability of leak: 0.0% 95% CI: 0.0–12.5 ns
──────────────────────────────────────────────────────────────What just happened
Section titled “What just happened”The library ran your function thousands of times with two types of inputs: baseline (zeros) and sample (random). It measured execution times and compared the timing distributions using Bayesian statistics. The output tells you if there’s a security-relevant timing difference above your threshold.
For the statistical details, see How It Works.
Next steps
Section titled “Next steps”- The Two-Class Pattern: Understanding how to choose input classes
- Attacker Models: Choosing the right threat model
- Interpreting Results: Understanding outcomes in depth
- Testing Cryptographic Code: Patterns for specific cryptographic operations