PQC Messaging & Mail

RougeChain includes two built-in communication systems — both fully end-to-end encrypted with post-quantum cryptography.

Where the data lives. Messenger, mail and the name registry are node-hosted services, not chain state: every write is an ML-DSA-65-signed request, but the ciphertext and directory are stored in the node's own databases, are not in blocks and are not replicated by consensus. A wallet's messaging identity is its ML-DSA-65 signing key. Since node release 1.6.3 (2026-10-05) a directory entry can be replaced only by the wallet that owns it, an encryption key already registered to another wallet is refused, and the unauthenticated legacy read routes return 410 Gone (release notes).

Overview

FeatureMessengerMail
PurposeReal-time chatAsync email
EncryptionML-KEM-768 + AES-GCMML-KEM-768 + AES-GCM
AddressesWallet public keys@rouge.quant / @qwalla.mail names
MediaImages, videos (auto-compressed)Text + attachments (up to 3 MB)
Self-destruct✅ Configurable timer❌
Folders—Inbox, Sent, Trash
ThreadingConversationsReply chains
Server seesCiphertext + metadata (sender, conversation members, timing, size, message type)Ciphertext + metadata (sender, recipients, timing, size)

Why Post-Quantum?

Classical encrypted messengers (Signal, WhatsApp) use algorithms like X25519 and Ed25519. A sufficiently powerful quantum computer could break these with Shor's algorithm.

RougeChain's messaging uses ML-KEM-768 (CRYSTALS-Kyber, FIPS 203) for key encapsulation — resistant to both classical and quantum attacks. Messages encrypted today remain secure even if quantum computers arrive in the future.

How It Works

Messenger Encryption

Alice                                       Bob
  │                                          │
  │ 1. Look up Bob's ML-KEM-768 public key   │
  │ 2. Encapsulate → shared secret           │
  │ 3. HKDF → AES-256 key                   │
  │ 4. AES-GCM encrypt (for Bob)            │
  │ 5. AES-GCM encrypt (for self)           │
  │                                          │
  │── Signed request to /api/v2/... ────────►│
  │                                          │
  │                  6. Decapsulate → same shared secret
  │                  7. HKDF → same AES-256 key
  │                  8. AES-GCM decrypt

Key principle: In a 1:1 conversation each message package holds two ciphertexts — one encapsulated to the recipient's ML-KEM key, one to the sender's own. Group conversations (2+ other members) use the same CEK pattern as mail: one ciphertext plus a per-member KEM-wrapped key (sender included). The server never has the private keys to decrypt any of them.

Your encryption key is derived from your seed phrase for wallets created or imported from the phrase on rougechain.io (since 2026-10-01) or in Qwalla: the ML-KEM-768 seed is SHA-512("<phrase>|rougee-gram|kem-v1"), so restoring from the 24-word phrase in either app restores the same key and your old messages and mail. Wallets created on the website before that change (and extension wallets) have a random key: restore those from their .pqcbackup file to keep old messages. A backup that contains encryption keys always keeps them as they are.

Mail Encryption (CEK Pattern)

Mail uses a Content Encryption Key (CEK) pattern for efficient multi-recipient support:

  1. Generate a random 256-bit AES key (the CEK)
  2. Encrypt all mail content (subject, body, attachment) once with the CEK via AES-256-GCM
  3. For each recipient (and the sender): KEM-wrap the CEK using their ML-KEM-768 public key
  4. Sign the concatenation of all encrypted parts with ML-DSA-65 (unified signature)

This design encrypts content only once regardless of recipient count.

Messenger

The real-time messenger is built into the RougeChain web app and browser extension.

Features

  • E2E encrypted conversations between any two wallets
  • Media sharing — images (auto-converted to WebP) and videos (VP9 WebM), compressed client-side
  • Self-destruct messages — set a timer; message disappears after being read
  • Display names — register a name that shows in conversations
  • Contact blocking — client-side block list stored in localStorage
  • Conversation management — create, view, and delete conversations

Getting Started

  1. Go to Messenger in the sidebar
  2. Your wallet is automatically registered
  3. Enter a recipient's public key to start a conversation
  4. Messages are encrypted before leaving your browser

API Endpoints

See Messenger API Reference for full endpoint documentation.

PQC Mail

Mail adds traditional email features on top of the same PQC encryption layer.

@rouge.quant Addresses

Instead of sharing long public keys, register a human-readable name:

ActionResult
Register "alice"You get alice@rouge.quant
Send to "bob"System looks up Bob's public key automatically

Names are unique and first-come-first-served.

