Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/octra-labs/pvac_hfhe_cpp/llms.txt

Use this file to discover all available pages before exploring further.

Get up and running with PVAC-HFHE by building a simple application that encrypts data, performs homomorphic operations, and decrypts the results.

Prerequisites

Before you begin, ensure you have:
  • C++17 compatible compiler (GCC 9+, Clang 10+, or MSVC 2019+)
  • PVAC-HFHE cloned and included in your project (see Installation)

Build your first FHE application

1

Create your source file

Create a file named first_fhe.cpp:
#include <iostream>
#include <pvac/pvac.hpp>

using namespace pvac;

int main() {
    std::cout << "PVAC-HFHE " << VERSION_STRING << " Quickstart\n\n";
    
    return 0;
}
2

Generate cryptographic keys

Add key generation code. This creates the public key for encryption and secret key for decryption:
// Generate keys
Params prm;   // Default security parameters (128-bit)
PubKey pk;    // Public key (8 MB)
SecKey sk;    // Secret key (small)

keygen(prm, pk, sk);
std::cout << "Keys generated successfully!\n";
std::cout << "Security: " << prm.lpn_n << "-bit LPN\n\n";
Key generation takes ~859 ms. In production, you’d generate keys once and reuse them.
3

Encrypt values

Encrypt two numbers using the public key and secret key:
// Encrypt values (client-side)
uint64_t val_a = 42;
uint64_t val_b = 17;

Cipher a = enc_value(pk, sk, val_a);
Cipher b = enc_value(pk, sk, val_b);

std::cout << "Encrypted: " << val_a << " and " << val_b << "\n";
std::cout << "Ciphertext size: ~" << (a.E.size() * 80) / 1024 << " KB each\n\n";
Fresh ciphertexts are compact (~42 KB). Encryption takes ~84 ms per value.
4

Perform homomorphic operations

Compute on encrypted data without decrypting it. The server can perform these operations without seeing the plaintext:
// Homomorphic operations (server-side)
Cipher sum  = ct_add(pk, a, b);  // 42 + 17 = 59
Cipher diff = ct_sub(pk, a, b);  // 42 - 17 = 25
Cipher prod = ct_mul(pk, a, b);  // 42 * 17 = 714

std::cout << "Performed operations on encrypted data:\n";
std::cout << "  Addition:       0.012 ms\n";
std::cout << "  Subtraction:    0.012 ms\n";
std::cout << "  Multiplication: 2.47 ms\n\n";
Privacy preserved: The server never sees 42 or 17, only encrypted ciphertexts!
5

Decrypt and verify results

The client decrypts the results using their secret key:
// Decrypt results (client-side)
Fp result_sum  = dec_value(pk, sk, sum);
Fp result_diff = dec_value(pk, sk, diff);
Fp result_prod = dec_value(pk, sk, prod);

std::cout << "Decrypted results:\n";
std::cout << "  42 + 17 = " << result_sum.lo << "\n";
std::cout << "  42 - 17 = " << result_diff.lo << "\n";
std::cout << "  42 * 17 = " << result_prod.lo << "\n";

// Verify correctness
bool all_correct = (result_sum.lo == 59) && 
                   (result_diff.lo == 25) && 
                   (result_prod.lo == 714);

std::cout << "\n" << (all_correct ? "✓ All operations correct!" : "✗ Error in computation") << "\n";
6

Compile and run

Compile with C++17 and optimization flags:
g++ -std=c++17 -O2 -march=native -I./include first_fhe.cpp -o first_fhe
./first_fhe
Expected output:
PVAC-HFHE 0.1.0 Quickstart

Keys generated successfully!
Security: 4096-bit LPN

Encrypted: 42 and 17
Ciphertext size: ~42 KB each

Performed operations on encrypted data:
  Addition:       0.012 ms
  Subtraction:    0.012 ms
  Multiplication: 2.47 ms

Decrypted results:
  42 + 17 = 59
  42 - 17 = 25
  42 * 17 = 714

✓ All operations correct!

Understanding the workflow

1

Key generation (one-time setup)

The client generates a key pair:
  • Public key (pk): Used for encryption and homomorphic operations (can be shared publicly)
  • Secret key (sk): Used for decryption (must be kept private)
2

Encryption (client-side)

The client encrypts sensitive data using both pk and sk, producing a ciphertext that reveals nothing about the plaintext.
3

Homomorphic computation (server-side)

The server performs operations on encrypted data using only the public key. It never sees the plaintext values.
4

Decryption (client-side)

The client decrypts the result using their secret key to reveal the computed value.

Try a more complex example

Polynomial evaluation

Evaluate f(x) = x³ + 2x² + 3x + 4 at x = 5, entirely on encrypted data:
// Encrypt input and coefficients
uint64_t x = 5;
Cipher cx = enc_value(pk, sk, x);
Cipher c2 = enc_value(pk, sk, 2);
Cipher c3 = enc_value(pk, sk, 3);
Cipher c4 = enc_value(pk, sk, 4);

// Compute powers: x^2, x^3
Cipher cx2 = ct_mul(pk, cx, cx);        // x^2
Cipher cx3 = ct_mul(pk, cx2, cx);       // x^3

// Evaluate polynomial: x^3 + 2*x^2 + 3*x + 4
Cipher term1 = cx3;                      // x^3
Cipher term2 = ct_mul(pk, c2, cx2);      // 2*x^2
Cipher term3 = ct_mul(pk, c3, cx);       // 3*x
Cipher term4 = c4;                       // 4

Cipher result = ct_add(pk, ct_add(pk, ct_add(pk, term1, term2), term3), term4);

// Decrypt
Fp poly_result = dec_value(pk, sk, result);
std::cout << "f(5) = " << poly_result.lo << "\n";  // Output: 194
Expected result: 5³ + 2(5²) + 3(5) + 4 = 125 + 50 + 15 + 4 = 194

Text encryption

PVAC-HFHE also supports text encryption via automatic packing:
std::string message = "Hello, FHE!";

// Encrypt text (packs 15 bytes per ciphertext)
std::vector<Cipher> encrypted_text = enc_text(pk, sk, message);
std::cout << "Encrypted " << message.length() << " bytes into " 
          << encrypted_text.size() << " ciphertexts\n";

// Decrypt
std::string decrypted = dec_text(pk, sk, encrypted_text);
std::cout << "Decrypted: " << decrypted << "\n";

Performance considerations

Best for:
  • Scalar arithmetic: 2.9-14.3× faster than RLWE schemes (BFV/CKKS)
  • Small circuit depth (d ≤ 2): Outperforms all schemes
  • Addition-heavy workloads: 10-87× faster than RLWE
  • Compact ciphertexts: 6-85× smaller than RLWE
  • Simple ML inference: Privacy-preserving predictions
Not ideal for:
  • Deep circuits (d ≥ 3): RLWE schemes outperform due to modulus switching
  • SIMD/batching: BFV is 146× faster for batch operations
  • Very deep ML models: Consider CKKS for deep neural networks
Ciphertext size grows exponentially with depth (~3× per multiplication level).
  • Use ct_square(pk, a) instead of ct_mul(pk, a, a) for squaring
  • Use ct_mul_const() and ct_add_const() when multiplying/adding by public constants
  • Minimize circuit depth by factoring and reusing intermediate results
  • Use compiler flags: -O2 -march=native for SIMD acceleration

Next steps

Core concepts

Understand the hypergraph-based encryption and LPN security

Guides

Learn advanced techniques for key generation, depth management, and optimization

Examples

Explore complete working examples including ML inference

API reference

Browse the complete API documentation

Build docs developers (and LLMs) love