UNPKG

@hashcloak/semaphore-noir-artifacts

Version:
171 lines (164 loc) 6.94 kB
/** * @module @hashcloak/semaphore-noir-artifacts * @version 1.0.0 * @file Utilities for downloading snark artifacts * @copyright Ethereum Foundation 2025 * @license MIT * @see [Github]{@link https://github.com/privacy-scaling-explorations/snark-artifacts/tree/main/packages/artifacts} */ import { createWriteStream, existsSync } from 'node:fs'; import fs, { mkdir } from 'node:fs/promises'; import { dirname } from 'node:path'; import { tmpdir } from 'node:os'; async function download(url, outputPath) { const { body, ok, statusText } = await fetch(url); if (!ok) throw new Error(`Failed to fetch ${url}: ${statusText}`); if (!body) throw new Error('Failed to get response body'); const dir = dirname(outputPath); await mkdir(dir, { recursive: true }); const fileStream = createWriteStream(outputPath); const reader = body.getReader(); try { const pump = async () => { const { done, value } = await reader.read(); if (done) { fileStream.end(); return; } fileStream.write(Buffer.from(value)); await pump(); }; await pump(); } catch (error) { fileStream.close(); throw error; } } async function maybeDownload(url, outputPath) { if (!existsSync(outputPath)) await download(url, outputPath); return outputPath; } var Project; (function (Project) { Project["POSEIDON"] = "poseidon"; // RLN = 'rln', Project["SEMAPHORE"] = "semaphore"; Project["SEMAPHORE_IDENTITY"] = "semaphore-identity"; Project["SEMAPHORE_NOIR"] = "semaphore-noir"; })(Project || (Project = {})); const projects = Object.values(Project).sort(); const BASE_URL = 'https://snark-artifacts.pse.dev'; const NOIR_BASE_URL = 'https://hashcloak.github.io/noir-artifacts-host'; const getBaseUrl = (project, version) => `${BASE_URL}/${project}/${version}/${project}`; const getNoirArtifactUrl = (filename) => `${NOIR_BASE_URL}/${filename}`; async function maybeGetSnarkArtifacts$1(project, options = {}) { if (!projects.includes(project)) throw new Error(`Project '${project}' is not supported`); options.version ??= 'latest'; const url = getBaseUrl(project, options.version); const parameters = options.parameters ? `-${options.parameters.join('-')}` : ''; return { wasm: `${url}${parameters}.wasm`, zkey: `${url}${parameters}.zkey`, }; } const extractEndPath = (url) => url.split('pse.dev/')[1]; /** * Downloads SNARK artifacts (`wasm` and `zkey`) files if not already present in OS tmp folder. * @example * ```ts * { * wasm: "/tmp/@zk-kit/semaphore-artifacts@latest/semaphore-3.wasm", * zkey: "/tmp/@zk-kit/semaphore-artifacts@latest/semaphore-3.zkey" . * } * ``` * @returns {@link SnarkArtifacts} */ async function maybeGetSnarkArtifacts(...pars) { const urls = await maybeGetSnarkArtifacts$1(...pars); const outputPath = `${tmpdir()}/snark-artifacts/${extractEndPath(urls.wasm)}`; const [wasm, zkey] = await Promise.all([ maybeDownload(urls.wasm, outputPath), maybeDownload(urls.zkey, outputPath.replace(/.wasm$/, '.zkey')), ]); return { wasm, zkey, }; } /** * Download the compiled Noir circuit file, if it isn't already present in the OS tmp folder * @param project The project type, this should be Semaphore Noir * @param merkleTreeDepth The merkleTreeDepth for wich the circuit should be returned * @returns the compiled Noir circuit */ async function maybeGetCompiledNoirCircuit(project, merkleTreeDepth) { if (project !== Project.SEMAPHORE_NOIR) throw new Error(`Unsupported project '${project}'`); const url = getNoirArtifactUrl(`semaphore-noir-${merkleTreeDepth}.json`); const outputPath = `${tmpdir()}/snark-artifacts/semaphore-noir-${merkleTreeDepth}.json`; await maybeDownload(url, outputPath); const json = await fs.readFile(outputPath, 'utf-8'); return JSON.parse(json); } async function getCompiledNoirCircuitWithPath(project, merkleTreeDepth) { if (project !== Project.SEMAPHORE_NOIR) throw new Error(`Unsupported project '${project}'`); const url = getNoirArtifactUrl(`semaphore-noir-${merkleTreeDepth}.json`); const outputPath = `${tmpdir()}/snark-artifacts/semaphore-noir-${merkleTreeDepth}.json`; await maybeDownload(url, outputPath); const json = await fs.readFile(outputPath, 'utf-8'); return { path: outputPath, circuit: JSON.parse(json), }; } var BatchingCircuitType; (function (BatchingCircuitType) { BatchingCircuitType["Leaves"] = "batch_2_leaves"; BatchingCircuitType["Nodes"] = "batch_2_nodes"; })(BatchingCircuitType || (BatchingCircuitType = {})); async function getCompiledBatchCircuitWithPath(project, circuitType) { if (project !== Project.SEMAPHORE_NOIR) throw new Error(`Unsupported project '${project}'`); const url = getNoirArtifactUrl(`/batching/${circuitType}.json`); const outputPath = `${tmpdir()}/snark-artifacts/batching/circuit_${circuitType}.json`; await maybeDownload(url, outputPath); const json = await fs.readFile(outputPath, 'utf-8'); return { path: outputPath, circuit: JSON.parse(json), }; } async function maybeGetNoirVk(project, merkleTreeDepth) { if (project !== Project.SEMAPHORE_NOIR) throw new Error(`Unsupported project '${project}'`); const url = getNoirArtifactUrl(`semaphore-vks/semaphore-vk-${merkleTreeDepth}`); const outputPath = `${tmpdir()}/snark-artifacts/semaphore-vks/semaphore-vk-${merkleTreeDepth}`; await maybeDownload(url, outputPath); const vk = await fs.readFile(outputPath); return vk; } async function maybeGetBatchVkPath(project, keccak) { if (project !== Project.SEMAPHORE_NOIR) throw new Error(`Unsupported project '${project}'`); const suffix = keccak ? '-keccak' : ''; const url = getNoirArtifactUrl(`/batching/vk${suffix}`); const outputPath = `${tmpdir()}/snark-artifacts/batching/vk${suffix}`; await maybeDownload(url, outputPath); return outputPath; } async function maybeGetBatchSemaphoreVk(project, merkleTreeDepth) { if (project !== Project.SEMAPHORE_NOIR) throw new Error(`Unsupported project '${project}'`); const url = getNoirArtifactUrl(`/batching/semaphore-vks-fields/semaphore-vk-${merkleTreeDepth}.json`); const outputPath = `${tmpdir()}/snark-artifacts/batching/semaphore-vk-${merkleTreeDepth}.json`; await maybeDownload(url, outputPath); const json = await fs.readFile(outputPath, 'utf-8'); return JSON.parse(json); } export { BatchingCircuitType, Project, download, getCompiledBatchCircuitWithPath, getCompiledNoirCircuitWithPath, maybeDownload, maybeGetBatchSemaphoreVk, maybeGetBatchVkPath, maybeGetCompiledNoirCircuit, maybeGetNoirVk, maybeGetSnarkArtifacts, projects };