Skip to main content

Verification Guide

VecLabs provides cryptographic verification of your vector data using Merkle trees stored on Solana. This ensures data integrity and enables trustless vector search.

Why Verification Matters

Traditional vector databases are black boxes:
  • No proof that your data wasn’t tampered with
  • No way to verify query results
  • Complete trust in the centralized provider
VecLabs solves this with on-chain Merkle roots:

Tamper-Proof

Any change to your vectors changes the Merkle rootStored immutably on Solana

Verifiable

Anyone can verify your data matches on-chain commitmentNo trust required

Transparent

All verification proofs are publicView on Solana Explorer

Decentralized

No central authority controls your dataSelf-sovereign storage

How Verification Works

1

Vectors are hashed

Each vector ID is hashed using SHA-256
2

Merkle tree is built

Vector hashes form leaves of a Merkle treeTree is built bottom-up by hashing pairs
3

Root is stored on Solana

Final Merkle root (32 bytes) is posted to Solana blockchainCosts ~0.001 SOL per update
4

Verification compares roots

Local Merkle root is recomputed from current vectorsCompared against on-chain rootMatch = verified ✓

Using the verify() Method

Basic Verification

import { SolVec } from 'solvec';

const sv = new SolVec({ 
  network: 'devnet',
  walletPath: '~/.config/solana/id.json'
});

const col = sv.collection('my-vectors', { dimensions: 1536 });

// Upsert some vectors
await col.upsert([
  { id: 'vec_1', values: [...] },
  { id: 'vec_2', values: [...] },
]);

// Verify integrity
const result = await col.verify();

console.log(result);
// {
//   verified: true,
//   onChainRoot: 'a3f2c1e4...',
//   localRoot: 'a3f2c1e4...',
//   match: true,
//   vectorCount: 2,
//   solanaExplorerUrl: 'https://explorer.solana.com/address/...',
//   timestamp: 1678901234567
// }
Verification requires a connected wallet. Initialize the client with walletPath to enable verification.

Verification Response

interface VerificationResult {
  verified: boolean;         // Overall verification status
  onChainRoot: string;       // Merkle root from Solana
  localRoot: string;         // Computed Merkle root from local vectors
  match: boolean;            // Whether roots match
  vectorCount: number;       // Number of vectors verified
  solanaExplorerUrl: string; // Link to view proof on Solana Explorer
  timestamp: number;         // Verification timestamp (ms)
}

Understanding Merkle Roots

What is a Merkle Root?

A Merkle root is a single hash that represents the entire collection:
         Root Hash (on-chain)
            /        \
          H(AB)      H(CD)
          /  \        /  \
        H(A) H(B)  H(C) H(D)
         |    |     |    |
       vec_1 vec_2 vec_3 vec_4
Changing any vector changes the root:
  • Add vector → root changes
  • Delete vector → root changes
  • Modify vector → root changes
  • Reorder vectors → root stays same (deterministic sorting)

Merkle Root Properties

Deterministic

Same vectors → same rootOrder-independent

Collision-Resistant

Impossible to find two different sets with same rootSHA-256 security

Compact

32 bytes represents millions of vectorsEfficient on-chain storage

Fast to Compute

O(n log n) constructionEfficient for large collections

Viewing Proofs on Solana Explorer

Every verification includes a Solana Explorer link:
const result = await col.verify();
console.log(result.solanaExplorerUrl);
// https://explorer.solana.com/address/8iLpyegDt8Vx2Q56kdvDJYpmnkTD2VDZvHXXead75Fm7?cluster=devnet
1

Click the Explorer URL

Opens the collection’s Program Derived Address (PDA) on Solana Explorer
2

View account data

See the on-chain Merkle root, vector count, and metadata
3

Check transaction history

View all updates to the collection’s Merkle root
4

Verify independently

Anyone can verify the data matches the on-chain commitment
The collection address is a Program Derived Address (PDA) derived from your wallet and collection name. It’s deterministic and reproducible.

When Verification Fails

Scenario 1: Data Tampering

If local data doesn’t match on-chain commitment:
const result = await col.verify();

if (!result.match) {
  console.error('VERIFICATION FAILED!');
  console.error('Local root:', result.localRoot);
  console.error('On-chain root:', result.onChainRoot);
  console.error('Vector count:', result.vectorCount);
  
  // Possible causes:
  // 1. Local data was modified
  // 2. On-chain update failed
  // 3. Using wrong wallet/network
}

Scenario 2: No Wallet Connected

const sv = new SolVec({ network: 'devnet' }); // No wallet
const col = sv.collection('test', { dimensions: 1536 });

const result = await col.verify();
console.log(result.onChainRoot); // 'wallet-required'
console.log(result.verified);    // false
Verification requires a wallet to fetch the on-chain Merkle root. Connect a wallet with walletPath config.

Verification Workflow

For Applications

