Integration Best Practices

Production-ready patterns and recommendations for integrating ZKVAULT into your Solana applications.

Architecture Patterns

Client-Side vs Server-Side Proving

Choose the right proving architecture based on your security and performance requirements.

AspectClient-SideServer-Side
SecurityHigher (witness never leaves device)Requires trusted server
PerformanceVaries by deviceConsistent, optimized
UXMay block UI during provingAsync, non-blocking
CostFree (user's compute)Server infrastructure costs
// Client-side proving (recommended for sensitive data)
async function clientSideProve(privateData: any) {
  const circuit = await zkvault.circuit.load('myCircuit');
  const witness = circuit.calculateWitness(privateData);
  
  // Proving happens in browser
  const proof = await zkvault.prover.prove(circuit, witness);
  
  // Only proof and public inputs sent to chain
  return proof;
}

// Server-side proving (for complex circuits)
async function serverSideProve(publicData: any) {
  const response = await fetch('/api/prove', {
    method: 'POST',
    body: JSON.stringify({ publicData }),
  });
  
  const { proof, publicInputs } = await response.json();
  return { proof, publicInputs };
}

// Hybrid approach: witness generation client-side, proving server-side
async function hybridProve(privateData: any) {
  // Generate witness locally (keeps private data private)
  const circuit = await zkvault.circuit.load('myCircuit');
  const witness = circuit.calculateWitness(privateData);
  
  // Send only witness to server for proving
  const response = await fetch('/api/prove-from-witness', {
    method: 'POST',
    body: JSON.stringify({ witness: witness.toHex() }),
  });
  
  return await response.json();
}

Error Handling

Robust Error Recovery

import { ZKVaultError, ErrorCode } from '@zkvault/sdk';

async function submitProofWithRetry(
  proof: Proof,
  maxRetries = 3
): Promise<string> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const signature = await zkvault.program.methods
        .submitProof(proof.bytes, proof.publicInputs, metadata)
        .accounts({ vault: vaultPda })
        .rpc();
      
      return signature;
      
    } catch (error) {
      if (error instanceof ZKVaultError) {
        switch (error.code) {
          case ErrorCode.INVALID_PROOF_FORMAT:
            // Don't retry - proof is malformed
            throw error;
            
          case ErrorCode.INSUFFICIENT_COMPUTE_UNITS:
            // Retry with higher CU limit
            console.log(`Attempt ${attempt + 1}: Insufficient CU, retrying...`);
            await increaseComputeUnits();
            continue;
            
          case ErrorCode.NETWORK_ERROR:
            // Retry with exponential backoff
            const delay = Math.pow(2, attempt) * 1000;
            console.log(`Attempt ${attempt + 1}: Network error, retrying in ${delay}ms...`);
            await sleep(delay);
            continue;
            
          case ErrorCode.VAULT_FROZEN:
            // Don't retry - vault is frozen
            throw new Error('Vault is frozen. Contact administrator.');
            
          default:
            throw error;
        }
      }
      
      // Unknown error
      if (attempt === maxRetries - 1) throw error;
    }
  }
  
  throw new Error('Max retries exceeded');
}

Graceful Degradation

// Fallback when ZK proof generation fails
async function submitWithFallback(data: SensitiveData) {
  try {
    // Try ZK proof flow
    const proof = await generateProof(data);
    await submitProof(proof);
    return { method: 'zk-proof', status: 'success' };
    
  } catch (error) {
    console.warn('ZK proof failed, falling back to trusted oracle:', error);
    
    // Fallback: Submit via trusted oracle with cryptographic attestation
    const attestation = await trustedOracle.attest(data);
    await submitAttestation(attestation);
    return { method: 'oracle-attestation', status: 'fallback' };
  }
}

Performance Optimization

Proof Caching Strategy

import { LRUCache } from 'lru-cache';

// Cache proofs by input hash
const proofCache = new LRUCache<string, Proof>({
  max: 100,
  ttl: 1000 * 60 * 60, // 1 hour
  updateAgeOnGet: true,
});

async function getOrGenerateProof(input: any): Promise<Proof> {
  const inputHash = hashInput(input);
  
  // Check cache first
  const cached = proofCache.get(inputHash);
  if (cached) {
    console.log('Proof cache hit');
    return cached;
  }
  
  // Generate new proof
  console.log('Proof cache miss, generating...');
  const proof = await zkvault.prover.prove(circuit, input);
  
  // Cache for future use
  proofCache.set(inputHash, proof);
  
  return proof;
}

// Pre-generate common proofs
async function warmupCache(commonInputs: any[]) {
  console.log('Warming up proof cache...');
  await Promise.all(
    commonInputs.map(input => getOrGenerateProof(input))
  );
  console.log(`Cache warmed up with ${commonInputs.length} proofs`);
}

Circuit Optimization

// BAD: Inefficient constraint usage
template Inefficient() {
    signal input a;
    signal input b;
    signal output c;
    
    signal intermediate1 <== a * a;
    signal intermediate2 <== b * b;
    signal intermediate3 <== intermediate1 + intermediate2;
    c <== intermediate3 * intermediate3; // Many intermediate signals
}

