Programs can create authorization PDAs to grant users the right to request MPC decryption of encrypted values.
Overview
When your program produces encrypted results that users should be able to decrypt, create an authorization PDA:
Authorization Types
DecryptionAuth
Standard authorization for a specific user to decrypt specific data:
use privora_sdk_program::auth::pda::DecryptionAuth;
// Find the authorization PDA
let (auth_pda, bump) = DecryptionAuth::find_pda(
&data_hash, // Hash of encrypted data
&user_pubkey, // User being authorized
);
PDA Seeds:
["decrypt_auth", data_hash, user_pubkey]
MatchAuth
For orderbook-style applications where matching creates authorization:
use privora_sdk_program::auth::pda::MatchAuth;
let (auth_pda, bump) = MatchAuth::find_pda(
buy_order_id, // First order ID
sell_order_id, // Second order ID
"price", // Field being authorized
&user_pubkey, // User being authorized
);
PDA Seeds:
["match_auth", buy_id, sell_id, field, user_pubkey]
Creating Authorization via CPI
Basic Authorization
use privora_sdk_program::auth::cpi::create_decryption_auth;
pub fn authorize_user(
program_id: &Pubkey,
accounts: &[AccountInfo],
data_hash: [u8; 32],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let fhe_auth_program = next_account_info(account_info_iter)?;
let auth_pda = next_account_info(account_info_iter)?;
let user = next_account_info(account_info_iter)?;
let payer = next_account_info(account_info_iter)?;
let system_program = next_account_info(account_info_iter)?;
// Create the authorization PDA
create_decryption_auth(
fhe_auth_program,
auth_pda,
&data_hash,
user.key,
payer,
system_program,
)?;
msg!("Authorization created for user {}", user.key);
Ok(())
}
Match-Based Authorization
For orderbooks and similar applications:
use borsh::BorshSerialize;
use solana_program::program::invoke;
use solana_program::instruction::{Instruction, AccountMeta};
pub fn create_match_authorization(
fhe_auth_program: &AccountInfo,
auth_pda: &AccountInfo,
payer: &AccountInfo,
system_program: &AccountInfo,
buy_order_id: u64,
sell_order_id: u64,
field: &str,
user: &Pubkey,
) -> ProgramResult {
// Build seeds for PDA
let (expected_pda, bump) = Pubkey::find_program_address(
&[
b"match_auth",
&buy_order_id.to_le_bytes(),
&sell_order_id.to_le_bytes(),
field.as_bytes(),
user.as_ref(),
],
&FHE_AUTH_PROGRAM_ID,
);
// Verify PDA matches
if expected_pda != *auth_pda.key {
return Err(ProgramError::InvalidSeeds);
}
// Build instruction data
#[derive(BorshSerialize)]
struct AuthorizeWithSeedsData {
seeds: Vec<Vec<u8>>,
bump: u8,
authorized_user: Pubkey,
}
let data = AuthorizeWithSeedsData {
seeds: vec![
b"match_auth".to_vec(),
buy_order_id.to_le_bytes().to_vec(),
sell_order_id.to_le_bytes().to_vec(),
field.as_bytes().to_vec(),
user.to_bytes().to_vec(),
],
bump,
authorized_user: *user,
};
let mut instruction_data = vec![2u8]; // AuthorizeWithSeeds discriminator
data.serialize(&mut instruction_data)?;
let instruction = Instruction {
program_id: FHE_AUTH_PROGRAM_ID,
accounts: vec![
AccountMeta::new(*auth_pda.key, false),
AccountMeta::new(*payer.key, true),
AccountMeta::new_readonly(*system_program.key, false),
],
data: instruction_data,
};
invoke(
&instruction,
&[
auth_pda.clone(),
payer.clone(),
system_program.clone(),
fhe_auth_program.clone(),
],
)
}
Complete Orderbook Example
pub fn match_orders(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let orderbook = next_account_info(account_info_iter)?;
let buy_order_account = next_account_info(account_info_iter)?;
let sell_order_account = next_account_info(account_info_iter)?;
let match_result_account = next_account_info(account_info_iter)?;
let fhe_auth_program = next_account_info(account_info_iter)?;
let buy_user_auth_pda = next_account_info(account_info_iter)?;
let sell_user_auth_pda = next_account_info(account_info_iter)?;
let payer = next_account_info(account_info_iter)?;
let system_program = next_account_info(account_info_iter)?;
// Load orders
let buy_order = Order::try_from_slice(&buy_order_account.data.borrow())?;
let sell_order = Order::try_from_slice(&sell_order_account.data.borrow())?;
// Load encrypted data and perform matching
let buy_price = buy_order.price_ref.load()?;
let sell_price = sell_order.price_ref.load()?;
let buy_qty = buy_order.qty_ref.load()?;
let sell_qty = sell_order.qty_ref.load()?;
// Check match and calculate fill
let can_match = buy_price.ge(&sell_price)?;
let fill_qty = buy_qty.min(&sell_qty)?;
let fill_qty_ref = fill_qty.store()?;
// Authorize buy user to decrypt fill details
create_match_authorization(
fhe_auth_program,
buy_user_auth_pda,
payer,
system_program,
buy_order.order_id,
sell_order.order_id,
"fill_qty",
&buy_order.owner,
)?;
// Authorize sell user to decrypt fill details
create_match_authorization(
fhe_auth_program,
sell_user_auth_pda,
payer,
system_program,
buy_order.order_id,
sell_order.order_id,
"fill_qty",
&sell_order.owner,
)?;
msg!("Orders matched, users authorized to decrypt results");
Ok(())
}
FHE Auth Program
The FHE Auth program ID:
pub const FHE_AUTH_PROGRAM_ID: Pubkey =
solana_pubkey::pubkey!("FheAuth111111111111111111111111111111111111");
Instructions
| Discriminator | Instruction | Description |
|---|
| 0 | Authorize | Create standard auth PDA |
| 2 | AuthorizeWithSeeds | Create auth PDA with custom seeds |
| 3 | RequestDecryption | Request MPC decryption |
Authorization Patterns
Pattern 1: Self-Authorization
Authorize users for their own data:
// When user deposits
let encrypted_balance = amount.store()?;
// Authorize user to decrypt their own balance
create_decryption_auth(
&encrypted_balance.hash(),
&depositor_pubkey,
)?;
Pattern 2: Conditional Authorization
// Only authorize if trade executes
if trade_executed {
// Authorize buyer
create_decryption_auth(&fill_details_hash, &buyer)?;
// Authorize seller
create_decryption_auth(&fill_details_hash, &seller)?;
}
Pattern 3: Multi-Party Authorization
// Authorize multiple parties for the same data
for party in authorized_parties.iter() {
create_decryption_auth(&data_hash, party)?;
}
Security Considerations
Authorization is permanent. Once created, an authorization PDA grants decryption rights. Design your authorization logic carefully.
Best Practices
- Minimize scope: Only authorize what’s necessary
- Verify conditions: Check all conditions before authorizing
- Log authorizations: Emit events for auditability
- Validate users: Ensure authorized user is who they claim to be
// Good: Verify ownership before authorizing
if order.owner != user_pubkey {
return Err(ProgramError::InvalidArgument);
}
create_decryption_auth(&order.price_ref.hash(), &user_pubkey)?;
// Bad: Authorize without verification
create_decryption_auth(&order.price_ref.hash(), &any_pubkey)?;
Account Requirements
When invoking CPI to create authorization:
| Account | Writable | Signer | Description |
|---|
| Auth PDA | Yes | No | The authorization PDA to create |
| Payer | Yes | Yes | Pays for account rent |
| System Program | No | No | For account creation |
Next Steps