Overview
The encryption module provides AES-256-GCM encryption for secure vector storage. Keys are derived from Solana wallet public keys, ensuring only the wallet owner can decrypt their vectors.
Use Case
Encryption enables:
Private vector storage : Vectors remain confidential on Shadow Drive
Wallet-based access control : Only wallet owner can decrypt
Batch encryption : Efficient encryption of multiple vectors
Authenticated encryption : Detects tampering
AES-256-GCM Industry-standard authenticated encryption
Wallet-Derived Keys Keys derived from Solana public keys
Constants
const NONCE_SIZE : usize = 12 ; // GCM nonce size in bytes
Core Functions
encrypt_vectors
Encrypt a batch of vectors using AES-256-GCM.
pub fn encrypt_vectors (
vectors : & [ Vec < f32 >],
key : & [ u8 ; 32 ]
) -> Result < Vec < u8 >, SolVecError >
Array of vectors to encrypt. All vectors must have the same dimension.
32-byte encryption key. Should be derived from user’s Solana wallet using derive_key_from_pubkey.
Result
Result<Vec<u8>, SolVecError>
Returns encrypted bytes: nonce (12 bytes) + ciphertext. Returns SolVecError::EncryptionError on failure.
Format:
[nonce: 12 bytes][ciphertext: variable length]
Plaintext structure:
[num_vectors: 8 bytes][dimension: 8 bytes][vector_data: num_vectors * dimension * 4 bytes]
Example:
use solvec_core :: encryption :: encrypt_vectors;
let key = [ 42 u8 ; 32 ]; // In production, use derive_key_from_pubkey
let vectors = vec! [
vec! [ 1.0 f32 , 2.0 , 3.0 , 4.0 ],
vec! [ 5.0 f32 , 6.0 , 7.0 , 8.0 ],
];
let encrypted = encrypt_vectors ( & vectors , & key ) ? ;
println! ( "Encrypted {} bytes" , encrypted . len ());
// Upload encrypted to Shadow Drive
// shadow_drive.upload(encrypted)?;
decrypt_vectors
Decrypt vectors from AES-256-GCM ciphertext.
pub fn decrypt_vectors (
encrypted : & [ u8 ],
key : & [ u8 ; 32 ]
) -> Result < Vec < Vec < f32 >>, SolVecError >
Encrypted data from encrypt_vectors (nonce + ciphertext).
Same 32-byte key used for encryption.
Result
Result<Vec<Vec<f32>>, SolVecError>
Returns decrypted vectors. Returns SolVecError::DecryptionError if:
Ciphertext is too short
Wrong key used
Data has been tampered with
Invalid plaintext format
Example:
use solvec_core :: encryption :: {encrypt_vectors, decrypt_vectors};
let key = [ 42 u8 ; 32 ];
let vectors = vec! [
vec! [ 1.0 f32 , 2.0 , 3.0 ],
vec! [ 4.0 f32 , 5.0 , 6.0 ],
];
// Encrypt
let encrypted = encrypt_vectors ( & vectors , & key ) ? ;
// Decrypt
let decrypted = decrypt_vectors ( & encrypted , & key ) ? ;
assert_eq! ( vectors , decrypted );
derive_key_from_pubkey
Generate a deterministic key from a Solana wallet public key.
pub fn derive_key_from_pubkey ( pubkey_bytes : & [ u8 ; 32 ]) -> [ u8 ; 32 ]
Solana wallet public key (32 bytes).
Derived 32-byte encryption key. Same public key always produces the same key.
Derivation:
key = SHA256 ( "solvec-encryption-key-v1:" || pubkey_bytes )
Example:
use solvec_core :: encryption :: derive_key_from_pubkey;
use solana_sdk :: pubkey :: Pubkey ;
use std :: str :: FromStr ;
// Get user's wallet public key
let pubkey = Pubkey :: from_str ( "7xj9F...abc" ) ? ;
let key = derive_key_from_pubkey ( & pubkey . to_bytes ());
// Use key for encryption
let encrypted = encrypt_vectors ( & vectors , & key ) ? ;
Complete Example: Full Pipeline
use solvec_core :: encryption ::* ;
use solvec_core :: hnsw :: HNSWIndex ;
use solvec_core :: types :: { DistanceMetric , Vector };
fn main () -> Result <(), Box < dyn std :: error :: Error >> {
// Step 1: Create index and add vectors
let mut index = HNSWIndex :: new ( 16 , 200 , DistanceMetric :: Cosine );
let vectors = vec! [
vec! [ 0.9 f32 , 0.1 , 0.0 , 0.0 ],
vec! [ 0.8 f32 , 0.2 , 0.1 , 0.0 ],
vec! [ 0.0 f32 , 0.0 , 0.9 , 0.1 ],
];
for ( i , values ) in vectors . iter () . enumerate () {
index . insert ( Vector :: new ( format! ( "vec_{}" , i ), values . clone ())) ? ;
}
// Step 2: Derive key from Solana wallet
let wallet_pubkey = [ 1 u8 ; 32 ]; // In production: get from Solana SDK
let key = derive_key_from_pubkey ( & wallet_pubkey );
// Step 3: Encrypt vectors for Shadow Drive storage
let encrypted = encrypt_vectors ( & vectors , & key ) ? ;
println! ( "Encrypted {} vectors into {} bytes" , vectors . len (), encrypted . len ());
// Step 4: Upload to Shadow Drive
// shadow_drive_client.upload(encrypted)?;
// Step 5: Later, download and decrypt
// let downloaded = shadow_drive_client.download()?;
let decrypted = decrypt_vectors ( & encrypted , & key ) ? ;
// Step 6: Verify integrity
assert_eq! ( vectors . len (), decrypted . len ());
for ( orig , dec ) in vectors . iter () . zip ( decrypted . iter ()) {
for ( a , b ) in orig . iter () . zip ( dec . iter ()) {
assert! (( a - b ) . abs () < 1 e- 6 , "Decryption mismatch" );
}
}
println! ( "✅ Encryption roundtrip successful" );
Ok (())
}
Integration Test Example
From the VecLabs full pipeline integration test:
// Encrypt vectors for Shadow Drive
let key = [ 0 u8 ; 32 ];
let raw_vectors : Vec < Vec < f32 >> = test_vectors . iter () . map ( | ( _ , v ) | v . clone ()) . collect ();
let encrypted = encrypt_vectors ( & raw_vectors , & key ) . unwrap ();
let decrypted = decrypt_vectors ( & encrypted , & key ) . unwrap ();
assert_eq! ( raw_vectors . len (), decrypted . len ());
for ( orig , dec ) in raw_vectors . iter () . zip ( decrypted . iter ()) {
for ( a , b ) in orig . iter () . zip ( dec . iter ()) {
assert! (
( a - b ) . abs () < 1 e- 6 ,
"Decrypted values must match original"
);
}
}
println! ( "✅ Step 4: Encryption/decryption roundtrip passed" );
println! ( " Encrypted size: {} bytes → Shadow Drive" , encrypted . len ());
Security Properties
Authenticated Encryption AES-256-GCM provides both confidentiality and authenticity
Random Nonces Each encryption uses a unique random nonce (prevents replay attacks)
Key Derivation Deterministic keys from Solana wallets
Tamper Detection Decryption fails if ciphertext is modified
Error Handling
EncryptionError
Thrown when encryption fails:
let result = encrypt_vectors ( & vectors , & key );
match result {
Ok ( encrypted ) => println! ( "Encrypted {} bytes" , encrypted . len ()),
Err ( SolVecError :: EncryptionError ( msg )) => eprintln! ( "Encryption failed: {}" , msg ),
Err ( e ) => eprintln! ( "Other error: {}" , e ),
}
DecryptionError
Thrown when decryption fails:
let result = decrypt_vectors ( & encrypted , & wrong_key );
match result {
Ok ( vectors ) => println! ( "Decrypted {} vectors" , vectors . len ()),
Err ( SolVecError :: DecryptionError ( msg )) => eprintln! ( "Decryption failed: {}" , msg ),
Err ( e ) => eprintln! ( "Other error: {}" , e ),
}
Common causes:
Wrong key : Key doesn’t match the one used for encryption
Corrupted data : Ciphertext has been modified or corrupted
Truncated data : Incomplete encrypted data
Invalid format : Plaintext doesn’t match expected format
Advanced Usage
Multiple Vector Batches
let batch1 = vec! [ vec! [ 1.0 , 2.0 ], vec! [ 3.0 , 4.0 ]];
let batch2 = vec! [ vec! [ 5.0 , 6.0 ], vec! [ 7.0 , 8.0 ]];
let encrypted1 = encrypt_vectors ( & batch1 , & key ) ? ;
let encrypted2 = encrypt_vectors ( & batch2 , & key ) ? ;
// Store with different keys in Shadow Drive
// shadow_drive.upload("batch1", encrypted1)?;
// shadow_drive.upload("batch2", encrypted2)?;
Verification Without Decryption
// Verify ciphertext is valid without full decryption
let is_valid = decrypt_vectors ( & encrypted , & key ) . is_ok ();
if is_valid {
println! ( "✅ Ciphertext is valid" );
} else {
println! ( "❌ Ciphertext is corrupted or wrong key" );
}
Different Keys Per User
let user1_pubkey = [ 1 u8 ; 32 ];
let user2_pubkey = [ 2 u8 ; 32 ];
let key1 = derive_key_from_pubkey ( & user1_pubkey );
let key2 = derive_key_from_pubkey ( & user2_pubkey );
// Each user's vectors encrypted with their own key
let encrypted1 = encrypt_vectors ( & user1_vectors , & key1 ) ? ;
let encrypted2 = encrypt_vectors ( & user2_vectors , & key2 ) ? ;
// user2 cannot decrypt user1's data
assert! ( decrypt_vectors ( & encrypted1 , & key2 ) . is_err ());
Encryption : O(N * D) where N = number of vectors, D = dimension
Decryption : O(N * D)
Key derivation : O(1) (single SHA-256 hash)
Overhead : 12 bytes (nonce) + 16 bytes (GCM tag) + 16 bytes (header)
Best Practices
Always use derive_key_from_pubkey in production
Never reuse keys across different applications (version string prevents this)
Store encrypted data on Shadow Drive , not raw vectors
Verify decryption success before using vectors
Handle decryption errors gracefully (user might not have access)
Integration with VecLabs Pipeline
┌─────────────┐
│ User Wallet │ → derive_key_from_pubkey → [32-byte key]
└─────────────┘
↓
┌─────────────┐
│ Vectors │ → encrypt_vectors → [encrypted blob]
└─────────────┘
↓
┌─────────────┐
│ Shadow Drive│ ← upload encrypted data
└─────────────┘
↓
┌─────────────┐
│ Download │ → decrypt_vectors → [original vectors]
└─────────────┘
↓
┌─────────────┐
│ HNSW Index │ ← load decrypted vectors
└─────────────┘
See Also