The Federated GM Service
An interoperable inbox for Aptos
The Federated GM Service (FGS) is a protocol for providing secure and anonymous communication between addresses on Aptos and an Interoperable inbox that can be used across dApps.
Federated here is used to note the fact that a lot of the operations on the protocol run off the main blockchain, and on a federated network of servers.
This document will take you through how we tackle the following important aspects:
Identity
Anonymity and Message Delivery
Encryption and Verification
Identity
There are 2 main entities in the FGS protocol:
Nodes
These are servers that host the FGS protocol. They are registered on chain but run the protocol off chain. Will typically be controlled by a dApp that's integrating the FGS protocol for its users. Node's provide storage, validation and delivery of messages submitted by users on the network. Node's have an inbox/outbox system, where all incoming messages from other nodes, gets put in its inbox and all outgoing communication will get put in its outbox after successful delivery to other nodes.
An example node setup scenario:
Poseidon, a client of Kade wants to add end to end encrypted direct messaging for their users.
In order to join the FGS network, Poseidon will first need to register its namespace on chain so that other nodes running the protocol can recognise messages targeted to Poseidon users, and so that other servers can be able to verify all messages coming from Poseidon.
To do this the developers would register their namespace "poseidon" through the fgs protocol smart contract on Aptos. Think of this like registering a domain name with ICANN.
They will provide the following information:
Once this is registered on chain, both other nodes and other users will know details necessary to communicate with the poseidon servers.
Inboxes
These are users on the protocol. They get registered on chain, where they advertise information necessary to send messages to them.
An example inbox setup(Note, these steps should get abstracted by the application, and made easier for users):
IMPORTANT NOTE: All these operations should always occur on the user's device and never on the applications servers
Alice a user of Poseidon, wants to start messaging Bob a user of wallet x.
Both Poseidon and wallet x have been registered on the network and each run a server that hosts the FGS protocol.
Bob has already registered his inbox on wallet x.
Poseidon will abstract away the registration process from Alice, but the steps taken for registration will be as follows.
Poseidon will generate a long random string, known as a Random Auth String
this Random Auth String will look something like this:
Poseidon will then request Alice to sign the rand auth string above
The resultant signature will be Alice's secret signature
Poseidon will then generate an encryption key pair and a sign key pair for Alice
Poseidon(ON DEVICE) will then combine the secret keys for signing and encryption, into a single formatted string with this structure:
Once this has been registered on chain, Bob will now know how to encrypt conversation invites for Alice and what node to send his message to, and since Bob had already gone through the same process on Wallet X, Alice will as well.
Alice can always reconstruct the keypairs she needs for encryption and signing on any other application. All she will need to do is sign the public random auth string with her Aptos wallet private key to obtain her secret signature, which she can use to decrypt the
encrypted_private_key_set
In case Alice or Bob feels any of their private details have been exposed, they can always update their inboxes. And any new messages sent on the protocol will dynamically adapt the new details.
Anonymity
Anonymity of conversations is an important thing to consider when dealing with communication on a decentralised protocol.
The main goal with our approach is to hide conversations in "plain sight" and make it difficult for an individual to guess what conversation belongs to a specific set of users, think of a needle in a big jar of other needles at the bottom of a river in a forest, it's technically possible to find it, but before you do you must use a tonne of resources, and if you some how do, you'll find that all communication has been encrypted.
The path to enabling this is relatively simple. It all starts with a shared secret.
Both Alice and Bob can construct a shared secret from their public private key pairs through the Diffie Hellman key exchange.
If Bob is initiating the conversation, once he has constructed a shared secret with Alice's
encryption_public_key
he can use it to encrypt aCONVERSATION HEADER
which he can include in an invitation that he forwards to Alice. The forwarding will involve the invitation being sent to Wallet X's sever, which will lookup the currently active node of the to address that Bob specified, and forwarding the invitation to that server'sprotocol_endpoint
An invitation will look like this:
This is what an unencrypted CONVERSATION_HEADR
would look like:
As Bob forwards his invite, he will also update his on chain conversation list to include this header. This is what the
encrypted_conversation_list
field of the inbox will contain.When Alice receives the invite, She'll accept it/ she can optionally reject it(which involves sending back and accept/reject response through the server system)
Then updates her inbox's
encrypted_conversation_list
with it as well.Going forward all messages sent by Alice will be encrypted with the
CONVERSATION_KEY
and tagged with theCONVERSATION_ID
that no one apart from Bob knows about.Additionally, the target of all her messages will not be Bob's address but instead Alice will simply use Wallet X's namespace i.e Wallet X. Poseidon's server will be able to lookup the destination server for the message, and deliver the message to Wallet X's inbox.
NOTE: the
CONVERSATION_ID
is different from theencrypted_conversation_id
We decrypted the
encrypted_conversation_id
to get theCONVERSATION_HEADER
which contains the actualCONVERSATION_ID
Unless exposed by either parties, the
CONVERSATION_ID
can not be tracked back to either of them.Tracking of conversation ids may seem possible because for our example we have only Alice and Bob, but imagine a situation with more than 100,000 conversations are running on the system.
The needle, in a jar of needles at the bottom of a river in a forest starts to play out.
Delivery
Continuing with the Alice Bob example. Lets examine how messages sent by Alice will get delivered to Bob and ViceVersa.
Previously we looked at conversation initialisation, where Bob creates a CONVERSATION_HEADER
, encrypts and sends it publicly to Alice, who decrypts it and gets back the CONVERSATION_HEADER
, which importantly includes the CONVERSATION_ID
and a CONVERSATION_KEY
.
Now if Alice needs to send a GM to Bob, or send a picture of her cat. She'll go through the following steps:
Construct a
MESSAGE
payload, which looks like this:
Encrypt the
MESSAGE
with theCONVERSATION_KEY
once Alice has an encrypted version of the message, she can then construct a payload to send to Poseidon's servers.
This will look something like this:
NOTE how no user/ inbox identifiable information is included in what Alice submits to Poseidon's severs.
Only Bob and Alice will be able to figure out who this conversation is meant for by tracking the
conversation_id
Encryption and Verification
Verification
The FGS protocol has self verifying data primitives.
This means all data submitted on the network, will include a signature that can point to the original signer of the data. We use a custom wrapper data type for all data or what we call activities on the network. This wrapper is called a SignedActivity, this is what it's typescript implementation looks like:
Before any activity makes its way to a node's inbox or outbox, the node has to verify that that the actor claiming to be the sender of an activity was actually the one who created it.
Encryption
Encryption on the FGS protocol is used in 3 key places:
The On chain Inbox:
The
encrypted_private_key_set
gets encrypted with thesecret_signature
derived from therandom_auth_string
The
encrypted_conversation_list
is a list of serialised conversation headers for the conversations the user is involved with, that have been encrypted with the user'sencryption_key
which can get derived from theencrypted_private_key_set
Invites/Accepts/Rejects:
For these we send encrypted versions of the
CONVERSATION_HEADER
as theencrypted_conversation_id
field. TheCONVERSATION_HEADER
gets encrypted using a shared secret generated by the 2 parties involved, usually through the Diffie-Helman key exchange
Message encryption
All messages on the network get encrypted with the
CONVERSATION_KEY
found in theCONVERSATION_HEADER
Conclusion
Work on the Federated GM Service was a product of previous work done on the Hermes protocol for Kade, which was meant to provide on chain direct messaging. However, when we run into issues around privacy of user data we decided to begin work on something that would ensure conversation anonymity.
We found that a federated approach was a lot more privacy friendly while still enabling us to build interoperable inbox for users on Aptos, without having to expose all their conversations to the world.
Regarding encryption, we had initially began with the idea of implementing Signal's Double Ratchet algorithm for message encryption, however the metadata involved for handling message keys and constructing the encryption and decryption trees is something that is a lot easier to do with a centralised system, but complicates the process for a decentralised system.
We plan to further work on things like conversation key rotation to give a form of forward healing property to the network in case of key leakage.
The FGS protocol is now being actively used by Poseidon for E2EE Direct Messaging.
Last updated