ERC-7627 On-chain secure messaging protocol (Secure Messaging Protocol)

Final · Other · created 2024-02-19

Defines a common interface for end-to-end encryption messaging, where users register a public key on chain and send messages encrypted with the recipient's public key to an address.

Why it is needed

Layer 2 chains with block times under one second have appeared, and account abstraction has been introduced, which lays the groundwork for more decentralized apps that offer real-time communication and online chat on chain. But without a common standard for exchanging encrypted messages, each app would differ in how it registers public keys, which function it uses to send messages, and how it distinguishes conversations. Then a message sent from one app is hard for another app to read or display, and developers who want to add encrypted communication to a smart contract must design it from scratch each time. This standard aims to unify user registration and message sending in a single interface, so that encrypted communication can be integrated into contracts easily and messages can move smoothly between platforms with interoperability.

What to implement

In depth

The PublicKey struct and the algorithm enum

A public key is represented by the PublicKey struct. It has three fields: public_key (the key value bytes), valid_before (the expiry as a uint64), and algorithm (the algorithm used). The algorithm is specified with the PublicKeyAlgorithm enum, which defines three values: ECDSA, ED25519, and X25519. Keys are distinguished by a key index (_keyIndex) of type bytes32, so one user can manage several keys by index. The goal of the specification is to include the algorithm enum and the user information struct in the standard so that it supports a variety of cryptographic algorithms and user information management, and increases the flexibility and extensibility of the messaging system.

updatePublicKey, key registration that only the owner can update

updatePublicKey(bytes32 _keyIndex, PublicKey memory _publicKey) stores the caller's (msg.sender) public key at the given index and emits the PublicKeyUpdated event. By design, only the owner of an address can update their own public key. This restriction is the core of preserving the integrity of end-to-end encryption. Because no one else can swap the key, a message encrypted with the registered public key can be decrypted and read only by the intended recipient.

sendMessage and conversations built on session IDs

sendMessage(address _to, bytes32 _keyIndex, bytes32 _sessionId, bytes calldata _encryptedMessage) sends an encrypted message to the recipient address. The message content must be encrypted with the recipient's public key when it is sent, and _keyIndex points to the index of the public key used for encryption. _sessionId is the session ID of the communication, and an arbitrarily chosen session ID can be used to group multiple messages into one conversation. This function is central to organizing and managing discussions within a decentralized app and gives users a consistent, structured messaging experience.

Off-chain integration through events

The MessageSent event is emitted when a message is sent, and the PublicKeyUpdated event is emitted when a public key is updated. MessageSent carries the sender (from), recipient (to), and key index (keyIndex) as indexed parameters, and also records the session ID and the encrypted message body. Off-chain decentralized apps can subscribe to these events to easily track and display the latest messages and key updates, which improves real-time responsiveness and user interaction. The event definitions also serve the specification's goal of improving the observability and auditability of contracts.

Storage structure of the reference implementation

The reference implementation stores public keys in mapping(address => mapping(bytes32 => PublicKey)) public pk. Because it is a two-level mapping from user address to a unique key index, each address can hold multiple keys. updatePublicKey writes the new key to pk[msg.sender][_keyIndex], and getUserPublicKey returns that slot as is. sendMessage is implemented so that it does not store the message in contract state and only emits the MessageSent event, so the message body remains in the event logs.

Things to watch out for

Original ERC · ← ERCs