|
|
/* Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom 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.
circom 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 along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
const fs = require("fs"); const path = require("path"); const bigInt = require("big-integer"); const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); const __MASK__ = new bigInt(2).pow(253).minus(1); const assert = require("assert"); const gen = require("./gencode"); const exec = require("./exec"); const lc = require("./lcalgebra");
module.exports = compile;
const parser = require("../parser/jaz.js").parser;
const timeout = ms => new Promise(res => setTimeout(res, ms));
async function compile(srcFile, options) { if (!options) { options = {}; } if (typeof options.reduceConstraints === "undefined") { options.reduceConstraints = true; } const fullFileName = srcFile; const fullFilePath = path.dirname(fullFileName);
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 };
exec(ctx, ast);
if (!ctx.components["main"]) { throw new Error("A main component must be defined"); }
classifySignals(ctx);
if (options.reduceConstraints) { reduceConstants(ctx);
// Repeat while reductions are performed
let oldNConstrains = -1; while (ctx.constraints.length != oldNConstrains) { oldNConstrains = ctx.constraints.length; reduceConstrains(ctx); } }
generateWitnessNames(ctx);
if (ctx.error) { throw(ctx.error); }
ctx.scopes = [{}];
const mainCode = gen(ctx,ast); if (ctx.error) throw(ctx.error);
const def = buildCircuitDef(ctx, mainCode);
return def; }
function classifySignals(ctx) {
function priorize(t1, t2) { if ((t1 == "error") || (t2=="error")) return "error"; if (t1 == "internal") { return t2; } else if (t2=="internal") { return t1; } if ((t1 == "one") || (t2 == "one")) return "one"; if ((t1 == "constant") || (t2 == "constant")) return "constant"; if (t1!=t2) return "error"; return t1; }
// First classify the signals
for (let s in ctx.signals) { const signal = ctx.signals[s]; let tAll = "internal"; let lSignal = signal; let end = false; while (!end) { let t = lSignal.category || "internal"; if (s == "one") { t = "one"; } else if (lSignal.value) { t = "constant"; } else if (lSignal.component=="main") { if (lSignal.direction == "IN") { if (lSignal.private) { t = "prvInput"; } else { t = "pubInput"; } } else if (lSignal.direction == "OUT") { t = "output"; } } tAll = priorize(t,tAll); if (lSignal.equivalence) { lSignal = ctx.signals[lSignal.equivalence]; } else { end=true; } } if (tAll == "error") { throw new Error("Incompatible types in signal: " + s); } lSignal.category = tAll; } }
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["output"] = 1; ids["pubInput"] = ids["output"] + totals["output"]; ids["prvInput"] = ids["pubInput"] + totals["pubInput"]; ids["internal"] = ids["prvInput"] + totals["prvInput"]; ids["constant"] = ids["internal"] + totals["internal"]; const nSignals = ids["constant"] + totals["constant"];
ctx.signalNames = new Array(nSignals); for (let i=0; i< nSignals; i++) ctx.signalNames[i] = []; ctx.signalName2Idx = {};
for (let s in ctx.signals) { const signal = ctx.signals[s]; let lSignal = signal; while (lSignal.equivalence) { lSignal = ctx.signals[lSignal.equivalence]; } if ( typeof(lSignal.id) === "undefined" ) { lSignal.id = ids[lSignal.category] ++; }
signal.id = lSignal.id; ctx.signalNames[signal.id].push(signal.fullName); ctx.signalName2Idx[signal.fullName] = signal.id; }
ctx.totals = totals; }
function reduceConstants(ctx) { const newConstraints = []; for (let i=0; i<ctx.constraints.length; i++) { const c = lc.canonize(ctx, ctx.constraints[i]); if (!lc.isZero(c)) { newConstraints.push(c); } } 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<newConstraints.length; j++) { newConstraints[j] = lc.substitute(newConstraints[j], isolatedSignal, isolatedSignalEquivalence); } for (let j=i+1; j<ctx.constraints.length; j++ ) { ctx.constraints[j] = lc.substitute(ctx.constraints[j], 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 = { mainCode: mainCode }; res.signalName2Idx = ctx.signalName2Idx;
res.components = []; res.componentName2Idx = {}; for (let c in ctx.components) { const idCoponent = res.components.length; res.components.push({ name: c, params: ctx.components[c].params, template: ctx.components[c].template, inputSignals: 0 }); res.componentName2Idx[c] = idCoponent; }
res.signals = new Array(ctx.signalNames.length); for (let i=0; i<ctx.signalNames.length; i++) { res.signals[i] = { names: ctx.signalNames[i], triggerComponents: [] }; ctx.signalNames[i].map( (fullName) => { const idComponet = res.componentName2Idx[ctx.signals[fullName].component]; if (ctx.signals[fullName].direction == "IN") { res.signals[i].triggerComponents.push(idComponet); res.components[idComponet].inputSignals++; } }); }
res.constraints = buildConstraints(ctx);
res.templates = ctx.templates;
res.functions = {}; for (let f in ctx.functions) { res.functions[f] = { params: ctx.functionParams[f], func: ctx.functions[f] }; }
res.nPrvInputs = ctx.totals.prvInput; res.nPubInputs = ctx.totals.pubInput; res.nInputs = res.nPrvInputs + res.nPubInputs; res.nOutputs = ctx.totals.output; res.nVars = res.nInputs + res.nOutputs + ctx.totals.one + ctx.totals.internal; res.nConstants = ctx.totals.constant; res.nSignals = res.nVars + res.nConstants;
return res; }
/* Build constraints
A constraint like this
[s1 + 2*s2 + 3*s3] * [ s2 + 5*s4] - [s0 ] = 0 [ 5*s2 + 6*s3] * [ s2 + ] - [s0 + 2* s2] = 0 [s1 + s3] * [ s2 + 5*s3] - [s4 ] = 0
is converted to
[ [{"1":"1","2":"2","3":"3"} , {"2":"1","4":"5"} , {"0":"1" }], [{ "2":"5","3":"6"} , {"2":"1" } , {"0":"1", "2":"2"}], [{"1":"1", "3":"1"} , {"2":"1","3":"5"} , {"4":"1" }] ] ^ ^ ^ | | | A B C
*/
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]; dst[id] = v; } }
for (let i=0; i<ctx.constraints.length; i++) { const A = {}; const B = {}; const C = {};
fillLC(A, ctx.constraints[i].a); fillLC(B, ctx.constraints[i].b); fillLC(C, lc.negate(ctx.constraints[i].c));
res.push([A,B,C]); }
return res; }
|