An open source federated messaging protocol, with a focus on encryption, self-soverign identity, privacy, and ability to migrate homeservers.
Messages are arbitrary payloads - they can be arbitrary information. Common use cases include user's current social VR lobby, their current user bio, their avatar, direct messsages, microblogging - its up to what you as a developer want to support.
You can even tell users "hey, talk to me using this other entirely different protocol that is specific to my game", using the Nexus protocol merely to coordinate that handoff (likely to be very common when joining a multiplayer lobby in video games, for example).
Apps and games often run out of money and shutdown, or just never become popular due to the powerful impact of network effects inhibiting adoption.
By using a federated system instead of a centralized one, users can self-host servers (reducing costs for developers), access new apps with low friction, and seamlessly engage in social interactions across different apps. The interoperability will lead to a dilution of the aforementioned network effects, helping smaller indie or open source games and apps, and also improve users ability to migrate between ecosystems.
The basic features in Nexus are:
- Create an account/identity that you can prove you own
- Migrate identity to new keys and new hosts
- Migrate your homeserver
- Automatically listen for homeserver migrations of people you know
- Send and receive messages to others
- End-to-end encryption
- Sign messages
- A concept of "avatars", "lobbies", etc. This information is to be represented by the application-specific schemas for the message payloads. We will be providing separate JSON schemas that developers can adopt for ineroperable ways to represent this info.
- If possible to hide information from a party that doesn't need to see it, we should
- We want to trust homeservers and identity providers as little as possible
- We want the system to be federated, which means there is no single group that holds control.
- We will (eventually) use public key cryptography and Decentralized Identifiers (DID protocol) to allow users to sign and encrypt messages/data.
There are several pieces to the software, described below as an overview.
There are servers (homeservers) and clients (users). Each user belongs to a homeserver, although they can migrate to different homeservers. A user might choose to migrate to a different homeserver for any reason, most commonly because the homeserver is dead, performing poorly, untrustworthy, or moderating unfairly.
Clients talk with their homeservers over a Client-Server HTTP api, using JSON-LD. Some of these messages terminate at the homeserver, and some are queued to be sent to other homeservers, which in turn deliver them to other clients.
There is also a Server-Server HTTP api, with JSON-LD as the payload. This is the part of the protocol that performs federation amongst servers.
Clients are generally fairly transient and have less state than servers. Also, the client-server api should be simpler than the server-server api, making it easier to develop a client. Clients are generally run as part of a game or program on an end-user's computer.
The servers are hosted by technical individuals, often community members invested in a game that uses Nexus, and might be a paid or free service. The servers set their own access policies for clients, rate limiting, etc. We might add this into the spec to have a standardized way of doing this.
Decentralized Identifiers are a way of representing in a global namespace a user's identity. They typically also a way for users to describe a cryptographic public key, which corresponds to a private key they keep safe and hidden. The DID provides a mechanism to resolve this public key + metadata.
Using their DID, users can sign messages they send (proving that the message was not tampered and that they are the original author), and they can encrypt messages to other uses using the other user's DID.
DIDs can take many forms - some are just public keys in base64 encoding, some
are links to web servers that serve the public keys, some are stored on
blockchains or cyrptographic file systems like IPFS. For this implementation, we
chose two use solutions that don't require any sort of blockchain - did:key
and did:web
.
Users will add a new DID by sending a message to their homeserver and all their friends The same goes for removal. Users can have multiple DIDs. They need at least one. They can remove old DIDs.
Note that did:web
is served by a HTTP api and that server could change the
keys of the user it is representing. This is both a good thing (allows users to
request their server admin to replace the public key for them, if they forgot
the old one), and a bad thing (server hosters must be trusted to not steal
accounts from users by replacing the public key served with one that corresponds
to a private key that the server hoster controls).
did:key
has no such indirection, and encodes the key directly in the identity.
But this means that if the private key is lost or leaked, the user has no
recourse other than deleting that did:key
from their account via a DID removal
message (see later in the document), and risking a possiblity of that message
not fully propogating to all of their friends.
TODO: Uncertain about this paragraph. Homeservers also use DIDs, but they are
restricted to only did:web
as they should manage their own keys and this
allows the protocol to guarantee that each homeserver has one and only one DID.
Note again, that did:web
allows the homeserver to swap its public-private
keypair at any time.
Every message is signed first by its original sender and then the sender's homeserver.
TODO: Should it also be signed by the recipient's homeserver too? TODO: What
happens when did:web
changes its referenced key? Wont validating the message
fail?
Unlike a regular peer to peer network, a federated network allows clients to avoid communicating directly with other clients. This means clients only have to reveal their ip address to their own homeserver, but not to other random users. Note that because messages sent between users are generic JSON-LD payloads, the sender could provide a link to a server they control, and if the user retrieves the data at the link, their ip address would be leaked.
To mitigate this, users might be able to request that their homeservers download linked documents on their behalf, so the user never directly connects to those resources.
For any message, not just DID migration messages, recipients validate the signature of the message using their current understanding of the sender's DIDs.
If they encounter a message that doesn't match one of the last known DIDs of the sender, one of a few scenarios must be happening:
- The recipient's homeserver is malicious, and sending them fake did migrations.
- The recipient has been offline for so long that their homeserver could not hold the undelivered messages any longer and had to skip some.
- The recipient's homeserver was offline for so long that when it came back online and pulled DID updates from all its neighbor homeservers, those neighbor homeservers had ran out of space buffering the updates, and had to skip some.
- Someone is spamming the recipient with fake messages.
These can be differentiated accordingly: ... TODO: I don't understand how we
solved this in the face of DID migraton. If the keys can change (both in did
migration and when did:web
changes the referenced key), then how can we really
know anything?
Each user's "username" / "identity" is uniquely described by a "root" DID. Two examples:
did:web:pki.example.com/alice#nexus-root-key
did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbrkpaRsa5Pmq4uMGcfH8ZpCr17NLSQ65Y4KwFmeMPK8MsqitrEFGBPzJDLBu49faYwiokzH43yBMjNQCf5PcTS8urcdLuFqjLKiX1kHGWn9NMoM6WbPMnbqdfwu7Eo9THMe
NOTE: the root DID is the DID URI, and NOT necessarily the DID document itself. Every DID URI resolves to a DID resource - in the case of
did:key
this DID resourece is just the public keys encoded - without a need for any indirection. Whereas indid:web
the DID document is retrieved via a GET request, adding a layer of indirection.Critically, this means that
did:web
trades away security for convenience - it is able to have complete control over what it returns in the GET request, so the actual public key can change indid:web
. This is good for users because it means they can log into their identity server and upload a new public key in case they forgot or lost or leaked their private key.
However, this root identity is very sensitive and losing control over it means
losing access to your account, or worse, getting impersonated. So for day to day
affairs we generally will have a key-chain instead of one single root key. The
user will generate another DID, give it some permissions, and the root key will
sign that (did, perms)
tuple so others know it is valid. The child key(s) can
then also sign additional child keys, forming a tree of keys with the root key
at the root. This allows a lot of flexibility. Parent keys can also easily
revoke child keys.
For example, I may use a did:key
as my root DID, ensuring that I have final
control over my account. I want to keep this private key very safe - I print out
the private key, and bury it in random holes across the country. This is because
if I lose control over the DID, I lose my whole account.
Obviously I do not want to keep the private key on my computer, because it is
going to be used for everything in the games I play, and I don't trust that I
won't get hacked. So I take my private key and sign that I am allowing a
different DID to control my account - did:web:did.thebutlah.com/nexus#my-key
.
I actually own thebutlah.com
- I purchased the domain name and have it
registered with DNS and letsencrypt. I go ahead and delete my root private key
from my computer, it is safe in the paper backups.
I update my DNS to redirect did.thebutlah.com
to another location called
thebutlah.github.io/my-keys/nexus/did.json
. I get to login over OAuth2 to
github, upload a new public key at that URL, and better yet any server can
easily retrieve the public key because its hosted by github - I dont need to run
a webserver. I save the corresponding key to my google drive for safe keeping -
its a lot easier to use the google drive for stuff than unburying the paper
backup of the root key.
Finally, I use the github key to sign a did:key
that I'll store on my personal
computer. This is the leaf node of this chain and I will use it to actually send
and receive messages on Nexus. This is the key that I use day to day.
If I ever get my computer hacked, I can invalidate it and re-issue a key from
github. If github ever goes down or becomes evil, I can change my dns record to
point to a different web hoster. If I forget to pay my domain host and they
assign my domain to someone else, I can dig up my paper backup, invalidate my
did:web:did.thebutlah.com
, and reissue a new DID.
Hopefully I demonstrated that the strategy is very flexible, and ultimately as long as the root key remains intact and in control, accounts can conduct business, get child keys stolen, and still have recourse.
We will not attempt global consensus, but rather lazy consensus. When you friend a user, you send
Then I
The protocol is split into several layers:
At this layer, the server is essentially just a relay + queue of HTTP requests.
Client:
- Client polls its homeserver via HTTP to receive messages.
- Client sends messages to
(DID, protocol, homeserver)
s by mapping the tuple to an HTTP endpoint, and making an HTTP request. - Responsible for converting DID Message format to a HTTP Request
Server:
- Outbound and inbound message queues.
- Successfully sending messages to other homeservers pops them from outbound.
- Successfully responding to a poll from client pops them from inbound.
- Messages can timeout.
- Outbound messages will be sent with exponential backoff.
- Outbound messages sent via HTTP request
- Inbound messages sent as response to HTTP request from client.
Responsible for encrypting, decrypting, signing, and verifying messages. Handles DID chain migration.
TODO: This is all no-ops for now. We figured it out already but will document and implement it soon.
Responsible for keeping track of the homeserver of other DIDs an application protocol cares about.
Client
- Send our homeserver migration notification to a set of
(DID, protocol)
s. - Send
(DID, protocol)
messages. - Add a known user at
(DID, protocol, homeserver)
to local list and also to homeserver.
Server:
- Check for receipt of
- Stores the map of
DID -> protocol -> homeserver
- Ta
- Maps
(DID, protocol)
to/from(DID, protocol, homeserver)
This is whatever you want. Often, friend requests, follow/follows, specific message types, etc go here.
There are many possible protocols at the Application Layer. However, we will be explaining one in particular that is likely to be very useful - the Discovery Protocol.
Provides a way of asking a single homeserver for the full list of tuples of
(protocol, homeserver)
for a given DID. This is for convenience - you only
need to remember the discovery protocol homeserver for a given DID to learn all
of the homeservers for its other protocols.
Security note: Relying on this is good for convenience, but means that if a
client migrates its discovery homeserver, it should keep a cache of
DID -> protocol -> homeserver
to notify those DIDs of the migration via the
DID Location Layer.
Unless otherwise specified, all code in this repository is dual-licensed under either:
- MIT License (LICENSE-MIT)
- BSD 2-Clause Plus Patent License (LICENSE-BSD)
at your option. This means you can select the license you prefer!
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be dual licensed as above, without any additional terms or conditions.