From e458d30234765c982ff6d5ff23e6a62c9ff02c9c Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 9 May 2024 15:58:49 -0400 Subject: [PATCH] Creates the notion of event-owned keys --- 68.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 68.md diff --git a/68.md b/68.md new file mode 100644 index 0000000..e4ee5e3 --- /dev/null +++ b/68.md @@ -0,0 +1,101 @@ +NIP-68 +====== + +Shared Replaceables +------------------- + +`draft` `optional` + +This NIP creates replaceable events that can be changed by any public key in the list of editors. Editors can also add and remove new editors. + +Every shared replaceable event MUST be signed with it's own private key. The event owns itself. + +The event's private key MUST be shared with all editors through `p` tags. The key is [NIP-44](44.md)-encrypted to each editor and placed as the 4th element in a regular `p` tag. + +```js +val edittingKeyPair = nostr.generateKeyPair() + +{ + "pubkey": edittingKeyPair.publicKey + "kind": 3xxxx or 1xxxx, + "tags": [ + ["d", ""] + ["p", "", "", nip44Encrypt(edittingKeyPair.privateKeyHex, edittingKeyPair.privateKey, "") ] + ["p", "", "", nip44Encrypt(edittingKeyPair.privateKeyHex, edittingKeyPair.privateKey, "") ] + ], + "content": "", + "sig": signWith(edittingKeyPair.privateKey) + // ... +} +``` + +Any replaceable event kind can be shared among editors. + +To update the event, receivers MUST: +1. find the ciphertext in the `p`-tag for their key +2. decrypt the ciphertext with `nip44Decrypt(tag[3], user.privatekey, event.pubkey)` to get the event's private key in hex. +3. use the event's private key to sign. + +## Encrypted Shared Replaceables + +Some use cases require separate editting and viewing permissions: the `.content` can be encrypted so that only users with viewing permissions can see the information. + +To achieve this dynamic, the replaceable event MUST own two shared private keys: one for editting and one for viewing. + +Both keys are shared as encrypted `p` tags between the editting key and each user's public key. + +The `.content` is then encrypted from the editing private key to the viewing public key. + +```js +val edittingKeyPair = nostr.generateKeyPair() +val viewingKeyPair = nostr.generateKeyPair() + +{ + "pubkey": edittingKeyPair.publicKey + "kind": 3xxxx or 1xxxx, + "tags": [ + ["d", ""] + ["p", "", "", nip44Encrypt(edittingKeyPair.privateKeyHex, edittingKeyPair.privateKey, "") ] + ["p", "", "", nip44Encrypt(edittingKeyPair.privateKeyHex, edittingKeyPair.privateKey, "") ] + ["p", "", "", nip44Encrypt(viewingKeyPair.privateKeyHex, edittingKeyPair.privateKey, "") ] // view only + ], + "content": nip44Encrypt("some text", edittingKeyPair.privateKey, viewingKeyPair.publicKey), + "sig": signWith(edittingKeyPair.privateKey) + // ... +} +``` + +To decrypt the event, all receivers MUST: +1. find the ciphertext in the `p`-tag for their key +2. decrypt the ciphertext with `nip44Decrypt(tag[3], user.privatekey, event.pubkey)` to get the event's private key in hex. +3. calculate the public key of the shared key. +4. if the public key is the same as `.pubkey`, this is an editing key, if not this is the viewing key +5. if it is the editing key, decrypt all the other `p`-tag keys and find the viewing key +6. once both keys are known, decrypt the `.content` with `nip44Decrypt(event.content, viewingKeyPair.privatekey, event.pubkey)` + +### Special Case: No Viewing Keys + +If the group if users that only have viewing permissions is empty there won't be a `p`-tag to host the viewing key. In those cases, the `.content` MUST then be encrypted to the editing public key. + +```js +val edittingKeyPair = nostr.generateKeyPair() + +{ + "pubkey": edittingKeyPair.publicKey + "kind": 3xxxx or 1xxxx, + "tags": [ + ["d", ""] + ["p", "", "", nip44Encrypt(edittingKeyPair.privateKeyHex, edittingKeyPair.privateKey, "") ] + ["p", "", "", nip44Encrypt(edittingKeyPair.privateKeyHex, edittingKeyPair.privateKey, "") ] + ], + "content": nip44Encrypt("some text", edittingKeyPair.privateKey, edittingKeyPair.publicKey), + "sig": signWith(edittingKeyPair.privateKey) + // ... +} +``` + +Similarly, when decrypting the `.content`, if the receiver client can't find a viewing key, it SHOULD use the editing key to decrypt: `nip44Decrypt(event.content, edittingKeyPair.privateKey, edittingKeyPair.publcKey)` + +## Final Considerations + +If any of the event's private keys are lost due to an encrypting bug or if there is a failure to add the ciphertext in the p-tags before signing, and if relays don't have previous versions of this event, the event might become permanentely unmodifiable and undecryptable, which can also be a feature in some use cases. \ No newline at end of file