A framework for building Confidential Compute applications, which use trusted execution environments (TEEs) and threshold-encrypted secrets in conjunction.
Applications built on this framework receive abstractions for the infrastructure required to implement the Confidential Compute protocol:
- A client for enclave communication.
- An integration test harness that uses a simulated threshold encryption system.
- Cloud-provider-specific enclave environment code (currently AWS Nitro only).
- Core enclave services: a combiner for secret key shares, an ephemeral keychain that issues ephemeral asymmetric keys, and an attestation service that produces remote attestations of request execution inside the enclave.
The confidential-http application lets CRE workflow authors make external API requests containing secrets encrypted in the Vault DON. The secrets are never revealed to external parties — only the secret owner and the enclave see plaintext.
It lives at enclave/apps/confidential-http/:
enclave/apps/confidential-http/
├── app/ # Core application logic that runs inside an enclave.
├── capability/ # Capability source for the Confidential HTTP CRE Capability.
├── environments/ # Supported enclave environments.
│ ├── fake # Local fake enclave entrypoint (emulates Nitro over loopback).
│ └── nitro # AWS Nitro Enclave environment entrypoint.
└── types/ # Protobuf types shared by the enclave app and the CRE Capability.
The confidential-workflows application lets CRE workflow authors run their workflows entirely in a TEE, once they have been triggered from a public workflow DON.
It lives at enclave/apps/confidential-workflows/:
enclave/apps/confidential-workflows/
├── app/ # Core logic that runs inside the enclave (WASM execution, fetching, dispatch).
├── capability/ # Capability source for the Confidential Workflows CRE Capability.
├── environments/ # Supported enclave environments.
│ ├── fake # Local fake enclave entrypoint.
│ └── nitro # AWS Nitro Enclave environment entrypoint.
├── gateway/ # Client for communicating with the CRE Gateway.
└── httpfetch/ # Helper for sending outbound HTTPS requests.
confidential-echo is a minimal reference implementation of the EnclaveApp interface. It treats the public input as a Go text/template and renders it with the injected secrets substituted by name, returning the rendered bytes. It has no network access or external dependencies, so it serves as the canonical example of the secret-injection pattern and end-to-end test.
It lives at enclave/apps/confidential-echo/:
enclave/apps/confidential-echo/
├── app/ # The EnclaveApp implementation and its unit tests.
└── environments/ # Supported enclave environments.
├── fake # Local fake enclave entrypoint.
└── nitro # AWS Nitro Enclave environment entrypoint.
Its end-to-end test can be found at: TestConfidentialEchoEnclave in tests/enclave_test.go.
cd tests
go test -v . -run '^TestConfidentialEchoEnclave$'confidential-compute/
├── capabilities/ # Chainlink CRE-specific resources.
│ ├── examples/ # Example capabilities for development & demonstration.
│ └── framework/ # Library abstracting CRE capability logic.
├── deploy/ # Helm charts & configs for Griddle infrastructure.
├── enclave/ # Enclave apps, environments, examples, and services.
│ ├── apps/ # Confidential Compute applications.
│ │ ├── confidential-echo/ # Minimal reference app: renders a template with injected secrets.
│ │ ├── confidential-http/ # Executes an HTTP request template with secrets.
│ │ └── confidential-workflows/ # Runs CRE workflows entirely inside a TEE.
│ ├── examples/ # Informative example enclave setups.
│ │ ├── hello-enclave/ # Basic AWS Nitro "Hello World" enclave.
│ │ └── ticker/ # Nitro enclave demonstrating a reliable system clock.
│ ├── fake/ # Fake enclave runtime: runs the stack as local processes.
│ ├── vsock/ # vsock abstraction; emulates vsock over loopback TCP.
│ ├── nitro/ # AWS Nitro-specific environment code.
│ │ ├── host/ # Parent host for inbound traffic and outbound tunnels.
│ │ │ └── proxy-server/ # Parent-side VSOCK-to-TCP relay.
│ │ └── proxy-client/ # Enclave-side policy-aware VSOCK egress dialer.
│ ├── server/ # Trusted enclave server that dispatches requests to an app.
│ └── services/ # Generic services available to enclave applications.
│ ├── attestor/ # Generates remote attestations.
│ ├── combiner/ # Combines threshold decryption key shares.
│ ├── emitter/ # Exports non-sensitive data to an ingestion service.
│ ├── keychain/ # Generates ephemeral asymmetric keypairs on a schedule.
│ └── signature-verifier/ # Signature verifier service.
├── enclave-client/ # Routing logic and HTTP client for enclave communication.
│ ├── attestation-validator/ # Verifies remote attestations.
│ ├── enclave-selector/ # Chooses enclaves to execute a given request.
│ ├── spec/ # API spec for communicating with enclaves.
│ └── test-data/ # Test attestations & PCR measurements.
├── tests/ # Cloud-specific enclave tests & end-to-end tests.
│ └── e2e/ # End-to-end tests.
├── types/ # Shared types.
└── util/ # Shared helper logic.
Create a folder for your app under enclave/apps/ (mirroring confidential-echo):
enclave/apps/<your-app>/
├── app/ # EnclaveApp implementation and its unit tests.
└── environments/ # Enclave environment entrypoints.
├── fake # Local fake enclave entrypoint.
└── nitro # AWS Nitro enclave entrypoint.
Then:
- In
app/, implement theEnclaveAppinterface, which performs logic over public input bytes and a map of secrets. Add anAppIDconstant intypes/constants.go. - Choose an input encoding. Simple apps can treat the public input as raw bytes (see
confidential-echo). If you plan to build a CRE Capability, add atypes.protodefining your input type, with theSecretIdentifiertype fromtypes/frameworktypes/framework.protoat its root. - Add enclave environment entrypoints under
environments/. Create afake/main.go(packagemain) that callsStartFakeEnclavefromenclave/fake/runner(for local dev and tests) and anitro/main.gothat callsStartNitroEnclavefromenclave/nitro. Both inject yourEnclaveAppfrom step 1. - Add an integration test to
tests/enclave_test.gousing the shared harness —SetupEnclaveAppstarts the enclave andExecuteEnclaveAppE2Edrives a request via anEnclaveExecutionconfig. SeeTestConfidentialEchoEnclave(fake) andTestConfidentialHttpEnclave. - Test your application (see Tests).
-
Create a CRE Capability for your application:
- Generate CRE SDK code from your
types.proto. - After adding your SDK to the CRE SDK repo and a Capability Server to Chainlink Common, create a
capability/folder. - Implement the
ConfidentialActioninterface and expose a CRE entrypoint viaServeNewfromgithub.com/smartcontractkit/capabilities/libs/loopserver. - Reusing the same
types.protofor both the enclave app input and the capability SDK gives workflow authors a "virtualized" view of enclave execution — they understand what runs in the enclave from the request they make to the SDK.
- Generate CRE SDK code from your
-
Add CRE E2E tests:
- Add your test application struct to the
appsslice intests/e2e/e2e_test.go, defining the secrets, requests, and response validation to run against your application. - Add a
capability_configsTOML entry intests/e2e/configs/capability_defaults.tomlsettingbinary_path = "./binaries/[YOUR_APP_NAME]". Seecapability_configs.confidential-httpfor reference. - Run your tests.
- Add your test application struct to the
confidential-echo is the smallest reference for steps 1–5; confidential-http is a good reference for the full flow including a CRE Capability.
Testing happens at two levels:
- Enclave integration tests (
tests/enclave_test.go) exercise a single enclave app in isolation — inject secrets, run a request, check the output. - End-to-end suites (
tests/e2e) run an app through the full CRE stack (chainlink node, job distributor, capabilities) against a real workflow.
Both run against either fake enclaves (no hardware — runs anywhere with Docker) or real AWS Nitro enclaves. Fake enclaves emulate the Nitro environment by running the same enclave app, sidecars, and untrusted host as ordinary local processes, with the enclave/vsock package emulating vsock over loopback TCP (VSOCK_BACKEND=tcp). This exercises the same attestation/keychain/combiner/host code paths as production without Nitro hardware.
The shared harness starts an enclave running your app and drives requests against it:
SetupEnclaveApp(t, appName)builds and starts the enclave for the named app underenclave/apps/, returning a cleanup func.ExecuteEnclaveAppE2E(t, EnclaveExecution{...})configures the threshold parameters, secrets, and public input, then runs one request end-to-end (set config → fetch public keys → execute) and returns the response.
TestConfidentialEchoEnclave is the minimal example; TestConfidentialHttpEnclave shows a fuller app. To run them:
cd tests
ENCLAVE_TYPE=FAKE go test -v . -run '^TestConfidentialEchoEnclave$'The environment is selected automatically: the harness uses a real Nitro enclave when nitro-cli is on PATH, and otherwise falls back to a fake enclave (logging a warning). Set ENCLAVE_TYPE=FAKE to force fake and silence the warning, or ENCLAVE_TYPE=NITRO to require real hardware.
Pure application logic that doesn't need a running enclave belongs in ordinary unit tests next to the app (see enclave/apps/confidential-echo/app/app_test.go).
Requirements: Docker (≥ 24 GB, root disk < 85% full) and an authenticated gh CLI. No local chainlink checkout or Nitro hardware needed.
The root Makefile automates the whole setup:
make e2e-local-conf-http # TestConfidentialHTTPE2E
make e2e-local-conf-workflows # TestConfidentialWorkflowsEngineE2EIt shallow-clones chainlink (plus job-distributor and, for the engine suite, chainlink-testing-framework) into /tmp/cc-e2e at the refs pinned in go-tests.yaml — your own checkouts are never touched — builds the chainlink node image and CC plugin binaries, symlinks core for the chiprouter, then runs the suite with ENCLAVE_TYPE=FAKE. Images are cached by tag, so re-runs skip the heavy builds.
make e2e-images— build/cache all required images without running a suite.make clean-e2e— remove the scratch clones, plugin binaries, and thecoresymlink.make help— list targets and show the resolved pins.
Manual equivalent (for debugging)
- Build the chainlink node image with the CRE capability plugins baked in. The plugins (
cron,consensus,http_action,http_trigger) come fromplugins/plugins.private.yaml, soCL_INSTALL_PRIVATE_PLUGINSmust betrue(the default). Remove theconfidential-http:andconfidential-workflows:blocks from that file first — the e2e supplies them as local binaries, and building them would pull an unrelated CC version:cd <chainlink> # checked out at CHAINLINK_COMMIT_SHA # delete the `confidential-http:` and `confidential-workflows:` entries from plugins/plugins.private.yaml gh auth token > /tmp/ghtoken docker build \ --secret id=GIT_AUTH_TOKEN,src=/tmp/ghtoken \ --build-arg CL_INSTALL_PRIVATE_PLUGINS=true \ --build-arg CL_IS_PROD_BUILD=false \ -f core/chainlink.Dockerfile -t chainlink:latest . rm -f /tmp/ghtoken
- The CRE chiprouter loads the environment state file via a path hardcoded four directories above
tests/e2e. Symlinkcorethere to your chainlink checkout (CI does the equivalent):ln -sfn <chainlink>/core "$(cd tests/e2e && cd ../../../.. && pwd)/core" # may need sudo
- Run a suite with fake enclaves:
For the engine suite, use
cd tests/e2e CI=1 ENCLAVE_TYPE=FAKE \ CTF_CONFIGS=configs/workflow-don.toml \ CTF_CHAINLINK_IMAGE=chainlink:latest \ CTF_JD_IMAGE=job-distributor:0.22.1 \ go test -tags e2e -v -timeout 60m -run '^TestConfidentialHTTPE2E$' .
CTF_CONFIGS=configs/workflow-don-engine.tomland-run '^TestConfidentialWorkflowsEngineE2E$'.
On a Nitro-capable host (nitro-cli installed, Docker ≥ 24 GB), run the same Makefile targets with ENCLAVE_TYPE=NITRO. The harness provisions real Nitro enclaves instead of fake ones; everything else (image build, plugin binaries, core symlink) is identical to the fake flow.
make e2e-local-conf-http ENCLAVE_TYPE=NITRO # TestConfidentialHTTPE2E
make e2e-local-conf-workflows ENCLAVE_TYPE=NITRO # TestConfidentialWorkflowsEngineE2EThese targets clear stale Nitro state (leftover enclaves and cached EIF/PCR artifacts) automatically before each run. To run that cleanup on its own:
make clean-e2e-nitroCI (go-tests.yaml)
- Pull requests run the full module test suite — including
tests/andtests/e2e— against fake enclaves on GitHub-hosted runners. The three heavy CRE images (chainlink, job-distributor, chip-router/ingress/config) are built once and cached in GHCR, keyed on their pinned refs. - Nightly (scheduled) and release-branch pushes re-run
tests/andtests/e2eagainst real Nitro enclaves on self-hosted runners, catching hardware/attestation regressions the fake environment can't. - The
e2e-real-enclaveslabel adds a real Nitro e2e + integration run on top of the usual fake-enclave suite. Re-applying the label triggers a fresh run. - A backwards-compatibility variant runs the suite with prior-release capability binaries, and a legacy-enclaves variant checks out a prior release and runs its enclave app locally as a fake enclave to exercise protocol compatibility.
The enclave build process is publicly verifiable so users can trust what runs inside the enclave. Cutting a release branch produces a GitHub Actions-generated Docker image (example). Because the image is built by GitHub from transparent source, it can be used to create reproducible Enclave Image Files.
# Create measurements — produces [ENCLAVE_NAME].eif.measurements.json
./enclave/nitro/build-or-verify-enclave.sh --docker-uri [DOCKER_IMAGE] --output-file [EIF_NAME]
# Verify measurements
./enclave/nitro/build-or-verify-enclave.sh --docker-uri [DOCKER_IMAGE] --output-file [EIF_NAME] --measurements-file [MEASUREMENTS_FILE]This repository provides reusable components for spinning up local enclaves in E2E tests, designed to be consumed from any Go repository.
A composite action at .github/actions/setup-nitro-enclave prepares a self-hosted Nitro runner with all prerequisites (nitro-cli, allocator, hugepages, wireguard-tools, lsof, socat). Reference it from your workflow:
steps:
- uses: actions/checkout@v4
with:
repository: smartcontractkit/chainlink-confidential-compute
path: chainlink-confidential-compute
# pin to a release tag or commit SHA
ref: main
- name: Setup Nitro Enclave Environment
uses: ./chainlink-confidential-compute/.github/actions/setup-nitro-enclave
with:
total-cpu-count: "4" # optional, default "4"
total-memory-mib: "2048" # optional, default "2048"
hugepages: "1024" # optional, default "1024"After this step, nitro-cli, wg, lsof, and socat are available and the allocator is running. The action targets Amazon Linux (dnf), matching the self-hosted Nitro runners.
Import tests/testhelpers to programmatically launch local enclaves. It is a standalone module whose dependency set is just the root module plus testify — no go-ethereum, no chainlink:
import (
"github.com/smartcontractkit/chainlink-confidential-compute/tests/testhelpers"
)
func TestMyFeature(t *testing.T) {
// repoRoot is the absolute path to where you checked out this repository.
repoRoot := os.Getenv("CONFIDENTIAL_COMPUTE_ROOT")
cfg := testhelpers.DefaultLocalEnclaveSetupConfig(repoRoot, "confidential-http")
// Optionally override:
// cfg.EnclaveCount = 1
// cfg.BinaryPath = "/path/to/prebuilt/binary"
// cfg.Region = "us-west-2"
result := testhelpers.SetupLocalEnclaves(t, cfg)
defer result.CleanupAll()
// result.Enclaves – []types.Enclave ready for the enclave client pool
// result.ConfigURLs – config-plane URLs for pushing EnclaveConfig
}In your go.mod, add replace directives pointing at your local checkout:
require github.com/smartcontractkit/chainlink-confidential-compute/tests/testhelpers v0.0.0
replace github.com/smartcontractkit/chainlink-confidential-compute/tests/testhelpers => ./path/to/chainlink-confidential-compute/tests/testhelpers
replace github.com/smartcontractkit/chainlink-confidential-compute => ./path/to/chainlink-confidential-compute
| Symbol | Description |
|---|---|
LocalEnclaveSetupConfig |
Configuration struct (repo root, app name, ports, CIDs, enclave type, region, extra env) |
DefaultLocalEnclaveSetupConfig(repoRoot, appName) |
Config with sensible defaults (2 enclaves, CID 16+, ports 8080+/8082+) |
SetupLocalEnclaves(t, config) |
Provisions local enclaves, returns *LocalEnclaveResult |
ParseRemoteEnclaves(config) |
Parses pre-deployed enclave URLs + PCR JSON into []types.Enclave |
MustSetupEnclave(t, rootDir, cid, httpPort, configPort, app, name, isFirst) |
Low-level: starts a single enclave |
MustSetupEnclaveWithEnv(...) |
MustSetupEnclave with extra environment entries for the build script |
EnsureEnclaveAndGetMeasurements(cid) |
Retrieves PCR measurements from a running enclave |
UseFakeEnclave() |
Reports whether fake enclaves are selected (via ENCLAVE_TYPE, or no nitro-cli) |
KillProcessOnPort(t, port) |
Utility: kills any process listening on a port |
DetectHostIP() |
Returns the host IP reachable from Docker containers |
- Nitro instance: For real enclaves, the host must be an EC2 instance with Nitro Enclaves enabled. Without
nitro-clionPATH, the helpers fall back to fake enclaves (seeUseFakeEnclave). - Allocator configured: Use the
setup-nitro-enclaveaction above, or configure manually. - Capability binary: Either build the binary into
tests/e2e/binaries/<app>before callingSetupLocalEnclaves, or setconfig.BinaryPathto a pre-built binary. - Repository checkout: This repo must be checked out — the build script, Dockerfile, and host binary are resolved relative to
config.RepoRoot.
name: E2E Tests
on: [pull_request]
jobs:
e2e:
runs-on: [self-hosted, Linux, X64] # must be a Nitro-capable instance
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: smartcontractkit/chainlink-confidential-compute
path: chainlink-confidential-compute
ref: main # or pin to a release tag
- name: Setup Nitro Enclave Environment
uses: ./chainlink-confidential-compute/.github/actions/setup-nitro-enclave
- uses: actions/setup-go@v5
with:
go-version: '1.26'
- name: Run E2E tests
env:
CONFIDENTIAL_COMPUTE_ROOT: ${{ github.workspace }}/chainlink-confidential-compute
run: go test -v ./e2e/... -timeout 90m