perf: cache the DISCO X25519 shared secret - #31
Open
kedimuzafer wants to merge 1 commit into
Open
Conversation
nacl_box()/nacl_box_open() derive the shared key with nacl_box_beforenm() on every call, which is an X25519 scalar multiplication. Measured on an ESP32-S3 at 240 MHz that is 30-40 ms, and DISCO does one per packet in each direction. The node's own disco secret key never changes at runtime, so the result depends only on the peer's public key. Adds nacl_box_shared_get(), a 24-slot memo keyed on the peer public key, and routes the four DISCO call sites in ml_wg_mgr.c through it plus the existing _afternm() primitives. Effect on a 15-peer tailnet: disco_periodic_probes() went from ~113 ms per one-second round to under a millisecond, and processing a single 110-byte DISCO PONG from 30-40 ms to negligible. Those 113 ms rounds ran on the same task that drains the WireGuard receive queue, so the queue overflowed behind them and tunnelled TCP could not stay up. 64 bytes of state per slot, 1.5 KB total, no allocation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqLmUG7wJ7p9ya2Vp4A9J
This was referenced Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
nacl_box()andnacl_box_open()both callnacl_box_beforenm()internally, which is an X25519 scalar multiplication. DISCO calls them once per packet, in each direction.On an ESP32-S3 at 240 MHz that scalar multiplication is 30–40 ms. Timestamps from the device's own log, processing one 110-byte DISCO PONG:
30 ms between receiving the packet and finishing with it. And the periodic probe round over a 15-peer tailnet:
That runs every second, on the same task that drains
wg_rx_queue. The queue is 8 deep and the enqueue is non-blocking, so every WireGuard packet arriving inside those 113 ms was dropped without a trace. That was enough to keep TCP through the tunnel from ever getting going.Change
The node's own disco secret key is fixed for its lifetime, so the shared key depends only on the peer's public key. Adds
nacl_box_shared_get()— a 24-slot memo keyed on the peer public key, replaced round-robin — and routes the four DISCO call sites inml_wg_mgr.cthrough it plus the existingnacl_box_afternm()/nacl_box_open_afternm()primitives, which are already exported and already take a precomputed key.Cost: 64 bytes per slot, 1.5 KB of
.bss, no allocation, no change to the wire format or to the crypto itself.After
disco_periodic_probes: ~113 ms → under 1 ms per roundA miss costs exactly what the old path cost, so worst case is unchanged.
Measured together with #30; the two are independent and can land in either order.