diff --git a/package-lock.json b/package-lock.json index 19aec093..52d925b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@0xpolygonid/js-sdk", - "version": "1.43.0", + "version": "1.43.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@0xpolygonid/js-sdk", - "version": "1.43.0", + "version": "1.43.1", "license": "MIT or Apache-2.0", "dependencies": { "@iden3/onchain-non-merklized-issuer-base-abi": "0.0.3", diff --git a/package.json b/package.json index b2bae3c8..04947d98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@0xpolygonid/js-sdk", - "version": "1.43.0", + "version": "1.43.1", "description": "SDK to work with Polygon ID", "source": "./src/index.ts", "exports": { diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 19e1eaea..d33762bb 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -31,7 +31,7 @@ import { parseQueryMetadata, transformQueryValueToBigInts } from './common'; -import { IZKProver, NativeProver } from './provers/prover'; +import { IZKProver, NativeProver, ProverOptions } from './provers/prover'; import { Merklizer, Options, getDocumentLoader } from '@iden3/js-jsonld-merklization'; import { ZKProof } from '@iden3/js-jwz'; @@ -80,6 +80,7 @@ export type VerificationResultMetadata = { export type ProofServiceOptions = Options & { prover?: IZKProver; proofsCacheStorage?: IProofStorage; + defaultProverOptions?: ProverOptions; }; export interface ProofVerifyOpts { @@ -234,7 +235,7 @@ export class ProofService implements IProofService { private readonly _stateStorage: IStateStorage, opts?: ProofServiceOptions ) { - this._prover = opts?.prover ?? new NativeProver(_circuitStorage); + this._prover = opts?.prover ?? new NativeProver(_circuitStorage, opts?.defaultProverOptions); this._ldOptions = { ...opts, documentLoader: opts?.documentLoader ?? cacheLoader(opts) }; this._inputsGenerator = new InputGenerator(_identityWallet, _credentialWallet, _stateStorage); this._pubSignalsVerifier = new PubSignalsVerifier( diff --git a/src/proof/provers/prover.ts b/src/proof/provers/prover.ts index c6bc91e1..7da6db46 100644 --- a/src/proof/provers/prover.ts +++ b/src/proof/provers/prover.ts @@ -31,6 +31,21 @@ export interface IZKProver { verify(zkp: ZKProof, circuitId: string): Promise; } +/** + * Options for NativeProver + * @public + * @interface ProverOptions + */ +export interface ProverOptions { + /** + * Maximum number of proofs that can be generated in parallel. + * + * If not set or set to a non-positive value, no concurrency + * limiting is applied. + */ + maxParallelProofs?: number; +} + /** * NativeProver service responsible for zk generation and verification of groth16 algorithm with bn128 curve * @public @@ -39,7 +54,13 @@ export interface IZKProver { */ export class NativeProver implements IZKProver { private static readonly curveName = 'bn128'; - constructor(private readonly _circuitStorage: ICircuitStorage) {} + private readonly _maxParallelProofs?: number; + private _activeProofs = 0; + private _queue: Array<() => void> = []; + + constructor(private readonly _circuitStorage: ICircuitStorage, options?: ProverOptions) { + this._maxParallelProofs = options?.maxParallelProofs; + } /** * verifies zero knowledge proof @@ -77,32 +98,72 @@ export class NativeProver implements IZKProver { const circuitData = await this._circuitStorage.loadCircuitData(circuitId, { mode: CircuitLoadMode.Proving }); - if (!circuitData.wasm) { - throw new Error(`wasm file doesn't exist for circuit ${circuitId}`); - } + return this.withConcurrencyLimit(async () => { + if (!circuitData.wasm) { + throw new Error(`wasm file doesn't exist for circuit ${circuitId}`); + } - const witnessCalculator = await witnessBuilder(circuitData.wasm.buffer as ArrayBuffer); + const witnessCalculator = await witnessBuilder(circuitData.wasm.buffer as ArrayBuffer); - const parsedData = JSON.parse(byteDecoder.decode(inputs)); + const parsedData = JSON.parse(byteDecoder.decode(inputs)); - const wtnsBytes: Uint8Array = await witnessCalculator.calculateWTNSBin(parsedData, 0); + const wtnsBytes: Uint8Array = await witnessCalculator.calculateWTNSBin(parsedData, 0); - if (!circuitData.provingKey) { - throw new Error(`proving file doesn't exist for circuit ${circuitId}`); - } - const { proof, publicSignals } = await snarkjs.groth16.prove(circuitData.provingKey, wtnsBytes); + if (!circuitData.provingKey) { + throw new Error(`proving file doesn't exist for circuit ${circuitId}`); + } + const { proof, publicSignals } = await snarkjs.groth16.prove( + circuitData.provingKey, + wtnsBytes + ); - // we need to terminate curve manually - await this.terminateCurve(); + // we need to terminate curve manually + await this.terminateCurve(); - return { - proof, - pub_signals: publicSignals - }; + return { + proof, + pub_signals: publicSignals + }; + }); } private async terminateCurve(): Promise { const curve = await ffjavascript.getCurveFromName(NativeProver.curveName); curve.terminate(); } + + private async withConcurrencyLimit(fn: () => Promise): Promise { + if (!this._maxParallelProofs || this._maxParallelProofs <= 0) { + return fn(); + } + + await this.acquireSlot(); + try { + return await fn(); + } finally { + this.releaseSlot(); + } + } + + private async acquireSlot(): Promise { + if (this._activeProofs < (this._maxParallelProofs as number)) { + this._activeProofs++; + return; + } + + return new Promise((resolve) => { + this._queue.push(() => { + this._activeProofs++; + resolve(); + }); + }); + } + + private releaseSlot(): void { + this._activeProofs--; + const next = this._queue.shift(); + if (next) { + next(); + } + } } diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index cb7bc9ed..dbdc2b60 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -2128,7 +2128,12 @@ describe('auth', () => { credWallet = new CredentialWallet(dataStorage, resolvers); idWallet = new IdentityWallet(kms, dataStorage, credWallet); - proofService = new ProofService(idWallet, credWallet, circuitStorage, eth, merklizeOpts); + proofService = new ProofService(idWallet, credWallet, circuitStorage, eth, { + ...merklizeOpts, + proverOptions: { + maxParallelProofs: 1 + } + }); const { did: issuerDID } = await createIdentity(idWallet, { seed: getRandomBytes(32) });