# Vault

Orama Vault is a zero-knowledge secret storage service built on Shamir's Secret Sharing. It splits secrets client-side and distributes shares across guardian nodes so that no single node ever sees the full secret. Guardian nodes run the `vault-guardian` service, written in Zig, and authenticate requests using HMAC-based challenge-response with per-identity session tokens.

## How It Works

When you store a secret, the SDK performs the following entirely on the client:

1. The secret is split into shares using Shamir's Secret Sharing over GF(2^8) polynomial interpolation.
2. Each share is encrypted and sent to a different guardian node over an authenticated session.
3. The adaptive threshold (minimum shares needed for reconstruction) is calculated automatically from the guardian count.

To retrieve the secret, the SDK collects shares from guardians and reconstructs the original value. Shares are wiped from memory immediately after each operation.

This means:

- **Zero-knowledge** — No single guardian ever sees the full secret.
- **Client-side splitting** — Secrets are split before leaving the client.
- **Fault tolerant** — Secrets survive guardian failures as long as the read threshold is met.
- **Cryptographically secure** — Based on polynomial interpolation over finite fields (GF(2^8)).
- **Write quorum** — A majority of guardians must acknowledge before a write is considered successful.
- **Memory safe** — Shares and keys are wiped from memory after every operation.

---

## Architecture

The vault has two access paths:

### Direct (SDK → Guardians)

The SDK connects directly to guardian nodes using the V2 API. This is the recommended path for applications. The SDK handles authentication, Shamir splitting/combining, and fan-out to all guardians.

### Gateway Proxy (Client → Gateway → Guardians)

The Orama gateway exposes V1 vault endpoints that perform Shamir splitting/combining server-side. This path is for clients that cannot connect directly to guardians (e.g., the gateway runs on the WireGuard mesh while guardians bind to internal IPs).

| Path | API Version | Who splits/combines | Auth |
|------|-------------|-------------------|------|
| SDK → Guardians | V2 | Client (SDK) | HMAC session tokens |
| Client → Gateway | V1 | Gateway | Namespace API key |

---

## Setup

Install the SDK and create a client with vault configuration:

```typescript
import { createClient } from "@debros/network-ts-sdk";

const client = createClient({
  baseURL: "https://gateway.example.com",
  apiKey: "ak_your_key:namespace",
  vaultConfig: {
    guardians: [
      { address: "10.0.0.1", port: 7500 },
      { address: "10.0.0.2", port: 7500 },
      { address: "10.0.0.3", port: 7500 },
    ],
    hmacKey: derivedHmacKey,
    identityHex: "your-identity-hex",
    timeoutMs: 5000,
  },
});
```

If you omit `vaultConfig`, `client.vault` will be `null`.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `guardians` | `GuardianEndpoint[]` | Yes | Guardian nodes that hold shares. Each needs `address` and `port`. |
| `hmacKey` | `Uint8Array` | Yes | HMAC key for authentication, derived from your secret (e.g., via `deriveKeyHKDF`). |
| `identityHex` | `string` | Yes | Your identity in hex. Used for challenge-response authentication and secret scoping. |
| `timeoutMs` | `number` | No | Request timeout in milliseconds per guardian. |

---

## Storing Secrets

Pass a name, the secret as a `Uint8Array`, and a version number:

```typescript
const data = new TextEncoder().encode("my-secret-data");
const result = await client.vault.store("api-key", data, 1);

console.log(result.quorumMet);       // true if enough guardians ACKed
console.log(result.ackCount);         // number of successful writes
console.log(result.totalContacted);   // total guardians contacted
console.log(result.failCount);        // number of write failures
console.log(result.guardianResults);  // per-guardian results
```

The write succeeds when `quorumMet` is `true`, meaning a majority of guardians acknowledged the share.

### Secret Name Constraints

