CRE Workflow

The Chainlink CRE (Compute Runtime Environment) workflow is the autonomous security analysis engine at the heart of ENShell. It runs as compiled WASM on Chainlink oracle nodes, decrypts instructions that are invisible on-chain, analyzes them with Claude via Confidential HTTP, and writes DON-attested verdicts back to the smart contract.

For judges: This section explains the simulation vs. production distinction, which is critical for understanding the current deployment model.

Pipeline Overview

The workflow executes an 8-step pipeline for every ActionSubmitted event:

1. TRIGGER    →  ActionSubmitted event on AgentFirewall
2. FETCH      →  GET /relay/{instructionHash} from relay
3. DECRYPT    →  ECDH + AES-256-GCM using oracle private key
4. ANALYZE    →  Claude via Confidential HTTP
5. CHECK      →  Read contract to verify action isn't already resolved
6. POST       →  Store analysis on relay for dashboard
7. SIGN       →  DON consensus on the report
8. BROADCAST  →  writeReport → KeystoneForwarder → onReport()

Step 1: Trigger

The workflow watches for ActionSubmitted events on the AgentFirewall contract:

ActionSubmitted(
    uint256 indexed actionId,
    string indexed agentId,
    address target,
    uint256 value,
    bytes32 instructionHash
)

The event contains only the instructionHash -- a keccak256 hash. The actual instruction is encrypted and stored off-chain on the relay.

Step 2: Fetch Encrypted Payload

The workflow makes an HTTP GET to {relayUrl}/relay/{instructionHash} to retrieve the encrypted instruction payload. The payload is a hex-encoded blob containing the ECIES-encrypted instruction.

Step 3: Decrypt

Inside the CRE runtime, the workflow decrypts the instruction using the oracle's private key:

  1. Parse the packed format: ephemeralPubKey (33 bytes) || nonce (12 bytes) || ciphertext
  2. ECDH key agreement: sharedSecret = ECDH(oraclePrivateKey, ephemeralPubKey)
  3. Key derivation: aesKey = SHA-256(sharedSecret)
  4. Decrypt: plaintext = AES-256-GCM(aesKey, nonce, ciphertext)

The oracle's private key is stored in Chainlink's Vault DON -- threshold-encrypted across multiple oracle nodes. No single operator has access to the full key. In simulation mode, the key is read from a local .env file.

See Encryption for the full ECIES deep dive.

Step 4: Claude Analysis

The decrypted instruction is sent to Claude (claude-sonnet-4-20250514) via the CRE SDK's Confidential HTTP client. This is distinct from a regular HTTP call:

  • The Anthropic API key is stored in the Vault DON and injected at runtime via Confidential HTTP
  • The key never appears in plaintext outside the Confidential HTTP channel
  • The request template uses '{{.ANTHROPIC_API_KEY}}' for server-side secret injection

Prompt Design

The system prompt defines a security analyzer that outputs structured JSON:

{
  "score": 5000,
  "decision": 1,
  "reasoning": "Standard swap on well-known Uniswap V2 Router..."
}

Scoring Calibration

Score Range Decision Meaning
0 - 29,999 1 (APPROVE) Routine DeFi operations, known safe contracts
30,000 - 69,999 2 (ESCALATE) Multiple red flags, unknown targets, suspicious patterns
70,000 - 100,000 3 (BLOCK) Obvious attacks: prompt injection, burn addresses, zero address

The prompt includes a whitelist of well-known contract addresses (Uniswap, WETH, DAI, USDC, UNI) that Claude recognizes as safe targets. It also includes calibration rules to prevent over-escalation of routine operations.

Failure Handling

If Claude's API returns an error or the response can't be parsed as JSON, the workflow defaults to:

{ "score": 50000, "decision": 2 }

This ensures the system never silently approves on failure -- it escalates to a human instead.

Step 5: Duplicate Check

Before writing the report, the workflow reads the queued action from the contract to check if decision !== 0 (already resolved). This prevents double-resolution if the CRE processes a stale event.

Step 6: Post Analysis to Relay

The analysis result (score, decision, reasoning, instruction, target, value) is POSTed to {relayUrl}/analysis/{actionId}. This makes the analysis available for:

  • The CLI's escalation display
  • The ENShell website's Live Threat Feed
  • Dashboard visualization

Step 7: DON Consensus

