|
|
/* 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 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<ctx.signals.length; s++) {
if ((ctx.verbose)&&(s%10000 == 0)) console.log("generate witness (counting): ", s);
const signal = ctx.signals[s]; let lSignal = signal; while (lSignal.e>=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<ctx.signals.length; s++) {
if ((ctx.verbose)&&(s%10000 == 0)) console.log("seting id: ", s);
const signal = ctx.signals[s]; let lSignal = signal; while (lSignal.e>=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; i<ctx.constraints.length; i++) { if ((ctx.verbose)&&(i%10000 == 0)) console.log("reducing constants: ", i); const c = ctx.lc.canonize(ctx, ctx.constraints[i]); if (!ctx.lc.isZero(c)) { newConstraints.push(c); } delete ctx.constraints[i]; } ctx.constraints = newConstraints; }
function reduceConstrains(ctx) { indexVariables(); let possibleConstraints = Object.keys(ctx.constraints); let ii=0; while (possibleConstraints.length>0) { 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<i) { nextPossibleConstraints[j] = true; } } }
ctx.constraints[i] = null;
lSignal.c = ctx.stDISCARDED; } else { if (ctx.lc.isZero(c.c)) ctx.constraints[i] = null; } } } possibleConstraints = Object.keys(nextPossibleConstraints); } unindexVariables();
// Pack the constraints
let o = 0; for (let i=0; i<ctx.constraints.length; i++) { if (ctx.constraints[i]) { if (o != i) { ctx.constraints[o] = ctx.constraints[i]; } o++; } } ctx.constraints.length = o;
function indexVariables() { for (let i=0; i<ctx.constraints.length; i++) linkSignalsConstraint(i); }
function linkSignalsConstraint(cidx) { const ct = ctx.constraints[cidx]; for (let k in ct.a.coefs) linkSignal(k, cidx); for (let k in ct.b.coefs) linkSignal(k, cidx); for (let k in ct.c.coefs) linkSignal(k, cidx); }
function unindexVariables() { for (let s in ctx.signals) { let lSignal = ctx.signals[s]; while (lSignal.e>=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<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.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<ctx.constraints.length; i++) { const A = {}; const B = {}; const C = {};
fillLC(A, ctx.constraints[i].a); fillLC(B, ctx.constraints[i].b); fillLC(C, ctx.lc.negate(ctx.constraints[i].c));
res.push([A,B,C]); }
return res; }
function buildSyms(ctx, strm) {
let nSyms;
addSymbolsComponent(ctx.mainComponent + ".", ctx.getComponentIdx(ctx.mainComponent));
function addSymbolsComponent(prefix, idComponet) { for (let n in ctx.components[idComponet].names.o) { const entrie = ctx.components[idComponet].names.o[n]; addSymbolArray(prefix+n, entrie.type, entrie.sizes, entrie.offset); } }
function addSymbolArray(prefix, type, sizes, offset) { if (sizes.length==0) { if (type == "S") { let s=offset; while (ctx.signals[s].e >= 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<sizes[0]; i++) { acc += addSymbolArray(`${prefix}[${i}]`, type, sizes.slice(1), offset + acc ); } return acc; } }
}
|