From 40883ed4f4c58c17156a90d03d92f50e84552139 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Mon, 13 May 2024 15:54:34 +0300 Subject: [PATCH 01/14] a tiny bit of doc --- src/CircomZKit.ts | 30 +++++++++++ src/CircuitZKit.ts | 127 +++++++++++++++++++++++++++++++++++++++++++-- src/ManagerZKit.ts | 97 ++++++++++++++++++++++++++++++++-- src/config.ts | 43 +++++++++++++++ src/defaults.ts | 14 ----- src/index.ts | 14 +---- src/types.ts | 31 ----------- 7 files changed, 293 insertions(+), 63 deletions(-) create mode 100644 src/config.ts delete mode 100644 src/defaults.ts diff --git a/src/CircomZKit.ts b/src/CircomZKit.ts index 5896417..48aebac 100644 --- a/src/CircomZKit.ts +++ b/src/CircomZKit.ts @@ -6,13 +6,31 @@ import { ManagerZKit } from "./ManagerZKit"; import { CircuitInfo } from "./types"; import { readDirRecursively } from "./utils"; +/** + * `CircomZKit` acts as a factory for `CircuitZKit` instances. + */ export class CircomZKit { private readonly _manager: ManagerZKit; + /** + * Creates a new `CircomZKit` instance. + * + * @dev If no `ManagerZKit` instance is provided, a default instance will be created. + * + * @param {ManagerZKit | undefined} manager - The `ManagerZKit` instance to use. + */ constructor(manager?: ManagerZKit) { this._manager = manager ?? new ManagerZKit(); } + /** + * Returns a `CircuitZKit` instance for the specified circuit. + * + * @dev If the circuit id is not unique, the path to the circuit file must be provided. + * + * @param {string} circuit - The path to the circuit file or the circuit id (filename without extension). + * @returns {CircomZKit} The `CircuitZKit` instance. + */ public getCircuit(circuit: string): CircuitZKit { const circuits = this._getAllCircuits(); @@ -40,6 +58,13 @@ export class CircomZKit { return new CircuitZKit(this._manager, path.join(this._manager.getCircuitsDir(), candidates[0])); } + /** + * Returns an array of all circuits available in the circuits directory. + * + * @dev If a circuit id is not unique, the id will be set to `null`. + * + * @returns {CircuitInfo[]} An array of circuit information objects. + */ public getCircuits(): CircuitInfo[] { const circuits = this._getAllCircuits(); @@ -65,6 +90,11 @@ export class CircomZKit { return result; } + /** + * Returns an array of all circuits paths available in the circuits directory. + * + * @returns {string[]} An array of circuit paths. + */ private _getAllCircuits(): string[] { const circuitsDir = this._manager.getCircuitsDir(); diff --git a/src/CircuitZKit.ts b/src/CircuitZKit.ts index 3abd738..d396929 100644 --- a/src/CircuitZKit.ts +++ b/src/CircuitZKit.ts @@ -3,19 +3,39 @@ import fs from "fs"; import path from "path"; import * as snarkjs from "snarkjs"; -import { defaultCompileOptions } from "./defaults"; +import { defaultCompileOptions } from "./config"; import { ManagerZKit } from "./ManagerZKit"; -import { Calldata, CompileOptions, DirType, FileType, Inputs, ProofStruct } from "./types"; +import { CompileOptions } from "./config"; +import { Calldata, DirType, FileType, Inputs, ProofStruct } from "./types"; import { readDirRecursively } from "./utils"; const { CircomRunner, bindings } = require("@distributedlab/circom2"); +/** + * `CircuitZKit` represents a single circuit and provides a high-level API to work with it. + * + * @dev If you want to work with multiple circuits, consider using the `CircomZKit`. + */ export class CircuitZKit { + /** + * Creates a new instance of `CircuitZKit`. + * + * @param {ManagerZKit} _manager - The manager that maintains the global state. + * @param {string} _circuit - The path to the circuit. + */ constructor( private readonly _manager: ManagerZKit, private readonly _circuit: string, ) {} + /** + * Compiles the circuit and generates the artifacts. + * + * @dev If compilation fails, the latest valid artifacts will be preserved. + * @dev Doesn't show the compilation error if `quiet` is set to `true`. + * + * @param {Partial} [options=defaultCompileOptions] - Compilation options. + */ public async compile(options: Partial = defaultCompileOptions): Promise { const tempDir = this._manager.getTempDir(); @@ -40,6 +60,9 @@ export class CircuitZKit { } } + /** + * Creates a verifier contract. + */ public async createVerifier(): Promise { const tempDir = this._manager.getTempDir(); @@ -66,6 +89,15 @@ export class CircuitZKit { } } + /** + * Generates a proof for the given inputs. + * + * @dev The `inputs` should be in the same order as the circuit expects them. + * + * @param {Inputs} inputs - The inputs for the circuit. + * @returns {Promise} The generated proof. + * @todo Add support for other proving systems. + */ public async generateProof(inputs: Inputs): Promise { const zKeyFile = this._mustGetFile("zkey"); const wasmFile = this._mustGetFile("wasm"); @@ -73,6 +105,15 @@ export class CircuitZKit { return (await snarkjs.groth16.fullProve(inputs, wasmFile, zKeyFile)) as ProofStruct; } + /** + * Verifies the given proof. + * + * @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. + * @returns {Promise} Whether the proof is valid. + */ public async verifyProof(proof: ProofStruct): Promise { const vKeyFile = this._mustGetFile("vkey"); @@ -81,20 +122,43 @@ export class CircuitZKit { return await snarkjs.groth16.verify(verifier, proof.publicSignals, proof.proof); } + /** + * 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. + * @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; } + /** + * Returns the circuit ID. The circuit ID is the name of the circuit file without the extension. + * + * @returns {string} The circuit ID. + */ public getCircuitId(): string { return path.parse(this._circuit).name; } + /** + * Returns the verifier ID. The verifier ID is the name of the circuit file without the extension, suffixed with "Verifier". + * + * @returns {string} The verifier ID. + */ public getVerifierId(): string { return `${path.parse(this._circuit).name}Verifier`; } + /** + * Generates zero-knowledge key for the circuit. + * + * @param {string} outDir - The directory to save the generated key. + * @todo This method may cause issues https://github.com/iden3/snarkjs/issues/494 + */ private async _generateZKey(outDir: string): Promise { const r1csFile = this._getFile("r1cs", outDir); const zKeyFile = this._getFile("zkey", outDir); @@ -105,6 +169,11 @@ export class CircuitZKit { await snarkjs.zKey.newZKey(r1csFile, ptauFile, zKeyFile); } + /** + * Generates verification key for the circuit. + * + * @param {string} outDir - The directory to save the generated key. + */ private async _generateVKey(outDir: string): Promise { const zKeyFile = this._getFile("zkey", outDir); const vKeyFile = this._getFile("vkey", outDir); @@ -114,6 +183,13 @@ export class CircuitZKit { fs.writeFileSync(vKeyFile, JSON.stringify(vKeyData)); } + /** + * Returns the arguments to compile the circuit. + * + * @param {CompileOptions} options - Compilation options. + * @param {string} outDir - The directory to save the compiled artifacts. + * @returns {string[]} The arguments to compile the circuit. + */ private _getCompileArgs(options: CompileOptions, outDir: string): string[] { let args = [this._circuit, "--r1cs", "--wasm"]; @@ -126,6 +202,12 @@ export class CircuitZKit { return args; } + /** + * Compiles the circuit. + * + * @param {CompileOptions} options - Compilation options. + * @param {string} outDir - The directory to save the compiled artifacts. + */ private async _compile(options: CompileOptions, outDir: string): Promise { const args = this._getCompileArgs(options, outDir); @@ -143,6 +225,12 @@ export class CircuitZKit { } } + /** + * Returns the number of constraints in the circuit. This value is used to fetch the correct `ptau` file. + * + * @param {string} outDir - The directory where the compiled artifacts are saved. + * @returns {Promise} The number of constraints in the circuit. + */ async _getConstraints(outDir: string): Promise { const r1csFile = this._getFile("r1cs", outDir); @@ -177,6 +265,13 @@ export class CircuitZKit { throw new Error("Header section is not found."); } + /** + * Returns the path to the file of the given type. + * + * @param {FileType} fileType - The type of the file. + * @param {string | undefined} temp - The temporary directory to use. + * @returns {string} The path to the file. + */ private _getFile(fileType: FileType, temp?: string): string { const circuitId = this.getCircuitId(); @@ -200,6 +295,12 @@ export class CircuitZKit { } } + /** + * Returns the path to the directory of the given type. + * + * @param {DirType} dirType - The type of the directory. + * @returns {string} The path to the directory. + */ private _getDir(dirType: DirType): string { const circuitRelativePath = path.relative(this._manager.getCircuitsDir(), this._circuit); @@ -215,6 +316,13 @@ export class CircuitZKit { } } + /** + * Returns the path to the file of the given type. Throws an error if the file doesn't exist. + * + * @param {FileType} fileType - The type of the file. + * @param {string | undefined} temp - The temporary directory to use. + * @returns {string} The path to the file. + */ private _mustGetFile(fileType: FileType, temp?: string): string { const file = this._getFile(fileType, temp); @@ -225,7 +333,13 @@ export class CircuitZKit { return file; } - private _moveFromTempDirToOutDir(tempDir: string, outDir: string) { + /** + * Moves the files from the temporary directory to the output directory. + * + * @param {string} tempDir - The temporary directory. + * @param {string} outDir - The output directory. + */ + private _moveFromTempDirToOutDir(tempDir: string, outDir: string): void { fs.rmSync(outDir, { recursive: true, force: true }); fs.mkdirSync(outDir, { recursive: true }); @@ -241,6 +355,13 @@ export class CircuitZKit { }); } + /** + * Returns a new instance of `CircomRunner`. The `CircomRunner` is a wasm module that compiles the circuit. + * + * @param {string[]} args - The arguments to run the `circom` compiler. + * @param {boolean} quiet - Whether to suppress the compilation error. + * @returns {typeof CircomRunner} The `CircomRunner` instance. + */ private _getCircomRunner(args: string[], quiet: boolean): typeof CircomRunner { return new CircomRunner({ args, diff --git a/src/ManagerZKit.ts b/src/ManagerZKit.ts index d6b7c9d..276faed 100644 --- a/src/ManagerZKit.ts +++ b/src/ManagerZKit.ts @@ -6,12 +6,20 @@ import process from "process"; import * as readline from "readline"; import { v4 as uuid } from "uuid"; -import { ManagerZKitConfig, ManagerZKitPrivateConfig, TemplateType } from "./types"; -import { defaultManagerOptions } from "./defaults"; +import { ManagerZKitConfig, ManagerZKitPrivateConfig, defaultManagerOptions } from "./config"; +import { TemplateType } from "./types"; +/** + * `ManagerZKit` provides configuration options and utility methods used by the `CircomZKit` and `CircuitZKit` classes. + */ export class ManagerZKit { private _config: ManagerZKitPrivateConfig; + /** + * Creates a new `ManagerZKit` instance. + * + * @param {Partial} [config=defaultManagerOptions] - The configuration options to use. + */ constructor(config: Partial = defaultManagerOptions) { const overriddenConfig = { ...defaultManagerOptions, @@ -47,6 +55,15 @@ export class ManagerZKit { }; } + /** + * Fetches the `ptau` file. + * + * @dev If the local `ptauFile` is not provided, the method will attempt to download it. + * The user will be prompted every time a download is required. + * + * @param {number} minConstraints - The minimum number of constraints the `ptau` file must support. + * @returns {Promise} The path to the `ptau` file. + */ public async fetchPtauFile(minConstraints: number): Promise { if (this._config.ptau.isGlobal) { return await this._fetchGlobalPtau(minConstraints); @@ -55,30 +72,71 @@ export class ManagerZKit { return this._fetchLocalPtau(); } + /** + * Returns the path to the artifacts' directory. + * + * @returns {string} The path to the artifacts' directory. + */ public getArtifactsDir(): string { return this._config.artifactsDir; } + /** + * Returns the path to the circuits' directory. + * + * @returns {string} The path to the circuits' directory. + */ public getCircuitsDir(): string { return this._config.circuitsDir; } + /** + * Returns the path to the verifiers' directory. + * + * @returns {string} The path to the verifiers' directory. + */ public getVerifiersDir(): string { return this._config.verifiersDir; } + /** + * Returns the path to the `ptau` file or directory. + * + * @dev If the local `ptauFile` is not provided, the method will return the global `ptau` directory. + * @dev The global `ptau` directory is located at `~/.zkit/.ptau`. + * + * @returns {string} The path to the `ptau` file or directory. + */ public getPtauPath(): string { return this._config.ptau.path; } + /** + * Returns the circom compiler's wasm binary. + * + * @returns {string} The circom compiler's wasm binary. + */ public getCompiler(): string { return this._config.compiler; } + /** + * Returns a temporary directory path. + * + * @dev Temporary files are stored in the OS's temporary directory. + * + * @returns {string} A temporary directory path. + */ public getTempDir(): string { return path.join(this._config.tempDir, uuid()); } + /** + * Returns the Solidity verifier template for the specified proving system. + * + * @param {TemplateType} templateType - The template type. + * @returns {string} The Solidity verifier template. + */ public getTemplate(templateType: TemplateType): string { switch (templateType) { case "groth16": @@ -88,6 +146,13 @@ export class ManagerZKit { } } + /** + * Fetches the `ptau` file from the global directory that supports the specified number of constraints. + * Downloads the `ptau` file if it doesn't exist. + * + * @param {number} minConstraints - The minimum number of constraints the `ptau` file must support. + * @returns {Promise} The path to the `ptau` file. + */ private async _fetchGlobalPtau(minConstraints: number): Promise { const ptauId = Math.max(Math.ceil(Math.log2(minConstraints)), 8); @@ -106,12 +171,19 @@ export class ManagerZKit { fs.mkdirSync(this._config.ptau.path, { recursive: true }); - await this._downloadPtau(ptauInfo.file, ptauInfo.url); + if (!(await this._downloadPtau(ptauInfo.file, ptauInfo.url))) { + throw new Error("Something went wrong while downloading the ptau file."); + } } return ptauInfo.file; } + /** + * Fetches the `ptau` file from the local directory. + * + * @returns {string} The path to the `ptau` file. + */ private _fetchLocalPtau(): string { if (!fs.existsSync(this._config.ptau.path)) { throw new Error(`Ptau file "${this._config.ptau.path}" doesn't exist.`); @@ -120,6 +192,12 @@ export class ManagerZKit { return this._config.ptau.path; } + /** + * Searches for the `ptau` file that supports the specified number of constraints. + * + * @param {number} ptauId - The `ptau` file id. + * @returns {{ file: string; url: string | null }} The `ptau` file path and download url if the file doesn't exist. + */ private _searchGlobalPtau(ptauId: number): { file: string; url: string | null } { let entries = [] as fs.Dirent[]; @@ -159,6 +237,13 @@ export class ManagerZKit { return { file, url }; } + /** + * Downloads the `ptau` file from the specified url. + * + * @param {string} file - The path to the `ptau` file. + * @param {string} url - The url to download the `ptau` file from. + * @returns {Promise} Whether the download is successful. + */ private async _downloadPtau(file: string, url: string): Promise { const ptauFileStream = fs.createWriteStream(file); @@ -181,6 +266,12 @@ export class ManagerZKit { }); } + /** + * Prompts the user to allow the download of the `ptau` file. + * + * @param {string} url - The url to download the `ptau` file from. + * @returns {Promise} Whether the download is allowed. + */ private _askForDownloadAllowance(url: string): Promise { return new Promise((resolve) => { const readLine = readline.createInterface({ diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..afe38d5 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,43 @@ +const { Context } = require("@distributedlab/circom2"); + +export type ManagerZKitConfig = { + circuitsDir: string; + artifactsDir: string; + verifiersDir: string; + ptauFile: string; +}; + +export const defaultManagerOptions: Partial = { + circuitsDir: "circuits", + artifactsDir: "zkit-artifacts", + verifiersDir: "contracts/verifiers", +}; + +export type CompileOptions = { + sym: boolean; + json: boolean; + c: boolean; + quiet: boolean; +}; + +export const defaultCompileOptions: CompileOptions = { + sym: false, + json: false, + c: false, + quiet: false, +}; + +export type ManagerZKitPrivateConfig = { + circuitsDir: string; + artifactsDir: string; + verifiersDir: string; + tempDir: string; + ptau: { + isGlobal: boolean; + path: string; + }; + compiler: typeof Context; + templates: { + groth16: string; + }; +}; diff --git a/src/defaults.ts b/src/defaults.ts deleted file mode 100644 index 7d02d28..0000000 --- a/src/defaults.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CompileOptions, ManagerZKitConfig } from "./types"; - -export const defaultManagerOptions: Partial = { - circuitsDir: "circuits", - artifactsDir: "zkit-artifacts", - verifiersDir: "contracts/verifiers", -}; - -export const defaultCompileOptions: CompileOptions = { - sym: false, - json: false, - c: false, - quiet: false, -}; diff --git a/src/index.ts b/src/index.ts index 2402bb0..b38f5b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,16 +2,6 @@ export * from "./CircomZKit"; export * from "./CircuitZKit"; export * from "./ManagerZKit"; -export { - NumericString, - PublicSignals, - Groth16Proof, - Calldata, - ProofStruct, - Inputs, - ManagerZKitConfig, - CompileOptions, - CircuitInfo, -} from "./types"; +export { NumericString, PublicSignals, Groth16Proof, Calldata, ProofStruct, Inputs, CircuitInfo } from "./types"; -export * from "./defaults"; +export { CompileOptions, ManagerZKitConfig, defaultCompileOptions, defaultManagerOptions } from "./config"; diff --git a/src/types.ts b/src/types.ts index 312b323..2912eb4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,3 @@ -const { Context } = require("@distributedlab/circom2"); - export type NumericString = `${number}` | string; export type PublicSignals = NumericString[]; @@ -30,35 +28,6 @@ export type InputLike = NumberLike | ArrayLike; export type Inputs = Record; -export type ManagerZKitConfig = { - circuitsDir: string; - artifactsDir: string; - verifiersDir: string; - ptauFile: string; -}; - -export type ManagerZKitPrivateConfig = { - circuitsDir: string; - artifactsDir: string; - verifiersDir: string; - tempDir: string; - ptau: { - isGlobal: boolean; - path: string; - }; - compiler: typeof Context; - templates: { - groth16: string; - }; -}; - -export type CompileOptions = { - sym: boolean; - json: boolean; - c: boolean; - quiet: boolean; -}; - export type CircuitInfo = { path: string; id: string | null; From 8c6b5ac5e2e978041a6a1d7865e47aae3c83ec18 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Mon, 13 May 2024 15:56:40 +0300 Subject: [PATCH 02/14] refactored & bumped version --- package-lock.json | 4 +- package.json | 4 +- src/ManagerZKit.ts | 288 ------------------ src/{ => config}/config.ts | 14 +- src/{ => core}/CircomZKit.ts | 17 +- src/{ => core}/CircuitZKit.ts | 20 +- src/core/ManagerZKit.ts | 226 ++++++++++++++ .../templates/verifier_groth16.sol.ejs | 0 src/index.ts | 10 +- src/{ => types}/types.ts | 5 + src/utils.ts | 23 -- src/utils/utils.ts | 60 ++++ test/CircomZKit.test.ts | 10 +- tsconfig.json | 2 +- 14 files changed, 326 insertions(+), 357 deletions(-) delete mode 100644 src/ManagerZKit.ts rename src/{ => config}/config.ts (75%) rename src/{ => core}/CircomZKit.ts (81%) rename src/{ => core}/CircuitZKit.ts (96%) create mode 100644 src/core/ManagerZKit.ts rename src/{ => core}/templates/verifier_groth16.sol.ejs (100%) rename src/{ => types}/types.ts (93%) delete mode 100644 src/utils.ts create mode 100644 src/utils/utils.ts diff --git a/package-lock.json b/package-lock.json index ca0d25a..9d501bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@solarity/zkit", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/zkit", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@distributedlab/circom2": "0.2.18-rc.2", diff --git a/package.json b/package.json index 6b09c95..37f2efc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/zkit", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "author": "Distributed Lab", "readme": "README.md", @@ -27,7 +27,7 @@ "build": "tsc", "test": "jest --forceExit", "lint-fix": "prettier --write 'src/**/*.ts'", - "publish-to-npm": "npm run build && rm -rf dist/templates && cp -rf src/templates dist/templates && npm publish ./ --access public" + "publish-to-npm": "npm run build && rm -rf dist/core/templates && cp -rf src/core/templates dist/core/templates && npm publish ./ --access public" }, "dependencies": { "@distributedlab/circom2": "0.2.18-rc.2", diff --git a/src/ManagerZKit.ts b/src/ManagerZKit.ts deleted file mode 100644 index 276faed..0000000 --- a/src/ManagerZKit.ts +++ /dev/null @@ -1,288 +0,0 @@ -import fs from "fs"; -import https from "https"; -import os from "os"; -import path from "path"; -import process from "process"; -import * as readline from "readline"; -import { v4 as uuid } from "uuid"; - -import { ManagerZKitConfig, ManagerZKitPrivateConfig, defaultManagerOptions } from "./config"; -import { TemplateType } from "./types"; - -/** - * `ManagerZKit` provides configuration options and utility methods used by the `CircomZKit` and `CircuitZKit` classes. - */ -export class ManagerZKit { - private _config: ManagerZKitPrivateConfig; - - /** - * Creates a new `ManagerZKit` instance. - * - * @param {Partial} [config=defaultManagerOptions] - The configuration options to use. - */ - constructor(config: Partial = defaultManagerOptions) { - const overriddenConfig = { - ...defaultManagerOptions, - ...config, - } as ManagerZKitConfig; - - const projectRoot = process.cwd(); - - const isGlobalPtau = !overriddenConfig.ptauFile; - - if (!isGlobalPtau && path.extname(overriddenConfig.ptauFile!) != ".ptau") { - throw new Error('Ptau file must have ".ptau" extension.'); - } - - const tempDir = path.join(os.tmpdir(), ".zkit"); - const ptauPath = isGlobalPtau - ? path.join(os.homedir(), ".zkit", ".ptau") - : path.join(projectRoot, overriddenConfig.ptauFile!); - - this._config = { - circuitsDir: path.join(projectRoot, overriddenConfig.circuitsDir), - artifactsDir: path.join(projectRoot, overriddenConfig.artifactsDir), - verifiersDir: path.join(projectRoot, overriddenConfig.verifiersDir), - tempDir, - ptau: { - isGlobal: isGlobalPtau, - path: ptauPath, - }, - compiler: fs.readFileSync(require.resolve("@distributedlab/circom2/circom.wasm")), - templates: { - groth16: fs.readFileSync(path.join(__dirname, "templates", "verifier_groth16.sol.ejs"), "utf8"), - }, - }; - } - - /** - * Fetches the `ptau` file. - * - * @dev If the local `ptauFile` is not provided, the method will attempt to download it. - * The user will be prompted every time a download is required. - * - * @param {number} minConstraints - The minimum number of constraints the `ptau` file must support. - * @returns {Promise} The path to the `ptau` file. - */ - public async fetchPtauFile(minConstraints: number): Promise { - if (this._config.ptau.isGlobal) { - return await this._fetchGlobalPtau(minConstraints); - } - - return this._fetchLocalPtau(); - } - - /** - * Returns the path to the artifacts' directory. - * - * @returns {string} The path to the artifacts' directory. - */ - public getArtifactsDir(): string { - return this._config.artifactsDir; - } - - /** - * Returns the path to the circuits' directory. - * - * @returns {string} The path to the circuits' directory. - */ - public getCircuitsDir(): string { - return this._config.circuitsDir; - } - - /** - * Returns the path to the verifiers' directory. - * - * @returns {string} The path to the verifiers' directory. - */ - public getVerifiersDir(): string { - return this._config.verifiersDir; - } - - /** - * Returns the path to the `ptau` file or directory. - * - * @dev If the local `ptauFile` is not provided, the method will return the global `ptau` directory. - * @dev The global `ptau` directory is located at `~/.zkit/.ptau`. - * - * @returns {string} The path to the `ptau` file or directory. - */ - public getPtauPath(): string { - return this._config.ptau.path; - } - - /** - * Returns the circom compiler's wasm binary. - * - * @returns {string} The circom compiler's wasm binary. - */ - public getCompiler(): string { - return this._config.compiler; - } - - /** - * Returns a temporary directory path. - * - * @dev Temporary files are stored in the OS's temporary directory. - * - * @returns {string} A temporary directory path. - */ - public getTempDir(): string { - return path.join(this._config.tempDir, uuid()); - } - - /** - * Returns the Solidity verifier template for the specified proving system. - * - * @param {TemplateType} templateType - The template type. - * @returns {string} The Solidity verifier template. - */ - public getTemplate(templateType: TemplateType): string { - switch (templateType) { - case "groth16": - return this._config.templates.groth16; - default: - throw new Error(`Ambiguous template type: ${templateType}.`); - } - } - - /** - * Fetches the `ptau` file from the global directory that supports the specified number of constraints. - * Downloads the `ptau` file if it doesn't exist. - * - * @param {number} minConstraints - The minimum number of constraints the `ptau` file must support. - * @returns {Promise} The path to the `ptau` file. - */ - private async _fetchGlobalPtau(minConstraints: number): Promise { - const ptauId = Math.max(Math.ceil(Math.log2(minConstraints)), 8); - - if (ptauId > 20) { - throw new Error( - 'Circuit has too many constraints. The maximum number of constraints is 2^20. Consider passing "ptau=PATH_TO_FILE".', - ); - } - - const ptauInfo = this._searchGlobalPtau(ptauId); - - if (ptauInfo.url) { - if (!(await this._askForDownloadAllowance(ptauInfo.url))) { - throw new Error('Download is cancelled. Allow download or consider passing "ptauFile=PATH_TO_FILE"'); - } - - fs.mkdirSync(this._config.ptau.path, { recursive: true }); - - if (!(await this._downloadPtau(ptauInfo.file, ptauInfo.url))) { - throw new Error("Something went wrong while downloading the ptau file."); - } - } - - return ptauInfo.file; - } - - /** - * Fetches the `ptau` file from the local directory. - * - * @returns {string} The path to the `ptau` file. - */ - private _fetchLocalPtau(): string { - if (!fs.existsSync(this._config.ptau.path)) { - throw new Error(`Ptau file "${this._config.ptau.path}" doesn't exist.`); - } - - return this._config.ptau.path; - } - - /** - * Searches for the `ptau` file that supports the specified number of constraints. - * - * @param {number} ptauId - The `ptau` file id. - * @returns {{ file: string; url: string | null }} The `ptau` file path and download url if the file doesn't exist. - */ - private _searchGlobalPtau(ptauId: number): { file: string; url: string | null } { - let entries = [] as fs.Dirent[]; - - if (fs.existsSync(this._config.ptau.path)) { - entries = fs.readdirSync(this._config.ptau.path, { withFileTypes: true }); - } - - const entry = entries.find((entry) => { - if (!entry.isFile()) { - return false; - } - - const match = entry.name.match(/^powers-of-tau-(\d+)\.ptau$/); - - if (!match) { - return false; - } - - const entryPtauId = parseInt(match[1]); - - return ptauId <= entryPtauId; - }); - - const file = path.join(this._config.ptau.path, entry ? entry.name : `powers-of-tau-${ptauId}.ptau`); - const url = (() => { - if (entry) { - return null; - } - - if (ptauId < 10) { - return `https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_0${ptauId}.ptau`; - } - - return `https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_${ptauId}.ptau`; - })(); - - return { file, url }; - } - - /** - * Downloads the `ptau` file from the specified url. - * - * @param {string} file - The path to the `ptau` file. - * @param {string} url - The url to download the `ptau` file from. - * @returns {Promise} Whether the download is successful. - */ - private async _downloadPtau(file: string, url: string): Promise { - const ptauFileStream = fs.createWriteStream(file); - - return new Promise((resolve, reject) => { - const request = https.get(url, (response) => { - response.pipe(ptauFileStream); - }); - - ptauFileStream.on("finish", () => resolve(true)); - - request.on("error", (err) => { - fs.unlink(file, () => reject(err)); - }); - - ptauFileStream.on("error", (err) => { - fs.unlink(file, () => reject(err)); - }); - - request.end(); - }); - } - - /** - * Prompts the user to allow the download of the `ptau` file. - * - * @param {string} url - The url to download the `ptau` file from. - * @returns {Promise} Whether the download is allowed. - */ - private _askForDownloadAllowance(url: string): Promise { - return new Promise((resolve) => { - const readLine = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - readLine.question(`No ptau found. Press [Y] to download it from "${url}": `, (response) => { - readLine.close(); - resolve(response.toUpperCase() == "Y"); - }); - }); - } -} diff --git a/src/config.ts b/src/config/config.ts similarity index 75% rename from src/config.ts rename to src/config/config.ts index afe38d5..34b618b 100644 --- a/src/config.ts +++ b/src/config/config.ts @@ -4,13 +4,15 @@ export type ManagerZKitConfig = { circuitsDir: string; artifactsDir: string; verifiersDir: string; - ptauFile: string; + ptauDir: string; + allowDownload: boolean; }; export const defaultManagerOptions: Partial = { circuitsDir: "circuits", artifactsDir: "zkit-artifacts", verifiersDir: "contracts/verifiers", + allowDownload: true, }; export type CompileOptions = { @@ -27,15 +29,7 @@ export const defaultCompileOptions: CompileOptions = { quiet: false, }; -export type ManagerZKitPrivateConfig = { - circuitsDir: string; - artifactsDir: string; - verifiersDir: string; - tempDir: string; - ptau: { - isGlobal: boolean; - path: string; - }; +export type ManagerZKitPrivateConfig = ManagerZKitConfig & { compiler: typeof Context; templates: { groth16: string; diff --git a/src/CircomZKit.ts b/src/core/CircomZKit.ts similarity index 81% rename from src/CircomZKit.ts rename to src/core/CircomZKit.ts index 48aebac..41bcac7 100644 --- a/src/CircomZKit.ts +++ b/src/core/CircomZKit.ts @@ -3,8 +3,9 @@ import path from "path"; import { CircuitZKit } from "./CircuitZKit"; import { ManagerZKit } from "./ManagerZKit"; -import { CircuitInfo } from "./types"; -import { readDirRecursively } from "./utils"; +import { CircuitInfo } from "../types/types"; +import { readDirRecursively } from "../utils/utils"; +import { defaultManagerOptions, ManagerZKitConfig } from "../config/config"; /** * `CircomZKit` acts as a factory for `CircuitZKit` instances. @@ -15,12 +16,10 @@ export class CircomZKit { /** * Creates a new `CircomZKit` instance. * - * @dev If no `ManagerZKit` instance is provided, a default instance will be created. - * - * @param {ManagerZKit | undefined} manager - The `ManagerZKit` instance to use. + * @param {Partial} [options=defaultManagerOptions] - The configuration options to use. */ - constructor(manager?: ManagerZKit) { - this._manager = manager ?? new ManagerZKit(); + constructor(options: Partial = defaultManagerOptions) { + this._manager = new ManagerZKit({ ...defaultManagerOptions, ...options }); } /** @@ -55,7 +54,7 @@ export class CircomZKit { ); } - return new CircuitZKit(this._manager, path.join(this._manager.getCircuitsDir(), candidates[0])); + return new CircuitZKit(path.join(this._manager.getCircuitsDir(), candidates[0]), this._manager); } /** @@ -100,7 +99,7 @@ export class CircomZKit { let circuits = [] as string[]; - readDirRecursively(circuitsDir, (dir: string, file: string) => { + readDirRecursively(circuitsDir, (_dir: string, file: string) => { if (path.extname(file) == ".circom") { circuits.push(path.relative(circuitsDir, file)); } diff --git a/src/CircuitZKit.ts b/src/core/CircuitZKit.ts similarity index 96% rename from src/CircuitZKit.ts rename to src/core/CircuitZKit.ts index d396929..ecae079 100644 --- a/src/CircuitZKit.ts +++ b/src/core/CircuitZKit.ts @@ -3,29 +3,28 @@ import fs from "fs"; import path from "path"; import * as snarkjs from "snarkjs"; -import { defaultCompileOptions } from "./config"; +import { defaultCompileOptions, CompileOptions } from "../config/config"; import { ManagerZKit } from "./ManagerZKit"; -import { CompileOptions } from "./config"; -import { Calldata, DirType, FileType, Inputs, ProofStruct } from "./types"; -import { readDirRecursively } from "./utils"; +import { Calldata, DirType, FileType, Inputs, ProofStruct } from "../types/types"; +import { readDirRecursively } from "../utils/utils"; const { CircomRunner, bindings } = require("@distributedlab/circom2"); /** * `CircuitZKit` represents a single circuit and provides a high-level API to work with it. * - * @dev If you want to work with multiple circuits, consider using the `CircomZKit`. + * @dev This class is not meant to be used directly. Use the `CircomZKit` to create its instance. */ export class CircuitZKit { /** * Creates a new instance of `CircuitZKit`. * - * @param {ManagerZKit} _manager - The manager that maintains the global state. * @param {string} _circuit - The path to the circuit. + * @param {ManagerZKit} _manager - The manager that maintains the global state. */ constructor( - private readonly _manager: ManagerZKit, private readonly _circuit: string, + private readonly _manager: ManagerZKit, ) {} /** @@ -44,10 +43,7 @@ export class CircuitZKit { fs.mkdirSync(tempDir, { recursive: true }); - const overriddenOptions: CompileOptions = { - ...defaultCompileOptions, - ...options, - }; + const overriddenOptions: CompileOptions = { ...defaultCompileOptions, ...options }; await this._compile(overriddenOptions, tempDir); @@ -356,7 +352,7 @@ export class CircuitZKit { } /** - * Returns a new instance of `CircomRunner`. The `CircomRunner` is a wasm module that compiles the circuit. + * Returns a new instance of `CircomRunner`. The `CircomRunner` is used to compile the circuit. * * @param {string[]} args - The arguments to run the `circom` compiler. * @param {boolean} quiet - Whether to suppress the compilation error. diff --git a/src/core/ManagerZKit.ts b/src/core/ManagerZKit.ts new file mode 100644 index 0000000..30b47f1 --- /dev/null +++ b/src/core/ManagerZKit.ts @@ -0,0 +1,226 @@ +import fs from "fs"; +import os from "os"; +import path from "path"; +import process from "process"; +import * as readline from "readline"; +import { v4 as uuid } from "uuid"; + +import { ManagerZKitConfig, ManagerZKitPrivateConfig, defaultManagerOptions } from "../config/config"; +import { PtauInfo, TemplateType } from "../types/types"; +import { downloadFile, readDirRecursively } from "../utils/utils"; + +/** + * `ManagerZKit` provides configuration options and utility methods used by the `CircomZKit` and `CircuitZKit` classes. + */ +export class ManagerZKit { + private _config: ManagerZKitPrivateConfig; + + /** + * Creates a new `ManagerZKit` instance. + * + * @param {Partial} [config=defaultManagerOptions] - The configuration options to use. + */ + constructor(config: Partial = defaultManagerOptions) { + const overriddenConfig = { ...defaultManagerOptions, ...config } as ManagerZKitConfig; + + overriddenConfig.circuitsDir = path.join(process.cwd(), overriddenConfig.circuitsDir); + overriddenConfig.artifactsDir = path.join(process.cwd(), overriddenConfig.artifactsDir); + overriddenConfig.verifiersDir = path.join(process.cwd(), overriddenConfig.verifiersDir); + + if (overriddenConfig.ptauDir) { + overriddenConfig.ptauDir = path.join(process.cwd(), overriddenConfig.ptauDir); + } else { + overriddenConfig.ptauDir = path.join(os.homedir(), ".zkit", ".ptau"); + } + + this._config = { + ...overriddenConfig, + compiler: fs.readFileSync(require.resolve("@distributedlab/circom2/circom.wasm")), + templates: { + groth16: fs.readFileSync(path.join(__dirname, "templates", "verifier_groth16.sol.ejs"), "utf8"), + }, + }; + } + + /** + * Fetches the `ptau` file. + * + * @dev If `ptau` file is not found, this method will try to download it. Use `allowDownload=false` to disable this behavior. + * + * @param {number} minConstraints - The minimum number of constraints the `ptau` file must support. + * @returns {Promise} The path to the `ptau` file. + */ + public async fetchPtauFile(minConstraints: number): Promise { + const ptauId = Math.max(Math.ceil(Math.log2(minConstraints)), 8); + + if (ptauId > 20) { + throw new Error( + 'Circuit has too many constraints. The maximum number of constraints is 2^20. Consider passing "ptauDir=PATH_TO_LOCAL_DIR".', + ); + } + + const ptauInfo = this._searchPtau(ptauId); + + if (ptauInfo.url) { + await this._downloadPtau(ptauInfo); + } + + return ptauInfo.file; + } + + /** + * Returns the path to the artifacts' directory. + * + * @returns {string} The path to the artifacts' directory. + */ + public getArtifactsDir(): string { + return this._config.artifactsDir; + } + + /** + * Returns the path to the circuits' directory. + * + * @returns {string} The path to the circuits' directory. + */ + public getCircuitsDir(): string { + return this._config.circuitsDir; + } + + /** + * Returns the path to the verifiers' directory. + * + * @returns {string} The path to the verifiers' directory. + */ + public getVerifiersDir(): string { + return this._config.verifiersDir; + } + + /** + * Returns the path to the `ptau` directory. + * + * @dev The default `ptau` directory is located at `${HOME}/.zkit/.ptau`. + * + * @returns {string} The path to the `ptau` directory. + */ + public getPtauDir(): string { + return this._config.ptauDir; + } + + /** + * Returns a temporary directory path. + * + * @dev Temporary files are stored in the OS's temporary directory. + * + * @returns {string} A temporary directory path. + */ + public getTempDir(): string { + return path.join(os.tmpdir(), ".zkit", uuid()); + } + + /** + * Returns the circom compiler's wasm binary. + * + * @returns {string} The circom compiler's wasm binary. + */ + public getCompiler(): string { + return this._config.compiler; + } + + /** + * Returns the Solidity verifier template for the specified proving system. + * + * @param {TemplateType} templateType - The template type. + * @returns {string} The Solidity verifier template. + */ + public getTemplate(templateType: TemplateType): string { + switch (templateType) { + case "groth16": + return this._config.templates.groth16; + default: + throw new Error(`Ambiguous template type: ${templateType}.`); + } + } + + /** + * Returns whether the download of the `ptau` file is allowed. + * + * @returns {boolean} Whether the download of the `ptau` file is allowed. + */ + public getAllowDownload(): boolean { + return this._config.allowDownload; + } + + /** + * Downloads the `ptau` file. The download is allowed only if the user confirms it. + * + * @param {PtauInfo} ptauInfo - The `ptau` file and download url. + */ + private async _downloadPtau(ptauInfo: PtauInfo): Promise { + if (!this.getAllowDownload() && !(await this._askForDownloadAllowance(ptauInfo))) { + throw new Error('Download is cancelled. Allow download or consider passing "ptauDir=PATH_TO_LOCAL_DIR"'); + } + + fs.mkdirSync(this.getPtauDir(), { recursive: true }); + + if (!(await downloadFile(ptauInfo.file, ptauInfo.url!))) { + throw new Error("Something went wrong while downloading the ptau file."); + } + } + + /** + * Searches for the `ptau` file that supports the specified number of constraints. + * + * @param {number} ptauId - The `ptau` file id. + * @returns {PtauInfo} The `ptau` file path and download url if the file doesn't exist. + */ + private _searchPtau(ptauId: number): PtauInfo { + let foundPtauId = 0; + let foundFile = ""; + + readDirRecursively(this.getPtauDir(), (_dir: string, file: string) => { + const match = path.basename(file).match(/^powers-of-tau-(\d+)\.ptau$/); + + if (!match) { + return; + } + + const entryPtauId = parseInt(match[1]); + + if (entryPtauId >= ptauId && entryPtauId >= foundPtauId) { + foundPtauId = entryPtauId; + foundFile = file; + } + }); + + const file = foundPtauId == 0 ? path.join(this.getPtauDir(), `powers-of-tau-${ptauId}.ptau`) : foundFile; + const url = + foundPtauId == 0 + ? `https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_${ptauId.toString().padStart(2, "0")}.ptau` + : null; + + return { file, url }; + } + + /** + * Prompts the user to allow the download of the `ptau` file. + * + * @param {PtauInfo} ptauInfo - The `ptau` file and download url. + * @returns {Promise} Whether the download is allowed. + */ + private _askForDownloadAllowance(ptauInfo: PtauInfo): Promise { + return new Promise((resolve) => { + const readLine = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + readLine.question( + `No ptau found. Press [Y] to download it from "${ptauInfo.url!}" to ${ptauInfo.file}: `, + (response) => { + readLine.close(); + resolve(response.toUpperCase() == "Y"); + }, + ); + }); + } +} diff --git a/src/templates/verifier_groth16.sol.ejs b/src/core/templates/verifier_groth16.sol.ejs similarity index 100% rename from src/templates/verifier_groth16.sol.ejs rename to src/core/templates/verifier_groth16.sol.ejs diff --git a/src/index.ts b/src/index.ts index b38f5b8..a3197aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -export * from "./CircomZKit"; -export * from "./CircuitZKit"; -export * from "./ManagerZKit"; +export * from "./core/CircomZKit"; +export * from "./core/CircuitZKit"; +export * from "./core/ManagerZKit"; -export { NumericString, PublicSignals, Groth16Proof, Calldata, ProofStruct, Inputs, CircuitInfo } from "./types"; +export { NumericString, PublicSignals, Groth16Proof, Calldata, ProofStruct, Inputs, CircuitInfo } from "./types/types"; -export { CompileOptions, ManagerZKitConfig, defaultCompileOptions, defaultManagerOptions } from "./config"; +export { CompileOptions, ManagerZKitConfig, defaultCompileOptions, defaultManagerOptions } from "./config/config"; diff --git a/src/types.ts b/src/types/types.ts similarity index 93% rename from src/types.ts rename to src/types/types.ts index 2912eb4..4a3264a 100644 --- a/src/types.ts +++ b/src/types/types.ts @@ -36,3 +36,8 @@ export type CircuitInfo = { export type FileType = "r1cs" | "zkey" | "vkey" | "sym" | "json" | "wasm" | "sol"; export type DirType = "circuit" | "artifact" | "verifier"; export type TemplateType = "groth16"; + +export type PtauInfo = { + file: string; + url: string | null; +}; diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index cbc1e4d..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import fs from "fs"; -import path from "path"; - -/// @dev After Node.js v20 `recursive` flag can be passed to `fs.readdir` -export function readDirRecursively(dir: string, callback: (dir: string, file: string) => void): void { - if (!fs.existsSync(dir)) { - return; - } - - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - const entryPath = path.join(dir, entry.name); - - if (entry.isDirectory()) { - readDirRecursively(entryPath, callback); - } - - if (entry.isFile()) { - callback(dir, entryPath); - } - } -} diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..0925296 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,60 @@ +import fs from "fs"; +import path from "path"; +import https from "https"; + +/** + * Reads a directory recursively and calls the callback for each file. + * + * @dev After Node.js 20.0.0 the `recursive` option is available. + * + * @param {string} dir - The directory to read. + * @param {(dir: string, file: string) => void} callback - The callback function. + */ +export function readDirRecursively(dir: string, callback: (dir: string, file: string) => void): void { + if (!fs.existsSync(dir)) { + return; + } + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + readDirRecursively(entryPath, callback); + } + + if (entry.isFile()) { + callback(dir, entryPath); + } + } +} + +/** + * Downloads a file from the specified URL. + * + * @param {string} file - The path to save the file to. + * @param {string} url - The URL to download the file from. + * @returns {Promise} Whether the file was downloaded successfully. + */ +export async function downloadFile(file: string, url: string): Promise { + const fileStream = fs.createWriteStream(file); + + return new Promise((resolve, reject) => { + const request = https.get(url, (response) => { + response.pipe(fileStream); + }); + + fileStream.on("finish", () => resolve(true)); + + request.on("error", (err) => { + fs.unlink(file, () => reject(err)); + }); + + fileStream.on("error", (err) => { + fs.unlink(file, () => reject(err)); + }); + + request.end(); + }); +} diff --git a/test/CircomZKit.test.ts b/test/CircomZKit.test.ts index 80b1af7..c91302c 100644 --- a/test/CircomZKit.test.ts +++ b/test/CircomZKit.test.ts @@ -1,4 +1,4 @@ -import { CircomZKit, ManagerZKit } from "../src"; +import { CircomZKit } from "../src"; jest.mock("readline", () => ({ createInterface: jest.fn().mockReturnValue({ @@ -13,14 +13,14 @@ jest.mock("readline", () => ({ describe("happy flow", function () { test("happy flow", async () => { - const manager = new ManagerZKit({ + const circom = new CircomZKit({ circuitsDir: "test/circuits", artifactsDir: "test/zkit-artifacts", - verifiersDir: "test/verifiers" + verifiersDir: "test/verifiers", + ptauDir: "test/ptau", + allowDownload: false, }); - const circom = new CircomZKit(manager); - console.log(circom.getCircuits()); const circuit = circom.getCircuit("Addition"); diff --git a/tsconfig.json b/tsconfig.json index bc6b971..c16c188 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ "skipDefaultLibCheck": true, "skipLibCheck": true, "outDir": "dist", - "experimentalDecorators": true + "experimentalDecorators": true, }, "exclude": [ "dist", From 1a592822811c5a922c637c8bd253ca8e81194946 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 17:31:21 +0300 Subject: [PATCH 03/14] rollback version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9d501bc..ca0d25a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@solarity/zkit", - "version": "0.1.1", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/zkit", - "version": "0.1.1", + "version": "0.1.0", "license": "MIT", "dependencies": { "@distributedlab/circom2": "0.2.18-rc.2", diff --git a/package.json b/package.json index 37f2efc..06bd7c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/zkit", - "version": "0.1.1", + "version": "0.1.0", "license": "MIT", "author": "Distributed Lab", "readme": "README.md", From 6564915aa744ffe6593c48b3b1e1fe69ce456eca Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 17:55:00 +0300 Subject: [PATCH 04/14] shallow search & changed error msg --- src/core/ManagerZKit.ts | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/core/ManagerZKit.ts b/src/core/ManagerZKit.ts index 30b47f1..8b1c705 100644 --- a/src/core/ManagerZKit.ts +++ b/src/core/ManagerZKit.ts @@ -7,7 +7,7 @@ import { v4 as uuid } from "uuid"; import { ManagerZKitConfig, ManagerZKitPrivateConfig, defaultManagerOptions } from "../config/config"; import { PtauInfo, TemplateType } from "../types/types"; -import { downloadFile, readDirRecursively } from "../utils/utils"; +import { downloadFile } from "../utils/utils"; /** * `ManagerZKit` provides configuration options and utility methods used by the `CircomZKit` and `CircuitZKit` classes. @@ -157,7 +157,9 @@ export class ManagerZKit { */ private async _downloadPtau(ptauInfo: PtauInfo): Promise { if (!this.getAllowDownload() && !(await this._askForDownloadAllowance(ptauInfo))) { - throw new Error('Download is cancelled. Allow download or consider passing "ptauDir=PATH_TO_LOCAL_DIR"'); + throw new Error( + 'Download is cancelled. Allow download or consider passing "ptauDir=PATH_TO_LOCAL_DIR" to the existing ptau files', + ); } fs.mkdirSync(this.getPtauDir(), { recursive: true }); @@ -174,29 +176,32 @@ export class ManagerZKit { * @returns {PtauInfo} The `ptau` file path and download url if the file doesn't exist. */ private _searchPtau(ptauId: number): PtauInfo { - let foundPtauId = 0; - let foundFile = ""; + let entries = [] as fs.Dirent[]; - readDirRecursively(this.getPtauDir(), (_dir: string, file: string) => { - const match = path.basename(file).match(/^powers-of-tau-(\d+)\.ptau$/); + if (fs.existsSync(this.getPtauDir())) { + entries = fs.readdirSync(this.getPtauDir(), { withFileTypes: true }); + } + + const entry = entries.find((entry) => { + if (!entry.isFile()) { + return false; + } + + const match = entry.name.match(/^powers-of-tau-(\d+)\.ptau$/); if (!match) { - return; + return false; } const entryPtauId = parseInt(match[1]); - if (entryPtauId >= ptauId && entryPtauId >= foundPtauId) { - foundPtauId = entryPtauId; - foundFile = file; - } + return ptauId <= entryPtauId; }); - const file = foundPtauId == 0 ? path.join(this.getPtauDir(), `powers-of-tau-${ptauId}.ptau`) : foundFile; - const url = - foundPtauId == 0 - ? `https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_${ptauId.toString().padStart(2, "0")}.ptau` - : null; + const file = path.join(this.getPtauDir(), entry ? entry.name : `powers-of-tau-${ptauId}.ptau`); + const url = entry + ? null + : `https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_${ptauId.toString().padStart(2, "0")}.ptau`; return { file, url }; } From 1f6346b2e1b2760c85da144b31ad0eb952c6b964 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 18:20:53 +0300 Subject: [PATCH 05/14] added readme --- README.md | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 127 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9480b8a..387e543 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,134 @@ -# @solarity/zkit +[![npm](https://img.shields.io/npm/v/@solarity/zkit.svg)](https://www.npmjs.com/package/@solarity/zkit) -To install dependencies: +# ZKit + +This package provides a set of utilities to help you develop zk circuits using circom. + +## Installation + +To install the package, run: ```bash -npm install +npm install --save-dev @solarity/zkit ``` -To run tests: +## Usage -```bash -npm run test +### CircomZKit + +ZKit is a configless package, which means you don't need to provide any configuration to use it. + +Suppose you have the following circuit: + +```circom +pragma circom 2.1.6; + +template Multiplier() { + signal input a; + signal input b; + signal output out; + out <== a * b; +} + +component main = Multiplier(); +``` + +You can start work with it as follows: + +```typescript +import { CircomZKit } from "@solarity/zkit"; + +async function main() { + const zkit = new CircomZKit(); + + const multiplier = zkit.getCircuit("Multiplier"); + + /// Generates artifacts in the "./zkit-artifacts" directory + await multiplier.compile(); +} + +main() + .catch((err) => { + process.exit(1); + }); +``` + +By default, ZKit will look for the circuit file in the `./circuits` directory. You can change this by providing your custom directory: + +```typescript +new CircomZKit({ circuitsDir: "./my-circuits" }); ``` + +To generate zkey, the power-of-tau file is required. ZKit automatically downloads those files to the `${HOME}/.zkit/.ptau` so you don't need to re-download them every time you start a new project. + +You can provide a custom path to the directory where the power-of-tau files are stored: + +```typescript +new CircomZKit({ ptauDir: "./my-ptau" }); +``` + +> [!NOTE] +> Note that all the files in the `ptauDir` directory must have the `powers-of-tau-{x}.ptau` name format, where `{x}` is a maximum degree (2x) of constraints a circuit supports. + +ZKit may also ask you for the permission to download the power-of-tau files. You can enable this by providing the `allowDownload` option: + +```typescript +new CircomZKit({ allowDownload: false }); +``` + +### CircuitZKit + +Once you created a `CircuitZKit` instance using the `getCircuit` method, you can manage the underlying circuit using the following methods: + +#### compile + +Compiles the circuit and generates the artifacts in the `./zkit-artifacts` or in the provided `artifactsDir` directory. The default output is `r1cs`, `zkey` and `vkey` files. + +```typescript +await multiplier.compile(); +``` + +#### createVerifier + +Creates Solidity verifier contract in the `./contracts/verifiers` or in the provided `verifiersDir` directory. + +> [!NOTE] +> You should first compile the circuit before creating the verifier. + +```typescript +await multiplier.createVerifier(); +``` + +#### generateProof + +Generates a proof for the given inputs. + +> [!NOTE] +> You should first compile the circuit before generating the proof. + +```typescript +/// { proof: { pi_a, pi_b, pi_c, protocol, curve }, publicSignals: [6] } +const proof = await multiplier.createVerifier({ a: 2, b: 3}); +``` + +#### verifyProof + +Verifies the proof. + +```typescript +/// true +const isValidProof = await multiplier.verifyProof(proof); +``` + +#### generateCalldata + +Generates calldata by proof for the Solidity verifier's `verifyProof` method. + +```typescript +/// You can use this calldata to call the verifier contract +const calldata = await multiplier.verifyProof(proof); +``` + +## How it works + +ZKit is a wrapper over the `@distributedlab/circom2` compiler and `snarkjs` library. From be6796428b2ba37747559bde235d98ad35fab5c9 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 21:06:45 +0300 Subject: [PATCH 06/14] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 387e543..e229589 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ By default, ZKit will look for the circuit file in the `./circuits` directory. Y new CircomZKit({ circuitsDir: "./my-circuits" }); ``` -To generate zkey, the power-of-tau file is required. ZKit automatically downloads those files to the `${HOME}/.zkit/.ptau` so you don't need to re-download them every time you start a new project. +To generate zkey, the power-of-tau file is required. ZKit automatically downloads those files to the `${HOME}/.zkit/.ptau` directory, so you don't need to re-download them every time you start a new project. You can provide a custom path to the directory where the power-of-tau files are stored: From 51775f5c73cc3a1b3b41e5aecdc17c2350192643 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 21:10:39 +0300 Subject: [PATCH 07/14] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e229589..c830588 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ new CircomZKit({ ptauDir: "./my-ptau" }); ``` > [!NOTE] -> Note that all the files in the `ptauDir` directory must have the `powers-of-tau-{x}.ptau` name format, where `{x}` is a maximum degree (2x) of constraints a circuit supports. +> Note that all the files in the `ptauDir` directory must have the `powers-of-tau-{x}.ptau` name format, where `{x}` is a maximum degree (2x) of constraints a `ptau` supports. ZKit may also ask you for the permission to download the power-of-tau files. You can enable this by providing the `allowDownload` option: From 6fc25d32383e9309ac327c2845b6a8d5d101b293 Mon Sep 17 00:00:00 2001 From: dovgopoly <69435717+dovgopoly@users.noreply.github.com> Date: Tue, 14 May 2024 21:11:22 +0300 Subject: [PATCH 08/14] Update README.md Co-authored-by: Artem Chystiakov <47551140+Arvolear@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c830588..4819f93 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ main() }); ``` -By default, ZKit will look for the circuit file in the `./circuits` directory. You can change this by providing your custom directory: +By default, ZKit will look for the circuit file in the `./circuits` directory. However, you can change this by providing a custom one: ```typescript new CircomZKit({ circuitsDir: "./my-circuits" }); From 39e4347b98ec55585b73b49653f4f84eaa3a6cf4 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 21:13:29 +0300 Subject: [PATCH 09/14] hermes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4819f93..009c1d3 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ By default, ZKit will look for the circuit file in the `./circuits` directory. H new CircomZKit({ circuitsDir: "./my-circuits" }); ``` -To generate zkey, the power-of-tau file is required. ZKit automatically downloads those files to the `${HOME}/.zkit/.ptau` directory, so you don't need to re-download them every time you start a new project. +To generate zkey, the power-of-tau file is required. ZKit automatically downloads those files from [Hermes](https://hermez.s3-eu-west-1.amazonaws.com/) to the `${HOME}/.zkit/.ptau` directory, so you don't need to re-download them every time you start a new project. You can provide a custom path to the directory where the power-of-tau files are stored: From 15a74707a3be0a2c9d8a9bb12a0d9a88b9890fa0 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 21:14:56 +0300 Subject: [PATCH 10/14] typos --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 009c1d3..1594af5 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ new CircomZKit({ circuitsDir: "./my-circuits" }); To generate zkey, the power-of-tau file is required. ZKit automatically downloads those files from [Hermes](https://hermez.s3-eu-west-1.amazonaws.com/) to the `${HOME}/.zkit/.ptau` directory, so you don't need to re-download them every time you start a new project. -You can provide a custom path to the directory where the power-of-tau files are stored: +You can also provide a custom path to the directory where the power-of-tau files are stored: ```typescript new CircomZKit({ ptauDir: "./my-ptau" }); @@ -70,7 +70,7 @@ new CircomZKit({ ptauDir: "./my-ptau" }); > [!NOTE] > Note that all the files in the `ptauDir` directory must have the `powers-of-tau-{x}.ptau` name format, where `{x}` is a maximum degree (2x) of constraints a `ptau` supports. -ZKit may also ask you for the permission to download the power-of-tau files. You can enable this by providing the `allowDownload` option: +ZKit may also ask you for the permission to download the power-of-tau files. You can enable this by toggling off the `allowDownload` option: ```typescript new CircomZKit({ allowDownload: false }); @@ -80,7 +80,7 @@ new CircomZKit({ allowDownload: false }); Once you created a `CircuitZKit` instance using the `getCircuit` method, you can manage the underlying circuit using the following methods: -#### compile +#### compile() Compiles the circuit and generates the artifacts in the `./zkit-artifacts` or in the provided `artifactsDir` directory. The default output is `r1cs`, `zkey` and `vkey` files. @@ -88,7 +88,7 @@ Compiles the circuit and generates the artifacts in the `./zkit-artifacts` or in await multiplier.compile(); ``` -#### createVerifier +#### createVerifier() Creates Solidity verifier contract in the `./contracts/verifiers` or in the provided `verifiersDir` directory. @@ -99,7 +99,7 @@ Creates Solidity verifier contract in the `./contracts/verifiers` or in the pro await multiplier.createVerifier(); ``` -#### generateProof +#### generateProof() Generates a proof for the given inputs. @@ -111,7 +111,7 @@ Generates a proof for the given inputs. const proof = await multiplier.createVerifier({ a: 2, b: 3}); ``` -#### verifyProof +#### verifyProof() Verifies the proof. @@ -120,7 +120,7 @@ Verifies the proof. const isValidProof = await multiplier.verifyProof(proof); ``` -#### generateCalldata +#### generateCalldata() Generates calldata by proof for the Solidity verifier's `verifyProof` method. From e7c759706472c64f67cf6efa842b1fb87ce11635 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 21:20:31 +0300 Subject: [PATCH 11/14] known limitations --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1594af5..1f8a29e 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Generates calldata by proof for the Solidity verifier's `verifyProof` method. const calldata = await multiplier.verifyProof(proof); ``` -## How it works +## Known limitations -ZKit is a wrapper over the `@distributedlab/circom2` compiler and `snarkjs` library. +- Currently, ZKit supports only Groth16 proving system. +- The `compile` method may cause [issues](https://github.com/iden3/snarkjs/issues/494). \ No newline at end of file From d8d05b254e463d73b8ee8db6087c9acf86dd4f3e Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 21:20:53 +0300 Subject: [PATCH 12/14] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f8a29e..31e4800 100644 --- a/README.md +++ b/README.md @@ -131,5 +131,5 @@ const calldata = await multiplier.verifyProof(proof); ## Known limitations -- Currently, ZKit supports only Groth16 proving system. +- Currently, ZKit supports only the Groth16 proving system. - The `compile` method may cause [issues](https://github.com/iden3/snarkjs/issues/494). \ No newline at end of file From 2bc203e310983fd735eaec7c5af3921d632b0b90 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Tue, 14 May 2024 21:23:11 +0300 Subject: [PATCH 13/14] add limitation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 31e4800..8e69c02 100644 --- a/README.md +++ b/README.md @@ -132,4 +132,5 @@ const calldata = await multiplier.verifyProof(proof); ## Known limitations - Currently, ZKit supports only the Groth16 proving system. +- Zkey generation doesn't allow additional contributions. - The `compile` method may cause [issues](https://github.com/iden3/snarkjs/issues/494). \ No newline at end of file From 4918542d535d913e96a791f54395dc43be39b7c4 Mon Sep 17 00:00:00 2001 From: Artem Chystiakov Date: Tue, 14 May 2024 21:42:50 +0300 Subject: [PATCH 14/14] update readme --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8e69c02..9515744 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # ZKit -This package provides a set of utilities to help you develop zk circuits using circom. +A zero knowledge kit that helps you develop circuits using Circom. ## Installation @@ -14,6 +14,13 @@ npm install --save-dev @solarity/zkit ## Usage +ZKit is an S-tier Circom assistant: + +- Compile and interact with circuits without snarkjs hassle +- Generate and verify ZK proofs with a single line of code +- Render optimized Solidity verifiers +- Forget about native dependencies - everything is in TypeScript + ### CircomZKit ZKit is a configless package, which means you don't need to provide any configuration to use it. @@ -133,4 +140,4 @@ const calldata = await multiplier.verifyProof(proof); - Currently, ZKit supports only the Groth16 proving system. - Zkey generation doesn't allow additional contributions. -- The `compile` method may cause [issues](https://github.com/iden3/snarkjs/issues/494). \ No newline at end of file +- The `compile` method may cause [issues](https://github.com/iden3/snarkjs/issues/494).