Documentation

How PineSign works

The problem

Email attachments prove nothing. The sender cannot show what was delivered, the recipient cannot show what arrived, and both parties can plausibly claim the other is lying. Registered post solved this for paper a century ago. PineSign does it for files, without asking anyone to trust the service in the middle.

Two keys, two jobs

Every user ends up with two distinct keys, which is deliberate.

The wallet key (Circle)

Created when you sign in with Google. It is a multi-party computation wallet: the key is never assembled in one place, not even in your browser, and never on our server. It is your identity — it owns your name and calls the receipt contract. It does no encryption, because an MPC key cannot perform the key agreement encryption needs.

The encryption key (extension)

Generated locally the first time you open the extension, and stored only there. Web pages never receive it; they ask the extension to act and get back a result. This key does the encrypting, the decrypting, and signs for what you send and receive.

The name binds them: your wallet owns alice.pinesign.eth, and your extension's public key is published in its records.

Sending

The sender looks up the recipient's name, which returns their encryption public key. Combining the sender's private key with that public key produces a shared secret; the recipient will later compute the identical secret from their own private key and the sender's public key. This is Diffie–Hellman, and it means no key is ever transmitted — each side derives it independently.

That secret encrypts the file with AES-256-GCM. The ciphertext goes to the gateway, which uploads it to Swarm and pays the storage cost. The gateway never holds a key and never sees a readable byte.

Before the file goes anywhere, the sender signs a digest covering their key, the recipient's key and the hash of the plaintext. That signature is the sender's half of the proof, committed before the recipient has seen anything.

Receiving

The recipient opens a link naming them specifically. Anyone else who opens it is told the file is not theirs, and could not decrypt it regardless. The page fetches the ciphertext and hands it to the extension, which derives the same shared secret, decrypts, and checks the result against the hash the sender committed to.

Decryption and signing the receipt are a single operation inside the extension. A page cannot take the file and then decline to sign for it.

What the receipt proves

The receipt contract is write-once. It has no owner, no admin key and no upgrade path: it can record that a transfer happened, once, and then cannot be made to say otherwise.

There is a subtlety worth stating plainly. A naive design stores the plaintext hash when the file is sent and has the recipient sign it — which proves nothing, because the hash is public and can be signed without ever opening the file. So the contract stores only a commitment: the hash of the plaintext hash. Claiming requires supplying the preimage, which the recipient can only obtain by decrypting.

send(recipient, commitment, swarmRef)   // commitment = keccak256(plaintextHash)
claim(id, plaintextHash)                // reverts unless keccak256(it) == commitment

Both parties authenticate by being the caller. The sender calls send, the recipient calls claim; no signature verification is needed on chain because msg.sender already is the signature.

What this establishes is proof of delivery — the same thing registered post establishes. It does not prove the recipient read or agreed to the contents, and PineSign does not claim it does.

Expiry

The stored blob and the name pointing at it lapse on the same clock. If the recipient never accepts, both simply stop existing — no deletion job, no retention promise anyone has to be trusted to keep. If they do accept, the receipt is permanent while the file itself still disappears.

Run your own

PineSign is open source and expects to be self-hosted. A fresh server knows nothing; the setup page walks through choosing a name, generating a deployer wallet, funding it, and deploying the receipt contract.

git clone <repo> && cd pinesign
npm install && npm run build
node gateway/server.js            # prints a setup link with a one-time token

The deployer key is generated on your machine, written only to .storage/, and never enters the repository — so every deployment has its own. It pays for the contract and afterwards sponsors each user's subname, which is what lets your users never hold gas or know a chain is involved.

That is the whole mechanism.

Get started