Skip to main content

FAQ

General

Which class should I use, L2PSMessagingPeer or MessagingPeer?

Use L2PSMessagingPeer unless you have a specific reason not to. It is the production messaging client (default port 3006) and is the only one that provides:
  • Conversation history with pagination (history())
  • Offline delivery (server queues messages for offline recipients)
  • Server-side delivery acknowledgement (message_sent / message_queued frames)
  • L2PS pipeline persistence (mempool → batch → L1 proof → L1 finality)
  • L2PS network isolation via l2psUid
  • peer_joined / peer_left presence events
Use MessagingPeer only when you need the legacy signaling-server transport (default port 3005). It is a transient relay — no history, no persistence, no offline queue, no ack.

What is the Instant Messaging Protocol?

A pair of WebSocket protocols, both with end-to-end encryption applied client-side. The L2PS protocol additionally persists messages through the Demos L2PS rollup pipeline so they can be retrieved later and delivered to peers that were offline at send time.

What platforms does it support?

Anywhere WebSocket and crypto.getRandomValues are available — modern browsers, Node, mobile and desktop runtimes that ship those primitives.

Encryption

How does the encryption work?

Both classes use end-to-end public-key encryption: each peer has a public/private key pair, and the sender encrypts with the recipient’s public key. Only the recipient’s private key can decrypt.

What cryptographic algorithms are used?

ml-kem-aes for message encryption (ML-KEM-768 key encapsulation + AES-GCM), and ml-dsa for signing keys that bind the encryption identity to the registration identity.

Does the protocol support perfect forward secrecy?

Yes. Each message uses a fresh ML-KEM encapsulation, so compromising one decapsulated AES key does not expose past or future messages.

How are peers authenticated?

For L2PSMessagingPeer, registration carries an ed25519 proof — the client signs register:{publicKey}:{timestamp} with the configured signFn and the server verifies it before accepting subsequent frames. The same signFn is used to authorize history requests. For MessagingPeer, the client signs its ML-KEM public key with an ML-DSA signing key (via unifiedCrypto) during registerAndWait(), binding the encryption key to the signing identity.

Delivery and reliability (L2PSMessagingPeer)

How is delivery acknowledged?

L2PSMessagingPeer.send() resolves with one of two server frames: message_sent ({ messageHash, l2psStatus }) when the message was delivered to an online recipient, or message_queued ({ messageHash, status: "queued" }) when the recipient was offline and the message was stored.

Are messages queued if the recipient is offline?

Yes — but only with L2PSMessagingPeer. The server stores the encrypted payload and delivers it the next time the recipient connects. Inbound message frames carry an offline?: boolean flag indicating delivery from offline storage. MessagingPeer (legacy) has no offline queue.

Can I retrieve past messages?

Yes — but only with L2PSMessagingPeer. Call peer.history(peerKey, { before, limit }) and you’ll get back { messages: StoredMessage[], hasMore }. Each StoredMessage includes its status from the L2PS pipeline (delivered, queued, sent, failed, l2ps_pending, l2ps_batched, l2ps_confirmed) and the l2psTxHash once it has been included in a batch.

How does the protocol handle network issues?

Both clients use exponential-backoff reconnection (base 1000 ms, capped at 30 000 ms, up to 10 attempts). L2PSMessagingPeer re-runs register automatically after reconnection.

Implementation

How do I set up the messaging server?

It is launched automatically as part of a Demos node. The L2PS messaging server defaults to port 3006; the legacy signaling server defaults to port 3005.

How do I create an L2PSMessagingPeer instance?

import { instantMessaging } from "@kynesyslabs/demosdk"

const peer = new instantMessaging.L2PSMessagingPeer({
    serverUrl: "ws://your-demos-node:3006",
    publicKey: myEd25519PublicKeyHex,
    l2psUid: "your-l2ps-network-uid",
    signFn: async (message) => myEd25519.signHex(message),
})

await peer.connect()
See the Quickstart for the full flow including encryption, sending, and history.

How do I create a MessagingPeer instance (legacy)?

import { instantMessaging, encryption } from "@kynesyslabs/demosdk"
const unifiedCrypto = encryption.ucrypto

// Set up unifiedCrypto as in the quickstart, or reuse an existing instance
const mlKemAes = await unifiedCrypto.getIdentity("ml-kem-aes")

const peer = new instantMessaging.MessagingPeer({
    serverUrl: "ws://your-signaling-server:3005",
    clientId: "your-unique-id",
    publicKey: mlKemAes.publicKey,
})
The only canonical import path is import { instantMessaging } from "@kynesyslabs/demosdk". There is no @kynesyslabs/demosdk/instant_messaging subpath.

How do I send a message?

With L2PSMessagingPeer, encrypt the plaintext yourself, serialize to SerializedEncryptedMessage ({ ciphertext, nonce, ephemeralKey? } with base64 / hex fields), then call peer.send(to, encrypted, messageHash). With MessagingPeer, call peer.sendMessage(targetId, plaintext) — encryption is performed automatically against the recipient’s public key.

How do I handle incoming messages?

// L2PSMessagingPeer
peer.onMessage(({ from, encrypted, messageHash, offline }) => {
    // Decrypt `encrypted` with your ML-KEM private key here.
})

// MessagingPeer (legacy)
peer.onMessage((message, fromId) => {
    console.log(`Message from ${fromId}:`, message)
})

How do I handle errors?

// L2PSMessagingPeer
peer.onError(({ code, message, details }) => {
    console.error(`L2PS error [${code}]:`, message, details)
})

// MessagingPeer (legacy)
peer.onError(error => {
    console.error(`Error: ${error.type} - ${error.details}`)
})
L2PSMessagingPeer server-side error codes are: INVALID_MESSAGE, REGISTRATION_REQUIRED, INVALID_PROOF, PEER_NOT_FOUND, L2PS_NOT_FOUND, L2PS_SUBMIT_FAILED, RATE_LIMITED, INTERNAL_ERROR.

How do I handle peer presence?

// L2PSMessagingPeer
peer.onPeerJoined(publicKey => console.log("joined:", publicKey))
peer.onPeerLeft(publicKey => console.log("left:", publicKey))

// MessagingPeer (legacy) — only "disconnected" is exposed
peer.onPeerDisconnected(peerId => console.log(`Peer disconnected: ${peerId}`))

Troubleshooting

What should I do if I can’t connect?

  • Verify the server is running and the URL is reachable (port 3006 for L2PS, 3005 for legacy).
  • Check network and firewall rules.
  • For L2PSMessagingPeer, make sure your signFn actually returns a hex ed25519 signature of the input string — bad proofs surface as INVALID_PROOF.
  • For MessagingPeer, make sure your unifiedCrypto instance has identities generated (see the Quickstart).

What should I do if messages are not being delivered?

  • With L2PSMessagingPeer, look at the resolved value of send()message_queued means the recipient is offline, not that delivery failed. The recipient will receive it on next connect with offline: true.
  • With MessagingPeer, the recipient must be connected at the moment of send; there is no offline queue.
  • Confirm the recipient public key / client ID is correct, and check onError for server-reported issues.

How do I debug the connection?

peer.onConnectionStateChange(state => console.log("state:", state))
peer.onError(err => console.error("error:", err))

Additional resources