Dm3 - decentralized messaging for web3

Hello! We are the team of dm3 protocol that presented on the Ecosystem WG call, and we would love to have the feedback from the ENS community and research any possibility of integration! Down below is an explanation of our protocol! Thanks!

Current stage

With the rise of web3, self-sovereign identity management and non-custodial key management have gained a lot of attention again. There are millions of people controlling their own keys through wallets such as MetaMask and others. The introduction of the Ethereum Name Service (ENS) has given us simple names, such as alice.eth, which we truly own. But not only that: It is a public record secured by private keys which can contain more than a simple domain name. It can also host public keys used for encrypted communication. Only the actual owner of the ENS domain can change the registered public encryption and signing keys, and there is an immutable record secured by global consensus around all changes and its most current status. This is the basis for secure encrypted communication in web3, tied to the web3 username: Your ENS domain.

What is dm3?

dm3 allows you to easily write a signed and encrypted message to another ENS domain owner, such as alice.eth, without having any single point of failure.

We will walk you through the steps and components of DM3:

Initial Sign-In

Alice owns her name: alice.eth. Nobody can take it from her as long as she keeps the associated ethereum account key secure.

When dm3 is first used, public signing and encryption keys are generated based on the key in her Ethereum wallet (MetaMask, WalletConnect, …). The associated private keys can be regenerated at any time using the key in the Ethereum wallet.

In addition to those public keys, Alice provides an URL to the delivery service. The delivery service is responsible for forwarding and notifying the recipient when messages arrive. This service can run on her server or be provided by a third party. This service cannot read or modify the messages since they are signed and encrypted. The public keys and the delivery service URL are made public using an ENS text record.

Message Storage

The location where the messages are stored can also be freely chosen. It can be locally in the browser (not recommended, since the browser storage can get deleted from time to time), a local file, IPFS (web3 storage), or the user’s own personal cloud. Even if the user opts for a centralized cloud provider such as GDrive, Google has no power to read or manipulate messages or gain any control over the user’s account/ENS Domain.

Summary

In summary, the core of dm3 are the published text records on ENS that provide the public encryption and signing keys. On top of this, the user can choose self-hosted or third-party hosted service providers that act as delivery services and/or storage providers.

app.dm3.network also offers those services, but the user is in no way bound to use any of them.

Guiding principles

The principles in the design of dm3 are:

  • Decentralization as in
    • works without us, and the user is free to choose and change a service provider or host all the services themselves
  • Non-custodial - your keys, your messages
  • Key revocation/rotation securely done as ENS text records, verified by global consensus on Ethereum
  • “can’t be evil” - we cannot read, delete, censor anything you do within dm3
  • fully open source (BSD 2 license)

ENS Integration

dm3 is designed after the same principles as ENS. Therefore it could be more tightly integrated with the ENS app. As a first step, the ENS app could show the eth.dm3.profile text record value. In a later stage, the ENS app could directly integrate the dm3 widget into the details page of an ENS name.

dm3 Protocol

:incoming_envelope: Delivery Flow & API

The following example depicts how the delivery would work if Alice sent a message to Bob.

  1. Alice types her message for Bob into the dm3 App and submits it.
  2. The dm3 App asks on-chain for Bob’sdm3 profile. To do so,the app queries the eth.ens-mail text record linked to his ENS name. The profile contains Bob’s public encryption key, his public signing key, and the URL to the Delivery Service heuses.
  3. The message content is signed using Alice’s private signing key and encrypted using Bob’s public encryption key. After the message is encrypted, it is sent to the delivery service used by Bob via its WebSocket API.
  4. The delivery service pushes the message to Bob’s instance of the dm3 app. The app decrypts the message using Bob’s private encryption key.
  5. The app queries Alice’s dm3 profil and uses it to check whether the message signature is valid.
  6. If the signature is valid, Bob is able to read the message

There is no central delivery service. Alice and Bob can change their delivery service by changing the delivery service URL in their profile. They could, for example, run a delivery service instance on their DAppNode at home.

:closed_lock_with_key: Key Management

The aim of the key management is to give the owner of the key, who controls the corresponding ESN name, complete control over the dm3 identity. No one who does not have access to this key is able to read messages, send messages, or change profile settings.

The following types of keys exist:

  • Ethereum Account Key: The private key of the Ethereum account that owns your ENS name.
  • Storage Encryption Key: The symmetric key used to encrypt the user storage. This key is generated by signing a message including a salt with the Ethereum Account Key.
    Storage_Encryption_Key = keccak256( personal_sign( salt ) )
  • Message Encryption Key Pair: The key pair used to encrypt/decrypt messages.
  • Signing Key Pair: The key pair used to sign/verify messages.

:writing_hand: Sign-In Flow

