Version 7, 2025-01-24

# SMP agent protocol - duplex communication over SMP protocol

## Table of contents

- [Abstract](#abstract)
- [SMP agent](#smp-agent)
- [SMP routers management](#smp-routers-management)
- [SMP agent protocol scope](#smp-agent-protocol-scope)
- [Duplex connection procedure](#duplex-connection-procedure)
- [Fast duplex connection procedure](#fast-duplex-connection-procedure)
- [Contact addresses](#contact-addresses)
- [Communication between SMP agents](#communication-between-smp-agents)
  - [Message syntax](#messages-between-smp-agents)
    - [HELLO message](#hello-message)
    - [A_MSG message](#a_msg-message)
    - [A_RCVD message](#a_rcvd-message)
    - [EREADY message](#eready-message)
    - [A_QCONT message](#a_qcont-message)
    - [Rotating messaging queue](#rotating-messaging-queue)
- [End-to-end encryption](#end-to-end-encryption)
- [Connection link: 1-time invitation and contact address](#connection-link-1-time-invitation-and-contact-address)
  - [Full connection link syntax](#full-connection-link-syntax)
  - [Short connection link syntax](#short-connection-link-syntax)
- [Short links](#short-links)
  - [Link key derivation](#link-key-derivation)
  - [Link data encryption](#link-data-encryption)
  - [Short link resolution](#short-link-resolution)
  - [Link data management](#link-data-management)
- [Appendix A: SMP agent API](#appendix-a-smp-agent-api)
  - [API functions](#api-functions)
  - [API events](#api-events)

## Abstract

The purpose of SMP agent protocol is to define the syntax and the semantics of communications between the client and the agent that connects to [SMP](./simplex-messaging.md) routers.

It provides:
- API to create and manage bi-directional (duplex) connections between the users of SMP agents consisting of two (or more) separate unidirectional (simplex) SMP queues, abstracting away multiple steps required to establish bi-directional connections and any information about the routers location from the users of the agent protocol.
- management of E2E encryption between SMP agents, generating ephemeral asymmetric keys for each connection.
- SMP command authentication on SMP routers, generating ephemeral keys for each SMP queue.
- TCP/TLS transport handshake with SMP routers.
- validation of message integrity.

SMP agent API provides no security between the agent and the client - it is assumed that the agent is executed in the trusted and secure environment, via the agent library, when the agent logic is included directly into the client application - [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) uses this approach.

This document describes SMP agent protocol version 7. The version history:

- v1: initial version
- v2: duplex handshake - allows including reply queue(s) in the initial confirmation
- v3: ratchet sync - supports re-negotiating double ratchet encryption
- v4: delivery receipts - supports acknowledging message delivery to the sender
- v5: post-quantum - supports post-quantum key exchange in double ratchet (PQDR)
- v6: sender auth key - supports sender authentication key in confirmations
- v7: ratchet on confirmation - initializes double ratchet during confirmation

## SMP agent

SMP agents communicate with each other via SMP routers using [simplex messaging protocol (SMP)](./simplex-messaging.md) according to the API calls used by the client applications. This protocol is a middle layer in SimpleX protocols (above SMP protocol but below any application level protocol) - it is intended to be used by client-side applications that need secure asynchronous bi-directional communication channels ("connections").

The agent must have a persistent storage to manage the states of known connections and of the client-side information of SMP queues that each connection consists of, and also the buffer of the most recent sent and received messages. The number of the messages that should be stored is implementation specific, depending on the error management approach that the agent implements; at the very least the agent must store the hashes and IDs of the last received and sent messages.

## SMP routers management

SMP agent API does not use the addresses of the SMP routers that the agent will use to create and use the connections (excluding the router address in queue URIs used in JOIN command). The list of the routers is a part of the agent configuration and can be dynamically changed by the agent implementation:
- by the client applications via any API that is outside of scope of this protocol.
- by the agents themselves based on availability and latency of the configured routers.

## SMP agent protocol scope

SMP agent protocol has 2 main parts:

- the messages that SMP agents exchange with each other in order to:
  - negotiate establishing unidirectional (simplex) encrypted queues on SMP routers.
  - exchange client messages and delivery notifications, providing sequential message IDs and message integrity (by including the hash of the previous message).
  - re-negotiate messaging queues to use and connection e2e encryption.
- the messages that the clients of SMP agents should send out-of-band (as pre-shared "invitation" including queue URIs) to protect [E2E encryption][1] from active attacks ([MITM attacks][2]).

[Appendix A](#appendix-a-smp-agent-api) of this document describes:
- the functional API used by the client application with the agent. This API allows to create and manage multiple connections, each consisting of two or more SMP queues.
- events that the agent passes to the clients.

## Duplex connection procedure

![Duplex connection procedure](./diagrams/duplex-messaging/duplex-creating.svg)

The procedure of establishing a duplex connection is explained on the example of Alice and Bob creating a bi-directional connection consisting of two unidirectional (simplex) queues, using SMP agents (A and B) to facilitate it, and two different SMP routers (which could be the same router). It is shown on the diagram above and has these steps:

1. Alice requests the new connection from the SMP agent A using agent `createConnection` api function.
2. Agent A creates an SMP queue on the router (using [SMP protocol](./simplex-messaging.md) `NEW` command) and responds to Alice with the invitation that contains queue information and the encryption keys Bob's agent B should use. The invitation format is described in [Connection link](connection-link-1-time-invitation-and-contact-address).
3. Alice sends the [connection link](#connection-link-1-time-invitation-and-contact-address) to Bob via any secure channel (out-of-band message) - as a link or as a QR code.
4. Bob uses agent `joinConnection` api function with the connection link as a parameter to agent B to accept the connection.
5. Agent B creates Bob's SMP reply queue with SMP router `NEW` command.
6. Agent B confirms the connection: sends an "SMP confirmation" with SMP router `SEND` command to the SMP queue specified in the connection link - SMP confirmation is an unauthenticated message with an ephemeral key that will be used to authenticate Bob's commands to the queue, as described in SMP protocol, and Bob's info (profile, public key for E2E encryption, and the connection link to this 2nd queue to Agent A - this connection link SHOULD use "simplex" URI scheme). This message is encrypted using key passed in the connection link (or with the derived shared secret, in which case public key for key derivation should be sent in clear text).
7. Alice confirms and continues the connection:
  - Agent A receives the SMP confirmation containing Bob's key, reply queue and info as SMP router `MSG`.
  - Agent A notifies Alice sending `CONF` notification with Bob's info.
  - Alice allows connection to continue with agent `allowConnection` api function.
  - Agent A secures the queue with SMP router `KEY` command.
  - Agent A sends SMP confirmation with ephemeral sender key, ephemeral public encryption key and profile (but without reply queue).
8. Agent B confirms the connection:
  - receives the confirmation.
  - sends the notification `INFO` with Alice's information to Bob.
  - secures SMP queue that it sent to Alice in the first confirmation with SMP `KEY` command .
  - sends `HELLO` message via SMP `SEND` command. This confirms that the reply queue is secured and also validates that Agent A secured the first SMP queue
9. Agent A notifies Alice.
  - receives `HELLO` message from Agent B.
  - sends `HELLO` message to Agent B via SMP `SEND` command.
  - sends `CON` notification to Alice, confirming that the connection is established.
10. Agent B notifies Bob.
  - Once Agent B receives `HELLO` from Agent A, it sends to Bob `CON` notification as well.

At this point the duplex connection between Alice and Bob is established, they can use `SEND` command to send messages. The diagram also shows how the connection status changes for both parties, where the first part is the status of the SMP queue to receive messages, and the second part - the status of the queue to send messages.

The most communication happens between the agents and routers, from the point of view of Alice and Bob there are 4 steps (not including notifications):

1. Alice requests a new connection with `createConnection` agent API function and receives the connection link.
2. Alice passes connection link out-of-band to Bob.
3. Bob accepts the connection with `joinConnection` agent API function with the connection link to his agent.
4. Alice accepts the connection with `allowConnection` agent API function.
5. Both parties receive `CON` notification once duplex connection is established.

Clients SHOULD support establishing duplex connection asynchronously (when parties are intermittently offline) by persisting intermediate states and resuming SMP queue subscriptions.

## Fast duplex connection procedure

Previously described duplex connection procedure requires sending 4 messages creating a bad UX for the users - it requires waiting until each party in online before the messages can be sent.

It allows users validating connecting party profile before proceeding with the connection, but it turned out to be unnecessary UX step and is not used in the client applications.

It also protects against an attacker who compromised TLS and uses the sender queue ID sent to the recipient to secure the queue before the sender can. This attack is very hard, and this accepting its risk is better than worse UX. Future protocol versions could mitigate this attack by encrypting entity IDs.

Faster duplex connection process is possible with the `SKEY` command added in v9 of SMP protocol.

![Fast duplex connection procedure](./diagrams/duplex-messaging/duplex-creating-fast.svg)

1. Alice requests the new connection from the SMP agent A using agent `createConnection` api function
2. Agent A creates an SMP queue on the router (using [SMP protocol](./simplex-messaging.md) `NEW` command with the flag allowing the sender to secure the queue) and responds to Alice with the invitation that contains queue information and the encryption keys Bob's agent B should use. The invitation format is described in [Connection link](connection-link-1-time-invitation-and-contact-address).
3. Alice sends the [connection link](connection-link-1-time-invitation-and-contact-address) to Bob via any secure channel (out-of-band message) - as a link or as a QR code. This link contains the flag that the queue can be secured by the sender.
4. Bob uses agent `joinConnection` api function with the connection link as a parameter to agent B to accept the connection.
5. Agent B secures Alice's queue with SMP command `SKEY` - this command can be proxied.
6. Agent B creates Bob's SMP reply queue with SMP router `NEW` command (with the flag allowing the sender to secure the queue).
7. Agent B confirms the connection: sends an "SMP confirmation" with SMP router `SEND` command to the SMP queue specified in the connection link - SMP confirmation is an unauthenticated message with an ephemeral key that will be used to authenticate Bob's commands to the queue, as described in SMP protocol, and Bob's info (profile, public key for E2E encryption, and the connection link to this 2nd queue to Agent A - this connection link SHOULD use "simplex" URI scheme). This message is encrypted using key passed in the connection link (or with the derived shared secret, in which case public key for key derivation should be sent in clear text).
8. Alice confirms the connection:
  - Agent A receives the SMP confirmation containing Bob's key, reply queue and info as SMP router `MSG`.
  - Agent A notifies Alice sending `CONF` notification with Bob's info (that indicates that Agent B already secured the queue).
  - Alice allows connection to continue with agent `allowConnection` api function.
  - Agent A secures Bob's queue with SMP command `SKEY`.
  - Agent A sends SMP confirmation with ephemeral public encryption key and profile (but without reply queue, and without sender key).
9. Agent A notifies Alice with `CON` notification.
10. Agent B notifies Bob about connection success:
  - receives confirmation message from Alice.
  - sends the notification `INFO` with Alice's information to Bob.
  - sends `CON` notification to Bob.

## Contact addresses

SMP agents support creating a special type of connection - a contact address - that allows to connect to multiple network users who can send connection requests by sending 1-time connection links to the message queue. 

This connection address uses a messaging queue on SMP router to receive invitations to connect - see `agentInvitation` message below. Once connection request is accepted, a new connection is created and the address itself is no longer used to send the messages - deleting this address does not disrupt the connections that were created via it.

## Communication between SMP agents

To establish duplex connections and to send messages on behalf of their clients, SMP agents communicate via SMP routers.

Agents use SMP message client body (the part of the SMP message after header - see [SMP protocol](./simplex-messaging.md)) to transmit agent client messages and exchange messages between each other.

These messages are encrypted with per-queue shared secret using NaCL crypto_box and can be of 4 types, as defined by `decryptedSMPClientMessage`:
- `agentConfirmation` - used when confirming SMP queues, contains connection information encrypted with double ratchet. This envelope can only contain `agentConnInfo` or `agentConnInfoReply` encrypted with double ratchet.
- `agentMsgEnvelope` - contains different agent messages encrypted with double ratchet, as defined in `agentMessage`.
- `agentInvitation` - sent to SMP queue that is used as contact address, does not use double ratchet.
- `agentRatchetKey` - used to re-negotiate double ratchet encryption - can contain additional information in `agentRatchetInfo`.

```abnf
decryptedSMPClientMessage = agentConfirmation / agentMsgEnvelope / agentInvitation / agentRatchetKey
agentConfirmation = agentVersion %s"C" ("0" / "1" sndE2EEncryptionParams) encConnInfo
agentVersion = 2*2 OCTET
sndE2EEncryptionParams = <sender E2E ratchet parameters, see pqdr.md>
encConnInfo = doubleRatchetEncryptedMessage

agentMsgEnvelope = agentVersion %s"M" encAgentMessage
encAgentMessage = doubleRatchetEncryptedMessage

agentInvitation = agentVersion %s"I" connReqLength connReq connInfo
connReqLength = 2*2 OCTET ; Word16
connReq = *OCTET ; URI text encoding of connection link, length given by connReqLength
connInfo = *OCTET ; opaque connection information (remaining bytes)

agentRatchetKey = agentVersion %s"R" rcvE2EEncryptionParams ratchetKeyInfo
rcvE2EEncryptionParams = <receiver E2E ratchet parameters, see pqdr.md>
ratchetKeyInfo = *OCTET ; additional ratchet renegotiation info (remaining bytes)

doubleRatchetEncryptedMessage = <double ratchet encrypted message, see pqdr.md>
```

The maximum size of the encrypted connection info and agent message depend on whether post-quantum key exchange is used:

| Constant | PQ on | PQ off |
|----------|-------|--------|
| `e2eEncConnInfoLength` | 11106 | 14832 |
| `e2eEncAgentMsgLength` | 13618 | 15840 |

The PQ-on sizes are smaller because the ratchet header and reply link include larger PQ keys (SNTRUP761).

This syntax of decrypted SMP client message body is defined by `decryptedAgentMessage` below.

Decrypted SMP message client body can be one of 4 types:
- `agentConnInfo` - used by the initiating party when confirming reply queue - sent in `agentConfirmation` envelope.
- `agentConnInfoReply` - used by accepting party, includes reply queue(s) in the initial confirmation - sent in `agentConfirmation` envelope.
- `agentRatchetInfo` - used to pass additional information when renegotiating double ratchet encryption - sent in `agentRatchetKey` envelope.
- `agentMessage` - all other agent messages.

`agentMessage` contains these parts:
- `agentMsgHeader` - agent message header that contains sequential agent message ID for a particular SMP queue and the hash of the previous message.
- `aMessage` - a command/message to the other SMP agent:
  - to confirm the connection (`HELLO`).
  - to send and to confirm reception of user messages (`A_MSG`, `A_RCVD`).
  - to confirm that the new double ratchet encryption is agreed (`EREADY`).
  - to notify another party that it can continue sending messages after queue capacity was exceeded (`A_QCONT`).
  - to manage SMP queue rotation (`QADD`, `QKEY`, `QUSE`, `QTEST`).

The encoded `agentMessage` is padded to a fixed size by the double ratchet encryption layer (see [ratchet message wire format](./pqdr.md#ratchet-message-wire-format)) to make all SMP messages have constant size, preventing routers from observing the actual message size.

### Messages between SMP agents

Message syntax below uses [ABNF][3] with [case-sensitive strings extension][4].

```abnf
decryptedAgentMessage = agentConnInfo / agentConnInfoReply / agentRatchetInfo / agentMessage
agentConnInfo = %s"I" connInfo
connInfo = *OCTET
agentConnInfoReply = %s"D" smpQueues connInfo
smpQueues = length 1*newQueueInfo ; NonEmpty list of reply queues
agentRatchetInfo = %s"R" ratchetInfo
ratchetInfo = *OCTET

agentMessage = %s"M" agentMsgHeader aMessage
agentMsgHeader = agentMsgId prevMsgHash
agentMsgId = 8*8 OCTET ; Int64
prevMsgHash = shortString

aMessage = HELLO / A_MSG / A_RCVD / EREADY / A_QCONT /
           QADD / QKEY / QUSE / QTEST

HELLO = %s"H"

A_MSG = %s"M" userMsgBody
userMsgBody = *OCTET ; remaining bytes

A_RCVD = %s"V" msgReceipts
msgReceipts = length 1*msgReceipt ; NonEmpty list
msgReceipt = agentMsgId msgHash rcptLength rcptInfo
msgHash = shortString
rcptInfo = *OCTET ; opaque receipt info, length given by rcptLength (Word16)

EREADY = %s"E" agentMsgId

A_QCONT = %s"QC" sndQueueAddr

QADD = %s"QA" sndQueues
sndQueues = length 1*(newQueueUri replacedSndQueue)
newQueueUri = clientVRange smpRouter senderId dhPublicKey [queueMode]
dhPublicKey = length x509encoded
queueMode = %s"M" / %s"C" ; M - messaging (sender can secure), C - contact
replacedSndQueue = "0" / "1" sndQueueAddr

QKEY = %s"QK" sndQueueKeys
sndQueueKeys = length 1*(newQueueInfo senderKey)
newQueueInfo = version smpRouter senderId dhPublicKey [queueMode]
senderKey = length x509encoded

QUSE = %s"QU" sndQueuesReady
sndQueuesReady = length 1*(sndQueueAddr primary)
primary = %s"T" / %s"F"

QTEST = %s"QT" sndQueueAddrs
sndQueueAddrs = length 1*sndQueueAddr

sndQueueAddr = smpRouter senderId
smpRouter = hosts port keyHash
hosts = length 1*host
host = shortString
port = shortString
keyHash = shortString
senderId = shortString

clientVRange = version version
version = 2*2 OCTET

rcptLength = 2*2 OCTET
shortString = length *OCTET
length = 1*1 OCTET
```

#### HELLO message

This is the first message that both agents send after the respective SMP queue is secured by the receiving agent (see diagram).

This message is not used with [fast duplex connection](#fast-duplex-connection-procedure).

#### A_MSG message

This is the agent envelope used to send client messages once the connection is established. This is different from the MSG sent by SMP router to the agent and MSG event from SMP agent to the client that are sent in different contexts.

#### A_RCVD message

This message is sent to confirm the client message reception. It includes a list of message receipts, each containing the received message number, message hash and receipt info.

#### EREADY message

This message is sent after re-negotiating a new double ratchet encryption with `agentRatchetKey`.

#### A_QCONT message

This message is sent to notify the sender client that it can continue sending the messages after queue capacity was exhausted.

### Rotating messaging queue

SMP agents SHOULD support 4 messages to rotate message reception to another messaging router:
`QADD`: add the new queue address(es) to the connection - sent by the client that initiates rotation.
`QKEY`: pass sender's key via existing connection (SMP confirmation message will not be used, to avoid the same "race" of the initial key exchange that would create the risk of intercepting the queue for the attacker) - sent by the client accepting the rotation
`QUSE`: instruct the sender to use the new queue with sender's queue ID as parameter. From this point some messages can be sent to both the new queue and the old queue.
`QTEST`: send test message to the new connection. Any other message can be sent if available to continue rotation, the absence of this message is not an error. Once this message is successfully sent the sender will stop using the old queue. Once this message (or any other message in the new queue) is received, the recipient will stop using the old queue and delete it.

**Queue rotation procedure**

![Queue rotation procedure](./diagrams/duplex-messaging/queue-rotation.svg)

`SKEY` command added in v9 of SMP protocol allows for faster queue rotation procedure.

**Fast queue rotation procedure**

![Fast queue rotation procedure](./diagrams/duplex-messaging/queue-rotation-fast.svg)

## End-to-end encryption

Messages between SMP agents have two layers of e2e encryption:
- simple encryption agreed in SMP protocol with a fixed key agreed when the messaging queue is agreed by parties.
- post-quantum resistant augmented double ratchet algorithm (PQDR) specified in [this document](./pqdr.md).

The protocol supports adding and removing post-quantum KEM primitive to the key agreement in double ratchet:
- to support migration of pre-existing connections to PQDR.
- to be able to disable PQ key agreement.
- to be able to use invitation links and contact addresses without large PQ keys.

Possible scenarios below show the possible states of PQ key agreement, assuming that both clients support it.

Possible options for each stage are:
- no KEM encapsulation key was sent (No PQ key),
- only KEM encapsulation key was sent, but not ciphertext yet (PQ key sent),
- both KEM encapsulation key from one KEM agreement and ciphertext from the previous agreement were sent (PQ key + PQ ct sent).

`+` in the table means that this scenario is possible, and `-` - that it is not possible.

| Connection stage                                       | No PQ key        | PQ key sent      | PQ key + PQ ct sent |
|:------------------------------------------------------:|:----------------:|:----------------:|:-------------------:|
| invitation                                             |         +        |         +        |          -          |
| confirmation, in reply to: <br>no-pq inv <br>pq inv    | &nbsp;<br>+<br>+ | &nbsp;<br>+<br>- | &nbsp;<br>-<br>+    |
| 1st msg, in reply to: <br>no-pq conf <br>pq/pq+ct conf | &nbsp;<br>+<br>+ | &nbsp;<br>+<br>- | &nbsp;<br>-<br>+    |
| Nth msg, in reply to: <br>no-pq msg <br>pq/pq+ct msg   | &nbsp;<br>+<br>+ | &nbsp;<br>+<br>- | &nbsp;<br>-<br>+    |

These scenarios can be reduced to:
1. initial invitation optionally has PQ key, but must not have ciphertext.
2. all subsequent messages should be allowed without PQ key/ciphertext, but:
  - if the previous message had PQ key or PQ key with ciphertext, they must either have no PQ key, or have PQ key with ciphertext (PQ key without ciphertext is an error).
  - if the previous message had no PQ key, they must either have no PQ key, or have PQ key without ciphertext (PQ key with ciphertext is an error).

The rules for calculating the shared secret for received/sent messages are (assuming received message is valid according to the above rules):

|  sent msg > <br>V received msg | no-pq       | pq      | pq+ct           |
|:------------------------------:|:-----------:|:-------:|:---------------:|
| no-pq                          | DH / DH     | DH / DH | err             |
| pq (sent msg was NOT pq)       | DH / DH     | err     | DH / DH+KEM     |
| pq+ct (sent msg was NOT no-pq) | DH+KEM / DH | err     | DH+KEM / DH+KEM |

To summarize, the upgrade to DH+KEM secret happens in a sent message that has PQ key with ciphertext sent in reply to message with PQ key only (without ciphertext), and the downgrade to DH secret happens in the message that has no PQ key.

## Connection link: 1-time invitation and contact address

Connection links are generated by SMP agent in response to `createConnection` api call, used by another party user with `joinConnection` api, and then another connection link is sent by the agent in `agentConnInfoReply` and used by the first party agent to connect to the reply queue (the second part of the process is invisible to the users).

### Full connection link syntax

```
connectionLink = connectionScheme "/" connLinkType "#/?v=" versionRange "&smp=" smpQueues ["&e2e=" e2eEncryption] ["&data=" clientData]
connLinkType = %s"invitation" / %s"contact"
connectionScheme = (%s"https://" clientAppServer) / %s"simplex:"
clientAppServer = hostname [ ":" port ]
; client app server, e.g. simplex.chat
versionRange = 1*DIGIT / 1*DIGIT "-" 1*DIGIT ; agent version range
e2eEncryption = <e2e encryption parameters for double ratchet>
smpQueues = smpQueue *(";" smpQueue) ; SMP queues for the connection (semicolon-separated)
smpQueue = <URL-encoded queueURI defined in SMP protocol>
clientData = <URL-encoded application-specific data>
```

All parameters are passed via URI hash to avoid sending them to the router (in case "https" scheme is used) - they can be used by the client-side code and processed by the client application. Parameters can be present in any order, any unknown additional parameters SHOULD be ignored.

`clientAppServer` is not an SMP router - it is a server that shows the instruction on how to download the client app that will connect using this connection link. This server can also host a mobile or desktop app manifest so that this link is opened directly in the app if it is installed on the device.

"simplex" URI scheme in `connectionProtocol` can be used instead of client app router, to connect without creating any web traffic. Client apps MUST support this URI scheme.

See SMP protocol [out-of-band messages](./simplex-messaging.md#out-of-band-messages) for syntax of `queueURI`.

### Short connection link syntax

Short links provide a more compact representation by storing connection data on the router:

```
shortLink = shortLinkScheme "/" linkType "#" [linkId "/"] linkKey ["?" shortLinkParams]
shortLinkScheme = %s"simplex:" / (%s"https://" serverHost)
linkType = %s"i" / contactType ; i - invitation, or contact type
contactType = %s"a" / %s"c" / %s"g" / %s"r" ; a - contact, c - channel, g - group, r - relay
linkId = base64url ; only for invitation links
linkKey = base64url ; SHA3-256 hash of fixed data, used to decrypt link data
shortLinkParams = hostParam ["&" portParam] ["&" keyHashParam]
hostParam = %s"h=" hostList
hostList = host *("," host)
portParam = %s"p=" port
keyHashParam = %s"c=" base64url ; router certificate fingerprint
```

Contact types:
- `a` (CCTContact) - direct contact connection
- `c` (CCTChannel) - channel connection
- `g` (CCTGroup) - group connection
- `r` (CCTRelay) - relay connection

Short links can use either the `simplex:` scheme or `https://` with a router hostname. When using the simplex scheme, router information is included in query parameters.

## Short links

Short links provide a compact representation of connection links by storing encrypted connection data on the SMP router. The link key in the URI fragment (after `#`) is never sent to the router, ensuring the router cannot decrypt the stored connection data.

### Link key derivation

The link key is derived from the fixed link data using SHA3-256 hash function:

```
linkKey = SHA3-256(fixedLinkData)
```

The fixed link data includes:
- Agent version range
- Root public key (Ed25519) for signing
- SMP queue connection request (router, queue IDs, encryption keys)
- Optional link entity ID

For contact links, the link ID and encryption key are derived from the link key using HKDF:

```
(linkId, encryptionKey) = HKDF(info="SimpleXContactLink", key=linkKey, outputLen=56)
; linkId = first 24 bytes, encryptionKey = remaining 32 bytes
```

For invitation links, the link ID is stored separately (usually included in the URI), and only the encryption key is derived:

```
encryptionKey = HKDF(info="SimpleXInvLink", key=linkKey, outputLen=32)
```

### Link data encryption

Link data stored on the router consists of two encrypted parts: fixed data and user data. Both are encrypted using NaCl secret_box (XSalsa20-Poly1305) with the derived encryption key:

```abnf
queueLinkData = encFixedData encUserData
encFixedData = largeString ; encrypted padded(signedFixedData, 2008)
encUserData = largeString ; encrypted padded(signedUserData, 13784)

signedFixedData = signature fixedData
signedUserData = signature userData
signature = length 64*64 OCTET ; Ed25519 signature

fixedData = agentVersionRange rootKey linkConnReq [linkEntityId]
agentVersionRange = version version ; min and max agent protocol version
version = 2*2 OCTET
rootKey = length x509encoded ; Ed25519 public key
linkConnReq = invitationConnReq / contactConnReq ; binary encoding of connection request
invitationConnReq = %s"I" connReqData e2eRatchetParams
contactConnReq = %s"C" connReqData
linkEntityId = shortString
userData = invitationLinkData / contactLinkData
invitationLinkData = %s"I" agentVersionRange userLinkData
contactLinkData = %s"C" agentVersionRange userContactData
userLinkData = shortString / (%xFF largeString) ; opaque application data (e.g., user profile)
  ; shortString length byte 0x00-0xFE (max 254 bytes); 0xFF is reserved as largeString sentinel
userContactData = direct ownersList relaysList userLinkData
direct = %s"T" / %s"F" ; whether direct connection via connReq is allowed
ownersList = length *ownerAuth
ownerAuth = shortString ; length-prefixed encoding of (ownerId ownerKey authOwnerSig)
ownerId = shortString ; application-specific owner ID (e.g., MemberId)
ownerKey = length x509encoded ; Ed25519 public key
authOwnerSig = length 64*64 OCTET ; Ed25519 signature of (ownerId || ownerKey) by previous owner
relaysList = length *connShortLink ; alternative relay short links

; Binary encoding of connection request (used in linkConnReq)
connReqData = agentVersionRange smpQueueUris clientData
smpQueueUris = length 1*smpQueueUri
clientData = %s"0" / (%s"1" largeString) ; Maybe (Large ByteString)
smpQueueUri = smpClientVersionRange smpServer senderId smpDhPublicKey [queueMode]
smpClientVersionRange = version version ; min and max SMP client versions
smpServer = hosts port serverKeyHash
hosts = length 1*host
host = shortString ; text-encoded hostname or IP address
port = shortString ; text-encoded port number
serverKeyHash = shortString ; CA certificate fingerprint
senderId = shortString ; queue sender ID
smpDhPublicKey = length x509encoded ; X25519 DH public key
queueMode = %s"M" / %s"C" ; messaging or contact (version-dependent trailing field)
e2eRatchetParams = e2eVersionRange e2eDhKey e2eDhKey kemParams
e2eVersionRange = version version ; min and max e2e encryption versions
e2eDhKey = length x509encoded ; X448 DH public key
kemParams = %s"0" / (%s"1" ratchetKEMParams)
ratchetKEMParams = %s"P" kemPublicKey / %s"A" kemCiphertext kemPublicKey
kemPublicKey = largeString ; sntrup761 public key
kemCiphertext = largeString ; sntrup761 ciphertext

; Binary encoding of short link (used in relaysList)
connShortLink = invShortLink / contactShortLink
invShortLink = %s"I" smpServer linkId linkKey
contactShortLink = %s"C" contactConnType smpServer linkKey
contactConnType = %s"A" / %s"C" / %s"G" / %s"R" ; contact / channel / group / relay
linkId = shortString
linkKey = shortString

x509encoded = *OCTET ; DER-encoded X.509 SubjectPublicKeyInfo
largeString = 2*2 OCTET *OCTET ; Word16 length prefix
length = 1*1 OCTET
shortString = length *OCTET
```

The fixed data is signed with the root key and its hash becomes the link key. The user data is signed either with the root key (for invitations) or with an owner key (for contact addresses).

### Short link resolution

When a user receives a short link, the agent resolves it as follows:

1. Extract the link key from the URI fragment
2. Send `LGET` command to the SMP router with the link ID
3. Receive encrypted link data from the router
4. Decrypt the link data using the link key
5. Extract the full connection information (SMP queue URI, encryption keys, profile)
6. Proceed with the standard connection procedure using `joinConnection`

For invitation links, the `LKEY` command is used to set the sender key when getting link data. Repeated `LKEY` would require using the same key.

### Link data management

The recipient who created the queue can manage the short link data:

- **LSET** - Set or update the link data associated with a queue. This is used when creating a short link or updating the user data (e.g., profile changes).
- **LDEL** - Delete the link data from the router. This effectively invalidates the short link.

Short links support different connection modes:
- **invitation** - One-time invitation links that can only be used once
- **contact** - Reusable contact address links that can be used multiple times

For contact addresses, the link data includes additional information about the contact type:
- **contact** - Direct contact connection
- **channel** - Channel connection
- **group** - Group connection
- **relay** - Relay connection

The agent maintains the link data and updates it when connection parameters change, ensuring short links remain valid and reflect current connection information.

## Appendix A: SMP agent API

The exact specification of agent library API and of the events that the agent sends to the client application is out of scope of the protocol specification.

The list of some of the API functions and events below is supported by the reference implementation, and they are likely to be required by the client applications.

### API functions

The list of APIs below is not exhaustive and provided for information only. Please consult the source code for more information.

#### Create connection

`createConnection` api is used to create a connection - it returns the connection link that should be sent out-of-band to another protocol user (the joining party). It should be used by the client of the agent that initiates creating a duplex connection (the initiating party).

This api is also used to create a contact address - a special connection that can be used by multiple people to connect to the user.

Some communication scenarios may require fault-tolerant mechanism of creating connections that retries on network failures and continue retrying after the client is restarted. Such asynchronous API would return its result via `INV` event once it succeeds.

#### Join connection

`joinConnection` is used to create a connection record and accept the connection invitation received out-of-band. It should be used by the client of the agent that accepts the connection (the joining party).

This api can also be required as asynchronous, in which case `OK` event will be dispatched to the client to indicate the success or `ERR` in case it permanently failed (e.g., in case connection was deleted by another party).

#### Allow connection

Once the client receives `CONF` event, it should use synchronous `allowConnection` api to proceed with the connection (both for the [standard](#duplex-connection-procedure) and for the [fast duplex procedure](#fast-duplex-connection-procedure)).

In case this API is used as asynchronous it will return its result via `OK` or `ERR` event.

#### Accept and reject connection requests

Connection requests are delivered to the client application via `REQ` event.

Client can `acceptContact` and `rejectContact`, with `OK` and `ERR` events in case of asynchronous calls.

#### Send message

`sendMessage` api is always asynchronous. The api call returns message ID, `SENT` event once the message is sent to the router, `MWARN` event in case of temporary delivery failure that can be resolved by the user (e.g., by connecting via Tor or by upgrading the client) and `MERR` in case of permanent delivery failure.

#### Acknowledge received message

Messages are delivered to the client application via `MSG` event.

Client application must always `ackMessage` to receive the next one - failure to call it in reference implementation will prevent the delivery of subsequent messages until the client reconnects to the router.

This api is also used to acknowledge message delivery to the sending party - that party client application will receive `RCVD` event.

#### Subscribe connection

`subscribeConnection` api is used by the client to resume receiving messages from the connection that was created in another TCP/client session.

#### Get notification message

`getNotificationMessage` is used by push notification subsystem of the client application to receive the message from a specific messaging queue mentioned in the notification. The client application would receive `MSG` and any other events from the agent, and then `MSGNTF` event once the message related to this notification is received.

#### Set short link data

`setConnectionLink` api (`LSET` command) is used to set or update short link data associated with a contact address queue. Returns `LINK` event with the short link URI.

#### Get short link data

`getConnectionLink` api (`LGET` command) is used to retrieve and decrypt the short link data from the router. Returns `LDATA` event with the decrypted link data.

#### Rotate message queue to another router

`switchConnection` api is used to rotate connection queues to another messaging router.

#### Renegotiate e2e encryption

`synchronizeRatchet` api is used to re-negotiate double ratchet encryption for the connection.

#### Delete connection

`deleteConnection` api is used to delete connection. In case of asynchronous call, the connection deletion will be confirmed with `DEL_RCVQS` and `DEL_CONNS` events.

#### Suspend connection

`suspendConnection` api is used to prevent any further messages delivered to the connection without deleting it.

### API events

Agent API uses these events dispatch to notify client application about events related to the connections:
- `INV` - connection invitation or connection address URI after connection is created.
- `CONF` - confirmation that connection is accepted by another party. When the accepting party uses `joinConnection` api to accept connection invitation, the initiating party will receive `CONF` notification with some identifier and additional information from the accepting party (e.g., profile). To continue the connection the initiating party client should use `allowConnection` api.
- `REQ` - connection request is sent when another party uses `joinConnection` api with contact address. The client application can use `acceptContact` or `rejectContact` api.
- `INFO` - information from the party that initiated the connection with `createConnection` sent to the party accepting the connection with `joinConnection`.
- `CON` - notification that connection is established sent to both parties of the connection.
- `END` - notification that connection subscription is terminated when another client subscribed to the same messaging queue.  
- `DOWN` - notification that connection router is temporarily unavailable.
- `UP` - notification that the subscriptions made in the current client session are resumed after the router became available.
- `SWITCH` - notification about queue rotation process.
- `RSYNC` - notification about e2e encryption re-negotiation process.
- `SENT` - notification to confirm that the message was delivered to at least one of SMP routers. This notification contains the same message ID as returned to `sendMessage` api. `SENT` notification, depending on network availability, can be sent at any time later, potentially in the next client session.
- `MWARN` - temporary delivery failure that can be resolved by the user (e.g., by connecting via Tor or by upgrading the client).
- `MERR` - notification about permanent message delivery failure.
- `MERRS` - notification about permanent message delivery failure for multiple messages (e.g., when multiple messages expire).
- `MSG` - sent when agent receives the message from the SMP router.
- `MSGNTF` - sent after agent received and processed the message referenced in the push notification.
- `RCVD` - notification confirming message receipt by another party.
- `QCONT` - notification that the agent continued sending messages after queue capacity was exceeded and recipient received all messages.
- `LINK` - short link URI created or updated for a contact address.
- `LDATA` - decrypted short link data received from the router.
- `DELD` - notification that the connection was deleted.
- `JOINED` - notification that a member joined via a contact address.
- `STAT` - connection statistics event.
- `DEL_RCVQS` - confirmation that receiver message queues were deleted.
- `DEL_CONNS` - confirmation that connections were deleted.
- `OK` - confirmation that asynchronous api call was successful.
- `ERR` - error of asynchronous api call or some other error event.

This list of events is not exhaustive and provided for information only. Please consult the source code for more information.

## Threat model

This threat model complements SimpleX Messaging Protocol [threat model](./security.md#threat-model) with agent-level concerns: duplex connections, end-to-end encryption with [post-quantum double ratchet](./pqdr.md), message integrity, connection establishment and queue rotation. Only additional properties not covered in the SMP threat model are listed below.

#### Additional global assumptions

 - The connection link is shared via a trusted out-of-band channel.
 - Both agents support post-quantum double ratchet (PQDR).

#### A passive adversary

*cannot:*
 - learn the contents of packets, which are additionally encrypted with the double ratchet independently from per-queue encryption.

#### Destination router (chosen by the receiving client application)

*can:*
 - correlate queues belonging to the same duplex connection when queue rotation creates a new queue on the same router.
 - when both peers of a connection chose the same router, correlate the two directions of the duplex connection.

*cannot:*
 - compromise end-to-end encryption even with full access to the per-queue NaCl DH secret.
 - correlate queues belonging to the same connection after queue rotation to a different router.

#### An attacker who obtained a client application's (decrypted) database

*can:*
 - learn the full communication graph: all communication peers, associated router addresses, and queue identifiers.

*cannot:*
 - decrypt future messages once the client application resumes communication and the double ratchet completes a new ratchet step, provided PQDR is active.

#### A communication peer

*can:*
 - send malformed agent messages that may affect the client application processing them.
 - skip message IDs, causing the recipient to generate and store excessive intermediate ratchet keys.
 - prevent double ratchet advancement by not sending messages, delaying break-in recovery.

*cannot:*
 - disrupt packet delivery in other queues.

#### An attacker who obtained a connection link

*can:*
 - learn the initiating party's chosen router address and public keys.

*cannot:*
 - use the link after the intended recipient has completed the connection.

[1]: https://en.wikipedia.org/wiki/End-to-end_encryption
[2]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
[3]: https://tools.ietf.org/html/rfc5234
[4]: https://tools.ietf.org/html/rfc7405
