Test Environment
The Privora testing SDK provides helpers for FHE orderbook testing.Setup
Copy
use privora_sdk_testing::prelude::*;
const PROGRAM_ID: Pubkey = pubkey!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[test]
fn test_order_matching() {
let mut env = FheTestEnv::new();
env.sync_data_store_to_syscalls();
// Deploy program
let program_bytes = include_bytes!("../target/deploy/fhe_orderbook.so");
env.deploy_program(PROGRAM_ID, program_bytes);
// Create accounts
let authority = env.create_funded_keypair();
let buyer = env.create_funded_keypair();
let seller = env.create_funded_keypair();
}
Encrypting Order Data
Copy
// Encrypt values
let buy_price: u8 = 100;
let buy_qty: u8 = 50;
// Encrypt and submit to data store
let buy_price_hash = env.encrypt_and_submit_u8(buy_price);
let buy_price_ref = env.encrypted_ref_u8(buy_price_hash);
let buy_qty_hash = env.encrypt_and_submit_u8(buy_qty);
let buy_qty_ref = env.encrypted_ref_u8(buy_qty_hash);
Submitting Orders
Copy
#[derive(BorshSerialize)]
struct SubmitOrderData {
price_ref: EncryptedRef<u8>,
qty_ref: EncryptedRef<u8>,
side: OrderSide,
}
fn submit_order(
env: &mut FheTestEnv,
owner: &Keypair,
orderbook: &Keypair,
order: &Keypair,
price_ref: EncryptedRef<u8>,
qty_ref: EncryptedRef<u8>,
side: OrderSide,
) {
let data = SubmitOrderData { price_ref, qty_ref, side };
let mut instruction_data = vec![1]; // SubmitOrder discriminator
instruction_data.extend_from_slice(&borsh::to_vec(&data).unwrap());
let ix = Instruction {
program_id: PROGRAM_ID,
accounts: vec![
AccountMeta::new(orderbook.pubkey(), false),
AccountMeta::new(order.pubkey(), false),
AccountMeta::new_readonly(owner.pubkey(), true),
],
data: instruction_data,
};
let mut tx = Transaction::new_with_payer(&[ix], Some(&owner.pubkey()));
tx.sign(&[owner], env.latest_blockhash());
env.svm.send_transaction(tx).expect("Failed to submit order");
}
Matching Orders
Copy
fn match_orders(
env: &mut FheTestEnv,
authority: &Keypair,
orderbook: &Keypair,
buy_order: &Keypair,
sell_order: &Keypair,
match_result: &Keypair,
) -> ([u8; 32], [u8; 32]) {
let ix = Instruction {
program_id: PROGRAM_ID,
accounts: vec![
AccountMeta::new(orderbook.pubkey(), false),
AccountMeta::new(buy_order.pubkey(), false),
AccountMeta::new(sell_order.pubkey(), false),
AccountMeta::new(match_result.pubkey(), false),
],
data: vec![2], // MatchOrders discriminator
};
// Sync data store before matching
env.sync_data_store_to_syscalls();
let mut tx = Transaction::new_with_payer(&[ix], Some(&authority.pubkey()));
tx.sign(&[authority], env.latest_blockhash());
let result = env.svm.send_transaction(tx).expect("Failed to match");
// Sync data store after matching
env.sync_data_store_from_syscalls();
// Extract fill hashes from return data
let return_data = result.return_data;
let fill_price_hash: [u8; 32] = return_data.data[0..32].try_into().unwrap();
let fill_qty_hash: [u8; 32] = return_data.data[32..64].try_into().unwrap();
(fill_price_hash, fill_qty_hash)
}
Verifying Results
Copy
// Verify fill values
env.assert_encrypted_eq_u8(&fill_price_hash, sell_price);
env.assert_encrypted_eq_u8(&fill_qty_hash, expected_qty); // min(buy_qty, sell_qty)
println!("Match verified: fill_price={}, fill_qty={}", sell_price, expected_qty);
Complete Test Example
Copy
#[test]
fn test_basic_order_match() {
let mut env = FheTestEnv::new();
env.sync_data_store_to_syscalls();
// Deploy program
let program_bytes = include_bytes!("../target/deploy/fhe_orderbook.so");
env.deploy_program(PROGRAM_ID, program_bytes);
// Create participants
let authority = env.create_funded_keypair();
let buyer = env.create_funded_keypair();
let seller = env.create_funded_keypair();
// Create accounts
let orderbook = Keypair::new();
let buy_order = Keypair::new();
let sell_order = Keypair::new();
let match_result = Keypair::new();
// Initialize orderbook
create_account(&mut env, &authority, &orderbook, 56, &PROGRAM_ID);
initialize_orderbook(&mut env, &authority, &orderbook);
// Order data
let buy_price: u8 = 100;
let buy_qty: u8 = 50;
let sell_price: u8 = 95;
let sell_qty: u8 = 60;
// Encrypt and create refs
let buy_price_ref = env.encrypted_ref_u8(env.encrypt_and_submit_u8(buy_price));
let buy_qty_ref = env.encrypted_ref_u8(env.encrypt_and_submit_u8(buy_qty));
let sell_price_ref = env.encrypted_ref_u8(env.encrypt_and_submit_u8(sell_price));
let sell_qty_ref = env.encrypted_ref_u8(env.encrypt_and_submit_u8(sell_qty));
// Submit orders
create_account(&mut env, &buyer, &buy_order, 106, &PROGRAM_ID);
submit_order(&mut env, &buyer, &orderbook, &buy_order,
buy_price_ref, buy_qty_ref, OrderSide::Buy);
create_account(&mut env, &seller, &sell_order, 106, &PROGRAM_ID);
submit_order(&mut env, &seller, &orderbook, &sell_order,
sell_price_ref, sell_qty_ref, OrderSide::Sell);
// Match orders
create_account(&mut env, &authority, &match_result, 88, &PROGRAM_ID);
let (fill_price_hash, fill_qty_hash) = match_orders(
&mut env, &authority, &orderbook, &buy_order, &sell_order, &match_result
);
// Verify: fill_price = sell_price, fill_qty = min(50, 60) = 50
env.assert_encrypted_eq_u8(&fill_price_hash, 95);
env.assert_encrypted_eq_u8(&fill_qty_hash, 50);
}
TypeScript Client
For production use with the TypeScript SDK:Copy
import { Privora, FheType } from '@privora/sdk';
import { Connection, Keypair, Transaction, PublicKey } from '@solana/web3.js';
const PROGRAM_ID = new PublicKey('Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS');
async function submitOrder(
privora: Privora,
connection: Connection,
owner: Keypair,
orderbook: PublicKey,
orderAccount: PublicKey,
price: number,
quantity: number,
side: 'buy' | 'sell'
) {
// Encrypt order data
const priceHash = await privora.encrypt(price, FheType.U8);
const qtyHash = await privora.encrypt(quantity, FheType.U8);
// Build instruction data
const sideValue = side === 'buy' ? 0 : 1;
const data = Buffer.concat([
Buffer.from([1]), // SubmitOrder discriminator
Buffer.from(priceHash),
Buffer.from(qtyHash),
Buffer.from([sideValue])
]);
// Create instruction
const instruction = {
programId: PROGRAM_ID,
keys: [
{ pubkey: orderbook, isSigner: false, isWritable: true },
{ pubkey: orderAccount, isSigner: false, isWritable: true },
{ pubkey: owner.publicKey, isSigner: true, isWritable: false },
],
data,
};
// Send transaction with FHE data
const tx = new Transaction().add(instruction);
const signature = await privora.sendTransaction(tx, connection, [owner], {
fheDataHashes: [priceHash, qtyHash],
});
return signature;
}
Rust Client
For Rust clients:Copy
use privora_sdk_client::prelude::*;
async fn submit_order(
client: &PrivoraClient,
owner: &Keypair,
orderbook: Pubkey,
order_account: Pubkey,
price: u8,
quantity: u8,
side: OrderSide,
) -> Result<Signature> {
// Encrypt order data
let price_hash = client.encryptor().encrypt_u8(price).await?;
let qty_hash = client.encryptor().encrypt_u8(quantity).await?;
// Build transaction
let tx = TransactionBuilder::new()
.add_instruction(
PROGRAM_ID,
vec![
AccountMeta::new(orderbook, false),
AccountMeta::new(order_account, false),
AccountMeta::new_readonly(owner.pubkey(), true),
],
build_submit_order_data(price_hash, qty_hash, side),
)
.with_fhe_data(vec![price_hash, qty_hash])
.build()?;
client.send_transaction(tx, &[owner]).await
}
Key Integration Points
Encrypt Client-Side
Always encrypt prices and quantities before submission
Include FHE Hashes
Pass
fheDataHashes when sending transactionsSync Data Store
Call
sync_data_store_to_syscalls before FHE operations in testsVerify with Decryption
Use assert helpers to verify encrypted results
Summary
The FHE orderbook demonstrates a complete privacy-preserving trading system:- Client: Encrypts prices and quantities
- Program: Matches orders using FHE operations
- Result: Fill values remain encrypted until authorized decryption