{
"author": {
"about": "🎵Die Gedanken sind frei.",
"banner": "https://i.nostr.build/FEZrgz9lthZrfndJ.jpg",
"display_name": "Laeserin",
"lud16": "silberengel@minibits.cash",
"name": "Laeserin",
"nip05": "laeserin@gitcitadel.com",
"website": "https://jumble.imwald.eu",
"picture": "https://i.nostr.build/thumb/RlkuVFMWOXpshf8k.webp",
"displayName": "Laeserin"
},
"event": {
"content": "NIP-59\n======\n\nGift Wrap\n---------\n\n`optional`\n\nThis NIP defines a protocol for encapsulating any nostr event. This makes it possible to obscure most metadata\nfor a given event, perform collaborative signing, and more.\n\nThis NIP *does not* define any messaging protocol. Applications of this NIP should be defined separately.\n\nThis NIP relies on [NIP-44](./44.md)'s versioned encryption algorithms.\n\n# Overview\n\nThis protocol uses three main concepts to protect the transmission of a target event: `rumor`s, `seal`s, and `gift wrap`s.\n\n- A `rumor` is a regular nostr event, but is **not signed**. This means that if it is leaked, it cannot be verified.\n- A `rumor` is serialized to JSON, encrypted, and placed in the `content` field of a `seal`. The `seal` is then\n signed by the author of the note. The only information publicly available on a `seal` is who signed it, but not what was said.\n- A `seal` is serialized to JSON, encrypted, and placed in the `content` field of a `gift wrap`.\n\nThis allows the isolation of concerns across layers:\n\n- A rumor carries the content but is unsigned, which means if leaked it will be rejected by relays and clients,\n and can't be authenticated. This provides a measure of deniability.\n- A seal identifies the author without revealing the content or the recipient.\n- A gift wrap can add metadata (recipient, tags, a different author) without revealing the true author.\n\n# Protocol Description\n\n## 1. The Rumor Event Kind\n\nA `rumor` is the same thing as an unsigned event. Any event kind can be made a `rumor` by removing the signature.\n\n## 2. The Seal Event Kind\n\nA `seal` is a `kind:13` event that wraps a `rumor` with the sender's regular key. The `seal` is **always** encrypted\nto a receiver's pubkey but there is no `p` tag pointing to the receiver. There is no way to know who the rumor is for\nwithout the receiver's or the sender's private key. The only public information in this event is who is signing it.\n\n```js\n{\n \"id\": \"<id>\",\n \"pubkey\": \"<real author's pubkey>\",\n \"content\": \"<encrypted rumor>\",\n \"kind\": 13,\n \"created_at\": 1686840217,\n \"tags\": [],\n \"sig\": \"<real author's pubkey signature>\"\n}\n```\n\nTags MUST must always be empty in a `kind:13`. The inner event MUST always be unsigned.\n\n## 3. Gift Wrap Event Kind\n\nA `gift wrap` event is a `kind:1059` event that wraps any other event. `tags` SHOULD include any information\nneeded to route the event to its intended recipient, including the recipient's `p` tag or [NIP-13](13.md) proof of work.\n\n```js\n{\n \"id\": \"<id>\",\n \"pubkey\": \"<random, one-time-use pubkey>\",\n \"content\": \"<encrypted kind 13>\",\n \"kind\": 1059,\n \"created_at\": 1686840217,\n \"tags\": [[\"p\", \"<recipient pubkey>\"]],\n \"sig\": \"<random, one-time-use pubkey signature>\"\n}\n```\n\n# Encrypting Payloads\n\nEncryption is done following [NIP-44](44.md) on the JSON-encoded event. Place the encryption payload in the `.content`\nof the wrapper event (either a `seal` or a `gift wrap`).\n\n# Other Considerations\n\nIf a `rumor` is intended for more than one party, or if the author wants to retain an encrypted copy, a single\n`rumor` may be wrapped and addressed for each recipient individually.\n\nThe canonical `created_at` time belongs to the `rumor`. All other timestamps SHOULD be tweaked to thwart\ntime-analysis attacks. Note that some relays don't serve events dated in the future, so all timestamps\nSHOULD be in the past.\n\nRelays may choose not to store gift wrapped events due to them not being publicly useful. Clients MAY choose\nto attach a certain amount of proof-of-work to the wrapper event per [NIP-13](13.md) in a bid to demonstrate that\nthe event is not spam or a denial-of-service attack.\n\nTo protect recipient metadata, relays SHOULD guard access to `kind 1059` events based on user AUTH. When\npossible, clients should only send wrapped events to relays that offer this protection.\n\nTo protect recipient metadata, relays SHOULD only serve `kind 1059` events intended for the marked recipient.\nWhen possible, clients should only send wrapped events to `read` relays for the recipient that implement\nAUTH, and refuse to serve wrapped events to non-recipients.\n\n# An Example\n\nLet's send a wrapped `kind 1` message between two parties asking \"Are you going to the party tonight?\"\n\n- Author private key: `0beebd062ec8735f4243466049d7747ef5d6594ee838de147f8aab842b15e273`\n- Recipient private key: `e108399bd8424357a710b606ae0c13166d853d327e47a6e5e038197346bdbf45`\n- Ephemeral wrapper key: `4f02eac59266002db5801adc5270700ca69d5b8f761d8732fab2fbf233c90cbd`\n\nNote that this messaging protocol should not be used in practice, this is just an example. Refer to other\nNIPs for concrete messaging protocols that depend on gift wraps.\n\n## 1. Create an event\n\nCreate a `kind 1` event with the message, the receivers, and any other tags you want, signed by the author.\nDo not sign the event.\n\n```json\n{\n \"created_at\": 1691518405,\n \"content\": \"Are you going to the party tonight?\",\n \"tags\": [],\n \"kind\": 1,\n \"pubkey\": \"611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9\",\n \"id\": \"9dd003c6d3b73b74a85a9ab099469ce251653a7af76f523671ab828acd2a0ef9\"\n}\n```\n\n## 2. Seal the rumor\n\nEncrypt the JSON-encoded `rumor` with a conversation key derived using the author's private key and\nthe recipient's public key. Place the result in the `content` field of a `kind 13` `seal` event. Sign\nit with the author's key.\n\n```json\n{\n \"content\": \"AqBCdwoS7/tPK+QGkPCadJTn8FxGkd24iApo3BR9/M0uw6n4RFAFSPAKKMgkzVMoRyR3ZS/aqATDFvoZJOkE9cPG/TAzmyZvr/WUIS8kLmuI1dCA+itFF6+ULZqbkWS0YcVU0j6UDvMBvVlGTzHz+UHzWYJLUq2LnlynJtFap5k8560+tBGtxi9Gx2NIycKgbOUv0gEqhfVzAwvg1IhTltfSwOeZXvDvd40rozONRxwq8hjKy+4DbfrO0iRtlT7G/eVEO9aJJnqagomFSkqCscttf/o6VeT2+A9JhcSxLmjcKFG3FEK3Try/WkarJa1jM3lMRQqVOZrzHAaLFW/5sXano6DqqC5ERD6CcVVsrny0tYN4iHHB8BHJ9zvjff0NjLGG/v5Wsy31+BwZA8cUlfAZ0f5EYRo9/vKSd8TV0wRb9DQ=\",\n \"kind\": 13,\n \"created_at\": 1703015180,\n \"pubkey\": \"611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9\",\n \"tags\": [],\n \"id\": \"28a87d7c074d94a58e9e89bb3e9e4e813e2189f285d797b1c56069d36f59eaa7\",\n \"sig\": \"02fc3facf6621196c32912b1ef53bac8f8bfe9db51c0e7102c073103586b0d29c3f39bdaa1e62856c20e90b6c7cc5dc34ca8bb6a528872cf6e65e6284519ad73\"\n}\n```\n\n## 3. Wrap the seal\n\nEncrypt the JSON-encoded `kind 13` event with your ephemeral, single-use random key. Place the result\nin the `content` field of a `kind 1059`. Add a single `p` tag containing the recipient's public key.\nSign the `gift wrap` using the random key generated in the previous step.\n\n```json\n{\n \"content\": \"AhC3Qj/QsKJFWuf6xroiYip+2yK95qPwJjVvFujhzSguJWb/6TlPpBW0CGFwfufCs2Zyb0JeuLmZhNlnqecAAalC4ZCugB+I9ViA5pxLyFfQjs1lcE6KdX3euCHBLAnE9GL/+IzdV9vZnfJH6atVjvBkNPNzxU+OLCHO/DAPmzmMVx0SR63frRTCz6Cuth40D+VzluKu1/Fg2Q1LSst65DE7o2efTtZ4Z9j15rQAOZfE9jwMCQZt27rBBK3yVwqVEriFpg2mHXc1DDwHhDADO8eiyOTWF1ghDds/DxhMcjkIi/o+FS3gG1dG7gJHu3KkGK5UXpmgyFKt+421m5o++RMD/BylS3iazS1S93IzTLeGfMCk+7IKxuSCO06k1+DaasJJe8RE4/rmismUvwrHu/HDutZWkvOAhd4z4khZo7bJLtiCzZCZ74lZcjOB4CYtuAX2ZGpc4I1iOKkvwTuQy9BWYpkzGg3ZoSWRD6ty7U+KN+fTTmIS4CelhBTT15QVqD02JxfLF7nA6sg3UlYgtiGw61oH68lSbx16P3vwSeQQpEB5JbhofW7t9TLZIbIW/ODnI4hpwj8didtk7IMBI3Ra3uUP7ya6vptkd9TwQkd/7cOFaSJmU+BIsLpOXbirJACMn+URoDXhuEtiO6xirNtrPN8jYqpwvMUm5lMMVzGT3kMMVNBqgbj8Ln8VmqouK0DR+gRyNb8fHT0BFPwsHxDskFk5yhe5c/2VUUoKCGe0kfCcX/EsHbJLUUtlHXmTqaOJpmQnW1tZ/siPwKRl6oEsIJWTUYxPQmrM2fUpYZCuAo/29lTLHiHMlTbarFOd6J/ybIbICy2gRRH/LFSryty3Cnf6aae+A9uizFBUdCwTwffc3vCBae802+R92OL78bbqHKPbSZOXNC+6ybqziezwG+OPWHx1Qk39RYaF0aFsM4uZWrFic97WwVrH5i+/Nsf/OtwWiuH0gV/SqvN1hnkxCTF/+XNn/laWKmS3e7wFzBsG8+qwqwmO9aVbDVMhOmeUXRMkxcj4QreQkHxLkCx97euZpC7xhvYnCHarHTDeD6nVK+xzbPNtzeGzNpYoiMqxZ9bBJwMaHnEoI944Vxoodf51cMIIwpTmmRvAzI1QgrfnOLOUS7uUjQ/IZ1Qa3lY08Nqm9MAGxZ2Ou6R0/Z5z30ha/Q71q6meAs3uHQcpSuRaQeV29IASmye2A2Nif+lmbhV7w8hjFYoaLCRsdchiVyNjOEM4VmxUhX4VEvw6KoCAZ/XvO2eBF/SyNU3Of4SO\",\n \"kind\": 1059,\n \"created_at\": 1703021488,\n \"pubkey\": \"18b1a75918f1f2c90c23da616bce317d36e348bcf5f7ba55e75949319210c87c\",\n \"id\": \"5c005f3ccf01950aa8d131203248544fb1e41a0d698e846bd419cec3890903ac\",\n \"sig\": \"35fabdae4634eb630880a1896a886e40fd6ea8a60958e30b89b33a93e6235df750097b04f9e13053764251b8bc5dd7e8e0794a3426a90b6bcc7e5ff660f54259\",\n \"tags\": [[\"p\", \"166bf3765ebd1fc55decfe395beff2ea3b2a4e0a8946e7eb578512b555737c99\"]],\n}\n```\n\n## 4. Broadcast Selectively\n\nBroadcast the `kind 1059` event to the recipient's relays only. Delete all the other events.\n\n# Code Samples\n\n## JavaScript\n\n```javascript\nimport {bytesToHex} from \"@noble/hashes/utils\"\nimport type {EventTemplate, UnsignedEvent, Event} from \"nostr-tools\"\nimport {getPublicKey, getEventHash, nip19, nip44, finalizeEvent, generateSecretKey} from \"nostr-tools\"\n\ntype Rumor = UnsignedEvent & {id: string}\n\nconst TWO_DAYS = 2 * 24 * 60 * 60\n\nconst now = () => Math.round(Date.now() / 1000)\nconst randomNow = () => Math.round(now() - (Math.random() * TWO_DAYS))\n\nconst nip44ConversationKey = (privateKey: Uint8Array, publicKey: string) =>\n nip44.v2.utils.getConversationKey(bytesToHex(privateKey), publicKey)\n\nconst nip44Encrypt = (data: EventTemplate, privateKey: Uint8Array, publicKey: string) =>\n nip44.v2.encrypt(JSON.stringify(data), nip44ConversationKey(privateKey, publicKey))\n\nconst nip44Decrypt = (data: Event, privateKey: Uint8Array) =>\n JSON.parse(nip44.v2.decrypt(data.content, nip44ConversationKey(privateKey, data.pubkey)))\n\nconst createRumor = (event: Partial<UnsignedEvent>, privateKey: Uint8Array) => {\n const rumor = {\n created_at: now(),\n content: \"\",\n tags: [],\n ...event,\n pubkey: getPublicKey(privateKey),\n } as any\n\n rumor.id = getEventHash(rumor)\n\n return rumor as Rumor\n}\n\nconst createSeal = (rumor: Rumor, privateKey: Uint8Array, recipientPublicKey: string) => {\n return finalizeEvent(\n {\n kind: 13,\n content: nip44Encrypt(rumor, privateKey, recipientPublicKey),\n created_at: randomNow(),\n tags: [],\n },\n privateKey\n ) as Event\n}\n\nconst createWrap = (event: Event, recipientPublicKey: string) => {\n const randomKey = generateSecretKey()\n\n return finalizeEvent(\n {\n kind: 1059,\n content: nip44Encrypt(event, randomKey, recipientPublicKey),\n created_at: randomNow(),\n tags: [[\"p\", recipientPublicKey]],\n },\n randomKey\n ) as Event\n}\n\n// Test case using the above example\nconst senderPrivateKey = nip19.decode(`nsec1p0ht6p3wepe47sjrgesyn4m50m6avk2waqudu9rl324cg2c4ufesyp6rdg`).data\nconst recipientPrivateKey = nip19.decode(`nsec1uyyrnx7cgfp40fcskcr2urqnzekc20fj0er6de0q8qvhx34ahazsvs9p36`).data\nconst recipientPublicKey = getPublicKey(recipientPrivateKey)\n\nconst rumor = createRumor(\n {\n kind: 1,\n content: \"Are you going to the party tonight?\",\n },\n senderPrivateKey\n)\n\nconst seal = createSeal(rumor, senderPrivateKey, recipientPublicKey)\nconst wrap = createWrap(seal, recipientPublicKey)\n\n// Recipient unwraps with his/her private key.\n\nconst unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey)\nconst unsealedRumor = nip44Decrypt(unwrappedSeal, recipientPrivateKey)\n```\n",
"created_at": 1716813924,
"id": "00c1fcb7fec285e61ac58a3c97e3a6cadb1ba4e5157f8c6ee4564d9e415f2ee6",
"kind": 30818,
"pubkey": "dd664d5e4016433a8cd69f005ae1480804351789b59de5af06276de65633d319",
"sig": "34f956c2d18e80ee770a511d6509b9b05b889c7797767b7d1bd529600d42d73ebd64cb5e7b5f1759cc4c52ec7b9e2ea34db5f7e7d6ae0c43b099cb96a456b369",
"tags": [
[
"d",
"nip-59"
],
[
"client",
"wikifreedia",
"31990:fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52:1716498133442"
],
[
"title",
"NIP-59"
],
[
"c",
"Nostr"
],
[
"published_at",
"1716813924"
]
]
},
"relays": [
"wss://theforest.nostr1.com/",
"wss://nostr.land/",
"wss://nostr21.com/",
"wss://relay.layer.systems/"
],
"address": {
"identifier": "nip-59",
"pubkey": "dd664d5e4016433a8cd69f005ae1480804351789b59de5af06276de65633d319",
"kind": 30818,
"relays": [
"wss://nos.lol/"
]
}
}