diff --git a/src/app_network.cpp b/src/app_network.cpp index 35ca3b0..a0742c5 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -34,6 +34,7 @@ #include "rpc/rpc_worker.h" #include "rpc/connection.h" #include "chat/chat_identity.h" // deriveChatIdentityFromSecret for HushChat identity provisioning +#include "ui/windows/chat_tab.h" // ui::ResetChatTab — wipe chat UI plaintext on a wallet switch #include // sodium_memzero for wiping the fetched mnemonic #include #include "config/settings.h" @@ -2672,6 +2673,7 @@ void App::resetChatSession() // Drop identity + decrypted plaintext in RAM, lock the seed-encrypted DB, re-arm provisioning. chat_service_.clearIdentity(); chat_service_.store().clear(); + ui::ResetChatTab(); // wipe the chat UI's typed plaintext + selection so it can't leak into the next wallet chat_db_.lock(); chat_identity_provisioned_ = false; chat_identity_fetch_in_flight_ = false; diff --git a/src/ui/windows/chat_tab.cpp b/src/ui/windows/chat_tab.cpp index 60da1ff..da8dca4 100644 --- a/src/ui/windows/chat_tab.cpp +++ b/src/ui/windows/chat_tab.cpp @@ -2,8 +2,8 @@ // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // -// chat_tab.cpp — read-only HushChat view (Phase 3): a conversation list + the -// selected thread, both read from the App-owned ChatService store. +// chat_tab.cpp — HushChat view: a conversation list + the selected thread (both read +// from the App-owned ChatService store), with a composer and a new-conversation flow. #include "chat_tab.h" #include "../../app.h" @@ -15,6 +15,8 @@ #include "../layout.h" // Layout::dpiScale() #include "imgui.h" +#include // sodium_memzero — wipe typed plaintext on a wallet switch + #include #include #include @@ -32,6 +34,7 @@ std::string s_scroll_to_cid; // when set, scroll the thread to the bottom next // Composer + new-conversation UI state. char s_compose[512] = ""; +std::string s_compose_cid; // the conversation s_compose is a draft for; draft is wiped when it changes bool s_show_new_convo = false; char s_new_zaddr[128] = ""; char s_new_msg[256] = ""; @@ -132,6 +135,12 @@ void RenderChatTab(App* app) s_selected_cid = convs.front().cid; s_scroll_to_cid = s_selected_cid; } + // The composer holds a single draft — wipe it when the active conversation changes so text typed + // for one contact can't be sent to another (B5). + if (s_selected_cid != s_compose_cid) { + sodium_memzero(s_compose, sizeof(s_compose)); + s_compose_cid = s_selected_cid; + } const ImVec2 avail = ImGui::GetContentRegionAvail(); const float listW = std::clamp(avail.x * 0.32f, 220.0f, 360.0f); @@ -318,5 +327,18 @@ void RenderChatTab(App* app) } } +void ResetChatTab() +{ + // Securely wipe typed plaintext (a private message / recipient z-addr) so it can't resurface under + // the next wallet after a switch/lock, and drop the selection ids. + sodium_memzero(s_compose, sizeof(s_compose)); + sodium_memzero(s_new_zaddr, sizeof(s_new_zaddr)); + sodium_memzero(s_new_msg, sizeof(s_new_msg)); + s_selected_cid.clear(); + s_scroll_to_cid.clear(); + s_compose_cid.clear(); + s_show_new_convo = false; +} + } // namespace ui } // namespace dragonx diff --git a/src/ui/windows/chat_tab.h b/src/ui/windows/chat_tab.h index c34dea4..687f050 100644 --- a/src/ui/windows/chat_tab.h +++ b/src/ui/windows/chat_tab.h @@ -11,17 +11,26 @@ class App; namespace ui { /** - * @brief Render the Chat tab (read-only HushChat conversation view — Phase 3). + * @brief Render the Chat tab (HushChat conversation view). * - * A two-pane view: a conversation list on the left and the selected thread on - * the right, both read from the App-owned ChatService store (App::chatService()). - * Peer z-addresses are resolved to contact names via the address book when known. - * Read-only for now — composing/sending arrives in a later phase. The tab is only - * reachable when built with DRAGONX_ENABLE_CHAT (gated via WalletUiSurface::Chat). + * A two-pane view: a conversation list on the left and the selected thread on the + * right, both read from the App-owned ChatService store (App::chatService()). Peer + * z-addresses are resolved to contact names via the address book when known. + * Composing/sending and starting new conversations are wired (App::sendChatMessage / + * startChatConversation). Reachable when built with DRAGONX_ENABLE_CHAT + * (gated via WalletUiSurface::Chat). * * @param app Pointer to the app instance. */ void RenderChatTab(App* app); +/** + * @brief Securely wipe the Chat tab's UI-local state (composer / new-conversation + * plaintext buffers + the selected-conversation ids). Called by + * App::resetChatSession() on a wallet switch/lock so one wallet's typed + * plaintext can't resurface (or linger unwiped in RAM) under the next wallet. + */ +void ResetChatTab(); + } // namespace ui } // namespace dragonx