#pragma once // DragonX Wallet - HushChat persistent message store (Phase 2). // // Sqlite-backed, encrypted at rest with a SEED-DERIVED key (no wallet passphrase). Every record — // message bodies, peer z-addresses, threading (conversation id), and timestamps — is AEAD-encrypted // under a key derived from the wallet's own seed secret (the same secret used for the chat // identity), and even the per-message dedup key is a KEYED hash of the txid — so the database // reveals nothing about your conversations to disk-level access without the seed. Rows are // partitioned by a seed-derived wallet tag so one file can hold several wallets, each readable only // with its own seed. Not thread-safe — drive from the main thread. Mirrors the lifecycle of // data::TransactionHistoryCache. #include "chat_message.h" #include #include #include #include struct sqlite3; namespace dragonx::chat { class ChatDatabase { public: ChatDatabase(); explicit ChatDatabase(std::string databasePath); ~ChatDatabase(); ChatDatabase(const ChatDatabase&) = delete; ChatDatabase& operator=(const ChatDatabase&) = delete; static std::string defaultDatabasePath(); // Derive the storage key + wallet tag from the wallet's seed secret and open the DB. The caller // still owns and must wipe `secret`. Returns false on sodium/db failure (DB then stays locked). bool unlockWithSecret(const std::string& secret); void lock(); // wipe the key material (DB handle stays open); load()/append() then no-op bool hasKey() const { return key_ready_; } // Persist one message (INSERT OR IGNORE, deduped by a keyed hash of txid+payload_position). // Returns true if newly inserted; false on duplicate or while locked. bool append(const ChatMessage& message); // Decrypt and return every stored message for the unlocked wallet, in insertion order. Empty // while locked or if none. Rows that fail to decrypt/parse are skipped. std::vector load(); void clearWallet(); // delete the unlocked wallet's rows private: bool ensureOpen(); bool exec(const char* sql); bool createSchema(); std::string dedupHash(const std::string& txid, std::size_t position) const; std::string serialize(const ChatMessage& message) const; bool deserialize(const std::string& json, ChatMessage& out) const; bool encrypt(const std::string& plain, std::vector& nonce, std::vector& cipher) const; bool decrypt(const std::vector& nonce, const std::vector& cipher, std::string& plain) const; void close(); sqlite3* db_ = nullptr; std::string database_path_; std::array key_{}; // AEAD storage key (seed-derived) std::string wallet_tag_; // seed-derived row partition (a keyed hash, hex) bool key_ready_ = false; }; } // namespace dragonx::chat