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:
# 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:
| Port | Protocol | Source | Purpose |
|---|---|---|---|
| 22 | TCP | Your IP | SSH |
| 8080 | TCP | Your app server | Parent HTTP API |
Step 2: Install Dependencies
SSH into the instance and install everything:
# 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:
# 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
# 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.
# 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
# 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):
# 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
# 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
# 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 attestationStep 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_KEYSenvironment variable - Save the PCR0 measurement — this is your enclave's identity for verification
- Set up monitoring on
/healthendpoint - 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:
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
| File | Purpose |
|---|---|
server/commit-service/Dockerfile.enclave | Builds the enclave Docker image |
server/commit-service/src/enclave/app.ts | Enclave application — proof signing, slot management |
server/commit-service/src/parent/server.ts | Parent HTTP API — commit, key, health endpoints |
server/commit-service/src/parent/vsock-client.ts | TCP bridge client to enclave |
server/commit-service/deploy.sh | Automated deployment script |
packages/adapter-nitro/src/nitro-host.ts | NSM device interface — attestation, measurement |
packages/hosted/src/authorization.ts | Dashboard integration — calls TEE_URL |
How the Enclave Works Internally
On startup, the enclave:
- Generates a fresh Ed25519 keypair in memory (never exported)
- Fetches the PCR0 measurement from the NSM device (
/dev/nsm) - Generates a boot nonce from the NSM hardware RNG
- Computes
epochId = SHA-256(publicKeyB64 + ":" + bootNonceB64) - Listens on a Unix socket for proof requests
For each proof request:
- Validates the slot exists (OCC causal gate — no slot, no proof)
- Increments the chain counter
- Builds the signed body: artifact, commit, policy, principal
- Signs with Ed25519
- Gets a Nitro attestation report from the NSM device
- 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
epochIdis 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.