From 1074fe25892d5fec947e06d7a521f90fe0abeca8 Mon Sep 17 00:00:00 2001 From: Oleg Komendant <44612825+Hrom131@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:28:19 +0300 Subject: [PATCH] Added `Plonk` support (#10) * Add plonk support * Update package version * added vyper plonk verifier template * fixed P_TOTAL_SIZE in vyper plonk verifier template * template fix * modified vyper plonk verifier template --------- Co-authored-by: mllwchrry --- package-lock.json | 4 +- package.json | 2 +- src/core/CircuitZKit.ts | 103 +-- src/core/protocols/AbstractImplementer.ts | 67 ++ src/core/protocols/Groth16Implementer.ts | 29 + src/core/protocols/PlonkImplementer.ts | 32 + src/core/protocols/index.ts | 3 + src/core/templates/verifier_plonk.sol.ejs | 681 ++++++++++++++++++ src/core/templates/verifier_plonk.vy.ejs | 650 +++++++++++++++++ src/index.ts | 6 +- src/types/circuit-zkit.ts | 31 - src/types/proof-utils.ts | 9 + src/types/protocols/groth16.ts | 21 + src/types/protocols/index.ts | 49 ++ src/types/protocols/plonk.ts | 28 + test/CircuitZKit.test.ts | 262 ++++--- .../MultiDimensionalArray.groth16.vkey.json | 1 + .../MultiDimensionalArray.groth16.zkey | Bin 0 -> 7874 bytes .../MultiDimensionalArray.plonk.vkey.json | 63 ++ .../MultiDimensionalArray.plonk.zkey | Bin 0 -> 95932 bytes .../MultiDimensionalArray.r1cs | Bin 0 -> 784 bytes .../MultiDimensionalArray.sym | 12 + .../MultiDimensionalArray_artifacts.json | 38 + .../MultiDimensionalArray.wasm | Bin 0 -> 34796 bytes ...vkey.json => Multiplier.groth16.vkey.json} | 0 ...ultiplier.zkey => Multiplier.groth16.zkey} | Bin .../Multiplier.plonk.vkey.json | 63 ++ .../Multiplier.circom/Multiplier.plonk.zkey | Bin 0 -> 13420 bytes 28 files changed, 1971 insertions(+), 183 deletions(-) create mode 100644 src/core/protocols/AbstractImplementer.ts create mode 100644 src/core/protocols/Groth16Implementer.ts create mode 100644 src/core/protocols/PlonkImplementer.ts create mode 100644 src/core/protocols/index.ts create mode 100644 src/core/templates/verifier_plonk.sol.ejs create mode 100644 src/core/templates/verifier_plonk.vy.ejs create mode 100644 src/types/proof-utils.ts create mode 100644 src/types/protocols/groth16.ts create mode 100644 src/types/protocols/index.ts create mode 100644 src/types/protocols/plonk.ts create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.groth16.vkey.json create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.groth16.zkey create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.plonk.vkey.json create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.plonk.zkey create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.r1cs create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.sym create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray_artifacts.json create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray_js/MultiDimensionalArray.wasm rename test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/{Multiplier.vkey.json => Multiplier.groth16.vkey.json} (100%) rename test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/{Multiplier.zkey => Multiplier.groth16.zkey} (100%) create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.plonk.vkey.json create mode 100644 test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.plonk.zkey diff --git a/package-lock.json b/package-lock.json index 5807c44..8d4dae0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@solarity/zkit", - "version": "0.2.6", + "version": "0.3.0-rc.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/zkit", - "version": "0.2.6", + "version": "0.3.0-rc.0", "license": "MIT", "dependencies": { "ejs": "3.1.10", diff --git a/package.json b/package.json index 9e3fc7b..8ef295c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/zkit", - "version": "0.2.6", + "version": "0.3.0-rc.0", "license": "MIT", "author": "Distributed Lab", "readme": "README.md", diff --git a/src/core/CircuitZKit.ts b/src/core/CircuitZKit.ts index 30e0207..ee59c81 100644 --- a/src/core/CircuitZKit.ts +++ b/src/core/CircuitZKit.ts @@ -1,60 +1,32 @@ -import ejs from "ejs"; import fs from "fs"; -import * as os from "os"; import path from "path"; +import * as os from "os"; import * as snarkjs from "snarkjs"; -import { - ArtifactsFileType, - Calldata, - CircuitZKitConfig, - Signals, - ProofStruct, - VerifierProvingSystem, - VerifierLanguageType, -} from "../types/circuit-zkit"; +import { ArtifactsFileType, CircuitZKitConfig, VerifierLanguageType } from "../types/circuit-zkit"; +import { Signals } from "../types/proof-utils"; +import { CalldataByProtocol, IProtocolImplementer, ProofStructByProtocol, ProvingSystemType } from "../types/protocols"; /** * `CircuitZKit` represents a single circuit and provides a high-level API to work with it. */ -export class CircuitZKit { - constructor(private readonly _config: CircuitZKitConfig) {} - - /** - * Returns the verifier template for the specified proving system and contract language. - * - * @param {VerifierProvingSystem} provingSystem - The template proving system. - * @param {VerifierLanguageType} fileExtension - The file extension. - * @returns {string} The verifier template. - */ - public static getTemplate(provingSystem: VerifierProvingSystem, fileExtension: VerifierLanguageType): string { - switch (provingSystem) { - case "groth16": - return fs.readFileSync(path.join(__dirname, "templates", `verifier_groth16.${fileExtension}.ejs`), "utf8"); - default: - throw new Error(`Ambiguous proving system: ${provingSystem}.`); - } - } +export class CircuitZKit { + constructor( + private readonly _config: CircuitZKitConfig, + private readonly _implementer: IProtocolImplementer, + ) {} /** * Creates a verifier contract for the specified contract language. */ public async createVerifier(languageExtension: VerifierLanguageType): Promise { const vKeyFilePath: string = this.mustGetArtifactsFilePath("vkey"); - const verifierFilePath = path.join(this._config.verifierDirPath, `${this.getVerifierName()}.${languageExtension}`); + const verifierFilePath = path.join( + this._config.verifierDirPath, + `${this._implementer.getVerifierName(this._config.circuitName)}.${languageExtension}`, + ); - const verifierTemplate: string = CircuitZKit.getTemplate(this.getProvingSystem(), languageExtension); - - if (!fs.existsSync(this._config.verifierDirPath)) { - fs.mkdirSync(this._config.verifierDirPath, { recursive: true }); - } - - const templateParams = JSON.parse(fs.readFileSync(vKeyFilePath, "utf-8")); - templateParams["verifier_id"] = this.getVerifierName(); - - const verifierCode = ejs.render(verifierTemplate, templateParams); - - fs.writeFileSync(verifierFilePath, verifierCode, "utf-8"); + this._implementer.createVerifier(this._config.circuitName, vKeyFilePath, verifierFilePath, languageExtension); } /** @@ -84,14 +56,14 @@ export class CircuitZKit { * @dev The `inputs` should be in the same order as the circuit expects them. * * @param {Signals} inputs - The inputs for the circuit. - * @returns {Promise} The generated proof. + * @returns {Promise>} The generated proof. * @todo Add support for other proving systems. */ - public async generateProof(inputs: Signals): Promise { + public async generateProof(inputs: Signals): Promise> { const zKeyFile = this.mustGetArtifactsFilePath("zkey"); const wasmFile = this.mustGetArtifactsFilePath("wasm"); - return (await snarkjs.groth16.fullProve(inputs, wasmFile, zKeyFile)) as ProofStruct; + return await this._implementer.generateProof(inputs, zKeyFile, wasmFile); } /** @@ -100,28 +72,24 @@ export class CircuitZKit { * @dev The `proof` can be generated using the `generateProof` method. * @dev The `proof.publicSignals` should be in the same order as the circuit expects them. * - * @param {ProofStruct} proof - The proof to verify. + * @param {ProofStructByProtocol} proof - The proof to verify. * @returns {Promise} Whether the proof is valid. */ - public async verifyProof(proof: ProofStruct): Promise { + public async verifyProof(proof: ProofStructByProtocol): Promise { const vKeyFile = this.mustGetArtifactsFilePath("vkey"); - const verifier = JSON.parse(fs.readFileSync(vKeyFile).toString()); - - return await snarkjs.groth16.verify(verifier, proof.publicSignals, proof.proof); + return this._implementer.verifyProof(proof, vKeyFile); } /** * Generates the calldata for the given proof. The calldata can be used to verify the proof on-chain. * - * @param {ProofStruct} proof - The proof to generate calldata for. - * @returns {Promise} - The generated calldata. + * @param {ProofStructByProtocol} proof - The proof to generate calldata for. + * @returns {Promise>} - The generated calldata. * @todo Add other types of calldata. */ - public async generateCalldata(proof: ProofStruct): Promise { - const calldata = await snarkjs.groth16.exportSolidityCallData(proof.proof, proof.publicSignals); - - return JSON.parse(`[${calldata}]`) as Calldata; + public async generateCalldata(proof: ProofStructByProtocol): Promise> { + return await this._implementer.generateCalldata(proof); } /** @@ -139,16 +107,25 @@ export class CircuitZKit { * @returns {string} The verifier name. */ public getVerifierName(): string { - return `${this._config.circuitName}Verifier`; + return this._implementer.getVerifierName(this._config.circuitName); + } + + /** + * Returns the type of the proving protocol + * + * @returns {ProvingSystemType} The protocol proving system type. + */ + public getProvingSystemType(): ProvingSystemType { + return this._implementer.getProvingSystemType(); } /** - * Returns the proving system of verifier template that was stored in the config + * Returns the Solidity verifier template. * - * @returns {VerifierProvingSystem} The verifier proving system. + * @returns {string} The Solidity verifier template. */ - public getProvingSystem(): VerifierProvingSystem { - return this._config.provingSystem ?? "groth16"; + public getVerifierTemplate(languageExtension: VerifierLanguageType): string { + return this._implementer.getTemplate(languageExtension); } /** @@ -184,10 +161,10 @@ export class CircuitZKit { fileName = `${circuitName}.r1cs`; break; case "zkey": - fileName = `${circuitName}.zkey`; + fileName = `${this._implementer.getZKeyFileName(circuitName)}`; break; case "vkey": - fileName = `${circuitName}.vkey.json`; + fileName = `${this._implementer.getVKeyFileName(circuitName)}`; break; case "sym": fileName = `${circuitName}.sym`; diff --git a/src/core/protocols/AbstractImplementer.ts b/src/core/protocols/AbstractImplementer.ts new file mode 100644 index 0000000..63f4845 --- /dev/null +++ b/src/core/protocols/AbstractImplementer.ts @@ -0,0 +1,67 @@ +import fs from "fs"; +import ejs from "ejs"; +import path from "path"; + +import { Signals } from "../../types/proof-utils"; +import { + IProtocolImplementer, + ProvingSystemType, + ProofStructByProtocol, + CalldataByProtocol, +} from "../../types/protocols"; +import { VerifierLanguageType } from "../../types/circuit-zkit"; + +export abstract class AbstractProtocolImplementer implements IProtocolImplementer { + public async createVerifier( + circuitName: string, + vKeyFilePath: string, + verifierFilePath: string, + languageExtension: VerifierLanguageType, + ): Promise { + const verifierTemplate: string = this.getTemplate(languageExtension); + + if (!fs.existsSync(path.dirname(verifierFilePath))) { + fs.mkdirSync(path.dirname(verifierFilePath), { recursive: true }); + } + + const templateParams = JSON.parse(fs.readFileSync(vKeyFilePath, "utf-8")); + templateParams["verifier_id"] = this.getVerifierName(circuitName); + + const verifierCode = ejs.render(verifierTemplate, templateParams); + + fs.writeFileSync(verifierFilePath, verifierCode, "utf-8"); + } + + public abstract generateProof( + inputs: Signals, + zKeyFilePath: string, + wasmFilePath: string, + ): Promise>; + + public abstract verifyProof(proof: ProofStructByProtocol, vKeyFilePath: string): Promise; + + public abstract generateCalldata(proof: ProofStructByProtocol): Promise>; + + public abstract getProvingSystemType(): ProvingSystemType; + + public getTemplate(languageExtension: VerifierLanguageType): string { + return fs.readFileSync( + path.join(__dirname, "..", "templates", `verifier_${this.getProvingSystemType()}.${languageExtension}.ejs`), + "utf8", + ); + } + + public getVerifierName(circuitName: string): string { + const protocolType: ProvingSystemType = this.getProvingSystemType(); + + return `${circuitName}${protocolType.charAt(0).toUpperCase() + protocolType.slice(1)}Verifier`; + } + + public getZKeyFileName(circuitName: string): string { + return `${circuitName}.${this.getProvingSystemType()}.zkey`; + } + + public getVKeyFileName(circuitName: string): string { + return `${circuitName}.${this.getProvingSystemType()}.vkey.json`; + } +} diff --git a/src/core/protocols/Groth16Implementer.ts b/src/core/protocols/Groth16Implementer.ts new file mode 100644 index 0000000..f2ee3bd --- /dev/null +++ b/src/core/protocols/Groth16Implementer.ts @@ -0,0 +1,29 @@ +import fs from "fs"; +import * as snarkjs from "snarkjs"; + +import { AbstractProtocolImplementer } from "./AbstractImplementer"; + +import { Signals } from "../../types/proof-utils"; +import { Groth16ProofStruct, ProvingSystemType, Groth16Calldata } from "../../types/protocols"; + +export class Groth16Implementer extends AbstractProtocolImplementer<"groth16"> { + public async generateProof(inputs: Signals, zKeyFilePath: string, wasmFilePath: string): Promise { + return (await snarkjs.groth16.fullProve(inputs, wasmFilePath, zKeyFilePath)) as Groth16ProofStruct; + } + + public async verifyProof(proof: Groth16ProofStruct, vKeyFilePath: string): Promise { + const verifier = JSON.parse(fs.readFileSync(vKeyFilePath).toString()); + + return await snarkjs.groth16.verify(verifier, proof.publicSignals, proof.proof); + } + + public async generateCalldata(proof: Groth16ProofStruct): Promise { + const calldata = await snarkjs.groth16.exportSolidityCallData(proof.proof, proof.publicSignals); + + return JSON.parse(`[${calldata}]`) as Groth16Calldata; + } + + public getProvingSystemType(): ProvingSystemType { + return "groth16"; + } +} diff --git a/src/core/protocols/PlonkImplementer.ts b/src/core/protocols/PlonkImplementer.ts new file mode 100644 index 0000000..91adc0c --- /dev/null +++ b/src/core/protocols/PlonkImplementer.ts @@ -0,0 +1,32 @@ +import fs from "fs"; +import * as snarkjs from "snarkjs"; + +import { AbstractProtocolImplementer } from "./AbstractImplementer"; + +import { Signals } from "../../types/proof-utils"; +import { PlonkCalldata, PlonkProofStruct, ProvingSystemType } from "../../types/protocols"; + +export class PlonkImplementer extends AbstractProtocolImplementer<"plonk"> { + public async generateProof(inputs: Signals, zKeyFilePath: string, wasmFilePath: string): Promise { + return (await snarkjs.plonk.fullProve(inputs, wasmFilePath, zKeyFilePath)) as PlonkProofStruct; + } + + public async verifyProof(proof: PlonkProofStruct, vKeyFilePath: string): Promise { + const verifier = JSON.parse(fs.readFileSync(vKeyFilePath).toString()); + + return await snarkjs.plonk.verify(verifier, proof.publicSignals, proof.proof); + } + + public async generateCalldata(proof: PlonkProofStruct): Promise { + const calldata = await snarkjs.plonk.exportSolidityCallData(proof.proof, proof.publicSignals); + const proofArrEndIndex: number = calldata.indexOf("]") + 1; + + return JSON.parse( + `[${calldata.slice(0, proofArrEndIndex)},${calldata.slice(proofArrEndIndex, calldata.length)}]`, + ) as PlonkCalldata; + } + + public getProvingSystemType(): ProvingSystemType { + return "plonk"; + } +} diff --git a/src/core/protocols/index.ts b/src/core/protocols/index.ts new file mode 100644 index 0000000..5f047cd --- /dev/null +++ b/src/core/protocols/index.ts @@ -0,0 +1,3 @@ +export { AbstractProtocolImplementer } from "./AbstractImplementer"; +export { Groth16Implementer } from "./Groth16Implementer"; +export { PlonkImplementer } from "./PlonkImplementer"; diff --git a/src/core/templates/verifier_plonk.sol.ejs b/src/core/templates/verifier_plonk.sol.ejs new file mode 100644 index 0000000..7a684cf --- /dev/null +++ b/src/core/templates/verifier_plonk.sol.ejs @@ -0,0 +1,681 @@ +// SPDX-License-Identifier: MIT + +/* AUTOGENERATED FILE BY HARDHAT-ZKIT. DO NOT EDIT. */ + +pragma solidity >=0.7.0 <0.9.0; + +contract <%=verifier_id%> { + // Omega + uint256 constant W1 = <%=w%>; + // Scalar field size + uint256 constant Q = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant QF = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // [1]_1 + uint256 constant G1_X = 1; + uint256 constant G1_Y = 2; + // [1]_2 + uint256 constant G2_X1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant G2_X2 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant G2_Y1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant G2_Y2 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + + // Verification Key data + uint32 constant N = <%=2**power%>; + uint16 constant N_PUBLIC = <%=nPublic%>; + uint16 constant N_LAGRANGE = <%=Math.max(nPublic, 1)%>; + + uint256 constant QM_X = <%=Qm[0]%>; + uint256 constant QM_Y = <%=Qm[0] == "0" ? "0" : Qm[1]%>; + uint256 constant QL_X = <%=Ql[0]%>; + uint256 constant QL_Y = <%=Ql[0] == "0" ? "0" : Ql[1]%>; + uint256 constant QR_X = <%=Qr[0]%>; + uint256 constant QR_Y = <%=Qr[0] == "0" ? "0" : Qr[1]%>; + uint256 constant QO_X = <%=Qo[0]%>; + uint256 constant QO_Y = <%=Qo[0] == "0" ? "0" : Qo[1]%>; + uint256 constant QC_X = <%=Qc[0]%>; + uint256 constant QC_Y = <%=Qc[0] == "0" ? "0" : Qc[1]%>; + uint256 constant S1_X = <%=S1[0]%>; + uint256 constant S1_Y = <%=S1[0] == "0" ? "0" : S1[1]%>; + uint256 constant S2_X = <%=S2[0]%>; + uint256 constant S2_Y = <%=S2[0] == "0" ? "0" : S2[1]%>; + uint256 constant S3_X = <%=S3[0]%>; + uint256 constant S3_Y = <%=S3[0] == "0" ? "0" : S3[1]%>; + uint256 constant K1 = <%=k1%>; + uint256 constant K2 = <%=k2%>; + uint256 constant X2_X1 = <%=X_2[0][0]%>; + uint256 constant X2_X2 = <%=X_2[0][1]%>; + uint256 constant X2_Y1 = <%=X_2[1][0]%>; + uint256 constant X2_Y2 = <%=X_2[1][1]%>; + + // Proof calldata + // Byte offset of every parameter of the calldata + // Polynomial commitments + uint16 constant P_A = 4 + 0; + uint16 constant P_B = 4 + 64; + uint16 constant P_C = 4 + 128; + uint16 constant P_Z = 4 + 192; + uint16 constant P_T1 = 4 + 256; + uint16 constant P_T2 = 4 + 320; + uint16 constant P_T3 = 4 + 384; + uint16 constant P_WX_I = 4 + 448; + uint16 constant P_WX_IW = 4 + 512; + // Opening evaluations + uint16 constant P_EVAL_A = 4 + 576; + uint16 constant P_EVAL_B = 4 + 608; + uint16 constant P_EVAL_C = 4 + 640; + uint16 constant P_EVAL_S1 = 4 + 672; + uint16 constant P_EVAL_S2 = 4 + 704; + uint16 constant P_EVAL_ZW = 4 + 736; + + // Memory data + // Challenges + uint16 constant P_ALPHA = 0; + uint16 constant P_BETA = 32; + uint16 constant P_GAMMA = 64; + uint16 constant P_XI = 96; + uint16 constant P_XIN = 128; + uint16 constant P_BETA_XI = 160; + uint16 constant P_V1 = 192; + uint16 constant P_V2 = 224; + uint16 constant P_V3 = 256; + uint16 constant P_V4 = 288; + uint16 constant P_V5 = 320; + uint16 constant P_U = 352; + + uint16 constant P_PI = 384; + uint16 constant P_EVAL_R0 = 416; + uint16 constant P_D = 448; + uint16 constant P_F = 512; + uint16 constant P_E = 576; + uint16 constant P_TMP = 640; + uint16 constant P_ALPHA2 = 704; + uint16 constant P_ZH = 736; + uint16 constant P_ZH_INV = 768; + + <% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> + uint16 constant P_EVAL_L<%=i%> = <%=768+i*32%>; + <% } %> + <% let pLastMem = 800+32*Math.max(nPublic,1) %> + uint16 constant LAST_MEM = <%=pLastMem%>; + + function verifyProof(uint256[24] calldata proof_, uint256[<%=nPublic%>] calldata pubSignals_) public view returns (bool) { + assembly { + 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 + } + + 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(P_EVAL_A)) + checkField(calldataload(P_EVAL_B)) + checkField(calldataload(P_EVAL_C)) + checkField(calldataload(P_EVAL_S1)) + checkField(calldataload(P_EVAL_S2)) + checkField(calldataload(P_EVAL_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, QM_X) + mstore(add(mIn, 32), QM_Y) + mstore(add(mIn, 64), QL_X) + mstore(add(mIn, 96), QL_Y) + mstore(add(mIn, 128), QR_X) + mstore(add(mIn, 160), QR_Y) + mstore(add(mIn, 192), QO_X) + mstore(add(mIn, 224), QO_Y) + mstore(add(mIn, 256), QC_X) + mstore(add(mIn, 288), QC_Y) + mstore(add(mIn, 320), S1_X) + mstore(add(mIn, 352), S1_Y) + mstore(add(mIn, 384), S2_X) + mstore(add(mIn, 416), S2_Y) + mstore(add(mIn, 448), S3_X) + mstore(add(mIn, 480), S3_Y) + + <%for (let i=0; i + mstore(add(mIn, <%= 512 + i*32 %>), calldataload(add(pPublic, <%=i*32%>))) + <%}%> + mstore(add(mIn, <%= 512 + nPublic*32 + 0 %> ), calldataload(P_A)) + mstore(add(mIn, <%= 512 + nPublic*32 + 32 %> ), calldataload(add(P_A, 32))) + mstore(add(mIn, <%= 512 + nPublic*32 + 64 %> ), calldataload(P_B)) + mstore(add(mIn, <%= 512 + nPublic*32 + 96 %> ), calldataload(add(P_B, 32))) + mstore(add(mIn, <%= 512 + nPublic*32 + 128 %> ), calldataload(P_C)) + mstore(add(mIn, <%= 512 + nPublic*32 + 160 %> ), calldataload(add(P_C, 32))) + + beta := mod(keccak256(mIn, <%= 704 + 32 * nPublic %>), Q) + mstore(add(pMem, P_BETA), beta) + + // challenges.gamma + mstore(add(pMem, P_GAMMA), mod(keccak256(add(pMem, P_BETA), 32), Q)) + + // challenges.alpha + mstore(mIn, mload(add(pMem, P_BETA))) + mstore(add(mIn, 32), mload(add(pMem, P_GAMMA))) + mstore(add(mIn, 64), calldataload(P_Z)) + mstore(add(mIn, 96), calldataload(add(P_Z, 32))) + + aux := mod(keccak256(mIn, 128), Q) + mstore(add(pMem, P_ALPHA), aux) + mstore(add(pMem, P_ALPHA2), mulmod(aux, aux, Q)) + + // challenges.xi + mstore(mIn, aux) + mstore(add(mIn, 32), calldataload(P_T1)) + mstore(add(mIn, 64), calldataload(add(P_T1, 32))) + mstore(add(mIn, 96), calldataload(P_T2)) + mstore(add(mIn, 128), calldataload(add(P_T2, 32))) + mstore(add(mIn, 160), calldataload(P_T3)) + mstore(add(mIn, 192), calldataload(add(P_T3, 32))) + + aux := mod(keccak256(mIn, 224), Q) + mstore( add(pMem, P_XI), aux) + + // challenges.v + mstore(mIn, aux) + mstore(add(mIn, 32), calldataload(P_EVAL_A)) + mstore(add(mIn, 64), calldataload(P_EVAL_B)) + mstore(add(mIn, 96), calldataload(P_EVAL_C)) + mstore(add(mIn, 128), calldataload(P_EVAL_S1)) + mstore(add(mIn, 160), calldataload(P_EVAL_S2)) + mstore(add(mIn, 192), calldataload(P_EVAL_ZW)) + + let v1 := mod(keccak256(mIn, 224), Q) + mstore(add(pMem, P_V1), v1) + + // challenges.beta * challenges.xi + mstore(add(pMem, P_BETA_XI), mulmod(beta, aux, Q)) + + // challenges.xi^n + <%for (let i=0; i + aux:= mulmod(aux, aux, Q) + <%}%> + mstore(add(pMem, P_XIN), aux) + + // Zh + aux:= mod(add(sub(aux, 1), Q), Q) + mstore(add(pMem, P_ZH), aux) + mstore(add(pMem, P_ZH_INV), 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, P_V2), aux) + aux := mulmod(aux, v1, Q) + mstore(add(pMem, P_V3), aux) + aux := mulmod(aux, v1, Q) + mstore(add(pMem, P_V4), aux) + aux := mulmod(aux, v1, Q) + mstore(add(pMem, P_V5), aux) + + // challenges.u + mstore(mIn, calldataload(P_WX_I)) + mstore(add(mIn, 32), calldataload(add(P_WX_I, 32))) + mstore(add(mIn, 64), calldataload(P_WX_IW)) + mstore(add(mIn, 96), calldataload(add(P_WX_IW, 32))) + + mstore(add(pMem, P_U), mod(keccak256(mIn, 128), Q)) + } + + function calculateLagrange(pMem) { + let w := 1 + <% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> + mstore( + add(pMem, P_EVAL_L<%=i%>), + mulmod( + N, + mod( + add( + sub( + mload(add(pMem, P_XI)), + w + ), + Q + ), + Q + ), + Q + ) + ) + <% if (i + w := mulmod(w, W1, Q) + <% } %> + <% } %> + + inverseArray(add(pMem, P_ZH_INV), <%=Math.max(nPublic, 1)+1%> ) + + let zh := mload(add(pMem, P_ZH)) + w := 1 + <% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> + <% if (i==1) { %> + mstore( + add(pMem, P_EVAL_L1 ), + mulmod( + mload(add(pMem, P_EVAL_L1 )), + zh, + Q + ) + ) + <% } else { %> + mstore( + add(pMem, P_EVAL_L<%=i%>), + mulmod( + w, + mulmod( + mload(add(pMem, P_EVAL_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, P_EVAL_L<%=i+1%>)), + calldataload(add(pPub, <%=i*32%>)), + Q + ) + ), + Q + ), + Q + ) + <% } %> + + mstore(add(pMem, P_PI), pl) + } + + function calculateR0(pMem) { + let e1 := mload(add(pMem, P_PI)) + + let e2 := mulmod(mload(add(pMem, P_EVAL_L1)), mload(add(pMem, P_ALPHA2)), Q) + + let e3a := addmod( + calldataload(P_EVAL_A), + mulmod(mload(add(pMem, P_BETA)), calldataload(P_EVAL_S1), Q), + Q) + e3a := addmod(e3a, mload(add(pMem, P_GAMMA)), Q) + + let e3b := addmod( + calldataload(P_EVAL_B), + mulmod(mload(add(pMem, P_BETA)), calldataload(P_EVAL_S2), Q), + Q) + e3b := addmod(e3b, mload(add(pMem, P_GAMMA)), Q) + + let e3c := addmod( + calldataload(P_EVAL_C), + mload(add(pMem, P_GAMMA)), + Q) + + let e3 := mulmod(mulmod(e3a, e3b, Q), e3c, Q) + e3 := mulmod(e3, calldataload(P_EVAL_ZW), Q) + e3 := mulmod(e3, mload(add(pMem, P_ALPHA)), Q) + + let r0 := addmod(e1, mod(sub(Q, e2), Q), Q) + r0 := addmod(r0, mod(sub(Q, e3), Q), Q) + + mstore(add(pMem, P_EVAL_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, P_D) + let gamma := mload(add(pMem, P_GAMMA)) + let mIn := mload(0x40) + mstore(0x40, add(mIn, 256)) // d1, d2, d3 & d4 (4*64 bytes) + + g1_setC(_pD, QC_X, QC_Y) + g1_mulAccC(_pD, QM_X, QM_Y, mulmod(calldataload(P_EVAL_A), calldataload(P_EVAL_B), Q)) + g1_mulAccC(_pD, QL_X, QL_Y, calldataload(P_EVAL_A)) + g1_mulAccC(_pD, QR_X, QR_Y, calldataload(P_EVAL_B)) + g1_mulAccC(_pD, QO_X, QO_Y, calldataload(P_EVAL_C)) + + let betaxi := mload(add(pMem, P_BETA_XI)) + let val1 := addmod( + addmod(calldataload(P_EVAL_A), betaxi, Q), + gamma, Q) + + let val2 := addmod( + addmod( + calldataload(P_EVAL_B), + mulmod(betaxi, K1, Q), + Q), gamma, Q) + + let val3 := addmod( + addmod( + calldataload(P_EVAL_C), + mulmod(betaxi, K2, Q), + Q), gamma, Q) + + let d2a := mulmod( + mulmod(mulmod(val1, val2, Q), val3, Q), + mload(add(pMem, P_ALPHA)), + Q + ) + + let d2b := mulmod( + mload(add(pMem, P_EVAL_L1)), + mload(add(pMem, P_ALPHA2)), + Q + ) + + // We'll use mIn to save d2 + g1_calldataSet(add(mIn, 192), P_Z) + g1_mulSet( + mIn, + add(mIn, 192), + addmod(addmod(d2a, d2b, Q), mload(add(pMem, P_U)), Q)) + + + val1 := addmod( + addmod( + calldataload(P_EVAL_A), + mulmod(mload(add(pMem, P_BETA)), calldataload(P_EVAL_S1), Q), + Q), gamma, Q) + + val2 := addmod( + addmod( + calldataload(P_EVAL_B), + mulmod(mload(add(pMem, P_BETA)), calldataload(P_EVAL_S2), Q), + Q), gamma, Q) + + val3 := mulmod( + mulmod(mload(add(pMem, P_ALPHA)), mload(add(pMem, P_BETA)), Q), + calldataload(P_EVAL_ZW), Q) + + + // We'll use mIn + 64 to save d3 + g1_mulSetC( + add(mIn, 64), + S3_X, + S3_Y, + mulmod(mulmod(val1, val2, Q), val3, Q)) + + // We'll use mIn + 128 to save d4 + g1_calldataSet(add(mIn, 128), P_T1) + + g1_mulAccC(add(mIn, 128), calldataload(P_T2), calldataload(add(P_T2, 32)), mload(add(pMem, P_XIN))) + let xin2 := mulmod(mload(add(pMem, P_XIN)), mload(add(pMem, P_XIN)), Q) + g1_mulAccC(add(mIn, 128), calldataload(P_T3), calldataload(add(P_T3, 32)) , xin2) + + g1_mulSetC(add(mIn, 128), mload(add(mIn, 128)), mload(add(mIn, 160)), mload(add(pMem, P_ZH))) + + 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, P_F) + + g1_set(p, add(pMem, P_D)) + g1_mulAccC(p, calldataload(P_A), calldataload(add(P_A, 32)), mload(add(pMem, P_V1))) + g1_mulAccC(p, calldataload(P_B), calldataload(add(P_B, 32)), mload(add(pMem, P_V2))) + g1_mulAccC(p, calldataload(P_C), calldataload(add(P_C, 32)), mload(add(pMem, P_V3))) + g1_mulAccC(p, S1_X, S1_Y, mload(add(pMem, P_V4))) + g1_mulAccC(p, S2_X, S2_Y, mload(add(pMem, P_V5))) + } + + function calculateE(pMem) { + let s := mod(sub(Q, mload(add(pMem, P_EVAL_R0))), Q) + + s := addmod(s, mulmod(calldataload(P_EVAL_A), mload(add(pMem, P_V1)), Q), Q) + s := addmod(s, mulmod(calldataload(P_EVAL_B), mload(add(pMem, P_V2)), Q), Q) + s := addmod(s, mulmod(calldataload(P_EVAL_C), mload(add(pMem, P_V3)), Q), Q) + s := addmod(s, mulmod(calldataload(P_EVAL_S1), mload(add(pMem, P_V4)), Q), Q) + s := addmod(s, mulmod(calldataload(P_EVAL_S2), mload(add(pMem, P_V5)), Q), Q) + s := addmod(s, mulmod(calldataload(P_EVAL_ZW), mload(add(pMem, P_U)), Q), Q) + + g1_mulSetC(add(pMem, P_E), G1_X, G1_Y, s) + } + + function checkPairing(pMem) -> isOk { + let mIn := mload(0x40) + mstore(0x40, add(mIn, 576)) // [0..383] = pairing data, [384..447] = P_WX_I, [448..512] = P_WX_IW + + let _pWxi := add(mIn, 384) + let _pWxiw := add(mIn, 448) + let _aux := add(mIn, 512) + + g1_calldataSet(_pWxi, P_WX_I) + g1_calldataSet(_pWxiw, P_WX_IW) + + // A1 + g1_mulSet(mIn, _pWxiw, mload(add(pMem, P_U))) + g1_acc(mIn, _pWxi) + mstore(add(mIn, 32), mod(sub(QF, mload(add(mIn, 32))), QF)) + + // [X]_2 + mstore(add(mIn,64), X2_X2) + mstore(add(mIn,96), X2_X1) + mstore(add(mIn,128), X2_Y2) + mstore(add(mIn,160), X2_Y1) + + // B1 + g1_mulSet(add(mIn, 192), _pWxi, mload(add(pMem, P_XI))) + + let s := mulmod(mload(add(pMem, P_U)), mload(add(pMem, P_XI)), Q) + s := mulmod(s, W1, Q) + g1_mulSet(_aux, _pWxiw, s) + g1_acc(add(mIn, 192), _aux) + g1_acc(add(mIn, 192), add(pMem, P_F)) + mstore(add(pMem, add(P_E, 32)), mod(sub(QF, mload(add(pMem, add(P_E, 32)))), QF)) + g1_acc(add(mIn, 192), add(pMem, P_E)) + + // [1]_2 + mstore(add(mIn,256), G2_X2) + mstore(add(mIn,288), G2_X1) + mstore(add(mIn,320), G2_Y2) + mstore(add(mIn,352), G2_Y1) + + let success := staticcall(sub(gas(), 2000), 8, mIn, 384, mIn, 0x20) + + isOk := and(success, mload(mIn)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, LAST_MEM)) + + 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, LAST_MEM)) + mstore(0, isValid) + return(0,0x20) + } + + } +} \ No newline at end of file diff --git a/src/core/templates/verifier_plonk.vy.ejs b/src/core/templates/verifier_plonk.vy.ejs new file mode 100644 index 0000000..f555bdd --- /dev/null +++ b/src/core/templates/verifier_plonk.vy.ejs @@ -0,0 +1,650 @@ +# pragma version ~=0.4.0 + +# AUTOGENERATED FILE BY HARDHAT-ZKIT. DO NOT EDIT. + +# Omega +W1: constant(uint256) = <%=w%> +# Scalar field size +BASE_FIELD_SIZE: constant(uint256) = 21888242871839275222246405745257275088548364400416034343698204186575808495617 +# Base field size +QF: constant(uint256) = 21888242871839275222246405745257275088696311157297823662689037894645226208583 + +# [1]_1 +G1_X: constant(uint256) = 1 +G1_Y: constant(uint256) = 2 +# [1]_2 +G2_X1: constant(uint256) = 10857046999023057135944570762232829481370756359578518086990519993285655852781 +G2_X2: constant(uint256) = 11559732032986387107991004021392285783925812861821192530917403151452391805634 +G2_Y1: constant(uint256) = 8495653923123431417604973247489272438418190587263600148770280649306958101930 +G2_Y2: constant(uint256) = 4082367875863433681332203403145435568316851327593401208105741076214120093531 + +# Verification Key data +N: constant(uint256) = <%=2**power%> +N_PUBLIC: constant(uint256) = <%=nPublic%> +N_LAGRANGE: constant(uint256) = <%=nPublic%> + +QM_X: constant(uint256) = <%=Qm[0]%> +QM_Y: constant(uint256) = <%=Qm[0] == "0" ? "0" : Qm[1]%> +QL_X: constant(uint256) = <%=Ql[0]%> +QL_Y: constant(uint256) = <%=Ql[0] == "0" ? "0" : Ql[1]%> +QR_X: constant(uint256) = <%=Qr[0]%> +QR_Y: constant(uint256) = <%=Qr[0] == "0" ? "0" : Qr[1]%> +QO_X: constant(uint256) = <%=Qo[0]%> +QO_Y: constant(uint256) = <%=Qo[0] == "0" ? "0" : Qo[1]%> +QC_X: constant(uint256) = <%=Qc[0]%> +QC_Y: constant(uint256) = <%=Qc[0] == "0" ? "0" : Qc[1]%> +S1_X: constant(uint256) = <%=S1[0]%> +S1_Y: constant(uint256) = <%=S1[0] == "0" ? "0" : S1[1]%> +S2_X: constant(uint256) = <%=S2[0]%> +S2_Y: constant(uint256) = <%=S2[0] == "0" ? "0" : S2[1]%> +S3_X: constant(uint256) = <%=S3[0]%> +S3_Y: constant(uint256) = <%=S3[0] == "0" ? "0" : S3[1]%> +K1: constant(uint256) = <%=k1%> +K2: constant(uint256) = <%=k2%> +X2_X1: constant(uint256) = <%=X_2[0][0]%> +X2_X2: constant(uint256) = <%=X_2[0][1]%> +X2_Y1: constant(uint256) = <%=X_2[1][0]%> +X2_Y2: constant(uint256) = <%=X_2[1][1]%> + +# Proof values offsets +# Byte offset of every parameter of the proof array +# Polynomial commitments +P_A: constant(uint256) = 0 +P_B: constant(uint256) = 2 +P_C: constant(uint256) = 4 +P_Z: constant(uint256) = 6 +P_T1: constant(uint256) = 8 +P_T2: constant(uint256) = 10 +P_T3: constant(uint256) = 12 +P_WX_I: constant(uint256) = 14 +P_WX_IW: constant(uint256) = 16 + +# Opening evaluations +P_EVAL_A: constant(uint256) = 18 +P_EVAL_B: constant(uint256) = 19 +P_EVAL_C: constant(uint256) = 20 +P_EVAL_S1: constant(uint256) = 21 +P_EVAL_S2: constant(uint256) = 22 +P_EVAL_ZW: constant(uint256) = 23 + +# Memory data +# Challenges +P_ALPHA: constant(uint256) = 0 +P_BETA: constant(uint256) = 1 +P_GAMMA: constant(uint256) = 2 +P_XI: constant(uint256) = 3 +P_XIN: constant(uint256) = 4 +P_BETA_XI: constant(uint256) = 5 +P_V1: constant(uint256) = 6 +P_V2: constant(uint256) = 7 +P_V3: constant(uint256) = 8 +P_V4: constant(uint256) = 9 +P_V5: constant(uint256) = 10 +P_U: constant(uint256) = 11 + +P_PI: constant(uint256) = 12 +P_EVAL_R0: constant(uint256) = 13 +P_D: constant(uint256) = 14 +P_F: constant(uint256) = 16 +P_E: constant(uint256) = 18 +P_TMP: constant(uint256) = 20 +P_ALPHA2: constant(uint256) = 22 +P_ZH: constant(uint256) = 23 +P_ZH_INV: constant(uint256) = 24 + +P_EVAL_L1: constant(uint256) = 25 + +P_TOTAL_SIZE: constant(uint256) = <%=25+nPublic%> + +EC_ADD_PRECOMPILED_ADDRESS: constant(address) = 0x0000000000000000000000000000000000000006 +EC_MUL_PRECOMPILED_ADDRESS: constant(address) = 0x0000000000000000000000000000000000000007 +EC_PAIRING_PRECOMPILED_ADDRESS: constant(address) = 0x0000000000000000000000000000000000000008 + + +@pure +@external +def verifyProof(proofArr: uint256[24], publicSignals: uint256[<%=nPublic%>]) -> bool: + if not self._checkInput(proofArr): + return False + + p: uint256[P_TOTAL_SIZE] = self._calculateChallenges(proofArr, publicSignals) + + p = self._calculateLagrange(p) + + p[P_PI] = self._calculatePI(p, publicSignals) + p[P_EVAL_R0] = self._calculateR0(p, proofArr) + + success: bool = True + tmpPoint: uint256[2] = [0, 0] + + success, tmpPoint = self._calculateD(p, proofArr) + if not success: + return False + + p[P_D] = tmpPoint[0] + p[P_D + 1] = tmpPoint[1] + + success, tmpPoint = self._calculateF(p, proofArr) + if not success: + return False + + p[P_F] = tmpPoint[0] + p[P_F + 1] = tmpPoint[1] + + success, tmpPoint = self._calculateE(p, proofArr) + if not success: + return False + + p[P_E] = tmpPoint[0] + p[P_E + 1] = tmpPoint[1] + + return self._checkPairing(p, proofArr) + + +@pure +@internal +def _ecadd(a: uint256[2], b: uint256[2]) -> (bool, uint256[2]): + success: bool = True + response: Bytes[64] = b"" + success, response = raw_call( + EC_ADD_PRECOMPILED_ADDRESS, + abi_encode(a, b), + max_outsize=64, + is_static_call=True, + revert_on_failure=False + ) + + if not success or len(response) != 64: + return (False, [0, 0]) + + x: uint256 = convert(slice(response, 0, 32), uint256) + y: uint256 = convert(slice(response, 32, 32), uint256) + + return (True, [x, y]) + + +@pure +@internal +def _ecmul(p: uint256[2], s: uint256) -> (bool, uint256[2]): + success: bool = True + response: Bytes[64] = b"" + success, response = raw_call( + EC_MUL_PRECOMPILED_ADDRESS, + abi_encode(p, s), + max_outsize=64, + is_static_call=True, + revert_on_failure=False + ) + + if not success or len(response) != 64: + return (False, [0, 0]) + + x: uint256 = convert(slice(response, 0, 32), uint256) + y: uint256 = convert(slice(response, 32, 32), uint256) + + return (True, [x, y]) + + +@pure +@internal +def _inverse(a: uint256, q: uint256) -> uint256: + t: int256 = 0 + newt: int256 = 1 + r: uint256 = q + newr: uint256 = a + quotient: uint256 = 0 + aux: int256 = 0 + + for _: uint256 in range(256): + if newr == 0: + break + + quotient = r // newr + aux = t - convert(quotient, int256) * newt + t = newt + newt = aux + + aux = convert(r - quotient * newr, int256) + r = newr + newr = convert(aux, uint256) + + assert r <= 1, "Inverse does not exist." + + if t < 0: + t += convert(q, int256) + + return convert(t, uint256) + + +@pure +@internal +def _inverseArray(pVals: uint256[<%=nPublic+1%>]) -> uint256[<%=nPublic+1%>]: + acc: uint256 = pVals[0] + inverses: uint256[<%=nPublic+1%>] = empty(uint256[<%=nPublic+1%>]) + pAux: uint256[<%=nPublic+1%>] = empty(uint256[<%=nPublic+1%>]) + + for i: uint256 in range(1, <%=nPublic+1%>): + pAux[i] = acc + acc = uint256_mulmod(acc, pVals[i], BASE_FIELD_SIZE) + + inv_total: uint256 = self._inverse(acc, BASE_FIELD_SIZE) + + for i: uint256 in range(<%=nPublic%>): + inverses[<%=nPublic%> - i] = uint256_mulmod(inv_total, pAux[<%=nPublic%> - i], BASE_FIELD_SIZE) + inv_total = uint256_mulmod(inv_total, pVals[<%=nPublic%> - i], BASE_FIELD_SIZE) + + inverses[0] = inv_total + + return inverses + + +@pure +@internal +def _checkInput(proof: uint256[24]) -> bool: + if proof[P_EVAL_A] >= BASE_FIELD_SIZE: + return False + + if proof[P_EVAL_B] >= BASE_FIELD_SIZE: + return False + + if proof[P_EVAL_C] >= BASE_FIELD_SIZE: + return False + + if proof[P_EVAL_S1] >= BASE_FIELD_SIZE: + return False + + if proof[P_EVAL_S2] >= BASE_FIELD_SIZE: + return False + + if proof[P_EVAL_ZW] >= BASE_FIELD_SIZE: + return False + + return True + + +@pure +@internal +def _calculateChallenges(proof: uint256[24], pubSignals: uint256[<%=nPublic%>]) -> uint256[P_TOTAL_SIZE]: + mIn<%=22+nPublic%>: uint256[<%=22+nPublic%>] = [ + QM_X, QM_Y, QL_X, QL_Y, QR_X, QR_Y, QO_X, QO_Y, QC_X, QC_Y, S1_X, S1_Y, S2_X, S2_Y, S3_X, S3_Y, + <% for (let i = 0; i < nPublic; i++) { %>pubSignals[<%=i%>], <% } %> + proof[P_A], proof[P_A + 1], proof[P_B], proof[P_B + 1], proof[P_C], proof[P_C + 1], + ] + + beta: uint256 = convert(keccak256(abi_encode(mIn<%=22+nPublic%>)), uint256) % BASE_FIELD_SIZE + + p: uint256[P_TOTAL_SIZE] = empty(uint256[P_TOTAL_SIZE]) + p[P_BETA] = beta + + # challenges.gamma + p[P_GAMMA] = convert(keccak256(abi_encode(p[P_BETA])), uint256) % BASE_FIELD_SIZE + + # challenges.alpha + mIn4: uint256[4] = [beta, p[P_GAMMA], proof[P_Z], proof[P_Z + 1]] + aux: uint256 = convert(keccak256(abi_encode(mIn4)), uint256) % BASE_FIELD_SIZE + p[P_ALPHA] = aux + p[P_ALPHA2] = uint256_mulmod(aux, aux, BASE_FIELD_SIZE) + + # challenges.xi + mIn7: uint256[7] = [aux, proof[P_T1], proof[P_T1 + 1], proof[P_T2], proof[P_T2 + 1], proof[P_T3], proof[P_T3 + 1]] + aux = convert(keccak256(abi_encode(mIn7)), uint256) % BASE_FIELD_SIZE + p[P_XI] = aux + + # challenges.v + mIn7 = [ + aux, proof[P_EVAL_A], proof[P_EVAL_B], proof[P_EVAL_C], proof[P_EVAL_S1], proof[P_EVAL_S2], proof[P_EVAL_ZW] + ] + v1: uint256 = convert(keccak256(abi_encode(mIn7)), uint256) % BASE_FIELD_SIZE + p[P_V1] = v1 + + # challenges.v^2, challenges.v^3, challenges.v^4, challenges.v^5 + p[P_V2] = uint256_mulmod(v1, v1, BASE_FIELD_SIZE) + p[P_V3] = uint256_mulmod(p[P_V2], v1, BASE_FIELD_SIZE) + p[P_V4] = uint256_mulmod(p[P_V3], v1, BASE_FIELD_SIZE) + p[P_V5] = uint256_mulmod(p[P_V4], v1, BASE_FIELD_SIZE) + + # challenges.beta * challenges.xi + p[P_BETA_XI] = uint256_mulmod(beta, aux, BASE_FIELD_SIZE) + + # challenges.xi^n + <%for (let i=0; i + aux = uint256_mulmod(aux, aux, BASE_FIELD_SIZE)<% } %> + p[P_XIN] = aux + + # Zh + aux = (aux - 1 + BASE_FIELD_SIZE) % BASE_FIELD_SIZE + p[P_ZH] = aux + p[P_ZH_INV] = aux + + # challenges.u + mIn4 = [proof[P_WX_I], proof[P_WX_I + 1], proof[P_WX_IW], proof[P_WX_IW + 1]] + p[P_U] = convert(keccak256(abi_encode(mIn4)), uint256) % BASE_FIELD_SIZE + + return p + + +@pure +@internal +def _evaluateLagrange(w: uint256, xi: uint256) -> uint256: + return uint256_mulmod(N, uint256_addmod(xi, BASE_FIELD_SIZE - w, BASE_FIELD_SIZE), BASE_FIELD_SIZE) + + +@pure +@internal +def _calculateLagrange(p: uint256[P_TOTAL_SIZE]) -> uint256[P_TOTAL_SIZE]: + w: uint256 = 1 + + for i: uint256 in range(1, <%=nPublic+1%>): + p[P_EVAL_L1 + (i - 1)] = self._evaluateLagrange(w, p[P_XI]) + w = uint256_mulmod(w, W1, BASE_FIELD_SIZE) + + pointsToInverse: uint256[<%=nPublic+1%>] = empty(uint256[<%=nPublic+1%>]) + for i: uint256 in range(<%=nPublic+1%>): + pointsToInverse[i] = p[P_ZH_INV + i] + + inverses: uint256[<%=nPublic+1%>] = self._inverseArray(pointsToInverse) + + for i: uint256 in range(<%=nPublic+1%>): + p[P_ZH_INV + i] = inverses[i] + + zh: uint256 = p[P_ZH] + w = 1 + + for i: uint256 in range(1, <%=nPublic+1%>): + p[P_EVAL_L1 + (i - 1)] = uint256_mulmod( + uint256_mulmod(p[P_EVAL_L1 + (i - 1)], zh, BASE_FIELD_SIZE), + w, + BASE_FIELD_SIZE + ) + w = uint256_mulmod(w, W1, BASE_FIELD_SIZE) + + return p + + +@pure +@internal +def _calculatePI(p: uint256[P_TOTAL_SIZE], pPub: uint256[<%=nPublic%>]) -> uint256: + pl: uint256 = 0 + + for i: uint256 in range(<%=nPublic%>): + pl = uint256_addmod( + pl, + BASE_FIELD_SIZE - uint256_mulmod(p[P_EVAL_L1 + i], pPub[i], BASE_FIELD_SIZE), + BASE_FIELD_SIZE + ) + + return pl + + +@pure +@internal +def _calculateR0(p: uint256[P_TOTAL_SIZE], proof: uint256[24]) -> uint256: + e1: uint256 = p[P_PI] + + e2: uint256 = uint256_mulmod(p[P_EVAL_L1], p[P_ALPHA2], BASE_FIELD_SIZE) + + e3a: uint256 = uint256_addmod( + proof[P_EVAL_A], + uint256_mulmod(p[P_BETA], proof[P_EVAL_S1], BASE_FIELD_SIZE), + BASE_FIELD_SIZE + ) + e3a = uint256_addmod(e3a, p[P_GAMMA], BASE_FIELD_SIZE) + + e3b: uint256 = uint256_addmod( + proof[P_EVAL_B], + uint256_mulmod(p[P_BETA], proof[P_EVAL_S2], BASE_FIELD_SIZE), + BASE_FIELD_SIZE + ) + e3b = uint256_addmod(e3b, p[P_GAMMA], BASE_FIELD_SIZE) + + e3c: uint256 = uint256_addmod(proof[P_EVAL_C], p[P_GAMMA], BASE_FIELD_SIZE) + + e3: uint256 = uint256_mulmod(uint256_mulmod(e3a, e3b, BASE_FIELD_SIZE), e3c, BASE_FIELD_SIZE) + e3 = uint256_mulmod(e3, proof[P_EVAL_ZW], BASE_FIELD_SIZE) + e3 = uint256_mulmod(e3, p[P_ALPHA], BASE_FIELD_SIZE) + + r0: uint256 = uint256_addmod(e1, BASE_FIELD_SIZE - e2, BASE_FIELD_SIZE) + return uint256_addmod(r0, BASE_FIELD_SIZE - e3, BASE_FIELD_SIZE) + + +@pure +@internal +def _g1_mulAccC(pR: uint256[2], point: uint256[2], s: uint256) -> (bool, uint256[2]): + success: bool = True + mP: uint256[2] = [0, 0] + + success, mP = self._ecmul(point, s) + + return self._ecadd(mP, pR) + + +@pure +@internal +def _calculateD(p: uint256[P_TOTAL_SIZE], proof: uint256[24]) -> (bool, uint256[2]): + success: bool = True + pd: uint256[2] = [QC_X, QC_Y] + + success, pd = self._g1_mulAccC(pd, [QM_X, QM_Y], uint256_mulmod(proof[P_EVAL_A], proof[P_EVAL_B], BASE_FIELD_SIZE)) + if not success: + return (False, [0, 0]) + + success, pd = self._g1_mulAccC(pd, [QL_X, QL_Y], proof[P_EVAL_A]) + if not success: + return (False, [0, 0]) + + success, pd = self._g1_mulAccC(pd, [QR_X, QR_Y], proof[P_EVAL_B]) + if not success: + return (False, [0, 0]) + + success, pd = self._g1_mulAccC(pd, [QO_X, QO_Y], proof[P_EVAL_C]) + if not success: + return (False, [0, 0]) + + val1: uint256 = uint256_addmod( + uint256_addmod(proof[P_EVAL_A], p[P_BETA_XI], BASE_FIELD_SIZE), + p[P_GAMMA], + BASE_FIELD_SIZE + ) + + val2: uint256 = uint256_addmod( + uint256_addmod(proof[P_EVAL_B], uint256_mulmod(p[P_BETA_XI], K1, BASE_FIELD_SIZE), BASE_FIELD_SIZE), + p[P_GAMMA], + BASE_FIELD_SIZE + ) + + val3: uint256 = uint256_addmod( + uint256_addmod(proof[P_EVAL_C], uint256_mulmod(p[P_BETA_XI], K2, BASE_FIELD_SIZE), BASE_FIELD_SIZE), + p[P_GAMMA], + BASE_FIELD_SIZE + ) + + d2a: uint256 = uint256_mulmod( + uint256_mulmod(uint256_mulmod(val1, val2, BASE_FIELD_SIZE), val3, BASE_FIELD_SIZE), + p[P_ALPHA], + BASE_FIELD_SIZE + ) + + d2b: uint256 = uint256_mulmod(p[P_EVAL_L1], p[P_ALPHA2], BASE_FIELD_SIZE) + + mP: uint256[2] = [0, 0] + + success, mP = self._ecmul( + [proof[P_Z], proof[P_Z + 1]], + uint256_addmod(uint256_addmod(d2a, d2b, BASE_FIELD_SIZE), p[P_U], BASE_FIELD_SIZE) + ) + if not success: + return (False, [0, 0]) + + val1 = uint256_addmod( + uint256_addmod(proof[P_EVAL_A], uint256_mulmod(p[P_BETA], proof[P_EVAL_S1], BASE_FIELD_SIZE), BASE_FIELD_SIZE), + p[P_GAMMA], + BASE_FIELD_SIZE + ) + + val2 = uint256_addmod( + uint256_addmod(proof[P_EVAL_B], uint256_mulmod(p[P_BETA], proof[P_EVAL_S2], BASE_FIELD_SIZE), BASE_FIELD_SIZE), + p[P_GAMMA], + BASE_FIELD_SIZE + ) + + val3 = uint256_mulmod( + uint256_mulmod(p[P_ALPHA], p[P_BETA], BASE_FIELD_SIZE), + proof[P_EVAL_ZW], + BASE_FIELD_SIZE + ) + + success, mP = self._ecmul( + [proof[P_Z], proof[P_Z + 1]], + uint256_addmod(uint256_addmod(d2a, d2b, BASE_FIELD_SIZE), p[P_U], BASE_FIELD_SIZE) + ) + if not success: + return (False, [0, 0]) + + success, pd = self._ecadd(pd, mP) + if not success: + return (False, [0, 0]) + + success, mP = self._ecmul( + [S3_X, S3_Y], + uint256_mulmod(uint256_mulmod(val1, val2, BASE_FIELD_SIZE), val3, BASE_FIELD_SIZE) + ) + mP[1] = (QF - mP[1]) % QF + + success, pd = self._ecadd(pd, mP) + if not success: + return (False, [0, 0]) + + pd2: uint256[2] = [proof[P_T1], proof[P_T1 + 1]] + success, pd2 = self._g1_mulAccC(pd2, [proof[P_T2], proof[P_T2 + 1]], p[P_XIN]) + if not success: + return (False, [0, 0]) + + xin2: uint256 = uint256_mulmod(p[P_XIN], p[P_XIN], BASE_FIELD_SIZE) + success, pd2 = self._g1_mulAccC(pd2, [proof[P_T3], proof[P_T3 + 1]], xin2) + if not success: + return (False, [0, 0]) + + success, mP = self._ecmul(pd2, p[P_ZH]) + mP[1] = (QF - mP[1]) % QF + + return self._ecadd(pd, mP) + + +@pure +@internal +def _calculateF(p: uint256[P_TOTAL_SIZE], proof: uint256[24]) -> (bool, uint256[2]): + success: bool = True + pf: uint256[2] = [p[P_D], p[P_D + 1]] + + success, pf = self._g1_mulAccC(pf, [proof[P_A], proof[P_A + 1]], p[P_V1]) + if not success: + return (False, [0, 0]) + + success, pf = self._g1_mulAccC(pf, [proof[P_B], proof[P_B + 1]], p[P_V2]) + if not success: + return (False, [0, 0]) + + success, pf = self._g1_mulAccC(pf, [proof[P_C], proof[P_C + 1]], p[P_V3]) + if not success: + return (False, [0, 0]) + + success, pf = self._g1_mulAccC(pf, [S1_X, S1_Y], p[P_V4]) + if not success: + return (False, [0, 0]) + + return self._g1_mulAccC(pf, [S2_X, S2_Y], p[P_V5]) + + +@pure +@internal +def _calculateE(p: uint256[P_TOTAL_SIZE], proof: uint256[24]) -> (bool, uint256[2]): + s: uint256 = (BASE_FIELD_SIZE - p[P_EVAL_R0]) % BASE_FIELD_SIZE + + s = uint256_addmod(s, uint256_mulmod(proof[P_EVAL_A], p[P_V1], BASE_FIELD_SIZE), BASE_FIELD_SIZE) + s = uint256_addmod(s, uint256_mulmod(proof[P_EVAL_B], p[P_V2], BASE_FIELD_SIZE), BASE_FIELD_SIZE) + s = uint256_addmod(s, uint256_mulmod(proof[P_EVAL_C], p[P_V3], BASE_FIELD_SIZE), BASE_FIELD_SIZE) + s = uint256_addmod(s, uint256_mulmod(proof[P_EVAL_S1], p[P_V4], BASE_FIELD_SIZE), BASE_FIELD_SIZE) + s = uint256_addmod(s, uint256_mulmod(proof[P_EVAL_S2], p[P_V5], BASE_FIELD_SIZE), BASE_FIELD_SIZE) + s = uint256_addmod(s, uint256_mulmod(proof[P_EVAL_ZW], p[P_U], BASE_FIELD_SIZE), BASE_FIELD_SIZE) + + return self._ecmul([G1_X, G1_Y], s) + + +@pure +@internal +def _checkPairing(p: uint256[P_TOTAL_SIZE], proof: uint256[24]) -> bool: + mIn: uint256[12] = empty(uint256[12]) + + success: bool = True + mP: uint256[2] = [0, 0] + + # A1 + success, mP = self._ecmul([proof[P_WX_IW], proof[P_WX_IW + 1]], p[P_U]) + if not success: + return False + + aP: uint256[2] = [0, 0] + success, aP = self._ecadd([proof[P_WX_I], proof[P_WX_I + 1]], mP) + if not success: + return False + + mIn[0] = aP[0] + mIn[1] = (QF - aP[1]) % QF + + # [X]_2 + mIn[2] = X2_X2 + mIn[3] = X2_X1 + mIn[4] = X2_Y2 + mIn[5] = X2_Y1 + + # B1 + success, mP = self._ecmul([proof[P_WX_I], proof[P_WX_I + 1]], p[P_XI]) + if not success: + return False + + mIn[6] = mP[0] + mIn[7] = mP[1] + + s: uint256 = uint256_mulmod(p[P_U], p[P_XI], BASE_FIELD_SIZE) + s = uint256_mulmod(s, W1, BASE_FIELD_SIZE) + + success, mP = self._ecmul([proof[P_WX_IW], proof[P_WX_IW + 1]], s) + if not success: + return False + + success, aP = self._ecadd([mIn[6], mIn[7]], mP) + if not success: + return False + + success, aP = self._ecadd(aP, [p[P_F], p[P_F + 1]]) + if not success: + return False + + p[P_E + 1] = (QF - p[P_E + 1]) % QF + + success, aP = self._ecadd(aP, [p[P_E], p[P_E + 1]]) + if not success: + return False + + mIn[6] = aP[0] + mIn[7] = aP[1] + + # [1]_2 + mIn[8] = G2_X2 + mIn[9] = G2_X1 + mIn[10] = G2_Y2 + mIn[11] = G2_Y1 + + response: Bytes[32] = b"" + success, response = raw_call( + EC_PAIRING_PRECOMPILED_ADDRESS, + abi_encode(mIn), + max_outsize=32, + is_static_call=True, + revert_on_failure=False + ) + + if not success: + return False + + return convert(response, bool) diff --git a/src/index.ts b/src/index.ts index d1db63f..19b105c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,6 @@ -export * from "./core/CircuitZKit"; +export { CircuitZKit } from "./core/CircuitZKit"; +export * from "./core/protocols"; + export * from "./types/circuit-zkit"; +export * from "./types/proof-utils"; +export * from "./types/protocols"; diff --git a/src/types/circuit-zkit.ts b/src/types/circuit-zkit.ts index b48399d..9f0a3e7 100644 --- a/src/types/circuit-zkit.ts +++ b/src/types/circuit-zkit.ts @@ -1,39 +1,8 @@ -export type NumericString = `${number}` | string; - -export type PublicSignals = NumericString[]; - -export type Groth16Proof = { - pi_a: [NumericString, NumericString]; - pi_b: [[NumericString, NumericString], [NumericString, NumericString]]; - pi_c: [NumericString, NumericString]; - protocol: string; - curve: string; -}; - -export type Calldata = [ - [NumericString, NumericString], - [[NumericString, NumericString], [NumericString, NumericString]], - [NumericString, NumericString], - PublicSignals, -]; - -export type ProofStruct = { - proof: Groth16Proof; - publicSignals: PublicSignals; -}; - -export type NumberLike = number | bigint | `${number}`; -export type ArrayLike = NumberLike[] | ArrayLike[]; -export type Signal = NumberLike | ArrayLike; -export type Signals = Record; - export type ArtifactsFileType = "r1cs" | "zkey" | "vkey" | "sym" | "json" | "wasm"; -export type VerifierProvingSystem = "groth16"; export type VerifierLanguageType = "sol" | "vy"; export type CircuitZKitConfig = { circuitName: string; circuitArtifactsPath: string; verifierDirPath: string; - provingSystem?: VerifierProvingSystem; }; diff --git a/src/types/proof-utils.ts b/src/types/proof-utils.ts new file mode 100644 index 0000000..0667e6e --- /dev/null +++ b/src/types/proof-utils.ts @@ -0,0 +1,9 @@ +export type NumericString = `${number}` | string; + +export type PublicSignals = NumericString[]; + +export type NumberLike = number | bigint | `${number}`; +export type ArrayLike = NumberLike[] | ArrayLike[]; + +export type Signal = NumberLike | ArrayLike; +export type Signals = Record; diff --git a/src/types/protocols/groth16.ts b/src/types/protocols/groth16.ts new file mode 100644 index 0000000..895851b --- /dev/null +++ b/src/types/protocols/groth16.ts @@ -0,0 +1,21 @@ +import { NumericString, PublicSignals } from "../proof-utils"; + +export interface Groth16Proof { + pi_a: [NumericString, NumericString]; + pi_b: [[NumericString, NumericString], [NumericString, NumericString]]; + pi_c: [NumericString, NumericString]; + protocol: string; + curve: string; +} + +export interface Groth16ProofStruct { + proof: Groth16Proof; + publicSignals: PublicSignals; +} + +export type Groth16Calldata = [ + [NumericString, NumericString], + [[NumericString, NumericString], [NumericString, NumericString]], + [NumericString, NumericString], + PublicSignals, +]; diff --git a/src/types/protocols/index.ts b/src/types/protocols/index.ts new file mode 100644 index 0000000..18bd840 --- /dev/null +++ b/src/types/protocols/index.ts @@ -0,0 +1,49 @@ +import { Groth16ProofStruct, Groth16Calldata } from "./groth16"; +import { PlonkProofStruct, PlonkCalldata } from "./plonk"; + +import { Signals } from "../proof-utils"; +import { VerifierLanguageType } from "../circuit-zkit"; + +export * from "./groth16"; +export * from "./plonk"; + +export interface IProtocolImplementer { + createVerifier( + circuitName: string, + vKeyFilePath: string, + verifierFilePath: string, + languageExtension: VerifierLanguageType, + ): Promise; + + generateProof(inputs: Signals, zKeyFilePath: string, wasmFilePath: string): Promise>; + + verifyProof(proof: ProofStructByProtocol, vKeyFilePath: string): Promise; + + generateCalldata(proof: ProofStructByProtocol): Promise>; + + getProvingSystemType(): ProvingSystemType; + + getTemplate(fileExtension: VerifierLanguageType): string; + + getVerifierName(circuitName: string): string; + + getZKeyFileName(circuitName: string): string; + + getVKeyFileName(circuitName: string): string; +} + +export interface ProvingSystemStructMap { + groth16: { + proofStruct: Groth16ProofStruct; + calldata: Groth16Calldata; + }; + plonk: { + proofStruct: PlonkProofStruct; + calldata: PlonkCalldata; + }; +} + +export type ProvingSystemType = keyof ProvingSystemStructMap; + +export type ProofStructByProtocol = ProvingSystemStructMap[T]["proofStruct"]; +export type CalldataByProtocol = ProvingSystemStructMap[T]["calldata"]; diff --git a/src/types/protocols/plonk.ts b/src/types/protocols/plonk.ts new file mode 100644 index 0000000..6f25078 --- /dev/null +++ b/src/types/protocols/plonk.ts @@ -0,0 +1,28 @@ +import { NumericString, PublicSignals } from "../proof-utils"; + +export interface PlonkProof { + A: [NumericString, NumericString]; + B: [NumericString, NumericString]; + C: [NumericString, NumericString]; + Z: [NumericString, NumericString]; + T1: [NumericString, NumericString]; + T2: [NumericString, NumericString]; + T3: [NumericString, NumericString]; + Wxi: [NumericString, NumericString]; + Wxiw: [NumericString, NumericString]; + eval_a: NumericString; + eval_b: NumericString; + eval_c: NumericString; + eval_s1: NumericString; + eval_s2: NumericString; + eval_zw: NumericString; + protocol: string; + curve: string; +} + +export interface PlonkProofStruct { + proof: PlonkProof; + publicSignals: PublicSignals; +} + +export type PlonkCalldata = [NumericString[], PublicSignals]; diff --git a/test/CircuitZKit.test.ts b/test/CircuitZKit.test.ts index 07bc0a5..69eaf69 100644 --- a/test/CircuitZKit.test.ts +++ b/test/CircuitZKit.test.ts @@ -1,12 +1,23 @@ import ejs from "ejs"; import fs from "fs"; -import * as os from "os"; import path from "path"; +import * as os from "os"; import { expect } from "chai"; import { useFixtureProject } from "./helpers"; -import { CircuitZKit, ProofStruct } from "../src"; +import { + CircuitZKit, + CircuitZKitConfig, + Groth16Implementer, + PlonkImplementer, + ProvingSystemType, + IProtocolImplementer, + Groth16ProofStruct, + PlonkProofStruct, + Groth16Calldata, + PlonkCalldata, +} from "../src"; describe("CircuitZKit", () => { function getArtifactsFullPath(circuitDirSourceName: string): string { @@ -17,26 +28,53 @@ describe("CircuitZKit", () => { return path.join(process.cwd(), "contracts", "verifiers"); } + function getCircuitZKit( + circuitName: string, + protocolType: ProvingSystemType, + config?: CircuitZKitConfig, + ): CircuitZKit { + let implementer: IProtocolImplementer; + + switch (protocolType) { + case "groth16": + implementer = new Groth16Implementer(); + break; + case "plonk": + implementer = new PlonkImplementer(); + break; + default: + throw new Error(`Invalid protocol type - ${protocolType}`); + } + + if (!config) { + config = { + circuitName, + circuitArtifactsPath: getArtifactsFullPath(`${circuitName}.circom`), + verifierDirPath: getVerifiersDirFullPath(), + }; + } + + return new CircuitZKit(config, implementer); + } + describe("CircuitZKit creation", () => { useFixtureProject("simple-circuits"); it("should correctly set config parameters", async () => { const circuitName = "Multiplier"; - const multiplierCircuit: CircuitZKit = new CircuitZKit({ - circuitName, - circuitArtifactsPath: getArtifactsFullPath(`${circuitName}.circom`), - verifierDirPath: getVerifiersDirFullPath(), - }); + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16"); expect(multiplierCircuit.getCircuitName()).to.be.eq(circuitName); - expect(multiplierCircuit.getVerifierName()).to.be.eq(`${circuitName}Verifier`); - expect(multiplierCircuit.getProvingSystem()).to.be.eq("groth16"); + expect(multiplierCircuit.getVerifierName()).to.be.eq(`${circuitName}Groth16Verifier`); + expect(multiplierCircuit.getProvingSystemType()).to.be.eq("groth16"); }); }); describe("getTemplate", () => { it("should return correct 'groth16' Solidity template", async () => { + const multiplierCircuit = getCircuitZKit<"groth16">("Multiplier", "groth16"); + const groth16TemplatePath: string = path.join( __dirname, "..", @@ -46,10 +84,12 @@ describe("CircuitZKit", () => { "verifier_groth16.sol.ejs", ); - expect(CircuitZKit.getTemplate("groth16", "sol")).to.be.eq(fs.readFileSync(groth16TemplatePath, "utf-8")); + expect(multiplierCircuit.getVerifierTemplate("sol")).to.be.eq(fs.readFileSync(groth16TemplatePath, "utf-8")); }); it("should return correct 'groth16' Vyper template", async () => { + const multiplierCircuit = getCircuitZKit<"groth16">("Multiplier", "groth16"); + const groth16TemplatePath: string = path.join( __dirname, "..", @@ -59,17 +99,22 @@ describe("CircuitZKit", () => { "verifier_groth16.vy.ejs", ); - expect(CircuitZKit.getTemplate("groth16", "vy")).to.be.eq(fs.readFileSync(groth16TemplatePath, "utf-8")); + expect(multiplierCircuit.getVerifierTemplate("vy")).to.be.eq(fs.readFileSync(groth16TemplatePath, "utf-8")); }); - it("should get exception if pass invalid proving system", async () => { - const circuitZKit: any = CircuitZKit; + it("should return correct 'plonk' Solidity template", async () => { + const multiplierCircuit = getCircuitZKit<"plonk">("Multiplier", "plonk"); - const invalidTemplate = "fflonk"; + const plonkTemplatePath: string = path.join( + __dirname, + "..", + "src", + "core", + "templates", + "verifier_plonk.sol.ejs", + ); - expect(function () { - circuitZKit.getTemplate(invalidTemplate, "sol"); - }).to.throw(`Ambiguous proving system: ${invalidTemplate}.`); + expect(multiplierCircuit.getVerifierTemplate("sol")).to.be.eq(fs.readFileSync(plonkTemplatePath, "utf-8")); }); }); @@ -80,18 +125,19 @@ describe("CircuitZKit", () => { fs.rmSync(getVerifiersDirFullPath(), { recursive: true, force: true }); }); - it("should correctly create verifier Solidity file", async () => { + it("should correctly create 'groth16' Solidity verifier file", async () => { const circuitName = "Multiplier"; const verifierDirPath = getVerifiersDirFullPath(); const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`); + const protocolType: ProvingSystemType = "groth16"; - const multiplierCircuit: CircuitZKit = new CircuitZKit({ + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16", { circuitName, circuitArtifactsPath: artifactsDirFullPath, verifierDirPath, }); - expect(multiplierCircuit.getVerifierName()).to.be.eq(`${circuitName}Verifier`); + expect(multiplierCircuit.getVerifierName()).to.be.eq(`${circuitName}Groth16Verifier`); const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.sol`); @@ -101,53 +147,54 @@ describe("CircuitZKit", () => { expect(fs.existsSync(expectedVerifierFilePath)).to.be.true; - const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.vkey.json`); + const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.${protocolType}.vkey.json`); expect(multiplierCircuit.getArtifactsFilePath("vkey")).to.be.eq(expectedVKeyFilePath); - const template = CircuitZKit.getTemplate("groth16", "sol"); + const template = multiplierCircuit.getVerifierTemplate("sol"); const templateParams = JSON.parse(fs.readFileSync(expectedVKeyFilePath, "utf-8")); templateParams["verifier_id"] = multiplierCircuit.getVerifierName(); expect(fs.readFileSync(expectedVerifierFilePath, "utf-8")).to.be.eq(ejs.render(template, templateParams)); }); - it("should correctly create verifier Vyper file", async () => { + it("should correctly create 'plonk' verifier file", async () => { const circuitName = "Multiplier"; const verifierDirPath = getVerifiersDirFullPath(); const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`); + const protocolType: ProvingSystemType = "plonk"; - const multiplierCircuit: CircuitZKit = new CircuitZKit({ + const multiplierCircuit = getCircuitZKit<"plonk">(circuitName, "plonk", { circuitName, circuitArtifactsPath: artifactsDirFullPath, verifierDirPath, }); - expect(multiplierCircuit.getVerifierName()).to.be.eq(`${circuitName}Verifier`); + expect(multiplierCircuit.getVerifierName()).to.be.eq(`${circuitName}PlonkVerifier`); - const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.vy`); + const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.sol`); expect(fs.existsSync(expectedVerifierFilePath)).to.be.false; - await multiplierCircuit.createVerifier("vy"); + await multiplierCircuit.createVerifier("sol"); expect(fs.existsSync(expectedVerifierFilePath)).to.be.true; - const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.vkey.json`); + const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.${protocolType}.vkey.json`); expect(multiplierCircuit.getArtifactsFilePath("vkey")).to.be.eq(expectedVKeyFilePath); - const template = CircuitZKit.getTemplate("groth16", "vy"); + const template = multiplierCircuit.getVerifierTemplate("sol"); const templateParams = JSON.parse(fs.readFileSync(expectedVKeyFilePath, "utf-8")); templateParams["verifier_id"] = multiplierCircuit.getVerifierName(); expect(fs.readFileSync(expectedVerifierFilePath, "utf-8")).to.be.eq(ejs.render(template, templateParams)); }); - it("should correctly create Solidity verifier and verify proof", async function () { + it("should correctly create verifier and verify 'groth16' proof", async function () { const circuitName = "Multiplier"; const verifierDirPath = getVerifiersDirFullPath(); const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`); - const multiplierCircuit: CircuitZKit = new CircuitZKit({ + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16", { circuitName, circuitArtifactsPath: artifactsDirFullPath, verifierDirPath, @@ -158,7 +205,7 @@ describe("CircuitZKit", () => { await multiplierCircuit.createVerifier("sol"); expect(fs.existsSync(expectedVerifierFilePath)).to.be.true; - await this.hre.run("compile", { quiet: true }); + await this.hre.run("compile", { quiet: true, force: true }); const proof = await multiplierCircuit.generateProof({ a: 10, @@ -169,27 +216,27 @@ describe("CircuitZKit", () => { const data = await multiplierCircuit.generateCalldata(proof); - const MultiplierVerifierFactory = await this.hre.ethers.getContractFactory("MultiplierVerifier"); + const MultiplierVerifierFactory = await this.hre.ethers.getContractFactory("MultiplierGroth16Verifier"); const verifier = await MultiplierVerifierFactory.deploy(); expect(await verifier.verifyProof(...data)).to.be.true; }); - it("should correctly create Vyper verifier and verify proof", async function () { + it("should correctly create Solidity verifier and verify 'plonk' proof", async function () { const circuitName = "Multiplier"; const verifierDirPath = getVerifiersDirFullPath(); const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`); - const multiplierCircuit: CircuitZKit = new CircuitZKit({ + const multiplierCircuit = getCircuitZKit<"plonk">(circuitName, "plonk", { circuitName, circuitArtifactsPath: artifactsDirFullPath, verifierDirPath, }); - const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.vy`); + const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.sol`); - await multiplierCircuit.createVerifier("vy"); + await multiplierCircuit.createVerifier("sol"); expect(fs.existsSync(expectedVerifierFilePath)).to.be.true; await this.hre.run("compile", { quiet: true }); @@ -203,19 +250,56 @@ describe("CircuitZKit", () => { const data = await multiplierCircuit.generateCalldata(proof); - const MultiplierVerifierFactory = await this.hre.ethers.getContractFactory("MultiplierVerifier"); + const MultiplierVerifierFactory = await this.hre.ethers.getContractFactory("MultiplierPlonkVerifier"); const verifier = await MultiplierVerifierFactory.deploy(); expect(await verifier.verifyProof(...data)).to.be.true; }); + it("should correctly create Vyper verifier and verify 'plonk' proof", async function () { + const circuitName = "MultiDimensionalArray"; + const verifierDirPath = getVerifiersDirFullPath(); + const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`); + + const mdArrayCircuit = getCircuitZKit<"plonk">(circuitName, "plonk", { + circuitName, + circuitArtifactsPath: artifactsDirFullPath, + verifierDirPath, + }); + + const expectedVerifierFilePath = path.join(verifierDirPath, `${mdArrayCircuit.getVerifierName()}.vy`); + + await mdArrayCircuit.createVerifier("vy"); + expect(fs.existsSync(expectedVerifierFilePath)).to.be.true; + + await this.hre.run("compile", { quiet: true }); + + const a = 2; + const b = [ + [3, 1], + [44, 2], + ]; + + const proof: any = await mdArrayCircuit.generateProof({ a, b }); + + expect(await mdArrayCircuit.verifyProof(proof)).to.be.true; + + let data = await mdArrayCircuit.generateCalldata(proof); + + const MdArrayVerifierFactory = await this.hre.ethers.getContractFactory("MultiDimensionalArrayPlonkVerifier"); + const verifier = await MdArrayVerifierFactory.deploy(); + + expect(await verifier.verifyProof(...data)).to.be.true; + }); + it("should correctly create verifier several times", async () => { const circuitName = "Multiplier"; const verifierDirPath = getVerifiersDirFullPath(); const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`); + const protocolType: ProvingSystemType = "groth16"; - const multiplierCircuit: CircuitZKit = new CircuitZKit({ + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, protocolType, { circuitName, circuitArtifactsPath: artifactsDirFullPath, verifierDirPath, @@ -229,10 +313,10 @@ describe("CircuitZKit", () => { await multiplierCircuit.createVerifier("sol"); expect(fs.existsSync(expectedVerifierFilePath)).to.be.true; - const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.vkey.json`); + const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.${protocolType}.vkey.json`); expect(multiplierCircuit.getArtifactsFilePath("vkey")).to.be.eq(expectedVKeyFilePath); - const template = CircuitZKit.getTemplate("groth16", "sol"); + const template = multiplierCircuit.getVerifierTemplate("sol"); const templateParams = JSON.parse(fs.readFileSync(expectedVKeyFilePath, "utf-8")); templateParams["verifier_id"] = multiplierCircuit.getVerifierName(); @@ -242,7 +326,7 @@ describe("CircuitZKit", () => { it("should get exception if vKey file does not exist", async () => { const circuitName = "Multiplier"; - const multiplierCircuit: CircuitZKit = new CircuitZKit({ + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16", { circuitName, circuitArtifactsPath: getArtifactsFullPath(`a/Addition.circom`), verifierDirPath: getVerifiersDirFullPath(), @@ -263,7 +347,7 @@ describe("CircuitZKit", () => { const circuitName = "Multiplier"; const circuitArtifactsPath = getArtifactsFullPath(`${circuitName}.circom`); - const multiplierCircuit: CircuitZKit = new CircuitZKit({ + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16", { circuitName, circuitArtifactsPath, verifierDirPath: getVerifiersDirFullPath(), @@ -285,19 +369,29 @@ describe("CircuitZKit", () => { describe("generateProof/verifyProof", () => { useFixtureProject("simple-circuits"); - it("should correctly generate and verify proof", async () => { + it("should correctly generate and verify 'groth16' proof", async () => { const circuitName = "Multiplier"; - const multiplierCircuit: CircuitZKit = new CircuitZKit({ - circuitName, - circuitArtifactsPath: getArtifactsFullPath(`${circuitName}.circom`), - verifierDirPath: getVerifiersDirFullPath(), - }); + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16"); + + const b = 10, + a = 20; + + const proof: Groth16ProofStruct = await multiplierCircuit.generateProof({ a, b }); + + expect(proof.publicSignals).to.be.deep.eq([(b * a).toString()]); + expect(await multiplierCircuit.verifyProof(proof)).to.be.true; + }); + + it("should correctly generate and verify 'plonk' proof", async () => { + const circuitName = "Multiplier"; + + const multiplierCircuit = getCircuitZKit<"plonk">(circuitName, "plonk"); const b = 10, a = 20; - const proof: ProofStruct = await multiplierCircuit.generateProof({ a, b }); + const proof: PlonkProofStruct = await multiplierCircuit.generateProof({ a, b }); expect(proof.publicSignals).to.be.deep.eq([(b * a).toString()]); expect(await multiplierCircuit.verifyProof(proof)).to.be.true; @@ -311,36 +405,46 @@ describe("CircuitZKit", () => { fs.rmSync(getVerifiersDirFullPath(), { recursive: true, force: true }); }); - it("should correctly generate calldata and verify proof on the verifier contract", async function () { + it("should correctly generate 'groth16' calldata and verify proof on the verifier contract", async function () { const circuitName = "Multiplier"; - const multiplierCircuit: CircuitZKit = new CircuitZKit({ - circuitName, - circuitArtifactsPath: getArtifactsFullPath(`${circuitName}.circom`), - verifierDirPath: getVerifiersDirFullPath(), - }); + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16"); await multiplierCircuit.createVerifier("sol"); await this.hre.run("compile", { quiet: true }); - const MultiplierVerifierFactory = await this.hre.ethers.getContractFactory("MultiplierVerifier"); + const MultiplierVerifierFactory = await this.hre.ethers.getContractFactory("MultiplierGroth16Verifier"); const verifier = await MultiplierVerifierFactory.deploy(); const b = 10, a = 20; - const proof: ProofStruct = await multiplierCircuit.generateProof({ a, b }); - const generatedCalldata = await multiplierCircuit.generateCalldata(proof); + const proof: Groth16ProofStruct = await multiplierCircuit.generateProof({ a, b }); + const generatedCalldata: Groth16Calldata = await multiplierCircuit.generateCalldata(proof); - expect( - await verifier.verifyProof( - generatedCalldata[0], - generatedCalldata[1], - generatedCalldata[2], - generatedCalldata[3], - ), - ); + expect(await verifier.verifyProof(...generatedCalldata)); + }); + + it("should correctly generate 'plonk' calldata and verify proof on the verifier contract", async function () { + const circuitName = "Multiplier"; + + const multiplierCircuit = getCircuitZKit<"plonk">(circuitName, "plonk"); + + await multiplierCircuit.createVerifier("sol"); + + await this.hre.run("compile", { quiet: true }); + + const MultiplierVerifierFactory = await this.hre.ethers.getContractFactory("MultiplierPlonkVerifier"); + const verifier = await MultiplierVerifierFactory.deploy(); + + const b = 10, + a = 20; + + const proof: PlonkProofStruct = await multiplierCircuit.generateProof({ a, b }); + const generatedCalldata: PlonkCalldata = await multiplierCircuit.generateCalldata(proof); + + expect(await verifier.verifyProof(...generatedCalldata)); }); }); @@ -351,11 +455,7 @@ describe("CircuitZKit", () => { const circuitName = "Multiplier"; const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`); - const multiplierCircuit: CircuitZKit = new CircuitZKit({ - circuitName, - circuitArtifactsPath: artifactsDirFullPath, - verifierDirPath: getVerifiersDirFullPath(), - }); + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16"); expect(multiplierCircuit.mustGetArtifactsFilePath("wasm")).to.be.eq( path.join(artifactsDirFullPath, `${circuitName}_js`, `${circuitName}.wasm`), @@ -366,7 +466,7 @@ describe("CircuitZKit", () => { const circuitName = "Addition"; const artifactsDirFullPath = getArtifactsFullPath(`a/${circuitName}.circom`); - const multiplierCircuit: CircuitZKit = new CircuitZKit({ + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16", { circuitName, circuitArtifactsPath: artifactsDirFullPath, verifierDirPath: getVerifiersDirFullPath(), @@ -385,20 +485,16 @@ describe("CircuitZKit", () => { const circuitName = "Multiplier"; const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`); - const multiplierCircuit: CircuitZKit = new CircuitZKit({ - circuitName, - circuitArtifactsPath: artifactsDirFullPath, - verifierDirPath: getVerifiersDirFullPath(), - }); + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16"); expect(multiplierCircuit.getArtifactsFilePath("r1cs")).to.be.eq( path.join(artifactsDirFullPath, `${circuitName}.r1cs`), ); expect(multiplierCircuit.getArtifactsFilePath("zkey")).to.be.eq( - path.join(artifactsDirFullPath, `${circuitName}.zkey`), + path.join(artifactsDirFullPath, `${circuitName}.groth16.zkey`), ); expect(multiplierCircuit.getArtifactsFilePath("vkey")).to.be.eq( - path.join(artifactsDirFullPath, `${circuitName}.vkey.json`), + path.join(artifactsDirFullPath, `${circuitName}.groth16.vkey.json`), ); expect(multiplierCircuit.getArtifactsFilePath("sym")).to.be.eq( path.join(artifactsDirFullPath, `${circuitName}.sym`), @@ -414,11 +510,7 @@ describe("CircuitZKit", () => { it("should get exception if pass invalid file type", async () => { const circuitName = "Multiplier"; - const multiplierCircuit: any = new CircuitZKit({ - circuitName, - circuitArtifactsPath: getArtifactsFullPath(`${circuitName}.circom`), - verifierDirPath: getVerifiersDirFullPath(), - }); + const multiplierCircuit = getCircuitZKit<"groth16">(circuitName, "groth16") as any; const invalidFileType = "wwasm"; diff --git a/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.groth16.vkey.json b/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.groth16.vkey.json new file mode 100644 index 0000000..0b9e3e9 --- /dev/null +++ b/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.groth16.vkey.json @@ -0,0 +1 @@ +{"protocol":"groth16","curve":"bn128","nPublic":10,"vk_alpha_1":["20491192805390485299153009773594534940189261866228447918068658471970481763042","9383485363053290200918347156157836566562967994039712273449902621266178545958","1"],"vk_beta_2":[["6375614351688725206403948262868962793625744043794305715222011528459656738731","4252822878758300859123897981450591353533073413197771768651442665752259397132"],["10505242626370262277552901082094356697409835680220590971873171140371331206856","21847035105528745403288232691147584728191162732299865338377159692350059136679"],["1","0"]],"vk_gamma_2":[["10857046999023057135944570762232829481370756359578518086990519993285655852781","11559732032986387107991004021392285783925812861821192530917403151452391805634"],["8495653923123431417604973247489272438418190587263600148770280649306958101930","4082367875863433681332203403145435568316851327593401208105741076214120093531"],["1","0"]],"vk_delta_2":[["11121757548064786277347723654771777697589351852136688710265908558867619706034","9511594120864330497760845197961554462019421854459034930600159412099448314441"],["11128786009117993081930366203489510400564801483205509889654407453019543621040","11890586469707710102408840231956795235484070722927440244326941071149147642216"],["1","0"]],"vk_alphabeta_12":[[["2029413683389138792403550203267699914886160938906632433982220835551125967885","21072700047562757817161031222997517981543347628379360635925549008442030252106"],["5940354580057074848093997050200682056184807770593307860589430076672439820312","12156638873931618554171829126792193045421052652279363021382169897324752428276"],["7898200236362823042373859371574133993780991612861777490112507062703164551277","7074218545237549455313236346927434013100842096812539264420499035217050630853"]],[["7077479683546002997211712695946002074877511277312570035766170199895071832130","10093483419865920389913245021038182291233451549023025229112148274109565435465"],["4595479056700221319381530156280926371456704509942304414423590385166031118820","19831328484489333784475432780421641293929726139240675179672856274388269393268"],["11934129596455521040620786944827826205713621633706285934057045369193958244500","8037395052364110730298837004334506829870972346962140206007064471173334027475"]]],"IC":[["3495694661568299179878948819174043851099678885336797490532661625634509456620","16449691720535956946494884801163388207063380307723678096092208850886491784850","1"],["2054279236838943706274953313756236537292322036282609347844303283687859336981","9310409617016868204445399592192768984461464076263793259131162336791588460855","1"],["4549101963883882136918061717506048747536543905969847743955187512947029251427","286982476162680491638647191378244700891252328354935273227013602617490386723","1"],["11884708174745769330192779892650439092194966066571302312721385264001937381049","13268131990047414176823694405854210162868219587848624459696126135172990017117","1"],["9799474276727028348664964041671679914724919422982579865092122769382950313536","6713034376041512319939194002721319007645465116926144109484961044854657959476","1"],["14022296220194218890981220204229983993676986201618170329944669925145440704166","2519510297529842692804647714336136314718121866392421315400801702201549332604","1"],["11474327268813938951012760319915851216855228323616198999638918058098493270120","16500656588235391685173826059205066302968692203113601429633113299933455194629","1"],["20796238970784755517182802061293333668486710089460310703934446287285882219146","3213077213885759377398277506040700138865811142585808906555900192062143733907","1"],["4755241111656492396903273326956608795143382767922486875641623966708572717243","16619885589196258576144193149671578663463975844477089736502404890231654038593","1"],["14950193769065094782935245708676799536470457473265243091124885117312549616769","6465459252878178455071904296538722022857571708009548619696960085030101615929","1"],["385979438052586810495960114303961400723993708222649653587148950133873626122","13447321594156279890979317717555593354311661716332933890908752718991044747964","1"]]} \ No newline at end of file diff --git a/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.groth16.zkey b/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.groth16.zkey new file mode 100644 index 0000000000000000000000000000000000000000..f5656659342af3cecd562047009134f65eb939d1 GIT binary patch literal 7874 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 literal 0 HcmV?d00001 diff --git a/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.plonk.vkey.json b/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.plonk.vkey.json new file mode 100644 index 0000000..c9c77ed --- /dev/null +++ b/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.plonk.vkey.json @@ -0,0 +1,63 @@ +{ + "protocol": "plonk", + "curve": "bn128", + "nPublic": 10, + "power": 5, + "k1": "2", + "k2": "3", + "Qm": [ + "20725296897021676741273642056169240329285346916285158361938444652581257033543", + "19707517720670499485357295484830119779349041426479603650432203457136506504553", + "1" + ], + "Ql": [ + "17359212988294702391946214274081597524316206017774228682133243429316382466169", + "13186074013007479625987897051305103109875388027117735083702144928328328417354", + "1" + ], + "Qr": [ + "14758822207824189241801199980321211283852275118707770109155494450459528308433", + "3111427114158633020352054765874786995654636141967641700746131063752278357531", + "1" + ], + "Qo": [ + "6244574162614808265956747865576107056431639803026857745017764366785514855788", + "3702796362167115123753264446046062519683988921847065794467224235285920160499", + "1" + ], + "Qc": [ + "19044597030438802983566910140227291914987665533437629098515891922685580896837", + "4657908630292315873601421240069348459791191118362492110690688921349850751286", + "1" + ], + "S1": [ + "15429054937789019171757319325502284862527279286683877739760989574604467061479", + "7821930593052013494045041470471816095239727048579657070946420897376673917026", + "1" + ], + "S2": [ + "3173429328132607418336872096074210942038805266431548245139519027688183520874", + "15855441316377869984246871206599918935607091929588958108942938040758330929807", + "1" + ], + "S3": [ + "9040900506899513085679484076599580398059396139904684129570618813481205004777", + "16820393608784268482740650194967033277242455801099197806602971357487892770048", + "1" + ], + "X_2": [ + [ + "21831381940315734285607113342023901060522397560371972897001948545212302161822", + "17231025384763736816414546592865244497437017442647097510447326538965263639101" + ], + [ + "2388026358213174446665280700919698872609886601280537296205114254867301080648", + "11507326595632554467052522095592665270651932854513688777769618397986436103170" + ], + [ + "1", + "0" + ] + ], + "w": "4419234939496763621076330863786513495701855246241724391626358375488475697872" +} \ No newline at end of file diff --git a/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.plonk.zkey b/test/fixture-projects/simple-circuits/zkit/artifacts/MultiDimensionalArray.circom/MultiDimensionalArray.plonk.zkey new file mode 100644 index 0000000000000000000000000000000000000000..b437f046f2da7f5a89ac7c87e4eeeec3bf2f4724 GIT binary patch literal 95932 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 KPA$Y;;r|an<)G#O literal 0 HcmV?d00001 diff --git a/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.vkey.json b/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.groth16.vkey.json similarity index 100% rename from test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.vkey.json rename to test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.groth16.vkey.json diff --git a/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.zkey b/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.groth16.zkey similarity index 100% rename from test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.zkey rename to test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.groth16.zkey diff --git a/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.plonk.vkey.json b/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.plonk.vkey.json new file mode 100644 index 0000000..cf334af --- /dev/null +++ b/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.plonk.vkey.json @@ -0,0 +1,63 @@ +{ + "protocol": "plonk", + "curve": "bn128", + "nPublic": 1, + "power": 3, + "k1": "2", + "k2": "3", + "Qm": [ + "10294367845524522889674980414658158979115219665406612861401259333422895729896", + "17339696279167455564514853058684962930296864414660175742312401951183098671156", + "1" + ], + "Ql": [ + "14297155691368363150439281660551929853142513799648244067851273621337387750022", + "9875708754215138125887194540142977977698752545665252035430927128878501866163", + "1" + ], + "Qr": [ + "0", + "1", + "0" + ], + "Qo": [ + "10294367845524522889674980414658158979115219665406612861401259333422895729896", + "4548546592671819657731552686572312158399446742637647920376635943462127537427", + "1" + ], + "Qc": [ + "0", + "1", + "0" + ], + "S1": [ + "2694761611667402433549058650401049833973608710551146850129171008254242491412", + "4407815622841625592989621790140274705225113068749062773093818734897989348824", + "1" + ], + "S2": [ + "174950894878901504258554221888959060942005622520060188523927601854050691737", + "4218570225917094256073281485929194442981718242484973947497628954679969816940", + "1" + ], + "S3": [ + "5287191920074181963852791792640835974097875195213946104826736553520071559657", + "20309358409622118500558363825899879958276827259299017541094321370900713472551", + "1" + ], + "X_2": [ + [ + "21831381940315734285607113342023901060522397560371972897001948545212302161822", + "17231025384763736816414546592865244497437017442647097510447326538965263639101" + ], + [ + "2388026358213174446665280700919698872609886601280537296205114254867301080648", + "11507326595632554467052522095592665270651932854513688777769618397986436103170" + ], + [ + "1", + "0" + ] + ], + "w": "19540430494807482326159819597004422086093766032135589407132600596362845576832" +} \ No newline at end of file diff --git a/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.plonk.zkey b/test/fixture-projects/simple-circuits/zkit/artifacts/Multiplier.circom/Multiplier.plonk.zkey new file mode 100644 index 0000000000000000000000000000000000000000..35465152183dd0a448fc7d101e39ddb75cc70bc2 GIT binary patch literal 13420 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 literal 0 HcmV?d00001