# Backup Service API Reference ## Overview The backup-service exposes RESTful APIs for managing MPC backup shares. All endpoints (except health checks) require service-to-service JWT authentication. **Base URL:** `http://localhost:3002` --- ## Authentication All `/backup-share/*` endpoints require a service JWT token in the `X-Service-Token` header. ### Token Format ```json { "service": "identity-service", "iat": 1704067200, "exp": 1704153600 } ``` ### Generating Service Tokens ```typescript import jwt from 'jsonwebtoken'; const token = jwt.sign( { service: 'identity-service' }, process.env.SERVICE_JWT_SECRET, { expiresIn: '24h' } ); ``` ### Allowed Services Only the following services can access the backup-service: - `identity-service` - Primary service for MPC operations - `recovery-service` - Account recovery operations Configure via `ALLOWED_SERVICES` environment variable. --- ## API Endpoints ### 1. Store Backup Share Store an encrypted MPC backup share for a user. **Endpoint:** `POST /backup-share/store` **Headers:** | Header | Required | Description | |--------|----------|-------------| | `X-Service-Token` | Yes | Service JWT token | | `Content-Type` | Yes | `application/json` | **Request Body:** ```json { "userId": "12345", "accountSequence": 1001, "publicKey": "02aabbccdd...", "encryptedShareData": "base64-encoded-encrypted-data", "threshold": 2, "totalParties": 3 } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `userId` | string | Yes | User identifier from identity-service | | `accountSequence` | number | Yes | Account sequence number (min: 1) | | `publicKey` | string | Yes | MPC public key (66-130 characters) | | `encryptedShareData` | string | Yes | Pre-encrypted share data (AES-256-GCM by identity-service) | | `threshold` | number | No | Reconstruction threshold (default: 2, range: 2-10) | | `totalParties` | number | No | Total MPC parties (default: 3, range: 2-10) | **Success Response (201 Created):** ```json { "success": true, "shareId": "1", "message": "Backup share stored successfully" } ``` **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 400 | `VALIDATION_ERROR` | Invalid request body | | 401 | `UNAUTHORIZED` | Missing or invalid service token | | 409 | `SHARE_ALREADY_EXISTS` | Share already exists for this user | **Example:** ```bash curl -X POST http://localhost:3002/backup-share/store \ -H "X-Service-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -H "Content-Type: application/json" \ -d '{ "userId": "12345", "accountSequence": 1001, "publicKey": "02aabbccddee...", "encryptedShareData": "encrypted-share-data-here" }' ``` --- ### 2. Retrieve Backup Share Retrieve an encrypted MPC backup share for account recovery. **Endpoint:** `POST /backup-share/retrieve` **Headers:** | Header | Required | Description | |--------|----------|-------------| | `X-Service-Token` | Yes | Service JWT token | | `Content-Type` | Yes | `application/json` | **Request Body:** ```json { "userId": "12345", "publicKey": "02aabbccdd...", "recoveryToken": "valid-recovery-token", "deviceId": "device-uuid" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `userId` | string | Yes | User identifier | | `publicKey` | string | Yes | MPC public key (66-130 characters) | | `recoveryToken` | string | Yes | Recovery token verified by identity-service | | `deviceId` | string | No | Device identifier for audit logging | **Success Response (200 OK):** ```json { "success": true, "encryptedShareData": "base64-encoded-encrypted-data", "partyIndex": 2, "publicKey": "02aabbccdd..." } ``` | Field | Type | Description | |-------|------|-------------| | `success` | boolean | Operation success status | | `encryptedShareData` | string | Decrypted share data (still encrypted by identity-service) | | `partyIndex` | number | Party index (always 2 for backup share) | | `publicKey` | string | MPC public key | **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 400 | `SHARE_NOT_ACTIVE` | Share has been revoked | | 401 | `UNAUTHORIZED` | Missing or invalid service token | | 404 | `SHARE_NOT_FOUND` | No share found for user/publicKey | | 429 | `RATE_LIMIT_EXCEEDED` | Max 3 retrievals per day exceeded | **Rate Limiting:** - Maximum 3 retrieves per user per day - Configurable via `MAX_RETRIEVE_PER_DAY` environment variable - Counter resets at midnight UTC **Example:** ```bash curl -X POST http://localhost:3002/backup-share/retrieve \ -H "X-Service-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -H "Content-Type: application/json" \ -d '{ "userId": "12345", "publicKey": "02aabbccddee...", "recoveryToken": "valid-token-from-identity-service" }' ``` --- ### 3. Revoke Backup Share Revoke an existing backup share (for key rotation or account closure). **Endpoint:** `POST /backup-share/revoke` **Headers:** | Header | Required | Description | |--------|----------|-------------| | `X-Service-Token` | Yes | Service JWT token | | `Content-Type` | Yes | `application/json` | **Request Body:** ```json { "userId": "12345", "publicKey": "02aabbccdd...", "reason": "ROTATION" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `userId` | string | Yes | User identifier | | `publicKey` | string | Yes | MPC public key (66-130 characters) | | `reason` | string | Yes | Revocation reason (see below) | **Valid Revocation Reasons:** | Reason | Description | |--------|-------------| | `ROTATION` | Key rotation (new share will be stored) | | `ACCOUNT_CLOSED` | User account permanently closed | | `SECURITY_BREACH` | Security incident requiring key invalidation | | `USER_REQUEST` | User requested share removal | **Success Response (200 OK):** ```json { "success": true, "message": "Backup share revoked successfully" } ``` **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 400 | `VALIDATION_ERROR` | Invalid reason value | | 401 | `UNAUTHORIZED` | Missing or invalid service token | | 404 | `SHARE_NOT_FOUND` | No share found for user/publicKey | **Example:** ```bash curl -X POST http://localhost:3002/backup-share/revoke \ -H "X-Service-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -H "Content-Type: application/json" \ -d '{ "userId": "12345", "publicKey": "02aabbccddee...", "reason": "ROTATION" }' ``` --- ### 4. Health Check Basic health check endpoint (no authentication required). **Endpoint:** `GET /health` **Response (200 OK):** ```json { "status": "ok", "timestamp": "2025-01-15T10:30:45.123Z", "service": "backup-service" } ``` --- ### 5. Readiness Probe Kubernetes readiness probe with database connectivity check. **Endpoint:** `GET /health/ready` **Success Response (200 OK):** ```json { "status": "ready", "database": "connected", "timestamp": "2025-01-15T10:30:45.123Z" } ``` **Failure Response (503 Service Unavailable):** ```json { "status": "not ready", "database": "disconnected", "error": "Connection refused", "timestamp": "2025-01-15T10:30:45.123Z" } ``` --- ### 6. Liveness Probe Kubernetes liveness probe. **Endpoint:** `GET /health/live` **Response (200 OK):** ```json { "status": "alive", "timestamp": "2025-01-15T10:30:45.123Z" } ``` --- ## Error Response Format All error responses follow this standardized format: ```json { "success": false, "error": "Human-readable error message", "code": "MACHINE_READABLE_CODE", "timestamp": "2025-01-15T10:30:45.123Z", "path": "/backup-share/store" } ``` ### Error Codes Reference | Code | HTTP Status | Description | |------|-------------|-------------| | `VALIDATION_ERROR` | 400 | Request validation failed | | `SHARE_NOT_ACTIVE` | 400 | Share has been revoked | | `UNAUTHORIZED` | 401 | Authentication failed | | `FORBIDDEN` | 403 | Service not authorized | | `SHARE_NOT_FOUND` | 404 | Share not found | | `SHARE_ALREADY_EXISTS` | 409 | Duplicate share | | `RATE_LIMIT_EXCEEDED` | 429 | Rate limit exceeded | | `INTERNAL_ERROR` | 500 | Internal server error | --- ## Data Validation Rules ### Public Key - Length: 66-130 characters - Format: Hexadecimal string - 66 chars = compressed ECDSA public key (02/03 prefix + 64 hex chars) - 130 chars = uncompressed ECDSA public key (04 prefix + 128 hex chars) ### User ID - Type: String (numeric format) - Required: Yes - Pattern: Positive integer as string ### Account Sequence - Type: Number - Minimum: 1 - Required: Yes ### Encrypted Share Data - Type: String - Required: Yes - Format: Base64-encoded encrypted data (from identity-service) --- ## Workflow Examples ### Complete Account Setup Flow ``` 1. User creates account on identity-service 2. identity-service generates MPC key shares (3 parties) 3. identity-service stores Party 0 (Server Share) locally 4. identity-service sends Party 1 (Client Share) to user device 5. identity-service calls backup-service to store Party 2 (Backup Share) POST /backup-share/store { "userId": "12345", "accountSequence": 1001, "publicKey": "02...", "encryptedShareData": "encrypted-party-2-data" } ``` ### Account Recovery Flow ``` 1. User loses device and initiates recovery 2. User verifies identity through identity-service 3. identity-service generates recovery token 4. identity-service retrieves backup share POST /backup-share/retrieve { "userId": "12345", "publicKey": "02...", "recoveryToken": "valid-recovery-token" } 5. identity-service combines Party 0 + Party 2 to reconstruct key 6. New Party 1 share generated for new device ``` ### Key Rotation Flow ``` 1. Security event triggers key rotation 2. New key shares generated 3. Old backup share revoked POST /backup-share/revoke { "userId": "12345", "publicKey": "02...", "reason": "ROTATION" } 4. New backup share stored POST /backup-share/store { "userId": "12345", "accountSequence": 1002, "publicKey": "03...", "encryptedShareData": "new-encrypted-data" } ``` --- ## Security Considerations ### Double Encryption Data is encrypted twice: 1. **First layer:** identity-service encrypts share data before sending 2. **Second layer:** backup-service encrypts again using AES-256-GCM This ensures data remains protected even if one system is compromised. ### Rate Limiting - Retrieves are limited to prevent brute-force attacks - Default: 3 retrieves per user per day - Configurable via environment variable ### Audit Logging All operations are logged with: - Timestamp - User ID - Action (STORE/RETRIEVE/REVOKE) - Source service - Source IP address - Success/failure status ### Sensitive Data Handling The following fields are redacted in logs: - `encryptedShareData` - `recoveryToken` - `password` - `secret` --- ## SDK Examples ### TypeScript/JavaScript ```typescript import axios from 'axios'; import jwt from 'jsonwebtoken'; const BACKUP_SERVICE_URL = 'http://localhost:3002'; const SERVICE_JWT_SECRET = process.env.SERVICE_JWT_SECRET; function generateServiceToken(): string { return jwt.sign( { service: 'identity-service' }, SERVICE_JWT_SECRET, { expiresIn: '1h' } ); } async function storeBackupShare( userId: string, accountSequence: number, publicKey: string, encryptedShareData: string ): Promise { const response = await axios.post( `${BACKUP_SERVICE_URL}/backup-share/store`, { userId, accountSequence, publicKey, encryptedShareData, }, { headers: { 'X-Service-Token': generateServiceToken(), 'Content-Type': 'application/json', }, } ); return response.data.shareId; } async function retrieveBackupShare( userId: string, publicKey: string, recoveryToken: string ): Promise<{ encryptedShareData: string; partyIndex: number }> { const response = await axios.post( `${BACKUP_SERVICE_URL}/backup-share/retrieve`, { userId, publicKey, recoveryToken, }, { headers: { 'X-Service-Token': generateServiceToken(), 'Content-Type': 'application/json', }, } ); return { encryptedShareData: response.data.encryptedShareData, partyIndex: response.data.partyIndex, }; } async function revokeBackupShare( userId: string, publicKey: string, reason: 'ROTATION' | 'ACCOUNT_CLOSED' | 'SECURITY_BREACH' | 'USER_REQUEST' ): Promise { await axios.post( `${BACKUP_SERVICE_URL}/backup-share/revoke`, { userId, publicKey, reason, }, { headers: { 'X-Service-Token': generateServiceToken(), 'Content-Type': 'application/json', }, } ); } ``` ### cURL Examples ```bash # Store a backup share curl -X POST http://localhost:3002/backup-share/store \ -H "X-Service-Token: $SERVICE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "userId": "12345", "accountSequence": 1001, "publicKey": "02aabbccddee1122334455667788990011223344556677889900112233445566", "encryptedShareData": "encrypted-data-here" }' # Retrieve a backup share curl -X POST http://localhost:3002/backup-share/retrieve \ -H "X-Service-Token: $SERVICE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "userId": "12345", "publicKey": "02aabbccddee1122334455667788990011223344556677889900112233445566", "recoveryToken": "recovery-token-here" }' # Revoke a backup share curl -X POST http://localhost:3002/backup-share/revoke \ -H "X-Service-Token: $SERVICE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "userId": "12345", "publicKey": "02aabbccddee1122334455667788990011223344556677889900112233445566", "reason": "ROTATION" }' # Health check curl http://localhost:3002/health ```