diff --git a/.github/workflows/test-noir-js.yml b/.github/workflows/test-noir-js.yml index 1dac020002..907f10f49d 100644 --- a/.github/workflows/test-noir-js.yml +++ b/.github/workflows/test-noir-js.yml @@ -47,6 +47,9 @@ jobs: - name: Build noirc_abi run: yarn workspace @noir-lang/noirc_abi build + + - name: Build barretenberg wrapper + run: yarn workspace @noir-lang/backend_barretenberg build - name: Run noir_js tests run: | diff --git a/package.json b/package.json index 07ca6b4689..0cbcc7d977 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "tooling/noir_js_types", "tooling/noirc_abi_wasm", "tooling/noir_js", + "tooling/noir_js_backend_barretenberg", "acvm-repo/acvm_js", "release-tests" ], diff --git a/tooling/noir_js/test/node/e2e.test.ts b/tooling/noir_js/test/node/e2e.test.ts index 1120f08c81..fe0d26c7e3 100644 --- a/tooling/noir_js/test/node/e2e.test.ts +++ b/tooling/noir_js/test/node/e2e.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' }; import { generateWitness } from '../../src/index.js'; import { Noir } from '../../src/program.js'; -import { BarretenbergBackend as Backend } from '../backend/barretenberg.js'; +import { BarretenbergBackend as Backend } from '@noir-lang/backend_barretenberg'; it('end-to-end proof creation and verification (outer)', async () => { // Noir.Js part @@ -15,7 +15,7 @@ it('end-to-end proof creation and verification (outer)', async () => { // bb.js part // // Proof creation - const prover = await Backend.initialize(assert_lt_json); + const prover = new Backend(assert_lt_json); const proof = await prover.generateFinalProof(serializedWitness); // Proof verification @@ -31,7 +31,7 @@ it('end-to-end proof creation and verification (outer) -- Program API', async () }; // Initialize backend - const backend = await Backend.initialize(assert_lt_json); + const backend = new Backend(assert_lt_json); // Initialize program const program = new Noir(assert_lt_json, backend); // Generate proof @@ -53,7 +53,7 @@ it('end-to-end proof creation and verification (inner)', async () => { // bb.js part // // Proof creation - const prover = await Backend.initialize(assert_lt_json); + const prover = new Backend(assert_lt_json); const proof = await prover.generateIntermediateProof(serializedWitness); // Proof verification @@ -82,12 +82,12 @@ it('[BUG] -- bb.js null function or function signature mismatch (different insta const serializedWitness = await generateWitness(assert_lt_json, inputs); // bb.js part - const prover = await Backend.initialize(assert_lt_json); + const prover = new Backend(assert_lt_json); const proof = await prover.generateFinalProof(serializedWitness); try { - const verifier = await Backend.initialize(assert_lt_json); + const verifier = new Backend(assert_lt_json); await verifier.verifyFinalProof(proof); expect.fail( 'bb.js currently returns a bug when we try to verify a proof with a different Barretenberg instance that created it.', @@ -117,7 +117,7 @@ it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', // // Proof creation // - const prover = await Backend.initialize(assert_lt_json); + const prover = new Backend(assert_lt_json); // Create a proof using both proving systems, the majority of the time // one would only use outer proofs. const proofOuter = await prover.generateFinalProof(serializedWitness); diff --git a/tooling/noir_js_backend_barretenberg/.eslintignore b/tooling/noir_js_backend_barretenberg/.eslintignore new file mode 100644 index 0000000000..b512c09d47 --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/.eslintignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/tooling/noir_js_backend_barretenberg/.eslintrc.cjs b/tooling/noir_js_backend_barretenberg/.eslintrc.cjs new file mode 100644 index 0000000000..33335c2a87 --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/.eslintrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: ["../../.eslintrc.js"], +}; diff --git a/tooling/noir_js_backend_barretenberg/.gitignore b/tooling/noir_js_backend_barretenberg/.gitignore new file mode 100644 index 0000000000..689b3ca070 --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/.gitignore @@ -0,0 +1,2 @@ +crs +lib diff --git a/tooling/noir_js_backend_barretenberg/package.json b/tooling/noir_js_backend_barretenberg/package.json new file mode 100644 index 0000000000..1ab6c7bb86 --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/package.json @@ -0,0 +1,43 @@ +{ + "name": "@noir-lang/backend_barretenberg", + "collaborators": [ + "The Noir Team " + ], + "version": "0.7.10", + "packageManager": "yarn@3.5.1", + "license": "(MIT OR Apache-2.0)", + "type": "module", + "source": "src/index.ts", + "main": "lib/cjs/index.cjs", + "module": "lib/esm/index.js", + "exports": { + "require": "./lib/cjs/index.cjs", + "default": "./lib/esm/index.js", + "types": "./lib/esm/index.d.ts" + }, + "types": "lib/esm/index.d.ts", + "scripts": { + "dev": "tsc --watch", + "build": "yarn clean && tsc && tsc -p ./tsconfig.cjs.json && mv ./lib/cjs/index.js ./lib/cjs/index.cjs && mv ./lib/cjs/serialize.js ./lib/cjs/serialize.cjs && mv ./lib/cjs/base64_decode.js ./lib/cjs/base64_decode.cjs", + "clean": "rm -rf ./lib", + "prettier": "prettier 'src/**/*.ts'", + "prettier:fix": "prettier --write 'src/**/*.ts' 'test/**/*.ts'", + "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" + }, + "dependencies": { + "@aztec/bb.js": "0.7.10", + "@noir-lang/types": "workspace:*", + "fflate": "^0.8.0" + }, + "peerDependencies": { + "@noir-lang/backend_barretenberg": "workspace:*" + }, + "devDependencies": { + "@types/node": "^20.6.2", + "@types/prettier": "^3", + "eslint": "^8.40.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "3.0.3", + "typescript": "5.1.5" + } +} diff --git a/tooling/noir_js_backend_barretenberg/src/base64_decode.ts b/tooling/noir_js_backend_barretenberg/src/base64_decode.ts new file mode 100644 index 0000000000..d53aed187c --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/src/base64_decode.ts @@ -0,0 +1,13 @@ +// Since this is a simple function, we can use feature detection to +// see if we are in the nodeJs environment or the browser environment. +export function base64Decode(input: string): Uint8Array { + if (typeof Buffer !== 'undefined') { + // Node.js environment + return Buffer.from(input, 'base64'); + } else if (typeof atob === 'function') { + // Browser environment + return Uint8Array.from(atob(input), (c) => c.charCodeAt(0)); + } else { + throw new Error('No implementation found for base64 decoding.'); + } +} diff --git a/tooling/noir_js/test/backend/barretenberg.ts b/tooling/noir_js_backend_barretenberg/src/index.ts similarity index 74% rename from tooling/noir_js/test/backend/barretenberg.ts rename to tooling/noir_js_backend_barretenberg/src/index.ts index 557f2b3a74..763de88cf9 100644 --- a/tooling/noir_js/test/backend/barretenberg.ts +++ b/tooling/noir_js_backend_barretenberg/src/index.ts @@ -1,49 +1,38 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import { Barretenberg, Crs, RawBuffer } from '@aztec/bb.js'; -import { acirToUint8Array } from '../../src/index.js'; -import { Backend } from '@noir-lang/types'; +import { acirToUint8Array } from './serialize.js'; +import { Backend, CompiledCircuit } from '@noir-lang/types'; export class BarretenbergBackend implements Backend { // These type assertions are used so that we don't // have to initialize `api` and `acirComposer` in the constructor. // These are initialized asynchronously in the `init` function, // constructors cannot be asynchronous which is why we do this. - api = {} as Barretenberg; - acirComposer = {} as any; - acirUncompressedBytecode: Uint8Array; + private api: any; + private acirComposer: any; + private acirUncompressedBytecode: Uint8Array; + private numberOfThreads = 1; - private constructor(acirCircuit: { bytecode: string }) { + constructor(acirCircuit: CompiledCircuit, numberOfThreads = 1) { const acirBytecodeBase64 = acirCircuit.bytecode; + this.numberOfThreads = numberOfThreads; this.acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); } - static async initialize(acirCircuit: { bytecode: string }): Promise { - const backend = new BarretenbergBackend(acirCircuit); - await backend.init(); - return backend; - } - - private async init(): Promise { - const numThreads = 4; - - const { api, composer } = await this.initBarretenberg(numThreads, this.acirUncompressedBytecode); - - this.api = api; - this.acirComposer = composer; - } - - private async initBarretenberg(numThreads: number, acirUncompressedBytecode: Uint8Array) { - const api = await Barretenberg.new(numThreads); - - const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes(acirUncompressedBytecode); - const crs = await Crs.new(subgroupSize + 1); - await api.commonInitSlabAllocator(subgroupSize); - await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); - - const acirComposer = await api.acirNewAcirComposer(subgroupSize); - return { api: api, composer: acirComposer }; + private async instantiate(): Promise { + if (!this.api) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); + const api = await Barretenberg.new(this.numberOfThreads); + + const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes(this.acirUncompressedBytecode); + const crs = await Crs.new(subgroupSize + 1); + await api.commonInitSlabAllocator(subgroupSize); + await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); + + this.acirComposer = await api.acirNewAcirComposer(subgroupSize); + this.api = api; + } } // Generate an outer proof. This is the proof for the circuit which will verify @@ -73,6 +62,7 @@ export class BarretenbergBackend implements Backend { } async generateProof(decompressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { + await this.instantiate(); const proof = await this.api.acirCreateProof( this.acirComposer, this.acirUncompressedBytecode, @@ -100,6 +90,7 @@ export class BarretenbergBackend implements Backend { vkAsFields: string[]; vkHash: string; }> { + await this.instantiate(); const proofAsFields = await this.api.acirSerializeProofIntoFields(this.acirComposer, proof, numOfPublicInputs); // TODO: perhaps we should put this in the init function. Need to benchmark @@ -128,11 +119,15 @@ export class BarretenbergBackend implements Backend { } async verifyProof(proof: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { + await this.instantiate(); await this.api.acirInitVerificationKey(this.acirComposer); return await this.api.acirVerifyProof(this.acirComposer, proof, makeEasyToVerifyInCircuit); } async destroy(): Promise { + if (!this.api) { + return; + } await this.api.destroy(); } } diff --git a/tooling/noir_js_backend_barretenberg/src/serialize.ts b/tooling/noir_js_backend_barretenberg/src/serialize.ts new file mode 100644 index 0000000000..af07913f3c --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/src/serialize.ts @@ -0,0 +1,8 @@ +import { decompressSync as gunzip } from 'fflate'; +import { base64Decode } from './base64_decode.js'; + +// Converts bytecode from a base64 string to a Uint8Array +export function acirToUint8Array(base64EncodedBytecode): Uint8Array { + const compressedByteCode = base64Decode(base64EncodedBytecode); + return gunzip(compressedByteCode); +} diff --git a/tooling/noir_js_backend_barretenberg/tsconfig.cjs.json b/tooling/noir_js_backend_barretenberg/tsconfig.cjs.json new file mode 100644 index 0000000000..15d273af62 --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/tsconfig.cjs.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "./lib/cjs" + }, +} diff --git a/tooling/noir_js_backend_barretenberg/tsconfig.json b/tooling/noir_js_backend_barretenberg/tsconfig.json new file mode 100644 index 0000000000..bd76f50af0 --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "declaration": true, + "emitDeclarationOnly": false, + "module": "ESNext", + "moduleResolution": "NodeNext", + "outDir": "./lib/esm", + "esModuleInterop": true, + "resolveJsonModule": true, + "strict": true, + "noImplicitAny": false, + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/yarn.lock b/yarn.lock index 5998147de8..52c5915c8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,6 +29,20 @@ __metadata: languageName: node linkType: hard +"@aztec/bb.js@npm:0.7.10": + version: 0.7.10 + resolution: "@aztec/bb.js@npm:0.7.10" + dependencies: + comlink: ^4.4.1 + commander: ^10.0.1 + debug: ^4.3.4 + tslib: ^2.4.0 + bin: + bb.js: dest/node/main.js + checksum: 0410278e6ec2a6ecdcbaa58633b181ec1d91e1c267c76e7e587fb69c8f2fd394e79f65bd96cfcdb2a2b20fe5abeb86ababd45bd6364ba07555fc0643bf0e4307 + languageName: node + linkType: hard + "@aztec/bb.js@npm:0.7.2": version: 0.7.2 resolution: "@aztec/bb.js@npm:0.7.2" @@ -440,6 +454,24 @@ __metadata: languageName: unknown linkType: soft +"@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg": + version: 0.0.0-use.local + resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" + dependencies: + "@aztec/bb.js": 0.7.10 + "@noir-lang/types": "workspace:*" + "@types/node": ^20.6.2 + "@types/prettier": ^3 + eslint: ^8.40.0 + eslint-plugin-prettier: ^5.0.0 + fflate: ^0.8.0 + prettier: 3.0.3 + typescript: 5.1.5 + peerDependencies: + "@noir-lang/backend_barretenberg": "workspace:*" + languageName: unknown + linkType: soft + "@noir-lang/noir_js@workspace:*, @noir-lang/noir_js@workspace:tooling/noir_js": version: 0.0.0-use.local resolution: "@noir-lang/noir_js@workspace:tooling/noir_js" @@ -7549,6 +7581,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:5.1.5": + version: 5.1.5 + resolution: "typescript@npm:5.1.5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 0eef8699e05ae767096924dbed633c340b4d36e953bb8ed87fb12e9dd9dcea5055ceac7182c614a556dbd346a8a82df799d330e1e286ae66e17c84e1710f6a6f + languageName: node + linkType: hard + "typescript@npm:^5.0.4, typescript@npm:^5.2.2": version: 5.2.2 resolution: "typescript@npm:5.2.2" @@ -7569,6 +7611,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@5.1.5#~builtin": + version: 5.1.5 + resolution: "typescript@patch:typescript@npm%3A5.1.5#~builtin::version=5.1.5&hash=5da071" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 12ff5d14888805f24479e54bc8a3f83647107a6345f6c29dffcd429fb345be55f584a37e262cca58a0105203e41d4cb4e31b1b9096c9abeca0e2ace8eb00935e + languageName: node + linkType: hard + "typescript@patch:typescript@^5.0.4#~builtin, typescript@patch:typescript@^5.2.2#~builtin": version: 5.2.2 resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin::version=5.2.2&hash=f3b441"