Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: remove noir_js_backend_barretenberg #9338

Merged
merged 11 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading