cheatah
Source

tests/benchmarks/integrity_bench.cpp

1// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).
2// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.
3// Micro-benchmarks for the per-load cost of each module-integrity tier
4// (runtime/integrity.cpp). Module verification is paid exactly ONCE, when the runtime
5// loads a module just before dlopen — never during the program's execution. So this
6// measures verify_module() directly (no process spawn, no program run) to isolate the
7// pure added work of each tier, at a couple of representative module sizes.
8//
9// Off no sidecars — the baseline: the module isn't even read.
10// Checksum + <mod>.sha512 — one SHA-512 pass over the module bytes.
11// Signed + <mod>.sig (strict) — SHA-512 checksum + one Ed25519 verification.
12// Full + <mod>.rt/.rt.sig — all of the above + parse/compare the runtime
13// manifest and verify its (separately-keyed) signature.
14//
15// Build with the `release` preset; run e.g.
16// ./build/release/bin/cheatah_benchmarks --benchmark_filter=Integrity
17#include <benchmark/benchmark.h>
19#include <cstdint>
20#include <filesystem>
21#include <fstream>
22#include <map>
23#include <string>
24#include <vector>
26#include "build_fingerprint.hpp" // build_runtime_manifest() — the host's own manifest
27#include "ed25519.hpp"
28#include "hashlib.hpp"
29#include "integrity.hpp"
31namespace {
32namespace fs = std::filesystem;
33namespace ig = cheatah::integrity;
35std::string base_name(const std::string& p) {
36 const std::size_t s = p.find_last_of('/');
37 return s == std::string::npos ? p : p.substr(s + 1);
39void write_file(const std::string& path, const std::string& data) {
40 std::ofstream f(path, std::ios::binary | std::ios::trunc);
41 f << data;
43std::string sig_sidecar(std::string_view secret, const std::string& message) {
44 const std::string pub = cheatah::ed25519::public_key(secret);
45 const std::string sig = cheatah::ed25519::sign(secret, message);
46 return "cheatah-sig v1\npubkey " + pub + "\nsig " + sig + "\n";
49// A fake module of `size` bytes on disk, plus the sidecars for tiers up to `tier`:
50// 0 none · 1 +.sha512 · 2 +.sig · 3 +.rt/.rt.sig. The .rt is the HOST's own manifest, so
51// the compatibility check passes and we time the success path, not an early rejection.
52struct Fixture {
53 std::string path, pubkey, rt_pubkey;
54};
55Fixture make_fixture(std::size_t size, int tier, const std::string& tag) {
56 const fs::path dir = fs::temp_directory_path() / "cheatah_integrity_bench";
57 fs::create_directories(dir);
58 Fixture fx;
59 fx.path = (dir / ("mod_" + tag + ".bin")).string();
61 std::string bytes(size, '\0'); // deterministic pseudo-random fill (no real .so needed)
62 std::uint32_t s = 0x9e3779b9u;
63 for (std::size_t i = 0; i < size; ++i) {
64 s = s * 1103515245u + 12345u;
65 bytes[i] = static_cast<char>(s >> 16);
66 }
67 write_file(fx.path, bytes);
69 if (tier >= 1) {
70 const std::string hex = cheatah::hashlib::sha512(bytes);
71 write_file(fx.path + ".sha512", hex + " " + base_name(fx.path) + "\n");
72 }
73 if (tier >= 2) {
74 const std::string secret = cheatah::ed25519::generate();
75 fx.pubkey = cheatah::ed25519::public_key(secret);
76 write_file(fx.path + ".sig", sig_sidecar(secret, bytes));
77 }
78 if (tier >= 3) {
79 const std::string manifest = cheatah::build_runtime_manifest();
80 write_file(fx.path + ".rt", manifest);
81 const std::string rt_secret = cheatah::ed25519::generate();
82 fx.rt_pubkey = cheatah::ed25519::public_key(rt_secret);
83 write_file(fx.path + ".rt.sig", sig_sidecar(rt_secret, manifest));
84 }
85 return fx;
88void BM_Integrity(benchmark::State& state, std::size_t size, int tier) {
89 // Build the fixture once (outside the timed loop) and reuse it across iterations.
90 static std::map<std::string, Fixture> cache;
91 const std::string tag = std::to_string(size) + "_" + std::to_string(tier);
92 auto it = cache.find(tag);
93 if (it == cache.end()) it = cache.emplace(tag, make_fixture(size, tier, tag)).first;
94 const Fixture& fx = it->second;
96 const ig::Policy policy = (tier >= 2) ? ig::Policy::Strict : ig::Policy::Off;
97 const std::vector<std::string> keys = (tier >= 2) ? std::vector<std::string>{fx.pubkey}
98 : std::vector<std::string>{};
99 const std::vector<std::string> rt_keys =
100 (tier >= 3) ? std::vector<std::string>{fx.rt_pubkey} : std::vector<std::string>{};
102 for (auto _ : state) {
103 ig::Result r = ig::verify_module(fx.path, policy, keys, rt_keys);
104 if (!r.ok) {
105 state.SkipWithError(("verify_module failed: " + r.error).c_str());
106 break;
107 }
108 ig::release(r); // close the fd verify_module opened, so we don't leak descriptors
109 benchmark::DoNotOptimize(r.fd);
110 }
111 state.SetBytesProcessed(static_cast<std::int64_t>(state.iterations()) *
112 static_cast<std::int64_t>(size));
114} // namespace
116// 64 KiB ≈ a small program module; 1 MiB ≈ a larger one with several imported modules.
117BENCHMARK_CAPTURE(BM_Integrity, Off/64K, 64u * 1024, 0);
118BENCHMARK_CAPTURE(BM_Integrity, Checksum/64K, 64u * 1024, 1);
119BENCHMARK_CAPTURE(BM_Integrity, Signed/64K, 64u * 1024, 2);
120BENCHMARK_CAPTURE(BM_Integrity, Full/64K, 64u * 1024, 3);
121BENCHMARK_CAPTURE(BM_Integrity, Off/1M, 1024u * 1024, 0);
122BENCHMARK_CAPTURE(BM_Integrity, Checksum/1M, 1024u * 1024, 1);
123BENCHMARK_CAPTURE(BM_Integrity, Signed/1M, 1024u * 1024, 2);
124BENCHMARK_CAPTURE(BM_Integrity, Full/1M, 1024u * 1024, 3);