PricingDemo
Log InGet API Key
Tutorial

Homomorphic Encryption for Python Developers

A practical guide to BFV, CKKS, and TFHE concepts with code examples and production considerations

If you write Python for a living, homomorphic encryption probably sounds like something from a cryptography PhD thesis. It is. But it is also becoming a practical tool that solves real problems: running machine learning on data you cannot see, computing on medical records without exposing patient information, and performing financial analysis on encrypted portfolios. This tutorial explains the three major FHE schemes in terms a Python developer can reason about, shows what the code looks like conceptually, and explains why production FHE systems are written in Rust while remaining accessible through APIs.

Homomorphic encryption (FHE, for Fully Homomorphic Encryption) lets you perform computations on encrypted data without decrypting it. The result, when decrypted by the key holder, matches what you would get by computing on the plaintext. Think of it as a locked glove box: you reach through the gloves, manipulate what is inside, but you never touch the actual objects directly. Mathematically, this means that the encryption function preserves the algebraic structure of the operations you want to perform.

The Three Schemes: BFV, CKKS, and TFHE

BFV: Exact Integer Arithmetic

BFV (Brakerski-Fan-Vercauteren) is the scheme you want when you need exact integer arithmetic on encrypted data. It works by encoding integers into polynomial rings and performing modular arithmetic on the polynomial coefficients. The key property is that addition and multiplication on ciphertexts correspond exactly to addition and multiplication on the plaintext integers, with no rounding or approximation.

In Python terms, think of BFV like operating on a list of integers where every operation happens behind a lock. Here is the conceptual flow:

# Conceptual BFV workflow (pseudocode, not a real library)
params = BFVParams(poly_degree=4096, plaintext_modulus=65537)
secret_key, public_key = keygen(params)

# Encrypt two integer vectors (up to 4096 values each)
a = [42, 17, 99, 3, ...]  # up to 4096 SIMD slots
b = [10, 20, 30, 5, ...]

ct_a = encrypt(public_key, a)
ct_b = encrypt(public_key, b)

# Compute on ciphertexts - server never sees plaintext
ct_sum = ct_a + ct_b        # element-wise addition
ct_prod = ct_a * ct_b       # element-wise multiplication

# Decrypt to get result
result = decrypt(secret_key, ct_prod)
# result == [420, 340, 2970, 15, ...]  (exact, no approximation)

The critical feature of BFV is SIMD (Single Instruction, Multiple Data) batching. A single BFV ciphertext can hold up to 4,096 independent integer values (called slots), and a single homomorphic operation processes all 4,096 slots simultaneously. This is why BFV is the workhorse for batched workloads like biometric authentication, where you need to compute inner products over encrypted feature vectors. H33's production pipeline uses BFV with 4,096 SIMD slots to process 32 users per ciphertext, achieving 2,293,766 authenticated operations per second.

CKKS: Approximate Floating-Point

CKKS (Cheon-Kim-Kim-Song) is the scheme for floating-point computations. Unlike BFV, CKKS trades exact precision for the ability to work with real numbers. Each operation introduces a small amount of noise, similar to floating-point rounding in standard computation. For machine learning inference, statistical analysis, and signal processing on encrypted data, this tradeoff is natural and acceptable.

# Conceptual CKKS workflow (pseudocode)
params = CKKSParams(poly_degree=4096, scale=2**40)
secret_key, public_key = keygen(params)

# Encrypt floating-point vectors
weights = [0.85, -0.32, 1.47, 0.09, ...]
features = [0.91, 0.44, 0.73, 0.88, ...]

ct_w = encrypt(public_key, weights)
ct_f = encrypt(public_key, features)

# Encrypted dot product for ML inference
ct_prod = ct_w * ct_f
ct_sum = sum_slots(ct_prod)  # rotate-and-add to sum slots

# Decrypt
score = decrypt(secret_key, ct_sum)
# score ~= 1.6823 (approximate, within precision bounds)

The "approximate" in CKKS is well-bounded. You choose a scale parameter that determines how many bits of precision you maintain, and the scheme guarantees that the error stays within that bound through a certain number of multiplications (the multiplicative depth). For practical workloads like computing the average blood pressure across encrypted patient records or scoring a credit model on encrypted financial data, CKKS precision is more than sufficient.

TFHE: Bit-Level Boolean Operations

