Self-Host TEE

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

Architecture

The BitGraph 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/bitgraph.git
cd bitgraph

# Build the enclave EIF reproducibly. This pins every input (base image by
# digest, OS packages by version, sharp by lockfile, and nitro-cli 1.4.4 which
# fixes the kernel measured into PCR0), builds the image with kaniko in
# --reproducible mode, and packs the EIF with a pinned nitro-cli. Requires
# Docker + git on a linux/amd64 host; Nitro hardware is NOT needed to build the
# EIF (only to run it). Build the tagged enclave source release: the production
# enclave is built from the enclave-v4 tag, not necessarily the latest commit.
./server/commit-service/reproducible-build/build-eif.sh enclave-v4

# PCR0 is printed at the end and written to eif-out/pcr0.txt.

Step 5: Verify the PCR0 is reproducible

PCR0 is a SHA-384 measurement of the entire EIF, and it is the enclave's identity that every proof embeds. Because the build above pins all of its inputs, you can prove the build is deterministic: build it twice and confirm the PCR0 is byte-identical, and that it equals the value BitGraph publishes. If it matches, you have independently confirmed the production enclave runs exactly the code at the tagged enclave source release (enclave-v4) in this repository, trusting no one.

Shell
# Build twice from clean state and assert identical PCR0 == the published value:
./server/commit-service/reproducible-build/verify-pcr0.sh enclave-v4 \
  e2fccbae77ee40aac4830e84f195e05d69eb4547bbd961f4d3459feba10807140424aca42ad03810354982598c86b9cb

# PASS: two independent builds produced identical PCR0:
#   e2fccbae...8c86b9cb
# PASS: matches the published PCR0.

# See server/commit-service/reproducible-build/PINS.md for every pinned
# digest/version, and README.md for how the determinism is achieved.
BitGraph's published measurement

The BitGraph enclave image in production measures as:

PCR0 e2fccbae77ee40aac4830e84f195e05d69eb4547bbd961f4d3459feba10807140424aca42ad03810354982598c86b9cb

This is the value BitGraph publishes and stands behind. Every proof embeds this measurement, and the "Verify Attestation" check confirms the attestation's PCR0 matches it. The build is bit-for-bit reproducible: rebuild from this source on any linux/amd64 host with verify-pcr0.sh and you will re-derive exactly this PCR0. You do not have to trust BitGraph's assertion, you can recompute it yourself. The one input you trust AWS for is their signed enclave kernel, which is what PCR1 independently measures; everything else folded into PCR0 is built from the auditable source in this repository.

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/bitgraph/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 BitGraph proof with TEE attestation

Step 10: Point BitGraph 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 bitgraph/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 (BitGraph 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 BitGraph 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.