9.3 KiB
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
{
"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:
{
"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:
{
"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 but with a few modifications.
Rumor
A kind:18 rumor is created that holds the keys in a "key" tag. An example event is:
{
"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:
- The hash function used is SHA256.
- 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
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 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.
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.
- Only elligeble candidates must be allowed to vote.
- Participants shouldn't be able to associate a response to another participant.
- 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.
{
"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
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
const filter: Filter = {
kinds: [30169],
"#a": [`30168:${pubKey}:${formId}`],
};
if (allowedPubkeys) filter.authors = allowedPubkeys;
Requesting Acces
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.