Skip to main content

Overview

Writing smart contracts with Fully Homomorphic Encryption (FHE) changes how you handle conditionals. Since all data is encrypted, you can’t use traditional if...else statementsthere’s no way to view the values being compared. Moreover, conditionals in FHE must evaluate both branches simultaneously. This is similar to constant-time cryptographic programming, where branching can leak information through timing attacksfor example, if one path takes longer to execute, an observer could infer which condition was true.
Using traditional if...else on encrypted data might result in unexpected behavior and leak information about your encrypted values.

The Select Function

To handle encrypted conditionals, Fhenix uses a concept called a selectora function that takes an encrypted condition and two possible values, returning one based on the encrypted result. In practice, this is done with the select function. It behaves like a ternary operator (condition ? a : b) but works entirely on encrypted data.

How It Works

FHE.select takes the encrypted ebool returned by comparison operations like gt. If the condition represents encrypted true, it returns the first value; otherwise, it returns the second valueall without revealing which path was taken.

Quick Start

euint32 a = FHE.asEuint32(10);
euint32 b = FHE.asEuint32(20);
euint32 max;

// This won't work as expected!
if (a.gt(b)) { // gt returns encrypted boolean (ebool)
   max = a;    // Traditional if..else leaks information
} else {
   max = b;
}

Key Points to Remember

Encrypted Operations Only

All operations take place on encrypted data, so the actual values and comparison results stay concealed from observers.

No Traditional Branching

Traditional if...else statements on encrypted data leak information through execution paths and timing.

Select is Your Friend

The select function is the only way to handle conditional execution in FHE without leaking information.

Both Paths Execute

Both branches are evaluated, then the correct result is selected based on the encrypted condition.

Common Use Cases

Here are some common scenarios where you’ll use select:

1. Maximum/Minimum Operations

Find the larger or smaller of two encrypted values:
// Maximum
euint32 max = FHE.select(a.gt(b), a, b);

// Minimum
euint32 min = FHE.select(a.lt(b), a, b);

2. Conditional Updates

Update a value only when a condition is met:
ebool shouldUpdate = checkCondition();
euint32 newValue = FHE.select(shouldUpdate, updatedValue, currentValue);

// Store the result (either updated or unchanged)
_storedValue = newValue;

3. Threshold Checks

Cap values at a certain threshold:
ebool isAboveThreshold = value.gt(threshold);
euint32 result = FHE.select(isAboveThreshold, threshold, value);

// Result will be capped at threshold if value exceeds it

4. Conditional Access Control

Grant different permissions based on encrypted conditions:
ebool hasPermission = userRole.eq(ADMIN_ROLE);
euint32 accessLevel = FHE.select(hasPermission, FULL_ACCESS, LIMITED_ACCESS);

5. Fee Calculations

Apply different rates based on encrypted criteria:
ebool isPremiumUser = userTier.gt(STANDARD_TIER);
euint32 feeRate = FHE.select(isPremiumUser, PREMIUM_FEE, STANDARD_FEE);
euint32 totalFee = FHE.mul(amount, feeRate);

Best Practices

Never try to implement branching logic with traditional if...else statements on encrypted data. Always use select to ensure constant-time execution and prevent information leakage.
// Good
euint32 result = FHE.select(condition, valueA, valueB);

// Bad - leaks information!
if (FHE.decrypt(condition)) { // Don't do this!
    result = valueA;
} else {
    result = valueB;
}
Complex nested conditions should be broken down into simpler operations. Each select can only choose between two values, so chain them carefully.
// For multiple conditions, chain select operations
ebool condition1 = a.gt(b);
ebool condition2 = c.lt(d);

euint32 temp = FHE.select(condition1, valueA, valueB);
euint32 final = FHE.select(condition2, temp, valueC);
Every value, comparison, and result remains encrypted throughout the entire process. The blockchain never sees plaintext values.
ebool condition = encryptedValue.gt(encryptedThreshold); // Encrypted comparison
euint32 result = FHE.select(condition, encA, encB);      // Encrypted selection
// Both condition and result remain encrypted
Since both branches of a select are always evaluated, complex operations in both paths will always execute. Structure your code to minimize unnecessary computations.
// Both computations happen regardless of condition
euint32 expensiveA = complexOperation(a);
euint32 expensiveB = complexOperation(b);
euint32 result = FHE.select(condition, expensiveA, expensiveB);

// Consider pre-computing when possible

Complete Example: Auction Bid

Here’s a practical example showing how to handle encrypted bids in an auction:
contract EncryptedAuction {
    euint32 public highestBid;
    address public highestBidder;

    function placeBid(InEuint32 memory encryptedBid) public {
        euint32 bid = FHE.asEuint32(encryptedBid);

        // Compare new bid with current highest (encrypted comparison)
        ebool isHigher = bid.gt(highestBid);

        // Update highest bid using select
        euint32 newHighestBid = FHE.select(isHigher, bid, highestBid);

        // Grant contract access to the new highest bid
        FHE.allowThis(newHighestBid);

        highestBid = newHighestBid;

        // Update highest bidder (note: address is not encrypted)
        // In production, you'd handle this more carefully
        if (/* some non-encrypted condition */) {
            highestBidder = msg.sender;
        }
    }

    function revealWinner() public view returns (uint32) {
        // Only authorized parties can decrypt
        return FHE.decrypt(highestBid);
    }
}
In the example above, the actual bid amounts remain encrypted throughout the auction. Only the final winner can be revealed through controlled decryption.