arnaucube
arnaucube/circomPublic Self-hosted repository
Optimization added for removing linear combination only constraints with an internal variable
Jordi Baylina jordi@baylina.cat
Sun, 14 Oct 2018 10:43:03 +0200
commit

ac9f0510678e5f85f7f9a82d63315498b579ba50

parent

f445a95c8f4fbede3e61f808e412461161cd3e57

6 files changed, 245 insertions(+), 84 deletions(-)

jump to
M cli.js → cli.js

@@ -5,14 +5,14 @@ Copyright 2018 0KIMS association.

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 @@

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);
M package-lock.json → 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 @@ "camelcase": "^4.1.0"

} }, "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 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",

"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 @@ "optionator": "^0.8.2",

"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 @@ "strip-ansi": "^4.0.0",

"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" + } } } }
M package.json → package.json

@@ -7,7 +7,7 @@ "directories": {

"test": "test" }, "scripts": { - "test": "mocha", + "test": "mocha --max-old-space-size=4000", "buildParser": "jison parser/jaz.jison -o parser/jaz.js" }, "bin": {
M src/compiler.js → src/compiler.js

@@ -3,14 +3,14 @@ Copyright 2018 0KIMS association.

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"); + + const ctx = { + scopes: [{}], + signals: { + one: { + fullName: "one", + value: bigInt(1), + equivalence: "", + direction: "" + } + }, + currentComponent: "", + constraints: [], + components: {}, + templates: {}, + functions: {}, + functionParams: {}, + filePath: fullFilePath, + fileName: fullFileName + }; - assert(ast.type == "BLOCK"); + exec(ctx, ast); - const ctx = { - scopes: [{}], - signals: { - one: { - fullName: "one", - value: bigInt(1), - equivalence: "", - direction: "" - } - }, - currentComponent: "", - constraints: [], - components: {}, - templates: {}, - functions: {}, - functionParams: {}, - filePath: fullFilePath, - fileName: fullFileName - }; + classifySignals(ctx); + reduceConstants(ctx); - exec(ctx, ast); + // Repeat while reductions are performed + let oldNConstrains = -1; + while (ctx.constraints.length != oldNConstrains) { + oldNConstrains = ctx.constraints.length; + reduceConstrains(ctx); + } - reduceConstraints(ctx); - generateWitnessNames(ctx); + generateWitnessNames(ctx); - if (ctx.error) { - reject(ctx.error); - } + if (ctx.error) { + throw(ctx.error); + } - ctx.scopes = [{}]; + ctx.scopes = [{}]; - const mainCode = gen(ctx,ast); - if (ctx.error) reject(ctx.error); + const mainCode = gen(ctx,ast); + if (ctx.error) throw(ctx.error); - const def = buildCircuitDef(ctx, mainCode); + const def = buildCircuitDef(ctx, mainCode); - resolve(def); - }); + return def; } -function generateWitnessNames(ctx) { - - 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 @@ }

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 @@

ctx.totals = totals; } -function reduceConstraints(ctx) { +function reduceConstants(ctx) { const newConstraints = []; for (let i=0; i<ctx.constraints.length; i++) { const c = lc.canonize(ctx, ctx.constraints[i]);

@@ -187,6 +211,86 @@ }

ctx.constraints = newConstraints; } +function reduceConstrains(ctx) { + const newConstraints = []; + for (let i=0; i<ctx.constraints.length; i++) { + const c = ctx.constraints[i]; + + // Swap a and b if b has more variables. + if (Object.keys(c.b).length > 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<ctx.constraints.length; j++ ) { + const c2 = ctx.constraints[j]; + if (i!=j) { + lc.substitute(c2, isolatedSignal, isolatedSignalEquivalence); + } + } + c.a={ type: "LINEARCOMBINATION", values: {} }; + c.b={ type: "LINEARCOMBINATION", values: {} }; + c.c={ type: "LINEARCOMBINATION", values: {} }; + isolatedSignal.category = "constant"; + } + } + + if (!lc.isZero(c)) { + newConstraints.push(c); + } + } + ctx.constraints = newConstraints; + + function getFirstInternalSignal(ctx, l) { + for (let k in l.values) { + const signal = ctx.signals[k]; + if (signal.category == "internal") return k; + } + return null; + } + + function isConstant(l) { + for (let k in l.values) { + if ((k != "one") && (!l.values[k].isZero())) return false; + } + if (!l.values["one"] || l.values["one"].isZero()) return false; + return true; + } + +} + function buildCircuitDef(ctx, mainCode) { const res = {

@@ -271,6 +375,7 @@ function buildConstraints(ctx) {

const res = []; function fillLC(dst, src) { + if (src.type != "LINEARCOMBINATION") throw new Error("Constraint is not a LINEARCOMBINATION"); for (let s in src.values) { const v = src.values[s].toString(); const id = ctx.signalName2Idx[s];
M src/lcalgebra.js → src/lcalgebra.js

@@ -3,14 +3,14 @@ Copyright 2018 0KIMS association.

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

@@ -70,6 +70,7 @@ exports.toQEQ = toQEQ;

exports.isZero = isZero; exports.toString = toString; exports.canonize = canonize; +exports.substitute = substitute; function signal2lc(a) { let lc;

@@ -465,3 +466,27 @@ }

return a; } +function substitute(where, signal, equivalence) { + if (equivalence.type != "LINEARCOMBINATION") throw new Error("Equivalence must be a Linear Combination"); + if (where.type == "LINEARCOMBINATION") { + if (!where.values[signal] || where.values[signal].isZero()) return where; + const coef = where.values[signal]; + for (let k in equivalence.values) { + if (k != signal) { + const v = coef.times(equivalence.values[k]).mod(__P__); + if (!where.values[k]) { + where.values[k]=v; + } else { + where.values[k]= where.values[k].add(v).mod(__P__); + } + if (where.values[k].isZero()) delete where.values[k]; + } + } + delete where.values[signal]; + } else if (where.type == "QEQ") { + substitute(where.a, signal, equivalence); + substitute(where.b, signal, equivalence); + substitute(where.c, signal, equivalence); + } +} +
M test/sha256.js → test/sha256.js

@@ -57,6 +57,9 @@ it("Should calculate a hash", async () => {

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);