🤖 ## Summary Clients cannot tell who created a channel, and only the channel-metadata event says what type of channel it is. This PR makes every relay-signed channel-state event carry both facts, so a client can find "the channel that this key created" with one ordinary Nostr filter. - Channel metadata, admin list and member list events (kinds 39000, 39001 and 39002) now all carry the channel type as their first `t` tag, then the creator as a `P` tag. - The creator comes from the relay's own record of who signed the channel's create request. No client can set it, and it does not change when ownership moves. - Channel metadata (kind:39000) also carries `["created_at", <unix seconds>]`, the time the channel was created. The event's own `created_at` changes on every edit, so before this a client could not tell which of two channels is older. This is part 1 of a 3-part stack (channel labels and creator lookup). The system channels stack (#8180–#8182) builds on it. ### Related issue None found. ### Details - **Why `P`.** Several Nostr specs (NIP-22, NIP-34, NIP-72) use uppercase `P` for the author of the root object, and lowercase `p` for people involved. Lowercase `p` is already used on these events for members and DM participants, and a filter matches tag values, not their meaning, so `#p` would mix them. - **Trust.** A client that trusts a channel because of its creator must also check that the relay key signed the event. - **Order.** The type `t` tag comes first, so clients that read the first `t` tag still get the type after part 2 adds labels as more `t` tags. - **Creation time.** The relay reads it from `channels.created_at`, so an edit does not change it. #8176 uses it to pick one channel when two creates race. - `buzz-admin reconcile` writes the same tags when it rebuilds these events. `NOSTR.md` documents the tags. - **Spec.** New `docs/nips/NIP-CL.md` defines these tags, the trust rule and lookup by creator. Later PRs in both stacks add their parts to it. `NOSTR.md` links to it. ### Testing - Relay tests in `group_state_postgres_tests.rs`: each of the three kinds carries the type first, then the creator; the creator stays the same after an ownership transfer; kind:39000 carries the stored creation time, and still does after an edit. - Unit test for the shared tag builder that both the relay and `buzz-admin reconcile` use. ### Stack These are two stacks. Merge them in this order. Client support (creating, labeling and finding these channels) will be in block/buzz-app, not the buzz SDK and CLI. **Stack 1: channel labels and creator lookup (#8174–#8176)** 1. #8174 channel type and creator tags, and the channel creation time ← this PR 2. #8175 channel labels 3. #8176 database-side label and creator match, and the rule for choosing one channel **Stack 2: system channels (#8180–#8182)**, stacked on stack 1 because it uses labels and the creator tag 4. #8180 the system channel type 5. #8181 leave system channels out of reads that do not name them 6. #8182 channel-state filters opt in to system channels --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
3.5 KiB
NIP-CL
Channel Identity and Discovery
draft optional relay
This NIP adds identity facts to the relay-signed NIP-29 channel-state events (kinds 39000–39002). With these facts, a client can find channels with ordinary NIP-01 filters. It needs no new filter key and no relay-specific API.
Motivation
Plugins and agents often create a channel for their own use and must find it again later, for example "the config channel that this agent created". With plain NIP-29, a client cannot:
- tell who created a channel;
- read the channel type from any event except kind:39000; or
- tell which of two channels is older. The
created_atof a kind:39000 event is the time of the last edit, not the time of creation.
Channel identity tags
On every channel-state event that the relay signs, the relay MUST put these
tags in this order, before any other t or P tag:
["t", <channel type>], for examplestream,forum,dm,workflow.["P", <creator public key, hex>].
kind:39000 also carries ["created_at", <unix seconds>].
Example kind:39000 (signing fields and unrelated tags omitted):
{
"kind": 39000,
"pubkey": "<relay public key>",
"created_at": 1760025600,
"tags": [
["d", "9b353519-f4fe-4757-aef4-bec6cc0ae54c"],
["name", "agent-config"],
["t", "stream"],
["P", "<creator public key>"],
["created_at", "1759939200"]
]
}
| Tag | Kinds | Meaning |
|---|---|---|
first t |
39000–39002 | Channel type. |
P |
39000–39002 | Public key that signed the kind:9007 that created the channel. |
created_at |
39000 | Time the channel was created, in unix seconds, as a decimal string. |
Channel type. Clients MUST read the channel type from the first t tag.
A relay can add other t tags after it.
Creator. The relay writes P from its own record of the kind:9007 that
created the channel. No client can set it. It does not change when ownership
moves to another member or when the channel is edited.
Uppercase P follows NIP-22, NIP-34 and NIP-72, where P names the author of
the root object. On these kinds, lowercase p already lists members, admins
and DM participants. A filter matches tag values, not their roles, so #p
would mix creators with members.
Creation time. The created_at tag does not change when the channel is
edited. Use it, not the event's created_at, to compare the age of channels.
Older events. A relay adds these tags only when it publishes a
channel-state event. An event that a relay version without this NIP published
can lack them, including one published during an upgrade. Such an event stays
until the channel next changes, for example on an edit or a membership
change. A client MUST treat a missing tag as unknown. A missing P does not
mean that a key did not create the channel.
Finding channels
Use ordinary NIP-01 filters on the channel-state kinds.
| To find | Filter |
|---|---|
| Channels that a key created | {"kinds": [39000], "#P": ["<creator>"]} |
| Channels of one type | {"kinds": [39000], "#t": ["<type>"]} |
A #P filter does not return a channel whose events lack P (see
Older events). So it can miss channels that a key created before the relay
supported this NIP.
Trust
A client that relies on these tags MUST check that the relay's key signed the event, as for all NIP-29 group-state events.
Relation to other NIPs
- NIP-29: the group-state events that this NIP adds tags to.
- NIP-01: the filters that find channels.
- NIP-22, NIP-34 and NIP-72: the convention for uppercase
P.