14 KiB
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
{
"service": "identity-service",
"iat": 1704067200,
"exp": 1704153600
}
Generating Service Tokens
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 operationsrecovery-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:
{
"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):
{
"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:
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:
{
"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):
{
"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_DAYenvironment variable - Counter resets at midnight UTC
Example:
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:
{
"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):
{
"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:
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):
{
"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):
{
"status": "ready",
"database": "connected",
"timestamp": "2025-01-15T10:30:45.123Z"
}
Failure Response (503 Service Unavailable):
{
"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):
{
"status": "alive",
"timestamp": "2025-01-15T10:30:45.123Z"
}
Error Response Format
All error responses follow this standardized format:
{
"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:
- First layer: identity-service encrypts share data before sending
- 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:
encryptedShareDatarecoveryTokenpasswordsecret
SDK Examples
TypeScript/JavaScript
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<string> {
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<void> {
await axios.post(
`${BACKUP_SERVICE_URL}/backup-share/revoke`,
{
userId,
publicKey,
reason,
},
{
headers: {
'X-Service-Token': generateServiceToken(),
'Content-Type': 'application/json',
},
}
);
}
cURL Examples
# 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