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_A

  • Transfer amount: euint64_transfer

  • Their 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 amounts

  • The confidentialTransferAndCall() pattern enables smart contract interactions

  • Total 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:

  1. The smart contract marks a value as "publicly decryptable" via FHE.makePubliclyDecryptable()

  2. An indexer monitors blockchain events and exposes these encrypted handles

  3. Anyone can decrypt the value using the fhEVM SDK's decryptPublic() function

  4. The decrypted value is submitted to the blockchain in the finalisation transaction

  5. 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:

Token
Decimals
Rate
Example Conversion
Notes

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