5.6 KiB
Document Signing
A proposal for signing documents in a secure and private way. Implemented by https://sigit.io
Agreement
A signing round may occur for one or more documents, and involve one or more counterparties. This collection of documents, counterparties, and associated metadata constitutes a single "Agreement".
It is possible to complete an Agreement offline, or online without any external observer knowing that a counterparty was involved in an Agreement.
Roles
When it comes to signing documents, the following roles (counterparties) are relevant:
- Creator - uploads the files, defines the remaining counterparties and signing order
- Signer - validates previous signatures and adds their own
- Viewer - has ability to view the Agreement
Privacy
It should not be possible for observers to know the members or contents of an Agreement. This is achieved by using ephemeral keys for each counterparty, and encrypting all documents (before uploading to blossom) and metadata (before uploading to relays).
Creation Process
The files themselves are zipped, and this files.zip
file is then encrypted and uploaded to Blossom.
The Creator prepares an Agreement by creating and signing (but NOT publishing) a Kind 938
event. The content field contains an object with the following information:
title
(string)signers
(array) - list of npubs which can sign the Agreementviewers
(array) - list of npubs which can view the Agreementfilehashes
(array) - series of objects containing the file name and file hashzipUrl
(string) - the location of the encrypted zip file (blossom server)markConfig
(object) - the template by which counterparties will make Annotations in any PDFs
This object is formed as follows:
{
"signers":["npub1nqulz...","npub1d0csynr..."],
"viewers":["npub1viewr.."],
"fileHashes":[{
"name":"d51fe683-4c37-40b-b63c-d503387b0b75.png",
"hash":"8c4f41e76be536a4d6cc180cf1a6014fa0bacbb6cc5e7a1e12dde41f8f5158a8"
}],
"title": "our amazing agreement",
"zipUrl":"https://blossom.one/asdfafafaer23wsdf343fff4343fs"
}
Once the creator has signed, the meta.json
file will contain the createObject
(above) and a keys
object that contains the decryption key for the zip file, NIP-44 encrypted to each counterparty.
{
"createSignature": "{\n \"kind\": 938,\n \"content\": \"{\\\"signers\\\":[\\\"npub1d0csynr..\\\",\\\"npub1nqulz..\\\"],\\\"viewers\\\":[\\\"npub1viewr..\\\"]...}\",\n \"created_at\": 1716564780,\n \"tags\": [],\n \"pubkey\": \"6bf1024...\",\n \"id\": \"...\",\n \"sig\": \"...\"\n}",
"keys": {"npub1nqulz...":"ENCRYPTD","npub1d0csynr...":"ENCRYPTD","npub1viewr..":"ENCRYPTD"},
"docSignatures": {
}
}
This meta.json
file is now sealed (using unsigned Kind 938
to differentiate from DMs and speed up decryptions) and gift wrapped (with some PoW) per NIP-59, for each recipient, and the gift wrap is broadcast to each recipients relays.
Kind 4 DMs
(from an ephemeral key) may optionally be broadcast, with a link to the signing application.
Signing Process
Before signing, the client should first verify the signature from the create event, ensure the create event has a timestamp in the past, download / decrypt the files.zip
from the zipUrl
, and verify the hashes of each document.
A Kind 938
event can now be signed that contains an object with the following attributes:
prevSig
(string) - the signature of the previousKind 938
event (mandatory)marks
(object) - any pdf annotations, as described in the Annotations section
After signing, the meta.json
can now be updated, eg as follows:
{
"createSignature": "{\n \"kind\": 938,\n \"content\": \"{\\\"signers\\\":[\\\"npub1d0csynr..\\\",\\\"npub1nqulz..\\\"],\\\"viewers\\\":[\\\"npub1viewr..\\\"]...}\",\n \"created_at\": 1716564780,\n \"tags\": [],\n \"pubkey\": \"6bf1024...\",\n \"id\": \"...\",\n \"sig\": \"...\"\n}",
"keys": {"npub1nqulz...":"ENCRYPTD","npub1d0csynr...":"ENCRYPTD","npub1viewr..":"ENCRYPTD"},
"docSignatures": {
"npub1d0csynrrxcynkcedktdzrdj6gnras2psg48mf46kxjazs8skrjgq9uzhlq": "{\n \"kind\": 938,\n \"content\": \"{\\\"prevSig\\\":\\\"a76fdd0..",
"npub1nqulzcxcj0d2uesusstgupl5du8pa96xl6uy8xndweeckjkn964qjs23sn": "{\n \"kind\": 938,\n \"content\": \"{\\\"prevSig\\\":\\\"585c134.."
},
}
After each signature, this file is sealed using Kind 938
and gift wrapped to each counterparty.
Storing App Data
App data (list of all sigits) is stored as an encrypted file on Blossom. The file also contains a list of 'processed' id
's from Kind 1059
events (to avoid having to continually decrypt when logging in, as well as enabling notifications).
A Kind 30078
is also created, which contains a link to the blossom server, and an ephemeral key pair that can be used to sign the blossom requests. The user should be shown this npub
so they can whitelist it on their blossom server.
NIP-78 requires a d-tag to provide some application context. To avoid revealing metadata, the d tag will be the first 32 chars of the encrypted result of the string "938" plus the users npub.
File Types
All data is stored either on the relay(s) or on the blossome server(s). File types include the following:
- App Metadata Event - the kind 30078 (NIP-78) that lives on the relay
- App Data Object - the encrypted JSON file that lives on the blossom server
- Sigit Data Object - the encrypted meta.json file that lives on blossom
- Sigit Files Zip - the encrypted zip file with all the unchanged files, that lives on blossom
- Sigit Notification Event - the NIP-17 nostr event that lives on the relay