Expand description
CORD-03 Chat Plane — a Channel’s messages over the v2 stream envelope.
Every chat action is an unsigned rumor in an encrypted seal (CORD-02 §5 makes 20013 mandatory here — chat content must never be liftable as a standalone public event) inside a wrap at the Channel’s stream address. The rumor kinds reuse standard Nostr shapes wherever one fits:
- kind 9 message (NIP-C7: content = text, replies via a
qtag naming the parent RUMOR id — never the outer wrap’s, which differs per re-wrap) - kind 7 reaction (NIP-25:
e/p/kname the target) - kind 5 delete (NIP-09:
e= the author’s own rumor id,kits kind) - kind 3302 edit (
e= own message rumor id, content = replacement text) - kind 3310 WebXDC peer signal (payload opaque to the protocol)
- kind 23311 typing (ephemeral tier — rides the 21059 wrap)
Two Vector inner-tag conventions carry over from v1: NIP-30
["emoji", shortcode, url] tags and verbatim extra tags (NIP-92 imeta
attachments) ride inside the signed rumor, so they are author-committed.
Every rumor MUST commit ["channel", id] + ["epoch", n], checked
strict-equal against the coordinate whose key decrypted the wrap (CORD-03
§3) — the rumor’s own claim is never trusted, so a keyholder of two planes
cannot re-seal a rumor across Channels or replay it across epochs.
The wrap kind (1059/21059) is a transport tier, not a content authority: the open side admits any allowlisted rumor kind on either wrap and lets the rumor kind govern meaning. Publishers still MUST put typing on 21059 (relays MUST NOT store it) — that is a send-side duty, not a read gate.
Structs§
- Reply
Ref - A parsed reply reference — the NIP-C7
qtag’s parent rumor id and (when carried, a SHOULD upstream) its author.
Enums§
- Chat
Error - Errors from the chat plane layer (envelope errors ride inside).
- Chat
Event - A fully verified, typed chat event. Every variant keeps its
OpenedStream— the proven author, rumor id, and ms time live there.
Functions§
- build_
comment_ rumor - Build a kind-1111 threaded-reply rumor (NIP-22, CORD-03 §3). Uppercase
K/E/Ppin the immutable thread ROOT, lowercasek/e/pthe immediate PARENT — all rumor ids.parent_rootnames the parent’s own root when the parent is itself a reply (inherited verbatim, so the root stays stable at any depth — the exact shape Armada builds);Nonemeans the parent IS the root. - build_
delete_ rumor - Build a kind-5 delete rumor (NIP-09):
e= the author’s OWN rumor id,k= its kind. Semantic within the plane only — members stop rendering; the wrap ciphertext on relays needs a separate NIP-09 scrub by itsptag. - build_
edit_ rumor - Build a kind-3302 edit rumor:
e= the author’s own message rumor id, content = the replacement text (fields unpinned upstream; this shape matches the CORD examples). - build_
message_ rumor - Build a kind-9 message rumor.
reply_tois the parent’s(rumor_id_hex, author_hex)— the NIP-C7qtag;emojithe NIP-30(shortcode, url)pairs for any:shortcode:in the content;extra_tagsride verbatim (NIP-92imetaattachments), author-committed by the seal. - build_
reaction_ rumor - Build a kind-7 reaction rumor (NIP-25):
e= the target rumor id,pits author,k= the target’s kind (9for a message,1111for a threaded reply).emoji_contentis the reaction itself ("+", an emoji, or a:shortcode:);emojicarries the NIP-30 pair when the content is a custom-emoji shortcode. - build_
typing_ rumor - Build a kind-23311 typing rumor — presence of the event is the signal, it
carries nothing. Seal it with
ephemeral: trueso relays never store it. - build_
webxdc_ rumor - Build a kind-3310 WebXDC peer-signal rumor:
contentandextra_tagsare the app payload, opaque to the protocol, carried verbatim. - chat_
group_ key - A Channel’s Chat Plane group key.
secretis whatever feeds the Channel at this epoch: thecommunity_rootfor a Public Channel (at the root epoch), or the Channel’s independent key for a Private one (at its own channel epoch). The channel id inside the derivation gives every Channel a distinct address regardless of which secret feeds it. - open_
chat_ event - Open and fully verify one chat wrap against the
(channel, epoch)whose key is being tried: envelope verification (stream::open_wrap), the encrypted-seal gate, the strict channel/epoch binding, the kind allowlist, then the typed parse. Malformed targets are errors, never panics. - open_
chat_ event_ multi - Open a chat wrap against every
(epoch, secret)a client holds for one Channel — the CORD-03 §3 read path, where history spanning a rekey is queried across all held epoch pubkeys. Selection is by the wrap’s author (each epoch’s derived address), NEVER trial decryption, and the binding is then enforced against the epoch that actually matched — so an epoch-N rumor re-sealed under epoch M’s key still dies as a splice. - seal_
chat_ rumor - Seal a chat rumor into its wrap: encrypted seal (mandatory on this plane),
then the durable 1059 wrap — or the ephemeral 21059 when
ephemeral(the typing tier). Refuses non-chat rumor kinds so a control edition can never be published onto a Channel’s stream by mistake. Returns the wrap plus the ephemeralpkeypair (retain it to best-effort NIP-09-scrub the wrap later). Local-keys convenience; bunker accounts usestream::seal_content