GitHub - aethyrai/esp32-awp-edge: Post-quantum encrypted IoT edge node for ESP32-S3. ML-KEM-768 key exchange, BLAKE3 integrity, XChaCha20-Poly1305 encryption. Formally verified. 2.1s cold boot to PQC session.

3 min read Original article ↗

Post-quantum encrypted IoT edge node for ESP32-S3. Speaks the AethyrWire Protocol (AWP) with ML-KEM-768 key exchange, BLAKE3 frame integrity, and XChaCha20-Poly1305 payload encryption.

2.1 seconds from cold boot to post-quantum encrypted session on a $5 chip.

Benchmarks

All numbers measured on ESP32-S3-WROOM-1 @ 240MHz, 50 iterations, reporting mean and standard deviation.

Operation Mean StdDev
ML-KEM-768 keygen 9,052us 164us
ML-KEM-768 encapsulate 10,070us 11us
ML-KEM-768 decapsulate 12,197us 11us
XChaCha20-Poly1305 encrypt 243us 46us
BLAKE3 (1KB) 255us 102us
BLAKE3 KDF 49us 60us
AWP frame encode + decode 363us 95us
Metric Value
Cold boot to PQC session 2.1 seconds
Firmware size 833KB
Free heap (runtime) 157KB

Security

Layer Algorithm Implementation
Key exchange ML-KEM-768 mlkem-native v1.0.0 (formally verified)
Frame integrity BLAKE3-256 Official C (portable)
Payload encryption XChaCha20-Poly1305 mbedTLS
Key derivation BLAKE3
Nonce persistence ESP32 NVS flash

Hardening

  • No plaintext fallback. Handshake aborts without post-quantum key exchange.
  • Constant-time cryptographic operations. Timing side-channels mitigated.
  • Nonce reuse prevention. Counter persisted to flash, survives unclean power loss.
  • Mutual authentication. Session key possession verified at establishment.
  • Boot self-test. 14 crypto tests on every boot. Firmware halts on failure.

Verification

  • 14 self-tests on every boot
  • Cross-platform interop proven byte-for-byte
  • 410,000 fuzz iterations (AddressSanitizer + UBSan), zero crashes
  • 100,000 single-bit-flip tests, 100% detected

Quick Start

Requirements: ESP32-S3 board, USB cable, ESP-IDF v5.4.

git clone https://github.com/aethyrai/esp32-awp-edge
cd esp32-awp-edge

# Configure WiFi and upstream node
idf.py menuconfig
# → AWP Edge Node Configuration
#   WiFi SSID / Password
#   Upstream host IP and port

# Build and flash
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

Serial output on boot:

Crypto Self-Test Suite
  [1] BLAKE3: empty input...                           PASS
  [2] BLAKE3: 251 sequential bytes...                  PASS
  [3] BLAKE3: derive_key (KDF mode)...                 PASS
  [4] XChaCha20-Poly1305: encrypt/decrypt round-trip...PASS
  [5] XChaCha20-Poly1305: tamper detection...          PASS
  [6] XChaCha20-Poly1305: wrong key rejection...       PASS
  [7] XChaCha20-Poly1305: nonce uniqueness...          PASS
  [8] ML-KEM-768: keygen + encap/decap round-trip...   PASS
  [9] ML-KEM-768: wrong secret key rejection...        PASS
  [10] INTEROP: BLAKE3 KDF matches Python...           PASS
  [11] INTEROP: decrypt Python-produced ciphertext...  PASS
  [12] Replay window: accept / duplicate / too-old...  PASS
  [13] AWP: frame encode/decode round-trip...          PASS
  [14] AWP: BLAKE3 checksum tamper detection...        PASS
  ALL 14 TESTS PASSED (261ms)

ML-KEM-768 keypair ready
WiFi connected
TCP connected to upstream
PQC session established

Project Structure

esp32-awp-edge/
├── main/
│   ├── awp_protocol.{c,h}     Frame encoder/decoder
│   ├── awp_stream.{c,h}       TCP stream reassembly
│   ├── awp_edge_node.{c,h}    Edge node lifecycle
│   ├── awp_crypto.{c,h}       Post-quantum crypto
│   ├── sensor_hub.{c,h}       Pluggable sensor drivers
│   ├── crypto_test.{c,h}      Boot self-tests
│   └── main.c                 Entry point
├── components/
│   ├── blake3/                 BLAKE3 official C (portable, no SIMD)
│   └── mlkem768/               mlkem-native v1.0.0 (formally verified ML-KEM-768)
├── test/
│   └── fuzz_awp.c             Frame decoder fuzzer
├── jetson/
│   ├── setup-mesh-ap.sh       Create dedicated WiFi AP on Jetson
│   ├── stop-mesh-ap.sh        Stop mesh AP
│   └── aios-node.service      systemd service for AWP node
├── demo/
│   └── run_demo.py            Scripted 2-minute demo
└── sdkconfig.defaults         Build defaults

Stack Usage

Minimum safe task stack for ML-KEM operations: 24KB.

Fuzz Testing

Compile and run on the host (not ESP32):

cc -fsanitize=address,undefined -g -O1 \
   -Itest -Imain -Icomponents/blake3 \
   test/fuzz_awp.c main/awp_protocol.c main/awp_stream.c \
   components/blake3/blake3.c components/blake3/blake3_portable.c \
   components/blake3/blake3_dispatch.c \
   -DBLAKE3_NO_SSE2 -DBLAKE3_NO_SSE41 -DBLAKE3_NO_AVX2 \
   -DBLAKE3_NO_AVX512 -DBLAKE3_NO_NEON \
   -o test/fuzz_awp -Wno-format -lm && ./test/fuzz_awp

Hardware

Tested on:

  • ESP32-S3-WROOM-1 (QFN56, rev v0.2) — WiFi, BLE, 8MB PSRAM
  • Jetson Orin Nano Super — upstream NODE, dedicated 2.4GHz WiFi AP

Should work on any ESP32-S3 board with ESP-IDF v5.4.

What This Connects To

This firmware is the edge node for the Aethyr distributed agent mesh. The ESP32 handles the sensory edge: sensors, actuators, and encrypted transport. The intelligence lives upstream.

License

Apache 2.0