From fc5a4f1169718392d2005c90b7ce70040d238295 Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Fri, 12 Apr 2024 17:13:40 +0100 Subject: [PATCH] Add plonk to semaphore scripts --- .../src/templates/semaphore/index.ts | 12 +- .../scripts/groth16/groth16_trusted_setup.ts | 53 ++ .../scripts/groth16/groth16_zkproof.ts | 108 +++ .../scripts/plonk/plonk_trusted_setup.ts | 37 + .../semaphore/scripts/plonk/plonk_zkproof.ts | 139 ++++ .../templates/plonk_verifier.sol.ejs | 710 ++++++++++++++++++ 6 files changed, 1056 insertions(+), 3 deletions(-) create mode 100644 libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_trusted_setup.ts create mode 100644 libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts create mode 100644 libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_trusted_setup.ts create mode 100644 libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts create mode 100644 libs/remix-ws-templates/src/templates/semaphore/templates/plonk_verifier.sol.ejs diff --git a/libs/remix-ws-templates/src/templates/semaphore/index.ts b/libs/remix-ws-templates/src/templates/semaphore/index.ts index f53eb5459e..37f8484829 100644 --- a/libs/remix-ws-templates/src/templates/semaphore/index.ts +++ b/libs/remix-ws-templates/src/templates/semaphore/index.ts @@ -5,12 +5,18 @@ export default async () => { // @ts-ignore 'circuits/tree.circom': (await import('!!raw-loader!./circuits/tree.circom')).default, // @ts-ignore - 'scripts/run_setup.ts': (await import('!!raw-loader!./scripts/run_setup.ts')).default, + 'scripts/groth16/groth16_trusted_setup.ts': (await import('!!raw-loader!./scripts/groth16/groth16_trusted_setup.ts')).default, // @ts-ignore - 'scripts/run_verification.ts': (await import('!!raw-loader!./scripts/run_verification.ts')).default, + 'scripts/groth16/groth16_zkproof.ts': (await import('!!raw-loader!./scripts/groth16/groth16_zkproof.ts')).default, + // @ts-ignore + 'scripts/plonk/plonk_trusted_setup.ts': (await import('!!raw-loader!./scripts/plonk/plonk_trusted_setup.ts')).default, + // @ts-ignore + 'scripts/plonk/plonk_zkproof.ts': (await import('!!raw-loader!./scripts/plonk/plonk_zkproof.ts')).default, // @ts-ignore 'templates/groth16_verifier.sol.ejs': (await import('!!raw-loader!./templates/groth16_verifier.sol.ejs')).default, // @ts-ignore + 'templates/plonk_verifier.sol.ejs': (await import('!!raw-loader!./templates/plonk_verifier.sol.ejs')).default, + // @ts-ignore 'README.md': (await import('raw-loader!./README.md')).default } -} \ No newline at end of file +} diff --git a/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_trusted_setup.ts b/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_trusted_setup.ts new file mode 100644 index 0000000000..d3b205f858 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_trusted_setup.ts @@ -0,0 +1,53 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const snarkjs = require('snarkjs'); + +const logger = { + info: (...args) => console.log(...args), + debug: (...args) => console.log(...args) +}; + +(async () => { + try { + // @ts-ignore + await remix.call('circuit-compiler', 'generateR1cs', 'circuits/semaphore.circom'); + + const ptau_final = "https://ipfs-cluster.ethdevops.io/ipfs/QmTiT4eiYz5KF7gQrDsgfCSTRv3wBPYJ4bRN1MmTRshpnW"; + // @ts-ignore + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', { encoding: null }); + // @ts-ignore + const r1cs = new Uint8Array(r1csBuffer); + const zkey_0 = { type: "mem" }; + const zkey_1 = { type: "mem" }; + const zkey_final = { type: "mem" }; + + console.log('newZkey') + await snarkjs.zKey.newZKey(r1cs, ptau_final, zkey_0); + + console.log('contribute') + await snarkjs.zKey.contribute(zkey_0, zkey_1, "p2_C1", "pa_Entropy1"); + + console.log('beacon') + await snarkjs.zKey.beacon(zkey_1, zkey_final, "B3", "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", 10); + + console.log('verifyFromR1cs') + const verifyFromR1csResult = await snarkjs.zKey.verifyFromR1cs(r1cs, ptau_final, zkey_final); + console.assert(verifyFromR1csResult); + + console.log('verifyFromInit') + const verifyFromInit = await snarkjs.zKey.verifyFromInit(zkey_0, ptau_final, zkey_final); + console.assert(verifyFromInit); + + console.log('exportVerificationKey') + const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final) + await remix.call('fileManager', 'writeFile', './zk/keys/groth16/verification_key.json', JSON.stringify(vKey, null, 2)) + + console.log('save zkey_final') + // @ts-ignore + await remix.call('fileManager', 'writeFile', './zk/keys/groth16/zkey_final.txt', (zkey_final as any).data, { encoding: null }) + + console.log('setup done.') + + } catch (e) { + console.error(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts b/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts new file mode 100644 index 0000000000..0ac12e2d7b --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts @@ -0,0 +1,108 @@ +import { ethers, BigNumber } from 'ethers' +import { IncrementalMerkleTree } from "@zk-kit/incremental-merkle-tree" +import { poseidon } from "circomlibjs" // v0.0.8 + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const snarkjs = require('snarkjs'); + +const logger = { + info: (...args) => console.log(...args), + debug: (...args) => console.log(...args), + error: (...args) => console.error(...args), +} + +/** + * Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. + * @param message The message to be hashed. + * @returns The message digest. + */ +function hash(message: any): bigint { + message = BigNumber.from(message).toTwos(256).toHexString() + message = ethers.utils.zeroPad(message, 32) + return BigInt(ethers.utils.keccak256(message)) >> BigInt(8) +} + +(async () => { + try { + // @ts-ignore + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true); + // @ts-ignore + const r1cs = new Uint8Array(r1csBuffer); + // @ts-ignore + await remix.call('circuit-compiler', 'compile', 'circuits/semaphore.circom'); + // @ts-ignore + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.wasm', true); + // @ts-ignore + const wasm = new Uint8Array(wasmBuffer); + + const zkey_final = { + type: "mem", + // @ts-ignore + data: new Uint8Array(await remix.call('fileManager', 'readFile', './zk/keys/groth16/zkey_final.txt', { encoding: null })) + } + const wtns = { type: "mem" }; + + const vKey = JSON.parse(await remix.call('fileManager', 'readFile', './zk/keys/groth16/verification_key.json')) + + // build list of identity commitments + const secrets = [] + const identityCommitments = [] + for (let k = 0; k < 2; k++) { + const identityTrapdoor = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) + const identityNullifier = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) + secrets.push({identityTrapdoor, identityNullifier}) + + const secret = poseidon([identityNullifier, identityTrapdoor]) + const identityCommitment = poseidon([secret]) + identityCommitments.push(identityCommitment) + } + //console.log('incremental tree', identityCommitments.map((x) => x.toString())) + + let tree + + try { + tree = new IncrementalMerkleTree(poseidon, 20, BigInt(0), 2, identityCommitments) // Binary tree. + } catch (e) { + console.error(e.message) + return + } + const index = tree.indexOf(identityCommitments[0]) + + console.log(index.toString()) + + const proof1 = tree.createProof(0) + + console.log('prepare signals for id ', identityCommitments[0].toString(), tree.indexOf(identityCommitments[0]), proof1.siblings.map((x)=> x.toString())) + + const signals = { + identityTrapdoor: secrets[0].identityTrapdoor, + identityNullifier: secrets[0].identityNullifier, + treePathIndices: proof1.pathIndices, + treeSiblings: proof1.siblings, + externalNullifier: hash(42), + signalHash: hash(ethers.utils.formatBytes32String("Hello World")) + } + + console.log('calculate') + await snarkjs.wtns.calculate(signals, wasm, wtns); + + console.log('check') + await snarkjs.wtns.check(r1cs, wtns, logger); + + console.log('prove') + const { proof, publicSignals } = await snarkjs.groth16.prove(zkey_final, wtns); + + const verified = await snarkjs.groth16.verify(vKey, publicSignals, proof, logger); + console.log('zk proof validity', verified); + proof1.root.toString() === publicSignals[0] ? console.log('merkle proof valid') : console.log('merkle proof invalid') + + const templates = { + groth16: await remix.call('fileManager', 'readFile', 'templates/groth16_verifier.sol.ejs') + } + const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates) + + await remix.call('fileManager', 'writeFile', './zk/build/groth16/zk_verifier.sol', solidityContract) + } catch (e) { + console.error(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_trusted_setup.ts b/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_trusted_setup.ts new file mode 100644 index 0000000000..f23770246b --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_trusted_setup.ts @@ -0,0 +1,37 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const snarkjs = require('snarkjs'); + +const logger = { + info: (...args) => console.log(...args), + debug: (...args) => console.log(...args) +}; + +(async () => { + try { + // @ts-ignore + await remix.call('circuit-compiler', 'generateR1cs', 'circuits/semaphore.circom'); + + const ptau_final = "https://ipfs-cluster.ethdevops.io/ipfs/QmTiT4eiYz5KF7gQrDsgfCSTRv3wBPYJ4bRN1MmTRshpnW"; + // @ts-ignore + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', { encoding: null }); + // @ts-ignore + const r1cs = new Uint8Array(r1csBuffer); + const zkey_final = { type: "mem" }; + + console.log('plonk setup') + await snarkjs.plonk.setup(r1cs, ptau_final, zkey_final) + + console.log('exportVerificationKey') + const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final) + await remix.call('fileManager', 'writeFile', './zk/keys/plonk/verification_key.json', JSON.stringify(vKey, null, 2)) + + console.log('save zkey_final') + // @ts-ignore + await remix.call('fileManager', 'writeFile', './zk/keys/plonk/zkey_final.txt', (zkey_final as any).data, { encoding: null }) + + console.log('setup done.') + + } catch (e) { + console.error(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts b/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts new file mode 100644 index 0000000000..224c866018 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts @@ -0,0 +1,139 @@ +import { ethers, BigNumber } from 'ethers' +import { IncrementalMerkleTree } from "@zk-kit/incremental-merkle-tree" +import { poseidon } from "circomlibjs" // v0.0.8 + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const snarkjs = require('snarkjs'); + +const logger = { + info: (...args) => console.log(...args), + debug: (...args) => console.log(...args), + error: (...args) => console.error(...args), +} + +/** + * Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. + * @param message The message to be hashed. + * @returns The message digest. + */ +function hash(message: any): bigint { + message = BigNumber.from(message).toTwos(256).toHexString() + message = ethers.utils.zeroPad(message, 32) + return BigInt(ethers.utils.keccak256(message)) >> BigInt(8) +} + +(async () => { + try { + // @ts-ignore + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true); + // @ts-ignore + const r1cs = new Uint8Array(r1csBuffer); + // @ts-ignore + await remix.call('circuit-compiler', 'compile', 'circuits/semaphore.circom'); + // @ts-ignore + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.wasm', true); + // @ts-ignore + const wasm = new Uint8Array(wasmBuffer); + + const zkey_final = { + type: "mem", + // @ts-ignore + data: new Uint8Array(await remix.call('fileManager', 'readFile', './zk/keys/plonk/zkey_final.txt', { encoding: null })) + } + const wtns = { type: "mem" }; + + const vKey = JSON.parse(await remix.call('fileManager', 'readFile', './zk/keys/plonk/verification_key.json')) + + // build list of identity commitments + const secrets = [] + const identityCommitments = [] + for (let k = 0; k < 2; k++) { + const identityTrapdoor = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) + const identityNullifier = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) + secrets.push({identityTrapdoor, identityNullifier}) + + const secret = poseidon([identityNullifier, identityTrapdoor]) + const identityCommitment = poseidon([secret]) + identityCommitments.push(identityCommitment) + } + //console.log('incremental tree', identityCommitments.map((x) => x.toString())) + + let tree + + try { + tree = new IncrementalMerkleTree(poseidon, 20, BigInt(0), 2, identityCommitments) // Binary tree. + } catch (e) { + console.error(e.message) + return + } + const index = tree.indexOf(identityCommitments[0]) + + console.log(index.toString()) + + const proof1 = tree.createProof(0) + + console.log('prepare signals for id ', identityCommitments[0].toString(), tree.indexOf(identityCommitments[0]), proof1.siblings.map((x)=> x.toString())) + + const signals = { + identityTrapdoor: secrets[0].identityTrapdoor, + identityNullifier: secrets[0].identityNullifier, + treePathIndices: proof1.pathIndices, + treeSiblings: proof1.siblings, + externalNullifier: hash(42), + signalHash: hash(ethers.utils.formatBytes32String("Hello World")) + } + + console.log('calculate') + await snarkjs.wtns.calculate(signals, wasm, wtns); + + console.log('check') + await snarkjs.wtns.check(r1cs, wtns, logger); + + console.log('prove') + const { proof, publicSignals } = await snarkjs.plonk.prove(zkey_final, wtns); + + const verified = await snarkjs.plonk.verify(vKey, publicSignals, proof, logger); + console.log('zk proof validity', verified); + proof1.root.toString() === publicSignals[0] ? console.log('merkle proof valid') : console.log('merkle proof invalid') + + const templates = { + plonk: await remix.call('fileManager', 'readFile', 'templates/plonk_verifier.sol.ejs') + } + const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates) + + await remix.call('fileManager', 'writeFile', './zk/build/plonk/zk_verifier.sol', solidityContract) + await remix.call('fileManager', 'writeFile', 'zk/build/plonk/input.json', JSON.stringify({ + _pubSignals: publicSignals, + _proof: [ + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.A[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.A[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.B[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.B[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.C[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.C[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Z[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Z[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T1[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T1[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T2[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T2[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T3[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T3[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxi[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxi[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxiw[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxiw[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_a).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_b).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_c).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_s1).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_s2).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_zw).toHexString(), 32), + ] + }, null, 2)) + + console.log('proof done.') + } catch (e) { + console.error(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/templates/plonk_verifier.sol.ejs b/libs/remix-ws-templates/src/templates/semaphore/templates/plonk_verifier.sol.ejs new file mode 100644 index 0000000000..da3f145b53 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/templates/plonk_verifier.sol.ejs @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + + +pragma solidity >=0.7.0 <0.9.0; + +import "hardhat/console.sol"; + +contract PlonkVerifier { + // Omega + uint256 constant w1 = <%=w%>; + // Scalar field size + uint256 constant q = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant qf = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // [1]_1 + uint256 constant G1x = 1; + uint256 constant G1y = 2; + // [1]_2 + uint256 constant G2x1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant G2x2 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant G2y1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant G2y2 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + + // Verification Key data + uint32 constant n = <%=2**power%>; + uint16 constant nPublic = <%=nPublic%>; + uint16 constant nLagrange = <%=Math.max(nPublic, 1)%>; + + uint256 constant Qmx = <%=Qm[0]%>; + uint256 constant Qmy = <%=Qm[0] == "0" ? "0" : Qm[1]%>; + uint256 constant Qlx = <%=Ql[0]%>; + uint256 constant Qly = <%=Ql[0] == "0" ? "0" : Ql[1]%>; + uint256 constant Qrx = <%=Qr[0]%>; + uint256 constant Qry = <%=Qr[0] == "0" ? "0" : Qr[1]%>; + uint256 constant Qox = <%=Qo[0]%>; + uint256 constant Qoy = <%=Qo[0] == "0" ? "0" : Qo[1]%>; + uint256 constant Qcx = <%=Qc[0]%>; + uint256 constant Qcy = <%=Qc[0] == "0" ? "0" : Qc[1]%>; + uint256 constant S1x = <%=S1[0]%>; + uint256 constant S1y = <%=S1[0] == "0" ? "0" : S1[1]%>; + uint256 constant S2x = <%=S2[0]%>; + uint256 constant S2y = <%=S2[0] == "0" ? "0" : S2[1]%>; + uint256 constant S3x = <%=S3[0]%>; + uint256 constant S3y = <%=S3[0] == "0" ? "0" : S3[1]%>; + uint256 constant k1 = <%=k1%>; + uint256 constant k2 = <%=k2%>; + uint256 constant X2x1 = <%=X_2[0][0]%>; + uint256 constant X2x2 = <%=X_2[0][1]%>; + uint256 constant X2y1 = <%=X_2[1][0]%>; + uint256 constant X2y2 = <%=X_2[1][1]%>; + + // Proof calldata + // Byte offset of every parameter of the calldata + // Polynomial commitments + uint16 constant pA = 4 + 0; + uint16 constant pB = 4 + 64; + uint16 constant pC = 4 + 128; + uint16 constant pZ = 4 + 192; + uint16 constant pT1 = 4 + 256; + uint16 constant pT2 = 4 + 320; + uint16 constant pT3 = 4 + 384; + uint16 constant pWxi = 4 + 448; + uint16 constant pWxiw = 4 + 512; + // Opening evaluations + uint16 constant pEval_a = 4 + 576; + uint16 constant pEval_b = 4 + 608; + uint16 constant pEval_c = 4 + 640; + uint16 constant pEval_s1 = 4 + 672; + uint16 constant pEval_s2 = 4 + 704; + uint16 constant pEval_zw = 4 + 736; + + // Memory data + // Challenges + uint16 constant pAlpha = 0; + uint16 constant pBeta = 32; + uint16 constant pGamma = 64; + uint16 constant pXi = 96; + uint16 constant pXin = 128; + uint16 constant pBetaXi = 160; + uint16 constant pV1 = 192; + uint16 constant pV2 = 224; + uint16 constant pV3 = 256; + uint16 constant pV4 = 288; + uint16 constant pV5 = 320; + uint16 constant pU = 352; + + uint16 constant pPI = 384; + uint16 constant pEval_r0 = 416; + uint16 constant pD = 448; + uint16 constant pF = 512; + uint16 constant pE = 576; + uint16 constant pTmp = 640; + uint16 constant pAlpha2 = 704; + uint16 constant pZh = 736; + uint16 constant pZhInv = 768; + + <% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> + uint16 constant pEval_l<%=i%> = <%=768+i*32%>; + <% } %> + <% let pLastMem = 800+32*Math.max(nPublic,1) %> + + uint16 constant lastMem = <%=pLastMem%>; + + function verifyProof(uint256[24] calldata _proof, uint256[<%=nPublic%>] calldata _pubSignals) public view returns (bool) { + assembly { + ///////// + // Computes the inverse using the extended euclidean algorithm + ///////// + function inverse(a, q) -> inv { + let t := 0 + let newt := 1 + let r := q + let newr := a + let quotient + let aux + + for { } newr { } { + quotient := sdiv(r, newr) + aux := sub(t, mul(quotient, newt)) + t:= newt + newt:= aux + + aux := sub(r,mul(quotient, newr)) + r := newr + newr := aux + } + + if gt(r, 1) { revert(0,0) } + if slt(t, 0) { t:= add(t, q) } + + inv := t + } + + /////// + // Computes the inverse of an array of values + // See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations + ////// + function inverseArray(pVals, n) { + + let pAux := mload(0x40) // Point to the next free position + let pIn := pVals + let lastPIn := add(pVals, mul(n, 32)) // Read n elemnts + let acc := mload(pIn) // Read the first element + pIn := add(pIn, 32) // Point to the second element + let inv + + + for { } lt(pIn, lastPIn) { + pAux := add(pAux, 32) + pIn := add(pIn, 32) + } + { + mstore(pAux, acc) + acc := mulmod(acc, mload(pIn), q) + } + acc := inverse(acc, q) + + // At this point pAux pint to the next free position we substract 1 to point to the last used + pAux := sub(pAux, 32) + // pIn points to the n+1 element, we substract to point to n + pIn := sub(pIn, 32) + lastPIn := pVals // We don't process the first element + for { } gt(pIn, lastPIn) { + pAux := sub(pAux, 32) + pIn := sub(pIn, 32) + } + { + inv := mulmod(acc, mload(pAux), q) + acc := mulmod(acc, mload(pIn), q) + mstore(pIn, inv) + } + // pIn points to first element, we just set it. + mstore(pIn, acc) + } + + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return(0,0x20) + } + } + + function checkInput() { + checkField(calldataload(pEval_a)) + checkField(calldataload(pEval_b)) + checkField(calldataload(pEval_c)) + checkField(calldataload(pEval_s1)) + checkField(calldataload(pEval_s2)) + checkField(calldataload(pEval_zw)) + } + + function calculateChallenges(pMem, pPublic) { + let beta + let aux + + let mIn := mload(0x40) // Pointer to the next free memory position + + // Compute challenge.beta & challenge.gamma + mstore(mIn, Qmx) + mstore(add(mIn, 32), Qmy) + mstore(add(mIn, 64), Qlx) + mstore(add(mIn, 96), Qly) + mstore(add(mIn, 128), Qrx) + mstore(add(mIn, 160), Qry) + mstore(add(mIn, 192), Qox) + mstore(add(mIn, 224), Qoy) + mstore(add(mIn, 256), Qcx) + mstore(add(mIn, 288), Qcy) + mstore(add(mIn, 320), S1x) + mstore(add(mIn, 352), S1y) + mstore(add(mIn, 384), S2x) + mstore(add(mIn, 416), S2y) + mstore(add(mIn, 448), S3x) + mstore(add(mIn, 480), S3y) + + <%for (let i=0; i + mstore(add(mIn, <%= 512 + i*32 %>), calldataload(add(pPublic, <%=i*32%>))) + <%}%> + mstore(add(mIn, <%= 512 + nPublic*32 + 0 %> ), calldataload(pA)) + mstore(add(mIn, <%= 512 + nPublic*32 + 32 %> ), calldataload(add(pA, 32))) + mstore(add(mIn, <%= 512 + nPublic*32 + 64 %> ), calldataload(pB)) + mstore(add(mIn, <%= 512 + nPublic*32 + 96 %> ), calldataload(add(pB, 32))) + mstore(add(mIn, <%= 512 + nPublic*32 + 128 %> ), calldataload(pC)) + mstore(add(mIn, <%= 512 + nPublic*32 + 160 %> ), calldataload(add(pC, 32))) + + beta := mod(keccak256(mIn, <%= 704 + 32 * nPublic %>), q) + mstore(add(pMem, pBeta), beta) + + // challenges.gamma + mstore(add(pMem, pGamma), mod(keccak256(add(pMem, pBeta), 32), q)) + + // challenges.alpha + mstore(mIn, mload(add(pMem, pBeta))) + mstore(add(mIn, 32), mload(add(pMem, pGamma))) + mstore(add(mIn, 64), calldataload(pZ)) + mstore(add(mIn, 96), calldataload(add(pZ, 32))) + + aux := mod(keccak256(mIn, 128), q) + mstore(add(pMem, pAlpha), aux) + mstore(add(pMem, pAlpha2), mulmod(aux, aux, q)) + + // challenges.xi + mstore(mIn, aux) + mstore(add(mIn, 32), calldataload(pT1)) + mstore(add(mIn, 64), calldataload(add(pT1, 32))) + mstore(add(mIn, 96), calldataload(pT2)) + mstore(add(mIn, 128), calldataload(add(pT2, 32))) + mstore(add(mIn, 160), calldataload(pT3)) + mstore(add(mIn, 192), calldataload(add(pT3, 32))) + + aux := mod(keccak256(mIn, 224), q) + mstore( add(pMem, pXi), aux) + + // challenges.v + mstore(mIn, aux) + mstore(add(mIn, 32), calldataload(pEval_a)) + mstore(add(mIn, 64), calldataload(pEval_b)) + mstore(add(mIn, 96), calldataload(pEval_c)) + mstore(add(mIn, 128), calldataload(pEval_s1)) + mstore(add(mIn, 160), calldataload(pEval_s2)) + mstore(add(mIn, 192), calldataload(pEval_zw)) + + let v1 := mod(keccak256(mIn, 224), q) + mstore(add(pMem, pV1), v1) + + // challenges.beta * challenges.xi + mstore(add(pMem, pBetaXi), mulmod(beta, aux, q)) + + // challenges.xi^n + <%for (let i=0; i + aux:= mulmod(aux, aux, q) + <%}%> + mstore(add(pMem, pXin), aux) + + // Zh + aux:= mod(add(sub(aux, 1), q), q) + mstore(add(pMem, pZh), aux) + mstore(add(pMem, pZhInv), aux) // We will invert later together with lagrange pols + + // challenges.v^2, challenges.v^3, challenges.v^4, challenges.v^5 + aux := mulmod(v1, v1, q) + mstore(add(pMem, pV2), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV3), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV4), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV5), aux) + + // challenges.u + mstore(mIn, calldataload(pWxi)) + mstore(add(mIn, 32), calldataload(add(pWxi, 32))) + mstore(add(mIn, 64), calldataload(pWxiw)) + mstore(add(mIn, 96), calldataload(add(pWxiw, 32))) + + mstore(add(pMem, pU), mod(keccak256(mIn, 128), q)) + } + + function calculateLagrange(pMem) { + let w := 1 + <% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> + mstore( + add(pMem, pEval_l<%=i%>), + mulmod( + n, + mod( + add( + sub( + mload(add(pMem, pXi)), + w + ), + q + ), + q + ), + q + ) + ) + <% if (i + w := mulmod(w, w1, q) + <% } %> + <% } %> + + inverseArray(add(pMem, pZhInv), <%=Math.max(nPublic, 1)+1%> ) + + let zh := mload(add(pMem, pZh)) + w := 1 + <% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> + <% if (i==1) { %> + mstore( + add(pMem, pEval_l1 ), + mulmod( + mload(add(pMem, pEval_l1 )), + zh, + q + ) + ) + <% } else { %> + mstore( + add(pMem, pEval_l<%=i%>), + mulmod( + w, + mulmod( + mload(add(pMem, pEval_l<%=i%>)), + zh, + q + ), + q + ) + ) + <% } %> + <% if (i + w := mulmod(w, w1, q) + <% } %> + <% } %> + + + } + + function calculatePI(pMem, pPub) { + let pl := 0 + + <% for (let i=0; i + pl := mod( + add( + sub( + pl, + mulmod( + mload(add(pMem, pEval_l<%=i+1%>)), + calldataload(add(pPub, <%=i*32%>)), + q + ) + ), + q + ), + q + ) + <% } %> + + mstore(add(pMem, pPI), pl) + } + + function calculateR0(pMem) { + let e1 := mload(add(pMem, pPI)) + + let e2 := mulmod(mload(add(pMem, pEval_l1)), mload(add(pMem, pAlpha2)), q) + + let e3a := addmod( + calldataload(pEval_a), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q), + q) + e3a := addmod(e3a, mload(add(pMem, pGamma)), q) + + let e3b := addmod( + calldataload(pEval_b), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q), + q) + e3b := addmod(e3b, mload(add(pMem, pGamma)), q) + + let e3c := addmod( + calldataload(pEval_c), + mload(add(pMem, pGamma)), + q) + + let e3 := mulmod(mulmod(e3a, e3b, q), e3c, q) + e3 := mulmod(e3, calldataload(pEval_zw), q) + e3 := mulmod(e3, mload(add(pMem, pAlpha)), q) + + let r0 := addmod(e1, mod(sub(q, e2), q), q) + r0 := addmod(r0, mod(sub(q, e3), q), q) + + mstore(add(pMem, pEval_r0) , r0) + } + + function g1_set(pR, pP) { + mstore(pR, mload(pP)) + mstore(add(pR, 32), mload(add(pP,32))) + } + + function g1_setC(pR, x, y) { + mstore(pR, x) + mstore(add(pR, 32), y) + } + + function g1_calldataSet(pR, pP) { + mstore(pR, calldataload(pP)) + mstore(add(pR, 32), calldataload(add(pP, 32))) + } + + function g1_acc(pR, pP) { + let mIn := mload(0x40) + mstore(mIn, mload(pR)) + mstore(add(mIn,32), mload(add(pR, 32))) + mstore(add(mIn,64), mload(pP)) + mstore(add(mIn,96), mload(add(pP, 32))) + + let success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulAcc(pR, pP, s) { + let success + let mIn := mload(0x40) + mstore(mIn, mload(pP)) + mstore(add(mIn,32), mload(add(pP, 32))) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + mstore(add(mIn,64), mload(pR)) + mstore(add(mIn,96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + } + + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn,32), y) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + mstore(add(mIn,64), mload(pR)) + mstore(add(mIn,96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulSetC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn,32), y) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulSet(pR, pP, s) { + g1_mulSetC(pR, mload(pP), mload(add(pP, 32)), s) + } + + function calculateD(pMem) { + let _pD:= add(pMem, pD) + let gamma := mload(add(pMem, pGamma)) + let mIn := mload(0x40) + mstore(0x40, add(mIn, 256)) // d1, d2, d3 & d4 (4*64 bytes) + + g1_setC(_pD, Qcx, Qcy) + g1_mulAccC(_pD, Qmx, Qmy, mulmod(calldataload(pEval_a), calldataload(pEval_b), q)) + g1_mulAccC(_pD, Qlx, Qly, calldataload(pEval_a)) + g1_mulAccC(_pD, Qrx, Qry, calldataload(pEval_b)) + g1_mulAccC(_pD, Qox, Qoy, calldataload(pEval_c)) + + let betaxi := mload(add(pMem, pBetaXi)) + let val1 := addmod( + addmod(calldataload(pEval_a), betaxi, q), + gamma, q) + + let val2 := addmod( + addmod( + calldataload(pEval_b), + mulmod(betaxi, k1, q), + q), gamma, q) + + let val3 := addmod( + addmod( + calldataload(pEval_c), + mulmod(betaxi, k2, q), + q), gamma, q) + + let d2a := mulmod( + mulmod(mulmod(val1, val2, q), val3, q), + mload(add(pMem, pAlpha)), + q + ) + + let d2b := mulmod( + mload(add(pMem, pEval_l1)), + mload(add(pMem, pAlpha2)), + q + ) + + // We'll use mIn to save d2 + g1_calldataSet(add(mIn, 192), pZ) + g1_mulSet( + mIn, + add(mIn, 192), + addmod(addmod(d2a, d2b, q), mload(add(pMem, pU)), q)) + + + val1 := addmod( + addmod( + calldataload(pEval_a), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q), + q), gamma, q) + + val2 := addmod( + addmod( + calldataload(pEval_b), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q), + q), gamma, q) + + val3 := mulmod( + mulmod(mload(add(pMem, pAlpha)), mload(add(pMem, pBeta)), q), + calldataload(pEval_zw), q) + + + // We'll use mIn + 64 to save d3 + g1_mulSetC( + add(mIn, 64), + S3x, + S3y, + mulmod(mulmod(val1, val2, q), val3, q)) + + // We'll use mIn + 128 to save d4 + g1_calldataSet(add(mIn, 128), pT1) + + g1_mulAccC(add(mIn, 128), calldataload(pT2), calldataload(add(pT2, 32)), mload(add(pMem, pXin))) + let xin2 := mulmod(mload(add(pMem, pXin)), mload(add(pMem, pXin)), q) + g1_mulAccC(add(mIn, 128), calldataload(pT3), calldataload(add(pT3, 32)) , xin2) + + g1_mulSetC(add(mIn, 128), mload(add(mIn, 128)), mload(add(mIn, 160)), mload(add(pMem, pZh))) + + mstore(add(add(mIn, 64), 32), mod(sub(qf, mload(add(add(mIn, 64), 32))), qf)) + mstore(add(mIn, 160), mod(sub(qf, mload(add(mIn, 160))), qf)) + g1_acc(_pD, mIn) + g1_acc(_pD, add(mIn, 64)) + g1_acc(_pD, add(mIn, 128)) + } + + function calculateF(pMem) { + let p := add(pMem, pF) + + g1_set(p, add(pMem, pD)) + g1_mulAccC(p, calldataload(pA), calldataload(add(pA, 32)), mload(add(pMem, pV1))) + g1_mulAccC(p, calldataload(pB), calldataload(add(pB, 32)), mload(add(pMem, pV2))) + g1_mulAccC(p, calldataload(pC), calldataload(add(pC, 32)), mload(add(pMem, pV3))) + g1_mulAccC(p, S1x, S1y, mload(add(pMem, pV4))) + g1_mulAccC(p, S2x, S2y, mload(add(pMem, pV5))) + } + + function calculateE(pMem) { + let s := mod(sub(q, mload(add(pMem, pEval_r0))), q) + + s := addmod(s, mulmod(calldataload(pEval_a), mload(add(pMem, pV1)), q), q) + s := addmod(s, mulmod(calldataload(pEval_b), mload(add(pMem, pV2)), q), q) + s := addmod(s, mulmod(calldataload(pEval_c), mload(add(pMem, pV3)), q), q) + s := addmod(s, mulmod(calldataload(pEval_s1), mload(add(pMem, pV4)), q), q) + s := addmod(s, mulmod(calldataload(pEval_s2), mload(add(pMem, pV5)), q), q) + s := addmod(s, mulmod(calldataload(pEval_zw), mload(add(pMem, pU)), q), q) + + g1_mulSetC(add(pMem, pE), G1x, G1y, s) + } + + function checkPairing(pMem) -> isOk { + let mIn := mload(0x40) + mstore(0x40, add(mIn, 576)) // [0..383] = pairing data, [384..447] = pWxi, [448..512] = pWxiw + + let _pWxi := add(mIn, 384) + let _pWxiw := add(mIn, 448) + let _aux := add(mIn, 512) + + g1_calldataSet(_pWxi, pWxi) + g1_calldataSet(_pWxiw, pWxiw) + + // A1 + g1_mulSet(mIn, _pWxiw, mload(add(pMem, pU))) + g1_acc(mIn, _pWxi) + mstore(add(mIn, 32), mod(sub(qf, mload(add(mIn, 32))), qf)) + + // [X]_2 + mstore(add(mIn,64), X2x2) + mstore(add(mIn,96), X2x1) + mstore(add(mIn,128), X2y2) + mstore(add(mIn,160), X2y1) + + // B1 + g1_mulSet(add(mIn, 192), _pWxi, mload(add(pMem, pXi))) + + let s := mulmod(mload(add(pMem, pU)), mload(add(pMem, pXi)), q) + s := mulmod(s, w1, q) + g1_mulSet(_aux, _pWxiw, s) + g1_acc(add(mIn, 192), _aux) + g1_acc(add(mIn, 192), add(pMem, pF)) + mstore(add(pMem, add(pE, 32)), mod(sub(qf, mload(add(pMem, add(pE, 32)))), qf)) + g1_acc(add(mIn, 192), add(pMem, pE)) + + // [1]_2 + mstore(add(mIn,256), G2x2) + mstore(add(mIn,288), G2x1) + mstore(add(mIn,320), G2y2) + mstore(add(mIn,352), G2y1) + + let success := staticcall(sub(gas(), 2000), 8, mIn, 384, mIn, 0x20) + + isOk := and(success, mload(mIn)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, lastMem)) + + checkInput() + calculateChallenges(pMem, _pubSignals) + calculateLagrange(pMem) + calculatePI(pMem, _pubSignals) + calculateR0(pMem) + calculateD(pMem) + calculateF(pMem) + calculateE(pMem) + let isValid := checkPairing(pMem) + + mstore(0x40, sub(pMem, lastMem)) + mstore(0, isValid) + return(0,0x20) + } + + } +} \ No newline at end of file