From 64202db37ea7c3fadd72f74cf1eddb1b4056af85 Mon Sep 17 00:00:00 2001 From: erhant Date: Fri, 19 May 2023 22:35:07 +0300 Subject: [PATCH 1/2] static func + better outputs soon --- README.md | 7 ++-- package.json | 2 +- ptau/README.md | 2 +- tests/fibonacci.test.ts | 8 ++-- tests/float_add.test.ts | 18 ++++---- tests/multiplier.test.ts | 15 ++++--- tests/sudoku.test.ts | 14 +++---- types/circuit.ts | 30 ++++++++++--- types/wasmTester.ts | 4 +- utils/instantiate.ts | 2 +- utils/proofTester.ts | 2 +- utils/wasmTester.ts | 91 ++++++++++++++++++++++++++++++---------- 12 files changed, 135 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 7a7988b..35a1bc4 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ - [x] **Simple CLI**: A very easy to use CLI is provided as a wrapper around SnarkJS commands, and they are all provided as `package.json` scripts! - [x] **Easily Configurable**: Just change the configured proof-system & elliptic curve at [`.cli.env`](./.cli.env) and you are good to go. - [x] **Witness Testing**: You can test computations & assertions for every template in a circuit, with minimal code-repetition. +- [ ] **Easy Outputs**: Parsing the output from the witness is no more a headache. - [x] **Proof Testing**: With prover & verification keys and the WASM circuit, you can test proof generation & verification. - [x] **Type-safe**: Witness & proof testers, as well as circuit signal inputs & outputs are type-safe. - [x] **Solidity Exports**: Export a verifier contract in Solidity, or export a calldata for your proofs & public signals. @@ -133,7 +134,7 @@ To run a circuit, you need to create a `main` component in Circom, where your ma ```ts import {instantiate} from '../utils/instantiate'; -import {createWasmTester} from '../utils/wasmTester'; +import {WasmTester} from '../utils/wasmTester'; describe('multiplier', () => { // templates parameters! @@ -150,7 +151,7 @@ describe('multiplier', () => { publicInputs: [], templateParams: [N], }); - circuit = await createWasmTester(circuitName); + circuit = await WasmTester.new(circuitName); // constraint count checks! await circuit.checkConstraintCount(N - 1); @@ -204,7 +205,7 @@ describe('multiplier utilities', () => { }, 'test/multiplier' ); - circuit = await createWasmTester(circuitName, 'test/multiplier'); + circuit = await WasmTester.new(circuitName, 'test/multiplier'); }); it('should pass for in range', async () => { diff --git a/package.json b/package.json index 67dc66f..8935599 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "erhant", "license": "MIT", "engines": { - "node": ">=10.4.0" + "node": ">=12.0.0" }, "scripts": { "test:all": "npx mocha", diff --git a/ptau/README.md b/ptau/README.md index e26070f..229de04 100644 --- a/ptau/README.md +++ b/ptau/README.md @@ -2,4 +2,4 @@ $$ \left(g^\tau, g^{\tau^2}, g^{\tau^3}, g^{\tau^4}, \ldots, g^{\tau^d}\right) $$ -For circuit-specific setup phase, you need to have `ptau` files for the first phase. See [SnarkJS docs](https://github.com/iden3/snarkjs#7-prepare-phase-2) for more information. +For circuit setups, you need to have `ptau` files from the universal setup phase. See [SnarkJS docs](https://github.com/iden3/snarkjs#7-prepare-phase-2) for more information. diff --git a/tests/fibonacci.test.ts b/tests/fibonacci.test.ts index 98905b6..2cc279e 100644 --- a/tests/fibonacci.test.ts +++ b/tests/fibonacci.test.ts @@ -1,5 +1,5 @@ -import {instantiate} from '../utils/instantiate'; -import {WasmTester, createWasmTester} from '../utils/wasmTester'; +import instantiate from '../utils/instantiate'; +import WasmTester from '../utils/wasmTester'; const CIRCUIT_FILE = 'fibonacci'; describe(CIRCUIT_FILE, () => { @@ -14,7 +14,7 @@ describe(CIRCUIT_FILE, () => { publicInputs: [], templateParams: [N], }); - circuit = await createWasmTester(circuitName); + circuit = await WasmTester.new(circuitName); await circuit.checkConstraintCount(); }); @@ -43,7 +43,7 @@ describe.skip(CIRCUIT_FILE + ' recursive', () => { publicInputs: [], templateParams: [N], }); - circuit = await createWasmTester(circuitName); + circuit = await WasmTester.new(circuitName); await circuit.checkConstraintCount(); }); diff --git a/tests/float_add.test.ts b/tests/float_add.test.ts index 63eb9f4..c6d29cc 100644 --- a/tests/float_add.test.ts +++ b/tests/float_add.test.ts @@ -1,5 +1,5 @@ -import {instantiate} from '../utils/instantiate'; -import {WasmTester, createWasmTester} from '../utils/wasmTester'; +import instantiate from '../utils/instantiate'; +import WasmTester from '../utils/wasmTester'; // tests adapted from https://github.com/rdi-berkeley/zkp-mooc-lab @@ -25,7 +25,7 @@ describe('float_add 32-bit', () => { publicInputs: [], templateParams: [k, p], }); - circuit = await createWasmTester('fp32'); + circuit = await WasmTester.new('fp32'); await circuit.checkConstraintCount(401); }); @@ -130,7 +130,7 @@ describe('float_add 64-bit', () => { publicInputs: [], templateParams: [k, p], }); - circuit = await createWasmTester('fp64'); + circuit = await WasmTester.new('fp64'); await circuit.checkConstraintCount(819); }); @@ -226,7 +226,7 @@ describe('float_add utilities', () => { }, 'test/float_add' ); - circuit = await createWasmTester(circuitName, 'test/float_add'); + circuit = await WasmTester.new(circuitName, 'test/float_add'); await circuit.checkConstraintCount(expectedConstraints.checkBitLength(b)); }); @@ -265,7 +265,7 @@ describe('float_add utilities', () => { }, 'test/float_add' ); - circuit = await createWasmTester(circuitName, 'test/float_add'); + circuit = await WasmTester.new(circuitName, 'test/float_add'); await circuit.checkConstraintCount(expectedConstraints.leftShift(shift_bound)); }); @@ -325,7 +325,7 @@ describe('float_add utilities', () => { }, 'test/float_add' ); - circuit = await createWasmTester(circuitName, 'test/float_add'); + circuit = await WasmTester.new(circuitName, 'test/float_add'); await circuit.checkConstraintCount(b); }); @@ -363,7 +363,7 @@ describe('float_add utilities', () => { }, 'test/float_add' ); - circuit = await createWasmTester(circuitName, 'test/float_add'); + circuit = await WasmTester.new(circuitName, 'test/float_add'); await circuit.checkConstraintCount(expectedConstraints.normalize(P)); }); @@ -422,7 +422,7 @@ describe('float_add utilities', () => { }, 'test/float_add' ); - circuit = await createWasmTester(circuitName, 'test/float_add'); + circuit = await WasmTester.new(circuitName, 'test/float_add'); await circuit.checkConstraintCount(expectedConstraints.msnzb(b)); }); diff --git a/tests/multiplier.test.ts b/tests/multiplier.test.ts index ce9d667..ba1bd1c 100644 --- a/tests/multiplier.test.ts +++ b/tests/multiplier.test.ts @@ -1,7 +1,7 @@ -import {WasmTester, createWasmTester} from '../utils/wasmTester'; -import {ProofTester} from '../utils/proofTester'; +import WasmTester from '../utils/wasmTester'; +import ProofTester from '../utils/proofTester'; import type {FullProof} from '../types/circuit'; -import {instantiate} from '../utils/instantiate'; +import instantiate from '../utils/instantiate'; describe('multiplier', () => { // templates parameters! @@ -18,10 +18,15 @@ describe('multiplier', () => { publicInputs: [], templateParams: [N], }); - circuit = await createWasmTester(circuitName); + circuit = await WasmTester.new(circuitName); // constraint count checks! await circuit.checkConstraintCount(N - 1); + + // TODO: output test + // @ts-ignore + const output = await circuit.parseOutput({in: [2, 3, 8]}, 'in', 'out'); + console.log('Output:', output); }); it('should multiply correctly', async () => { @@ -53,7 +58,7 @@ describe('multiplier utilities', () => { }, 'test/multiplier' ); - circuit = await createWasmTester(circuitName, 'test/multiplier'); + circuit = await WasmTester.new(circuitName, 'test/multiplier'); }); it('should multiply correctly', async () => { diff --git a/tests/sudoku.test.ts b/tests/sudoku.test.ts index fd4ab4f..5bd5274 100644 --- a/tests/sudoku.test.ts +++ b/tests/sudoku.test.ts @@ -1,5 +1,5 @@ -import {instantiate} from '../utils/instantiate'; -import {WasmTester, createWasmTester} from '../utils/wasmTester'; +import instantiate from '../utils/instantiate'; +import WasmTester from '../utils/wasmTester'; type BoardSizes = 4 | 9; @@ -47,7 +47,7 @@ const INPUTS: {[N in BoardSizes]: any} = { ([4, 9] as BoardSizes[]).map(N => describe(`sudoku (${N} by ${N})`, () => { const INPUT = INPUTS[N]; - let circuit: WasmTester<['solution', 'puzzle'], []>; + let circuit: WasmTester<['solution', 'puzzle']>; before(async () => { const circuitName = `sudoku_${N}x${N}`; @@ -57,7 +57,7 @@ const INPUTS: {[N in BoardSizes]: any} = { publicInputs: ['puzzle'], templateParams: [Math.sqrt(N)], }); - circuit = await createWasmTester(circuitName); + circuit = await WasmTester.new(circuitName); await circuit.checkConstraintCount(); }); @@ -115,7 +115,7 @@ describe('sudoku utilities', () => { }, 'test/sudoku' ); - circuit = await createWasmTester(circuitName, 'test/sudoku'); + circuit = await WasmTester.new(circuitName, 'test/sudoku'); }); it('should pass for input < 2^b', async () => { @@ -151,7 +151,7 @@ describe('sudoku utilities', () => { }, 'test/sudoku' ); - circuit = await createWasmTester(circuitName, 'test/sudoku'); + circuit = await WasmTester.new(circuitName, 'test/sudoku'); }); it('should pass if all inputs are unique', async () => { @@ -191,7 +191,7 @@ describe('sudoku utilities', () => { }, 'test/sudoku' ); - circuit = await createWasmTester(circuitName, 'test/sudoku'); + circuit = await WasmTester.new(circuitName, 'test/sudoku'); }); it('should pass for in range', async () => { diff --git a/types/circuit.ts b/types/circuit.ts index 580d8ac..e7da0c5 100644 --- a/types/circuit.ts +++ b/types/circuit.ts @@ -1,15 +1,23 @@ -type IntegerValue = `${number}` | number | bigint; -type SignalValue = IntegerValue | SignalValue[]; +/** + * An integer value is a numerical string, a number, or a bigint. + */ +type IntegerValueType = `${number}` | number | bigint; + +/** + * A signal value is a number, or an array of numbers (recursively). + */ +export type SignalValueType = IntegerValueType | SignalValueType[]; /** * An object with string keys and array of numerical values. * Each key represents a signal name as it appears in the circuit. + * * By default, signal names are not typed, but you can pass an array of signal names * to make them type-safe, e.g. `CircuitSignals<['sig1', 'sig2']>` */ export type CircuitSignals = T extends [] - ? {[signal: string]: SignalValue} - : {[signal in T[number]]: SignalValue}; + ? {[signal: string]: SignalValueType} + : {[signal in T[number]]: SignalValueType}; /** * A witness is an array of bigints, corresponding to the values of each wire in @@ -17,6 +25,18 @@ export type CircuitSignals = T extends [] */ export type WitnessType = bigint[]; +/** + * Symbols are a mapping of each circuit `wire` to an object with three keys. Within them, + * the most important is `varIdx` which indicates the position of this signal in the witness array. + */ +export type SymbolsType = { + [symbol: string]: { + labelIdx: number; + varIdx: number; + componentIdx: number; + }; +}; + /** * A FullProof, as returned from SnarkJS `fullProve` function. */ @@ -26,6 +46,6 @@ export type FullProof = { }; /** - * Proof system to be used + * Proof system to be used by SnarkJS. */ export type ProofSystem = 'groth16' | 'plonk' | 'fflonk'; diff --git a/types/wasmTester.ts b/types/wasmTester.ts index 14c3105..af852bb 100644 --- a/types/wasmTester.ts +++ b/types/wasmTester.ts @@ -1,4 +1,4 @@ -import {WitnessType, CircuitSignals} from './circuit'; +import {WitnessType, CircuitSignals, SymbolsType} from './circuit'; /** * A simple type-wrapper for `circom_tester` WASM tester class. @@ -13,6 +13,6 @@ export type CircomWasmTester = { loadConstraints: () => Promise; constraints: any[] | undefined; loadSymbols: () => Promise; - symbols: object | undefined; + symbols: SymbolsType | undefined; getDecoratedOutput: (witness: WitnessType) => Promise; }; diff --git a/utils/instantiate.ts b/utils/instantiate.ts index e4c1888..0c3c2cc 100644 --- a/utils/instantiate.ts +++ b/utils/instantiate.ts @@ -9,7 +9,7 @@ import {CircuitConfig} from '../types/config'; * @param circuitConfig circuit configurations, if `undefined` then `circuit.config.ts` will be used. * @param directory name of the directory under circuits to be created. Can be given sub-folders like `test/myCircuit/foobar`. Defaults to `test` */ -export function instantiate(name: string, circuitConfig?: CircuitConfig, directory = 'test') { +export default function instantiate(name: string, circuitConfig?: CircuitConfig, directory = 'test') { // get config from circuit.config.ts if none are given if (circuitConfig === undefined) { if (!(name in config)) { diff --git a/utils/proofTester.ts b/utils/proofTester.ts index dd32c1c..6a3d75b 100644 --- a/utils/proofTester.ts +++ b/utils/proofTester.ts @@ -9,7 +9,7 @@ const PROOF_SYSTEMS = ['groth16', 'plonk', 'fflonk'] as const; * A more extensive Circuit class, able to generate proofs & verify them. * Assumes that prover key and verifier key have been computed. */ -export class ProofTester { +export default class ProofTester { public readonly protocol: ProofSystem; private readonly wasmPath: string; private readonly proverKeyPath: string; diff --git a/utils/wasmTester.ts b/utils/wasmTester.ts index 3213380..3c7d0d9 100644 --- a/utils/wasmTester.ts +++ b/utils/wasmTester.ts @@ -1,12 +1,12 @@ const wasm_tester = require('circom_tester').wasm; -import {WitnessType, CircuitSignals} from '../types/circuit'; +import {WitnessType, CircuitSignals, SymbolsType, SignalValueType} from '../types/circuit'; import {CircomWasmTester} from '../types/wasmTester'; import {assert, expect} from 'chai'; /** A utility class to test your circuits. Use `expectFailedAssert` and `expectCorrectAssert` to test out evaluations */ -export class WasmTester { +export default class WasmTester { /** * The underlying `circom_tester` object */ @@ -15,7 +15,7 @@ export class WasmTester { await this.circomWasmTester.loadSymbols(); @@ -139,20 +142,66 @@ export class WasmTester( - circuitName: string, - dir = 'test' -): Promise> { - const circomWasmTester: CircomWasmTester = await wasm_tester(`./circuits/${dir}/${circuitName}.circom`, { - include: 'node_modules', // will link circomlib circuits - }); - return new WasmTester(circomWasmTester); + /** + * @param input input signals + * @param outputSignals an array of signal names + */ + async parseOutput( + input: CircuitSignals, + ...outputSignals: OUT[number][] + ): Promise>> { + const witness = await this.calculateWitness(input, true); + + // get symbols of main component + await this.loadSymbols(); + const symbolNames = Object.keys(this.symbols!).filter(signal => !signal.includes('.', 5)); // non-main signals have an additional `.` in them after `main.symbol` + + // for each out signal, process the respective symbol + const entries: [OUT[number], SignalValueType][] = []; + console.log('SYMBOL NAMES:', this.symbols); + + for (const outSignal of outputSignals) { + // get the symbol values from symbol names + const symbols = symbolNames.filter(s => s.startsWith(outSignal, 5)); + console.log('SYMBOLS:', symbols); + + // we can assume that a symbol with this name appears only once in `main`, and that the depth is same for + // all occurences of this symbol, given the type system used in Circom. So, we can just count the number + // of `[`s in the first symbol of this signal to find the number of dimensions of this signal. + const splits = symbols.at(-1)!.split('['); + const depth = splits.length - 1; + const startIdx = this.symbols![symbols[0]].varIdx; + + if (depth === 0) { + // easy case, just return the witness of this symbol + entries.push([outSignal, witness[startIdx]]); + } else { + const dims = splits.slice(1).map(dim => parseInt(dim.slice(0, -1))); + + // TODO + + entries.push([outSignal, 0]); // TODO + } + } + + // TODO: find better typing + return Object.fromEntries(entries) as CircuitSignals; + } + + /** + * Compiles and reutrns a circuit tester class instance. + * @param circuit name of circuit + * @param dir directory to read the circuit from, defaults to `test` + * @returns a `WasmTester` instance + */ + static async new( + circuitName: string, + dir = 'test' + ): Promise> { + const circomWasmTester: CircomWasmTester = await wasm_tester(`./circuits/${dir}/${circuitName}.circom`, { + include: 'node_modules', // will link circomlib circuits + }); + return new WasmTester(circomWasmTester); + } } From 69863fc2d27c9cbbbad2ab287131e87b1e19fe9a Mon Sep 17 00:00:00 2001 From: erhant Date: Sat, 20 May 2023 00:30:46 +0300 Subject: [PATCH 2/2] outputs --- README.md | 22 ++++++++++-- tests/float_add.test.ts | 12 ++++++- tests/multiplier.test.ts | 7 +--- utils/wasmTester.ts | 76 +++++++++++++++++++++++++++------------- 4 files changed, 83 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 35a1bc4..9ccb1c2 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ - [x] **Simple CLI**: A very easy to use CLI is provided as a wrapper around SnarkJS commands, and they are all provided as `package.json` scripts! - [x] **Easily Configurable**: Just change the configured proof-system & elliptic curve at [`.cli.env`](./.cli.env) and you are good to go. - [x] **Witness Testing**: You can test computations & assertions for every template in a circuit, with minimal code-repetition. -- [ ] **Easy Outputs**: Parsing the output from the witness is no more a headache. - [x] **Proof Testing**: With prover & verification keys and the WASM circuit, you can test proof generation & verification. -- [x] **Type-safe**: Witness & proof testers, as well as circuit signal inputs & outputs are type-safe. +- [x] **Simple Outputs**: Easily see the output signals of your circuit, without generating a proof. +- [x] **Type-safe**: Witness & proof testers, as well as circuit signal inputs & outputs are all type-safe via generics. - [x] **Solidity Exports**: Export a verifier contract in Solidity, or export a calldata for your proofs & public signals. ## Usage @@ -184,6 +184,22 @@ With the circuit object, we can do the following: - `circuit.expectCorrectAssert(input)` to test whether the circuit assertions pass for some given input - `circuit.expectFailedAssert(input)` to test whether the circuit assertions pass for some given input +#### Circuit outputs + +What if we would just like to see what the output is, instead of comparing it to some witness? Well, that would be a trouble because we would have to parse the witness array (which is huge for some circuits) with respect to which signals the output signals correspond to. Thankfully, Circomkit has a function for that: + +```ts +const outputSignals = ['foo', 'bar']; +const output = await circuit.compute(INPUT, outputSignals); +console.log(output); +/* { + foo: [[1n, 2n], [3n, 4n]] + bar: 48n +} */ +``` + +Note that this operation requires parsing the symbols file (`.sym`) and reading the witness array, which may be costly for large circuits. Most of the time, you won't need this for testing; instead, you will likely use it to see what the circuit actually does for debugging. + #### Multiple templates You will often have multiple templates in your circuit code, and you might want to test them in the same test file of your main circuit too. Well, you can! @@ -241,7 +257,7 @@ describe('multiplier proofs', () => { await circuit.expectVerificationPass(fullProof.proof, fullProof.publicSignals); }); - it('should NOT verify a wrong multiplication', async () => { + it('should NOT verify', async () => { // just give a prime number as the output, assuming none of the inputs are 1 await circuit.expectVerificationFail(fullProof.proof, ['13']); }); diff --git a/tests/float_add.test.ts b/tests/float_add.test.ts index c6d29cc..0689294 100644 --- a/tests/float_add.test.ts +++ b/tests/float_add.test.ts @@ -37,6 +37,16 @@ describe('float_add 32-bit', () => { }, {e_out: '43', m_out: '11672136'} ); + + console.log( + await circuit.compute( + { + e: ['43', '5'], + m: ['11672136', '10566265'], + }, + ['e_out', 'm_out'] + ) + ); }); it('case II test 1', async () => { @@ -230,7 +240,7 @@ describe('float_add utilities', () => { await circuit.checkConstraintCount(expectedConstraints.checkBitLength(b)); }); - it('should give 1 for in <= b', async () => { + it('should give 1 for in ≤ b', async () => { await circuit.expectCorrectAssert( { in: '4903265', diff --git a/tests/multiplier.test.ts b/tests/multiplier.test.ts index ba1bd1c..9686bdd 100644 --- a/tests/multiplier.test.ts +++ b/tests/multiplier.test.ts @@ -22,11 +22,6 @@ describe('multiplier', () => { // constraint count checks! await circuit.checkConstraintCount(N - 1); - - // TODO: output test - // @ts-ignore - const output = await circuit.parseOutput({in: [2, 3, 8]}, 'in', 'out'); - console.log('Output:', output); }); it('should multiply correctly', async () => { @@ -88,7 +83,7 @@ describe('multiplier proofs', () => { await circuit.expectVerificationPass(fullProof.proof, fullProof.publicSignals); }); - it('should NOT verify a wrong multiplication', async () => { + it('should NOT verify', async () => { // just give a prime number as the output, assuming none of the inputs are 1 await circuit.expectVerificationFail(fullProof.proof, ['13']); }); diff --git a/utils/wasmTester.ts b/utils/wasmTester.ts index 3c7d0d9..a4e7322 100644 --- a/utils/wasmTester.ts +++ b/utils/wasmTester.ts @@ -106,15 +106,15 @@ export default class WasmTester expected) { - console.log(`\x1b[0;33m! expectation ${expected}\x1b[0m`); + console.log(`\x1b[0;33m! expectation: ${expected}\x1b[0m`); } else { - console.log(`\x1b[0;32m✔\x1b[2;37m expectation ${expected}\x1b[0m`); + console.log(`\x1b[0;32m✔\x1b[2;37m expectation: ${expected}\x1b[0m`); } } } @@ -144,13 +144,21 @@ export default class WasmTester, - ...outputSignals: OUT[number][] - ): Promise>> { + async compute(input: CircuitSignals, outputSignals: OUT): Promise>> { const witness = await this.calculateWitness(input, true); // get symbols of main component @@ -159,34 +167,54 @@ export default class WasmTester s.startsWith(outSignal, 5)); - console.log('SYMBOLS:', symbols); - // we can assume that a symbol with this name appears only once in `main`, and that the depth is same for - // all occurences of this symbol, given the type system used in Circom. So, we can just count the number - // of `[`s in the first symbol of this signal to find the number of dimensions of this signal. + /* + we can assume that a symbol with this name appears only once in `main`, and that the depth is same for + all occurences of this symbol, given the type system used in Circom. So, we can just count the number + of `[`s in any symbol of this signal to find the number of dimensions of this signal. + we particularly choose the last symbol in the array, as that holds the maximum index of each dimension of this array. + */ const splits = symbols.at(-1)!.split('['); - const depth = splits.length - 1; - const startIdx = this.symbols![symbols[0]].varIdx; - if (depth === 0) { + // since we chose the last symbol, we have something like `main.signal[dim1][dim2]...[dimN]` which we can parse + const dims = splits.slice(1).map(dim => parseInt(dim.slice(0, -1)) + 1); // +1 is needed because the final value is 0-indexed + + // since signal names are consequent, we only need to know the witness index of the first symbol + let idx = this.symbols![symbols[0]].varIdx; + + if (dims.length === 0) { // easy case, just return the witness of this symbol - entries.push([outSignal, witness[startIdx]]); + entries.push([outSignal, witness[idx]]); } else { - const dims = splits.slice(1).map(dim => parseInt(dim.slice(0, -1))); - - // TODO - - entries.push([outSignal, 0]); // TODO + /* + at this point, we have an array of signals like `main.signal[0..dim1][0..dim2]..[0..dimN]` and we must construct + the necessary multi-dimensional array out of it. + */ + // eslint-disable-next-line no-inner-declarations + function processDepth(d: number): SignalValueType { + const acc: SignalValueType = []; + if (d === dims.length - 1) { + // final depth, count witnesses + for (let i = 0; i < dims[d]; i++) { + acc.push(witness[idx++]); + } + } else { + // not final depth, recurse to next + for (let i = 0; i < dims[d]; i++) { + acc.push(processDepth(d + 1)); + } + } + return acc; + } + entries.push([outSignal, processDepth(0)]); } } - // TODO: find better typing - return Object.fromEntries(entries) as CircuitSignals; + return Object.fromEntries(entries) as CircuitSignals; } /**