How FHE Powers cTokens
The privacy of confidential tokens is enabled by FHE, which allows encrypted integers (euint64) to be manipulated inside smart contracts.
Encrypted Balances
When you hold cUSDC, your balance isn't stored as a regular number like "1000". Instead, it's stored as an encrypted value called a euint64: an encrypted 64-bit unsigned integer. Think of it as a locked box containing a number between 0 and 18.4 quintillion.
Only the holder (or authorised parties) can decrypt it to see the actual value. To everyone else, it's just encrypted values.
Encrypted Operations
When you transfer 100 cUSDC to someone:
Your encrypted balance:
euint64_ATransfer amount:
euint64_transferTheir encrypted balance:
euint64_B
The smart contract performs:
euint64_A = euint64_A - euint64_transfer(FHE subtraction)euint64_B = euint64_B + euint64_transfer(FHE addition)
These operations happen entirely on encrypted values. The blockchain nodes processing your transaction never see the actual amounts. They just shuffle around encrypted data and perform encrypted arithmetic.
Selective Decryption
You control who can see your encrypted balances. When you need to view your balance in a wallet, your private key allows you to decrypt it. When you need to unshield (convert back to regular tokens), the amount is made publicly decryptable so you can decrypt it yourself using the fhEVM SDK and provide it to the wrapper contract.
The ERC-7984 Standard and FHE
The ERC-7984 standard specifies how confidential tokens should use FHE:
Balances are stored as
euint64(encrypted 64-bit integers)Transfers use
confidentialTransfer()with encrypted amountsThe
confidentialTransferAndCall()pattern enables smart contract interactionsTotal supply can optionally be public (since wrapping/unwrapping reveals it anyway)
This standardisation means:
Wallets can support all ERC-7984 tokens with one integration
DeFi protocols can interact with confidential tokens using known patterns
Developers can build on a solid, audited foundation (OpenZeppelin)
Public Decryption
Certain operations (notably unshielding) require decrypting an encrypted value publicly. This is handled through public decryption:
The smart contract marks a value as "publicly decryptable" via
FHE.makePubliclyDecryptable()An indexer monitors blockchain events and exposes these encrypted handles
Anyone can decrypt the value using the fhEVM SDK's
decryptPublic()functionThe decrypted value is submitted to the blockchain in the finalisation transaction
The smart contract uses the decrypted value to complete the operation
This process is secure because only values explicitly marked as publicly decryptable can be decrypted this way. Your regular encrypted balances remain confidential. We'll see this in action in the unshield process.
Understanding euint64 Limits
The euint64 type can hold values from 0 to 18,446,744,073,709,551,615 (about 18.4 quintillion). This sounds like a lot, but tokens on Ethereum can have up to 18 decimals. A token with 18 decimals uses the smallest unit (wei for ETH), meaning 1 ETH = 1,000,000,000,000,000,000 wei.
If we tried to store large ETH balances directly as euint64, we'd quickly overflow. That's why the system uses rate conversion to scale down high-decimal tokens to fit comfortably within euint64 limits.
The Rate Conversion System
Because many ERC-20 tokens use 18 decimals, storing raw values in euint64 could overflow. Zaïffer uses rate conversion to scale values down to a uniform 6 decimal internal precision.
This ensures:
all tokens fit safely within euint64
consistent precision across tokens
predictable behaviour for developers
Examples:
USDC uses 6 decimals (1 USDC = 1,000,000 smallest units) → fits naturally
ETH uses 18 decimals (1 ETH = 1,000,000,000,000,000,000 wei) → too large for euint64
Without scaling, users could hold as little as ~18 ETH before reaching the encrypted integer limit. To solve this, confidential tokens standardise on a maximum of 6 decimals internally. The rate conversion formula is:
For tokens with ≤6 decimals:
rate = 1(no conversion needed)For tokens with >6 decimals:
rate = 10^(decimals - 6)
Examples:
USDC
6
1
1,000 USDC = 1,000 cUSDC
Direct 1:1 mapping
WETH
18
10^(18 - 6) = 10^12
1^18 wei = 1^6 cETH units
scales 18 → 6 decimals
Rate conversion is handled automatically and invisibly by the wrapper contract. You:
Shield 1.5 ETH → Receive equivalent in cETH (automatically rate-adjusted)
Transfer cETH to someone → Amounts are encrypted, rate doesn't matter
Unshield cETH → Receive 1.5 ETH back (automatically rate-adjusted)
When converting between decimal formats, tiny remainders may occur. These are dust amounts too small to represent in 6-decimal precision.
Example: Shielding 1.000000000000000001 ETH (18 decimals):
After rate conversion to 6 decimals: 1.000000 cETH
Dust: 0.000000000000000001 ETH (too small to represent)
This dust is sent to the protocol fee recipient, ensuring no tokens are "lost" in conversion. They're just too small to be meaningful and go toward protocol revenue. In practice, dust amounts are negligible (fractions of a cent).
Last updated