From 25051996e49b785d2aa1d8b1b8ac0ee66f7dc05b Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 14 May 2024 17:35:10 +0100 Subject: [PATCH] refactor(utils)!: make snark-artifacts utility functions dynamic (#275) * refactor(utils)!: make snark-artifacts utility functions dynamic re #272 * refactor(utils): use promise.all to fetch artifacts concurrently re #272 * refactor(utils): add more type checks to versions re #272 * refactor(utils): add list of supported snark-artifacts projects re #272 * refactor(utils): update project type re #272 --- jest.config.ts | 2 +- packages/eddsa-proof/src/generate.ts | 6 +- packages/eddsa-proof/src/verify.ts | 2 +- packages/poseidon-proof/src/generate.ts | 6 +- packages/poseidon-proof/src/verify.ts | 2 +- packages/poseidon-proof/tests/index.test.ts | 69 +--- packages/utils/package.json | 9 + packages/utils/src/index.ts | 16 +- packages/utils/src/snark-artifacts/config.ts | 87 ----- .../utils/src/snark-artifacts/projects.ts | 9 + .../snark-artifacts.browser.ts | 45 +-- .../snark-artifacts/snark-artifacts.node.ts | 93 +---- packages/utils/src/types/index.ts | 40 +- packages/utils/tests/snark-artifacts.test.ts | 363 +++--------------- 14 files changed, 158 insertions(+), 591 deletions(-) delete mode 100644 packages/utils/src/snark-artifacts/config.ts create mode 100644 packages/utils/src/snark-artifacts/projects.ts diff --git a/jest.config.ts b/jest.config.ts index 8388111bc..99d2a2194 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -12,7 +12,7 @@ const projects: any = fs rootDir: `packages/${name}`, displayName: name, moduleNameMapper: { - "@zk-kit/(.*)/(.*)": "/../$1/src/$2.ts", + "@zk-kit/(.*)/(.*)": ["/../$1/src/$2.ts", "/../$1/src/$2/$2.node.ts"], "@zk-kit/(.*)": "/../$1/src/index.ts" } })) diff --git a/packages/eddsa-proof/src/generate.ts b/packages/eddsa-proof/src/generate.ts index ba2b39409..b40c488bd 100644 --- a/packages/eddsa-proof/src/generate.ts +++ b/packages/eddsa-proof/src/generate.ts @@ -1,5 +1,7 @@ import { deriveSecretScalar } from "@zk-kit/eddsa-poseidon" -import { SnarkArtifacts, maybeGetEdDSASnarkArtifacts, packGroth16Proof } from "@zk-kit/utils" +import type { SnarkArtifacts } from "@zk-kit/utils" +import { packGroth16Proof } from "@zk-kit/utils/proof-packing" +import maybeGetSnarkArtifacts, { Project } from "@zk-kit/utils/snark-artifacts" import type { BigNumberish } from "ethers" import { NumericString, groth16 } from "snarkjs" import hash from "./hash" @@ -30,7 +32,7 @@ export default async function generate( // allow user to override our artifacts // otherwise they'll be downloaded if not already in local tmp folder - snarkArtifacts ??= await maybeGetEdDSASnarkArtifacts() + snarkArtifacts ??= await maybeGetSnarkArtifacts(Project.EDDSA) const { wasm, zkey } = snarkArtifacts const secretScalar = deriveSecretScalar(privateKey) diff --git a/packages/eddsa-proof/src/verify.ts b/packages/eddsa-proof/src/verify.ts index f5f2b448e..c27f86c24 100644 --- a/packages/eddsa-proof/src/verify.ts +++ b/packages/eddsa-proof/src/verify.ts @@ -1,5 +1,5 @@ +import { unpackGroth16Proof } from "@zk-kit/utils/proof-packing" import { groth16 } from "snarkjs" -import { unpackGroth16Proof } from "@zk-kit/utils" import hash from "./hash" import { EddsaProof } from "./types" import verificationKey from "./verification-key.json" diff --git a/packages/poseidon-proof/src/generate.ts b/packages/poseidon-proof/src/generate.ts index 2a2727311..cdaa56be9 100644 --- a/packages/poseidon-proof/src/generate.ts +++ b/packages/poseidon-proof/src/generate.ts @@ -1,5 +1,7 @@ import { BigNumber } from "@ethersproject/bignumber" -import { SnarkArtifacts, maybeGetPoseidonSnarkArtifacts, packGroth16Proof } from "@zk-kit/utils" +import type { SnarkArtifacts } from "@zk-kit/utils" +import { packGroth16Proof } from "@zk-kit/utils/proof-packing" +import maybeGetSnarkArtifacts, { Project } from "@zk-kit/utils/snark-artifacts" import { BigNumberish } from "ethers" import { NumericString, groth16 } from "snarkjs" import hash from "./hash" @@ -30,7 +32,7 @@ export default async function generate( // allow user to override our artifacts // otherwise they'll be downloaded if not already in local tmp folder - snarkArtifacts ??= await maybeGetPoseidonSnarkArtifacts(preimages.length) + snarkArtifacts ??= await maybeGetSnarkArtifacts(Project.POSEIDON, { parameters: [preimages.length] }) const { wasm, zkey } = snarkArtifacts const { proof, publicSignals } = await groth16.fullProve( diff --git a/packages/poseidon-proof/src/verify.ts b/packages/poseidon-proof/src/verify.ts index d85e19a20..72a873918 100644 --- a/packages/poseidon-proof/src/verify.ts +++ b/packages/poseidon-proof/src/verify.ts @@ -1,5 +1,5 @@ import { groth16 } from "snarkjs" -import { unpackGroth16Proof } from "@zk-kit/utils" +import { unpackGroth16Proof } from "@zk-kit/utils/proof-packing" import hash from "./hash" import { PoseidonProof } from "./types" import verificationKeys from "./verification-keys.json" diff --git a/packages/poseidon-proof/tests/index.test.ts b/packages/poseidon-proof/tests/index.test.ts index c11cd0716..5eb1e1bb5 100644 --- a/packages/poseidon-proof/tests/index.test.ts +++ b/packages/poseidon-proof/tests/index.test.ts @@ -1,63 +1,23 @@ import { buildBn128 } from "@zk-kit/groth16" import { decodeBytes32String, toBeHex } from "ethers" -import { - poseidon1, - poseidon10, - poseidon11, - poseidon12, - poseidon13, - poseidon14, - poseidon15, - poseidon16, - poseidon2, - poseidon3, - poseidon4, - poseidon5, - poseidon6, - poseidon7, - poseidon8, - poseidon9 -} from "poseidon-lite" +import { poseidon2 } from "poseidon-lite" import generate from "../src/generate" import hash from "../src/hash" import { PoseidonProof } from "../src/types" import verify from "../src/verify" -const poseidonFunctions = [ - poseidon1, - poseidon2, - poseidon3, - poseidon4, - poseidon5, - poseidon6, - poseidon7, - poseidon8, - poseidon9, - poseidon10, - poseidon11, - poseidon12, - poseidon13, - poseidon14, - poseidon15, - poseidon16 -] - -const computePoseidon = (preimages: string[]) => poseidonFunctions[preimages.length - 1](preimages) - -const preimages = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] const scope = "scope" let curve: any -const proofs: Array<{ fullProof: PoseidonProof; digest: bigint }> = [] +let digest: bigint +let fullProof: PoseidonProof beforeAll(async () => { curve = await buildBn128() - for (const preimage of preimages) { - const currentPreimages = preimages.slice(0, preimage) - const fullProof = await generate(currentPreimages, scope) - const digest = computePoseidon(currentPreimages.map((preimage) => hash(preimage))) - proofs.push({ fullProof, digest }) - } -}, 180_000) + + fullProof = await generate([1, 2], scope) + + digest = poseidon2([hash(1), hash(2)]) +}, 20_000) afterAll(async () => { await curve.terminate() @@ -65,21 +25,16 @@ afterAll(async () => { describe("PoseidonProof", () => { it("should generate a Poseidon proof from 1 to 16 preimages", async () => { - for (const { fullProof, digest } of proofs) { - expect(fullProof.proof).toHaveLength(8) - expect(decodeBytes32String(toBeHex(fullProof.scope, 32))).toBe(scope.toString()) - expect(fullProof.digest).toBe(digest.toString()) - } + expect(fullProof.proof).toHaveLength(8) + expect(decodeBytes32String(toBeHex(fullProof.scope, 32))).toBe(scope.toString()) + expect(fullProof.digest).toBe(digest.toString()) }) it("Should verify a Poseidon proof from 1 to 16 preimage(s)", async () => { - proofs.forEach(async ({ fullProof }) => { - await expect(verify(fullProof)).resolves.toBe(true) - }) + await expect(verify(fullProof)).resolves.toBe(true) }) it("Should verify an invalid Poseidon proof", async () => { - const { fullProof } = proofs[0] fullProof.digest = "3" const response = await verify(fullProof) diff --git a/packages/utils/package.json b/packages/utils/package.json index a32e5d8ff..2ca5f3ebb 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -48,6 +48,15 @@ "types": "./dist/types/f1-field.d.ts", "require": "./dist/lib.commonjs/f1-field.cjs", "default": "./dist/lib.esm/f1-field.js" + }, + "./snark-artifacts": { + "types": "./dist/types/snark-artifacts/snark-artifacts.browser.d.ts", + "node": { + "require": "./dist/lib.commonjs/snark-artifacts/snark-artifacts.node.cjs", + "default": "./dist/lib.esm/snark-artifacts/snark-artifacts.node.js" + }, + "browser": "./dist/lib.esm/snark-artifacts/snark-artifacts.browser.js", + "default": "./dist/lib.esm/snark-artifacts/snark-artifacts.browser.js" } }, "files": [ diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index e2406354d..0a0ca8979 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,15 +1,15 @@ -import F1Field from "./f1-field" -import * as scalar from "./scalar" import * as conversions from "./conversions" +import * as crypto from "./crypto/crypto.node" +import * as errorHandlers from "./error-handlers" +import F1Field from "./f1-field" import * as packing from "./proof-packing" +import * as scalar from "./scalar" +import maybeGetSnarkArtifacts from "./snark-artifacts/snark-artifacts.node" import * as typeChecks from "./type-checks" -import * as errorHandlers from "./error-handlers" -import * as crypto from "./crypto/crypto.node" -export { F1Field, scalar, conversions, packing, typeChecks, errorHandlers, crypto } -export * from "./types" export * from "./conversions" +export * from "./error-handlers" export * from "./proof-packing" export * from "./type-checks" -export * from "./error-handlers" -export * from "./snark-artifacts/snark-artifacts.node" +export * from "./types" +export { F1Field, conversions, crypto, errorHandlers, maybeGetSnarkArtifacts, packing, scalar, typeChecks } diff --git a/packages/utils/src/snark-artifacts/config.ts b/packages/utils/src/snark-artifacts/config.ts deleted file mode 100644 index 9d8c60fe0..000000000 --- a/packages/utils/src/snark-artifacts/config.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Artifact, Proof, SnarkArtifacts, Version } from "../types" - -const ARTIFACTS_BASE_URL = "https://unpkg.com/@zk-kit" - -const getPackageVersions = async (proof: Proof) => - fetch(`${ARTIFACTS_BASE_URL}/${proof}-artifacts`) - .then((res) => res.json()) - .then((data) => Object.keys(data.versions)) - -export async function GetSnarkArtifactUrls({ - proof, - version -}: { - proof: Proof.EDDSA - version?: Version -}): Promise -export async function GetSnarkArtifactUrls({ - proof, - numberOfInputs, - version -}: { - proof: Proof.POSEIDON - numberOfInputs: number - version?: Version -}): Promise -export async function GetSnarkArtifactUrls({ - proof, - treeDepth, - version -}: { - proof: Proof.SEMAPHORE - treeDepth: number - version?: Version -}): Promise -export async function GetSnarkArtifactUrls({ - proof, - numberOfInputs, - treeDepth, - version -}: { - proof: Proof - numberOfInputs?: number - treeDepth?: number - version?: Version -}) { - if (version !== undefined) { - const availableVersions = await getPackageVersions(proof) - - if (!availableVersions.includes(version)) - throw new Error( - `Version ${version} is not available for ${proof} proofs, available versions are: ${availableVersions}` - ) - } else { - version ??= "latest" - } - - const BASE_URL = `https://unpkg.com/@zk-kit/${proof}-artifacts@${version}` - - switch (proof) { - case Proof.EDDSA: - return { - [Artifact.WASM]: `${BASE_URL}/${proof}.${Artifact.WASM}`, - [Artifact.ZKEY]: `${BASE_URL}/${proof}.${Artifact.ZKEY}` - } - - case Proof.POSEIDON: - if (numberOfInputs === undefined) throw new Error("numberOfInputs is required for Poseidon proof") - if (numberOfInputs < 1) throw new Error("numberOfInputs must be greater than 0") - - return { - [Artifact.WASM]: `${BASE_URL}/${proof}-${numberOfInputs}.${Artifact.WASM}`, - [Artifact.ZKEY]: `${BASE_URL}/${proof}-${numberOfInputs}.${Artifact.ZKEY}` - } - - case Proof.SEMAPHORE: - if (treeDepth === undefined) throw new Error("treeDepth is required for Semaphore proof") - if (treeDepth < 1) throw new Error("treeDepth must be greater than 0") - - return { - [Artifact.WASM]: `${BASE_URL}/${proof}-${treeDepth}.${Artifact.WASM}`, - [Artifact.ZKEY]: `${BASE_URL}/${proof}-${treeDepth}.${Artifact.ZKEY}` - } - - default: - throw new Error("Unknown proof type") - } -} diff --git a/packages/utils/src/snark-artifacts/projects.ts b/packages/utils/src/snark-artifacts/projects.ts new file mode 100644 index 000000000..b2ee7e720 --- /dev/null +++ b/packages/utils/src/snark-artifacts/projects.ts @@ -0,0 +1,9 @@ +enum Project { + EDDSA = "eddsa", + POSEIDON = "poseidon", + SEMAPHORE = "semaphore" +} + +export const projects = Object.values(Project) + +export default Project diff --git a/packages/utils/src/snark-artifacts/snark-artifacts.browser.ts b/packages/utils/src/snark-artifacts/snark-artifacts.browser.ts index 66b858149..6eb1bfb62 100644 --- a/packages/utils/src/snark-artifacts/snark-artifacts.browser.ts +++ b/packages/utils/src/snark-artifacts/snark-artifacts.browser.ts @@ -1,31 +1,28 @@ -import { GetSnarkArtifactUrls } from "./config" -import { Proof, SnarkArtifacts, Version } from "../types" +import { BigNumber, SnarkArtifacts, Version } from "../types" +import Project, { projects } from "./projects" -function MaybeGetSnarkArtifacts(proof: Proof.EDDSA, version?: Version): () => Promise -function MaybeGetSnarkArtifacts( - proof: Proof.POSEIDON, - version?: Version -): (numberOfInputs: number) => Promise -function MaybeGetSnarkArtifacts( - proof: Proof.SEMAPHORE, - version?: Version -): (treeDepth: number) => Promise -function MaybeGetSnarkArtifacts(proof: Proof, version?: Version) { - switch (proof) { - case Proof.POSEIDON: - return async (numberOfInputs: number) => GetSnarkArtifactUrls({ proof, numberOfInputs, version }) +export default async function maybeGetSnarkArtifacts( + project: Project, + options: { + parameters?: (BigNumber | number)[] + version?: Version + cdnUrl?: string + } = {} +): Promise { + if (!projects.includes(project)) { + throw new Error(`Project '${project}' is not supported`) + } - case Proof.SEMAPHORE: - return async (treeDepth: number) => GetSnarkArtifactUrls({ proof, treeDepth, version }) + options.version ??= "latest" + options.cdnUrl ??= "https://unpkg.com" - case Proof.EDDSA: - return async () => GetSnarkArtifactUrls({ proof, version }) + const BASE_URL = `${options.cdnUrl}/@zk-kit/${project}-artifacts@${options.version}` + const parameters = options.parameters ? `-${options.parameters.join("-")}` : "" - default: - throw new Error("Unknown proof type") + return { + wasm: `${BASE_URL}/${project}${parameters}.wasm`, + zkey: `${BASE_URL}/${project}${parameters}.zkey` } } -export const maybeGetPoseidonSnarkArtifacts = MaybeGetSnarkArtifacts(Proof.POSEIDON) -export const maybeGetEdDSASnarkArtifacts = MaybeGetSnarkArtifacts(Proof.EDDSA) -export const maybeGetSemaphoreSnarkArtifacts = MaybeGetSnarkArtifacts(Proof.SEMAPHORE) +export { Project, projects } diff --git a/packages/utils/src/snark-artifacts/snark-artifacts.node.ts b/packages/utils/src/snark-artifacts/snark-artifacts.node.ts index 8f02115ce..2845cb612 100644 --- a/packages/utils/src/snark-artifacts/snark-artifacts.node.ts +++ b/packages/utils/src/snark-artifacts/snark-artifacts.node.ts @@ -1,9 +1,9 @@ import { createWriteStream, existsSync } from "node:fs" import { mkdir } from "node:fs/promises" -import { dirname } from "node:path" import os from "node:os" -import { SnarkArtifacts, Proof, Artifact, Version } from "../types" -import { GetSnarkArtifactUrls } from "./config" +import { dirname } from "node:path" +import { SnarkArtifacts } from "../types" +import _maybeGetSnarkArtifacts, { Project, projects } from "./snark-artifacts.browser" async function download(url: string, outputPath: string) { const response = await fetch(url) @@ -47,84 +47,27 @@ async function maybeDownload(url: string) { return outputPath } -async function maybeGetSnarkArtifact({ - artifact, - url -}: { - artifact: Artifact - url: string -}): Promise> { - const outputPath = await maybeDownload(url) - return { [artifact]: outputPath } -} - -const maybeGetSnarkArtifacts = async (urls: SnarkArtifacts) => - Promise.all( - Object.entries(urls).map(([artifact, url]) => maybeGetSnarkArtifact({ artifact: artifact as Artifact, url })) - ).then((artifacts) => - artifacts.reduce((acc, artifact) => ({ ...acc, ...artifact }), {} as SnarkArtifacts) - ) - -function MaybeGetSnarkArtifacts(proof: Proof.EDDSA, version?: Version): () => Promise -function MaybeGetSnarkArtifacts( - proof: Proof.POSEIDON, - version?: Version -): (numberOfInputs: number) => Promise -function MaybeGetSnarkArtifacts(proof: Proof.SEMAPHORE): (treeDepth: number) => Promise -function MaybeGetSnarkArtifacts(proof: Proof, version?: Version) { - switch (proof) { - case Proof.POSEIDON: - return async (numberOfInputs: number) => - GetSnarkArtifactUrls({ proof, numberOfInputs, version }).then(maybeGetSnarkArtifacts) - case Proof.SEMAPHORE: - return async (treeDepth: number) => - GetSnarkArtifactUrls({ proof, treeDepth, version }).then(maybeGetSnarkArtifacts) - - case Proof.EDDSA: - return async () => GetSnarkArtifactUrls({ proof, version }).then(maybeGetSnarkArtifacts) - - default: - throw new Error("Unknown proof type") - } -} - /** - * Downloads {@link @zk-kit/eddsa-proof!generate | EdDSA} snark artifacts (`wasm` and `zkey`) files if not already present in OS tmp folder. - * @example - * ``` - * { - * wasm: "/tmm/@zk-kit/eddsa-artifacts@latest/eddsa.wasm", - * zkey: "/tmp/@zk-kit/eddsa-artifacts@latest/eddsa.zkey" - * } - * ``` - * @returns {@link SnarkArtifacts} - */ -export const maybeGetEdDSASnarkArtifacts = MaybeGetSnarkArtifacts(Proof.EDDSA) - -/** - * Downloads {@link @zk-kit/poseidon-proof!generate | Poseidon} snark artifacts (`wasm` and `zkey`) files if not already present in OS tmp folder. - * @param numberOfInputs - The number of inputs to hash + * Downloads SNARK artifacts (`wasm` and `zkey`) files if not already present in OS tmp folder. * @example * ```ts * { - * wasm: "/tmm/@zk-kit/poseidon-artifacts@latest/poseidon-2.wasm", - * zkey: "/tmp/@zk-kit/poseidon-artifacts@latest/poseidon-2.zkey" - * } - * ``` - * @returns {@link SnarkArtifacts} - */ -export const maybeGetPoseidonSnarkArtifacts = MaybeGetSnarkArtifacts(Proof.POSEIDON) - -/** - * Downloads {@link https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof | Semaphore} snark artifacts (`wasm` and `zkey`) files if not already present in OS tmp folder. - * @param treeDepth - The depth of the tree - * @example - * ```ts - * { - * wasm: "/tmm/@zk-kit/semaphore-artifacts@latest/semaphore-3.wasm", + * wasm: "/tmp/@zk-kit/semaphore-artifacts@latest/semaphore-3.wasm", * zkey: "/tmp/@zk-kit/semaphore-artifacts@latest/semaphore-3.zkey" * } * ``` * @returns {@link SnarkArtifacts} */ -export const maybeGetSemaphoreSnarkArtifacts = MaybeGetSnarkArtifacts(Proof.SEMAPHORE) +export default async function maybeGetSnarkArtifacts( + ...pars: Parameters +): Promise { + const { wasm: wasmUrl, zkey: zkeyUrl } = await _maybeGetSnarkArtifacts(...pars) + const [wasm, zkey] = await Promise.all([maybeDownload(wasmUrl), maybeDownload(zkeyUrl)]) + + return { + wasm, + zkey + } +} + +export { Project, projects } diff --git a/packages/utils/src/types/index.ts b/packages/utils/src/types/index.ts index d5cdd3034..879843a39 100644 --- a/packages/utils/src/types/index.ts +++ b/packages/utils/src/types/index.ts @@ -17,41 +17,25 @@ export type PackedGroth16Proof = [ ] /** - * Supported proof types: - * {@link @zk-kit/eddsa-proof!generate | EdDSA} - * {@link @zk-kit/poseidon-proof!generate | Poseidon} - * {@link https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof | Semaphore}. - * @enum - */ -export enum Proof { - EDDSA = "eddsa", - POSEIDON = "poseidon", - SEMAPHORE = "semaphore" -} - -/** - * Circom Snark Artifact file extensions. - * @enum - */ -export enum Artifact { - WASM = "wasm", - ZKEY = "zkey" -} - -/** - * @prop Artifact.WASM - * @prop Artifact.ZKEY + * @prop SnarkArtifacts.wasm + * @prop SnarkArtifacts.zkey * @interface */ -export type SnarkArtifacts = Record +export type SnarkArtifacts = Record<"wasm" | "zkey", string> -type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" +type Digit = `${number}` +type PreRelease = "alpha" | "beta" /** * Semantic version. * @example - * 1.0.0 + * 1.0.0-beta + * 2.0.0 * @example * "latest" */ -export type Version = `${Digit}.${Digit}.${Digit}` | "latest" +export type Version = + | `${Digit}.${Digit}.${Digit}` + | `${Digit}.${Digit}.${Digit}-${PreRelease}` + | `${Digit}.${Digit}.${Digit}-${PreRelease}.${Digit}` + | "latest" diff --git a/packages/utils/tests/snark-artifacts.test.ts b/packages/utils/tests/snark-artifacts.test.ts index 3cfcf2606..6ba8ba07c 100644 --- a/packages/utils/tests/snark-artifacts.test.ts +++ b/packages/utils/tests/snark-artifacts.test.ts @@ -1,113 +1,8 @@ import fs from "node:fs" import fsPromises from "node:fs/promises" -import { - maybeGetEdDSASnarkArtifacts, - maybeGetPoseidonSnarkArtifacts, - maybeGetSemaphoreSnarkArtifacts -} from "../src/snark-artifacts/snark-artifacts.node" -import { GetSnarkArtifactUrls } from "../src/snark-artifacts/config" -import { Artifact, Proof } from "../src/types" +import Project from "../src/snark-artifacts/projects" +import maybeGetSnarkArtifacts from "../src/snark-artifacts/snark-artifacts.node" -describe("GetSnarkArtifactUrls", () => { - it("should throw if proof type is unknown", async () => { - await expect( - GetSnarkArtifactUrls({ - // @ts-expect-error type checking prevents this, bypassing for explicit testing - proof: "unknown" as Proof - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Unknown proof type"`) - }) - - it("should default to latest version", async () => { - const { wasm, zkey } = await GetSnarkArtifactUrls({ proof: Proof.EDDSA }) - - expect(wasm).toMatchInlineSnapshot(`"https://unpkg.com/@zk-kit/eddsa-artifacts@latest/eddsa.wasm"`) - expect(zkey).toMatchInlineSnapshot(`"https://unpkg.com/@zk-kit/eddsa-artifacts@latest/eddsa.zkey"`) - }) - - it("should throw if version is not available", async () => { - jest.spyOn(global, "fetch").mockResolvedValueOnce( - new Response(JSON.stringify({ versions: { "0.0.1": {} } }), { - status: 200, - statusText: "OK", - headers: { - "Content-Type": "application/json" - } - }) - ) - - await expect( - GetSnarkArtifactUrls({ proof: Proof.EDDSA, version: "0.1.0" }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Version 0.1.0 is not available for eddsa proofs, available versions are: 0.0.1"` - ) - - expect(fetch).toHaveBeenCalledTimes(1) - expect(fetch).toHaveBeenCalledWith("https://unpkg.com/@zk-kit/eddsa-artifacts") - }) - - describe("EdDSA artifacts", () => { - it("should return the correct artifact URLs for an EdDSA proof", async () => { - const { wasm, zkey } = await GetSnarkArtifactUrls({ proof: Proof.EDDSA }) - - expect(wasm).toMatchInlineSnapshot(`"https://unpkg.com/@zk-kit/eddsa-artifacts@latest/eddsa.wasm"`) - expect(zkey).toMatchInlineSnapshot(`"https://unpkg.com/@zk-kit/eddsa-artifacts@latest/eddsa.zkey"`) - }) - }) - - describe("Semaphore artifacts", () => { - it("should return the correct artifact URLs for a Semaphore proof", async () => { - const { wasm, zkey } = await GetSnarkArtifactUrls({ proof: Proof.SEMAPHORE, treeDepth: 2 }) - - expect(wasm).toMatchInlineSnapshot( - `"https://unpkg.com/@zk-kit/semaphore-artifacts@latest/semaphore-2.wasm"` - ) - expect(zkey).toMatchInlineSnapshot( - `"https://unpkg.com/@zk-kit/semaphore-artifacts@latest/semaphore-2.zkey"` - ) - }) - - it("should throw if treeDepth is not provided for a Semaphore proof", async () => { - await expect( - // @ts-expect-error expect-error function overloading prevents this, bypassing for extra explicit testing - GetSnarkArtifactUrls({ artifact: Artifact.WASM, proof: Proof.SEMAPHORE }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"treeDepth is required for Semaphore proof"`) - }) - - it("should throw if treeDepth is less than 1 for Semaphore proof", async () => { - await expect( - GetSnarkArtifactUrls({ - proof: Proof.SEMAPHORE, - treeDepth: 0 - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"treeDepth must be greater than 0"`) - }) - }) - - describe("Poseidon artifacts", () => { - it("should return the correct artifact URLs for a Poseidon proof", async () => { - const { wasm, zkey } = await GetSnarkArtifactUrls({ proof: Proof.POSEIDON, numberOfInputs: 3 }) - - expect(wasm).toMatchInlineSnapshot(`"https://unpkg.com/@zk-kit/poseidon-artifacts@latest/poseidon-3.wasm"`) - expect(zkey).toMatchInlineSnapshot(`"https://unpkg.com/@zk-kit/poseidon-artifacts@latest/poseidon-3.zkey"`) - }) - it("should throw if numberOfInputs is not provided for Poseidon proof", async () => { - await expect( - // @ts-expect-error expect-error type checking prevents this, bypassing for extra explicit testing - GetSnarkArtifactUrls({ proof: Proof.POSEIDON }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"numberOfInputs is required for Poseidon proof"`) - }) - - it("should throw if numberOfInputs is less than 1 for Poseidon proof", async () => { - await expect( - GetSnarkArtifactUrls({ - proof: Proof.POSEIDON, - numberOfInputs: 0 - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"numberOfInputs must be greater than 0"`) - }) - }) -}) describe("MaybeGetSnarkArtifacts", () => { let fetchSpy: jest.SpyInstance let mkdirSpy: jest.SpyInstance @@ -123,219 +18,77 @@ describe("MaybeGetSnarkArtifacts", () => { mkdirSpy.mockResolvedValue(undefined) }) - describe("maybeGetPoseidonSnarkArtifacts", () => { - it("should throw on fetch errors", async () => { - existsSyncSpy.mockReturnValue(false) - fetchSpy.mockResolvedValueOnce({ - ok: false, - statusText: "TEST" - }) - - await expect(maybeGetPoseidonSnarkArtifacts(2)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to fetch https://unpkg.com/@zk-kit/poseidon-artifacts@latest/poseidon-2.wasm: TEST"` - ) - }) - - it("should throw on invalid numberOfInputs", async () => { - await expect(maybeGetPoseidonSnarkArtifacts(0)).rejects.toThrowErrorMatchingInlineSnapshot( - `"numberOfInputs must be greater than 0"` - ) - }) - - it("should throw if missing body", async () => { - existsSyncSpy.mockReturnValue(false) - fetchSpy.mockResolvedValueOnce({ - ok: true, - statusText: "OK" - }) - - await expect(maybeGetPoseidonSnarkArtifacts(2)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to get response body"` - ) - }) - - it("should throw on stream error", async () => { - existsSyncSpy.mockReturnValue(false) - const mockResponseStream = { - body: { - getReader: jest.fn(() => ({ - read: jest.fn().mockRejectedValueOnce(new Error("TEST STREAM ERROR")) - })) - }, - ok: true, - statusText: "OK" - } - fetchSpy.mockResolvedValue(mockResponseStream) - createWriteStreamSpy.mockReturnValue({ - close: jest.fn(), - end: jest.fn(), - write: jest.fn() - }) - - await expect(maybeGetPoseidonSnarkArtifacts(2)).rejects.toThrowErrorMatchingInlineSnapshot( - `"TEST STREAM ERROR"` - ) - }) - - it("should download files only if don't exist yet", async () => { - existsSyncSpy.mockReturnValue(true) - - await maybeGetPoseidonSnarkArtifacts(2) - - expect(global.fetch).not.toHaveBeenCalled() - }) - - it("should return artifact file paths", async () => { - mkdirSpy.mockRestore() - existsSyncSpy.mockReturnValue(false) - - const { wasm, zkey } = await maybeGetPoseidonSnarkArtifacts(2) - - expect(wasm).toMatchInlineSnapshot(`"/tmp/@zk-kit/poseidon-artifacts@latest/poseidon-2.wasm"`) - expect(zkey).toMatchInlineSnapshot(`"/tmp/@zk-kit/poseidon-artifacts@latest/poseidon-2.zkey"`) - expect(fetchSpy).toHaveBeenCalledTimes(2) - }, 20_000) + it("Should throw an error if the project is not supported", async () => { + await expect( + maybeGetSnarkArtifacts("project" as Project, { parameters: ["2"], version: "latest" }) + ).rejects.toThrow("Project 'project' is not supported") }) - describe("maybeGetEdDSASnarkArtifacts", () => { - it("should handle fetch errors", async () => { - existsSyncSpy.mockReturnValue(false) - fetchSpy.mockResolvedValueOnce({ - ok: false, - statusText: "test error message" - }) - - await expect(maybeGetEdDSASnarkArtifacts()).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to fetch https://unpkg.com/@zk-kit/eddsa-artifacts@latest/eddsa.wasm: test error message"` - ) - }) - - it("should throw if missing body", async () => { - existsSyncSpy.mockReturnValue(false) - fetchSpy.mockResolvedValueOnce({ - ok: true, - statusText: "OK" - }) - - await expect(maybeGetEdDSASnarkArtifacts()).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to get response body"` - ) + it("Should throw on fetch errors", async () => { + existsSyncSpy.mockReturnValue(false) + fetchSpy.mockResolvedValueOnce({ + ok: false, + statusText: "TEST" }) - it("should throw on stream error", async () => { - existsSyncSpy.mockReturnValue(false) - const mockResponseStream = { - body: { - getReader: jest.fn(() => ({ - read: jest.fn().mockRejectedValueOnce(new Error("TEST STREAM ERROR")) - })) - }, - ok: true, - statusText: "OK" - } - fetchSpy.mockResolvedValue(mockResponseStream) - createWriteStreamSpy.mockReturnValue({ - close: jest.fn(), - end: jest.fn(), - write: jest.fn() - }) - - await expect(maybeGetEdDSASnarkArtifacts()).rejects.toThrowErrorMatchingInlineSnapshot( - `"TEST STREAM ERROR"` - ) - }) - - it("should download files only if don't exist yet", async () => { - existsSyncSpy.mockReturnValue(true) - - await maybeGetPoseidonSnarkArtifacts(2) - - expect(global.fetch).not.toHaveBeenCalled() - }) - - it("should return artifact file paths", async () => { - mkdirSpy.mockRestore() - existsSyncSpy.mockReturnValue(false) - - const { wasm, zkey } = await maybeGetEdDSASnarkArtifacts() - - expect(wasm).toMatchInlineSnapshot(`"/tmp/@zk-kit/eddsa-artifacts@latest/eddsa.wasm"`) - expect(zkey).toMatchInlineSnapshot(`"/tmp/@zk-kit/eddsa-artifacts@latest/eddsa.zkey"`) - expect(fetchSpy).toHaveBeenCalledTimes(2) - }, 15000) + await expect( + maybeGetSnarkArtifacts(Project.POSEIDON, { parameters: ["2"], version: "latest" }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to fetch https://unpkg.com/@zk-kit/poseidon-artifacts@latest/poseidon-2.wasm: TEST"` + ) }) - describe("maybeGetSemaphoreSnarkArtifacts", () => { - it("should throw on fetch errors", async () => { - existsSyncSpy.mockReturnValue(false) - fetchSpy.mockResolvedValueOnce({ - ok: false, - statusText: "TEST" - }) - - await expect(maybeGetSemaphoreSnarkArtifacts(2)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to fetch https://unpkg.com/@zk-kit/semaphore-artifacts@latest/semaphore-2.wasm: TEST"` - ) - }) - - it("should throw on invalid treeDepth", async () => { - await expect(maybeGetSemaphoreSnarkArtifacts(0)).rejects.toThrowErrorMatchingInlineSnapshot( - `"treeDepth must be greater than 0"` - ) + it("Should throw if missing body", async () => { + existsSyncSpy.mockReturnValue(false) + fetchSpy.mockResolvedValueOnce({ + ok: true, + statusText: "OK" }) - it("should throw if missing body", async () => { - existsSyncSpy.mockReturnValue(false) - fetchSpy.mockResolvedValueOnce({ - ok: true, - statusText: "OK" - }) + await expect( + maybeGetSnarkArtifacts(Project.POSEIDON, { parameters: ["2"], version: "0.1.0-beta" }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Failed to get response body"`) + }) - await expect(maybeGetSemaphoreSnarkArtifacts(2)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to get response body"` - ) + it("Should throw on stream error", async () => { + existsSyncSpy.mockReturnValue(false) + const mockResponseStream = { + body: { + getReader: jest.fn(() => ({ + read: jest.fn().mockRejectedValueOnce(new Error("TEST STREAM ERROR")) + })) + }, + ok: true, + statusText: "OK" + } + fetchSpy.mockResolvedValue(mockResponseStream) + createWriteStreamSpy.mockReturnValue({ + close: jest.fn(), + end: jest.fn(), + write: jest.fn() }) - it("should throw on stream error", async () => { - existsSyncSpy.mockReturnValue(false) - const mockResponseStream = { - body: { - getReader: jest.fn(() => ({ - read: jest.fn().mockRejectedValueOnce(new Error("TEST STREAM ERROR")) - })) - }, - ok: true, - statusText: "OK" - } - fetchSpy.mockResolvedValue(mockResponseStream) - createWriteStreamSpy.mockReturnValue({ - close: jest.fn(), - end: jest.fn(), - write: jest.fn() - }) - - await expect(maybeGetSemaphoreSnarkArtifacts(2)).rejects.toThrowErrorMatchingInlineSnapshot( - `"TEST STREAM ERROR"` - ) - }) + await expect( + maybeGetSnarkArtifacts(Project.POSEIDON, { parameters: ["2"], version: "0.1.0-beta.1" }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"TEST STREAM ERROR"`) + }) - it("should download files only if don't exist yet", async () => { - existsSyncSpy.mockReturnValue(true) + it("Should download files only if don't exist yet", async () => { + existsSyncSpy.mockReturnValue(true) - await maybeGetSemaphoreSnarkArtifacts(2) + await maybeGetSnarkArtifacts(Project.POSEIDON, { parameters: ["2"] }) - expect(global.fetch).not.toHaveBeenCalled() - }) + expect(global.fetch).not.toHaveBeenCalled() + }) - it("should return artifact file paths", async () => { - mkdirSpy.mockRestore() - existsSyncSpy.mockReturnValue(false) + it("Should return artifact file paths", async () => { + mkdirSpy.mockRestore() + existsSyncSpy.mockReturnValue(false) - const { wasm, zkey } = await maybeGetSemaphoreSnarkArtifacts(2) + const { wasm, zkey } = await maybeGetSnarkArtifacts(Project.POSEIDON, { parameters: ["2"] }) - expect(wasm).toMatchInlineSnapshot(`"/tmp/@zk-kit/semaphore-artifacts@latest/semaphore-2.wasm"`) - expect(zkey).toMatchInlineSnapshot(`"/tmp/@zk-kit/semaphore-artifacts@latest/semaphore-2.zkey"`) - expect(fetchSpy).toHaveBeenCalledTimes(2) - }, 15000) - }) + expect(wasm).toMatchInlineSnapshot(`"/tmp/@zk-kit/poseidon-artifacts@latest/poseidon-2.wasm"`) + expect(zkey).toMatchInlineSnapshot(`"/tmp/@zk-kit/poseidon-artifacts@latest/poseidon-2.zkey"`) + expect(fetchSpy).toHaveBeenCalledTimes(2) + }, 20_000) })