Messenger API

Endpoints for the end-to-end encrypted PQC messenger.

All messages are encrypted client-side using ML-KEM-768 key encapsulation and AES-GCM. The server only stores encrypted blobs — it cannot read message contents.

Register Messenger Wallet

POST /api/messenger/wallets/register
Content-Type: application/json

Register your wallet's encryption public key so others can send you messages.

Request Body

{
  "publicKey": "signing-public-key-hex",
  "encPublicKey": "ml-kem768-public-key-hex",
  "displayName": "Alice"
}

Response

{
  "success": true
}

List Messenger Wallets

GET /api/messenger/wallets

Returns all registered messenger wallets.

Response

{
  "wallets": [
    {
      "publicKey": "abc123...",
      "encPublicKey": "def456...",
      "displayName": "Alice"
    }
  ]
}

Send Message

POST /api/messenger/messages
Content-Type: application/json

Request Body

{
  "conversationId": "conv-uuid",
  "senderPublicKey": "sender-pub-hex",
  "recipientPublicKey": "recipient-pub-hex",
  "senderEncrypted": "base64-encrypted-for-sender",
  "recipientEncrypted": "base64-encrypted-for-recipient",
  "senderDisplayName": "Alice",
  "recipientDisplayName": "Bob",
  "selfDestruct": false,
  "destructSeconds": 0
}

Messages are encrypted twice — once for the sender (so they can read their sent messages) and once for the recipient. The server stores both ciphertexts.

Self-Destruct Messages

Set selfDestruct: true and destructSeconds to a value. After the recipient reads the message, it is marked as read and hidden from the UI.

Response

{
  "success": true,
  "messageId": "msg-uuid"
}

Get Messages

GET /api/messenger/messages?conversationId=conv-uuid&publicKey=your-pub-hex

Query Parameters

ParameterTypeDescription
conversationIdstringThe conversation ID
publicKeystringYour public key (to get your encrypted copy)

Response

{
  "messages": [
    {
      "id": "msg-uuid",
      "conversationId": "conv-uuid",
      "senderPublicKey": "abc...",
      "senderDisplayName": "Alice",
      "encrypted": "base64-ciphertext",
      "timestamp": 1706745600000,
      "selfDestruct": false,
      "mediaType": null
    }
  ]
}

Mark Message as Read

POST /api/messenger/messages/read
Content-Type: application/json

Used for self-destruct messages.

{
  "messageId": "msg-uuid"
}

Get Conversations

GET /api/messenger/conversations?publicKey=your-pub-hex

Returns all conversations for a wallet.

Response

{
  "conversations": [
    {
      "conversationId": "conv-uuid",
      "participants": [
        {
          "publicKey": "abc...",
          "displayName": "Alice"
        },
        {
          "publicKey": "def...",
          "displayName": "Bob"
        }
      ],
      "lastMessage": "2024-01-31T12:00:00Z",
      "unreadCount": 2
    }
  ]
}

Media Messages

The messenger supports image and video attachments. Media is encrypted and sent as base64 within the message payload.

LimitValue
Max upload size50 MB (before compression)
Compressed target~1.5 MB
Image formatConverted to WebP client-side
Video formatConverted to WebM (VP9) client-side

The client automatically compresses large media before encryption and sending.


Encryption Flow

Sender                                    Recipient
  │                                          │
  │ 1. Generate shared secret via ML-KEM-768 │
  │ 2. Derive AES-256 key via HKDF           │
  │ 3. Encrypt message with AES-GCM          │
  │ 4. Encrypt for self (sender copy)        │
  │ 5. Encrypt for recipient                 │
  │                                          │
  │── POST /messages (both ciphertexts) ────►│
  │                                          │
  │                    6. Decapsulate shared secret
  │                    7. Derive same AES key
  │                    8. Decrypt message

Wallet Blocking

Users can block wallets client-side. Blocked wallets are filtered from conversations and contacts. The block list is stored in browser localStorage under pqc_blocked_wallets.

This is a client-side feature — the server is not involved. Blocked users can still send messages, but they won't appear in the blocking user's UI.