Skip to content

FHE Operations Cheat Sheet

A comprehensive quick-reference for every FHE operation, type, and pattern used in this bootcamp. Bookmark this page — you'll come back to it constantly.


Encrypted Types

Every encrypted type is stored on-chain as a bytes32 handle. The actual ciphertext lives in the coprocessor.

Encrypted TypeSolidity TypeExternal Input TypeBit WidthPlaintext EquivalentIntroduced
BooleaneboolexternalEbool1boolWeek 2
Unsigned 8-biteuint8externalEuint88uint8Week 1
Unsigned 16-biteuint16externalEuint1616uint16Week 1
Unsigned 32-biteuint32externalEuint3232uint32Week 1
Unsigned 64-biteuint64externalEuint6464uint64Week 2
Unsigned 128-biteuint128externalEuint128128uint128Week 1
Unsigned 256-biteuint256externalEuint256256uint256Week 1
AddresseaddressexternalEaddress160addressWeek 4

Import Paths

solidity
// Internal encrypted types (for state variables and computations)
import {ebool, euint8, euint16, euint32, euint64, euint128, euint256, eaddress}
    from "encrypted-types/EncryptedTypes.sol";

// External encrypted types (for function parameters — user inputs)
import {externalEbool, externalEuint8, externalEuint16, externalEuint32,
        externalEuint64, externalEuint128, externalEuint256, externalEaddress}
    from "encrypted-types/EncryptedTypes.sol";

// FHE library (all operations)
import {FHE} from "@fhevm/solidity/lib/FHE.sol";

// Config (inheritable — sets coprocessor/ACL/KMS addresses)
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";

Arithmetic Operations

All arithmetic operations take two encrypted operands of the same type and return the same type.

FHE.add(a, b) — Encrypted Addition

solidity
function add(euint64 a, euint64 b) returns (euint64)

Adds two encrypted values. Returns an encrypted result.

PropertyValue
OperandsSame encrypted type (e.g., both euint64)
ReturnsSame encrypted type
Gas estimate~50–80k
OverflowWraps silently (no revert)
solidity
// Example: accumulate a deposit into an encrypted balance
_balances[msg.sender] = FHE.add(_balances[msg.sender], amount);

FHE.sub(a, b) — Encrypted Subtraction

solidity
function sub(euint64 a, euint64 b) returns (euint64)

Subtracts b from a. Returns an encrypted result.

PropertyValue
OperandsSame encrypted type
ReturnsSame encrypted type
Gas estimate~50–80k
UnderflowWraps silently — always guard with compare+select
solidity
// Example: deduct withdrawal from balance (after guard)
_balances[msg.sender] = FHE.sub(_balances[msg.sender], safeAmount);

Underflow Danger

FHE.sub does not revert on underflow — it wraps. Always use the encrypted guard pattern before subtraction.

FHE.mul(a, b) — Encrypted Multiplication

solidity
function mul(euint32 a, euint32 b) returns (euint32)

Multiplies two encrypted values. Returns an encrypted result.

PropertyValue
OperandsSame encrypted type
ReturnsSame encrypted type
Gas estimate~80–150k
OverflowWraps silently
solidity
// Example: double an encrypted counter
_count = FHE.mul(_count, FHE.asEuint32(2));

Comparison Operations

All comparisons take two encrypted operands and return ebool — an encrypted boolean. You cannot use ebool in Solidity if statements. Use FHE.select() instead.

FHE.eq(a, b) — Equal

solidity
function eq(euint64 a, euint64 b) returns (ebool)

Returns encrypted true if a == b.

solidity
ebool isZero = FHE.eq(balance, FHE.asEuint64(0));

FHE.ne(a, b) — Not Equal

solidity
function ne(euint64 a, euint64 b) returns (ebool)

Returns encrypted true if a != b.

solidity
ebool hasBalance = FHE.ne(balance, FHE.asEuint64(0));

FHE.gt(a, b) — Greater Than

solidity
function gt(euint64 a, euint64 b) returns (ebool)

Returns encrypted true if a > b. Used in ranking and highest-value tracking.

solidity
// Example: check if new bid exceeds current highest
ebool isHigher = FHE.gt(bid, _highestBid);

FHE.ge(a, b) — Greater Than or Equal

solidity
function ge(euint64 a, euint64 b) returns (ebool)

Returns encrypted true if a >= b.

solidity
ebool meetsMinimum = FHE.ge(bid, minimumBid);

FHE.lt(a, b) — Less Than

solidity
function lt(euint64 a, euint64 b) returns (ebool)

Returns encrypted true if a < b.