TFHE (Torus Fully Homomorphic Encryption) operates at the bit level. Instead of encrypting integers or floating-point numbers, TFHE encrypts individual bits and provides homomorphic versions of boolean gates: AND, OR, NOT, XOR, and more. Each gate operation includes a bootstrapping step that resets the noise, which means you can chain an unlimited number of operations without the noise growing beyond the decryption threshold.

# Conceptual TFHE workflow (pseudocode)
params = TFHEParams(security_level=128)
secret_key, public_key = keygen(params)

# Encrypt individual bits
bit_a = encrypt_bit(public_key, 1)
bit_b = encrypt_bit(public_key, 0)

# Boolean operations on encrypted bits
ct_and = gate_and(bit_a, bit_b)   # 1 AND 0 = 0
ct_or  = gate_or(bit_a, bit_b)    # 1 OR 0 = 1
ct_xor = gate_xor(bit_a, bit_b)   # 1 XOR 0 = 1

# Each gate bootstraps automatically - unlimited depth
ct_not = gate_not(ct_and)          # NOT 0 = 1

# Build arbitrary circuits from gates
# Example: encrypted 8-bit greater-than comparison
ct_result = encrypted_greater_than_8bit(ct_x, ct_y)

TFHE is the right choice when you need to perform arbitrary boolean logic on encrypted data, such as encrypted comparisons, encrypted sorting, or encrypted control flow. It is the most flexible of the three schemes but also the slowest per operation, because each gate involves a computationally expensive bootstrapping step. H33's TFHE implementation achieves 768 operations per second for 8-bit greater-than comparisons on 96 channels, which is fast enough for many real-world circuits but orders of magnitude slower than BFV's batched throughput.

Choosing the Right Scheme

The choice between BFV, CKKS, and TFHE depends entirely on your data types and operations. Here is the decision framework that H33 uses internally:

Use BFV when your computation is integer arithmetic on large batches. Biometric matching (inner products), encrypted database queries (equality and range checks over encoded values), and access control decisions (integer threshold comparisons) are all BFV workloads. The 4,096 SIMD slots make BFV dramatically more efficient than computing on individual values.

Use CKKS when your computation involves real numbers. Machine learning inference on encrypted features, statistical aggregation on encrypted records, and financial risk scoring on encrypted portfolios are CKKS workloads. Accept that results will have bounded approximation error, just like regular floating-point arithmetic.

Use TFHE when your computation requires arbitrary boolean logic. Encrypted string matching (character-by-character comparison), encrypted conditional branching (if-then-else on encrypted conditions), and encrypted hash computation (bit-level mixing operations) are TFHE workloads. Accept that throughput will be much lower than BFV or CKKS.

The Noise Budget Problem

Every FHE scheme has a noise problem. When you encrypt a value, a small amount of random noise is added to the ciphertext. This noise is what makes the encryption secure (without it, an attacker could solve a system of linear equations to recover the key). But every homomorphic operation increases the noise. When the noise exceeds a threshold, decryption fails and produces garbage.

In BFV and CKKS, you start with a noise budget determined by your parameter choices, and every operation consumes part of that budget. Multiplications consume much more budget than additions. The maximum number of sequential multiplications you can perform before decryption fails is called the multiplicative depth of your circuit. Choosing parameters is a balancing act: larger parameters give you more noise budget (and thus more computation depth) but make every operation slower and every ciphertext larger.

TFHE sidesteps this problem through programmable bootstrapping: after every gate operation, the noise is reset to a fresh level. This gives TFHE unlimited computation depth at the cost of a bootstrapping operation per gate. The trade-off is that each gate is much more expensive than a single BFV or CKKS operation, but you never run out of noise budget.

As a Python developer, you typically do not manage noise budgets directly. The FHE system selects parameters based on the computation you need to perform. But understanding that noise exists helps you reason about why FHE has the performance characteristics it does: larger parameters mean more noise tolerance, which means larger polynomials, which means more computation per operation.

Why Production FHE Needs Rust

Python is a wonderful language for prototyping, data analysis, and application logic. It is not the right language for the inner loop of a production FHE system. The reasons are concrete and measurable.

First, FHE operations are dominated by polynomial arithmetic on large coefficient arrays. BFV with a polynomial degree of 4,096 means every operation manipulates arrays of 4,096 64-bit integers through modular multiplications and additions. Python's interpreter overhead on these tight numerical loops is orders of magnitude slower than compiled code. NumPy helps, but FHE's modular arithmetic patterns do not map cleanly to NumPy's BLAS-optimized linear algebra primitives.

