inco-lightning

Crates.ioinco-lightning
lib.rsinco-lightning
version0.1.4
created_at2026-01-08 13:41:18.85338+00
updated_at2026-01-08 14:47:16.788689+00
descriptionSDK for Inco Lightning
homepage
repository
max_upload_size
id2030241
size93,146
muskbuster (muskbuster)

documentation

README

Inco Lightning

Program ID: 5sjEbPiqgZrYwR31ahR6Uk9wf5awoX61YGg7jExQSwaj

Installation

Add to your Cargo.toml:

[dependencies]
inco-lightning = { version = "0.1.0", features = ["cpi"] }

Setup

1. Add the program to your Anchor.toml

[programs.devnet]
inco_lightning = "5sjEbPiqgZrYwR31ahR6Uk9wf5awoX61YGg7jExQSwaj"

2. Import the crate in your program

use anchor_lang::prelude::*;
use inco_lightning::cpi::accounts::Operation;
use inco_lightning::cpi::{e_add, e_sub, e_ge, e_select, new_euint128};
use inco_lightning::types::{Euint128, Ebool};
use inco_lightning::ID as INCO_LIGHTNING_ID;

3. Add Inco Lightning program to your account struct

#[derive(Accounts)]
pub struct MyInstruction<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,

    /// CHECK: Inco Lightning program for encrypted operations
    #[account(address = INCO_LIGHTNING_ID)]
    pub inco_lightning_program: AccountInfo<'info>,
}

Concepts Guide

Introduction

Inco Lightning provides encrypted computation primitives for Solana programs. It enables confidential smart contracts where sensitive data remains encrypted throughout computation. The covalidator network processes encrypted operations off-chain while maintaining cryptographic guarantees.

Key benefits:

  • Privacy: Values remain encrypted on-chain
  • Composability: Programs can perform arithmetic and logic on encrypted values
  • Verification: Results can be decrypted with cryptographic attestation

Handles

A handle is a 128-bit reference to an encrypted value stored off-chain by the covalidator network.

use inco_lightning::types::{Euint128, Ebool};

// Euint128 - handle to an encrypted unsigned 128-bit integer
pub struct Euint128(pub u128);

// Ebool - handle to an encrypted boolean
pub struct Ebool(pub u128);

Handles are deterministically derived from operations, so the same operation with the same inputs always produces the same handle. This allows programs to store and reference encrypted values without storing the actual ciphertext on-chain.

// Store a handle in your account
#[account]
pub struct ConfidentialAccount {
    pub balance: Euint128,      // Handle to encrypted balance
    pub is_active: Ebool,       // Handle to encrypted boolean
}

Inputs

To create an encrypted value, use new_euint128 or new_ebool with client-encrypted ciphertext.

Creating encrypted values

pub fn deposit(
    ctx: Context<Deposit>,
    ciphertext: Vec<u8>,  // Client-encrypted amount
    input_type: u8,
) -> Result<()> {
    // Create CPI context
    let cpi_ctx = CpiContext::new(
        ctx.accounts.inco_lightning_program.to_account_info(),
        Operation {
            signer: ctx.accounts.authority.to_account_info(),
        },
    );

    // Create encrypted handle from ciphertext
    let encrypted_amount: Euint128 = new_euint128(cpi_ctx, ciphertext, input_type)?;

    // Store the handle
    ctx.accounts.vault.balance = encrypted_amount;

    Ok(())
}

Trivial Encryption

Use as_euint128 and as_ebool to create encrypted handles from plaintext values. This is called "trivial encryption" because the value is known - useful for constants, thresholds, or initialization.

Note: Trivially encrypted values are deterministic - anyone can compute the same handle from the same plaintext. Use new_euint128 with client-encrypted ciphertext for truly private values.

Functions

Function Description Signature
as_euint128 Convert plaintext u128 to encrypted handle (u128) -> Euint128
as_ebool Convert plaintext bool to encrypted handle (bool) -> Ebool

Example: Creating a zero balance

