It is Mastermind, except with words, and both people are playing at the same time. You each pick a secret four-letter word and commit to it on chain. Then you take turns guessing at each other’s word, and when someone guesses at yours you tell them how many hits they got (right letter, right place) and how many blows (right letter, wrong place).
The obvious hole in that is that you could just lie. So every answer comes with a Groth16 proof over BN254 saying the numbers really are what your committed word implies, and that your word is actually in the dictionary and not something you made up on the spot. Your word never leaves your browser. Only the proof does. The type script verifies it and runs the rest of the rules.
The verification rests on @Mulandi_Cecilia’s groth16-ckb, the on-chain Groth16 verifier for CKB-VM. That is the foundational work this is built on.
The bit I want people to look at
I wrote this same game on Starknet before, and that contract had a bug I did not see at the time. A proof does not say “this player answered honestly”. It says “some witness satisfies this circuit for these public inputs”. And the public inputs come in through the witness, which is written by whoever is spending the cell. So they can just make them up.
The fix is that the script has to bind every public input to state it read out of the game cell before it verifies anything: the proven guess against the guess actually pending, the proven commitment against that player’s committed hash, the proven hits and blows against what the output cell is about to claim. Miss any one of those and the proof is decoration. My Starknet version missed three.
If anyone has time to poke holes in that part, I would really appreciate it.
Everything else is polish, that is the thing the game stands on.
Numbers
| Circuit | 3,149 constraints, four public inputs, about 360 ms to prove in the browser |
| Dictionary | 1,807 words, membership proven with a Poseidon Merkle path |
| Script | 93,232 bytes |
| One answering move | 98,209,564 cycles in ckb-testtool, 105,988,770 on testnet once the lock is included |
| Each public input | around 285,000 cycles, which is why I stopped at four |
Precomputing the verifying key into its own cell drops that move to 70,463,905 cycles, 28.3% cheaper, for 384 more bytes on chain. It works and I measured it, but it is off by default because it means shipping another artifact.
Where it is
Testnet, and testnet only. The verifier under it is pre-audit and says so, so please do not put this on mainnet.
If your opponent walks off you can claim the game after a timeout.
Happy to answer anything about the circuit or the cell state machine.
And thanks again to @Mulandi_Cecilia for groth16-ckb. None of this happens without it.