// GOOD: Optimized constraint usage
template Efficient() {
    signal input a;
    signal input b;
    signal output c;
    
    signal a2 <== a * a;
    signal b2 <== b * b;
    c <== (a2 + b2) * (a2 + b2); // Fewer constraints
}

// Use library templates when possible
include "circomlib/comparators.circom";
include "circomlib/bitify.circom";

// These are highly optimized
template OptimizedComparison() {
    signal input a;
    signal input b;
    signal output result;
    
    component lt = LessThan(252);
    lt.in[0] <== a;
    lt.in[1] <== b;
    result <== lt.out;
}

Parallel Processing

// Process multiple proofs in parallel
async function batchGenerateProofs(inputs: any[]): Promise<Proof[]> {
  const workers = navigator.hardwareConcurrency || 4;
  const chunks = chunkArray(inputs, Math.ceil(inputs.length / workers));
  
  const proofChunks = await Promise.all(
    chunks.map(async (chunk) => {
      return Promise.all(
        chunk.map(input => zkvault.prover.prove(circuit, input))
      );
    })
  );
  
  return proofChunks.flat();
}

// Worker-based proving for non-blocking UI
const proverWorker = new Worker('/workers/prover.js');

function proveInWorker(input: any): Promise<Proof> {
  return new Promise((resolve, reject) => {
    proverWorker.postMessage({ type: 'prove', input });
    
    proverWorker.onmessage = (e) => {
      if (e.data.type === 'proof-complete') {
        resolve(e.data.proof);
      } else if (e.data.type === 'error') {
        reject(new Error(e.data.error));
      }
    };
  });
}

Security Best Practices

Input Validation

// Always validate inputs before proving
function validateProofInputs(input: ProofInput): boolean {
  // Check input ranges
  if (input.amount < 0 || input.amount > MAX_AMOUNT) {
    throw new Error('Amount out of valid range');
  }
  
  // Verify nullifiers are unique
  if (usedNullifiers.has(input.nullifier)) {
    throw new Error('Nullifier already used (double-spend attempt)');
  }
  
  // Validate Merkle proof
  if (!verifyMerkleProof(input.merkleProof, input.commitment)) {
    throw new Error('Invalid Merkle proof');
  }
  
  // Check timestamp freshness
  const age = Date.now() - input.timestamp;
  if (age > MAX_PROOF_AGE) {
    throw new Error('Proof inputs too old');
  }
  
  return true;
}

// Sanitize user inputs
function sanitizeInput(userInput: string): bigint {
  // Remove any non-numeric characters
  const cleaned = userInput.replace(/[^0-9]/g, '');
  
  // Convert to bigint and validate range
  const value = BigInt(cleaned);
  
  if (value < 0n || value >= FIELD_MODULUS) {
    throw new Error('Input value out of field range');
  }
  
  return value;
}

Secure Key Management

// Store keys securely in the application
// For client-side: use secure storage
import { SecureStore } from '@zkvault/secure-storage';

async function storeWitness(witness: Witness) {
  // Store encrypted in browser's secure storage
  await SecureStore.set('witness', witness, {
    encryption: 'AES-256-GCM',
    authentication: true,
  });
}

async function getWitness(): Promise<Witness> {
  return await SecureStore.get('witness');
}

// Clear sensitive data after use
function clearSensitiveData() {
  // Overwrite memory before garbage collection
  if (witness) {
    witness.data.fill(0);
    witness = null;
  }
}

// Use secure keypair storage
const keypair = await loadKeypairFromSecureStorage();

Access Control

// Implement multi-layer access control
async function submitProofWithAccessControl(
  proof: Proof,
  user: PublicKey
) {
  // 1. Verify user has permission
  const hasPermission = await checkUserPermission(user, 'submit_proof');
  if (!hasPermission) {
    throw new Error('Insufficient permissions');
  }
  
  // 2. Rate limiting
  const rateLimitOk = await checkRateLimit(user);
  if (!rateLimitOk) {
    throw new Error('Rate limit exceeded');
  }
  
  // 3. Verify proof format
  const isValidFormat = validateProofFormat(proof);
  if (!isValidFormat) {
    throw new Error('Invalid proof format');
  }
  
  // 4. Check vault access policy
  const vault = await getVault(vaultPda);
  const canAccess = await vault.checkAccess(user);
  if (!canAccess) {
    throw new Error('Vault access denied');
  }
  
  // 5. Submit proof
  return await zkvault.program.methods
    .submitProof(proof.bytes, proof.publicInputs)
    .accounts({ vault: vaultPda, authority: user })
    .rpc();
}

Testing Strategies

Comprehensive Test Coverage

import { expect } from 'chai';
import { ZKVaultTestHarness } from '@zkvault/testing';

