Skip to content

Commit a7bdc67

Browse files
authored
feat: send and receive unflattened public inputs to backend (#3543)
1 parent 8225b2b commit a7bdc67

4 files changed

Lines changed: 99 additions & 28 deletions

File tree

compiler/integration-tests/test/node/smart_contract_verifier.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import toml from 'toml';
77

88
import { compile, init_log_level as compilerLogLevel } from '@noir-lang/noir_wasm';
99
import { Noir } from '@noir-lang/noir_js';
10-
import { BarretenbergBackend } from '@noir-lang/backend_barretenberg';
10+
import { BarretenbergBackend, flattenPublicInputs } from '@noir-lang/backend_barretenberg';
1111

1212
compilerLogLevel('INFO');
1313

@@ -59,7 +59,7 @@ test_cases.forEach((testInfo) => {
5959

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

62-
const result = await contract.verify(proofData.proof, proofData.publicInputs);
62+
const result = await contract.verify(proofData.proof, flattenPublicInputs(proofData.publicInputs));
6363

6464
expect(result).to.be.true;
6565
});

tooling/noir_js_backend_barretenberg/src/index.ts

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { decompressSync as gunzip } from 'fflate';
33
import { acirToUint8Array } from './serialize.js';
44
import { Backend, CompiledCircuit, ProofData } from '@noir-lang/types';
55
import { BackendOptions } from './types.js';
6+
import { deflattenPublicInputs, flattenPublicInputsAsArray } from './public_inputs.js';
7+
8+
export { flattenPublicInputs } from './public_inputs.js';
69

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

2023
constructor(
21-
acirCircuit: CompiledCircuit,
24+
private acirCircuit: CompiledCircuit,
2225
private options: BackendOptions = { threads: 1 },
2326
) {
2427
const acirBytecodeBase64 = acirCircuit.bytecode;
@@ -91,16 +94,8 @@ export class BarretenbergBackend implements Backend {
9194
const splitIndex = proofWithPublicInputs.length - numBytesInProofWithoutPublicInputs;
9295

9396
const publicInputsConcatenated = proofWithPublicInputs.slice(0, splitIndex);
94-
95-
const publicInputSize = 32;
96-
const publicInputs: Uint8Array[] = [];
97-
98-
for (let i = 0; i < publicInputsConcatenated.length; i += publicInputSize) {
99-
const publicInput = publicInputsConcatenated.slice(i, i + publicInputSize);
100-
publicInputs.push(publicInput);
101-
}
102-
10397
const proof = proofWithPublicInputs.slice(splitIndex);
98+
const publicInputs = deflattenPublicInputs(publicInputsConcatenated, this.acirCircuit.abi);
10499

105100
return { proof, publicInputs };
106101
}
@@ -185,26 +180,13 @@ export class BarretenbergBackend implements Backend {
185180

186181
function reconstructProofWithPublicInputs(proofData: ProofData): Uint8Array {
187182
// Flatten publicInputs
188-
const publicInputsConcatenated = flattenUint8Arrays(proofData.publicInputs);
183+
const publicInputsConcatenated = flattenPublicInputsAsArray(proofData.publicInputs);
189184

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

193188
return proofWithPublicInputs;
194189
}
195190

196-
function flattenUint8Arrays(arrays: Uint8Array[]): Uint8Array {
197-
const totalLength = arrays.reduce((acc, val) => acc + val.length, 0);
198-
const result = new Uint8Array(totalLength);
199-
200-
let offset = 0;
201-
for (const arr of arrays) {
202-
result.set(arr, offset);
203-
offset += arr.length;
204-
}
205-
206-
return result;
207-
}
208-
209191
// typedoc exports
210192
export { Backend, BackendOptions, CompiledCircuit, ProofData };
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Abi, WitnessMap } from '@noir-lang/types';
2+
3+
export function flattenPublicInputs(publicInputs: WitnessMap): string[] {
4+
const publicInputIndices = [...publicInputs.keys()].sort();
5+
const flattenedPublicInputs = publicInputIndices.map((index) => publicInputs.get(index) as string);
6+
return flattenedPublicInputs;
7+
}
8+
9+
export function flattenPublicInputsAsArray(publicInputs: WitnessMap): Uint8Array {
10+
const flatPublicInputs = flattenPublicInputs(publicInputs);
11+
const flattenedPublicInputs = flatPublicInputs.map(hexToUint8Array);
12+
return flattenUint8Arrays(flattenedPublicInputs);
13+
}
14+
15+
export function deflattenPublicInputs(flattenedPublicInputs: Uint8Array, abi: Abi): WitnessMap {
16+
const publicInputSize = 32;
17+
const chunkedFlattenedPublicInputs: Uint8Array[] = [];
18+
19+
for (let i = 0; i < flattenedPublicInputs.length; i += publicInputSize) {
20+
const publicInput = flattenedPublicInputs.slice(i, i + publicInputSize);
21+
chunkedFlattenedPublicInputs.push(publicInput);
22+
}
23+
24+
const return_value_witnesses = abi.return_witnesses;
25+
const public_parameters = abi.parameters.filter((param) => param.visibility === 'public');
26+
const public_parameter_witnesses: number[] = public_parameters.flatMap((param) =>
27+
abi.param_witnesses[param.name].flatMap((witness_range) =>
28+
Array.from({ length: witness_range.end - witness_range.start }, (_, i) => witness_range.start + i),
29+
),
30+
);
31+
32+
// We now have an array of witness indices which have been deduplicated and sorted in ascending order.
33+
// The elements of this array should correspond to the elements of `flattenedPublicInputs` so that we can build up a `WitnessMap`.
34+
const public_input_witnesses = [...new Set(public_parameter_witnesses.concat(return_value_witnesses))].sort();
35+
36+
const publicInputs: WitnessMap = new Map();
37+
public_input_witnesses.forEach((witness_index, index) => {
38+
const witness_value = uint8ArrayToHex(chunkedFlattenedPublicInputs[index]);
39+
publicInputs.set(witness_index, witness_value);
40+
});
41+
42+
return publicInputs;
43+
}
44+
45+
function flattenUint8Arrays(arrays: Uint8Array[]): Uint8Array {
46+
const totalLength = arrays.reduce((acc, val) => acc + val.length, 0);
47+
const result = new Uint8Array(totalLength);
48+
49+
let offset = 0;
50+
for (const arr of arrays) {
51+
result.set(arr, offset);
52+
offset += arr.length;
53+
}
54+
55+
return result;
56+
}
57+
58+
function uint8ArrayToHex(buffer: Uint8Array): string {
59+
const hex: string[] = [];
60+
61+
buffer.forEach(function (i) {
62+
let h = i.toString(16);
63+
if (h.length % 2) {
64+
h = '0' + h;
65+
}
66+
hex.push(h);
67+
});
68+
69+
return '0x' + hex.join('');
70+
}
71+
72+
function hexToUint8Array(hex: string): Uint8Array {
73+
const sanitised_hex = BigInt(hex).toString(16).padStart(64, '0');
74+
75+
const len = sanitised_hex.length / 2;
76+
const u8 = new Uint8Array(len);
77+
78+
let i = 0;
79+
let j = 0;
80+
while (i < len) {
81+
u8[i] = parseInt(sanitised_hex.slice(j, j + 2), 16);
82+
i += 1;
83+
j += 2;
84+
}
85+
86+
return u8;
87+
}

tooling/noir_js_types/src/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Abi } from '@noir-lang/noirc_abi';
1+
import { Abi, WitnessMap } from '@noir-lang/noirc_abi';
2+
3+
export { Abi, WitnessMap } from '@noir-lang/noirc_abi';
24

35
export interface Backend {
46
/**
@@ -43,7 +45,7 @@ export interface Backend {
4345
* */
4446
export type ProofData = {
4547
/** @description Public inputs of a proof */
46-
publicInputs: Uint8Array[];
48+
publicInputs: WitnessMap;
4749
/** @description An byte array representing the proof */
4850
proof: Uint8Array;
4951
};

0 commit comments

Comments
 (0)