The Privora Program SDK enables you to build Solana programs that perform computations on encrypted data. This guide covers the SDK structure and how to get started.
SDK Structure
The privora-sdk-program crate provides:
privora_sdk_program/
├── prelude # Common imports
├── types # Encrypted types (EncryptedRef, Encrypted, EncryptedBool)
├── ops # FHE operations and syscalls
├── store # Data store access
├── memory # Memory allocator
├── auth # Authorization PDA helpers
└── error # Error types
Quick Start
1. Add Dependencies
[ dependencies ]
privora-sdk-program = { git = "https://github.com/privora-xyz/privora" }
solana-program = "2.0"
borsh = "1.5"
2. Import the Prelude
use privora_sdk_program :: prelude ::* ;
The prelude exports everything you need:
EncryptedRef<T>, Encrypted<T>, EncryptedBool
Common Solana types (AccountInfo, Pubkey, ProgramResult, etc.)
Helper macros and traits
3. Set Up the Allocator
// CRITICAL: Required for FHE operations
privora_sdk_program :: setup_fhe_allocator! ();
The allocator macro must be called at the crate root before any FHE operations. Without it, your program will fail with out-of-memory errors.
4. Write Your Program
use privora_sdk_program :: prelude ::* ;
use borsh :: { BorshSerialize , BorshDeserialize };
privora_sdk_program :: setup_fhe_allocator! ();
solana_program :: entrypoint! ( process_instruction );
pub fn process_instruction (
program_id : & Pubkey ,
accounts : & [ AccountInfo ],
instruction_data : & [ u8 ],
) -> ProgramResult {
// Your FHE logic here
Ok (())
}
Key Concepts
References vs Values
Type Description Size Storage EncryptedRef<T>Hash pointer 32 bytes On-chain accounts Encrypted<T>Loaded ciphertext 10-100KB Runtime memory
Always store EncryptedRef in accounts, not Encrypted values.
The FHE Workflow
Load : Fetch ciphertext from content store
Compute : Perform FHE operations
Store : Submit results back to content store
Save : Store new hash reference in account
Module Reference
types
Core encrypted value types:
use privora_sdk_program :: types :: {
EncryptedRef , // Hash reference (32 bytes)
Encrypted , // Loaded encrypted value
EncryptedBool , // Encrypted boolean
EncryptedBoolRef , // Boolean hash reference
EncryptedInt , // Trait for supported integer types
};
ops
FHE operations and operator implementations:
// Arithmetic (via methods or operators)
let sum = a . add ( & b ) ? ; // or: &a + &b
let diff = a . sub ( & b ) ? ; // or: &a - &b
let prod = a . mul ( & b ) ? ; // or: &a * &b
// Comparison
let is_ge : EncryptedBool = a . ge ( & b ) ? ;
let is_lt : EncryptedBool = a . lt ( & b ) ? ;
// Min/Max
let min_val = a . min ( & b ) ? ;
let max_val = a . max ( & b ) ? ;
// Conditional
let result = condition . select ( & true_val , & false_val ) ? ;
store
Content store access:
use privora_sdk_program :: store :: {fetch_data, submit_data};
// Low-level access (prefer EncryptedRef methods)
let data = fetch_data ( & hash ) ? ;
let hash = submit_data ( & ciphertext ) ? ;
memory
Memory allocator for FHE operations:
// Use the macro at crate root
privora_sdk_program :: setup_fhe_allocator! ();
// Or with custom heap size
privora_sdk_program :: setup_fhe_allocator! ( 2 * 1024 * 1024 ); // 2MB
auth
Authorization PDA helpers:
use privora_sdk_program :: auth :: {
DecryptionAuth , // Standard decryption authorization
MatchAuth , // Match-based authorization
DecryptedResult , // Decrypted result storage
FHE_AUTH_PROGRAM_ID ,
};
Example: Encrypted Transfer
use privora_sdk_program :: prelude ::* ;
use borsh :: { BorshSerialize , BorshDeserialize };
privora_sdk_program :: setup_fhe_allocator! ();
solana_program :: entrypoint! ( process_instruction );
#[derive( BorshSerialize , BorshDeserialize )]
pub struct Account {
pub owner : Pubkey ,
pub balance_ref : EncryptedRef < u64 >,
}
pub fn process_instruction (
_program_id : & Pubkey ,
accounts : & [ AccountInfo ],
instruction_data : & [ u8 ],
) -> ProgramResult {
let accounts_iter = & mut accounts . iter ();
let from_account = next_account_info ( accounts_iter ) ? ;
let to_account = next_account_info ( accounts_iter ) ? ;
// Parse amount reference from instruction data
let amount_ref = EncryptedRef :: < u64 > :: from_hash (
instruction_data [ 0 .. 32 ] . try_into () . unwrap ()
);
// Load accounts
let mut from_state = Account :: try_from_slice ( & from_account . data . borrow ()) ? ;
let mut to_state = Account :: try_from_slice ( & to_account . data . borrow ()) ? ;
// Load encrypted balances
let from_balance = from_state . balance_ref . load () ? ;
let to_balance = to_state . balance_ref . load () ? ;
let amount = amount_ref . load () ? ;
// Perform encrypted transfer
let new_from_balance = from_balance . sub ( & amount ) ? ;
let new_to_balance = to_balance . add ( & amount ) ? ;
// Store results
from_state . balance_ref = new_from_balance . store () ? ;
to_state . balance_ref = new_to_balance . store () ? ;
// Save to accounts
from_state . serialize ( & mut * from_account . data . borrow_mut ()) ? ;
to_state . serialize ( & mut * to_account . data . borrow_mut ()) ? ;
msg! ( "Transfer completed" );
Ok (())
}
Best Practices
Always Use Prelude Import prelude::* for all common types and traits
Set Up Allocator First Call setup_fhe_allocator!() before any other code
Use Smallest Types Choose u8 over u64 when possible for better performance
Store References Never store Encrypted<T> in accounts, only EncryptedRef<T>
Next Steps