diff --git a/circuit/src/voterollup.circom b/circuit/src/voterollup.circom index c5d7553..737db04 100644 --- a/circuit/src/voterollup.circom +++ b/circuit/src/voterollup.circom @@ -85,7 +85,8 @@ template VoteRollup(nBatchSize, nLevels) { /// check votes -------------------------------------------------------------- - var computedResult = 0; + signal computedResult[nBatchSize+1]; + computedResult[0] <== 0; component sigVerification[nBatchSize]; component processor[nBatchSize]; @@ -144,9 +145,8 @@ template VoteRollup(nBatchSize, nLevels) { lastRootEqual[i].in[0] <== processor[i].newRoot; lastRootEqual[i].in[1] <== newNullifiersRoot; - computedResult = computedResult + voteValue[i]; + computedResult[i+1] <== computedResult[i] + voteValue[i] * verify[i].out; } - result === computedResult; - + result === computedResult[nBatchSize]; } diff --git a/circuit/test/rollup.test.js b/circuit/test/rollup.test.js index cff90a4..23b6648 100644 --- a/circuit/test/rollup.test.js +++ b/circuit/test/rollup.test.js @@ -17,18 +17,18 @@ describe("Test rollup", function () { before( async() => { const circuitCode = ` - include "../src/voterollup.circom"; - component main = VoteRollup(2,2); - `; - - fs.writeFileSync(circuitPath, circuitCode, "utf8"); - circuit = await tester(circuitPath, {reduceConstraints:false}); - await circuit.loadConstraints(); - console.log("Constraints: " + circuit.constraints.length + "\n"); + include "../src/voterollup.circom"; + component main = VoteRollup(2,2); + `; + + fs.writeFileSync(circuitPath, circuitCode, "utf8"); + circuit = await tester(circuitPath, {reduceConstraints:false}); + await circuit.loadConstraints(); + console.log("Constraints: " + circuit.constraints.length + "\n"); }); after( async() => { - fs.unlinkSync(circuitPath); + fs.unlinkSync(circuitPath); }); it("1 batch, 1 vote, batchSize 2", async () => { @@ -63,10 +63,46 @@ describe("Test rollup", function () { let input2 = await rollup.rollup([ await V3.vote(1n,30000n), ]); - + const w2 = await circuit.calculateWitness(input2, { logTrigger:false, logOutput: false, logSet: false }); await circuit.checkConstraints(w2); + }); + + it("Ensure that voteValues that don't have their signature are not counted", async () => { + // The circuit should not allow a prover to generate a valid proof + // without real votes (real votes = voteValue + valid signature), as + // then, it would be possible to present a proof that would pass + // verifications, claiming a result that has no real votes. Meaning + // that a prover could provide a valid zk-proof claiming a result + // without real votes behind it, and the verification would accept it. + // + // If this test fails, means that a prover could provide a valid + // zk-proof claiming a result without real votes behind it. + + let rollup = new Rollup(1n,2,2); + let input = await rollup.rollup([ + await V1.vote(1n,10000n), + ]); + + // add the extra non-valid vote (without any signature) + input.voteValue[1] = 30000n; + // result should still be 10000, without counting that extra 30000 + // voteValue that does not have any signature. Set the result to 40000 + // simulating a malicious prover, to check if the circuit does not + // accept it. + input.result = 40000n; + + try { + const w = await circuit.calculateWitness(input, { logTrigger:false, logOutput: false, logSet: false }); + await circuit.checkConstraints(w); + + // The line will only be reached if no error is thrown above + throw new Error(`If this line is reached, means that the circuit`+ + `counted in the result invalid votes that don't have signatures.`); + } catch(err) { + // the fake vote with value 30000 should not be included in the result + expect(err.message).to.not.contain("If this line is reached, means that"); + } }); }); -