ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
crypto_opt.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file crypto_opt.h
6 * @brief Per-translation-unit optimization override for hot, pure-integer crypto.
7 *
8 * The library ships at the arduino framework's `-Os` (the framework appends it AFTER any PlatformIO env
9 * `build_flags`, so a plain `-O2` there is overridden). `-Os` roughly halves the throughput of the software
10 * ciphers/MACs. Put @ref PC_CRYPTO_HOT at the top of such a `.cpp` (after its includes) to force a higher
11 * level for that one translation unit, regardless of the consumer's size-optimized build:
12 *
13 * @code
14 * #include "crypto/cipher/chacha20.h"
15 * #include "crypto/crypto_opt.h"
16 * PC_CRYPTO_HOT // this TU builds at -O2 (or the configured level)
17 * @endcode
18 *
19 * The level is `PC_CRYPTO_OPT_LEVEL`: `2` or `3`, or `0` to inherit the framework `-Os`. It is NOT a
20 * ServerConfig / user knob - it is internal crypto tuning with a measured per-die default (see below), which
21 * a hot TU can override for its own build with `#define PC_CRYPTO_OPT_LEVEL 3` before this include. Only GCC
22 * honors `#pragma GCC optimize`; clang/other compilers get a no-op and inherit their normal level.
23 *
24 * When a TU's `-O3` win bisects (on-device) to a SINGLE named transform, prefer the deliberate
25 * `PC_CRYPTO_HOT_PEEL` / `PC_CRYPTO_HOT_UNSWITCH` pin (the `-O2` floor + just that transform) over full
26 * `-O3`, scoped inside the TU's own die guard - same speed, none of `-O3`'s extra code-size / miscompile risk:
27 * @code
28 * #include "crypto/crypto_opt.h"
29 * #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_IDF_TARGET_ESP32S3
30 * PC_CRYPTO_HOT_UNSWITCH // S3: the -O3 win here is entirely -funswitch-loops (bisected)
31 * #else
32 * PC_CRYPTO_HOT // every other die: the per-die default
33 * #endif
34 * @endcode
35 *
36 * ═══════════════════════════════════════════════════════════════════════════
37 * CAVEATS - read before applying this to a new file
38 * ═══════════════════════════════════════════════════════════════════════════
39 *
40 * 1. CONSTANT TIME. Apply this ONLY to code that is constant-time by STRUCTURE - no secret-dependent
41 * branches and no secret-dependent memory indexing: stream ciphers (ChaCha20), MACs (Poly1305), hashes.
42 * Do NOT put it on scalar-multiplication / bignum / point-arithmetic code that relies on branchless
43 * mask-selects to stay constant-time: an aggressive optimizer can (rarely, but it is documented) turn a
44 * mask-select into a data-dependent branch and reintroduce a timing side channel. Those paths are also
45 * HW-accelerator-dominated (RSA/MPI MODMULT, HW AES/SHA), so the `-O` level buys them almost nothing -
46 * all risk, no reward. When in doubt, leave it off.
47 *
48 * 2. SIZE. `-O2`/`-O3` inline and unroll aggressively -> larger flash and IRAM; `-O3` more so. On the
49 * classic ESP32 (tight internal DRAM/IRAM, where TLS example builds already run close to the limit)
50 * prefer level `2` or `0`.
51 *
52 * 3. `-O3` IS NOT UNIVERSALLY FASTER than `-O2` here - it is per-die and per-algo. The bigger unrolled code
53 * can thrash the instruction / flash cache and regress (e.g. the S3's Ed25519 sign is ~1.2% SLOWER at
54 * `-O3`), and `-O3` widens the miscompile / latent-UB surface. So full `-O3` is taken only where it was
55 * MEASURED to help wholesale (the P4, whose win is `-O3`'s parameter budget - see below); on the S3 the
56 * win of a given TU is one transform, taken via a `PC_CRYPTO_HOT_*` pin, and `-O2` is the floor elsewhere.
57 */
58
59#ifndef PROTOCORE_CRYPTO_OPT_H
60#define PROTOCORE_CRYPTO_OPT_H
61
62#if defined(__GNUC__) && !defined(__clang__)
63#ifdef ARDUINO
64#include "sdkconfig.h" // CONFIG_IDF_TARGET_* - the per-die default below + the single-transform die guards in the TUs
65#endif
66#ifndef PC_CRYPTO_OPT_LEVEL
67// Per-variant default, measured on the crypto bench (main_cryptobench). The ESP32-P4 (RISC-V) is faster or flat
68// at -O3 across the whole software crypto suite (chacha -22.9%, poly -15.6%, x25519 -6.8%, ed25519 -4.5%; the HW
69// ops flat) - and its win is -O3's larger inline / unroll PARAMETER budget, not any one -f transform (verified:
70// -O3 with all 14 O2->O3 delta flags disabled still hits the full -O3 numbers), so the P4 takes -O3 wholesale.
71// Every other die defaults to -O2 (the big -Os -> -O2 win); the S3's few -O3 wins are each a SINGLE transform,
72// pinned per-TU with the PC_CRYPTO_HOT_* macros below (see ecdsa/chacha20/hmac_sha256).
73#if defined(CONFIG_IDF_TARGET_ESP32P4) && CONFIG_IDF_TARGET_ESP32P4
74#define PC_CRYPTO_OPT_LEVEL 3
75#else
76#define PC_CRYPTO_OPT_LEVEL 2
77#endif
78#endif
79#if PC_CRYPTO_OPT_LEVEL == 3
80#define PC_CRYPTO_HOT _Pragma("GCC optimize(\"O3\")")
81#elif PC_CRYPTO_OPT_LEVEL == 2
82#define PC_CRYPTO_HOT _Pragma("GCC optimize(\"O2\")")
83#else
84#define PC_CRYPTO_HOT // 0 / other: inherit the framework -Os
85#endif
86// Deliberate single-transform pins: the -O2 floor + exactly ONE -O3 transform that (alone, not full -O3) was
87// MEASURED to carry a TU's win - capturing it without -O3's code-size / miscompile / cache-thrash baggage. A TU
88// selects one inside its own die guard (e.g. #if CONFIG_IDF_TARGET_ESP32S3) and uses PC_CRYPTO_HOT otherwise.
89// This is a closed set (add one only after bisecting the win to a named flag on-device); it is NOT a user knob.
90#define PC_CRYPTO_HOT_PEEL _Pragma("GCC optimize(\"O2\",\"peel-loops\")")
91#define PC_CRYPTO_HOT_UNSWITCH _Pragma("GCC optimize(\"O2\",\"unswitch-loops\")")
92#else
93#define PC_CRYPTO_HOT // non-GCC: no per-TU pragma; inherit the toolchain default
94#define PC_CRYPTO_HOT_PEEL // "
95#define PC_CRYPTO_HOT_UNSWITCH // "
96#endif
97
98#endif // PROTOCORE_CRYPTO_OPT_H