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
).
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 entity through a NIP-21 URI: nostr:nset1qqstn...794234d
.
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":
{
"kind": 30023,
"tags": [
["d", "<random>"],
["n", "a-set-name-example"]
// other tags
]
// other fields
}
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 |
---|---|---|---|
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:
{
"kind": 30382,
"pubkey": "<me>",
"tags": [
["d", "<another-user-pubkey>"],
["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
}
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 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", obfuscate("<event-kind>")] , ["p", obfuscate("<event-pubkey>")] |
(parameterized) replaceable event (private) | 31384 | obfuscate(<event-kind>:<event-pubkey>:<event-d-tag> ) |
["k", obfuscate("<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> ) |
addToEncryptedContent(["emoji", "<shortcode>"]) |
word (private) | 31389 | obfuscate(<lowercase-string> ) |
Example event:
{
"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
}
There are two types of sets: those with a "standard" name and those with a "custom" one (most likely user-generated name).
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 |
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:
{
"kind": 30061,
"tags": [
["d", ""]
]
"content": nip44Encrypt(JSON.stringify([
["map", "<public-name>", "<real-name>"],
["map", "Good Fellow", "Good Fellow"],
["map", "6064460175057025", "Debtor"]
])),
// ...other fields
}
{
"kind": 30061,
"tags": [
["d", "file"]
]
"content": nip44Encrypt(JSON.stringify([
["map", "4234649720408324", "/sub/directory"],
["map", "/home", "/home"]
]))
// other fields
}