Enclave is now The Interfold.
Build
E3 Program Anatomy
Complete Example

Complete Example

This page follows one E3 through the default template. The E3 adds two encrypted numbers. Each step names the file that does the work, the contract function it calls, and the event that marks the result. It is for developers who want to see how the parts of an E3 program connect before they change one. At the end, you know where to look when a step fails.

Before you start

Make a project with interfold init and complete its setup. See Quick Start. Template paths on this page are relative to the project root. Paths that start with crates/ or packages/ are in the Interfold repository.

The local scripts run in dev mode (E3_PROGRAM__DEV=true, unless TEMPLATE_REAL_PROOFS=1). The compute proof is a mock, and MockOpenVmReceiptVerifier accepts it. The rest of the flow is the same as in production.

The E3 at a glance

One E3 in the template
dev_ciphernodes.shE3ProgramRegistered

Deploy

deploy/default.ts deploys the verifiers and MyProgram, and registers the program.

Start the stack

Run pnpm dev:all in the project root. scripts/dev_all.sh calls scripts/dev_all_concurrently.sh, which starts six processes:

ProcessCommandWhat it does
FRONTENDpnpm dev:frontendServes the client at http://localhost:3000
EVManvilLocal chain on port 8545, chain ID 31337, one block each second
CHAINnode ./scripts/anvil-automine.mjsMoves the chain time forward and fulfills local randomness requests
CIPHERpnpm dev:ciphernodesDeploys the contracts and starts five ciphernodes
SERVERTEST_MODE=1 pnpm dev:serverRuns the coordination server, server/index.ts, on port 8080
PROGRAMpnpm dev:programRuns interfold program start, which starts the program server on port 13151

Follow one E3

Deploy the contracts

scripts/dev_ciphernodes.sh runs scripts/deploy-local.ts. That script deploys the Interfold contracts with mocks, and then calls deployTemplate in deploy/default.ts. deployTemplate does these actions in order:

  1. Deploys MockOpenVmReceiptVerifier (with TEMPLATE_UNPROVED_TEST=1 on the local chain) or an OpenVmReceiptVerifier for the guest that interfold program compile built.
  2. Deploys OpenVmBfvCiphertextVerifier with that receipt verifier and its imageId.
  3. Calls setCiphertextVerifier for keccak256("fhe.rs:BFV").
  4. Deploys MyProgram with the Interfold address, the receipt verifier, and the imageId.
  5. Calls registerE3Program. The Interfold contract probes supportsInterface and emits E3ProgramRegistered.

deployTemplate writes the addresses to interfold.config.yaml. Then dev_ciphernodes.sh starts the ciphernodes with interfold nodes up.

Request the E3

In the client, client/src/pages/steps/RequestComputation.tsx builds the request. It uses committee size 0 (Minimum), parameter set 0, and an input window. The window opens 15 seconds after the current chain time and lasts 100 seconds. It calls getE3Quote, approveFeeToken, and requestE3 from the SDK.

On-chain, Interfold.request stores the E3 and calls MyProgram.validate. validate stores keccak256(e3ProgramParams), starts an input tree of depth 20, and returns keccak256("fhe.rs:BFV"). The Interfold contract takes the fee, asks the registry for a committee, and emits E3Requested.

Publish the committee key

The ciphernodes select a committee, run DKG, and publish the committee public key. The E3 moves to KeyPublished. The serialized key arrives in CommitteePublicKeyChunkPublished events.

server/index.ts listens for these events in setupEventListeners. It assembles the key and checks it against the commitment with validatePublicKeyCommitment. Then scheduleE3 waits until the chain time reaches inputWindow[1].

Encrypt and submit the inputs

client/src/pages/steps/EnterInputs.tsx encrypts each number with encryptNumber and computes its SAFE commitment with computeCiphertextCommitment. It calls publishInput in client/src/utils/input.ts for each input:

client/src/utils/input.ts
const data = encodeAbiParameters(parseAbiParameters('bytes, bytes32'), [
  input,
  ciphertextCommitment,
])

