Repository navigation
fix: C6 decoys could never enroll (re-sent OFFER re-rolled the session) - #15
Merged
Merged
Conversation
The Vigil re-sends the same signed OFFER once a second for its 30 s pairing window. The decoy treated every resend as a new session: fresh nonce_d, full Ed25519 verify, fresh crypto_box REQUEST. On a 160 MHz C6 that is ~2 s per OFFER, so resends queued faster than they drained (RX ring overflowed), and every GRANT sat behind a newer OFFER that had already re-rolled nonce_d. The GRANT's nonce echo then never matched and it was dropped without a log line. The same backlog kept espnow_task on the CPU, starving IDLE into a task watchdog trip every 5 s. The 240 MHz C5s usually drain an OFFER before the next arrives, so they mostly won the race and nobody noticed. A byte-identical resend of the OFFER we already verified and answered now replays the cached REQUEST: same session, no crypto, same 60 s replay bound. After the GRANT lands, resends are ignored outright. A GRANT rejected for a session mismatch is now logged, since that silent drop is what hid this. Hardware: before, a C6 answered 39 OFFERs in one window, tripped the watchdog 13 times and never enrolled. After, one OFFER answered, GRANT accepted, 0 watchdog trips, enrollment persists across reboot.
Yverr0y
pushed a commit
to Yverr0y/simulacra
that referenced
this pull request
Oct 7, 2026
…ent fixes - Jump-to: the Architecture link pointed at #architecture--the-nodes; the heading's dash became a hyphen, so the anchor is #architecture---the-nodes. Add the "What it doesn't do" section, which had no link. - Recent updates claimed Wi-Fi probe shape is learned at runtime. It isn't: probe_frame.c ships a fixed table of five capture-derived archetypes. Only BLE AD structure is learned. Say what actually happened to each. - Recent updates and CHANGELOG had nothing on Em3ritus#12, Em3ritus#14 or Em3ritus#15 (the flasher key block, C5 rev v1.2 flashing, C6 enrollment), the fixes users hit. - Name the surveillance signatures (Flock, Axon) the detector carries. - Reflow the follower bullet from Em3ritus#16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Symptom. An ESP32-C6 decoy never enrolls. Its serial log shows
enroll: answered OFFER -> sent REQUESTabout every 2 s for the whole pairing window, then a task watchdog trip every 5 s withespnowpinned on CPU 0 (backtrace inside TweetNaCl'sM/car25519), thenrx: dropped N frame(s) (ring full). C5 decoys enroll fine.Cause. The Vigil re-sends the same signed OFFER once a second for its 30 s window (
cyd_main.c). The decoy (esp_now_link.c) treated each resend as a new session: a freshnonce_d, a full Ed25519 verify and a fresh crypto_box REQUEST. The Vigil seals each GRANT to thenonce_dof the REQUEST it answered, and the decoy only accepts a GRANT whose echo matches its currentnonce_d.On a C6 (160 MHz), each OFFER costs about 2 s of curve math, and OFFERs arrive every 1 s. So the RX ring backs up, and every GRANT is queued behind a newer OFFER that has already re-rolled
nonce_d. The GRANT's nonce never matches, and it was dropped without a log line. The C5 (240 MHz) usually drains each OFFER before the next arrives, which is why it mostly won the race.Fix.
enr_nonce_answerablereplay bound.nonce_vand different bytes) still takes the full path.Security: only an OFFER that has already passed verification and been answered is cached, so a byte-for-byte match never skips a check that matters. The replay bound still applies to resends.
Hardware, same C6 and the same Vigil build:
A rev v1.2 C5 was also reflashed with this change and stays enrolled across reboot.