Mina Developer Book
  • What is this book about?
  • Module 1
    • Introduction
    • Why ZK?
    • Mathematics of Zero-Knowledge Proofs
    • OPTIONAL: Advanced Resources
  • Module 2
    • zkFibonacci
    • Mina Protocol
    • o1js
      • SmartContract
      • ZkProgram
      • AccountUpdate
      • Merkle Tree
    • End-to-End zkApp development
      • Mastermind introduction
      • Building the zkApp
      • Testing environment
  • Next steps
  • New to blockchain pack
Powered by GitBook
On this page
  1. Module 2
  2. o1js

ZkProgram

PreviousSmartContractNextAccountUpdate

Last updated 10 months ago

ZkProgram enables you to write recursive and incrementally verifiable computation zkSNARK circuits. What this means is this: You choose some private/public inputs, write methods that you want to make computation of and get the public output with the proof. Also, every step you take with your methods needs proofs of the previous step for ensuring the computation is incrementally verified. Since every step also depends on previous step, it is a recursive system, where previous proofs are encapsulated somehow to the most current proof.

Remember ZkFibonacci example: to get to the 31th^{th}th step, you must have computed 30th^{th}thstep, and 29, 28.. to the first step. since you've proved each step and moved to the next step

In , more properties of ZkProgram is given with code examples.

Example:

We will check how recursion is used in choz. You can the whole file. Here we will be sharing the ZkProgram part where only score calculation is written in a recursive manner.

In the github file of this code snippet, methods are defined without async keyword, which is changed in the current version. Methods inside of a ZkProgram should be async.

export const CalculateScore = ZkProgram({
    name: "calculate-score",
    publicInput: Field,
    publicOutput: PublicOutputs,

    methods: {
        baseCase: {
            privateInputs: [Field, Field, Field],

            /* async */method(secureHash: Field, answers: Field, userAnswers: Field, index: Field) {
                index.mul(INDEX_MULTIPLIER).assertEquals(1);
                secureHash.assertEquals(Poseidon.hash([answers, userAnswers, index]));

                return new PublicOutputs(UInt240.from(INITIAL_CORRECTS), UInt240.from(INITIAL_INCORRECTS));
            },
        },

        calculate: {
            privateInputs: [SelfProof, Field, Field, Field],

            /* async */method (
                secureHash: Field,
                earlierProof: SelfProof<Field, PublicOutputs>,
                answers: Field,
                userAnswers: Field,
                index: Field
            ) { 
                earlierProof.verify();
                
                earlierProof.publicInput.assertEquals(Poseidon.hash([answers, userAnswers, index.div(INDEX_MULTIPLIER)]));
                secureHash.assertEquals(Poseidon.hash([answers, userAnswers, index]));

                
            
                const publicOutputs = earlierProof.publicOutput;

                const i = UInt240.from(index);

                const a = UInt240.from(answers);
                const ua = UInt240.from(userAnswers);

                const remainderOfAnswers = a.div(i).mod(ANSWER_DIVISOR).toField();
                const remainderOfUserAnswers = ua.div(i).mod(ANSWER_DIVISOR).toField();

                const equation = remainderOfAnswers.equals(BLANK_VALUE).not().and(remainderOfAnswers.equals(remainderOfUserAnswers));

                const newPublicOutput = Provable.if (
                    equation,
                    PublicOutputs,
                    new PublicOutputs(earlierProof.publicOutput.corrects.add(1), earlierProof.publicOutput.incorrects),
                    new PublicOutputs(earlierProof.publicOutput.corrects, earlierProof.publicOutput.incorrects.add(1)),
                );
                return new PublicOutputs(newPublicOutput.corrects, newPublicOutput.incorrects);
            },
        },
    },
});

In here, they specified the public input and outputs. In every method, there is a SelfProof class which represents the proof of the previous computation.

let proof = await CalculateScore.baseCase(secureHash, answers, user2Answers, index)
let publicOutputs = proof.publicOutput
console.log("starting recursion score:", publicOutputs.corrects.toString())

for (let i = 0; i < 3; i++) {
    index = index.mul(10)
    secureHash = Poseidon.hash([answers, user2Answers, index])

    proof = await CalculateScore.calculate(secureHash, proof, answers, user2Answers, index)
    publicOutputs = proof.publicOutput
            
    console.log("recursion score:", publicOutputs.corrects.toString())
}

In the first line, proof is started with the baseCase with parameters defined in the file. Public output is extracted from the proof object. Recursively proof is calculated and updated in the for loop and in each iteration score is printed.

By reading the other parts of the code, you can see how to create any privacy needed computation with ZkProgram API.

To see how this class is used, let's check the of this code. Some specifics part we should explain here instead of every part of the code.

Mina docs
Choz (prev. Examina)
see
test file