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.
- It crosses to the DON with
usingTheDons()and readsrewall.v,rewall.enc,rewall.bloband its own wrap,rewall.key.<fingerprint>, in one batchedresolvecall toUniversalResolverV2. 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. - Back inside the enclave, it fetches its X25519 secret scalar with
runtime.getSecret({ id: "ENCLAVE_SCALAR" }). - 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. - It returns a SHA-256 digest of the plaintext. The plaintext itself never crosses the boundary.
The run prints
opened=truewhen the digest matches theexpectedDigestinconfig.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_callthroughUniversalResolverV2. - The cryptography, verified byte for byte against libsodium and WebCrypto before it went near the
runtime.
check-batched-read.mjsalso checks the batched read against the SDK’s per key reads.
Not real:
- The enclave.
cre workflow simulateruns 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.
handlerInTeedoes not exist below it. - Bun, which the CRE toolchain compiles with.
enclave-grantee/is the one folder in the repo managed withbunrather thanpnpm. - 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.tsprovision.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 0A 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 revokeThis 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.