Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.inco.org/llms.txt

Use this file to discover all available pages before exploring further.

Operations

The Inco Lightning SDK provides arithmetic, comparison, bitwise, and control flow operations on encrypted values.

Arithmetic Operations

All arithmetic operations take a CpiContext, two operands, and a scalar_byte parameter. They return Result<Euint128>.
FunctionDescriptionSignature
e_addAddition(CpiContext, Euint128, Euint128, u8) -> Result<Euint128>
e_subSubtraction(CpiContext, Euint128, Euint128, u8) -> Result<Euint128>
e_mulMultiplication(CpiContext, Euint128, Euint128, u8) -> Result<Euint128>
e_remRemainder(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 return Result<Ebool>.
FunctionDescriptionSignature
e_geGreater than or equal(CpiContext, Euint128, Euint128, u8) -> Result<Ebool>
e_gtGreater than(CpiContext, Euint128, Euint128, u8) -> Result<Ebool>
e_leLess than or equal(CpiContext, Euint128, Euint128, u8) -> Result<Ebool>
e_ltLess than(CpiContext, Euint128, Euint128, u8) -> Result<Ebool>
e_eqEqual(CpiContext, Euint128, Euint128, u8) -> Result<Ebool>

Bitwise Operations

FunctionDescriptionSignature
e_andBitwise AND(CpiContext, Euint128, Euint128, u8) -> Result<Euint128>
e_orBitwise OR(CpiContext, Euint128, Euint128, u8) -> Result<Euint128>
e_notBitwise NOT(CpiContext, Euint128, u8) -> Result<Euint128>
e_shlShift left(CpiContext, Euint128, Euint128, u8) -> Result<Euint128>
e_shrShift right(CpiContext, Euint128, Euint128, u8) -> Result<Euint128>

Random Number Generation

FunctionDescriptionSignature
e_randGenerate 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>
Returns 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.
FunctionDescriptionSignature
allowGrant/revoke decryption access(CpiContext<Allow>, u128, bool, Pubkey) -> Result<()>
is_allowedCheck 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.