import { SolVec } from 'solvec';

const sv = new SolVec({ 
  network: 'mainnet-beta',
  walletPath: process.env.WALLET_PATH
});

const col = sv.collection('production-vectors', { dimensions: 1536 });

// 1. Upsert vectors
await col.upsert(vectors);

// 2. Verify immediately
const result = await col.verify();

if (!result.match) {
  throw new Error('Verification failed! Data integrity compromised.');
}

// 3. Log proof URL
console.log('Verified on Solana:', result.solanaExplorerUrl);

// 4. Continue with queries
const matches = await col.query({ vector: [...], topK: 5 });

For Auditors

Anyone can verify a collection independently:
import { SolVec } from 'solvec';
import { Connection, PublicKey } from '@solana/web3.js';

// 1. Connect to Solana
const connection = new Connection('https://api.mainnet-beta.solana.com');

// 2. Fetch on-chain data
const collectionPDA = new PublicKey('8iLpyegDt8Vx2Q56kdvDJYpmnkTD2VDZvHXXead75Fm7');
const accountInfo = await connection.getAccountInfo(collectionPDA);

// 3. Parse Merkle root
const onChainRoot = parseCollectionData(accountInfo.data);

// 4. Download vector IDs from Shadow Drive
const vectorIds = await fetchVectorIds(collectionPDA);

// 5. Compute local Merkle root
const localRoot = computeMerkleRoot(vectorIds);

// 6. Compare
if (localRoot === onChainRoot) {
  console.log('✓ Collection verified!');
} else {
  console.error('✗ Verification failed!');
}

Cost of Verification

Each on-chain update costs approximately:
  • Devnet: Free (using devnet SOL)
  • Mainnet: 0.001 SOL (0.10at0.10 at 100/SOL)
Batch your upserts to reduce verification costs. The SDK automatically updates the Merkle root after each upsert batch.

Optimizing Verification Costs

// ❌ BAD: 100 on-chain updates
for (const vec of vectors) {
  await col.upsert([vec]); // Updates Merkle root each time
}

// ✅ GOOD: 1 on-chain update
await col.upsert(vectors); // Updates Merkle root once

Verification in Production

Automated Verification

import cron from 'node-cron';

// Verify integrity every hour
cron.schedule('0 * * * *', async () => {
  const result = await col.verify();
  
  if (!result.match) {
    // Alert admin
    await sendAlert({
      severity: 'critical',
      message: 'Vector database verification failed',
      explorerUrl: result.solanaExplorerUrl
    });
  } else {
    console.log(`✓ Verified ${result.vectorCount} vectors`);
  }
});

Health Check Endpoint

app.get('/health/vectors', async (req, res) => {
  const result = await col.verify();
  
  res.json({
    status: result.match ? 'healthy' : 'unhealthy',
    verified: result.verified,
    vectorCount: result.vectorCount,
    merkleRoot: result.onChainRoot,
    explorerUrl: result.solanaExplorerUrl,
    lastChecked: new Date().toISOString()
  });
});

Advanced: Merkle Proofs

VecLabs stores the Merkle root on-chain. The Rust core provides Merkle proof generation and verification:
use solvec_core::merkle::MerkleTree;

let vector_ids = vec!["vec_1".to_string(), "vec_2".to_string(), "vec_3".to_string()];
let tree = MerkleTree::new(&vector_ids);

// Generate proof for a specific vector
if let Some(proof) = tree.generate_proof("vec_2") {
    // Proof contains sibling hashes needed for verification
    let is_valid = proof.verify("vec_2", tree.root());
    println!("Proof valid: {}", is_valid);
}
The TypeScript and Python SDKs currently expose the verify() method which checks the entire collection against the on-chain root. Individual vector proof generation is available in the Rust core API.
Merkle proofs enable:
  • Selective disclosure: Prove a single vector exists without revealing others
  • Light clients: Verify vectors without downloading entire collection
  • Zero-knowledge proofs: Build privacy-preserving applications
Merkle proof generation is coming in a future release. Join our Discord for updates.

Security Considerations

1

Protect your wallet

The wallet that creates the collection is the only one that can update the Merkle root.Store wallet securely.
2

Verify after every update

Always verify after upserting or deleting vectors to ensure on-chain update succeeded.
3

Monitor verification status

Set up automated verification checks in production.
4

Use mainnet for production

Devnet can reset. Use mainnet-beta for production workloads.

Limitations

Current limitations (to be addressed in future releases):
  • ✗ Merkle proofs not yet exposed in SDK
  • ✗ No automatic re-verification on query
  • ✗ Verification requires wallet (read-only verification coming soon)

Next Steps

Performance Tuning

Optimize query speed for large collections

Collections

Learn about collection configuration

TypeScript Guide

Complete TypeScript SDK reference

Python Guide

Complete Python SDK reference

Build docs developers (and LLMs) love