mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-12-12 18:36:24 +00:00
Merge 22c700c944
into a1ca7a194b
This commit is contained in:
commit
c42f2fc19f
267
101.md
Normal file
267
101.md
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
# NIP-101 - Forms On Nostr
|
||||||
|
|
||||||
|
`draft` `optional` `author:abhay-raizada`
|
||||||
|
|
||||||
|
The nip provides a way to implement a feedback mechanism(forms) on nostr.
|
||||||
|
|
||||||
|
## Form Template - Public
|
||||||
|
|
||||||
|
Event `30168` describes a form as a parametrized replaceable event, with `field` tags that contain the description of each form field, with optional settings
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"kind": 30168,
|
||||||
|
"content" : ""
|
||||||
|
"tags": [
|
||||||
|
["d", "<form identifier>"],
|
||||||
|
["name", "Name of the form"],
|
||||||
|
["settings", JSON.stringify({description: "<description of the form."})],
|
||||||
|
["field", "<fieldId>","<input-type>","<label for the field>","<Options (for option type)>", "<stringified JSON settings>"],
|
||||||
|
["field", "<fieldId>", "option", "label for options field",
|
||||||
|
JSON.stringify([["<optionId1>", "option label", "<optionId2>", "option label"]]),
|
||||||
|
"<stringified JSON settings>"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"pubkey": "<Author of the form>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The different tags used to detail the form are described as:
|
||||||
|
|
||||||
|
**d** - The unique identifier of a form, for a user
|
||||||
|
|
||||||
|
**name** - The name of the form`
|
||||||
|
|
||||||
|
**settings** - A form wide settings object, that can detail styles and visual representation of the form, certain fields in the object may be client specific
|
||||||
|
|
||||||
|
**field** - contains the description of a form field.
|
||||||
|
|
||||||
|
## Field Tag
|
||||||
|
|
||||||
|
A field tag is described as the following array:
|
||||||
|
`["field", FieldId, InputType, Label, Options, Field Settings ]`
|
||||||
|
|
||||||
|
**FieldId** - Is used to identify the field in the form, can be any alphanumeric string.
|
||||||
|
|
||||||
|
**InputType** - Can be one of: "text", "option", "label". All other types can be derived from the text type.
|
||||||
|
|
||||||
|
**Label** - Value that describes the field, can be a question the user needs feedback eg: "What is your age?"
|
||||||
|
|
||||||
|
**Options** - A stringified array of Options in case the user selected an option type.
|
||||||
|
|
||||||
|
**Field Settings** - A stringified JSON that may contain metadata about a field, like wether the field is a "required" field or "prerequisites" for a field.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
The options array is a stringified Array of Option Tags, where each option tag is
|
||||||
|
represented as:
|
||||||
|
`[<OptionId>, <label>, <optional config>]`
|
||||||
|
|
||||||
|
**OptionId** - Similar to FieldId it is an alphanumeric Id that serves as an identifier for that Id in the form.
|
||||||
|
|
||||||
|
**label** - A label describing the option.
|
||||||
|
|
||||||
|
**Optional Config** - metadata object holds information about the options like "prerequisites".
|
||||||
|
|
||||||
|
## Responses
|
||||||
|
|
||||||
|
Response events are events that are attached to a form(3068 kind event), and the response data is stored in a `kind: 1069` event
|
||||||
|
|
||||||
|
Response structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 1069,
|
||||||
|
"content": "",
|
||||||
|
"tags": [
|
||||||
|
["a", "30168:<pubkey of the author>:<form identifier>"],
|
||||||
|
[
|
||||||
|
"response",
|
||||||
|
"<FieldId>",
|
||||||
|
"<response as string>",
|
||||||
|
"<stringified metadata object>"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"pubkey": "Author of Response"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The Response tag contains a "response" tag identifier "fieldId" the Id of the field as mentioned in the `kind:30168` event, response for the field as a string value, and a stringified metadata object.
|
||||||
|
|
||||||
|
for option fields, the response is the id of the option selected. In case of multiple-choice fields these id's maybe delimited by a semi-colon, ";", For example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 1069,
|
||||||
|
"content": "",
|
||||||
|
"tags": [
|
||||||
|
["a", "30168:<pubkey of the form author>:<formId>"],
|
||||||
|
[
|
||||||
|
"response",
|
||||||
|
"<FieldId>",
|
||||||
|
"ZCZC;XCZXCZ;Z34Z",
|
||||||
|
"<stringified metadata object>"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"pubkey": "Response author"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response Editability
|
||||||
|
|
||||||
|
if the form setting allows for editable responses. The latest timestamp event should be used to render the response.
|
||||||
|
|
||||||
|
For uneditable responses, an open timestamp attestion [[03.md]] should be added, the event with an attestation for the earliest time should be used.
|
||||||
|
|
||||||
|
## Access Control
|
||||||
|
|
||||||
|
Access control is managed by sending a set of 3 keys to users using gift wrap events as described in [nip-59](./59.md) but with a few modifications.
|
||||||
|
|
||||||
|
### Rumor
|
||||||
|
|
||||||
|
A kind:18 rumor is created that holds the keys in a "key" tag. An example event is:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"created_at": 1718188232,
|
||||||
|
"content": "",
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
"key",
|
||||||
|
"026b71adbdc2fd168c17778311950c40afd33a7e08b4d3159f441cfe37a83882",
|
||||||
|
"ca2f78e4fb0132232fbb673e4517498d2aa95d0cd2f9913d820a3fb91912c2d5",
|
||||||
|
"ce6b464f2aff3ca2aa46359a0b91d28027a0283e2946d9a7dc742d20470cdd52"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"kind": 18,
|
||||||
|
"pubkey": "373a87e2c80114a4c15c422a5c3a59acf36ff383cb534d576aea6b08f8e58ee0",
|
||||||
|
"id": "3e2f775af6867b2220bce59a5c2eacde3e2f405c7fc956af09939f7b02a60771"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Seal
|
||||||
|
|
||||||
|
Works exactly as defined in [[59.md]]
|
||||||
|
|
||||||
|
### Wrap
|
||||||
|
|
||||||
|
The kind `1059` event also works similar to as described in [[59.md]] except that instead of referencing a users pubkey, we refer to an alias pubkey which is derived by hashing the form event information to the users pubkey as follows:
|
||||||
|
|
||||||
|
1. The hash function used is SHA256.
|
||||||
|
2. The input to the hash function is: `${30168}:${formAuthor}:${formId}:${userPub}`
|
||||||
|
|
||||||
|
**formAuthor** - The pubkey for the `kind:30168` event
|
||||||
|
**formId** - d-tag of the event
|
||||||
|
**userPub** - pubkey to recepient being given the access.
|
||||||
|
|
||||||
|
code for alias generation
|
||||||
|
```ts
|
||||||
|
import {bytesToHex } from "@noble/hashes/utils"
|
||||||
|
import { sha256 } from "@noble/hashes/sha256"
|
||||||
|
|
||||||
|
let aliasPubKey = bytesToHex(sha256(`${30168}:${formAuthor}:${formId}:${userPub}`));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keys
|
||||||
|
|
||||||
|
The "key" tag in the `kind:18` rumor is represented as:
|
||||||
|
`["key", "<view key>", "<signing key>", "voter key"]`
|
||||||
|
|
||||||
|
- **View Key** - A key which used to encrypt a form content, the view key can make the form viewable.
|
||||||
|
|
||||||
|
- **Signing Key** - The private key to the form event. Anybody with this key WILL BE ABLE TO EDIT THE FORM EVENT.
|
||||||
|
|
||||||
|
- **Voter Key** - A key used to submit a response to the form in a poll-like scenario.
|
||||||
|
|
||||||
|
### Submit Access.
|
||||||
|
|
||||||
|
p-tags of the selected participants must be added to the form tags, and only query the responses from the p-tags mentioned in the form.
|
||||||
|
|
||||||
|
### Encrypted Responses.
|
||||||
|
|
||||||
|
Response tags are added to the `.content` field of the event and encrypted as per the spec in [nip-44](./44.md) by the responders private key and the form authors public key.
|
||||||
|
|
||||||
|
### Private Forms only viewable by a group.
|
||||||
|
|
||||||
|
Form fields and settings should be ommitted from the tags array and placed in the `.content` key, nip-44 encrypted by the corresponding public key of the view-key, and the signing key as private key. The selected responders can decrypt the form using the view key. The `tags` array is used to keep track of the allowed-responders identities.
|
||||||
|
|
||||||
|
```js
|
||||||
|
let encryptionKey = nip44.v2.utils.getConversationKey(
|
||||||
|
bytesToHex(signingKey),
|
||||||
|
getPublicKey(viewKey)
|
||||||
|
);
|
||||||
|
content = nip44.v2.encrypt(JSON.stringify(form), encryptionKey);
|
||||||
|
```
|
||||||
|
|
||||||
|
They view key is shared as a gift wrap to the participants as described above.
|
||||||
|
|
||||||
|
### Polls
|
||||||
|
|
||||||
|
There are some important parts for a form(general feedback mechanism) to become a poll.
|
||||||
|
|
||||||
|
1. Only elligeble candidates must be allowed to vote.
|
||||||
|
2. Participants shouldn't be able to associate a response to another participant.
|
||||||
|
3. Participants should be able to verify that their response is counted.
|
||||||
|
|
||||||
|
All of these conditions can be met by establishing a "Voter Key". The Voter Key is a private key generated by someone with edit access to the form (Issuer).
|
||||||
|
|
||||||
|
The Issuer must then add a "p" tag to the form event, followed by a pubkey corresponding to the voterId.
|
||||||
|
|
||||||
|
The voter must sign their responses with the issued voter key.
|
||||||
|
|
||||||
|
The p tag is used to query eligible votes.
|
||||||
|
|
||||||
|
Example form with a voter id.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"content": "",
|
||||||
|
"created_at": 1718186931,
|
||||||
|
"id": "0bb2e5d100271c11957cc0a753246acbc91f29a20c40cbd4c560731e324ed069",
|
||||||
|
"kind": 30168,
|
||||||
|
"pubkey": "f767d6a03639aa0f9c0fd671d496086a0fcd86d958ea31f0789c9b27daf66d70",
|
||||||
|
"sig": "d4b725fff0811e64b05616b3320a291ee698d2dbb0f5ef6a9706ed9944aef38f48449916e9598b7a190f54def5c6141e625712d118550ef10da1ef26442f5f72",
|
||||||
|
"tags": [
|
||||||
|
["d", "bestBreakfast"],
|
||||||
|
["name", "This is the title of your form! Tap to edit."],
|
||||||
|
["field", "egtD6v", "option", "What is the best breakfast?", "[[\"2KJ6h4\",\"Omelette\"],[\"1m3a5q\",\"Pancakes\"]]", "{\"renderElement\":\"radioButton\"}"
|
||||||
|
]
|
||||||
|
["p", "fb740690af9329e25b0a3c1f6ce6a24c4ff98dcba56d3579381ee340ea0350d4"],
|
||||||
|
["p", "6b557be286b13d7a85b3823c630050db043c9a28bf606aa49c65b3db0c3208b6"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Querying Form Template & Responses
|
||||||
|
|
||||||
|
form template maybe filtered using the form author's pubkey and the d-tag as follows
|
||||||
|
|
||||||
|
```js
|
||||||
|
const filter = {
|
||||||
|
kinds: [30168],
|
||||||
|
authors: [formIdPubkey],
|
||||||
|
"#d": [formIdentifier],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Responses can be queried using the a-tag, and an optional authors tag depending the visibility of the form, for example
|
||||||
|
|
||||||
|
```js
|
||||||
|
const filter: Filter = {
|
||||||
|
kinds: [30169],
|
||||||
|
"#a": [`30168:${pubKey}:${formId}`],
|
||||||
|
};
|
||||||
|
if (allowedPubkeys) filter.authors = allowedPubkeys;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requesting Acces
|
||||||
|
|
||||||
|
<TBD>
|
||||||
|
|
||||||
|
## Tradeoffs
|
||||||
|
|
||||||
|
- Alias pubkey on gift wrap means that there is no notification mechanism for the user, unless the user is expecting access to an event.
|
||||||
|
|
||||||
|
- Alias pubkey also means that it can be checked that a particular user received a gift wrap for a form event, but it cannot be directly determined who all received the gift wraps, it also makes it easier in disambiguating between event kinds.
|
||||||
|
|
||||||
|
- Voter Key might make it anoymous to other participants, but the issuer can still know who a particular user voted for. In this implementation, the issuer is to be "trusted", but there may be out of band ways of having a "trustless" issuer. For example distrubiting voter Id chits in a physical meetup.
|
Loading…
Reference in New Issue
Block a user