diff --git a/examples/CRISP/client/vite.config.ts b/examples/CRISP/client/vite.config.ts index 9d811d384c..aa7d4a5bb3 100644 --- a/examples/CRISP/client/vite.config.ts +++ b/examples/CRISP/client/vite.config.ts @@ -23,7 +23,15 @@ export default defineConfig({ }, optimizeDeps: { esbuildOptions: { target: 'esnext' }, - exclude: ['@rollup/browser', '@crisp-e3/zk-inputs', '@noir-lang/noirc_abi', '@noir-lang/acvm_js', '@noir-lang/noir_js', '@aztec/bb.js'], + exclude: [ + '@rollup/browser', + '@crisp-e3/zk-inputs', + '@crisp-e3/zk-inputs/init', + '@noir-lang/noirc_abi', + '@noir-lang/acvm_js', + '@noir-lang/noir_js', + '@aztec/bb.js', + ], }, resolve: { alias: { diff --git a/examples/CRISP/package.json b/examples/CRISP/package.json index 2e33479df0..b289ea149f 100644 --- a/examples/CRISP/package.json +++ b/examples/CRISP/package.json @@ -23,7 +23,6 @@ "test:sdk": "cd sdk && pnpm test", "release:sdk": "cd sdk && pnpm release", "report": "playwright show-report", - "build:wasm": "wasm-pack build ./crates/zk-inputs-wasm --no-pack --out-dir ../../packages/crisp-zk-inputs/pkg --out-name index", "publish:packages": "tsx scripts/publish.ts" }, "devDependencies": { diff --git a/examples/CRISP/packages/crisp-sdk/package.json b/examples/CRISP/packages/crisp-sdk/package.json index 8cc9bbeab6..3374021d57 100644 --- a/examples/CRISP/packages/crisp-sdk/package.json +++ b/examples/CRISP/packages/crisp-sdk/package.json @@ -20,10 +20,9 @@ }, "homepage": "https://github.com/gnosisguild/enclave", "scripts": { - "build:wasm": "pnpm -C ../../ build:wasm", - "compile:circuit": "pnpm -C ../../ compile:circuit", - "build": "pnpm compile:circuit && pnpm build:wasm && tsup", - "test": "pnpm compile:circuit && vitest --run", + "build:wasm": "pnpm -C ../crisp-zk-inputs build", + "build": "pnpm build:wasm && tsup", + "test": "pnpm build:wasm && vitest --run", "prettier:fix": "pnpm prettier --write .", "prettier:check": "pnpm prettier --check .", "release": "pnpm build && pnpm publish --access public" diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 6c2e2895c8..74eb901224 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -5,6 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. import { ZKInputsGenerator } from '@crisp-e3/zk-inputs' +import initializeWasm from '@crisp-e3/zk-inputs/init' import { BFVParams, type CRISPCircuitInputs, type EncryptVoteAndGenerateCRISPInputsParams, type IVote, VotingMode } from './types' import { toBinary } from './utils' import { MAXIMUM_VOTE_VALUE, DEFAULT_BFV_PARAMS, HALF_LARGEST_MINIMUM_DEGREE, MESSAGE } from './constants' @@ -153,6 +154,7 @@ export const validateVote = (votingMode: VotingMode, vote: IVote, votingPower: b } export const encryptVote = async (encodedVote: string[], publicKey: Uint8Array): Promise => { + await initializeWasm() const zkInputsGenerator: ZKInputsGenerator = new ZKInputsGenerator( DEFAULT_BFV_PARAMS.degree, DEFAULT_BFV_PARAMS.plaintextModulus, @@ -191,6 +193,8 @@ export const encryptVoteAndGenerateCRISPInputs = async ({ slotAddress, isFirstVote, }: EncryptVoteAndGenerateCRISPInputsParams): Promise => { + await initializeWasm() + if (encodedVote.length !== bfvParams.degree) { throw new RangeError(`encodedVote length ${encodedVote.length} does not match BFV degree ${bfvParams.degree}`) } @@ -238,6 +242,8 @@ export const generateMaskVote = async ( slotAddress: string, isFirstVote: boolean, ): Promise => { + await initializeWasm() + const plaintextVote: IVote = { yes: 0n, no: 0n, diff --git a/examples/CRISP/packages/crisp-zk-inputs/README.md b/examples/CRISP/packages/crisp-zk-inputs/README.md new file mode 100644 index 0000000000..d88c1a8faf --- /dev/null +++ b/examples/CRISP/packages/crisp-zk-inputs/README.md @@ -0,0 +1,26 @@ +# Wasm bundle for crisp-zk-inputs + +Here we export wasm functionality for consumption in TypeScript to enable us to share code between Rust and TypeScript. + +## Usage + +This package exposes an `init` subpackage default function which should be used to universally load the wasm module instead of exporting the default loader. + +This is because in modern node there is no need for preloading however in the browser we still need to load the wasm bundle. + +### ❌ DONT USE THE DEFAULT INIT + +```ts +// Bad! Because this uses the raw loader which doesn't exist in node contexts +import init, { bfvEncryptNumber } from "@crisp-e3/zk-inputs"; +``` + +### ✅ DO USE THE EXPORTED SUBMODULE + +```ts +// Good! Use the universal loader +import init from "@crisp-e3/zk-inputs/init"; + +await init(); +// other package imports here +``` diff --git a/examples/CRISP/packages/crisp-zk-inputs/init.d.ts b/examples/CRISP/packages/crisp-zk-inputs/init.d.ts new file mode 100644 index 0000000000..fa29ba16f4 --- /dev/null +++ b/examples/CRISP/packages/crisp-zk-inputs/init.d.ts @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +declare function init(): Promise; +export default init; diff --git a/examples/CRISP/packages/crisp-zk-inputs/init_node.cjs b/examples/CRISP/packages/crisp-zk-inputs/init_node.cjs new file mode 100644 index 0000000000..2efc91fecf --- /dev/null +++ b/examples/CRISP/packages/crisp-zk-inputs/init_node.cjs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +module.exports = async function init() { + // Node does not need to be loaded async +}; diff --git a/examples/CRISP/packages/crisp-zk-inputs/init_node.js b/examples/CRISP/packages/crisp-zk-inputs/init_node.js new file mode 100644 index 0000000000..d0f22af2d0 --- /dev/null +++ b/examples/CRISP/packages/crisp-zk-inputs/init_node.js @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +export default async function init() { + // Node does not need to be loaded async +} diff --git a/examples/CRISP/packages/crisp-zk-inputs/init_web.js b/examples/CRISP/packages/crisp-zk-inputs/init_web.js new file mode 100644 index 0000000000..35ac69108d --- /dev/null +++ b/examples/CRISP/packages/crisp-zk-inputs/init_web.js @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +import * as bindgen from "./dist/web/index.js"; + +let promise; + +export default async function init() { + promise ??= (async () => { + const { default: base64 } = await import("./dist/web/index_base64.js"); + + const binaryString = atob(base64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + bindgen.initSync(bytes); + + return bindgen; + })(); + + return promise; +} diff --git a/examples/CRISP/packages/crisp-zk-inputs/package.json b/examples/CRISP/packages/crisp-zk-inputs/package.json index 93d82ccc4c..c75bfb47bc 100644 --- a/examples/CRISP/packages/crisp-zk-inputs/package.json +++ b/examples/CRISP/packages/crisp-zk-inputs/package.json @@ -8,19 +8,60 @@ "type": "git", "url": "https://github.com/gnosisguild/enclave" }, + "main": "dist/node/index.js", + "module": "dist/web/index.js", + "types": "dist/web/index.d.ts", "files": [ - "pkg/index_bg.wasm", - "pkg/index.js", - "pkg/index_bg.js", - "pkg/index.d.ts" + "dist/", + "dist/node/**", + "dist/web/**", + "init_node.js", + "init_node.cjs", + "init_web.js", + "init.d.ts" ], + "exports": { + ".": { + "node": { + "types": "./dist/node/index.d.ts", + "default": "./dist/node/index.js" + }, + "browser": { + "types": "./dist/web/index.d.ts", + "default": "./dist/web/index.js" + }, + "default": { + "types": "./dist/web/index.d.ts", + "default": "./dist/web/index.js" + } + }, + "./init": { + "node": { + "types": "./init.d.ts", + "import": "./init_node.js", + "require": "./init_node.cjs" + }, + "browser": { + "types": "./init.d.ts", + "default": "./init_web.js" + }, + "default": { + "types": "./init.d.ts", + "import": "./init_node.js", + "require": "./init_node.cjs", + "default": "./init_web.js" + } + } + }, "publishConfig": { "access": "public" }, - "main": "pkg/index.js", - "types": "pkg/index.d.ts", - "sideEffects": [ - "./pkg/index.js", - "./pkg/snippets/*" - ] + "scripts": { + "compile:circuit": "pnpm -C ../../ compile:circuit", + "build": "rm -rf dist && pnpm compile:circuit && node scripts/build.js" + }, + "devDependencies": { + "execa": "^8.0.1", + "replace-in-file": "^7.2.0" + } } diff --git a/examples/CRISP/packages/crisp-zk-inputs/scripts/build.js b/examples/CRISP/packages/crisp-zk-inputs/scripts/build.js new file mode 100644 index 0000000000..3fe35987a1 --- /dev/null +++ b/examples/CRISP/packages/crisp-zk-inputs/scripts/build.js @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +import { execa } from "execa"; +import { readFile, writeFile, rm } from "fs/promises"; +import { resolve } from "path"; +import replaceInFile from "replace-in-file"; + +try { + // Build WASM with web and node target - generates index.js and index_bg.wasm. + const distWeb = resolve(process.cwd(), "dist/web"); + const distNode = resolve(process.cwd(), "dist/node"); + + await execa("wasm-pack", [ + "build", + "../../crates/zk-inputs-wasm", + "--target=web", + `--out-dir=${distWeb}`, + "--no-pack", + "--out-name=index", + ]); + await execa("wasm-pack", [ + "build", + "../../crates/zk-inputs-wasm", + "--target=nodejs", + `--out-dir=${distNode}`, + "--no-pack", + "--out-name=index", + ]); + + // Convert WASM binary to base64 for bundler compatibility. + const wasmBinary = await readFile("./dist/web/index_bg.wasm"); + const base64Src = `export default '${wasmBinary.toString("base64")}';\n`; + + // Parallel cleanup and JS modification to prevent Next.js and other bundlers static analysis issues. + await Promise.all([ + rm("./dist/web/index_bg.wasm", { force: true }), + rm("./dist/web/index_bg.wasm.d.ts", { force: true }), + rm("./dist/web/.gitignore", { force: true }), + rm("./dist/node/.gitignore", { force: true }), + replaceInFile({ + files: "./dist/web/index.js", + from: /module_or_path\s*=\s*new URL\(['"]index_bg\.wasm['"],\s*import\.meta\.url\);\s*/g, + to: "/* wasm URL disabled: load via @crisp-e3/zk-inputs/init */\n", + }), + writeFile("./dist/web/index_base64.js", base64Src), + ]); +} catch (error) { + console.error(error); + process.exit(1); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f4f19f131..dd7303d7f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -383,7 +383,14 @@ importers: specifier: ^1.6.1 version: 1.6.1(@types/node@22.7.5) - examples/CRISP/packages/crisp-zk-inputs: {} + examples/CRISP/packages/crisp-zk-inputs: + devDependencies: + execa: + specifier: ^8.0.1 + version: 8.0.1 + replace-in-file: + specifier: ^7.2.0 + version: 7.2.0 packages/enclave-config: dependencies: