> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vana.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Storage & encryption

> Key derivation, OpenPGP encryption, storage backends, and the data sync model.

The Data Portability Protocol encrypts user data before it leaves the Personal Server. This page covers how encryption keys are derived, how data is encrypted, where encrypted blobs are stored, and how multiple Personal Server instances stay in sync.

## Key derivation

Encryption keys are deterministically derived from the user's wallet signature. This means any Personal Server instance with the correct signature can decrypt the user's data — no key exchange required.

### Derivation chain

```
Wallet
  → EIP-191 personal_sign("vana-master-key-v1")
  → raw signature bytes (master key material)
  → HKDF-SHA256(master_key_material, "vana", "scope:{scope}")
  → 32-byte scope key
  → hex(scope_key) = encryption password
```

### Step by step

1. **Master key material.** The user signs the fixed message `"vana-master-key-v1"` using EIP-191 `personal_sign`. The raw signature bytes become the master key material. This happens once — when the user first opens the Desktop App or when enabling ODL Cloud.

2. **Scope key.** For each [scope](/protocol-reference/scopes-schemas), a 32-byte scope key is derived using HKDF-SHA256:
   ```
   scope_key = HKDF-SHA256(
     ikm:  master_key_material,
     salt: "vana",
     info: "scope:{scope}"
   )
   ```
   For example, `instagram.profile` produces `HKDF-SHA256(master_key_material, "vana", "scope:instagram.profile")`.

3. **Encryption password.** The scope key is hex-encoded to a 64-character string, which serves as the password for OpenPGP symmetric encryption.

<Warning>
  Changing a scope name changes the derived key. If a scope is renamed, existing encrypted data for that scope cannot be decrypted with the new key. Keep scope names stable.
</Warning>

### Key derivation stability

The derivation inputs are versioned and stable:

* **Signed message:** `"vana-master-key-v1"` — the `v1` suffix is a version marker.
* **HKDF salt:** `"vana"` (fixed).
* **HKDF info:** `"scope:{scope}"` (deterministic from scope name).

These inputs are part of the protocol specification. They will not change without a major protocol version bump. If a future version introduces a new derivation (e.g. `v2`), Personal Servers will support both versions during a migration window so existing encrypted data remains accessible.

Builders do not manage encryption keys — the Personal Server and Desktop App handle all key derivation, encryption, and decryption internally. Builders read plaintext data from the Personal Server API; the encryption layer is transparent.

### What the Personal Server needs

The Personal Server does **not** need the user's wallet private key. It only needs the master key signature (the output of `personal_sign`). For desktop-bundled servers, the user signs on app open. For ODL Cloud, the signature is stored encrypted in the Sprite (see [delegated signature](/protocol-reference/personal-servers#odl-cloud)).

## Encryption

The protocol uses **OpenPGP password-based symmetric encryption**.

### How it works

