Skip to content

ondefy/eigencloud-signer

Repository files navigation

tee-signer

Development

Setup & Local Testing

npm install
cp .env.example .env
npm run dev

Docker Testing

docker build -t my-app .
docker run --rm --env-file .env my-app

Environment

  • MNEMONIC: 12/24-word BIP39 phrase used to derive the signer (m/44'/60'/0'/0/0).
  • AUTHORIZED_ADDRESSES: Comma-separated list of Ethereum addresses allowed to sign requests (e.g., 0x1234...,0x5678...).
  • APP_PORT (optional): server port, defaults to 8080.
  • APP_HOST (optional): server host, defaults to 127.0.0.1.
  • TIMESTAMP_WINDOW (optional): Allowed timestamp difference in seconds, defaults to 300 (5 minutes).
  • NONCE_TTL (optional): Nonce time-to-live in seconds, defaults to 600 (10 minutes).

API

GET /public-key

Returns the EOA address derived from the mnemonic.

Response:

{
  "address": "0x..."
}

POST /sign

Signs a hash with the mnemonic. Protected by ECDSA signature authentication.

Authentication: Requests must include the following headers:

  • X-Client-Id: Ethereum address of the signer
  • X-Timestamp: Unix timestamp (seconds)
  • X-Nonce: Unique random string (UUID or random hex)
  • X-Signature: ECDSA signature of ${timestamp}|${nonce}|0x${bodyHash}

Request Body:

{
  "hash": "0x..."
}

Response:

{
  "signature": "0x..."
}

Error Responses:

  • 400: Missing or invalid request body/headers
  • 401: Authentication failed (invalid signature, expired timestamp, reused nonce)
  • 403: Client address not authorized
  • 500: Server error

Authentication

The service uses ECDSA signature authentication for the /sign endpoint. Clients must:

  1. Generate a random nonce (UUID or random hex string)
  2. Get current Unix timestamp
  3. Hash the request body with SHA256: bodyHash = SHA256(JSON.stringify(body))
  4. Construct message: ${timestamp}|${nonce}|0x${bodyHash}
  5. Sign the message with their private key using EIP-191 personal sign format
  6. Send request with all required headers

Client Implementation Example:

import { privateKeyToAccount } from 'viem/accounts';
import { createHash, randomBytes } from 'crypto';

const account = privateKeyToAccount('0x...'); // Client's private key
const clientId = account.address;

// Generate nonce
const nonce = '0x' + randomBytes(32).toString('hex');
// Or: const nonce = randomUUID();

// Get timestamp
const timestamp = Math.floor(Date.now() / 1000).toString();

// Prepare request body
const body = { hash: '0x...' };
const bodyString = JSON.stringify(body);

// Hash the body
const bodyHash = createHash('sha256').update(bodyString).digest('hex');

// Construct message to sign
const message = `${timestamp}|${nonce}|0x${bodyHash}`;

// Sign the message
const signature = await account.signMessage({ message });

// Send request
const response = await fetch('http://localhost:8080/sign', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Client-Id': clientId,
    'X-Timestamp': timestamp,
    'X-Nonce': nonce,
    'X-Signature': signature,
  },
  body: bodyString,
});

Security Features:

  • Replay Attack Prevention: Nonces are tracked and can only be used once
  • Timestamp Validation: Requests must be within the configured time window
  • Request Integrity: Body hash is included in the signature, preventing tampering
  • Address Whitelist: Only addresses in AUTHORIZED_ADDRESSES can sign requests

Prerequisites

Before deploying, you'll need:

Deployment

# Store your private key (generate new or use existing)
eigenx auth generate --store
# OR: eigenx auth login (if you have an existing key)

eigenx app deploy username/image-name

The CLI will automatically detect the Dockerfile and build your app before deploying.

Management & Monitoring

App Lifecycle

eigenx app list                    # List all apps
eigenx app info [app-name]         # Get app details
eigenx app logs [app-name]         # View logs
eigenx app start [app-name]        # Start stopped app
eigenx app stop [app-name]         # Stop running app
eigenx app terminate [app-name]    # Terminate app
eigenx app upgrade [app-name] [image] # Update deployment
eigenx app configure tls            # Configure TLS

App Profile

eigenx app profile set [app-id]  # Set app name, website, description, social links, and icon

TLS Configuration (Optional)

This project includes optional automatic TLS certificate management using Caddy. The Caddyfile is not required - if you don't need TLS termination or prefer to handle it differently, you can simply delete the Caddyfile.

How It Works

When a Caddyfile is present in your project root:

  • Caddy will automatically start as a reverse proxy
  • It handles TLS certificate acquisition and renewal via Let's Encrypt
  • Your app runs on APP_PORT and Caddy forwards HTTPS traffic to it
  • Certificates are stored persistently in the TEE's encrypted storage

Without a Caddyfile:

  • Your application runs directly on the configured ports
  • You can handle TLS in your application code or use an external load balancer

Deployment Checklist

Before deploying with TLS:

  1. Configure TLS: Run eigenx app configure tls to add the necessary configuration files for domain setup with private traffic termination in the TEE.

  2. DNS: Ensure A/AAAA record points to your instance (or reserved static IP). Note: If this is your first deployment, you will need to get your IP after deployment from the eigenx app info command.

  3. Required configuration in .env:

    DOMAIN=mydomain.com          # Your domain name
    APP_PORT=8000               # Your app's port
    ACME_STAGING=true           # Test with staging first to avoid rate limits
    ENABLE_CADDY_LOGS=true      # Enable logs for debugging
  4. Optional ACME configuration (all optional, with sensible defaults):

    # ACME email for Let's Encrypt notifications
    ACME_EMAIL=admin@example.com
    
    # Certificate Authority directory URL
    # Default: https://acme-v02.api.letsencrypt.org/directory
    ACME_CA=https://acme-v02.api.letsencrypt.org/directory
    
    # ACME Challenge Type
    # How to prove domain ownership to Let's Encrypt
    # Both result in the same TLS certificate, just different validation methods:
    # - http-01: Uses port 80 (default)
    # - tls-alpn-01: Uses port 443
    ACME_CHALLENGE=http-01
    
    # Use Let's Encrypt Staging (for testing)
    # Set to true to use staging environment (certificates won't be trusted by browsers)
    # Great for testing without hitting rate limits
    ACME_STAGING=true
    
    # Force certificate reissue
    # Set to true to force a new certificate even if one exists
    # This will delete the existing certificate from storage and get a new one
    ACME_FORCE_ISSUE=true
  5. Customize Caddyfile (optional):

    • Edit Caddyfile to match your application port
    • Modify security headers as needed
    • Configure rate limiting or other middleware

TLS Testing & Debugging

  • Enable Caddy logs to see TLS-related output:

    ENABLE_CADDY_LOGS=true
  • Use Let's Encrypt staging for testing (avoids rate limits, but certificates won't be trusted by browsers):

    ACME_STAGING=true

Local Development

For local development without TLS, leave DOMAIN empty or set to localhost in your .env file.

Custom Certificates

To use custom certificates instead of Let's Encrypt, modify the Caddyfile:

tls /path/to/cert.pem /path/to/key.pem

Documentation

EigenX CLI Documentation

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published