From f273d3f875856c15355ea304a9c190c6cdabc3b9 Mon Sep 17 00:00:00 2001 From: Jason P Date: Thu, 2 Jul 2026 18:26:54 -0500 Subject: [PATCH] XEdDSA packet signing UI (BaseUI) (#10841) * XEdDSA packet signing UI (BaseUI) * Move Pixels around for TFT --- src/MessageStore.cpp | 7 +++++++ src/MessageStore.h | 4 +++- src/graphics/draw/MessageRenderer.cpp | 15 +++++++++++++-- src/graphics/draw/UIRenderer.cpp | 24 +++++++++++++++++++++++- src/graphics/images.h | 4 ++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/MessageStore.cpp b/src/MessageStore.cpp index 705e37170..3360857b9 100644 --- a/src/MessageStore.cpp +++ b/src/MessageStore.cpp @@ -183,6 +183,10 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa sm.type = isDM ? MessageType::DM_TO_US : MessageType::BROADCAST; sm.ackStatus = (packet.from == 0) ? AckStatus::NONE : AckStatus::ACKED; +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) + sm.xeddsaSigned = packet.xeddsa_signed; +#endif + addLiveMessage(sm); #if ENABLE_MESSAGE_PERSISTENCE @@ -230,6 +234,7 @@ struct __attribute__((packed)) StoredMessageRecord { uint8_t isBootRelative; uint8_t ackStatus; // static_cast(AckStatus) uint8_t type; // static_cast(MessageType) + uint8_t xeddsaSigned; // 1 if packet carried a verified XEdDSA signature uint16_t textLength; // message length char text[MAX_MESSAGE_SIZE]; // store actual text here }; @@ -245,6 +250,7 @@ static inline void writeMessageRecord(SafeFile &f, const StoredMessage &m) rec.isBootRelative = m.isBootRelative; rec.ackStatus = static_cast(m.ackStatus); rec.type = static_cast(m.type); + rec.xeddsaSigned = m.xeddsaSigned ? 1 : 0; rec.textLength = m.textLength; // Copy the actual text into the record from RAM pool @@ -269,6 +275,7 @@ static inline bool readMessageRecord(File &f, StoredMessage &m) m.isBootRelative = rec.isBootRelative; m.ackStatus = static_cast(rec.ackStatus); m.type = static_cast(rec.type); + m.xeddsaSigned = rec.xeddsaSigned != 0; m.textLength = rec.textLength; // 💡 Re-store text into pool and update offset diff --git a/src/MessageStore.h b/src/MessageStore.h index 6057be202..89c9f83bf 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -72,10 +72,12 @@ struct StoredMessage { uint16_t textOffset; // Offset into global text pool (valid only after loadFromFlash()) uint16_t textLength; // Length of text in bytes + bool xeddsaSigned; // true if packet carried a verified XEdDSA signature + // Default constructor initializes all fields safely StoredMessage() : timestamp(0), sender(0), channelIndex(0), dest(0xffffffff), type(MessageType::BROADCAST), isBootRelative(false), - ackStatus(AckStatus::NONE), textOffset(0), textLength(0) + ackStatus(AckStatus::NONE), textOffset(0), textLength(0), xeddsaSigned(false) { } }; diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index a42ef1e08..bc5e027fb 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -615,12 +615,22 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Shrink Sender name if needed int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) - - display->getStringWidth(chanType) - graphics::UIRenderer::measureStringWithEmotes(display, " @..."); + display->getStringWidth(chanType) - graphics::UIRenderer::measureStringWithEmotes(display, " *@..."); if (availWidth < 0) availWidth = 0; char truncatedSender[64]; graphics::UIRenderer::truncateStringWithEmotes(display, senderName, truncatedSender, sizeof(truncatedSender), availWidth); + // Determine signed-message prefix before building the header line, since it needs to go + // at the front of headerStr rather than appended after (strncat only appends at the end). + const char *signPrefix = ""; +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) + bool is_xeddsa_signed = m.xeddsaSigned; + if (is_xeddsa_signed) { + signPrefix = "*"; + } +#endif + // Final header line char headerStr[128]; if (mine) { @@ -634,7 +644,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 snprintf(headerStr, sizeof(headerStr), "%s", timeBuf); } } else { - snprintf(headerStr, sizeof(headerStr), chanType[0] ? "%s @%s %s" : "%s @%s", timeBuf, truncatedSender, chanType); + snprintf(headerStr, sizeof(headerStr), chanType[0] ? "%s %s@%s %s" : "%s %s@%s", timeBuf, signPrefix, truncatedSender, + chanType); } // Push header line diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 352023c83..c14b53839 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -788,12 +788,34 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat // Print node's long name (e.g. "Backpack Node") if (username) { + int username_buffer = 0; +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) + if (nodeInfoLiteHasXeddsaSigned(node)) { + if (currentResolution == ScreenResolution::High) { + graphics::NodeListRenderer::drawScaledXBitmap16x16(x + 2, getTextPositions(display)[line] + 1, + xeddsa_shield_width, xeddsa_shield_height, xeddsa_shield, + display); + username_buffer = (xeddsa_shield_width * 2) + 4; + } else { + display->drawXbm(x, getTextPositions(display)[line] + 3, xeddsa_shield_width, xeddsa_shield_height, + xeddsa_shield); + username_buffer = xeddsa_shield_width + 2; + } + } +#endif #if GRAPHICS_TFT_COLORING_ENABLED const int usernameWidth = UIRenderer::measureStringWithEmotes(display, username); +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) + if (nodeInfoLiteHasXeddsaSigned(node)) { + setAndRegisterTFTColorRole(TFTColorRole::FavoriteNodeBGHighlight, TFTPalette::Yellow, TFTPalette::Black, + x + usernameWidth, getTextPositions(display)[line], username_buffer, FONT_HEIGHT_SMALL); + } +#endif setAndRegisterTFTColorRole(TFTColorRole::FavoriteNodeBGHighlight, TFTPalette::Yellow, TFTPalette::Black, x, getTextPositions(display)[line], usernameWidth, FONT_HEIGHT_SMALL); #endif - UIRenderer::drawStringWithEmotes(display, x, getTextPositions(display)[line++], username, FONT_HEIGHT_SMALL, 1, false); + UIRenderer::drawStringWithEmotes(display, x + username_buffer, getTextPositions(display)[line++], username, + FONT_HEIGHT_SMALL, 1, false); } #if !MESHTASTIC_EXCLUDE_STATUS && !MESHTASTIC_EXCLUDE_STATUSDB diff --git a/src/graphics/images.h b/src/graphics/images.h index 6e7ebb27e..95c0861fd 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -291,6 +291,10 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000, 0b00100100, 0b01000010, 0b01000010, 0b11111111}; +#define xeddsa_shield_width 8 +#define xeddsa_shield_height 8 +const uint8_t xeddsa_shield[] PROGMEM = {0x7E, 0x8F, 0x8F, 0x8F, 0xF1, 0xF1, 0x72, 0x3C}; + #define chirpy_width 38 #define chirpy_height 50 const uint8_t chirpy[] = {