Skip to content

Commit

Permalink
added Vyper groth16 verifier template
Browse files Browse the repository at this point in the history
  • Loading branch information
mllwchrry committed Aug 16, 2024
1 parent 5e54df4 commit bafd4cd
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 453 deletions.
522 changes: 92 additions & 430 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,19 @@
},
"devDependencies": {
"@nomicfoundation/hardhat-ethers": "3.0.5",
"@types/ejs": "^3.1.5",
"@types/snarkjs": "^0.7.8",
"@nomiclabs/hardhat-vyper": "^3.0.7",
"@types/chai": "^4.3.12",
"@types/chai-as-promised": "^7.1.8",
"@types/ejs": "^3.1.5",
"@types/mocha": "^10.0.6",
"@types/snarkjs": "^0.7.8",
"chai": "^4.4.1",
"chai-as-promised": "^7.1.1",
"mocha": "^10.3.0",
"nyc": "^15.1.0",
"ethers": "6.11.1",
"hardhat": "2.20.1",
"hardhat": "2.22.7",
"husky": "^9.0.11",
"mocha": "^10.3.0",
"nyc": "^15.1.0",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
Expand Down
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const languageMap = {
solidity: "sol",
vyper: "vy",
};
26 changes: 21 additions & 5 deletions src/core/CircuitZKit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {
Signals,
ProofStruct,
VerifierTemplateType,
Language,
LanguageExtension,
} from "../types/circuit-zkit";
import { languageMap } from "../constants";

/**
* `CircuitZKit` represents a single circuit and provides a high-level API to work with it.
Expand All @@ -23,12 +26,13 @@ export class CircuitZKit {
* Returns the Solidity verifier template for the specified proving system.
*
* @param {VerifierTemplateType} templateType - The template type.
* @param {LanguageExtension} fileExtension - The file extension.
* @returns {string} The Solidity verifier template.
*/
public static getTemplate(templateType: VerifierTemplateType): string {
public static getTemplate(templateType: VerifierTemplateType, fileExtension: LanguageExtension): string {
switch (templateType) {
case "groth16":
return fs.readFileSync(path.join(__dirname, "templates", "verifier_groth16.sol.ejs"), "utf8");
return fs.readFileSync(path.join(__dirname, "templates", `verifier_groth16.${fileExtension}.ejs`), "utf8");
default:
throw new Error(`Ambiguous template type: ${templateType}.`);
}
Expand All @@ -37,11 +41,13 @@ export class CircuitZKit {
/**
* Creates a Solidity verifier contract.
*/
public async createVerifier(): Promise<void> {
public async createVerifier(contractLanguage: Language): Promise<void> {
const fileExtension = this.getLanguageExtension(contractLanguage);

const vKeyFilePath: string = this.mustGetArtifactsFilePath("vkey");
const verifierFilePath = path.join(this._config.verifierDirPath, `${this.getVerifierName()}.sol`);
const verifierFilePath = path.join(this._config.verifierDirPath, `${this.getVerifierName()}.${fileExtension}`);

const verifierTemplate: string = CircuitZKit.getTemplate(this.getTemplateType());
const verifierTemplate: string = CircuitZKit.getTemplate(this.getTemplateType(), fileExtension);

if (!fs.existsSync(this._config.verifierDirPath)) {
fs.mkdirSync(this._config.verifierDirPath, { recursive: true });
Expand Down Expand Up @@ -149,6 +155,16 @@ export class CircuitZKit {
return this._config.templateType ?? "groth16";
}

/**
* Returns the file extension depending on the contract language
*
* @param {Language} language - The smart contract language.
* @returns {LanguageExtension} The verifier file language extension.
*/
public getLanguageExtension(language: Language): LanguageExtension {
return languageMap[language];
}

/**
* Returns the path to the file of the given type inside artifacts directory. Throws an error if the file doesn't exist.
*
Expand Down
92 changes: 92 additions & 0 deletions src/core/templates/verifier_groth16.vy.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# pragma version ~=0.4.0

# AUTOGENERATED FILE BY HARDHAT-ZKIT. DO NOT EDIT.

# @dev base field size
BASE_FIELD_SIZE: constant(uint256) = 21888242871839275222246405745257275088696311157297823662689037894645226208583

# @dev verification key data
ALPHA_X: constant(uint256) = <%=vk_alpha_1[0]%>
ALPHA_Y: constant(uint256) = <%=vk_alpha_1[1]%>
BETA_X1: constant(uint256) = <%=vk_beta_2[0][1]%>
BETA_X2: constant(uint256) = <%=vk_beta_2[0][0]%>
BETA_Y1: constant(uint256) = <%=vk_beta_2[1][1]%>
BETA_Y2: constant(uint256) = <%=vk_beta_2[1][0]%>
GAMMA_X1: constant(uint256) = <%=vk_gamma_2[0][1]%>
GAMMA_X2: constant(uint256) = <%=vk_gamma_2[0][0]%>
GAMMA_Y1: constant(uint256) = <%=vk_gamma_2[1][1]%>
GAMMA_Y2: constant(uint256) = <%=vk_gamma_2[1][0]%>
DELTA_X1: constant(uint256) = <%=vk_delta_2[0][1]%>
DELTA_X2: constant(uint256) = <%=vk_delta_2[0][0]%>
DELTA_Y1: constant(uint256) = <%=vk_delta_2[1][1]%>
DELTA_Y2: constant(uint256) = <%=vk_delta_2[1][0] -%>


<% for (let i = 0; i < IC.length; i++) { %>IC<%=i%>_X: constant(uint256) = <%=IC[i][0]%>
IC<%=i%>_Y: constant(uint256) = <%=IC[i][1]%>
<% } -%>

EC_PAIRING_PRECOMPILED_ADDRESS: constant(address) = 0x0000000000000000000000000000000000000008


@view
@external
def verifyProof(pointA: uint256[2], pointB: uint256[2][2], pointC: uint256[2], publicSignals: uint256[<%=IC.length-1%>]) -> bool:
# @dev check that all public signals are in F
<% for (let i = 0; i < nPublic; i++) { %>if publicSignals[<%=i%>] >= BASE_FIELD_SIZE:
return False
<% } -%>

return self._checkPairing(pointA, pointB, pointC, publicSignals)


@view
@internal
def _g1MulAdd(pR: uint256[2], pP: uint256[2], s: uint256) -> (bool, uint256[2]):
pS: uint256[2] = ecmul(pP, s)

if pS[0] == 0 and pS[1] == 0:
return (False, [0, 0])

finalPoint: uint256[2] = ecadd(pR, pS)

if finalPoint[0] == 0 and finalPoint[1] == 0:
return (False, [0, 0])

return (True, finalPoint)


@view
@internal
def _checkPairing(pA: uint256[2], pB: uint256[2][2], pC: uint256[2], pubSignals: uint256[<%=IC.length-1%>]) -> bool:
success: bool = True
mulAddResult: uint256[2] = [IC0_X, IC0_Y]

# @dev compute the linear combination of public signals
<% for (let i = 1; i <= nPublic; i++) { %>success, mulAddResult = self._g1MulAdd(mulAddResult, [IC<%=i%>_X, IC<%=i%>_Y], pubSignals[<%=i-1%>])
if not success:
return False
<% } -%>

response: Bytes[32] = b""
success, response = raw_call(
EC_PAIRING_PRECOMPILED_ADDRESS,
abi_encode(
pA[0], (BASE_FIELD_SIZE - pA[1]) % BASE_FIELD_SIZE,
pB,
ALPHA_X, ALPHA_Y,
BETA_X1, BETA_X2, BETA_Y1, BETA_Y2,
mulAddResult,
GAMMA_X1, GAMMA_X2, GAMMA_Y1, GAMMA_Y2,
pC,
DELTA_X1, DELTA_X2, DELTA_Y1, DELTA_Y2
),
max_outsize=32,
is_static_call=True,
revert_on_failure=False
)

if not success:
return False

return convert(response, bool)
6 changes: 6 additions & 0 deletions src/types/circuit-zkit.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { languageMap } from "../constants";

export type NumericString = `${number}` | string;

export type PublicSignals = NumericString[];
Expand Down Expand Up @@ -30,6 +32,10 @@ export type Signals = Record<string, Signal>;
export type ArtifactsFileType = "r1cs" | "zkey" | "vkey" | "sym" | "json" | "wasm";
export type VerifierTemplateType = "groth16";

export type LanguageMap = typeof languageMap;
export type Language = keyof LanguageMap;
export type LanguageExtension = LanguageMap[Language];

export type CircuitZKitConfig = {
circuitName: string;
circuitArtifactsPath: string;
Expand Down
104 changes: 91 additions & 13 deletions test/CircuitZKit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe("CircuitZKit", () => {
});

describe("getTemplate", () => {
it("should return correct 'groth16' template", async () => {
it("should return correct 'groth16' Solidity template", async () => {
const groth16TemplatePath: string = path.join(
__dirname,
"..",
Expand All @@ -46,7 +46,20 @@ describe("CircuitZKit", () => {
"verifier_groth16.sol.ejs",
);

expect(CircuitZKit.getTemplate("groth16")).to.be.eq(fs.readFileSync(groth16TemplatePath, "utf-8"));
expect(CircuitZKit.getTemplate("groth16", "sol")).to.be.eq(fs.readFileSync(groth16TemplatePath, "utf-8"));
});

it("should return correct 'groth16' Vyper template", async () => {
const groth16TemplatePath: string = path.join(
__dirname,
"..",
"src",
"core",
"templates",
"verifier_groth16.vy.ejs",
);

expect(CircuitZKit.getTemplate("groth16", "vy")).to.be.eq(fs.readFileSync(groth16TemplatePath, "utf-8"));
});

it("should get exception if pass invalid template type", async () => {
Expand All @@ -55,7 +68,7 @@ describe("CircuitZKit", () => {
const invalidTemplate = "fflonk";

expect(function () {
circuitZKit.getTemplate(invalidTemplate);
circuitZKit.getTemplate(invalidTemplate, "sol");
}).to.throw(`Ambiguous template type: ${invalidTemplate}.`);
});
});
Expand All @@ -67,7 +80,7 @@ describe("CircuitZKit", () => {
fs.rmSync(getVerifiersDirFullPath(), { recursive: true, force: true });
});

it("should correctly create verifier file", async () => {
it("should correctly create verifier Solidity file", async () => {
const circuitName = "Multiplier";
const verifierDirPath = getVerifiersDirFullPath();
const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`);
Expand All @@ -84,21 +97,52 @@ describe("CircuitZKit", () => {

expect(fs.existsSync(expectedVerifierFilePath)).to.be.false;

await multiplierCircuit.createVerifier();
await multiplierCircuit.createVerifier("solidity");

expect(fs.existsSync(expectedVerifierFilePath)).to.be.true;

const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.vkey.json`);
expect(multiplierCircuit.getArtifactsFilePath("vkey")).to.be.eq(expectedVKeyFilePath);

const template = CircuitZKit.getTemplate("groth16", "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 () => {
const circuitName = "Multiplier";
const verifierDirPath = getVerifiersDirFullPath();
const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`);

const multiplierCircuit: CircuitZKit = new CircuitZKit({
circuitName,
circuitArtifactsPath: artifactsDirFullPath,
verifierDirPath,
});

expect(multiplierCircuit.getVerifierName()).to.be.eq(`${circuitName}Verifier`);

const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.vy`);

expect(fs.existsSync(expectedVerifierFilePath)).to.be.false;

await multiplierCircuit.createVerifier("vyper");

expect(fs.existsSync(expectedVerifierFilePath)).to.be.true;

const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.vkey.json`);
expect(multiplierCircuit.getArtifactsFilePath("vkey")).to.be.eq(expectedVKeyFilePath);

const template = CircuitZKit.getTemplate("groth16");
const template = CircuitZKit.getTemplate("groth16", "vy");
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 and verify proof", async function () {
it("should correctly create Solidity verifier and verify proof", async function () {
const circuitName = "Multiplier";
const verifierDirPath = getVerifiersDirFullPath();
const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`);
Expand All @@ -111,7 +155,41 @@ describe("CircuitZKit", () => {

const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.sol`);

await multiplierCircuit.createVerifier();
await multiplierCircuit.createVerifier("solidity");
expect(fs.existsSync(expectedVerifierFilePath)).to.be.true;

await this.hre.run("compile", { quiet: true });

const proof = await multiplierCircuit.generateProof({
a: 10,
b: 20,
});

expect(await multiplierCircuit.verifyProof(proof)).to.be.true;

const data = await multiplierCircuit.generateCalldata(proof);

const MultiplierVerifierFactory = await this.hre.ethers.getContractFactory("MultiplierVerifier");

const verifier = await MultiplierVerifierFactory.deploy();

expect(await verifier.verifyProof(...data)).to.be.true;
});

it("should correctly create Vyper verifier and verify proof", async function () {
const circuitName = "Multiplier";
const verifierDirPath = getVerifiersDirFullPath();
const artifactsDirFullPath = getArtifactsFullPath(`${circuitName}.circom`);

const multiplierCircuit: CircuitZKit = new CircuitZKit({
circuitName,
circuitArtifactsPath: artifactsDirFullPath,
verifierDirPath,
});

const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.vy`);

await multiplierCircuit.createVerifier("vyper");
expect(fs.existsSync(expectedVerifierFilePath)).to.be.true;

await this.hre.run("compile", { quiet: true });
Expand Down Expand Up @@ -145,16 +223,16 @@ describe("CircuitZKit", () => {

const expectedVerifierFilePath = path.join(verifierDirPath, `${multiplierCircuit.getVerifierName()}.sol`);

await multiplierCircuit.createVerifier();
await multiplierCircuit.createVerifier("solidity");
expect(fs.existsSync(expectedVerifierFilePath)).to.be.true;

await multiplierCircuit.createVerifier();
await multiplierCircuit.createVerifier("solidity");
expect(fs.existsSync(expectedVerifierFilePath)).to.be.true;

const expectedVKeyFilePath = path.join(artifactsDirFullPath, `${circuitName}.vkey.json`);
expect(multiplierCircuit.getArtifactsFilePath("vkey")).to.be.eq(expectedVKeyFilePath);

const template = CircuitZKit.getTemplate("groth16");
const template = CircuitZKit.getTemplate("groth16", "sol");
const templateParams = JSON.parse(fs.readFileSync(expectedVKeyFilePath, "utf-8"));
templateParams["verifier_id"] = multiplierCircuit.getVerifierName();

Expand All @@ -172,7 +250,7 @@ describe("CircuitZKit", () => {

const invalidVKeyFilePath = multiplierCircuit.getArtifactsFilePath("vkey");

await expect(multiplierCircuit.createVerifier()).to.be.rejectedWith(
await expect(multiplierCircuit.createVerifier("solidity")).to.be.rejectedWith(
`Expected the file "${invalidVKeyFilePath}" to exist`,
);
});
Expand Down Expand Up @@ -242,7 +320,7 @@ describe("CircuitZKit", () => {
verifierDirPath: getVerifiersDirFullPath(),
});

await multiplierCircuit.createVerifier();
await multiplierCircuit.createVerifier("solidity");

await this.hre.run("compile", { quiet: true });

Expand Down
Loading

0 comments on commit bafd4cd

Please sign in to comment.