This guide covers all FHE operations available in the Privora Program SDK.
Loading Encrypted Values
Before performing operations, load encrypted values from their references:
use privora_sdk_program::prelude::*;
// From hash reference stored in account
let price_ref: EncryptedRef<u8> = account.price_ref;
// Load the actual encrypted value
let price: Encrypted<u8> = price_ref.load()?;
The load() method:
- Takes the hash from
EncryptedRef
- Fetches the ciphertext from the content store via syscall
- Returns an
Encrypted<T> ready for computation
Arithmetic Operations
Addition
// Method syntax (returns Result)
let sum: Encrypted<u8> = a.add(&b)?;
// Operator syntax (panics on error)
let sum: Encrypted<u8> = &a + &b;
Operators use references (&a + &b) to avoid consuming the values.
Subtraction
// Method syntax
let difference: Encrypted<u8> = a.sub(&b)?;
// Operator syntax
let difference: Encrypted<u8> = &a - &b;
FHE subtraction can underflow. If a is less than b, the result wraps around (modular arithmetic). Design your program to avoid or handle this.
Multiplication
// Method syntax
let product: Encrypted<u8> = a.mul(&b)?;
// Operator syntax
let product: Encrypted<u8> = &a * &b;
Chaining Operations
// Calculate: (a + b) * c
let result = a.add(&b)?.mul(&c)?;
// With operators (use parentheses carefully)
let result = &(&a + &b) * &c;
// More readable with intermediate variables
let sum = &a + &b;
let result = &sum * &c;
Comparison Operations
Comparisons return EncryptedBool, which can be used for conditional logic.
Greater Than or Equal
let is_ge: EncryptedBool = buy_price.ge(&sell_price)?;
Greater Than
let is_gt: EncryptedBool = price.gt(&threshold)?;
Less Than or Equal
let is_le: EncryptedBool = amount.le(&limit)?;
Less Than
let is_lt: EncryptedBool = balance.lt(&required)?;
Equality
let is_equal: EncryptedBool = value_a.eq_enc(&value_b)?;
The method is eq_enc (not eq) to avoid conflict with Rust’s Eq trait.
Min/Max Operations
Get the minimum or maximum of two encrypted values:
// Minimum
let min_value: Encrypted<u8> = buy_qty.min(&sell_qty)?;
// Maximum
let max_value: Encrypted<u8> = price_a.max(&price_b)?;
These are implemented using encrypted comparison and selection.
Conditional Selection
The select method implements encrypted if-then-else:
let condition: EncryptedBool = buy_price.ge(&sell_price)?;
// If condition is true, return sell_price; else return buy_price
let fill_price: Encrypted<u8> = condition.select(&sell_price, &buy_price)?;
This is equivalent to:
if (buy_price >= sell_price) {
fill_price = sell_price;
} else {
fill_price = buy_price;
}
Key insight: Both branches are always computed. FHE cannot skip computation based on encrypted conditions.
Storing Results
After computation, store results back to the content store:
// Compute result
let result: Encrypted<u8> = a.add(&b)?;
// Store and get hash reference
let result_ref: EncryptedRef<u8> = result.store()?;
// Save reference to account
account.result_ref = result_ref;
Complete Example
use privora_sdk_program::prelude::*;
pub fn match_orders(
buy_price_ref: EncryptedRef<u8>,
buy_qty_ref: EncryptedRef<u8>,
sell_price_ref: EncryptedRef<u8>,
sell_qty_ref: EncryptedRef<u8>,
) -> Result<(EncryptedRef<u8>, EncryptedRef<u8>), ProgramError> {
// 1. Load all encrypted values
let buy_price = buy_price_ref.load()?;
let buy_qty = buy_qty_ref.load()?;
let sell_price = sell_price_ref.load()?;
let sell_qty = sell_qty_ref.load()?;
// 2. Check if orders can match (buy >= sell)
let can_match: EncryptedBool = buy_price.ge(&sell_price)?;
// 3. Calculate fill quantity (minimum of both)
let fill_qty: Encrypted<u8> = buy_qty.min(&sell_qty)?;
// 4. Use sell price as fill price (standard matching)
// The fill price is the sell_price (taker price)
// 5. Store results
let fill_price_ref = sell_price.store()?;
let fill_qty_ref = fill_qty.store()?;
Ok((fill_price_ref, fill_qty_ref))
}
Operation Costs
FHE operations have different computational costs:
| Operation | Relative Cost | Notes |
|---|
| Addition | 1x | Fast, use freely |
| Subtraction | 1x | Same as addition |
| Multiplication | 2-3x | More expensive |
| Comparison | 5-10x | Requires bootstrapping |
| Min/Max | 5-10x | Built on comparison |
| Select | 5-10x | Built on comparison |
Optimization Tips
- Batch comparisons: Do all arithmetic first, then comparisons
// Good: Arithmetic first, then compare
let total_a = price_a.mul(&qty_a)?;
let total_b = price_b.mul(&qty_b)?;
let a_is_larger = total_a.gt(&total_b)?;
// Avoid: Interleaving comparisons with arithmetic
- Minimize comparisons: Each comparison is expensive
// Good: One comparison
let can_match = buy_price.ge(&sell_price)?;
// Avoid: Redundant comparisons
let can_match = buy_price.ge(&sell_price)?;
let cant_match = buy_price.lt(&sell_price)?; // Redundant!
- Reuse loaded values
// Good: Load once, use multiple times
let price = price_ref.load()?;
let result1 = price.add(&amount1)?;
let result2 = price.add(&amount2)?;
// Avoid: Loading multiple times
let result1 = price_ref.load()?.add(&amount1)?;
let result2 = price_ref.load()?.add(&amount2)?; // Unnecessary second load
Type Safety
The SDK enforces type safety at compile time:
let a: Encrypted<u8> = a_ref.load()?;
let b: Encrypted<u64> = b_ref.load()?;
// This won't compile!
let sum = a.add(&b)?; // Error: expected Encrypted<u8>, found Encrypted<u64>
Error Handling
All operations return Result:
// Handle errors explicitly
let price = match price_ref.load() {
Ok(p) => p,
Err(e) => {
msg!("Failed to load price: {:?}", e);
return Err(e);
}
};
// Or use ? operator
let price = price_ref.load()?;
let sum = price.add(&other)?;
let result_ref = sum.store()?;
Next Steps