From 3899016a62bf8f58b9afe775593ad33b4e3d9bfa Mon Sep 17 00:00:00 2001 From: numtel Date: Wed, 4 Dec 2024 02:18:49 -0800 Subject: [PATCH 1/8] Better matching for witnessTester compute signal names --- src/testers/witnessTester.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testers/witnessTester.ts b/src/testers/witnessTester.ts index 8f362aa..82dddca 100644 --- a/src/testers/witnessTester.ts +++ b/src/testers/witnessTester.ts @@ -223,7 +223,7 @@ export class WitnessTester s.startsWith(`main.${signal}`) && signalDotCount === dotCount(s) + s => s.match(new RegExp(`^main\\.${signal}(\\[|$|\\.)`)) && signalDotCount === dotCount(s) ); // get the symbol values from symbol names, ignoring `main.` prefix From eacfa6b88bd5ccf47497bcdda0360854897ffe6c Mon Sep 17 00:00:00 2001 From: numtel Date: Wed, 4 Dec 2024 03:14:09 -0800 Subject: [PATCH 2/8] WitnessTester.parseConstraints helper --- README.md | 11 ++++++ src/testers/witnessTester.ts | 67 ++++++++++++++++++++++++++++++++++++ tests/witnessTester.test.ts | 16 +++++++++ 3 files changed, 94 insertions(+) diff --git a/README.md b/README.md index 7db1487..416d4b6 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,17 @@ it('should have correct number of constraints', async () => { }); ``` +> +> +> Warning +>
+> +> You can also generate an array of human-readable formulas for each constraint using `parseConstraints`: +> +> ```ts +> console.log((await circuit.parseConstraints()).join('\n')); +> ``` + If you want more control over the output signals, you can use the `compute` function. It takes in an input, and an array of output signal names used in the `main` component so that they can be extracted from the witness. ```ts diff --git a/src/testers/witnessTester.ts b/src/testers/witnessTester.ts index 82dddca..f377eae 100644 --- a/src/testers/witnessTester.ts +++ b/src/testers/witnessTester.ts @@ -138,6 +138,73 @@ export class WitnessTester { + await this.loadConstraints(); + await this.loadSymbols(); + + // @ts-ignore + const varsById = Object.entries(this.symbols).reduce((out, cur) => { + // @ts-ignore + const id = cur[1].varIdx; + if(id !== -1) { + // @ts-ignore + out[id] = cur[0]; + } + return out; + }, {}); + + // @ts-ignore + const fieldSize = this.circomTester.witnessCalculator.prime; + + // @ts-ignore + const parsedConstraints = this.constraints.map(constraint => { + // @ts-ignore + const groups = constraint.map(item => { + const vars = Object.keys(item).reduce((out, cur) => { + // @ts-ignore + const coeffRaw = item[cur]; + const coeff = coeffRaw > fieldSize / 2n ? coeffRaw - fieldSize : coeffRaw; + // @ts-ignore + const varName = varsById[cur]; + out.push( + // @ts-ignore + coeff === -1n && varName ? '-' + varName : + coeff === 1n && varName ? varName : + !varName ? `${coeff}` : + `(${coeff} * ${varName})`, + ); + return out; + }, []); + return vars.reduce((out, cur, index) => + // @ts-ignore + out + (index > 0 ? cur.startsWith('-') ? ` - ${cur.slice(1)}` : ` + ${cur}` : cur), + ''); + }) + // @ts-ignore + .map(curVar => curVar.indexOf(' ') === -1 ? curVar : `(${curVar})`); + + return ( + groups[0] + + (groups[1] ? ' * ' + groups[1] : '') + + (groups[2] ? + groups[2].startsWith('-') ? + ` + ${groups[2].slice(1)}` + : groups[0] || groups[1] ? + ' - ' + groups[2] + : groups[2].startsWith('(') ? + groups[2].slice(1, -1) + : groups[2] + : '') + + ' = 0' + ); + }); + // @ts-ignore + return parsedConstraints; + } + /** * Override witness value to try and fake a proof. If the circuit has soundness problems (i.e. * some signals are not constrained correctly), then you may be able to create a fake witness by diff --git a/tests/witnessTester.test.ts b/tests/witnessTester.test.ts index dce8957..664ce59 100644 --- a/tests/witnessTester.test.ts +++ b/tests/witnessTester.test.ts @@ -31,6 +31,22 @@ describe('witness tester', () => { // should also work for non-exact too, where we expect at least some amount await circuit.expectConstraintCount(size!); await circuit.expectConstraintCount(size! - 1); + + const parsedConstraints = await circuit.parseConstraints(); + + expect(parsedConstraints.join('\n')).toEqual( +`-main.in[0] * main.in[1] + main.inner[0] = 0 +-main.inner[0] * main.in[2] + main.inner[1] = 0 +-main.inner[1] * main.in[3] + main.out = 0 +main.isZero[0].in * main.isZero[0].inv - 1 = 0 +main.isZero[1].in * main.isZero[1].inv - 1 = 0 +main.isZero[2].in * main.isZero[2].inv - 1 = 0 +main.isZero[3].in * main.isZero[3].inv - 1 = 0 +-1 + main.in[0] - main.isZero[0].in = 0 +-1 + main.in[1] - main.isZero[1].in = 0 +-1 + main.in[2] - main.isZero[2].in = 0 +-1 + main.in[3] - main.isZero[3].in = 0`); + }); it('should assert correctly', async () => { From 9aaa378b42dae106dac49731ab7c78608bff0a3c Mon Sep 17 00:00:00 2001 From: numtel Date: Wed, 4 Dec 2024 03:26:01 -0800 Subject: [PATCH 3/8] Fix build --- src/testers/witnessTester.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/testers/witnessTester.ts b/src/testers/witnessTester.ts index f377eae..872097f 100644 --- a/src/testers/witnessTester.ts +++ b/src/testers/witnessTester.ts @@ -166,13 +166,13 @@ export class WitnessTester { // @ts-ignore const coeffRaw = item[cur]; - const coeff = coeffRaw > fieldSize / 2n ? coeffRaw - fieldSize : coeffRaw; + const coeff = coeffRaw > fieldSize / BigInt(2) ? coeffRaw - fieldSize : coeffRaw; // @ts-ignore const varName = varsById[cur]; out.push( // @ts-ignore - coeff === -1n && varName ? '-' + varName : - coeff === 1n && varName ? varName : + coeff === BigInt(-1) && varName ? '-' + varName : + coeff === BigInt(1) && varName ? varName : !varName ? `${coeff}` : `(${coeff} * ${varName})`, ); From 41c66a326d788ecead38e86f55af300489bd8ccf Mon Sep 17 00:00:00 2001 From: numtel Date: Wed, 4 Dec 2024 03:45:09 -0800 Subject: [PATCH 4/8] Remove "main." from output --- src/testers/witnessTester.ts | 2 +- tests/witnessTester.test.ts | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/testers/witnessTester.ts b/src/testers/witnessTester.ts index 872097f..dca2a30 100644 --- a/src/testers/witnessTester.ts +++ b/src/testers/witnessTester.ts @@ -151,7 +151,7 @@ export class WitnessTester { const parsedConstraints = await circuit.parseConstraints(); expect(parsedConstraints.join('\n')).toEqual( -`-main.in[0] * main.in[1] + main.inner[0] = 0 --main.inner[0] * main.in[2] + main.inner[1] = 0 --main.inner[1] * main.in[3] + main.out = 0 -main.isZero[0].in * main.isZero[0].inv - 1 = 0 -main.isZero[1].in * main.isZero[1].inv - 1 = 0 -main.isZero[2].in * main.isZero[2].inv - 1 = 0 -main.isZero[3].in * main.isZero[3].inv - 1 = 0 --1 + main.in[0] - main.isZero[0].in = 0 --1 + main.in[1] - main.isZero[1].in = 0 --1 + main.in[2] - main.isZero[2].in = 0 --1 + main.in[3] - main.isZero[3].in = 0`); +`-in[0] * in[1] + inner[0] = 0 +-inner[0] * in[2] + inner[1] = 0 +-inner[1] * in[3] + out = 0 +isZero[0].in * isZero[0].inv - 1 = 0 +isZero[1].in * isZero[1].inv - 1 = 0 +isZero[2].in * isZero[2].inv - 1 = 0 +isZero[3].in * isZero[3].inv - 1 = 0 +-1 + in[0] - isZero[0].in = 0 +-1 + in[1] - isZero[1].in = 0 +-1 + in[2] - isZero[2].in = 0 +-1 + in[3] - isZero[3].in = 0`); }); From 1f09009cc7842e842c067cba1aad947fe9fd99fc Mon Sep 17 00:00:00 2001 From: numtel Date: Wed, 4 Dec 2024 04:01:12 -0800 Subject: [PATCH 5/8] Generate parsed constraints for multiplier n>=3 --- tests/common/circuits.ts | 14 ++++++++++++++ tests/witnessTester.test.ts | 17 +++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/common/circuits.ts b/tests/common/circuits.ts index 54f8954..5b120c6 100644 --- a/tests/common/circuits.ts +++ b/tests/common/circuits.ts @@ -7,6 +7,8 @@ type PreparedTestCircuit> = { signals: S; /** Name for the input path to an existing input JSON file under `inputs` folder. */ inputName: string; + /** To compare against generated value. */ + parsedConstraints: string[]; /** Circuit information. */ circuit: { /** Circuit name. */ @@ -45,6 +47,17 @@ export function prepareMultiplier(N: number, order: bigint = primes['bn128']) { // TOTAL: 3*N - 1 const size = 3 * N - 1; + let parsedConstraints; + if(N >= 3) { + parsedConstraints = [ + '-in[0] * in[1] + inner[0] = 0', + ...Array(N-3).fill(0).map((_, i) => `-inner[${i}] * in[${i+2}] + inner[${i+1}] = 0`), + `-inner[${N-3}] * in[${N-1}] + out = 0`, + ...Array(N).fill(0).map((_, i) => `isZero[${i}].in * isZero[${i}].inv - 1 = 0`), + ...Array(N).fill(0).map((_, i) => `-1 + in[${i}] - isZero[${i}].in = 0`), + ]; + } + const numbers: bigint[] = Array.from({length: N}, () => BigInt(2) + BigInt('0x' + randomBytes(8).toString('hex'))); const product: bigint = numbers.reduce((prev, acc) => acc * prev) % order; const malicious: bigint[] = Array.from({length: N}, () => BigInt(1)); @@ -64,5 +77,6 @@ export function prepareMultiplier(N: number, order: bigint = primes['bn128']) { signals, circuit: {name, config, size, exact: true}, inputName: 'input.test', + parsedConstraints, } as PreparedTestCircuit; } diff --git a/tests/witnessTester.test.ts b/tests/witnessTester.test.ts index 07af5d5..ad48394 100644 --- a/tests/witnessTester.test.ts +++ b/tests/witnessTester.test.ts @@ -9,6 +9,7 @@ describe('witness tester', () => { const { circuit: {name, config, size, exact}, signals, + parsedConstraints, } = prepareMultiplier(4); beforeAll(async () => { @@ -32,20 +33,8 @@ describe('witness tester', () => { await circuit.expectConstraintCount(size!); await circuit.expectConstraintCount(size! - 1); - const parsedConstraints = await circuit.parseConstraints(); - - expect(parsedConstraints.join('\n')).toEqual( -`-in[0] * in[1] + inner[0] = 0 --inner[0] * in[2] + inner[1] = 0 --inner[1] * in[3] + out = 0 -isZero[0].in * isZero[0].inv - 1 = 0 -isZero[1].in * isZero[1].inv - 1 = 0 -isZero[2].in * isZero[2].inv - 1 = 0 -isZero[3].in * isZero[3].inv - 1 = 0 --1 + in[0] - isZero[0].in = 0 --1 + in[1] - isZero[1].in = 0 --1 + in[2] - isZero[2].in = 0 --1 + in[3] - isZero[3].in = 0`); + const myParsedConstraints = await circuit.parseConstraints(); + expect(myParsedConstraints).toStrictEqual(parsedConstraints); }); From 65ef7b61c3bf6846e29089e23b1e5fef3ba93d89 Mon Sep 17 00:00:00 2001 From: numtel Date: Wed, 4 Dec 2024 10:42:27 -0800 Subject: [PATCH 6/8] Factored display of coefficients close to the field size --- src/testers/witnessTester.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/testers/witnessTester.ts b/src/testers/witnessTester.ts index dca2a30..af1c952 100644 --- a/src/testers/witnessTester.ts +++ b/src/testers/witnessTester.ts @@ -161,12 +161,22 @@ export class WitnessTester { + // Every constraint is 3 groups: <1> * <2> - <3> = 0 // @ts-ignore const groups = constraint.map(item => { + // Each group can contain many signals (with coefficients) summed const vars = Object.keys(item).reduce((out, cur) => { // @ts-ignore const coeffRaw = item[cur]; - const coeff = coeffRaw > fieldSize / BigInt(2) ? coeffRaw - fieldSize : coeffRaw; + // Display the coefficient as a signed value, helps a lot with -1 + let coeff = coeffRaw > fieldSize / BigInt(2) ? coeffRaw - fieldSize : coeffRaw; + // Reduce numbers that are factors of the field size for better readability + // @ts-ignore + const modP = BigInt(fieldSize % coeff); + // XXX: Why within 10000? + if(modP !== BigInt(0) && modP <= BigInt(10000)) { + coeff = `(ρ-${fieldSize % coeff})/${fieldSize/coeff}`; + } // @ts-ignore const varName = varsById[cur]; out.push( @@ -178,6 +188,8 @@ export class WitnessTester // @ts-ignore out + (index > 0 ? cur.startsWith('-') ? ` - ${cur.slice(1)}` : ` + ${cur}` : cur), From 0427aad840d00e3e75e019c5867c36a882a7df5e Mon Sep 17 00:00:00 2001 From: numtel Date: Fri, 6 Dec 2024 06:34:29 -0800 Subject: [PATCH 7/8] Use real p, not some other unicode look-a-like --- src/testers/witnessTester.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testers/witnessTester.ts b/src/testers/witnessTester.ts index af1c952..5a28fa5 100644 --- a/src/testers/witnessTester.ts +++ b/src/testers/witnessTester.ts @@ -175,7 +175,7 @@ export class WitnessTester Date: Sun, 8 Dec 2024 10:33:07 -0800 Subject: [PATCH 8/8] Add cli, core function for constraint printing --- README.md | 6 ++ src/cli.ts | 10 +++ src/core/index.ts | 22 ++++++- src/functions/r1cs.ts | 117 +++++++++++++++++++++++++++++++++++ src/testers/witnessTester.ts | 71 +-------------------- 5 files changed, 156 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 416d4b6..c16090c 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,12 @@ npx circomkit vkey [pkey-path] # Automatically download PTAU (for BN128) npx circomkit ptau + +# Display circuit info (e.g. constraint count) +npx circomkit info + +# Display human-readable constraint formulas +npx circomkit constraints ``` > [!NOTE] diff --git a/src/cli.ts b/src/cli.ts index ad421f1..2a71aa1 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -47,6 +47,15 @@ function cli(args: string[]) { console.log(`Number of Labels: ${info.labels}`); }); + /////////////////////////////////////////////////////////////////////////////// + const constraints = new Command('constraints') + .description('print constraint formulas') + .argument('', 'Circuit name') + .action(async circuit => { + const formulas = await circomkit.constraints(circuit); + console.log(formulas.join('\n')); + }); + /////////////////////////////////////////////////////////////////////////////// const clear = new Command('clear') .description('clear circuit build artifacts') @@ -232,6 +241,7 @@ function cli(args: string[]) { .addCommand(circuit) .addCommand(instantiate) .addCommand(info) + .addCommand(constraints) .addCommand(clear) .addCommand(contract) .addCommand(vkey) diff --git a/src/core/index.ts b/src/core/index.ts index 901e93e..e809398 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -15,7 +15,15 @@ import type {CircuitConfig, CircuitSignals, CircomTester} from '../types'; import {WitnessTester, ProofTester} from '../testers'; import {prettyStringify} from '../utils'; import {CircomkitConfig, DEFAULT, PRIMES, PROTOCOLS} from '../configs'; -import {compileCircuit, instantiateCircuit, readR1CSInfo, getCalldata} from '../functions'; +import { + compileCircuit, + instantiateCircuit, + readR1CSInfo, + getCalldata, + parseConstraints, + readSymbols, + stringifyBigIntsWithField, +} from '../functions'; import {CircomkitPath} from './pathing'; /** @@ -132,6 +140,18 @@ export class Circomkit { return await readR1CSInfo(this.path.ofCircuit(circuit, 'r1cs')); } + /** Returns human-readable constraint formula array. */ + async constraints(circuit: string) { + const r1csPath = this.path.ofCircuit(circuit, 'r1cs'); + const symPath = this.path.ofCircuit(circuit, 'sym'); + // @ts-ignore + const r1csFile = await import('r1csfile'); + const r1cs = await r1csFile.readR1cs(r1csPath, true, true, true); + const sym = await readSymbols(symPath); + const constraints = stringifyBigIntsWithField(r1cs.curve.Fr, r1cs.constraints); + return parseConstraints(constraints, sym, r1cs.prime); + } + /** Downloads the phase-1 setup PTAU file for a circuit based on it's number of constraints. * * The downloaded PTAU files can be seen at [SnarkJS docs](https://github.com/iden3/snarkjs#7-prepare-phase-2). diff --git a/src/functions/r1cs.ts b/src/functions/r1cs.ts index 05bd7cb..a993458 100644 --- a/src/functions/r1cs.ts +++ b/src/functions/r1cs.ts @@ -1,4 +1,7 @@ import {ReadPosition, openSync, readSync} from 'fs'; +// @ts-ignore +import * as fastFile from "fastfile"; +import type {SymbolsType} from '../types/'; import {primeToName} from '../utils'; /** @@ -100,6 +103,98 @@ export async function readR1CSInfo(r1csPath: string): Promise { return r1csInfoType; } +export async function readSymbols(symFileName: string): Promise { + const fd = await fastFile.readExisting(symFileName); + const buff = await fd.read(fd.totalSize); + await fd.close(); + const symsStr = new TextDecoder("utf-8").decode(buff); + const lines = symsStr.split("\n"); + const out = {}; + for (let i=0; i { + // @ts-ignore + const varsById = Object.entries(symbols).reduce((out, cur) => { + // @ts-ignore + const id = cur[1].varIdx; + if(id !== -1) { + // @ts-ignore + out[id] = cur[0].slice(5); // Remove main. + } + return out; + }, {}); + + // @ts-ignore + const parsedConstraints = constraints.map(constraint => { + // Every constraint is 3 groups: <1> * <2> - <3> = 0 + // @ts-ignore + const groups = constraint.map(item => { + // Each group can contain many signals (with coefficients) summed + const vars = Object.keys(item).reduce((out, cur) => { + // @ts-ignore + const coeffRaw = BigInt(item[cur]); + // Display the coefficient as a signed value, helps a lot with -1 + let coeff = coeffRaw > fieldSize / BigInt(2) ? coeffRaw - fieldSize : coeffRaw; + // Reduce numbers that are factors of the field size for better readability + // @ts-ignore + const modP = BigInt(fieldSize) % BigInt(coeff); + // XXX: Why within 10000? + if(modP !== BigInt(0) && modP <= BigInt(10000)) { + // @ts-ignore + coeff = `(p-${fieldSize % coeff})/${fieldSize/coeff}`; + } + // @ts-ignore + const varName = varsById[cur]; + out.push( + // @ts-ignore + coeff === BigInt(-1) && varName ? '-' + varName : + coeff === BigInt(1) && varName ? varName : + !varName ? `${coeff}` : + `(${coeff} * ${varName})`, + ); + return out; + }, []); + + // Combine all the signals into one statement + return vars.reduce((out, cur, index) => + // @ts-ignore + out + (index > 0 ? cur.startsWith('-') ? ` - ${cur.slice(1)}` : ` + ${cur}` : cur), + ''); + }) + // @ts-ignore + .map(curVar => curVar.indexOf(' ') === -1 ? curVar : `(${curVar})`); + + return ( + groups[0] + + (groups[1] ? ' * ' + groups[1] : '') + + (groups[2] ? + groups[2].startsWith('-') ? + ` + ${groups[2].slice(1)}` + : groups[0] || groups[1] ? + ' - ' + groups[2] + : groups[2].startsWith('(') ? + groups[2].slice(1, -1) + : groups[2] + : '') + + ' = 0' + ); + }); + // @ts-ignore + return parsedConstraints; +} + /** Reads a specified number of bytes from a file and converts them to a `BigInt`. * * @param offset The position in `buffer` to write the data to. @@ -113,3 +208,25 @@ export function readBytesFromFile(fd: number, offset: number, length: number, po return BigInt(`0x${buffer.reverse().toString('hex')}`); } + +// From Snarkjs +// @ts-ignore +export function stringifyBigIntsWithField(Fr, o) { + if (o instanceof Uint8Array) { + return Fr.toString(o); + } else if (Array.isArray(o)) { + return o.map(stringifyBigIntsWithField.bind(null, Fr)); + } else if (typeof o == "object") { + const res = {}; + const keys = Object.keys(o); + keys.forEach( (k) => { + // @ts-ignore + res[k] = stringifyBigIntsWithField(Fr, o[k]); + }); + return res; + } else if ((typeof(o) == "bigint") || o.eq !== undefined) { + return o.toString(10); + } else { + return o; + } +} diff --git a/src/testers/witnessTester.ts b/src/testers/witnessTester.ts index 5a28fa5..25c75a3 100644 --- a/src/testers/witnessTester.ts +++ b/src/testers/witnessTester.ts @@ -1,5 +1,6 @@ import {AssertionError} from 'node:assert'; import type {CircomTester, WitnessType, CircuitSignals, SymbolsType, SignalValueType} from '../types/'; +import {parseConstraints} from '../functions/'; // @todo detect optimized symbols https://github.com/erhant/circomkit/issues/80 @@ -144,77 +145,9 @@ export class WitnessTester { await this.loadConstraints(); await this.loadSymbols(); - - // @ts-ignore - const varsById = Object.entries(this.symbols).reduce((out, cur) => { - // @ts-ignore - const id = cur[1].varIdx; - if(id !== -1) { - // @ts-ignore - out[id] = cur[0].slice(5); // Remove main. - } - return out; - }, {}); - // @ts-ignore const fieldSize = this.circomTester.witnessCalculator.prime; - - // @ts-ignore - const parsedConstraints = this.constraints.map(constraint => { - // Every constraint is 3 groups: <1> * <2> - <3> = 0 - // @ts-ignore - const groups = constraint.map(item => { - // Each group can contain many signals (with coefficients) summed - const vars = Object.keys(item).reduce((out, cur) => { - // @ts-ignore - const coeffRaw = item[cur]; - // Display the coefficient as a signed value, helps a lot with -1 - let coeff = coeffRaw > fieldSize / BigInt(2) ? coeffRaw - fieldSize : coeffRaw; - // Reduce numbers that are factors of the field size for better readability - // @ts-ignore - const modP = BigInt(fieldSize % coeff); - // XXX: Why within 10000? - if(modP !== BigInt(0) && modP <= BigInt(10000)) { - coeff = `(p-${fieldSize % coeff})/${fieldSize/coeff}`; - } - // @ts-ignore - const varName = varsById[cur]; - out.push( - // @ts-ignore - coeff === BigInt(-1) && varName ? '-' + varName : - coeff === BigInt(1) && varName ? varName : - !varName ? `${coeff}` : - `(${coeff} * ${varName})`, - ); - return out; - }, []); - - // Combine all the signals into one statement - return vars.reduce((out, cur, index) => - // @ts-ignore - out + (index > 0 ? cur.startsWith('-') ? ` - ${cur.slice(1)}` : ` + ${cur}` : cur), - ''); - }) - // @ts-ignore - .map(curVar => curVar.indexOf(' ') === -1 ? curVar : `(${curVar})`); - - return ( - groups[0] + - (groups[1] ? ' * ' + groups[1] : '') + - (groups[2] ? - groups[2].startsWith('-') ? - ` + ${groups[2].slice(1)}` - : groups[0] || groups[1] ? - ' - ' + groups[2] - : groups[2].startsWith('(') ? - groups[2].slice(1, -1) - : groups[2] - : '') + - ' = 0' - ); - }); - // @ts-ignore - return parsedConstraints; + return parseConstraints(this.constraints, this.symbols, fieldSize); } /**