From a7bdc67ef3ec2037bffc4f1f472907cad786c319 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Mon, 27 Nov 2023 23:47:53 +0000 Subject: [PATCH] feat: send and receive unflattened public inputs to backend (#3543) --- .../test/node/smart_contract_verifier.test.ts | 4 +- .../noir_js_backend_barretenberg/src/index.ts | 30 ++----- .../src/public_inputs.ts | 87 +++++++++++++++++++ tooling/noir_js_types/src/types.ts | 6 +- 4 files changed, 99 insertions(+), 28 deletions(-) create mode 100644 tooling/noir_js_backend_barretenberg/src/public_inputs.ts diff --git a/compiler/integration-tests/test/node/smart_contract_verifier.test.ts b/compiler/integration-tests/test/node/smart_contract_verifier.test.ts index 738bc2df8dd..a57f2dcbfb6 100644 --- a/compiler/integration-tests/test/node/smart_contract_verifier.test.ts +++ b/compiler/integration-tests/test/node/smart_contract_verifier.test.ts @@ -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'); @@ -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; }); diff --git a/tooling/noir_js_backend_barretenberg/src/index.ts b/tooling/noir_js_backend_barretenberg/src/index.ts index 820cda93c83..100418debd0 100644 --- a/tooling/noir_js_backend_barretenberg/src/index.ts +++ b/tooling/noir_js_backend_barretenberg/src/index.ts @@ -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. @@ -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; @@ -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 }; } @@ -185,7 +180,7 @@ 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]); @@ -193,18 +188,5 @@ function reconstructProofWithPublicInputs(proofData: ProofData): Uint8Array { 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 }; diff --git a/tooling/noir_js_backend_barretenberg/src/public_inputs.ts b/tooling/noir_js_backend_barretenberg/src/public_inputs.ts new file mode 100644 index 00000000000..fb7fee59beb --- /dev/null +++ b/tooling/noir_js_backend_barretenberg/src/public_inputs.ts @@ -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; +} diff --git a/tooling/noir_js_types/src/types.ts b/tooling/noir_js_types/src/types.ts index 5ed6b1721e9..b997d92425d 100644 --- a/tooling/noir_js_types/src/types.ts +++ b/tooling/noir_js_types/src/types.ts @@ -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 { /** @@ -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; };