| name | fhevm-overview |
| description | Used when an AI coding agent needs a mental model of the Zama Confidential Blockchain Protocol — FHEVM architecture, the four off-chain components (coprocessor, KMS threshold network, gateway, relayer), the on-chain contracts (ACL, FHEVMExecutor, KMSVerifier, InputVerifier), the ZKPoK-bound encrypted-input flow, and the v0.9+ async decryption pattern. Triggers on prompts mentioning FHEVM, confidential smart contracts, FHE on-chain, Zama protocol, coprocessor, KMS, relayer, gateway, input proofs, ciphertext handles, or how confidential transactions flow end-to-end. |
fhevm-overview
A structured mental model of the Zama Confidential Blockchain Protocol. Read this before writing any contract, test, or client that touches FHEVM. Other skills assume the agent knows what the pieces are and when each one runs — this skill provides that baseline.
Essential Principles
1. FHE is not ZK — and FHE hides values, NOT relationships
The host chain never sees plaintext. The off-chain coprocessor executes TFHE
operations on ciphertext handles that live in its own store. The chain carries
only opaque 32-byte handles and symbolic compute requests. There is no
"decrypt on-chain" primitive.
But that's only half of the FHE-vs-ZK distinction — and the half that matters
for product design is the half about what privacy property each provides.
FHE hides the values inside ciphertexts. It does NOT hide:
- which addresses interact with which contracts
- which storage slots a transaction reads or writes
- which note in a pool is being spent
- the existence, ordering, and graph of transactions
- timing patterns
Set-membership privacy — mixers, shielded transfers, anonymous credentials,
"which one of N inputs am I spending" — requires ZK primitives FHEVM does not
provide. A confidential ERC-7984 transfer leaks (from, to) to anyone
watching the chain; the amount is hidden, the relationship is not. A
sealed-bid auction can hide the winning bid amount via FHE, but the list of
bidder addresses is structurally on-chain. Product designs that assume
otherwise will ship a privacy bug, not a privacy feature.
If your product needs to hide who is interacting (not just how much they're
moving), FHEVM is the wrong primitive. Look at zk-rollups (Aztec, Railgun) or
zkSNARK-based mixers (Tornado-style protocols) for those use cases. FHEVM gives
you confidential amounts and confidential predicates; it does not give
you anonymity sets or transaction-graph privacy.
2. Handles are permissioned, not values
Every encrypted value is addressed by a bytes32 handle. Reading or reusing a handle requires an ACL grant. New handles produced by FHE.* operations inherit no permissions from their inputs — the contract must call FHE.allowThis(newHandle) and, if a user needs to decrypt, FHE.allow(newHandle, user). Forgetting this is the single most common production bug.
3. Input proofs bind ciphertext to (contract, user)
Clients encrypt inputs under a chain-specific public key and attach a ZKPoK that proves the ciphertext was produced correctly and is bound to (contract_address, user_address). The on-chain InputVerifier validates the proof before FHE.fromExternal(handle, proof) hands the contract a usable euint*. A proof bound to the wrong contract is rejected on-chain — this is not a validation you can skip.
4. Decryption is async and multi-step (v0.9+ pattern)
There is no synchronous decrypt(). The flow is:
- On-chain:
FHE.makePubliclyDecryptable(handle) (public) or FHE.allow(handle, user) (user-scoped) grants the KMS permission to reveal.
- Off-chain: the client calls
publicDecrypt([handles]) or userDecrypt([{ handle, contract }]) via @zama-fhe/sdk (v3, provider-agnostic — works with viem, ethers, or any EVM library). The KMS threshold network produces a signed cleartext payload.
- On-chain (public only):
FHE.checkSignatures(handles, encoded, signatures) verifies the KMS threshold signatures before the revealed value is trusted by the contract.
Settlement, reveal, claim, and auction-close are always multi-transaction.
5. One transaction, three actors
An FHEVM transaction touches three independent actors beyond the user:
- The host chain (EVM) — holds ACL state, emits symbolic compute events, verifies input proofs and KMS signatures.
- The coprocessor — off-chain; listens for events, runs TFHE compute, persists result ciphertexts, returns handles.
- The KMS threshold network — off-chain; holds shares of the FHE secret key; signs reveal requests.
The relayer is the HTTPS edge that brokers client ↔ coprocessor and client ↔ KMS traffic. The gateway coordinates the off-chain services. Agents that model FHEVM as "a contract call that returns a plaintext" will write broken code every time.
Component Diagram
flowchart LR
U[Client / Browser<br/>@zama-fhe/sdk]
subgraph HC[Host Chain — EVM]
UC[User Contract<br/>inherits ZamaEthereumConfig]
ACL[ACL]
FEX[FHEVMExecutor]
IV[InputVerifier]
KV[KMSVerifier]
end
subgraph OFF[Off-chain services]
R[Relayer<br/>HTTPS edge]
G[Gateway<br/>coordinator]
CP[Coprocessor<br/>TFHE compute + ciphertext store]
KMS[KMS threshold network<br/>holds FHE key shares]
end
U -- "tx: handle + ZKPoK" --> UC
UC -- "fromExternal(h, proof)" --> IV
UC -- "FHE.add/select/..." --> FEX
FEX -- "symbolic event" --> CP
UC -- "allow / allowThis / makePubliclyDecryptable" --> ACL
U -- "publicDecrypt / userDecrypt" --> R
R --> G
G --> CP
G --> KMS
KMS -- "threshold signature" --> U
U -- "revealed value + sig" --> UC
UC -- "checkSignatures" --> KV
End-to-end flow — encrypted-input transaction
- Client encrypts. Browser or test signer encrypts input with the chain-specific FHE public key, generates a ZKPoK binding the ciphertext to
(contract_address, user_address), and submits the resulting 32-byte handle plus proof bytes in the transaction calldata.
- Contract ingests. The user's contract calls
FHE.fromExternal(externalEuint64 handle, bytes inputProof). InputVerifier validates the ZKPoK on-chain; FHEVMExecutor records the handle as belonging to the contract.
- FHE op is symbolic. When the contract runs
FHE.add(a, b), FHE.select(cond, t, f), etc., the EVM emits a symbolic event to FHEVMExecutor and returns a new handle — no compute happens on-chain.
- Coprocessor observes and executes. The off-chain coprocessor watches events, checks preconditions (handles exist, ACL grants are in place), runs the TFHE computation over real ciphertexts, and persists the result indexed by the new handle.
- Contract grants ACL on results. Derived handles carry no grants. The contract must call
FHE.allowThis(newHandle) to keep using it in future txs, and FHE.allow(newHandle, user) if a user later needs to decrypt it.
End-to-end flow — decryption (v0.9+ pattern)
Public decryption (value is world-readable):
// On-chain: authorize the reveal
FHE.makePubliclyDecryptable(resultHandle);
const { [handle]: value, signatures } = await instance.publicDecrypt([handle]);
// On-chain: verify the signature before using the cleartext
FHE.checkSignatures(handles, abi.encode(value), signatures);
User decryption (value is scoped to a single address):
// On-chain: pre-grant ACL to the user
FHE.allow(handle, user);
const { [handle]: value } = await sdk.userDecrypt([{ handle, contractAddress }]);
User decryption returns a plaintext the client holds; the chain only knows the user was authorized. Public decryption returns a plaintext + signature any party can verify.
On-chain contracts — roles at a glance
| Contract | Role |
|---|
ACL | Stores per-handle permissions (allow, allowThis, allowTransient, makePubliclyDecryptable). Every contract read of a handle checks the ACL. |
FHEVMExecutor | Emits symbolic events that the coprocessor listens to. Records which handles exist, which contract produced them, and which operation produced them. |
InputVerifier | On-chain verifier for client-side ZKPoK input proofs. Gates FHE.fromExternal. |
KMSVerifier | On-chain verifier for KMS threshold signatures returned by publicDecrypt. Gates FHE.checkSignatures. |
All four are deployed per host chain. Their addresses are wired into your contract when it inherits ZamaEthereumConfig from @fhevm/solidity/config/ZamaConfig.sol — do not hard-code them.
Off-chain services — roles at a glance
| Service | Role |
|---|
| Coprocessor | Verifies ZK input proofs server-side, runs TFHE operations, persists result ciphertexts indexed by handle. Stateful. |
| KMS threshold network | Holds shares of the FHE secret key. Signs reveals under a threshold of signers. Stateless w.r.t. ciphertexts. |
| Gateway | Coordinator between the coprocessor, the KMS, and the relayer. Handles retries and ordering. |
| Relayer | HTTPS edge clients talk to. Proxies encrypt / decrypt / reencrypt traffic between browser and coprocessor / KMS. The SDK's relayerUrl points here — it is not the JSON-RPC URL. |
When to Use
- Explaining what the coprocessor, KMS, gateway, and relayer each do, and when they run.
- Describing the lifetime of a ciphertext handle from client encryption to on-chain consumption.
- Explaining why decryption is async and why reveals are multi-transaction.
- Grounding a reviewer who asks "where does the FHE math actually happen?"
- Picking which other skill to route to (setup, contracts, testing, decryption, frontend, anti-patterns).
When NOT to Use
- Writing a specific Solidity contract →
fhevm-contracts.
- Bootstrapping a Hardhat workspace →
fhevm-setup.
- Debugging
ACLNotAllowed or TFHE.* → FHE.* migrations → fhevm-antipatterns.
- Implementing
publicDecrypt / userDecrypt flows → fhevm-decryption.
- Writing Mocha/Foundry tests →
fhevm-testing.
- Wiring React / Next.js clients →
fhevm-frontend.
When FHEVM is the wrong tool
FHEVM is great when you need confidential values with public computation
rules. It is the wrong primitive when the privacy requirement is one of:
- Anonymous senders / hidden transaction graph (mixers, shielded transfers,
privacy coins). Sender and recipient addresses appear in plaintext on the
host chain regardless of how the transferred amount is encoded. Use a
zk-rollup (Aztec, Railgun) or zkSNARK-based mixer instead.
- Set-membership without revealing which (anonymous credentials, "I am one
of these N approved users without revealing which one"). FHE has no
primitive for proving membership in a set without disclosing the witness.
Use zkSNARK / zkSTARK membership proofs.
- Hiding which storage slot was touched (oblivious access, ORAM-style
patterns). Read/write access patterns are visible to anyone reading the
chain or the coprocessor's event log.
- Hiding the existence or timing of a transaction. Tx existence, gas paid,
timing relative to other txs — all public.
If a product spec uses words like "anonymous," "unlinkable," "hidden sender,"
or "untraceable," the answer is almost never "use FHEVM." Push back at design
time, not at audit time.
Conversely, FHEVM is exactly right for: confidential balances on a known
account (ERC-7984), encrypted bid amounts in a known auction with known
participants (BlindAuction), confidential predicates over plaintext metadata
(KYC-gated transfers via canTransact), and any design where the values are
private but the participants are not.
Routing
Which question does the user's prompt match?
- How does the protocol work end-to-end? → this file.
- Which on-chain contract does X? → "On-chain contracts" table above.
- What is the relayer vs the gateway vs the KMS? → "Off-chain services" table above.
- How does an encrypted-input tx flow? → "End-to-end flow — encrypted-input transaction".
- How does decryption work and why is it async? → "End-to-end flow — decryption (v0.9+ pattern)".
- Show me a diagram. → "Component Diagram".
If the prompt is about writing, testing, or debugging code, this skill is the wrong entry point — route via When NOT to Use.
Stack versions (verified April 2026)
| Package | Version | Role |
|---|
@fhevm/solidity | 0.11.1 | FHE.* library, euint*, ebool, eaddress, ZamaEthereumConfig |
@fhevm/hardhat-plugin | 0.4.2 | Injects the mock coprocessor on the hardhat network |
@fhevm/mock-utils | 0.4.2 | EIP-712 + KMS mocking helpers for unit tests |
@openzeppelin/confidential-contracts | 0.4.x | ERC-7984 confidential token standard + extensions |
@zama-fhe/sdk | 3.0.0 | Primary client SDK. Provider-agnostic (viem, ethers, or any EVM lib). new ZamaSDK({ relayer, signer, storage }) returns an instance whose publicDecrypt / userDecrypt / encrypt / allow methods drive the off-chain flows. |
@zama-fhe/react-sdk | 3.0.x | React hooks over @zama-fhe/sdk (powered by @tanstack/query-core) |
@zama-fhe/relayer-sdk | ~0.4.2 | Transitive — low-level relayer transport. Pulled in by @zama-fhe/sdk; do not install directly for new code |
Do not use fhevmjs — it is the deprecated SDK name and is superseded by @zama-fhe/*.
External references