light-sdk 0.20.0

Rust SDK for ZK Compression on Solana
Documentation

The base library to use Compressed Accounts in Solana on-chain Rust and Anchor programs. Compressed accounts do not require rent-exemption, which makes them suitable for:

  • user owned accounts
  • not for config accounts which are often read
  • not pool accounts, since compressed accounts cannot be used concurrently

Compressed Accounts store state as account hashes in State Merkle trees. and unique addresses in Address Merkle trees. Validity proofs (zero-knowledge proofs) verify that compressed account state exists and new addresses do not exist yet.

  • No rent exemption payment required.
  • Constant 128-byte validity proof per transaction for one or multiple compressed accounts and addresses.
  • Compressed account data is sent as instruction data when accessed.
  • State and address trees are managed by the protocol.

For full program examples, see the Program Examples. For detailed documentation, visit zkcompression.com. For pinocchio solana program development see light-sdk-pinocchio. For rust client development see light-client. For rust program testing see light-program-test. For local test validator with light system programs see Light CLI.

Difference to Light-Accounts (Light-PDA)

Light-PDAs are Solana accounts with sponsored rent-exemption. There is no proof required for interactions with Light-PDAs which makes them suitable for Defi Usecases. Compressed PDAs don't require rent-exemption, but a proof for interactions.

Using Compressed Accounts in Solana Programs

  1. Instruction
    • CompressedAccountMeta - Compressed account metadata structs for instruction data.
    • PackedAccounts - Abstraction to prepare accounts offchain for instructions with compressed accounts.
    • ValidityProof - Proves that new addresses don't exist yet, and compressed account state exists.
  2. Compressed Account in Program
    • LightAccount - Compressed account abstraction similar to anchor Account.
    • derive_address - Create a compressed account address.
    • [LightDiscriminator] - DeriveMacro to derive a compressed account discriminator.
  3. Cpi
    • CpiAccounts - Prepare accounts to cpi the light system program.
    • LightSystemProgramCpi - Prepare instruction data to cpi the light system program.
    • InvokeLightSystemProgram::invoke - Invoke the light system program via cpi.

Client Program Interaction Flow

 ├─ 𝐂𝐥𝐢𝐞𝐧𝐭
 │  ├─ Get ValidityProof from RPC.
 │  ├─ pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo.
 │  ├─ pack CompressedAccountMeta.
 │  ├─ Build Instruction from PackedAccounts and CompressedAccountMetas.
 │  └─ Send transaction.
 │
 └─ 𝐂𝐮𝐬𝐭𝐨𝐦 𝐏𝐫𝐨𝐠𝐫𝐚𝐦
    ├─ CpiAccounts parse accounts consistent with PackedAccounts.
    ├─ LightAccount instantiates from CompressedAccountMeta.
    │
    └─ 𝐋𝐢𝐠𝐡𝐭 𝐒𝐲𝐬𝐭𝐞𝐦 𝐏𝐫𝐨𝐠𝐫𝐚𝐦 𝐂𝐏𝐈
       ├─ Verify ValidityProof.
       ├─ Update State Merkle tree.
       ├─ Update Address Merkle tree.
       └─ Complete atomic state transition.

Features

  1. anchor - Derives AnchorSerialize, AnchorDeserialize instead of BorshSerialize, BorshDeserialize.

  2. v2

    • available on devnet, localnet, and light-program-test.
    • Support for optimized v2 light system program instructions.
  3. cpi-context - Enables CPI context operations for batched compressed account operations.

    • available on devnet, localnet, and light-program-test.
    • Enables the use of one validity proof across multiple cpis from different programs in one instruction.
    • For example spending compressed tokens (owned by the ctoken program) and updating a compressed pda (owned by a custom program) with one validity proof.
    • An instruction should not use more than one validity proof.
    • Requires the v2 feature.

Example Solana program code to create a compressed account

use anchor_lang::{prelude::*, Discriminator};
use light_sdk::{
    account::LightAccount,
    address::v1::derive_address,
    cpi::{v1::LightSystemProgramCpi, CpiAccounts, InvokeLightSystemProgram, LightCpiInstruction},
    derive_light_cpi_signer,
    instruction::{account_meta::CompressedAccountMeta, PackedAddressTreeInfo},
    CpiSigner, LightDiscriminator, LightHasher, ValidityProof,
};

declare_id!("2tzfijPBGbrR5PboyFUFKzfEoLTwdDSHUjANCw929wyt");

pub const LIGHT_CPI_SIGNER: CpiSigner =
    derive_light_cpi_signer!("2tzfijPBGbrR5PboyFUFKzfEoLTwdDSHUjANCw929wyt");

#[program]
pub mod counter {

    use super::*;

    pub fn create_compressed_account<'info>(
        ctx: Context<'_, '_, '_, 'info, CreateCompressedAccount<'info>>,
        proof: ValidityProof,
        address_tree_info: PackedAddressTreeInfo,
        output_tree_index: u8,
    ) -> Result<()> {
        let light_cpi_accounts = CpiAccounts::new(
            ctx.accounts.fee_payer.as_ref(),
            ctx.remaining_accounts,
            crate::LIGHT_CPI_SIGNER,
        )?;

        let (address, address_seed) = derive_address(
            &[b"counter", ctx.accounts.fee_payer.key().as_ref()],
            &address_tree_info.get_tree_pubkey(&light_cpi_accounts)?,
            &crate::ID,
        );
        let new_address_params = address_tree_info
            .into_new_address_params_packed(address_seed);

        let mut my_compressed_account = LightAccount::<CounterAccount>::new_init(
            &crate::ID,
            Some(address),
            output_tree_index,
        );

        my_compressed_account.owner = ctx.accounts.fee_payer.key();

        LightSystemProgramCpi::new_cpi(crate::LIGHT_CPI_SIGNER, proof)
            .with_light_account(my_compressed_account)?
            .with_new_addresses(&[new_address_params])
            .invoke(light_cpi_accounts)
    }
}

#[derive(Accounts)]
pub struct CreateCompressedAccount<'info> {
   #[account(mut)]
   pub fee_payer: Signer<'info>,
}

#[derive(Clone, Debug, Default, LightDiscriminator)]
pub struct CounterAccount {
   pub owner: Pubkey,
   pub counter: u64
}