zk-mastermind
v2.2.0
Published
[](https://github.com/navigators-exploration-team/recursive-mastermind-zkApp/actions/workflows/ci.yml)
Maintainers
Readme
Mina Mastermind

Table of Contents
Understanding the Mastermind Game
Overview
- The game involves two players: a
Code Masterand aCode Breaker. - Inspired by mastermind-noir, this version replaces colored pegs with a combination of 4 unique digits between
0and7.
Game Rules
The Code Master hosts a game and sets a secret combination for the Code Breaker to guess.
The Code Breaker makes a guess and waits for the Code Master to provide a clue.
The clue indicates the following:
- Hits: Digits that are correctly guessed and in the correct position.
- Blows: Digits that are correct but in the wrong position.
Example:
| | P1 | P2 | P3 | P4 | | ------ | --- | --- | --- | --- | | Secret | 5 | 6 | 3 | 4 | | Guess | 5 | 7 | 2 | 6 |
| | Hits | Blows | | ---- | ---- | ----- | | Clue | 1 | 1 |
- Code Master's secret combination: 5 6 3 4
- Code Breaker's guess: 5 7 2 6
- Result:
1hit and1blow.- The hit is
5in the first position. - The blow is
6in the fourth position.
- The hit is
The game continues with alternating guesses and clues until the Code Breaker achieves 4 hits and uncovers the secret combination or fails to do so within the maximum allowed attempts = 7.
Mastermind zkApp Structure
Following the game rules, the MastermindZkApp should be deployed:
The game master uses the
initGamemethod to set the secret of the game, set referee public key, set the reward amount for solving the game and send it to the contract. This method takes these parameters:secretCombination: The secret combination set by the Code Master.salt: A random field to salt the secret combination before hashing.refereePubKey: The public key of the referee who will penalize misbehaving players.rewardAmount: The amount of tokens to be rewarded to the codeBreaker upon solving the game.
The initiated game is accepted by another player using the
acceptGamemethod, the player becomes a code breaker by depositing a reward equal to the amount deposited by the game master and the game starts. Additionally, a finalize slot is determined to set the maximum end time of the game.After the game is started, players continue playing the game until the
finalize slotis reached, either by using recursion withStepProgramalong with theStepProgramProofthey generated off-chain, or by using themakeGuessmethod for the code breaker and thegiveCluemethod for the code master.The Code Master submits the solution again to be checked against the previous guess and provides a clue.
The Code Breaker should analyze the given clue and make another meaningful guess.
The game continues by alternating between
makeGuessandgiveCluemethods until the Code Breaker either uncovers the secret combination or fails by exceeding the allowedMAX_ATTEMPT = 7, concluding the game.
Now, let's dive deeper into the states and methods of our Mastermind zkApp.
Mastermind States
The Mastermind zkApp uses all 8 available states.
compressedState
- The
compressedStateis a packed state variable that usesGameStateclass to compress and decompress the state variable. - The
GameStateclass contains the following states:rewardAmount(UInt64) - the amount of tokens to be rewarded to the winner.finalizeSlot(UInt32) - the slot number when the game is hard finalized.turnCount(UInt8) - the number of turns taken in the game.isSolved(Bool) - indicates whether the secret combination has been solved.lastPlayedSlot(UInt32) - Updated to reflect the timestamp of the latest action; used as a reference in the fully on-chain fallback mode to enforce round-based penalization.
codemasterId & codebreakerId
These states represent the unique identifiers of the players, which are stored as the hash of their
PublicKey.Player identifiers are crucial for correctly associating each method call with the appropriate player, such as linking
makeGuessto the Code Breaker andgiveClueto the Code Master andclaimRewardto the winner.
refereeId
This state represents the unique identifier of the referee, stored as the hash of their
PublicKey.The referee is responsible for penalizing misbehaving players, ensuring fair play.
solutionHash
The solution must remain private; otherwise, the game loses its purpose. Therefore, whenever the Code Master provides a clue, they should enter the
secretCombinationas a method parameter.To maintain the integrity of the solution, the solution is hashed and stored on-chain when the game is first created.
Each time the Code Master calls the
giveCluemethod, the entered private secret combination is salted, hashed, and compared against thesolutionHashstored on-chain. This process ensures the integrity of the combination and helps prevent side-channel attacks.Note: Unlike player IDs, where hashing is used for data compression, here it is used to preserve the privacy of the on-chain state and to ensure the integrity of the values entered privately with each method call.
packedGuessHistory
This state is a compressed state variable that stores the history of all guesses made by the Code Breaker. It uses
Combinationclass to compress the state by usingupdateHistorymethod.Each guess is represented as a single
Fieldvalue, with the four digits packed into oneFieldwith bit manipulation.Each digit is represented as a 3-bit number, allowing for a range of
0to7. The digits are combined and stored on-chain as a12-bit * MAX_ATTEMPTfield in decimal.
packedClueHistory
This state is a compressed state variable that stores the history of all clues given by the Code Master. It uses
Clueclass to compress the state by usingupdateHistorymethod.Each clue is represented as a two
Fieldvalue, with the hits and blows packed into oneFieldwith bit manipulation.Each clue is represented as a 3-bit number, allowing for a range of
0to4. The hits and blows are combined and stored on-chain as a6-bit * MAX_ATTEMPTfield in decimal.
Mastermind Methods
initGame
This method should be called first and can be called only once to initialize the game.
It is recommended to call this method with the same transaction that deploys the zkApp, due to lower fee and security reasons.
initGameis the first method called to set up the game, initialize the secret combination, define the reward amount for the challenge and the referee's public key. The first user to call this method with valid inputs will be designated as the code master.This method takes five arguments:
secretCombination: The secret combination set by the Code Master.salt: A random field to salt the secret combination before hashing.refereePubKey: The public key of the referee who will penalize misbehaving players.rewardAmount: The amount of tokens to be rewarded to the codeBreaker upon solving the game.
The method executes successfully when the following conditions are met:
The game is not already initialized.
The
secretCombinationis validated with theCombinationclass, which separates the digits and checks for uniqueness and0to7range through thevalidatemethod.The
rewardAmountis received from the caller and stored in the contract.The secret combination is then hashed with the salt and stored on-chain as
solutionHash.The caller's
PublicKeyis hashed and stored on-chain ascodemasterIdonce the combination is validated.Finally, the
turnCountis incremented, signaling that the game is ready for the code breaker tomakeGuess.
For simplicity, security checks in this method have been abstracted. For more details, please refer to the Security Considerations.
submitGameProof
This method should be called after the game is initialized and the code breaker has accepted the game. The code breaker and the code master should generate a recursive proof off-chain using the
StepProgramand submit it to the contract using this method.This method can be called by anyone who has the valid proof generated by the
StepProgram.The method takes
proofandwinnerPubKeyas arguments and updates the contract states when the following conditions are met:- The game is accepted by the code breaker.
- The game is not finalized (i.e., the finalize slot has not been reached), and the game is not solved.
- The given proof is valid and belongs to the current game.
- The proof has more steps than the current game state (i.e., the proof is not belonging to the previous game state).
The prize amount will be sent to the winner if the following optional conditions are met:
- The game is solved (i.e., the code breaker has guessed the secret combination, or exceeded the maximum number of attempts).
- The provided
winnerPubKeyhash is equal to the calculated winner's hash, which is the code breaker if the game is solved, or the code master if the game is not solved.
If the
winnerPubKeyis not provided correctly, the contract will not transfer the reward to the winner, and the winner must call theclaimRewardmethod to claim their reward.The method updates the
turnCountandisSolvedstates based on the proof provided.isSolvedis set totrueif the proof's turn count is not exceeding the max attempts and the game is solved.
claimReward
This method should be called after the game is finalized by the code breaker (i.e., the game is solved or the max attempts are reached) or by the code master (i.e., the game is not solved and the finalize slot is reached).
The method can be called by only the winner of the game, if the following conditions are met contract will transfer the reward to the winner:
- The game is finalized.
- For the code breaker, the game must be solved within the max attempts and the proof must be submitted before the finalize slot.
- For the code master, the game either must not be solved within the max attempts or the finalize slot must be reached without the code breaker solving the game.
- The caller is the winner of the game.
- The game is finalized.
forfeitWin
This method should be called by the referee to penalize the player who misbehaves during the game. When the player forfeits the game, the reward is transferred to the other player.
The method can be called by the referee only, taking the
playerPubKeyas an argument. The method updates the contract states when the following conditions are met:- The game is accepted by the code breaker.
- The caller is the referee.
- The contract still has the reward (i.e., the claimReward method has not been called).
- The provided
playerPubKeyis either the code master or the code breaker.
The current situation of the penalty mechanism is simple and trusted. It assumes that the referee is honest and will not penalize the player without a valid reason. In the future, we will explore more advanced mechanisms to penalize players without the need for trust of reputation.
makeGuess
This method is not intended to be used in the normal game flow. It is used only for recovery purposes when the code breaker fails to send the proof to code master or server went down before the code master could finalize the game.
The method can be called by the code breaker only, taking the
guessCombinationas an argument. The method updates the contract states when the following conditions are met:- The game is is accepted by the code breaker.
- The game is not finalized (i.e., the finalize slot has not been reached), and the game is not solved.
- The caller is the code breaker.
- The provided
guessCombinationis a valid guess. - The
turnCountis less than the2 * MAX_ATTEMPT = 14and odd (i.e., it is the code breaker's turn).
After all the preceding checks pass, the code breaker's guess combination is validated, stored on-chain, and the
turnCountis incremented. This then awaits the code master to read the guess and provide a clue.
giveClue
Similar to the
makeGuessmethod, there are a few restrictions on calling this method to maintain a consistent progression of the game:- The caller is restricted to be only the registered code master.
- The game must be accepted by the code breaker.
- The correct sequence is enforced by checking that
turnCountis non-zero (to avoid colliding with thecreateGamemethod call) and even. - If the game
isSolved, this method is blocked and cannot be executed. - The game must not be finalized (i.e., the finalize slot has not been reached), and the game is not solved.
- The
turnCountis less than or equal to2 * MAX_ATTEMPT = 14and even (i.e., it is the code master's turn). - The provided
secretCombinationandsaltare valid and match the storedsolutionHash.
Next, the guess from the previous turn is fetched, separated, and compared against the secret combination digits to provide a clue:
- The clue is generated with
Clueclass, which compares the digits of the guess and the secret combination and returns the hits and blows. - If the clue results in 4 hits the game is marked as solved, and the
isSolvedstate is set toBool(true).
- The clue is generated with
Finally, the
turnCountis incremented, making it odd and awaiting the code breaker to deserialize and read the clue before making a meaningful guess—assuming the game is not already solved or has not reached the maximum number of attempts.
Step Program
- The StepProgram is a recursive zk-SNARK circuit that allows the Code Breaker and Code Master to generate proofs off-chain and submit them to the zkApp contract.
Step Program Public Inputs
authPubKey: is the public key of the player who is generating the proof.authSignature: is the signature of the player who is generating the proof.
Step Program Public Outputs
codeMasterIdandcodeBreakerId: should be same with the on-chain values of players.solutionHash: should also be same with the one on-chain value.lastCompressedGuessandlastcompressedClue: are the values obtained from themakeGuessandgiveCluemethods, respectively.turnCount: is the turn count of the game. Even turn counts represent the turns of code master and odd turn counts represent the turn of the code breaker.packedGuessHistory: is a serialized data that keeps all guesses done so far.packedClueHistory: is a serialized data that keeps all clues given so far.
Step Program Methods
createGame
This method is called by the Code Master to create a new game proof by setting the secret combination and salt. You can think of this as base case of the recursion.
The method takes two arguments as private inputs besides the public inputs:
secretCombination: The secret combination set by the Code Master.salt: A random field to salt the secret combination before hashing.
The method returns the Step Program Proof with public outputs of the Step Program, if the following conditions are met:
- The
secretCombinationis valid as specified in theinitGamemethod. - The provided signature is valid for the given public key. (Will be saved as
codeMasterId)
- The
makeGuess
This method is called by the Code Breaker to generate a step proof with the guess combination by using recursion.
The method takes the
guessCombinationandprevious proofas private inputs besides the public inputs:guessCombination: The guess combination made by the Code Breaker.previousProof: The previous proof generated by the Code Master.
The method appends last guess to the
packedGuessHistoryand updates the public outputs of the Step Program, when the following conditions are met:- The
previousProofis valid and belongs to the current game state (i.e., public outputs of the previous proof match the current game state). - The provided signature is valid for the given public key.(Will be saved as
codeBreakerIdif the first guess, otherwise it need to be the same with the previous proof'scodeBreakerId)
- The
giveClue
This method is called by the Code Master to generate a step proof with the clue combination by using recursion.
The method takes the
secretCombination,saltandprevious proofas private inputs besides the public inputs:secretCombination: The secret combination set by the Code Master.salt: A random field to salt the secret combination before hashing.previousProof: The previous proof generated by the Code Breaker.
The method appends the serialized clue to the
packedClueHistoryand updates the public outputs of the Step Program, when the following conditions are met:- The
previousProofis valid and belongs to the current game state (i.e., public outputs of the previous proof match the current game state). - The provided signature is valid for the given public key.(It need to be the same with the previous proof's
codeMasterId) - The hash of
secretCombinationandsaltis equal to thesolutionHashof the previous proof. - The
turnCountis even and greater than zero.
- The
Project Structure
The MastermindZkApp project is organized to enhance clarity, maintainability, and ease of testing. Key files include:
src/
├──benchmark/
│ └── browser/
│ └── node/
| └──benchmark.md
├──test/
│ └── Mastermind.test.ts
│ └── mock.ts
│ └── stepProgram.test.ts
│ └── testUtils.test.ts
│ └── testUtils.ts
│ └── utils.test.ts
├── constants.ts
├── index.ts
├── Mastermind.ts
├── stepProgram.ts
└── utils.tsbenchmark/: Contains benchmarking scripts for the zkApp, which can be run in the browser or NodeJS environment. Thebenchmark.mdfile provides previous benchmarking results for reference.test/: Contains test files for the zkApp, including unit tests for the zkApp methods, integration tests with using local blockchain, Lightnet and Devnet.mock.ts: Provides mock private keys and public keys for testing purposes for devnet.testUtils.ts: Contains reusable utility functions for testing, such as generating step proofs or the proof of the whole game.Mastermind.test.ts: Contains integration tests using alocalBlockchain. These tests validate the zkApp’s behavior in various scenarios, ensuring it functions correctly and securely in a simulated environment.stepProgram.test.ts: Contains unit tests for theStepProgrammethods, ensuring the zkProgram generates proofs correctly and maintains the game state accurately.utils.test.ts: Provides unit tests for the functions inutils.ts, ensuring each logic component works correctly before integration into the zkApp. This helps catch issues early and improves overall reliability.testUtils.test.ts: Contains unit tests for the functions intestUtils.ts, ensuring each logic component works correctly before integration into the zkApp. This helps catch issues early and improves overall reliability.
constants.ts: Contains constants used in the zkApp, such asMAX_ATTEMPT, andPER_TURN_GAME_DURATIONwhich are essential for maintaining the game rules and logic.index.ts: Serves as the entry point, importing and exporting all essential smart contract classes for the zkApp(s).Mastermind.ts: The core file where the game logic and blockchain interactions are defined. This ensures that the zkApp's mechanics and state management are clear and maintainable.stepProgram.ts: Contains theStepProgramzkProgram, which enables the Code Breaker and Code Master to generate recursive proofs off-chain and submit them to the zkApp contract. This way, the game can be played securely and efficiently without needing to Mina transactions for every turn.utils.ts: Contains reusable utility functions that act as standalonecircuit templates. By isolating these logic components, the main zkApp code remains focused and uncluttered.
For more details refer to the
How to Build & Test
How to build
npm run buildHow to run tests
# default mode in local logs disabled
npm run test
# lightnet mode with logs disabled
TEST_ENV=lightnet LOGS_ENABLED=0 npm run test
# devnet mode with logs enabled
TEST_ENV=devnet LOGS_ENABLED=1 npm run testHow to run benchmark
For NodeJS
npm run benchmarkFor Browser
cd src/benchmark/browser
npm install
npm run devHow to run coverage
npm run coverage