solidity
ebool isBelow = FHE.lt(amount, threshold);

FHE.le(a, b) — Less Than or Equal

solidity
function le(euint64 a, euint64 b) returns (ebool)

Returns encrypted true if a <= b. The most common comparison — used in every balance sufficiency check.

solidity
// Example: check if withdrawal amount <= balance
ebool canWithdraw = FHE.le(amount, _balances[msg.sender]);

FHE.min(a, b) — Encrypted Minimum

solidity
function min(euint64 a, euint64 b) returns (euint64)

Returns the smaller of a and b as an encrypted value. Not an ebool — returns the same encrypted type as the inputs.

Equivalent to:

solidity
FHE.select(FHE.le(a, b), a, b)
solidity
// Example: cap withdrawal at balance
euint64 actualAmount = FHE.min(requestedAmount, balance);

FHE.max(a, b) — Encrypted Maximum

solidity
function max(euint64 a, euint64 b) returns (euint64)

Returns the larger of a and b as an encrypted value.

Equivalent to:

solidity
FHE.select(FHE.ge(a, b), a, b)
solidity
// Example: enforce minimum bid
euint64 effectiveBid = FHE.max(bid, minimumBid);

Comparison Summary Table

OperationFunctionReturnsGas Estimate
EqualFHE.eq(a, b)ebool~80–100k
Not EqualFHE.ne(a, b)ebool~80–100k
Greater ThanFHE.gt(a, b)ebool~80–100k
Greater or EqualFHE.ge(a, b)ebool~80–100k
Less ThanFHE.lt(a, b)ebool~80–100k
Less or EqualFHE.le(a, b)ebool~80–100k
MinimumFHE.min(a, b)Same type~150–200k
MaximumFHE.max(a, b)Same type~150–200k

Cannot Branch on ebool

solidity
// ❌ IMPOSSIBLE — EVM cannot evaluate encrypted boolean
ebool isGreater = FHE.gt(a, b);
if (isGreater) { ... }  // Compilation error or meaningless

// ✅ CORRECT — use FHE.select()
ebool isGreater = FHE.gt(a, b);
euint64 result = FHE.select(isGreater, valueIfTrue, valueIfFalse);

Conditional Operation

FHE.select(condition, ifTrue, ifFalse) — Encrypted Ternary

solidity
function select(ebool condition, euint64 ifTrue, euint64 ifFalse) returns (euint64)

The encrypted equivalent of condition ? a : b. This is the most important FHE operation — it replaces all if/else logic on encrypted data.

PropertyValue
Conditionebool (from any comparison)
BranchesMust be the same encrypted type
ReturnsSame encrypted type as branches
Gas estimate~80–100k
EvaluationBoth branches are always evaluated (no short-circuit)
PrivacyZero leakage — observer cannot tell which branch was selected

Works with all encrypted types — euint64, eaddress, ebool, etc.:

solidity
// With euint64
euint64 safeAmount = FHE.select(hasFunds, amount, FHE.asEuint64(0));

// With eaddress
_highestBidder = FHE.select(isHigher, FHE.asEaddress(msg.sender), _highestBidder);

Type Conversions

FHE.asEuint*(plaintext) — Trivial Encryption

Converts a plaintext value into an encrypted handle. Required when mixing plaintext and encrypted values in FHE operations.

solidity
// Integer types
euint8  enc8  = FHE.asEuint8(42);
euint16 enc16 = FHE.asEuint16(1000);
euint32 enc32 = FHE.asEuint32(123456);
euint64 enc64 = FHE.asEuint64(1_000_000);
euint128 enc128 = FHE.asEuint128(value128);
euint256 enc256 = FHE.asEuint256(value256);

// Boolean
ebool encBool = FHE.asEbool(true);
PropertyValue
Gas estimate~30–50k
Common usesMinting (plaintext amount), comparing to zero, setting caps/thresholds
First seenWeek 3: Confidential Token
solidity
// Example: mint converts plaintext amount to encrypted for addition
euint64 encAmount = FHE.asEuint64(amount);
_balances[to] = FHE.add(_balances[to], encAmount);

FHE.asEaddress(address) — Address Encryption

solidity
eaddress encAddr = FHE.asEaddress(msg.sender);

Converts a plaintext address into an encrypted eaddress handle. Used to hide addresses on-chain (e.g., auction winners).

PropertyValue
Gas estimate~30–50k
First seenWeek 4: Sealed Auction

FHE.fromExternal(external, proof) — Verify User Input

solidity
function fromExternal(externalEuint64 handle, bytes calldata proof) returns (euint64)

Verifies a user-submitted encrypted input and converts it from an external* type to the corresponding internal type. This is the entry point for all user-submitted encrypted data.