Mail Features

  • Subject lines — encrypted along with the body
  • Folders — Inbox, Sent, Trash with move/delete operations
  • Threading — reply chains built client-side via replyToId
  • Mark as read — track read status per message

Email Domains

DomainPlatform
@rouge.quantWebsite and browser extension
@qwalla.mailQWalla mobile app

Both domains resolve against the same node-hosted name registry (not chain state) — the domain is a client-side display choice only.

Getting Started

  1. Go to Mail in the sidebar
  2. Click Register Name and choose your @rouge.quant address
  3. Send encrypted mail to any registered name
  4. All encryption/decryption happens in your browser

API Endpoints

See Mail API Reference for full endpoint documentation.

Authenticated Requests

All mail, messenger, and name registry write operations require ML-DSA-65 signed requests via /api/v2/ endpoints. Each request includes:

  • from — Sender's ML-DSA-65 public key
  • timestamp — Millisecond-precision timestamp (valid within a 5-minute window)
  • nonce — 16 bytes of random hex (prevents replay attacks)
  • signature — ML-DSA-65 signature over the canonical JSON payload

The server verifies the signature, validates the timestamp, confirms the sender owns the wallet, and rejects duplicate nonces. Legacy unsigned endpoints return HTTP 410 (Gone) in production — since release 1.6.3 this includes the legacy read routes (GET /api/mail/inbox, /sent, /trash, /api/mail/message/:id, /api/messenger/conversations, /api/messenger/messages); clients use the signed POST /api/v2/mail/folder, /api/v2/messenger/conversations/list and /api/v2/messenger/messages/list.

Directory ownership (release 1.6.3). The messenger directory maps a wallet to its display name, signing key and ML-KEM-768 encryption key. A re-registration replaces an entry only when it is signed by the key that owns it; a registration whose encryption key is already registered to another wallet is refused with This encryption key is already registered to another wallet. Mail names and mail folders follow the owning wallet only. Nodes before 1.6.3 do not enforce this.

Trust-on-First-Use (TOFU)

The messenger tracks public key fingerprints (SHA-256 hash) for contacts:

  • On first interaction, the contact's key fingerprint is stored locally
  • On subsequent interactions, the fingerprint is compared — a "Key Changed" warning appears if it differs
  • The shortened fingerprint is shown in the chat header for manual verification

Security Properties

PropertyDetails
Quantum-resistantML-KEM-768 key encapsulation (FIPS 203)
No forward secrecyEach message uses a fresh encapsulation, but always to the recipient's (and sender's) long-term ML-KEM key — there is no ratchet. Anyone who later obtains that key can decrypt every stored message sent to or from it. Protect your encryption key and backups accordingly
Server can't read contentServer stores only ciphertext and cannot read messages, but it does see metadata (who talks to whom, when, and message size)
Client-side cryptoAll encryption/decryption happens on the client
Per-member ciphertextSender and every recipient can each decrypt their own copy (two ciphertexts in 1:1, wrapped CEK per member in groups)
Signed requestsAll API calls authenticated with ML-DSA-65 signatures
Anti-replayNonce + timestamp prevents request replay attacks
TOFUKey fingerprint tracking with change detection
Unified signaturesMail signed over all encrypted parts (subject + body + attachment)
CEK multi-recipientEfficient per-recipient key wrapping without re-encryption
Atomic name registryCompare-and-swap prevents race conditions on name claims
Directory ownershipSince node 1.6.3: only the owning signing key can replace a directory entry; a taken encryption key is refused
Node-hostedNot on-chain: stored by the node you talk to, not replicated by consensus
Vaulted keysPrivate keys persisted only as an AES-256-GCM encrypted blob (vault password required); active session keys in sessionStorage

Notifications & Unread Badges

Browser Extension

The browser extension tracks unread counts for both Chat and Mail tabs:

FeatureDetails
Tab badgesChat and Mail tabs display a numeric badge when unread items exist
TooltipsHovering a badged tab shows "3 unread messages" or "2 unread emails"
Extension icon badgeThe combined unread total (chat + mail) is shown on the browser toolbar icon via chrome.action.setBadgeText
System notificationsNative OS notifications for new messages, new mail, received/sent tokens, contract events, staking, and balance changes
Badge clearingViewing a conversation or inbox marks items as read server-side and updates the badge immediately

Notifications are powered by two channels:

  • WebSocket — Real-time transaction and balance events via wss://testnet.rougechain.io/api/ws
  • Polling — Unread messenger and mail counts checked every 15 seconds via signed /api/v2/ endpoints

QWalla Mobile App

QWalla provides the same unread badge experience:

