diff --git a/barretenberg/acir_tests/browser-test-app/src/index.ts b/barretenberg/acir_tests/browser-test-app/src/index.ts index 45b39346ddf..5bd1f0a010c 100644 --- a/barretenberg/acir_tests/browser-test-app/src/index.ts +++ b/barretenberg/acir_tests/browser-test-app/src/index.ts @@ -9,34 +9,21 @@ async function runTest( witness: Uint8Array, threads?: number ) { - const { Barretenberg, RawBuffer, Crs } = await import("@aztec/bb.js"); - const CIRCUIT_SIZE = 2 ** 19; + const { UltraPlonkBackend, BarretenbergVerifier } = await import("@aztec/bb.js"); debug("starting test..."); - const api = await Barretenberg.new({ threads }); + const backend = new UltraPlonkBackend(bytecode, { threads }); + const proof = await backend.generateProof(witness); - // Important to init slab allocator as first thing, to ensure maximum memory efficiency. - await api.commonInitSlabAllocator(CIRCUIT_SIZE); + const verificationKey = await backend.getVerificationKey(); + await backend.destroy(); - // Plus 1 needed! - const crs = await Crs.new(CIRCUIT_SIZE + 1); - await api.srsInitSrs( - new RawBuffer(crs.getG1Data()), - crs.numPoints, - new RawBuffer(crs.getG2Data()) - ); - - const acirComposer = await api.acirNewAcirComposer(CIRCUIT_SIZE); - const proof = await api.acirCreateProof( - acirComposer, - bytecode, - witness, - ); debug(`verifying...`); - const verified = await api.acirVerifyProof(acirComposer, proof); + const verifier = new BarretenbergVerifier({ threads }); + const verified = await verifier.verifyUltraplonkProof(proof, verificationKey); debug(`verified: ${verified}`); - await api.destroy(); + await verifier.destroy(); debug("test complete."); return verified; diff --git a/barretenberg/ts/src/barretenberg/backend.ts b/barretenberg/ts/src/barretenberg/backend.ts new file mode 100644 index 00000000000..663dd666678 --- /dev/null +++ b/barretenberg/ts/src/barretenberg/backend.ts @@ -0,0 +1,203 @@ +import { BackendOptions, Barretenberg } from './index.js'; +import { RawBuffer } from '../types/raw_buffer.js'; + +export class UltraPlonkBackend { + // 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. + + protected api!: Barretenberg; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected acirComposer: any; + + constructor(protected acirUncompressedBytecode: Uint8Array, protected options: BackendOptions = { threads: 1 }) {} + + /** @ignore */ + async instantiate(): Promise { + if (!this.api) { + if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { + this.options.threads = navigator.hardwareConcurrency; + } else { + try { + const os = await import('os'); + this.options.threads = os.cpus().length; + } catch (e) { + console.log('Could not detect environment. Falling back to one thread.', e); + } + } + const api = await Barretenberg.new(this.options); + + const honkRecursion = false; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes( + this.acirUncompressedBytecode, + honkRecursion, + ); + + await api.initSRSForCircuitSize(subgroupSize); + this.acirComposer = await api.acirNewAcirComposer(subgroupSize); + await api.acirInitProvingKey(this.acirComposer, this.acirUncompressedBytecode); + this.api = api; + } + } + + /** @description Generates a proof */ + async generateProof(uncompressedWitness: Uint8Array): Promise { + await this.instantiate(); + return this.api.acirCreateProof(this.acirComposer, this.acirUncompressedBytecode, uncompressedWitness); + } + + /** + * 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 a circuit + * that has the #[recursive] attribute on its `main` method. + * + * The number of public inputs denotes how many public inputs are in the inner proof. + * + * @example + * ```typescript + * const artifacts = await backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs); + * ``` + */ + async generateRecursiveProofArtifacts( + proof: Uint8Array, + numOfPublicInputs = 0, + ): Promise<{ + proofAsFields: string[]; + vkAsFields: string[]; + vkHash: string; + }> { + await this.instantiate(); + const proofAsFields = ( + await this.api.acirSerializeProofIntoFields(this.acirComposer, proof, numOfPublicInputs) + ).slice(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(), + }; + } + + /** @description Verifies a proof */ + async verifyProof(proof: Uint8Array): Promise { + await this.instantiate(); + await this.api.acirInitVerificationKey(this.acirComposer); + return await this.api.acirVerifyProof(this.acirComposer, proof); + } + + async getVerificationKey(): Promise { + await this.instantiate(); + await this.api.acirInitVerificationKey(this.acirComposer); + return await this.api.acirGetVerificationKey(this.acirComposer); + } + + async destroy(): Promise { + if (!this.api) { + return; + } + await this.api.destroy(); + } +} + +export class UltraHonkBackend { + // These type assertions are used so that we don't + // have to initialize `api` in the constructor. + // These are initialized asynchronously in the `init` function, + // constructors cannot be asynchronous which is why we do this. + + protected api!: Barretenberg; + + constructor(protected acirUncompressedBytecode: Uint8Array, protected options: BackendOptions = { threads: 1 }) {} + + /** @ignore */ + async instantiate(): Promise { + if (!this.api) { + if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { + this.options.threads = navigator.hardwareConcurrency; + } else { + try { + const os = await import('os'); + this.options.threads = os.cpus().length; + } catch (e) { + console.log('Could not detect environment. Falling back to one thread.', e); + } + } + const api = await Barretenberg.new(this.options); + const honkRecursion = true; + await api.acirInitSRS(this.acirUncompressedBytecode, honkRecursion); + + // We don't init a proving key here in the Honk API + // await api.acirInitProvingKey(this.acirComposer, this.acirUncompressedBytecode); + this.api = api; + } + } + + async generateProof(uncompressedWitness: Uint8Array): Promise { + await this.instantiate(); + return this.api.acirProveUltraHonk(this.acirUncompressedBytecode, uncompressedWitness); + } + + async verifyProof(proof: Uint8Array): Promise { + await this.instantiate(); + const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); + + return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(vkBuf)); + } + + async getVerificationKey(): Promise { + await this.instantiate(); + return await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); + } + + // TODO(https://github.com/noir-lang/noir/issues/5661): Update this to handle Honk recursive aggregation in the browser once it is ready in the backend itself + async generateRecursiveProofArtifacts( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _proof: Uint8Array, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _numOfPublicInputs: number, + ): Promise<{ proofAsFields: string[]; vkAsFields: string[]; vkHash: string }> { + await this.instantiate(); + // TODO(https://github.com/noir-lang/noir/issues/5661): This needs to be updated to handle recursive aggregation. + // There is still a proofAsFields method but we could consider getting rid of it as the proof itself + // is a list of field elements. + // UltraHonk also does not have public inputs directly prepended to the proof and they are still instead + // inserted at an offset. + // const proof = reconstructProofWithPublicInputs(proofData); + // const proofAsFields = (await this.api.acirProofAsFieldsUltraHonk(proof)).slice(numOfPublicInputs); + + // TODO: perhaps we should put this in the init function. Need to benchmark + // TODO how long it takes. + const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); + const vk = await this.api.acirVkAsFieldsUltraHonk(vkBuf); + + return { + // TODO(https://github.com/noir-lang/noir/issues/5661) + proofAsFields: [], + vkAsFields: vk.map(vk => vk.toString()), + // We use an empty string for the vk hash here as it is unneeded as part of the recursive artifacts + // The user can be expected to hash the vk inside their circuit to check whether the vk is the circuit + // they expect + vkHash: '', + }; + } + + async destroy(): Promise { + if (!this.api) { + return; + } + await this.api.destroy(); + } +} diff --git a/barretenberg/ts/src/barretenberg/index.ts b/barretenberg/ts/src/barretenberg/index.ts index 73e935d3372..264f8e91d9b 100644 --- a/barretenberg/ts/src/barretenberg/index.ts +++ b/barretenberg/ts/src/barretenberg/index.ts @@ -5,6 +5,11 @@ import { BarretenbergWasmMain, BarretenbergWasmMainWorker } from '../barretenber import { getRemoteBarretenbergWasm } from '../barretenberg_wasm/helpers/index.js'; import { BarretenbergWasmWorker, fetchModuleAndThreads } from '../barretenberg_wasm/index.js'; import createDebug from 'debug'; +import { Crs } from '../crs/index.js'; +import { RawBuffer } from '../types/raw_buffer.js'; + +export { BarretenbergVerifier } from './verifier.js'; +export { UltraPlonkBackend, UltraHonkBackend } from './backend.js'; const debug = createDebug('bb.js:wasm'); @@ -40,6 +45,18 @@ export class Barretenberg extends BarretenbergApi { return await this.wasm.getNumThreads(); } + async initSRSForCircuitSize(circuitSize: number): Promise { + const crs = await Crs.new(circuitSize + 1); + await this.commonInitSlabAllocator(circuitSize); + await this.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); + } + + async acirInitSRS(bytecode: Uint8Array, honkRecursion: boolean): Promise { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_exact, _total, subgroupSize] = await this.acirGetCircuitSizes(bytecode, honkRecursion); + return this.initSRSForCircuitSize(subgroupSize); + } + async destroy() { await this.wasm.destroy(); await this.worker.terminate(); diff --git a/barretenberg/ts/src/barretenberg/verifier.ts b/barretenberg/ts/src/barretenberg/verifier.ts new file mode 100644 index 00000000000..0937ca814ea --- /dev/null +++ b/barretenberg/ts/src/barretenberg/verifier.ts @@ -0,0 +1,63 @@ +import { BackendOptions, Barretenberg } from './index.js'; +import { RawBuffer } from '../types/raw_buffer.js'; + +// TODO: once UP is removed we can just roll this into the bas `Barretenberg` class. + +export class BarretenbergVerifier { + // 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. + + private api!: Barretenberg; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private acirComposer: any; + + constructor(private options: BackendOptions = { threads: 1 }) {} + + /** @ignore */ + async instantiate(): Promise { + if (!this.api) { + if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { + this.options.threads = navigator.hardwareConcurrency; + } else { + try { + const os = await import('os'); + this.options.threads = os.cpus().length; + } catch (e) { + console.log('Could not detect environment. Falling back to one thread.', e); + } + } + + const api = await Barretenberg.new(this.options); + await api.initSRSForCircuitSize(0); + + this.acirComposer = await api.acirNewAcirComposer(0); + this.api = api; + } + } + + /** @description Verifies a proof */ + async verifyUltraplonkProof(proof: Uint8Array, verificationKey: Uint8Array): Promise { + await this.instantiate(); + // The verifier can be used for a variety of ACIR programs so we should not assume that it + // is preloaded with the correct verification key. + await this.api.acirLoadVerificationKey(this.acirComposer, new RawBuffer(verificationKey)); + + return await this.api.acirVerifyProof(this.acirComposer, proof); + } + + /** @description Verifies a proof */ + async verifyUltrahonkProof(proof: Uint8Array, verificationKey: Uint8Array): Promise { + await this.instantiate(); + + return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(verificationKey)); + } + + async destroy(): Promise { + if (!this.api) { + return; + } + await this.api.destroy(); + } +} diff --git a/barretenberg/ts/src/index.ts b/barretenberg/ts/src/index.ts index 49ce2404202..a58a25203a0 100644 --- a/barretenberg/ts/src/index.ts +++ b/barretenberg/ts/src/index.ts @@ -1,3 +1,10 @@ export { Crs } from './crs/index.js'; -export { Barretenberg, BarretenbergSync } from './barretenberg/index.js'; +export { + BackendOptions, + Barretenberg, + BarretenbergSync, + BarretenbergVerifier, + UltraPlonkBackend, + UltraHonkBackend, +} from './barretenberg/index.js'; export { RawBuffer, Fr } from './types/index.js'; diff --git a/barretenberg/ts/webpack.config.js b/barretenberg/ts/webpack.config.js index 25b55785b4f..6a809547b87 100644 --- a/barretenberg/ts/webpack.config.js +++ b/barretenberg/ts/webpack.config.js @@ -52,6 +52,9 @@ export default { ], resolve: { plugins: [new ResolveTypeScriptPlugin()], + fallback: { + "os": false + } }, devServer: { hot: false, diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts index 4fd256a7a81..791d9dd7d3f 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts @@ -1,74 +1,26 @@ -import { decompressSync as gunzip } from 'fflate'; import { acirToUint8Array } from './serialize.js'; import { Backend, CompiledCircuit, ProofData, VerifierBackend } from '@noir-lang/types'; -import { BackendOptions } from './types.js'; import { deflattenFields } from './public_inputs.js'; import { reconstructProofWithPublicInputs, reconstructProofWithPublicInputsHonk } from './verifier.js'; -import { type Barretenberg } from '@aztec/bb.js'; +import { BackendOptions, UltraPlonkBackend, UltraHonkBackend as UltraHonkBackendInternal } from '@aztec/bb.js'; +import { decompressSync as gunzip } from 'fflate'; // This is the number of bytes in a UltraPlonk proof // minus the public inputs. const numBytesInProofWithoutPublicInputs: number = 2144; export class BarretenbergBackend implements Backend, VerifierBackend { - // 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. - - protected api!: Barretenberg; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - protected acirComposer: any; - protected acirUncompressedBytecode: Uint8Array; + protected backend!: UltraPlonkBackend; - constructor( - acirCircuit: CompiledCircuit, - protected options: BackendOptions = { threads: 1 }, - ) { + constructor(acirCircuit: CompiledCircuit, options: BackendOptions = { threads: 1 }) { const acirBytecodeBase64 = acirCircuit.bytecode; - this.acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); - } - - /** @ignore */ - async instantiate(): Promise { - if (!this.api) { - if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { - this.options.threads = navigator.hardwareConcurrency; - } else { - try { - const os = await import('os'); - this.options.threads = os.cpus().length; - } catch (e) { - console.log('Could not detect environment. Falling back to one thread.', e); - } - } - const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); - const api = await Barretenberg.new(this.options); - - const honkRecursion = false; - const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes( - this.acirUncompressedBytecode, - honkRecursion, - ); - - 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); - await api.acirInitProvingKey(this.acirComposer, this.acirUncompressedBytecode); - this.api = api; - } + const acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); + this.backend = new UltraPlonkBackend(acirUncompressedBytecode, options); } /** @description Generates a proof */ async generateProof(compressedWitness: Uint8Array): Promise { - await this.instantiate(); - const proofWithPublicInputs = await this.api.acirCreateProof( - this.acirComposer, - this.acirUncompressedBytecode, - gunzip(compressedWitness), - ); + const proofWithPublicInputs = await this.backend.generateProof(gunzip(compressedWitness)); const splitIndex = proofWithPublicInputs.length - numBytesInProofWithoutPublicInputs; @@ -103,45 +55,22 @@ export class BarretenbergBackend implements Backend, VerifierBackend { vkAsFields: string[]; vkHash: string; }> { - await this.instantiate(); const proof = reconstructProofWithPublicInputs(proofData); - const proofAsFields = ( - await this.api.acirSerializeProofIntoFields(this.acirComposer, proof, numOfPublicInputs) - ).slice(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(), - }; + return this.backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs); } /** @description Verifies a proof */ async verifyProof(proofData: ProofData): Promise { const proof = reconstructProofWithPublicInputs(proofData); - await this.instantiate(); - await this.api.acirInitVerificationKey(this.acirComposer); - return await this.api.acirVerifyProof(this.acirComposer, proof); + return this.backend.verifyProof(proof); } async getVerificationKey(): Promise { - await this.instantiate(); - await this.api.acirInitVerificationKey(this.acirComposer); - return await this.api.acirGetVerificationKey(this.acirComposer); + return this.backend.getVerificationKey(); } async destroy(): Promise { - if (!this.api) { - return; - } - await this.api.destroy(); + await this.backend.destroy(); } } @@ -157,54 +86,16 @@ export class UltraHonkBackend implements Backend, VerifierBackend { // These are initialized asynchronously in the `init` function, // constructors cannot be asynchronous which is why we do this. - protected api!: Barretenberg; - protected acirUncompressedBytecode: Uint8Array; + protected backend!: UltraHonkBackendInternal; - constructor( - acirCircuit: CompiledCircuit, - protected options: BackendOptions = { threads: 1 }, - ) { + constructor(acirCircuit: CompiledCircuit, options: BackendOptions = { threads: 1 }) { const acirBytecodeBase64 = acirCircuit.bytecode; - this.acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); + const acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); + this.backend = new UltraHonkBackendInternal(acirUncompressedBytecode, options); } - /** @ignore */ - async instantiate(): Promise { - if (!this.api) { - if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { - this.options.threads = navigator.hardwareConcurrency; - } else { - try { - const os = await import('os'); - this.options.threads = os.cpus().length; - } catch (e) { - console.log('Could not detect environment. Falling back to one thread.', e); - } - } - const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); - const api = await Barretenberg.new(this.options); - - const honkRecursion = true; - const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes( - this.acirUncompressedBytecode, - honkRecursion, - ); - const crs = await Crs.new(subgroupSize + 1); - await api.commonInitSlabAllocator(subgroupSize); - await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); - - // We don't init a proving key here in the Honk API - // await api.acirInitProvingKey(this.acirComposer, this.acirUncompressedBytecode); - this.api = api; - } - } - - async generateProof(decompressedWitness: Uint8Array): Promise { - await this.instantiate(); - const proofWithPublicInputs = await this.api.acirProveUltraHonk( - this.acirUncompressedBytecode, - gunzip(decompressedWitness), - ); + async generateProof(compressedWitness: Uint8Array): Promise { + const proofWithPublicInputs = await this.backend.generateProof(gunzip(compressedWitness)); const proofAsStrings = deflattenFields(proofWithPublicInputs.slice(4)); const numPublicInputs = Number(proofAsStrings[1]); @@ -229,55 +120,25 @@ export class UltraHonkBackend implements Backend, VerifierBackend { } async verifyProof(proofData: ProofData): Promise { - const { RawBuffer } = await import('@aztec/bb.js'); - const proof = reconstructProofWithPublicInputsHonk(proofData); - await this.instantiate(); - const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); - - return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(vkBuf)); + return this.backend.verifyProof(proof); } async getVerificationKey(): Promise { - await this.instantiate(); - return await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); + return this.backend.getVerificationKey(); } // TODO(https://github.com/noir-lang/noir/issues/5661): Update this to handle Honk recursive aggregation in the browser once it is ready in the backend itself async generateRecursiveProofArtifacts( - _proofData: ProofData, - _numOfPublicInputs: number, + proofData: ProofData, + numOfPublicInputs: number, ): Promise<{ proofAsFields: string[]; vkAsFields: string[]; vkHash: string }> { - await this.instantiate(); - // TODO(https://github.com/noir-lang/noir/issues/5661): This needs to be updated to handle recursive aggregation. - // There is still a proofAsFields method but we could consider getting rid of it as the proof itself - // is a list of field elements. - // UltraHonk also does not have public inputs directly prepended to the proof and they are still instead - // inserted at an offset. - // const proof = reconstructProofWithPublicInputs(proofData); - // const proofAsFields = (await this.api.acirProofAsFieldsUltraHonk(proof)).slice(numOfPublicInputs); - - // TODO: perhaps we should put this in the init function. Need to benchmark - // TODO how long it takes. - const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); - const vk = await this.api.acirVkAsFieldsUltraHonk(vkBuf); - - return { - // TODO(https://github.com/noir-lang/noir/issues/5661) - proofAsFields: [], - vkAsFields: vk.map((vk) => vk.toString()), - // We use an empty string for the vk hash here as it is unneeded as part of the recursive artifacts - // The user can be expected to hash the vk inside their circuit to check whether the vk is the circuit - // they expect - vkHash: '', - }; + const proof = reconstructProofWithPublicInputsHonk(proofData); + return this.backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs) } async destroy(): Promise { - if (!this.api) { - return; - } - await this.api.destroy(); + await this.backend.destroy(); } } diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts index 6786c1eec48..f1786396a2a 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts @@ -3,4 +3,4 @@ export { BarretenbergVerifier, UltraHonkVerifier } from './verifier.js'; // typedoc exports export { Backend, CompiledCircuit, ProofData } from '@noir-lang/types'; -export { BackendOptions } from './types.js'; +export { BackendOptions } from '@aztec/bb.js'; diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/types.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/types.ts deleted file mode 100644 index fac23030aad..00000000000 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @description - * An options object, currently only used to specify the number of threads to use. - */ -export type BackendOptions = { - /** @description Number of threads */ - threads: number; - memory?: { maximum: number }; -}; diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts index 58612672b35..885ec80caa8 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts @@ -1,69 +1,22 @@ import { ProofData } from '@noir-lang/types'; -import { BackendOptions } from './types.js'; import { flattenFieldsAsArray } from './public_inputs.js'; -import { type Barretenberg } from '@aztec/bb.js'; +import { BackendOptions, BarretenbergVerifier as BarretenbergVerifierInternal } from '@aztec/bb.js'; export class BarretenbergVerifier { - // 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. - - private api!: Barretenberg; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private acirComposer: any; - - constructor(private options: BackendOptions = { threads: 1 }) {} - - /** @ignore */ - async instantiate(): Promise { - if (!this.api) { - if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { - this.options.threads = navigator.hardwareConcurrency; - } else { - try { - const os = await import('os'); - this.options.threads = os.cpus().length; - } catch (e) { - console.log('Could not detect environment. Falling back to one thread.', e); - } - } - const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); - - // This is the number of CRS points necessary to verify a Barretenberg proof. - const NUM_CRS_POINTS_FOR_VERIFICATION: number = 0; - const [api, crs] = await Promise.all([Barretenberg.new(this.options), Crs.new(NUM_CRS_POINTS_FOR_VERIFICATION)]); - - await api.commonInitSlabAllocator(NUM_CRS_POINTS_FOR_VERIFICATION); - await api.srsInitSrs( - new RawBuffer([] /* crs.getG1Data() */), - NUM_CRS_POINTS_FOR_VERIFICATION, - new RawBuffer(crs.getG2Data()), - ); - - this.acirComposer = await api.acirNewAcirComposer(NUM_CRS_POINTS_FOR_VERIFICATION); - this.api = api; - } + private verifier!: BarretenbergVerifierInternal; + + constructor(options: BackendOptions = { threads: 1 }) { + this.verifier = new BarretenbergVerifierInternal(options); } /** @description Verifies a proof */ async verifyProof(proofData: ProofData, verificationKey: Uint8Array): Promise { - const { RawBuffer } = await import('@aztec/bb.js'); - - await this.instantiate(); - // The verifier can be used for a variety of ACIR programs so we should not assume that it - // is preloaded with the correct verification key. - await this.api.acirLoadVerificationKey(this.acirComposer, new RawBuffer(verificationKey)); - const proof = reconstructProofWithPublicInputs(proofData); - return await this.api.acirVerifyProof(this.acirComposer, proof); + return this.verifier.verifyUltraplonkProof(proof, verificationKey); } async destroy(): Promise { - if (!this.api) { - return; - } - await this.api.destroy(); + await this.verifier.destroy(); } } @@ -78,60 +31,20 @@ export function reconstructProofWithPublicInputs(proofData: ProofData): Uint8Arr } export class UltraHonkVerifier { - // These type assertions are used so that we don't - // have to initialize `api` in the constructor. - // These are initialized asynchronously in the `init` function, - // constructors cannot be asynchronous which is why we do this. - - private api!: Barretenberg; - - constructor(private options: BackendOptions = { threads: 1 }) {} - - /** @ignore */ - async instantiate(): Promise { - if (!this.api) { - if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { - this.options.threads = navigator.hardwareConcurrency; - } else { - try { - const os = await import('os'); - this.options.threads = os.cpus().length; - } catch (e) { - console.log('Could not detect environment. Falling back to one thread.', e); - } - } - const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); - - // This is the number of CRS points necessary to verify a Barretenberg proof. - const NUM_CRS_POINTS_FOR_VERIFICATION: number = 0; - const [api, crs] = await Promise.all([Barretenberg.new(this.options), Crs.new(NUM_CRS_POINTS_FOR_VERIFICATION)]); - - await api.commonInitSlabAllocator(NUM_CRS_POINTS_FOR_VERIFICATION); - await api.srsInitSrs( - new RawBuffer([] /* crs.getG1Data() */), - NUM_CRS_POINTS_FOR_VERIFICATION, - new RawBuffer(crs.getG2Data()), - ); - - this.api = api; - } + private verifier!: BarretenbergVerifierInternal; + + constructor(options: BackendOptions = { threads: 1 }) { + this.verifier = new BarretenbergVerifierInternal(options); } /** @description Verifies a proof */ async verifyProof(proofData: ProofData, verificationKey: Uint8Array): Promise { - const { RawBuffer } = await import('@aztec/bb.js'); - - await this.instantiate(); - const proof = reconstructProofWithPublicInputsHonk(proofData); - return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(verificationKey)); + return this.verifier.verifyUltrahonkProof(proof, verificationKey); } async destroy(): Promise { - if (!this.api) { - return; - } - await this.api.destroy(); + await this.verifier.destroy(); } }