feat(chat): message model, in-memory store, and receive service
Phase 1 Steps 4-6 (the receive pipeline, minus the App/sync wiring which lands next). - chat_message: the in-memory ChatMessage model (direction, kind, txid, cid, peer zaddr/pubkey, body, timestamp, payload_position). No libsodium. - chat_store: threads messages by conversation_id and dedups by (txid, payload_position) so re-scanning the chain never double-inserts. - chat_service: owns the long-lived chat identity keypair (move-disabled, wiped on destruction/clear) and the store. ingest() decrypts each Message (drops undecryptable ones silently — no plaintext/memo logging), passes a ContactRequest's plaintext through, and threads the result. No-op without an identity. Tests: a metadata batch decrypts into a threaded conversation; re-ingest dedups; a contact request carries through; a wrong identity decrypts nothing; no-identity ingest is a no-op. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
56
src/chat/chat_service.cpp
Normal file
56
src/chat/chat_service.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// DragonX Wallet - HushChat service (implementation).
|
||||
|
||||
#include "chat_service.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace dragonx::chat {
|
||||
|
||||
ChatService::~ChatService() {
|
||||
clearIdentity();
|
||||
}
|
||||
|
||||
void ChatService::setIdentity(const ChatKeyPair& keys) {
|
||||
identity_ = keys;
|
||||
has_identity_ = true;
|
||||
}
|
||||
|
||||
void ChatService::clearIdentity() {
|
||||
wipeChatKeyPair(identity_);
|
||||
has_identity_ = false;
|
||||
}
|
||||
|
||||
int ChatService::ingest(const std::vector<HushChatTransactionMetadata>& metadata,
|
||||
std::int64_t txTimestamp) {
|
||||
if (!has_identity_) return 0;
|
||||
|
||||
int added = 0;
|
||||
for (const auto& meta : metadata) {
|
||||
ChatMessage message;
|
||||
message.direction = ChatDirection::Incoming;
|
||||
message.txid = meta.txid;
|
||||
message.conversation_id = meta.conversation_id;
|
||||
message.peer_zaddr = meta.reply_zaddr;
|
||||
message.peer_public_key_hex = meta.sender_public_key_hex;
|
||||
message.timestamp = txTimestamp;
|
||||
message.payload_position = meta.payload_position;
|
||||
|
||||
if (meta.type == HushChatHeaderType::ContactRequest) {
|
||||
message.kind = ChatMessageKind::ContactRequest;
|
||||
message.body = meta.payload_memo; // plaintext request text
|
||||
} else {
|
||||
message.kind = ChatMessageKind::Message;
|
||||
std::string plaintext;
|
||||
const ChatCryptoStatus status = decryptIncoming(
|
||||
identity_, meta.sender_public_key_hex, meta.secretstream_header_hex,
|
||||
meta.payload_memo, plaintext);
|
||||
if (status != ChatCryptoStatus::Ok) continue; // drop undecryptable silently
|
||||
message.body = std::move(plaintext);
|
||||
}
|
||||
|
||||
if (store_.append(message)) ++added;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
} // namespace dragonx::chat
|
||||
Reference in New Issue
Block a user