Skip to content

Latest commit

 

History

History
552 lines (408 loc) · 12 KB

File metadata and controls

552 lines (408 loc) · 12 KB

Wulong API Reference

Complete reference for all REST API endpoints in Wulong.

Base URL

https://localhost:3000

Note: In development, use -k flag with curl to accept self-signed certificates.

Table of Contents


Chest Endpoints

GET /chest/attestation

Get TEE attestation proving the service cannot access user data.

Authentication: None (publicly accessible)

Response:

{
  platform: 'amd-sev-snp' | 'intel-tdx' | 'aws-nitro' | 'phala' | 'none';
  report: string;           // Base64-encoded attestation report/quote from TEE
  measurement: string;      // Measurement/hash of the code running in the TEE
  timestamp: string;        // ISO 8601 timestamp when attestation was generated
  publicKey?: string;       // Public key of the TEE (if applicable)
  mlkemPublicKey?: string;  // ML-KEM-1024 public key for quantum-resistant encryption (base64)
}

Example:

# Request
curl -k https://localhost:3000/chest/attestation

# Response
{
  "platform": "intel-tdx",
  "report": "eyJhdHRlc3RhdGlvbiI6ICIuLi4ifQ==",
  "measurement": "abc123def456...",
  "timestamp": "2026-03-18T10:30:00.000Z",
  "mlkemPublicKey": "k3VARNFcS4hWl6AfR0DMy..."
}

Use Cases:

  1. Verify code integrity - Compare measurement with published source code hash
  2. Get encryption key - Use mlkemPublicKey to encrypt secrets client-side (quantum-resistant)
  3. Confirm TEE platform - Check that service is running in genuine TEE hardware

See also: Client-Side Encryption Guide


POST /chest/store

Store a secret with owner-based access control.

Authentication: None required for storing

Request Body:

{
  secret: string;              // The secret to store (recommend encrypting with ML-KEM first)
  publicAddresses: string[];   // Array of Ethereum addresses that can access this secret
}

Response:

{
  slot: string;  // Unique 64-character hex identifier for this secret
}

Status Codes:

  • 201 Created - Secret stored successfully
  • 400 Bad Request - Invalid request (empty secret, invalid addresses, etc.)

Example:

# Request
curl -k -X POST https://localhost:3000/chest/store \
  -H "Content-Type: application/json" \
  -d '{
    "secret": "苟全性命於亂世,不求聞達於諸侯。",
    "publicAddresses": ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"]
  }'

# Response
{
  "slot": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"
}

Security Recommendations:

  1. Encrypt secrets client-side with ML-KEM before storing:

    // Get ML-KEM public key from attestation
    const attestation = await fetch('/chest/attestation').then(r => r.json());
    
    // Encrypt with ML-KEM (see Client-Side Encryption guide)
    const encrypted = await encryptWithMlKem(secret, attestation.mlkemPublicKey);
    
    // Store encrypted secret
    await fetch('/chest/store', {
      method: 'POST',
      body: JSON.stringify({
        secret: JSON.stringify(encrypted),
        publicAddresses: [myAddress]
      })
    });
  2. Use checksummed Ethereum addresses - Both formats are accepted

  3. Store slot ID securely - You'll need it to retrieve the secret later

See also: Client-Side Encryption Guide


GET /chest/access/:slot

Access a stored secret.

Authentication: Required (SIWE)

Path Parameters:

  • slot - The slot identifier returned from /chest/store

Headers:

x-siwe-message: <base64-encoded SIWE message>
x-siwe-signature: <hex signature>

Response:

{
  secret: string;  // The stored secret (decrypted if it was encrypted with ML-KEM)
}

Status Codes:

  • 200 OK - Secret retrieved successfully
  • 401 Unauthorized - Missing or invalid SIWE authentication
  • 403 Forbidden - Caller is not an owner of this secret
  • 404 Not Found - Slot does not exist

Example:

