Skip to main content
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

LimitValue
Maximum fetch size100KB
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.

Result Buffer Format

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:
ErrorCause
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:
PropertyBehavior
Transaction scopeData submitted is available immediately
Cross-transactionData persists after transaction completes
DeduplicationSame 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