Skip to content

Commit

Permalink
chore: remove noir_js_backend_barretenberg (#9338)
Browse files Browse the repository at this point in the history
This PR pushes the remaining logic from `noir_js_backend_barretenberg`
into bb.js
  • Loading branch information
TomAFrench authored Oct 23, 2024
1 parent 1d1d76d commit cefe3d9
Show file tree
Hide file tree
Showing 36 changed files with 258 additions and 581 deletions.
14 changes: 5 additions & 9 deletions barretenberg/acir_tests/browser-test-app/src/index.ts

Large diffs are not rendered by default.

33 changes: 10 additions & 23 deletions barretenberg/acir_tests/headless-test/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { chromium, firefox, webkit } from "playwright";
import fs from "fs";
import { Command } from "commander";
import { gunzipSync } from "zlib";
import chalk from "chalk";
import os from "os";

Expand Down Expand Up @@ -37,25 +36,14 @@ function formatAndPrintLog(message: string): void {
console.log(formattedMessage);
}

const readBytecodeFile = (path: string): Uint8Array => {
const extension = path.substring(path.lastIndexOf(".") + 1);

if (extension == "json") {
const encodedCircuit = JSON.parse(fs.readFileSync(path, "utf8"));
const decompressed = gunzipSync(
Uint8Array.from(atob(encodedCircuit.bytecode), (c) => c.charCodeAt(0))
);
return decompressed;
}

const encodedCircuit = fs.readFileSync(path);
const decompressed = gunzipSync(encodedCircuit);
return decompressed;
const readBytecodeFile = (path: string): string => {
const encodedCircuit = JSON.parse(fs.readFileSync(path, "utf8"));
return encodedCircuit.bytecode;
};

const readWitnessFile = (path: string): Uint8Array => {
const buffer = fs.readFileSync(path);
return gunzipSync(buffer);
return buffer;
};

// Set up the command-line interface
Expand All @@ -70,8 +58,8 @@ program
)
.option(
"-b, --bytecode-path <path>",
"Specify the path to the gzip encoded ACIR bytecode",
"./target/acir.gz"
"Specify the path to the ACIR artifact json file",
"./target/acir.json"
)
.option(
"-w, --witness-path <path>",
Expand Down Expand Up @@ -102,19 +90,18 @@ program
await page.goto("http://localhost:8080");

const result: boolean = await page.evaluate(
([acirData, witnessData, threads]) => {
([acir, witnessData, threads]: [string, number[], number]) => {
// Convert the input data to Uint8Arrays within the browser context
const acirUint8Array = new Uint8Array(acirData as number[]);
const witnessUint8Array = new Uint8Array(witnessData as number[]);
const witnessUint8Array = new Uint8Array(witnessData);

// Call the desired function and return the result
return (window as any).runTest(
acirUint8Array,
acir,
witnessUint8Array,
threads
);
},
[Array.from(acir), Array.from(witness), threads]
[acir, Array.from(witness), threads]
);

await browser.close();
Expand Down
1 change: 1 addition & 0 deletions barretenberg/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"comlink": "^4.4.1",
"commander": "^10.0.1",
"debug": "^4.3.4",
"fflate": "^0.8.0",
"tslib": "^2.4.0"
},
"devDependencies": {
Expand Down
106 changes: 96 additions & 10 deletions barretenberg/ts/src/barretenberg/backend.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { BackendOptions, Barretenberg } from './index.js';
import { RawBuffer } from '../types/raw_buffer.js';
import { decompressSync as gunzip } from 'fflate';
import {
deflattenFields,
flattenFieldsAsArray,
ProofData,
reconstructHonkProof,
reconstructUltraPlonkProof,
} from '../proof/index.js';

export class UltraPlonkBackend {
// These type assertions are used so that we don't
Expand All @@ -11,7 +19,11 @@ export class UltraPlonkBackend {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected acirComposer: any;

constructor(protected acirUncompressedBytecode: Uint8Array, protected options: BackendOptions = { threads: 1 }) {}
protected acirUncompressedBytecode: Uint8Array;

constructor(acirBytecode: string, protected options: BackendOptions = { threads: 1 }) {
this.acirUncompressedBytecode = acirToUint8Array(acirBytecode);
}

/** @ignore */
async instantiate(): Promise<void> {
Expand All @@ -30,9 +42,25 @@ export class UltraPlonkBackend {
}

/** @description Generates a proof */
async generateProof(uncompressedWitness: Uint8Array): Promise<Uint8Array> {
async generateProof(compressedWitness: Uint8Array): Promise<ProofData> {
await this.instantiate();
return this.api.acirCreateProof(this.acirComposer, this.acirUncompressedBytecode, uncompressedWitness);
const proofWithPublicInputs = await this.api.acirCreateProof(
this.acirComposer,
this.acirUncompressedBytecode,
gunzip(compressedWitness),
);

// This is the number of bytes in a UltraPlonk proof
// minus the public inputs.
const numBytesInProofWithoutPublicInputs = 2144;

const splitIndex = proofWithPublicInputs.length - numBytesInProofWithoutPublicInputs;

const publicInputsConcatenated = proofWithPublicInputs.slice(0, splitIndex);
const proof = proofWithPublicInputs.slice(splitIndex);
const publicInputs = deflattenFields(publicInputsConcatenated);

return { proof, publicInputs };
}

/**
Expand All @@ -52,14 +80,16 @@ export class UltraPlonkBackend {
* ```
*/
async generateRecursiveProofArtifacts(
proof: Uint8Array,
proofData: ProofData,
numOfPublicInputs = 0,
): Promise<{
proofAsFields: string[];
vkAsFields: string[];
vkHash: string;
}> {
await this.instantiate();

const proof = reconstructUltraPlonkProof(proofData);
const proofAsFields = (
await this.api.acirSerializeProofIntoFields(this.acirComposer, proof, numOfPublicInputs)
).slice(numOfPublicInputs);
Expand All @@ -79,9 +109,10 @@ export class UltraPlonkBackend {
}

/** @description Verifies a proof */
async verifyProof(proof: Uint8Array): Promise<boolean> {
async verifyProof(proofData: ProofData): Promise<boolean> {
await this.instantiate();
await this.api.acirInitVerificationKey(this.acirComposer);
const proof = reconstructUltraPlonkProof(proofData);
return await this.api.acirVerifyProof(this.acirComposer, proof);
}

Expand All @@ -99,16 +130,24 @@ export class UltraPlonkBackend {
}
}

// 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 {
// 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;
protected acirUncompressedBytecode: Uint8Array;

constructor(protected acirUncompressedBytecode: Uint8Array, protected options: BackendOptions = { threads: 1 }) {}

constructor(acirBytecode: string, protected options: BackendOptions = { threads: 1 }) {
this.acirUncompressedBytecode = acirToUint8Array(acirBytecode);
}
/** @ignore */
async instantiate(): Promise<void> {
if (!this.api) {
Expand All @@ -122,13 +161,39 @@ export class UltraHonkBackend {
}
}

async generateProof(uncompressedWitness: Uint8Array): Promise<Uint8Array> {
async generateProof(compressedWitness: Uint8Array): Promise<ProofData> {
await this.instantiate();
return this.api.acirProveUltraHonk(this.acirUncompressedBytecode, uncompressedWitness);
const proofWithPublicInputs = await this.api.acirProveUltraHonk(
this.acirUncompressedBytecode,
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);

return { proof, publicInputs };
}

async verifyProof(proof: Uint8Array): Promise<boolean> {
async verifyProof(proofData: ProofData): Promise<boolean> {
await this.instantiate();
const proof = reconstructHonkProof(flattenFieldsAsArray(proofData.publicInputs), proofData.proof);
const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode);

return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(vkBuf));
Expand Down Expand Up @@ -178,3 +243,24 @@ export class UltraHonkBackend {
await this.api.destroy();
}
}

// Converts bytecode from a base64 string to a Uint8Array
function acirToUint8Array(base64EncodedBytecode: string): Uint8Array {
const compressedByteCode = base64Decode(base64EncodedBytecode);
return gunzip(compressedByteCode);
}

// 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: string): Uint8Array {
if (typeof Buffer !== 'undefined') {
// Node.js environment
const b = Buffer.from(input, 'base64');
return new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
} 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.');
}
}
7 changes: 5 additions & 2 deletions barretenberg/ts/src/barretenberg/verifier.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BackendOptions, Barretenberg } from './index.js';
import { RawBuffer } from '../types/raw_buffer.js';
import { flattenFieldsAsArray, ProofData, reconstructHonkProof, reconstructUltraPlonkProof } from '../proof/index.js';

// TODO: once UP is removed we can just roll this into the bas `Barretenberg` class.

Expand Down Expand Up @@ -27,19 +28,21 @@ export class BarretenbergVerifier {
}

/** @description Verifies a proof */
async verifyUltraplonkProof(proof: Uint8Array, verificationKey: Uint8Array): Promise<boolean> {
async verifyUltraPlonkProof(proofData: ProofData, verificationKey: Uint8Array): Promise<boolean> {
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 = reconstructUltraPlonkProof(proofData);
return await this.api.acirVerifyProof(this.acirComposer, proof);
}

/** @description Verifies a proof */
async verifyUltrahonkProof(proof: Uint8Array, verificationKey: Uint8Array): Promise<boolean> {
async verifyUltraHonkProof(proofData: ProofData, verificationKey: Uint8Array): Promise<boolean> {
await this.instantiate();

const proof = reconstructHonkProof(flattenFieldsAsArray(proofData.publicInputs), proofData.proof);
return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(verificationKey));
}

Expand Down
2 changes: 1 addition & 1 deletion barretenberg/ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ export {
UltraHonkBackend,
} from './barretenberg/index.js';
export { RawBuffer, Fr } from './types/index.js';
export { splitHonkProof, reconstructHonkProof } from './proof/index.js';
export { splitHonkProof, reconstructHonkProof, ProofData } from './proof/index.js';
58 changes: 57 additions & 1 deletion barretenberg/ts/src/proof/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
/**
* @description
* The representation of a proof
* */
export type ProofData = {
/** @description Public inputs of a proof */
publicInputs: string[];
/** @description An byte array representing the proof */
proof: Uint8Array;
};

// Buffers are prepended with their size. The size takes 4 bytes.
const serializedBufferSize = 4;
const fieldByteSize = 32;
Expand Down Expand Up @@ -37,7 +48,17 @@ export function reconstructHonkProof(publicInputs: Uint8Array, proof: Uint8Array
return proofWithPublicInputs;
}

function deflattenFields(flattenedFields: Uint8Array): string[] {
export function reconstructUltraPlonkProof(proofData: ProofData): Uint8Array {
// Flatten publicInputs
const publicInputsConcatenated = flattenFieldsAsArray(proofData.publicInputs);

// Concatenate publicInputs and proof
const proofWithPublicInputs = Uint8Array.from([...publicInputsConcatenated, ...proofData.proof]);

return proofWithPublicInputs;
}

export function deflattenFields(flattenedFields: Uint8Array): string[] {
const publicInputSize = 32;
const chunkedFlattenedPublicInputs: Uint8Array[] = [];

Expand All @@ -49,6 +70,24 @@ function deflattenFields(flattenedFields: Uint8Array): string[] {
return chunkedFlattenedPublicInputs.map(uint8ArrayToHex);
}

export function flattenFieldsAsArray(fields: string[]): Uint8Array {
const flattenedPublicInputs = fields.map(hexToUint8Array);
return flattenUint8Arrays(flattenedPublicInputs);
}

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;
}

function uint8ArrayToHex(buffer: Uint8Array): string {
const hex: string[] = [];

Expand All @@ -62,3 +101,20 @@ function uint8ArrayToHex(buffer: Uint8Array): string {

return '0x' + hex.join('');
}

function hexToUint8Array(hex: string): Uint8Array {
const sanitisedHex = BigInt(hex).toString(16).padStart(64, '0');

const len = sanitisedHex.length / 2;
const u8 = new Uint8Array(len);

let i = 0;
let j = 0;
while (i < len) {
u8[i] = parseInt(sanitisedHex.slice(j, j + 2), 16);
i += 1;
j += 2;
}

return u8;
}
8 changes: 8 additions & 0 deletions barretenberg/ts/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ __metadata:
debug: ^4.3.4
eslint: ^8.35.0
eslint-config-prettier: ^8.8.0
fflate: ^0.8.0
html-webpack-plugin: ^5.5.1
idb-keyval: ^6.2.1
jest: ^29.5.0
Expand Down Expand Up @@ -3271,6 +3272,13 @@ __metadata:
languageName: node
linkType: hard

"fflate@npm:^0.8.0":
version: 0.8.2
resolution: "fflate@npm:0.8.2"
checksum: 29470337b85d3831826758e78f370e15cda3169c5cd4477c9b5eea2402261a74b2975bae816afabe1c15d21d98591e0d30a574f7103aa117bff60756fa3035d4
languageName: node
linkType: hard

"file-entry-cache@npm:^6.0.1":
version: 6.0.1
resolution: "file-entry-cache@npm:6.0.1"
Expand Down
Loading

0 comments on commit cefe3d9

Please sign in to comment.