Skip to main content
Privora provides a type-safe system for working with encrypted values. This prevents common errors like mixing different integer sizes or confusing references with loaded values.

Core Types

EncryptedRef<T>

A 32-byte hash reference to encrypted data stored in the content-addressable store.
use privora_sdk_program::prelude::*;

// Create from a hash (e.g., from instruction data)
let price_ref = EncryptedRef::<u8>::from_hash(hash_bytes);

// EncryptedRef is only 32 bytes - perfect for on-chain storage
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Order {
    pub price_ref: EncryptedRef<u8>,  // 32 bytes
    pub qty_ref: EncryptedRef<u8>,    // 32 bytes
}
Key Properties:
  • Size: Always 32 bytes (SHA256 hash)
  • Borsh serializable for account storage
  • Type parameter T indicates the encrypted integer type
  • Does not contain actual ciphertext data

Encrypted<T>

A loaded encrypted value ready for FHE computation.
// Load from reference
let price: Encrypted<u8> = price_ref.load()?;

// Perform operations
let doubled = &price + &price;
let is_high: EncryptedBool = price.ge(&threshold)?;

// Store back to get a new reference
let result_ref: EncryptedRef<u8> = doubled.store()?;
Key Properties:
  • Contains actual ciphertext data (10KB-100KB)
  • Supports arithmetic and comparison operations
  • Created by loading from EncryptedRef or as result of operations
  • Must be stored back to get a reference for account storage

EncryptedBool

The result of comparison operations on encrypted values.
// Created from comparisons
let can_match: EncryptedBool = buy_price.ge(&sell_price)?;
let is_equal: EncryptedBool = value_a.eq_enc(&value_b)?;

// Used for conditional selection
let fill_price = can_match.select(&sell_price, &buy_price)?;

// Can be stored if needed
let bool_ref: EncryptedBoolRef = can_match.store()?;
Key Properties:
  • Result of comparison operations (ge, gt, le, lt, eq_enc)
  • Used with select() for conditional logic
  • Can be stored and loaded like other encrypted values

Type Flow

Complete Example

use privora_sdk_program::prelude::*;
use borsh::{BorshSerialize, BorshDeserialize};

/// Order state with typed references
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Order {
    pub owner: Pubkey,
    pub price_ref: EncryptedRef<u8>,   // 32 bytes
    pub quantity_ref: EncryptedRef<u8>, // 32 bytes
}

pub fn match_orders(
    buy_order: &Order,
    sell_order: &Order,
) -> ProgramResult {
    // Step 1: Load encrypted values from references
    let buy_price: Encrypted<u8> = buy_order.price_ref.load()?;
    let buy_qty: Encrypted<u8> = buy_order.quantity_ref.load()?;
    let sell_price: Encrypted<u8> = sell_order.price_ref.load()?;
    let sell_qty: Encrypted<u8> = sell_order.quantity_ref.load()?;

    // Step 2: Perform FHE comparison
    let can_match: EncryptedBool = buy_price.ge(&sell_price)?;

    // Step 3: Calculate fill quantity
    let fill_qty: Encrypted<u8> = buy_qty.min(&sell_qty)?;

    // Step 4: Store results back
    let fill_qty_ref: EncryptedRef<u8> = fill_qty.store()?;

    // fill_qty_ref.hash() can now be stored in account data
    msg!("Fill quantity hash: {:?}", fill_qty_ref.hash());

    Ok(())
}

Type Safety Benefits

The type system prevents several classes of errors:

1. Mixing Integer Sizes

// This won't compile - types don't match!
let a: Encrypted<u8> = a_ref.load()?;
let b: Encrypted<u64> = b_ref.load()?;
let sum = a.add(&b)?; // ERROR: expected Encrypted<u8>, found Encrypted<u64>

2. Confusing References and Values

// Clear distinction between reference and loaded value
let price_ref: EncryptedRef<u8> = ...;
let price: Encrypted<u8> = price_ref.load()?;

// Can't accidentally use reference in computation
let doubled = &price_ref + &price_ref; // ERROR: no Add impl for EncryptedRef

3. Incorrect Storage

// Must store to get a reference
let result: Encrypted<u8> = a.add(&b)?;
account.result_ref = result;  // ERROR: expected EncryptedRef<u8>

// Correct:
let result: Encrypted<u8> = a.add(&b)?;
account.result_ref = result.store()?;  // OK

Working with Hashes

EncryptedRef<T> provides several methods for working with the underlying hash:
// Get hash as [u8; 32]
let hash: [u8; 32] = encrypted_ref.hash();

// Get reference to hash
let hash_ref: &[u8; 32] = encrypted_ref.hash_ref();

// Create from raw bytes
let price_ref = EncryptedRef::<u8>::from_hash(hash_bytes);

// Convert to/from [u8; 32]
let bytes: [u8; 32] = encrypted_ref.into();
let encrypted_ref: EncryptedRef<u8> = bytes.into();

Serialization

All reference types implement Borsh serialization for on-chain storage:
use borsh::{BorshSerialize, BorshDeserialize};

#[derive(BorshSerialize, BorshDeserialize)]
pub struct MyState {
    pub balance: EncryptedRef<u64>,      // 32 bytes
    pub limit: EncryptedRef<u64>,        // 32 bytes
    pub is_active: EncryptedBoolRef,     // 32 bytes
}

impl MyState {
    pub const LEN: usize = 32 + 32 + 32; // 96 bytes total
}

Best Practices

Use Smallest Type

Choose u8 when values fit in 0-255. Smaller types have faster operations.

Store References

Always store EncryptedRef in accounts, not Encrypted values.

Load Once

Load encrypted values once and reuse them to minimize syscalls.

Batch Stores

Store results at the end of computation, not after each operation.

Next Steps