The content-addressable data store is where FHE ciphertexts are stored. This page covers the low-level syscall interface and advanced usage.
Overview
Programs interact with the data store through two syscalls:
fetch_data: Retrieve ciphertext by hash
submit_data: Store ciphertext and get its hash
High-Level API
The SDK provides convenient methods on the encrypted types:
Loading Data
use privora_sdk_program :: prelude ::* ;
// From EncryptedRef
let price_ref : EncryptedRef < u8 > = account . price_ref;
let price : Encrypted < u8 > = price_ref . load () ? ;
Storing Data
// Compute result
let result : Encrypted < u8 > = a . add ( & b ) ? ;
// Store and get reference
let result_ref : EncryptedRef < u8 > = result . store () ? ;
Low-Level Syscalls
For advanced use cases, you can access the syscalls directly:
fetch_data
use privora_sdk_program :: ops :: syscalls :: fetch_data;
let hash : [ u8 ; 32 ] = account . price_ref . hash ();
let ciphertext : Vec < u8 > = fetch_data ( & hash ) ? ;
Parameters:
hash: 32-byte SHA256 hash of the data
Returns:
Ok(Vec<u8>): The ciphertext bytes
Err(ProgramError): If data not found or fetch failed
submit_data
use privora_sdk_program :: ops :: syscalls :: submit_data;
let ciphertext : Vec < u8 > = /* computed ciphertext */ ;
let hash : [ u8 ; 32 ] = submit_data ( & ciphertext ) ? ;
Parameters:
data: The ciphertext bytes to store
Returns:
Ok([u8; 32]): SHA256 hash of the stored data
Err(ProgramError): If submission failed
Syscall Implementation
Under the hood, the syscalls use the Solana BPF syscall interface:
// fetch_data syscall
extern "C" {
fn sol_fetch_data (
hash : * const u8 ,
hash_len : u64 ,
result : * mut u8 ,
) -> u64 ;
}
// submit_data syscall
extern "C" {
fn sol_submit_data (
data : * const u8 ,
data_len : u64 ,
hash_result : * mut u8 ,
) -> u64 ;
}
The SDK handles:
Buffer allocation and management
Length-prefixed result format
Error code conversion
Data Size Limits
Limit Value Maximum fetch size 100KB Typical u8 ciphertext ~10KB Typical u64 ciphertext ~80KB
Attempting to fetch data larger than 100KB will fail. This limit is sufficient for all supported types.
Syscalls use a length-prefixed buffer format:
+-------------------+------------------+
| Length (8 bytes) | Data (variable) |
+-------------------+------------------+
Input: First 8 bytes contain capacity
Output: First 8 bytes contain actual data length
Error Handling
Common errors when working with the data store:
Error Cause Custom(1)Data not found Custom(2)Fetch failed Custom(3)Submit failed Custom(4)Buffer too small
Example error handling:
match price_ref . load () {
Ok ( price ) => {
// Use price
}
Err ( ProgramError :: Custom ( 1 )) => {
msg! ( "Price data not found in store" );
return Err ( ProgramError :: InvalidArgument );
}
Err ( e ) => {
msg! ( "Failed to load price: {:?}" , e );
return Err ( e );
}
}
Data Persistence
The data store provides:
Property Behavior Transaction scope Data submitted is available immediately Cross-transaction Data persists after transaction completes Deduplication Same data stored once (content-addressed)
Best Practices
Load Once, Use Multiple Times
// Good: Single load
let price = price_ref . load () ? ;
let double = price . add ( & price ) ? ;
let triple = double . add ( & price ) ? ;
// Bad: Multiple loads of same data
let double = price_ref . load () ?. add ( & price_ref . load () ? ) ? ;
Store at the End
// Good: Store after all computation
let a = a_ref . load () ? ;
let b = b_ref . load () ? ;
let c = c_ref . load () ? ;
let result1 = a . add ( & b ) ? ;
let result2 = result1 . mul ( & c ) ? ;
// Single store at the end
let result_ref = result2 . store () ? ;
// Avoid: Storing intermediate results unnecessarily
Handle Missing Data
pub fn process ( data_ref : EncryptedRef < u8 >) -> ProgramResult {
let data = data_ref . load () . map_err ( | e | {
msg! ( "Failed to load encrypted data" );
ProgramError :: InvalidAccountData
}) ? ;
// Continue processing...
Ok (())
}
Working with Raw Bytes
If you need to work with raw ciphertext:
// Get raw bytes from Encrypted
let encrypted : Encrypted < u8 > = price_ref . load () ? ;
let raw_bytes : & [ u8 ] = encrypted . as_bytes ();
// Create Encrypted from raw bytes
let encrypted = Encrypted :: < u8 > :: from_raw ( raw_bytes . to_vec ());
// Consume and get owned bytes
let bytes : Vec < u8 > = encrypted . into_bytes ();
Testing Considerations
In tests, the data store is provided by FheTestEnv:
use privora_sdk_testing :: prelude ::* ;
let mut env = FheTestEnv :: new ();
// Submit data
let hash = env . submit_data ( ciphertext_bytes );
// Retrieve data
let data = env . get_data ( & hash ) . unwrap ();
// Sync for transaction execution
env . sync_data_store_to_syscalls ();
// ... execute transaction ...
env . sync_data_store_from_syscalls ();
Next Steps
Authorization Control decryption access
Memory Allocator Manage memory for FHE