Self-Host TEE

Deploy your own OCC Trusted Execution Environment using AWS Nitro Enclaves. This guide assumes no prior TEE experience.

Architecture

The OCC TEE consists of three components running on a single EC2 instance:

  • Enclave — isolated TEE that holds the Ed25519 signing key and produces cryptographically signed proofs. The key is generated inside the enclave and never leaves.
  • Parent server — HTTP server running on the EC2 host. Receives proof requests, forwards them to the enclave via vsock, returns signed proofs.
  • Vsock bridge — socat process that bridges TCP (parent) to vsock (enclave). Required because Node.js doesn't support AF_VSOCK natively.

Communication flow:

Client (HTTPS) → Parent Server (port 8080) → Socat (TCP:9000 ↔ Vsock:5000) → Enclave App

Prerequisites

  • AWS account with EC2 access
  • A Nitro-capable EC2 instance (c5, c6, m5, m6, r5, r6 families — not t2/t3)
  • At least 2 vCPUs and 4 GB RAM (the enclave needs dedicated CPU/memory)
  • Docker installed on the instance
  • Node.js 20+ installed on the instance

Step 1: Launch EC2 Instance

Launch a Nitro-capable instance with enclave support enabled:

AWS Console or CLI
# Example: c6a.xlarge (4 vCPU, 8 GB RAM)
# AMI: Amazon Linux 2023

# IMPORTANT: Enable "Nitro Enclave" in Advanced Details when launching
# Or via CLI:
aws ec2 run-instances \
  --instance-type c6a.xlarge \
  --image-id ami-0abcdef1234567890 \
  --enclave-options Enabled=true \
  --key-name your-key-pair

Security group — allow inbound:

PortProtocolSourcePurpose
22TCPYour IPSSH
8080TCPYour app serverParent HTTP API

Step 2: Install Dependencies

SSH into the instance and install everything:

Shell
# Install Nitro CLI
sudo amazon-linux-extras install aws-nitro-enclaves-cli -y
sudo yum install aws-nitro-enclaves-cli-devel -y

# Install Docker
sudo yum install docker -y
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker $USER

# Install Node.js 20
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo yum install nodejs -y

# Install socat
sudo yum install socat -y

# Install build tools (for NSM helper)
sudo yum install gcc musl-devel -y

# Start the Nitro enclave allocator
sudo systemctl start nitro-enclaves-allocator
sudo systemctl enable nitro-enclaves-allocator

# Add yourself to the enclave group
sudo usermod -aG ne $USER

# IMPORTANT: Log out and back in for group changes
exit

Step 3: Configure Enclave Resources

The enclave needs dedicated CPU and memory allocated from the host. Edit the allocator config:

/etc/nitro_enclaves/allocator.yaml
# Allocate 2 CPUs and 1024 MB to the enclave
memory_mib: 1024
cpu_count: 2
# Restart allocator after changes
sudo systemctl restart nitro-enclaves-allocator

Step 4: Clone and Build

Shell
# Clone the repo
git clone https://github.com/mikeargento/occ.git
cd occ

# Install dependencies
npm ci

# Build the Docker image for the enclave
# Context must be the repo root (monorepo build)
cd server/commit-service
docker build -f Dockerfile.enclave -t occ-enclave ../../

Step 5: Build the Enclave Image (EIF)

The EIF (Enclave Image Format) is a sealed binary that runs inside the Nitro Enclave. The build process measures the image and produces a PCR0 hash — this is the enclave's identity.

Shell
# Build the EIF from the Docker image
nitro-cli build-enclave \
  --docker-uri occ-enclave \
  --output-file enclave.eif

# Output will show:
# Enclave Image successfully created.
# {
#   "Measurements": {
#     "HashAlgorithm": "Sha384 { ... }",
#     "PCR0": "abc123def456...",   ← SAVE THIS
#     "PCR1": "...",
#     "PCR2": "..."
#   }
# }

# IMPORTANT: Save the PCR0 value.
# This is the measurement that proves which code is running.
# Verifiers use this to confirm proofs came from YOUR enclave.

Step 6: Launch the Enclave

Shell
# Terminate any existing enclave
nitro-cli terminate-enclave --all 2>/dev/null

# Launch the enclave
nitro-cli run-enclave \
  --eif-path enclave.eif \
  --cpu-count 2 \
  --memory 1024

# Verify it's running
nitro-cli describe-enclaves
# Should show: State: "RUNNING", EnclaveCID: <number>

# Save the CID — you need it for the vsock bridge
ENCLAVE_CID=$(nitro-cli describe-enclaves | jq -r '.[0].EnclaveCID')

Step 7: Start the Vsock Bridge

The bridge connects the parent server (TCP) to the enclave (vsock):

