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:
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
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!