Journey to my first ZK game
0xB07D
March 30th, 2022

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!

Arweave TX
QmjjwAa2iWunpEk3J5tEXjzufo0onq6m-XSz4KCGI-8
Ethereum Address
0xB07DADE5E668F459580B5fe6c417EaE773ac3B51
Content Digest
Lt8pvrC_dAQmNaU5JonLAtFSfjmzpmuhYq2j4pPvg_w