Second, memory safety matters in cryptographic code. A buffer overflow in an FHE implementation can leak secret key material or allow an attacker to manipulate ciphertexts. Rust's ownership system prevents these bugs at compile time, without the runtime overhead of garbage collection. C++ can match Rust's performance but cannot match its memory safety guarantees.

Third, parallelism. Production FHE workloads need to saturate all available CPU cores. H33 runs on 192-vCPU Graviton4 machines with all cores executing FHE operations concurrently. Rust's type system prevents data races at compile time, allowing fearless concurrency across all 192 cores. Python's GIL makes this impossible without multiprocessing, which introduces serialization overhead for the large ciphertext objects that FHE operations pass between stages.

This does not mean Python developers cannot use FHE. It means the right architecture is to write the FHE engine in Rust and expose it through an API that Python (or any other language) can call. H33 takes this approach: the entire pipeline is Rust, and developers interact with it through a REST API or through the h33-cli command-line tool.

Getting Started with h33-cli

The h33-cli tool is the fastest way to start working with H33's FHE pipeline from a development machine. It wraps the REST API in a command-line interface that handles authentication, parameter selection, and result formatting.

# Install h33-cli (see docs for platform-specific instructions)
# Authenticate with your API key
h33 auth login --key YOUR_API_KEY

# Encrypt a biometric feature vector (BFV mode)
h33 encrypt --scheme bfv --input features.json --output encrypted.h33

# Run encrypted computation
h33 compute --input encrypted.h33 --circuit match --output result.h33

# Verify and decrypt
h33 verify --input result.h33
h33 decrypt --input result.h33 --key your-secret.key

Each command maps to an API call. The encrypt command selects parameters automatically based on the input size and the computation you intend to perform. The compute command runs the homomorphic operation on the server without exposing your plaintext data. The verify command checks the STARK proof and post-quantum signatures. The decrypt command recovers the plaintext result using your secret key, which never leaves your machine.

What Happens Behind the API

When you make a single API call to H33, here is what happens inside the pipeline. Your input data is encrypted using BFV with parameters selected for your workload. The ciphertext is processed through the homomorphic computation circuit. A STARK zero-knowledge proof is generated to verify the computation was performed correctly. The result is signed with three post-quantum signature families based on three independent hardness assumptions. The full attestation is distilled into a 74-byte H33-74 primitive. All of this happens in a single process, in shared memory, with no network hops between stages, at 38 microseconds per authentication.

As a Python developer, you see a simple API call and a JSON response. Behind that response is a complete cryptographic pipeline that would take a dedicated team years to build, audit, and optimize. That is the value proposition of an API-first approach to FHE: you get production-grade encrypted computation without becoming a cryptographer yourself.

Common Misconceptions

FHE is not slow. The perception that FHE is too slow for production comes from early implementations and from benchmarking individual operations without batching. H33's BFV pipeline processes over 2.29 million operations per second with full ZKP verification and PQ signing. The key insight is SIMD batching: one ciphertext operation processes 4,096 values simultaneously, which amortizes the cost of FHE across thousands of plaintext operations.

FHE is not a replacement for traditional encryption. AES encrypts data at rest and TLS encrypts data in transit. FHE encrypts data during computation. These are three different threat models, and a complete security architecture uses all three. H33 provides the computation layer; you still need standard encryption for storage and transport.

FHE does not eliminate the need for access control. Even though the compute server never sees plaintext data, someone still holds the decryption key. FHE shifts the trust boundary from the compute environment to the key holder. Proper key management, access policies, and audit trails are still essential.

Next Steps

Start with the H33 documentation to understand the API surface and supported computation types. The free tier allows you to experiment with BFV, CKKS, and TFHE operations on small datasets. For production workloads, the dedicated tier provides sustained throughput with SLA guarantees. The h33-cli tool lets you interact with the pipeline from the command line, and the REST API integrates with any language that can make HTTP requests, including Python.

The gap between understanding FHE and deploying it in production is closing rapidly. Five years ago, FHE was an academic curiosity. Today, it processes millions of operations per second with post-quantum attestation. As a Python developer, you do not need to implement the cryptography yourself. You need to understand what it can do for your application and how to call the API. That is what this tutorial was for.

Ready to Try FHE?

Start with the free tier and make your first encrypted computation in minutes.

Verify It Yourself