Solana Program Integration

Step-by-step guide for integrating ZKVAULT into your Solana programs with composable privacy.

Integration Patterns

ZKVAULT supports three integration patterns depending on your use case and architecture requirements.

1. Direct Client Integration

Call ZKVAULT from your frontend client - simplest approach for most applications.

import { useAnchorWallet } from '@solana/wallet-adapter-react';
import { Program } from '@coral-xyz/anchor';
import * as zkvault from '@zkvault/sdk';

function MyComponent() {
  const wallet = useAnchorWallet();
  
  const submitProof = async () => {
    // Generate proof off-chain
    const proof = await zkvault.prover.build(circuit).prove(witness);
    
    // Submit to ZKVAULT
    const tx = await zkvault.program.methods
      .submitProof(proof.bytes, proof.publicInputs, metadata)
      .accounts({
        vault: vaultPda,
        prover: wallet.publicKey,
      })
      .rpc();
    
    console.log("Proof submitted:", tx);
  };
  
  return <button onClick={submitProof}>Submit Proof</button>;
}

2. Cross-Program Invocation (CPI)

Invoke ZKVAULT from within your Solana program for atomic privacy operations.

use anchor_lang::prelude::*;
use zkvault::cpi::accounts::VerifyProof;
use zkvault::program::Zkvault;
use zkvault::{self, ProofSubmission, Vault};

#[program]
pub mod my_program {
    pub fn private_transfer(
        ctx: Context<PrivateTransfer>,
        encrypted_amount: Vec<u8>,
        range_proof: Vec<u8>,
        nullifier: [u8; 32],
    ) -> Result<()> {
        // Verify range proof via CPI to ZKVAULT
        let cpi_accounts = VerifyProof {
            vault: ctx.accounts.vault.to_account_info(),
            proof_submission: ctx.accounts.proof_submission.to_account_info(),
            authority: ctx.accounts.signer.to_account_info(),
            system_program: ctx.accounts.system_program.to_account_info(),
        };
        
        let cpi_ctx = CpiContext::new(
            ctx.accounts.zkvault_program.to_account_info(),
            cpi_accounts,
        );
        
        zkvault::cpi::verify_proof(cpi_ctx, range_proof, vec![nullifier[0] as u64])?;
        
        // Continue with private transfer logic
        msg!("Range proof verified, executing transfer");
        // ...
        
        Ok(())
    }
}

#[derive(Accounts)]
pub struct PrivateTransfer<'info> {
    #[account(mut)]
    pub vault: Account<'info, Vault>,
    
    #[account(mut)]
    pub proof_submission: Account<'info, ProofSubmission>,
    
    #[account(mut)]
    pub signer: Signer<'info>,
    
    pub zkvault_program: Program<'info, Zkvault>,
    pub system_program: Program<'info, System>,
}

3. Program-to-Program Messaging

Use Solana's account data for asynchronous proof verification workflows.

#[program]
pub mod async_verifier {
    pub fn initiate_verification(ctx: Context<InitiateVerification>) -> Result<()> {
        // Store verification request
        let request = &mut ctx.accounts.verification_request;
        request.vault = ctx.accounts.vault.key();
        request.proof_id = ctx.accounts.proof_submission.key();
        request.callback_program = ctx.accounts.callback_program.key();
        request.status = RequestStatus::Pending;
        
        emit!(VerificationRequested {
            vault: request.vault,
            proof_id: request.proof_id,
        });
        
        Ok(())
    }
    
    pub fn process_verification_callback(
        ctx: Context<ProcessCallback>,
        result: bool,
    ) -> Result<()> {
        // Called by ZKVAULT after verification
        let request = &mut ctx.accounts.verification_request;
        request.status = if result {
            RequestStatus::Verified
        } else {
            RequestStatus::Failed
        };
        request.result = Some(result);
        
        // Execute application logic based on result
        if result {
            msg!("Proof verified, executing business logic");
            // ...
        }
        
        Ok(())
    }
}

#[account]
pub struct VerificationRequest {
    pub vault: Pubkey,
    pub proof_id: Pubkey,
    pub callback_program: Pubkey,
    pub status: RequestStatus,
    pub result: Option<bool>,
}

Use Case Examples

Private Token Transfers

Integrate ZKVAULT for confidential token amounts using range proofs.