# Step 1: Get nonce
NONCE=$(curl -k -X POST https://localhost:3000/auth/nonce | jq -r '.nonce')

# Step 2: Create and sign SIWE message (using your wallet)
# Message format:
# localhost wants you to sign in with your Ethereum account:
# 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
#
# URI: https://localhost:3000
# Version: 1
# Chain ID: 1
# Nonce: <NONCE>
# Issued At: 2026-03-18T10:30:00.000Z

# Step 3: Access secret with SIWE authentication
curl -k https://localhost:3000/chest/access/a1b2c3d4... \
  -H "x-siwe-message: $(echo -n "$MESSAGE" | base64)" \
  -H "x-siwe-signature: $SIGNATURE"

# Response
{
  "secret": "苟全性命於亂世,不求聞達於諸侯。"
}

TypeScript Example:

import { SiweMessage } from 'siwe';

// Get nonce
const { nonce } = await fetch('https://localhost:3000/auth/nonce', {
  method: 'POST'
}).then(r => r.json());

// Create SIWE message
const siweMessage = new SiweMessage({
  domain: 'localhost',
  address: walletAddress,
  uri: 'https://localhost:3000',
  version: '1',
  chainId: 1,
  nonce: nonce,
  issuedAt: new Date().toISOString(),
});

const message = siweMessage.prepareMessage();
const signature = await wallet.signMessage(message);

// Access secret
const response = await fetch(`https://localhost:3000/chest/access/${slot}`, {
  headers: {
    'x-siwe-message': Buffer.from(message).toString('base64'),
    'x-siwe-signature': signature,
  },
});

const { secret } = await response.json();
console.log('Secret:', secret);

Security Notes:

  • SIWE nonces are single-use and expire after 5 minutes
  • Signatures must be valid for the authenticated Ethereum address
  • Only addresses in the publicAddresses array can access the secret
  • Case-insensitive address matching (checksummed or lowercase both work)

See also: SIWE Authentication Guide


Authentication Endpoints

POST /auth/nonce

Generate a SIWE (Sign-In with Ethereum) nonce for authentication.

Authentication: None

Response:

{
  nonce: string;  // Random nonce for SIWE message (single-use, expires in 5 minutes)
}

Example:

# Request
curl -k -X POST https://localhost:3000/auth/nonce

# Response
{
  "nonce": "1a2b3c4d5e6f7890"
}

Usage Flow:

  1. Call /auth/nonce to get a fresh nonce
  2. Create SIWE message with the nonce
  3. Sign the message with your Ethereum wallet
  4. Use the signature in x-siwe-signature header for protected endpoints

Example SIWE Message Format:

localhost wants you to sign in with your Ethereum account:
0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb

URI: https://localhost:3000
Version: 1
Chain ID: 1
Nonce: 1a2b3c4d5e6f7890
Issued At: 2026-03-18T10:30:00.000Z

See also: SIWE Authentication Guide


Health Check Endpoints

GET /health

General health check endpoint.

Authentication: None

Response:

{
  status: 'ok';
  timestamp: string;  // ISO 8601 timestamp
}

Example:

curl -k https://localhost:3000/health

# Response
{
  "status": "ok",
  "timestamp": "2026-03-18T10:30:00.000Z"
}

GET /health/ready

Readiness probe for orchestration systems (Kubernetes, etc.).

Authentication: None

Response:

{
  status: 'ready' | 'not ready';
  checks: {
    database?: boolean;
    tee?: boolean;
    encryption?: boolean;
  };
}

Status Codes:

  • 200 OK - Service is ready to accept traffic
  • 503 Service Unavailable - Service is not ready

Example:

curl -k https://localhost:3000/health/ready

# Response
{
  "status": "ready",
  "checks": {
    "tee": true,
    "encryption": true
  }
}

GET /health/live

Liveness probe for orchestration systems.

Authentication: None

Response:

{
  status: 'alive';
}

Status Codes:

  • 200 OK - Service is alive
  • 503 Service Unavailable - Service should be restarted

Example:

curl -k https://localhost:3000/health/live

# Response
{
  "status": "alive"
}

Error Responses

All endpoints return consistent error responses:

{
  statusCode: number;
  message: string;
  error?: string;  // Error type (BadRequest, Unauthorized, etc.)
}

Common Status Codes:

  • 400 Bad Request - Invalid request parameters or body
  • 401 Unauthorized - Missing or invalid authentication
  • 403 Forbidden - Authenticated but not authorized
  • 404 Not Found - Resource does not exist
  • 500 Internal Server Error - Unexpected server error

Example Error:

{
  "statusCode": 400,
  "message": "Secret cannot be empty",
  "error": "Bad Request"
}

Rate Limiting

All endpoints are rate-limited to prevent abuse:

  • Global limit: 100 requests per minute per IP
  • Auth endpoints: 10 requests per minute per IP
  • Store endpoint: 50 requests per minute per IP

Rate Limit Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1234567890

Rate Limit Exceeded Response:

{
  "statusCode": 429,
  "message": "Too Many Requests",
  "error": "ThrottlerException"
}

Swagger/OpenAPI Documentation

Interactive API documentation is available at:

https://localhost:3000

Features:

  • Try out endpoints directly in the browser
  • See request/response schemas
  • View example requests and responses
  • Download OpenAPI specification

Client Libraries

TypeScript/JavaScript:

npm install mlkem siwe ethers

Recommended Libraries:

  • mlkem - Quantum-resistant encryption
  • siwe - Sign-In with Ethereum
  • ethers - Ethereum wallet interaction
  • w3pk - Web3 passkey SDK (for wallet + encryption)

See also:


Security Best Practices

  1. Always verify attestation before encrypting secrets

    const attestation = await fetch('/chest/attestation').then(r => r.json());
    if (attestation.measurement !== EXPECTED_MEASUREMENT) {
      throw new Error('Code measurement mismatch!');
    }
  2. Encrypt secrets client-side with ML-KEM before storing

  3. Use HTTPS in production with real TLS certificates

    • Never use -k flag in production
    • Generate certificates inside TEE
  4. Store slot IDs securely

    • Don't expose in URLs or logs
    • Consider encrypting slot IDs client-side
  5. Validate Ethereum addresses before storing

    • Use checksummed addresses
    • Verify addresses are owned by intended users
  6. Handle SIWE nonces properly

    • Request fresh nonce for each authentication
    • Don't reuse nonces
    • Check nonce expiration (5 minutes)

Support


Related Documentation