#pragma once #include #include #include #include #include #ifndef DRAGONX_ENABLE_CHAT #define DRAGONX_ENABLE_CHAT 0 #endif namespace dragonx::chat { enum class HushChatHeaderType { Message, ContactRequest, Control // a self-addressed control signal (delete / restore / mark sensitive); carries an `op` }; struct HushChatHeader { int header_number = 0; int version = 0; std::string reply_zaddr; std::string conversation_id; HushChatHeaderType type = HushChatHeaderType::Message; std::string op; // Control only ("del" / "res" / "sensitive" / "unsensitive" / future ops); empty otherwise std::string secretstream_header_hex; std::string public_key_hex; // Message only: how many payload outputs this message's ciphertext is split across (header "n"). // 1 = the classic single-payload message (field omitted on the wire). >1 means the payloads are // index-prefixed ciphertext chunks reassembled in order. For tier 2 all N chunks ride ONE tx; for // tier 3 (message_id set) "n" is the TOTAL chunk count spread across several sibling transactions. int parts = 1; // Message only, larger-messages tier 3: when a message needs more chunks than one tx can carry, it // is split across several transactions that all share this random id (header "mid"); each tx's // payloads carry their GLOBAL chunk index (0..parts-1). Empty = a single-tx message (tier 1/2). std::string message_id; // 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; // Larger-messages tier 3: a single transaction that carries only PART of a bigger message (the rest // ride sibling txs sharing message_id). payload_memo is then empty; partial_chunks holds this tx's // (global index, ciphertext-hex chunk) pairs and total_parts is the whole message's chunk count. std::string message_id; int total_parts = 0; std::vector> partial_chunks; }; 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 pairs; std::vector issues; std::size_t ignored_memo_count = 0; }; struct HushChatTransactionInput { std::string txid; std::vector outputs; }; struct HushChatTransactionMetadata { std::string txid; HushChatHeaderType type = HushChatHeaderType::Message; std::string op; // Control only: the control op ("del"/"res"/...); empty otherwise 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 // Larger-messages tier 3 (a multi-transaction Message): message_id ties this tx's slice to its // siblings, total_parts is the whole message's chunk count, and partial_chunks are this tx's // (global index, ciphertext-hex) chunks. Empty message_id = an ordinary single-tx message (payload // in payload_memo). The chat service accumulates partials by message_id and reassembles when whole. std::string message_id; int total_parts = 0; std::vector> partial_chunks; }; struct HushChatTransactionExtractionResult { bool feature_enabled = false; std::vector metadata; std::vector issues; std::size_t ignored_memo_count = 0; }; constexpr int kHushChatSupportedVersion = 0; constexpr std::size_t kHushChatMemoByteLimit = 512; // Larger-messages tier 2: the most payload outputs a single transaction may carry (one tx). Keeps the // chat tx well under the shielded max_tx_size after Sietch decoy padding + proving cost. A message // needing more chunks than this spills into sibling transactions (tier 3), up to kHushChatMaxMessageChunks. constexpr int kHushChatMaxMessageParts = 16; // Larger-messages tier 3: the most chunks a single message may span IN TOTAL, across all its sibling // transactions (ceil(chunks / kHushChatMaxMessageParts) txs, each a separate 0-value chat send + fee). // Bounds the on-chain cost / broadcast time of one message; beyond this compose returns TooLong. The // 2-hex global part index (below) tops out at 256, so this must stay ≤ 256. constexpr int kHushChatMaxMessageChunks = 64; // Each multi-part payload chunk is prefixed with a 2-hex-char (1-byte) part index so the receiver can // reorder chunks after the daemon shuffles a tx's outputs. 2 hex covers indices 0..255 (>> the cap). constexpr std::size_t kHushChatPartIndexHexLen = 2; 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& outputs); HushChatTransactionExtractionResult extractHushChatTransactionMetadata( const HushChatTransactionInput& transaction, bool featureEnabled = hushChatFeatureEnabledAtBuild()); const char* hushChatHeaderTypeName(HushChatHeaderType type); const char* hushChatMemoGroupingIssueName(HushChatMemoGroupingIssue issue); } // namespace dragonx::chat