Math a Server Can't See
a playground for fully homomorphic encryption
Fully homomorphic encryption, FHE for short, is the strangest idea in cryptography. You encrypt two numbers, hand them to a server that has no way to decrypt them, and it does the math anyway. The answer comes back encrypted. Only your browser can open it.
The name is less scary than it looks. "Homomorphic" comes from the Greek for same form: run a multiply on the encrypted bytes and, hidden inside, the values multiply too. The structure of the math survives the encryption. "Fully" means it works for any computation, not just a couple of lucky operations.
Everything below is real. Your browser generates keys and encrypts using Short for Fast Fully Homomorphic Encryption over the Torus, a scheme first published in 2016 and known for fast bootstrapping. The torus here is a circle: the numbers wrap around so that 1 comes back to 0. This page runs Zama's implementation of it, TFHE-rs. compiled to A format that lets compiled code run inside a web page at near-native speed. Here it's the same cryptography library the server uses, compiled for your browser., a small A programming language built for speed and low-level control. The whole compute server here is one Rust file. server does the encrypted math, and the numbers you see are measured as it runs.
what just happened
The playground just did this with real bytes. Your browser made two keys: a secret key that stays here, and an evaluation key it sent to the server, enough to compute on encrypted numbers but never enough to read them.
Your 12 and 5 turn to noise before they leave. Multiplying the blobs multiplies the values sealed inside them, so what comes back decrypts to 60. The server never held a number it could read.
why the first run waits
You probably felt it: the first run takes a while, the rest are quick. The secret key that opens answers is tiny. The evaluation key the server computes with is about 57 MB. Working on data you can't see takes far more machinery than working on data you can.
It uploads once. The server keeps it for your whole session and reuses it, so every later run sends only a small ciphertext, about 130 KB against the key's 57 MB. The first run pays for the key; the rest just do math.
noise is the budget
TFHE hides every value under deliberate random noise. The noise is the security. Every operation makes the noise grow, and past a threshold, decryption returns garbage.
Early FHE schemes lived and died by this. You had to decide up front how many operations your computation would need, size everything for that, and when the budget ran out, the computation was over. A battery you couldn't recharge.
Past the line, the answer is lost for good. The last step spends a "bootstrap" to reset the noise. That's the next idea.
bootstrapping, the recharge
In 2009, Craig Gentry found the way out: bootstrapping. The server runs the decryption recipe on the noisy ciphertext as encrypted math, using an encrypted copy of the secret key. Unwrapped and rewrapped in one motion, the value comes out fresh, its noise reset. The case was never opened.
The same value walks in drowning in noise and walks out clean. The unwrapping runs under its own layer of encryption, so the middle box does the work yet learns nothing.
The first version was slow: a refresh took thirty seconds to half an hour. TFHE made it fast enough to run inside every operation, and each refresh can even apply a small function at no extra cost. That's why the compound expression in the playground can chain multiply, add, and compare without running out of budget. The battery now recharges mid-computation.
there is no if
One thing encrypted compute can't do: branch on a secret. The CPU can't take the true path or the false path, because picking a path would mean knowing the secret.
// impossible on the server
if (encrypted_age > 18) { grant() }
// what actually runs
is_adult = age.greater_than(18) // the answer is encrypted too
result = is_adult.if_then_else(yes, no) // both branches run, one is kept
// and if_then_else is just arithmetic underneath
result = is_adult × yes + (1 − is_adult) × noSo who decides? Nobody does. The comparison still works, but its verdict comes out encrypted too: a sealed bit, 1 or 0, that even the server can't read. Multiply one branch by the bit and the other by its opposite, add them, and the losing branch quietly becomes zero. The choice dissolves into arithmetic.
This is what the max(a, b) chip runs. Try it and the server card shows exactly these two steps, one compare, one select. The server does the whole computation and still can't say which input won; only your decrypt reveals the choice.
Any program can be rewritten this way. Every branch becomes compute-both-and-blend, every loop runs its worst case. It stops being code that takes paths and becomes a circuit that data flows through. And because both lanes always run, the server's work is identical whichever value wins, so not even timing leaks the answer.
where you'd want this
The trick pays for its cost wherever the data is the sensitive part and the server is something you'd rather not fully trust.
A score without the data behind it
A bank or a clinic runs its model on your encrypted records and returns an encrypted score. It never sees the inputs, and only you can open the answer.
A lookup the server can't read
You check a name against a list, or a query against a database, and the server answers without learning what you asked or what it found.
Data that stays sealed in the cloud
Your files sit encrypted on a machine you don't control and get processed right where they are, so anyone who breaks in finds only noise.
Most of this is still early and compute-heavy. The measured card above put a number on it. But the boundary it draws is real today.
what's real here, what isn't
The cryptography is TFHE-rs 1.7.0 by Zama (BSD-3-Clause-Clear licence: free for research and prototyping, a separate licence from Zama for commercial use). Its standard settings give A measure of how hard the encryption is to break: the best known attacks would need around 2 to the power 128 guesses, which is not happening. Mainstream web encryption targets the same class., the same strength class as the padlock in your address bar. Your browser runs it compiled to WebAssembly; the server is one Rust file using the same library natively, logging byte counts, never contents. Numbers run 0 to 65,535 (16 bits), so subtraction and multiplication wrap around like a car odometer.
One honest caveat: the noise bars above are a teaching picture, not a live reading. The library doesn't expose per-ciphertext noise, and the bars show the older rhythm of letting noise build toward a bootstrap. The ciphertexts, the timings, and the sizes are all measured live.
FHE isn't the only way to compute on data you'd rather not hand over. Secure enclaves wall off a zone inside a processor that promises to hide your data while it works; multi-party computation splits your data into meaningless shares across several servers that compute together. Both still ask you to trust something: the chip maker, or the servers not to collude.
FHE asks for less: the server never sees the values it works on at all. But be precise about what that buys. It hides the data, not the computation. On its own it doesn't prove the server ran the operation you asked for, and the sizes, timings, and choice of operation are all still visible.
Slow is a problem engineering keeps shrinking. Blind is permanent. If you want to go deeper, Zama's docs are good.