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.