← writing

The other side of the wall: FHE where ZK stops

When I built ZK dark chess I was careful about where it stopped. It proves your move is legal against your own committed board — right piece, legal geometry, clear path over your pieces — and hides the rest. But fog-of-war chess has three things that don’t fit in that box, because they depend on the opponent’s hidden pieces:

  • capturing a hidden piece — is the square I’m moving onto occupied?
  • a blocked slider — is there an enemy piece on my rook’s path?
  • check — does a hidden enemy piece attack my king?

No proof about your board can answer these. They’re joint predicates over two secret boards, and that’s a real wall — the place every honest ZK fog-of-war project (mine included) stops. This post is about crossing it.

Why FHE is the tool

A ZK proof convinces someone of a statement about a witness you hold. But here neither player holds the whole truth: whether your king is in check is a function of my hidden pieces and your hidden king together. You can’t prove what you can’t see.

Fully homomorphic encryption flips the move. Instead of proving a statement about plaintext you have, you compute on ciphertext you don’t. Encrypt the opponent’s board; evaluate “is this square attacked?” homomorphically; decrypt only the one resulting bit. The board stays encrypted the whole way through — the computation itself never sees a plaintext piece.

So I built it, for real, with Zama’s tfhe-rs.

ZK decides own-board legality; FHE decides joint predicates over the encrypted opponent board Two halves of fog of war ZK — your own board prove your move is legal vs your committed pieces · hides the rest FHE — the opponent's board compute capture / block / check on ciphertext · reveal only the bit the wall → Measured over an ENCRYPTED board (Apple Silicon laptop): capture: ~35 ms blocked slider: ~135 ms in check?: ~7.6 s (one bit) One key locally = the COMPUTATION is referee-free. Distributing the key = the TRUST.
ZK covers legality against your own board; FHE covers the predicates that touch the opponent's hidden pieces. The check predicate — the one ZK can't decide — runs on an encrypted board in 7.6 seconds and leaks a single bit.

A real check, over an encrypted board

in_check is the interesting one. Over the opponent’s encrypted 64-cell board it walks the eight rays from the king: at each step it OR-accumulates “an enemy rook/queen (or bishop/queen) sits here,” gated by a running encrypted “the ray is still clear” flag, then folds in knight, pawn, and king-adjacency offsets — all reduced to a single encrypted boolean. (Your own pieces block rays too, but you know your board in the clear, so those truncate the walk before any FHE happens.) The contract decrypts exactly that one bit.

The tests are the proof it’s real: each predicate is computed on encrypted boards and asserted equal to a plaintext oracle on known positions — an open rook on the file is check; the same rook behind an enemy pawn is not; a knight on the right square is check; a quiet position isn’t. It runs:

1. occupancy(e8)   = true   (captured piece = rook)   [35 ms]
2. blocked(a1->a4) = true   (pawn on a3 blocks)        [135 ms]
3. in_check(e1)    = true   (open enemy rook)          [7.6 s]

Each line leaks exactly one bit — or, for a capture, the single square you captured on, which is precisely what fog of war is supposed to reveal.

The line I won’t cross: computation vs trust

Here’s the part it would be easy to oversell, so I’ll be blunt. My demo encrypts both boards under one key. Whoever holds that key can decrypt everything — which is exactly the referee I set out to remove. So the local program proves one thing and not another:

  • The computation is referee-free. The predicate runs on ciphertext and only the answer bit is ever revealed. That’s real, and it’s what cargo test checks.
  • The trust is not. Removing the single key-holder needs a threshold KMS — a committee where no member can decrypt alone. That’s Zama’s fhEVM: the board lives on-chain as ciphertext handles, a coprocessor runs the FHE, and the committee reveals only the ACL-permitted bit. I wrote that contract as a design (FogChessFHE.sol) — it needs the Zama network, so it’s designed, not deployed.

Update. I took that contract off the sketchpad. FogChessFHE.sol now compiles against the real fhEVM SDK (@fhevm/solidity 0.11.1) and runs in its mock coprocessor: a hardhat test commits an encrypted 64-cell board on-chain, runs occupancy and the full inCheck ray-walk on the coprocessor, and the caller decrypts exactly one ACL-gated bit — open rook on the file is check, blocked rook is not, the same positions the Rust tests use. So the on-chain path is real now, not pseudocode. What’s still designed-and-not-deployed is precisely the trust: the mock decrypts with one key, where a live deployment uses the threshold committee. A Sepolia run against the real Gateway/KMS is the one remaining step — and it’s the one that matters most.

Update 2 — it’s live. I took the last step. FogChessFHE is deployed on Ethereum Sepolia at 0x99db…da12. An encrypted 64-cell board was committed on-chain, inCheck ran on the live coprocessor, and the result bit was decrypted by the real threshold KMS committee — no single key-holder, not a mock key — returning true for an open rook on the e-file. So the trust is now real, not designed: the one thing that mattered most is done. The honest residual is small — a single demo signer played both seats (a real game uses two keys), and it’s testnet with FHE-heavy gas. But the load-bearing claim, “no one is the referee,” is now true on a live chain.

The maths runs today; the trust distribution is drawn, not built. Conflating those two is how “trustless” gets oversold, and I’d rather show you the seam.

The honest cost

FHE isn’t free: 7.6 seconds for one check, and an estimated eight minutes if you also hide the king’s square (you’d multiplex the predicate over all 64 candidates). That’s correspondence chess, not blitz — and on fhEVM it’s the coprocessor’s latency, not your laptop’s. But “minutes per move, leaking one bit” is a real answer to a problem that didn’t have a referee-free one before.

The whole arc

Put the three pieces together and you can see the shape of a real kriegspiel protocol: ZK proves each player’s move legal against their own hidden board; FHE decides the predicates that couple the two boards; a threshold KMS holds the key so no one is the referee. Each tool does precisely what it claims and not an inch more — the same lesson as randomness, proof binding, and CoW settlement: the cryptography is rarely the hard part. Composing it honestly, and marking exactly where the real ends and the designed begins, is.

The running predicates, the tests, and the fhEVM sketch are at fhe-dark-chess.