feat(chat): tier-3 larger messages — protocol + split + cross-tx reassembly core
Foundation for messages too large for a single transaction (tier 2's 16-output ceiling, ~4000 chars). A long message is now split across SEVERAL transactions that share a random message id; each carries a slice of the one ciphertext with GLOBAL chunk indices, and the receiver reassembles across the siblings. Protocol: the Message header gains an optional "mid" (message id); "n" is reused as the TOTAL chunk count and its bound raised to kHushChatMaxMessageChunks (64 ⇒ up to ~16 KB / 4 txs, tunable, ≤256 for the 2-hex index). The grouper, when a tx's header carries "mid", emits a PARTIAL (its global-indexed chunks + message id + total) instead of reassembling in-place; HushChatTransactionMetadata carries those partials through. Crypto is untouched — one secretstream session for the whole message, chunking is transport-only. Compose: splitOutgoingMessage() returns one OutgoingChatMemos per transaction (one element = the classic tier-1/2 single tx, byte-identical; several = tier 3). buildOutgoingMessage (single-tx) is unchanged. Old clients bound "n" to 16 so a tier-3 header fails to parse and its memos are dropped — graceful degradation. Unit-tested (testHushChatLargeMessageTier3): a ~9000-char message splits into 3 txs, every output shuffled, slices gathered across siblings, reassembled and decrypted back to the original; short message stays single-tx (no mid); the total-chunk cap is enforced. The stateful accumulator (persisted, confirm-gated), sequential broadcast, and progressive/incomplete UI follow in later commits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifndef DRAGONX_ENABLE_CHAT
|
||||
@@ -27,8 +29,13 @@ struct HushChatHeader {
|
||||
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 (larger-messages tier 2, one transaction).
|
||||
// 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;
|
||||
@@ -50,6 +57,12 @@ struct HushChatMemoPair {
|
||||
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<std::pair<int, std::string>> partial_chunks;
|
||||
};
|
||||
|
||||
enum class HushChatMemoGroupingIssue {
|
||||
@@ -91,6 +104,13 @@ struct HushChatTransactionMetadata {
|
||||
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<std::pair<int, std::string>> partial_chunks;
|
||||
};
|
||||
|
||||
struct HushChatTransactionExtractionResult {
|
||||
@@ -102,10 +122,15 @@ struct HushChatTransactionExtractionResult {
|
||||
|
||||
constexpr int kHushChatSupportedVersion = 0;
|
||||
constexpr std::size_t kHushChatMemoByteLimit = 512;
|
||||
// Larger-messages tier 2: the most payload outputs a single message may be split across (one tx).
|
||||
// Keeps the chat tx well under the shielded max_tx_size after Sietch decoy padding + proving cost.
|
||||
// A message that would need more parts than this is refused at compose time (tier 3 would lift it).
|
||||
// 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;
|
||||
|
||||
Reference in New Issue
Block a user