feat(chat): App vault lifecycle for sensitive conversations

Adds the App-level glue that makes sensitive mode operable:
- Owns ChatSensitiveVault (path <configDir>/chat_sensitive.vault); provisioning
  wires the service's local sensitive predicate to Settings::isChatSensitive and
  points the vault at its path (left LOCKED until the user enters its passphrase).
- chatSensitiveVaultState() (NotConfigured/Locked/Unlocked) + create / unlock /
  lock: unlock pushes the key to chat_db_ and pulls sensitive rows into the store
  (+ re-harvest); lock drops the key and reloads so sensitive threads vanish.
- markChatConversationSensitive / unmark: migrate the thread's existing rows
  to/from the vault key via upsert (so the whole thread hides when locked) and
  broadcast the on-chain "sensitive"/"unsensitive" signal via sendChatControl.
  (wallet lock already wipes the vault key via chat_db_.lock().)

Phase B, step 5a (App lifecycle). UI next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 17:47:57 -05:00
co-authored by Claude Opus 4.8
parent fe3d2aa935
commit 6e285bd4a4
2 changed files with 99 additions and 0 deletions
+18
View File
@@ -49,6 +49,7 @@
#include "wallet/wallet_capabilities.h"
#include "chat/chat_service.h"
#include "chat/chat_database.h"
#include "chat/chat_sensitive_vault.h"
#include "ui/sidebar.h"
#include "ui/windows/console_tab.h"
#include "imgui.h"
@@ -325,6 +326,21 @@ public:
// hides/restores this conversation. `label` is a plaintext hint (peer address/name) carried on-chain.
// Reuses the chat send coordinator + the 0-value kChatMinFeeDrgx z_sendmany path; no UI echo bubble.
void sendChatControl(const std::string& conversationId, const std::string& op, const std::string& label);
// --- Sensitive conversations (per-conversation local-key encryption) ---
enum class ChatVaultState { NotConfigured, Locked, Unlocked };
ChatVaultState chatSensitiveVaultState() const;
bool chatSensitiveVaultConfigured() const; // a vault file exists (regardless of lock state)
// Create the vault (first-time setup) and unlock it. Returns false if one already exists or on error.
bool createChatSensitiveVault(const std::string& passphrase);
// Provide the vault key to the chat DB so sensitive rows decrypt; also pulls them into the store.
bool unlockChatSensitiveVault(const std::string& passphrase);
void lockChatSensitiveVault(); // drop the key; sensitive threads vanish from the view
// Mark/unmark a conversation sensitive. Requires the vault UNLOCKED (migrates its rows to/from the
// vault key) and broadcasts the on-chain "sensitive"/"unsensitive" signal. `peerAddr` labels it.
bool markChatConversationSensitive(const std::string& cid, const std::string& peerAddr);
void unmarkChatConversationSensitive(const std::string& cid);
// Debug/sweep convenience: give the Chat tab a demo identity + a few sample conversations
// (in-memory only, not persisted) so the screenshot sweep captures the populated UI. No-op when
// the chat feature is off. Returns true when it had to fabricate the (random) demo identity
@@ -1024,6 +1040,8 @@ private:
// z_exportmnemonic / lite exportSeed → the same KDF), so it is portable across both variants.
chat::ChatService chat_service_;
chat::ChatDatabase chat_db_; // persistent backing (seed-derived encryption at rest)
chat::ChatSensitiveVault chat_sensitive_vault_; // local key vault for "sensitive" conversations
std::string chatSensitiveVaultPath() const; // <configDir>/chat_sensitive.vault
bool chat_identity_provisioned_ = false; // identity set on the service this session
bool chat_identity_fetch_in_flight_ = false; // a z_exportmnemonic worker job is pending
std::chrono::steady_clock::time_point chat_identity_retry_at_{}; // after a transient failure