Documentation Index
Fetch the complete documentation index at: https://docs.privora.xyz/llms.txt
Use this file to discover all available pages before exploring further.
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
| Scenario | Path | Reason |
|---|
| User views own balance | User Recovery | Data user encrypted |
| User views own order | User Recovery | Data user encrypted |
| User views match result | MPC | Computed by program |
| Counterparty views match | MPC | Not 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');
}
}