From ac9f0510678e5f85f7f9a82d63315498b579ba50 Mon Sep 17 00:00:00 2001 From: Jordi Baylina Date: Sun, 14 Oct 2018 10:43:03 +0200 Subject: [PATCH] Optimization added for removing linear combination only constraints with an internal variable --- cli.js | 9 +- package-lock.json | 65 ++++++++++---- package.json | 2 +- src/compiler.js | 217 ++++++++++++++++++++++++++++++++++------------ src/lcalgebra.js | 33 ++++++- test/sha256.js | 3 + 6 files changed, 245 insertions(+), 84 deletions(-) diff --git a/cli.js b/cli.js index 6fd77b3..60f754e 100755 --- a/cli.js +++ b/cli.js @@ -5,14 +5,14 @@ This file is part of jaz (Zero Knowledge Circuit Compiler). - jaz is a free software: you can redistribute it and/or modify it + jaz is a free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - jaz is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + jaz is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License @@ -48,6 +48,7 @@ const fullFileName = path.resolve(process.cwd(), argv.source); compiler(fullFileName).then( (cir) => { fs.writeFileSync(argv.output, JSON.stringify(cir, null, 1), "utf8"); }, (err) => { + console.log(err); console.error(`ERROR at ${err.errFile}:${err.pos.first_line},${err.pos.first_column}-${err.pos.last_line},${err.pos.last_column} ${err.errStr}`); console.error(JSON.stringify(err.ast, null, 1)); process.exit(1); diff --git a/package-lock.json b/package-lock.json index 7effa75..9432608 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "circom", - "version": "0.0.6", + "version": "0.0.7", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1723,9 +1723,9 @@ } }, "zksnark": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/zksnark/-/zksnark-0.0.10.tgz", - "integrity": "sha512-i8Pbhof3hv3okhrEKcVrF0q0AQHR4HfHMmOgWBzgG6Dk5DzYwUukFUAhGBpizm+pPbEVMNoDq/7Lx+42KH0Lzw==", + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/zksnark/-/zksnark-0.0.11.tgz", + "integrity": "sha512-YIOk93pLvc8NDVvedB0SDM1kGjPTdTYC/sgAvc9Dm6qMSYnS7tzCr844QaUlMApFTldz7D/6xlF1l24ttTGLXw==", "dev": true, "requires": { "big-integer": "^1.6.35", @@ -1757,17 +1757,26 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, "eslint": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.6.0.tgz", - "integrity": "sha512-/eVYs9VVVboX286mBK7bbKnO1yamUy2UCRjiY6MryhQL2PaaXCExsCQ2aO83OeYRhU2eCU/FMFP+tVMoOrzNrA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.7.0.tgz", + "integrity": "sha512-zYCeFQahsxffGl87U2aJ7DPyH8CbWgxBC213Y8+TCanhUTf2gEvfq3EKpHmEcozTLyPmGe9LZdMAwC/CpJBM5A==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.5.3", "chalk": "^2.1.0", "cross-spawn": "^6.0.5", - "debug": "^3.1.0", + "debug": "^4.0.1", "doctrine": "^2.1.0", "eslint-scope": "^4.0.0", "eslint-utils": "^1.3.1", @@ -1794,12 +1803,12 @@ "path-is-inside": "^1.0.2", "pluralize": "^7.0.0", "progress": "^2.0.0", - "regexpp": "^2.0.0", + "regexpp": "^2.0.1", "require-uncached": "^1.0.3", "semver": "^5.5.1", "strip-ansi": "^4.0.0", "strip-json-comments": "^2.0.1", - "table": "^4.0.3", + "table": "^5.0.2", "text-table": "^0.2.0" } }, @@ -1850,26 +1859,44 @@ "through": "^2.3.6" } }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, "regexpp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", - "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, "rxjs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz", - "integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { "tslib": "^1.9.0" } }, "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true + }, + "table": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/table/-/table-5.1.0.tgz", + "integrity": "sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg==", + "dev": true, + "requires": { + "ajv": "^6.5.3", + "lodash": "^4.17.10", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } } } } diff --git a/package.json b/package.json index c899af5..0556998 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "test" }, "scripts": { - "test": "mocha", + "test": "mocha --max-old-space-size=4000", "buildParser": "jison parser/jaz.jison -o parser/jaz.js" }, "bin": { diff --git a/src/compiler.js b/src/compiler.js index ad65a38..33554c1 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -3,14 +3,14 @@ This file is part of jaz (Zero Knowledge Circuit Compiler). - jaz is a free software: you can redistribute it and/or modify it + jaz is a free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - jaz is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + jaz is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License @@ -31,69 +31,67 @@ module.exports = compile; const parser = require("../parser/jaz.js").parser; -function compile(srcFile) { +const timeout = ms => new Promise(res => setTimeout(res, ms)) - return new Promise ((resolve, reject) => { - const fullFileName = srcFile; - const fullFilePath = path.dirname(fullFileName); +async function compile(srcFile) { + const fullFileName = srcFile; + const fullFilePath = path.dirname(fullFileName); - const src = fs.readFileSync(fullFileName, "utf8"); - const ast = parser.parse(src); + const src = fs.readFileSync(fullFileName, "utf8"); + const ast = parser.parse(src); - assert(ast.type == "BLOCK"); + assert(ast.type == "BLOCK"); - const ctx = { - scopes: [{}], - signals: { - one: { - fullName: "one", - value: bigInt(1), - equivalence: "", - direction: "" - } - }, - currentComponent: "", - constraints: [], - components: {}, - templates: {}, - functions: {}, - functionParams: {}, - filePath: fullFilePath, - fileName: fullFileName - }; + const ctx = { + scopes: [{}], + signals: { + one: { + fullName: "one", + value: bigInt(1), + equivalence: "", + direction: "" + } + }, + currentComponent: "", + constraints: [], + components: {}, + templates: {}, + functions: {}, + functionParams: {}, + filePath: fullFilePath, + fileName: fullFileName + }; - exec(ctx, ast); + exec(ctx, ast); - reduceConstraints(ctx); - generateWitnessNames(ctx); + classifySignals(ctx); + reduceConstants(ctx); - if (ctx.error) { - reject(ctx.error); - } + // Repeat while reductions are performed + let oldNConstrains = -1; + while (ctx.constraints.length != oldNConstrains) { + oldNConstrains = ctx.constraints.length; + reduceConstrains(ctx); + } - ctx.scopes = [{}]; + generateWitnessNames(ctx); - const mainCode = gen(ctx,ast); - if (ctx.error) reject(ctx.error); + if (ctx.error) { + throw(ctx.error); + } - const def = buildCircuitDef(ctx, mainCode); + ctx.scopes = [{}]; - resolve(def); - }); -} + const mainCode = gen(ctx,ast); + if (ctx.error) throw(ctx.error); + const def = buildCircuitDef(ctx, mainCode); -function generateWitnessNames(ctx) { + return def; +} - const totals = { - "output": 0, - "pubInput": 0, - "one": 0, - "prvInput": 0, - "internal": 0, - "constant": 0, - }; - const ids = {}; + +function classifySignals(ctx) { function priorize(t1, t2) { if ((t1 == "error") || (t2=="error")) return "error"; @@ -141,9 +139,35 @@ function generateWitnessNames(ctx) { if (tAll == "error") { throw new Error("Incompatible types in signal: " + s); } - if (lSignal.category) totals[lSignal.category]--; lSignal.category = tAll; - totals[lSignal.category] ++; + } +} + + +function generateWitnessNames(ctx) { + + const totals = { + "output": 0, + "pubInput": 0, + "one": 0, + "prvInput": 0, + "internal": 0, + "constant": 0, + }; + const ids = {}; + + const counted = {}; + + // First classify the signals + for (let s in ctx.signals) { + const signal = ctx.signals[s]; + let lSignal = signal; + while (lSignal.equivalence) lSignal = ctx.signals[lSignal.equivalence]; + + if (!counted[lSignal.fullName]) { + counted[lSignal.fullName] = true; + totals[lSignal.category] ++; + } } ids["one"] = 0; @@ -176,7 +200,7 @@ function generateWitnessNames(ctx) { ctx.totals = totals; } -function reduceConstraints(ctx) { +function reduceConstants(ctx) { const newConstraints = []; for (let i=0; i Object.keys(c.a).length) { + const aux = c.a; + c.a=c.b; + c.b=aux; + } + + // Mov to C if possible. + if (isConstant(c.a)) { + const ct = {type: "NUMBER", value: c.a.values["one"]}; + c.c = lc.add(lc.mul(c.b, ct), c.c); + c.a = { type: "LINEARCOMBINATION", values: {} }; + c.b = { type: "LINEARCOMBINATION", values: {} }; + } + if (isConstant(c.b)) { + const ct = {type: "NUMBER", value: c.b.values["one"]}; + c.c = lc.add(lc.mul(c.a, ct), c.c); + c.a = { type: "LINEARCOMBINATION", values: {} }; + c.b = { type: "LINEARCOMBINATION", values: {} }; + } + + if (lc.isZero(c.a) || lc.isZero(c.b)) { + const isolatedSignal = getFirstInternalSignal(ctx, c.c); + if (isolatedSignal) { + const isolatedSignalEquivalence = { + type: "LINEARCOMBINATION", + values: {} + }; + const invCoef = c.c.values[isolatedSignal].modInv(__P__); + for (const s in c.c.values) { + if (s != isolatedSignal) { + const v = __P__.minus(c.c.values[s]).times(invCoef).mod(__P__); + if (!v.isZero()) { + isolatedSignalEquivalence.values[s] = v; + } + } + } + + for (let j=0; j { const cirDef = await compiler(path.join(__dirname, "circuits", "sha256_2_test.circom")); const circuit = new zkSnark.Circuit(cirDef); + console.log("Vars: "+circuit.nVars); + console.log("Constraints: "+circuit.nConstraints); + const witness = circuit.calculateWitness({ "a": "1", "b": "2" }); const b = new Buffer.alloc(54);