FeatureDetails
Tab badgesExpo Router tabBarBadge on Chat and Mail tabs
Initial pollOn app launch, actual unread counts are fetched from the server so badges are accurate from the start
Real-time updatesWebSocket events increment the badge for new messages and mail
Push notificationsExpo push notifications for messages, mail, and transfers (requires registerPushToken)
Badge clearingNavigating to the Chat or Mail tab clears the respective unread count

Deriving Unread Counts

The server does not expose a dedicated "unread total" endpoint. Clients derive counts from existing data:

  • Chat: Sum unread_count from each conversation returned by POST /api/v2/messenger/conversations/list
  • Mail: Count inbox items where the label's is_read field is false from POST /api/v2/mail/folder

SDK Usage

All write operations now require a wallet parameter for ML-DSA-65 request signing:

import { RougeChain, Wallet } from '@rougechain/sdk';

const rc = new RougeChain('https://testnet.rougechain.io/api');
const wallet = Wallet.generate();

// Step 1: Register wallet (signed request)
await rc.messenger.registerWallet(wallet, {
  id: wallet.publicKey,
  displayName: 'Alice',
  signingPublicKey: wallet.publicKey,
  encryptionPublicKey: encPubKey,
});

// Step 2: Register a mail name (signed request)
await rc.mail.registerName(wallet, 'alice', wallet.publicKey);

// Resolve a recipient's name → wallet + encryption key (public, no signing needed)
const bob = await rc.mail.resolveName('bob');
// bob.wallet.encryption_public_key → use for ML-KEM encryption

// Reverse lookup (public, no signing needed)
const name = await rc.mail.reverseLookup(wallet.publicKey); // "alice"

For the full messenger and mail APIs, see the SDK documentation.

Social Layer

RougeChain includes a built-in social layer for posts, likes, reposts, follows, comments, and play tracking. Social data is stored server-side in sled with ML-DSA-65 signed writes — tips settle on-chain via rc.transfer().

Features

FeatureDescription
PostsStandalone text posts (max 4000 chars) with threaded replies via replyToId
TimelineGlobal timeline (newest first) and personalized following feed
LikesToggle likes on posts or tracks — reuses the same endpoint
RepostsToggle reposts on any post
CommentsTrack-level comments with pagination
FollowsFollow/unfollow any user; follower and following counts
Play countsRecord plays on tracks (debounced per session)
TipsSend XRGE tips to creators via rc.transfer() — settles on-chain

API Endpoints

Read (unsigned GET):

  • GET /api/social/timeline — Global timeline
  • GET /api/social/post/:postId — Single post with stats
  • GET /api/social/post/:postId/stats — Post engagement stats (likes, reposts, replies)
  • GET /api/social/post/:postId/replies — Threaded replies
  • GET /api/social/user/:pubkey/posts — User's posts
  • GET /api/social/track/:trackId/stats — Track stats (plays, likes, comments)
  • GET /api/social/track/:trackId/comments — Track comments
  • GET /api/social/artist/:pubkey/stats — Artist stats (followers, following)
  • GET /api/social/user/:pubkey/likes — User's liked IDs
  • GET /api/social/user/:pubkey/following — User's followed artists

Write (v2 signed POST):

  • POST /api/v2/social/post — Create a post
  • POST /api/v2/social/post/delete — Delete your post
  • POST /api/v2/social/like — Toggle like
  • POST /api/v2/social/repost — Toggle repost
  • POST /api/v2/social/comment — Post a comment
  • POST /api/v2/social/comment/delete — Delete your comment
  • POST /api/v2/social/follow — Toggle follow
  • POST /api/v2/social/play — Record a play
  • POST /api/v2/social/feed — Get following feed (authenticated)

SDK Usage

import { RougeChain, Wallet } from '@rougechain/sdk';

const rc = new RougeChain('https://testnet.rougechain.io/api');
const wallet = Wallet.generate();

// Create a post
const { post } = await rc.social.createPost(wallet, "Hello RougeChain!");

// Reply
await rc.social.createPost(wallet, "Great post!", post.id);

// Like, repost, follow
await rc.social.toggleLike(wallet, post.id);
await rc.social.toggleRepost(wallet, post.id);
await rc.social.toggleFollow(wallet, artistPubKey);

// Read timeline
const timeline = await rc.social.getGlobalTimeline();
const feed = await rc.social.getFollowingFeed(wallet);

CLI Usage

# Post
rougechain post "Hello from the CLI!"

# Reply
rougechain post "Nice post!" --reply-to <post-id>

# Timeline
rougechain timeline --limit 20

# Your feed (posts from people you follow)
rougechain feed

# Like / repost
rougechain like <post-or-track-id>
rougechain repost <post-id>

# Get a post
rougechain get-post <post-id>