How your contract requests computation
The chain you deploy to, for example Arbitrum One, cannot run FHE computation natively. CoFHE runs it offchain instead. Your contract records which operations to perform; it does not perform them. This is symbolic execution: the contract builds a graph of pending operations, and the FHE Engine evaluates it asynchronously.How the request reaches the FHE Engine
Through events. EveryFHE.sol function that needs FHE computation calls TaskManager.createTask, and the TaskManager emits a TaskCreated event. CoFHE watches for that event and routes the work to the FHE Engine.
TaskCreated event describing a subtraction over two operands. The FHE Engine computes the ciphertext later, and your transaction continues without waiting.
More examples
Creating a trivially encrypted value:42”. See trivial encryption for when this is safe to use.
Adding encrypted values:
amount and balance”.
How does CoFHE connect
amount and balance to the underlying encrypted data? That is what a handle is for.How encrypted values are represented
A real FHE ciphertext is far too large to store onchain or emit in an event. So your contract never holds one. It holds a handle: a 32-byte reference to a ciphertext that CoFHE stores offchain. Every encrypted type inFHE.sol is a bytes32 handle:
external* input types (externalEuint32 and friends) are bytes32 too. The handle width does not depend on the encrypted type: an ebool handle and an euint128 handle are both 32 bytes.
So when your contract evaluates this:
FHE.sol submits a task meaning “compare the ciphertexts behind 0xab12... and 0xcd34...”. The handle of the result lands in isBigger, typed ebool.
Working with a raw handle
Solidity user-defined value types do not convert implicitly, soFHE.sol gives you explicit accessors:
isInitialized is the check you want before using a handle read from storage. An unwritten storage slot yields bytes32(0), which is not a valid handle.
What the 32 bytes contain
A handle is not an opaque counter. The TaskManager derives it from the operation itself and packs two metadata fields into the low bytes:
The type codes in byte 30 are:
Because the type and security zone travel inside the handle, the TaskManager validates an operation without a storage lookup. It reads both fields off the operand handles and reverts on a mismatch, for example
InvalidInputForFunction when you pass an eaddress to an arithmetic operation.
Two ABI types describe the same 32 bytes. Contract functions that expose an encrypted type encode it as
bytes32. The ITaskManager functions, the InEuintNN input structs, and the TaskManager events (TaskCreated, DecryptionResult, InputVerified) all use uint256 ctHash. If you are writing an offchain decoder, decode against the signature you are actually calling, and cast between the two as needed.How handles are known before the result exists
Computation is asynchronous, so how does the contract get a handle immediately?
Computation is asynchronous, so how does the contract get a handle immediately?
The handle does not depend on the ciphertext’s value. It is derived from the operation that will produce it, so the TaskManager can compute it synchronously and hand it back in the same call.The handle of
TaskManager.calcPlaceholderKey hashes the operand handles together with the operation’s function ID, then overwrites the low two bytes with the result type and security zone. Nothing in that preimage requires the answer.Example:num derives from “trivially encrypt 31 as euint64”. The handle of meaning derives from “add these two operand handles”. The FHE Engine fills in the ciphertexts afterwards.Key concepts
Event-driven communication
FHE operations call
TaskManager.createTask, which emits a task event for the offchain FHE Engine. The contract records the operation instead of running it.32-byte handles
Every encrypted type is a
bytes32 handle referencing a ciphertext held offchain. The low two bytes carry the ciphertext type and security zone.Asynchronous execution
The FHE Engine computes offchain. Handles are derived from the operation, not the result, so your contract gets one in the same transaction.
Deterministic and public
The same operation over the same operands always yields the same handle. Handles are not secrets; access control is enforced by the ACL.