| Constraint | Limit |
|-----------|-------|
| Allowed characters | Alphanumeric, `_`, `-` |
| Max name length | 128 characters |
| Max secrets per identity | 1,000 |
| Max secret size | 512 KiB |

## Retrieving Secrets

The SDK automatically gathers the minimum number of shares from guardians and reconstructs the original value:

```typescript
const retrieved = await client.vault.retrieve("api-key");

console.log(new TextDecoder().decode(retrieved.data)); // "my-secret-data"
console.log(retrieved.sharesCollected);                // number of shares used
```

Reconstruction requires at least the read threshold number of shares. The threshold is calculated automatically from the guardian count.

## Listing Secrets

List all secrets stored under your identity:

```typescript
const secrets = await client.vault.list();
console.log(secrets.secrets);
```

## Deleting Secrets

Delete a secret from all guardians:

```typescript
const deleteResult = await client.vault.delete("api-key");
console.log(deleteResult.quorumMet); // true if enough guardians deleted the shares
```

Like writes, deletion requires a quorum acknowledgment to be considered successful.

---

## Gateway Proxy API

For clients that cannot connect directly to guardians, the gateway exposes V1 vault endpoints. The gateway performs Shamir splitting/combining server-side.

### Store via Gateway

```typescript
const res = await fetch("https://ns-myapp.orama-devnet.network/v1/vault/push", {
  method: "POST",
  headers: {
    "X-API-Key": apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ envelope: encryptedData }),
});

const { ack_count, total, quorum, threshold } = await res.json();
```

### Retrieve via Gateway

```typescript
const res = await fetch("https://ns-myapp.orama-devnet.network/v1/vault/pull", {
  method: "POST",
  headers: {
    "X-API-Key": apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ identity: identityHex }),
});

const { envelope, collected, threshold } = await res.json();
```

### Health Check

```bash
curl https://ns-myapp.orama-devnet.network/v1/vault/health \
  -H "X-API-Key: $API_KEY"
```

Returns aggregate guardian health: `healthy`, `degraded`, or `unavailable`.

### Rate Limits

The gateway proxy enforces per-identity rate limits:

| Operation | Limit |
|-----------|-------|
| Push (store) | 30 requests/hour |
| Pull (retrieve) | 120 requests/hour |

---

## REST API Reference

### Guardian V2 Endpoints (Direct)

Used by the SDK when connecting directly to guardian nodes.

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/v2/vault/auth/challenge` | None | Request authentication challenge |
| POST | `/v2/vault/auth/session` | None | Exchange challenge response for session token |
| PUT | `/v2/vault/secrets/{name}` | Session | Store a named secret share |
| GET | `/v2/vault/secrets/{name}` | Session | Retrieve a named secret share |
| DELETE | `/v2/vault/secrets/{name}` | Session | Delete a named secret share |
| GET | `/v2/vault/secrets` | Session | List all secrets for identity |

Session tokens are passed via the `X-Session-Token` header. Sessions expire after 1 hour and are invalidated on guardian restart.

### Gateway V1 Endpoints (Proxy)

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/v1/vault/push` | API key | Store secret (gateway splits into shares) |
| POST | `/v1/vault/pull` | API key | Retrieve secret (gateway combines shares) |
| GET | `/v1/vault/health` | API key | Aggregate guardian health |
| GET | `/v1/vault/status` | API key | Guardian count, healthy count, threshold |

