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