Browse Source

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

wasm
Jordi Baylina 6 years ago
parent
commit
ac9f051067
No known key found for this signature in database GPG Key ID: 7480C80C1BE43112
6 changed files with 245 additions and 84 deletions
  1. +5
    -4
      cli.js
  2. +46
    -19
      package-lock.json
  3. +1
    -1
      package.json
  4. +161
    -56
      src/compiler.js
  5. +29
    -4
      src/lcalgebra.js
  6. +3
    -0
      test/sha256.js

+ 5
- 4
cli.js

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

+ 46
- 19
package-lock.json

@ -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",
"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, "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",
"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, "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",
"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 "dev": true
}, },
"rxjs": { "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, "dev": true,
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
} }
}, },
"semver": { "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 "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"
}
} }
} }
} }

+ 1
- 1
package.json

@ -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": {

+ 161
- 56
src/compiler.js

@ -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
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. 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) => {
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) { 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];

+ 29
- 4
src/lcalgebra.js

@ -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
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. 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);
}
}

+ 3
- 0
test/sha256.js

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

Loading…
Cancel
Save