Skip to content

feat: follow relay-update announcements with a jittered connect - #18

Merged
TheCryptoDonkey merged 2 commits into
mainfrom
feat/relay-update-follow
Sep 25, 2026
Merged

TheCryptoDonkey merged 2 commits into
mainfrom
feat/relay-update-follow

Conversation

@TheCryptoDonkey

Copy link
Copy Markdown
Member

Heartwood firmware (heartwood-esp32 branch feat/unlock-relay-update, not yet merged) tells enrolled phones when the board's relays change. The update is a kind 24135 built exactly like a lock announcement, with the sealed t set to "relays" instead of "locked".

What changes

  • Adds the firmware's relay-update test vector (phone-unlock-v1-relays.json), with tests showing that an update opens, is judged NOT_LOCKED, never prompts (including when duplicate or stale), and that Cambium still follows its relays.
  • RelayGate / RelayJitter: a relay learned from a message waits a random 30 s to 10 min (SecureRandom) before Cambium connects to it, so a new relay can't tie the phone to the board by timing. A board's relays from pairing, enrolment or app start are trusted at once.
  • A genuine unlock never waits: deliver() trusts the lock prompt's own relays before syncing, so after a relay change the 24136 reaches the board's new relay rather than the old ones. The owner's tap already exposes that timing.
  • Forgetting a board drops relays that only it used from the ready set.

Works with current firmware: 0.5.0 already judged unknown t values as NOT_LOCKED.

Tested

  • ./gradlew :app:testDebugUnitTest: 240 tests pass.

Not tested

  • UnlockCoordinator has no JVM tests (no Robolectric in this project), so its wiring is covered only at the RelayGate level and by reading the code.
  • Not yet run on a phone against a board sending relay updates. That's in the Heartwood hardware checklist, section 27.

Heartwood now sends a kind 24135 relay-update (identical construction to a
lock announcement, only the sealed t differs) when its relays change outside
a restart. judge already refuses to prompt for it and withRelaysFrom already
follows the relays; add the firmware's own vector and tests proving both
hold, plus the duplicate/stale cases.

Add RelayGate/RelayJitter so a relay Cambium has never spoken to before is
not connected the instant a message teaches it about it -- a uniform 30
second to 10 minute jitter, drawn from SecureRandom by default and
injectable for tests, so that relay cannot correlate its first connection to
the moment the board's broadcast changed. A relay already known from
pairing, enrolment or a prior session is unaffected. deliver's relay choice
and sync's own return are untouched by the jitter, so answering a genuine
lock prompt is never held up by it.
deliver() called sync() and then published to match.context.relays, but
sync() now only connects relayGate-ready relays, and RelayWatch.publish
falls back to whatever is already connected. If the board had moved to a
new relay less than a jitter ago and then restarted locked on that same
relay, a tap would send the 24136 to the board's stale relays -- exactly
the case relay-update exists to avoid -- until the jitter elapsed (up to
10 minutes).

deliver() now trusts a genuine lock prompt's own relays outright before
calling sync(): the owner's tap already exposes the timing, so there is
nothing left for the jitter to protect there. Guarded on t == "locked"
even though judge() already guarantees a relay-update can never reach this
method. sync() blocks on RelayWatch.locks's own connection wait, so by the
time deliver() reaches publish() the newly trusted relay is normally
already connected; publish()'s fallback to whatever succeeded is left as
the fastest remaining option for a relay that failed to connect in that
window, rather than blocking every delivery on a relay that may not answer
at all.

Also prune a forgotten board's relays from RelayGate's ready set, unless
another remaining board still needs one of them.
@TheCryptoDonkey
TheCryptoDonkey merged commit 0e14107 into main Sep 25, 2026
3 of 4 checks passed
@TheCryptoDonkey TheCryptoDonkey mentioned this pull request Sep 25, 2026
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