Skip to main content
FHE programs cannot use traditional if-else statements with encrypted conditions. Instead, Privora provides encrypted comparison and selection operations.

The Challenge

In normal programming:
// This won't work with encrypted values!
if encrypted_price >= threshold {
    // do something
} else {
    // do something else
}
The problem: We can’t branch on encrypted data without decrypting it, which would break privacy.

The Solution: Encrypted Selection

FHE uses oblivious selection: compute both branches and select the result based on an encrypted condition.
use privora_sdk_program::prelude::*;

// Compare encrypted values
let condition: EncryptedBool = price.ge(&threshold)?;

// Select based on encrypted condition
let result = condition.select(&high_value, &low_value)?;
Both high_value and low_value are computed; the select operation picks one based on the encrypted condition without revealing which.

Comparison Operations

Available Comparisons

MethodComparisonReturns
.ge(&other)>=EncryptedBool
.gt(&other)>EncryptedBool
.le(&other)<=EncryptedBool
.lt(&other)<EncryptedBool
.eq_enc(&other)==EncryptedBool

Examples

// Greater than or equal
let can_afford: EncryptedBool = balance.ge(&price)?;

// Strict greater than
let is_higher: EncryptedBool = bid.gt(&ask)?;

// Less than or equal
let within_limit: EncryptedBool = amount.le(&max_amount)?;

// Strict less than
let below_minimum: EncryptedBool = value.lt(&threshold)?;

// Equality check
let is_match: EncryptedBool = value_a.eq_enc(&value_b)?;

The select() Method

EncryptedBool::select() implements encrypted if-then-else:
let result = condition.select(&true_value, &false_value)?;
Condition (encrypted)Result
truetrue_value
falsefalse_value

Type Safety

Both values must have the same type:
let condition: EncryptedBool = ...;
let val_u8: Encrypted<u8> = ...;
let val_u64: Encrypted<u64> = ...;

// Won't compile - types must match!
condition.select(&val_u8, &val_u64)?; // Error!

// Correct - same types
let a: Encrypted<u8> = ...;
let b: Encrypted<u8> = ...;
condition.select(&a, &b)?; // OK

Common Patterns

Pattern 1: Price Matching

// Match if buy >= sell
let can_match: EncryptedBool = buy_price.ge(&sell_price)?;

// Fill price is seller's price (standard matching)
let fill_price = can_match.select(&sell_price, &buy_price)?;

Pattern 2: Clamping Values

// Ensure value is within bounds
fn clamp(
    value: &Encrypted<u8>,
    min: &Encrypted<u8>,
    max: &Encrypted<u8>,
) -> Result<Encrypted<u8>, ProgramError> {
    // If value < min, use min
    let below_min = value.lt(min)?;
    let clamped_low = below_min.select(min, value)?;

    // If clamped_low > max, use max
    let above_max = clamped_low.gt(max)?;
    let result = above_max.select(max, &clamped_low)?;

    Ok(result)
}

Pattern 3: Conditional Fees

// Apply fee only if amount exceeds threshold
fn calculate_fee(
    amount: &Encrypted<u64>,
    threshold: &Encrypted<u64>,
    fee_amount: &Encrypted<u64>,
    zero: &Encrypted<u64>,
) -> Result<Encrypted<u64>, ProgramError> {
    let exceeds_threshold = amount.gt(threshold)?;
    let fee = exceeds_threshold.select(fee_amount, zero)?;
    Ok(fee)
}

Pattern 4: Safe Subtraction

// Subtract only if we have enough balance
fn safe_subtract(
    balance: &Encrypted<u64>,
    amount: &Encrypted<u64>,
) -> Result<Encrypted<u64>, ProgramError> {
    let has_enough = balance.ge(amount)?;
    let new_balance = balance.sub(amount)?;

    // If not enough, return original balance (no change)
    let result = has_enough.select(&new_balance, balance)?;
    Ok(result)
}

Min/Max Operations

Privora provides built-in min/max that are more efficient than manual comparison + select:
// Get minimum
let fill_qty: Encrypted<u8> = buy_qty.min(&sell_qty)?;

// Get maximum
let best_price: Encrypted<u8> = price_a.max(&price_b)?;
These are preferred over:
// Less efficient - use min/max methods instead
let is_smaller = a.lt(&b)?;
let min_value = is_smaller.select(&a, &b)?;

Storing Boolean Results

If you need to persist comparison results:
// Perform comparison
let is_valid: EncryptedBool = price.ge(&minimum)?;

// Store to get reference
let is_valid_ref: EncryptedBoolRef = is_valid.store()?;

// Save in account
account.is_valid_ref = is_valid_ref;

// Later: Load and use
let is_valid: EncryptedBool = account.is_valid_ref.load()?;
let value = is_valid.select(&yes_value, &no_value)?;

Performance Considerations

Comparison Cost

Comparisons are expensive (5-10x more than addition):
Encrypted Value → Compare → Bootstrap → EncryptedBool
The bootstrapping step is computationally intensive.

Optimization Strategies

  1. Minimize comparisons
// Bad: Multiple redundant comparisons
let a_ge_b = a.ge(&b)?;
let a_lt_b = a.lt(&b)?;  // Redundant!

// Good: One comparison, derive the rest
let a_ge_b = a.ge(&b)?;
// a_lt_b is just the negation - use select if needed
  1. Use min/max instead of compare + select
// Bad
let is_smaller = a.lt(&b)?;
let min = is_smaller.select(&a, &b)?;

// Good
let min = a.min(&b)?;
  1. Batch arithmetic before comparisons
// Good: All arithmetic first
let total_a = price_a.mul(&qty_a)?;
let total_b = price_b.mul(&qty_b)?;
let a_larger = total_a.gt(&total_b)?;  // Single comparison

// Avoid: Comparisons interleaved

Limitations

Cannot Short-Circuit

FHE cannot skip computation based on conditions:
// In normal code, this might skip expensive_op() if condition is false
if condition {
    expensive_op();
}

// In FHE, expensive_op() is ALWAYS computed
let result = condition.select(&expensive_result, &default)?;

Both Branches Execute

// Both compute_a() and compute_b() run
let a = compute_a()?;
let b = compute_b()?;
let result = condition.select(&a, &b)?;
Design your program knowing that all code paths execute.

Next Steps