Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NIP-61 - Event Sets #784

Closed
Closed
Changes from 33 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
eb58bbb
Add bunch
arthurfranca Sep 17, 2023
0b56cbc
Change to unbounded lists
arthurfranca Sep 18, 2023
2885dcf
Use s tag and nset entity for referencing
arthurfranca Sep 20, 2023
02dbc0f
Change how unbounded lists are created and referenced
arthurfranca Sep 20, 2023
01d5527
Reserve kind and add example
arthurfranca Sep 21, 2023
17f6cbd
Add contact event example
arthurfranca Sep 21, 2023
502a40b
Update examples
arthurfranca Sep 21, 2023
b0e96e5
Add suggestion
arthurfranca Sep 21, 2023
c209e55
Fix relationship status event example
arthurfranca Sep 21, 2023
fc97c78
make text more intelligible
arthurfranca May 10, 2024
7d20215
Add contacts list to the example
arthurfranca May 10, 2024
5773354
Add lowercase convention
arthurfranca May 10, 2024
eefdb7e
Add more info about differences in relation to NIP-51
arthurfranca May 10, 2024
9dbedf0
Update 61.md
arthurfranca May 10, 2024
4cb16ea
Update 61.md
arthurfranca May 10, 2024
049a6ec
Update 61.md
arthurfranca May 10, 2024
f49e3ef
Update 61.md
arthurfranca May 10, 2024
e916266
Apply suggestions
arthurfranca May 10, 2024
ca6a812
Replace list with set
arthurfranca May 10, 2024
f1df1d6
Make room for NIP-51 migration
arthurfranca May 15, 2024
4149111
Apply suggestions
arthurfranca May 16, 2024
3566a45
Add private sets
arthurfranca May 21, 2024
4415ffc
move sections
arthurfranca May 21, 2024
fec7136
Add missing item types
arthurfranca May 22, 2024
7c39167
Add pin set
arthurfranca May 22, 2024
3d904c3
Update 61.md
arthurfranca May 23, 2024
03bd37f
Update 61.md
arthurfranca May 23, 2024
d16e8b9
Update 61.md
arthurfranca May 23, 2024
47a0ace
Update 61.md
arthurfranca May 23, 2024
6877070
Move things and change kind 30061
arthurfranca May 23, 2024
b259608
Remove orphaned sentence
arthurfranca May 28, 2024
dc0d92c
Add p tag to 30383 and 31383
arthurfranca Jun 9, 2024
fa9ec29
Add notification and upload set names
arthurfranca Jun 9, 2024
1f06fb1
hide private tags
arthurfranca Jun 14, 2024
c339d5d
Remove set names related to NIP-51
arthurfranca Jun 20, 2024
fdb6444
Remove set item reference events
arthurfranca Jun 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions 61.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
NIP-61
======

Event Set
---------

`draft` `optional`

An event set is represented by a group of events, not a single event.
Each event of a specific set has the same pubkey and `n` (set "n"ame) tag.

An event can have many `n` tags, thus being member of many event sets.
To remove an event from a set, remove the corresponding `n` tag from the event or
send a deletion event (`kind:5`).

## Referencing

An event set is referenced by an `s` tag. It
references many events from an author instead of a single one:

`["s", "<kind>:<pubkey>:<n-tag>", "<recommended-relay-URL, optional>"]`

One may also use an `nset` [NIP-19](19.md) entity through a [NIP-21](21.md) URI: `nostr:nset1qqstn...794234d`.

## How to add items to a set

Just add an `n` tag to your event. For example, adding one of your long-form posts to a set named "a-set-name-example":

```js
{
"kind": 30023,
"tags": [
["d", "<random>"],
["n", "a-set-name-example"]
// other tags
]
// other fields
}
```

## Tagging into Event Sets

You SHOULD use a list of pre-defined event kinds to tag existing events, public keys, or other references into sets.

The kind and `d` tag value will vary depending on the type of item being referenced.

**Some** of the event kinds **require** a `k` tag set to the referenced item kind to help with filtering or other tags.

**Table of "Set Item Reference" Event Kinds and Structure by Referenced Item Type**:

| Type | Kind | d-tag | other required tags |
arthurfranca marked this conversation as resolved.
Show resolved Hide resolved
|-|-|-|-|
| **pubkey** | 30382 | `<user-pubkey>` | |
| **event** | 30383 | `<event-id>` | `["k", "<event-kind>"]`, `["p", "<event-pubkey>"]` |
| **(parameterized) replaceable event** | 30384 | `<event-kind>:<event-pubkey>:<event-d-tag>` | `["k", "<event-kind>"]` |
| **hashtag** | 30385 | `<t-tag>` | |
| **url** | 30386 | `<url-with-protocol-without-trailing-slash>` | |
| **relay** | 30387 | `<relay-url-with-protocol-without-trailing-slash>` | |
| **emoji** | 30388 | `<emoji-image-url-with-protocol-without-trailing-slash>`) | `["emoji", "<shortcode>"]` |
| **word** | 30389 | `<lowercase-string>` | |

