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
event MessageSent(address indexed from, address indexed to, bytes32 indexed keyIndex, bytes32 sessionId, bytes encryptedMessage);: The event emitted when a message is sent. It carries the sender, the recipient, the index of the public key used for encryption, the session ID, and the encrypted message.event PublicKeyUpdated(address indexed user, bytes32 indexed keyIndex, PublicKey newPublicKey);: The event emitted when a user's public key is updated. It carries the user address, the key index, and the new public key data.function updatePublicKey(bytes32 _keyIndex, PublicKey memory _publicKey) external;: Registers or updates the caller's own public key at the given key index.function sendMessage(address _to, bytes32 _keyIndex, bytes32 _sessionId, bytes calldata _encryptedMessage) external;: Sends an encrypted message to the given address. It records which public key was used for encryption and which conversation the message belongs to.function getUserPublicKey(address _user, bytes32 _keyIndex) external view returns (PublicKey memory);: Looks up the public key data registered for a given user at a given key index.
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
- Use up-to-date, secure cryptographic algorithms: When choosing an algorithm, keep checking the latest security news and recommendations. Avoid asymmetric encryption algorithms that have known vulnerabilities or are not recommended, and update algorithms regularly as security threats change.
- Strictly encrypt message content with public keys: To preserve message confidentiality, the content being sent must be encrypted with the recipient's public key. Sending plaintext information can lead to information leaks and security risks, so data must always be encrypted at every stage of transmission and storage to prevent unauthorized access to sensitive information.
- Key management and protection: Both user public keys and private keys need robust key management and protection measures. Keys must be stored and transmitted securely to prevent leaks and tampering, key security should be strengthened with multi-factor authentication and key rotation strategies, and key management procedures should be reviewed regularly to reduce potential risks.