#[program]
pub mod private_token {
    pub fn transfer(
        ctx: Context<PrivateTokenTransfer>,
        encrypted_amount: EncryptedValue,
        range_proof: Vec<u8>,
        sender_balance_proof: Vec<u8>,
    ) -> Result<()> {
        // 1. Verify range proof (amount > 0 and < max)
        zkvault::cpi::verify_proof(
            ctx.accounts.zkvault_cpi_context(),
            range_proof,
            vec![], // No public inputs needed
        )?;
        
        // 2. Verify sender has sufficient balance
        zkvault::cpi::verify_proof(
            ctx.accounts.zkvault_cpi_context(),
            sender_balance_proof,
            vec![ctx.accounts.sender.key().to_bytes()[0] as u64],
        )?;
        
        // 3. Update encrypted balances
        let sender_account = &mut ctx.accounts.sender_balance;
        let receiver_account = &mut ctx.accounts.receiver_balance;
        
        sender_account.encrypted_balance = encrypted_amount.sender_new_balance;
        receiver_account.encrypted_balance = encrypted_amount.receiver_new_balance;
        
        emit!(PrivateTransferExecuted {
            sender: ctx.accounts.sender.key(),
            receiver: ctx.accounts.receiver.key(),
            nullifier: encrypted_amount.nullifier,
        });
        
        Ok(())
    }
}

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct EncryptedValue {
    pub sender_new_balance: [u8; 32],
    pub receiver_new_balance: [u8; 32],
    pub nullifier: [u8; 32],
}

Private Voting

Build anonymous voting systems with vote validation proofs.

#[program]
pub mod private_voting {
    pub fn cast_vote(
        ctx: Context<CastVote>,
        vote_commitment: [u8; 32],
        eligibility_proof: Vec<u8>,
        double_vote_nullifier: [u8; 32],
    ) -> Result<()> {
        // Verify eligibility proof (voter is in eligible set)
        zkvault::cpi::verify_proof(
            ctx.accounts.zkvault_cpi_context(),
            eligibility_proof,
            vec![ctx.accounts.election.merkle_root[0] as u64],
        )?;
        
        // Check nullifier hasn't been used
        require!(
            !ctx.accounts.election.used_nullifiers.contains(&double_vote_nullifier),
            VotingError::DoubleVote
        );
        
        // Record vote
        let election = &mut ctx.accounts.election;
        election.votes.push(vote_commitment);
        election.used_nullifiers.push(double_vote_nullifier);
        election.total_votes += 1;
        
        emit!(VoteCast {
            election: election.key(),
            commitment: vote_commitment,
            nullifier: double_vote_nullifier,
        });
        
        Ok(())
    }
    
    pub fn tally_votes(ctx: Context<TallyVotes>, tally_proof: Vec<u8>) -> Result<()> {
        // Verify correct tallying with ZK proof
        let election = &ctx.accounts.election;
        
        zkvault::cpi::verify_proof(
            ctx.accounts.zkvault_cpi_context(),
            tally_proof,
            vec![
                election.total_votes,
                election.merkle_root[0] as u64,
            ],
        )?;
        
        election.status = ElectionStatus::Tallied;
        
        Ok(())
    }
}

Confidential DeFi

Create privacy-preserving DeFi protocols with encrypted positions.

#[program]
pub mod private_defi {
    pub fn open_position(
        ctx: Context<OpenPosition>,
        encrypted_collateral: [u8; 32],
        encrypted_debt: [u8; 32],
        collateralization_proof: Vec<u8>,
    ) -> Result<()> {
        // Verify collateralization ratio without revealing amounts
        // Proof: collateral / debt >= min_ratio (e.g., 150%)
        zkvault::cpi::verify_proof(
            ctx.accounts.zkvault_cpi_context(),
            collateralization_proof,
            vec![ctx.accounts.protocol.min_collateral_ratio],
        )?;
        
        let position = &mut ctx.accounts.position;
        position.owner = ctx.accounts.user.key();
        position.encrypted_collateral = encrypted_collateral;
        position.encrypted_debt = encrypted_debt;
        position.opened_at = Clock::get()?.unix_timestamp;
        
        Ok(())
    }
    
    pub fn liquidate_position(
        ctx: Context<LiquidatePosition>,
        undercollateralization_proof: Vec<u8>,
    ) -> Result<()> {
        // Verify position is undercollateralized
        // Proof: collateral / debt < liquidation_threshold
        zkvault::cpi::verify_proof(
            ctx.accounts.zkvault_cpi_context(),
            undercollateralization_proof,
            vec![ctx.accounts.protocol.liquidation_threshold],
        )?;
        
        // Execute liquidation
        let position = &mut ctx.accounts.position;
        position.status = PositionStatus::Liquidated;
        
        emit!(PositionLiquidated {
            position: position.key(),
            liquidator: ctx.accounts.liquidator.key(),
        });
        
        Ok(())
    }
}

