This commit is contained in:
abhay-raizada 2024-12-12 09:15:34 +05:30 committed by GitHub
commit c42f2fc19f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

267
101.md Normal file
View 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.