Docs · 09

Encryption

Where the key is made, where it is stored, and the honest limit of v1.

One key per product#

When a creator launches, their browser calls crypto.subtle.generateKey for a fresh 256-bit AES-GCM key. The key is not derived from a password or the wallet; it is random, and it exists for this product only.

Ciphertext format#

payload = iv (12 bytes) ‖ AES-256-GCM(key, iv, plaintext)   // GCM tag included
  • Files: the payload is uploaded straight from the browser to blob storage using a short-lived token from POST /api/upload. It never passes through the API. Limit 200 MB.
  • Secret text: the payload (≤ 64 KB) is sent with the launch form and stored inline in the database.

Storage and database therefore only ever hold ciphertext. A leaked blob URL is useless without the key.

Key wrapping#

The raw key is sent to the server over TLS with the listing details. The server wraps it with a master key (AES-256-GCM again) and stores the result:

wrapped = base64( iv(12) ‖ tag(16) ‖ AES-256-GCM(MASTER_KEY, iv, contentKey) )

MASTER_KEY lives only in the server environment. For GitHub products the same slot holds the creator's wrapped GitHub token.

Unlock#

After the chain confirms payment, GET /api/purchases/:id/content returns the unwrapped key (base64) together with either the inline ciphertext or the blob URL. The buyer's browser fetches the ciphertext, calls crypto.subtle.decrypt, and hands the result to a download or shows it as text. The plaintext is never assembled on a server.

The honest limit#

Because EVERYNTH holds the master key, it can unwrap any content key. This is a deliberate v1 trade-off, made for two reasons:

  • A purchase at 3 a.m. must be deliverable while the creator is asleep. Someone has to hand the buyer the key, and in v1 that is the server.
  • A reported product must be inspectable, or moderation is impossible.

What this means for you: EVERYNTH's operators are in the trust boundary; storage providers, RPC nodes, the chain and network observers are not. Fully end-to-end delivery — keys derived per wallet so the server holds nothing it can open — is the goal state and arrives together with private buyer–creator chat.

Wallet signatures#

Login is an ed25519 signature over a fixed message that includes the site host, the wallet and a timestamp. The server verifies the signature, rejects anything older than five minutes, and issues an HMAC-signed session cookie valid for seven days. No nonce store is needed: the host binding stops cross-site replay and the window limits any replay to five minutes.

Launching asks for a second signature, over the listing terms themselves — title, price in lamports, delivery kind, creator, timestamp. The server rebuilds that message from what was actually submitted and verifies it against the session wallet, so a session cookie on its own cannot list anything, and nothing can be altered between the wallet dialog and the database row. Buying needs no extra message: the payment transaction is itself the signed instruction.

The same rule covers everything else that changes what a buyer sees: editing a listing is signed over the new title and price, and taking one down is signed over the product id and whether the creator is being blocked with it. A stolen session can therefore read, but it cannot list, reprice or empty someone's shop.

NextArchitecture →