Skip to main content
The FHE orderbook uses three main data structures, all leveraging EncryptedRef<T> for type-safe encrypted data references.

Order

Represents a buy or sell order with encrypted price and quantity.
use privora_sdk_program::prelude::EncryptedRef;

#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)]
pub struct Order {
    /// Owner of the order
    pub owner: Pubkey,
    /// Typed reference to encrypted price data (u8)
    pub price_ref: EncryptedRef<u8>,
    /// Typed reference to encrypted quantity data (u8)
    pub qty_ref: EncryptedRef<u8>,
    /// Order side (Buy or Sell)
    pub side: OrderSide,
    /// Order status
    pub status: OrderStatus,
    /// Order ID
    pub order_id: u64,
}

impl Order {
    pub const LEN: usize = 32 + // owner
        32 + // price_ref (EncryptedRef is 32 bytes)
        32 + // qty_ref
        1 +  // side
        1 +  // status
        8;   // order_id (106 bytes total)
}

Key Design Decisions

EncryptedRef<u8> is exactly 32 bytes - the SHA256 hash of the ciphertext. The actual encrypted data (potentially kilobytes) lives in the content-addressable store, not on-chain.

Order Side

#[derive(BorshSerialize, BorshDeserialize, Clone, Copy, Debug, PartialEq)]
pub enum OrderSide {
    Buy,
    Sell,
}

Order Status

#[derive(BorshSerialize, BorshDeserialize, Clone, Copy, Debug, PartialEq)]
pub enum OrderStatus {
    Open,
    Matched,
    Cancelled,
}

Orderbook

Global state tracking order IDs and match counts.
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)]
pub struct Orderbook {
    /// Authority that can manage the orderbook
    pub authority: Pubkey,
    /// Next order ID
    pub next_order_id: u64,
    /// Total number of orders
    pub total_orders: u64,
    /// Total number of matches
    pub total_matches: u64,
}

impl Orderbook {
    pub const LEN: usize = 32 + // authority
        8 + // next_order_id
        8 + // total_orders
        8;  // total_matches (56 bytes)
}

MatchResult

Result of matching two orders, with references to the computed fill values.
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)]
pub struct MatchResult {
    /// Buy order ID
    pub buy_order_id: u64,
    /// Sell order ID
    pub sell_order_id: u64,
    /// Typed reference to encrypted fill price
    pub fill_price_ref: EncryptedRef<u8>,
    /// Typed reference to encrypted fill quantity
    pub fill_qty_ref: EncryptedRef<u8>,
    /// Match ID
    pub match_id: u64,
}

impl MatchResult {
    pub const LEN: usize = 8 +  // buy_order_id
        8 +  // sell_order_id
        32 + // fill_price_ref
        32 + // fill_qty_ref
        8;   // match_id (88 bytes total)
}

Fill Values

FieldDescription
fill_price_refReference to the sell price (standard orderbook convention)
fill_qty_refReference to min(buy_qty, sell_qty) computed via FHE

Storage Comparison

Traditional vs Privora approach:
ApproachOrder SizeMatch SizeNotes
Plaintext50 bytes40 bytesNo privacy
Raw ciphertext~8 KB~8 KBToo large for on-chain
Privora (hash refs)106 bytes88 bytesBest of both

Working with State

Reading Order Data

// Load order from account
let order = Order::try_from_slice(&order_account.data.borrow())?;

// Load encrypted price for computation
let price = order.price_ref.load()?;

Creating New Orders

let order = Order {
    owner: *owner_account.key,
    price_ref: order_data.price_ref,  // EncryptedRef from instruction
    qty_ref: order_data.qty_ref,
    side: order_data.side,
    status: OrderStatus::Open,
    order_id,
};

order.serialize(&mut *order_account.data.borrow_mut())?;

Updating Order Status

let mut order = Order::try_from_slice(&order_account.data.borrow())?;
order.status = OrderStatus::Matched;
order.serialize(&mut *order_account.data.borrow_mut())?;

Type Safety

The generic parameter in EncryptedRef<T> provides compile-time type safety:
// These are different types - can't mix them up!
price_ref: EncryptedRef<u8>,   // Price is u8
qty_ref: EncryptedRef<u32>,    // Quantity could be u32

// Loading preserves the type
let price: Encrypted<u8> = order.price_ref.load()?;
let qty: Encrypted<u32> = order.qty_ref.load()?;

// Type errors caught at compile time
let _wrong = price.add(&qty);  // Error: mismatched types

Next Steps

Instructions

See how these structures are used in instruction handlers