From 9aa58e35bfb9165f062624508934b6eb9ef82c11 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:48:39 +0100 Subject: [PATCH] chore: push proof splitting helpers into bb.js (#8795) bb.js doesn't have any utilities for manipulating proofs for verifying public inputs, feeding proofs into the smart contract, etc. This pushes the utilities in the noir repo into bb.js. --- barretenberg/ts/src/index.ts | 1 + barretenberg/ts/src/proof/index.ts | 64 +++++++++++++++++++ .../src/backend.ts | 49 +++++--------- .../src/verifier.ts | 27 ++------ 4 files changed, 89 insertions(+), 52 deletions(-) create mode 100644 barretenberg/ts/src/proof/index.ts diff --git a/barretenberg/ts/src/index.ts b/barretenberg/ts/src/index.ts index a58a25203a03..d67bad794af3 100644 --- a/barretenberg/ts/src/index.ts +++ b/barretenberg/ts/src/index.ts @@ -8,3 +8,4 @@ export { UltraHonkBackend, } from './barretenberg/index.js'; export { RawBuffer, Fr } from './types/index.js'; +export { splitHonkProof, reconstructHonkProof } from './proof/index.js'; diff --git a/barretenberg/ts/src/proof/index.ts b/barretenberg/ts/src/proof/index.ts new file mode 100644 index 000000000000..4219f167fc9b --- /dev/null +++ b/barretenberg/ts/src/proof/index.ts @@ -0,0 +1,64 @@ +// Buffers are prepended with their size. The size takes 4 bytes. +const serializedBufferSize = 4; +const fieldByteSize = 32; +const publicInputOffset = 3; +const publicInputsOffsetBytes = publicInputOffset * fieldByteSize; + +export function splitHonkProof(proofWithPublicInputs: Uint8Array): { publicInputs: Uint8Array; proof: Uint8Array } { + const proofAsStrings = deflattenFields(proofWithPublicInputs.slice(4)); + + const numPublicInputs = Number(proofAsStrings[1]); + + // Account for the serialized buffer size at start + const publicInputsOffset = publicInputsOffsetBytes + serializedBufferSize; + // Get the part before and after the public inputs + const proofStart = proofWithPublicInputs.slice(0, publicInputsOffset); + const publicInputsSplitIndex = numPublicInputs * fieldByteSize; + const proofEnd = proofWithPublicInputs.slice(publicInputsOffset + publicInputsSplitIndex); + // Construct the proof without the public inputs + const proof = new Uint8Array([...proofStart, ...proofEnd]); + + // Fetch the number of public inputs out of the proof string + const publicInputs = proofWithPublicInputs.slice(publicInputsOffset, publicInputsOffset + publicInputsSplitIndex); + + return { + proof, + publicInputs, + }; +} + +export function reconstructHonkProof(publicInputs: Uint8Array, proof: Uint8Array): Uint8Array { + const proofStart = proof.slice(0, publicInputsOffsetBytes + serializedBufferSize); + const proofEnd = proof.slice(publicInputsOffsetBytes + serializedBufferSize); + + // Concatenate publicInputs and proof + const proofWithPublicInputs = Uint8Array.from([...proofStart, ...publicInputs, ...proofEnd]); + + return proofWithPublicInputs; +} + +function deflattenFields(flattenedFields: Uint8Array): string[] { + const publicInputSize = 32; + const chunkedFlattenedPublicInputs: Uint8Array[] = []; + + for (let i = 0; i < flattenedFields.length; i += publicInputSize) { + const publicInput = flattenedFields.slice(i, i + publicInputSize); + chunkedFlattenedPublicInputs.push(publicInput); + } + + return chunkedFlattenedPublicInputs.map(uint8ArrayToHex); +} + +function uint8ArrayToHex(buffer: Uint8Array): string { + const hex: string[] = []; + + buffer.forEach(function (i) { + let h = i.toString(16); + if (h.length % 2) { + h = '0' + h; + } + hex.push(h); + }); + + return '0x' + hex.join(''); +} 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 791d9dd7d3ff..812a7173ad25 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,8 +1,14 @@ import { acirToUint8Array } from './serialize.js'; import { Backend, CompiledCircuit, ProofData, VerifierBackend } from '@noir-lang/types'; -import { deflattenFields } from './public_inputs.js'; -import { reconstructProofWithPublicInputs, reconstructProofWithPublicInputsHonk } from './verifier.js'; -import { BackendOptions, UltraPlonkBackend, UltraHonkBackend as UltraHonkBackendInternal } from '@aztec/bb.js'; +import { deflattenFields, flattenFieldsAsArray } from './public_inputs.js'; +import { reconstructProofWithPublicInputs } from './verifier.js'; +import { + BackendOptions, + reconstructHonkProof, + splitHonkProof, + UltraPlonkBackend, + UltraHonkBackend as UltraHonkBackendInternal, +} from '@aztec/bb.js'; import { decompressSync as gunzip } from 'fflate'; // This is the number of bytes in a UltraPlonk proof @@ -74,12 +80,6 @@ export class BarretenbergBackend implements Backend, VerifierBackend { } } -// Buffers are prepended with their size. The size takes 4 bytes. -const serializedBufferSize = 4; -const fieldByteSize = 32; -const publicInputOffset = 3; -const publicInputsOffsetBytes = publicInputOffset * fieldByteSize; - export class UltraHonkBackend implements Backend, VerifierBackend { // These type assertions are used so that we don't // have to initialize `api` in the constructor. @@ -96,32 +96,16 @@ export class UltraHonkBackend implements Backend, VerifierBackend { async generateProof(compressedWitness: Uint8Array): Promise { const proofWithPublicInputs = await this.backend.generateProof(gunzip(compressedWitness)); - const proofAsStrings = deflattenFields(proofWithPublicInputs.slice(4)); - - const numPublicInputs = Number(proofAsStrings[1]); - - // Account for the serialized buffer size at start - const publicInputsOffset = publicInputsOffsetBytes + serializedBufferSize; - // Get the part before and after the public inputs - const proofStart = proofWithPublicInputs.slice(0, publicInputsOffset); - const publicInputsSplitIndex = numPublicInputs * fieldByteSize; - const proofEnd = proofWithPublicInputs.slice(publicInputsOffset + publicInputsSplitIndex); - // Construct the proof without the public inputs - const proof = new Uint8Array([...proofStart, ...proofEnd]); - - // Fetch the number of public inputs out of the proof string - const publicInputsConcatenated = proofWithPublicInputs.slice( - publicInputsOffset, - publicInputsOffset + publicInputsSplitIndex, - ); - const publicInputs = deflattenFields(publicInputsConcatenated); + + const { proof, publicInputs: flatPublicInputs } = splitHonkProof(proofWithPublicInputs); + const publicInputs = deflattenFields(flatPublicInputs); return { proof, publicInputs }; } async verifyProof(proofData: ProofData): Promise { - const proof = reconstructProofWithPublicInputsHonk(proofData); - + const flattenedPublicInputs = flattenFieldsAsArray(proofData.publicInputs); + const proof = reconstructHonkProof(flattenedPublicInputs, proofData.proof); return this.backend.verifyProof(proof); } @@ -134,8 +118,9 @@ export class UltraHonkBackend implements Backend, VerifierBackend { proofData: ProofData, numOfPublicInputs: number, ): Promise<{ proofAsFields: string[]; vkAsFields: string[]; vkHash: string }> { - const proof = reconstructProofWithPublicInputsHonk(proofData); - return this.backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs) + const flattenedPublicInputs = flattenFieldsAsArray(proofData.publicInputs); + const proof = reconstructHonkProof(flattenedPublicInputs, proofData.proof); + return this.backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs); } async destroy(): Promise { 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 885ec80caa80..7a8fe01d3a21 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,6 +1,10 @@ import { ProofData } from '@noir-lang/types'; import { flattenFieldsAsArray } from './public_inputs.js'; -import { BackendOptions, BarretenbergVerifier as BarretenbergVerifierInternal } from '@aztec/bb.js'; +import { + BackendOptions, + BarretenbergVerifier as BarretenbergVerifierInternal, + reconstructHonkProof, +} from '@aztec/bb.js'; export class BarretenbergVerifier { private verifier!: BarretenbergVerifierInternal; @@ -39,7 +43,8 @@ export class UltraHonkVerifier { /** @description Verifies a proof */ async verifyProof(proofData: ProofData, verificationKey: Uint8Array): Promise { - const proof = reconstructProofWithPublicInputsHonk(proofData); + const flattenedPublicInputs = flattenFieldsAsArray(proofData.publicInputs); + const proof = reconstructHonkProof(flattenedPublicInputs, proofData.proof); return this.verifier.verifyUltrahonkProof(proof, verificationKey); } @@ -47,21 +52,3 @@ export class UltraHonkVerifier { await this.verifier.destroy(); } } - -const serializedBufferSize = 4; -const fieldByteSize = 32; -const publicInputOffset = 3; -const publicInputsOffsetBytes = publicInputOffset * fieldByteSize; - -export function reconstructProofWithPublicInputsHonk(proofData: ProofData): Uint8Array { - // Flatten publicInputs - const publicInputsConcatenated = flattenFieldsAsArray(proofData.publicInputs); - - const proofStart = proofData.proof.slice(0, publicInputsOffsetBytes + serializedBufferSize); - const proofEnd = proofData.proof.slice(publicInputsOffsetBytes + serializedBufferSize); - - // Concatenate publicInputs and proof - const proofWithPublicInputs = Uint8Array.from([...proofStart, ...publicInputsConcatenated, ...proofEnd]); - - return proofWithPublicInputs; -}