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.
One key, derived from your passkey
Signing in takes an emailed code and then your passkey. The passkey's PRF extension returns a secret that only that passkey can produce, and the browser stretches it with HKDF into a secp256k1 private key.
That key is never stored and never sent. It exists in the page for as long as you are signed in, and signing in again on another machine derives the very same key from the very same passkey. It does the encrypting, the decrypting, and signs for what you send and receive.
The name binds it to you: your public key is published in the records of
alice.pinesign.eth, which is what other people encrypt to.
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, 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. 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