nips/73.md
2024-05-01 19:05:37 -04:00

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.