Skip to main content

Control Flow

The e_select function enables conditional logic on encrypted values without revealing the condition.

e_select

e_select(condition: Ebool, if_true: Euint128, if_false: Euint128) -> Euint128
Returns if_true when condition is encrypted true, if_false otherwise.

Example: Confidential Transfer with Balance Check

pub fn confidential_transfer(
    ctx: Context<Transfer>,
    transfer_amount: Euint128,
) -> Result<()> {
    let inco = ctx.accounts.inco_lightning_program.to_account_info();
    let signer = ctx.accounts.authority.to_account_info();

    let source_balance = ctx.accounts.source.balance;
    let dest_balance = ctx.accounts.destination.balance;

    // Check if source has sufficient balance (encrypted comparison)
    let cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
    let has_sufficient: Ebool = e_ge(cpi_ctx, source_balance, transfer_amount, 0)?;

    // Create zero for failed transfer case
    let zero = Euint128::wrap(0);

    // Select actual transfer amount: if sufficient balance, use amount; else use 0
    let cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
    let actual_amount: Euint128 = e_select(cpi_ctx, has_sufficient, transfer_amount, zero, 0)?;

    // Subtract from source (will subtract 0 if insufficient)
    let cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
    let new_source_balance: Euint128 = e_sub(cpi_ctx, source_balance, actual_amount, 0)?;

    // Add to destination (will add 0 if insufficient)
    let cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
    let new_dest_balance: Euint128 = e_add(cpi_ctx, dest_balance, actual_amount, 0)?;

    // Update balances
    ctx.accounts.source.balance = new_source_balance;
    ctx.accounts.destination.balance = new_dest_balance;

    Ok(())
}

Why Use e_select?

This pattern ensures:
  • No information leakage: Balance check happens on encrypted values
  • Atomic transfers: Either full amount transfers or nothing
  • No negative balances: Impossible to overdraw
Never branch on decrypted values in your program. Use e_select to keep logic encrypted:
// BAD: Leaks information through control flow
if decrypted_balance > amount {
    transfer(amount);
}

// GOOD: Encrypted conditional
let sufficient = e_ge(ctx, balance, amount, 0)?;
let actual = e_select(ctx, sufficient, amount, zero, 0)?;
e_sub(ctx, balance, actual, 0)?;
The last parameter in operation calls identifies whether the left-hand side operand is ciphertext (0) or plaintext (1). Always pass 0 when working with encrypted handles.