Client-side encryption · Zero-Knowledge architecture

zkArchive Documentation

zkArchive is a zero-knowledge encrypted file vault. Files are encrypted client-side using AES-256-GCM before they ever leave your browser. The server only stores ciphertext and metadata — it never sees your decryption key.

This document explains how the app works, how wallets are used, what the backend expects, and how to safely operate zkArchive.

⚡ Client-side AES-256-GCM 🧩 Wallet-bound key derivation 🛡️ Zero-Knowledge secret management

1. High-Level Flow

zkArchive splits the user experience into three main screens: Upload ZKarchive, Upload Successful, and My ZKarchive. The full flow looks like this:

  1. The user connects a wallet (Phantom or MetaMask).
  2. zkArchive derives a deterministic secret key bound to that wallet address.
  3. The user selects a file and triggers Encrypt & Secure with ZK-Proof.
  4. The app encrypts the file in the browser using AES-256-GCM and uploads only the encrypted blob plus metadata to the backend.
  5. The backend stores the ciphertext and returns a file ID and optional hash.
  6. The user can later open My ZKarchive, pick a file, enter the same secret key, download the encrypted blob and decrypt it locally.
Upload ZKarchive

Wallet connection, file selection, local encryption, and upload.

Upload Successful

Displays server hash or locally derived checksum for reference.

My ZKarchive

Lists wallet-scoped encrypted files and lets users unlock & preview them.

2. Wallets & Identity

zkArchive uses the connected wallet as the primary identity and anchor for key derivation. The app supports the following providers:

  • Phantom (Solana) — via window.solana.connect()
  • MetaMask (EVM) — via ethereum.request({ method: "eth_requestAccounts" })

After a successful connection, the frontend stores only the wallet type and public address in memory:

wallet = {
  type: "phantom" | "metamask",
  address: "<public wallet address>"
};

The address is sent along with the encrypted file to the backend, so the server can associate stored ciphertext with the correct wallet. No private keys are ever touched or transmitted.

The top-right navbar displays the shortened wallet address for quick visual confirmation.

3. Encryption & Secret Management

All encryption and decryption happen strictly in the browser using the Web Crypto API (AES-256-GCM). The backend never sees the raw file contents or the derived key.

3.1 Deterministic Secret

zkArchive shows an “Encryption Password (Auto-Generated)” field. This is a deterministic, human-readable secret derived from the wallet address and a static application salt:

const SALT = "zkarchive-v1";
const secret = `${SALT}-${walletAddress}`;

This value is visible to the user and can be copied, but it is never sent to the backend. It is later fed into a KDF to generate the real AES key.

3.2 KDF & AES-256-GCM

The deterministic secret goes through PBKDF2 to obtain a strong symmetric key:

PBKDF2(
  secret = "zkarchive-v1-<wallet>",
  salt   = "zkarchive-kdf-salt",
  hash   = "SHA-256",
  iterations = 150,000
) --> AES-256-GCM key

For each file, zkArchive generates a fresh 96-bit IV (Uint8Array(12)) and encrypts the file buffer:

cipher = AES-GCM-Encrypt(key, iv, plaintext)

The stored blob is the IV concatenated with the ciphertext:

encryptedBlob = [ IV (12 bytes) ] + [ ciphertext ]

3.3 Decryption

During “Unlock & Preview”, the app:

  • Downloads the encrypted blob from the backend.
  • Splits the first 12 bytes as the IV and the rest as ciphertext.
  • Derives the AES key from the user-supplied secret using the same KDF.
  • Decrypts and exposes the clear file via a temporary object URL.

If the user enters the wrong secret, decryption fails and the app shows an error without ever sending the secret to the server.

4. Backend API Contract

The frontend talks to a single backend base URL: https://zkarchive.onrender.com. Endpoints can be adjusted but the default docs assume the following REST contract:

4.1 Upload Encrypted File

POST /api/files

Accepts multipart form data containing the encrypted blob and metadata.

FormData fields:
  file      : Blob   // IV + ciphertext, filename ends with .zkf
  wallet    : string // connected public wallet address
  filename  : string // original filename (before encryption)
  size      : string // original size in bytes
  mime      : string // original mimetype (optional)
  iv        : string // IV as hex string (optional metadata)

The server should store the uploaded blob and return JSON with at least an id. Optionally it can add a cryptographic hash:

{
  "id": "file_xxx",
  "hash": "sha256-of-stored-blob-or-metadata"
}

4.2 List Files for Wallet

GET /api/files?wallet=<address>

Returns a list of file metadata associated with a given wallet.

[
  {
    "id": "file_xxx",
    "name": "Encrypted File.zkf",
    "size": 73890,
    "createdAt": "2025-11-19T10:15:12.000Z"
  },
  ...
]

4.3 Download Encrypted File

GET /api/files/:id/download

Returns the encrypted blob that was originally uploaded (IV + ciphertext). Content type is typically application/octet-stream.

The frontend uses this blob to perform local AES-GCM decryption after the user enters their secret key.

5. Security Notes & Guarantees

zkArchive aims to provide strong privacy guarantees while remaining simple for users. The following points describe the security model.

  • Client-side encryption only. Files are never uploaded in cleartext. All encryption happens before any network request is sent.
  • Secrets never leave the browser. The human-readable secret and the derived AES keys are used locally and are not transmitted to the backend.
  • Wallet-bound identity. Access to the encrypted files list is scoped to a wallet address; the backend should not allow cross-wallet listing.
  • IV per file. Each upload uses a unique 96-bit IV to avoid re-use issues with AES-GCM.
  • No recovery without the secret. If the user loses their secret key, the backend cannot decrypt or recover their files.

For production, it is recommended to run the backend behind HTTPS, enable strict CORS (only zkarchive.us, app.zkarchive.us, and for example), and log minimal metadata.

6. FAQ

Do you ever see my private key?

No. zkArchive only interacts with wallet providers through their standard connection APIs and reads your public address. Private keys stay inside Phantom or MetaMask and are never exposed to the app or backend.

Can the server decrypt my files?

No. The server only stores the encrypted blob plus basic metadata. It never receives the deterministic secret or the AES key, so it cannot decrypt files. Only the user, from their browser, can perform decryption.

What happens if I lose my secret key?

If you lose the secret key generated for your wallet, the files tied to that secret cannot be decrypted. There is no “forgot password” or recovery mechanism by design — this is a trade-off to guarantee that only you control access.

Can I use multiple wallets?

Yes. Each wallet address will have its own deterministic secret and its own set of encrypted files. Switching wallets in the app will change which encrypted files you see in My ZKarchive.

Is there a maximum file size?

The UI suggests a soft limit of 100 MB per file. Actual limits are controlled by the backend and hosting provider; adjust them according to your infrastructure.