Skip to main content
Privora supports two decryption paths: user recovery and MPC decryption.

Overview

Path 1: User Recovery

For values encrypted with dual encryption:
// User encrypted their order with recovery
const encrypted = privora
  .encrypt(100, 'u8')
  .withUserRecovery(userCrypto);

// Later: decrypt locally
const plaintext = userCrypto.decryptRecovery(encrypted.userRecovery!);
Characteristics:
  • Instant (no network)
  • Only for user’s own data
  • Requires stored recovery data
  • No authorization needed

Path 2: MPC Decryption

For values without user recovery or computed results:

Step 1: Create Authorization

In your program, create an authorization PDA:
pub fn authorize_user_for_result(
    result_hash: &[u8; 32],
    user: &Pubkey,
) -> ProgramResult {
    create_decryption_auth(result_hash, user)?;
    Ok(())
}

Step 2: Request Decryption

Client requests decryption from the sequencer:
// Request decryption (requires auth PDA to exist)
const encryptedResult = await privora.requestDecryption(dataHash);

// Result is encrypted with user's X25519 key
const plaintext = userCrypto.decryptFromMPC(
  encryptedResult.ciphertext,
  encryptedResult.nonce,
  encryptedResult.ephemeralPubkey
);

MPC Flow

Characteristics:
  • Requires authorization PDA
  • Network round-trip
  • Works for any authorized data
  • Threshold security (k-of-n MPC)

Choosing a Path

ScenarioPathReason
User views own balanceUser RecoveryData user encrypted
User views own orderUser RecoveryData user encrypted
User views match resultMPCComputed by program
Counterparty views matchMPCNot their encrypted data

Implementation Patterns

Pattern 1: Self-Viewing Data

For data users encrypt and want to view later:
// On submission
const encrypted = privora
  .encrypt(amount, 'u64')
  .withUserRecovery(userCrypto);

// Store recovery data
localStorage.setItem(`balance_${txId}`, JSON.stringify(encrypted.userRecovery));

// On viewing
const recovery = JSON.parse(localStorage.getItem(`balance_${txId}`));
const amount = userCrypto.decryptRecovery(recovery);

Pattern 2: Authorized Results

For computed results:
// In program: Authorize after computation
let result = a.add(&b)?;
let result_ref = result.store()?;

// Authorize user to decrypt
create_decryption_auth(&result_ref.hash(), &user)?;
// Client: Request decryption
const result = await privora.requestDecryption(resultHash);
const plaintext = userCrypto.decryptFromMPC(
  result.ciphertext,
  result.nonce,
  result.ephemeralPubkey
);

Pattern 3: Conditional Results

Share results only under certain conditions:
// In match_orders: Only authorize if match executed
if buy_price.ge(&sell_price)? {
    // Authorize both parties for fill details
    create_decryption_auth(&fill_price_ref.hash(), &buyer)?;
    create_decryption_auth(&fill_price_ref.hash(), &seller)?;
}

Security Considerations

User Recovery

  • Recovery data must be stored securely
  • Loss of recovery data = must use MPC
  • Never transmit recovery data unencrypted

MPC Decryption

  • Authorization is permanent
  • Verify conditions before authorizing
  • Threshold security protects against single-party compromise

Error Handling

Missing Recovery Data

if (!encrypted.userRecovery) {
  // Fall back to MPC if authorized
  try {
    const result = await privora.requestDecryption(hash);
    return userCrypto.decryptFromMPC(...);
  } catch {
    throw new Error('Cannot decrypt: no recovery data and not authorized');
  }
}

Missing Authorization

try {
  const result = await privora.requestDecryption(hash);
} catch (error) {
  if (error.message.includes('not authorized')) {
    // User is not authorized to decrypt this data
    showError('You are not authorized to view this value');
  }
}