Skip to content

Commit

Permalink
chore: push proof splitting helpers into bb.js (#8795)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
TomAFrench authored and Rumata888 committed Sep 27, 2024
1 parent ed43ebd commit 9aa58e3
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 52 deletions.
1 change: 1 addition & 0 deletions barretenberg/ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export {
UltraHonkBackend,
} from './barretenberg/index.js';
export { RawBuffer, Fr } from './types/index.js';
export { splitHonkProof, reconstructHonkProof } from './proof/index.js';
64 changes: 64 additions & 0 deletions barretenberg/ts/src/proof/index.ts
Original file line number Diff line number Diff line change
@@ -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('');
}
49 changes: 17 additions & 32 deletions noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -96,32 +96,16 @@ export class UltraHonkBackend implements Backend, VerifierBackend {

async generateProof(compressedWitness: Uint8Array): Promise<ProofData> {
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<boolean> {
const proof = reconstructProofWithPublicInputsHonk(proofData);

const flattenedPublicInputs = flattenFieldsAsArray(proofData.publicInputs);
const proof = reconstructHonkProof(flattenedPublicInputs, proofData.proof);
return this.backend.verifyProof(proof);
}

Expand All @@ -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<void> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -39,29 +43,12 @@ export class UltraHonkVerifier {

/** @description Verifies a proof */
async verifyProof(proofData: ProofData, verificationKey: Uint8Array): Promise<boolean> {
const proof = reconstructProofWithPublicInputsHonk(proofData);
const flattenedPublicInputs = flattenFieldsAsArray(proofData.publicInputs);
const proof = reconstructHonkProof(flattenedPublicInputs, proofData.proof);
return this.verifier.verifyUltrahonkProof(proof, verificationKey);
}

async destroy(): Promise<void> {
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;
}

0 comments on commit 9aa58e3

Please sign in to comment.