The dm3 app requires users to sign in using their Ethereum account. The initial sign-in consists of the following steps:

  1. Connect an Ethereum account: The dm3 app connects to an Ethereum Account which is injected by a wallet (e.g., MetaMask).
  2. Storage Encryption Key creation: A message containing a salt (randomly generated characters) is created and signed with the user’s Ethereum Account Key. The hash of the signature serves as Storage Encryption Key. The salt is stored unencrypted in the user storage.
  3. dm3 Profile creation: The app generates a new Message Encryption Key Pair and Signing Key Pair and stores the private keys in the encrypted part of the User Storage. The public keys and the delivery service URL constitute the dm3 Profile.

After the initial sign-in, the user can interact with all users connected to the same delivery service. To connect with users of other delivery services, the user must publish the URL pointing to theirdm3 Profile as ENS text record.

Any subsequent sign-in consists of two steps:

  1. Connect an Ethereum account
  2. Decryption of the User Storage: The app retrieves the user storage from the selected storage location. The user storage contains the unencrypted salt message and signs it using the Ethereum account to recreate the Storage Encryption Key. This key’s purpose is to decrypt the encrypted part of the user storage.

:lock_with_ink_pen: Profile Registry

For the protocol to work, there needs to be one registry where the dm3 App can look up dm3 profiles of other users. dm3 uses ENS text records for this purpose. The text record named eth.ens-mail contains a URL to the actual profile. It is essential to verify the integrity of the profile entry. For example, if the eth.ens-mail text record contains a simple HTTP-URL, it would be possible to hijack the server and change the profile entry. Therefore, the URL must be an IPFS-URL or an URL containing a hash of the entry.

:floppy_disk: Storage Management

During delivery, the messages are encrypted withthe recipient’s Encryption Public Key. However, the delivery service deletes the messages after they are delivered. To access past messages, the dm3 app stores the messages in the encrypted part of the user storage. The user storage is a JSON file.

The separation of delivery and storage of messages allows the users to decide on the storage location of their sent and received messages.

Wherever the storage is located, only the user who owns the corresponding Ethereum account key is able to decrypt it.

Further Use Case

The first use case of the dm3 Protocol is to send signed and end-to-end encrypted messages between two ENS name owners. However, the protocol can also be used to create public message feeds for ENS name owners (“decentralized Twitter”).

Give dm3 a try today. If you have any questions, our team is always available to help. You can reach us at ens-mail.eth or hello@dm3.network.

For the latest news and updates about dm3, be sure to follow us on Twitter.

7 Likes

Thanks Haiko! This is a great write up!

Some questions:

  • Is the salt supposed to be a secret too? Is it app specific? In the case that the user wants to use a second client to read his dm3 messages, do they need the salt too or will the new app just generate a new key with a new salt? In that case, we would need to list multiple public keys so that the user could receive messages in many devices.

  • Any service could decide to be the server of the user’s messages. Is there a standard way for one service to retrieve messages from the other? I would suggest that every DM3 compatible service should have an API that returns an (encrypted?) RSS feed with messages for/from any user. This way messages could be replicated among many services, if the user so desires

  • I see some very different applications that would require slightly different implementations: one to one private chats, group private chats, and social media posts (both public and within a given audience). Do you see the same protocol as serving all of these?

3 Likes

Thanks a lot for your great feedback!

Is the salt supposed to be a secret too? Is it app specific? In the case that the user wants to use a second client to read his dm3 messages, do they need the salt too or will the new app just generate a new key with a new salt? In that case, we would need to list multiple public keys so that the user could receive messages in many devices.

The salt isn’t supposed to be a secret. It’s always stored unencrypted together with the encrypted payload (messages, contact list, …). The following snippet shows the typings of a storage object:

{
  version: string;
  salt: string; // unencrypted randomly generated string
  payload: string; // encrypted user storage
}

If the app retrieves the storage JSON file, it will read the salt property and ask the user to sign the salt with his wallet. The signature will then be used to derive the key to decrypt the payload. This way, the wallet owner will always be able to decrypt a storage object. We did it this way because MetaMask deprecated eth_decrypt and eth_getEncryptionPublicKey. The good thing about not using eth_decrypt and eth_getEncryptionPublicKey is that we are not bound to MetaMask anymore.

So, if a user wants to load his messages on a second device/client, he just needs to load the storage file and sign the salt with his wallet.

It is also possible to export the encryption key. The export makes it possible to use dm3 on a device without access to the Ethereum private key. For example, if you used dm3 at first on your desktop computer and now you also want to use it on another device but you don’t want to put your ethereum private key there. Therefore you just need to scan a QR code representation of the encryption key with the new device.

