Speeding up crypto on Wire desktop apps

One of the defining principles behind web technologies is building things once and making them available everywhere regardless of the device or platform. This inspired us to build a web version of Wire that anyone could use in a WebRTC-enabled browser.

However, there’s one major challenge with web applications. JavaScript, the web’s programming language, was built for programmer productivity at the expense of performance efficiency. It was the right decision by Brendan Eich when he created JavaScript in 1995 to make interactive websites a reality.

But the internet has evolved since then, and so have the expectations of users and web developers. Hardware access, offline operation, and real-time communication are the new norm for developers, and JavaScript serves as the foundation for these technologies.

Since JavaScript is an interpreted language, code compilation is done at runtime. This requires extra computational time during program execution compared to compiled programming languages. The impact depends on the application — the speed difference is not very noticeable when displaying static images or text, but can have a significant effect during cryptographic calculations.

Wire’s end-to-end encryption protocol Proteus, originally written in Rust, makes use of cryptographic primitives from the implementations of HMAC-SHA256, Curve25519 and HKDF. These algorithms are essential for everything that happens in Wire and are provided by the Sodium crypto library.

The Sodium library is available for numerous platforms — a native version written in C (libsodium), bindings for Rust (rust_sodium) and a pure JavaScript version (libsodium.js), which has been compiled using Emscripten.

The Rust version of Proteus is tightly integrated in our mobile apps and uses the Rust bindings to libsodium, which are significantly faster than their JavaScript counterparts:

Neon is the one

This speed difference between libsodium and libsodium.js forced us to look for alternatives that could increase the encryption and decryption speed in our web application. Fortunately we came across Neon, a Rust abstraction layer for native Node.js modules.

As Wire desktop apps are built with Electron (which runs Node.js), using Neon proved an ideal way to achieve near-native encryption and decryption performance.

We created a Node.js module that provides a JavaScript interface, which can talk to the Rust bindings of libsodium. This interface is publicly available as “libsodium-neon” on GitHub.

The reason for using rust_sodium instead of the well-known sodiumoxide is that we rely on libsodium’s system functions — which means every Wire user would need to have libsodium installed, or libsodium-neon would not work at all. However, rust_sodium provides an option to download libsodium in advance and integrate it into the finished Node.js module. That’s the only functionality that sets it apart from sodiumoxide.

Up to 141× faster

We then analyzed the cryptographic functions according to their performance in interpreted code (provided by libsodium.js), compiled code for Node.js (provided by libsodium-neon) and compiled code for native programs (provided by rust_sodium). The benchmark results were very positive:

Tested with Linux Debian 9, 64-bit (2.7 GHz Intel Core i7, 16 GB RAM)

As expected, rust_sodium is the fastest. It’s the big difference between libsodium-neon and libsodium.js that was a pleasant surprise. In almost all the functions the difference in speed is significant — up to 141 times faster.

The additional benefit is that we gain type safety as a result of the Rust bindings that are used by libsodium-neon.

Both of these advantages made Neon a very appealing choice. With a strict adherence to a well-defined interface, replacing existing calls to libsodium.js with libsodium-neon was as easy as this:

libsodium.js

const sodium = require('libsodium-wrapper-sumo');
sodium.crypto_sign_verify_detached(signature, message, this.pub_edward);

libsodium-neon

const sodium = require('libsodium-neon');
sodium.crypto_sign_verify_detached(signature, message, this.pub_edward);

After implementing these changes, Wire on macOS and Linux is visibly faster on startup for example, when decrypting 1000 new messages:

Captured from the internal desktop build to show speed difference.

We’re happy to share that libsodium-neon has been already integrated into the latest versions of our macOS & Linux apps. Windows will get the update soon.

A big “Thank You!” to Dave Herman, the creator of Neon, who’s been collaborating with us on speeding up encryption in Wire.

The next step is to research a solution to bring similar speed improvements to the web version of Wire. You can keep an eye on our progress and check out our code on GitHub.

Benny Neugebauer, Florian Keller — Wire web/desktop team