Skip to main content
The orderbook’s matching logic demonstrates the core FHE operations: comparison and minimum.

Matching Algorithm

A buy order matches with a sell order when buy_price >= sell_price:
Buy:  100 encrypted
Sell:  95 encrypted

   FHE Compare

  EncryptedBool (true)
The fill quantity is the minimum of both quantities:
Buy Qty:  50 encrypted
Sell Qty: 60 encrypted

       FHE Min

     50 encrypted

Step-by-Step Implementation

Step 1: Load Encrypted Data

// Load encrypted price data
msg!("Loading encrypted price data...");
let buy_price = buy_order
    .price_ref
    .load()
    .map_err(|_| ProgramError::InvalidArgument)?;
let sell_price = sell_order
    .price_ref
    .load()
    .map_err(|_| ProgramError::InvalidArgument)?;

// Load encrypted quantity data
msg!("Loading encrypted quantity data...");
let buy_qty = buy_order
    .qty_ref
    .load()
    .map_err(|_| ProgramError::InvalidArgument)?;
let sell_qty = sell_order
    .qty_ref
    .load()
    .map_err(|_| ProgramError::InvalidArgument)?;
The load() method fetches the ciphertext from the content-addressable store using the hash reference. This is a syscall that the Privora sequencer handles.

Step 2: Compare Prices

// Use SDK's comparison method: buy_price >= sell_price
msg!("Performing FHE comparison (buy_price >= sell_price)...");
let can_match = buy_price
    .ge(&sell_price)
    .map_err(|_| ProgramError::InvalidArgument)?;
msg!("FHE comparison completed");
The comparison returns an EncryptedBool - the result is encrypted and not revealed to anyone.

Step 3: Calculate Fill Quantity

// Calculate fill quantity using SDK's min method
msg!("Calculating fill quantity using FHE min...");
let fill_qty = buy_qty
    .min(&sell_qty)
    .map_err(|_| ProgramError::InvalidArgument)?;
msg!("FHE min calculation completed");

Step 4: Store Result

// Use sell price as fill price (standard orderbook matching)
let fill_price_ref = sell_order.price_ref;

// Store the computed fill_qty
msg!("Submitting fill quantity to data store...");
let fill_qty_ref = fill_qty
    .store()
    .map_err(|_| ProgramError::InvalidArgument)?;
msg!("Fill quantity submitted with hash");

Available FHE Operations

Comparison Operations

OperationMethodReturns
Greater than or equala.ge(&b)EncryptedBool
Greater thana.gt(&b)EncryptedBool
Less than or equala.le(&b)EncryptedBool
Less thana.lt(&b)EncryptedBool

Min/Max Operations

OperationMethodReturns
Minimuma.min(&b)Encrypted<T>
Maximuma.max(&b)Encrypted<T>
These operations internally use comparison and select:
// min(a, b) is equivalent to:
let is_less = a.le(&b)?;
let min = is_less.select(&a, &b)?;

Conditional Logic with select()

The select operation is the FHE equivalent of a ternary operator:
// If can_match is true, use fill_qty, else use zero
let final_qty = can_match.select(&fill_qty, &zero)?;
This could be used for more complex matching logic:
// Example: Only fill if prices cross
let can_match = buy_price.ge(&sell_price)?;
let fill_qty = buy_qty.min(&sell_qty)?;
let zero = /* encrypted zero */;

// final_qty = can_match ? fill_qty : 0
let final_qty = can_match.select(&fill_qty, &zero)?;

Performance Considerations

FHE operations are computationally expensive:
OperationRelative Cost
LoadLow (data fetch)
Add/Sub/MulMedium
ComparisonHigh
Min/MaxHigh (uses comparison)
StoreLow (data store)

Optimization Tips

Minimize comparisons: Each comparison is expensive. If possible, batch multiple values before comparing.
Reuse loaded values: Don’t load the same EncryptedRef multiple times. Load once and reuse.
// Good: Load once, use multiple times
let price = order.price_ref.load()?;
let is_above_min = price.ge(&min_price)?;
let is_below_max = price.le(&max_price)?;

// Bad: Multiple loads of same data
let is_above_min = order.price_ref.load()?.ge(&min_price)?;
let is_below_max = order.price_ref.load()?.le(&max_price)?;  // Redundant load

Why No Conditional Execution?

You cannot conditionally execute code based on encrypted values. The comparison result is encrypted.
// This is NOT possible - can_match is encrypted!
if can_match.decrypt() {  // ERROR: No decrypt in program
    execute_trade();
}
Instead, use select to compute both outcomes and pick the right one:
// Both outcomes computed, select chooses which to use
let matched_status = /* encrypted matched status */;
let open_status = /* encrypted open status */;
let new_status = can_match.select(&matched_status, &open_status)?;

Return Data

The matching function returns the fill hashes via Solana’s return data mechanism:
#[cfg(target_os = "solana")]
{
    extern "C" {
        fn sol_set_return_data(data: *const u8, length: u64);
    }
    let mut return_data = [0u8; 64];
    return_data[0..32].copy_from_slice(&fill_price_ref.hash());
    return_data[32..64].copy_from_slice(&fill_qty_ref.hash());
    unsafe {
        sol_set_return_data(return_data.as_ptr(), 64);
    }
}
This allows clients to retrieve the fill hashes from the transaction result.

Next Steps

Client Usage

See how to interact with the orderbook from a client