Delphus, our secure, decentralized research manager, just got a very important new feature: end-to-end encrypted and federated chat, voice, and video calls, powered by Matrix and Riot. Here’s how we built it.

Why Matrix?

End-to-end encryption is necessary for our private researcher-participant communications, where sensitive information is often exchanged. This made Matrix, one of the only chat protocols that truly offers end-to-end encryption, our ideal choice. Its encryption library has been audited by NCC Group and is supported on all platforms, including web, iOS, and Android. Its mission to create a global, open “matrix” of communication also fits well with our own goal to unify and share scientific research.

Further, Matrix’s Synapse server is open source, allowing us to self-host it. It has several useful administration features, such as the ability to send system alerts to all of our users or to automatically add users to a server-wide discussion or announcement room.

Matrix also has a featureful first-party client: Riot. We opted to integrate this client into our app instead of coding one ourselves to take advantage of their end-to-end encryption support and extensive efforts to improve the user experience. Riot also supports voice and video calls through WebRTC.

Integration

Unfortunately, Riot is not itself designed to be embeddable. It currently does not expose any hooks to tell when the application is loaded or to trigger specific UI actions.

Since we wanted to integrate it into our application—for example, by adding links to start chats with participants or researchers—we added these hooks by forking Riot. (Changes are open source on our GitLab instance at delphus-riot-web and delphus-matrix-react-sdk.)

At specific locations, such as to mark when the user has logged in, our fork of Riot sends a cross-frame message to Delphus:

{
    type: "LOAD_COMPLETE",
    matrixId: this._matrixClient.credentials.userId
};

Using an event listener, the main Delphus application picks up these messages and triggers an action. We save the current load state (loaded, signed in) of Riot in our local state so that buttons in the app can know when Delphus Chat is loaded, e.g. to show a specific chat window.

To show notifications from Riot, we added a similar notification cross-frame message, which is used to update a counter in our code and show a notification badge on the Delphus Chat button (The user can also enable desktop notifications in Riot, which will show notifications as popups on their computer).

To open specific views in Riot, we programmed a way to manipulate the current location of the iframe, since Riot stores its view in the URL. For example, viewing the Matrix HQ (a general chat room about Matrix) is at the URL /#/room/#matrix:matrix.org, while viewing my profile is at the URL /#/user/@kevin:chat.delph.us. By manipulating the iframe URL, we can effectively remote-control Riot.

Identity Verification

However, in order to let participants chat with researchers (or vice-versa), we still needed a way to link a user’s Ethereum address (their pseudonymous identifier on our platform) with a Matrix ID (a username on the Matrix side). We decided to write a simple smart contract to handle this:

pragma solidity ^0.5.0;

/// @dev Stores mappings of addresses <-> Matrix IDs as part of the chat
/// system.
contract ChatRegistry {
    mapping (address => string) public matrixIds;

    function setMatrixId(string calldata _mxid) external {
        matrixIds[msg.sender] = _mxid;
    }
}

This Ethereum smart contract maps a user’s Ethereum address to their Matrix ID.

Then, when we receive the LOAD_COMPLETE message from Riot, containing the user’s Matrix ID, we can compare it to the one saved by ChatRegistry. If the two differ, Delphus triggers a transaction to update the registry.

Thus, when we show Ethereum addresses on other parts of the site (such as when we display the researcher or participants in a study), we can include a “Start chat with this user” button to provide seamless integration. This button will automatically open Riot, load it if not loaded (since Riot is lazy-loaded to reduce the performance impact), and navigate to the specified user.

The end result is that we have a user-friendly, integrated embedded form of Riot on Delphus, allowing users to easily start encrypted chats, voice calls, or video calls with other users on the site.

Interested in learning more about Delphus? Contact us at info@scintillating.us, or sign up for our mailing list. If you are interested in learning more about Matrix, check out their blog as well.

Leave a Reply