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