describe('ZK Proof Integration', () => {
  let harness: ZKVaultTestHarness;
  
  before(async () => {
    harness = await ZKVaultTestHarness.setup({
      network: 'localnet',
      circuits: ['./circuits/my_circuit.circom'],
    });
  });
  
  it('generates valid proof for correct inputs', async () => {
    const input = { a: 5, b: 10, c: 50 };
    const proof = await harness.prove('my_circuit', input);
    
    const isValid = await harness.verify(proof);
    expect(isValid).to.be.true;
  });
  
  it('rejects invalid proofs', async () => {
    const input = { a: 5, b: 10, c: 99 }; // Wrong output
    
    await expect(
      harness.prove('my_circuit', input)
    ).to.be.rejectedWith('Constraint not satisfied');
  });
  
  it('handles on-chain verification', async () => {
    const input = { a: 5, b: 10, c: 50 };
    const proof = await harness.prove('my_circuit', input);
    
    const signature = await harness.submitProof(proof);
    const status = await harness.getProofStatus(signature);
    
    expect(status.verified).to.be.true;
    expect(status.result).to.equal('valid');
  });
  
  it('respects compute unit limits', async () => {
    const largeInput = generateLargeInput();
    const proof = await harness.prove('complex_circuit', largeInput);
    
    const estimate = await harness.estimateComputeUnits(proof);
    expect(estimate).to.be.lessThan(400_000); // Under Solana limit
  });
});

Load Testing

// Simulate high-load scenarios
async function loadTest() {
  const concurrency = 50;
  const iterations = 100;
  
  console.log(`Starting load test: ${concurrency} concurrent, ${iterations} iterations`);
  
  const results = {
    success: 0,
    failure: 0,
    totalTime: 0,
  };
  
  for (let i = 0; i < iterations; i++) {
    const batch = Array(concurrency).fill(null).map((_, j) => ({
      a: i * concurrency + j,
      b: (i * concurrency + j) * 2,
    }));
    
    const startTime = Date.now();
    
    const batchResults = await Promise.allSettled(
      batch.map(input => generateAndSubmitProof(input))
    );
    
    const batchTime = Date.now() - startTime;
    results.totalTime += batchTime;
    
    batchResults.forEach(result => {
      if (result.status === 'fulfilled') {
        results.success++;
      } else {
        results.failure++;
        console.error('Batch error:', result.reason);
      }
    });
    
    console.log(`Iteration ${i + 1}/${iterations}: ${batchResults.filter(r => r.status === 'fulfilled').length}/${concurrency} succeeded`);
  }
  
  console.log('Load test complete:');
  console.log(`  Success: ${results.success}`);
  console.log(`  Failure: ${results.failure}`);
  console.log(`  Total time: ${results.totalTime}ms`);
  console.log(`  Avg time per proof: ${results.totalTime / (iterations * concurrency)}ms`);
}

Monitoring and Observability

Instrumentation

import { metrics } from '@zkvault/monitoring';

// Track proof generation metrics
async function instrumentedProve(input: any): Promise<Proof> {
  const startTime = Date.now();
  
  try {
    const proof = await zkvault.prover.prove(circuit, input);
    
    const duration = Date.now() - startTime;
    metrics.histogram('proof_generation_duration_ms', duration);
    metrics.increment('proofs_generated_total', { status: 'success' });
    
    return proof;
    
  } catch (error) {
    metrics.increment('proofs_generated_total', { status: 'error' });
    metrics.increment('proof_errors_total', { error: error.name });
    throw error;
  }
}

// Monitor verification success rates
async function instrumentedVerify(proof: Proof): Promise<boolean> {
  try {
    const result = await zkvault.verify(proof);
    
    metrics.increment('proofs_verified_total', {
      result: result ? 'valid' : 'invalid',
    });
    
    return result;
    
  } catch (error) {
    metrics.increment('verification_errors_total', { error: error.name });
    throw error;
  }
}

Deployment Checklist

Pre-Production Validation

  • ✓ All circuits audited by cryptography experts
  • ✓ Verification keys generated with secure setup ceremony
  • ✓ Integration tests passing on devnet
  • ✓ Load tests demonstrate adequate performance
  • ✓ Error handling covers all failure modes
  • ✓ Monitoring and alerting configured
  • ✓ Rollback procedure documented
  • ✓ Security audit completed
  • ✓ Documentation updated

Go-Live Process

# 1. Deploy to mainnet-beta
anchor build --verifiable
anchor deploy --provider.cluster mainnet-beta

# 2. Upload verification keys
zkvault vk upload \
  --vault <MAINNET_VAULT> \
  --vk-file ./keys/production_vk.json \
  --network mainnet-beta

# 3. Verify integration
npm run test:integration -- --network mainnet-beta

# 4. Enable monitoring
npm run monitoring:enable -- --network mainnet-beta

# 5. Gradual rollout
# Start with 1% of traffic
npm run deploy:canary -- --traffic 0.01

# Monitor for 24 hours, then increase
npm run deploy:canary -- --traffic 0.10
npm run deploy:canary -- --traffic 0.50
npm run deploy:canary -- --traffic 1.00