diff --git a/compiler/integration-tests/test/node/smart_contract_verifier.test.ts b/compiler/integration-tests/test/node/smart_contract_verifier.test.ts index 777ef692876..038c692220f 100644 --- a/compiler/integration-tests/test/node/smart_contract_verifier.test.ts +++ b/compiler/integration-tests/test/node/smart_contract_verifier.test.ts @@ -8,7 +8,6 @@ import toml from 'toml'; import { 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 { separatePublicInputsFromProof } from '../shared/proof'; compilerLogLevel('INFO'); @@ -44,19 +43,18 @@ test_cases.forEach((testInfo) => { const prover_toml = readFileSync(resolve(`${base_relative_path}/${test_case}/Prover.toml`)).toString(); const inputs = toml.parse(prover_toml); - const proofWithPublicInputs = await program.generateFinalProof(inputs); + const proofData = await program.generateFinalProof(inputs); // JS verification - const verified = await program.verifyFinalProof(proofWithPublicInputs); + const verified = await program.verifyFinalProof(proofData); expect(verified, 'Proof fails verification in JS').to.be.true; // Smart contract verification const contract = await ethers.deployContract(testInfo.compiled, [], {}); - const { proof, publicInputs } = separatePublicInputsFromProof(proofWithPublicInputs, testInfo.numPublicInputs); - const result = await contract.verify(proof, publicInputs); + const result = await contract.verify(proofData.proof, proofData.publicInputs); expect(result).to.be.true; }); diff --git a/compiler/integration-tests/test/shared/proof.ts b/compiler/integration-tests/test/shared/proof.ts deleted file mode 100644 index e792a8f509e..00000000000 --- a/compiler/integration-tests/test/shared/proof.ts +++ /dev/null @@ -1,17 +0,0 @@ -const FIELD_ELEMENT_BYTES = 32; - -export function separatePublicInputsFromProof( - proof: Uint8Array, - numPublicInputs: number, -): { proof: Uint8Array; publicInputs: Uint8Array[] } { - const publicInputs = Array.from({ length: numPublicInputs }, (_, i) => { - const offset = i * FIELD_ELEMENT_BYTES; - return proof.slice(offset, offset + FIELD_ELEMENT_BYTES); - }); - const slicedProof = proof.slice(numPublicInputs * FIELD_ELEMENT_BYTES); - - return { - proof: slicedProof, - publicInputs, - }; -} diff --git a/tooling/noir_js/src/program.ts b/tooling/noir_js/src/program.ts index 01ca8b2f675..1fd32862010 100644 --- a/tooling/noir_js/src/program.ts +++ b/tooling/noir_js/src/program.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Backend, CompiledCircuit } from '@noir-lang/types'; +import { Backend, CompiledCircuit, ProofData } from '@noir-lang/types'; import { generateWitness } from './witness_generation.js'; import initAbi from '@noir-lang/noirc_abi'; import initACVM from '@noir-lang/acvm_js'; @@ -20,13 +20,13 @@ export class Noir { } // Initial inputs to your program - async generateFinalProof(inputs: any): Promise { + async generateFinalProof(inputs: any): Promise { await this.init(); const serializedWitness = await generateWitness(this.circuit, inputs); return this.backend.generateFinalProof(serializedWitness); } - async verifyFinalProof(proof: Uint8Array): Promise { - return this.backend.verifyFinalProof(proof); + async verifyFinalProof(proofData: ProofData): Promise { + return this.backend.verifyFinalProof(proofData); } } diff --git a/tooling/noir_js_backend_barretenberg/src/index.ts b/tooling/noir_js_backend_barretenberg/src/index.ts index 763de88cf93..b3d3af79b9e 100644 --- a/tooling/noir_js_backend_barretenberg/src/index.ts +++ b/tooling/noir_js_backend_barretenberg/src/index.ts @@ -1,6 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { acirToUint8Array } from './serialize.js'; -import { Backend, CompiledCircuit } from '@noir-lang/types'; +import { Backend, CompiledCircuit, ProofData } from '@noir-lang/types'; + +// This is the number of bytes in a UltraPlonk proof +// minus the public inputs. +const numBytesInProofWithoutPublicInputs: number = 2144; export class BarretenbergBackend implements Backend { // These type assertions are used so that we don't @@ -40,7 +44,7 @@ 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 { + async generateFinalProof(decompressedWitness: Uint8Array): Promise { const makeEasyToVerifyInCircuit = false; return this.generateProof(decompressedWitness, makeEasyToVerifyInCircuit); } @@ -56,21 +60,35 @@ export class BarretenbergBackend implements Backend { // 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: Uint8Array): Promise { + async generateIntermediateProof(witness: Uint8Array): Promise { const makeEasyToVerifyInCircuit = true; return this.generateProof(witness, makeEasyToVerifyInCircuit); } - async generateProof(decompressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { + async generateProof(decompressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { await this.instantiate(); - const proof = await this.api.acirCreateProof( + const proofWithPublicInputs = await this.api.acirCreateProof( this.acirComposer, this.acirUncompressedBytecode, decompressedWitness, makeEasyToVerifyInCircuit, ); - return proof; + const splitIndex = proofWithPublicInputs.length - numBytesInProofWithoutPublicInputs; + + const publicInputsConcatenated = proofWithPublicInputs.slice(0, splitIndex); + + const publicInputSize = 32; + const publicInputs: Uint8Array[] = []; + + for (let i = 0; i < publicInputsConcatenated.length; i += publicInputSize) { + const publicInput = publicInputsConcatenated.slice(i, i + publicInputSize); + publicInputs.push(publicInput); + } + + const proof = proofWithPublicInputs.slice(splitIndex); + + return { proof, publicInputs }; } // Generates artifacts that will be passed to a circuit that will verify this proof. @@ -83,7 +101,7 @@ export class BarretenbergBackend implements Backend { // // The number of public inputs denotes how many public inputs are in the inner proof. async generateIntermediateProofArtifacts( - proof: Uint8Array, + proofData: ProofData, numOfPublicInputs = 0, ): Promise<{ proofAsFields: string[]; @@ -91,6 +109,7 @@ export class BarretenbergBackend implements Backend { vkHash: string; }> { await this.instantiate(); + const proof = reconstructProofWithPublicInputs(proofData); const proofAsFields = await this.api.acirSerializeProofIntoFields(this.acirComposer, proof, numOfPublicInputs); // TODO: perhaps we should put this in the init function. Need to benchmark @@ -107,13 +126,15 @@ export class BarretenbergBackend implements Backend { }; } - async verifyFinalProof(proof: Uint8Array): Promise { + async verifyFinalProof(proofData: ProofData): Promise { + const proof = reconstructProofWithPublicInputs(proofData); const makeEasyToVerifyInCircuit = false; const verified = await this.verifyProof(proof, makeEasyToVerifyInCircuit); return verified; } - async verifyIntermediateProof(proof: Uint8Array): Promise { + async verifyIntermediateProof(proofData: ProofData): Promise { + const proof = reconstructProofWithPublicInputs(proofData); const makeEasyToVerifyInCircuit = true; return this.verifyProof(proof, makeEasyToVerifyInCircuit); } @@ -131,3 +152,26 @@ export class BarretenbergBackend implements Backend { await this.api.destroy(); } } + +function reconstructProofWithPublicInputs(proofData: ProofData): Uint8Array { + // Flatten publicInputs + const publicInputsConcatenated = flattenUint8Arrays(proofData.publicInputs); + + // Concatenate publicInputs and proof + const proofWithPublicInputs = Uint8Array.from([...publicInputsConcatenated, ...proofData.proof]); + + return proofWithPublicInputs; +} + +function flattenUint8Arrays(arrays: Uint8Array[]): Uint8Array { + const totalLength = arrays.reduce((acc, val) => acc + val.length, 0); + const result = new Uint8Array(totalLength); + + let offset = 0; + for (const arr of arrays) { + result.set(arr, offset); + offset += arr.length; + } + + return result; +} diff --git a/tooling/noir_js_types/src/types.ts b/tooling/noir_js_types/src/types.ts index 2b6ea8ad4b0..357e440f155 100644 --- a/tooling/noir_js_types/src/types.ts +++ b/tooling/noir_js_types/src/types.ts @@ -1,17 +1,22 @@ export interface Backend { // 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. - generateFinalProof(decompressedWitness: Uint8Array): Promise; + generateFinalProof(decompressedWitness: Uint8Array): Promise; // Generates an inner proof. This is the proof that will be verified // in another circuit. - generateIntermediateProof(decompressedWitness: Uint8Array): Promise; + generateIntermediateProof(decompressedWitness: Uint8Array): Promise; - verifyFinalProof(proof: Uint8Array): Promise; + verifyFinalProof(proofData: ProofData): Promise; - verifyIntermediateProof(proof: Uint8Array): Promise; + verifyIntermediateProof(proofData: ProofData): Promise; } +export type ProofData = { + publicInputs: Uint8Array[]; + proof: Uint8Array; +}; + export type CompiledCircuit = { bytecode: string; abi: object;