4.7 KiB
NIP-73
Spreadsheets
draft
optional
This NIP provides a simple way to save spreedsheets on Nostr.
Event kind 35337
describes a workbook with data
tags that contain the value of each cell as well as optional styling.
{
"kind": 35337,
"tags": [
["d", "<unique identifier>"],
["title", "Name of this spreadsheet"], // public title
["data", "<sheet name>", "<column letter>", "<row number>", "<value>"],
["data", "<sheet name>", "<column letter>", "<row number>", "<value>", "<style>"],
["style", "<TBD>"], // Need to specify all options here.
],
"content": "",
// ...
}
Sheets MUST be rendered in the order of their tags.
As an example:
{
"id": "32360b52b11616ea331aacac516494e36bd4079d8908edc8f26ad1e4acab5a53",
"kind": 35337,
"tags": [
[ "d", "SheetStr Demo" ],
[ "data", "Sheet1", "J", "25", "3" ],
[ "data", "Sheet1", "J", "26", "5" ],
[ "data", "Sheet1", "J", "27", "=SUM(J25:J26)" ]
],
"created_at": 1713819120,
"content": "",
"pubkey": "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
"sig": "880eb3d67fc66ca2d4e7819ae9b9ca577df35950fb5d11d24f95f350cfeab0b4532646c52113d5bb629cf9a2e4d8ef646ff434b59f1c894c8f719f65d59ed8f0",
}
Styling
TBD
Private SpreadSheets
A private spreadsheet has all tags in a JSON-stringified and NIP-44-encrypted to the user's own pubkey .content
{
"kind": 35337,
"tags": [
["d", "<unique identifier>"]
],
"content": nip44Encrypt([
["title", "Name of this spreadsheet"], // private title
["data", "<sheet name>", "<column letter>", "<row number>", "<value>"], // private data
["data", "<sheet name>", "<column letter>", "<row number>", "<value>", "<style>"],
["style", "<TBD>"], // Need to specify all options here.
// ... other tags
], privateKey, pubkey),
// ...
}
Sharing Encrypted Spreadsheets with Viewer-only permission.
Ready-only sharing is achieved by adding a p
tag to each receiver with a shared secret to decrypt the .content
.
The shared secret is a Nostr Private Key in hex, NIP-44-encrypted to each p
tag and placed as a 4th value in each tag.
The .content
is then encrypted by a conversation key between the new private and the public key.
val keyPair = nostr.generateKeyPair()
{
"kind": 35337,
"tags": [
["d", "<unique identifier>"]
["p", "<pubkey 1>", "<relay url>", nip44Encrypt(keyPair.privateKeyHex, "<pubkey 1>") ]
["p", "<pubkey 2>", "<relay url>", nip44Encrypt(keyPair.privateKeyHex, "<pubkey 2>") ]
["p", "<pubkey 3>", "<relay url>", nip44Encrypt(keyPair.privateKeyHex, "<pubkey 3>") ]
],
"content": nip44Encrypt([
["title", "Name of this spreadsheet"], // private title
["data", "<sheet name>", "<column letter>", "<row number>", "<value>"], // private data
["data", "<sheet name>", "<column letter>", "<row number>", "<value>", "<style>"],
["style", "<TBD>"], // Need to specify all options here.
// ... other tags
], keyPair.privateKey, keyPair.publicKey),
// ...
}
To decrypt, receivers SHOULD find the ciphertext for their key, decrypt that to get the shared private key and use the shared private key to decrypt the .content
.
Clients SHOULD include the author as a p
tag to save the secret that will allow the author to decrypt and modify the event further.
Sharing Encrypted Spreadsheets with Editor permission.
This method allows anyone listed as the p
tag to edit the Spreadsheet. It follows a similar process to read-only spreadsheets, but instead of signing the event with the author's main key, the event is signed by the shared secret as well.
val keyPair = nostr.generateKeyPair()
{
"pubkey": keyPair.publicKey
"kind": 35337,
"tags": [
["d", "<unique identifier>"]
["p", "<pubkey 1>", "<relay url>", nip44Encrypt(keyPair.privateKeyHex, "<pubkey 1>") ]
["p", "<pubkey 2>", "<relay url>", nip44Encrypt(keyPair.privateKeyHex, "<pubkey 2>") ]
["p", "<pubkey 3>", "<relay url>", nip44Encrypt(keyPair.privateKeyHex, "<pubkey 3>") ]
],
"content": nip44Encrypt([
["title", "Name of this spreadsheet"], // private title
["data", "<sheet name>", "<column letter>", "<row number>", "<value>"], // private data
["data", "<sheet name>", "<column letter>", "<row number>", "<value>", "<style>"],
["style", "<TBD>"], // Need to specify all options here.
// ... other tags
], keyPair.privateKey, keyPair.publicKey),
"sig": signWith(id, keyPair.privateKey)
// ...
}
To edit, receivers SHOULD find the ciphertext for their key, decrypt that to get the shared private key and use the shared private key to encrypt the .content
and sign the event.