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