Cover photo

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.

https://twitter.com/0xB07DAD/status/1483935652500553731

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:

https://twitter.com/0xB07DAD/status/1489323754773573633?s=20&t=vNu55l_j46h1p8mV3XwwhA

https://twitter.com/0xB07DAD/status/1493336351453765638?s=20&t=vNu55l_j46h1p8mV3XwwhA

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.

https://twitter.com/0xPARC/status/1493704577002049537

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.

https://twitter.com/0xB07DAD/status/1499247835908243456?s=20&t=vNu55l_j46h1p8mV3XwwhA

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.

https://github.com/botdad/arbirps/

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!