Make composed messages actually reach the wire: broadcastChatMemos sends the header + payload as two 0-value memo outputs to the peer's z-address (header first, the lower memo position), on both variants. - chat_outgoing: chatSendOutputs(memos, utf8Prefix) — the pure, testable memo encoder. Full-node memos get a "utf8:" prefix (dragonxd rejects raw JSON and then UTF-8-encodes on-chain, byte-identical to SilentDragonXLite's Memo::from_str); the payload is NOT double-hex. Lite memos are raw UTF-8 (the backend does Memo::from_str directly). Header is always output 0. - App::broadcastChatMemos: full-node builds a two-recipient z_sendmany array (amount 0, from the spendable reply z-address, default fee) via submitZSendMany with markFeeGapRetry=true — deliberately, to suppress the fee-gap auto-retry, which rebuilds a single-recipient tx and would drop the payload output. Lite routes to broadcastChatMemosLite (two 0-value LiteSendRecipients, raw memos; the backend accepts duplicate addresses + 0-value for exactly this pattern). - Encoding + design established by a four-codebase mapping (wallet, daemon, SDXL, lite backend) and an adversarial review of the wiring (0 confirmed findings). - Tests: chatSendOutputs (utf8:-prefix + header-first for full-node, raw for lite) + on-chain round-trip (strip "utf8:" -> the harvest parser re-pairs and decrypts). Remaining as a LIVE test (cannot be proven from source): that dragonxd returns the memo under memoStr verbatim, that recipient-array order maps to note position, that a 0-value memo-only tx relays, and full SDXL<->DragonX interop. Gated by DRAGONX_ENABLE_CHAT (default OFF). Verified: Linux + Windows(mingw) build with chat ON, ctest 100%, hygiene clean; caches restored to the OFF default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
3.4 KiB
C++
67 lines
3.4 KiB
C++
#pragma once
|
|
|
|
// DragonX Wallet - HushChat outgoing memo construction (the inverse of the receive parser).
|
|
//
|
|
// Given the sender's identity and the peer, produce the header memo JSON + payload memo that,
|
|
// sent as two 0-value memo outputs to the peer's z-address (header at the LOWER memo position),
|
|
// another HushChat client parses and decrypts. The byte format matches SilentDragonXLite: the
|
|
// header keys serialize alphabetically (nlohmann default) to cid,e,h,p,t,v,z. Pure — no I/O, no
|
|
// network; broadcasting the memos is the caller's job (the transport lands in a later phase).
|
|
|
|
#include "chat_crypto.h" // ChatKeyPair
|
|
|
|
#include <array>
|
|
#include <string>
|
|
|
|
namespace dragonx::chat {
|
|
|
|
struct OutgoingChatMemos {
|
|
std::string recipientZaddr; // the peer's z-address (recipient of both memo outputs)
|
|
std::string headerMemo; // JSON header — MUST occupy the lower memo position on the wire
|
|
std::string payloadMemo; // ciphertext hex (Message) or plaintext (ContactRequest)
|
|
};
|
|
|
|
// One of the two 0-value memo outputs a HushChat send produces (amount is always 0).
|
|
struct ChatSendOutput {
|
|
std::string address; // the peer's z-address
|
|
std::string memo; // memo encoded for the target transport (utf8:-prefixed or raw)
|
|
};
|
|
|
|
// The two outputs for a HushChat send, HEADER FIRST (it must occupy the lower memo position).
|
|
// `utf8Prefix` prepends the daemon's "utf8:" marker required by full-node z_sendmany (which then
|
|
// UTF-8-encodes the bytes on-chain, byte-identical to SDXLite's Memo::from_str); lite backends take
|
|
// raw UTF-8, so pass false there.
|
|
std::array<ChatSendOutput, 2> chatSendOutputs(const OutgoingChatMemos& memos, bool utf8Prefix);
|
|
|
|
enum class ChatComposeStatus {
|
|
Ok,
|
|
EmptyBody,
|
|
MissingField,
|
|
BadPeerKey,
|
|
BadRequestText, // a contact request text must not start with '{' (parser would read it as a header)
|
|
EncryptFailed,
|
|
TooLong // a resulting memo exceeds the HushChat 512-byte memo limit
|
|
};
|
|
|
|
// Build an ENCRYPTED message to a peer whose public key you already learned from a memo they sent
|
|
// you. `mine` is the sender's identity keypair; `myPublicKeyHex` its public half (goes in header p).
|
|
ChatComposeStatus buildOutgoingMessage(const ChatKeyPair& mine,
|
|
const std::string& myPublicKeyHex,
|
|
const std::string& myReplyZaddr,
|
|
const std::string& peerPublicKeyHex,
|
|
const std::string& peerZaddr,
|
|
const std::string& conversationId,
|
|
const std::string& plaintext,
|
|
OutgoingChatMemos& out);
|
|
|
|
// Build a plaintext contact request — no peer public key needed yet; this is how the peer first
|
|
// learns your public key + reply address. The payload is the (plaintext) request text.
|
|
ChatComposeStatus buildOutgoingContactRequest(const std::string& myPublicKeyHex,
|
|
const std::string& myReplyZaddr,
|
|
const std::string& peerZaddr,
|
|
const std::string& conversationId,
|
|
const std::string& requestText,
|
|
OutgoingChatMemos& out);
|
|
|
|
} // namespace dragonx::chat
|