Raven — A Steganographic, Metadata-Resistant Messenger
Technical White Paper · Draft v0.1 Scope: Solana mainnet-beta deployment · Last updated 2026-08-15
This document describes the mechanism and use case of Raven for a technical audience. It is a layered read: Sections 1–2 and 10 are accessible to any reader; Sections 3–9 are the protocol in full detail; Section 11 is a candid security analysis. It is derived from an internal, sprint-by-sprint engineering specification that is not public; where this paper and the implementation disagree, the implementation is authoritative.
Abstract
End-to-end encryption protects message content, but conventional secure messengers still leak metadata: who is talking to whom, when, how often, and — most fundamentally — that a conversation exists at all. For many users that metadata, and the mere presence of a messaging app, is the threat.
Raven conceals the existence of communication itself. Messages are encrypted client-side and stored as opaque blobs on the Solana blockchain; the ordinary public text a sender shares ("carrier text") carries no payload at all. A descriptive encoding rule — a statistical fingerprint of that text — is what tells the intended recipient's client that a message is waiting, and, in stealth mode, where to look. There is no messaging server, no account graph, and no contact list on any server. A shared pool wallet pays all on-chain costs, so a sender never holds cryptocurrency or signs a transaction. Crucially, recipients never poll: a message is discovered only when the recipient deliberately scans text they encounter, or receives a contentless real-time nudge over an optional signaling overlay ("Flock"). The result is a messenger whose traffic pattern, account graph, and message presence are all designed to be unobservable.
1. Introduction & Motivation
Modern E2E messengers (Signal, WhatsApp, etc.) solve content confidentiality well. What they do not, and largely cannot, hide:
- The social graph. A server knows that account A exchanges messages with account B. Even sealed-sender designs leak timing and volume.
- Presence and delivery metadata. Online status, read receipts, typing indicators, push tokens.
- The fact of installation. Having the app on a seized device is itself incriminating in some threat models.
Raven takes a different posture, summarized as deniability over convenience. Its carrier is infrastructure an adversary cannot easily take down or correlate: ordinary posts on public platforms (X, Reddit, LinkedIn, …) plus a public, globally-replicated blockchain. A Raven message looks like a normal social-media post to anyone who does not hold the recipient key and the carrier text simultaneously. There is no Raven account to subpoena, no server log to seize, and — by design — no background network chatter that betrays a user is "checking for messages."
Raven is built as three client surfaces over one shared protocol core, and runs against Solana mainnet-beta.
2. Threat Model & Design Principles
2.1 Adversaries considered
| Adversary | Capability | What Raven denies them |
|---|---|---|
| Passive platform/social-media observer | Reads all public carrier posts; can intercept Flock broadcasts on a public frequency | Steganography hides that any message exists; the carrier is plausibly an ordinary post. The Flock frequency is not among the denials: it is a stable pseudonymous identifier derived from the recipient's public key — the key they hand out to establish contact — so it names no account, but anyone holding that key can compute it and watch it (§7.3) |
| Network observer | Sees relayer and RPC traffic | Content is E2E encrypted before it reaches the relayer; the relayer never sees plaintext; Solana reads are public and unauthenticated, so a read reveals only interest in one opaque address |
| Solana node / chain analyst | Sees every stored blob forever | Blobs are opaque AES-256-GCM ciphertext; addresses are rotating or rule-derived and unlinkable without the counterparty key |
| Device seizure | Full access to the unlocked device | Vault is encrypted at rest; the in-memory session view is wiped on lock/close; a compromised key can be socially revoked (see §11 for honest limits) |
2.2 Constitutional commitments
Four principles are treated as non-negotiable invariants in the codebase and the spec. They are quoted here because they define what Raven is:
- No ambient polling. "There is no ambient polling, no periodic background sync, no speculative reads against registered keys… 'Performance' and 'convenience' are not valid reasons to relax this." Every RPC read is the result of a user action or a Flock push for a frequency the user explicitly subscribed to.
- Public keys are not feeds. Broadcast channels expose exactly
PUBLIC_KEY_SLOT_COUNT = 10recent messages in a rolling window — a product decision, not a storage limit. No unbounded history retrieval. - The webapp is a gateway, not a control panel. Administrative and settings surfaces live in the extension/iOS app; the webapp is intentionally limited to keys / compose / scan / tune / about.
- The session view is memory-only. Revealed messages live in memory for the app session in chronological order, then vanish on lock or close. "Nothing is written to disk. Nothing crosses session boundaries."
These commitments are what make Raven's traffic pattern unobservable; they are also why several "obvious" convenience features (inboxes, history, background refresh) are deliberately absent.
3. System Architecture
Raven is a monorepo of six packages plus an Anchor smart contract. Three client surfaces share one protocol core; two backend components mediate Solana.
raven-coreis the source of truth for all cross-surface protocol behavior. It is consumed as TypeScript source (no build step) by the extension and webapp, and mirrored byte-for-byte by the SwiftRavenCoreand the Flock SDK.- The relayer is stateless. It receives an already-encrypted blob plus a recipient identifier, signs the on-chain write with the pool wallet, and returns the transaction signature. It never holds plaintext or user keys.
- The sweeper is a cron job that enumerates aged on-chain accounts and closes them, returning their rent reserve to the pool wallet.
4. Cryptographic Protocol
All cryptography happens client-side. Primitives are standard and conservative.
4.1 Primitive stack
| Layer | Algorithm | Library | Parameters |
|---|---|---|---|
| Key agreement | X25519 ECDH | tweetnacl (scalarMult) |
32-byte keys |
| Key derivation (messages) | HKDF-SHA256 | Web Crypto | salt = empty, info = "raven-v1", → 256-bit key |
| Message encryption | AES-256-GCM | Web Crypto | random 12-byte IV, 16-byte auth tag |
| Broadcast signing | Ed25519 detached | tweetnacl (sign) |
64-byte signature over canonical JSON |
| Vault KDF | Argon2id (legacy PBKDF2) | hash-wasm / Web Crypto | see §8 |
Note on naming. Some internal comments loosely refer to a "
nacl.boxenvelope." In the shipping code, tweetnacl is used only for X25519 scalar multiplication, keypair generation, and Ed25519 signatures. The actual authenticated encryption of every message and the vault is AES-256-GCM via Web Crypto over an HKDF-derived key. This paper describes the implemented primitive.
4.2 Pairwise message encryption
To send to a known recipient, the sender computes the shared secret
X25519(sender_secret, recipient_public), runs it through HKDF-SHA256 with
info = "raven-v1" to obtain a 256-bit AES key, and encrypts the plaintext with
AES-256-GCM under a fresh random 12-byte IV. The wire blob is:
base64( 0x00 ‖ IV(12) ‖ ciphertext ‖ GCM_tag(16) )
The leading flag byte discriminates wire formats. 0x00 = pairwise (the
receiver must already know the sender's identity public key).
4.3 Anonymous first-contact (ephemeral) encryption
For the first message to a freshly imported key — where the receiver does not yet
know the sender — Raven uses an ephemeral variant (flag 0x01). The sender
generates a one-time X25519 keypair, derives the key from
X25519(ephemeral_secret, recipient_public), and places the ephemeral public
key in the clear on the wire:
base64( 0x01 ‖ ephemeral_pub(32) ‖ IV(12) ‖ ciphertext ‖ GCM_tag(16) )
The receiver decrypts with X25519(my_secret, ephemeral_pub) — no prior
knowledge of the sender required. The sender's identity public key travels
inside the encrypted body (as sender_public_key) so the two sides can
bootstrap a stable channel afterward (§8.2). This buys sender anonymity for first
contact; it is not a per-message forward-secrecy ratchet (§11).
4.4 Blob versions
A decrypted payload is interpreted by trying versions in order
(decryptRavenBlob: v3 → v2 → v1):
- v1 (legacy): the plaintext is a raw string.
- v2 (private channel): JSON
{ version: 2, message, next_query_id, encoding_rule, timestamp, revoked?, revocation_reason?, sender_public_key? }.next_query_idadvances the rotating-address chain on each successful decrypt (§6.2). Integrity is provided by AES-256-GCM; v2 is unsigned. - v3 (signed broadcast): adds
sender_public_key(Ed25519),signature(Ed25519 detached), and an optional monotonicsequence_number. The signature is computed over the recursively key-sorted canonical JSON of all fields exceptsignature, then the whole blob is encrypted with the §4.2 path. Recursive sorting is a cross-platform interop contract: Swift'sJSONEncoder.outputFormatting = .sortedKeysmust produce identical bytes, or signatures fail to verify across surfaces.
5. Steganographic Encoding
5.1 Carrier text and encoding rules
Raven does not generate cover text. The user writes (or chooses) ordinary public text — the carrier. Raven derives a descriptive fingerprint of that text, the encoding rule, which both (a) selects which sentence in a post carries a message and (b) seeds stealth addressing (§7.4).
An encoding rule is a JSON object with a mandatory vowel backbone
(total_vowels, a_count, e_count, i_count, o_count, u_count) plus a
variable pool of features that are recorded only when present in the carrier:
word_count, positional word_at[] checks, consonant_count, longest/shortest
word lengths, words starting/ending with a vowel, and counts of uppercase,
digits, punctuation, emoji, and double letters.
5.2 Descriptive, not prescriptive; iterate-present matching
Rules count what is in the text — they never instruct a user to write text a certain way. Matching is iterate-present-only: fields absent from the rule are not checked, and a sentence matches a rule iff every present condition holds. This makes carriers natural-looking while keeping matching deterministic.
5.3 Canonicalization (parity-critical)
Because the same rule must be computed identically on every platform, text
analysis pins exact semantics: NFC normalization, counting by Unicode
codepoint (not UTF-16 units), an enumerated punctuation set (not a
locale-dependent class), and Intl.Segmenter with Extended_Pictographic for
emoji. The canonical byte serialization of a rule (canonicalRuleBytes) is used
directly as HKDF salt in stealth derivation, so its byte-exactness is load-bearing.
6. On-Chain Storage Model
6.1 The program and the pool wallet
The Anchor program (mainnet-beta ID
C9DUi6mFpMxpjkCGw7oxw23Ty63jEg9nujmqgwB4nrWo) stores each message in a Program
Derived Address (PDA). A single hard-coded pool wallet is the only
authorized signer for every store_* and close_* instruction. The relayer
holds that keypair; the sweeper reuses it. Reads are permissionless and free — anyone
can getAccountInfo on a PDA — but they reveal only interest in one opaque
address, not who can decrypt it.
Hard limits, enforced on-chain: MAX_BLOB_LEN = 1024 bytes,
MAX_RECIPIENT_KEY_ID_LEN = 64 chars. Each account holds the 8-byte Anchor
discriminator, its fields, and a blob of up to 1,024 bytes (≈1.05–1.1 KB total).
6.2 Three address families
| Family | PDA seeds | Addressing model |
|---|---|---|
| Private | ["raven", recipient_key_id] |
Rotating per message via a next_query_id chain; recipient_key_id is normalized to ≤32 bytes |
| Public broadcast | ["raven-pk", encryption_pubkey, slot_index] |
Fixed 10-slot rolling window, slot_index ∈ [0, 10) |
| Stealth | ["raven-stealth", recipient_encryption_pubkey, query_id] |
Rule-derived dead-drop, query_id = 16 bytes (§7.4) |
Private addresses rotate: each delivered v2 blob carries the next address in the chain, so an observer cannot link two messages to the same conversation by address. Public channels are intentionally bounded to 10 slots. Stealth addresses are derived from a shared secret and the carrier's encoding rule, so they are unlinkable without both the counterparty key and the exact carrier.
6.3 Economics and lifecycle
The pool wallet funds the rent-exempt reserve when an account is created. The sweeper (cron-triggered, with a kill switch and a dry-run mode) later enumerates program accounts, classifies them by age, and closes the aged ones — returning the reserve to the pool wallet. Default age thresholds:
- Private: closed at ≥ 8 days (1-day grace) — recipients get roughly a
[7d, 8d)"Quiet" window to retrieve. - Public: a channel is closed only when all 10 slots are ≥ 60 days old.
- Stealth: closed at ≥ 90 days (longer fuse, supports late discovery).
This makes write-cost recoverable and bounds Raven's permanent on-chain footprint.
6.4 The relayer
A stateless Express service exposes:
| Endpoint | Purpose |
|---|---|
POST /submit |
Store a private raven |
POST /submit-public |
Store a public-broadcast slot |
POST /submit-stealth |
Store a stealth raven |
GET /fetch |
Allow-listed (X/Reddit/LinkedIn) URL→plaintext proxy for scanning |
GET /health |
Liveness + pool wallet pubkey |
It validates blob size and identifier shape, then signs and submits. Two independent layers of rate limiting protect the pool wallet: an always-on in-memory per-IP limiter and a sliding-window per-recipient limiter backed by a shared store. An admin denylist refuses flagged identifiers with a 403 before anything touches the chain, logging no metadata.
7. Message Delivery: Two Modes Over One Store
A defining property: both delivery modes write the identical blob to the identical PDA. Flock is a signaling overlay, not a storage path — it never carries ciphertext. The sender chooses per message.
7.1 Manual / stealth-in-the-wild
The sender simply posts the carrier text. The recipient discovers the message by scanning text they come across — no real-time signal, no Flock metadata, maximum deniability. Discovery is always pull, never push.
7.2 Fast / social (Flock)
The sender additionally broadcasts { frequency, carrier_text, timestamp } to
api.theflock.ink. The recipient's auto-tune WebSocket — subscribed to the
recipient's own derived frequency (§7.3) — receives a real-time nudge and runs
a scan.
The overlay is not enabled uniformly across surfaces, and the catch-up behavior differs accordingly:
- Browser surfaces (extension, webapp). The WebSocket is live. On reconnect the Flock server replays up to 48 h of history. If the gap since the last Flock-driven activity meets or exceeds that same 48 h buffer, the replay may no longer cover it, so the client fires one Solana catch-up scan on connect. Below 48 h the replay suffices and no extra read is made.
- iOS. The Flock overlay ships disabled. That client does not connect to the signaling network at all — no broadcast, no history replay, and no real-time nudge reaches it. In its place, a single Solana catch-up scan runs on foreground or unlock when at least 1 h has elapsed since the last recorded activity. That scan is the only automatic delivery path on the surface.
The automatic paths do not deliver everything. A discovery floor applies to every bulk scan — history replay, the offline catch-up, and a manual "scan all keys": a private raven younger than 7 days is skipped, and skipped without advancing its address cursor, so it remains retrievable later. Inside that window the raven is discoverable only by scanning the carrier directly, which is the deniable path §7.1 describes and the one the sender chose by posting in the wild. Scans the user initiates against a carrier — paste, screenshot, snip — are never gated, and neither is a live Flock push, which arrives at age ~0. The floor is set against the retention schedule in §6.3 so a raven cannot pass out of the skip window and into the close threshold between two catch-up scans.
None of this is polling: every read is triggered either by an explicit subscription the user opted into or by the user bringing the app to the foreground, and the overlay only ever says "go look."
7.3 Frequency derivation
A frequency is derived from a single public key — the recipient's — so any sender holding that key computes the same channel without coordination. The derivation takes one key, never a pair: a recipient has exactly one frequency, and every sender to that recipient broadcasts on it.
HKDF-SHA256( ikm = recipient_pubkey, salt = zeros(32), info = "flock-frequency-v1", L = 12 )
→ three big-endian 4-byte chunks → (u32 mod 36^4) → base36, zero-padded to 4
→ "@xxxx.xxxx.xxxx"
The 12-byte output yields a short, human-readable handle. This derivation must agree byte-for-byte across TypeScript, Swift, and the Flock SDK; a pinned fixture enforces it.
7.4 Stealth addressing
The stealth path lets a sender drop a message at an address only the intended recipient can compute, derived from the carrier itself:
query_id = HKDF-SHA256(
ikm = X25519(sender_secret, recipient_public), // pairwise shared secret
salt = canonicalRuleBytes(encoding_rule), // raw canonical bytes, NOT hex
info = "raven-stealth-v1",
L = 16 bytes )
The account is a PDA seeded with the recipient's own encryption public key and that tag — not the sender's — so the sender writes into an address in the recipient's key space, and the recipient looks only in their own.
Discovery is a probe, not a lookup. The X25519 agreement is symmetric, so both sides derive the same tag from the same carrier. But a pasted carrier arrives with no sender attribution: the recipient does not know whose key to use. On scanning, the client recomputes the encoding rule from the carrier and then walks its own key list — for each key, one candidate tag is derived and one account read is issued at the corresponding PDA. A miss returns nothing and is silent; a hit is decrypted and surfaced. A scan therefore costs one derivation and one read per key held, and the practical bound on stealth discovery is the size of the user's own key list — there is no directory, no index, and no server-side lookup to consult. Rotating chain addresses are tried first, so an ordinary delivery costs no stealth probes at all.
Stealth blobs sit at a fixed (non-rotating) address and can be re-queried later, enabling late discovery. An observer who sees the carrier post on social media cannot derive the address without the counterparty's key. (For comparison, ordinary private-channel addresses use a different primitive — SHA-256 truncated to 16 bytes — and rotate; the two are intentionally not interchangeable.)
A second variant serves public broadcast channels. A channel has no counterparty, so its tag is derived by a self-agreement — X25519 of the channel keypair with itself — which the channel owner and any follower holding the channel keys both compute identically. That variant seeds the PDA on the channel's public key and carries a v3 signed payload, verified on read, where the pairwise variant above carries v2 and is unsigned (§4.4). A scan probes both families in turn: the recipient's private keys first, then each followed channel.
8. Identity, Vault & Key Lifecycle
8.1 Vault at rest
Each client stores its keys in an encrypted vault. The vault JSON is sealed
with AES-256-GCM under a key derived from the user's passphrase via
Argon2id (memory = 64 MiB, iterations = 3, parallelism = 1, 32-byte output,
16-byte salt). This is CURRENT_KDF_VERSION = 2. Legacy vaults derived with
PBKDF2-SHA256 (100,000 iterations) are version 1 and are transparently
migrated on unlock (re-derived under Argon2id with a fresh salt, re-encrypted,
verified, then written — legacy data is preserved on any failure). The stored
payload tags its kdfVersion, so detection needs no heuristics. The same
StorageAdapter interface backs chrome.storage.local (extension) and IndexedDB
(webapp).
8.2 Identity, keys, and the handshake
An identity is an X25519 encryption keypair plus an Ed25519 signing keypair
(backfilled on first unlock if a legacy vault lacks one). Each correspondent is a
RavenKey record carrying rotating address state
(initialQueryId/currentQueryId/previousQueryId), the last seen encoding
rule, a connection state (pending → imported → connected), a direction, and
revocation status. First contact uses the ephemeral path (§4.3): the first blob
on an imported key embeds the sender's sender_public_key, letting the receiver
bootstrap a stable bidirectional channel.
8.3 Revocation
If a key is compromised, its owner can send a v2 blob with revoked: true. The
counterparty's scanner then stops advancing the chain, marks the key revoked, and
removes it from the compose picker. This is an authenticated social signal,
not cryptographic proof of compromise (see §11).
9. Cross-Platform Parity
Raven's TypeScript core, the Swift RavenCore, and the Flock SDK implement the
protocol byte-for-byte. This is enforced by checked-in JSON fixtures that
pin canonical outputs for: frequency derivation, stealth query_id, channel
derivation, ephemeral encryption, v3 canonical signing, and the vault KDF. Any
change to canonicalization, signing, encoding-rule shape, or a derivation moves
all surfaces and the fixtures together. For a white-paper reader the takeaway is
that Raven's wire and address formats are specified and test-pinned, not
incidental to one implementation.
10. Use Cases
Raven targets users for whom the existence of a conversation is the sensitive fact:
- Close pairs and small circles — couples, families, old friends — who want one channel that is genuinely theirs: no account linking them, no thread to leak or breach, no record that the channel even exists.
- People whose feeds and screens are casually visible to others — shared homes, shared offices, group settings — for whom an ordinary post draws no attention where a secure-messenger notification would.
- Privacy maximalists who want communication with no account graph, no server logs, and no background traffic signature.
Two postures serve these users: fast-social (Flock notifications for active, casual exchange) and maximum-stealth (post in the wild, no real-time signal at all). Because the choice is per message, the same relationship can move between "chatty" and "dark" without changing keys or addresses.
11. Security Analysis & Limitations
Stated candidly, because honest limits are what make the strengths credible.
Strengths.
- Content is end-to-end encrypted with conservative, standard primitives (X25519 / HKDF-SHA256 / AES-256-GCM / Ed25519).
- There is no messaging server and no server-side account graph or contact list.
- On-chain blobs are opaque; private addresses rotate and stealth addresses are rule-derived, frustrating linkage.
- The "no ambient polling" rule removes the traffic-analysis signature that betrays most messengers.
- Broadcast is bounded (10 slots), limiting both feed-like exposure and on-chain footprint.
Limitations.
- No forward secrecy ratchet. Pairwise messages use static X25519 identity keys; the ephemeral path (§4.3) provides first-contact anonymity, not Signal- style per-message forward/future secrecy. Compromise of a long-term key exposes past pairwise messages that an adversary has captured.
- Revocation is social, not cryptographic. Anyone holding a key has equal authority to send or to revoke; revocation is a trust signal, not proof.
- Write authority is centralized. Exactly one signer (the pool wallet, held by the relayer) can write or close accounts. This is what frees senders from holding SOL, but it is a centralization point and a deliberate write-layer censorship vector (the denylist). Reads remain permissionless; only writes are gated. Decentralized relaying is future work.
- The relayer sees sender IP. It is rate-limited but not anonymizing; users who need network-layer anonymity must supply their own (Tor/VPN). The relayer never sees plaintext or keys.
- Device compromise is game over for the session. An unlocked device exposes the in-memory session view and, once unlocked, the vault.
- Steganographic strength is scoped. The carrier/encoding-rule scheme resists casual inspection and makes a post plausibly ordinary; it is not claimed to be robust against a dedicated statistical steganalysis of an author's full posting history. Carrier choice matters.
- Mainnet, but early. The deployment runs on Solana mainnet-beta, so rent is paid in real SOL — roughly 0.0084 SOL per message, reclaimed by the sweeper on the retention schedule in §6. Economics at scale (sustained write volume, pool-wallet funding, rent recovery over long horizons) and durability under production load are not yet characterized.
12. Conclusion & Future Work
Raven demonstrates that hiding that a conversation exists — not only its contents — is achievable by composing well-understood cryptography with a public blockchain dead-drop, steganographic carriers, and a strict no-polling discipline. The system is solo-built and running in production — relayer and sweeper on Railway, webapp on Vercel, iOS and browser-extension clients shipping on their own cadence — today against Solana mainnet-beta.
Natural next directions: a forward-secrecy ratchet for pairwise channels; decentralized or user-run relaying to remove the single-signer trust assumption; economic modeling for rent and sweeping at scale; and a formal steganographic analysis of the carrier scheme.
Appendix A — Parameters & Constants
| Name | Value |
|---|---|
| Program ID (mainnet-beta) | C9DUi6mFpMxpjkCGw7oxw23Ty63jEg9nujmqgwB4nrWo |
MAX_BLOB_LEN |
1024 bytes |
MAX_RECIPIENT_KEY_ID_LEN |
64 chars |
PUBLIC_KEY_SLOT_COUNT |
10 |
| Message KDF | HKDF-SHA256, empty salt, info = "raven-v1" |
| Message AEAD | AES-256-GCM, 12-byte IV, 16-byte tag |
| Wire flags | 0x00 pairwise · 0x01 ephemeral |
| Vault KDF (current) | Argon2id, 64 MiB, t=3, p=1, 32-byte key, 16-byte salt |
| Vault KDF (legacy v1) | PBKDF2-SHA256, 100,000 iters |
Stealth query_id (pairwise) |
HKDF-SHA256, ikm = X25519 pairwise secret, salt = canonical rule bytes, info = "raven-stealth-v1", 16 bytes |
Stealth query_id (broadcast) |
as above, ikm = X25519 of the channel keypair with itself |
Channel query_id |
SHA-256 truncated to 16 bytes (rotating) |
| Flock frequency | HKDF-SHA256, ikm = recipient public key, salt = zeros(32), info = "flock-frequency-v1", 12 bytes → @xxxx.xxxx.xxxx |
| Broadcast signature | Ed25519 detached over recursively key-sorted JSON |
| Sweeper thresholds (default) | private ≥ 8 d (1 d grace) · public ≥ 60 d (all slots) · stealth ≥ 90 d |
| Auto-tune catch-up | browser surfaces ≥ 48 h since last activity (on reconnect) · iOS ≥ 1 h (on foreground/unlock) |
Appendix B — Notation
‖— byte concatenation.X25519(a, B)— Curve25519 scalar multiplication of secretawith publicB.HKDF-SHA256(ikm, salt, info, L)— RFC 5869 extract-and-expand toLbytes.- PDA — Solana Program Derived Address; a deterministic, program-owned account address derived from seed bytes.
- Carrier text — ordinary public text that signals a message is waiting; it carries no payload. Its descriptive fingerprint (the encoding rule) gates recognition and, in stealth mode, address derivation.
- Encoding rule — the descriptive fingerprint of a carrier used for matching and stealth addressing.
The protocol description in this paper reflects the implementation as of 2026-06-24; deployment facts (network and program ID) were updated 2026-07-26 for the mainnet-beta cutover; and the delivery, stealth-discovery, and relayer sections were corrected against the implementation on 2026-08-15.