Skip to main content
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

DiscriminatorInstructionDescription
0AuthorizeCreate standard auth PDA
2AuthorizeWithSeedsCreate auth PDA with custom seeds
3RequestDecryptionRequest 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

  1. Minimize scope: Only authorize what’s necessary
  2. Verify conditions: Check all conditions before authorizing
  3. Log authorizations: Emit events for auditability
  4. 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:
AccountWritableSignerDescription
Auth PDAYesNoThe authorization PDA to create
PayerYesYesPays for account rent
System ProgramNoNoFor account creation

Next Steps