MyProgram.publishInput inserts the commitment as a leaf and emits InputPublished(e3Id, ciphertext, index).

Run the secure process

When the input window closes, runProgram in server/index.ts reads paramSetRegistry(e3.paramSet) from the Interfold contract. It reads all InputPublished events for the E3 from MyProgram. The server skips the E3 when it has fewer than two inputs.

callFheRunner in server/runner.ts posts the job to http://127.0.0.1:13151/run_compute. The body has the E3 ID, the proof domain, the parameters, the inputs, and the callback URL http://127.0.0.1:8080.

The dev runner (crates/program/runner/src/dev.rs) builds a ComputeManager with your fhe_processor from program/src/lib.rs. start(policy()) builds the input tree, runs the processor, and computes the output hash, commitment, parameter hash, and input root. The runner encodes a mock proof with the parameter hash and the input root. With real proofs, the OpenVM service runs the same secure process natively, proves it in guest/, and returns the verified OpenVM envelope instead.

Publish the output

The program server posts the webhook to the server. handleWebhookRequest in server/index.ts stores the ciphertext under its Keccak-256 hash in .interfold/data-availability. Then it calls publishCiphertextOutput through the SDK.

Interfold.publishCiphertextOutput does these checks in order:

  1. The E3 is in KeyPublished, the input window is closed, and the compute deadline has not passed. The output is not published yet, and the committee still meets its threshold.
  2. MyProgram.verifyDataAvailability checks that the Keccak-256 hash of the raw ciphertext equals the content hash.
  3. OpenVmBfvCiphertextVerifier.verify rebuilds the journal and calls the receipt verifier.
  4. MyProgram.verify compares the parameter hash and the input root, rebuilds the journal, and calls the receipt verifier.

The E3 moves to CiphertextReady. The Interfold contract emits CiphertextOutputReferencePublished and E3StageChanged.

Decrypt the result

The ciphernodes fetch the output bytes from the server at /availability/objects/<contentHash>. This is the mock_http data-availability mode in interfold.config.yaml. The committee makes decryption shares. The aggregator combines T + 1 of them and calls publishPlaintextOutput. The E3 moves to Complete, and the Interfold contract emits PlaintextOutputPublished.

client/src/pages/steps/EncryptSubmit.tsx listens for PlaintextOutputPublished. It decodes the output with decodePlaintextOutput and shows the sum.

The Interfold contract emits CiphertextOutputReferencePublished when it accepts the output. The SDK event type CIPHERTEXT_OUTPUT_PUBLISHED listens for CiphertextOutputPublished, which Interfold.sol declares but does not emit.

Where to change the example

ChangeFiles
The computationprogram/src/lib.rs (fhe_processor)
The input leaf or the selectionprogram/src/lib.rs (policy) and contracts/MyProgram.sol (publishInput), together
Input validationcontracts/MyProgram.sol (publishInput)
The request parametersclient/src/pages/steps/RequestComputation.tsx
How inputs reach the compute providerserver/index.ts (runProgram) and server/runner.ts
How the result is shownclient/src/pages/steps/EncryptSubmit.tsx and client/src/pages/steps/Results.tsx

Compare with CRISP

CRISP uses the same parts with production choices:

StepTemplateCRISP
Input submissionOne publishInput call with ciphertext and commitmentpublishInput with a Noir proof, then finalizeInput with a DA receipt
Input leafThe SAFE commitmentsha256(keccak256(ciphertext) ‖ commitment ‖ slot ‖ parent) mod the BN254 field
Input policyInputPolicy::default()policy::crisp(), one input for each slot
Compute providerDev runner with a mock proofCRISP's OpenVM service and guest, proved on the operator's GPU or CPU
Data availabilityLocal file store and a hash checkAvailVectorXDataAvailabilityVerifier (a mock on a local chain)
Output publisherserver/index.tsserver/ (opens in a new tab) in the CRISP repository (/state/add-result webhook)

Get help

Gnosis Guild (opens in a new tab) is the initial development team of the Interfold. Contact Gnosis Guild with questions about this example.

Next steps