Skip to content

Commit

Permalink
feat: send and receive unflattened public inputs to backend (#3543)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench authored Nov 27, 2023
1 parent 8225b2b commit a7bdc67
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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 { BarretenbergBackend, flattenPublicInputs } from '@noir-lang/backend_barretenberg';

compilerLogLevel('INFO');

Expand Down Expand Up @@ -59,7 +59,7 @@ test_cases.forEach((testInfo) => {

const contract = await ethers.deployContract(testInfo.compiled, [], {});

const result = await contract.verify(proofData.proof, proofData.publicInputs);
const result = await contract.verify(proofData.proof, flattenPublicInputs(proofData.publicInputs));

expect(result).to.be.true;
});
Expand Down
30 changes: 6 additions & 24 deletions tooling/noir_js_backend_barretenberg/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { decompressSync as gunzip } from 'fflate';
import { acirToUint8Array } from './serialize.js';
import { Backend, CompiledCircuit, ProofData } from '@noir-lang/types';
import { BackendOptions } from './types.js';
import { deflattenPublicInputs, flattenPublicInputsAsArray } from './public_inputs.js';

export { flattenPublicInputs } from './public_inputs.js';

// This is the number of bytes in a UltraPlonk proof
// minus the public inputs.
Expand All @@ -18,7 +21,7 @@ export class BarretenbergBackend implements Backend {
private acirUncompressedBytecode: Uint8Array;

constructor(
acirCircuit: CompiledCircuit,
private acirCircuit: CompiledCircuit,
private options: BackendOptions = { threads: 1 },
) {
const acirBytecodeBase64 = acirCircuit.bytecode;
Expand Down Expand Up @@ -91,16 +94,8 @@ export class BarretenbergBackend implements Backend {
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);
const publicInputs = deflattenPublicInputs(publicInputsConcatenated, this.acirCircuit.abi);

return { proof, publicInputs };
}
Expand Down Expand Up @@ -185,26 +180,13 @@ export class BarretenbergBackend implements Backend {

function reconstructProofWithPublicInputs(proofData: ProofData): Uint8Array {
// Flatten publicInputs
const publicInputsConcatenated = flattenUint8Arrays(proofData.publicInputs);
const publicInputsConcatenated = flattenPublicInputsAsArray(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;
}

// typedoc exports
export { Backend, BackendOptions, CompiledCircuit, ProofData };
87 changes: 87 additions & 0 deletions tooling/noir_js_backend_barretenberg/src/public_inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Abi, WitnessMap } from '@noir-lang/types';

export function flattenPublicInputs(publicInputs: WitnessMap): string[] {
const publicInputIndices = [...publicInputs.keys()].sort();
const flattenedPublicInputs = publicInputIndices.map((index) => publicInputs.get(index) as string);
return flattenedPublicInputs;
}

export function flattenPublicInputsAsArray(publicInputs: WitnessMap): Uint8Array {
const flatPublicInputs = flattenPublicInputs(publicInputs);
const flattenedPublicInputs = flatPublicInputs.map(hexToUint8Array);
return flattenUint8Arrays(flattenedPublicInputs);
}

export function deflattenPublicInputs(flattenedPublicInputs: Uint8Array, abi: Abi): WitnessMap {
const publicInputSize = 32;
const chunkedFlattenedPublicInputs: Uint8Array[] = [];

for (let i = 0; i < flattenedPublicInputs.length; i += publicInputSize) {
const publicInput = flattenedPublicInputs.slice(i, i + publicInputSize);
chunkedFlattenedPublicInputs.push(publicInput);
}

const return_value_witnesses = abi.return_witnesses;
const public_parameters = abi.parameters.filter((param) => param.visibility === 'public');
const public_parameter_witnesses: number[] = public_parameters.flatMap((param) =>
abi.param_witnesses[param.name].flatMap((witness_range) =>
Array.from({ length: witness_range.end - witness_range.start }, (_, i) => witness_range.start + i),
),
);

// We now have an array of witness indices which have been deduplicated and sorted in ascending order.
// The elements of this array should correspond to the elements of `flattenedPublicInputs` so that we can build up a `WitnessMap`.
const public_input_witnesses = [...new Set(public_parameter_witnesses.concat(return_value_witnesses))].sort();

const publicInputs: WitnessMap = new Map();
public_input_witnesses.forEach((witness_index, index) => {
const witness_value = uint8ArrayToHex(chunkedFlattenedPublicInputs[index]);
publicInputs.set(witness_index, witness_value);
});

return publicInputs;
}

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[] = [];

buffer.forEach(function (i) {
let h = i.toString(16);
if (h.length % 2) {
h = '0' + h;
}
hex.push(h);
});

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

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

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

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

return u8;
}
6 changes: 4 additions & 2 deletions tooling/noir_js_types/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Abi } from '@noir-lang/noirc_abi';
import { Abi, WitnessMap } from '@noir-lang/noirc_abi';

export { Abi, WitnessMap } from '@noir-lang/noirc_abi';

export interface Backend {
/**
Expand Down Expand Up @@ -43,7 +45,7 @@ export interface Backend {
* */
export type ProofData = {
/** @description Public inputs of a proof */
publicInputs: Uint8Array[];
publicInputs: WitnessMap;
/** @description An byte array representing the proof */
proof: Uint8Array;
};
Expand Down

0 comments on commit a7bdc67

Please sign in to comment.