From 90ff6d75c8479ebad193a5fe739a6fdb0a580f59 Mon Sep 17 00:00:00 2001 From: Anderson Juhasc Date: Mon, 28 Jul 2025 20:52:48 -0300 Subject: [PATCH] add NIP Encrypted Group Messages --- EGM.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 EGM.md diff --git a/EGM.md b/EGM.md new file mode 100644 index 0000000000..d08dc4f57f --- /dev/null +++ b/EGM.md @@ -0,0 +1,89 @@ +NIP-EGM +====== + +Encrypted Group Messages via Symmetric Key Distribution +------------- + +`draft` `optional` + +## Abstract + +This NIP defines a method for sending encrypted messages to multiple recipients using symmetric AES encryption. The shared AES key is encrypted individually for each recipient using NIP-44, allowing the message to be decrypted by any of the intended recipients using a single Nostr event. + +## Motivation + +NIP-44 (v2) provides a secure encryption method for private 1-to-1 messages but does not address group messaging without duplicating events. + +This NIP introduces a way to distribute a single event that contains a message encrypted with a symmetric AES key. The AES key is then individually encrypted for each recipient using the standard NIP-44 flow. This enables efficient and private group communication within the Nostr protocol. + +## Tags + +- `["rec", ]`: Declares each intended recipient's public key. +- `["key", , ]`: Contains the AES key encrypted specifically for each recipient using `nip44.encrypt`. + +## Specification + +### Event Structure + +- `kind`: Any valid event kind. Default is `1` (text note). +- `content`: A JSON object containing: + - `ciphertext`: AES-GCM encrypted message, encoded in base64. + - `iv`: AES-GCM initialization vector, encoded in base64. +- `tags`: One or more of the following per recipient: + - `["rec", ]`: Declares a recipient. + - `["key", , ]`: Contains the encrypted AES key using NIP-44. + +### Message Creation + +1. Generate a 256-bit AES key: `Uint8Array(32)`. +2. Encrypt the plaintext message using AES-GCM with a randomly generated IV. +3. For each recipient: + - Derive a `conversationKey` using `nip44.getConversationKey(senderPrivkey, recipientPubkey)`. + - Encrypt the AES key with `nip44.encrypt(aesKeyHex, conversationKey)`. +4. Construct a single Nostr event with `rec` and `key` tags. +5. Sign the event with `finalizeEvent(event, senderPrivkey)`. + +### Message Decryption + +1. From the recipient's private key, derive the public key. +2. Locate the `["key", pubkey, encrypted_key]` tag that matches. +3. Derive the `conversationKey` using `nip44.getConversationKey(recipientPrivkey, senderPubkey)`. +4. Decrypt the AES key using `nip44.decrypt`. +5. Decrypt the message content using AES-GCM. + +## Example + +```json +{ + "kind": 1, + "created_at": 1680000000, + "pubkey": "", + "tags": [ + ["rec", ""], + ["rec", ""], + ["key", "", ""], + ["key", "", ""] + ], + "content": "{\"ciphertext\":\"...\",\"iv\":\"...\"}" +} +``` + +## Compatibility + +- Compatible with clients and libraries that already implement NIP-44 (v2). +- Events using this format can be safely ignored by clients that do not recognize this standard. + +--- + +## Rationale + +This approach enables the publication of a **single event** for multiple recipients, reducing redundancy, bandwidth usage, and synchronization issues. +Each recipient is only able to decrypt the message if they are explicitly included in the `key` tag list. + +--- + +## Future Considerations + +- Integration with `kind:1059` (from NIP-59) to support "sealable" or temporary messages. +- Support for partial anonymity: use `["anon-key", ]` for messages where only those who can decrypt the key will know they are recipients. +- Consider implementing deduplication by hashing the decrypted message content.