Skip to Content
ComponentsChainlink CRE

Chainlink CRE

CRE is the Chainlink Runtime Environment. A workflow is a small program that Chainlink’s network of nodes runs on a schedule or on an event. A Confidential Workflow runs its handler inside a TEE, a trusted execution environment. Here that is an AWS Nitro enclave, a sealed part of a server that even its operator cannot look into.

The cre/ folder holds one such workflow. It is granted a Rewall secret like any other participant, opens it inside the enclave, and returns only a digest of what it found.

This runs on Sepolia, the Ethereum test network. Real credentials do not belong in it.

Why an enclave grantee exists

Every Rewall read needs the identity key. That key comes from a wallet signature. This is fine while a human is at the keyboard. It is not fine for anything unattended. The only way to run a nightly job today is to leave an identity key on the machine. Read access in Rewall can never be revoked, so every key placed on a machine is a permanent liability.

An enclave closes that gap. It gets its own ENS name and publishes rewall.pubkey like any participant. The owner grants it a secret with an ordinary grant. Revoking it is an ordinary rotation. The key lives in Chainlink’s Vault DON. A DON is a decentralized oracle network, a group of independent Chainlink nodes. The Vault DON is the part that holds workflow secrets. The key appears only inside an attested enclave, for the length of one handler run.

Nothing in the SDK knows an enclave exists. That is the claim worth checking.

What the workflow does

enclave-grantee/workflow.ts registers one handlerInTee on a cron trigger. Each run does four things.

  1. It crosses to the DON with usingTheDons() and reads rewall.v, rewall.enc, rewall.blob and its own wrap, rewall.key.<fingerprint>, in one batched resolve call to UniversalResolverV2. Chain reads never execute inside a TEE. Every record read is public ciphertext, so the crossing discloses nothing a chain watcher does not already have.
  2. Back inside the enclave, it fetches its X25519 secret scalar with runtime.getSecret({ id: "ENCLAVE_SCALAR" }).
  3. It opens the libsodium sealed box, checks the key commitment, decrypts the blob with AES-256-GCM using namehash(secretName) as additional data, and unpads strictly. This is the same read path the SDK runs.
  4. It returns a SHA-256 digest of the plaintext. The plaintext itself never crosses the boundary. The run prints opened=true when the digest matches the expectedDigest in config.json.

The QuickJS runtime that runs the handler has no WebCrypto and no libsodium. So step 3 is rebuilt from @noble primitives. It also has no atob, so base64 goes through Buffer.

What is real and what is not

Real:

  • The secret, its records and the grant, all on ENSv2 Sepolia.
  • The enclave’s ENS name, enclave.rewall-test-2.eth, with a generated key that is not derived from a wallet.
  • The read, which is a live eth_call through UniversalResolverV2.
  • The cryptography, verified byte for byte against libsodium and WebCrypto before it went near the runtime. check-batched-read.mjs also checks the batched read against the SDK’s per key reads.

Not real:

  • The enclave. cre workflow simulate runs the handler on your own machine and says so on every run. Deploying to a Nitro enclave needs private beta enrollment, which gates deployment only.

Requirements

  • The CRE CLI at v1.29.0 or newer. handlerInTee does not exist below it.
  • Bun, which the CRE toolchain compiles with. enclave-grantee/ is the one folder in the repo managed with bun rather than pnpm.
  • A funded Sepolia wallet in tools/.env, for the two provisioning scripts.

On Windows, install the CLI inside WSL and run every cre command there. cre update downloads the new binary and then refuses to replace itself. cre init ignores --non-interactive and opens a full screen prompt that never returns.

Setup

Run these from the cre/ folder, with sdk/ already built. pnpm install links the built SDK. .env must exist before provision.ts runs, because the script rewrites that file in place.

pnpm install cp .env.example .env cd enclave-grantee && bun install && cd .. node --env-file=../tools/.env --experimental-strip-types provision.ts node --env-file=../tools/.env --experimental-strip-types grant.ts

provision.ts registers enclave.rewall-test-2.eth as a subname of rewall-test-2.eth. Its key comes from a throwaway signer that is discarded at once. The parent writes rewall.pubkey for it, because the enclave holds no wallet. The scalar goes straight into cre/.env as SECRET_ENCLAVE_SCALAR without being printed, and secrets.yaml maps it to the ENCLAVE_SCALAR id the workflow asks for.

grant.ts stores enclave-spike.rewall.rewall-test-1.eth, grants the enclave, and names rewall-test-3.eth for recovery. Then it writes enclave-grantee/config.json, which says which secret to open and what digest to expect. Nothing in it is enclave specific.

Running

cre workflow simulate enclave-grantee --target staging-settings --non-interactive --trigger-index 0

A cold run takes about ninety seconds, nearly all of it compilation. Build once and reuse the binary to get that to three seconds.

cre workflow build enclave-grantee -o enclave-grantee.wasm cre workflow simulate enclave-grantee --target staging-settings --non-interactive \ --trigger-index 0 --wasm $PWD/enclave-grantee.wasm

--wasm needs an absolute path. Rebuild after editing the workflow.

Watching a revocation land

node --env-file=../tools/.env --experimental-strip-types access.ts revoke

This is a full rotation. The wrap is cleared, the grantee list empties and the approved keys drop the enclave. Simulate again and the same binary refuses with no wrap at rewall.key.<fingerprint>, this enclave is not a grantee. Run access.ts grant to put it back.

Nothing about the workflow changes between those runs. The only thing that changed is a record on Ethereum saying whether this name may still read.

Caveats

The enclave’s key is generated on an operator machine and uploaded to the Vault DON. So the honest claim is that an operator plus an attested enclave holds it, not the enclave alone. Nothing published binds rewall.pubkey to a measured enclave image, and the CRE docs expose no attestation document to workflow authors. A grantor cannot check what is behind that name.

The enclave’s name is a subname of rewall-test-2.eth, so the parent can rewrite its published key. That is the exact attack rewall.auth.keys exists to stop. Binding the key at grant time is what stops it. See Share for why the owner signs the reader list.