2024-04-22 18:48:56 -04:00
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.
```js
{
"kind": 35337,
"tags": [
["d", "< unique identifier > "],
2024-05-01 19:05:37 -04:00
["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.
2024-04-22 18:48:56 -04:00
],
2024-05-02 14:18:56 -04:00
"content": "", // reserved for private content below
2024-04-22 18:48:56 -04:00
// ...
}
```
2024-04-22 18:51:16 -04:00
Sheets MUST be rendered in the order of their tags.
2024-05-01 19:05:37 -04:00
As an example:
2024-04-22 18:48:56 -04:00
```json
{
"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",
}
2024-05-01 19:05:37 -04:00
```
## Styling
TBD
2024-05-02 14:18:56 -04:00
## Private Spreadsheets
2024-05-01 19:05:37 -04:00
2024-05-02 14:18:56 -04:00
A private spreadsheet has tags in a JSON-stringified and NIP-44-encrypted to the user's own pubkey `.content`
2024-05-01 19:05:37 -04:00
```js
{
"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
2024-05-02 14:18:56 -04:00
], author.privateKey, author.pubkey),
2024-05-01 19:05:37 -04:00
// ...
}
```
2024-05-02 14:18:56 -04:00
## Sharing Encrypted Spreadsheets with View-only permission to a Group
2024-05-01 19:05:37 -04:00
Ready-only sharing is achieved by adding a `p` tag to each receiver with a shared secret to decrypt the `.content` .
2024-05-02 14:18:56 -04:00
The shared secret is a new Nostr Private Key in hex, NIP-44-encrypted to each `p` tag and placed as a 4th value in each tag.
2024-05-01 19:05:37 -04:00
The `.content` is then encrypted by a conversation key between the new private and the public key.
```js
val keyPair = nostr.generateKeyPair()
{
2024-05-02 14:18:56 -04:00
"pubkey": author.pubkey
2024-05-01 19:05:37 -04:00
"kind": 35337,
"tags": [
["d", "< unique identifier > "]
2024-05-02 14:18:56 -04:00
["p", "< pubkey 1 > ", "< relay url > ", nip44Encrypt(keyPair.privateKeyHex, author.privateKey, "< pubkey 1 > ") ]
["p", "< pubkey 2 > ", "< relay url > ", nip44Encrypt(keyPair.privateKeyHex, author.privateKey, "< pubkey 2 > ") ]
["p", "< pubkey 3 > ", "< relay url > ", nip44Encrypt(keyPair.privateKeyHex, author.privateKey, "< pubkey 3 > ") ]
2024-05-01 19:05:37 -04:00
],
"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),
2024-05-02 14:18:56 -04:00
"sig": signWith(author.privateKey)
2024-05-01 19:05:37 -04:00
// ...
}
```
2024-05-02 14:18:56 -04:00
To decrypt, receivers SHOULD find the `p` -tag ciphertext for their key, decrypt that to get the shared private key and use the shared private key to decrypt the `.content` .
2024-05-01 19:05:37 -04:00
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.
2024-05-02 14:18:56 -04:00
## Sharing Encrypted or Public Spreadsheets with Edit permission to a Group
2024-05-01 19:05:37 -04:00
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.
```js
val keyPair = nostr.generateKeyPair()
{
"pubkey": keyPair.publicKey
"kind": 35337,
"tags": [
["d", "< unique identifier > "]
2024-05-02 14:18:56 -04:00
["p", "< pubkey 1 > ", "< relay url > ", nip44Encrypt(keyPair.privateKeyHex, keyPair.privateKey, "< pubkey 1 > ") ]
["p", "< pubkey 2 > ", "< relay url > ", nip44Encrypt(keyPair.privateKeyHex, keyPair.privateKey, "< pubkey 2 > ") ]
["p", "< pubkey 3 > ", "< relay url > ", nip44Encrypt(keyPair.privateKeyHex, keyPair.privateKey, "< pubkey 3 > ") ]
["title", "Name of this spreadsheet"], // public title
["data", "< sheet name > ", "< column letter > ", "< row number > ", "< value > "], // public data
["data", "< sheet name > ", "< column letter > ", "< row number > ", "< value > ", "< style > " ] ,
["style", "< TBD > "], // public styles
2024-05-01 19:05:37 -04:00
],
"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 > " ] ,
2024-05-02 14:18:56 -04:00
["style", "< TBD > "], // private styles
2024-05-01 19:05:37 -04:00
// ... other tags
], keyPair.privateKey, keyPair.publicKey),
2024-05-02 14:18:56 -04:00
"sig": signWith(keyPair.privateKey)
2024-05-01 19:05:37 -04:00
// ...
}
```
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.
2024-05-02 13:43:15 -04:00
The Client knows when it's user has the permission to edit when the pubkey of the secret matches the pubkey of the event.
2024-05-01 19:05:37 -04:00
2024-05-02 13:43:15 -04:00
Spreadsheets MAY have private and public parts at the same time.