pub fn initialize_account(ctx: Context<Initialize>) -> Result<()> {
    let cpi_ctx = CpiContext::new(
        ctx.accounts.inco_lightning_program.to_account_info(),
        Operation {
            signer: ctx.accounts.authority.to_account_info(),
        },
    );

    // Create encrypted zero for initial balance
    let zero_balance: Euint128 = as_euint128(cpi_ctx, 0)?;

    ctx.accounts.account.balance = zero_balance;
    Ok(())
}

Example: Creating a threshold for comparison

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

    // Create encrypted threshold (e.g., 100 tokens with 6 decimals)
    let cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
    let min_threshold: Euint128 = as_euint128(cpi_ctx, 100_000_000)?;

    // Compare balance against threshold
    let cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
    let meets_minimum: Ebool = e_ge(cpi_ctx, balance, min_threshold, 0)?;

    // Use result in e_select or store it
    ctx.accounts.account.meets_minimum = meets_minimum;
    Ok(())
}

Random Values

Use e_rand to generate cryptographically secure random encrypted values.

Function

Function Description Signature
e_rand Generate random encrypted value (scalar_byte) -> Euint128

Example: Generate random number

pub fn generate_random(ctx: Context<GenerateRandom>) -> Result<()> {
    let cpi_ctx = CpiContext::new(
        ctx.accounts.inco_lightning_program.to_account_info(),
        Operation {
            signer: ctx.accounts.authority.to_account_info(),
        },
    );

    // Generate random encrypted value
    let random_value: Euint128 = e_rand(cpi_ctx, 0)?;

    ctx.accounts.game.random_seed = random_value;
    Ok(())
}

Operations

Inco Lightning provides arithmetic and comparison operations on encrypted values.

Arithmetic Operations

Function Description Signature
e_add Addition (Euint128, Euint128) -> Euint128
e_sub Subtraction (Euint128, Euint128) -> Euint128
e_mul Multiplication (Euint128, Euint128) -> Euint128
e_rem Remainder (Euint128, Euint128) -> Euint128

Comparison Operations

Function Description Signature
e_ge Greater than or equal (Euint128, Euint128) -> Ebool
e_gt Greater than (Euint128, Euint128) -> Ebool
e_le Less than or equal (Euint128, Euint128) -> Ebool
e_lt Less than (Euint128, Euint128) -> Ebool
e_eq Equal (Euint128, Euint128) -> Ebool

Bitwise Operations

Function Description Signature
e_and Bitwise AND (Euint128, Euint128) -> Euint128
e_or Bitwise OR (Euint128, Euint128) -> Euint128
e_not Bitwise NOT (Euint128) -> Euint128
e_shl Shift left (Euint128, Euint128) -> Euint128
e_shr Shift right (Euint128, Euint128) -> Euint128

Example: Encrypted addition

pub fn add_balances(
    ctx: Context<AddBalances>,
    amount_a: Euint128,
    amount_b: Euint128,
) -> Result<()> {
    let cpi_ctx = CpiContext::new(
        ctx.accounts.inco_lightning_program.to_account_info(),
        Operation {
            signer: ctx.accounts.authority.to_account_info(),
        },
    );

    // Add two encrypted values
    let result: Euint128 = e_add(cpi_ctx, amount_a, amount_b, 0)?;

    ctx.accounts.account.total = result;
    Ok(())
}

Control Flow

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

Signature

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(())
}

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

Access Control

Use allow and is_allowed to manage decryption permissions for handles.

Granting access

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(())
}

Checking access

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)
}

Verifying Attestations

The is_validsignature function verifies Ed25519 signatures for attested decryption results.

On-chain verification

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(())
}

Best Practices

1. Use e_select for conditional logic

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)?;

2. Handle initialization

Always check if handles are initialized before operations:

if !handle.is_initialized() {
    // Handle is zero/uninitialized
    return Err(ErrorCode::UninitializedHandle.into());
}

3. Access control

Grant minimal decryption permissions:

// Only allow the account owner to decrypt their balance
allow(ctx, balance_handle, true, owner_pubkey)?;

4. Store handles, not ciphertext

Handles are 16 bytes. Store them on-chain; ciphertext lives off-chain:

#[account]
pub struct Vault {
    pub owner: Pubkey,           // 32 bytes
    pub balance: Euint128,       // 16 bytes (handle only!)
    pub bump: u8,
}
Commit count: 0

cargo fmt