# Post-Quantum Encrypted Vaults

Post-quantum encrypted vaults with DIDComm protocol for Credo. Client-side encryption using ML-KEM-768, AES-256-GCM, and Shamir secret sharing with P2P sharing via DIDComm messages.

## Overview

The Vaults module provides secure, client-side encrypted storage with peer-to-peer sharing capabilities. All cryptographic operations happen locally on the agent - no server ever sees plaintext data. It supports multiple encryption suites including post-quantum ML-KEM-768, flexible access policies (passphrase, any-of, all-of, threshold), and optional external storage (S3) for large files.

## Table of Contents

- [Features](#features)
- [How It Works](#how-it-works)
- [Encryption Suites](#encryption-suites)
- [Access Policies](#access-policies)
- [Protocol Flows](#protocol-flows)
- [Usage Examples](#usage-examples)
- [Installation](#installation)
- [Message Types](#message-types)
- [Event System](#event-system)
- [Security Considerations](#security-considerations)

## Features

### Encryption Suites
- **S3 Suite**: Passphrase-based encryption (Argon2id KDF + AES-256-GCM)
- **P1 Suite**: Post-quantum encryption (ML-KEM-768 + AES-256-GCM)
- **H1 Suite**: Hybrid mode (planned)

### Access Policies
- **Passphrase**: Single-user encryption with passphrase-derived key
- **Any-of**: Multiple recipients, any one can decrypt independently
- **All-of**: Multiple recipients, all must cooperate to decrypt
- **Threshold**: Shamir secret sharing with configurable t-of-n threshold

### Vault Operations
- Create, open, update, delete encrypted vaults
- List vaults and get metadata without decrypting
- KEM keypair generation and management
- Document signing vault workflows

### DIDComm Protocol
- 15 message types for P2P vault sharing
- Request, grant, and deny access to vaults
- Threshold share request and distribution
- External storage operator support
- Problem reporting for error handling

### Storage
- Inline storage in agent wallet
- External S3 storage for large files
- Configurable inline/external threshold
- Storage operator mode

## How It Works

### Architecture

```
                     Application Code
                          |
                      VaultsApi
          create / open / update / delete / list
          KEM key mgmt / signing vaults
                          |
         +----------------+----------------+
         |                |                |
    VaultService    KEM Service    SigningVaultSvc
         |                |                |
         +--------+-------+-------+--------+
                  |               |
           EncryptionSvc    HPKEService
                  |               |
              WASM Crypto (30+ functions)
                  |
       +----------+----------+
       |          |          |
     AEAD       KEM      Shamir
   AES-256   ML-KEM-768  Secret
    GCM                  Sharing
       |          |          |
  VaultRepository    StorageService
       |                   |
  Agent Wallet       S3 / External
```

### Encryption Flow

1. **Passphrase vault**: Passphrase -> Argon2id KDF -> 256-bit CEK -> AES-256-GCM encrypt
2. **Post-quantum vault**: ML-KEM-768 encapsulate -> shared secret -> HKDF -> CEK -> AES-256-GCM encrypt
3. **Threshold vault**: CEK -> Shamir split into n shares -> wrap each share with recipient's KEM public key

### Key Commitment

Every vault includes a key commitment (`kcmp`) in the header to verify the correct key was used during decryption, preventing key confusion attacks.

## Encryption Suites

### S3 Suite (Passphrase)

**KDF**: Argon2id (configurable memory and iterations)
**AEAD**: AES-256-GCM
**Use Case**: Personal vaults protected by a passphrase

- Passphrase is stretched via Argon2id with random salt
- Derived key encrypts data with AES-256-GCM
- Key commitment ensures correct passphrase verification

### P1 Suite (Post-Quantum)

**KEM**: ML-KEM-768 (NIST PQC standard)
**AEAD**: AES-256-GCM
**Use Case**: Sharing vaults between agents with quantum-resistant security

- Recipient's ML-KEM public key encapsulates a shared secret
- Shared secret is expanded via HKDF to derive CEK
- CEK encrypts data with AES-256-GCM

## Access Policies

### Passphrase Policy
Single user, passphrase-derived encryption key.

### Any-of Policy
Multiple recipients each get an independently-encrypted copy of the CEK wrapped with their KEM public key. Any single recipient can decrypt.

### All-of Policy
All participants must cooperate. The CEK is split such that every participant's contribution is required for reconstruction.

### Threshold Policy
Shamir secret sharing splits the CEK into `n` shares with a threshold of `t`. Any `t` shares can reconstruct the CEK. Each share is wrapped with the respective participant's KEM public key.

## Protocol Flows

### 1. Create and Open a Vault

```
Agent
  |-- create(passphrase, data) --> Argon2id + AES-256-GCM --> VaultRecord stored
  |-- open(passphrase) --> derive key --> AES-256-GCM decrypt --> plaintext
```

### 2. Share Vault via DIDComm

```
Owner                                  Recipient
  |                                        |
  |  1. GrantAccessMessage (header + CT)   |
  |--------------------------------------->|
  |                                        |
  |  2. VaultStoredAckMessage              |
  |<---------------------------------------|
```

### 3. Request Access

```
Requester                                Owner
  |                                        |
  |  1. RequestAccessMessage               |
  |--------------------------------------->|
  |                                        |
  |  2. GrantAccessMessage / DenyAccess    |
  |<---------------------------------------|
```

### 4. Threshold Reconstruction

```
Requester                   ShareHolder1    ShareHolder2
  |                              |               |
  |  RequestShareMessage         |               |
  |----------------------------->|               |
  |  RequestShareMessage                         |
  |--------------------------------------------->|
  |                              |               |
  |  ProvideShareMessage         |               |
  |<-----------------------------|               |
  |  ProvideShareMessage                         |
  |<---------------------------------------------|
  |                                              |
  |  [Reconstruct CEK from t shares]             |
```

### 5. Document Signing Vault

```
Owner                                    Signer
  |                                        |
  |  createSigningVault(doc, recipientDid) |
  |  shareSigningVault(connectionId)       |
  |--------------------------------------->|
  |                                        |
  |                      openSigningVault()|
  |                      sign document     |
  |  returnSignedDocument()                |
  |<---------------------------------------|
```

## Usage Examples

### Basic Vault Operations

```typescript
import { Agent } from '@credo-ts/core'
import { VaultsModule } from '@ajna-inc/vaults'

const agent = new Agent({
  config: { /* ... */ },
  modules: {
    vaults: new VaultsModule()
  }
})

await agent.initialize()

// Create a vault
const vault = await agent.modules.vaults.create({
  passphrase: 'my-secure-passphrase',
  data: Buffer.from('sensitive document content'),
  metadata: {
    description: 'Contract draft',
    tags: ['legal', 'draft']
  }
})

// Open (decrypt) a vault
const plaintext = await agent.modules.vaults.open(vault.vaultId, {
  passphrase: 'my-secure-passphrase'
})

// List all vaults
const vaults = await agent.modules.vaults.list()

// Get vault info without decrypting
const info = await agent.modules.vaults.getInfo(vault.vaultId)

// Update vault data
await agent.modules.vaults.update(vault.vaultId, {
  passphrase: 'my-secure-passphrase',
  data: Buffer.from('updated content')
})

// Delete a vault
await agent.modules.vaults.delete(vault.vaultId)
```

### KEM Key Management

```typescript
// Generate an ML-KEM-768 keypair
const keypair = await agent.modules.vaults.generateKemKeypair()

// Store a peer's KEM public key
await agent.modules.vaults.storePeerKemKey(connectionId, peerPublicKey)

// Retrieve peer's key for encryption
const peerKey = await agent.modules.vaults.getPeerKemKey(connectionId)
```

### Signing Vault Workflow

```typescript
// Owner creates a signing vault for a specific recipient
const signingVault = await agent.modules.vaults.createSigningVault({
  data: documentBytes,
  recipientDid: signerDid
})

// Owner shares the vault with the signer
await agent.modules.vaults.shareSigningVault(connectionId, signingVault.vaultId)

// Signer opens the vault
const doc = await agent.modules.vaults.openSigningVault(vaultId)

// Signer signs and returns the document
await agent.modules.vaults.returnSignedDocument(connectionId, {
  vaultId,
  signedData: signedDocBytes
})
```

### S3 Storage Configuration

```typescript
const agent = new Agent({
  modules: {
    vaults: new VaultsModule({
      storage: {
        bucket: 'my-vault-bucket',
        region: 'us-east-1',
        accessKeyId: 'AKIA...',
        secretAccessKey: '...'
      },
      inlineThreshold: 1024 * 1024, // 1MB - larger vaults go to S3
      operatorMode: false
    })
  }
})
```

## Installation

```bash
pnpm add @ajna-inc/vaults
```

## Message Types

| Message | Purpose |
|---------|---------|
| `CreateVaultMessage` | Notify of vault creation |
| `UpdateVaultMessage` | Update vault data |
| `DeleteVaultMessage` | Delete vault |
| `RequestAccessMessage` | Request access to a vault |
| `GrantAccessMessage` | Grant access with encrypted data |
| `DenyAccessMessage` | Deny access request |
| `RequestShareMessage` | Request a threshold share |
| `ProvideShareMessage` | Provide a threshold share |
| `DenyShareMessage` | Deny share request |
| `StoreVaultMessage` | Store vault at storage service |
| `VaultStoredAckMessage` | Confirm storage |
| `RetrieveVaultMessage` | Retrieve vault from storage |
| `VaultDataMessage` | Vault data transmission |
| `VaultReferenceMessage` | Storage reference |
| `VaultProblemReportMessage` | Error reporting |

## Event System

Subscribe to vault lifecycle events:

```typescript
import { VaultEventTypes } from '@ajna-inc/vaults'

agent.events.on(VaultEventTypes.VaultCreated, (event) => {
  console.log('Vault created:', event.payload.vaultId)
})

agent.events.on(VaultEventTypes.AccessGranted, (event) => {
  console.log('Access granted to vault:', event.payload.vaultId)
})

agent.events.on(VaultEventTypes.ThresholdMet, (event) => {
  console.log('Threshold met, vault can be decrypted')
})
```

### Available Events

- `VaultCreated`, `VaultOpened`, `VaultUpdated`, `VaultDeleted`
- `VaultShared`, `VaultError`
- `AccessRequested`, `AccessGranted`, `AccessDenied`
- `ThresholdMet`, `ShareProvided`, `ShareRequested`, `ShareDenied`
- `StorageAllocated`, `StorageUploaded`, `StorageDownloaded`

## Security Considerations

### Cryptographic Primitives
- **ML-KEM-768**: NIST post-quantum standard for key encapsulation
- **AES-256-GCM**: Authenticated encryption with 256-bit keys
- **Argon2id**: Memory-hard KDF resistant to GPU/ASIC attacks
- **Shamir Secret Sharing**: Information-theoretically secure threshold scheme
- **Key Commitment**: Prevents key confusion and related attacks

### Best Practices
1. Use strong passphrases for passphrase-based vaults
2. Configure adequate Argon2id memory (default is reasonable for most use cases)
3. Store KEM private keys securely in the agent wallet
4. Verify key commitments are checked on every decryption
5. Use threshold policies for high-value data requiring multi-party authorization
6. Consider external storage for large files to keep wallet lean

### Client-Side Only
All encryption and decryption happens locally on the agent. No server, mediator, or storage operator ever has access to plaintext data or encryption keys.

## Vault Header Format

```typescript
{
  v: 1,                          // Protocol version
  suite: 'S3' | 'P1' | 'H1',   // Encryption suite
  aead: 'AES-256-GCM',          // AEAD algorithm
  docId: string,                 // Document identifier
  vaultId: string,               // Vault identifier
  epoch: number,                 // Version counter
  nonce: string,                 // base64url nonce
  kcmp: string,                  // Key commitment
  salt?: string,                 // Argon2id salt (S3 suite)
  argon2?: {                     // KDF parameters (S3 suite)
    memory: number,
    iterations: number
  },
  policy?: {                     // Access policy
    mode: 'passphrase' | 'any-of' | 'all-of' | 'threshold'
  },
  recipients?: RecipientWrap[],  // Per-recipient wrapped keys
  shares?: ThresholdShare[],     // Threshold shares
  metadata?: {                   // Optional metadata
    description?: string,
    tags?: string[]
  }
}
```

## Error Handling

```typescript
import { VaultError, BadSuiteError, DecryptKemError, DecryptAeadError, PolicyError } from '@ajna-inc/vaults'

try {
  await agent.modules.vaults.open(vaultId, { passphrase: 'wrong' })
} catch (error) {
  if (error instanceof DecryptAeadError) {
    console.log('Wrong passphrase or corrupted data')
  } else if (error instanceof DecryptKemError) {
    console.log('KEM decapsulation failed - wrong key')
  } else if (error instanceof PolicyError) {
    console.log('Access policy violation')
  } else if (error instanceof BadSuiteError) {
    console.log('Unknown encryption suite')
  }
}
```

## Testing

```bash
# Run vault tests
pnpm test

# Watch mode
pnpm test:watch
```

## License

Apache-2.0
