/* 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 . */ const bigInt = require("big-integer"); const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); const sONE = 0; const buildC = require("./c_build"); const constructionPhase = require("./construction_phase"); const Ctx = require("./ctx"); const ZqField = require("fflib").ZqField; const utils = require("./utils"); const buildR1cs = require("./r1csfile").buildR1cs; module.exports = compile; async function compile(srcFile, options) { options.p = options.p || __P__; if (!options) { options = {}; } if (typeof options.reduceConstraints === "undefined") { options.reduceConstraints = true; } const ctx = new Ctx(); ctx.field = new ZqField(options.p); ctx.verbose= options.verbose || false; ctx.mainComponent = options.mainComponent || "main"; constructionPhase(ctx, srcFile); console.log("NConstraints Before: "+ctx.constraints.length); if (ctx.error) { throw(ctx.error); } if (ctx.getComponentIdx(ctx.mainComponent)<0) { throw new Error("A main component must be defined"); } if (ctx.verbose) console.log("Classify Signals"); classifySignals(ctx); if (ctx.verbose) console.log("Reduce Constants"); reduceConstants(ctx); if (options.reduceConstraints) { if (ctx.verbose) console.log("Reduce Constraints"); // Repeat while reductions are performed let oldNConstrains = -1; while (ctx.constraints.length != oldNConstrains) { console.log("Reducing constraints: "+ctx.constraints.length); oldNConstrains = ctx.constraints.length; reduceConstrains(ctx); } } console.log("NConstraints After: "+ctx.constraints.length); generateWitnessNames(ctx); if (ctx.error) { throw(ctx.error); } if (options.cSourceWriteStream) { const cSrc = buildC(ctx); options.cSourceWriteStream.write(cSrc); } // const mainCode = gen(ctx,ast); if (ctx.error) throw(ctx.error); if (options.r1csFileName) { await buildR1cs(ctx, options.r1csFileName); } if (options.symWriteStream) { buildSyms(ctx, options.symWriteStream); } // const def = buildCircuitDef(ctx, mainCode); } function classifySignals(ctx) { const ERROR = 0xFFFF; function priorize(t1, t2) { if ((t1 == ERROR) || (t2==ERROR)) return ERROR; if (t1 == ctx.stINTERNAL) { return t2; } else if (t2==ctx.stINTERNAL) { return t1; } if ((t1 == ctx.stONE) || (t2 == ctx.stONE)) return ctx.stONE; if ((t1 == ctx.stOUTPUT) || (t2 == ctx.stOUTPUT)) return ctx.stOUTPUT; if ((t1 == ctx.stCONSTANT) || (t2 == ctx.stCONSTANT)) return ctx.stCONSTANT; if ((t1 == ctx.stDISCARDED) || (t2 == ctx.stDISCARDED)) return ctx.stDISCARDED; if (t1!=t2) return ERROR; return t1; } // First classify the signals for (let s in ctx.signals) { const signal = ctx.signals[s]; let tAll = ctx.stINTERNAL; let lSignal = signal; let end = false; while (!end) { let t = lSignal.c || ctx.stINTERNAL; if (s == 0) { t = ctx.stONE; } else if (lSignal.o & ctx.MAIN) { if (lSignal.o & ctx.IN) { if (lSignal.o & ctx.PRV) { t = ctx.stPRVINPUT; } else { t = ctx.stPUBINPUT; } } else if (lSignal.o & ctx.OUT) { t = ctx.stOUTPUT; } } else if (utils.isDefined(lSignal.v)) { t = ctx.stCONSTANT; } tAll = priorize(t,tAll); if (lSignal.e>=0) { lSignal = ctx.signals[lSignal.e]; } else { end=true; } } if (tAll == ERROR) { throw new Error("Incompatible types in signal: " + s); } lSignal.c = tAll; } } function generateWitnessNames(ctx) { const totals = {}; totals[ctx.stONE] = 0; totals[ctx.stOUTPUT] = 0; totals[ctx.stPUBINPUT] = 0; totals[ctx.stPRVINPUT] = 0; totals[ctx.stINTERNAL] = 0; totals[ctx.stDISCARDED] = 0; totals[ctx.stCONSTANT] = 0; const ids = {}; // First classify the signals for (let s=0; s=0) lSignal = ctx.signals[lSignal.e]; if (!( lSignal.o & ctx.COUNTED) ) { lSignal.o |= ctx.COUNTED; totals[lSignal.c] ++; } } ids[ctx.stONE] = 0; ids[ctx.stOUTPUT] = 1; ids[ctx.stPUBINPUT] = ids[ctx.stOUTPUT] + totals[ctx.stOUTPUT]; ids[ctx.stPRVINPUT] = ids[ctx.stPUBINPUT] + totals[ctx.stPUBINPUT]; ids[ctx.stINTERNAL] = ids[ctx.stPRVINPUT] + totals[ctx.stPRVINPUT]; ids[ctx.stDISCARDED] = ids[ctx.stINTERNAL] + totals[ctx.stINTERNAL]; ids[ctx.stCONSTANT] = ids[ctx.stDISCARDED] + totals[ctx.stDISCARDED]; const nSignals = ids[ctx.stCONSTANT] + totals[ctx.stCONSTANT]; for (let s=0; s=0) { lSignal = ctx.signals[lSignal.e]; } if ( typeof(lSignal.id) === "undefined" ) { lSignal.id = ids[lSignal.c] ++; } signal.id = lSignal.id; } ctx.totals = totals; } function reduceConstants(ctx) { const newConstraints = []; for (let i=0; i0) { let nextPossibleConstraints = {}; for (let i in possibleConstraints) { ii++; if ((ctx.verbose)&&(ii%10000 == 0)) console.log("reducing constraints: ", i); if (!ctx.constraints[i]) continue; 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 = {t: "N", v: c.a.coefs[sONE]}; c.c = ctx.lc.add(ctx.lc.mul(c.b, ct), c.c); c.a = { t: "LC", coefs: {} }; c.b = { t: "LC", coefs: {} }; } if (isConstant(c.b)) { const ct = {t: "N", v: c.b.coefs[sONE]}; c.c = ctx.lc.add(ctx.lc.mul(c.a, ct), c.c); c.a = { t: "LC", coefs: {} }; c.b = { t: "LC", coefs: {} }; } if (ctx.lc.isZero(c.a) || ctx.lc.isZero(c.b)) { const isolatedSignal = getFirstInternalSignal(ctx, c.c); if (isolatedSignal) { let lSignal = ctx.signals[isolatedSignal]; while (lSignal.e>=0) { lSignal = ctx.signals[lSignal.e]; } const isolatedSignalEquivalence = { t: "LC", coefs: {} }; const invCoef = c.c.coefs[isolatedSignal].modInv(__P__); for (const s in c.c.coefs) { if (s != isolatedSignal) { const v = __P__.minus(c.c.coefs[s]).times(invCoef).mod(__P__); if (!v.isZero()) { isolatedSignalEquivalence.coefs[s] = v; } } } for (let j in lSignal.inConstraints) { if ((j!=i)&&(ctx.constraints[j])) { ctx.constraints[j] = ctx.lc.substitute(ctx.constraints[j], isolatedSignal, isolatedSignalEquivalence); linkSignalsConstraint(j); if (j=0) { lSignal = ctx.signals[lSignal.e]; } if (lSignal.inConstraints) delete lSignal.inConstraints; } } /* function unlinkSignal(signalName, cidx) { let lSignal = ctx.signals[signalName]; while (lSignal.e>=0) { lSignal = ctx.signals[lSignal.e]; } if ((lSignal.inConstraints)&&(lSignal.inConstraints[cidx])) { delete lSignal.inConstraints[cidx]; } } */ function linkSignal(signalName, cidx) { let lSignal = ctx.signals[signalName]; while (lSignal.e>=0) { lSignal = ctx.signals[lSignal.e]; } if (!lSignal.inConstraints) lSignal.inConstraints = {}; lSignal.inConstraints[cidx] = true; } function getFirstInternalSignal(ctx, l) { for (let k in l.coefs) { const signal = ctx.signals[k]; if (signal.c == ctx.stINTERNAL) return k; } return null; } function isConstant(l) { for (let k in l.coefs) { if ((k != sONE) && (!l.coefs[k].isZero())) return false; } if (!l.coefs[sONE] || l.coefs[sONE].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 { 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.t != "LC") throw new Error("Constraint is not a LINEARCOMBINATION"); for (let s in src.coefs) { const v = src.coefs[s].toString(); const id = ctx.signalName2Idx[s]; dst[id] = v; } } for (let i=0; i= 0) s = ctx.signals[s].e; let wId = ctx.signals[s].id; if (typeof(wId) == "undefined") wId=-1; strm.write(`${offset},${wId},${prefix}\n`); nSyms ++; if ((ctx.verbose)&&(nSyms%10000 == 0)) console.log("Symbols saved: "+nSyms); } else { addSymbolsComponent(prefix+".", offset); } return 1; } else { let acc = 0; for (let i=0; i