PropertyValue
Gas estimate~50–100k
Must callBefore any FHE operation on user input
What it doesAsks InputVerifier to validate the ciphertext + proof
First seenWeek 1: Hello FHE
solidity
function deposit(externalEuint64 encAmount, bytes calldata inputProof) external {
    euint64 amount = FHE.fromExternal(encAmount, inputProof);
    // Now `amount` is a verified encrypted value
}

ACL Operations

The Access Control List determines who can read (decrypt) encrypted values. Every FHE operation produces a new handle — the old handle's permissions are gone.

FHE.allowThis(handle) — Contract Self-Permission

solidity
FHE.allowThis(_balances[msg.sender]);

Grants the current contract permission to use this handle in future transactions. Without this, the contract cannot read or operate on the value in a later call.

FHE.allow(handle, address) — Grant Decryption Permission

solidity
FHE.allow(_balances[msg.sender], msg.sender);

Grants a specific address permission to decrypt this value off-chain via fhevmjs and the KMS.

FHE.allowTransient(handle, address) — Temporary Permission

solidity
FHE.allowTransient(result, msg.sender);

Grants permission only for the current transaction. Useful for intermediate values that don't need to persist.

The Permission Dance

After every FHE operation that produces a new handle you intend to store, you must call:

  1. FHE.allowThis(handle) — so the contract can use it later
  2. FHE.allow(handle, user) — so the relevant user(s) can decrypt it
solidity
// The complete permission dance (after every balance update)
_balances[user] = FHE.add(_balances[user], amount);
FHE.allowThis(_balances[user]);     // Contract can use in future txns
FHE.allow(_balances[user], user);   // User can decrypt off-chain

Dual-Party Permissions

For allowances (approve/transferFrom), grant to both owner and spender:

solidity
FHE.allow(_allowances[owner][spender], owner);   // Owner sees allowance
FHE.allow(_allowances[owner][spender], spender);  // Spender sees allowance

First seen in Week 3: Advanced Patterns.

ACL OperationGas EstimateUse Case
FHE.allowThis(handle)~20–30kContract can reuse handle
FHE.allow(handle, addr)~20–30kUser can decrypt off-chain
FHE.allowTransient(handle, addr)~15–25kTemporary within-tx permission

Common Patterns

These are the recurring design patterns that appear throughout the bootcamp. Master these and you can build any FHE contract.

The FHE Pattern (Core Lifecycle)

Every FHE function follows this four-step pattern:

fromExternal → Operate → allowThis → allow
solidity
function deposit(externalEuint64 encAmount, bytes calldata inputProof) external {
    // 1. Verify input
    euint64 amount = FHE.fromExternal(encAmount, inputProof);

    // 2. Operate
    _balances[msg.sender] = FHE.add(_balances[msg.sender], amount);

    // 3. Self-permit
    FHE.allowThis(_balances[msg.sender]);

    // 4. User-permit
    FHE.allow(_balances[msg.sender], msg.sender);
}

First seen: Week 1: Hello FHE


Encrypted Guard (Compare → Select → Operate)

The FHE replacement for require() + operation. Use whenever you would check a condition before mutating state.

solidity
// 1. COMPARE — produce an encrypted boolean
ebool canWithdraw = FHE.le(amount, _balances[msg.sender]);

// 2. SELECT — choose the safe value
euint64 safeAmount = FHE.select(canWithdraw, amount, _balances[msg.sender]);

// 3. OPERATE — guaranteed safe (no underflow)
_balances[msg.sender] = FHE.sub(_balances[msg.sender], safeAmount);

First seen: Week 2: FHE Patterns


Silent-Zero Pattern

Transfer 0 instead of reverting on insufficient balance. Preserves privacy — an observer cannot tell whether the transfer had sufficient funds.

solidity
ebool hasFunds = FHE.le(amount, _balances[from]);
euint64 actualAmount = FHE.select(hasFunds, amount, FHE.asEuint64(0));

_balances[from] = FHE.sub(_balances[from], actualAmount);
_balances[to] = FHE.add(_balances[to], actualAmount);

Use when: Transferring between parties — better to send nothing than a different amount.

First seen: Week 3: Confidential Token


Silent-Fail / Cap Pattern

Cap the amount at the maximum available instead of reverting. The operation always "succeeds" — just with a potentially reduced amount.

solidity
ebool canWithdraw = FHE.le(amount, _balances[msg.sender]);
euint64 actualAmount = FHE.select(canWithdraw, amount, _balances[msg.sender]);

_balances[msg.sender] = FHE.sub(_balances[msg.sender], actualAmount);

