Journey to my first ZK game

I’ve had a passing interest in zero knowledge proofs for a long time, when I first heard of them they felt more like magic than cryptography. I learn best with a project but I hadn’t had any practical need to implement anything with zero knowledge proofs until:

The ZKP Nerd Snipe
The ZKP Nerd Snipe

So I wanted to build a Rock Paper Scissors game for Arbibots owners but I had no idea how to 1) do it on chain 2) hide the moves from other players and 3) ensure that players submit a valid move.

I got a bit stuck here, tweeted this out. 0 = Rock, 1 = paper, 2 = scissors.

I wasn’t yet familiar with some of the ZKP commit/reveal mechanism so the replies were a nudge in the right direction but I still couldn’t quite figure it out. I started researching more building blocks, reading and understanding the tornado cash codebase and circuits, circom docs, dark forest circuits, circom-ecdsa, and the iden3 circom libs.

I started dreaming up some ideas of things to work on from here:

Started to gain some confidence around this whole stack, researching more projects and paying attention to @0xPARC which lead to disclosing a flaw in one of their projects.

But I still couldn’t figure out how to make RPS work. I let it simmer in the back of my mind for a few weeks and then it clicked.

Commit reveal scheme with user provided secret. Verifying the move with the following:

  signal temp;
  temp <== (move - 1) * (move - 2);
  0 === temp * move; // ensure that the move is 0, 1, or 2

The steps of the game is the following

  • Player 1 commits their valid move with a secret
  • Player 2 plays their move in the clear
  • Player 1 reveals their move by providing the same secret

I did not want Player 1 to have to save their secret, but with my stealthdrop flaw investigation I knew that even though a ECDSA signature is not unique, it is idempotent when generated by a wallet. If I provided the same message to sign to the wallet during both steps I could get the same secret out!

Now what if player 1 forgot their move? How about we brute force it inside a circuit???

  // brute force all possible moves
  for(var move = 0; move < totalMoves; move++){
    mimcAttestation[move] = MiMCSponge(2, 220, 1);
    isEqual[move] = IsEqual();

    mimcAttestation[move].ins[0] <== move;
    mimcAttestation[move].ins[1] <== secret;
    mimcAttestation[move].k <== 0;

    isEqual[move].in[0] <== moveAttestation;
    isEqual[move].in[1] <== mimcAttestation[move].outs[0];

    // 0 * move when not equal -- 1 * move when equal
    calcTotal.in[move] <== isEqual[move].out * move;
    // should add up to 1 since only 1 move will be equal
    calcTotalVerify.in[move] <== isEqual[move].out;
  }

  // make sure we found one and only one match
  1 === calcTotalVerify.out;
  
  foundMove <== calcTotal.out;

All three of my requirements for the game: ✅

I wrapped it all up, threw in some solidity and an interface, added some betting (and if player 1 is a jerk and decides to not reveal they lose their bet after a deadline. jerk)

code is here, launch to the Arbibots community soon.

I’ve been taking part in @0xPARC’s ZK learning group #2 and am feeling pretty good about my skills with this stack. On to the next bigger ZK project!

Subscribe to botdad
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.