You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Show when a node and its broadcasts are cryptographically signed and verified. The radio does all the crypto and drops bad signatures before the client ever sees them β so there's no error state to render. Clients only ever affirm the good state: π‘οΈ shield = authentic (signed broadcast), π lock = private (encrypted DM). Two separate ideas, two separate marks. Never warn on unsigned traffic.
Top bubble (14:02) verified β green π‘οΈ; bottom (14:03) unsigned, no shield.
"Signed node Β· Verified automatically" in the security section, paired with the manually-verified cell.
Shield beside the key/lock status icon in the row β visible without opening details.
Trust states at a glance
State
Detect
Mark
Label
β Verified signed
xeddsa_signed == true
π‘οΈ shield-with-check (affirmative)
"Signed"
βͺ Unsigned
no verified signature
β (render nothing)
β
π Encrypted DM
PKI (separate mechanism)
existing lock
"Encrypted"
Goal
Show users when a node and its broadcast packets are cryptographically signed and verified (XEdDSA over the node's identity key) β primarily in node details and the messaging view.
Guiding principle
Affirm the good state; stay silent otherwise. The radio drops invalid and stripped-signature broadcasts from a known signer before the client sees them, so there is no "tampered/invalid" state to render. A small broadcast you can see from a signing node is verified by construction. We only ever say "this is signed and verified" β we never warn, flag, or downgrade unsigned traffic.
What the radio gives the client
Read-only flags β no client-side crypto.
Field
Type
Meaning
MeshPacket.xeddsa_signed
bool (22)
Verified signature for this received packet. Primary "verified" signal.
NodeInfo.has_xeddsa_signed
bool (14)
This node signs its packets (β₯1 verified). Node-level signal.
Data.xeddsa_signature
bytes[64] (10)
The raw signature. Not needed for v1 (see decisions).
What is signed: unencrypted broadcasts that fit a 64-byte signature β NodeInfo, position, telemetry, channel text. The signature covers from | id | portnum | payload. Not signed:DMs (those are PKI-encrypted β a separate mechanism) and oversized broadcasts.
Two distinct affordances β shield vs lock
Signing β encryption. They answer different questions ("who really sent this" vs "who can read this") and apply to disjoint traffic, so they stay separate:
π‘οΈ shield = authentic (a verified, signed broadcast)
π lock = private (a PKI-encrypted DM)
Use a shield-with-check mark for signing β each platform picks its nearest native equivalent (Android used the Material Symbols verified_user glyph). Leave the existing DM lock untouched.
Caution
Never reuse the lock for signing. Never use red / error styling anywhere in this feature β there is no error state.
Node details & node list
Signed node: when has_xeddsa_signed, show a "Signed node" π‘οΈ shield. Because NodeInfo is itself a signed broadcast, the node's name/identity is verified by extension.
Node details: slot a "Signed node" row into the existing key/security section, ordered most-trusted-first: manual-verified β signed β has-key β no-key. Keep it a distinct cell from manual key verification (Android pairs it with the manually-verified cell in one row; stack or pair as suits the platform) β the two are earned differently. Signing is automatic trust (observed from the radio); manual verification is user-asserted. Label it accordingly (e.g. "Verified automatically").
Node list (rows): also show the shield in each node row's security cluster, right beside the key/lock status icon, so signing is visible at a glance without opening details. Apply to every list density (compact + full). Group it with the key icon as one affordance rather than a scattered separate badge.
Phrase it as observed ("signs its packets" / "Signed node"), never as a configurable setting ("signing enabled") β there is no such advertised flag.
Tap to explain: make the shield tappable (mirroring the key-status icon), opening a plain-language explanation. Suggested copy β title "Signed node", body "This node signs its broadcasts with XEdDSA. Broadcasts you see from it are verified by the radio using its identity key."
Copy: row label "Signed node", value "Verified automatically"; per-message reveal "Signed Β· verified"; message detail "Signed & verified β verified with their key." Keep wording consistent across clients.
Note
The shield sits on node-tinted surfaces (bubbles, rows) β make sure it stays legible against them (each platform's own way).
Messaging view
Channel/broadcast messages: a green π‘οΈ on xeddsa_signed bubbles; reveal "Signed Β· verified" on tap. Per-message β a node may send a signed small message and an unsigned oversized one in the same channel.
Direct messages: keep the existing π; do not show the signing shield. Lock = private (DM); shield = authentic (signed broadcast).
Tip
The firmware only sets xeddsa_signed on broadcasts and never on DMs, so gating the shield on the boolean alone is sufficient β clients don't need to detect broadcast-vs-DM in the UI. It cannot false-positive on a DM.
Message detail/info: plain-language, affirmative state β e.g. "Signed & verified β sent by Alice (!a1b2c3d4), verified with their key." For unsigned, use neutral copy ("not signed β older firmware or too large") and never anything accusatory.
Resolved decisions (v1)
The original open questions are settled for v1:
Signing vs encryption language β two separate affordances (shield + lock), not a merged "security" concept.
Node trust state β two distinct cells (manual key-verify and signature-verify), most-trusted-first and laid out per platform (Android pairs them in one row); don't conflate them.
Per-data-field signed badges (badging individual position/telemetry/sensor rows) β deferred. The node-level "Signed node" row already conveys trust; the per-field visual cost wasn't justified for v1. Revisit on demand.
Treatment for "unsigned" β strictly silent. No icon, no label, no neutral chip.
Compose-time "will be signed" indicator β out for v1.
"Signed but unverifiable" state (signature present, xeddsa_signed == false because the radio lacked the key) β dropped for v1. It's rare and transient β it resolves once the key is known. Render trust from the verified boolean only; clients don't need to read Data.xeddsa_signature yet. Revisit only if it proves non-transient in the field.
Don'ts
Don't reuse the lock icon for signing.
Don't use red / error styling anywhere β nothing here is an error.
Don't flag unsigned DMs / large / legacy traffic as insecure.
Don't phrase node UI as "signing enabled"; phrase it as observed ("signs its packets").
π‘οΈ XEdDSA packet signing β client UI spec
Important
Show when a node and its broadcasts are cryptographically signed and verified. The radio does all the crypto and drops bad signatures before the client ever sees them β so there's no error state to render. Clients only ever affirm the good state: π‘οΈ shield = authentic (signed broadcast), π lock = private (encrypted DM). Two separate ideas, two separate marks. Never warn on unsigned traffic.
Tracking: design#113 Β· Firmware: firmware#10478 (2.8) Β· Reference build: Meshtastic-Android#5976 (merged) + #5980 + #5985
What it looks like (Android reference)
Trust states at a glance
xeddsa_signed == trueGoal
Show users when a node and its broadcast packets are cryptographically signed and verified (XEdDSA over the node's identity key) β primarily in node details and the messaging view.
Guiding principle
Affirm the good state; stay silent otherwise. The radio drops invalid and stripped-signature broadcasts from a known signer before the client sees them, so there is no "tampered/invalid" state to render. A small broadcast you can see from a signing node is verified by construction. We only ever say "this is signed and verified" β we never warn, flag, or downgrade unsigned traffic.
What the radio gives the client
Read-only flags β no client-side crypto.
MeshPacket.xeddsa_signedNodeInfo.has_xeddsa_signedData.xeddsa_signatureWhat is signed: unencrypted broadcasts that fit a 64-byte signature β NodeInfo, position, telemetry, channel text. The signature covers
from | id | portnum | payload.Not signed: DMs (those are PKI-encrypted β a separate mechanism) and oversized broadcasts.
Two distinct affordances β shield vs lock
Signing β encryption. They answer different questions ("who really sent this" vs "who can read this") and apply to disjoint traffic, so they stay separate:
Use a shield-with-check mark for signing β each platform picks its nearest native equivalent (Android used the Material Symbols
verified_userglyph). Leave the existing DM lock untouched.Caution
Never reuse the lock for signing. Never use red / error styling anywhere in this feature β there is no error state.
Node details & node list
has_xeddsa_signed, show a "Signed node" π‘οΈ shield. Because NodeInfo is itself a signed broadcast, the node's name/identity is verified by extension.Note
The shield sits on node-tinted surfaces (bubbles, rows) β make sure it stays legible against them (each platform's own way).
Messaging view
xeddsa_signedbubbles; reveal "Signed Β· verified" on tap. Per-message β a node may send a signed small message and an unsigned oversized one in the same channel.Tip
The firmware only sets
xeddsa_signedon broadcasts and never on DMs, so gating the shield on the boolean alone is sufficient β clients don't need to detect broadcast-vs-DM in the UI. It cannot false-positive on a DM.Resolved decisions (v1)
The original open questions are settled for v1:
xeddsa_signed == falsebecause the radio lacked the key) β dropped for v1. It's rare and transient β it resolves once the key is known. Render trust from the verified boolean only; clients don't need to readData.xeddsa_signatureyet. Revisit only if it proves non-transient in the field.Don'ts
Per-client status