diff --git a/.gitignore b/.gitignore index 94e8f1a8db0..3ce5a1ebc4b 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,4 @@ compiler/wasm/nodejs compiler/wasm/web tooling/noirc_abi_wasm/nodejs tooling/noirc_abi_wasm/web -tooling/noir_js/lib +!tooling/noir_js/lib diff --git a/compiler/integration-tests/package.json b/compiler/integration-tests/package.json index 5d87dc96f12..8cd7a0e722f 100644 --- a/compiler/integration-tests/package.json +++ b/compiler/integration-tests/package.json @@ -12,7 +12,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "^0.7.3", + "@noir-lang/backend_barretenberg": "workspace:*", "@noir-lang/noir_js": "workspace:*", "@noir-lang/noir_wasm": "workspace:*", "@noir-lang/source-resolver": "workspace:*", diff --git a/compiler/integration-tests/test/integration/browser/1_mul.test.ts b/compiler/integration-tests/test/integration/browser/1_mul.test.ts new file mode 100644 index 00000000000..33f52cb80ff --- /dev/null +++ b/compiler/integration-tests/test/integration/browser/1_mul.test.ts @@ -0,0 +1,93 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { expect } from "@esm-bundle/chai"; +import { TEST_LOG_LEVEL } from "../../environment.js"; +import { Logger } from "tslog"; +import { initializeResolver } from "@noir-lang/source-resolver"; +import newCompiler, { + compile, + init_log_level as compilerLogLevel, +} from "@noir-lang/noir_wasm"; +import { Noir } from "@noir-lang/noir_js"; +import { BarretenbergBackend } from "@noir-lang/backend_barretenberg"; +import * as TOML from "smol-toml"; +const logger = new Logger({ name: "test", minLevel: TEST_LOG_LEVEL }); + +await newCompiler(); + +compilerLogLevel("INFO"); + +const base_relative_path = "../../../../.."; +const one_mul = "tooling/nargo_cli/tests/execution_success/1_mul"; +const circuit_recursion = "compiler/integration-tests/test/circuits/recursion"; + +async function getFile(url: URL): Promise { + const response = await fetch(url); + + if (!response.ok) throw new Error("Network response was not OK"); + + return await response.text(); +} + +async function getCircuit(noirSource: string) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + initializeResolver((id: string) => { + logger.debug("source-resolver: resolving:", id); + return noirSource; + }); + + return compile({}); +} + +describe("It compiles noir program code, receiving circuit bytes and abi object.", () => { + let circuit_main_source; + let circuit_main_toml; + let circuit_recursion_source; + + before(async () => { + const circuit_main_source_url = new URL( + `${base_relative_path}/${one_mul}/src/main.nr`, + import.meta.url, + ); + const circuit_main_toml_url = new URL( + `${base_relative_path}/${one_mul}/Prover.toml`, + import.meta.url, + ); + + circuit_main_source = await getFile(circuit_main_source_url); + circuit_main_toml = await getFile(circuit_main_toml_url); + + const circuit_recursion_source_url = new URL( + `${base_relative_path}/${circuit_recursion}/src/main.nr`, + import.meta.url, + ); + + circuit_recursion_source = await getFile(circuit_recursion_source_url); + }); + + it("Should generate valid inner proof for correct input, then verify proof within a proof", async () => { + const compiled_main_circuit = await getCircuit(circuit_main_source); + + const main_inputs = TOML.parse(circuit_main_toml); + + const backend = new BarretenbergBackend({ + bytecode: compiled_main_circuit.circuit, + abi: compiled_main_circuit.abi, + }); + + const program = new Noir( + { + bytecode: compiled_main_circuit.circuit, + abi: compiled_main_circuit.abi, + }, + backend, + ); + + const main_proof = await program.generateFinalProof(main_inputs); + + const main_verification = await program.verifyFinalProof(main_proof); + + logger.debug("main_verification", main_verification); + + expect(main_verification).to.be.true; + }).timeout(60 * 20e3); +}); diff --git a/compiler/integration-tests/test/integration/browser/recursion.test.ts b/compiler/integration-tests/test/integration/browser/recursion.test.ts index 41c807f1ca0..22b162037cd 100644 --- a/compiler/integration-tests/test/integration/browser/recursion.test.ts +++ b/compiler/integration-tests/test/integration/browser/recursion.test.ts @@ -108,7 +108,7 @@ describe("It compiles noir program code, receiving circuit bytes and abi object. const circuit_recursion_source_url = new URL( `${base_relative_path}/${circuit_recursion}/src/main.nr`, - import.meta.url, + import.meta.url ); circuit_recursion_source = await getFile(circuit_recursion_source_url); diff --git a/compiler/source-resolver/types/index.d.ts b/compiler/source-resolver/types/index.d.ts index bf144c3928f..a17f7bc36bb 100644 --- a/compiler/source-resolver/types/index.d.ts +++ b/compiler/source-resolver/types/index.d.ts @@ -1,4 +1,2 @@ export declare const read_file: (source_id: string) => string; -export declare function initializeResolver( - resolver: (source_id: string) => string, -): void; +export declare function initializeResolver(resolver: (source_id: string) => string): void; diff --git a/package.json b/package.json index 84d44a38383..972b6de7ced 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,16 @@ { "name": "@noir-lang/root", - "version": "0.11.1", + "version": "0.14.1", "private": true, "workspaces": [ + "acvm-repo/acvm_js", "compiler/wasm", "compiler/source-resolver", - "tooling/noirc_abi_wasm", "compiler/integration-tests", + "tooling/noirc_abi_wasm", + "tooling/noir_js_types", "tooling/noir_js", - "acvm-repo/acvm_js", + "tooling/backend_barretenberg", "release-tests" ], "scripts": { @@ -34,10 +36,8 @@ "mocha": "^10.2.0", "prettier": "3.0.3", "ts-node": "^10.9.1", + "tslog": "^4.9.2", "typescript": "^5.0.4" }, - "packageManager": "yarn@3.6.3", - "dependencies": { - "tslog": "^4.9.2" - } + "packageManager": "yarn@3.6.3" } diff --git a/tooling/backend_barretenberg/.eslintignore b/tooling/backend_barretenberg/.eslintignore new file mode 100644 index 00000000000..fcbc654a839 --- /dev/null +++ b/tooling/backend_barretenberg/.eslintignore @@ -0,0 +1,2 @@ +node_modules +test/backend/barretenberg.ts \ No newline at end of file diff --git a/tooling/backend_barretenberg/.eslintrc.cjs b/tooling/backend_barretenberg/.eslintrc.cjs new file mode 100644 index 00000000000..33335c2a877 --- /dev/null +++ b/tooling/backend_barretenberg/.eslintrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: ["../../.eslintrc.js"], +}; diff --git a/tooling/backend_barretenberg/.gitignore b/tooling/backend_barretenberg/.gitignore new file mode 100644 index 00000000000..5b57ba1708d --- /dev/null +++ b/tooling/backend_barretenberg/.gitignore @@ -0,0 +1,3 @@ +crs + +!test/noir_compiled_examples/*/target diff --git a/tooling/backend_barretenberg/.prettierrc b/tooling/backend_barretenberg/.prettierrc new file mode 100644 index 00000000000..ef937f9697a --- /dev/null +++ b/tooling/backend_barretenberg/.prettierrc @@ -0,0 +1,6 @@ +{ + "parser": "typescript", + "printWidth": 120, + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/tooling/backend_barretenberg/lib/cjs/index.cjs b/tooling/backend_barretenberg/lib/cjs/index.cjs new file mode 100644 index 00000000000..258d024ac1c --- /dev/null +++ b/tooling/backend_barretenberg/lib/cjs/index.cjs @@ -0,0 +1,109 @@ +"use strict"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BarretenbergBackend = exports.acirToUint8Array = exports.base64Decode = void 0; +// import { Barretenberg, Crs, RawBuffer } from '@aztec/bb.js/dest/browser/types/index.js'; +const fflate_1 = require("fflate"); +// Since this is a simple function, we can use feature detection to +// see if we are in the nodeJs environment or the browser environment. +function base64Decode(input) { + return Uint8Array.from(atob(input), (c) => c.charCodeAt(0)); +} +exports.base64Decode = base64Decode; +// Converts an bytecode to a Uint8Array +function acirToUint8Array(base64EncodedBytecode) { + const compressedByteCode = base64Decode(base64EncodedBytecode); + return (0, fflate_1.decompressSync)(compressedByteCode); +} +exports.acirToUint8Array = acirToUint8Array; +class BarretenbergBackend { + // 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; + acirComposer; + acirUncompressedBytecode; + numberOfThreads = 1; + constructor(acirCircuit, numberOfThreads = 1) { + const acirBytecodeBase64 = acirCircuit.bytecode; + this.numberOfThreads = numberOfThreads; + this.acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); + } + async instantiate() { + 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 + // inner proofs and or can be seen as the proof created for regular circuits. + // + // The settings for this proof are the same as the settings for a "normal" proof + // ie one that is not in the recursive setting. + async generateFinalProof(decompressedWitness, optimizeForVerifyInCircuit = false) { + await this.instantiate(); + const proof = await this.api.acirCreateProof(this.acirComposer, this.acirUncompressedBytecode, decompressedWitness, optimizeForVerifyInCircuit); + return proof; + } + // Generates an inner proof. This is the proof that will be verified + // in another circuit. + // + // This is sometimes referred to as a recursive proof. + // We avoid this terminology as the only property of this proof + // that matters, is the fact that it is easy to verify in another + // circuit. We _could_ choose to verify this proof in the CLI. + // + // We set `makeEasyToVerifyInCircuit` to true, which will tell the backend to + // generate the proof using components that will make the proof + // easier to verify in a circuit. + async generateIntermediateProof(witness) { + const optimizeForVerifyInCircuit = true; + return this.generateFinalProof(witness, optimizeForVerifyInCircuit); + } + // Generates artifacts that will be passed to a circuit that will verify this proof. + // + // Instead of passing the proof and verification key as a byte array, we pass them + // as fields which makes it cheaper to verify in a circuit. + // + // The proof that is passed here will have been created using the `generateInnerProof` + // method. + // + // The number of public inputs denotes how many public inputs are in the inner proof. + async generateIntermediateProofArtifacts(proof, numOfPublicInputs = 0) { + 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 + // TODO how long it takes. + await this.api.acirInitVerificationKey(this.acirComposer); + // Note: If you don't init verification key, `acirSerializeVerificationKeyIntoFields`` will just hang on serialization + const vk = await this.api.acirSerializeVerificationKeyIntoFields(this.acirComposer); + return { + proofAsFields: proofAsFields.map((p) => p.toString()), + vkAsFields: vk[0].map((vk) => vk.toString()), + vkHash: vk[1].toString(), + }; + } + async verifyIntermediateProof(proof) { + const optimizeForVerifyInCircuit = true; + return this.verifyFinalProof(proof, optimizeForVerifyInCircuit); + } + async verifyFinalProof(proof, optimizeForVerifyInCircuit = false) { + await this.instantiate(); + await this.api.acirInitVerificationKey(this.acirComposer); + return await this.api.acirVerifyProof(this.acirComposer, proof, optimizeForVerifyInCircuit); + } + async destroy() { + await this.api.destroy(); + } +} +exports.BarretenbergBackend = BarretenbergBackend; diff --git a/tooling/backend_barretenberg/lib/cjs/index.d.ts b/tooling/backend_barretenberg/lib/cjs/index.d.ts new file mode 100644 index 00000000000..12bdc5e1a66 --- /dev/null +++ b/tooling/backend_barretenberg/lib/cjs/index.d.ts @@ -0,0 +1,21 @@ +import { Backend, CompiledCircuit } from '@noir-lang/types'; +export declare function base64Decode(input: string): Uint8Array; +export declare function acirToUint8Array(base64EncodedBytecode: any): Uint8Array; +export declare class BarretenbergBackend implements Backend { + private api; + private acirComposer; + private acirUncompressedBytecode; + private numberOfThreads; + constructor(acirCircuit: CompiledCircuit, numberOfThreads?: number); + instantiate(): Promise; + generateFinalProof(decompressedWitness: Uint8Array, optimizeForVerifyInCircuit?: boolean): Promise; + generateIntermediateProof(witness: Uint8Array): Promise; + generateIntermediateProofArtifacts(proof: Uint8Array, numOfPublicInputs?: number): Promise<{ + proofAsFields: string[]; + vkAsFields: string[]; + vkHash: string; + }>; + verifyIntermediateProof(proof: Uint8Array): Promise; + verifyFinalProof(proof: Uint8Array, optimizeForVerifyInCircuit?: boolean): Promise; + destroy(): Promise; +} diff --git a/tooling/backend_barretenberg/lib/esm/index.d.ts b/tooling/backend_barretenberg/lib/esm/index.d.ts new file mode 100644 index 00000000000..12bdc5e1a66 --- /dev/null +++ b/tooling/backend_barretenberg/lib/esm/index.d.ts @@ -0,0 +1,21 @@ +import { Backend, CompiledCircuit } from '@noir-lang/types'; +export declare function base64Decode(input: string): Uint8Array; +export declare function acirToUint8Array(base64EncodedBytecode: any): Uint8Array; +export declare class BarretenbergBackend implements Backend { + private api; + private acirComposer; + private acirUncompressedBytecode; + private numberOfThreads; + constructor(acirCircuit: CompiledCircuit, numberOfThreads?: number); + instantiate(): Promise; + generateFinalProof(decompressedWitness: Uint8Array, optimizeForVerifyInCircuit?: boolean): Promise; + generateIntermediateProof(witness: Uint8Array): Promise; + generateIntermediateProofArtifacts(proof: Uint8Array, numOfPublicInputs?: number): Promise<{ + proofAsFields: string[]; + vkAsFields: string[]; + vkHash: string; + }>; + verifyIntermediateProof(proof: Uint8Array): Promise; + verifyFinalProof(proof: Uint8Array, optimizeForVerifyInCircuit?: boolean): Promise; + destroy(): Promise; +} diff --git a/tooling/backend_barretenberg/lib/esm/index.js b/tooling/backend_barretenberg/lib/esm/index.js new file mode 100644 index 00000000000..69f0d185284 --- /dev/null +++ b/tooling/backend_barretenberg/lib/esm/index.js @@ -0,0 +1,103 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// import { Barretenberg, Crs, RawBuffer } from '@aztec/bb.js/dest/browser/types/index.js'; +import { decompressSync as gunzip } from 'fflate'; +// 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) { + return Uint8Array.from(atob(input), (c) => c.charCodeAt(0)); +} +// Converts an bytecode to a Uint8Array +export function acirToUint8Array(base64EncodedBytecode) { + const compressedByteCode = base64Decode(base64EncodedBytecode); + return gunzip(compressedByteCode); +} +export class BarretenbergBackend { + // 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; + acirComposer; + acirUncompressedBytecode; + numberOfThreads = 1; + constructor(acirCircuit, numberOfThreads = 1) { + const acirBytecodeBase64 = acirCircuit.bytecode; + this.numberOfThreads = numberOfThreads; + this.acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); + } + async instantiate() { + 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 + // inner proofs and or can be seen as the proof created for regular circuits. + // + // The settings for this proof are the same as the settings for a "normal" proof + // ie one that is not in the recursive setting. + async generateFinalProof(decompressedWitness, optimizeForVerifyInCircuit = false) { + await this.instantiate(); + const proof = await this.api.acirCreateProof(this.acirComposer, this.acirUncompressedBytecode, decompressedWitness, optimizeForVerifyInCircuit); + return proof; + } + // Generates an inner proof. This is the proof that will be verified + // in another circuit. + // + // This is sometimes referred to as a recursive proof. + // We avoid this terminology as the only property of this proof + // that matters, is the fact that it is easy to verify in another + // circuit. We _could_ choose to verify this proof in the CLI. + // + // We set `makeEasyToVerifyInCircuit` to true, which will tell the backend to + // generate the proof using components that will make the proof + // easier to verify in a circuit. + async generateIntermediateProof(witness) { + const optimizeForVerifyInCircuit = true; + return this.generateFinalProof(witness, optimizeForVerifyInCircuit); + } + // Generates artifacts that will be passed to a circuit that will verify this proof. + // + // Instead of passing the proof and verification key as a byte array, we pass them + // as fields which makes it cheaper to verify in a circuit. + // + // The proof that is passed here will have been created using the `generateInnerProof` + // method. + // + // The number of public inputs denotes how many public inputs are in the inner proof. + async generateIntermediateProofArtifacts(proof, numOfPublicInputs = 0) { + 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 + // TODO how long it takes. + await this.api.acirInitVerificationKey(this.acirComposer); + // Note: If you don't init verification key, `acirSerializeVerificationKeyIntoFields`` will just hang on serialization + const vk = await this.api.acirSerializeVerificationKeyIntoFields(this.acirComposer); + return { + proofAsFields: proofAsFields.map((p) => p.toString()), + vkAsFields: vk[0].map((vk) => vk.toString()), + vkHash: vk[1].toString(), + }; + } + async verifyIntermediateProof(proof) { + const optimizeForVerifyInCircuit = true; + return this.verifyFinalProof(proof, optimizeForVerifyInCircuit); + } + async verifyFinalProof(proof, optimizeForVerifyInCircuit = false) { + await this.instantiate(); + await this.api.acirInitVerificationKey(this.acirComposer); + return await this.api.acirVerifyProof(this.acirComposer, proof, optimizeForVerifyInCircuit); + } + async destroy() { + await this.api.destroy(); + } +} diff --git a/tooling/backend_barretenberg/package.json b/tooling/backend_barretenberg/package.json new file mode 100644 index 00000000000..8c40617992c --- /dev/null +++ b/tooling/backend_barretenberg/package.json @@ -0,0 +1,39 @@ +{ + "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", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" + }, + "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", + "clean": "rm -r ./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:*" + }, + "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/test/backend/barretenberg.ts b/tooling/backend_barretenberg/src/index.ts similarity index 58% rename from tooling/noir_js/test/backend/barretenberg.ts rename to tooling/backend_barretenberg/src/index.ts index 55b7bbf2b7c..3cc804ba94f 100644 --- a/tooling/noir_js/test/backend/barretenberg.ts +++ b/tooling/backend_barretenberg/src/index.ts @@ -1,49 +1,53 @@ /* 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 '../../src/backend/backend_interface.js'; + +// import { Barretenberg, Crs, RawBuffer } from '@aztec/bb.js/dest/browser/types/index.js'; +import { decompressSync as gunzip } from 'fflate'; +import { Backend, CompiledCircuit } from '@noir-lang/types'; + +// 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 { + return Uint8Array.from(atob(input), (c) => c.charCodeAt(0)); +} + +// Converts an bytecode to a Uint8Array +export function acirToUint8Array(base64EncodedBytecode): Uint8Array { + const compressedByteCode = base64Decode(base64EncodedBytecode); + return gunzip(compressedByteCode); +} 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 }; + 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 @@ -51,9 +55,16 @@ export class BarretenbergBackend implements Backend { // // The settings for this proof are the same as the settings for a "normal" proof // ie one that is not in the recursive setting. - async generateFinalProof(decompressedWitness: Uint8Array): Promise { - const makeEasyToVerifyInCircuit = false; - return this.generateProof(decompressedWitness, makeEasyToVerifyInCircuit); + async generateFinalProof(decompressedWitness: Uint8Array, optimizeForVerifyInCircuit = false): Promise { + await this.instantiate(); + const proof = await this.api.acirCreateProof( + this.acirComposer, + this.acirUncompressedBytecode, + decompressedWitness, + optimizeForVerifyInCircuit, + ); + + return proof; } // Generates an inner proof. This is the proof that will be verified @@ -68,19 +79,8 @@ export class BarretenbergBackend implements Backend { // generate the proof using components that will make the proof // easier to verify in a circuit. async generateIntermediateProof(witness: Uint8Array): Promise { - const makeEasyToVerifyInCircuit = true; - return this.generateProof(witness, makeEasyToVerifyInCircuit); - } - - async generateProof(decompressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { - const proof = await this.api.acirCreateProof( - this.acirComposer, - this.acirUncompressedBytecode, - decompressedWitness, - makeEasyToVerifyInCircuit, - ); - - return proof; + const optimizeForVerifyInCircuit = true; + return this.generateFinalProof(witness, optimizeForVerifyInCircuit); } // Generates artifacts that will be passed to a circuit that will verify this proof. @@ -100,6 +100,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 @@ -116,20 +117,15 @@ export class BarretenbergBackend implements Backend { }; } - async verifyFinalProof(proof: Uint8Array): Promise { - const makeEasyToVerifyInCircuit = false; - const verified = await this.verifyProof(proof, makeEasyToVerifyInCircuit); - return verified; - } - async verifyIntermediateProof(proof: Uint8Array): Promise { - const makeEasyToVerifyInCircuit = true; - return this.verifyProof(proof, makeEasyToVerifyInCircuit); + const optimizeForVerifyInCircuit = true; + return this.verifyFinalProof(proof, optimizeForVerifyInCircuit); } - async verifyProof(proof: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { + async verifyFinalProof(proof: Uint8Array, optimizeForVerifyInCircuit = false): Promise { + await this.instantiate(); await this.api.acirInitVerificationKey(this.acirComposer); - return await this.api.acirVerifyProof(this.acirComposer, proof, makeEasyToVerifyInCircuit); + return await this.api.acirVerifyProof(this.acirComposer, proof, optimizeForVerifyInCircuit); } async destroy(): Promise { diff --git a/tooling/backend_barretenberg/tsconfig.cjs.json b/tooling/backend_barretenberg/tsconfig.cjs.json new file mode 100644 index 00000000000..15d273af62e --- /dev/null +++ b/tooling/backend_barretenberg/tsconfig.cjs.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "./lib/cjs" + }, +} diff --git a/tooling/backend_barretenberg/tsconfig.json b/tooling/backend_barretenberg/tsconfig.json new file mode 100644 index 00000000000..bd76f50af0f --- /dev/null +++ b/tooling/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/tooling/noir_js/lib/base64_decode.cjs b/tooling/noir_js/lib/base64_decode.cjs new file mode 100644 index 00000000000..607fe6f4db4 --- /dev/null +++ b/tooling/noir_js/lib/base64_decode.cjs @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.base64Decode = void 0; +// Since this is a simple function, we can use feature detection to +// see if we are in the nodeJs environment or the browser environment. +function base64Decode(input) { + 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.'); + } +} +exports.base64Decode = base64Decode; diff --git a/tooling/noir_js/lib/base64_decode.d.ts b/tooling/noir_js/lib/base64_decode.d.ts new file mode 100644 index 00000000000..01a69a6f336 --- /dev/null +++ b/tooling/noir_js/lib/base64_decode.d.ts @@ -0,0 +1 @@ +export declare function base64Decode(input: string): Uint8Array; diff --git a/tooling/noir_js/lib/base64_decode.mjs b/tooling/noir_js/lib/base64_decode.mjs new file mode 100644 index 00000000000..257c89b2c31 --- /dev/null +++ b/tooling/noir_js/lib/base64_decode.mjs @@ -0,0 +1,15 @@ +// 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) { + 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/lib/index.cjs b/tooling/noir_js/lib/index.cjs new file mode 100644 index 00000000000..1366dd9502b --- /dev/null +++ b/tooling/noir_js/lib/index.cjs @@ -0,0 +1,37 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Noir = exports.witnessMapToUint8Array = exports.acirToUint8Array = exports.generateWitness = exports.abi = exports.acvm = void 0; +const acvm = __importStar(require("@noir-lang/acvm_js")); +exports.acvm = acvm; +const abi = __importStar(require("@noir-lang/noirc_abi")); +exports.abi = abi; +var witness_generation_js_1 = require("./witness_generation.cjs"); +Object.defineProperty(exports, "generateWitness", { enumerable: true, get: function () { return witness_generation_js_1.generateWitness; } }); +var serialize_js_1 = require("./serialize.cjs"); +Object.defineProperty(exports, "acirToUint8Array", { enumerable: true, get: function () { return serialize_js_1.acirToUint8Array; } }); +Object.defineProperty(exports, "witnessMapToUint8Array", { enumerable: true, get: function () { return serialize_js_1.witnessMapToUint8Array; } }); +var program_js_1 = require("./program.cjs"); +Object.defineProperty(exports, "Noir", { enumerable: true, get: function () { return program_js_1.Noir; } }); diff --git a/tooling/noir_js/lib/index.d.ts b/tooling/noir_js/lib/index.d.ts new file mode 100644 index 00000000000..18fd81d1594 --- /dev/null +++ b/tooling/noir_js/lib/index.d.ts @@ -0,0 +1,6 @@ +import * as acvm from '@noir-lang/acvm_js'; +import * as abi from '@noir-lang/noirc_abi'; +export { acvm, abi }; +export { generateWitness } from './witness_generation.js'; +export { acirToUint8Array, witnessMapToUint8Array } from './serialize.js'; +export { Noir } from './program.js'; diff --git a/tooling/noir_js/lib/index.mjs b/tooling/noir_js/lib/index.mjs new file mode 100644 index 00000000000..0014b16da27 --- /dev/null +++ b/tooling/noir_js/lib/index.mjs @@ -0,0 +1,6 @@ +import * as acvm from '@noir-lang/acvm_js'; +import * as abi from '@noir-lang/noirc_abi'; +export { acvm, abi }; +export { generateWitness } from "./witness_generation.mjs"; +export { acirToUint8Array, witnessMapToUint8Array } from "./serialize.mjs"; +export { Noir } from "./program.mjs"; diff --git a/tooling/noir_js/lib/program.cjs b/tooling/noir_js/lib/program.cjs new file mode 100644 index 00000000000..6350a7e034e --- /dev/null +++ b/tooling/noir_js/lib/program.cjs @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Noir = void 0; +const witness_generation_js_1 = require("./witness_generation.cjs"); +class Noir { + circuit; + backend; + constructor(circuit, backend) { + this.circuit = circuit; + this.backend = backend; + } + // Initial inputs to your program + async generateFinalProof(inputs) { + const serializedWitness = await (0, witness_generation_js_1.generateWitness)(this.circuit, inputs); + return this.backend.generateFinalProof(serializedWitness); + } + async verifyFinalProof(proof) { + return this.backend.verifyFinalProof(proof); + } +} +exports.Noir = Noir; diff --git a/tooling/noir_js/lib/program.d.ts b/tooling/noir_js/lib/program.d.ts new file mode 100644 index 00000000000..49653c8d7fb --- /dev/null +++ b/tooling/noir_js/lib/program.d.ts @@ -0,0 +1,8 @@ +import { Backend, CompiledCircuit } from '@noir-lang/types'; +export declare class Noir { + private circuit; + private backend; + constructor(circuit: CompiledCircuit, backend: Backend); + generateFinalProof(inputs: any): Promise; + verifyFinalProof(proof: Uint8Array): Promise; +} diff --git a/tooling/noir_js/lib/program.mjs b/tooling/noir_js/lib/program.mjs new file mode 100644 index 00000000000..a7fa1c6545f --- /dev/null +++ b/tooling/noir_js/lib/program.mjs @@ -0,0 +1,17 @@ +import { generateWitness } from "./witness_generation.mjs"; +export class Noir { + circuit; + backend; + constructor(circuit, backend) { + this.circuit = circuit; + this.backend = backend; + } + // Initial inputs to your program + async generateFinalProof(inputs) { + const serializedWitness = await generateWitness(this.circuit, inputs); + return this.backend.generateFinalProof(serializedWitness); + } + async verifyFinalProof(proof) { + return this.backend.verifyFinalProof(proof); + } +} diff --git a/tooling/noir_js/lib/serialize.cjs b/tooling/noir_js/lib/serialize.cjs new file mode 100644 index 00000000000..ac367178e8f --- /dev/null +++ b/tooling/noir_js/lib/serialize.cjs @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.acirToUint8Array = exports.witnessMapToUint8Array = void 0; +const acvm_js_1 = require("@noir-lang/acvm_js"); +const fflate_1 = require("fflate"); +const base64_decode_js_1 = require("./base64_decode.cjs"); +// After solving the witness, to pass it a backend, we need to serialize it to a Uint8Array +function witnessMapToUint8Array(solvedWitness) { + // TODO: We just want to serialize, but this will zip up the witness + // TODO so its not ideal + const compressedWitness = (0, acvm_js_1.compressWitness)(solvedWitness); + return (0, fflate_1.decompressSync)(compressedWitness); +} +exports.witnessMapToUint8Array = witnessMapToUint8Array; +// Converts an bytecode to a Uint8Array +function acirToUint8Array(base64EncodedBytecode) { + const compressedByteCode = (0, base64_decode_js_1.base64Decode)(base64EncodedBytecode); + return (0, fflate_1.decompressSync)(compressedByteCode); +} +exports.acirToUint8Array = acirToUint8Array; diff --git a/tooling/noir_js/lib/serialize.d.ts b/tooling/noir_js/lib/serialize.d.ts new file mode 100644 index 00000000000..30f13d16421 --- /dev/null +++ b/tooling/noir_js/lib/serialize.d.ts @@ -0,0 +1,3 @@ +import { WitnessMap } from '@noir-lang/acvm_js'; +export declare function witnessMapToUint8Array(solvedWitness: WitnessMap): Uint8Array; +export declare function acirToUint8Array(base64EncodedBytecode: any): Uint8Array; diff --git a/tooling/noir_js/lib/serialize.mjs b/tooling/noir_js/lib/serialize.mjs new file mode 100644 index 00000000000..f126dc0112d --- /dev/null +++ b/tooling/noir_js/lib/serialize.mjs @@ -0,0 +1,15 @@ +import { compressWitness } from '@noir-lang/acvm_js'; +import { decompressSync as gunzip } from 'fflate'; +import { base64Decode } from "./base64_decode.mjs"; +// After solving the witness, to pass it a backend, we need to serialize it to a Uint8Array +export function witnessMapToUint8Array(solvedWitness) { + // TODO: We just want to serialize, but this will zip up the witness + // TODO so its not ideal + const compressedWitness = compressWitness(solvedWitness); + return gunzip(compressedWitness); +} +// Converts an bytecode to a Uint8Array +export function acirToUint8Array(base64EncodedBytecode) { + const compressedByteCode = base64Decode(base64EncodedBytecode); + return gunzip(compressedByteCode); +} diff --git a/tooling/noir_js/lib/witness_generation.cjs b/tooling/noir_js/lib/witness_generation.cjs new file mode 100644 index 00000000000..c2e0abebed5 --- /dev/null +++ b/tooling/noir_js/lib/witness_generation.cjs @@ -0,0 +1,51 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generateWitness = void 0; +const noirc_abi_1 = __importStar(require("@noir-lang/noirc_abi")); +const base64_decode_js_1 = require("./base64_decode.cjs"); +const acvm_js_1 = __importStar(require("@noir-lang/acvm_js")); +const serialize_js_1 = require("./serialize.cjs"); +// Generates the witnesses needed to feed into the chosen proving system +async function generateWitness(compiledProgram, inputs) { + if (typeof noirc_abi_1.default === 'function' && typeof acvm_js_1.default === 'function') { + await (0, noirc_abi_1.default)(); + await (0, acvm_js_1.default)(); + } + // Throws on ABI encoding error + const witnessMap = (0, noirc_abi_1.abiEncode)(compiledProgram.abi, inputs, null); + // Execute the circuit to generate the rest of the witnesses and serialize + // them into a Uint8Array. + try { + const solvedWitness = await (0, acvm_js_1.executeCircuit)((0, base64_decode_js_1.base64Decode)(compiledProgram.bytecode), witnessMap, () => { + throw Error('unexpected oracle during execution'); + }); + return (0, serialize_js_1.witnessMapToUint8Array)(solvedWitness); + } + catch (err) { + throw new Error(`Circuit execution failed: ${err}`); + } +} +exports.generateWitness = generateWitness; diff --git a/tooling/noir_js/lib/witness_generation.d.ts b/tooling/noir_js/lib/witness_generation.d.ts new file mode 100644 index 00000000000..06275674d0e --- /dev/null +++ b/tooling/noir_js/lib/witness_generation.d.ts @@ -0,0 +1 @@ +export declare function generateWitness(compiledProgram: any, inputs: any): Promise; diff --git a/tooling/noir_js/lib/witness_generation.mjs b/tooling/noir_js/lib/witness_generation.mjs new file mode 100644 index 00000000000..f206d3effd3 --- /dev/null +++ b/tooling/noir_js/lib/witness_generation.mjs @@ -0,0 +1,24 @@ +import initABIWASM, { abiEncode } from '@noir-lang/noirc_abi'; +import { base64Decode } from "./base64_decode.mjs"; +import initACVMWASM, { executeCircuit } from '@noir-lang/acvm_js'; +import { witnessMapToUint8Array } from "./serialize.mjs"; +// Generates the witnesses needed to feed into the chosen proving system +export async function generateWitness(compiledProgram, inputs) { + if (typeof initABIWASM === 'function' && typeof initACVMWASM === 'function') { + await initABIWASM(); + await initACVMWASM(); + } + // Throws on ABI encoding error + const witnessMap = abiEncode(compiledProgram.abi, inputs, null); + // Execute the circuit to generate the rest of the witnesses and serialize + // them into a Uint8Array. + try { + const solvedWitness = await executeCircuit(base64Decode(compiledProgram.bytecode), witnessMap, () => { + throw Error('unexpected oracle during execution'); + }); + return witnessMapToUint8Array(solvedWitness); + } + catch (err) { + throw new Error(`Circuit execution failed: ${err}`); + } +} diff --git a/tooling/noir_js/package.json b/tooling/noir_js/package.json index a79c4437d6a..c5df2ea0d7c 100644 --- a/tooling/noir_js/package.json +++ b/tooling/noir_js/package.json @@ -10,8 +10,12 @@ "dependencies": { "@noir-lang/acvm_js": "workspace:*", "@noir-lang/noirc_abi": "workspace:*", + "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, + "peerDependencies": { + "@noir-lang/backend_barretenberg": "workspace:*" + }, "files": [ "lib", "package.json" @@ -37,7 +41,6 @@ "clean": "rm -rf ./lib" }, "devDependencies": { - "@aztec/bb.js": "0.7.2", "@types/chai": "^4", "@types/mocha": "^10.0.1", "@types/node": "^20.6.2", @@ -51,4 +54,4 @@ "tsc-multi": "^1.1.0", "typescript": "^5.2.2" } -} \ No newline at end of file +} diff --git a/tooling/noir_js/src/index.ts b/tooling/noir_js/src/index.ts index 3672db76638..4578a6cd568 100644 --- a/tooling/noir_js/src/index.ts +++ b/tooling/noir_js/src/index.ts @@ -5,3 +5,4 @@ export { acvm, abi }; export { generateWitness } from './witness_generation.js'; export { acirToUint8Array, witnessMapToUint8Array } from './serialize.js'; +export { Noir } from './program.js'; diff --git a/tooling/noir_js/src/program.ts b/tooling/noir_js/src/program.ts index 851f79cb35d..a54e7767517 100644 --- a/tooling/noir_js/src/program.ts +++ b/tooling/noir_js/src/program.ts @@ -1,15 +1,15 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Backend } from './backend/backend_interface.js'; +import { Backend, CompiledCircuit } from '@noir-lang/types'; import { generateWitness } from './witness_generation.js'; export class Noir { constructor( - private circuit: { bytecode: string; abi: any }, + private circuit: CompiledCircuit, private backend: Backend, ) {} // Initial inputs to your program - async generateFinalProof(inputs: any): Promise { + async generateFinalProof(inputs: any, optimizeRecursionProofForRecursion = false): Promise { const serializedWitness = await generateWitness(this.circuit, inputs); return this.backend.generateFinalProof(serializedWitness); } diff --git a/tooling/noir_js/src/witness_generation.ts b/tooling/noir_js/src/witness_generation.ts index c4f129dd2a6..42384e9d65c 100644 --- a/tooling/noir_js/src/witness_generation.ts +++ b/tooling/noir_js/src/witness_generation.ts @@ -1,13 +1,19 @@ -import { abiEncode } from '@noir-lang/noirc_abi'; +import initABIWASM, { abiEncode } from '@noir-lang/noirc_abi'; import { base64Decode } from './base64_decode.js'; -import { executeCircuit } from '@noir-lang/acvm_js'; +import initACVMWASM, { executeCircuit } from '@noir-lang/acvm_js'; import { witnessMapToUint8Array } from './serialize.js'; // Generates the witnesses needed to feed into the chosen proving system -export async function generateWitness( - compiledProgram: { bytecode: string; abi: unknown }, - inputs: unknown, -): Promise { +export async function generateWitness(compiledProgram, inputs): Promise { + try { + if (typeof initABIWASM === 'function' && typeof initACVMWASM === 'function') { + await initABIWASM(); + await initACVMWASM(); + } + } catch (err) { + throw new Error(`Could not initialize noir_js libraries ${err}`); + } + // Throws on ABI encoding error const witnessMap = abiEncode(compiledProgram.abi, inputs, null); diff --git a/tooling/noir_js/test/node/cjs.test.cjs b/tooling/noir_js/test/node/cjs.test.cjs deleted file mode 100644 index b7b30d7dcdb..00000000000 --- a/tooling/noir_js/test/node/cjs.test.cjs +++ /dev/null @@ -1,80 +0,0 @@ -/* eslint-disable no-undef */ -/* eslint-disable @typescript-eslint/no-var-requires */ -const chai = require('chai'); -const assert_lt_json = require('../noir_compiled_examples/assert_lt/target/assert_lt.json'); -const noirjs = require('@noir-lang/noir_js'); - -it('generates witnesses successfully', async () => { - const inputs = { - x: '2', - y: '3', - }; - const _solvedWitness = await noirjs.generateWitness(assert_lt_json, inputs); -}); - -it('string input and number input are the same', async () => { - const inputsString = { - x: '2', - y: '3', - }; - const inputsNumber = { - x: 2, - y: 3, - }; - const solvedWitnessString = await noirjs.generateWitness(assert_lt_json, inputsString); - const solvedWitnessNumber = await noirjs.generateWitness(assert_lt_json, inputsNumber); - chai.expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); -}); - -it('string input and number input are the same', async () => { - const inputsString = { - x: '2', - y: '3', - }; - const inputsNumber = { - x: 2, - y: 3, - }; - - const solvedWitnessString = await noirjs.generateWitness(assert_lt_json, inputsString); - const solvedWitnessNumber = await noirjs.generateWitness(assert_lt_json, inputsNumber); - chai.expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); -}); - -it('0x prefixed string input for inputs will throw', async () => { - const inputsHexPrefix = { - x: '0x2', - y: '0x3', - }; - - try { - await noirjs.generateWitness(assert_lt_json, inputsHexPrefix); - chai.expect.fail( - 'Expected generatedWitness to throw, due to inputs being prefixed with 0x. Currently not supported', - ); - } catch (error) { - // Successfully errored due to 0x not being supported. Update this test once/if we choose - // to support 0x prefixed inputs. - } -}); - -describe('input validation', () => { - it('x should be a uint64 not a string', async () => { - const inputs = { - x: 'foo', - y: '3', - }; - - try { - await noirjs.generateWitness(assert_lt_json, inputs); - chai.expect.fail('Expected generatedWitness to throw, due to x not being convertible to a uint64'); - } catch (error) { - const knownError = error; - chai - .expect(knownError.message) - .to.equal( - 'Expected witness values to be integers, provided value causes `invalid digit found in string` error', - ); - } - }); -}); diff --git a/tooling/noir_js/test/node/e2e.test.cjs b/tooling/noir_js/test/node/e2e.test.cjs new file mode 100644 index 00000000000..66cb1e51027 --- /dev/null +++ b/tooling/noir_js/test/node/e2e.test.cjs @@ -0,0 +1,118 @@ +/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const { expect } = require('chai'); +const assert_lt_json = require('../noir_compiled_examples/assert_lt/target/assert_lt.json'); +const { Noir, generateWitness } = require('@noir-lang/noir_js'); +const { BarretenbergBackend } = require('@noir-lang/backend_barretenberg'); + +it('end-to-end proof creation and verification (outer)', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + + const backend = new BarretenbergBackend(assert_lt_json); + + const program = new Noir(assert_lt_json, backend); + + const proof = await program.generateFinalProof(inputs); + + const isValid = await program.verifyFinalProof(proof); + + expect(isValid).to.be.true; +}); + +it('end-to-end proof creation and verification (inner)', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + const serializedWitness = await generateWitness(assert_lt_json, inputs); + + // bb.js part + // + // Proof creation + const prover = new BarretenbergBackend(assert_lt_json); + + const proof = await prover.generateIntermediateProof(serializedWitness); + + // Proof verification + const isValid = await prover.verifyIntermediateProof(proof); + expect(isValid).to.be.true; +}); + +// The "real" workflow will involve a prover and a verifier on different systems. +// We cannot do this in our tests because they will panic with: +// `RuntimeError: null function or function signature mismatch` +// This happens when we we create a proof with one barretenberg instance and +// try to verify it with another. +// If this bug is fixed, we can remove this test and split barretenberg into +// a prover and verifier class to more accurately reflect what happens in production. +// If its not fixable, we can leave it in as documentation of this behavior. +it('[BUG] -- bb.js null function or function signature mismatch (different instance) ', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + const serializedWitness = await generateWitness(assert_lt_json, inputs); + + // bb.js part + const prover = new BarretenbergBackend(assert_lt_json); + + const proof = await prover.generateFinalProof(serializedWitness); + + try { + const verifier = new BarretenbergBackend(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.', + ); + } catch (error) { + const knownError = error; + expect(knownError.message).to.contain('null function or function signature mismatch'); + } +}); + +// This bug occurs when we use the same backend to create an inner proof and then an outer proof +// and then try to verify either one of them. +// +// The panic occurs when we try to verify the outer/inner proof that was created. +// If we only create one type of proof, then this works as expected. +// +// If we do not create an inner proof, then this will work as expected. +it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + const serializedWitness = await generateWitness(assert_lt_json, inputs); + + // bb.js part + // + // Proof creation + // + const prover = new BarretenbergBackend(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); + const _proofInner = await prover.generateIntermediateProof(serializedWitness); + + // Proof verification + // + try { + const isValidOuter = await prover.verifyFinalProof(proofOuter); + expect(isValidOuter).to.be.true; + // We can also try verifying an inner proof and it will fail. + // const isValidInner = await prover.verifyInnerProof(_proofInner); + // expect(isValidInner).to.be.true; + expect.fail('bb.js currently returns a bug when we try to verify an inner and outer proof with the same backend'); + } catch (error) { + const knownError = error; + expect(knownError.message).to.contain('null function or function signature mismatch'); + } +}); diff --git a/tooling/noir_js/test/node/e2e.test.ts b/tooling/noir_js/test/node/e2e.test.ts index 1120f08c81b..0ffcff33edc 100644 --- a/tooling/noir_js/test/node/e2e.test.ts +++ b/tooling/noir_js/test/node/e2e.test.ts @@ -1,8 +1,8 @@ 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 { Noir as NoirProgram, generateWitness } from '@noir-lang/noir_js'; +import { BarretenbergBackend } from '@noir-lang/backend_barretenberg'; it('end-to-end proof creation and verification (outer)', async () => { // Noir.Js part @@ -10,35 +10,14 @@ it('end-to-end proof creation and verification (outer)', async () => { x: '2', y: '3', }; - const serializedWitness = await generateWitness(assert_lt_json, inputs); + const backend = new BarretenbergBackend(assert_lt_json); - // bb.js part - // - // Proof creation - const prover = await Backend.initialize(assert_lt_json); - const proof = await prover.generateFinalProof(serializedWitness); + const program = new NoirProgram(assert_lt_json, backend); - // Proof verification - const isValid = await prover.verifyFinalProof(proof); - expect(isValid).to.be.true; -}); - -it('end-to-end proof creation and verification (outer) -- Program API', async () => { - // Noir.Js part - const inputs = { - x: '2', - y: '3', - }; - - // Initialize backend - const backend = await Backend.initialize(assert_lt_json); - // Initialize program - const program = new Noir(assert_lt_json, backend); - // Generate proof const proof = await program.generateFinalProof(inputs); - // Proof verification const isValid = await program.verifyFinalProof(proof); + expect(isValid).to.be.true; }); @@ -53,7 +32,8 @@ 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 BarretenbergBackend(assert_lt_json); + const proof = await prover.generateIntermediateProof(serializedWitness); // Proof verification @@ -82,12 +62,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 BarretenbergBackend(assert_lt_json); const proof = await prover.generateFinalProof(serializedWitness); try { - const verifier = await Backend.initialize(assert_lt_json); + const verifier = new BarretenbergBackend(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.', @@ -114,10 +94,8 @@ it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', const serializedWitness = await generateWitness(assert_lt_json, inputs); // bb.js part - // // Proof creation - // - const prover = await Backend.initialize(assert_lt_json); + const prover = new BarretenbergBackend(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_types/.eslintignore b/tooling/noir_js_types/.eslintignore new file mode 100644 index 00000000000..fcbc654a839 --- /dev/null +++ b/tooling/noir_js_types/.eslintignore @@ -0,0 +1,2 @@ +node_modules +test/backend/barretenberg.ts \ No newline at end of file diff --git a/tooling/noir_js_types/.eslintrc.cjs b/tooling/noir_js_types/.eslintrc.cjs new file mode 100644 index 00000000000..5a2cc7f1ec0 --- /dev/null +++ b/tooling/noir_js_types/.eslintrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: ['../../.eslintrc.js'], +}; diff --git a/tooling/noir_js_types/.gitignore b/tooling/noir_js_types/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/noir_js_types/.prettierrc b/tooling/noir_js_types/.prettierrc new file mode 100644 index 00000000000..ef937f9697a --- /dev/null +++ b/tooling/noir_js_types/.prettierrc @@ -0,0 +1,6 @@ +{ + "parser": "typescript", + "printWidth": 120, + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/tooling/noir_js/src/backend/backend_interface.ts b/tooling/noir_js_types/lib/types.ts similarity index 89% rename from tooling/noir_js/src/backend/backend_interface.ts rename to tooling/noir_js_types/lib/types.ts index c3b8e30cdb1..f55c94477d3 100644 --- a/tooling/noir_js/src/backend/backend_interface.ts +++ b/tooling/noir_js_types/lib/types.ts @@ -11,3 +11,8 @@ export interface Backend { verifyIntermediateProof(proof: Uint8Array): Promise; } + +export type CompiledCircuit = { + bytecode: string; + abi: object; +}; \ No newline at end of file diff --git a/tooling/noir_js_types/package.json b/tooling/noir_js_types/package.json new file mode 100644 index 00000000000..188b052273a --- /dev/null +++ b/tooling/noir_js_types/package.json @@ -0,0 +1,14 @@ +{ + "name": "@noir-lang/types", + "collaborators": [ + "The Noir Team " + ], + "version": "0.14.1", + "packageManager": "yarn@3.5.1", + "license": "(MIT OR Apache-2.0)", + "files": [ + "lib", + "package.json" + ], + "types": "lib/types.ts" +} diff --git a/yarn.lock b/yarn.lock index 2ec26866e93..e3cd9882132 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,23 +29,9 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.7.2": - version: 0.7.2 - resolution: "@aztec/bb.js@npm:0.7.2" - 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: 68299e5b0ef9583d78d1aaeb4e6a80cf5e049d45e48fb02798a41a2dfda0e68d729e8fbc1d23ab692914ce3857c729b48da9200fca49b838416b7988e5516d0b - languageName: node - linkType: hard - -"@aztec/bb.js@npm:^0.7.3": - version: 0.7.3 - resolution: "@aztec/bb.js@npm:0.7.3" +"@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 @@ -440,13 +426,28 @@ __metadata: languageName: unknown linkType: soft +"@noir-lang/backend_barretenberg@workspace:*, @noir-lang/backend_barretenberg@workspace:tooling/backend_barretenberg": + version: 0.0.0-use.local + resolution: "@noir-lang/backend_barretenberg@workspace:tooling/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 + prettier: 3.0.3 + typescript: 5.1.5 + 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" dependencies: - "@aztec/bb.js": 0.7.2 "@noir-lang/acvm_js": "workspace:*" "@noir-lang/noirc_abi": "workspace:*" + "@noir-lang/types": "workspace:*" "@types/chai": ^4 "@types/mocha": ^10.0.1 "@types/node": ^20.6.2 @@ -460,6 +461,8 @@ __metadata: ts-node: ^10.9.1 tsc-multi: ^1.1.0 typescript: ^5.2.2 + peerDependencies: + "@noir-lang/backend_barretenberg": "workspace:*" languageName: unknown linkType: soft @@ -519,6 +522,12 @@ __metadata: languageName: unknown linkType: soft +"@noir-lang/types@workspace:*, @noir-lang/types@workspace:tooling/noir_js_types": + version: 0.0.0-use.local + resolution: "@noir-lang/types@workspace:tooling/noir_js_types" + languageName: unknown + linkType: soft + "@npmcli/fs@npm:^3.1.0": version: 3.1.0 resolution: "@npmcli/fs@npm:3.1.0" @@ -4509,7 +4518,7 @@ __metadata: version: 0.0.0-use.local resolution: "integration-tests@workspace:compiler/integration-tests" dependencies: - "@aztec/bb.js": ^0.7.3 + "@noir-lang/backend_barretenberg": "workspace:*" "@noir-lang/noir_js": "workspace:*" "@noir-lang/noir_wasm": "workspace:*" "@noir-lang/source-resolver": "workspace:*" @@ -7542,6 +7551,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" @@ -7562,6 +7581,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"