Shell
# Start socat bridge in background
nohup socat TCP-LISTEN:9000,fork,reuseaddr \
  VSOCK-CONNECT:$ENCLAVE_CID:5000 \
  > /tmp/socat-bridge.log 2>&1 &

# Verify it's listening
ss -tlnp | grep 9000
# Should show: LISTEN 0 5 0.0.0.0:9000

Step 8: Build and Start the Parent Server

Shell
# Build the parent server (TypeScript → JavaScript)
cd /path/to/occ/server/commit-service
npx tsc -p tsconfig.parent.json

# Set environment variables
export PORT=8080
export VSOCK_BRIDGE_PORT=9000
export API_KEYS="your-secret-api-key-here"

# Start the parent server
nohup node dist/parent/server.js > /tmp/parent.log 2>&1 &

Step 9: Verify

Shell
# Health check
curl http://localhost:8080/health
# { "ok": true }

# Get the enclave's public key and measurement
curl http://localhost:8080/key
# {
#   "publicKeyB64": "...",
#   "measurement": "abc123...",
#   "enforcement": "measured-tee"
# }

# Test a commit
DIGEST=$(echo -n "hello world" | openssl dgst -sha256 -binary | base64)
curl -X POST http://localhost:8080/commit \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-secret-api-key-here" \
  -d "{
    \"digests\": [{\"digestB64\": \"$DIGEST\", \"hashAlg\": \"sha256\"}]
  }"
# Returns: signed OCC proof with TEE attestation

Step 10: Point OCC Dashboard at Your TEE

By default, the hosted dashboard at occ.bitgraph.ing points to nitro.occproof.com. To use your own TEE, set the TEE_URL environment variable on your hosted server:

# In your hosted server environment (Railway, etc.)
TEE_URL=https://your-tee-domain.com

The hosted server at packages/hosted/src/authorization.ts reads this variable:

const TEE_URL = process.env.TEE_URL || "https://nitro.occproof.com";

Production Checklist

  • Put an ALB or CloudFront in front of port 8080 with TLS termination
  • Restrict security group to only allow your app server's IP
  • Set strong API keys via the API_KEYS environment variable
  • Save the PCR0 measurement — this is your enclave's identity for verification
  • Set up monitoring on /health endpoint
  • Configure log rotation for parent server and socat logs
  • The enclave generates a new keypair on each restart — the epochId changes and the counter resets to 1. Cross-epoch sequencing is established by Ethereum anchors, not by an in-enclave chain.

Using the Deploy Script

For automated deployment, use the included script:

Shell
cd occ/server/commit-service
./deploy.sh

# This runs all steps automatically:
# 1. Builds Docker image
# 2. Builds EIF
# 3. Terminates existing enclave
# 4. Launches new enclave
# 5. Starts vsock bridge
# 6. Builds and starts parent server

Key Files

FilePurpose
server/commit-service/Dockerfile.enclaveBuilds the enclave Docker image
server/commit-service/src/enclave/app.tsEnclave application — proof signing, slot management
server/commit-service/src/parent/server.tsParent HTTP API — commit, key, health endpoints
server/commit-service/src/parent/vsock-client.tsTCP bridge client to enclave
server/commit-service/deploy.shAutomated deployment script
packages/adapter-nitro/src/nitro-host.tsNSM device interface — attestation, measurement
packages/hosted/src/authorization.tsDashboard integration — calls TEE_URL

How the Enclave Works Internally

On startup, the enclave:

  1. Generates a fresh Ed25519 keypair in memory (never exported)
  2. Fetches the PCR0 measurement from the NSM device (/dev/nsm)
  3. Generates a boot nonce from the NSM hardware RNG
  4. Computes epochId = SHA-256(publicKeyB64 + ":" + bootNonceB64)
  5. Listens on a Unix socket for proof requests

For each proof request:

  1. Validates the slot exists (OCC causal gate — no slot, no proof)
  2. Increments the chain counter
  3. Builds the signed body: artifact, commit, policy, principal
  4. Signs with Ed25519
  5. Gets a Nitro attestation report from the NSM device
  6. Returns the complete OCC proof with attestation embedded

Epoch Transitions

When the enclave restarts (deploy, crash, reboot):

  • A fresh Ed25519 keypair is generated inside the enclave from hardware entropy. The previous key is destroyed and exists nowhere outside the terminated enclave.
  • A new epochId is derived from the new public key plus a fresh boot nonce.
  • The monotonic counter resets to 1. The first proof of the new epoch has no prevB64 — the prior chain is closed, and the new chain begins at genesis.
  • During restart, all commit requests fail closed.

This is a containment property, not a limitation. Each epoch is a sealed compartment: any compromise of the live epoch cannot retroactively forge proofs under a prior epoch's key. Cross-epoch sequencing is established externally by Ethereum anchors, not by an in-enclave chain.