Use when: User is withdrawing their own funds — give them as much as possible.

First seen: Week 2: FHE Patterns


Double Protection Pattern

Two sequential silent-zero checks — first on allowance, then on balance. Used in delegated transfers (transferFrom).

solidity
// Check 1: Allowance guard
ebool hasAllowance = FHE.le(amount, _allowances[from][msg.sender]);
euint64 actualAmount = FHE.select(hasAllowance, amount, FHE.asEuint64(0));

// Deduct allowance
_allowances[from][msg.sender] = FHE.sub(_allowances[from][msg.sender], actualAmount);

// Check 2: Balance guard (inside _transfer)
ebool hasFunds = FHE.le(actualAmount, _balances[from]);
euint64 transferAmount = FHE.select(hasFunds, actualAmount, FHE.asEuint64(0));

Why two checks? Neither check leaks information about which one failed. An observer cannot distinguish "insufficient allowance" from "insufficient balance."

First seen: Week 3: Advanced Patterns


Deferred Permissions Pattern

Grant FHE.allow only when the state machine reaches the appropriate phase — not at creation time.

solidity
// During bidding: only contract can access winner data
FHE.allowThis(_highestBid);
FHE.allowThis(_highestBidder);

// After close: auctioneer gets permission to decrypt
function closeAuction() external onlyAuctioneer {
    FHE.allow(_highestBid, auctioneer);
    FHE.allow(_highestBidder, auctioneer);
}

First seen: Week 4: Sealed Auction


Hardhat Testing Quick Reference

The current bootcamp uses the Hardhat FHEVM runtime.

Encryption Helpers

HelperPurposeExample
fhevm.createEncryptedInput(addr, signer).add32(v).encrypt()Build encrypted euint32 inputadd32(1)
fhevm.createEncryptedInput(addr, signer).add64(v).encrypt()Build encrypted euint64 inputadd64(1_000)

Decryption Helpers

HelperReturnsExample
fhevm.userDecryptEuint(FhevmType.euint32, handle, contract, signer)bigintDecrypt counter values
fhevm.userDecryptEuint(FhevmType.euint64, handle, contract, signer)bigintDecrypt balances and bids
fhevm.userDecryptEaddress(handle, contract, signer)stringDecrypt encrypted addresses

The Test Pattern

ts
const encrypted = await fhevm.createEncryptedInput(contractAddress, signer.address).add64(500).encrypt();
await contract.connect(signer).someFunction(encrypted.handles[0], encrypted.inputProof);
const result = await fhevm.userDecryptEuint(FhevmType.euint64, await contract.getValue(), contractAddress, signer);
expect(result).to.equal(500n);

Gas Cost Reference

Approximate gas costs for FHE operations. These are estimates for the FHEVM coprocessor — mock mode costs are significantly lower.

OperationGas EstimateCategory
FHE.add / FHE.sub~50–80kArithmetic
FHE.mul~80–150kArithmetic
FHE.eq / FHE.ne~80–100kComparison
FHE.gt / FHE.ge / FHE.lt / FHE.le~80–100kComparison
FHE.min / FHE.max~150–200kComparison (compound)
FHE.select~80–100kConditional
FHE.asEuint* / FHE.asEaddress~30–50kTrivial encryption
FHE.fromExternal~50–100kInput verification
FHE.allowThis / FHE.allow~20–30kACL
Plaintext ERC20 transfer~50k(baseline)
Confidential _transfer~300–500k4–6 FHE operations
Confidential transferFrom~500–800k7–10 FHE operations

Quick Decision Guide

I need to...Use...Pattern
Accept encrypted user inputFHE.fromExternal(handle, proof)Core lifecycle
Add encrypted valuesFHE.add(a, b)
Subtract encrypted valuesFHE.sub(a, b) + guardEncrypted guard
Check if value is sufficientFHE.le(amount, balance)Compare → select
Handle insufficient balance (own funds)FHE.select(cond, amount, balance)Silent-fail/cap
Handle insufficient balance (transfer)FHE.select(cond, amount, FHE.asEuint64(0))Silent-zero
Mix plaintext with encryptedFHE.asEuint64(plaintext)Trivial encryption
Hide an addressFHE.asEaddress(addr)
Find higher of two valuesFHE.gt(a, b) + FHE.selectRanking
Store encrypted resultFHE.allowThis(handle)Permission dance
Let user decryptFHE.allow(handle, user)Permission dance
Grant temp permissionFHE.allowTransient(handle, user)
Delegate spendingDouble silent-zeroDouble protection

Built for the Zama Developer Program — Bounty Track