What is the River Encryption Protocol?

The River protocol supports end-to-end encryption and decryption between a group of user devices. When a user (Bob) wants to send a new message to a group of users, Bob’s device first creates a new group session. Using the outbound session key, it encrypts the message. Then the encrypted message is sent to the group.

When a recipient (Alice) gets the encrypted message, her device will start a new group session to import the inbound session key, and then use it to decrypt the message.

But how does Alice’s device gets the inbound session key in the first place?

The following section describes the inner workings of the group encryption protocol. It explains how Alice’s device makes a key solicitation request to get missing session keys. It also covers how other devices in the group can share session keys in a process called key fulfillment after checking that Bob is a group member.

Before diving into the key solicitation, key fulfillment, and key sharing algorithms, let’s first take a look at the core entities in the group encryption protocol.

Core entities in the River Encryption Protocol

GroupEncryptionCrypto : The main interface of the protocol. It initializes the EncryptionDelegate. This class “delegates” the encryption and decryption operations to the olm library from the Matrix.org foundation. This library implements the Double Ratchet algorithm. See notes on supported algorithm for future plans.

The River Encryption Protocol uses this libray to create a group session, and perform device-to-device encryption using the session keys.

The GroupEncryptionCrypto creates a GroupEncryption, a GroupDecryption, and an EncryptionDevice to handle the group encryption protocol:

  • GroupEncryption : handles group encryption using session keys. Outgoing messages are encrypted with outbound session keys.
  • GroupDecryption : handles group decryption using session keys. Incoming messages are decrypted with inbound session keys.
  • EncryptionDevice : interfaces with the EncryptionDelegate to perform cryptographic operations. It also uses the CryptoStore to get and save the inbound / outbound session keys.

Encrypting a message with GroupEncryption

A device will need an outgoing session to encrypt a message to the group. If it does not already have a session, it must create one. In addition, it will also create an inbound session. The inbound session is encrypted and sent to other devices in the group.

Decrypting a message with GroupDecryption

Key solicitation and key fulfillment

If a device does not have any session keys, it can make a KeySolicitation request to the group. Any device that is “online” at that moment can share its known session keys, and send a KeyFulfillment response to inform others in the group that the KeySolicitation request has already been fulfilled.

Continuing our example, suppose Alice’s device does not have the session key to decrypt a message from Bob. Alice’s device posts a KeySolicitation request to the stream. Bob’s device happens to be online at the moment. When it sees the request, it processes the request as follows:

Thus, Alice’s device is able to get the required session keys for decryption.


See river-tutorials/encryption-import for a tutorial on how to import the River Encryption npm package.


Supported algorithm

The River Encryption Protocol is designed to support new algorithms. There is an algorithm field in the protocol definition. The current value is r.group-encryption.v1.aes-sha2. This means that the protocol is using the olm Double Ratchet library for device-to-device session encryption. This field can be set to new algorithms to support future needs.

// protocol.proto
message EncryptedData {
    // ...
    * Encryption algorithm  used to encrypt this event.
    string algorithm = 2;
    // ...