These events may have tags other than `n` with extra metadata about the referenced item.
Because of that, when removing some metadata during an edit, clients should only delete the event when all tags other
than the `d` one were removed and the content is an empty string.

Example of 1 item (a pubkey) that is simultaneously part of 3 event sets:

```js
{
"kind": 30382,
"pubkey": "<me>",
"tags": [
["d", "<another-user-pubkey>"],
alexgleason marked this conversation as resolved.
Show resolved Hide resolved
["n", "follow"],
["n", "friend"],
["n", "Best Friends Forever"]
// ...other metadata
],
// ...empty string or secret metadata
"content": nip44Encrypt(JSON.stringify([
["petname", "Zygote"],
["summary", "Owes me a beer"],
// ...other secret metadata
])),
// ...other fields
}
```

### Private "Set Item Reference" Event:

What makes such events private is the process of obfuscating the `d` tag value and adding the clear version of it
inside the encrypted `.content`.

To obfuscate a value, it is deterministically hashed by generating an auxiliary private key
with the [NIP-44](44.md) HKDF function and salt set to "nip61", concatenating it with the to-be-obfuscated value,
hashing the result with SHA256, then encoding it to hex.

**Table of Private "Set Item Reference" Event Kinds and Structure by Referenced Item Type**: Note that `obfuscate(<value>)`
is representing the step taken to hash the `d` tag value explained above.

| Type | Kind | d-tag | other required tags |
|-|-|-|-|
| **pubkey** (private) | 31382 | obfuscate(`<user-pubkey>`) | |
| **event** (private) | 31383 | obfuscate(`<event-id>`) | `["k", "<event-kind>"]`, `["p", obfuscate("<event-pubkey>")]` |
| **(parameterized) replaceable event** (private) | 31384 | obfuscate(`<event-kind>:<event-pubkey>:<event-d-tag>`) | `["k", "<event-kind>"]` |
| **hashtag** (private) | 31385 | obfuscate(`<t-tag>`) | |
| **url** (private) | 31386 | obfuscate(`<url-with-protocol-without-trailing-slash>`) | |
| **relay** (private) | 31387 | obfuscate(`<relay-url-with-protocol-without-trailing-slash>` | |
| **emoji** (private) | 31388 | obfuscate(`<emoji-image-url-with-protocol-without-trailing-slash>`) | `["emoji", "<shortcode>"]` |
| **word** (private) | 31389 | obfuscate(`<lowercase-string>`) | |

Example event:

```js
{
"kind": 31382,
"pubkey": "<me>",
"tags": [
["d", toHex(sha256(hkdf("<my-private-key>", salt: "nip61") || "<another-user-pubkey>"))]
["n", "example-set-name"]
],
"content": nip44Encrypt(JSON.stringify([
["d", "<another-user-pubkey>"]
// ...other secret metadata
])),
// ...other fields
}
```

## Set Names

There are two types of sets: those with a "standard" name and those with a "custom" one (most likely user-generated name).

### Standard

Standard sets have one of the names (the `n` tag value) listed here to guarantee interoperability.
The convention is to prefer lowercase English words in singular form with dash separator.

| n-tag | description |
|-|-|
| bookmark | list of things a user wants to save |
| contact | pubkeys to whom the user sent DMs or started an interaction such as an one-on-one audio/video call |
| file | adds events and files (urls) to a root directory similar to "/" |
| follow | followed pubkeys, hashtags, etc |
| friend | friends' pubkeys |
| mute | pubkeys, hashtags, words or notes the user doesn't want to see in their feeds and blocked relays |
| notification | notes monitored for new replies, zaps and reactions |
| pin | events to showcase in the user profile page |
| upload | files (urls) - requires `m` tag set to the MIME type |

### Custom

To **keep track of the custom set names**, the encrypted parameterized replaceable "Custom Sets" `kind:30061` event is used.

Such event with `d-tag=""` lists custom set names with no expected special treatment by clients
(these sets effectively work like labels).

However, adding a **standard** set name to the `d` tag instructs clients to treat the listed custom sets the same as they treat
the corresponding **standard** set. For example, sets listed within a `kind:30061` with `d-tag="follow"` are treated
by clients the same way as the "follow" set, i.e., they could be used as custom feeds.

A `kind:30061` event has one `map` tag for each custom set. A `map` tag has the first value as the custom set name.
When the user doesn't want to reveal the real custom set name, the `map` tag's first value can be a fake or random string
that maps to the `map` tag's second value, which holds the real custom set name that just the user will see.

Examples:

```js
{
"kind": 30061,
"tags": [
["d", ""]
]
"content": nip44Encrypt(JSON.stringify([
["map", "<public-name>", "<real-name>"],
["map", "Good Fellow", "Good Fellow"],
["map", "6064460175057025", "Debtor"]
])),
// ...other fields
}
```

```js
{
"kind": 30061,
"tags": [
["d", "file"]
]
"content": nip44Encrypt(JSON.stringify([
["map", "4234649720408324", "/sub/directory"],
["map", "/home", "/home"]
]))
// other fields
}
```