ZkProgram
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 31 step, you must have computed 30step, and 29, 28.. to the first step. since you've proved each step and moved to the next step
In Mina docs, more properties of ZkProgram is given with code examples.
Example: Choz (prev. Examina)
We will check how recursion is used in choz. You can see 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.
To see how this class is used, let's check the test file of this code. Some specifics part we should explain here instead of every part of the code.
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.
Last updated