The report is prepared and signed via the CRE SDK's consensus mechanism:

encodedReport = ABI.encode(agentId, actionId, decision, threatScore)
signedReport = runtime.report(prepareReportRequest(encodedReport))

In production, multiple oracle nodes must agree on the report content before it can be submitted on-chain. In simulation, this is run on a single node.

Step 8: On-Chain Broadcast

The signed report is submitted to the AgentFirewall contract's onReport() function via the KeystoneForwarder:

evmClient.writeReport(runtime, {
  receiver: firewallContractAddress,
  report: signedReport,
  gasConfig: { gasLimit: '200000' },
})

The KeystoneForwarder at 0x15fC6ae953E024d975e77382eEeC56A9101f9F88 is the real Chainlink contract on Sepolia. It verifies the DON signature and routes the report to onReport().


Simulation vs. Production

This is the key distinction that judges need to understand.

Current State: Simulation with Real Broadcast

The CRE workflow is triggered manually via simulation:

cd ~/www/enshell-cre-workflow
./simulate.sh <TX_HASH>

This runs:

cre workflow simulate firewall-analyzer \
  --target staging-settings \
  --evm-tx-hash <TX_HASH> \
  --evm-event-index 0 \
  --trigger-index 0 \
  --non-interactive \
  --skip-type-checks \
  --broadcast

The --broadcast flag is critical. Here's what it means:

Aspect Without --broadcast With --broadcast
Encryption Real ECIES decryption Real ECIES decryption
Claude analysis Real API call Real API call
On-chain write Dry run (skipped) Real transaction on Sepolia
Gas cost Zero CRE signer pays ~200k gas

With --broadcast, the simulation produces a real on-chain transaction. The report goes through the actual Chainlink KeystoneForwarder on Sepolia. The contract's onReport() function receives the report, resolves the action, and updates the threat score.

What's Manual vs. What's Real

Component Manual or Real?
Trigger (event detection) Manual (replayed via --evm-tx-hash)
Fetch from relay Real HTTP request
ECIES decryption Real cryptographic operation
Claude analysis Real API call (real API key, real model)
DON consensus Simulated locally (single node)
KeystoneForwarder Real Chainlink contract
onReport() call Real on-chain transaction
Threat score update Real on-chain state change
ENS text record update Real ENS write

Production Mode

When Chainlink ships automated event triggers for CRE, the workflow would transition to:

cre workflow deploy firewall-analyzer --target production-settings
cre workflow activate firewall-analyzer

In production:

  • Events are detected automatically (no manual --evm-tx-hash)
  • Secrets come from the Vault DON (not local .env)
  • WASM binary runs on oracle nodes (not local machine)
  • DON consensus requires multiple node attestation
  • Everything else stays exactly the same

The workflow code, analysis logic, encryption, and on-chain integration are identical between simulation and production. The only difference is how the trigger fires and where secrets are stored.


Configuration

Workflow Config

config.staging.json / config.production.json:

{
  "chainSelectorName": "ethereum-testnet-sepolia",
  "firewallContractAddress": "0x410f4D119EF857879E42625381DB131457db78A7",
  "relayUrl": "https://relay.enshell.xyz"
}

Secrets

Two secrets are required, declared in secrets.yaml:

Secret Source (Simulation) Source (Production)
ANTHROPIC_API_KEY .env file Vault DON (threshold-encrypted)
ORACLE_PRIVATE_KEY .env file Vault DON (threshold-encrypted)

CRE Signer

The CRE signer (0x0D53Ff5e9AD522B94691e20a62B5D0947a15ade8) is funded with Sepolia ETH to pay gas for writeReport transactions. This is the wallet that submits the on-chain broadcast in simulation mode.


Running the Demo

The demo loop in the contract repository continuously generates actions and triggers CRE analysis:

cd ~/www/enshell-contract
MIN_DELAY_S=10 MAX_DELAY_S=30 npx hardhat run demo/demo-loop.ts --network sepolia

This script:

  1. Randomly selects agents from a pool of 60 pre-registered demo agents
  2. Picks from safe (70%), suspicious (20%), or malicious (10%) prompt pools
  3. Calls protect() via the SDK (encryption + relay + on-chain submission)
  4. Triggers ./simulate.sh for each action
  5. Performs trust checks between random agent pairs (15% of iterations)