Skip to content

fix: C6 decoys could never enroll (re-sent OFFER re-rolled the session) - #15

Merged
Em3ritus merged 1 commit into
mainfrom
fix/c6-enroll-race-2026-09-28
Sep 28, 2026
Merged

Em3ritus merged 1 commit into
mainfrom
fix/c6-enroll-race-2026-09-28

Conversation

@Em3ritus

Copy link
Copy Markdown
Owner

Symptom. An ESP32-C6 decoy never enrolls. Its serial log shows enroll: answered OFFER -> sent REQUEST about every 2 s for the whole pairing window, then a task watchdog trip every 5 s with espnow pinned on CPU 0 (backtrace inside TweetNaCl's M/car25519), then rx: 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 fresh nonce_d, a full Ed25519 verify and a fresh crypto_box REQUEST. The Vigil seals each GRANT to the nonce_d of the REQUEST it answered, and the decoy only accepts a GRANT whose echo matches its current nonce_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.

  • A byte-identical resend of the OFFER already verified and answered now replays the cached REQUEST: same session, no crypto, and the same 60 s enr_nonce_answerable replay bound.
  • Once the GRANT has landed, resends are ignored.
  • A new OFFER (a new window or a rotation, so a different nonce_v and different bytes) still takes the full path.
  • A GRANT dropped for a session mismatch is now logged.

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:

OFFERs answered GRANT watchdog trips enrolled after reboot
before 39 in one window never accepted 13 no
after 1 accepted 0 yes (epoch 3)

A rev v1.2 C5 was also reflashed with this change and stays enrolled across reboot.

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.
@Em3ritus
Em3ritus merged commit eeefa31 into main Sep 28, 2026
4 checks passed
@Em3ritus
Em3ritus deleted the fix/c6-enroll-race-2026-09-28 branch September 28, 2026 18:37
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant