Operations
The Inco Lightning SDK provides arithmetic, comparison, bitwise, and control flow operations on encrypted values.Arithmetic Operations
All arithmetic operations take aCpiContext, two operands, and a scalar_byte parameter. They return Result<Euint128>.
| Function | Description | Signature |
|---|---|---|
e_add | Addition | (CpiContext, Euint128, Euint128, u8) -> Result<Euint128> |
e_sub | Subtraction | (CpiContext, Euint128, Euint128, u8) -> Result<Euint128> |
e_mul | Multiplication | (CpiContext, Euint128, Euint128, u8) -> Result<Euint128> |
e_rem | Remainder | (CpiContext, Euint128, Euint128, u8) -> Result<Euint128> |
Example: Encrypted addition with allowance
use inco_lightning::cpi::accounts::{Operation, Allow};
use inco_lightning::cpi::{e_add, new_euint128, allow};
/// remaining_accounts:
/// [0] allowance_account (mut) - PDA for granting decrypt access
/// [1] owner_address (readonly) - The owner to grant access to
pub fn add_balances<'info>(
ctx: Context<'_, '_, '_, 'info, AddBalances<'info>>,
ciphertext_a: Vec<u8>,
ciphertext_b: Vec<u8>,
) -> Result<()> {
let cpi_program = ctx.accounts.inco_lightning_program.to_account_info();
let signer = ctx.accounts.authority.to_account_info();
// Convert ciphertexts to encrypted handles
let cpi_ctx = CpiContext::new(cpi_program.clone(), Operation { signer: signer.clone() });
let amount_a: Euint128 = new_euint128(cpi_ctx, ciphertext_a, 0)?;
let cpi_ctx = CpiContext::new(cpi_program.clone(), Operation { signer: signer.clone() });
let amount_b: Euint128 = new_euint128(cpi_ctx, ciphertext_b, 0)?;
// Add two encrypted values
let cpi_ctx = CpiContext::new(cpi_program.clone(), Operation { signer: signer.clone() });
let result: Euint128 = e_add(cpi_ctx, amount_a, amount_b, 0)?;
ctx.accounts.account.total = result;
// Grant decryption access to owner via remaining_accounts
if ctx.remaining_accounts.len() >= 2 {
let allowance_account = &ctx.remaining_accounts[0];
let allowed_address = &ctx.remaining_accounts[1];
let cpi_ctx = CpiContext::new(
cpi_program.clone(),
Allow {
allowance_account: allowance_account.clone(),
signer: signer.clone(),
allowed_address: allowed_address.clone(),
system_program: ctx.accounts.system_program.to_account_info(),
}
);
allow(cpi_ctx, result.0, true, ctx.accounts.account.owner)?;
}
Ok(())
}
#[derive(Accounts)]
pub struct AddBalances<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut)]
pub account: Account<'info, MyAccount>,
/// CHECK: Inco Lightning program
#[account(address = INCO_LIGHTNING_ID)]
pub inco_lightning_program: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}
Every operation that produces a new handle requires calling
allow() to grant decryption access. Use the remaining_accounts pattern to pass allowance PDAs and grant access in the same transaction. See Access Control for simulation patterns on the client side.Comparison Operations
Comparison operations returnResult<Ebool>.
| Function | Description | Signature |
|---|---|---|
e_ge | Greater than or equal | (CpiContext, Euint128, Euint128, u8) -> Result<Ebool> |
e_gt | Greater than | (CpiContext, Euint128, Euint128, u8) -> Result<Ebool> |
e_le | Less than or equal | (CpiContext, Euint128, Euint128, u8) -> Result<Ebool> |
e_lt | Less than | (CpiContext, Euint128, Euint128, u8) -> Result<Ebool> |
e_eq | Equal | (CpiContext, Euint128, Euint128, u8) -> Result<Ebool> |
Bitwise Operations
| Function | Description | Signature |
|---|---|---|
e_and | Bitwise AND | (CpiContext, Euint128, Euint128, u8) -> Result<Euint128> |
e_or | Bitwise OR | (CpiContext, Euint128, Euint128, u8) -> Result<Euint128> |
e_not | Bitwise NOT | (CpiContext, Euint128, u8) -> Result<Euint128> |
e_shl | Shift left | (CpiContext, Euint128, Euint128, u8) -> Result<Euint128> |
e_shr | Shift right | (CpiContext, Euint128, Euint128, u8) -> Result<Euint128> |
Random Number Generation
| Function | Description | Signature |
|---|---|---|
e_rand | Generate encrypted random number | (CpiContext, u8) -> Result<Euint128> |
Control Flow
e_select
Conditional selection on encrypted values without revealing the condition.e_select(ctx: CpiContext, condition: Ebool, if_true: Euint128, if_false: Euint128, scalar_byte: u8) -> Result<Euint128>
if_true when condition is encrypted true, if_false otherwise.
Example: Confidential transfer with balance check and allowance
use inco_lightning::cpi::accounts::{Operation, Allow};
use inco_lightning::cpi::{e_add, e_sub, e_ge, e_select, as_euint128, allow};
/// remaining_accounts:
/// [0] source_allowance_account (mut)
/// [1] source_owner_address (readonly)
/// [2] dest_allowance_account (mut)
/// [3] dest_owner_address (readonly)
pub fn confidential_transfer<'info>(
ctx: Context<'_, '_, '_, 'info, Transfer<'info>>,
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 cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
let zero = as_euint128(cpi_ctx, 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;
// Grant allowance to source owner for their new balance
if ctx.remaining_accounts.len() >= 2 {
let cpi_ctx = CpiContext::new(
inco.clone(),
Allow {
allowance_account: ctx.remaining_accounts[0].clone(),
signer: signer.clone(),
allowed_address: ctx.remaining_accounts[1].clone(),
system_program: ctx.accounts.system_program.to_account_info(),
}
);
allow(cpi_ctx, new_source_balance.0, true, ctx.accounts.source.owner)?;
}
// Grant allowance to destination owner for their new balance
if ctx.remaining_accounts.len() >= 4 {
let cpi_ctx = CpiContext::new(
inco.clone(),
Allow {
allowance_account: ctx.remaining_accounts[2].clone(),
signer: signer.clone(),
allowed_address: ctx.remaining_accounts[3].clone(),
system_program: ctx.accounts.system_program.to_account_info(),
}
);
allow(cpi_ctx, new_dest_balance.0, true, ctx.accounts.destination.owner)?;
}
Ok(())
}
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut)]
pub source: Account<'info, TokenAccount>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
/// CHECK: Inco Lightning program
#[account(address = INCO_LIGHTNING_ID)]
pub inco_lightning_program: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}
This pattern ensures:
- Balance check happens on encrypted values (no information leakage)
- Transfer is atomic: either full amount transfers or nothing
- No negative balances possible
- Both source and destination owners can decrypt their new balances
Access Control Functions
Manage decryption permissions for handles.| Function | Description | Signature |
|---|---|---|
allow | Grant/revoke decryption access | (CpiContext<Allow>, u128, bool, Pubkey) -> Result<()> |
is_allowed | Check if address has decryption permission | (CpiContext<IsAllowed>, u128) -> Result<bool> |
allow
Grant or revoke decryption access for a specific address.pub fn grant_decrypt_access(
ctx: Context<GrantAccess>,
handle: u128,
allowed_address: Pubkey,
) -> Result<()> {
let cpi_ctx = CpiContext::new(
ctx.accounts.inco_lightning_program.to_account_info(),
Allow {
allowance_account: ctx.accounts.allowance_account.to_account_info(),
signer: ctx.accounts.authority.to_account_info(),
allowed_address: ctx.accounts.allowed_address.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
},
);
allow(cpi_ctx, handle, true, allowed_address)?;
Ok(())
}
is_allowed
Check if an address has decryption permission for a handle.pub fn check_access(
ctx: Context<CheckAccess>,
handle: u128,
) -> Result<bool> {
let cpi_ctx = CpiContext::new(
ctx.accounts.inco_lightning_program.to_account_info(),
IsAllowed {
allowance_account: ctx.accounts.allowance_account.to_account_info(),
allowed_address: ctx.accounts.allowed_address.to_account_info(),
},
);
is_allowed(cpi_ctx, handle)
}
Attestation Verification
is_validsignature
Verify Ed25519 signatures for attested decryption results.is_validsignature(
ctx: CpiContext<VerifySignature>,
expected_signature_count: u8,
handles: Option<Vec<Vec<u8>>>,
plaintext_values: Option<Vec<Vec<u8>>>,
) -> Result<Vec<SignatureVerificationResult>>
Example
pub fn verify_decryption(
ctx: Context<VerifyDecryption>,
handles: Vec<Vec<u8>>,
plaintext_values: Vec<Vec<u8>>,
) -> Result<()> {
let cpi_ctx = CpiContext::new(
ctx.accounts.inco_lightning_program.to_account_info(),
VerifySignature {
instructions: ctx.accounts.instructions.to_account_info(),
signer: ctx.accounts.authority.to_account_info(),
},
);
// Verify Ed25519 signatures from previous instruction
// The covalidator signs hash(handle + plaintext_value)
let results = is_validsignature(
cpi_ctx,
1, // expected signature count
Some(handles), // handles being verified
Some(plaintext_values), // claimed plaintext values
)?;
// If we get here, signatures are valid
// Use the verified plaintext values
Ok(())
}
The last parameter (
scalar_byte) in operation calls identifies whether the left-hand side operand is ciphertext (0) or plaintext (1). Always pass 0 when working with encrypted handles.