Skip to main content
Level: Beginner | Duration: 3 hoursPrerequisites: Module 02: Development Setup

Overview

FHEVM provides a rich set of encrypted data types that mirror familiar Solidity types. Understanding these types — their sizes, capabilities, gas costs, and storage behavior — is fundamental to writing effective confidential smart contracts.

Learning Objectives

By completing this module you will be able to:
  1. List and describe all 8 core encrypted types: ebool, euint8 through euint256, eaddress
  2. Understand how encrypted values are represented as handles on-chain
  3. Choose the correct encrypted type for a given use case
  4. Properly store and manage encrypted state variables
  5. Convert between plaintext and encrypted types using FHE.asEuintXX()

1. Overview of Encrypted Types

FHEVM offers the following encrypted types:
Encrypted TypePlaintext EquivalentBit WidthTypical Use Case
eboolbool2 bitsFlags, conditions
euint8uint88 bitsSmall counters, scores
euint16uint1616 bitsModerate ranges
euint32uint3232 bitsGeneral purpose integers
euint64uint6464 bitsBalances, timestamps
euint128uint128128 bitsLarge numbers
euint256uint256256 bitsHashes, full-range values
eaddressaddress160 bitsEncrypted addresses

2. Handles: How Encrypted Data Lives On-Chain

What is a Handle?

When you create an encrypted value, the actual ciphertext is stored in the FHE co-processor. On the EVM side, your contract only holds a handle — a uint256 reference that points to the ciphertext.
┌─────────────────────┐     ┌──────────────────────────┐
│   EVM Storage        │     │   FHE Co-processor       │
│                      │     │                          │
│  euint32 balance ────┼────►│  Ciphertext #0x1a2b...   │
│  (handle: 0x1a2b..)  │     │  (encrypted 32-bit value)│
└─────────────────────┘     └──────────────────────────┘

Key Implications

Storing an encrypted value costs the same as storing a uint256 (one storage slot).
You cannot read or log the plaintext value from within the contract.
The same plaintext encrypted twice will produce different handles (different ciphertexts).
An uninitialized encrypted variable has handle 0, which is invalid for operations.

3. Boolean Type: ebool

Declaration and Initialization

ebool private _isActive;

constructor() {
    _isActive = FHE.asEbool(true);
    FHE.allowThis(_isActive);
}

Use Cases

  • Encrypted flags (is a user verified? is an account frozen?)
  • Results of encrypted comparisons
  • Conditional logic with FHE.select()

Creating from Comparisons

euint32 a = FHE.asEuint32(10);
euint32 b = FHE.asEuint32(20);
ebool isGreater = FHE.gt(a, b);  // encrypted false

4. Unsigned Integer Types: euint8 to euint256

euint8 — 8-bit Encrypted Integer

euint8 private _score;

function setScore(uint8 score) public {
    _score = FHE.asEuint8(score);
    FHE.allowThis(_score);
}
Range: 0 to 255.

euint16 — 16-bit Encrypted Integer

euint16 private _itemCount;

function addItem() public {
    _itemCount = FHE.add(_itemCount, FHE.asEuint16(1));
    FHE.allowThis(_itemCount);
}
Range: 0 to 65,535.

euint32 — 32-bit Encrypted Integer

The most commonly used type. Good balance between range and gas cost.
euint32 private _balance;

function deposit(uint32 amount) public {
    _balance = FHE.add(_balance, FHE.asEuint32(amount));
    FHE.allowThis(_balance);
}
Range: 0 to 4,294,967,295.

euint64 — 64-bit Encrypted Integer

euint64 private _totalSupply;

function mint(uint64 amount) public {
    _totalSupply = FHE.add(_totalSupply, FHE.asEuint64(amount));
    FHE.allowThis(_totalSupply);
}
Range: 0 to 18,446,744,073,709,551,615. Suitable for token balances and timestamps.

euint128 and euint256

For very large numbers. Note that larger types consume more gas for operations.
euint128 private _largeValue;
euint256 private _hash;
Important Limitation: euint256 does NOT support arithmetic operations (add, sub, mul, min, max) or ordering comparisons (le, lt, ge, gt). It supports:
  • Bitwise: FHE.and(), FHE.or(), FHE.xor()
  • Equality: FHE.eq(), FHE.ne()
  • Shift/Rotate: FHE.shl(), FHE.shr(), FHE.rotl(), FHE.rotr()
  • Conditional: FHE.select()
Use euint128 or smaller for arithmetic operations.

5. Encrypted Address: eaddress

Declaration and Usage

eaddress private _secretRecipient;

function setRecipient(address recipient) public {
    _secretRecipient = FHE.asEaddress(recipient);
    FHE.allowThis(_secretRecipient);
}

Use Cases

  • Hidden recipients in transfer protocols
  • Anonymous voting (hiding voter addresses)
  • Sealed-bid auctions (hiding bidder identity)

Comparing Encrypted Addresses

ebool isSame = FHE.eq(_secretRecipient, FHE.asEaddress(msg.sender));

6. Type Conversion Functions

All conversions go through FHE.asXXX():
FunctionInputOutput
FHE.asEbool(bool)boolebool
FHE.asEuint8(uint8)uint8euint8
FHE.asEuint16(uint16)uint16euint16
FHE.asEuint32(uint32)uint32euint32
FHE.asEuint64(uint64)uint64euint64
FHE.asEuint128(uint128)uint128euint128
FHE.asEuint256(uint256)uint256euint256
FHE.asEaddress(address)addresseaddress
These functions encrypt plaintext values on-chain. The plaintext is visible in the transaction calldata. For truly private inputs from users, use externalEuintXX and FHE.fromExternal() (covered in Module 06).

