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 root Stored immutably on Solana
Verifiable Anyone can verify your data matches on-chain commitment No trust required
Transparent All verification proofs are public View on Solana Explorer
Decentralized No central authority controls your data Self-sovereign storage
How Verification Works
Vectors are hashed
Each vector ID is hashed using SHA-256
Merkle tree is built
Vector hashes form leaves of a Merkle tree Tree is built bottom-up by hashing pairs
Root is stored on Solana
Final Merkle root (32 bytes) is posted to Solana blockchain Costs ~0.001 SOL per update
Verification compares roots
Local Merkle root is recomputed from current vectors Compared against on-chain root Match = 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
// }
from solvec import SolVec
sv = SolVec(
network = "devnet" ,
wallet = "~/.config/solana/id.json"
)
col = sv.collection( "my-vectors" , dimensions = 1536 )
# Upsert some vectors
col.upsert([
{ "id" : "vec_1" , "values" : [ ... ]},
{ "id" : "vec_2" , "values" : [ ... ]},
])
# Verify integrity
result = col.verify()
print (result.verified) # True
print (result.on_chain_root) # 'a3f2c1e4...'
print (result.local_root) # 'a3f2c1e4...'
print (result.match) # True
print (result.vector_count) # 2
print (result.solana_explorer_url) # 'https://explorer.solana.com/...'
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 root Order-independent
Collision-Resistant Impossible to find two different sets with same root SHA-256 security
Compact 32 bytes represents millions of vectors Efficient on-chain storage
Fast to Compute O(n log n) construction Efficient 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
Click the Explorer URL
Opens the collection’s Program Derived Address (PDA) on Solana Explorer
View account data
See the on-chain Merkle root, vector count, and metadata
Check transaction history
View all updates to the collection’s Merkle root
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.10 a t 0.10 at 0.10 a t 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
Protect your wallet
The wallet that creates the collection is the only one that can update the Merkle root. Store wallet securely.
Verify after every update
Always verify after upserting or deleting vectors to ensure on-chain update succeeded.
Monitor verification status
Set up automated verification checks in production.
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