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 TFHEShort 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 WebAssemblyA 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 RustA 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. Nothing is simulated.
what just happened
Your browser made two keys. The secret key encrypts and decrypts, and it stays here, in a background corner of this tab it never leaves. The evaluation key went to the server. It lets the server compute on your ciphertexts, the piles of encrypted bytes, and that is all it can do. It cannot decrypt anything.
When you press run, your numbers are encrypted locally. The server receives two blobs it can't tell apart from random bytes, runs the operation you picked, and sends back another blob. Your secret key turns that blob into the answer. At no point did the server hold anything it could read.
noise is the budget
TFHE hides every value under deliberate random noise. The noise is the security. Every operation on encrypted data makes it 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.
This is the shape of the problem in the older schemes: noise piles up across several operations until someone spends a bootstrap. TFHE, the scheme running above, refreshes inside every operation instead, so your ciphertexts never get near that line.
bootstrapping, the recharge
In 2009, Craig Gentry showed the way out: bootstrapping. The server takes a noisy ciphertext and runs the decryption recipe on it as encrypted math, using an encrypted copy of the secret key. The value gets decrypted and re-encrypted fresh in one motion, entirely inside the encryption, so nothing is ever exposed. Out comes a ciphertext holding the same value with the noise reset. The case was never opened.
In the first implementation a refresh took from about thirty seconds to about half an hour, depending on how large the parameters were. TFHE's contribution is making bootstrapping cheap enough to run constantly, and programmable on top: while refreshing a ciphertext it can apply a function to the hidden value for free. Every operation you ran above fires many of these internally.
That's why the compound expression can chain multiply, add, and compare without anyone planning a depth budget. The battery recharges mid-flight, so the computation can run as long as it wants.
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 keptA comparison still works, but its answer, true or false, comes out encrypted too. So programs compute both branches and select between them, still under encryption. That's what max(a, b) does above: one comparison, one select, and the server doesn't know which input won. Any program can be rewritten this way. It stops being code that takes branches and becomes a circuit that data flows through.
what's real here, what isn't
The cryptography is TFHE-rs 1.7.0 by Zama, released under the BSD-3-Clause-Clear licence — free to read and build on for research and prototyping, with commercial use needing a separate licence from Zama — with its standard settings at 128-bit securityA 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 browser's address bar. Your browser runs the library compiled to WebAssembly. The server is one Rust file using the same library natively, and it logs byte counts, never contents. Numbers here are whole numbers from 0 to 65,535 (16 bits), so subtraction and multiplication wrap around, like a car odometer rolling over.
Two honest caveats. The library doesn't expose a live noise reading per ciphertext, so the noise bars above are a mental model, drawn to teach rather than measured. And they show the older leveled-scheme rhythm, where noise accumulates until a bootstrap is spent; TFHE bootstraps inside each operation instead. The ciphertexts, timings, and sizes are all real.
Why bother, at these speeds? Because this is the only setup where trusting the server reduces to pure math. A model scoring medical records it never sees, risk computed by a counterparty that learns nothing, search over a mailbox the provider can't read. Other privacy tools get at similar goals: secure enclaves (a walled-off zone inside a processor that promises to hide your data while working on it) and multi-party computation (your data split into meaningless fragments across several servers that compute together). Both still ask you to trust something, the chip maker or the servers not colluding. FHE is the one where the server never sees the values it works on. Worth being precise about what that does and does not buy: it hides the data, not the computation. On its own it does not 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.