feat(security): surface XEdDSA packet signing in node & messaging UI#5976
Merged
Conversation
Firmware 2.8 signs unencrypted broadcasts with XEdDSA and verifies them before the app sees them. This affirms that good state to users — a node and its broadcasts are cryptographically signed and verified — without ever warning on legitimately-unsigned traffic (DMs, large broadcasts, legacy nodes). Read-only: the radio does all crypto and hands the client three flags. - Per-message: surface MeshPacket.xeddsa_signed through DataPacket → Message (mirrors the existing transportMechanism plumbing; no DB migration — DataPacket is a serialized blob). A green shield-check shows on verified broadcast bubbles and a "Signed · verified" row in the message info sheet. Never shown on DMs (the radio doesn't set the flag there) — lock stays for DM encryption, shield for signing. - Node-level: surface NodeInfo.has_xeddsa_signed onto the Node domain and persist it (NodeEntity column + DB 43→44 auto-migration). A "Signed node" shield-check row sits in the security section, ordered most-trusted-first (manual-verify → signed → has-key). "Verified automatically" distinguishes it from user-asserted manual verification. Adds MeshtasticIcons.ShieldCheck (Material Symbols verified_user) and CST preview screenshots for both surfaces (light/dark). Deferred (track design#113 open questions): the "unverifiable signature" state, per-data-field telemetry badges, and a compose-time indicator. Closes #5966 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
📄 Docs staleness check — advisoryThis PR modifies user-facing UI source files but does not update any page under
Changed source files: What to check:
New page checklist (if adding a new doc page):
If this PR does not require a doc update (e.g., internal refactor, bug fix, test change), add the
|
5 tasks
This was referenced Jun 27, 2026
5 tasks
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.
Closes #5966 · Tracking design#113 · Firmware firmware#10478 (2.8)
Why
Firmware 2.8 signs unencrypted broadcasts with XEdDSA over the node's identity key and verifies them on the radio — invalid or stripped-signature broadcasts are dropped before the app sees them. So there's no "tampered" state to render; our job is purely to affirm the good state: show users when a node and its broadcasts are signed and verified. We never warn on unsigned traffic, because DMs, oversized broadcasts, and legacy nodes are all legitimately unsigned.
This is read-only — the radio does all crypto and hands the client three flags (
MeshPacket.xeddsa_signed,NodeInfo.has_xeddsa_signed,Data.xeddsa_signature).🌟 What changed
Messaging view — a subtle green shield-check on verified broadcast bubbles (next to the transport icon), plus a "Signed · verified / Verified with the sender's key." row in the message info sheet. The flag is only ever set on broadcasts, so it never appears on DMs — the encryption lock stays for DMs, the shield means authentic. Per-message, since a node can send a signed small + unsigned large message in one channel.
Node details — a "Signed node / Verified automatically" shield-check row in the security section, ordered most-trusted-first (manual-verify → signed → has-key). "Automatically" distinguishes radio-observed signing from user-asserted manual key verification.
Icon — new
MeshtasticIcons.ShieldCheck(Material Symbolsverified_user, authored in the repo's 960-viewport house style). Lock is untouched.🛠️ How it's wired
MeshPacket.xeddsa_signed→DataPacket→Message, mirroring the existingtransportMechanismplumbing. No DB migration —DataPacketis a serialized blob.NodeInfo.has_xeddsa_signed→Node.signsPackets, persisted via a newNodeEntitycolumn and a DB 43→44 auto-migration (44.jsoncommitted). Threaded through all three node→entity mappers.Scope (deferred — track the open questions in design#113)
Out of v1: the "unverifiable signature" state (spec sanctions dropping it — "use only the
xeddsa_signedbool"), per-data-field telemetry badges (open Q3), and a compose-time "will be signed" indicator (open Q5). Each is an isolated add-on.🧪 Testing performed
./gradlew spotlessCheck detekt test allTests— all green.ScreenshotMessageItemSigned,ScreenshotNodeDetailsSectionSigned, light + dark); all 309 CST references pass. Reference PNGs committed underscreenshot-tests/.🤖 Generated with Claude Code