From 7be972b0d0005ff9f67ce906b5023b7ab4f22436 Mon Sep 17 00:00:00 2001 From: Tanapon Suwankesawong Date: Wed, 23 Jul 2025 17:07:02 +0100 Subject: [PATCH 1/3] Add unique temporary witness path with UUID to avoid I/O concurrent conflicts --- src/core/CircuitZKit.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/core/CircuitZKit.ts b/src/core/CircuitZKit.ts index df6e65d..662f3ac 100644 --- a/src/core/CircuitZKit.ts +++ b/src/core/CircuitZKit.ts @@ -2,6 +2,7 @@ import fs from "fs"; import path from "path"; import * as snarkjs from "snarkjs"; import { createHash } from "crypto"; +import { v4 as uuidv4 } from "uuid"; import { ArtifactsFileType, @@ -71,10 +72,15 @@ export class CircuitZKit { * * @param {Signals} inputs - The inputs for the circuit. * @param {Record} [witnessOverrides] - Optional map of signal names to override their witness values. + * @param {string} witnessFilePath - The inputs for the witness file path. * @returns {Promise} The generated witness. */ - public async calculateWitness(inputs: Signals, witnessOverrides?: Record): Promise { - const wtnsFile = this.getTemporaryWitnessPath(); + public async calculateWitness( + inputs: Signals, + witnessOverrides?: Record, + witnessFilePath?: string, + ): Promise { + const wtnsFile = witnessFilePath ?? this.getTemporaryWitnessPath(); const wasmFile = this.mustGetArtifactsFilePath("wasm"); let signalIndexes: Record = {}; @@ -105,19 +111,21 @@ export class CircuitZKit { * * @param {Signals} inputs - The inputs for the circuit. * @param {Record} [witnessOverrides] - Optional map of signal names to override their witness values. + * @param {boolean} uniqueWitnessPath - The flag for unique witness path to avoid I/O concurrent conflicts. default - false * @returns {Promise>} The generated proof. */ public async generateProof( inputs: Signals, witnessOverrides?: Record, + uniqueWitnessPath: boolean = false, ): Promise> { const zKeyFile = this.mustGetArtifactsFilePath("zkey"); - const witnessFile = this.getTemporaryWitnessPath(); + const witnessFile = uniqueWitnessPath ? this.getTemporaryUniqueWitnessPath() : this.getTemporaryWitnessPath(); let proof: ProofStructByProtocol; try { - const witness = await this.calculateWitness(inputs, witnessOverrides); + const witness = await this.calculateWitness(inputs, witnessOverrides, witnessFile); if (witnessOverrides) { await writeWitnessFile(witnessFile, witness); @@ -209,6 +217,18 @@ export class CircuitZKit { return path.join(getTmpDir(), `${this.getCircuitName()}.wtns`); } + /** + * Returns the path to the temporary witness file with uniqueness. + * + * The file is stored in the system temporary directory and is named after the circuit with uniqueness. + * This file is used for intermediate witness generation and may be deleted after usage. + * + * @returns {string} The full path to the temporary `.wtns` file. + */ + public getTemporaryUniqueWitnessPath(): string { + return path.join(getTmpDir(), `${this.getCircuitName()}-${uuidv4()}.wtns`); + } + /** * Returns the path to the file of the given type inside artifacts directory. Throws an error if the file doesn't exist. * From 8273139982bc3ad9c67c3006953a976455e45963 Mon Sep 17 00:00:00 2001 From: Tanapon Suwankesawong Date: Mon, 28 Jul 2025 19:31:14 +0100 Subject: [PATCH 2/3] add timeout handling --- src/core/protocols/AbstractImplementer.ts | 15 +++++++++++++++ src/core/protocols/Groth16Implementer.ts | 15 +++++++++++---- src/core/protocols/PlonkImplementer.ts | 15 +++++++++++---- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/core/protocols/AbstractImplementer.ts b/src/core/protocols/AbstractImplementer.ts index 36d44ea..30f48bf 100644 --- a/src/core/protocols/AbstractImplementer.ts +++ b/src/core/protocols/AbstractImplementer.ts @@ -59,4 +59,19 @@ export abstract class AbstractProtocolImplementer i public getVKeyFileName(circuitName: string): string { return `${circuitName}.${this.getProvingSystemType()}.vkey.json`; } + + protected async withTimeout(promise: Promise, ms: number): Promise { + return new Promise((resolve, reject) => { + const id = setTimeout(() => reject(new Error("Operation timed out")), ms); + promise + .then((res) => { + clearTimeout(id); + resolve(res); + }) + .catch((err) => { + clearTimeout(id); + reject(err); + }); + }); + } } diff --git a/src/core/protocols/Groth16Implementer.ts b/src/core/protocols/Groth16Implementer.ts index e121b11..c6eb772 100644 --- a/src/core/protocols/Groth16Implementer.ts +++ b/src/core/protocols/Groth16Implementer.ts @@ -8,12 +8,19 @@ import { Groth16ProofStruct, Groth16CalldataStruct, ProvingSystemType } from ".. import { terminateCurve } from "../../utils"; export class Groth16Implementer extends AbstractProtocolImplementer<"groth16"> { - public async generateProof(zKeyFilePath: string, witnessFilePath: string): Promise { - const proof = await snarkjs.groth16.prove(zKeyFilePath, witnessFilePath); + public async generateProof( + zKeyFilePath: string, + witnessFilePath: string, + timeout: number = 30000, + ): Promise { + const generate = async () => { + const proof = await snarkjs.groth16.prove(zKeyFilePath, witnessFilePath); - await terminateCurve(); + await terminateCurve(); - return proof as Groth16ProofStruct; + return proof as Groth16ProofStruct; + }; + return await this.withTimeout(generate(), timeout); } public async verifyProof(proof: Groth16ProofStruct, vKeyFilePath: string): Promise { diff --git a/src/core/protocols/PlonkImplementer.ts b/src/core/protocols/PlonkImplementer.ts index 1fdbee9..0a6bed5 100644 --- a/src/core/protocols/PlonkImplementer.ts +++ b/src/core/protocols/PlonkImplementer.ts @@ -8,12 +8,19 @@ import { PlonkProofStruct, PlonkCalldataStruct, ProvingSystemType } from "../../ import { terminateCurve } from "../../utils"; export class PlonkImplementer extends AbstractProtocolImplementer<"plonk"> { - public async generateProof(zKeyFilePath: string, witnessFilePath: string): Promise { - const proof = await snarkjs.plonk.prove(zKeyFilePath, witnessFilePath); + public async generateProof( + zKeyFilePath: string, + witnessFilePath: string, + timeout: number = 30000, + ): Promise { + const generate = async () => { + const proof = await snarkjs.plonk.prove(zKeyFilePath, witnessFilePath); - await terminateCurve(); + await terminateCurve(); - return proof as PlonkProofStruct; + return proof as PlonkProofStruct; + }; + return await this.withTimeout(generate(), timeout); } public async verifyProof(proof: PlonkProofStruct, vKeyFilePath: string): Promise { From 9744820dd7038f4e5556e8d3d88f90f6330d751b Mon Sep 17 00:00:00 2001 From: Tanapon Suwankesawong Date: Sat, 20 Sep 2025 22:40:51 +0700 Subject: [PATCH 3/3] modified gitignore for package link --- .gitignore | 2 +- dist/constants.d.ts | 3 + dist/constants.d.ts.map | 1 + dist/constants.js | 6 + dist/constants.js.map | 1 + dist/core/CircuitZKit.d.ts | 147 ++++++++++ dist/core/CircuitZKit.d.ts.map | 1 + dist/core/CircuitZKit.js | 272 ++++++++++++++++++ dist/core/CircuitZKit.js.map | 1 + dist/core/protocols/AbstractImplementer.d.ts | 26 ++ .../protocols/AbstractImplementer.d.ts.map | 1 + dist/core/protocols/AbstractImplementer.js | 51 ++++ .../core/protocols/AbstractImplementer.js.map | 1 + dist/core/protocols/Groth16Implementer.d.ts | 9 + .../protocols/Groth16Implementer.d.ts.map | 1 + dist/core/protocols/Groth16Implementer.js | 75 +++++ dist/core/protocols/Groth16Implementer.js.map | 1 + dist/core/protocols/PlonkImplementer.d.ts | 9 + dist/core/protocols/PlonkImplementer.d.ts.map | 1 + dist/core/protocols/PlonkImplementer.js | 75 +++++ dist/core/protocols/PlonkImplementer.js.map | 1 + dist/core/protocols/index.d.ts | 4 + dist/core/protocols/index.d.ts.map | 1 + dist/core/protocols/index.js | 10 + dist/core/protocols/index.js.map | 1 + dist/index.d.ts | 5 + dist/index.d.ts.map | 1 + dist/index.js | 23 ++ dist/index.js.map | 1 + dist/types/circuit-zkit.d.ts | 8 + dist/types/circuit-zkit.d.ts.map | 1 + dist/types/circuit-zkit.js | 3 + dist/types/circuit-zkit.js.map | 1 + dist/types/index.d.ts | 5 + dist/types/index.d.ts.map | 1 + dist/types/index.js | 21 ++ dist/types/index.js.map | 1 + dist/types/proof-utils.d.ts | 7 + dist/types/proof-utils.d.ts.map | 1 + dist/types/proof-utils.js | 3 + dist/types/proof-utils.js.map | 1 + dist/types/protocols/groth16.d.ts | 22 ++ dist/types/protocols/groth16.d.ts.map | 1 + dist/types/protocols/groth16.js | 3 + dist/types/protocols/groth16.js.map | 1 + dist/types/protocols/index.d.ts | 34 +++ dist/types/protocols/index.d.ts.map | 1 + dist/types/protocols/index.js | 19 ++ dist/types/protocols/index.js.map | 1 + dist/types/protocols/plonk.d.ts | 32 +++ dist/types/protocols/plonk.d.ts.map | 1 + dist/types/protocols/plonk.js | 3 + dist/types/protocols/plonk.js.map | 1 + dist/types/witness-utils.d.ts | 8 + dist/types/witness-utils.d.ts.map | 1 + dist/types/witness-utils.js | 3 + dist/types/witness-utils.js.map | 1 + dist/utils/index.d.ts | 3 + dist/utils/index.d.ts.map | 1 + dist/utils/index.js | 19 ++ dist/utils/index.js.map | 1 + dist/utils/protocol-utils.d.ts | 13 + dist/utils/protocol-utils.d.ts.map | 1 + dist/utils/protocol-utils.js | 66 +++++ dist/utils/protocol-utils.js.map | 1 + dist/utils/witness-utils.d.ts | 55 ++++ dist/utils/witness-utils.d.ts.map | 1 + dist/utils/witness-utils.js | 160 +++++++++++ dist/utils/witness-utils.js.map | 1 + package-lock.json | 35 ++- package.json | 7 +- 71 files changed, 1269 insertions(+), 11 deletions(-) create mode 100644 dist/constants.d.ts create mode 100644 dist/constants.d.ts.map create mode 100644 dist/constants.js create mode 100644 dist/constants.js.map create mode 100644 dist/core/CircuitZKit.d.ts create mode 100644 dist/core/CircuitZKit.d.ts.map create mode 100644 dist/core/CircuitZKit.js create mode 100644 dist/core/CircuitZKit.js.map create mode 100644 dist/core/protocols/AbstractImplementer.d.ts create mode 100644 dist/core/protocols/AbstractImplementer.d.ts.map create mode 100644 dist/core/protocols/AbstractImplementer.js create mode 100644 dist/core/protocols/AbstractImplementer.js.map create mode 100644 dist/core/protocols/Groth16Implementer.d.ts create mode 100644 dist/core/protocols/Groth16Implementer.d.ts.map create mode 100644 dist/core/protocols/Groth16Implementer.js create mode 100644 dist/core/protocols/Groth16Implementer.js.map create mode 100644 dist/core/protocols/PlonkImplementer.d.ts create mode 100644 dist/core/protocols/PlonkImplementer.d.ts.map create mode 100644 dist/core/protocols/PlonkImplementer.js create mode 100644 dist/core/protocols/PlonkImplementer.js.map create mode 100644 dist/core/protocols/index.d.ts create mode 100644 dist/core/protocols/index.d.ts.map create mode 100644 dist/core/protocols/index.js create mode 100644 dist/core/protocols/index.js.map create mode 100644 dist/index.d.ts create mode 100644 dist/index.d.ts.map create mode 100644 dist/index.js create mode 100644 dist/index.js.map create mode 100644 dist/types/circuit-zkit.d.ts create mode 100644 dist/types/circuit-zkit.d.ts.map create mode 100644 dist/types/circuit-zkit.js create mode 100644 dist/types/circuit-zkit.js.map create mode 100644 dist/types/index.d.ts create mode 100644 dist/types/index.d.ts.map create mode 100644 dist/types/index.js create mode 100644 dist/types/index.js.map create mode 100644 dist/types/proof-utils.d.ts create mode 100644 dist/types/proof-utils.d.ts.map create mode 100644 dist/types/proof-utils.js create mode 100644 dist/types/proof-utils.js.map create mode 100644 dist/types/protocols/groth16.d.ts create mode 100644 dist/types/protocols/groth16.d.ts.map create mode 100644 dist/types/protocols/groth16.js create mode 100644 dist/types/protocols/groth16.js.map create mode 100644 dist/types/protocols/index.d.ts create mode 100644 dist/types/protocols/index.d.ts.map create mode 100644 dist/types/protocols/index.js create mode 100644 dist/types/protocols/index.js.map create mode 100644 dist/types/protocols/plonk.d.ts create mode 100644 dist/types/protocols/plonk.d.ts.map create mode 100644 dist/types/protocols/plonk.js create mode 100644 dist/types/protocols/plonk.js.map create mode 100644 dist/types/witness-utils.d.ts create mode 100644 dist/types/witness-utils.d.ts.map create mode 100644 dist/types/witness-utils.js create mode 100644 dist/types/witness-utils.js.map create mode 100644 dist/utils/index.d.ts create mode 100644 dist/utils/index.d.ts.map create mode 100644 dist/utils/index.js create mode 100644 dist/utils/index.js.map create mode 100644 dist/utils/protocol-utils.d.ts create mode 100644 dist/utils/protocol-utils.d.ts.map create mode 100644 dist/utils/protocol-utils.js create mode 100644 dist/utils/protocol-utils.js.map create mode 100644 dist/utils/witness-utils.d.ts create mode 100644 dist/utils/witness-utils.d.ts.map create mode 100644 dist/utils/witness-utils.js create mode 100644 dist/utils/witness-utils.js.map diff --git a/.gitignore b/.gitignore index 6117ff7..eb324ca 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ publish # Compilation output /build-test/ -/dist +# /dist .nyc_output diff --git a/dist/constants.d.ts b/dist/constants.d.ts new file mode 100644 index 0000000..5e31d73 --- /dev/null +++ b/dist/constants.d.ts @@ -0,0 +1,3 @@ +export declare const BN128_CURVE_NAME = "bn128"; +export declare const MAX_FILE_NAME_LENGTH = 255; +//# sourceMappingURL=constants.d.ts.map diff --git a/dist/constants.d.ts.map b/dist/constants.d.ts.map new file mode 100644 index 0000000..4f7ea03 --- /dev/null +++ b/dist/constants.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,eAAO,MAAM,oBAAoB,MAAM,CAAC"} \ No newline at end of file diff --git a/dist/constants.js b/dist/constants.js new file mode 100644 index 0000000..dcf9d4b --- /dev/null +++ b/dist/constants.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MAX_FILE_NAME_LENGTH = exports.BN128_CURVE_NAME = void 0; +exports.BN128_CURVE_NAME = "bn128"; +exports.MAX_FILE_NAME_LENGTH = 255; +//# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/dist/constants.js.map b/dist/constants.js.map new file mode 100644 index 0000000..0059e85 --- /dev/null +++ b/dist/constants.js.map @@ -0,0 +1 @@ +{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,gBAAgB,GAAG,OAAO,CAAC;AAE3B,QAAA,oBAAoB,GAAG,GAAG,CAAC"} \ No newline at end of file diff --git a/dist/core/CircuitZKit.d.ts b/dist/core/CircuitZKit.d.ts new file mode 100644 index 0000000..d93a729 --- /dev/null +++ b/dist/core/CircuitZKit.d.ts @@ -0,0 +1,147 @@ +import { + ArtifactsFileType, + CircuitZKitConfig, + VerifierLanguageType, + Signals, + CalldataByProtocol, + IProtocolImplementer, + ProofStructByProtocol, + ProvingSystemType, +} from "../types"; +/** + * `CircuitZKit` represents a single circuit and provides a high-level API to work with it. + */ +export declare class CircuitZKit { + private readonly _config; + private readonly _implementer; + constructor(_config: CircuitZKitConfig, _implementer: IProtocolImplementer); + /** + * Creates a verifier contract for the specified contract language with optional name suffix. + * For more details regarding the structure of the contract verifier name, see {@link getVerifierName} description. + * + * In case the length of the verifier filename exceeds the {@link MAX_FILE_NAME_LENGTH}, + * the `verifierNameSuffix` will be replaced by the first four bytes of its `sha1` hash. + * + * If no suffix was passed, but the verifier's filename still exceeds {@link MAX_FILE_NAME_LENGTH}, an error will be thrown. + * + * @param {VerifierLanguageType} languageExtension - The verifier contract language extension. + * @param {string} verifierNameSuffix - The optional verifier name suffix. + */ + createVerifier(languageExtension: VerifierLanguageType, verifierNameSuffix?: string): Promise; + /** + * Calculates a witness for the given inputs. + * + * If `witnessOverrides` are provided, the corresponding witness values will be substituted in the result. + * + * Signal names in `witnessOverrides` must be provided in their full form as represented in the `.sym` file, e.g., + * `main.signal`, `main.component.signal`, or `main.component.signal[n][m]`. + * + * @param {Signals} inputs - The inputs for the circuit. + * @param {Record} [witnessOverrides] - Optional map of signal names to override their witness values. + * @param {string} witnessFilePath - The inputs for the witness file path. + * @returns {Promise} The generated witness. + */ + calculateWitness( + inputs: Signals, + witnessOverrides?: Record, + witnessFilePath?: string, + ): Promise; + /** + * Generates a proof for the given inputs. + * + * @dev The `inputs` should be in the same order as the circuit expects them. + * + * If `witnessOverrides` are provided, the witness will be calculated from the inputs and overridden accordingly. + * Otherwise, a standard witness will be calculated and used. + * + * Signal names in `witnessOverrides` must be provided in their full form as represented in the `.sym` file, e.g., + * `main.signal`, `main.component.signal`, or `main.component.signal[n][m]`. + * + * @param {Signals} inputs - The inputs for the circuit. + * @param {Record} [witnessOverrides] - Optional map of signal names to override their witness values. + * @param {boolean} uniqueWitnessPath - The flag for unique witness path to avoid I/O concurrent conflicts. default - false + * @returns {Promise>} The generated proof. + */ + generateProof( + inputs: Signals, + witnessOverrides?: Record, + uniqueWitnessPath?: boolean, + ): Promise>; + /** + * 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 {ProofStructByProtocol} proof - The proof to verify. + * @returns {Promise} Whether the proof is valid. + */ + verifyProof(proof: ProofStructByProtocol): Promise; + /** + * Generates the calldata for the given proof. The calldata can be used to verify the proof on-chain. + * + * @param {ProofStructByProtocol} proof - The proof to generate calldata for. + * @returns {Promise>} - The generated calldata. + */ + generateCalldata(proof: ProofStructByProtocol): Promise>; + /** + * Returns the circuit name. The circuit name is the name of the circuit file without the extension. + * + * @returns {string} The circuit name. + */ + getCircuitName(): string; + /** + * Returns the verifier name. The verifier name has the next structure: + * `