x3dh
TypeScript implementation of X3DH, as described in Going Bark: A Furry's Guide to End-to-End Encryption.
The install size is kind of large because this package includes a pre-bundled sodium
.
fork
This is a fork of soatok/rawr-x3dh.
Contents
What's This?
This library implements the Extended Triple Diffie-Hellman key exchange, with a few minor tweaks:
- Identity keys are Ed25519 public keys, not X25519 public keys. See this for an explanation.
- Encryption/decryption and KDF implementations are pluggable (assuming you implement the interface I provide), so you aren't married to HKDF or a particular cipher. (Although I recommend hard-coding it to your application!)
Installation
npm i -S @bicycle-codes/x3dh
If you're working server-side, you'll also want to install sodium-native, so that sodium-plus will run faster.
If you're working in a browser or browser extension, don't install sodium-native.
Usage
First, you'll want to import the X3DH class from our module.
import { X3DH } from '@bicycle-codes/x3dh'
const x3dh = new X3DH()
Note: You can pass some classes to the constructor to replace my algorithm implementations for your own.
import { X3DH } from '@bicycle-codes/x3dh'
const x3dh = new X3DH(
sessionKeyManager, /* SessionKeyManagerInterface */
identityKeyManager, /* IdentityKeyManagerInterface */
symmetricEncryptionHandler, /* SymmetricEncryptionInterface */
keyDerivationFunction /* KeyDerivationFunction */
)
Once your X3DH object's instantiated, you will be able to initialize handshakes either as a sender or as a recipient. Then you will be able to encrypt additional messages on either side, and the encryption key shall ratchet forward.
const firstEncrypted = await x3dh.initSend(
'recipient@server2',
serverApiCallFunc,
firstMessage
);
The serverApiCallFunc
parameter should be a function that sends a request to the server
to obtain the identity key, signed pre-key, and optional one-time key for the handshake.
See the definition of the InitClientFunction
type in lib/index.ts
.
Once this has completed, you can call encryptNext()
multiple times to append messages
to send.
const nextEncrypted = await x3dh.encryptNext(
'recipient@server2',
'This is a follow-up message UwU'
);
On the other side, your communication partner will use the following feature.
const [sender, firstMessage] = await x3dh.initRecv(senderInfo);
const nextMessage = await x3dh.decryptNext(sender, nextEncrypted);
Note: initRecv()
will always return the sender identity (a string) and the
message (a Buffer
that can be converted to a string). The sender identity
should be usable for decryptNext()
calls.
However, that doesn't mean it's trustworthy! This library only implements the X3DH pattern. It doesn't implement the Gossamer integration.
Should I Use This?
Don't use it in production until version 1.0.0 has been tagged. The API can break at any moment until that happens (especially if I decide I hate the default key management classes I wrote).
However, feel free to test and play with it.