Any service could decide to be the server of the user’s messages. Is there a standard way for one service to retrieve messages from the other? I would suggest that every DM3 compatible service should have an API that returns an (encrypted?) RSS feed with messages for/from any user. This way messages could be replicated among many services, if the user so desires

The user selects the delivery service he wants to use by putting it into his profile which can be accessed by querying the eth.dm3.profile ENS Text Record of the user. If I want to send a message to a specific user, the dm3 app will look up the URL of the delivery service this user is using and send the message there. The delivery service just buffers incoming messages until they are delivered. After the messages are delivered, the delivery service will delete the message (similar to pop3 for email). If the dm3 app instance retrieves the message, it will store it at the storage location the user selected (browser storage, local file, ipfs, google drive or dm3 storage service). This way, the user has complete control over the location of his data.

I agree it would make sense for the dm3 storage service to provide an API method to move the complete user storage of one user to another dm3 storage service instance or a completely other storage location. I created an issue for that https://github.com/corpus-ventures/dm3/issues/278

I see some very different applications that would require slightly different implementations: one to one private chats, group private chats, and social media posts (both public and within a given audience). Do you see the same protocol as serving all of these?

I think most of the use cases you mentioned should be possible to realize with just minor changes on the protocol side. For example, I implemented a public message feed demo version of dm3. In this case, the delivery service would store the feed and make it publicly available. So if your dm3 app instance would like to get my public feed, it would look up the eth.dm3.profile ENS text record of hai-ko.eth. There it would find the URL to my delivery service and my public signing key. Then the app would query the delivery service to get the feed and check the signatures of every message in the feed. The public messages would then be shown to you if the signatures are valid.

3 Likes

This is great! Loved your detailed presentation here and on the Ecosystem call @hai-ko.eth.

I just used dm3 for the first time and it was honestly a really good experience! Your approach to messaging reminds me of the sufficient decentralization approach that Farcaster takes to social protocols, which I’m personally a big fan of.

A few questions/pieces of feedback came to mind:

  • I assume the eth.ens-mail text record has to be on my primary ENS name so that dm3 knows where to look. What happens if I change my primary ENS name after first signing in to dm3?
  • Is there an implementation of a delivery service handling notifications for an incoming message? I’d love to see a demo of that since notifications seem to be one of the biggest problems with web3 messaging products.

  • I first signed into dm3 on my phone, then went to sign in with the same wallet on my computer. As a piece of feedback, I thought it was confusing that the sign-in screen includes the option to select a storage destination. It feels like that should be a later screen after the user has signed in. Also, I think people would feel a lot more comfortable signing this message if it had a description like some of the earlier prompts.

As far as integrations go, it would be cool if you allowed dapps to apply custom styling to the embedded chat. I think this would make it a lot easier to convince devs to integrate dm3. Rainbowkit does this really well as an example.

Excited to see where you take this!

2 Likes

I really appreciate the thought and effort that went into creating dm3.

Can you help me understand if this would work for wallet to wallet communication, if both parties do not have ENS name? I realize you can send messages to any address, however what takes the place of the ens text record?

Would non-ens communication necessitate the creation of on-chain registry to serve the purpose that ens text records are used for?

I have a few other thoughts that will take a bit of time to articulate well, please bear with me in the meantime.

2 Likes

The protocol you’re building seems super cool! Looks like you’re taking a Farcaster-style “sufficiently decentralized” approach, all makes sense to me. :slightly_smiling_face:

For what it’s worth, though, while I think ENS DAO should support this project with resources, I don’t think it would be wise for TNL to integrate a chat solution into the main frontend at this time. We need to see which protocol actually gets any traction, it’s far too early to choose a winner, imo.

1 Like

In order to communicate via dm3 you need to look up an address’s profile data (public messaging key, public signing key, and delivery service URL, which may not be one of dm3’s). I see no better place to store that info than as an ENS record…

Although you can use the protocol without an ENS name, it would only work within the confines of a single delivery service. So if you create your dm3 profile using the dm3 delivery service / storage, then you’ll only be able to send messages to accounts using the same dm3 delivery service. Other delivery services would not have the profile data for that account!

I would even say that the dm3 protocol should require an ENS name. It streamlines and future-proofs things, and ensures better decentralization, so that you don’t end up with a few large siloed, walled-garden providers. Not sure what @hai-ko.eth thinks though

4 Likes

Fully +1 on this perspective. If you don’t use ENS, you’ll end up using something that’s similar but bespoke, so might as well use ENS.

Thank you @gregskril for your feedback!

I wasn’t aware of Farcaster but you are right the basic concept is similar.

Yes, as you assumed during sign-in, dm3 looks up the primary ENS name (reverse record) for the selected address. If you change this and sign in, you will get an empty inbox, but I think this is ok because changing your ENS name is like changing your identity. Maybe there should be a method to migrate the message storage in case of a primary ENS name change.