1. Take the plaintext JSON [data file](/protocol-reference/scopes-schemas#data-file-format)
2. Derive the scope key for the file's scope
3. Hex-encode the scope key to get the encryption password
4. Encrypt the entire JSON as a single OpenPGP message using the password

The output is a standard OpenPGP binary message. No plaintext metadata is stored alongside the ciphertext — the entire file envelope (including `scope`, `collectedAt`, and `data`) is encrypted as one blob.

### Encryption requirements by hosting option

| Hosting option              | Encrypted at rest?    | Rationale                                             |
| --------------------------- | --------------------- | ----------------------------------------------------- |
| **ODL Cloud (Vana-hosted)** | **Must** encrypt      | Blind infrastructure — Vana must not access plaintext |
| **Desktop-bundled**         | May store unencrypted | User's device is the security zone                    |
| **Self-hosted**             | User's choice         | User controls the infrastructure                      |

In all cases, data **must** be encrypted before uploading to a storage backend, and **must** be encrypted in transit (TLS 1.3).

## Storage backends

Storage backends hold encrypted data blobs. The Personal Server encrypts data before upload and decrypts on download — the backend never sees plaintext.

Users select **one** storage backend for all their data (per-user, not per-file). The selection is made during Desktop App setup and stored in `~/.vana/server.json`. Until a backend is selected, the Personal Server operates in local-only mode with no remote storage or DataRegistry writes.

### Available backends

| Backend                    | URL format                                                        | Notes                        |
| -------------------------- | ----------------------------------------------------------------- | ---------------------------- |
| **Vana Storage** (default) | `https://storage.vana.com/v1/blobs/{owner}/{scope}/{collectedAt}` | Managed by Vana, zero-config |
| **Google Drive**           | `gdrive://{fileId}`                                               | User authorizes via OAuth    |
| **Dropbox**                | `dropbox://{path}`                                                | User authorizes via OAuth    |
| **IPFS**                   | `ipfs://{cid}`                                                    | Content-addressed, immutable |

### Backend requirements

Every storage backend must:

1. Accept only encrypted blobs
2. Authenticate requests (verify the requester is authorized)
3. Support hierarchical key format
4. Return a canonical URL after upload

### Backend interface

```typescript theme={null}
interface StorageBackend {
  upload(key: string, data: Uint8Array): Promise<string>;
  download(url: string): Promise<Uint8Array>;
  delete(url: string): Promise<boolean>;
  exists(url: string): Promise<boolean>;
  deleteScope?(scope: string): Promise<number>;
  deleteAll?(): Promise<number>;
}
```

New storage backends can be added by implementing this interface.

### Server configuration

Storage backend selection and OAuth tokens are stored in `~/.vana/server.json`:

```json theme={null}
{
  "version": "1.0",
  "server": {
    "address": "0x...",
    "url": "https://user-abc.server.vana.com",
    "capabilities": {
      "mcp": true,
      "compute": true
    }
  },
  "storage": {
    "backend": "vana",
    "config": {},
    "oauth": {
      "gdrive": { "accessToken": "...", "refreshToken": "...", "expiresAt": "..." },
      "dropbox": { "accessToken": "...", "refreshToken": "...", "expiresAt": "..." }
    }
  },
  "sync": {
    "lastProcessedTimestamp": "2026-01-21T10:00:00Z"
  }
}
```

The `backend` field accepts: `vana`, `gdrive`, `dropbox`, `ipfs`, or `local`.

## Data flow

When new data is collected:

1. Data Connector collects data from a platform
2. Desktop App sends raw data to the Personal Server via `POST /v1/data/{scope}`
3. Personal Server stores the data locally (unencrypted) at `~/.vana/data/{scope}/`
4. Personal Server encrypts the data with the scope key
5. Encrypted blob is uploaded to the storage backend
6. File record is registered in the DataRegistry via the DP RPC (with `schemaId`)

If no storage backend is selected, steps 4-6 are skipped and data remains local-only.

## Data sync

When a user has multiple Personal Server instances (e.g. desktop-bundled + ODL Cloud), they stay in sync through the **storage backend** and **Data Registry**. The storage backend is the source of truth for encrypted data; each Personal Server maintains a local decrypted copy.

### How sync works

Each Personal Server polls the DP RPC for new file records using a `lastProcessedTimestamp` cursor:

```
GET /v1/files?user={address}&since={lastProcessedTimestamp}
```

For each new file record:

1. Download the encrypted blob from the storage backend
2. Resolve the `schemaId` to the canonical scope (via `GET /v1/schemas/{schemaId}`)
3. Derive the scope key and decrypt
4. Read `scope` and `collectedAt` from the decrypted payload
5. Store locally at `~/.vana/data/{scope}/{collectedAt}.json`
6. Update the local index (`fileId` → path, scope, collectedAt)

### First activation

When a Personal Server starts for the first time (e.g. when a user enables ODL Cloud), it runs a full backfill:

1. Query the DP RPC for all file records for the user
2. Download, decrypt, and index each file
3. Set `lastProcessedTimestamp` to the most recent record

### Conflict resolution

If two instances write to the same scope concurrently, the **last-write-wins** strategy applies based on the `collectedAt` timestamp in the payload.

### Sync API (internal)

The Personal Server exposes internal sync endpoints:

| Method | Path                     | Description                                        |
| ------ | ------------------------ | -------------------------------------------------- |
| `POST` | `/v1/sync/trigger`       | Force a sync from the storage backend              |
| `GET`  | `/v1/sync/status`        | Get sync status (last sync, pending files, errors) |
| `POST` | `/v1/sync/file/{fileId}` | Sync a specific file from the storage backend      |

### Before storage backend selection

Until the user selects a storage backend, sync is disabled. Data exists only on the local Personal Server instance, and no DataRegistry writes occur.

## Data deletion

DataRegistry file entries are immutable — you cannot remove a record from the chain. Deletion is implemented as:

1. Delete the encrypted blob from the storage backend
2. Remove the local decrypted copy
3. Write a tombstone / delete marker via the DP RPC

Personal Servers treat tombstoned file records as non-existent and return `410 Gone` or `404 Not Found`.

<Info>
  Data deletion is user-initiated only. Builders cannot delete user data.
</Info>

## Related

* [Personal Servers](/protocol-reference/personal-servers) — Where data is stored and served
* [Scopes & Schemas](/protocol-reference/scopes-schemas) — Data taxonomy and file format
* [Grants & Permissions](/protocol-reference/grants-permissions) — Access control for stored data
