Skip to content

Commit

Permalink
Added Plonk support (#10)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
Hrom131 and mllwchrry authored Oct 3, 2024
1 parent 799178c commit 1074fe2
Show file tree
Hide file tree
Showing 28 changed files with 1,971 additions and 183 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
103 changes: 40 additions & 63 deletions src/core/CircuitZKit.ts
Original file line number Diff line number Diff line change
@@ -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<Type extends ProvingSystemType> {
constructor(
private readonly _config: CircuitZKitConfig,
private readonly _implementer: IProtocolImplementer<Type>,
) {}

/**
* Creates a verifier contract for the specified contract language.
*/
public async createVerifier(languageExtension: VerifierLanguageType): Promise<void> {
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);
}

/**
Expand Down Expand Up @@ -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<ProofStruct>} The generated proof.
* @returns {Promise<ProofStructByProtocol<Type>>} The generated proof.
* @todo Add support for other proving systems.
*/
public async generateProof(inputs: Signals): Promise<ProofStruct> {
public async generateProof(inputs: Signals): Promise<ProofStructByProtocol<Type>> {
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);
}

/**
Expand All @@ -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<Type>} proof - The proof to verify.
* @returns {Promise<boolean>} Whether the proof is valid.
*/
public async verifyProof(proof: ProofStruct): Promise<boolean> {
public async verifyProof(proof: ProofStructByProtocol<Type>): Promise<boolean> {
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<Calldata>} - The generated calldata.
* @param {ProofStructByProtocol<Type>} proof - The proof to generate calldata for.
* @returns {Promise<CalldataByProtocol<Type>>} - The generated calldata.
* @todo Add other types of calldata.
*/
public async generateCalldata(proof: ProofStruct): Promise<Calldata> {
const calldata = await snarkjs.groth16.exportSolidityCallData(proof.proof, proof.publicSignals);

return JSON.parse(`[${calldata}]`) as Calldata;
public async generateCalldata(proof: ProofStructByProtocol<Type>): Promise<CalldataByProtocol<Type>> {
return await this._implementer.generateCalldata(proof);
}

/**
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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`;
Expand Down
67 changes: 67 additions & 0 deletions src/core/protocols/AbstractImplementer.ts
Original file line number Diff line number Diff line change
@@ -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<T extends ProvingSystemType> implements IProtocolImplementer<T> {
public async createVerifier(
circuitName: string,
vKeyFilePath: string,
verifierFilePath: string,
languageExtension: VerifierLanguageType,
): Promise<void> {
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<ProofStructByProtocol<T>>;

public abstract verifyProof(proof: ProofStructByProtocol<T>, vKeyFilePath: string): Promise<boolean>;

public abstract generateCalldata(proof: ProofStructByProtocol<T>): Promise<CalldataByProtocol<T>>;

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`;
}
}
29 changes: 29 additions & 0 deletions src/core/protocols/Groth16Implementer.ts
Original file line number Diff line number Diff line change
@@ -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<Groth16ProofStruct> {
return (await snarkjs.groth16.fullProve(inputs, wasmFilePath, zKeyFilePath)) as Groth16ProofStruct;
}

public async verifyProof(proof: Groth16ProofStruct, vKeyFilePath: string): Promise<boolean> {
const verifier = JSON.parse(fs.readFileSync(vKeyFilePath).toString());

return await snarkjs.groth16.verify(verifier, proof.publicSignals, proof.proof);
}

public async generateCalldata(proof: Groth16ProofStruct): Promise<Groth16Calldata> {
const calldata = await snarkjs.groth16.exportSolidityCallData(proof.proof, proof.publicSignals);

return JSON.parse(`[${calldata}]`) as Groth16Calldata;
}

public getProvingSystemType(): ProvingSystemType {
return "groth16";
}
}
32 changes: 32 additions & 0 deletions src/core/protocols/PlonkImplementer.ts
Original file line number Diff line number Diff line change
@@ -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<PlonkProofStruct> {
return (await snarkjs.plonk.fullProve(inputs, wasmFilePath, zKeyFilePath)) as PlonkProofStruct;
}

public async verifyProof(proof: PlonkProofStruct, vKeyFilePath: string): Promise<boolean> {
const verifier = JSON.parse(fs.readFileSync(vKeyFilePath).toString());

return await snarkjs.plonk.verify(verifier, proof.publicSignals, proof.proof);
}

public async generateCalldata(proof: PlonkProofStruct): Promise<PlonkCalldata> {
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";
}
}
3 changes: 3 additions & 0 deletions src/core/protocols/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { AbstractProtocolImplementer } from "./AbstractImplementer";
export { Groth16Implementer } from "./Groth16Implementer";
export { PlonkImplementer } from "./PlonkImplementer";
Loading

0 comments on commit 1074fe2

Please sign in to comment.