Building with the Interfold
The Interfold smart contract acts as the central coordinator for all E3 operations. It manages computation requests, input validation, Ciphernode Committees (CiCos), and result publication while maintaining the security and privacy guarantees of the protocol.
The Interfold Smart Contract
Core Responsibilities
- Manage E3 computation requests
- Coordinate Ciphernode Committees
- Handle encrypted input submission
- Manage Merkle trees for input verification
- Publish computation results
- Emit events for off-chain services
Key State Variables
contract Enclave {
// Address of the Ciphernode registry.
ICiphernodeRegistry public ciphernodeRegistry;
// Address of the bonding registry.
IBondingRegistry public bondingRegistry;
// Address of the slashing manager.
ISlashingManager public slashingManager;
// Address of the refund manager.
IE3RefundManager public e3RefundManager;
// Fee token used for E3 payments.
IERC20 public feeToken;
// Mapping of allowed E3 Programs.
mapping(IE3Program e3Program => bool allowed) public e3Programs;
// Mapping of E3s.
mapping(uint256 e3Id => E3 e3) public e3s;
// Mapping of enabled encryption schemes.
mapping(bytes32 encryptionSchemeId => IDecryptionVerifier decryptionVerifier)
public decryptionVerifiers;
// Mapping of E3 payment amounts.
mapping(uint256 e3Id => uint256 amount) public e3Payments;
// Maximum allowed duration for an E3's input window.
uint256 public maxDuration;
// Auto-incrementing E3 identifier.
uint256 public nexte3Id;
}E3 Struct
Each computation is tracked by an E3 struct:
struct E3 {
uint256 seed; // Random seed for committee selection
uint32[2] threshold; // [M, N] - M required out of N members
uint256 requestBlock; // Block when E3 was requested
uint256[2] inputWindow; // [start, end] timestamps for input acceptance
bytes32 encryptionSchemeId; // Encryption scheme identifier
IE3Program e3Program; // E3 Program contract address
bytes e3ProgramParams; // ABI-encoded program params
bytes customParams; // Arbitrary application-defined params
IDecryptionVerifier decryptionVerifier; // Verifier for decryption proofs
bytes32 committeePublicKey; // Hash of committee's shared public key
bytes32 ciphertextOutput; // Hash of encrypted output
bytes plaintextOutput; // Decrypted final result
address requester; // Entity that requested computation
}Lifecycle Stages
Each E3 progresses through these stages:
| Stage | Value | Description |
|---|---|---|
None | 0 | E3 does not exist |
Requested | 1 | E3 submitted, committee selection initiated |
CommitteeFinalized | 2 | Sortition complete, DKG started |
KeyPublished | 3 | Committee public key published, inputs accepted |
CiphertextReady | 4 | Encrypted output published, awaiting decryption |
Complete | 5 | Plaintext output published, rewards distributed |
Failed | 6 | Timeout or fault detected, refund initiated |
View Functions
The Enclave contract exposes these read-only functions for querying E3 state:
// Fetch the complete E3 struct (reverts if e3Id doesn't exist)
function getE3(uint256 e3Id) external view returns (E3 memory e3);
// Get current lifecycle stage
function getE3Stage(uint256 e3Id) external view returns (E3Stage stage);
// Estimate the fee for an E3 request
function getE3Quote(E3RequestParams calldata params) external view returns (uint256 fee);
// Get the decryption verifier for an encryption scheme
function getDecryptionVerifier(
bytes32 encryptionSchemeId
) external view returns (IDecryptionVerifier);
// Get failure reason (if E3 has failed)
function getFailureReason(uint256 e3Id) external view returns (FailureReason reason);
// Get the address that requested the E3
function getRequester(uint256 e3Id) external view returns (address requester);
// Get timeline deadlines (dkgDeadline, computeDeadline, decryptionDeadline)
function getDeadlines(uint256 e3Id) external view returns (E3Deadlines memory deadlines);
// Get global timeout configuration
function getTimeoutConfig() external view returns (E3TimeoutConfig memory config);Requesting Computation
Request Flow
-
Users submit
E3RequestParamscontaining thresholds, timing windows, program references, and optional custom params.function request( E3RequestParams calldata requestParams ) external returns (uint256 e3Id, E3 memory e3); -
Contract validates the struct, estimates the fee, and stores an
E3record seeded with a random value derived from block entropy. -
The E3 program's
validatehook returns the encryption scheme ID, which is persisted on theE3struct. -
Committee selection is delegated to the
ciphernodeRegistryviarequestCommittee. -
E3Requested(e3Id, requester, e3ProgramAddress)is emitted for off-chain watchers.
Committee and Ciphernode Management
Committee Selection
- An E3 request triggers a deterministic sortition process to select a Ciphernode Committee (CiCo) from the global pool of available Ciphernodes.
- Selected nodes coordinate to produce a shared public key with the requested threshold.
- The public key is published to the Ciphernode Registry smart contract.
- For bonding, ticket economics, CLI commands, and operational responsibilities, see the Ciphernode Operators guide.
E3 Activation
After the committee is selected and finalized via sortition, the selected Ciphernodes perform a
Distributed Key Generation (DKG) to produce a shared public key. When the aggregated public key is
published, the CiphernodeRegistry calls onCommitteePublished() on the Interfold contract, which
transitions the E3 to the KeyPublished stage.
Once the E3 reaches the KeyPublished stage and the input window opens
(block.timestamp >= inputWindow[0]), Data Providers can submit inputs to the computation.
Input Publication
Inputs are published to Program contracts' publishInput() function.
function publishInput(
uint256 e3Id,
bytes memory data
) externalInputs can be accumulated incrementally into a Merkle tree (LazyIMT, or ultimately chosen by the program implementation). This allows you to:
- Use the published Merkle root as part of your E3 Program's Secure Process to ensure all published inputs are processed by the Compute Provider.
- Prevent input tampering or omission.
- Anchor proofs for correct execution.
Merkle Tree Construction
The Interfold uses a Lean Merkle Tree implementation. Each input is hashed using the PoseidonT3 Hash function, and the root is updated with each new input.
Your E3 program should:
- Reconstruct the Merkle tree from the given inputs
- Verify the resulting root matches on-chain root
Input Validation
As much as possible, you should aim to validate inputs via proofs generated by Data Providers, rather than in your Secure Process. This pushes computation to the edges and allows you to reduce the complexity of your FHE computation.
Your E3 Program must include logic that validates user inputs. For simplicity, this can be included
inside the publishInput function of the E3 Program contract.
At a minimum, this logic should validate a proof that the given ciphertext is a valid encryption to the E3's public key. It is also recommended to bundle in proofs to validate:
- The legitimacy of the Data Provider (e.g., ensuring they are listed in a registry of approved data providers).
- Range checks, checksums, and/or other well-formedness criteria for the plaintext that was encrypted.
Result Publication and Events
Key Events
Interfold Contract Events:
event E3Requested(uint256 e3Id, E3 e3, IE3Program indexed e3Program);
event E3StageChanged(uint256 indexed e3Id, E3Stage previousStage, E3Stage newStage);
event CiphertextOutputPublished(uint256 indexed e3Id, bytes ciphertextOutput);
event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput);
event E3Failed(uint256 indexed e3Id, E3Stage failedAtStage, FailureReason reason);
event RewardsDistributed(uint256 indexed e3Id, address[] nodes, uint256[] amounts);Program Contract Events:
event InputPublished(uint256 indexed e3Id, bytes data, uint256 inputHash, uint256 index);Result Publication Flow
- The Compute Provider submits a proof and ciphertext output.
- The Interfold uses the E3 Program contract to verify the proof and emits
CiphertextOutputPublished. - Ciphernodes decrypt the ciphertext output.
- The plaintext result is then published and the Decryption Verifier validates the decryption proof.
- Rewards are distributed to active committee members.
- Events are emitted for off-chain services.
Example Usage
Submitting a Request
const enclaveContract = new ethers.Contract(enclaveAddress, enclaveAbi, signer)
const requestParams = {
threshold: [3, 5],
inputWindow: [inputWindowStart, inputWindowEnd],
e3Program: e3ProgramAddress,
e3ProgramParams,
computeProviderParams,
customParams: '0x',
}
const tx = await enclaveContract.request(requestParams)
const receipt = await tx.wait()
const e3Id = receipt.logs
.map((log) => enclaveContract.interface.parseLog(log))
.find((parsed) => parsed?.name === 'E3Requested')?.args?.e3IdMonitoring Results
enclaveContract.on('PlaintextOutputPublished', (e3Id, plaintext) => {
console.log(`Computation ${e3Id} completed with result:`, plaintext)
})Submitting Inputs
You can submit inputs by calling the publishInput function on your E3 Program contract:
const tx = await programContract.publishInput(e3Id, encryptedInput)
await tx.wait()Security Considerations
- Committee Selection
The size and threshold of the Ciphernode Committee you select for your computation directly impacts both the cost and economic security of your E3. A higher threshold means more economic security (a higher cost to compromise the E3), but incurs a higher protocol fee.
- Input Validation
Your Program contract is critical to ensuring that inputs come from approved parties, are sanitized for your computation, and truthfully correspond to any specific sources of truth. You can use your Program contract to push computation to the edges, reducing the complexity of your FHE computation.
- Result Verification
The Interfold protocol will accept and decrypt whatever output is reported by your Compute Provider. In other words, your E3s inherit the trust assumptions of your Compute Provider. An incorrectly implemented Secure Process in your E3 Program or a dishonest or compromised Compute Provider could leak information.
- Event Handling
If you are consuming Interfold outputs offchain, allow for sufficient block confirmations to be confident in the finality of the outcome and to appropriately handle re-orgs.
Best Practices
See the dedicated Best Practices guide for updated operational playbooks covering parameter selection, validator design, compute provider trust, and observability.