The current version has no support for notifications, but I’m working on it. The current idea is that you can register notification alerts (email, SMS,…) on the delivery service.

During the initial sign-in, the app needs to ask the user where the messages should be stored, but I think it would make sense that the delivery service remembers the storage location of a user. In this case, you only have to select the storage location once during the initial sign-in. It could also be possible to set for example, the browser storage as default and that the user has to change it if they wish.

Absolutely I have to change this.

That’s already possible. The react component has a style property, but I’ll have a look at Rainbowkit maybe I can improve it.

2 Likes

I think it is a good idea to require an ENS name, but I wouldn’t go so far as to require the user to create the dm3 ENS text record during the initial sign-in because I think it would impact adoption negatively if users are forced to send a transaction before they can use the protocol. @serenae as you mentioned, the users are currently able to interact with other users on the same delivery service without sending a transaction. I think if the users try it out and then want to interact with users on other delivery services or switch themself to another delivery service, they are more willing to send a transaction.

1 Like

Looks like an awesome project and from what I can see, very well thought through!

If you ever require some help on the UI / Graphic design side of things, please feel free to message me!

I am involved in the ENS community working creatively with some other dev teams and projects and always love to collaborate.

Are you guys in the ENS builders discord ? If not I would suggest checking it out. Great people there.

@BrianMillsJr.Eth is your guy to talk to!

Good work !

3 Likes

You could also look for metadata stored directly on the user’s reverse record, and if you don’t find it there, check the primary name.

How do I add metadata directly to the user’s reverse record? I thought the reverse records are only for mapping from an Ethereum address to an ENS name. Could I just add a text record to a subdomain of addr.reverse like bcd6de065fd7e889e3ec86aa2d2780d7553ab3cc.addr.reverse?

That’s right. Reverse records use the same resolver system, so you can set text records on them. The default resolver used by the reverse registrar doesn’t support them, but you can set a custom one - and when we roll out the new reverse registrar soon, that won’t be necessary any longer.

For those who were unable to attend the ecosystem call this week, below is a summary of the discussion along with a couple other thoughts.

Questions Asked

1. What is DM3 looking for?

The team has been heads down building out dm3 and the key ask is feedback on the project. @hai-ko.eth has been very receptive to feedback and I encourage everyone to continue to provide their perspectives.

2. Will adoption be hampered by the current design?

As of August 2022, there are ~200 million addresses[1] with some activity. ENS has ~400 thousand primary owners. Though the protocol outlined above can handle wallet-to-wallet communication for non-ens address it depends on an alternative delivery services. A concern is the splitting and siloing of the data as result of multiple delivery services.

To this point, @vegayp (part of the project) stated

“It is not for everyone, it is for people who are looking for something very specific” - Eduardo

Overall, I’m fan of having more messaging protocols. We need many experiments to figure out which one is right for web3. Hopefully, the multiple protocols will work with one another to avoid the data siloing issues of web2.

For transparency sake, nfty chat (I’m a co-founder) is building a protocol to handle many-to-many wallet communication, with wallet to wallet being a subset.

For those who want to explore protocols related to messaging in one form or another here are a few to follow:

  1. xmtp - wallet to wallet messaging
  2. farcaster - social network [2]
  3. epns - notifications
  4. waku - a suite of privacy-preserving, peer-to-peer messaging protocols

[1] Ethereum Cumulative Unique Addresses.
[2] Sufficient Decentralization for Social Networks - Varun Srinivasan

I would love to see nifty.chat and others adopt the standard DM3 have pioneered. A federated system with a standardised API seems the only way to build this in a decentralised fashion.

4 Likes

Thanks a lot for all the feedback. We’ll now work on a more formalized protocol specification draft where we take the feedback into account. As soon as the draft is finished (next 1-2 weeks), I’ll post it here for the second round of feedback.

3 Likes

I agree that a standard API is key.

The team at nfty chat is about to chat with the folks over at dm3, I can only see this as a positive for the ecosystem.

3 Likes

Thanks! We are looking forward to that! :smiley:

2 Likes

Good to see this initiative and at WalletConnect we demo’d our Chat API in July which offered exactly these features with native SDKs for Web (Javascript), Android (Kotlin) and iOS (Swift).

We have a couple of opinions on the design of dm3 but I would emphasize the following:

  • Key Derivation (we are using x25519)
  • Payload Encryption (we are using chacha20-poly1305)
  • Invite System (we allow anyone to invite a peer but requires consent for future messages)
  • Relay Transport (we build everything on of our pub-sub network)

There is definitely more components to consider such as:

  • Message Storage
  • History Recovery
  • Media Embedding

But the first 4 topics would be the most important ones IMO to find standardization

4 Likes