Testing Integration

Local Testing Setup

// tests/integration.ts
import * as anchor from '@coral-xyz/anchor';
import { Program } from '@coral-xyz/anchor';
import { Zkvault } from '../target/types/zkvault';
import * as zkvault from '@zkvault/sdk';

describe('ZKVAULT Integration', () => {
  const provider = anchor.AnchorProvider.local();
  anchor.setProvider(provider);
  
  const zkprogram = anchor.workspace.Zkvault as Program<Zkvault>;
  const myprogram = anchor.workspace.MyProgram as Program<MyProgram>;
  
  it('CPI proof verification', async () => {
    // Setup vault
    const vaultKeypair = anchor.web3.Keypair.generate();
    await zkprogram.methods
      .initVault(vaultId, proofProtocol, vkHash, maxSize, accessControl)
      .accounts({ vault: vaultKeypair.publicKey })
      .rpc();
    
    // Generate proof
    const circuit = zkvault.circuit.fromFile('./circuits/test.circom');
    const proof = await zkvault.prover.build(circuit).prove(witness);
    
    // Call your program with CPI to ZKVAULT
    await myprogram.methods
      .myMethodWithCPI(proof.bytes, proof.publicInputs)
      .accounts({
        vault: vaultKeypair.publicKey,
        zkprogram: zkprogram.programId,
      })
      .rpc();
      
    // Verify state changes
    const vaultAccount = await zkprogram.account.vault.fetch(
      vaultKeypair.publicKey
    );
    assert.equal(vaultAccount.totalProofsVerified, 1);
  });
});

Deployment Checklist

Pre-deployment

  • ✓ Audit circuit implementations
  • ✓ Verify verification key generation
  • ✓ Test CPI interactions with ZKVAULT devnet
  • ✓ Benchmark compute unit usage
  • ✓ Security audit of access control logic

Deployment

# Deploy to devnet first
anchor build
anchor deploy --provider.cluster devnet

# Upload verification keys
zkvault vk upload \
  --vault <VAULT_ADDRESS> \
  --vk-file ./keys/verification_key.json \
  --protocol groth16 \
  --network devnet

# Verify integration
anchor test --provider.cluster devnet

Post-deployment

  • ✓ Monitor verification success rates
  • ✓ Track compute unit usage patterns
  • ✓ Set up proof verification alerts
  • ✓ Configure off-chain indexing
  • ✓ Document integration for users

Common Integration Patterns

Proof Batching

// Batch multiple proofs in one transaction
pub fn verify_batch(
    ctx: Context<VerifyBatch>,
    proofs: Vec<Vec<u8>>,
    public_inputs: Vec<Vec<u64>>,
) -> Result<()> {
    require!(proofs.len() == public_inputs.len(), InvalidBatch);
    require!(proofs.len() <= 10, BatchTooLarge); // CU limit
    
    for (proof, inputs) in proofs.iter().zip(public_inputs.iter()) {
        zkvault::cpi::verify_proof(
            ctx.accounts.zkvault_cpi_context(),
            proof.clone(),
            inputs.clone(),
        )?;
    }
    
    Ok(())
}

Proof Caching

// Cache verification results to avoid re-verification
#[account]
pub struct ProofCache {
    pub proof_hash: [u8; 32],
    pub verification_result: bool,
    pub verified_at: i64,
    pub expires_at: i64,
}

pub fn verify_with_cache(
    ctx: Context<VerifyWithCache>,
    proof: Vec<u8>,
) -> Result<bool> {
    let proof_hash = hash(&proof);
    
    // Check cache
    if let Some(cached) = ctx.accounts.cache.get(&proof_hash) {
        let now = Clock::get()?.unix_timestamp;
        if cached.expires_at > now {
            return Ok(cached.verification_result);
        }
    }
    
    // Verify and cache
    zkvault::cpi::verify_proof(ctx.accounts.cpi_ctx(), proof, inputs)?;
    
    // Update cache
    ctx.accounts.cache.insert(proof_hash, ProofCache {
        proof_hash,
        verification_result: true,
        verified_at: now,
        expires_at: now + 3600, // 1 hour
    });
    
    Ok(true)
}