Files
ObsidianDragon/src/chat/chat_protocol.h
DanS 4471f54842 feat(chat): stamp sender compose-time in message header (clock-clamped)
Carry the sender's compose time as an optional "ts" (Unix seconds) in the
plaintext header JSON that rides outside the AEAD, and prefer it as the
displayed message time so both ends show the same send time regardless of when
the tx confirms. Parse "ts" leniently. On ingest, clamp: reject a "ts"
implausibly in the future vs the receive/block time (1h skew tolerated) so a
wrong/ahead peer clock can't pin messages to the bottom of a thread; a past
compose time is fine (the note buffer may broadcast a queued message later, and
a confirmed tx's block time is always >= compose time). Tests cover the
round-trip and the future-clock clamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:59:46 -05:00

115 lines
3.5 KiB
C++

#pragma once
#include <cstddef>
#include <string>
#include <vector>
#ifndef DRAGONX_ENABLE_CHAT
#define DRAGONX_ENABLE_CHAT 0
#endif
namespace dragonx::chat {
enum class HushChatHeaderType {
Message,
ContactRequest
};
struct HushChatHeader {
int header_number = 0;
int version = 0;
std::string reply_zaddr;
std::string conversation_id;
HushChatHeaderType type = HushChatHeaderType::Message;
std::string secretstream_header_hex;
std::string public_key_hex;
// Optional sender-stamped compose time (header "ts", Unix seconds). 0 = absent (older sender) → the
// receiver falls back to the tx/receive time. Lets both sides show the SAME (send) time.
std::int64_t sent_at = 0;
};
struct HushChatHeaderParseResult {
bool ok = false;
HushChatHeader header;
std::string error;
};
struct HushChatMemoOutput {
std::size_t position = 0;
std::string memo;
};
struct HushChatMemoPair {
HushChatHeader header;
std::size_t header_position = 0;
std::size_t payload_position = 0;
std::string payload_memo;
};
enum class HushChatMemoGroupingIssue {
InvalidHeader,
MissingPayload,
DuplicateHeader,
OversizedMemo
};
struct HushChatMemoGroupingIssueInfo {
HushChatMemoGroupingIssue issue = HushChatMemoGroupingIssue::InvalidHeader;
std::size_t position = 0;
std::string detail;
};
struct HushChatMemoGroupingResult {
std::vector<HushChatMemoPair> pairs;
std::vector<HushChatMemoGroupingIssueInfo> issues;
std::size_t ignored_memo_count = 0;
};
struct HushChatTransactionInput {
std::string txid;
std::vector<HushChatMemoOutput> outputs;
};
struct HushChatTransactionMetadata {
std::string txid;
HushChatHeaderType type = HushChatHeaderType::Message;
std::string conversation_id;
std::string reply_zaddr;
std::size_t header_position = 0;
std::size_t payload_position = 0;
std::size_t payload_size = 0;
// Decrypt inputs carried through from the paired header + payload memos so the chat
// service can actually decrypt (a Message) or read the request (a ContactRequest).
std::string sender_public_key_hex; // header "p": peer crypto_kx public key (hex)
std::string secretstream_header_hex; // header "e": secretstream header (hex; empty for ContactRequest)
std::string payload_memo; // ciphertext hex (Message) or plaintext request text (ContactRequest)
std::int64_t sent_at = 0; // header "ts": sender compose time (Unix s); 0 = absent → use tx time
};
struct HushChatTransactionExtractionResult {
bool feature_enabled = false;
std::vector<HushChatTransactionMetadata> metadata;
std::vector<HushChatMemoGroupingIssueInfo> issues;
std::size_t ignored_memo_count = 0;
};
constexpr int kHushChatSupportedVersion = 0;
constexpr std::size_t kHushChatMemoByteLimit = 512;
constexpr std::size_t kHushChatPublicKeyHexLength = 64;
constexpr std::size_t kHushChatSecretstreamHeaderHexLength = 48;
constexpr bool hushChatFeatureEnabledAtBuild()
{
return DRAGONX_ENABLE_CHAT != 0;
}
HushChatHeaderParseResult parseHushChatHeaderMemo(const std::string& memo);
HushChatMemoGroupingResult groupHushChatMemoOutputs(const std::vector<HushChatMemoOutput>& outputs);
HushChatTransactionExtractionResult extractHushChatTransactionMetadata(
const HushChatTransactionInput& transaction,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
const char* hushChatHeaderTypeName(HushChatHeaderType type);
const char* hushChatMemoGroupingIssueName(HushChatMemoGroupingIssue issue);
} // namespace dragonx::chat