Update 52.md

Based on comments (thank you!), I rewrote this NIP and focused purely on badges, which was just one of the use cases under "Issued Claims". This greatly simplified the NIP. 

I also specified the data schemas for two different badge types to be part of the protocol itself, versus leaving it open. A bunch of stuff, like the Refresh Claim Request event or requiring event treated as parameterized replaceable event were removed as not necessary for just the badges use case.

There are probably more badge types that would be useful.
This commit is contained in:
neilck
2023-01-25 16:10:32 -08:00
committed by GitHub
parent 9208bdbd66
commit ce19442bde

187
52.md
View File

@@ -2,154 +2,117 @@
NIP-52 NIP-52
====== ======
Issued Claims Badges
------------- ------
`draft` `optional` `author:neilck` `draft` `optional` `author:neilck`
This NIP is currently in draft, and review / comments are sought. May overlap with [NIP-32](https://github.com/nostr-protocol/nips/blob/39bfb2db32899dd2eb4a7c9a313f27103a18409b/32.md). *This NIP is currently in draft, and review / comments are sought.*
A claim is a statement of fact made by an issuer regarding a subject. This NIP enables an app owner (or community manager) to award of badges to their members.
Example claims include Like Kind 0, badges provide metadata related to a pubkey, but the event is sent (and thus signed) by the app owner, not the subject's pubkey.
- Alice graduated from the University of Southern California
- Bob, the control of pubkey 123EED..., also controls Twitter handle tinydancer
- Charlie is over 21 years of age
Like Kind 0 metadata, issued claims provide additional data about pubkey, and by extension, the controller of the pukey. ## Kind XX: Issue Badge
Unlike Kind 0 metadata, the data is sent by and vouched for by issuers, which is more trusthworthy than self-reported data, provided we trust the issuer. EVENT sent from client to relay to issue a badge.
The concept of an issuer comes from [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/).
It reserves two event kinds (30001, 1001).
- `30001 - issue claim`
- `1001 - refresh claim request`
An issue claim event records what one user attests is true about another user.
## Kind 30001: Issue Claim
Sent by a user acting as an issuer, submitting a claim about another user, the claim's subject.
```json ```json
{ {
"id": <32-bytes lowercase hex-encoded sha256 of the the serialized event data> "id": <32-bytes lowercase hex-encoded sha256 of the the serialized event data>
"pubkey": <32-bytes lowercase hex-encoded public key of the issuer>, "pubkey": <32-bytes lowercase hex-encoded public key of the app owner issuing the badge>,
"created_at": <unix timestamp in seconds>, "created_at": <unix timestamp in seconds>,
"kind": 30001, "kind": XX,
"tags": [ "tags": [
["p", <32-bytes hex of the subject's pubkey>, <recommended relay URL>], ["p", <32-bytes hex pubkey of the member receiving the badge>],
["d", <32-bytes hex of the subject's pubkey + claim type>], ["badge_type", <integer specifying badge type>]
["claim_type", <optional string specifying claim type>],
["is_maintained", <optional boolean, yes if refresh claim supported>]
], ],
"content": <claim data>, "content": <stringified json object, matching badge_type schema>,
"sig": <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field> "sig": <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field>
} }
``` ```
For example, issuer nostr.directory with hexPubKey 2a1...234 is claiming that nostr user with hexPubKey 3bf...6w6 also controls twitter handle fiatjaf `p tag` MUST contain exactly one entry, the pubkey of the claim's subject.
`badge_type` identifies badge type. Current accept values are `0 - Achievement Badge` and `1 - Membership Badge`
Like Kind 0, badges provide metadata about a pubkey, but the event is sent (and thus signed) by the app owner, not the subject's pubkey.
Two badge types are defined, with the possibility of adding more if required.
### 0 - Achievement Badge
Awarded to a user for an achievement.
Achievement Badge Schema
```json ```json
{ {
"id": <32-bytes lowercase hex-encoded sha256 of the the serialized event data> "issuer_name": <string of app name or community name>,
"pubkey": "2a1...234", "name": <string of achievement name>,
"created_at": <unix timestamp in seconds>, "message": <string of message>,
"kind": 30001, "image": <url of badge image>,
"tags": [ "score": <optional integer of score value>
["p", "3bf...6w6", <recommended relay URL>],
["d", "3bf...6w6:twitteraccount"],
["claim_type", "twitteraccount"],
["is_maintained", true]
],
"content": {
"twitter_handle": "fiatjaf",
"profile_url": "https://twitter.com/fiatjaf"
},
"sig": <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field>
} }
``` ````
`"d" tag` is required as the event is treated as a parameterized replaceable event [NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md). Issuers issuing more than one type of claim will need to append a claim type identifier to the subject's pubkey hex to prevent overwrites. Example as content
The p tag MUST contain exactly one entry, the pubkey of the claim's subject.
Self-issued claims (issuer is the subject) is specifically allowed.
## Kind 1001: Refresh Claim Request
A claim may change over time. This event allows a user to request the issuer submit an updated issue claim event for the same subject and claim_type.
Refresh Claim Request
```json ```json
{ {
"id": <32-bytes lowercase hex-encoded sha256 of the the serialized event data> "content": "{\"issuer_name\": \"nostr.directory\", \"name\": \"Donation\", \"message\": \"User has sent a sats donation to noster.directory.\", \"image\": \"https://myapp.com/badges/donation.svg\", \"score\": 1000}",
"pubkey": <32-bytes lowercase hex-encoded public key of the sender>, ...
"created_at": 1674607473,
"kind": 1001,
"tags": [
["e", <kind>:<pubkey>:<d-tag>, <relay-url>]
],
"sig": <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field>
} }
``` ````
For example, if an issue claim states a user is employed by Microsoft, but is 1 year out of date (1643099996 = Jan 25, 2022), an client may request issuer update the issue claim to check if they still work there. - issuer_name - name of app or community. e.g. "Nosterplebs", "Continuous Delivery Foundation"
- name - name of the achievement. e.g. "Donation", "Top Bug Crusher"
- message - Text description of the achievement
- image - url link to square badge image (1:1 ratio). Ideally .svg image, but if not SHOULD be 32 x 32 pixels.
- score - optional integer value. Include when you need to rank achievements. e.g. Display "High Score" list in descending order
Issue Claim Example Client may include additional key/value pairs in content.
### 1 - Membership Badge
Sent by app owner to confirm that a user is the member of app, community or group.
Membership Badge Schema
```json ```json
{ {
"created_at": 1643099996, "issuer_name": <string of app name or community name>,
"kind": 30001, "image": <url of badge image>,
"tags": [ "role": <optional string of role of user within the community>,
["p", "37a...100", <recommended relay URL>], "id": <optional string of unique user identity>,
["d", "37a...100:employer"], "alias": <optional string ofuser alias>,
["claim_type", "employer"], "member_since": <optional unix timestamp of when member joined>,
["is_maintained", true] "expiry_date": <optional unix timestamp of membership expiry date>,
], "profile": "<optional url to user profile page in app>",
"content": { "followers": <optional integer count of followers if applicable>,
"organization_name": "microsoft", "connections": <optional integer count of connections if applicable>,
"title": "Senior Software Developer" "posts": <optional integer count of posts if applicable>
},
"sig": <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field>
} }
``` ````
Related Refresh Claim Request Example Example as content
```json ```json
{ {
"id": <32-bytes lowercase hex-encoded sha256 of the the serialized event data> "content": "{\"issuer_name\": \"Nostrplebs\", \"image\": \"https://nostrplebs.com/images/member.svg\", \"role\": \"Premium Member\"}",
"pubkey": <32-bytes lowercase hex-encoded public key of the sender>, ...
"created_at": 1674607473,
"kind": 1001,
"tags": [
["e", "30001:37a...100:employer", <relay-url>]
],
"sig": <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field>
} }
``` ````
A client SHOULD check that the original event contains "is_maintained": true before sending event to relay - issuer_name - name of app or community. e.g. "Nostrplebs", "Continuous Delivery Foundation"
- image - url link to square badge image (1:1 ratio). Ideally .svg image, but if not SHOULD be 32 x 32 pixels.
A relay MAY choose to ignore this event if original event does not contain "is_maintained" - role - role of user within the commnity. e.g. "Moderator", "Administrator", "Reviewer"
- id - unique user ID, usually system generated e.g. Twitter: user.fields.id
Design Notes - alias - unqiue user ID set by user e.g. Twitter: user.username
------------ - member_since - date user first became a member
- expiry_date - include if membership has expiry date, usually for paid memberships
### Why treat issue claim events as a parameterized replaceable event (NIP-33)? - profile - url link to user's profile wihin the app
- followers - count of followers, if applicable
Treating issue claim events as parameterized replaceable events allows any consumer to get the only latest issue claim event for a user by setting a subscription filter specifying the kind, issuer, the subject as the p tag, and a limit of 1. - connections - count of connections e.g. On LinkedIn, a user accepts connections for other users, but anyone can become a follower
- posts - total count of tweets, posts, messages, etc.
### Why is "is_maintained" necessary?
It's optional, and not technically necessary, but is a practical consideration.
- If adhered to, prevents clients from sending refresh claim request events that are useless, reducing "garbage" events relays have to handle
- Allows users acting as issuers to periodically send new issue claim events, updating claims for individual users "on demand" only when necessary
Client may include additional key/value pairs in content.
Motivation Motivation
---------- ----------
As a protocol, Nostr is application agnostic, easy to implement, and censorship resistant. While current apps built on Nostr focus on messaging (Twitter, Telegram, etc.) Nostr can be the underlying protocol of other social media apps as well (Instagram, LinkedIn, Tinder, etc.). These apps require more profile information than Nostr current supports. Rather than each app implementing its own profile, it would be nice if we can leverage Nostr for decentralized identity.
Badges allow a user to be recognized for their contributions within an app / community. With badges stored as events in Nostr against pubkeys, users can bring their reputation with them as they switch between apps, something they can't do with current social media apps.