### Guardian Health Endpoints

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/v1/vault/health` | None | Liveness check |
| GET | `/v1/vault/status` | None | Runtime config (data_dir, ports) |
| GET | `/v1/vault/guardians` | None | List known guardian nodes |

---

## Authentication

Vault uses HMAC-based challenge-response authentication:

1. Client requests a challenge from the guardian (`/v2/vault/auth/challenge`)
2. Guardian generates a random nonce
3. Client computes an HMAC tag using the shared `hmacKey` and nonce
4. Client sends the tag back to get a session token (`/v2/vault/auth/session`)
5. Session token is used for all subsequent requests via `X-Session-Token`

The SDK handles this automatically. Sessions are cached per-guardian and refreshed transparently when they expire.

### Session Lifecycle

| Property | Value |
|----------|-------|
| Expiry | 1 hour |
| Caching | Per-guardian, automatic |
| Invalidation | On guardian restart |
| Refresh | Automatic on 401 response |

---

## Threshold Configuration

The adaptive threshold is calculated automatically based on how many guardians you configure. You do not set it manually. The system determines:

- **Write quorum** — The majority of guardians that must acknowledge a write.
- **Read threshold** — The minimum number of shares needed to reconstruct the secret.

| Guardians | Read Threshold | Write Quorum |
|-----------|---------------|--------------|
| 3 | 2 | 2 |
| 5 | 3 | 3 |
| 7 | 4 | 4 |

More guardians means higher fault tolerance. With three guardians, losing one still allows full reconstruction. With five, you can tolerate two failures.

---

## Crypto Utilities

The SDK exports low-level cryptographic primitives that the vault uses internally:

```typescript
import {
  // Shamir's Secret Sharing
  shamirSplit,
  shamirCombine,

  // Symmetric encryption
  encrypt,
  decrypt,
  encryptString,
  decryptString,
  encryptAndSerialize,
  deserializeAndDecrypt,

  // Key management
  generateKey,
  generateNonce,
  clearKey,
  deriveKeyHKDF,

  // Serialization
  serializeEncrypted,
  deserializeEncrypted,
  encryptedToHex,
  encryptedFromHex,
  encryptedToBase64,
  encryptedFromBase64,

  // Validation
  isValidEncryptedData,
} from "@debros/network-ts-sdk";
```

| Function | Description |
|----------|-------------|
| `shamirSplit` / `shamirCombine` | Split and reconstruct secrets using Shamir's Secret Sharing |
| `encrypt` / `decrypt` | Symmetric encryption primitives (AES) |
| `encryptString` / `decryptString` | String convenience wrappers |
| `encryptAndSerialize` / `deserializeAndDecrypt` | Encrypt + serialize in one step |
| `generateKey` / `generateNonce` | Cryptographic key and nonce generation |
| `clearKey` | Securely wipe a key from memory |
| `deriveKeyHKDF` | Derive keys using HKDF-SHA256 |
| `serializeEncrypted` / `deserializeEncrypted` | Binary serialization helpers |
| `encryptedToHex` / `encryptedFromHex` / `encryptedToBase64` / `encryptedFromBase64` | Encoding utilities |
| `isValidEncryptedData` | Validate encrypted data structure |

---

## Guardian Service

The `vault-guardian` binary runs on each node as a systemd service. It is written in Zig and compiled as part of the Orama build pipeline.

### CLI Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--config` | `/opt/orama/.orama/data/vault/vault.yaml` | Path to config file |
| `--data-dir` | | Override data directory |
| `--port` | `7500` | Client API port |
| `--bind` | `0.0.0.0` | Bind address |
| `--version` | | Show version |

### Ports

| Port | Protocol | Description |
|------|----------|-------------|
| 7500 | HTTP | Client API (V1 + V2 endpoints) |
| 7501 | TCP (binary) | Peer protocol (heartbeat, share verification) |

### Peer Protocol

Guardians communicate with each other over a binary TCP protocol on port 7501 for health monitoring and share verification:

| Message | Description |
|---------|-------------|
| Heartbeat | Guardian announces health and share count |
| Heartbeat ACK | Peer acknowledges |
| Verify Request | Check if a peer has a specific share |
| Verify Response | Peer responds with share status |

---

## Access Control

Secrets are scoped to your identity. Each guardian authenticates requests via HMAC challenge-response, so only the identity that stored a secret can retrieve, list, or delete it. Different identities on the same guardian cluster have completely isolated secret namespaces.
