Optimization added for removing linear combination only constraints with an internal variable

This commit is contained in:
Jordi Baylina
2018-10-14 10:43:03 +02:00
parent f445a95c8f
commit ac9f051067
6 changed files with 245 additions and 84 deletions

9
cli.js
View File

@@ -5,14 +5,14 @@
This file is part of jaz (Zero Knowledge Circuit Compiler). 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 under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
jaz is distributed in the hope that it will be useful, but WITHOUT jaz is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details. License for more details.
You should have received a copy of the GNU General Public License 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) => { compiler(fullFileName).then( (cir) => {
fs.writeFileSync(argv.output, JSON.stringify(cir, null, 1), "utf8"); fs.writeFileSync(argv.output, JSON.stringify(cir, null, 1), "utf8");
}, (err) => { }, (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(`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)); console.error(JSON.stringify(err.ast, null, 1));
process.exit(1); process.exit(1);

65
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "circom", "name": "circom",
"version": "0.0.6", "version": "0.0.7",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -1723,9 +1723,9 @@
} }
}, },
"zksnark": { "zksnark": {
"version": "0.0.10", "version": "0.0.11",
"resolved": "https://registry.npmjs.org/zksnark/-/zksnark-0.0.10.tgz", "resolved": "https://registry.npmjs.org/zksnark/-/zksnark-0.0.11.tgz",
"integrity": "sha512-i8Pbhof3hv3okhrEKcVrF0q0AQHR4HfHMmOgWBzgG6Dk5DzYwUukFUAhGBpizm+pPbEVMNoDq/7Lx+42KH0Lzw==", "integrity": "sha512-YIOk93pLvc8NDVvedB0SDM1kGjPTdTYC/sgAvc9Dm6qMSYnS7tzCr844QaUlMApFTldz7D/6xlF1l24ttTGLXw==",
"dev": true, "dev": true,
"requires": { "requires": {
"big-integer": "^1.6.35", "big-integer": "^1.6.35",
@@ -1757,17 +1757,26 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true "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": { "eslint": {
"version": "5.6.0", "version": "5.7.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.6.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.7.0.tgz",
"integrity": "sha512-/eVYs9VVVboX286mBK7bbKnO1yamUy2UCRjiY6MryhQL2PaaXCExsCQ2aO83OeYRhU2eCU/FMFP+tVMoOrzNrA==", "integrity": "sha512-zYCeFQahsxffGl87U2aJ7DPyH8CbWgxBC213Y8+TCanhUTf2gEvfq3EKpHmEcozTLyPmGe9LZdMAwC/CpJBM5A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.0.0", "@babel/code-frame": "^7.0.0",
"ajv": "^6.5.3", "ajv": "^6.5.3",
"chalk": "^2.1.0", "chalk": "^2.1.0",
"cross-spawn": "^6.0.5", "cross-spawn": "^6.0.5",
"debug": "^3.1.0", "debug": "^4.0.1",
"doctrine": "^2.1.0", "doctrine": "^2.1.0",
"eslint-scope": "^4.0.0", "eslint-scope": "^4.0.0",
"eslint-utils": "^1.3.1", "eslint-utils": "^1.3.1",
@@ -1794,12 +1803,12 @@
"path-is-inside": "^1.0.2", "path-is-inside": "^1.0.2",
"pluralize": "^7.0.0", "pluralize": "^7.0.0",
"progress": "^2.0.0", "progress": "^2.0.0",
"regexpp": "^2.0.0", "regexpp": "^2.0.1",
"require-uncached": "^1.0.3", "require-uncached": "^1.0.3",
"semver": "^5.5.1", "semver": "^5.5.1",
"strip-ansi": "^4.0.0", "strip-ansi": "^4.0.0",
"strip-json-comments": "^2.0.1", "strip-json-comments": "^2.0.1",
"table": "^4.0.3", "table": "^5.0.2",
"text-table": "^0.2.0" "text-table": "^0.2.0"
} }
}, },
@@ -1850,26 +1859,44 @@
"through": "^2.3.6" "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": { "regexpp": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
"integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"dev": true "dev": true
}, },
"rxjs": { "rxjs": {
"version": "6.3.2", "version": "6.3.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
"integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==", "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==",
"dev": true, "dev": true,
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
} }
}, },
"semver": { "semver": {
"version": "5.5.1", "version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
"dev": true "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"
}
} }
} }
} }

View File

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

View File

@@ -3,14 +3,14 @@
This file is part of jaz (Zero Knowledge Circuit Compiler). 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 under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
jaz is distributed in the hope that it will be useful, but WITHOUT jaz is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details. License for more details.
You should have received a copy of the GNU General Public License 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; const parser = require("../parser/jaz.js").parser;
function compile(srcFile) { const timeout = ms => new Promise(res => setTimeout(res, ms))
return new Promise ((resolve, reject) => { async function compile(srcFile) {
const fullFileName = srcFile; const fullFileName = srcFile;
const fullFilePath = path.dirname(fullFileName); const fullFilePath = path.dirname(fullFileName);
const src = fs.readFileSync(fullFileName, "utf8"); const src = fs.readFileSync(fullFileName, "utf8");
const ast = parser.parse(src); const ast = parser.parse(src);
assert(ast.type == "BLOCK"); assert(ast.type == "BLOCK");
const ctx = { const ctx = {
scopes: [{}], scopes: [{}],
signals: { signals: {
one: { one: {
fullName: "one", fullName: "one",
value: bigInt(1), value: bigInt(1),
equivalence: "", equivalence: "",
direction: "" direction: ""
} }
}, },
currentComponent: "", currentComponent: "",
constraints: [], constraints: [],
components: {}, components: {},
templates: {}, templates: {},
functions: {}, functions: {},
functionParams: {}, functionParams: {},
filePath: fullFilePath, filePath: fullFilePath,
fileName: fullFileName fileName: fullFileName
}; };
exec(ctx, ast); exec(ctx, ast);
reduceConstraints(ctx); classifySignals(ctx);
generateWitnessNames(ctx); reduceConstants(ctx);
if (ctx.error) { // Repeat while reductions are performed
reject(ctx.error); 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) {
if (ctx.error) reject(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);
return def;
} }
function generateWitnessNames(ctx) { function classifySignals(ctx) {
const totals = {
"output": 0,
"pubInput": 0,
"one": 0,
"prvInput": 0,
"internal": 0,
"constant": 0,
};
const ids = {};
function priorize(t1, t2) { function priorize(t1, t2) {
if ((t1 == "error") || (t2=="error")) return "error"; if ((t1 == "error") || (t2=="error")) return "error";
@@ -141,9 +139,35 @@ function generateWitnessNames(ctx) {
if (tAll == "error") { if (tAll == "error") {
throw new Error("Incompatible types in signal: " + s); throw new Error("Incompatible types in signal: " + s);
} }
if (lSignal.category) totals[lSignal.category]--;
lSignal.category = tAll; 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; ids["one"] = 0;
@@ -176,7 +200,7 @@ function generateWitnessNames(ctx) {
ctx.totals = totals; ctx.totals = totals;
} }
function reduceConstraints(ctx) { function reduceConstants(ctx) {
const newConstraints = []; const newConstraints = [];
for (let i=0; i<ctx.constraints.length; i++) { for (let i=0; i<ctx.constraints.length; i++) {
const c = lc.canonize(ctx, ctx.constraints[i]); const c = lc.canonize(ctx, ctx.constraints[i]);
@@ -187,6 +211,86 @@ function reduceConstraints(ctx) {
ctx.constraints = newConstraints; 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) { function buildCircuitDef(ctx, mainCode) {
const res = { const res = {
@@ -271,6 +375,7 @@ function buildConstraints(ctx) {
const res = []; const res = [];
function fillLC(dst, src) { function fillLC(dst, src) {
if (src.type != "LINEARCOMBINATION") throw new Error("Constraint is not a LINEARCOMBINATION");
for (let s in src.values) { for (let s in src.values) {
const v = src.values[s].toString(); const v = src.values[s].toString();
const id = ctx.signalName2Idx[s]; const id = ctx.signalName2Idx[s];

View File

@@ -3,14 +3,14 @@
This file is part of jaz (Zero Knowledge Circuit Compiler). 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 under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
jaz is distributed in the hope that it will be useful, but WITHOUT jaz is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details. License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
@@ -70,6 +70,7 @@ exports.toQEQ = toQEQ;
exports.isZero = isZero; exports.isZero = isZero;
exports.toString = toString; exports.toString = toString;
exports.canonize = canonize; exports.canonize = canonize;
exports.substitute = substitute;
function signal2lc(a) { function signal2lc(a) {
let lc; let lc;
@@ -465,3 +466,27 @@ function canonize(ctx, a) {
return a; 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);
}
}

View File

@@ -57,6 +57,9 @@ describe("SHA256 test", () => {
const cirDef = await compiler(path.join(__dirname, "circuits", "sha256_2_test.circom")); const cirDef = await compiler(path.join(__dirname, "circuits", "sha256_2_test.circom"));
const circuit = new zkSnark.Circuit(cirDef); 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 witness = circuit.calculateWitness({ "a": "1", "b": "2" });
const b = new Buffer.alloc(54); const b = new Buffer.alloc(54);