diff --git a/pq-bench-rpi5/.gitignore b/pq-bench-rpi5/.gitignore new file mode 100644 index 0000000..4ea8a8a --- /dev/null +++ b/pq-bench-rpi5/.gitignore @@ -0,0 +1,38 @@ +# Build trees / vendored deps (rebuilt by setup/setup.sh) +/vendor/ +/build/ +setup/versions.lock + +# Compiled harness binaries +bench/kem_sig/bench_pq +bench/tls/bench_tls +*.o + +# Generated TLS material +bench/tls/pki/ + +# Per-run scratch dirs (intermediate artifacts; never committed) +results/.work-*/ + +# Local results are kept by users, not committed by default. +# Comment the next line out if you DO want to commit your machine's results. +results/*.json +!results/example-*.json +# Published baseline-grade RPi5 run (auto-calibrated full run) +!results/rasberrypi5-20260608T223040Z.json + +# Merged dataset consumed by the dashboard. Tracked: ships the published RPi5 +# baseline so a fresh clone renders charts out-of-the-box. Contributors who +# re-merge overwrite it locally (modified file they can commit or discard). +analyze/png/ + +# Python venv (matplotlib lives here; system python stays clean) +analyze/.venv/ + +# Machine-local Claude Code settings (never shared) +.claude/settings.local.json + +# Python / OS cruft +__pycache__/ +*.pyc +.DS_Store diff --git a/pq-bench-rpi5/Dockerfile b/pq-bench-rpi5/Dockerfile new file mode 100644 index 0000000..7ee9cba --- /dev/null +++ b/pq-bench-rpi5/Dockerfile @@ -0,0 +1,38 @@ +# ============================================================================= +# Reproducible build of the full PQ benchmark toolchain on Debian aarch64 +# (the same OS family as Raspberry Pi OS / Ubuntu on the RPi5). +# +# docker build -t pq-bench-rpi5 . +# # build/pin liboqs + openssl + oqs-provider inside the image: +# docker run --rm -v "$PWD/results:/app/results" pq-bench-rpi5 ./setup/setup.sh +# +# MEASUREMENT CAVEAT: a container cannot set the CPU governor or read the Pi's +# SoC sensors (vcgencmd) unless you grant it. For *baseline-grade* numbers run +# on the Pi natively, or grant the container what it needs, e.g.: +# docker run --rm --privileged --cpuset-cpus=3 \ +# -v /usr/bin/vcgencmd:/usr/bin/vcgencmd -v /opt/vc:/opt/vc \ +# -v "$PWD/results:/app/results" pq-bench-rpi5 ./run.sh +# Otherwise the results JSON is correctly stamped is_baseline_grade=false. +# ============================================================================= +FROM debian:bookworm-slim + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential cmake ninja-build git perl pkg-config \ + python3 python3-venv ca-certificates \ + libssl-dev cpufrequtils util-linux \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY . /app + +# Build + pin the toolchain at image-build time so the image is self-contained. +# (Comment this out to keep the image thin and run setup.sh at container start.) +RUN ./setup/setup.sh all || (echo "setup failed — see log above" && exit 1) + +# Optional: matplotlib PNG export in an isolated venv. +RUN python3 -m venv analyze/.venv \ + && analyze/.venv/bin/pip install --no-cache-dir -r analyze/requirements.txt + +ENTRYPOINT ["/bin/bash", "-lc"] +CMD ["./run.sh --smoke"] diff --git a/pq-bench-rpi5/README.md b/pq-bench-rpi5/README.md new file mode 100644 index 0000000..270d2c0 --- /dev/null +++ b/pq-bench-rpi5/README.md @@ -0,0 +1,289 @@ +# pq-bench-rpi5 + +A reproducible, general-purpose **post-quantum cryptography benchmark** whose +baseline target is the **Raspberry Pi 5** (Broadcom BCM2712, Cortex-A76, +aarch64). Anyone can run it on their own Pi 5 and the results aggregate and +compare apples-to-apples. + +**Framing — migration cost.** How much does moving from the cryptography Logos +uses *today* (X25519 key exchange + Ed25519 signatures) to PQ candidates cost on +validator-grade hardware? Every chart draws that classical baseline as the +reference line, so the PQ "tax" is always visible. + +Phase 1 covers **PQ KEMs**, **PQ signatures**, and **PQ TLS 1.3 handshakes**. +Hooks are left for a later SNARK/STARK phase (see `config.yaml`); it is not +implemented yet. + +--- + +## What gets measured + +| Layer | Metrics | +|-------|---------| +| **KEM** | keygen / encaps / decaps wall-clock (median, MAD, IQR, min, max, mean, stddev, ops/sec) · pk/sk/ct sizes · heap high-water | +| **Signature** | keygen / sign / verify wall-clock (same stats) · pk/sig sizes | +| **TLS 1.3** | full-handshake latency · handshakes/sec · bytes-on-wire · ClientHello size (+ fragmentation flag) — as a matrix of (KEM group × signature) | + +The **classical baseline** (X25519 / Ed25519 / X25519+Ed25519) is always +included as the reference point — measured as a real primitive via OpenSSL, not +hand-waved. + +--- + +## Project layout + +``` +pq-bench-rpi5/ + setup/ build + pin liboqs, OpenSSL 3.5+, oqs-provider (versions.env / versions.lock) + bench/kem_sig/ bench_pq.c primitive KEM/sig harness (liboqs + OpenSSL EVP baselines) + bench/tls/ bench_tls.c in-process TLS 1.3 handshake harness (OpenSSL API) + run_tls.sh PKI generation + (KEM × sig) matrix driver + bench/lib/ assemble.py / merge helpers / miniyaml.py (zero-dep YAML) + results/ results/-.json (one per run, full metadata) + analyze/ merge.py (combine machines) + plot.py (matplotlib PNGs, optional venv) + dashboard/ static HTML/JS (Chart.js) — no backend, GitHub-Pages deployable + run.sh governor + taskset + thermal wrapper + orchestrator + config.yaml candidate lists (extend here) + Dockerfile reproducible Debian-aarch64 build +``` + +--- + +## Quick start + +### On a Raspberry Pi 5 (the real measurement target) + +```bash +git clone && cd pq-bench-rpi5 +./setup/setup.sh # build + pin liboqs, OpenSSL 3.5+, oqs-provider +sudo ./run.sh # sudo needed to set the CPU governor +python3 analyze/merge.py results/*.json -o dashboard/data/merged.json +# open dashboard/index.html (or deploy dashboard/ to GitHub Pages) +``` + +`./run.sh --smoke` runs tiny iteration counts as a fast pipeline check. +`./run.sh --kemsig-only` / `--tls-only` scope the run. `--iters/--warmup/--reps` +override the `config.yaml` knobs. + +### On macOS (development / smoke testing only) + +```bash +brew install cmake openssl@3 git +./setup/setup.sh +./run.sh --smoke # produces valid JSON; stamped is_baseline_grade=false +``` + +> **macOS runs are never baseline data.** No `performance` governor, no +> `taskset` core pinning, and the build falls back to `-O3` (not +> `-mcpu=cortex-a76`). Every results file records `is_baseline_grade=false` +> with the exact reasons, and the dashboard hides such runs by default. macOS +> `clock_gettime` is also only microsecond-resolution; the Pi's is nanosecond. + +### Docker (reproducible build) + +```bash +docker build -t pq-bench-rpi5 . +docker run --rm -v "$PWD/results:/app/results" pq-bench-rpi5 ./run.sh --smoke +``` +See the `Dockerfile` header for granting governor/sensor access for real runs. + +--- + +## Measurement methodology (why the numbers are credible) + +`run.sh` is the wrapper that makes a number defensible: + +- **CPU governor → `performance`** (Linux). Recorded before/after; warns if it + couldn't be set (e.g. not root). +- **Core pinning via `taskset -c 3`.** The Pi 5 has 4 cores (0–3); core 3 is + chosen to stay clear of CPU0, where the kernel tends to steer IRQs/RPS. The + pinned core and exact `taskset` command are recorded. +- **Thermal/clock trace.** A background sampler logs ARM clock + (`vcgencmd measure_clock arm`) and SoC temperature (`vcgencmd measure_temp`) + ~once a second for the whole run. The full trace is embedded in the results + JSON, and **thermal throttling** (`vcgencmd get_throttled`, plus a clock-droop + heuristic) is detected and flagged — a throttled run is not baseline-grade. +- **Warmup + N timed iterations, multiple repetitions.** Primary metric is + wall-clock nanoseconds via `clock_gettime(CLOCK_MONOTONIC)`. We report + **median, MAD, IQR, min, max, mean, stddev, ops/sec**, plus per-repetition + medians — not just a mean. +- **Cycles mode (optional).** The harness probes whether the userspace ARM PMU + cycle counter (`PMCCNTR_EL0`) is readable. By default on Linux it traps + (needs a kernel module like `enable_arm_pmu`); we then **fall back to + time-based and say so** in the JSON (`run.cycles_available=false` + reason). +- **CPU features / Keccak acceleration.** NEON, SHA2, SHA3, SHA512, AES, PMULL + are detected (`/proc/cpuinfo` on Linux, `sysctl` on macOS). **Note:** the + Cortex-A76 has the SHA2/AES extensions but **not** the ARMv8.2 SHA3 + extension, so on the Pi 5 Keccak runs on NEON/scalar code — the results record + both the hardware capability and whether liboqs was compiled with SHA3 + instructions, so this is explicit rather than assumed. + +### The AArch64-optimized backend + +liboqs is built with `OQS_DIST_BUILD=OFF` and the pinned flags so the optimized +aarch64 ML-KEM backend (`mlkem-native`) and Falcon/Keccak asm are compiled in. +`setup/setup.sh` extracts the proof from the generated `oqsconfig.h` (e.g. +`OQS_ENABLE_KEM_ml_kem_768_aarch64 1`) into `versions.lock`, which is stamped +into every results file under `toolchain.liboqs_opt_defines`. + +--- + +## Reproducibility & provenance + +- **Pinned versions** live in `setup/versions.env` (liboqs `0.15.0`, OpenSSL + `3.5.0`/`≥3.5`, oqs-provider `0.9.0`). After cloning, `setup.sh` records the + **actually resolved git commits** and the **exact build flags + compiler + version** into `setup/versions.lock`. +- **Every results JSON carries full environment metadata**: RPi model, RAM, + kernel, OS, governor, the clock/temp trace during the run, compiler version, + liboqs/oqs-provider/OpenSSL versions+commits, build flags, and the candidate + list. A macOS smoke file and an RPi5 baseline file can never be confused. +- **Identical flags for every candidate:** `-O3 -mcpu=cortex-a76` on the Pi. + Document your `gcc`/`clang` version — it is auto-captured in `versions.lock` + (`CC_VERSION`). + +### `is_baseline_grade` + +The single gate that protects the dataset. It is `true` **only** when all hold: +real Raspberry Pi · `performance` governor · core-pinned · `cortex-a76` build +flags · no thermal throttling. Otherwise it is `false` with a list of reasons. +The dashboard and `plot.py` default to baseline-grade runs only. + +--- + +## Candidates (edit `config.yaml`) + +- **KEM:** ML-KEM-512/768/1024; hybrids X25519MLKEM768, SecP256r1MLKEM768 + (hybrids are benchmarked in the TLS layer; at the primitive layer liboqs + exposes them only as TLS groups, so they show as `enabled:false` there). + Baseline: **X25519**. +- **Signatures:** ML-DSA-44/65/87; SLH-DSA (SPHINCS+) variants; + Falcon/FN-DSA-512/1024. Baseline: **Ed25519**. +- **TLS:** matrix of configured KEM groups × signature algorithms, always + including the classical **X25519 + Ed25519** pair. + +Add FrodoKEM / HQC / Classic McEliece etc. by uncommenting/adding entries — the +harness skips anything your liboqs build doesn't enable (and says so). + +--- + +## Output & analysis + +- `results/-.json` — one self-describing file per run. +- `analyze/merge.py results/*.json -o dashboard/data/merged.json` — merge runs + from many machines into one dataset (keeps each run distinct; never mixes + baseline with smoke). +- `analyze/plot.py` — matplotlib PNGs for papers (optional; install into + `analyze/.venv` via `analyze/requirements.txt` to keep system python clean — + it gracefully skips if matplotlib is absent). +- `dashboard/` — static, no-backend dashboard: grouped bars by security level, + size-vs-speed scatter, TLS handshakes/sec, and ClientHello size — each with + the classical baseline drawn as a reference line. Deploy the folder to GitHub + Pages, or open `index.html` via any static server. + +--- + +## Contributing your RPi5 results + +The whole point is a **shared, aggregated baseline**: the more Raspberry Pi 5 +results we collect under identical conditions, the more confident the migration- +cost picture. If you have a Pi 5, please contribute a run — it takes one command +and a pull request. + +### 1. Run under baseline conditions + +For your numbers to count as baseline-grade, the run must satisfy the +`is_baseline_grade` gate (real Pi 5 · `performance` governor · core-pinned · +`cortex-a76` flags · no thermal throttling). To give it the best shot: + +- **Use a Raspberry Pi 5** with active cooling (the official Active Cooler or a + fan). PQ signing (esp. SLH-DSA) runs the core hot for a while; without cooling + you *will* throttle and the run is flagged non-baseline. +- **Use the official 27 W USB-C PSU.** Under-voltage also trips the throttle flag. +- **Run on a quiet machine** (close other workloads) so core 3 stays clean. +- **Don't edit `config.yaml`'s candidate list** if you want your run to be + directly comparable to others. (Extending it is fine — just say so in your PR; + extra algorithms simply add columns.) + +```bash +git clone && cd pq-bench-rpi5 +./setup/setup.sh # build + pin liboqs / OpenSSL 3.5+ / oqs-provider +sudo ./run.sh # sudo lets it set the performance governor +``` + +A full run takes a while (SLH-DSA signing dominates). To check the pipeline +first without committing to the full run, use `sudo ./run.sh --smoke` — but only +a **full** run (not `--smoke`) counts as a submission. + +### 2. Confirm it's baseline-grade + +When the run finishes, the summary prints `baseline-grade (RPi5): True`. Verify +in the JSON too: + +```bash +f=$(ls -t results/*.json | head -1) +python3 -c "import json;d=json.load(open('$f'));print('baseline_grade:',d['is_baseline_grade']);\ +print('reasons:',d['baseline_grade_reasons']);\ +print('throttled:',d['thermal_trace']['throttling_detected']);\ +print('aarch64 ML-KEM backend:', 'ml_kem_768_aarch64 1' in d['toolchain']['liboqs_opt_defines'])" +``` + +You want `baseline_grade: True`, `reasons: []`, `throttled: False`, and the +backend line `True`. If `is_baseline_grade` is false, the printed reasons tell +you what to fix (usually cooling/PSU/governor) — fix and re-run. + +### 3. Submit it + +Your `results/-.json` is fully self-describing (host model, +kernel, OS, governor, clock/temp trace, compiler + liboqs/oqs-provider/OpenSSL +commits, build flags). It contains your **hostname** and Pi model and nothing +else identifying — if you'd rather not share the hostname, set a name first with +`HOSTNAME=mypi5 sudo ./run.sh`, or just rename the file before submitting. + +`results/*.json` is git-ignored by default (so you never accidentally commit +local experiments), so add yours explicitly: + +```bash +git checkout -b results/-pi5 +git add -f results/-.json +git commit -m "results: RPi5 baseline from " +# push to your fork and open a PR +``` + +**PR checklist** (maintainers will look for these): + +- [ ] `is_baseline_grade: true` with empty `baseline_grade_reasons` +- [ ] `thermal_trace.throttling_detected: false` +- [ ] `host.is_rpi: true` and `host.rpi_model` mentions "Raspberry Pi 5" +- [ ] `run.governor_after: performance` and `run.pinned: true` +- [ ] `toolchain.cflags_target: cortex-a76` +- [ ] full run (not `--smoke`): `run.timed_iters` is the `config.yaml` value, not 25 +- [ ] unmodified candidate list (or extensions noted in the PR description) + +Once merged, your file joins `results/`; anyone can regenerate the aggregated +dataset and dashboard with +`python3 analyze/merge.py results/*.json -o dashboard/data/merged.json`. The +dashboard's run selector will then include your Pi alongside everyone else's. + +> Prefer not to use GitHub? Open an issue and attach the JSON file instead — a +> maintainer will add it. + +--- + +## Limitations + +- **macOS is smoke-only** (see above): coarse timer, no governor/pinning, + fallback flags. +- **Userspace cycle counts** require a kernel PMU module; default is time-based. +- **Heap/stack memory** is best-effort (`mallinfo2` on glibc; reported + unavailable elsewhere); pk/sk/ct/sig **sizes** are authoritative. +- **TLS handshakes are in-process over memory BIOs** — this isolates crypto + cost cleanly (no socket/scheduler noise) but is not a network RTT model; + ClientHello fragmentation is flagged against a typical 1400-byte MSS. +- A run inside Docker is not baseline-grade unless you grant it governor + Pi + sensors (see `Dockerfile`). + +## Future phase (not implemented) + +`config.yaml` reserves a `zk:` section for SNARK/STARK proving/verification +benchmarks; the results schema and dashboard are structured to absorb it later. diff --git a/pq-bench-rpi5/RUNNING-ON-YOUR-RPI5.md b/pq-bench-rpi5/RUNNING-ON-YOUR-RPI5.md new file mode 100644 index 0000000..c09d552 --- /dev/null +++ b/pq-bench-rpi5/RUNNING-ON-YOUR-RPI5.md @@ -0,0 +1,89 @@ +# Running pq-bench-rpi5 on Your Own Raspberry Pi 5 + +This benchmark measures post-quantum KEMs, signatures, and TLS 1.3 handshakes +against the classical baseline Logos uses today (X25519 / Ed25519), so every +chart shows the **migration cost** of moving to PQ on validator-grade hardware. + +There's no manual tuning: the benchmark **auto-calibrates the iteration count +per operation** to your Pi's speed, so results stay comparable across machines. + +## Prerequisites + +- **Raspberry Pi 5** (Cortex-A76, aarch64), ideally the 8GB model, with + **active cooling** so it doesn't thermal-throttle mid-run. +- **Raspberry Pi OS / Debian Trixie or newer** — system OpenSSL 3.5+ so PQ TLS + works without a source build. +- **Internet access** and **sudo**. + +## Step 1 — Clone (public repo, no auth) + +This tool lives as the `pq-bench-rpi5/` subfolder of the Logos PoCs mono-repo, +so clone the mono-repo and `cd` into the subfolder: + +```sh +git clone https://github.com/logos-blockchain/logos-blockchain-pocs.git +cd logos-blockchain-pocs/pq-bench-rpi5 +``` + +## Step 2 — Build the toolchain + +```sh +./setup/setup.sh all +``` + +Takes 5–15 min: installs dependencies and builds liboqs + oqs-provider. Run it +inside `tmux` so it survives an SSH disconnect. + +## Step 3 — Run + +```sh +sudo ./run.sh +``` + +`sudo` is needed to set the performance governor, pin cores, and read the +temperature. The run takes ~4–5 min, with no iteration counts to set. + +Output lands in `results/-.json`, stamped with full +provenance (Pi model, RAM, kernel, governor, thermal trace, library versions) +and an `is_baseline_grade` flag. + +## Step 4 — View results + +```sh +cd dashboard +python3 -m http.server 8765 +# then open http://:8765 +``` + +The charts show KEM, signature, and TLS results with the classical X25519 / +Ed25519 baseline drawn as a reference line. + +## Step 5 — Contribute (optional) + +Share your `results/*.json` (open a PR or send it over). To merge results from +multiple machines: + +```sh +python3 analyze/merge.py results/*.json -o dashboard/data/merged.json +``` + +The dashboard then shows every Pi side by side. + +## What the results tell you + +PQ is not so much *slower* as *bigger*. Lattice schemes (ML-KEM, ML-DSA) run +close to classical in speed but have much larger keys and signatures, while the +hash-based SLH-DSA (SPHINCS+) is an outlier in both signing time and signature +size. On TLS, the classical baseline fits in a single packet, while PQ and +hybrid handshakes grow past it and fragment. + +## Notes and limitations + +- Measures **liboqs** (C / assembly) implementations — a pure-Rust backend is a + separate, optional axis. +- Userspace PMU cycle counts are usually unavailable, so the primary metric is + **wall-clock time + ops/sec**. +- SNARK / STARK benchmarking is **out of scope** for this phase (`config.yaml` + reserves a hook for it). +- The candidate list lives in `config.yaml` — use the exact liboqs algorithm + names. diff --git a/pq-bench-rpi5/analyze/merge.py b/pq-bench-rpi5/analyze/merge.py new file mode 100644 index 0000000..29efb01 --- /dev/null +++ b/pq-bench-rpi5/analyze/merge.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""merge.py — merge many results/-.json files into one dataset the +dashboard (and PNG export) consume. + + python3 analyze/merge.py results/*.json -o dashboard/data/merged.json + +The merged file keeps every run as a separate record (so multiple machines / +repetitions can be compared) plus a flat index for quick charting. It never +collapses RPi5 baseline-grade runs together with non-baseline (e.g. macOS smoke) +runs — each record carries its own `is_baseline_grade` flag and host, and the +dashboard filters on it by default. +""" +from __future__ import annotations +import argparse +import glob +import json +import os +import sys + +MERGED_SCHEMA = "1.0.0" + + +def load_runs(paths): + runs = [] + for p in paths: + try: + with open(p) as f: + d = json.load(f) + d["_source_file"] = os.path.basename(p) + runs.append(d) + except (json.JSONDecodeError, OSError) as e: + print(f"[merge] skipping {p}: {e}", file=sys.stderr) + return runs + + +def run_id(run): + h = run.get("host", {}) + return f"{h.get('hostname','?')}/{h.get('cpu_brand','?')}@{run.get('generated_utc','?')}" + + +def flatten(runs): + """Produce flat per-(run, algorithm, operation) rows for easy charting.""" + kem_rows, sig_rows, tls_rows = [], [], [] + for run in runs: + rid = run_id(run) + host = run.get("host", {}) + meta = { + "run_id": rid, + "hostname": host.get("hostname"), + "cpu_brand": host.get("cpu_brand"), + "is_rpi": host.get("is_rpi"), + "is_baseline_grade": run.get("is_baseline_grade"), + "source_file": run.get("_source_file"), + } + for k in run.get("kem", []): + if not k.get("enabled"): + continue + for op, st in (k.get("operations") or {}).items(): + kem_rows.append({**meta, + "alg": k["alg"], "backend": k.get("backend"), + "classical": bool(k.get("classical")), + "nist_level": k.get("claimed_nist_level"), + "operation": op, + "median_ns": st.get("median"), "mad_ns": st.get("mad"), + "iqr_ns": st.get("iqr"), "min_ns": st.get("min"), + "stddev_ns": st.get("stddev"), "ops_per_sec": st.get("ops_per_sec"), + "sizes": k.get("sizes")}) + for s in run.get("sig", []): + if not s.get("enabled"): + continue + for op, st in (s.get("operations") or {}).items(): + sig_rows.append({**meta, + "alg": s["alg"], "backend": s.get("backend"), + "classical": bool(s.get("classical")), + "nist_level": s.get("claimed_nist_level"), + "operation": op, + "median_ns": st.get("median"), "mad_ns": st.get("mad"), + "iqr_ns": st.get("iqr"), "min_ns": st.get("min"), + "stddev_ns": st.get("stddev"), "ops_per_sec": st.get("ops_per_sec"), + "sizes": s.get("sizes")}) + tls = run.get("tls") or {} + for cell in (tls.get("matrix") or []): + if not cell.get("enabled"): + continue + tls_rows.append({**meta, + "label": cell.get("label"), "group": cell.get("group"), + "is_baseline_pair": cell.get("label") == (tls.get("baseline") or {}).get("label"), + "handshakes_per_sec": cell.get("handshakes_per_sec"), + "median_ns": (cell.get("handshake_latency_ns") or {}).get("median"), + "bytes_total": (cell.get("bytes_on_wire") or {}).get("total"), + "client_hello_bytes": cell.get("client_hello_bytes"), + "client_hello_fragmented": cell.get("client_hello_fragmented")}) + return kem_rows, sig_rows, tls_rows + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("inputs", nargs="+", help="results JSON files or globs") + ap.add_argument("-o", "--out", default="dashboard/data/merged.json") + args = ap.parse_args() + + paths = [] + for pat in args.inputs: + paths.extend(sorted(glob.glob(pat)) if any(c in pat for c in "*?[") else [pat]) + paths = [p for p in paths if os.path.isfile(p)] + if not paths: + sys.exit("no input files matched") + + runs = load_runs(paths) + kem_rows, sig_rows, tls_rows = flatten(runs) + + merged = { + "merged_schema": MERGED_SCHEMA, + "n_runs": len(runs), + "runs": [{ + "run_id": run_id(r), + "host": r.get("host"), + "is_baseline_grade": r.get("is_baseline_grade"), + "baseline_grade_reasons": r.get("baseline_grade_reasons", []), + "toolchain": r.get("toolchain"), + "cpu_features": r.get("cpu_features"), + "run": r.get("run"), + "thermal_summary": { + "temp_c": (r.get("thermal_trace") or {}).get("temp_c"), + "throttling_detected": (r.get("thermal_trace") or {}).get("throttling_detected"), + }, + "generated_utc": r.get("generated_utc"), + "source_file": r.get("_source_file"), + } for r in runs], + "kem": kem_rows, + "sig": sig_rows, + "tls": tls_rows, + } + + os.makedirs(os.path.dirname(args.out) or ".", exist_ok=True) + with open(args.out, "w") as f: + json.dump(merged, f, indent=2) + n_base = sum(1 for r in runs if r.get("is_baseline_grade")) + print(f"merged {len(runs)} run(s) -> {args.out} " + f"({n_base} baseline-grade, {len(runs)-n_base} smoke/other)") + print(f" kem rows: {len(kem_rows)} sig rows: {len(sig_rows)} tls rows: {len(tls_rows)}") + + +if __name__ == "__main__": + main() diff --git a/pq-bench-rpi5/analyze/plot.py b/pq-bench-rpi5/analyze/plot.py new file mode 100644 index 0000000..a1f7715 --- /dev/null +++ b/pq-bench-rpi5/analyze/plot.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +"""plot.py — export publication-ready PNGs from a merged dataset. + + python3 analyze/plot.py dashboard/data/merged.json -o analyze/png + +matplotlib is an OPTIONAL dependency. If it is not installed this prints a clear +hint and exits 0 (the HTML dashboard remains the primary, dependency-free view). + +By default it plots only baseline-grade (RPi5) runs so paper figures are never +polluted with macOS smoke data; pass --include-smoke to override. The classical +Logos baseline (X25519 / Ed25519) is drawn as a reference line on every chart. +""" +from __future__ import annotations +import argparse +import json +import os +import sys + +try: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt +except ImportError: + print("matplotlib not installed — skipping PNG export.\n" + " enable it in a project venv (keeps your system python clean):\n" + " python3 -m venv analyze/.venv\n" + " analyze/.venv/bin/pip install -r analyze/requirements.txt\n" + " analyze/.venv/bin/python analyze/plot.py dashboard/data/merged.json\n" + " (the HTML dashboard works without it)", file=sys.stderr) + sys.exit(0) + + +def median_ms(ns): + return (ns or 0) / 1e6 + + +def pick_runs(merged, include_smoke): + ids = set() + for r in merged.get("runs", []): + if include_smoke or r.get("is_baseline_grade"): + ids.add(r["run_id"]) + return ids + + +def grouped_bar_by_level(rows, op, title, outpath): + """Grouped bars of median latency per algorithm, grouped by NIST level.""" + data = [r for r in rows if r["operation"] == op] + if not data: + return + # one bar per algorithm; baseline highlighted + data.sort(key=lambda r: (r.get("nist_level") or 0, r["median_ns"])) + labels = [r["alg"] for r in data] + vals = [median_ms(r["median_ns"]) for r in data] + colors = ["#888" if r.get("classical") else "#3b6" for r in data] + + fig, ax = plt.subplots(figsize=(max(6, len(labels) * 0.55), 4)) + ax.bar(range(len(labels)), vals, color=colors) + # baseline reference line + base = next((r for r in data if r.get("classical")), None) + if base: + ax.axhline(median_ms(base["median_ns"]), color="#c33", ls="--", lw=1, + label=f"classical baseline ({base['alg']})") + ax.legend(fontsize=8) + ax.set_xticks(range(len(labels))) + ax.set_xticklabels(labels, rotation=45, ha="right", fontsize=8) + ax.set_ylabel("median latency (ms)") + ax.set_title(title) + ax.grid(axis="y", alpha=0.3) + fig.tight_layout() + fig.savefig(outpath, dpi=150) + plt.close(fig) + print("wrote", outpath) + + +def size_speed_scatter(rows, op, size_key, title, outpath): + data = [r for r in rows if r["operation"] == op and r.get("sizes")] + pts = [] + for r in data: + sz = (r["sizes"] or {}).get(size_key) + if sz: + pts.append((sz, median_ms(r["median_ns"]), r["alg"], r.get("classical"))) + if not pts: + return + fig, ax = plt.subplots(figsize=(7, 5)) + for sz, lat, alg, classical in pts: + ax.scatter(sz, lat, c="#c33" if classical else "#3b6", + s=60, edgecolors="k", linewidths=0.4, zorder=3) + ax.annotate(alg, (sz, lat), fontsize=7, xytext=(4, 3), + textcoords="offset points") + ax.set_xlabel(f"{size_key} size (bytes)") + ax.set_ylabel("median latency (ms)") + ax.set_title(title) + ax.grid(alpha=0.3) + fig.tight_layout() + fig.savefig(outpath, dpi=150) + plt.close(fig) + print("wrote", outpath) + + +def tls_bar(tls_rows, outpath): + if not tls_rows: + return + tls_rows = sorted(tls_rows, key=lambda r: -(r.get("handshakes_per_sec") or 0)) + labels = [r["label"] for r in tls_rows] + vals = [r.get("handshakes_per_sec") or 0 for r in tls_rows] + colors = ["#c33" if r.get("is_baseline_pair") else "#36c" for r in tls_rows] + fig, ax = plt.subplots(figsize=(max(6, len(labels) * 0.5), 4.5)) + ax.barh(range(len(labels)), vals, color=colors) + base = next((r for r in tls_rows if r.get("is_baseline_pair")), None) + if base: + ax.axvline(base.get("handshakes_per_sec") or 0, color="#c33", ls="--", lw=1, + label=f"classical baseline ({base['label']})") + ax.legend(fontsize=8) + ax.set_yticks(range(len(labels))) + ax.set_yticklabels(labels, fontsize=7) + ax.invert_yaxis() + ax.set_xlabel("handshakes / sec") + ax.set_title("TLS 1.3 handshake throughput (higher is better)") + ax.grid(axis="x", alpha=0.3) + fig.tight_layout() + fig.savefig(outpath, dpi=150) + plt.close(fig) + print("wrote", outpath) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("merged") + ap.add_argument("-o", "--outdir", default="analyze/png") + ap.add_argument("--include-smoke", action="store_true") + args = ap.parse_args() + + with open(args.merged) as f: + merged = json.load(f) + ids = pick_runs(merged, args.include_smoke) + if not ids: + print("no baseline-grade runs to plot (use --include-smoke for macOS/dev data)", + file=sys.stderr) + return + + def keep(rows): + return [r for r in rows if r["run_id"] in ids] + + kem, sig, tls = keep(merged["kem"]), keep(merged["sig"]), keep(merged["tls"]) + os.makedirs(args.outdir, exist_ok=True) + O = args.outdir + + grouped_bar_by_level(kem, "keygen", "KEM keygen latency by algorithm", f"{O}/kem_keygen.png") + grouped_bar_by_level(kem, "encaps", "KEM encapsulation latency", f"{O}/kem_encaps.png") + grouped_bar_by_level(kem, "decaps", "KEM decapsulation latency", f"{O}/kem_decaps.png") + grouped_bar_by_level(sig, "sign", "Signature signing latency", f"{O}/sig_sign.png") + grouped_bar_by_level(sig, "verify", "Signature verification latency", f"{O}/sig_verify.png") + size_speed_scatter(kem, "encaps", "public_key", + "KEM: public-key size vs encaps latency", f"{O}/kem_size_speed.png") + size_speed_scatter(sig, "sign", "signature", + "Signature: signature size vs sign latency", f"{O}/sig_size_speed.png") + tls_bar(tls, f"{O}/tls_throughput.png") + print(f"PNGs in {O}/ ({'incl. smoke' if args.include_smoke else 'baseline-grade only'})") + + +if __name__ == "__main__": + main() diff --git a/pq-bench-rpi5/analyze/requirements.txt b/pq-bench-rpi5/analyze/requirements.txt new file mode 100644 index 0000000..3cf9f81 --- /dev/null +++ b/pq-bench-rpi5/analyze/requirements.txt @@ -0,0 +1 @@ +matplotlib>=3.7 diff --git a/pq-bench-rpi5/bench/kem_sig/Makefile b/pq-bench-rpi5/bench/kem_sig/Makefile new file mode 100644 index 0000000..6561049 --- /dev/null +++ b/pq-bench-rpi5/bench/kem_sig/Makefile @@ -0,0 +1,26 @@ +# Build the primitive KEM/sig harness against the pinned liboqs + system OpenSSL. +# +# Paths/flags default to the vendored toolchain but are overridable so run.sh can +# pass the exact values from setup/versions.lock (single source of truth): +# make LIBOQS_PREFIX=... OPENSSL_PREFIX=... BENCH_CFLAGS="-O3 -mcpu=cortex-a76" + +ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))../..) +LIBOQS_PREFIX ?= $(ROOT)/vendor/install +# OpenSSL: prefer Homebrew openssl@3 on macOS, else system. +OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null || echo /usr) +BENCH_CFLAGS ?= -O3 + +CC ?= cc +CFLAGS := $(BENCH_CFLAGS) -std=c11 -Wall -Wextra \ + -I$(LIBOQS_PREFIX)/include -I$(OPENSSL_PREFIX)/include +LDFLAGS := -L$(LIBOQS_PREFIX)/lib -L$(OPENSSL_PREFIX)/lib \ + -Wl,-rpath,$(LIBOQS_PREFIX)/lib -Wl,-rpath,$(OPENSSL_PREFIX)/lib +LDLIBS := -loqs -lcrypto -lm + +bench_pq: bench_pq.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS) + +clean: + rm -f bench_pq + +.PHONY: clean diff --git a/pq-bench-rpi5/bench/kem_sig/bench_pq.c b/pq-bench-rpi5/bench/kem_sig/bench_pq.c new file mode 100644 index 0000000..1e740bb --- /dev/null +++ b/pq-bench-rpi5/bench/kem_sig/bench_pq.c @@ -0,0 +1,656 @@ +/* =========================================================================== + * bench_pq.c — primitive-level KEM / signature benchmark harness. + * + * One algorithm per invocation (fresh process keeps cache state clean): + * bench_pq --kind kem --alg ML-KEM-768 --warmup 1000 --iters 10000 --reps 5 + * bench_pq --kind sig --alg ML-DSA-65 ... + * + * Emits a single JSON object describing the algorithm to stdout. The orchestrator + * (run.sh / assemble.py) wraps these with environment metadata. + * + * Two backends, selected by algorithm name: + * - liboqs : all PQ candidates (ML-KEM, ML-DSA, Falcon, SLH-DSA, ...) + * - OpenSSL EVP : the classical Logos baselines X25519 (KEM-analog) and + * Ed25519 (signature), which liboqs does not implement. + * This lets the classical reference be drawn on the same primitive charts. + * + * Metrics per operation: full per-iteration wall-clock nanosecond distribution + * -> median, MAD, IQR, min, max, mean, stddev, ops/sec, plus per-repetition + * medians. Optional userspace PMU cycle counts when available. Heap high-water + * via mallinfo2 on glibc (the RPi5 target); honestly reported unavailable + * elsewhere (e.g. the macOS smoke box). + * ===========================================================================*/ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#if defined(__linux__) && defined(__GLIBC__) +#include +#define HAVE_MALLINFO2 1 +#endif + +/* ---- timing ------------------------------------------------------------- */ +static inline uint64_t now_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; +} + +/* ---- userspace PMU cycle counter probe (aarch64) ------------------------ */ +static sigjmp_buf g_sigill_jmp; +static volatile sig_atomic_t g_pmu_ok = 0; +static void sigill_handler(int sig) { (void)sig; siglongjmp(g_sigill_jmp, 1); } + +static inline uint64_t read_cycles(void) { +#if defined(__aarch64__) + uint64_t v; + __asm__ volatile("mrs %0, pmccntr_el0" : "=r"(v)); + return v; +#else + return 0; +#endif +} + +/* Returns 1 and a reason string if userspace cycle counting works. */ +static int probe_pmu(const char **reason) { +#if defined(__aarch64__) + struct sigaction sa, old; + memset(&sa, 0, sizeof sa); + sa.sa_handler = sigill_handler; + sigaction(SIGILL, &sa, &old); + if (sigsetjmp(g_sigill_jmp, 1) == 0) { + (void)read_cycles(); + g_pmu_ok = 1; + } else { + g_pmu_ok = 0; + } + sigaction(SIGILL, &old, NULL); + if (g_pmu_ok) { *reason = "PMCCNTR_EL0 readable from userspace"; return 1; } + *reason = "PMCCNTR_EL0 traps (kernel module not loaded; needs e.g. enable_arm_pmu)"; + return 0; +#else + *reason = "not aarch64"; + return 0; +#endif +} + +/* ---- statistics --------------------------------------------------------- */ +typedef struct { + double median, mad, iqr, q1, q3, min, max, mean, stddev; + double ops_per_sec; + uint64_t n; +} stats_t; + +static int cmp_u64(const void *a, const void *b) { + uint64_t x = *(const uint64_t *)a, y = *(const uint64_t *)b; + return (x > y) - (x < y); +} + +/* percentile on an already-sorted array (linear interpolation) */ +static double pct_sorted(const uint64_t *s, uint64_t n, double p) { + if (n == 0) return 0; + if (n == 1) return (double)s[0]; + double idx = p * (double)(n - 1); + uint64_t lo = (uint64_t)idx; + double frac = idx - (double)lo; + if (lo + 1 >= n) return (double)s[n - 1]; + return (double)s[lo] + frac * ((double)s[lo + 1] - (double)s[lo]); +} + +static stats_t compute_stats(uint64_t *samples, uint64_t n) { + stats_t st; memset(&st, 0, sizeof st); + st.n = n; + if (n == 0) return st; + qsort(samples, n, sizeof(uint64_t), cmp_u64); + st.min = (double)samples[0]; + st.max = (double)samples[n - 1]; + st.median = pct_sorted(samples, n, 0.5); + st.q1 = pct_sorted(samples, n, 0.25); + st.q3 = pct_sorted(samples, n, 0.75); + st.iqr = st.q3 - st.q1; + + double sum = 0; + for (uint64_t i = 0; i < n; i++) sum += (double)samples[i]; + st.mean = sum / (double)n; + double ss = 0; + for (uint64_t i = 0; i < n; i++) { + double d = (double)samples[i] - st.mean; + ss += d * d; + } + st.stddev = (n > 1) ? sqrt(ss / (double)(n - 1)) : 0; + + /* MAD = median(|x - median|); needs a second sorted buffer */ + uint64_t *dev = malloc(n * sizeof(uint64_t)); + if (dev) { + for (uint64_t i = 0; i < n; i++) { + double d = (double)samples[i] - st.median; + dev[i] = (uint64_t)(d < 0 ? -d : d); + } + qsort(dev, n, sizeof(uint64_t), cmp_u64); + st.mad = pct_sorted(dev, n, 0.5); + free(dev); + } + st.ops_per_sec = st.median > 0 ? 1e9 / st.median : 0; + return st; +} + +/* ---- measurement configuration + generic loop --------------------------- */ +/* A closure-ish: the caller provides a function pointer that runs one op. */ +typedef int (*op_fn)(void *ctx); + +/* How each op is sized. Two modes, decided per invocation: + * - fixed-count (fixed_iters > 0): exactly fixed_iters timed iters per rep, + * with the configured fixed-mode warmup. This is the explicit + * `--iters N` override / fixed-count fallback. + * - auto-calibrate(fixed_iters == 0): each op is timed long enough to put + * ~target_ns of aggregate timed work on it, so a 18 us keygen + * and a 750 ms SLH-DSA sign both yield a stable median. The + * chosen count is clamped to [min_samples, max_iters]: + * fast ops hit max_iters, slow ops hit min_samples. + * Auto mode estimates per-op cost with a short doubling calibration (which also + * serves as cache warmup), then derives the timed count + a per-rep re-warm. */ +typedef struct { + uint64_t fixed_iters; /* >0 => fixed-count mode; 0 => auto-calibrate */ + uint64_t target_ns; /* auto: per-op aggregate timed-work target */ + uint64_t min_samples; /* auto: floor on timed iters per rep */ + uint64_t max_iters; /* auto: ceiling on timed iters per rep */ + uint64_t warmup; /* fixed-mode warmup count per rep */ + uint64_t reps; +} bench_cfg; + +typedef struct { + uint64_t *all; /* all samples across reps */ + uint64_t all_n; + double per_rep_median[64]; + int n_rep_med; + uint64_t timed_iters; /* timed iters per rep actually used */ + uint64_t warmup_iters; /* warmup iters per rep actually used */ + uint64_t reps; + int calibrated; /* 1 if auto-calibrated, 0 if fixed-count */ + double est_ns; /* per-op cost estimate from calibration (auto) */ +} measure_out; + +static void print_stats_json(FILE *f, const char *name, stats_t st, + const measure_out *m) { + fprintf(f, "\"%s\":{", name); + fprintf(f, "\"unit\":\"ns\",\"warmup_iters\":%llu,\"timed_iters\":%llu,\"repetitions\":%llu,", + (unsigned long long)m->warmup_iters, (unsigned long long)m->timed_iters, + (unsigned long long)m->reps); + fprintf(f, "\"calibrated\":%s,", m->calibrated ? "true" : "false"); + if (m->calibrated) + fprintf(f, "\"calib_est_ns\":%.2f,", m->est_ns); + fprintf(f, "\"samples\":%llu,", (unsigned long long)st.n); + fprintf(f, "\"median\":%.2f,\"mad\":%.2f,\"iqr\":%.2f,\"q1\":%.2f,\"q3\":%.2f,", + st.median, st.mad, st.iqr, st.q1, st.q3); + fprintf(f, "\"min\":%.2f,\"max\":%.2f,\"mean\":%.2f,\"stddev\":%.2f,", + st.min, st.max, st.mean, st.stddev); + fprintf(f, "\"ops_per_sec\":%.2f,", st.ops_per_sec); + fprintf(f, "\"per_rep_median\":["); + for (int i = 0; i < m->n_rep_med; i++) + fprintf(f, "%s%.2f", i ? "," : "", m->per_rep_median[i]); + fprintf(f, "]}"); +} + +/* Pick the timed-iter count (and per-rep warmup) for one op under cfg. + * In auto mode this runs a doubling calibration loop on fn (which warms caches) + * to estimate per-op cost, then solves for the count that hits target_ns. + * Returns 0 on success, -2 if the op ever fails during calibration. */ +static int calibrate_op(op_fn fn, void *ctx, const bench_cfg *cfg, + uint64_t *timed_out, uint64_t *warm_out, + double *est_ns_out, int *calibrated_out) { + if (cfg->fixed_iters > 0) { + *timed_out = cfg->fixed_iters; + *warm_out = cfg->warmup; + *est_ns_out = 0.0; + *calibrated_out = 0; + return 0; + } + /* doubling calibration: run batches 1,2,4,... until ~CALIB_BUDGET elapses, + * capped at max_iters so a sub-microsecond op can't spin forever. */ + const uint64_t CALIB_BUDGET_NS = 30ull * 1000 * 1000; /* 30 ms */ + uint64_t cops = 0, cel = 0, batch = 1; + while (cel < CALIB_BUDGET_NS && cops < cfg->max_iters) { + uint64_t t0 = now_ns(); + for (uint64_t i = 0; i < batch; i++) + if (fn(ctx) != 0) return -2; + cel += now_ns() - t0; + cops += batch; + batch *= 2; + } + double est_ns = cops ? (double)cel / (double)cops : 1.0; + if (est_ns < 1.0) est_ns = 1.0; + + double want = (double)cfg->target_ns / est_ns; + uint64_t n = (uint64_t)(want + 0.5); + if (n < cfg->min_samples) n = cfg->min_samples; /* slow ops floor here */ + if (n > cfg->max_iters) n = cfg->max_iters; /* fast ops ceil here */ + + /* per-rep re-warm ~= 20% of the timed budget, at least 1, capped. */ + double w = ((double)cfg->target_ns * 0.2) / est_ns; + uint64_t warm = (uint64_t)(w + 0.5); + if (warm < 1) warm = 1; + if (warm > cfg->max_iters) warm = cfg->max_iters; + + *timed_out = n; + *warm_out = warm; + *est_ns_out = est_ns; + *calibrated_out = 1; + return 0; +} + +/* Returns 0 on success. Fills out with samples. Re-warms before each rep. */ +static int measure_op(op_fn fn, void *ctx, const bench_cfg *cfg, measure_out *out) { + uint64_t iters, warmup; + double est_ns; int calibrated; + if (calibrate_op(fn, ctx, cfg, &iters, &warmup, &est_ns, &calibrated) != 0) + return -2; + + out->all = malloc(iters * cfg->reps * sizeof(uint64_t)); + if (!out->all) return -1; + out->all_n = 0; + out->n_rep_med = 0; + out->timed_iters = iters; + out->warmup_iters = warmup; + out->reps = cfg->reps; + out->calibrated = calibrated; + out->est_ns = est_ns; + uint64_t *rep_buf = malloc(iters * sizeof(uint64_t)); + if (!rep_buf) { free(out->all); return -1; } + + for (uint64_t r = 0; r < cfg->reps; r++) { + for (uint64_t i = 0; i < warmup; i++) + if (fn(ctx) != 0) { free(rep_buf); free(out->all); return -2; } + for (uint64_t i = 0; i < iters; i++) { + uint64_t t0 = now_ns(); + int rc = fn(ctx); + uint64_t dt = now_ns() - t0; + if (rc != 0) { free(rep_buf); free(out->all); return -2; } + rep_buf[i] = dt; + out->all[out->all_n++] = dt; + } + /* per-rep median (sorts a copy of this rep's slice) */ + uint64_t *copy = malloc(iters * sizeof(uint64_t)); + if (copy) { + memcpy(copy, rep_buf, iters * sizeof(uint64_t)); + qsort(copy, iters, sizeof(uint64_t), cmp_u64); + if (out->n_rep_med < 64) + out->per_rep_median[out->n_rep_med++] = pct_sorted(copy, iters, 0.5); + free(copy); + } + } + free(rep_buf); + return 0; +} + +/* ---- anti-DCE sink + fatal helpers -------------------------------------- */ +/* File-scope volatile: the compiler must materialize every store, so it cannot + * dead-code-eliminate the crypto outputs we feed into it. Every timed op sinks + * one output byte here. */ +static volatile uint64_t g_sink = 0; + +/* Abort the whole run: a broken build must NEVER silently emit timing numbers. */ +static void die(const char *alg, const char *what) { + fprintf(stderr, "[bench_pq] FATAL: %s: %s — aborting so no numbers are emitted\n", + alg, what); + exit(3); +} + +/* measure_op wrapper: if any timed op ever returns failure (e.g. a verify that + * stopped succeeding), abort instead of reading freed/partial buffers. */ +static void must_measure(const char *alg, const char *op, op_fn fn, void *ctx, + const bench_cfg *cfg, measure_out *out) { + if (measure_op(fn, ctx, cfg, out) != 0) + die(alg, op); +} + +#define MSGLEN 32 + +/* ======================= liboqs KEM ====================================== */ +/* Timed ops consume canonical, pre-validated inputs; keygen/encaps write to + * scratch buffers so they never clobber the matched (pk,sk,ct) that the other + * timed ops depend on. Each op sinks an output byte into g_sink. */ +typedef struct { + OQS_KEM *kem; + uint8_t *pk, *sk; /* canonical matched keypair (encaps/decaps inputs) */ + uint8_t *ct; /* canonical ciphertext (decaps input) */ + uint8_t *pk_s, *sk_s; /* scratch: keygen outputs */ + uint8_t *ct_s, *ss_s; /* scratch: encaps outputs */ + uint8_t *ss_d; /* scratch: decaps output */ +} kem_ctx; +static int kem_keygen(void *c){ kem_ctx*x=c; + if (OQS_KEM_keypair(x->kem, x->pk_s, x->sk_s) != OQS_SUCCESS) return 1; + g_sink += x->pk_s[0]; return 0; } +static int kem_encaps(void *c){ kem_ctx*x=c; + if (OQS_KEM_encaps(x->kem, x->ct_s, x->ss_s, x->pk) != OQS_SUCCESS) return 1; + g_sink += (uint64_t)x->ss_s[0] ^ x->ct_s[0]; return 0; } +static int kem_decaps(void *c){ kem_ctx*x=c; + if (OQS_KEM_decaps(x->kem, x->ss_d, x->ct, x->sk) != OQS_SUCCESS) return 1; + g_sink += x->ss_d[0]; return 0; } + +static int run_kem(const char *alg, const bench_cfg *cfg) { + OQS_KEM *kem = OQS_KEM_new(alg); + if (!kem) { + printf("{\"alg\":\"%s\",\"kind\":\"kem\",\"backend\":\"liboqs\",\"enabled\":false," + "\"reason\":\"not enabled in this liboqs build\"}\n", alg); + return 0; + } + kem_ctx x; memset(&x, 0, sizeof x); x.kem = kem; + x.pk = malloc(kem->length_public_key); x.sk = malloc(kem->length_secret_key); + x.ct = malloc(kem->length_ciphertext); + x.pk_s = malloc(kem->length_public_key); x.sk_s = malloc(kem->length_secret_key); + x.ct_s = malloc(kem->length_ciphertext); + x.ss_s = malloc(kem->length_shared_secret); + x.ss_d = malloc(kem->length_shared_secret); + if (!x.pk||!x.sk||!x.ct||!x.pk_s||!x.sk_s||!x.ct_s||!x.ss_s||!x.ss_d) die(alg,"out of memory"); + + /* ---- correctness check (ONCE, outside the timed loop) ---- + * keygen -> encaps -> decaps, then assert the shared secrets match. */ + if (OQS_KEM_keypair(kem, x.pk, x.sk) != OQS_SUCCESS) die(alg, "keygen failed"); + if (OQS_KEM_encaps(kem, x.ct, x.ss_s, x.pk) != OQS_SUCCESS) die(alg, "encaps failed"); + if (OQS_KEM_decaps(kem, x.ss_d, x.ct, x.sk) != OQS_SUCCESS) die(alg, "decaps failed"); + if (memcmp(x.ss_s, x.ss_d, kem->length_shared_secret) != 0) + die(alg, "KEM shared-secret mismatch (ss_encaps != ss_decaps)"); + + /* timed phases run on the canonical, validated (pk,sk,ct); any op failure + * during timing aborts via must_measure. */ + measure_out kg={0}, en={0}, de={0}; + must_measure(alg,"keygen",kem_keygen,&x,cfg,&kg); + must_measure(alg,"encaps",kem_encaps,&x,cfg,&en); + must_measure(alg,"decaps",kem_decaps,&x,cfg,&de); + + printf("{\"alg\":\"%s\",\"kind\":\"kem\",\"backend\":\"liboqs\",\"enabled\":true,", alg); + printf("\"claimed_nist_level\":%d,", kem->claimed_nist_level); + printf("\"sizes\":{\"public_key\":%zu,\"secret_key\":%zu,\"ciphertext\":%zu,\"shared_secret\":%zu},", + kem->length_public_key, kem->length_secret_key, kem->length_ciphertext, kem->length_shared_secret); + printf("\"operations\":{"); + stats_t s; + s=compute_stats(kg.all,kg.all_n); print_stats_json(stdout,"keygen",s,&kg); printf(","); + s=compute_stats(en.all,en.all_n); print_stats_json(stdout,"encaps",s,&en); printf(","); + s=compute_stats(de.all,de.all_n); print_stats_json(stdout,"decaps",s,&de); + printf("}}\n"); + + free(kg.all); free(en.all); free(de.all); + free(x.pk); free(x.sk); free(x.ct); + free(x.pk_s); free(x.sk_s); free(x.ct_s); free(x.ss_s); free(x.ss_d); + OQS_KEM_free(kem); + return 0; +} + +/* ======================= liboqs SIG ====================================== */ +/* sign writes a scratch signature; verify reads the canonical (sg,sglen) over + * the canonical msg with the canonical pk — all pre-validated. */ +typedef struct { + OQS_SIG *sig; + uint8_t *pk, *sk; /* canonical keypair (sign/verify inputs) */ + uint8_t *msg; /* canonical message */ + uint8_t *sg; size_t sglen; /* canonical signature (verify input) */ + uint8_t *pk_s, *sk_s; /* scratch: keygen outputs */ + uint8_t *sg_s; size_t sg_s_len;/* scratch: sign output */ +} sig_ctx; +static int sig_keygen(void *c){ sig_ctx*x=c; + if (OQS_SIG_keypair(x->sig, x->pk_s, x->sk_s) != OQS_SUCCESS) return 1; + g_sink += x->pk_s[0]; return 0; } +static int sig_sign(void *c){ sig_ctx*x=c; + x->sg_s_len = x->sig->length_signature; + if (OQS_SIG_sign(x->sig, x->sg_s, &x->sg_s_len, x->msg, MSGLEN, x->sk) != OQS_SUCCESS) return 1; + g_sink += x->sg_s[0]; return 0; } +static int sig_verify(void *c){ sig_ctx*x=c; + if (OQS_SIG_verify(x->sig, x->msg, MSGLEN, x->sg, x->sglen, x->pk) != OQS_SUCCESS) return 1; + g_sink += 1; return 0; } + +static int run_sig(const char *alg, const bench_cfg *cfg) { + OQS_SIG *sig = OQS_SIG_new(alg); + if (!sig) { + printf("{\"alg\":\"%s\",\"kind\":\"sig\",\"backend\":\"liboqs\",\"enabled\":false," + "\"reason\":\"not enabled in this liboqs build\"}\n", alg); + return 0; + } + sig_ctx x; memset(&x,0,sizeof x); x.sig=sig; + x.pk = malloc(sig->length_public_key); x.sk = malloc(sig->length_secret_key); + x.msg = malloc(MSGLEN); + x.sg = malloc(sig->length_signature); + x.pk_s = malloc(sig->length_public_key); x.sk_s = malloc(sig->length_secret_key); + x.sg_s = malloc(sig->length_signature); + if (!x.pk||!x.sk||!x.msg||!x.sg||!x.pk_s||!x.sk_s||!x.sg_s) die(alg,"out of memory"); + memset(x.msg, 0xA5, MSGLEN); + + /* ---- correctness check (ONCE, outside the timed loop) ---- + * keygen -> sign -> verify; the verify MUST succeed on a valid signature. */ + if (OQS_SIG_keypair(sig, x.pk, x.sk) != OQS_SUCCESS) die(alg, "keygen failed"); + x.sglen = sig->length_signature; + if (OQS_SIG_sign(sig, x.sg, &x.sglen, x.msg, MSGLEN, x.sk) != OQS_SUCCESS) die(alg, "sign failed"); + if (OQS_SIG_verify(sig, x.msg, MSGLEN, x.sg, x.sglen, x.pk) != OQS_SUCCESS) + die(alg, "signature verify failed on a valid signature (broken build)"); + + measure_out kg={0}, sg={0}, vf={0}; + must_measure(alg,"keygen",sig_keygen,&x,cfg,&kg); + must_measure(alg,"sign", sig_sign, &x,cfg,&sg); + must_measure(alg,"verify",sig_verify,&x,cfg,&vf); + + printf("{\"alg\":\"%s\",\"kind\":\"sig\",\"backend\":\"liboqs\",\"enabled\":true,", alg); + printf("\"claimed_nist_level\":%d,", sig->claimed_nist_level); + printf("\"sizes\":{\"public_key\":%zu,\"secret_key\":%zu,\"signature\":%zu},", + sig->length_public_key, sig->length_secret_key, sig->length_signature); + printf("\"operations\":{"); + stats_t s; + s=compute_stats(kg.all,kg.all_n); print_stats_json(stdout,"keygen",s,&kg); printf(","); + s=compute_stats(sg.all,sg.all_n); print_stats_json(stdout,"sign",s,&sg); printf(","); + s=compute_stats(vf.all,vf.all_n); print_stats_json(stdout,"verify",s,&vf); + printf("}}\n"); + + free(kg.all); free(sg.all); free(vf.all); + free(x.pk); free(x.sk); free(x.msg); free(x.sg); + free(x.pk_s); free(x.sk_s); free(x.sg_s); + OQS_SIG_free(sig); + return 0; +} + +/* ======================= OpenSSL classical baselines ===================== */ +/* X25519 as a KEM-analog: keygen + ECDH derive (one shared-secret derivation). */ +typedef struct { EVP_PKEY *self; EVP_PKEY *peer; } x25519_ctx; +static int x25519_keygen(void *c){ + x25519_ctx*x=c; + if (x->self) { EVP_PKEY_free(x->self); x->self=NULL; } + EVP_PKEY_CTX *p = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519,NULL); + if(!p) return 1; + int ok = EVP_PKEY_keygen_init(p)>0 && EVP_PKEY_keygen(p,&x->self)>0; + EVP_PKEY_CTX_free(p); + return ok?0:1; +} +/* derive shared secret a·b into out[32]; returns 1 on success */ +static int x25519_derive_into(EVP_PKEY *a, EVP_PKEY *b, unsigned char out[32]){ + EVP_PKEY_CTX *p = EVP_PKEY_CTX_new(a,NULL); + if(!p) return 0; + size_t slen=32; + int ok = EVP_PKEY_derive_init(p)>0 && + EVP_PKEY_derive_set_peer(p,b)>0 && + EVP_PKEY_derive(p,out,&slen)>0; + EVP_PKEY_CTX_free(p); + return ok; +} +static int x25519_derive(void *c){ + x25519_ctx*x=c; + unsigned char secret[32]; + if(!x25519_derive_into(x->self,x->peer,secret)) return 1; + g_sink += secret[0]; /* sink the derived shared secret */ + return 0; +} + +static int run_x25519(const bench_cfg *cfg) { + x25519_ctx x={0}; + if (x25519_keygen(&x) != 0) die("X25519","keygen failed"); /* self */ + /* a fixed peer key for derive */ + EVP_PKEY_CTX *p = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519,NULL); + if(!p || EVP_PKEY_keygen_init(p)<=0 || EVP_PKEY_keygen(p,&x.peer)<=0) die("X25519","peer keygen failed"); + EVP_PKEY_CTX_free(p); + + /* ---- correctness check (ONCE, outside timing): ECDH must agree ---- */ + { + unsigned char sa[32], sb[32]; + if (!x25519_derive_into(x.self, x.peer, sa)) die("X25519","derive(self,peer) failed"); + if (!x25519_derive_into(x.peer, x.self, sb)) die("X25519","derive(peer,self) failed"); + if (memcmp(sa, sb, 32) != 0) die("X25519","ECDH shared-secret mismatch"); + } + + measure_out kg={0}, dv={0}; + must_measure("X25519","keygen",x25519_keygen,&x,cfg,&kg); + /* keygen frees+replaces self each call; re-make a stable self for derive */ + if (x25519_keygen(&x) != 0) die("X25519","keygen failed"); + must_measure("X25519","derive",x25519_derive,&x,cfg,&dv); + + printf("{\"alg\":\"X25519\",\"kind\":\"kem\",\"backend\":\"openssl\",\"classical\":true,\"enabled\":true,"); + printf("\"claimed_nist_level\":1,"); + printf("\"sizes\":{\"public_key\":32,\"secret_key\":32,\"ciphertext\":null,\"shared_secret\":32},"); + printf("\"operations\":{"); + stats_t s; + s=compute_stats(kg.all,kg.all_n); print_stats_json(stdout,"keygen",s,&kg); printf(","); + s=compute_stats(dv.all,dv.all_n); print_stats_json(stdout,"derive",s,&dv); + printf("}}\n"); + free(kg.all); free(dv.all); + if(x.self)EVP_PKEY_free(x.self); if(x.peer)EVP_PKEY_free(x.peer); + return 0; +} + +/* Ed25519 signature baseline. */ +typedef struct { EVP_PKEY *key; unsigned char msg[32]; unsigned char sig[64]; size_t siglen; } ed_ctx; +static int ed_keygen(void *c){ + ed_ctx*x=c; + if(x->key){EVP_PKEY_free(x->key);x->key=NULL;} + EVP_PKEY_CTX *p=EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519,NULL); + if(!p)return 1; + int ok=EVP_PKEY_keygen_init(p)>0 && EVP_PKEY_keygen(p,&x->key)>0; + EVP_PKEY_CTX_free(p); + return ok?0:1; +} +static int ed_sign(void *c){ + ed_ctx*x=c; + EVP_MD_CTX *m=EVP_MD_CTX_new(); if(!m)return 1; + x->siglen=sizeof x->sig; + int ok = EVP_DigestSignInit(m,NULL,NULL,NULL,x->key)>0 && + EVP_DigestSign(m,x->sig,&x->siglen,x->msg,sizeof x->msg)>0; + EVP_MD_CTX_free(m); + if(!ok) return 1; + g_sink += x->sig[0]; /* sink the signature */ + return 0; +} +static int ed_verify(void *c){ + ed_ctx*x=c; + EVP_MD_CTX *m=EVP_MD_CTX_new(); if(!m)return 1; + int ok = EVP_DigestVerifyInit(m,NULL,NULL,NULL,x->key)>0 && + EVP_DigestVerify(m,x->sig,x->siglen,x->msg,sizeof x->msg)>0; + EVP_MD_CTX_free(m); + if(!ok) return 1; + g_sink += 1; /* sink the (successful) verify result */ + return 0; +} + +static int run_ed25519(const bench_cfg *cfg) { + ed_ctx x; memset(&x,0,sizeof x); memset(x.msg,0xA5,sizeof x.msg); + + /* ---- correctness check (ONCE, outside timing): verify MUST succeed ---- */ + if (ed_keygen(&x) != 0) die("Ed25519","keygen failed"); + if (ed_sign(&x) != 0) die("Ed25519","sign failed"); + if (ed_verify(&x) != 0) die("Ed25519","verify failed on a valid signature (broken build)"); + + measure_out kg={0}, sg={0}, vf={0}; + must_measure("Ed25519","keygen",ed_keygen,&x,cfg,&kg); + if (ed_keygen(&x)!=0 || ed_sign(&x)!=0) die("Ed25519","re-priming key+sig failed"); + must_measure("Ed25519","sign", ed_sign, &x,cfg,&sg); + must_measure("Ed25519","verify",ed_verify,&x,cfg,&vf); + + printf("{\"alg\":\"Ed25519\",\"kind\":\"sig\",\"backend\":\"openssl\",\"classical\":true,\"enabled\":true,"); + printf("\"claimed_nist_level\":1,"); + printf("\"sizes\":{\"public_key\":32,\"secret_key\":32,\"signature\":64},"); + printf("\"operations\":{"); + stats_t s; + s=compute_stats(kg.all,kg.all_n); print_stats_json(stdout,"keygen",s,&kg); printf(","); + s=compute_stats(sg.all,sg.all_n); print_stats_json(stdout,"sign",s,&sg); printf(","); + s=compute_stats(vf.all,vf.all_n); print_stats_json(stdout,"verify",s,&vf); + printf("}}\n"); + free(kg.all); free(sg.all); free(vf.all); + if(x.key)EVP_PKEY_free(x.key); + return 0; +} + +/* ---- main --------------------------------------------------------------- */ +static void usage(void){ + fprintf(stderr, + "usage: bench_pq --kind kem|sig --alg NAME [options]\n" + " auto-calibration (default): each op is timed to ~--target-time-ms of\n" + " aggregate work, clamped to [--min-samples, --max-iters].\n" + " --target-time-ms N per-op timed-work target (default 250)\n" + " --min-samples N floor on timed iters per rep (default 30)\n" + " --max-iters N ceiling on timed iters per rep (default 20000)\n" + " --reps N independent repetitions (default 5)\n" + " --iters N FIXED-count fallback: exactly N timed iters/rep\n" + " (disables calibration; pairs with --warmup)\n" + " --warmup N warmup iters/rep in fixed-count mode (default 1000)\n"); +} + +int main(int argc, char **argv) { + const char *kind=NULL, *alg=NULL; + /* fixed-count fallback knobs */ + uint64_t warmup=1000, iters=0, reps=5; /* iters=0 => auto-calibrate */ + /* auto-calibration knobs */ + uint64_t target_time_ms=250, min_samples=30, max_iters=20000; + for (int i=1;i0 => fixed-count mode */ + .target_ns = target_time_ms * 1000000ull, + .min_samples = min_samples, + .max_iters = max_iters, + .warmup = warmup, + .reps = reps, + }; + if (iters>0) + fprintf(stderr,"[bench_pq] mode=fixed-count reps=%llu warmup=%llu iters=%llu\n", + (unsigned long long)reps,(unsigned long long)warmup,(unsigned long long)iters); + else + fprintf(stderr,"[bench_pq] mode=auto-calibrate reps=%llu target=%llums min_samples=%llu max_iters=%llu\n", + (unsigned long long)reps,(unsigned long long)target_time_ms, + (unsigned long long)min_samples,(unsigned long long)max_iters); + + /* PMU cycle availability probe (reported once via stderr-free channel: + * embedded into the JSON header line below). */ + const char *pmu_reason=NULL; + int pmu_ok = probe_pmu(&pmu_reason); + fprintf(stderr,"[bench_pq] cycles_available=%d (%s)\n", pmu_ok, pmu_reason); + + OQS_init(); + int rc; + if(!strcmp(kind,"kem")){ + if(!strcmp(alg,"X25519")) rc=run_x25519(&cfg); + else rc=run_kem(alg,&cfg); + } else if(!strcmp(kind,"sig")){ + if(!strcmp(alg,"Ed25519")) rc=run_ed25519(&cfg); + else rc=run_sig(alg,&cfg); + } else { usage(); rc=2; } + OQS_destroy(); + return rc; +} diff --git a/pq-bench-rpi5/bench/lib/assemble.py b/pq-bench-rpi5/bench/lib/assemble.py new file mode 100644 index 0000000..9fc5c8b --- /dev/null +++ b/pq-bench-rpi5/bench/lib/assemble.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +"""assemble.py — merge harness outputs + thermal trace + environment metadata +into one results JSON with full provenance. + +Inputs (all paths): + --meta meta.env KEY=VALUE run/host facts collected by run.sh + --lock versions.lock toolchain provenance from setup.sh + --features cpu_features.json CPU/crypto-extension detection (from run.sh) + --kemsig kemsig.jsonl one JSON object per algorithm from bench_pq + --tls tls.json output of the TLS harness (optional) + --thermal thermal.csv epoch_s,arm_clock_hz,temp_c,throttled_hex samples + --out results/-.json + +The single most important output field is `is_baseline_grade`: true ONLY on a +real RPi5 with performance governor, core pinning, A76-targeted flags, and no +thermal throttling. Everything else (notably macOS smoke runs) is false, with +reasons recorded — so smoke output can never be mistaken for the baseline. +""" +from __future__ import annotations +import argparse +import json +import os +import statistics +import sys + +SCHEMA_VERSION = "1.0.0" + + +def parse_envfile(path: str) -> dict: + """Parse KEY=VALUE / KEY="value" lines (shared format of meta.env + versions.lock).""" + out = {} + if not path or not os.path.exists(path): + return out + with open(path) as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + k, v = line.split("=", 1) + v = v.strip() + if len(v) >= 2 and v[0] == v[-1] and v[0] in "\"'": + v = v[1:-1] + out[k.strip()] = v + return out + + +def load_jsonl(path: str) -> list: + items = [] + if not path or not os.path.exists(path): + return items + with open(path) as f: + for line in f: + line = line.strip() + if line: + try: + items.append(json.loads(line)) + except json.JSONDecodeError as e: + print(f"[assemble] skipping bad JSONL line: {e}", file=sys.stderr) + return items + + +def load_json(path: str): + if path and os.path.exists(path): + with open(path) as f: + return json.load(f) + return None + + +def parse_thermal(path: str) -> dict: + """Reduce the raw CSV trace to a compact embedded record + summary.""" + cols = ["epoch_s", "arm_clock_hz", "temp_c", "throttled_hex"] + samples, temps, clocks = [], [], [] + throttling_detected = False + if path and os.path.exists(path): + with open(path) as f: + for line in f: + parts = line.strip().split(",") + if len(parts) != 4: + continue + ep, clk, temp, thr = parts + samples.append([ + int(ep) if ep else None, + int(clk) if clk else None, + float(temp) if temp else None, + thr or None, + ]) + if temp: + temps.append(float(temp)) + if clk: + clocks.append(int(clk)) + if thr: + try: + v = int(thr, 16) + # bit2 = throttling now, bit18 = throttling has occurred + if v & 0x4 or v & 0x40000: + throttling_detected = True + except ValueError: + pass + + def summarize(vals): + if not vals: + return None + return { + "min": min(vals), "max": max(vals), + "mean": round(statistics.fmean(vals), 3), + "samples": len(vals), + } + + clock_summary = summarize(clocks) + # Detect frequency droop as a secondary throttling signal. + if clock_summary and clocks: + spread = (max(clocks) - min(clocks)) / max(clocks) + clock_summary["spread_frac"] = round(spread, 4) + + return { + "columns": cols, + "samples": samples, + "temp_c": summarize(temps), + "arm_clock_hz": clock_summary, + "throttling_detected": throttling_detected, + } + + +def to_int(s, default=None): + try: + return int(s) + except (TypeError, ValueError): + return default + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--meta", required=True) + ap.add_argument("--lock", default="") + ap.add_argument("--features", default="") + ap.add_argument("--kemsig", default="") + ap.add_argument("--tls", default="") + ap.add_argument("--thermal", default="") + ap.add_argument("--config", default="") + ap.add_argument("--out", required=True) + args = ap.parse_args() + + meta = parse_envfile(args.meta) + lock = parse_envfile(args.lock) + features = load_json(args.features) or {} + kemsig = load_jsonl(args.kemsig) + tls = load_json(args.tls) + thermal = parse_thermal(args.thermal) + + is_rpi = meta.get("IS_RPI") == "1" + governor = meta.get("GOVERNOR_AFTER") or meta.get("GOVERNOR_BEFORE") or "unknown" + pinned = meta.get("PINNED") == "1" + cflags_target = lock.get("CFLAGS_TARGET", "unknown") + + # ---- the anti-confusion gate ----------------------------------------- + baseline_reasons = [] + if not is_rpi: + baseline_reasons.append( + f"host is not a Raspberry Pi (model='{meta.get('RPI_MODEL','')}', os={meta.get('OS')})") + if governor != "performance": + baseline_reasons.append(f"CPU governor is '{governor}', not 'performance'") + if not pinned: + baseline_reasons.append("benchmark was not pinned to a dedicated core (no taskset)") + if cflags_target != "cortex-a76": + baseline_reasons.append(f"build flags targeted '{cflags_target}', not cortex-a76") + if thermal.get("throttling_detected"): + baseline_reasons.append("thermal throttling was detected during the run") + is_baseline_grade = len(baseline_reasons) == 0 + + warnings = [] + raw_warn = meta.get("WARNINGS", "") + if raw_warn: + warnings.extend([w for w in raw_warn.split("||") if w]) + if not is_baseline_grade: + warnings.append("NOT RPi5-baseline-grade: " + "; ".join(baseline_reasons)) + + result = { + "schema_version": SCHEMA_VERSION, + "tool_version": meta.get("TOOL_VERSION", "0.1.0"), + "generated_utc": meta.get("TS_END_UTC", ""), + "is_baseline_grade": is_baseline_grade, + "baseline_grade_reasons": baseline_reasons, + "host": { + "hostname": meta.get("HOSTNAME", ""), + "os": meta.get("OS", ""), + "os_pretty": meta.get("OS_PRETTY", ""), + "arch": meta.get("ARCH", ""), + "kernel": meta.get("KERNEL", ""), + "is_rpi": is_rpi, + "rpi_model": meta.get("RPI_MODEL", ""), + "cpu_brand": meta.get("CPU_BRAND", ""), + "ncpu": to_int(meta.get("NCPU")), + "ram_bytes": to_int(meta.get("RAM_BYTES")), + }, + "cpu_features": features, + "run": { + "timestamp_start_utc": meta.get("TS_START_UTC", ""), + "timestamp_end_utc": meta.get("TS_END_UTC", ""), + "duration_s": to_int(meta.get("DURATION_S")), + "governor_requested": meta.get("GOVERNOR_REQUESTED", ""), + "governor_before": meta.get("GOVERNOR_BEFORE", ""), + "governor_after": meta.get("GOVERNOR_AFTER", ""), + "bench_core": to_int(meta.get("BENCH_CORE")), + "pinned": pinned, + "taskset_cmd": meta.get("TASKSET_CMD", ""), + # Per-op sizing. In auto-calibration mode warmup_iters/timed_iters are + # chosen per operation (see each entry under kem/sig "operations"); + # the run-level target/min/max below describe how they were derived. + "calibration_mode": meta.get("CALIB_MODE", "auto"), + "target_time_ms": to_int(meta.get("TARGET_TIME_MS")), + "min_samples": to_int(meta.get("MIN_SAMPLES")), + "max_iters": to_int(meta.get("MAX_ITERS")), + "warmup_iters": to_int(meta.get("WARMUP")), + "timed_iters": to_int(meta.get("ITERS")), + "repetitions": to_int(meta.get("REPS")), + "cycles_mode": meta.get("CYCLES_MODE", ""), + "cycles_available": meta.get("CYCLES_AVAILABLE") == "1", + "cycles_reason": meta.get("CYCLES_REASON", ""), + }, + "toolchain": { + "cc_version": lock.get("CC_VERSION", ""), + "bench_cflags": lock.get("BENCH_CFLAGS", ""), + "cflags_target": cflags_target, + "liboqs_ref": lock.get("LIBOQS_REF", ""), + "liboqs_commit": lock.get("LIBOQS_COMMIT", ""), + "liboqs_opt_defines": lock.get("LIBOQS_OPT_DEFINES", ""), + "openssl": lock.get("OPENSSL_COMMIT", ""), + "oqsprovider_ref": lock.get("OQSPROVIDER_REF", ""), + "oqsprovider_commit": lock.get("OQSPROVIDER_COMMIT", ""), + }, + "thermal_trace": thermal, + "warnings": warnings, + "kem": [r for r in kemsig if r.get("kind") == "kem"], + "sig": [r for r in kemsig if r.get("kind") == "sig"], + "tls": tls, + } + + os.makedirs(os.path.dirname(args.out) or ".", exist_ok=True) + with open(args.out, "w") as f: + json.dump(result, f, indent=2) + print(args.out) + + +if __name__ == "__main__": + main() diff --git a/pq-bench-rpi5/bench/lib/list_algs.py b/pq-bench-rpi5/bench/lib/list_algs.py new file mode 100644 index 0000000..5dcbc63 --- /dev/null +++ b/pq-bench-rpi5/bench/lib/list_algs.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""list_algs.py — expand config.yaml into shell-consumable lines for run.sh. + +Modes: + measurement -> KEY=VALUE lines (auto-calib: target/min_samples/max_iters/ + reps/cycles_mode; plus warmup/iters fixed-count fallback) + kemsig -> one "kindalgis_classical" line per algorithm, + baselines first (so charts always have the reference point) + tls -> emits the TLS matrix as JSON on one line + +Uses the dependency-free miniyaml parser so no PyYAML is required. +""" +import os +import sys +import json + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import miniyaml # noqa: E402 + + +def main(): + mode = sys.argv[1] if len(sys.argv) > 1 else "kemsig" + cfg_path = sys.argv[2] if len(sys.argv) > 2 else "config.yaml" + cfg = miniyaml.load_file(cfg_path) + + if mode == "measurement": + m = cfg.get("measurement", {}) or {} + # auto-calibration knobs (default path) + print(f"TARGET_TIME_MS={m.get('target_time_ms', 250)}") + print(f"MIN_SAMPLES={m.get('min_samples', 30)}") + print(f"MAX_ITERS={m.get('max_iters', 20000)}") + print(f"REPS={m.get('repetitions', 5)}") + print(f"CYCLES_MODE={m.get('cycles_mode', 'auto')}") + # fixed-count fallback knobs (used only with ./run.sh --iters) + print(f"WARMUP={m.get('warmup_iters', 1000)}") + print(f"ITERS={m.get('timed_iters', 10000)}") + + elif mode == "kemsig": + for kind in ("kem", "sig"): + section = cfg.get(kind, {}) or {} + for grp in ("baseline", "candidates"): + for item in (section.get(grp) or []): + if isinstance(item, dict): + name = item.get("name") + classical = "1" if item.get("classical") else "0" + else: + name, classical = item, "0" + if name: + print(f"{kind}\t{name}\t{classical}") + + elif mode == "tls": + print(json.dumps(cfg.get("tls", {}))) + + else: + sys.exit(f"unknown mode: {mode}") + + +if __name__ == "__main__": + main() diff --git a/pq-bench-rpi5/bench/lib/miniyaml.py b/pq-bench-rpi5/bench/lib/miniyaml.py new file mode 100644 index 0000000..03ee6be --- /dev/null +++ b/pq-bench-rpi5/bench/lib/miniyaml.py @@ -0,0 +1,151 @@ +"""miniyaml — a dependency-free parser for the restricted YAML subset used by +config.yaml. + +We deliberately avoid a PyYAML runtime dependency so the core pipeline runs on a +stock Python 3 (the RPi5 / Mac smoke box both have only stdlib by default). + +Supported subset (sufficient for config.yaml): + - nested mappings via indentation (2 spaces per level by convention) + - lists of scalars: "- value" + - lists of mappings: "- key: value" then indented "key: value" + - scalars: int, float, bool (true/false), null/~, quoted or bare strings + - "# comment" to end of line (outside quotes) + +It is NOT a general YAML implementation. If a user needs full YAML they can +install PyYAML and set PQB_USE_PYYAML=1. +""" +from __future__ import annotations +import os +import re + + +def _scalar(tok: str): + s = tok.strip() + if s == "" or s in ("~", "null", "Null", "NULL"): + return None + if (s[0] == '"' and s[-1] == '"') or (s[0] == "'" and s[-1] == "'"): + return s[1:-1] + low = s.lower() + if low in ("true", "yes"): + return True + if low in ("false", "no"): + return False + if re.fullmatch(r"[+-]?\d+", s): + return int(s) + if re.fullmatch(r"[+-]?\d*\.\d+([eE][+-]?\d+)?", s): + return float(s) + return s + + +def _strip_comment(line: str) -> str: + out, q = [], None + for ch in line: + if q: + out.append(ch) + if ch == q: + q = None + elif ch in ("'", '"'): + q = ch + out.append(ch) + elif ch == "#": + break + else: + out.append(ch) + return "".join(out).rstrip() + + +def _indent(line: str) -> int: + return len(line) - len(line.lstrip(" ")) + + +def loads(text: str): + # Tokenize into (indent, content) ignoring blank/comment-only lines. + lines = [] + for raw in text.splitlines(): + c = _strip_comment(raw) + if c.strip() == "": + continue + lines.append((_indent(c), c.strip(), c)) + pos = [0] + + def parse_block(min_indent: int): + if pos[0] >= len(lines): + return None + indent = lines[pos[0]][0] + if lines[pos[0]][1].startswith("- "): + return parse_list(indent) + return parse_map(indent) + + def parse_map(indent: int): + obj = {} + while pos[0] < len(lines): + ind, stripped, _ = lines[pos[0]] + if ind < indent or stripped.startswith("- "): + break + if ind > indent: # malformed; skip + pos[0] += 1 + continue + m = re.match(r"^([^:]+):\s*(.*)$", stripped) + if not m: + pos[0] += 1 + continue + key, val = m.group(1).strip(), m.group(2) + pos[0] += 1 + if val == "": + # nested block or empty + if pos[0] < len(lines) and lines[pos[0]][0] > indent: + obj[key] = parse_block(indent + 1) + else: + obj[key] = None + else: + obj[key] = _scalar(val) + return obj + + def parse_list(indent: int): + arr = [] + while pos[0] < len(lines): + ind, stripped, _ = lines[pos[0]] + if ind < indent or not stripped.startswith("- "): + break + if ind > indent: + break + item = stripped[2:].strip() + pos[0] += 1 + if ":" in item and not (item[0] in "'\""): + # list of mappings — first pair is inline, rest are indented deeper + sub = {} + m = re.match(r"^([^:]+):\s*(.*)$", item) + key, val = m.group(1).strip(), m.group(2) + sub[key] = _scalar(val) if val != "" else None + child_indent = indent + 2 + while pos[0] < len(lines) and lines[pos[0]][0] >= child_indent \ + and not lines[pos[0]][1].startswith("- "): + ind2, strip2, _ = lines[pos[0]] + m2 = re.match(r"^([^:]+):\s*(.*)$", strip2) + if not m2: + pos[0] += 1 + continue + k2, v2 = m2.group(1).strip(), m2.group(2) + pos[0] += 1 + sub[k2] = _scalar(v2) if v2 != "" else None + arr.append(sub) + else: + arr.append(_scalar(item)) + return arr + + return parse_block(0) or {} + + +def load_file(path: str): + if os.environ.get("PQB_USE_PYYAML") == "1": + import yaml # type: ignore + with open(path) as f: + return yaml.safe_load(f) + with open(path) as f: + return loads(f.read()) + + +if __name__ == "__main__": + import json + import sys + print(json.dumps(load_file(sys.argv[1]), indent=2)) diff --git a/pq-bench-rpi5/bench/tls/Makefile b/pq-bench-rpi5/bench/tls/Makefile new file mode 100644 index 0000000..5fcaf03 --- /dev/null +++ b/pq-bench-rpi5/bench/tls/Makefile @@ -0,0 +1,17 @@ +# Build the TLS handshake harness against the OpenSSL that has oqs-provider. +# make OPENSSL_PREFIX=... (defaults to Homebrew openssl@3, else system) + +OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null || echo /usr) + +CC ?= cc +CFLAGS := -O3 -std=c11 -Wall -Wextra -I$(OPENSSL_PREFIX)/include +LDFLAGS := -L$(OPENSSL_PREFIX)/lib -Wl,-rpath,$(OPENSSL_PREFIX)/lib +LDLIBS := -lssl -lcrypto -lm + +bench_tls: bench_tls.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS) + +clean: + rm -f bench_tls + +.PHONY: clean diff --git a/pq-bench-rpi5/bench/tls/bench_tls.c b/pq-bench-rpi5/bench/tls/bench_tls.c new file mode 100644 index 0000000..6d2481e --- /dev/null +++ b/pq-bench-rpi5/bench/tls/bench_tls.c @@ -0,0 +1,194 @@ +/* =========================================================================== + * bench_tls.c — TLS 1.3 handshake benchmark via the OpenSSL API. + * + * Performs full TLS 1.3 handshakes entirely in-process over a pair of memory + * BIOs (no sockets, no CLI scraping). This gives: + * - clean per-handshake wall-clock latency (no socket/scheduler noise) + * - exact bytes-on-the-wire each direction + * - the precise ClientHello flight size (with a fragmentation note vs MSS) + * - real server-cert signature verification cost (client verifies the chain), + * which is the whole point of sweeping PQ signature algorithms. + * + * bench_tls --group X25519MLKEM768 --ca ca.pem \ + * --cert server.pem --key server.key --connections 1000 \ + * --label "X25519MLKEM768+mldsa65" + * + * Emits one JSON object. PQ groups/sigs require oqs-provider to be loadable + * (point OPENSSL_MODULES at its directory); if it cannot load, we say so. + * ===========================================================================*/ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TYPICAL_MSS 1400 /* note fragmentation when ClientHello exceeds this */ + +static inline uint64_t now_ns(void) { + struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; +} + +static int cmp_u64(const void *a, const void *b) { + uint64_t x = *(const uint64_t *)a, y = *(const uint64_t *)b; + return (x > y) - (x < y); +} +static double pct(const uint64_t *s, uint64_t n, double p) { + if (!n) return 0; if (n == 1) return (double)s[0]; + double idx = p * (double)(n - 1); uint64_t lo = (uint64_t)idx; double f = idx - lo; + if (lo + 1 >= n) return (double)s[n - 1]; + return (double)s[lo] + f * ((double)s[lo + 1] - (double)s[lo]); +} + +/* Shuttle all pending bytes from src's mem BIO into dst's mem BIO. + * Returns bytes moved. */ +static size_t pump(BIO *src, BIO *dst) { + char buf[16384]; size_t total = 0; int n; + while ((n = BIO_read(src, buf, sizeof buf)) > 0) { + BIO_write(dst, buf, n); + total += (size_t)n; + } + return total; +} + +/* One full handshake. Records bytes each way and ClientHello size (first flight). + * Returns 0 on success. */ +static int one_handshake(SSL_CTX *cctx, SSL_CTX *sctx, + size_t *c2s_bytes, size_t *s2c_bytes, size_t *chello) { + SSL *cli = SSL_new(cctx), *srv = SSL_new(sctx); + if (!cli || !srv) { if (cli) SSL_free(cli); if (srv) SSL_free(srv); return -1; } + BIO *cli_rb = BIO_new(BIO_s_mem()), *cli_wb = BIO_new(BIO_s_mem()); + BIO *srv_rb = BIO_new(BIO_s_mem()), *srv_wb = BIO_new(BIO_s_mem()); + SSL_set_bio(cli, cli_rb, cli_wb); /* takes ownership */ + SSL_set_bio(srv, srv_rb, srv_wb); + SSL_set_connect_state(cli); + SSL_set_accept_state(srv); + + *c2s_bytes = *s2c_bytes = *chello = 0; + int first_flight = 1, rc = 0; + for (int i = 0; i < 64; i++) { + int r_c = SSL_do_handshake(cli); + size_t moved = pump(cli_wb, srv_rb); + if (first_flight && moved > 0) { *chello = moved; first_flight = 0; } + *c2s_bytes += moved; + (void)r_c; + + int r_s = SSL_do_handshake(srv); + *s2c_bytes += pump(srv_wb, cli_rb); + (void)r_s; + + if (SSL_is_init_finished(cli) && SSL_is_init_finished(srv)) break; + /* if both stalled with nothing to transfer, it's a failure */ + if (BIO_ctrl_pending(cli_wb) == 0 && BIO_ctrl_pending(srv_wb) == 0 && + !SSL_is_init_finished(cli) && i > 4) { rc = -2; break; } + } + if (!SSL_is_init_finished(cli) || !SSL_is_init_finished(srv)) rc = -2; + SSL_free(cli); SSL_free(srv); + return rc; +} + +int main(int argc, char **argv) { + const char *group = "X25519", *ca = NULL, *cert = NULL, *key = NULL, *label = ""; + uint64_t connections = 1000, warmup = 20; + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--group") && i+1 exercises PQ signature verify cost */ + int verify_setup = 1; + if (ca) { + if (SSL_CTX_load_verify_locations(cctx, ca, NULL) != 1) verify_setup = 0; + SSL_CTX_set_verify(cctx, SSL_VERIFY_PEER, NULL); + } else { + SSL_CTX_set_verify(cctx, SSL_VERIFY_NONE, NULL); + } + + if (!group_ok || !cert_ok || !verify_setup) { + printf("{\"label\":\"%s\",\"group\":\"%s\",\"enabled\":false,\"have_oqs_provider\":%s," + "\"reason\":\"%s%s%s\"}\n", + label, group, have_oqs?"true":"false", + group_ok?"":"group-not-supported ", + cert_ok?"":"cert-load-failed ", + verify_setup?"":"ca-load-failed"); + ERR_print_errors_fp(stderr); + return 0; + } + + /* warmup */ + size_t c2s, s2c, ch; + for (uint64_t i = 0; i < warmup; i++) { + if (one_handshake(cctx, sctx, &c2s, &s2c, &ch) != 0) { + printf("{\"label\":\"%s\",\"group\":\"%s\",\"enabled\":false," + "\"have_oqs_provider\":%s,\"reason\":\"handshake failed\"}\n", + label, group, have_oqs?"true":"false"); + ERR_print_errors_fp(stderr); + return 0; + } + } + + uint64_t *lat = malloc(connections * sizeof(uint64_t)); + size_t ch_last = 0, c2s_last = 0, s2c_last = 0; + uint64_t ok = 0; + for (uint64_t i = 0; i < connections; i++) { + uint64_t t0 = now_ns(); + int rc = one_handshake(cctx, sctx, &c2s, &s2c, &ch); + uint64_t dt = now_ns() - t0; + if (rc == 0) { lat[ok++] = dt; ch_last = ch; c2s_last = c2s; s2c_last = s2c; } + } + if (ok == 0) { fprintf(stderr,"all handshakes failed\n"); return 1; } + qsort(lat, ok, sizeof(uint64_t), cmp_u64); + double median = pct(lat, ok, 0.5); + double p95 = pct(lat, ok, 0.95); + double mn = (double)lat[0], mx = (double)lat[ok-1]; + double sum=0; for (uint64_t i=0;i1?sqrt(ss/(ok-1)):0; + double hs_per_sec = median>0 ? 1e9/median : 0; + + printf("{\"label\":\"%s\",\"group\":\"%s\",\"enabled\":true,\"have_oqs_provider\":%s,", + label, group, have_oqs?"true":"false"); + printf("\"connections\":%llu,\"succeeded\":%llu,", (unsigned long long)connections,(unsigned long long)ok); + printf("\"handshake_latency_ns\":{\"median\":%.1f,\"p95\":%.1f,\"min\":%.1f,\"max\":%.1f,\"mean\":%.1f,\"stddev\":%.1f},", + median,p95,mn,mx,mean,stddev); + printf("\"handshakes_per_sec\":%.1f,", hs_per_sec); + printf("\"bytes_on_wire\":{\"client_to_server\":%zu,\"server_to_client\":%zu,\"total\":%zu},", + c2s_last, s2c_last, c2s_last+s2c_last); + printf("\"client_hello_bytes\":%zu,\"client_hello_fragmented\":%s,\"mss_assumed\":%d}\n", + ch_last, ch_last>TYPICAL_MSS?"true":"false", TYPICAL_MSS); + free(lat); + return 0; +} diff --git a/pq-bench-rpi5/bench/tls/run_tls.sh b/pq-bench-rpi5/bench/tls/run_tls.sh new file mode 100755 index 0000000..82466f2 --- /dev/null +++ b/pq-bench-rpi5/bench/tls/run_tls.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +# ============================================================================= +# run_tls.sh — generate PKI and run the TLS 1.3 (KEM-group x signature) matrix. +# +# Always benchmarks the classical Logos baseline (X25519 key exchange + Ed25519 +# server auth) using stock OpenSSL. PQ rows additionally require oqs-provider to +# be loadable; if it is not present we record those rows as unavailable (with a +# reason) rather than failing — so this still smoke-tests cleanly on a dev box. +# +# ./run_tls.sh --out tls.json --connections 1000 +# +# Honors $PQB_TASKSET (a taskset/numactl prefix) to pin bench_tls to a core. +# ============================================================================= +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$HERE/../.." && pwd)" +# shellcheck source=setup/lib_platform.sh +source "$ROOT/setup/lib_platform.sh" +LOCK="$ROOT/setup/versions.lock" +# shellcheck disable=SC1090 +[ -f "$LOCK" ] && source "$LOCK" || true +pqb_detect_platform + +OUT=""; CONNS=1000; WARMUP=20 +while [ $# -gt 0 ]; do + case "$1" in + --out) OUT="$2"; shift ;; + --connections) CONNS="$2"; shift ;; + --warmup) WARMUP="$2"; shift ;; + *) pqb_err "unknown arg: $1"; exit 2 ;; + esac + shift +done +[ -n "$OUT" ] || { pqb_err "--out required"; exit 2; } + +TASKSET="${PQB_TASKSET:-}" + +# ---- choose the OpenSSL that has the provider ------------------------------ +OSSL="${OPENSSL_BIN:-$(command -v openssl)}" +OSSL_PREFIX="${OPENSSL_PREFIX:-$(brew --prefix openssl@3 2>/dev/null || echo /usr)}" +PROV_MODULE="${OQSPROVIDER_MODULE:-}" +PROV_ARGS="" +HAVE_OQS=0 +if [ -n "$PROV_MODULE" ] && [ -f "$PROV_MODULE" ]; then + export OPENSSL_MODULES="$(dirname "$PROV_MODULE")" + if "$OSSL" list -providers -provider oqsprovider -provider default >/dev/null 2>&1; then + PROV_ARGS="-provider oqsprovider -provider default" + HAVE_OQS=1 + pqb_log "oqs-provider available: $PROV_MODULE" + fi +fi +[ "$HAVE_OQS" = 0 ] && pqb_warn "oqs-provider not available — PQ TLS rows will be marked unavailable (classical baseline still runs)" + +# ---- build the harness ----------------------------------------------------- +make -C "$HERE" OPENSSL_PREFIX="$OSSL_PREFIX" >/dev/null +BENCH="$HERE/bench_tls" +[ -n "$TASKSET" ] && export OPENSSL_MODULES="${OPENSSL_MODULES:-}" + +# ---- PKI workspace --------------------------------------------------------- +PKI="$HERE/pki"; rm -rf "$PKI"; mkdir -p "$PKI" + +# gen_cert [provider] -> CA + server cert/key of that alg +gen_cert() { + local alg="$1" pfx="$2" prov="${3:-}" + local ca_key="$PKI/${pfx}_ca.key" ca_crt="$PKI/${pfx}_ca.pem" + local sv_key="$PKI/${pfx}_server.key" sv_csr="$PKI/${pfx}_server.csr" sv_crt="$PKI/${pfx}_server.pem" + # CA + "$OSSL" req -x509 -new -newkey "$alg" -nodes $prov \ + -keyout "$ca_key" -out "$ca_crt" -days 3650 \ + -subj "/CN=PQB Test CA ($alg)" >/dev/null 2>&1 || return 1 + # server key + CSR + cert signed by CA + "$OSSL" genpkey -algorithm "$alg" $prov -out "$sv_key" >/dev/null 2>&1 || return 1 + "$OSSL" req -new -key "$sv_key" $prov -out "$sv_csr" -subj "/CN=localhost" >/dev/null 2>&1 || return 1 + "$OSSL" x509 -req -in "$sv_csr" -CA "$ca_crt" -CAkey "$ca_key" $prov \ + -out "$sv_crt" -days 3650 -CAcreateserial >/dev/null 2>&1 || return 1 + return 0 +} + +# ---- read config ----------------------------------------------------------- +TLS_JSON="$(python3 "$ROOT/bench/lib/list_algs.py" tls "$ROOT/config.yaml")" +read_list() { python3 -c "import json,sys; print('\n'.join(json.loads(sys.argv[1]).get(sys.argv[2],[])))" "$TLS_JSON" "$1"; } +BASE_KEM="$(python3 -c "import json,sys;print(json.loads(sys.argv[1])['baseline']['kem_group'])" "$TLS_JSON")" +BASE_SIG="$(python3 -c "import json,sys;print(json.loads(sys.argv[1])['baseline']['sig_alg'])" "$TLS_JSON")" + +# ---- generate certs -------------------------------------------------------- +declare -a SIG_OK_ALGS=() +# classical baseline cert (always) +if gen_cert "$BASE_SIG" "base_$BASE_SIG"; then + pqb_log "generated baseline cert ($BASE_SIG)" +else + pqb_err "failed to generate classical baseline cert ($BASE_SIG) — TLS layer cannot run" + echo '{"available":false,"reason":"baseline cert generation failed"}' > "$OUT"; exit 0 +fi +if [ "$HAVE_OQS" = 1 ]; then + while IFS= read -r s; do + [ -z "$s" ] && continue + if gen_cert "$s" "pq_$s" "$PROV_ARGS"; then + SIG_OK_ALGS+=("$s"); pqb_log "generated PQ cert ($s)" + else + pqb_warn "could not generate cert for sig alg '$s' (skipping)" + fi + done < <(read_list sig_algs) +fi + +# cert path helpers +ca_for() { case "$1" in "$BASE_SIG") echo "$PKI/base_${BASE_SIG}_ca.pem";; *) echo "$PKI/pq_${1}_ca.pem";; esac; } +crt_for() { case "$1" in "$BASE_SIG") echo "$PKI/base_${BASE_SIG}_server.pem";; *) echo "$PKI/pq_${1}_server.pem";; esac; } +key_for() { case "$1" in "$BASE_SIG") echo "$PKI/base_${BASE_SIG}_server.key";; *) echo "$PKI/pq_${1}_server.key";; esac; } + +# run one matrix cell -> appends JSON object to $ROWS file +run_cell() { + local kem="$1" sig="$2" + local label="${kem}+${sig}" + # shellcheck disable=SC2086 + $TASKSET "$BENCH" --group "$kem" --ca "$(ca_for "$sig")" \ + --cert "$(crt_for "$sig")" --key "$(key_for "$sig")" \ + --connections "$CONNS" --warmup "$WARMUP" --label "$label" 2>>"$PKI/bench_tls.err" +} + +ROWS="$PKI/rows.jsonl"; : > "$ROWS" + +# baseline row always first +pqb_log "TLS baseline: $BASE_KEM + $BASE_SIG ($CONNS handshakes)" +run_cell "$BASE_KEM" "$BASE_SIG" >> "$ROWS" || pqb_warn "baseline TLS cell failed" + +# PQ matrix: (kem_groups x sig_algs) — only cells whose cert exists +if [ "$HAVE_OQS" = 1 ] && [ "${#SIG_OK_ALGS[@]}" -gt 0 ]; then + while IFS= read -r kem; do + [ -z "$kem" ] && continue + for sig in "${SIG_OK_ALGS[@]}"; do + pqb_log "TLS cell: $kem + $sig" + run_cell "$kem" "$sig" >> "$ROWS" || pqb_warn "cell $kem+$sig failed" + done + done < <(read_list kem_groups) +fi + +# ---- assemble tls.json ----------------------------------------------------- +python3 - "$ROWS" "$OUT" "$HAVE_OQS" "$BASE_KEM" "$BASE_SIG" <<'PY' +import json,sys +rows_path, out_path, have_oqs, base_kem, base_sig = sys.argv[1:6] +rows=[] +with open(rows_path) as f: + for line in f: + line=line.strip() + if line: + try: rows.append(json.loads(line)) + except json.JSONDecodeError: pass +out={ + "available": True, + "have_oqs_provider": have_oqs=="1", + "baseline": {"kem_group": base_kem, "sig_alg": base_sig, "label": f"{base_kem}+{base_sig}"}, + "matrix": rows, +} +json.dump(out, open(out_path,"w"), indent=2) +print(f"wrote {out_path}: {len(rows)} cells (have_oqs={have_oqs})") +PY diff --git a/pq-bench-rpi5/config.yaml b/pq-bench-rpi5/config.yaml new file mode 100644 index 0000000..a4a5066 --- /dev/null +++ b/pq-bench-rpi5/config.yaml @@ -0,0 +1,118 @@ +# ============================================================================= +# pq-bench-rpi5 candidate configuration +# +# This file is the single place users edit to extend the benchmark. The harness +# reads it with a dependency-free YAML subset parser (bench/lib/miniyaml.py), so +# keep to the simple shape used below: top-level maps, lists of scalars, and +# lists of "- key: value" maps. Comments (#) and quotes are fine. +# +# Algorithm names must match liboqs / oqs-provider identifiers exactly. To see +# what your built liboqs supports: ./vendor/liboqs/build/tests/test_kem (lists) +# or check the dashboard's "available algorithms" note after a run. +# ============================================================================= + +# ---- measurement knobs ------------------------------------------------------ +# Default sizing is PER-OPERATION AUTO-CALIBRATION: each op (keygen/encaps/sign/ +# ...) is timed long enough to accumulate ~target_time_ms of work, clamped to +# [min_samples, max_iters]. This makes an 18 us ML-KEM keygen and a ~750 ms +# SLH-DSA sign both yield a stable median without a hand-tuned per-alg count: +# - fast ops are bounded by max_iters +# - slow ops are bounded by min_samples (so even ~1 op per target still gets +# enough samples for a stable median/MAD) +# To force FIXED counts instead, pass `./run.sh --iters N` — that disables +# calibration and uses the warmup_iters/timed_iters fallback below. +measurement: + target_time_ms: 250 # auto: aggregate timed work to put on each op + min_samples: 30 # auto: floor on timed iters/rep (stable median+MAD) + max_iters: 20000 # auto: ceiling on timed iters/rep (caps fast ops) + repetitions: 5 # independent repetitions (fresh process each time) + cycles_mode: auto # auto | on | off (PMU userspace cycle counting) + # ---- fixed-count fallback (only used when ./run.sh --iters is given) ---- + warmup_iters: 1000 # untimed iterations to settle caches/branch predictors + timed_iters: 10000 # timed iterations per repetition + +# ---- KEMs ------------------------------------------------------------------ +# baseline = the classical reference Logos uses TODAY (drawn on every chart). +kem: + baseline: + - name: X25519 + classical: true + security_level: 1 # NIST category, for grouping on charts + candidates: + - name: ML-KEM-512 + security_level: 1 + - name: ML-KEM-768 + security_level: 3 + - name: ML-KEM-1024 + security_level: 5 + # Hybrids (classical + PQ) — these are oqs-provider TLS group names; for the + # raw KEM bench they are skipped unless liboqs exposes them as a KEM. + - name: X25519MLKEM768 + security_level: 3 + hybrid: true + - name: SecP256r1MLKEM768 + security_level: 3 + hybrid: true + # --- extend here, e.g. uncomment if your liboqs build enables them --- + # - name: FrodoKEM-640-AES + # security_level: 1 + # - name: HQC-128 + # security_level: 1 + # - name: Classic-McEliece-348864 + # security_level: 1 + +# ---- Signatures ------------------------------------------------------------ +sig: + baseline: + - name: Ed25519 + classical: true + security_level: 1 + candidates: + - name: ML-DSA-44 + security_level: 2 + - name: ML-DSA-65 + security_level: 3 + - name: ML-DSA-87 + security_level: 5 + - name: Falcon-512 + security_level: 1 + - name: Falcon-1024 + security_level: 5 + # SLH-DSA (SPHINCS+) — many parameter sets; a representative spread. + - name: SPHINCS+-SHA2-128f-simple + security_level: 1 + - name: SPHINCS+-SHA2-128s-simple + security_level: 1 + - name: SPHINCS+-SHA2-192f-simple + security_level: 3 + - name: SPHINCS+-SHA2-256f-simple + security_level: 5 + # --- extend here --- + +# ---- TLS 1.3 handshake matrix ---------------------------------------------- +# The bench runs (kem_group x sig_alg). The classical baseline pair is ALWAYS +# included as the reference point regardless of what is listed here. +tls: + baseline: + kem_group: X25519 + sig_alg: ed25519 # OpenSSL classical auth Logos uses today + # PQ key-exchange groups to test (oqs-provider TLS group names) + kem_groups: + - X25519MLKEM768 + - SecP256r1MLKEM768 + - mlkem512 + - mlkem768 + - mlkem1024 + # PQ signature algorithms for the server/CA cert (oqs-provider names) + sig_algs: + - mldsa44 + - mldsa65 + - mldsa87 + - falcon512 + - sphincssha2128fsimple + connections: 1000 # handshakes timed against a persistent s_server + +# ---- future phase (NOT implemented now — hooks only) ----------------------- +# zk: +# snark: [] # e.g. groth16, plonk, halo2 +# stark: [] # e.g. risc0, winterfell diff --git a/pq-bench-rpi5/dashboard/app.js b/pq-bench-rpi5/dashboard/app.js new file mode 100644 index 0000000..aaa3c62 --- /dev/null +++ b/pq-bench-rpi5/dashboard/app.js @@ -0,0 +1,304 @@ +/* pq-bench-rpi5 dashboard — pure client-side, reads a merged.json produced by + * analyze/merge.py. No backend. Renders KEM/sig/TLS charts with the classical + * Logos baseline as a reference line on each. Defaults to baseline-grade (RPi5) + * runs only; a toggle includes macOS/dev smoke runs. */ + +const LEVEL_COLORS = { 1:"#3bd67a", 2:"#46c0c0", 3:"#3a7bff", 5:"#b06bff", 0:"#888" }; +const BASE_COLOR = "#e0533d", PQ_COLOR = "#3a7bff"; +let MERGED = null, CHARTS = []; + +const $ = (id) => document.getElementById(id); +const nsToMs = (ns) => (ns || 0) / 1e6; +/* compact ms label: more decimals for small values, fewer for large. Guards its + * input — Chart.js may hand a value-label formatter a parsed {x,y} point or a + * non-number; extract the numeric value without coercing an object (Number() on + * a null-prototype object would itself throw), and return "" for anything not + * finite so the value-label draw never throws and halts later charts. */ +const fmtMs = (v) => { + const ms = typeof v === "number" ? v + : (v && typeof v === "object") ? (typeof v.y === "number" ? v.y : NaN) + : Number(v); + if (!Number.isFinite(ms)) return ""; + return ms>=100?ms.toFixed(0):ms>=10?ms.toFixed(1):ms>=1?ms.toFixed(2):ms.toFixed(3); +}; + +/* Inline Chart.js plugin: draw each bar's value just above the bar. Enabled + * per-chart via options.plugins.valueLabels.formatter; charts that don't set it + * are untouched (TLS/scatter stay clean). No external dependency, so it works + * even when only the Chart.js core CDN is reachable. */ +const valueLabels = { + id: "valueLabels", + afterDatasetsDraw(chart) { + const opt = (chart.options.plugins||{}).valueLabels; + if (!opt || !opt.formatter) return; + const ctx = chart.ctx; + ctx.save(); + ctx.fillStyle = "#e6e8ee"; ctx.font = "10px sans-serif"; + ctx.textAlign = "center"; ctx.textBaseline = "bottom"; + chart.data.datasets.forEach((ds, di) => { + const meta = chart.getDatasetMeta(di); + if (meta.hidden) return; + meta.data.forEach((el, i) => { + const v = ds.data[i]; + if (v == null) return; + ctx.fillText(opt.formatter(v, i), el.x, el.y - 3); + }); + }); + ctx.restore(); + } +}; +if (window.Chart) Chart.register(valueLabels); + +async function boot() { + $("fileInput").addEventListener("change", onFile); + $("includeSmoke").addEventListener("change", render); + $("runSelect").addEventListener("change", render); + try { + const r = await fetch("data/merged.json", { cache: "no-store" }); + if (r.ok) { MERGED = await r.json(); afterLoad(); } + else showEmpty("No data/merged.json yet. Run a benchmark, then: " + + "python3 analyze/merge.py results/*.json -o dashboard/data/merged.json " + + "— or load a results file above."); + } catch (e) { + showEmpty("Could not auto-load data/merged.json (open via a local server or use the file picker)."); + } +} + +function onFile(ev) { + const f = ev.target.files[0]; if (!f) return; + const rd = new FileReader(); + rd.onload = () => { + const d = JSON.parse(rd.result); + // accept either a merged file or a single results file + MERGED = d.merged_schema ? d : wrapSingle(d); + afterLoad(); + }; + rd.readAsText(f); +} + +/* wrap a single results JSON into the merged shape so the picker works too */ +function wrapSingle(d) { + const rid = `${(d.host||{}).hostname}@${d.generated_utc}`; + const meta = { run_id: rid, hostname:(d.host||{}).hostname, cpu_brand:(d.host||{}).cpu_brand, + is_rpi:(d.host||{}).is_rpi, is_baseline_grade:d.is_baseline_grade }; + const kem=[], sig=[], tls=[]; + (d.kem||[]).forEach(k=>{ if(k.enabled) Object.entries(k.operations||{}).forEach(([op,st])=> + kem.push({...meta, alg:k.alg, classical:!!k.classical, nist_level:k.claimed_nist_level, + operation:op, median_ns:st.median, sizes:k.sizes})); }); + (d.sig||[]).forEach(s=>{ if(s.enabled) Object.entries(s.operations||{}).forEach(([op,st])=> + sig.push({...meta, alg:s.alg, classical:!!s.classical, nist_level:s.claimed_nist_level, + operation:op, median_ns:st.median, sizes:s.sizes})); }); + ((d.tls||{}).matrix||[]).forEach(c=>{ if(c.enabled) tls.push({...meta, label:c.label, group:c.group, + is_baseline_pair:c.label===((d.tls||{}).baseline||{}).label, + handshakes_per_sec:c.handshakes_per_sec, median_ns:(c.handshake_latency_ns||{}).median, + bytes_total:(c.bytes_on_wire||{}).total, client_hello_bytes:c.client_hello_bytes, + client_hello_fragmented:c.client_hello_fragmented}); }); + return { merged_schema:"single", n_runs:1, + runs:[{run_id:rid, host:d.host, is_baseline_grade:d.is_baseline_grade, + baseline_grade_reasons:d.baseline_grade_reasons||[], toolchain:d.toolchain, + cpu_features:d.cpu_features, run:d.run, + thermal_summary:{temp_c:(d.thermal_trace||{}).temp_c, + throttling_detected:(d.thermal_trace||{}).throttling_detected}, + generated_utc:d.generated_utc}], + kem, sig, tls }; +} + +function afterLoad() { + const sel = $("runSelect"); + sel.innerHTML = ""; + MERGED.runs.forEach(r => { + const o = document.createElement("option"); + o.value = r.run_id; + o.textContent = `${(r.host||{}).cpu_brand||"?"} — ${r.is_baseline_grade?"✅ baseline":"⚠ smoke"} — ${r.generated_utc||""}`; + sel.appendChild(o); + }); + render(); +} + +function currentRun() { + const id = $("runSelect").value; + return MERGED.runs.find(r => r.run_id === id) || MERGED.runs[0]; +} + +function render() { + if (!MERGED) return; + CHARTS.forEach(c => c.destroy()); CHARTS = []; + const run = currentRun(); + const includeSmoke = $("includeSmoke").checked; + const allowed = new Set(MERGED.runs + .filter(r => includeSmoke || r.is_baseline_grade) + .map(r => r.run_id)); + // chart the selected run if allowed, else fall back to allowed set + const rid = allowed.has(run.run_id) ? run.run_id : null; + const filt = (rows) => rows.filter(r => rid ? r.run_id === rid : allowed.has(r.run_id)); + + renderBanner(run); + renderEnv(run); + + const kem = filt(MERGED.kem), sig = filt(MERGED.sig), tls = filt(MERGED.tls); + + barByLevel("kem_keygen", kem, "keygen", "KEM keygen — median latency (ms)"); + barByLevel("kem_encaps", kem, "encaps", "KEM encaps — median latency (ms)", "derive"); + barByLevel("kem_decaps", kem, "decaps", "KEM decaps — median latency (ms)", "derive"); + scatter("kem_scatter", kem, "encaps", "public_key", "KEM size vs speed (encaps)", "public key (B)"); + barByLevel("sig_sign", sig, "sign", "Signature sign — median latency (ms)", "sign", true); + barByLevel("sig_verify", sig, "verify", "Signature verify — median latency (ms)", "verify", true); + scatter("sig_scatter", sig, "sign", "signature", "Signature size vs speed (sign)", "signature (B)", true); + tlsThroughput("tls_hs", tls); + tlsClientHello("tls_chello", tls); +} + +function renderBanner(run) { + const el = $("quality-banner"); + if (run.is_baseline_grade) { + el.className = "banner-good"; + el.innerHTML = "✅ RPi5 baseline-grade run — performance governor, core-pinned, " + + "cortex-a76 flags, no thermal throttling."; + } else { + el.className = "banner-warn"; + const rs = (run.baseline_grade_reasons||[]).map(r=>`
  • ${r}
  • `).join(""); + el.innerHTML = "⚠ NOT RPi5 baseline-grade — treat as a pipeline smoke test, not measurement data." + + (rs ? `
      ${rs}
    ` : ""); + } +} + +function renderEnv(run) { + const h = run.host||{}, t = run.toolchain||{}, f = run.cpu_features||{}, rn = run.run||{}; + const chip = (label, val) => `${label} ${val}`; + const sha3 = f.sha3 ? "SHA3 hw ✓" : "SHA3 hw ✗ (Keccak on NEON)"; + $("env-summary").innerHTML = [ + chip("host", `${h.cpu_brand||"?"} (${h.os_pretty||h.os||"?"})`), + chip("governor", rn.governor_after||"?"), + chip("pinned core", rn.pinned ? rn.bench_core : "no"), + chip("flags", `${t.bench_cflags||"?"} → ${t.cflags_target||"?"}`), + chip("liboqs", `${t.liboqs_ref||"?"} ${(t.liboqs_commit||"").slice(0,8)}`), + chip("crypto-ext", `${f.neon?"NEON ":""}${f.sha2?"SHA2 ":""}${sha3}`), + chip("cycles", rn.cycles_available ? "PMU ✓" : "time-based"), + chip("temp", run.thermal_summary && run.thermal_summary.temp_c + ? `${run.thermal_summary.temp_c.mean}°C` + (run.thermal_summary.throttling_detected?" ⚠throttled":"") + : "n/a"), + ].join(""); +} + +/* ---- chart builders ----------------------------------------------------- */ +function baselineAnnotation(value, label) { + if (value == null) return {}; + return { annotations: { base: { + type:"line", yMin:value, yMax:value, borderColor:BASE_COLOR, + borderWidth:2, borderDash:[6,4], + label:{ display:true, content:label, position:"end", + backgroundColor:BASE_COLOR, font:{size:10} } } } }; +} + +function barByLevel(canvasId, rows, op, title, baselineOp = op, logY = false) { + const data = rows.filter(r => r.operation === op) + .sort((a,b)=>(a.nist_level||0)-(b.nist_level||0) || a.median_ns-b.median_ns); + if (!data.length) return drawEmpty(canvasId, title); + /* Baseline reference line: the classical row for baselineOp (defaults to this + * chart's own op). KEM encaps/decaps have no classical encaps/decaps op, so + * they map to the X25519 key-agreement (derive) timing instead. */ + const base = rows.find(r => r.classical && r.operation === baselineOp); + const ctx = $(canvasId).getContext("2d"); + CHARTS.push(new Chart(ctx, { + type:"bar", + data:{ labels:data.map(r=>r.alg), + datasets:[{ label:title, + data:data.map(r=>nsToMs(r.median_ns)), + backgroundColor:data.map(r=> r.classical?BASE_COLOR:(LEVEL_COLORS[r.nist_level]||PQ_COLOR)) }] }, + options:{ responsive:true, plugins:{ + title:{display:true,text:title,color:"#e6e8ee"}, + legend:{display:false}, + valueLabels:{ formatter:(v)=>fmtMs(v) }, + tooltip:{callbacks:{ + label:(it)=>`median ${it.raw.toFixed(4)} ms (${Math.round(it.raw*1e6).toLocaleString()} ns)`, + afterLabel:(it)=>{ + const r=data[it.dataIndex]; return `NIST L${r.nist_level} · ${r.classical?"classical baseline":"PQ"}`; }}}, + annotation: base ? baselineAnnotation(nsToMs(base.median_ns), + baselineOp === op ? `baseline ${base.alg}` : `baseline ${base.alg} ${base.operation}`) : {} }, + scales:{ x:{ticks:{color:"#9aa3b2",maxRotation:50,minRotation:40}}, + y:{ type: logY?"logarithmic":"linear", + title:{display:true,text:logY?"ms (log)":"ms",color:"#9aa3b2"},ticks:{color:"#9aa3b2"}} } } + })); +} + +function scatter(canvasId, rows, op, sizeKey, title, xlabel, logScale = false) { + const data = rows.filter(r => r.operation === op && r.sizes && r.sizes[sizeKey]); + if (!data.length) return drawEmpty(canvasId, title); + const pts = data.map(r => ({ x:r.sizes[sizeKey], y:nsToMs(r.median_ns), alg:r.alg, classical:r.classical })); + const ctx = $(canvasId).getContext("2d"); + CHARTS.push(new Chart(ctx, { + type:"scatter", + data:{ datasets:[{ label:title, data:pts, pointRadius:6, + backgroundColor:pts.map(p=>p.classical?BASE_COLOR:PQ_COLOR) }] }, + options:{ responsive:true, plugins:{ + title:{display:true,text:title,color:"#e6e8ee"}, legend:{display:false}, + tooltip:{callbacks:{label:(it)=>`${it.raw.alg}: ${it.raw.x} B, ${it.raw.y.toFixed(3)} ms`}} }, + scales:{ x:{ type: logScale?"logarithmic":"linear", + title:{display:true,text:logScale?`${xlabel} (log)`:xlabel,color:"#9aa3b2"},ticks:{color:"#9aa3b2"}}, + y:{ type: logScale?"logarithmic":"linear", + title:{display:true,text:logScale?"median latency (ms, log)":"median latency (ms)",color:"#9aa3b2"},ticks:{color:"#9aa3b2"}} } } + })); +} + +function tlsThroughput(canvasId, rows) { + if (!rows.length) return drawEmpty(canvasId, "TLS handshakes/sec — run the TLS layer"); + const data = rows.slice().sort((a,b)=>(b.handshakes_per_sec||0)-(a.handshakes_per_sec||0)); + const base = data.find(r => r.is_baseline_pair); + const ctx = $(canvasId).getContext("2d"); + CHARTS.push(new Chart(ctx, { + type:"bar", + data:{ labels:data.map(r=>r.label), + datasets:[{ label:"handshakes/sec", + data:data.map(r=>r.handshakes_per_sec), + backgroundColor:data.map(r=>r.is_baseline_pair?BASE_COLOR:PQ_COLOR) }] }, + options:{ indexAxis:"y", responsive:true, plugins:{ + title:{display:true,text:"TLS 1.3 handshake throughput (higher = better)",color:"#e6e8ee"}, + legend:{display:false}, + annotation: base ? { annotations:{ base:{ type:"line", + xMin:base.handshakes_per_sec, xMax:base.handshakes_per_sec, + borderColor:BASE_COLOR, borderWidth:2, borderDash:[6,4], + label:{display:true,content:`baseline ${base.label}`,position:"end", + backgroundColor:BASE_COLOR,font:{size:10}} } } } : {} }, + scales:{ x:{title:{display:true,text:"handshakes/sec",color:"#9aa3b2"},ticks:{color:"#9aa3b2"}}, + y:{ticks:{color:"#9aa3b2",font:{size:10}}} } } + })); +} + +function tlsClientHello(canvasId, rows) { + if (!rows.length) return drawEmpty(canvasId, "ClientHello size — run the TLS layer"); + const data = rows.slice().sort((a,b)=>(b.client_hello_bytes||0)-(a.client_hello_bytes||0)); + const base = data.find(r => r.is_baseline_pair); + const ctx = $(canvasId).getContext("2d"); + CHARTS.push(new Chart(ctx, { + type:"bar", + data:{ labels:data.map(r=>r.label), + datasets:[{ label:"ClientHello bytes", + data:data.map(r=>r.client_hello_bytes), + backgroundColor:data.map(r=> r.is_baseline_pair?BASE_COLOR + : (r.client_hello_fragmented?"#d98b2b":PQ_COLOR)) }] }, + options:{ indexAxis:"y", responsive:true, plugins:{ + title:{display:true,text:"ClientHello size (orange = exceeds ~1400B MSS → fragments)",color:"#e6e8ee"}, + legend:{display:false}, + annotation:{ annotations:{ mss:{ type:"line", xMin:1400, xMax:1400, + borderColor:"#d98b2b", borderWidth:1, borderDash:[4,4], + label:{display:true,content:"~MSS 1400B",position:"start",backgroundColor:"#d98b2b",font:{size:9}} }, + ...(base?{base:{type:"line",xMin:base.client_hello_bytes,xMax:base.client_hello_bytes, + borderColor:BASE_COLOR,borderWidth:2,borderDash:[6,4], + label:{display:true,content:`baseline ${base.label}`,position:"end",backgroundColor:BASE_COLOR,font:{size:10}}}}:{}) } } }, + scales:{ x:{title:{display:true,text:"bytes",color:"#9aa3b2"},ticks:{color:"#9aa3b2"}}, + y:{ticks:{color:"#9aa3b2",font:{size:10}}} } } + })); +} + +function drawEmpty(canvasId, msg) { + const c = $(canvasId); const ctx = c.getContext("2d"); + ctx.clearRect(0,0,c.width,c.height); + ctx.fillStyle = "#9aa3b2"; ctx.font = "13px sans-serif"; ctx.textAlign="center"; + ctx.fillText(msg, c.width/2, c.height/2); +} + +function showEmpty(html) { + document.querySelector("main").innerHTML = `
    ${html}
    `; +} + +boot(); diff --git a/pq-bench-rpi5/dashboard/data/merged.json b/pq-bench-rpi5/dashboard/data/merged.json new file mode 100644 index 0000000..6fc1cb3 --- /dev/null +++ b/pq-bench-rpi5/dashboard/data/merged.json @@ -0,0 +1,1492 @@ +{ + "merged_schema": "1.0.0", + "n_runs": 1, + "runs": [ + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "host": { + "hostname": "rasberrypi5", + "os": "linux", + "os_pretty": "Debian GNU/Linux 13 (trixie)", + "arch": "aarch64", + "kernel": "6.12.47+rpt-rpi-2712", + "is_rpi": true, + "rpi_model": "Raspberry Pi 5 Model B Rev 1.1", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "ncpu": 4, + "ram_bytes": 8454684672 + }, + "is_baseline_grade": true, + "baseline_grade_reasons": [], + "toolchain": { + "cc_version": "cc (Debian 14.2.0-19) 14.2.0", + "bench_cflags": "-O3 -mcpu=cortex-a76", + "cflags_target": "cortex-a76", + "liboqs_ref": "0.15.0", + "liboqs_commit": "97f6b86b1b6d109cfd43cf276ae39c2e776aed80", + "liboqs_opt_defines": "OQS_COMPILE_BUILD_TARGET aarch64-Linux-6.12.47+rpt-rpi-2712;ARCH_ARM64v8 1;OQS_OPT_TARGET auto;OQS_USE_ARM_AES_INSTRUCTIONS 1;OQS_USE_ARM_SHA2_INSTRUCTIONS 1;OQS_USE_ARM_NEON_INSTRUCTIONS 1;OQS_ENABLE_KEM_kyber_512_aarch64 1;OQS_ENABLE_KEM_kyber_768_aarch64 1;OQS_ENABLE_KEM_kyber_1024_aarch64 1;OQS_ENABLE_KEM_ml_kem_512_aarch64 1;OQS_ENABLE_KEM_ml_kem_768_aarch64 1;OQS_ENABLE_KEM_ml_kem_1024_aarch64 1;OQS_ENABLE_SIG_falcon_512_aarch64 1;OQS_ENABLE_SIG_falcon_1024_aarch64 1;OQS_ENABLE_SIG_falcon_padded_512_aarch64 1;OQS_ENABLE_SIG_falcon_padded_1024_aarch64 1;", + "openssl": "system:3.5.6", + "oqsprovider_ref": "0.9.0", + "oqsprovider_commit": "848b4e6abaa89e769c4db46ca78f91000f67ca52" + }, + "cpu_features": { + "source": "/proc/cpuinfo", + "neon": true, + "sha2": true, + "sha3": false, + "sha512": false, + "aes": true, + "pmull": true + }, + "run": { + "timestamp_start_utc": "2026-06-08T22:30:40Z", + "timestamp_end_utc": "2026-06-08T22:39:28Z", + "duration_s": 528, + "governor_requested": "performance", + "governor_before": "performance", + "governor_after": "performance", + "bench_core": 3, + "pinned": true, + "taskset_cmd": "taskset -c 3", + "calibration_mode": "auto", + "target_time_ms": 250, + "min_samples": 30, + "max_iters": 20000, + "warmup_iters": null, + "timed_iters": null, + "repetitions": 5, + "cycles_mode": "auto", + "cycles_available": false, + "cycles_reason": "PMCCNTR_EL0 traps (kernel module not loaded; needs e.g. enable_arm_pmu)" + }, + "thermal_summary": { + "temp_c": { + "min": 56.0, + "max": 60.9, + "mean": 59.41, + "samples": 520 + }, + "throttling_detected": false + }, + "generated_utc": "2026-06-08T22:39:28Z", + "source_file": "rasberrypi5-20260608T223040Z.json" + } + ], + "kem": [ + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "X25519", + "backend": "openssl", + "classical": true, + "nist_level": 1, + "operation": "keygen", + "median_ns": 57184.0, + "mad_ns": 55.0, + "iqr_ns": 112.0, + "min_ns": 56999.0, + "stddev_ns": 1706.55, + "ops_per_sec": 17487.41, + "sizes": { + "public_key": 32, + "secret_key": 32, + "ciphertext": null, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "X25519", + "backend": "openssl", + "classical": true, + "nist_level": 1, + "operation": "derive", + "median_ns": 165591.0, + "mad_ns": 92.0, + "iqr_ns": 204.0, + "min_ns": 164813.0, + "stddev_ns": 3666.74, + "ops_per_sec": 6038.98, + "sizes": { + "public_key": 32, + "secret_key": 32, + "ciphertext": null, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-512", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "keygen", + "median_ns": 18315.0, + "mad_ns": 37.0, + "iqr_ns": 74.0, + "min_ns": 18167.0, + "stddev_ns": 1316.56, + "ops_per_sec": 54600.05, + "sizes": { + "public_key": 800, + "secret_key": 1632, + "ciphertext": 768, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-512", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "encaps", + "median_ns": 20574.0, + "mad_ns": 18.0, + "iqr_ns": 37.0, + "min_ns": 20499.0, + "stddev_ns": 1133.35, + "ops_per_sec": 48605.04, + "sizes": { + "public_key": 800, + "secret_key": 1632, + "ciphertext": 768, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-512", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "decaps", + "median_ns": 23962.0, + "mad_ns": 18.0, + "iqr_ns": 37.0, + "min_ns": 23870.0, + "stddev_ns": 501.17, + "ops_per_sec": 41732.74, + "sizes": { + "public_key": 800, + "secret_key": 1632, + "ciphertext": 768, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-768", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "keygen", + "median_ns": 29851.0, + "mad_ns": 36.0, + "iqr_ns": 73.0, + "min_ns": 29703.0, + "stddev_ns": 821.12, + "ops_per_sec": 33499.72, + "sizes": { + "public_key": 1184, + "secret_key": 2400, + "ciphertext": 1088, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-768", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "encaps", + "median_ns": 31944.0, + "mad_ns": 19.0, + "iqr_ns": 37.0, + "min_ns": 31833.0, + "stddev_ns": 785.69, + "ops_per_sec": 31304.78, + "sizes": { + "public_key": 1184, + "secret_key": 2400, + "ciphertext": 1088, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-768", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "decaps", + "median_ns": 37370.0, + "mad_ns": 19.0, + "iqr_ns": 38.0, + "min_ns": 37277.0, + "stddev_ns": 15761.42, + "ops_per_sec": 26759.43, + "sizes": { + "public_key": 1184, + "secret_key": 2400, + "ciphertext": 1088, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-1024", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "keygen", + "median_ns": 44074.0, + "mad_ns": 37.0, + "iqr_ns": 92.0, + "min_ns": 43907.0, + "stddev_ns": 877.6, + "ops_per_sec": 22689.11, + "sizes": { + "public_key": 1568, + "secret_key": 3168, + "ciphertext": 1568, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-1024", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "encaps", + "median_ns": 47740.0, + "mad_ns": 19.0, + "iqr_ns": 38.0, + "min_ns": 47629.0, + "stddev_ns": 908.98, + "ops_per_sec": 20946.8, + "sizes": { + "public_key": 1568, + "secret_key": 3168, + "ciphertext": 1568, + "shared_secret": 32 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-KEM-1024", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "decaps", + "median_ns": 55370.0, + "mad_ns": 19.0, + "iqr_ns": 38.0, + "min_ns": 55241.0, + "stddev_ns": 450.16, + "ops_per_sec": 18060.32, + "sizes": { + "public_key": 1568, + "secret_key": 3168, + "ciphertext": 1568, + "shared_secret": 32 + } + } + ], + "sig": [ + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Ed25519", + "backend": "openssl", + "classical": true, + "nist_level": 1, + "operation": "keygen", + "median_ns": 58074.0, + "mad_ns": 19.0, + "iqr_ns": 38.0, + "min_ns": 57962.0, + "stddev_ns": 679.41, + "ops_per_sec": 17219.41, + "sizes": { + "public_key": 32, + "secret_key": 32, + "signature": 64 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Ed25519", + "backend": "openssl", + "classical": true, + "nist_level": 1, + "operation": "sign", + "median_ns": 57666.0, + "mad_ns": 19.0, + "iqr_ns": 38.0, + "min_ns": 57555.0, + "stddev_ns": 692.8, + "ops_per_sec": 17341.24, + "sizes": { + "public_key": 32, + "secret_key": 32, + "signature": 64 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Ed25519", + "backend": "openssl", + "classical": true, + "nist_level": 1, + "operation": "verify", + "median_ns": 143981.0, + "mad_ns": 56.0, + "iqr_ns": 130.0, + "min_ns": 143721.0, + "stddev_ns": 849.1, + "ops_per_sec": 6945.36, + "sizes": { + "public_key": 32, + "secret_key": 32, + "signature": 64 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-44", + "backend": "liboqs", + "classical": false, + "nist_level": 2, + "operation": "keygen", + "median_ns": 107166.0, + "mad_ns": 593.0, + "iqr_ns": 1185.0, + "min_ns": 104444.0, + "stddev_ns": 1318.81, + "ops_per_sec": 9331.32, + "sizes": { + "public_key": 1312, + "secret_key": 2560, + "signature": 2420 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-44", + "backend": "liboqs", + "classical": false, + "nist_level": 2, + "operation": "sign", + "median_ns": 398793.0, + "mad_ns": 181183.0, + "iqr_ns": 363331.0, + "min_ns": 215684.0, + "stddev_ns": 333546.57, + "ops_per_sec": 2507.57, + "sizes": { + "public_key": 1312, + "secret_key": 2560, + "signature": 2420 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-44", + "backend": "liboqs", + "classical": false, + "nist_level": 2, + "operation": "verify", + "median_ns": 119110.0, + "mad_ns": 129.0, + "iqr_ns": 315.0, + "min_ns": 118666.0, + "stddev_ns": 770.88, + "ops_per_sec": 8395.6, + "sizes": { + "public_key": 1312, + "secret_key": 2560, + "signature": 2420 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-65", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "keygen", + "median_ns": 191998.0, + "mad_ns": 999.0, + "iqr_ns": 2130.0, + "min_ns": 189721.0, + "stddev_ns": 1666.78, + "ops_per_sec": 5208.39, + "sizes": { + "public_key": 1952, + "secret_key": 4032, + "signature": 3309 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-65", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "sign", + "median_ns": 676421.0, + "mad_ns": 250590.5, + "iqr_ns": 655467.75, + "min_ns": 320794.0, + "stddev_ns": 581666.26, + "ops_per_sec": 1478.37, + "sizes": { + "public_key": 1952, + "secret_key": 4032, + "signature": 3309 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-65", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "verify", + "median_ns": 191073.0, + "mad_ns": 629.0, + "iqr_ns": 2037.0, + "min_ns": 188591.0, + "stddev_ns": 5690.1, + "ops_per_sec": 5233.6, + "sizes": { + "public_key": 1952, + "secret_key": 4032, + "signature": 3309 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-87", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "keygen", + "median_ns": 288997.0, + "mad_ns": 1314.0, + "iqr_ns": 3287.0, + "min_ns": 284776.0, + "stddev_ns": 2477.31, + "ops_per_sec": 3460.24, + "sizes": { + "public_key": 2592, + "secret_key": 4896, + "signature": 4627 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-87", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "sign", + "median_ns": 835827.0, + "mad_ns": 345664.0, + "iqr_ns": 692384.0, + "min_ns": 484737.0, + "stddev_ns": 603216.41, + "ops_per_sec": 1196.42, + "sizes": { + "public_key": 2592, + "secret_key": 4896, + "signature": 4627 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "ML-DSA-87", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "verify", + "median_ns": 305683.0, + "mad_ns": 334.0, + "iqr_ns": 1982.0, + "min_ns": 304942.0, + "stddev_ns": 3211.56, + "ops_per_sec": 3271.36, + "sizes": { + "public_key": 2592, + "secret_key": 4896, + "signature": 4627 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Falcon-512", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "keygen", + "median_ns": 8684444.5, + "mad_ns": 901798.5, + "iqr_ns": 1802995.25, + "min_ns": 7266149.0, + "stddev_ns": 2192639.48, + "ops_per_sec": 115.15, + "sizes": { + "public_key": 897, + "secret_key": 1281, + "signature": 752 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Falcon-512", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "sign", + "median_ns": 290220.0, + "mad_ns": 2185.0, + "iqr_ns": 4134.5, + "min_ns": 281109.0, + "stddev_ns": 3621.07, + "ops_per_sec": 3445.66, + "sizes": { + "public_key": 897, + "secret_key": 1281, + "signature": 752 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Falcon-512", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "verify", + "median_ns": 50851.0, + "mad_ns": 74.0, + "iqr_ns": 148.0, + "min_ns": 50518.0, + "stddev_ns": 432.33, + "ops_per_sec": 19665.3, + "sizes": { + "public_key": 897, + "secret_key": 1281, + "signature": 752 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Falcon-1024", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "keygen", + "median_ns": 26081341.5, + "mad_ns": 2350722.0, + "iqr_ns": 6023380.0, + "min_ns": 22857125.0, + "stddev_ns": 9509453.95, + "ops_per_sec": 38.34, + "sizes": { + "public_key": 1793, + "secret_key": 2305, + "signature": 1462 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Falcon-1024", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "sign", + "median_ns": 591440.0, + "mad_ns": 3056.0, + "iqr_ns": 5908.0, + "min_ns": 575848.0, + "stddev_ns": 4989.2, + "ops_per_sec": 1690.79, + "sizes": { + "public_key": 1793, + "secret_key": 2305, + "signature": 1462 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "Falcon-1024", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "verify", + "median_ns": 99796.0, + "mad_ns": 93.0, + "iqr_ns": 204.0, + "min_ns": 99351.0, + "stddev_ns": 692.04, + "ops_per_sec": 10020.44, + "sizes": { + "public_key": 1793, + "secret_key": 2305, + "signature": 1462 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-128f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "keygen", + "median_ns": 1531637.0, + "mad_ns": 427.0, + "iqr_ns": 2055.0, + "min_ns": 1529359.0, + "stddev_ns": 3783.89, + "ops_per_sec": 652.9, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 17088 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-128f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "sign", + "median_ns": 35791563.5, + "mad_ns": 53073.0, + "iqr_ns": 171179.75, + "min_ns": 35569268.0, + "stddev_ns": 96307.08, + "ops_per_sec": 27.94, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 17088 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-128f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "verify", + "median_ns": 2008448.0, + "mad_ns": 1667.0, + "iqr_ns": 2000.0, + "min_ns": 2006615.0, + "stddev_ns": 4827.43, + "ops_per_sec": 497.9, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 17088 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-128s-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "keygen", + "median_ns": 97953152.5, + "mad_ns": 118648.0, + "iqr_ns": 850748.0, + "min_ns": 97478666.0, + "stddev_ns": 513169.01, + "ops_per_sec": 10.21, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 7856 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-128s-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "sign", + "median_ns": 744477847.5, + "mad_ns": 960013.0, + "iqr_ns": 2358797.25, + "min_ns": 742006494.0, + "stddev_ns": 30960311.81, + "ops_per_sec": 1.34, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 7856 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-128s-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 1, + "operation": "verify", + "median_ns": 744438.0, + "mad_ns": 925.0, + "iqr_ns": 1000.0, + "min_ns": 742586.0, + "stddev_ns": 1980.75, + "ops_per_sec": 1343.3, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 7856 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-192f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "keygen", + "median_ns": 2295537.0, + "mad_ns": 1667.0, + "iqr_ns": 2185.0, + "min_ns": 2291111.0, + "stddev_ns": 4916.04, + "ops_per_sec": 435.63, + "sizes": { + "public_key": 48, + "secret_key": 96, + "signature": 35664 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-192f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "sign", + "median_ns": 61790386.0, + "mad_ns": 31583.0, + "iqr_ns": 64244.75, + "min_ns": 61684738.0, + "stddev_ns": 242816.92, + "ops_per_sec": 16.18, + "sizes": { + "public_key": 48, + "secret_key": 96, + "signature": 35664 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-192f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 3, + "operation": "verify", + "median_ns": 3339047.0, + "mad_ns": 1167.0, + "iqr_ns": 2981.5, + "min_ns": 3302713.0, + "stddev_ns": 6022.6, + "ops_per_sec": 299.49, + "sizes": { + "public_key": 48, + "secret_key": 96, + "signature": 35664 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-256f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "keygen", + "median_ns": 6315494.5, + "mad_ns": 2157.0, + "iqr_ns": 7397.25, + "min_ns": 6306560.0, + "stddev_ns": 8270.6, + "ops_per_sec": 158.34, + "sizes": { + "public_key": 64, + "secret_key": 128, + "signature": 49856 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-256f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "sign", + "median_ns": 127052863.5, + "mad_ns": 102035.5, + "iqr_ns": 5188286.0, + "min_ns": 126909235.0, + "stddev_ns": 2341248.91, + "ops_per_sec": 7.87, + "sizes": { + "public_key": 64, + "secret_key": 128, + "signature": 49856 + } + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "alg": "SPHINCS+-SHA2-256f-simple", + "backend": "liboqs", + "classical": false, + "nist_level": 5, + "operation": "verify", + "median_ns": 3367417.0, + "mad_ns": 241.0, + "iqr_ns": 501.0, + "min_ns": 3365250.0, + "stddev_ns": 6580.08, + "ops_per_sec": 296.96, + "sizes": { + "public_key": 64, + "secret_key": 128, + "signature": 49856 + } + } + ], + "tls": [ + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "X25519+ed25519", + "group": "X25519", + "is_baseline_pair": true, + "handshakes_per_sec": 755.4, + "median_ns": 1323878.0, + "bytes_total": 1571, + "client_hello_bytes": 302, + "client_hello_fragmented": false + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "X25519MLKEM768+mldsa44", + "group": "X25519MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 374.2, + "median_ns": 2672487.0, + "bytes_total": 9852, + "client_hello_bytes": 1478, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "X25519MLKEM768+mldsa65", + "group": "X25519MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 274.8, + "median_ns": 3639571.5, + "bytes_total": 12270, + "client_hello_bytes": 1478, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "X25519MLKEM768+mldsa87", + "group": "X25519MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 236.9, + "median_ns": 4221872.5, + "bytes_total": 15546, + "client_hello_bytes": 1478, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "X25519MLKEM768+falcon512", + "group": "X25519MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 310.2, + "median_ns": 3223843.5, + "bytes_total": 5896, + "client_hello_bytes": 1478, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "X25519MLKEM768+sphincssha2128fsimple", + "group": "X25519MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 22.7, + "median_ns": 44119787.5, + "bytes_total": 37952, + "client_hello_bytes": 1478, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "SecP256r1MLKEM768+mldsa44", + "group": "SecP256r1MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 369.2, + "median_ns": 2708792.0, + "bytes_total": 9918, + "client_hello_bytes": 1511, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "SecP256r1MLKEM768+mldsa65", + "group": "SecP256r1MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 275.3, + "median_ns": 3632831.0, + "bytes_total": 12336, + "client_hello_bytes": 1511, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "SecP256r1MLKEM768+mldsa87", + "group": "SecP256r1MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 234.1, + "median_ns": 4271222.5, + "bytes_total": 15612, + "client_hello_bytes": 1511, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "SecP256r1MLKEM768+falcon512", + "group": "SecP256r1MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 294.2, + "median_ns": 3399480.5, + "bytes_total": 5959, + "client_hello_bytes": 1511, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "SecP256r1MLKEM768+sphincssha2128fsimple", + "group": "SecP256r1MLKEM768", + "is_baseline_pair": false, + "handshakes_per_sec": 22.7, + "median_ns": 44087756.0, + "bytes_total": 38018, + "client_hello_bytes": 1511, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem512+mldsa44", + "group": "mlkem512", + "is_baseline_pair": false, + "handshakes_per_sec": 477.6, + "median_ns": 2093657.5, + "bytes_total": 9084, + "client_hello_bytes": 1062, + "client_hello_fragmented": false + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem512+mldsa65", + "group": "mlkem512", + "is_baseline_pair": false, + "handshakes_per_sec": 365.9, + "median_ns": 2732856.0, + "bytes_total": 11502, + "client_hello_bytes": 1062, + "client_hello_fragmented": false + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem512+mldsa87", + "group": "mlkem512", + "is_baseline_pair": false, + "handshakes_per_sec": 274.3, + "median_ns": 3645727.5, + "bytes_total": 14778, + "client_hello_bytes": 1062, + "client_hello_fragmented": false + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem512+falcon512", + "group": "mlkem512", + "is_baseline_pair": false, + "handshakes_per_sec": 386.5, + "median_ns": 2587209.0, + "bytes_total": 5125, + "client_hello_bytes": 1062, + "client_hello_fragmented": false + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem512+sphincssha2128fsimple", + "group": "mlkem512", + "is_baseline_pair": false, + "handshakes_per_sec": 23.2, + "median_ns": 43175152.5, + "bytes_total": 37184, + "client_hello_bytes": 1062, + "client_hello_fragmented": false + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem768+mldsa44", + "group": "mlkem768", + "is_baseline_pair": false, + "handshakes_per_sec": 451.6, + "median_ns": 2214554.5, + "bytes_total": 9788, + "client_hello_bytes": 1446, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem768+mldsa65", + "group": "mlkem768", + "is_baseline_pair": false, + "handshakes_per_sec": 323.7, + "median_ns": 3089621.0, + "bytes_total": 12206, + "client_hello_bytes": 1446, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem768+mldsa87", + "group": "mlkem768", + "is_baseline_pair": false, + "handshakes_per_sec": 266.9, + "median_ns": 3746911.5, + "bytes_total": 15482, + "client_hello_bytes": 1446, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem768+falcon512", + "group": "mlkem768", + "is_baseline_pair": false, + "handshakes_per_sec": 349.4, + "median_ns": 2862030.5, + "bytes_total": 5837, + "client_hello_bytes": 1446, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem768+sphincssha2128fsimple", + "group": "mlkem768", + "is_baseline_pair": false, + "handshakes_per_sec": 23.0, + "median_ns": 43543931.5, + "bytes_total": 37888, + "client_hello_bytes": 1446, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem1024+mldsa44", + "group": "mlkem1024", + "is_baseline_pair": false, + "handshakes_per_sec": 432.7, + "median_ns": 2311099.5, + "bytes_total": 10652, + "client_hello_bytes": 1830, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem1024+mldsa65", + "group": "mlkem1024", + "is_baseline_pair": false, + "handshakes_per_sec": 310.5, + "median_ns": 3220268.0, + "bytes_total": 13070, + "client_hello_bytes": 1830, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem1024+mldsa87", + "group": "mlkem1024", + "is_baseline_pair": false, + "handshakes_per_sec": 259.0, + "median_ns": 3860558.5, + "bytes_total": 16346, + "client_hello_bytes": 1830, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem1024+falcon512", + "group": "mlkem1024", + "is_baseline_pair": false, + "handshakes_per_sec": 357.4, + "median_ns": 2797984.0, + "bytes_total": 6699, + "client_hello_bytes": 1830, + "client_hello_fragmented": true + }, + { + "run_id": "rasberrypi5/Raspberry Pi 5 Model B Rev 1.1@2026-06-08T22:39:28Z", + "hostname": "rasberrypi5", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "is_rpi": true, + "is_baseline_grade": true, + "source_file": "rasberrypi5-20260608T223040Z.json", + "label": "mlkem1024+sphincssha2128fsimple", + "group": "mlkem1024", + "is_baseline_pair": false, + "handshakes_per_sec": 21.8, + "median_ns": 45815730.5, + "bytes_total": 38752, + "client_hello_bytes": 1830, + "client_hello_fragmented": true + } + ] +} \ No newline at end of file diff --git a/pq-bench-rpi5/dashboard/index.html b/pq-bench-rpi5/dashboard/index.html new file mode 100644 index 0000000..520a1dd --- /dev/null +++ b/pq-bench-rpi5/dashboard/index.html @@ -0,0 +1,68 @@ + + + + + +PQ Benchmark — RPi5 baseline + + + + + + +
    +

    Post-Quantum Crypto Benchmark Raspberry Pi 5 baseline

    +

    Migration cost: moving from what Logos uses today + (X25519 + Ed25519) to PQ candidates, on validator-grade hardware. The + classical baseline is drawn as a reference line on every chart.

    +
    + +
    + + + +
    + +
    +
    + +
    +

    Key Encapsulation (KEM)

    +
    +
    +
    +
    +
    +
    + +

    Signatures

    +
    +
    +
    +
    +
    + +

    TLS 1.3 Handshakes (KEM group × signature)

    +
    +
    +
    +
    +
    + +
    +

    Generated by pq-bench-rpi5. Numbers are only RPi5-baseline-grade + when the run banner says so (real Pi 5 · performance governor · core-pinned · + cortex-a76 flags · no thermal throttling). Everything else is a pipeline smoke test.

    +
    + + + + diff --git a/pq-bench-rpi5/dashboard/style.css b/pq-bench-rpi5/dashboard/style.css new file mode 100644 index 0000000..018a304 --- /dev/null +++ b/pq-bench-rpi5/dashboard/style.css @@ -0,0 +1,40 @@ +:root { + --bg:#0f1115; --panel:#181b22; --ink:#e6e8ee; --muted:#9aa3b2; + --accent:#3bd67a; --base:#e0533d; --pq:#3a7bff; --line:#2a2f3a; +} +* { box-sizing:border-box; } +body { margin:0; font:15px/1.5 -apple-system,Segoe UI,Roboto,sans-serif; + background:var(--bg); color:var(--ink); } +header { padding:24px 28px 8px; } +h1 { margin:0; font-size:22px; } +h1 .sub, h2 .sub { color:var(--muted); font-weight:400; font-size:0.7em; } +.framing { color:var(--muted); max-width:760px; } +h2 { margin:30px 28px 8px; border-bottom:1px solid var(--line); padding-bottom:6px; } + +#controls { display:flex; flex-wrap:wrap; gap:18px; align-items:center; + padding:12px 28px; background:var(--panel); margin:8px 0; } +#controls label { color:var(--muted); font-size:14px; } +#controls .hint { font-size:12px; opacity:.7; } +select, input[type=file] { color:var(--ink); background:#0c0e12; + border:1px solid var(--line); border-radius:6px; padding:4px 6px; } + +#quality-banner { margin:8px 28px; padding:12px 16px; border-radius:8px; font-weight:600; } +.banner-good { background:#12331f; border:1px solid #1f7a44; color:#7df0a8; } +.banner-warn { background:#3a2410; border:1px solid #aa6a1f; color:#ffce8a; } +.banner-warn ul { font-weight:400; margin:6px 0 0; color:#ffd9a8; } + +#env-summary { margin:0 28px 8px; color:var(--muted); font-size:13px; + display:flex; flex-wrap:wrap; gap:6px 18px; } +#env-summary b { color:var(--ink); } +#env-summary .chip { background:var(--panel); padding:3px 9px; border-radius:20px; + border:1px solid var(--line); } + +.chart-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(420px,1fr)); + gap:18px; padding:14px 28px; } +figure { margin:0; background:var(--panel); border:1px solid var(--line); + border-radius:10px; padding:12px; min-height:320px; } +canvas { max-height:340px; } + +footer { color:var(--muted); font-size:12px; padding:18px 28px 40px; max-width:820px; } +code { background:#0c0e12; padding:1px 5px; border-radius:4px; } +.empty { color:var(--muted); padding:30px; text-align:center; } diff --git a/pq-bench-rpi5/results/.gitkeep b/pq-bench-rpi5/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pq-bench-rpi5/results/example-macos-smoke.json b/pq-bench-rpi5/results/example-macos-smoke.json new file mode 100644 index 0000000..09149ce --- /dev/null +++ b/pq-bench-rpi5/results/example-macos-smoke.json @@ -0,0 +1,1984 @@ +{ + "schema_version": "1.0.0", + "tool_version": "0.1.0", + "generated_utc": "2026-06-07T21:58:43Z", + "is_baseline_grade": false, + "baseline_grade_reasons": [ + "host is not a Raspberry Pi (model='', os=macos)", + "CPU governor is 'unavailable', not 'performance'", + "benchmark was not pinned to a dedicated core (no taskset)", + "build flags targeted 'generic-fallback', not cortex-a76" + ], + "host": { + "hostname": "unknown52f9cccdc6d7.home", + "os": "macos", + "os_pretty": "macOS 26.3.1 (25D771280a)", + "arch": "arm64", + "kernel": "25.3.0", + "is_rpi": false, + "rpi_model": "", + "cpu_brand": "Apple M3", + "ncpu": 8, + "ram_bytes": 17179869184 + }, + "cpu_features": { + "source": "sysctl", + "neon": true, + "sha2": true, + "sha3": true, + "sha512": true, + "aes": true, + "pmull": true + }, + "run": { + "timestamp_start_utc": "2026-06-07T21:58:10Z", + "timestamp_end_utc": "2026-06-07T21:58:43Z", + "duration_s": 33, + "governor_requested": "performance", + "governor_before": "unavailable", + "governor_after": "unavailable", + "bench_core": 3, + "pinned": false, + "taskset_cmd": "", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "cycles_mode": "auto", + "cycles_available": false, + "cycles_reason": "PMCCNTR_EL0 traps (kernel module not loaded; needs e.g. enable_arm_pmu)" + }, + "toolchain": { + "cc_version": "Apple clang version 17.0.0 (clang-1700.6.4.2)", + "bench_cflags": "-O3", + "cflags_target": "generic-fallback", + "liboqs_ref": "0.15.0", + "liboqs_commit": "not-built", + "liboqs_opt_defines": "", + "openssl": "system:3.6.0", + "oqsprovider_ref": "0.9.0", + "oqsprovider_commit": "848b4e6abaa89e769c4db46ca78f91000f67ca52" + }, + "thermal_trace": { + "columns": [ + "epoch_s", + "arm_clock_hz", + "temp_c", + "throttled_hex" + ], + "samples": [ + [ + 1780869490, + null, + null, + null + ], + [ + 1780869490, + null, + null, + null + ], + [ + 1780869491, + null, + null, + null + ], + [ + 1780869492, + null, + null, + null + ], + [ + 1780869493, + null, + null, + null + ], + [ + 1780869494, + null, + null, + null + ], + [ + 1780869495, + null, + null, + null + ], + [ + 1780869496, + null, + null, + null + ], + [ + 1780869497, + null, + null, + null + ], + [ + 1780869498, + null, + null, + null + ], + [ + 1780869499, + null, + null, + null + ], + [ + 1780869500, + null, + null, + null + ], + [ + 1780869501, + null, + null, + null + ], + [ + 1780869502, + null, + null, + null + ], + [ + 1780869503, + null, + null, + null + ], + [ + 1780869504, + null, + null, + null + ], + [ + 1780869505, + null, + null, + null + ], + [ + 1780869506, + null, + null, + null + ], + [ + 1780869507, + null, + null, + null + ], + [ + 1780869508, + null, + null, + null + ], + [ + 1780869509, + null, + null, + null + ], + [ + 1780869510, + null, + null, + null + ], + [ + 1780869511, + null, + null, + null + ], + [ + 1780869512, + null, + null, + null + ], + [ + 1780869513, + null, + null, + null + ], + [ + 1780869514, + null, + null, + null + ], + [ + 1780869515, + null, + null, + null + ], + [ + 1780869516, + null, + null, + null + ], + [ + 1780869517, + null, + null, + null + ], + [ + 1780869518, + null, + null, + null + ], + [ + 1780869519, + null, + null, + null + ], + [ + 1780869520, + null, + null, + null + ], + [ + 1780869521, + null, + null, + null + ], + [ + 1780869522, + null, + null, + null + ] + ], + "temp_c": null, + "arm_clock_hz": null, + "throttling_detected": false + }, + "warnings": [ + "governor is 'unavailable', not 'performance' (need root on Linux, or unsupported on macOS)", + "core pinning unavailable (no taskset/numactl) \u2014 results will be noisier", + "NOT RPi5-baseline-grade: host is not a Raspberry Pi (model='', os=macos); CPU governor is 'unavailable', not 'performance'; benchmark was not pinned to a dedicated core (no taskset); build flags targeted 'generic-fallback', not cortex-a76" + ], + "kem": [ + { + "alg": "X25519", + "kind": "kem", + "backend": "openssl", + "classical": true, + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 32, + "secret_key": 32, + "ciphertext": null, + "shared_secret": 32 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 45000.0, + "mad": 0.0, + "iqr": 1000.0, + "q1": 44000.0, + "q3": 45000.0, + "min": 44000.0, + "max": 47000.0, + "mean": 44720.0, + "stddev": 678.23, + "ops_per_sec": 22222.22, + "per_rep_median": [ + 45000.0 + ] + }, + "derive": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 43000.0, + "mad": 0.0, + "iqr": 0.0, + "q1": 43000.0, + "q3": 43000.0, + "min": 43000.0, + "max": 44000.0, + "mean": 43120.0, + "stddev": 331.66, + "ops_per_sec": 23255.81, + "per_rep_median": [ + 43000.0 + ] + } + } + }, + { + "alg": "ML-KEM-512", + "kind": "kem", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 800, + "secret_key": 1632, + "ciphertext": 768, + "shared_secret": 32 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 10000.0, + "mad": 0.0, + "iqr": 1000.0, + "q1": 9000.0, + "q3": 10000.0, + "min": 9000.0, + "max": 12000.0, + "mean": 9800.0, + "stddev": 707.11, + "ops_per_sec": 100000.0, + "per_rep_median": [ + 10000.0 + ] + }, + "encaps": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 11000.0, + "mad": 0.0, + "iqr": 0.0, + "q1": 11000.0, + "q3": 11000.0, + "min": 10000.0, + "max": 11000.0, + "mean": 10800.0, + "stddev": 408.25, + "ops_per_sec": 90909.09, + "per_rep_median": [ + 11000.0 + ] + }, + "decaps": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 13000.0, + "mad": 0.0, + "iqr": 1000.0, + "q1": 12000.0, + "q3": 13000.0, + "min": 12000.0, + "max": 15000.0, + "mean": 12720.0, + "stddev": 678.23, + "ops_per_sec": 76923.08, + "per_rep_median": [ + 13000.0 + ] + } + } + }, + { + "alg": "ML-KEM-768", + "kind": "kem", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 3, + "sizes": { + "public_key": 1184, + "secret_key": 2400, + "ciphertext": 1088, + "shared_secret": 32 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 16000.0, + "mad": 0.0, + "iqr": 0.0, + "q1": 16000.0, + "q3": 16000.0, + "min": 15000.0, + "max": 20000.0, + "mean": 16200.0, + "stddev": 1118.03, + "ops_per_sec": 62500.0, + "per_rep_median": [ + 16000.0 + ] + }, + "encaps": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 17000.0, + "mad": 0.0, + "iqr": 0.0, + "q1": 17000.0, + "q3": 17000.0, + "min": 16000.0, + "max": 17000.0, + "mean": 16920.0, + "stddev": 276.89, + "ops_per_sec": 58823.53, + "per_rep_median": [ + 17000.0 + ] + }, + "decaps": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 20000.0, + "mad": 0.0, + "iqr": 0.0, + "q1": 20000.0, + "q3": 20000.0, + "min": 19000.0, + "max": 23000.0, + "mean": 19960.0, + "stddev": 888.82, + "ops_per_sec": 50000.0, + "per_rep_median": [ + 20000.0 + ] + } + } + }, + { + "alg": "ML-KEM-1024", + "kind": "kem", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 5, + "sizes": { + "public_key": 1568, + "secret_key": 3168, + "ciphertext": 1568, + "shared_secret": 32 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 24000.0, + "mad": 1000.0, + "iqr": 1000.0, + "q1": 23000.0, + "q3": 24000.0, + "min": 23000.0, + "max": 26000.0, + "mean": 23880.0, + "stddev": 832.67, + "ops_per_sec": 41666.67, + "per_rep_median": [ + 24000.0 + ] + }, + "encaps": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 26000.0, + "mad": 0.0, + "iqr": 1000.0, + "q1": 25000.0, + "q3": 26000.0, + "min": 25000.0, + "max": 26000.0, + "mean": 25680.0, + "stddev": 476.1, + "ops_per_sec": 38461.54, + "per_rep_median": [ + 26000.0 + ] + }, + "decaps": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 30000.0, + "mad": 0.0, + "iqr": 0.0, + "q1": 30000.0, + "q3": 30000.0, + "min": 30000.0, + "max": 32000.0, + "mean": 30080.0, + "stddev": 400.0, + "ops_per_sec": 33333.33, + "per_rep_median": [ + 30000.0 + ] + } + } + }, + { + "alg": "X25519MLKEM768", + "kind": "kem", + "backend": "liboqs", + "enabled": false, + "reason": "not enabled in this liboqs build" + }, + { + "alg": "SecP256r1MLKEM768", + "kind": "kem", + "backend": "liboqs", + "enabled": false, + "reason": "not enabled in this liboqs build" + } + ], + "sig": [ + { + "alg": "Ed25519", + "kind": "sig", + "backend": "openssl", + "classical": true, + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 32, + "secret_key": 32, + "signature": 64 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 45000.0, + "mad": 0.0, + "iqr": 2000.0, + "q1": 45000.0, + "q3": 47000.0, + "min": 45000.0, + "max": 52000.0, + "mean": 46160.0, + "stddev": 1841.2, + "ops_per_sec": 22222.22, + "per_rep_median": [ + 45000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 45000.0, + "mad": 0.0, + "iqr": 2000.0, + "q1": 45000.0, + "q3": 47000.0, + "min": 45000.0, + "max": 49000.0, + "mean": 45960.0, + "stddev": 1457.17, + "ops_per_sec": 22222.22, + "per_rep_median": [ + 45000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 128000.0, + "mad": 0.0, + "iqr": 0.0, + "q1": 128000.0, + "q3": 128000.0, + "min": 127000.0, + "max": 134000.0, + "mean": 128240.0, + "stddev": 1562.05, + "ops_per_sec": 7812.5, + "per_rep_median": [ + 128000.0 + ] + } + } + }, + { + "alg": "ML-DSA-44", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 2, + "sizes": { + "public_key": 1312, + "secret_key": 2560, + "signature": 2420 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 63000.0, + "mad": 0.0, + "iqr": 1000.0, + "q1": 62000.0, + "q3": 63000.0, + "min": 61000.0, + "max": 63000.0, + "mean": 62480.0, + "stddev": 585.95, + "ops_per_sec": 15873.02, + "per_rep_median": [ + 63000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 262000.0, + "mad": 137000.0, + "iqr": 328000.0, + "q1": 126000.0, + "q3": 454000.0, + "min": 115000.0, + "max": 1197000.0, + "mean": 328600.0, + "stddev": 249040.66, + "ops_per_sec": 3816.79, + "per_rep_median": [ + 262000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 67000.0, + "mad": 0.0, + "iqr": 1000.0, + "q1": 67000.0, + "q3": 68000.0, + "min": 67000.0, + "max": 68000.0, + "mean": 67360.0, + "stddev": 489.9, + "ops_per_sec": 14925.37, + "per_rep_median": [ + 67000.0 + ] + } + } + }, + { + "alg": "ML-DSA-65", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 3, + "sizes": { + "public_key": 1952, + "secret_key": 4032, + "signature": 3309 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 123000.0, + "mad": 1000.0, + "iqr": 3000.0, + "q1": 122000.0, + "q3": 125000.0, + "min": 121000.0, + "max": 133000.0, + "mean": 124080.0, + "stddev": 2812.47, + "ops_per_sec": 8130.08, + "per_rep_median": [ + 123000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 418000.0, + "mad": 242000.0, + "iqr": 490000.0, + "q1": 254000.0, + "q3": 744000.0, + "min": 172000.0, + "max": 1509000.0, + "mean": 551920.0, + "stddev": 401189.06, + "ops_per_sec": 2392.34, + "per_rep_median": [ + 418000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 112000.0, + "mad": 2000.0, + "iqr": 4000.0, + "q1": 109000.0, + "q3": 113000.0, + "min": 109000.0, + "max": 116000.0, + "mean": 111160.0, + "stddev": 2095.23, + "ops_per_sec": 8928.57, + "per_rep_median": [ + 112000.0 + ] + } + } + }, + { + "alg": "ML-DSA-87", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 5, + "sizes": { + "public_key": 2592, + "secret_key": 4896, + "signature": 4627 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 177000.0, + "mad": 2000.0, + "iqr": 3000.0, + "q1": 175000.0, + "q3": 178000.0, + "min": 173000.0, + "max": 279000.0, + "mean": 180520.0, + "stddev": 20631.93, + "ops_per_sec": 5649.72, + "per_rep_median": [ + 177000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 455000.0, + "mad": 186000.0, + "iqr": 413000.0, + "q1": 270000.0, + "q3": 683000.0, + "min": 267000.0, + "max": 1076000.0, + "mean": 503520.0, + "stddev": 251255.2, + "ops_per_sec": 2197.8, + "per_rep_median": [ + 455000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 181000.0, + "mad": 0.0, + "iqr": 0.0, + "q1": 181000.0, + "q3": 181000.0, + "min": 180000.0, + "max": 183000.0, + "mean": 180920.0, + "stddev": 571.55, + "ops_per_sec": 5524.86, + "per_rep_median": [ + 181000.0 + ] + } + } + }, + { + "alg": "Falcon-512", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 897, + "secret_key": 1281, + "signature": 752 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 6687000.0, + "mad": 849000.0, + "iqr": 2341000.0, + "q1": 5897000.0, + "q3": 8238000.0, + "min": 5646000.0, + "max": 12033000.0, + "mean": 7198280.0, + "stddev": 1695393.35, + "ops_per_sec": 149.54, + "per_rep_median": [ + 6687000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 205000.0, + "mad": 2000.0, + "iqr": 3000.0, + "q1": 204000.0, + "q3": 207000.0, + "min": 200000.0, + "max": 213000.0, + "mean": 205600.0, + "stddev": 2783.88, + "ops_per_sec": 4878.05, + "per_rep_median": [ + 205000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 32000.0, + "mad": 0.0, + "iqr": 1000.0, + "q1": 31000.0, + "q3": 32000.0, + "min": 31000.0, + "max": 32000.0, + "mean": 31680.0, + "stddev": 476.1, + "ops_per_sec": 31250.0, + "per_rep_median": [ + 32000.0 + ] + } + } + }, + { + "alg": "Falcon-1024", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 5, + "sizes": { + "public_key": 1793, + "secret_key": 2305, + "signature": 1462 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 20789000.0, + "mad": 1209000.0, + "iqr": 1648000.0, + "q1": 19580000.0, + "q3": 21228000.0, + "min": 19074000.0, + "max": 35284000.0, + "mean": 21662440.0, + "stddev": 3564747.46, + "ops_per_sec": 48.1, + "per_rep_median": [ + 20789000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 414000.0, + "mad": 3000.0, + "iqr": 7000.0, + "q1": 410000.0, + "q3": 417000.0, + "min": 399000.0, + "max": 426000.0, + "mean": 413960.0, + "stddev": 5175.91, + "ops_per_sec": 2415.46, + "per_rep_median": [ + 414000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 62000.0, + "mad": 1000.0, + "iqr": 1000.0, + "q1": 61000.0, + "q3": 62000.0, + "min": 61000.0, + "max": 65000.0, + "mean": 61880.0, + "stddev": 1129.9, + "ops_per_sec": 16129.03, + "per_rep_median": [ + 62000.0 + ] + } + } + }, + { + "alg": "SPHINCS+-SHA2-128f-simple", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 17088 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 876000.0, + "mad": 4000.0, + "iqr": 28000.0, + "q1": 873000.0, + "q3": 901000.0, + "min": 871000.0, + "max": 908000.0, + "mean": 884280.0, + "stddev": 14310.6, + "ops_per_sec": 1141.55, + "per_rep_median": [ + 876000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 20450000.0, + "mad": 80000.0, + "iqr": 152000.0, + "q1": 20403000.0, + "q3": 20555000.0, + "min": 20267000.0, + "max": 21063000.0, + "mean": 20496960.0, + "stddev": 179759.9, + "ops_per_sec": 48.9, + "per_rep_median": [ + 20450000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 1257000.0, + "mad": 2000.0, + "iqr": 5000.0, + "q1": 1255000.0, + "q3": 1260000.0, + "min": 1249000.0, + "max": 1279000.0, + "mean": 1258480.0, + "stddev": 5598.51, + "ops_per_sec": 795.54, + "per_rep_median": [ + 1257000.0 + ] + } + } + }, + { + "alg": "SPHINCS+-SHA2-128s-simple", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 7856 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 56080000.0, + "mad": 465000.0, + "iqr": 1017000.0, + "q1": 55411000.0, + "q3": 56428000.0, + "min": 54927000.0, + "max": 57378000.0, + "mean": 56011360.0, + "stddev": 673215.35, + "ops_per_sec": 17.83, + "per_rep_median": [ + 56080000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 424815000.0, + "mad": 997000.0, + "iqr": 1743000.0, + "q1": 423487000.0, + "q3": 425230000.0, + "min": 420032000.0, + "max": 427471000.0, + "mean": 424356920.0, + "stddev": 1941862.23, + "ops_per_sec": 2.35, + "per_rep_median": [ + 424815000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 428000.0, + "mad": 0.0, + "iqr": 1000.0, + "q1": 428000.0, + "q3": 429000.0, + "min": 428000.0, + "max": 434000.0, + "mean": 429040.0, + "stddev": 1645.2, + "ops_per_sec": 2336.45, + "per_rep_median": [ + 428000.0 + ] + } + } + }, + { + "alg": "SPHINCS+-SHA2-192f-simple", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 3, + "sizes": { + "public_key": 48, + "secret_key": 96, + "signature": 35664 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 1275000.0, + "mad": 1000.0, + "iqr": 3000.0, + "q1": 1274000.0, + "q3": 1277000.0, + "min": 1271000.0, + "max": 1279000.0, + "mean": 1275360.0, + "stddev": 2079.26, + "ops_per_sec": 784.31, + "per_rep_median": [ + 1275000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 33575000.0, + "mad": 221000.0, + "iqr": 338000.0, + "q1": 33458000.0, + "q3": 33796000.0, + "min": 33265000.0, + "max": 34036000.0, + "mean": 33614440.0, + "stddev": 252247.41, + "ops_per_sec": 29.78, + "per_rep_median": [ + 33575000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 1850000.0, + "mad": 1000.0, + "iqr": 2000.0, + "q1": 1849000.0, + "q3": 1851000.0, + "min": 1846000.0, + "max": 1858000.0, + "mean": 1850280.0, + "stddev": 2557.99, + "ops_per_sec": 540.54, + "per_rep_median": [ + 1850000.0 + ] + } + } + }, + { + "alg": "SPHINCS+-SHA2-256f-simple", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 5, + "sizes": { + "public_key": 64, + "secret_key": 128, + "signature": 49856 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 3340000.0, + "mad": 11000.0, + "iqr": 25000.0, + "q1": 3320000.0, + "q3": 3345000.0, + "min": 3313000.0, + "max": 3351000.0, + "mean": 3333560.0, + "stddev": 13323.16, + "ops_per_sec": 299.4, + "per_rep_median": [ + 3340000.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 68877000.0, + "mad": 345000.0, + "iqr": 579000.0, + "q1": 68515000.0, + "q3": 69094000.0, + "min": 68430000.0, + "max": 70399000.0, + "mean": 68939880.0, + "stddev": 520043.05, + "ops_per_sec": 14.52, + "per_rep_median": [ + 68877000.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 25, + "repetitions": 1, + "samples": 25, + "median": 1862000.0, + "mad": 7000.0, + "iqr": 12000.0, + "q1": 1855000.0, + "q3": 1867000.0, + "min": 1853000.0, + "max": 1897000.0, + "mean": 1863640.0, + "stddev": 10491.58, + "ops_per_sec": 537.06, + "per_rep_median": [ + 1862000.0 + ] + } + } + } + ], + "tls": { + "available": true, + "have_oqs_provider": true, + "baseline": { + "kem_group": "X25519", + "sig_alg": "ed25519", + "label": "X25519+ed25519" + }, + "matrix": [ + { + "label": "X25519+ed25519", + "group": "X25519", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 718000.0, + "p95": 828550.0, + "min": 714000.0, + "max": 830000.0, + "mean": 757620.0, + "stddev": 53231.8 + }, + "handshakes_per_sec": 1392.8, + "bytes_on_wire": { + "client_to_server": 371, + "server_to_client": 1189, + "total": 1560 + }, + "client_hello_bytes": 291, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+mldsa44", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1164500.0, + "p95": 2062150.0, + "min": 984000.0, + "max": 2852000.0, + "mean": 1351120.0, + "stddev": 413406.3 + }, + "handshakes_per_sec": 858.7, + "bytes_on_wire": { + "client_to_server": 1549, + "server_to_client": 8294, + "total": 9843 + }, + "client_hello_bytes": 1469, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+mldsa65", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1438500.0, + "p95": 2675000.0, + "min": 1184000.0, + "max": 2916000.0, + "mean": 1616580.0, + "stddev": 458735.5 + }, + "handshakes_per_sec": 695.2, + "bytes_on_wire": { + "client_to_server": 1549, + "server_to_client": 10712, + "total": 12261 + }, + "client_hello_bytes": 1469, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+mldsa87", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1849500.0, + "p95": 3023150.0, + "min": 1493000.0, + "max": 3842000.0, + "mean": 1976920.0, + "stddev": 571179.5 + }, + "handshakes_per_sec": 540.7, + "bytes_on_wire": { + "client_to_server": 1549, + "server_to_client": 13988, + "total": 15537 + }, + "client_hello_bytes": 1469, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+falcon512", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1666000.0, + "p95": 1679650.0, + "min": 1658000.0, + "max": 1704000.0, + "mean": 1667740.0, + "stddev": 7501.9 + }, + "handshakes_per_sec": 600.2, + "bytes_on_wire": { + "client_to_server": 1549, + "server_to_client": 4340, + "total": 5889 + }, + "client_hello_bytes": 1469, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+sphincssha2128fsimple", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 24694000.0, + "p95": 24968750.0, + "min": 24159000.0, + "max": 25160000.0, + "mean": 24587740.0, + "stddev": 286146.6 + }, + "handshakes_per_sec": 40.5, + "bytes_on_wire": { + "client_to_server": 1549, + "server_to_client": 36394, + "total": 37943 + }, + "client_hello_bytes": 1469, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+mldsa44", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1162500.0, + "p95": 1870200.0, + "min": 978000.0, + "max": 2223000.0, + "mean": 1279720.0, + "stddev": 308337.7 + }, + "handshakes_per_sec": 860.2, + "bytes_on_wire": { + "client_to_server": 1582, + "server_to_client": 8327, + "total": 9909 + }, + "client_hello_bytes": 1502, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+mldsa65", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1550500.0, + "p95": 2489750.0, + "min": 1177000.0, + "max": 3902000.0, + "mean": 1679860.0, + "stddev": 532703.9 + }, + "handshakes_per_sec": 645.0, + "bytes_on_wire": { + "client_to_server": 1582, + "server_to_client": 10745, + "total": 12327 + }, + "client_hello_bytes": 1502, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+mldsa87", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1848500.0, + "p95": 2764400.0, + "min": 1488000.0, + "max": 3460000.0, + "mean": 2009540.0, + "stddev": 468264.5 + }, + "handshakes_per_sec": 541.0, + "bytes_on_wire": { + "client_to_server": 1582, + "server_to_client": 14021, + "total": 15603 + }, + "client_hello_bytes": 1502, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+falcon512", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1659000.0, + "p95": 1686500.0, + "min": 1647000.0, + "max": 1703000.0, + "mean": 1661640.0, + "stddev": 10974.5 + }, + "handshakes_per_sec": 602.8, + "bytes_on_wire": { + "client_to_server": 1582, + "server_to_client": 4371, + "total": 5953 + }, + "client_hello_bytes": 1502, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+sphincssha2128fsimple", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 24647000.0, + "p95": 25309200.0, + "min": 24267000.0, + "max": 25345000.0, + "mean": 24707220.0, + "stddev": 259591.9 + }, + "handshakes_per_sec": 40.6, + "bytes_on_wire": { + "client_to_server": 1582, + "server_to_client": 36427, + "total": 38009 + }, + "client_hello_bytes": 1502, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+mldsa44", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 979500.0, + "p95": 1615750.0, + "min": 750000.0, + "max": 2178000.0, + "mean": 1057340.0, + "stddev": 313571.1 + }, + "handshakes_per_sec": 1020.9, + "bytes_on_wire": { + "client_to_server": 1133, + "server_to_client": 7942, + "total": 9075 + }, + "client_hello_bytes": 1053, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+mldsa65", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1214500.0, + "p95": 2321850.0, + "min": 947000.0, + "max": 3422000.0, + "mean": 1453700.0, + "stddev": 519516.9 + }, + "handshakes_per_sec": 823.4, + "bytes_on_wire": { + "client_to_server": 1133, + "server_to_client": 10360, + "total": 11493 + }, + "client_hello_bytes": 1053, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+mldsa87", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1619500.0, + "p95": 3321500.0, + "min": 1259000.0, + "max": 4298000.0, + "mean": 1856960.0, + "stddev": 678196.3 + }, + "handshakes_per_sec": 617.5, + "bytes_on_wire": { + "client_to_server": 1133, + "server_to_client": 13636, + "total": 14769 + }, + "client_hello_bytes": 1053, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+falcon512", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1426500.0, + "p95": 1443200.0, + "min": 1418000.0, + "max": 1457000.0, + "mean": 1428400.0, + "stddev": 8076.2 + }, + "handshakes_per_sec": 701.0, + "bytes_on_wire": { + "client_to_server": 1133, + "server_to_client": 3985, + "total": 5118 + }, + "client_hello_bytes": 1053, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+sphincssha2128fsimple", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 24783500.0, + "p95": 25203350.0, + "min": 24076000.0, + "max": 25279000.0, + "mean": 24785000.0, + "stddev": 300532.8 + }, + "handshakes_per_sec": 40.3, + "bytes_on_wire": { + "client_to_server": 1133, + "server_to_client": 36042, + "total": 37175 + }, + "client_hello_bytes": 1053, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+mldsa44", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1064500.0, + "p95": 1649400.0, + "min": 798000.0, + "max": 2045000.0, + "mean": 1091380.0, + "stddev": 284858.9 + }, + "handshakes_per_sec": 939.4, + "bytes_on_wire": { + "client_to_server": 1517, + "server_to_client": 8262, + "total": 9779 + }, + "client_hello_bytes": 1437, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+mldsa65", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1498000.0, + "p95": 2371300.0, + "min": 998000.0, + "max": 2498000.0, + "mean": 1557720.0, + "stddev": 441865.1 + }, + "handshakes_per_sec": 667.6, + "bytes_on_wire": { + "client_to_server": 1517, + "server_to_client": 10680, + "total": 12197 + }, + "client_hello_bytes": 1437, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+mldsa87", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1505500.0, + "p95": 2934650.0, + "min": 1307000.0, + "max": 4169000.0, + "mean": 1781420.0, + "stddev": 605205.2 + }, + "handshakes_per_sec": 664.2, + "bytes_on_wire": { + "client_to_server": 1517, + "server_to_client": 13956, + "total": 15473 + }, + "client_hello_bytes": 1437, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+falcon512", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1470500.0, + "p95": 1488550.0, + "min": 1462000.0, + "max": 1494000.0, + "mean": 1472240.0, + "stddev": 7506.6 + }, + "handshakes_per_sec": 680.0, + "bytes_on_wire": { + "client_to_server": 1517, + "server_to_client": 4304, + "total": 5821 + }, + "client_hello_bytes": 1437, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+sphincssha2128fsimple", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 24828000.0, + "p95": 25209050.0, + "min": 24098000.0, + "max": 25361000.0, + "mean": 24707580.0, + "stddev": 376056.4 + }, + "handshakes_per_sec": 40.3, + "bytes_on_wire": { + "client_to_server": 1517, + "server_to_client": 36362, + "total": 37879 + }, + "client_hello_bytes": 1437, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+mldsa44", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 955500.0, + "p95": 1726350.0, + "min": 858000.0, + "max": 1840000.0, + "mean": 1074120.0, + "stddev": 277874.3 + }, + "handshakes_per_sec": 1046.6, + "bytes_on_wire": { + "client_to_server": 1901, + "server_to_client": 8742, + "total": 10643 + }, + "client_hello_bytes": 1821, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+mldsa65", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1313000.0, + "p95": 2008600.0, + "min": 1058000.0, + "max": 2189000.0, + "mean": 1398980.0, + "stddev": 329537.9 + }, + "handshakes_per_sec": 761.6, + "bytes_on_wire": { + "client_to_server": 1901, + "server_to_client": 11160, + "total": 13061 + }, + "client_hello_bytes": 1821, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+mldsa87", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1594500.0, + "p95": 3019350.0, + "min": 1368000.0, + "max": 4951000.0, + "mean": 1903960.0, + "stddev": 680464.9 + }, + "handshakes_per_sec": 627.2, + "bytes_on_wire": { + "client_to_server": 1901, + "server_to_client": 14436, + "total": 16337 + }, + "client_hello_bytes": 1821, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+falcon512", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 1534500.0, + "p95": 1552200.0, + "min": 1528000.0, + "max": 1561000.0, + "mean": 1536420.0, + "stddev": 7154.5 + }, + "handshakes_per_sec": 651.7, + "bytes_on_wire": { + "client_to_server": 1901, + "server_to_client": 4785, + "total": 6686 + }, + "client_hello_bytes": 1821, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+sphincssha2128fsimple", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 50, + "succeeded": 50, + "handshake_latency_ns": { + "median": 24732000.0, + "p95": 25370350.0, + "min": 24139000.0, + "max": 25729000.0, + "mean": 24781760.0, + "stddev": 347989.0 + }, + "handshakes_per_sec": 40.4, + "bytes_on_wire": { + "client_to_server": 1901, + "server_to_client": 36842, + "total": 38743 + }, + "client_hello_bytes": 1821, + "client_hello_fragmented": true, + "mss_assumed": 1400 + } + ] + } +} \ No newline at end of file diff --git a/pq-bench-rpi5/results/rasberrypi5-20260608T223040Z.json b/pq-bench-rpi5/results/rasberrypi5-20260608T223040Z.json new file mode 100644 index 0000000..486cf86 --- /dev/null +++ b/pq-bench-rpi5/results/rasberrypi5-20260608T223040Z.json @@ -0,0 +1,5152 @@ +{ + "schema_version": "1.0.0", + "tool_version": "0.1.0", + "generated_utc": "2026-06-08T22:39:28Z", + "is_baseline_grade": true, + "baseline_grade_reasons": [], + "host": { + "hostname": "rasberrypi5", + "os": "linux", + "os_pretty": "Debian GNU/Linux 13 (trixie)", + "arch": "aarch64", + "kernel": "6.12.47+rpt-rpi-2712", + "is_rpi": true, + "rpi_model": "Raspberry Pi 5 Model B Rev 1.1", + "cpu_brand": "Raspberry Pi 5 Model B Rev 1.1", + "ncpu": 4, + "ram_bytes": 8454684672 + }, + "cpu_features": { + "source": "/proc/cpuinfo", + "neon": true, + "sha2": true, + "sha3": false, + "sha512": false, + "aes": true, + "pmull": true + }, + "run": { + "timestamp_start_utc": "2026-06-08T22:30:40Z", + "timestamp_end_utc": "2026-06-08T22:39:28Z", + "duration_s": 528, + "governor_requested": "performance", + "governor_before": "performance", + "governor_after": "performance", + "bench_core": 3, + "pinned": true, + "taskset_cmd": "taskset -c 3", + "calibration_mode": "auto", + "target_time_ms": 250, + "min_samples": 30, + "max_iters": 20000, + "warmup_iters": null, + "timed_iters": null, + "repetitions": 5, + "cycles_mode": "auto", + "cycles_available": false, + "cycles_reason": "PMCCNTR_EL0 traps (kernel module not loaded; needs e.g. enable_arm_pmu)" + }, + "toolchain": { + "cc_version": "cc (Debian 14.2.0-19) 14.2.0", + "bench_cflags": "-O3 -mcpu=cortex-a76", + "cflags_target": "cortex-a76", + "liboqs_ref": "0.15.0", + "liboqs_commit": "97f6b86b1b6d109cfd43cf276ae39c2e776aed80", + "liboqs_opt_defines": "OQS_COMPILE_BUILD_TARGET aarch64-Linux-6.12.47+rpt-rpi-2712;ARCH_ARM64v8 1;OQS_OPT_TARGET auto;OQS_USE_ARM_AES_INSTRUCTIONS 1;OQS_USE_ARM_SHA2_INSTRUCTIONS 1;OQS_USE_ARM_NEON_INSTRUCTIONS 1;OQS_ENABLE_KEM_kyber_512_aarch64 1;OQS_ENABLE_KEM_kyber_768_aarch64 1;OQS_ENABLE_KEM_kyber_1024_aarch64 1;OQS_ENABLE_KEM_ml_kem_512_aarch64 1;OQS_ENABLE_KEM_ml_kem_768_aarch64 1;OQS_ENABLE_KEM_ml_kem_1024_aarch64 1;OQS_ENABLE_SIG_falcon_512_aarch64 1;OQS_ENABLE_SIG_falcon_1024_aarch64 1;OQS_ENABLE_SIG_falcon_padded_512_aarch64 1;OQS_ENABLE_SIG_falcon_padded_1024_aarch64 1;", + "openssl": "system:3.5.6", + "oqsprovider_ref": "0.9.0", + "oqsprovider_commit": "848b4e6abaa89e769c4db46ca78f91000f67ca52" + }, + "thermal_trace": { + "columns": [ + "epoch_s", + "arm_clock_hz", + "temp_c", + "throttled_hex" + ], + "samples": [ + [ + 1780957840, + 2400027136, + 56.5, + "0x0" + ], + [ + 1780957840, + 2400023808, + 56.0, + "0x0" + ], + [ + 1780957841, + 2400027136, + 57.1, + "0x0" + ], + [ + 1780957842, + 2400027136, + 57.1, + "0x0" + ], + [ + 1780957843, + 2400027136, + 57.1, + "0x0" + ], + [ + 1780957844, + 2400033792, + 58.2, + "0x0" + ], + [ + 1780957845, + 2400027136, + 58.2, + "0x0" + ], + [ + 1780957846, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780957847, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780957848, + 2400023808, + 58.2, + "0x0" + ], + [ + 1780957849, + 2400027136, + 58.2, + "0x0" + ], + [ + 1780957850, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780957851, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780957852, + 2400037120, + 58.2, + "0x0" + ], + [ + 1780957853, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780957854, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780957855, + 2400027136, + 58.2, + "0x0" + ], + [ + 1780957856, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780957857, + 2400017408, + 58.2, + "0x0" + ], + [ + 1780957858, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780957859, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780957860, + 2400017408, + 58.2, + "0x0" + ], + [ + 1780957861, + 2400027136, + 57.6, + "0x0" + ], + [ + 1780957862, + 2400037120, + 58.7, + "0x0" + ], + [ + 1780957863, + 2400033792, + 58.7, + "0x0" + ], + [ + 1780957864, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957865, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780957866, + 2400030464, + 58.7, + "0x0" + ], + [ + 1780957867, + 2400023808, + 58.2, + "0x0" + ], + [ + 1780957868, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780957869, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780957870, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780957871, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780957872, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780957873, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780957875, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957876, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780957877, + 2400030464, + 58.7, + "0x0" + ], + [ + 1780957878, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780957879, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780957880, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780957881, + 2400030464, + 58.2, + "0x0" + ], + [ + 1780957882, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780957883, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780957884, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780957885, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780957886, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780957887, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780957888, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780957889, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780957890, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957891, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957892, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780957893, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957894, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780957895, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780957896, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780957897, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780957898, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780957899, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957900, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780957901, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780957902, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957903, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780957904, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957905, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780957906, + 2400030464, + 60.4, + "0x0" + ], + [ + 1780957907, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780957908, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780957909, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957910, + 2400027136, + 60.4, + "0x0" + ], + [ + 1780957911, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957912, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780957913, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957914, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957915, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780957916, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780957917, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957918, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957919, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957920, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780957921, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957922, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780957923, + 2400023808, + 60.4, + "0x0" + ], + [ + 1780957924, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957925, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780957926, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780957927, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957928, + 2400027136, + 60.4, + "0x0" + ], + [ + 1780957930, + 2400030464, + 60.4, + "0x0" + ], + [ + 1780957931, + 2400023808, + 60.4, + "0x0" + ], + [ + 1780957932, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780957933, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957934, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780957935, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957936, + 2400033792, + 60.4, + "0x0" + ], + [ + 1780957937, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957938, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957939, + 2400017408, + 60.4, + "0x0" + ], + [ + 1780957940, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780957941, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780957942, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957943, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780957944, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780957945, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957946, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780957947, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780957948, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780957949, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957950, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957951, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957952, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780957953, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957954, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957955, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957956, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780957957, + 2400027136, + 60.9, + "0x0" + ], + [ + 1780957958, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957959, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780957960, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780957961, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780957962, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780957963, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957964, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780957965, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780957966, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780957967, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780957968, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780957969, + 2400030464, + 60.4, + "0x0" + ], + [ + 1780957970, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780957971, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780957972, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780957973, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780957974, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780957975, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780957976, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780957977, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957978, + 2400023808, + 60.4, + "0x0" + ], + [ + 1780957979, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957980, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780957981, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780957982, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780957984, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780957985, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780957986, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780957987, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957988, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957989, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957990, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780957991, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780957992, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957993, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780957994, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780957995, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780957996, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780957997, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780957998, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780957999, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780958000, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958001, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958002, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958003, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958004, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958005, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958006, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958007, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958008, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958009, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780958010, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958011, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958012, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958013, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958014, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958015, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958016, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958017, + 2400020480, + 58.2, + "0x0" + ], + [ + 1780958018, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958019, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958020, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958021, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958022, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958023, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958024, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958025, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958026, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958027, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958028, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958029, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958030, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958031, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958032, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958033, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958034, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958035, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780958036, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958037, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958039, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958040, + 2400037120, + 58.7, + "0x0" + ], + [ + 1780958041, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780958042, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958043, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958044, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958045, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958046, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958047, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958048, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958049, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780958050, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958051, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958052, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958053, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958054, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780958055, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958056, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958057, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958058, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958059, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958060, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958061, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958062, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958063, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780958064, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958065, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958066, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958067, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958068, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958069, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958070, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958071, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958072, + 2400037120, + 58.7, + "0x0" + ], + [ + 1780958073, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958074, + 2400030464, + 58.7, + "0x0" + ], + [ + 1780958075, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780958076, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780958077, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958078, + 2400030464, + 58.7, + "0x0" + ], + [ + 1780958079, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780958080, + 2400030464, + 58.7, + "0x0" + ], + [ + 1780958081, + 2400037120, + 58.7, + "0x0" + ], + [ + 1780958082, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958083, + 2400027136, + 58.2, + "0x0" + ], + [ + 1780958084, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958085, + 2400030464, + 58.7, + "0x0" + ], + [ + 1780958086, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958088, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958089, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958090, + 2400037120, + 60.4, + "0x0" + ], + [ + 1780958091, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958092, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958093, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958094, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958095, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958096, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958097, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958098, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958099, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958100, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958101, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958102, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958103, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958104, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958105, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958106, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958107, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958108, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958109, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780958110, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958111, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958112, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958113, + 2400023808, + 60.4, + "0x0" + ], + [ + 1780958114, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958115, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958116, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958117, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958118, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958119, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958120, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958121, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780958122, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958123, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958124, + 2400023808, + 60.4, + "0x0" + ], + [ + 1780958125, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958126, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958127, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958128, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958129, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958130, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958131, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958132, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958133, + 2400037120, + 58.7, + "0x0" + ], + [ + 1780958134, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958135, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958136, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780958137, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958138, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958139, + 2400017408, + 58.7, + "0x0" + ], + [ + 1780958141, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958142, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958143, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780958144, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958145, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958146, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958147, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780958148, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958149, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958150, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958151, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958152, + 2400017408, + 60.4, + "0x0" + ], + [ + 1780958153, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958154, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958155, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958156, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958157, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958158, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958159, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958160, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958161, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958162, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958163, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958164, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780958165, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958166, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958167, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780958168, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958169, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958170, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958171, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958172, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958173, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958174, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958175, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780958176, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958177, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958178, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958179, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958180, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958181, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958182, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958183, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958184, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958185, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958186, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958187, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958188, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780958190, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958191, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958192, + 2400027136, + 60.4, + "0x0" + ], + [ + 1780958193, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958194, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958195, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958196, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780958197, + 2400033792, + 59.3, + "0x0" + ], + [ + 1780958198, + 2400027136, + 58.2, + "0x0" + ], + [ + 1780958199, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958200, + 2400037120, + 58.7, + "0x0" + ], + [ + 1780958201, + 2400033792, + 58.7, + "0x0" + ], + [ + 1780958202, + 2400027136, + 58.2, + "0x0" + ], + [ + 1780958203, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780958204, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958205, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958206, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958207, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780958208, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958209, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958210, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958211, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958212, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958213, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958214, + 2400027136, + 60.4, + "0x0" + ], + [ + 1780958215, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958216, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958217, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958218, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958219, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958220, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780958221, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958222, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958223, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958224, + 2400030464, + 60.4, + "0x0" + ], + [ + 1780958225, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958226, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958227, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958228, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958229, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958230, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958231, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958232, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958233, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958234, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958235, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958236, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958237, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958238, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958239, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958240, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958241, + 2400023808, + 60.4, + "0x0" + ], + [ + 1780958242, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958244, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958245, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958246, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958247, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958248, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958249, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958250, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958251, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958252, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780958253, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780958254, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958255, + 2400030464, + 58.2, + "0x0" + ], + [ + 1780958256, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958257, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958258, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958259, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958260, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780958261, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958262, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958263, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958264, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958265, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958266, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958267, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958268, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958269, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958270, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958271, + 2400020480, + 58.2, + "0x0" + ], + [ + 1780958272, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958273, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958274, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958275, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958276, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958277, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958278, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958279, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958280, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958281, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958282, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958283, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958284, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958285, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958286, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958287, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958288, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958289, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958290, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958291, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958292, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958293, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958294, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958295, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958296, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958298, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958299, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958300, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958301, + 2400033792, + 58.7, + "0x0" + ], + [ + 1780958302, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958303, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958304, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958305, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958306, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958307, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958308, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958309, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958310, + 2400030464, + 58.7, + "0x0" + ], + [ + 1780958311, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958312, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958313, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958314, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958315, + 2400033792, + 58.7, + "0x0" + ], + [ + 1780958316, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958317, + 2400020480, + 58.7, + "0x0" + ], + [ + 1780958318, + 2400017408, + 58.7, + "0x0" + ], + [ + 1780958319, + 2400017408, + 59.3, + "0x0" + ], + [ + 1780958320, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958321, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958322, + 2400033792, + 58.2, + "0x0" + ], + [ + 1780958323, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958324, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958325, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958326, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958327, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958328, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958329, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958330, + 2400030464, + 58.7, + "0x0" + ], + [ + 1780958331, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958332, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958333, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958334, + 2400017408, + 59.8, + "0x0" + ], + [ + 1780958335, + 2400023808, + 60.4, + "0x0" + ], + [ + 1780958336, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958337, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958338, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958339, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958340, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958341, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958342, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958343, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958344, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958345, + 2400020480, + 59.3, + "0x0" + ], + [ + 1780958346, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958347, + 2400020480, + 59.8, + "0x0" + ], + [ + 1780958348, + 2400023808, + 58.7, + "0x0" + ], + [ + 1780958349, + 2400027136, + 58.7, + "0x0" + ], + [ + 1780958351, + 2400033792, + 58.7, + "0x0" + ], + [ + 1780958352, + 2400033792, + 59.8, + "0x0" + ], + [ + 1780958353, + 2400030464, + 59.3, + "0x0" + ], + [ + 1780958354, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958355, + 2400020480, + 60.4, + "0x0" + ], + [ + 1780958356, + 2400027136, + 59.8, + "0x0" + ], + [ + 1780958357, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958358, + 2400027136, + 58.2, + "0x0" + ], + [ + 1780958359, + 2400030464, + 60.4, + "0x0" + ], + [ + 1780958360, + 2400027136, + 58.2, + "0x0" + ], + [ + 1780958361, + 2400023808, + 59.3, + "0x0" + ], + [ + 1780958362, + 2400037120, + 59.3, + "0x0" + ], + [ + 1780958363, + 2400033792, + 58.7, + "0x0" + ], + [ + 1780958364, + 2400027136, + 59.3, + "0x0" + ], + [ + 1780958365, + 2400023808, + 59.8, + "0x0" + ], + [ + 1780958366, + 2400037120, + 59.8, + "0x0" + ], + [ + 1780958367, + 2400030464, + 59.8, + "0x0" + ], + [ + 1780958368, + 2400030464, + 59.3, + "0x0" + ] + ], + "temp_c": { + "min": 56.0, + "max": 60.9, + "mean": 59.41, + "samples": 520 + }, + "arm_clock_hz": { + "min": 2400017408, + "max": 2400037120, + "mean": 2400026167.138, + "samples": 520, + "spread_frac": 0.0 + }, + "throttling_detected": false + }, + "warnings": [], + "kem": [ + { + "alg": "X25519", + "kind": "kem", + "backend": "openssl", + "classical": true, + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 32, + "secret_key": 32, + "ciphertext": null, + "shared_secret": 32 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 871, + "timed_iters": 4355, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 57402.55, + "samples": 21775, + "median": 57184.0, + "mad": 55.0, + "iqr": 112.0, + "q1": 57129.0, + "q3": 57241.0, + "min": 56999.0, + "max": 196072.0, + "mean": 57436.01, + "stddev": 1706.55, + "ops_per_sec": 17487.41, + "per_rep_median": [ + 57148.0, + 57166.0, + 57167.0, + 57185.0, + 57222.0 + ] + }, + "derive": { + "unit": "ns", + "warmup_iters": 301, + "timed_iters": 1507, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 165934.23, + "samples": 7535, + "median": 165591.0, + "mad": 92.0, + "iqr": 204.0, + "q1": 165517.0, + "q3": 165721.0, + "min": 164813.0, + "max": 428349.0, + "mean": 165929.15, + "stddev": 3666.74, + "ops_per_sec": 6038.98, + "per_rep_median": [ + 165573.0, + 165573.0, + 165591.0, + 165609.0, + 165609.0 + ] + } + } + }, + { + "alg": "ML-KEM-512", + "kind": "kem", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 800, + "secret_key": 1632, + "ciphertext": 768, + "shared_secret": 32 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 2721, + "timed_iters": 13606, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 18373.8, + "samples": 68030, + "median": 18315.0, + "mad": 37.0, + "iqr": 74.0, + "q1": 18278.0, + "q3": 18352.0, + "min": 18167.0, + "max": 315961.0, + "mean": 18415.78, + "stddev": 1316.56, + "ops_per_sec": 54600.05, + "per_rep_median": [ + 18296.0, + 18352.0, + 18333.0, + 18314.0, + 18259.0 + ] + }, + "encaps": { + "unit": "ns", + "warmup_iters": 2424, + "timed_iters": 12122, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 20623.41, + "samples": 60610, + "median": 20574.0, + "mad": 18.0, + "iqr": 37.0, + "q1": 20556.0, + "q3": 20593.0, + "min": 20499.0, + "max": 142277.0, + "mean": 20690.25, + "stddev": 1133.35, + "ops_per_sec": 48605.04, + "per_rep_median": [ + 20574.0, + 20592.0, + 20574.0, + 20574.0, + 20574.0 + ] + }, + "decaps": { + "unit": "ns", + "warmup_iters": 2083, + "timed_iters": 10417, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 23998.81, + "samples": 52085, + "median": 23962.0, + "mad": 18.0, + "iqr": 37.0, + "q1": 23944.0, + "q3": 23981.0, + "min": 23870.0, + "max": 56111.0, + "mean": 23994.53, + "stddev": 501.17, + "ops_per_sec": 41732.74, + "per_rep_median": [ + 23963.0, + 23945.0, + 23962.0, + 23962.0, + 23945.0 + ] + } + } + }, + { + "alg": "ML-KEM-768", + "kind": "kem", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 3, + "sizes": { + "public_key": 1184, + "secret_key": 2400, + "ciphertext": 1088, + "shared_secret": 32 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 1660, + "timed_iters": 8300, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 30121.61, + "samples": 41500, + "median": 29851.0, + "mad": 36.0, + "iqr": 73.0, + "q1": 29815.0, + "q3": 29888.0, + "min": 29703.0, + "max": 64259.0, + "mean": 30041.73, + "stddev": 821.12, + "ops_per_sec": 33499.72, + "per_rep_median": [ + 29852.0, + 29851.0, + 29852.0, + 29852.0, + 29851.0 + ] + }, + "encaps": { + "unit": "ns", + "warmup_iters": 1554, + "timed_iters": 7771, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 32168.87, + "samples": 38855, + "median": 31944.0, + "mad": 19.0, + "iqr": 37.0, + "q1": 31926.0, + "q3": 31963.0, + "min": 31833.0, + "max": 106814.0, + "mean": 32023.25, + "stddev": 785.69, + "ops_per_sec": 31304.78, + "per_rep_median": [ + 31945.0, + 31944.0, + 31944.0, + 31944.0, + 31944.0 + ] + }, + "decaps": { + "unit": "ns", + "warmup_iters": 1338, + "timed_iters": 6689, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 37373.39, + "samples": 33445, + "median": 37370.0, + "mad": 19.0, + "iqr": 38.0, + "q1": 37351.0, + "q3": 37389.0, + "min": 37277.0, + "max": 2918423.0, + "mean": 37491.29, + "stddev": 15761.42, + "ops_per_sec": 26759.43, + "per_rep_median": [ + 37370.0, + 37370.0, + 37370.0, + 37370.0, + 37370.0 + ] + } + } + }, + { + "alg": "ML-KEM-1024", + "kind": "kem", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 5, + "sizes": { + "public_key": 1568, + "secret_key": 3168, + "ciphertext": 1568, + "shared_secret": 32 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 1125, + "timed_iters": 5625, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 44447.92, + "samples": 28125, + "median": 44074.0, + "mad": 37.0, + "iqr": 92.0, + "q1": 44037.0, + "q3": 44129.0, + "min": 43907.0, + "max": 70444.0, + "mean": 44383.62, + "stddev": 877.6, + "ops_per_sec": 22689.11, + "per_rep_median": [ + 44074.0, + 44074.0, + 44092.0, + 44074.0, + 44074.0 + ] + }, + "encaps": { + "unit": "ns", + "warmup_iters": 1046, + "timed_iters": 5231, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 47792.83, + "samples": 26155, + "median": 47740.0, + "mad": 19.0, + "iqr": 38.0, + "q1": 47721.0, + "q3": 47759.0, + "min": 47629.0, + "max": 134110.0, + "mean": 47818.21, + "stddev": 908.98, + "ops_per_sec": 20946.8, + "per_rep_median": [ + 47759.0, + 47741.0, + 47740.0, + 47740.0, + 47721.0 + ] + }, + "decaps": { + "unit": "ns", + "warmup_iters": 903, + "timed_iters": 4516, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 55362.9, + "samples": 22580, + "median": 55370.0, + "mad": 19.0, + "iqr": 38.0, + "q1": 55351.0, + "q3": 55389.0, + "min": 55241.0, + "max": 91333.0, + "mean": 55407.19, + "stddev": 450.16, + "ops_per_sec": 18060.32, + "per_rep_median": [ + 55370.0, + 55370.0, + 55370.0, + 55370.0, + 55370.0 + ] + } + } + }, + { + "alg": "X25519MLKEM768", + "kind": "kem", + "backend": "liboqs", + "enabled": false, + "reason": "not enabled in this liboqs build" + }, + { + "alg": "SecP256r1MLKEM768", + "kind": "kem", + "backend": "liboqs", + "enabled": false, + "reason": "not enabled in this liboqs build" + } + ], + "sig": [ + { + "alg": "Ed25519", + "kind": "sig", + "backend": "openssl", + "classical": true, + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 32, + "secret_key": 32, + "signature": 64 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 857, + "timed_iters": 4287, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 58314.29, + "samples": 21435, + "median": 58074.0, + "mad": 19.0, + "iqr": 38.0, + "q1": 58055.0, + "q3": 58093.0, + "min": 57962.0, + "max": 93870.0, + "mean": 58151.74, + "stddev": 679.41, + "ops_per_sec": 17219.41, + "per_rep_median": [ + 58074.0, + 58073.0, + 58073.0, + 58074.0, + 58073.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 868, + "timed_iters": 4338, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 57635.21, + "samples": 21690, + "median": 57666.0, + "mad": 19.0, + "iqr": 38.0, + "q1": 57647.0, + "q3": 57685.0, + "min": 57555.0, + "max": 90259.0, + "mean": 57738.89, + "stddev": 692.8, + "ops_per_sec": 17341.24, + "per_rep_median": [ + 57666.0, + 57648.0, + 57666.0, + 57666.0, + 57667.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 346, + "timed_iters": 1730, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 144512.95, + "samples": 8650, + "median": 143981.0, + "mad": 56.0, + "iqr": 130.0, + "q1": 143943.0, + "q3": 144073.0, + "min": 143721.0, + "max": 173240.0, + "mean": 144118.54, + "stddev": 849.1, + "ops_per_sec": 6945.36, + "per_rep_median": [ + 144092.0, + 143980.0, + 143981.0, + 143980.0, + 143906.0 + ] + } + } + }, + { + "alg": "ML-DSA-44", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 2, + "sizes": { + "public_key": 1312, + "secret_key": 2560, + "signature": 2420 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 465, + "timed_iters": 2325, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 107536.86, + "samples": 11625, + "median": 107166.0, + "mad": 593.0, + "iqr": 1185.0, + "q1": 106592.0, + "q3": 107777.0, + "min": 104444.0, + "max": 144758.0, + "mean": 107244.09, + "stddev": 1318.81, + "ops_per_sec": 9331.32, + "per_rep_median": [ + 107240.0, + 107202.0, + 106906.0, + 107259.0, + 107092.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 88, + "timed_iters": 439, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 569404.97, + "samples": 2195, + "median": 398793.0, + "mad": 181183.0, + "iqr": 363331.0, + "q1": 296997.5, + "q3": 660328.5, + "min": 215684.0, + "max": 2839609.0, + "mean": 518101.27, + "stddev": 333546.57, + "ops_per_sec": 2507.57, + "per_rep_median": [ + 417386.0, + 459348.0, + 399590.0, + 378867.0, + 397404.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 420, + "timed_iters": 2100, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 119069.13, + "samples": 10500, + "median": 119110.0, + "mad": 129.0, + "iqr": 315.0, + "q1": 119017.0, + "q3": 119332.0, + "min": 118666.0, + "max": 149832.0, + "mean": 119368.65, + "stddev": 770.88, + "ops_per_sec": 8395.6, + "per_rep_median": [ + 119129.0, + 119036.0, + 120018.0, + 119128.0, + 119092.0 + ] + } + } + }, + { + "alg": "ML-DSA-65", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 3, + "sizes": { + "public_key": 1952, + "secret_key": 4032, + "signature": 3309 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 259, + "timed_iters": 1296, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 192881.86, + "samples": 6480, + "median": 191998.0, + "mad": 999.0, + "iqr": 2130.0, + "q1": 191313.0, + "q3": 193443.0, + "min": 189721.0, + "max": 228165.0, + "mean": 192429.63, + "stddev": 1666.78, + "ops_per_sec": 5208.39, + "per_rep_median": [ + 193387.0, + 191980.0, + 191406.0, + 192267.0, + 191980.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 69, + "timed_iters": 346, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 722786.51, + "samples": 1730, + "median": 676421.0, + "mad": 250590.5, + "iqr": 655467.75, + "q1": 434732.75, + "q3": 1090200.5, + "min": 320794.0, + "max": 5171739.0, + "mean": 851260.86, + "stddev": 581666.26, + "ops_per_sec": 1478.37, + "per_rep_median": [ + 675782.0, + 677430.0, + 677476.5, + 703874.5, + 675523.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 264, + "timed_iters": 1320, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 189381.31, + "samples": 6600, + "median": 191073.0, + "mad": 629.0, + "iqr": 2037.0, + "q1": 189277.0, + "q3": 191314.0, + "min": 188591.0, + "max": 529941.0, + "mean": 190752.26, + "stddev": 5690.1, + "ops_per_sec": 5233.6, + "per_rep_median": [ + 191128.0, + 191073.0, + 189573.0, + 190999.0, + 191166.0 + ] + } + } + }, + { + "alg": "ML-DSA-87", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 5, + "sizes": { + "public_key": 2592, + "secret_key": 4896, + "signature": 4627 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 173, + "timed_iters": 863, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 289692.51, + "samples": 4315, + "median": 288997.0, + "mad": 1314.0, + "iqr": 3287.0, + "q1": 288007.0, + "q3": 291294.0, + "min": 284776.0, + "max": 331349.0, + "mean": 289660.3, + "stddev": 2477.31, + "ops_per_sec": 3460.24, + "per_rep_median": [ + 289035.0, + 288664.0, + 289183.0, + 288812.0, + 289424.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 50, + "timed_iters": 251, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 995001.48, + "samples": 1255, + "median": 835827.0, + "mad": 345664.0, + "iqr": 692384.0, + "q1": 493996.5, + "q3": 1186380.5, + "min": 484737.0, + "max": 4519448.0, + "mean": 1000901.27, + "stddev": 603216.41, + "ops_per_sec": 1196.42, + "per_rep_median": [ + 834123.0, + 686513.0, + 873363.0, + 835438.0, + 839604.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 163, + "timed_iters": 817, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 305879.61, + "samples": 4085, + "median": 305683.0, + "mad": 334.0, + "iqr": 1982.0, + "q1": 305442.0, + "q3": 307424.0, + "min": 304942.0, + "max": 447256.0, + "mean": 306728.88, + "stddev": 3211.56, + "ops_per_sec": 3271.36, + "per_rep_median": [ + 305479.0, + 305423.0, + 306442.0, + 305794.0, + 305887.0 + ] + } + } + }, + { + "alg": "Falcon-512", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 897, + "secret_key": 1281, + "signature": 752 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 5, + "timed_iters": 30, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 10806598.33, + "samples": 150, + "median": 8684444.5, + "mad": 901798.5, + "iqr": 1802995.25, + "q1": 8030306.0, + "q3": 9833301.25, + "min": 7266149.0, + "max": 17971773.0, + "mean": 9372829.44, + "stddev": 2192639.48, + "ops_per_sec": 115.15, + "per_rep_median": [ + 8418298.5, + 9315050.5, + 8546075.5, + 8424159.0, + 9178709.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 172, + "timed_iters": 860, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 290587.65, + "samples": 4300, + "median": 290220.0, + "mad": 2185.0, + "iqr": 4134.5, + "q1": 288720.0, + "q3": 292854.5, + "min": 281109.0, + "max": 329387.0, + "mean": 290633.45, + "stddev": 3621.07, + "ops_per_sec": 3445.66, + "per_rep_median": [ + 290294.0, + 290276.0, + 290183.0, + 290165.0, + 290183.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 981, + "timed_iters": 4903, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 50984.19, + "samples": 24515, + "median": 50851.0, + "mad": 74.0, + "iqr": 148.0, + "q1": 50777.0, + "q3": 50925.0, + "min": 50518.0, + "max": 79869.0, + "mean": 50885.03, + "stddev": 432.33, + "ops_per_sec": 19665.3, + "per_rep_median": [ + 50963.0, + 50889.0, + 50778.0, + 50833.0, + 50740.0 + ] + } + } + }, + { + "alg": "Falcon-1024", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 5, + "sizes": { + "public_key": 1793, + "secret_key": 2305, + "signature": 1462 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 2, + "timed_iters": 30, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 29647765.33, + "samples": 150, + "median": 26081341.5, + "mad": 2350722.0, + "iqr": 6023380.0, + "q1": 24474543.25, + "q3": 30497923.25, + "min": 22857125.0, + "max": 83012210.0, + "mean": 29887741.99, + "stddev": 9509453.95, + "ops_per_sec": 38.34, + "per_rep_median": [ + 26095619.0, + 26100989.5, + 27223555.5, + 25517086.5, + 26250830.5 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 85, + "timed_iters": 423, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 590871.21, + "samples": 2115, + "median": 591440.0, + "mad": 3056.0, + "iqr": 5908.0, + "q1": 588273.0, + "q3": 594181.0, + "min": 575848.0, + "max": 644940.0, + "mean": 591412.93, + "stddev": 4989.2, + "ops_per_sec": 1690.79, + "per_rep_median": [ + 591403.0, + 591199.0, + 591477.0, + 591495.0, + 591606.0 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 498, + "timed_iters": 2491, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 100356.14, + "samples": 12455, + "median": 99796.0, + "mad": 93.0, + "iqr": 204.0, + "q1": 99703.0, + "q3": 99907.0, + "min": 99351.0, + "max": 133092.0, + "mean": 99893.38, + "stddev": 692.04, + "ops_per_sec": 10020.44, + "per_rep_median": [ + 100036.0, + 99851.0, + 99685.0, + 99814.0, + 99703.0 + ] + } + } + }, + { + "alg": "SPHINCS+-SHA2-128f-simple", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 17088 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 33, + "timed_iters": 163, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 1534244.71, + "samples": 815, + "median": 1531637.0, + "mad": 427.0, + "iqr": 2055.0, + "q1": 1531396.0, + "q3": 1533451.0, + "min": 1529359.0, + "max": 1606969.0, + "mean": 1532921.67, + "stddev": 3783.89, + "ops_per_sec": 652.9, + "per_rep_median": [ + 1533433.0, + 1531433.0, + 1531415.0, + 1531451.0, + 1531470.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 1, + "timed_iters": 30, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 35923989.0, + "samples": 150, + "median": 35791563.5, + "mad": 53073.0, + "iqr": 171179.75, + "q1": 35663258.5, + "q3": 35834438.25, + "min": 35569268.0, + "max": 35920266.0, + "mean": 35761623.86, + "stddev": 96307.08, + "ops_per_sec": 27.94, + "per_rep_median": [ + 35857424.0, + 35829516.5, + 35792581.5, + 35714369.0, + 35617008.5 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 25, + "timed_iters": 125, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 2007947.93, + "samples": 625, + "median": 2008448.0, + "mad": 1667.0, + "iqr": 2000.0, + "q1": 2006689.0, + "q3": 2008689.0, + "min": 2006615.0, + "max": 2081039.0, + "mean": 2008371.75, + "stddev": 4827.43, + "ops_per_sec": 497.9, + "per_rep_median": [ + 2008484.0, + 2008466.0, + 2006707.0, + 2008447.0, + 2008577.0 + ] + } + } + }, + { + "alg": "SPHINCS+-SHA2-128s-simple", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 1, + "sizes": { + "public_key": 32, + "secret_key": 64, + "signature": 7856 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 1, + "timed_iters": 30, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 99546079.0, + "samples": 150, + "median": 97953152.5, + "mad": 118648.0, + "iqr": 850748.0, + "q1": 97842562.75, + "q3": 98693310.75, + "min": 97478666.0, + "max": 100965710.0, + "mean": 98179435.83, + "stddev": 513169.01, + "ops_per_sec": 10.21, + "per_rep_median": [ + 98910074.0, + 97949116.5, + 97949598.0, + 97955456.0, + 97837586.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 1, + "timed_iters": 30, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 746375126.0, + "samples": 150, + "median": 744477847.5, + "mad": 960013.0, + "iqr": 2358797.25, + "q1": 744069155.0, + "q3": 746427952.25, + "min": 742006494.0, + "max": 851757420.0, + "mean": 758585492.81, + "stddev": 30960311.81, + "ops_per_sec": 1.34, + "per_rep_median": [ + 744452006.5, + 744446775.0, + 744212086.0, + 744522029.5, + 802883596.5 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 67, + "timed_iters": 336, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 743900.78, + "samples": 1680, + "median": 744438.0, + "mad": 925.0, + "iqr": 1000.0, + "q1": 743494.0, + "q3": 744494.0, + "min": 742586.0, + "max": 787049.0, + "mean": 744242.68, + "stddev": 1980.75, + "ops_per_sec": 1343.3, + "per_rep_median": [ + 742624.0, + 743531.0, + 743512.0, + 744494.0, + 744457.0 + ] + } + } + }, + { + "alg": "SPHINCS+-SHA2-192f-simple", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 3, + "sizes": { + "public_key": 48, + "secret_key": 96, + "signature": 35664 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 21, + "timed_iters": 105, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 2388946.13, + "samples": 525, + "median": 2295537.0, + "mad": 1667.0, + "iqr": 2185.0, + "q1": 2293685.0, + "q3": 2295870.0, + "min": 2291111.0, + "max": 2352295.0, + "mean": 2295493.81, + "stddev": 4916.04, + "ops_per_sec": 435.63, + "per_rep_median": [ + 2293833.0, + 2295556.0, + 2295555.0, + 2295593.0, + 2295574.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 1, + "timed_iters": 30, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 61818368.0, + "samples": 150, + "median": 61790386.0, + "mad": 31583.0, + "iqr": 64244.75, + "q1": 61763122.0, + "q3": 61827366.75, + "min": 61684738.0, + "max": 64712104.0, + "mean": 61811052.51, + "stddev": 242816.92, + "ops_per_sec": 16.18, + "per_rep_median": [ + 61781248.0, + 61783905.0, + 61822237.5, + 61790802.5, + 61747469.5 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 15, + "timed_iters": 75, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 3325879.07, + "samples": 375, + "median": 3339047.0, + "mad": 1167.0, + "iqr": 2981.5, + "q1": 3336380.5, + "q3": 3339362.0, + "min": 3302713.0, + "max": 3406787.0, + "mean": 3338650.74, + "stddev": 6022.6, + "ops_per_sec": 299.49, + "per_rep_median": [ + 3335750.0, + 3339121.0, + 3336214.0, + 3339213.0, + 3339158.0 + ] + } + } + }, + { + "alg": "SPHINCS+-SHA2-256f-simple", + "kind": "sig", + "backend": "liboqs", + "enabled": true, + "claimed_nist_level": 5, + "sizes": { + "public_key": 64, + "secret_key": 128, + "signature": 49856 + }, + "operations": { + "keygen": { + "unit": "ns", + "warmup_iters": 8, + "timed_iters": 40, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 6327665.29, + "samples": 200, + "median": 6315494.5, + "mad": 2157.0, + "iqr": 7397.25, + "q1": 6313611.25, + "q3": 6321008.5, + "min": 6306560.0, + "max": 6392152.0, + "mean": 6316911.33, + "stddev": 8270.6, + "ops_per_sec": 158.34, + "per_rep_median": [ + 6322874.0, + 6315754.0, + 6315310.0, + 6315346.5, + 6308430.0 + ] + }, + "sign": { + "unit": "ns", + "warmup_iters": 1, + "timed_iters": 30, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 132323346.0, + "samples": 150, + "median": 127052863.5, + "mad": 102035.5, + "iqr": 5188286.0, + "q1": 126988244.5, + "q3": 132176530.5, + "min": 126909235.0, + "max": 132465696.0, + "mean": 128452981.21, + "stddev": 2341248.91, + "ops_per_sec": 7.87, + "per_rep_median": [ + 132346169.0, + 127234986.0, + 127010764.5, + 126998828.0, + 127003733.5 + ] + }, + "verify": { + "unit": "ns", + "warmup_iters": 15, + "timed_iters": 74, + "repetitions": 5, + "calibrated": true, + "calib_est_ns": 3367705.53, + "samples": 370, + "median": 3367417.0, + "mad": 241.0, + "iqr": 501.0, + "q1": 3367250.0, + "q3": 3367751.0, + "min": 3365250.0, + "max": 3435028.0, + "mean": 3368405.21, + "stddev": 6580.08, + "ops_per_sec": 296.96, + "per_rep_median": [ + 3367426.0, + 3367500.0, + 3367361.0, + 3367380.0, + 3367741.0 + ] + } + } + } + ], + "tls": { + "available": true, + "have_oqs_provider": true, + "baseline": { + "kem_group": "X25519", + "sig_alg": "ed25519", + "label": "X25519+ed25519" + }, + "matrix": [ + { + "label": "X25519+ed25519", + "group": "X25519", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 1323878.0, + "p95": 1337293.4, + "min": 1317804.0, + "max": 1765208.0, + "mean": 1330643.5, + "stddev": 36841.3 + }, + "handshakes_per_sec": 755.4, + "bytes_on_wire": { + "client_to_server": 382, + "server_to_client": 1189, + "total": 1571 + }, + "client_hello_bytes": 302, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+mldsa44", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2672487.0, + "p95": 4545972.8, + "min": 2241833.0, + "max": 8314542.0, + "mean": 2949657.0, + "stddev": 804789.5 + }, + "handshakes_per_sec": 374.2, + "bytes_on_wire": { + "client_to_server": 1558, + "server_to_client": 8294, + "total": 9852 + }, + "client_hello_bytes": 1478, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+mldsa65", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3639571.5, + "p95": 6853448.9, + "min": 2750737.0, + "max": 12102030.0, + "mean": 4086835.8, + "stddev": 1427280.2 + }, + "handshakes_per_sec": 274.8, + "bytes_on_wire": { + "client_to_server": 1558, + "server_to_client": 10712, + "total": 12270 + }, + "client_hello_bytes": 1478, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+mldsa87", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 4221872.5, + "p95": 7573517.7, + "min": 3375028.0, + "max": 14351548.0, + "mean": 4694776.0, + "stddev": 1463440.4 + }, + "handshakes_per_sec": 236.9, + "bytes_on_wire": { + "client_to_server": 1558, + "server_to_client": 13988, + "total": 15546 + }, + "client_hello_bytes": 1478, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+falcon512", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3223843.5, + "p95": 3246957.4, + "min": 3200417.0, + "max": 4261057.0, + "mean": 3233943.1, + "stddev": 76205.0 + }, + "handshakes_per_sec": 310.2, + "bytes_on_wire": { + "client_to_server": 1558, + "server_to_client": 4338, + "total": 5896 + }, + "client_hello_bytes": 1478, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "X25519MLKEM768+sphincssha2128fsimple", + "group": "X25519MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 44119787.5, + "p95": 44530745.4, + "min": 43663839.0, + "max": 46978016.0, + "mean": 44150097.8, + "stddev": 213142.9 + }, + "handshakes_per_sec": 22.7, + "bytes_on_wire": { + "client_to_server": 1558, + "server_to_client": 36394, + "total": 37952 + }, + "client_hello_bytes": 1478, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+mldsa44", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2708792.0, + "p95": 4788983.9, + "min": 2275870.0, + "max": 8127413.0, + "mean": 3020474.2, + "stddev": 838306.1 + }, + "handshakes_per_sec": 369.2, + "bytes_on_wire": { + "client_to_server": 1591, + "server_to_client": 8327, + "total": 9918 + }, + "client_hello_bytes": 1511, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+mldsa65", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3632831.0, + "p95": 6843373.5, + "min": 2745884.0, + "max": 12676597.0, + "mean": 3979341.0, + "stddev": 1357544.1 + }, + "handshakes_per_sec": 275.3, + "bytes_on_wire": { + "client_to_server": 1591, + "server_to_client": 10745, + "total": 12336 + }, + "client_hello_bytes": 1511, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+mldsa87", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 4271222.5, + "p95": 7708526.2, + "min": 3422008.0, + "max": 20144700.0, + "mean": 4708469.1, + "stddev": 1514041.6 + }, + "handshakes_per_sec": 234.1, + "bytes_on_wire": { + "client_to_server": 1591, + "server_to_client": 14021, + "total": 15612 + }, + "client_hello_bytes": 1511, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+falcon512", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3399480.5, + "p95": 3455642.6, + "min": 3370009.0, + "max": 4742126.0, + "mean": 3415657.4, + "stddev": 93815.3 + }, + "handshakes_per_sec": 294.2, + "bytes_on_wire": { + "client_to_server": 1591, + "server_to_client": 4368, + "total": 5959 + }, + "client_hello_bytes": 1511, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "SecP256r1MLKEM768+sphincssha2128fsimple", + "group": "SecP256r1MLKEM768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 44087756.0, + "p95": 44521847.3, + "min": 43588723.0, + "max": 46984119.0, + "mean": 44135869.3, + "stddev": 233498.6 + }, + "handshakes_per_sec": 22.7, + "bytes_on_wire": { + "client_to_server": 1591, + "server_to_client": 36427, + "total": 38018 + }, + "client_hello_bytes": 1511, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+mldsa44", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2093657.5, + "p95": 3973789.9, + "min": 1666485.0, + "max": 7516195.0, + "mean": 2388548.1, + "stddev": 843440.7 + }, + "handshakes_per_sec": 477.6, + "bytes_on_wire": { + "client_to_server": 1142, + "server_to_client": 7942, + "total": 9084 + }, + "client_hello_bytes": 1062, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+mldsa65", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2732856.0, + "p95": 5936394.0, + "min": 2125000.0, + "max": 12948166.0, + "mean": 3306902.8, + "stddev": 1332775.7 + }, + "handshakes_per_sec": 365.9, + "bytes_on_wire": { + "client_to_server": 1142, + "server_to_client": 10360, + "total": 11502 + }, + "client_hello_bytes": 1062, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+mldsa87", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3645727.5, + "p95": 6993853.6, + "min": 2797920.0, + "max": 13266515.0, + "mean": 4107261.1, + "stddev": 1460068.5 + }, + "handshakes_per_sec": 274.3, + "bytes_on_wire": { + "client_to_server": 1142, + "server_to_client": 13636, + "total": 14778 + }, + "client_hello_bytes": 1062, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+falcon512", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2587209.0, + "p95": 2609818.0, + "min": 2564015.0, + "max": 3502618.0, + "mean": 2594518.2, + "stddev": 60072.9 + }, + "handshakes_per_sec": 386.5, + "bytes_on_wire": { + "client_to_server": 1142, + "server_to_client": 3983, + "total": 5125 + }, + "client_hello_bytes": 1062, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem512+sphincssha2128fsimple", + "group": "mlkem512", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 43175152.5, + "p95": 43541765.5, + "min": 42766924.0, + "max": 45910121.0, + "mean": 43201320.8, + "stddev": 208587.8 + }, + "handshakes_per_sec": 23.2, + "bytes_on_wire": { + "client_to_server": 1142, + "server_to_client": 36042, + "total": 37184 + }, + "client_hello_bytes": 1062, + "client_hello_fragmented": false, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+mldsa44", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2214554.5, + "p95": 4104033.0, + "min": 1785503.0, + "max": 8052522.0, + "mean": 2514419.8, + "stddev": 811474.9 + }, + "handshakes_per_sec": 451.6, + "bytes_on_wire": { + "client_to_server": 1526, + "server_to_client": 8262, + "total": 9788 + }, + "client_hello_bytes": 1446, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+mldsa65", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3089621.0, + "p95": 6105273.6, + "min": 2213666.0, + "max": 11283735.0, + "mean": 3424459.9, + "stddev": 1401566.6 + }, + "handshakes_per_sec": 323.7, + "bytes_on_wire": { + "client_to_server": 1526, + "server_to_client": 10680, + "total": 12206 + }, + "client_hello_bytes": 1446, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+mldsa87", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3746911.5, + "p95": 7495687.1, + "min": 2897364.0, + "max": 12087968.0, + "mean": 4205102.8, + "stddev": 1431567.1 + }, + "handshakes_per_sec": 266.9, + "bytes_on_wire": { + "client_to_server": 1526, + "server_to_client": 13956, + "total": 15482 + }, + "client_hello_bytes": 1446, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+falcon512", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2862030.5, + "p95": 2888959.3, + "min": 2838123.0, + "max": 3870947.0, + "mean": 2872400.4, + "stddev": 75475.8 + }, + "handshakes_per_sec": 349.4, + "bytes_on_wire": { + "client_to_server": 1526, + "server_to_client": 4311, + "total": 5837 + }, + "client_hello_bytes": 1446, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem768+sphincssha2128fsimple", + "group": "mlkem768", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 43543931.5, + "p95": 43904535.2, + "min": 43135342.0, + "max": 47014939.0, + "mean": 43573936.8, + "stddev": 217177.5 + }, + "handshakes_per_sec": 23.0, + "bytes_on_wire": { + "client_to_server": 1526, + "server_to_client": 36362, + "total": 37888 + }, + "client_hello_bytes": 1446, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+mldsa44", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2311099.5, + "p95": 4122667.9, + "min": 1880613.0, + "max": 7948652.0, + "mean": 2595214.7, + "stddev": 780246.5 + }, + "handshakes_per_sec": 432.7, + "bytes_on_wire": { + "client_to_server": 1910, + "server_to_client": 8742, + "total": 10652 + }, + "client_hello_bytes": 1830, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+mldsa65", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3220268.0, + "p95": 6441426.0, + "min": 2331091.0, + "max": 11099642.0, + "mean": 3598763.7, + "stddev": 1345439.6 + }, + "handshakes_per_sec": 310.5, + "bytes_on_wire": { + "client_to_server": 1910, + "server_to_client": 11160, + "total": 13070 + }, + "client_hello_bytes": 1830, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+mldsa87", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 3860558.5, + "p95": 6800849.1, + "min": 3013121.0, + "max": 16372558.0, + "mean": 4238980.8, + "stddev": 1473295.5 + }, + "handshakes_per_sec": 259.0, + "bytes_on_wire": { + "client_to_server": 1910, + "server_to_client": 14436, + "total": 16346 + }, + "client_hello_bytes": 1830, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+falcon512", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 2797984.0, + "p95": 2840939.4, + "min": 2771531.0, + "max": 3791726.0, + "mean": 2810929.6, + "stddev": 80478.6 + }, + "handshakes_per_sec": 357.4, + "bytes_on_wire": { + "client_to_server": 1910, + "server_to_client": 4789, + "total": 6699 + }, + "client_hello_bytes": 1830, + "client_hello_fragmented": true, + "mss_assumed": 1400 + }, + { + "label": "mlkem1024+sphincssha2128fsimple", + "group": "mlkem1024", + "enabled": true, + "have_oqs_provider": true, + "connections": 1000, + "succeeded": 1000, + "handshake_latency_ns": { + "median": 45815730.5, + "p95": 46257081.1, + "min": 45176744.0, + "max": 48877583.0, + "mean": 45833447.7, + "stddev": 243266.4 + }, + "handshakes_per_sec": 21.8, + "bytes_on_wire": { + "client_to_server": 1910, + "server_to_client": 36842, + "total": 38752 + }, + "client_hello_bytes": 1830, + "client_hello_fragmented": true, + "mss_assumed": 1400 + } + ] + } +} \ No newline at end of file diff --git a/pq-bench-rpi5/run.sh b/pq-bench-rpi5/run.sh new file mode 100755 index 0000000..55138b8 --- /dev/null +++ b/pq-bench-rpi5/run.sh @@ -0,0 +1,290 @@ +#!/usr/bin/env bash +# ============================================================================= +# run.sh — measurement wrapper + orchestrator. +# +# Does the things that make a number credible: +# * sets the CPU governor to `performance` (Linux; warns elsewhere) +# * pins the benchmark to a single isolated core via taskset (core 3 on RPi5; +# core 3 stays clear of CPU0 where the kernel steers IRQs/RPS) +# * logs ARM clock + SoC temperature throughout, embeds the trace in results, +# and warns on thermal throttling +# * runs every candidate from config.yaml, then assembles one results JSON +# stamped with full host + toolchain provenance. +# +# Usage: +# ./run.sh # full run using config.yaml knobs +# ./run.sh --smoke # tiny iteration counts: pipeline smoke test +# ./run.sh --kemsig-only # skip the TLS layer +# ./run.sh --tls-only # only the TLS layer +# ./run.sh --iters N --warmup N --reps N # override measurement knobs +# sudo ./run.sh # needed on Linux to set the governor +# ============================================================================= +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TOOL_VERSION="0.1.0" +# shellcheck source=setup/lib_platform.sh +source "$ROOT/setup/lib_platform.sh" +# shellcheck source=setup/versions.env +source "$ROOT/setup/versions.env" +LOCK="$ROOT/setup/versions.lock" +[ -f "$LOCK" ] && source "$LOCK" || pqb_warn "no versions.lock — run ./setup/setup.sh first" + +pqb_detect_platform + +# ---- args ------------------------------------------------------------------ +SMOKE=0; DO_KEMSIG=1; DO_TLS=1 +OVR_ITERS=""; OVR_WARMUP=""; OVR_REPS="" +while [ $# -gt 0 ]; do + case "$1" in + --smoke) SMOKE=1 ;; + --kemsig-only) DO_TLS=0 ;; + --tls-only) DO_KEMSIG=0 ;; + --no-tls) DO_TLS=0 ;; + --iters) OVR_ITERS="$2"; shift ;; + --warmup) OVR_WARMUP="$2"; shift ;; + --reps) OVR_REPS="$2"; shift ;; + -h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) pqb_err "unknown arg: $1"; exit 2 ;; + esac + shift +done + +# ---- measurement knobs (config.yaml, overridable) -------------------------- +# Sets TARGET_TIME_MS MIN_SAMPLES MAX_ITERS REPS CYCLES_MODE (auto-calibration, +# the default path) plus WARMUP ITERS (the fixed-count fallback). +eval "$(python3 "$ROOT/bench/lib/list_algs.py" measurement "$ROOT/config.yaml")" + +# Mode: auto-calibrate per op (default) unless --iters forces a fixed count. +CALIB_MODE="auto" +if [ -n "$OVR_ITERS" ]; then CALIB_MODE="fixed"; ITERS="$OVR_ITERS"; fi +[ -n "$OVR_WARMUP" ] && WARMUP="$OVR_WARMUP" +[ -n "$OVR_REPS" ] && REPS="$OVR_REPS" +if [ "$SMOKE" = 1 ]; then + # Keep auto-calibration (so each op still reaches target) but a single rep, so + # the sweep stays short. This is a pipeline test, NOT measurement data. + REPS=1 + pqb_warn "SMOKE MODE: reps=1 — pipeline test only, NOT measurement data" +fi + +# Assemble the per-op sizing args passed to every bench_pq invocation. +if [ "$CALIB_MODE" = "fixed" ]; then + BENCH_SIZE_ARGS=(--warmup "$WARMUP" --iters "$ITERS" --reps "$REPS") + pqb_log "sizing: FIXED-count warmup=$WARMUP iters=$ITERS reps=$REPS" +else + BENCH_SIZE_ARGS=(--target-time-ms "$TARGET_TIME_MS" --min-samples "$MIN_SAMPLES" \ + --max-iters "$MAX_ITERS" --reps "$REPS") + pqb_log "sizing: AUTO-calibrate target=${TARGET_TIME_MS}ms min_samples=$MIN_SAMPLES max_iters=$MAX_ITERS reps=$REPS" +fi +BENCH_CORE="${BENCH_CORE:-3}" +export PQB_BENCH_CORE="$BENCH_CORE" + +# ---- work directory -------------------------------------------------------- +HOST="$(pqb_resolve_hostname)" +TS="$(date -u +%Y%m%dT%H%M%SZ)" +WORK="$ROOT/results/.work-$HOST-$TS" +mkdir -p "$WORK" "$ROOT/results" +KEMSIG_OUT="$WORK/kemsig.jsonl"; : > "$KEMSIG_OUT" +TLS_OUT="$WORK/tls.json" +THERMAL="$WORK/thermal.csv"; : > "$THERMAL" +META="$WORK/meta.env" +FEATURES="$WORK/cpu_features.json" +WARN_ACC="" + +add_warn() { WARN_ACC="${WARN_ACC:+$WARN_ACC||}$1"; pqb_warn "$1"; } + +# ---- governor -------------------------------------------------------------- +GOV_BEFORE="$(pqb_get_governor)" +GOV_AFTER="$(pqb_set_governor_performance || true)" +GOV_REQUESTED="performance" +if [ "$GOV_AFTER" != "performance" ]; then + add_warn "governor is '$GOV_AFTER', not 'performance' (need root on Linux, or unsupported on macOS)" +fi + +# ---- core pinning ---------------------------------------------------------- +TASKSET="$(pqb_taskset_prefix "$BENCH_CORE")" +if [ -n "$TASKSET" ]; then + PINNED=1; pqb_log "pinning to core $BENCH_CORE via: $TASKSET" +else + PINNED=0; add_warn "core pinning unavailable (no taskset/numactl) — results will be noisier" +fi + +# ---- thermal sampler (background) ------------------------------------------ +SAMPLE_INTERVAL="${SAMPLE_INTERVAL:-1}" +pqb_log "starting thermal/clock sampler (every ${SAMPLE_INTERVAL}s) -> $THERMAL" +( while :; do pqb_sample_thermal >> "$THERMAL" 2>/dev/null; sleep "$SAMPLE_INTERVAL"; done ) & +SAMPLER_PID=$! +disown "$SAMPLER_PID" 2>/dev/null || true # suppress job-control "Terminated" noise on kill +# shellcheck disable=SC2064 +trap "kill $SAMPLER_PID 2>/dev/null || true" EXIT +pqb_sample_thermal >> "$THERMAL" 2>/dev/null # one immediate sample + +# ---- CPU features ---------------------------------------------------------- +pqb_cpu_features_json > "$FEATURES" +pqb_log "cpu features: $(cat "$FEATURES")" +# Report whether Keccak/SHA3 *instruction* acceleration is available + compiled. +SHA3_HW="$(python3 -c "import json;print(json.load(open('$FEATURES'))['sha3'])" 2>/dev/null || echo unknown)" +case "${LIBOQS_OPT_DEFINES:-}" in + *"OQS_USE_ARM_SHA3_INSTRUCTIONS 1"*) SHA3_COMPILED=1 ;; + *) SHA3_COMPILED=0 ;; +esac +pqb_log "Keccak/SHA3: hw_instructions=$SHA3_HW liboqs_compiled_sha3=$SHA3_COMPILED (A76 has no SHA3 ext; Keccak runs on NEON there)" + +TS_START="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +START_EPOCH="$(date +%s)" + +# ---- build harness if needed ----------------------------------------------- +OSSL_PREFIX_FOR_BUILD="${OPENSSL_PREFIX:-$(brew --prefix openssl@3 2>/dev/null || echo /usr)}" +if [ ! -x "$ROOT/bench/kem_sig/bench_pq" ] || [ "$ROOT/bench/kem_sig/bench_pq.c" -nt "$ROOT/bench/kem_sig/bench_pq" ]; then + pqb_log "building bench_pq harness" + make -C "$ROOT/bench/kem_sig" \ + LIBOQS_PREFIX="${PREFIX:-$ROOT/vendor/install}" \ + OPENSSL_PREFIX="$OSSL_PREFIX_FOR_BUILD" \ + BENCH_CFLAGS="${BENCH_CFLAGS:--O3}" >/dev/null +fi + +# ---- KEM/sig sweep --------------------------------------------------------- +CYCLES_AVAILABLE=0; CYCLES_REASON="not probed" +if [ "$DO_KEMSIG" = 1 ]; then + if [ "$CALIB_MODE" = "fixed" ]; then + pqb_log "running KEM/sig sweep (fixed: warmup=$WARMUP iters=$ITERS reps=$REPS)" + else + pqb_log "running KEM/sig sweep (auto-calibrate: target=${TARGET_TIME_MS}ms min_samples=$MIN_SAMPLES max_iters=$MAX_ITERS reps=$REPS)" + fi + while IFS=$'\t' read -r kind alg classical; do + [ -z "$alg" ] && continue + pqb_log " $kind $alg" + ERRF="$WORK/err.$kind.$alg.txt" + # shellcheck disable=SC2086 + if $TASKSET "$ROOT/bench/kem_sig/bench_pq" --kind "$kind" --alg "$alg" \ + "${BENCH_SIZE_ARGS[@]}" >> "$KEMSIG_OUT" 2>"$ERRF"; then + : + else + add_warn "harness failed for $kind $alg (see $ERRF)" + fi + # capture PMU availability from the harness's stderr (first occurrence) + if grep -q 'cycles_available=1' "$ERRF" 2>/dev/null; then + CYCLES_AVAILABLE=1; CYCLES_REASON="$(sed -n 's/.*cycles_available=1 (\(.*\))/\1/p' "$ERRF" | head -1)" + elif [ "$CYCLES_AVAILABLE" = 0 ] && grep -q 'cycles_available=0' "$ERRF" 2>/dev/null; then + CYCLES_REASON="$(sed -n 's/.*cycles_available=0 (\(.*\))/\1/p' "$ERRF" | head -1)" + fi + done < <(python3 "$ROOT/bench/lib/list_algs.py" kemsig "$ROOT/config.yaml") +fi + +# ---- TLS layer ------------------------------------------------------------- +if [ "$DO_TLS" = 1 ]; then + if [ -x "$ROOT/bench/tls/run_tls.sh" ]; then + TLS_CONNS="$(python3 -c "import json,sys;print(json.loads(sys.argv[1]).get('connections',1000))" \ + "$(python3 "$ROOT/bench/lib/list_algs.py" tls "$ROOT/config.yaml")")" + [ "$SMOKE" = 1 ] && TLS_CONNS=50 + pqb_log "running TLS handshake matrix ($TLS_CONNS handshakes/cell)" + if PQB_TASKSET="$TASKSET" "$ROOT/bench/tls/run_tls.sh" \ + --out "$TLS_OUT" --connections "$TLS_CONNS" >"$WORK/tls.log" 2>&1; then + pqb_log "TLS layer done ($(grep -c '"label"' "$TLS_OUT" 2>/dev/null || echo 0) cells)" + else + add_warn "TLS layer failed or unavailable (see $WORK/tls.log) — continuing without it" + TLS_OUT="" + fi + else + pqb_warn "TLS harness not present yet — skipping (will be added)" + TLS_OUT="" + fi +fi + +# ---- stop sampler, gather timing ------------------------------------------- +kill "$SAMPLER_PID" 2>/dev/null || true +trap - EXIT +TS_END="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +DURATION=$(( $(date +%s) - START_EPOCH )) +GOV_AFTER_END="$(pqb_get_governor)" + +# ---- host facts ------------------------------------------------------------ +collect_host_facts() { + local cpu_brand="" ncpu="" ram="" os_pretty="" kernel + kernel="$(uname -r)" + if [ "$PQB_OS" = "macos" ]; then + cpu_brand="$(sysctl -n machdep.cpu.brand_string 2>/dev/null)" + ncpu="$(sysctl -n hw.ncpu 2>/dev/null)" + ram="$(sysctl -n hw.memsize 2>/dev/null)" + os_pretty="macOS $(sw_vers -productVersion 2>/dev/null) ($(sw_vers -buildVersion 2>/dev/null))" + else + # aarch64 /proc/cpuinfo has no 'model name' line, so this grep misses; the + # trailing `|| true` keeps `set -o pipefail` from aborting the run (errexit) + # before the PQB_RPI_MODEL fallback below can supply the brand. + cpu_brand="$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | sed 's/.*: //' || true)" + [ -z "$cpu_brand" ] && cpu_brand="$PQB_RPI_MODEL" + ncpu="$( (command -v nproc >/dev/null && nproc) || grep -c ^processor /proc/cpuinfo)" + ram="$(( $(grep -m1 MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}') * 1024 ))" + os_pretty="$(. /etc/os-release 2>/dev/null; echo "$PRETTY_NAME")" + fi + { + echo "TOOL_VERSION=$TOOL_VERSION" + echo "HOSTNAME=$HOST" + echo "OS=$PQB_OS" + echo "ARCH=$PQB_ARCH" + echo "KERNEL=$kernel" + echo "OS_PRETTY=\"$os_pretty\"" + echo "IS_RPI=$PQB_IS_RPI" + echo "RPI_MODEL=\"$PQB_RPI_MODEL\"" + echo "CPU_BRAND=\"$cpu_brand\"" + echo "NCPU=$ncpu" + echo "RAM_BYTES=$ram" + echo "GOVERNOR_REQUESTED=$GOV_REQUESTED" + echo "GOVERNOR_BEFORE=$GOV_BEFORE" + echo "GOVERNOR_AFTER=$GOV_AFTER_END" + echo "BENCH_CORE=$BENCH_CORE" + echo "PINNED=$PINNED" + echo "TASKSET_CMD=\"$TASKSET\"" + echo "CALIB_MODE=$CALIB_MODE" + echo "TARGET_TIME_MS=$TARGET_TIME_MS" + echo "MIN_SAMPLES=$MIN_SAMPLES" + echo "MAX_ITERS=$MAX_ITERS" + echo "REPS=$REPS" + # warmup/timed_iters are single values only in fixed-count mode; in auto mode + # they are chosen per-op and recorded in each operation's JSON instead. + if [ "$CALIB_MODE" = "fixed" ]; then + echo "WARMUP=$WARMUP" + echo "ITERS=$ITERS" + else + echo "WARMUP=" + echo "ITERS=" + fi + echo "CYCLES_MODE=$CYCLES_MODE" + echo "CYCLES_AVAILABLE=$CYCLES_AVAILABLE" + echo "CYCLES_REASON=\"$CYCLES_REASON\"" + echo "TS_START_UTC=$TS_START" + echo "TS_END_UTC=$TS_END" + echo "DURATION_S=$DURATION" + echo "WARNINGS=\"$WARN_ACC\"" + } > "$META" +} +collect_host_facts + +# ---- assemble final results JSON ------------------------------------------- +OUT="$ROOT/results/${HOST}-${TS}.json" +python3 "$ROOT/bench/lib/assemble.py" \ + --meta "$META" --lock "$LOCK" --features "$FEATURES" \ + --kemsig "$KEMSIG_OUT" ${TLS_OUT:+--tls "$TLS_OUT"} \ + --thermal "$THERMAL" --config "$ROOT/config.yaml" \ + --out "$OUT" >/dev/null + +# ---- summary --------------------------------------------------------------- +echo +pqb_log "================ RUN COMPLETE ================" +python3 - "$OUT" <<'PY' +import json,sys +d=json.load(open(sys.argv[1])) +g=d["is_baseline_grade"] +print(f" results: {sys.argv[1]}") +print(f" host: {d['host']['cpu_brand']} ({d['host']['os_pretty']})") +print(f" baseline-grade (RPi5): {g}") +if not g: + for r in d['baseline_grade_reasons']: + print(f" - {r}") +tt=d['thermal_trace'] +print(f" thermal: {tt.get('temp_c')} throttling={tt.get('throttling_detected')}") +print(f" kem algos: {sum(1 for x in d['kem'] if x.get('enabled'))} enabled / {len(d['kem'])}") +print(f" sig algos: {sum(1 for x in d['sig'] if x.get('enabled'))} enabled / {len(d['sig'])}") +print(f" cycles available: {d['run']['cycles_available']}") +PY +pqb_log "keep raw work dir? -> $WORK (safe to delete)" diff --git a/pq-bench-rpi5/setup/lib_platform.sh b/pq-bench-rpi5/setup/lib_platform.sh new file mode 100644 index 0000000..6285f77 --- /dev/null +++ b/pq-bench-rpi5/setup/lib_platform.sh @@ -0,0 +1,227 @@ +# shellcheck shell=bash +# ============================================================================= +# lib_platform.sh — portable platform abstraction +# +# Sourced by setup/setup.sh and run.sh. Every operation that differs between the +# RPi5 (Debian/Ubuntu aarch64) measurement target and the macOS/Apple-Silicon +# dev box is funneled through one of these functions, so the *identical* codebase +# runs unchanged on both. Where a capability does not exist on a platform +# (governor control, core pinning, on-die thermal sensors), the function degrades +# gracefully and the caller records that it was unavailable — it never silently +# pretends the action happened. +# ============================================================================= + +# ---- platform detection ---------------------------------------------------- +# Sets: PQB_OS (macos|linux), PQB_ARCH, PQB_IS_RPI (1|0), PQB_RPI_MODEL +pqb_detect_platform() { + PQB_ARCH="$(uname -m)" + case "$(uname -s)" in + Darwin) PQB_OS="macos" ;; + Linux) PQB_OS="linux" ;; + *) PQB_OS="unknown" ;; + esac + + PQB_IS_RPI=0 + PQB_RPI_MODEL="" + if [ "$PQB_OS" = "linux" ] && [ -r /proc/device-tree/model ]; then + # /proc/device-tree/model is NUL-terminated + PQB_RPI_MODEL="$(tr -d '\0' < /proc/device-tree/model 2>/dev/null)" + case "$PQB_RPI_MODEL" in + *"Raspberry Pi"*) PQB_IS_RPI=1 ;; + esac + fi + export PQB_OS PQB_ARCH PQB_IS_RPI PQB_RPI_MODEL +} + +# ---- friendly logging ------------------------------------------------------ +pqb_log() { printf '\033[1;34m[pqb]\033[0m %s\n' "$*" >&2; } +pqb_warn() { printf '\033[1;33m[pqb WARN]\033[0m %s\n' "$*" >&2; } +pqb_err() { printf '\033[1;31m[pqb ERR]\033[0m %s\n' "$*" >&2; } + +# ---- hostname resolution --------------------------------------------------- +# A "good" host id is non-empty, not localhost, and not an avahi/macOS +# auto-assigned "unknown" placeholder (which is what shows up when no +# real hostname is set — that produced the ugly results filename before). +_pqb_good_host() { + local h="$1" + [ -n "$h" ] || return 1 + case "$h" in + localhost|localhost.*) return 1 ;; + esac + printf '%s' "$h" | grep -Eq '^[Uu]nknown[0-9a-fA-F]{6,}$' && return 1 + return 0 +} + +# Resolve a readable, stable host identifier, falling through: +# $HOSTNAME -> `hostname` -> hostnamectl --static (Linux) / +# scutil --get LocalHostName (macOS) -> short machine id (last resort). +# Domain suffixes (.home/.local/...) are stripped. On the RPi5 this yields the +# actual pi hostname; on this Mac it falls through to LocalHostName. +pqb_resolve_hostname() { + local cands=() c h + cands+=("${HOSTNAME:-}") + cands+=("$(hostname 2>/dev/null || true)") + if [ "${PQB_OS:-}" = "linux" ]; then + cands+=("$(hostnamectl --static 2>/dev/null || true)") + elif [ "${PQB_OS:-}" = "macos" ]; then + cands+=("$(scutil --get LocalHostName 2>/dev/null || true)") + fi + for c in "${cands[@]}"; do + h="${c%%.*}" # strip domain suffix + if _pqb_good_host "$h"; then echo "$h"; return 0; fi + done + # last resort: a short, stable machine id so files never collide as "unknown" + local mid="" + if [ -r /etc/machine-id ]; then + mid="$(cut -c1-12 /etc/machine-id 2>/dev/null)" + elif [ "${PQB_OS:-}" = "macos" ]; then + mid="$(ioreg -rd1 -c IOPlatformExpertDevice 2>/dev/null \ + | awk -F'"' '/IOPlatformUUID/{print $4}' | tr -d '-' | cut -c1-12)" + fi + echo "host-${mid:-unknown}" +} + +# ---- CPU governor ---------------------------------------------------------- +# Returns 0 if it set 'performance', 1 if unavailable. Prints the governor it +# left the system in on stdout. +pqb_set_governor_performance() { + if [ "$PQB_OS" = "linux" ] && [ -d /sys/devices/system/cpu/cpu0/cpufreq ]; then + local ok=1 g + for g in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do + [ -w "$g" ] || { ok=0; continue; } + echo performance > "$g" 2>/dev/null || ok=0 + done + if [ "$ok" = 1 ]; then + cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null + return 0 + fi + # try cpupower as a fallback (may need sudo) + if command -v cpupower >/dev/null 2>&1 && cpupower frequency-set -g performance >/dev/null 2>&1; then + echo performance; return 0 + fi + pqb_warn "could not set governor to performance (need root? try: sudo ./run.sh)" + cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null || echo "unknown" + return 1 + fi + # macOS / other: no userspace governor control. + echo "unavailable" + return 1 +} + +pqb_get_governor() { + if [ "$PQB_OS" = "linux" ] && [ -r /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then + cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor + else + echo "unavailable" + fi +} + +# ---- core pinning ---------------------------------------------------------- +# pqb_taskset_prefix -> echoes a command prefix to pin to that core, or +# empty string if pinning is unavailable (caller warns). +pqb_taskset_prefix() { + local core="$1" + if command -v taskset >/dev/null 2>&1; then + echo "taskset -c $core" + elif command -v numactl >/dev/null 2>&1; then + echo "numactl --physcpubind=$core" + else + echo "" # no pinning available (e.g. macOS) + fi +} + +# ---- thermal / clock sampling ---------------------------------------------- +# pqb_sample_thermal -> one CSV line: epoch_s,arm_clock_hz,temp_c,throttled_hex +# Fields that cannot be read on a platform are emitted as empty (no fake zeros). +pqb_sample_thermal() { + local ts clk temp thr + ts="$(date +%s)" + clk=""; temp=""; thr="" + + if command -v vcgencmd >/dev/null 2>&1; then + # Raspberry Pi: authoritative SoC sensors. + clk="$(vcgencmd measure_clock arm 2>/dev/null | sed -n 's/.*=//p')" + temp="$(vcgencmd measure_temp 2>/dev/null | sed -n "s/temp=\([0-9.]*\).*/\1/p")" + thr="$(vcgencmd get_throttled 2>/dev/null | sed -n 's/.*=//p')" + elif [ "$PQB_OS" = "linux" ]; then + # Generic Linux fallback (cpufreq + thermal_zone). + local f + f=/sys/devices/system/cpu/cpu${PQB_BENCH_CORE:-0}/cpufreq/scaling_cur_freq + [ -r "$f" ] && clk="$(( $(cat "$f") * 1000 ))" # kHz -> Hz + if [ -r /sys/class/thermal/thermal_zone0/temp ]; then + local milli; milli="$(cat /sys/class/thermal/thermal_zone0/temp)" + temp="$(awk -v m="$milli" 'BEGIN{printf "%.1f", m/1000}')" + fi + fi + # macOS: live per-core freq/temp require sudo powermetrics; we intentionally + # leave them empty rather than emit misleading values. (Smoke test only.) + + printf '%s,%s,%s,%s\n' "$ts" "$clk" "$temp" "$thr" +} + +# pqb_throttled_active -> 0 if thermal throttling currently/has +# occurred, 1 otherwise. RPi get_throttled bit 0 = under-voltage now, +# bit 1 = arm freq capped now, bit 2 = currently throttled, +# bit 3 = soft temp limit active (and bits 16-19 = "has occurred" latches). +pqb_throttled_active() { + local hex="${1#0x}" + [ -z "$hex" ] && return 1 + local val=$(( 16#$hex )) + # bit2 (throttling now) or bit18 (throttling has occurred) + if [ $(( val & 0x4 )) -ne 0 ] || [ $(( val & 0x40000 )) -ne 0 ]; then + return 0 + fi + return 1 +} + +# ---- CPU feature / crypto-extension detection ------------------------------ +# Echoes a JSON object describing NEON + SHA3/SHA512 acceleration. Consumed +# verbatim by env metadata so results record whether Keccak accel is in use. +pqb_cpu_features_json() { + local neon=false sha2=false sha3=false sha512=false aes=false pmull=false src="unknown" + if [ "$PQB_OS" = "linux" ] && [ -r /proc/cpuinfo ]; then + src="/proc/cpuinfo" + local feats; feats="$(grep -m1 -i '^Features' /proc/cpuinfo | tr 'A-Z' 'a-z')" + case "$feats" in *" asimd"*|*"neon"*) neon=true;; esac + case "$feats" in *" sha2"*) sha2=true;; esac + case "$feats" in *" sha3"*) sha3=true;; esac + case "$feats" in *" sha512"*) sha512=true;; esac + case "$feats" in *" aes"*) aes=true;; esac + case "$feats" in *" pmull"*) pmull=true;; esac + elif [ "$PQB_OS" = "macos" ]; then + src="sysctl" + neon=true # all Apple Silicon has NEON/ASIMD + [ "$(sysctl -n hw.optional.arm.FEAT_SHA256 2>/dev/null)" = 1 ] && sha2=true + [ "$(sysctl -n hw.optional.arm.FEAT_SHA3 2>/dev/null)" = 1 ] && sha3=true + [ "$(sysctl -n hw.optional.arm.FEAT_SHA512 2>/dev/null)" = 1 ] && sha512=true + [ "$(sysctl -n hw.optional.arm.FEAT_AES 2>/dev/null)" = 1 ] && aes=true + [ "$(sysctl -n hw.optional.arm.FEAT_PMULL 2>/dev/null)" = 1 ] && pmull=true + fi + printf '{"source":"%s","neon":%s,"sha2":%s,"sha3":%s,"sha512":%s,"aes":%s,"pmull":%s}' \ + "$src" "$neon" "$sha2" "$sha3" "$sha512" "$aes" "$pmull" +} + +# ---- package installation -------------------------------------------------- +# pqb_install_build_deps -> installs compiler/cmake/openssl headers per platform. +pqb_install_build_deps() { + if [ "$PQB_OS" = "macos" ]; then + command -v brew >/dev/null 2>&1 || { pqb_err "Homebrew required on macOS: https://brew.sh"; return 1; } + pqb_log "installing build deps via Homebrew" + brew install cmake ninja openssl@3 git python3 >/dev/null || true + elif [ "$PQB_OS" = "linux" ]; then + if command -v apt-get >/dev/null 2>&1; then + pqb_log "installing build deps via apt" + local SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" + $SUDO apt-get update -qq + # linux-cpupower provides the `cpupower` binary used by + # pqb_set_governor_performance. (Older releases shipped cpufrequtils, which + # was dropped in Debian 13/trixie — cpupower is the supported replacement.) + $SUDO apt-get install -y -qq \ + build-essential cmake ninja-build git python3 perl \ + libssl-dev pkg-config astyle doxygen \ + linux-cpupower util-linux >/dev/null + else + pqb_warn "no apt-get found; install cmake/ninja/gcc/libssl-dev manually" + fi + fi +} diff --git a/pq-bench-rpi5/setup/setup.sh b/pq-bench-rpi5/setup/setup.sh new file mode 100755 index 0000000..a34cad9 --- /dev/null +++ b/pq-bench-rpi5/setup/setup.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +# ============================================================================= +# setup.sh — build + pin the full PQ toolchain from scratch. +# +# ./setup/setup.sh # everything: deps, liboqs, openssl(if needed), oqs-provider +# ./setup/setup.sh liboqs # just liboqs +# ./setup/setup.sh openssl # just openssl (forced from source) +# ./setup/setup.sh provider # just oqs-provider +# ./setup/setup.sh deps # just OS packages +# +# Everything is installed under ./vendor/install (no system pollution). The exact +# resolved git commits + the optimization flags actually used are written to +# setup/versions.lock, which run.sh stamps into every results JSON. +# +# Identical flags for every candidate: -O3 -mcpu=cortex-a76 on the RPi5. On a +# non-A76 host (the macOS smoke box) we fall back to -O3 and RECORD that, so +# smoke-test numbers can never masquerade as the RPi5 baseline. +# ============================================================================= +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$HERE/.." && pwd)" +# shellcheck source=setup/lib_platform.sh +source "$HERE/lib_platform.sh" +# shellcheck source=setup/versions.env +source "$HERE/versions.env" + +pqb_detect_platform + +VENDOR="$ROOT/vendor" +SRC="$VENDOR/src" +PREFIX="$VENDOR/install" +mkdir -p "$SRC" "$PREFIX" + +JOBS="$( (command -v nproc >/dev/null && nproc) || sysctl -n hw.ncpu 2>/dev/null || echo 4)" + +# ---- decide the real optimization flags for THIS host ---------------------- +# We only use -mcpu=cortex-a76 if the compiler accepts it AND we're on aarch64. +choose_cflags() { + local cc="${CC:-cc}" probe="$SRC/.flagprobe.c" + echo 'int main(void){return 0;}' > "$probe" + if [ "$PQB_ARCH" = "aarch64" ] && $cc $TARGET_CFLAGS_RPI5 "$probe" -o "$probe.out" 2>/dev/null; then + BENCH_CFLAGS="$TARGET_CFLAGS_RPI5"; CFLAGS_TARGET="cortex-a76" + else + BENCH_CFLAGS="$TARGET_CFLAGS_FALLBACK"; CFLAGS_TARGET="generic-fallback" + fi + rm -f "$probe" "$probe.out" +} + +cc_version_string() { + local cc="${CC:-cc}" + "$cc" --version 2>/dev/null | head -1 +} + +git_pin() { # repo ref destdir + local repo="$1" ref="$2" dest="$3" + if [ -d "$dest/.git" ]; then + pqb_log "updating $(basename "$dest") -> $ref" + git -C "$dest" fetch -q --depth 1 origin "$ref" || git -C "$dest" fetch -q --tags origin + else + pqb_log "cloning $(basename "$dest") @ $ref" + git clone -q --depth 1 --branch "$ref" "$repo" "$dest" 2>/dev/null \ + || git clone -q "$repo" "$dest" + fi + git -C "$dest" checkout -q "$ref" 2>/dev/null || true + git -C "$dest" rev-parse HEAD +} + +# --------------------------------------------------------------------------- +build_liboqs() { + choose_cflags + local dest="$SRC/liboqs" commit + commit="$(git_pin "$LIBOQS_REPO" "$LIBOQS_REF" "$dest")" + pqb_log "building liboqs ($LIBOQS_REF @ ${commit:0:12}) flags: $BENCH_CFLAGS" + + # OQS_DIST_BUILD=OFF -> native build for the fixed target (no runtime CPU + # dispatch), so -mcpu=cortex-a76 fully drives codegen. The AArch64-optimized + # ML-KEM (mlkem-native) and AArch64 asm backends are enabled by default on + # aarch64 when DIST_BUILD is OFF (compile-time CPU features); verified post-build. + local GEN=(); command -v ninja >/dev/null 2>&1 && GEN=(-G Ninja) + cmake -S "$dest" -B "$dest/build" ${GEN[@]+"${GEN[@]}"} \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DOQS_DIST_BUILD=OFF \ + -DOQS_BUILD_ONLY_LIB=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_C_FLAGS="$BENCH_CFLAGS" >/dev/null + cmake --build "$dest/build" --parallel "$JOBS" >/dev/null + cmake --install "$dest/build" >/dev/null + + # Prove the optimized backend: capture the aarch64/native defines from the + # generated build config so versions.lock can show what was actually compiled. + local cfg="$dest/build/include/oqs/oqsconfig.h" + LIBOQS_OPT_DEFINES="(oqsconfig.h not found)" + if [ -r "$cfg" ]; then + # strip embedded double-quotes so the value stays valid in versions.lock + LIBOQS_OPT_DEFINES="$(grep -Ei 'AARCH64|ARM|_ASM|MLKEM_NATIVE|OPT_TARGET|CPU_EXT' "$cfg" \ + | grep -i 'define' | sed 's/^#define //' | tr -d '"' | tr '\n' ';' || true)" + fi + LIBOQS_COMMIT="$commit" +} + +# --------------------------------------------------------------------------- +locate_or_build_openssl() { + # Prefer an existing >= 3.5 openssl (Homebrew on macOS, distro on Linux) unless + # BUILD_OPENSSL=1. PQ sig certs for TLS only need >= 3.5.0. + local want_major=3 want_minor=5 + if [ "${1:-}" != "force" ] && [ "${BUILD_OPENSSL:-0}" != 1 ]; then + local cand + for cand in "$(command -v openssl || true)" /opt/homebrew/opt/openssl@3/bin/openssl /usr/bin/openssl; do + [ -x "$cand" ] || continue + local v; v="$("$cand" version 2>/dev/null | awk '{print $2}')" + # NB: assign on separate lines. A single `local a=.. b=.. c="${b..}"` makes + # bash 5.2 declare all names (unset) *before* expanding any RHS, so the + # reference to `rest` here trips `set -u` (unbound variable) on the Pi. + local maj rest min + maj="${v%%.*}"; rest="${v#*.}"; min="${rest%%.*}" + if [ "${maj:-0}" -gt "$want_major" ] 2>/dev/null || \ + { [ "${maj:-0}" -eq "$want_major" ] && [ "${min:-0}" -ge "$want_minor" ]; } 2>/dev/null; then + OPENSSL_BIN="$cand" + OPENSSL_PREFIX="$(dirname "$(dirname "$cand")")" + OPENSSL_COMMIT="system:$v" + pqb_log "using existing OpenSSL $v at $cand" + return 0 + fi + done + fi + pqb_log "building OpenSSL $OPENSSL_REF from source" + local dest="$SRC/openssl" commit + commit="$(git_pin "$OPENSSL_REPO" "$OPENSSL_REF" "$dest")" + ( cd "$dest" && ./Configure --prefix="$PREFIX" --openssldir="$PREFIX/ssl" shared \ + && make -j"$JOBS" >/dev/null && make install_sw >/dev/null ) + OPENSSL_BIN="$PREFIX/bin/openssl" + OPENSSL_PREFIX="$PREFIX" + OPENSSL_COMMIT="$commit" +} + +# --------------------------------------------------------------------------- +build_oqs_provider() { + [ -n "${OPENSSL_PREFIX:-}" ] || locate_or_build_openssl + local dest="$SRC/oqs-provider" commit + commit="$(git_pin "$OQSPROVIDER_REPO" "$OQSPROVIDER_REF" "$dest")" + pqb_log "building oqs-provider ($OQSPROVIDER_REF @ ${commit:0:12})" + local GEN=(); command -v ninja >/dev/null 2>&1 && GEN=(-G Ninja) + cmake -S "$dest" -B "$dest/build" ${GEN[@]+"${GEN[@]}"} \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DOPENSSL_ROOT_DIR="$OPENSSL_PREFIX" \ + -Dliboqs_DIR="$PREFIX/lib/cmake/liboqs" \ + -DCMAKE_C_FLAGS="${BENCH_CFLAGS:-$TARGET_CFLAGS_FALLBACK}" >/dev/null + cmake --build "$dest/build" --parallel "$JOBS" >/dev/null + cmake --install "$dest/build" >/dev/null 2>&1 || true + # Provider .so lands under .../lib/ossl-modules or .../oqsprovider + OQSPROVIDER_MODULE="$(find "$PREFIX" "$dest/build" -name 'oqsprovider.*' \( -name '*.so' -o -name '*.dylib' \) 2>/dev/null | head -1)" + OQSPROVIDER_COMMIT="$commit" +} + +# --------------------------------------------------------------------------- +write_lock() { + choose_cflags 2>/dev/null || true + local lock="$HERE/versions.lock" + { + echo "# Auto-generated by setup.sh — exact toolchain provenance. Stamped into results JSON." + echo "PQB_BUILD_HOST_OS=$PQB_OS" + echo "PQB_BUILD_HOST_ARCH=$PQB_ARCH" + echo "PQB_IS_RPI=$PQB_IS_RPI" + echo "PQB_RPI_MODEL=\"${PQB_RPI_MODEL}\"" + echo "BENCH_CFLAGS=\"${BENCH_CFLAGS:-unknown}\"" + echo "CFLAGS_TARGET=\"${CFLAGS_TARGET:-unknown}\"" + echo "CC_VERSION=\"$(cc_version_string)\"" + echo "LIBOQS_REF=\"$LIBOQS_REF\"" + echo "LIBOQS_COMMIT=\"${LIBOQS_COMMIT:-not-built}\"" + echo "LIBOQS_OPT_DEFINES=\"${LIBOQS_OPT_DEFINES:-}\"" + echo "OPENSSL_BIN=\"${OPENSSL_BIN:-}\"" + echo "OPENSSL_PREFIX=\"${OPENSSL_PREFIX:-}\"" + echo "OPENSSL_COMMIT=\"${OPENSSL_COMMIT:-not-built}\"" + echo "OQSPROVIDER_REF=\"$OQSPROVIDER_REF\"" + echo "OQSPROVIDER_COMMIT=\"${OQSPROVIDER_COMMIT:-not-built}\"" + echo "OQSPROVIDER_MODULE=\"${OQSPROVIDER_MODULE:-}\"" + echo "PREFIX=\"$PREFIX\"" + } > "$lock" + pqb_log "wrote $lock" + cat "$lock" >&2 +} + +# ---- dispatch -------------------------------------------------------------- +main() { + local what="${1:-all}" + case "$what" in + deps) pqb_install_build_deps ;; + liboqs) build_liboqs; write_lock ;; + openssl) locate_or_build_openssl force; write_lock ;; + provider) build_oqs_provider; write_lock ;; + all) + pqb_install_build_deps + build_liboqs + locate_or_build_openssl + build_oqs_provider + write_lock + pqb_log "setup complete. Next: ./run.sh --smoke" + ;; + *) pqb_err "unknown target: $what (deps|liboqs|openssl|provider|all)"; exit 2 ;; + esac +} +main "$@" diff --git a/pq-bench-rpi5/setup/versions.env b/pq-bench-rpi5/setup/versions.env new file mode 100644 index 0000000..362d3c7 --- /dev/null +++ b/pq-bench-rpi5/setup/versions.env @@ -0,0 +1,40 @@ +# ============================================================================= +# Pinned upstream versions for the PQ benchmark toolchain. +# +# These are the *intended* refs. setup/setup.sh clones each at the tag below +# and then records the *actually resolved* commit hash into setup/versions.lock, +# which is stamped verbatim into every results JSON. That way a results file is +# always traceable to exact source, even if a tag is ever re-pointed upstream. +# +# Override any of these from the environment, e.g.: +# LIBOQS_REF=main ./setup/setup.sh +# ============================================================================= + +# liboqs: KEM/signature implementations. >= 0.15.0 ships the AArch64-optimized +# ML-KEM backend (mlkem-native). We enable it explicitly in setup.sh. +LIBOQS_REPO="${LIBOQS_REPO:-https://github.com/open-quantum-safe/liboqs.git}" +LIBOQS_REF="${LIBOQS_REF:-0.15.0}" + +# OpenSSL >= 3.5.0 — required for PQ signatures in TLS 1.3 server auth. +# On macOS dev boxes we prefer the Homebrew openssl@3 if it is already >= 3.5; +# setup.sh only builds OpenSSL from source when the system one is too old or +# BUILD_OPENSSL=1 is set. +OPENSSL_REPO="${OPENSSL_REPO:-https://github.com/openssl/openssl.git}" +OPENSSL_REF="${OPENSSL_REF:-openssl-3.5.0}" + +# oqs-provider >= 0.9.0 — wires liboqs algorithms into OpenSSL as a provider, +# giving us PQ KEM groups and PQ signature certs for the TLS layer. +OQSPROVIDER_REPO="${OQSPROVIDER_REPO:-https://github.com/open-quantum-safe/oqs-provider.git}" +OQSPROVIDER_REF="${OQSPROVIDER_REF:-0.9.0}" + +# Identical optimization flags for every candidate (the credibility anchor). +# cortex-a76 is the RPi5 core. On non-A76 hosts (e.g. the macOS smoke box) +# setup.sh substitutes a safe fallback and records which flags were *actually* +# used in versions.lock + results JSON, so smoke numbers are never mistaken for +# the RPi5 baseline. +TARGET_CFLAGS_RPI5="${TARGET_CFLAGS_RPI5:--O3 -mcpu=cortex-a76}" +TARGET_CFLAGS_FALLBACK="${TARGET_CFLAGS_FALLBACK:--O3}" + +# Pinned core for taskset on the RPi5 (4 cores: 0-3). Core 3 is chosen to stay +# away from CPU0 where the kernel tends to steer IRQs/RPS. Documented in README. +BENCH_CORE="${BENCH_CORE:-3}"