Encrypted-to-Encrypted Casting

euint8 small = FHE.asEuint8(42);
euint32 bigger = FHE.asEuint32(small);  // 42 as euint32
Warning: Downcasting silently truncates. There is no overflow check on encrypted values!
Supported casting chain:
ebool <-> euint8 <-> euint16 <-> euint32 <-> euint64 <-> euint128 <-> euint256

7. Storage Patterns

Pattern 1: Initialize in Constructor

euint32 private _value;

constructor() {
    _value = FHE.asEuint32(0);
    FHE.allowThis(_value);
}

Pattern 2: Lazy Initialization

euint32 private _value;
bool private _initialized;

function initialize(uint32 val) public {
    require(!_initialized, "Already initialized");
    _value = FHE.asEuint32(val);
    FHE.allowThis(_value);
    _initialized = true;
}

Pattern 3: Mapping with Encrypted Values

mapping(address => euint32) private _balances;

function setBalance(address user, uint32 amount) internal {
    _balances[user] = FHE.asEuint32(amount);
    FHE.allowThis(_balances[user]);
    FHE.allow(_balances[user], user);
}

Pattern 4: Array of Encrypted Values

euint32[] private _values;

function addValue(uint32 val) public {
    euint32 encrypted = FHE.asEuint32(val);
    FHE.allowThis(encrypted);
    _values.push(encrypted);
}

8. ACL Basics: Who Can Use Encrypted Values?

When you create or update an encrypted value, you must explicitly grant access to it:
  • FHE.allowThis(handle) — Allows this contract to use the value in future transactions
  • FHE.allow(handle, address) — Allows a specific address to request decryption/reencryption of the value
Always call both after creating/updating an encrypted value:
_secretValue = FHE.asEuint32(42);
FHE.allowThis(_secretValue);          // Contract can use it in later txs
FHE.allow(_secretValue, msg.sender);   // Caller can decrypt/view it
  • allowThis is needed because even the contract that created the ciphertext cannot use it in a subsequent transaction without permission
  • allow is needed so users can request reencryption (off-chain viewing) of their data
Deep dive: Module 05 covers the full ACL system in detail.

9. Gas Considerations

Larger types cost more gas for operations:
TypeRelative Gas Cost
eboolLowest
euint8Low
euint16Low-Medium
euint32Medium
euint64Medium-High
euint128High
euint256Highest
Rule of thumb: Always use the smallest type that fits your data range.

10. Common Mistakes

Mistake 1: Using Uninitialized Encrypted Variables

euint32 private _value; // Handle is 0 — INVALID!

function bad() public {
    // This will revert — _value has no valid ciphertext
    _value = FHE.add(_value, FHE.asEuint32(1));
}
Fix: Always initialize in the constructor.

Mistake 2: Forgetting FHE.allowThis()

function bad() public {
    _value = FHE.asEuint32(42);
    // Missing FHE.allowThis(_value)!
    // Later operations on _value will fail
}

Mistake 3: Exposing Plaintext via FHE.asEuintXX()

// The value 42 is visible in transaction calldata!
function setSecret(uint32 val) public {
    _secret = FHE.asEuint32(val);
}
Fix: For user-provided secrets, use encrypted inputs (Module 06).

11. Practical Example: Encrypted Types Demo

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { FHE, ebool, euint8, euint16, euint32, euint64, euint128, euint256, eaddress } from "@fhevm/solidity/lib/FHE.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";

contract EncryptedTypes is ZamaEthereumConfig {
    ebool private _secretBool;
    euint8 private _secretUint8;
    euint16 private _secretUint16;
    euint32 private _secretUint32;
    euint64 private _secretUint64;
    eaddress private _secretAddress;

    constructor() {
        _secretBool = FHE.asEbool(false);
        FHE.allowThis(_secretBool);

        _secretUint32 = FHE.asEuint32(0);
        FHE.allowThis(_secretUint32);
    }

    // --- Bool ---
    function setBool(bool value) external {
        _secretBool = FHE.asEbool(value);
        FHE.allowThis(_secretBool);
        FHE.allow(_secretBool, msg.sender);
    }

    function getBool() external view returns (ebool) {
        return _secretBool;
    }

    // --- Uint32 ---
    function setUint32(uint32 value) external {
        _secretUint32 = FHE.asEuint32(value);
        FHE.allowThis(_secretUint32);
        FHE.allow(_secretUint32, msg.sender);
    }

    function getUint32() external view returns (euint32) {
        return _secretUint32;
    }

    // --- Address ---
    function setAddress(address value) external {
        _secretAddress = FHE.asEaddress(value);
        FHE.allowThis(_secretAddress);
        FHE.allow(_secretAddress, msg.sender);
    }

    function getAddress() external view returns (eaddress) {
        return _secretAddress;
    }
}

Summary

  • FHEVM provides 8 core encrypted types covering booleans, integers (8-256 bit), and addresses
  • Encrypted values are stored as handles (uint256 references) pointing to ciphertexts in the co-processor
  • Use FHE.asXXX() to convert plaintext to encrypted (but note the plaintext is visible on-chain)
  • Always initialize encrypted variables and call FHE.allowThis() after updates
  • Use FHE.allowThis() for contract access and FHE.allow() for user access — both are required after every update
  • Choose the smallest type that fits your data to minimize gas costs

Build docs developers (and LLMs) love