Skip to content


Repository files navigation



Netlify Status CI

Identities in Backchannel are created by redeeming a one-time invitation code. The code, much like the role of a phone number, is a lookup method to connect two people. Unlike a phone number, the code is temporary, and immediately discarded after use. The expiration of invitation codes gives certain advantages. There is no equivalent of “giving someone your permanent address or number” with this design. Although perhaps inconvenient for some use cases, this property can also be a strength. This design is inherently resistant to anonymous spam.

Once a user passes this code to another, both of their applications generate a strong shared cryptographic secret using SPAKE2, a PAKE protocol. This shared secret represents the relationships between those two users, and is used to generate a secure end-to-end encrypted connection.

Users then assign a name for each other, rather than naming themselves. Names in Backchannel are private – i.e., they are only seen by the person who created them, just like in a phone’s contact list. Naming contacts privately is important. Because there is no self-described user profile system, a user cannot be impersonated by someone else within the application.

Read more about Backchannel in our paper, at

Application prototype examples


This library is intended for use in a browser-like environment (progressive web app and electron would work too) as a websocket client.

npm install @inkandswitch/backchannel


Get a list of contacts

import Backchannel, { EVENTS } from '@inkandswitch/backchannel';

const DBNAME = 'myapp'
const SETTINGS = {
  relay: 'ws://localhost:3001'
let backchannel = new Backchannel(DBNAME, SETTINGS)
backchannel.once(EVENTS.OPEN, () => {
  let contacts = backchannel.listContacts()

Two users decide upon a single number that is at least 6 characters long OR generate a one-time invitation code for them:

let random = crypto.randomBytes(3)
let code = parseInt(Buffer.from(random).toString('hex'), 16)

Once both users have entered in a code, split the code into 'mailbox' and 'password' pieces. The password should be kept secret! The mailbox is public to the relay and so should not be derivative or in any way related to the password.

let mailbox = code.slice(0, 3)
let password = code.slice(3)
let key = await backchannel.accept(mailbox, password)

Add a contact and assign them a name and avatar

let id = await backchannel.addContact(key)
await backchannel.editName(id, 'jennie')
await backchannel.editAvatar(id, [avatarbytes])

Send an end to end encrypted message

let message = await backchannel.sendMessage(id, 'hi jennie')

Link a device, this works the same as adding a contact, but you call addDevice instead

let key = await backchannel.accept(mailbox, password)
let id = await backchannel.addDevice(key)

Unlink all devices

backchannel.on(EVENTS.ACK, ({ contactId }) => {
  console.log('Unlinked device with id=', contactId)
await backchannel.unlinkDevice()

Running a Relay

Backchannel depends upon @local-first/relay, which is a websocket relay. Clients need to connect to the same relay in order to find each other. You can run the relay either on the local machine or in the cloud. One way to do this in the cloud quickly is to remix it on Glitch.

You can use the relay provided in this repository at bin/server.js:

const { Server } = require('@localfirst/relay/dist')

const DEFAULT_PORT = 3001 
const port = Number(process.env.PORT) || DEFAULT_PORT

const server = new Server({ port })


And run it with

$ node bin/server.js


Viewing the Documentation

The docs can be auto-generated locally. The following command outputs the generated documentation into build/docs/api.

npm run docs

Then you can view the docs locally with an http-server, you can use something like the node http-server.

npm i -g http-server
http-server build/docs/api

Or with npx:

npx http-server build/docs/api

You can then view the main documentation locally at http://localhost:port/classes/backend_backchannel.backchannel.html


Open two browser windows that are not in private browsing mode. They can be tabs in the same browser program. Opening a private window doesn't work with IndexedDB.

Because we're using IndexedDB, to do local testing with the same browser on the same machine, you should open one of the tabs or windows at localhost:3000 and the other at This will ensure that they both have their own isolated database.

To run automated tests,

npm run relay

and then

npm test


To deploy the minified production build, run

npm run build

To build the api documentation, run

npm run docs


  • Karissa McKelvey, @okdistribute, Lead
  • Ben Royer, Design
  • Chris Sun, @daiyi, Frontend/UI


  • Cade Diehm
  • Peter van Hardenberg, @pvh
  • سلمان الجماز, @saljam
  • Herb Caudill, @herbcaudill
  • Martin Kleppman, @ept