circom/src/compiler.js
src/compiler.jsRaw
1/*
2 Copyright 2018 0KIMS association.
3
4 This file is part of circom (Zero Knowledge Circuit Compiler).
5
6 circom is a free software: you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 circom is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with circom. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20const Scalar = require("ffjavascript").Scalar;
21const sONE = 0;
22const build = require("./build");
23const BuilderC = require("../ports/c/builder.js");
24const BuilderWasm = require("../ports/wasm/builder.js");
25const constructionPhase = require("./construction_phase");
26const Ctx = require("./ctx");
27const utils = require("./utils");
28const buildR1cs = require("./r1csfile").buildR1cs;
29const BigArray = require("./bigarray");
30const buildSyms = require("./buildsyms");
31const {performance} = require("perf_hooks");
32
33module.exports = compile;
34const measures = {};
35
36function ms2String(v) {
37 v = Math.floor(v);
38 const ms = v % 1000;
39 v = Math.floor(v/1000);
40 const secs = v % 60;
41 v = Math.floor(v/60);
42 const mins = v % 60;
43 v = Math.floor(v/60);
44 const hours = v % 24;
45 const days = Math.floor(v/24);
46 let S = "";
47 if (days) S = S + days + "D ";
48 if ((S!="")||(hours)) S = S + hours.toString().padStart(2, "0") + ":";
49 if ((S!="")||(mins)) S = S + mins.toString().padStart(2, "0") + ":";
50 if ((S!="")||(secs)) S = S + secs.toString().padStart(2, "0");
51 S+=".";
52 S = S + ms.toString().padStart(3, "0");
53 return S;
54}
55
56async function compile(srcFile, options) {
57 options.prime = options.prime || Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617");
58 if (!options) {
59 options = {};
60 }
61 if (typeof options.reduceConstraints === "undefined") {
62 options.reduceConstraints = true;
63 }
64 const ctx = new Ctx(options.prime);
65 ctx.verbose= options.verbose || false;
66 ctx.mainComponent = options.mainComponent || "main";
67 ctx.newThreadTemplates = options.newThreadTemplates;
68
69 measures.constructionPhase = -performance.now();
70 constructionPhase(ctx, srcFile);
71 measures.constructionPhase += performance.now();
72
73 if (ctx.verbose) console.log("NConstraints Before: "+ctx.constraints.length);
74 if (ctx.verbose) console.log("NSignals Before: "+ctx.signals.length);
75
76 if (ctx.error) {
77 throw(ctx.error);
78 }
79
80 if (ctx.getComponentIdx(ctx.mainComponent)<0) {
81 throw new Error("A main component must be defined");
82 }
83
84 if (ctx.verbose) console.log("Classify Signals");
85 measures.classifySignals = -performance.now();
86 classifySignals(ctx);
87 measures.classifySignals += performance.now();
88
89 if (ctx.verbose) console.log("Reduce Constants");
90 measures.reduceConstants = -performance.now();
91 reduceConstants(ctx);
92 measures.reduceConstants += performance.now();
93
94 if (options.reduceConstraints) {
95
96 if (ctx.verbose) console.log("Reduce Constraints");
97 // Repeat while reductions are performed
98/*
99 let oldNConstrains = -1;
100 while (ctx.constraints.length != oldNConstrains) {
101 if (ctx.verbose) console.log("Reducing constraints: "+ctx.constraints.length);
102 oldNConstrains = ctx.constraints.length;
103 reduceConstrains(ctx);
104 }
105*/
106 measures.reduceConstraints = -performance.now();
107 await reduceConstrains(ctx);
108 measures.reduceConstraints += performance.now();
109
110 }
111
112 if (ctx.verbose) console.log("NConstraints After: "+ctx.constraints.length);
113
114 measures.generateWitnessNames = -performance.now();
115 generateWitnessNames(ctx);
116 measures.generateWitnessNames += performance.now();
117
118 if (ctx.error) {
119 throw(ctx.error);
120 }
121
122 if (options.r1csFileName) {
123 measures.generateR1cs = -performance.now();
124 await buildR1cs(ctx, options.r1csFileName);
125 measures.generateR1cs += performance.now();
126 }
127
128 if (ctx.error) throw(ctx.error);
129
130 delete ctx.constraints; // Liberate memory.
131
132 if (options.cSourceFile) {
133 if (ctx.verbose) console.log("Generating c...");
134 measures.generateC = -performance.now();
135 ctx.builder = new BuilderC(options.prime, ctx.verbose);
136 build(ctx);
137 await ctx.builder.build(options.cSourceFile, options.dataFile);
138 measures.generateC += performance.now();
139 }
140
141 if (ctx.error) throw(ctx.error);
142
143 if ((options.wasmFile)||(options.watFile)) {
144 if (ctx.verbose) console.log("Generating wasm...");
145 measures.generateWasm = -performance.now();
146 ctx.builder = new BuilderWasm(options.prime);
147 build(ctx);
148 if (options.wasmFile) {
149 await ctx.builder.build(options.wasmFile, "wasm");
150 }
151 if (options.watFile) {
152 await ctx.builder.build(options.watFile, "wat");
153 }
154 measures.generateWasm += performance.now();
155 }
156
157 // const mainCode = gen(ctx,ast);
158 if (ctx.error) throw(ctx.error);
159
160 if (options.symWriteStream) {
161 measures.generateSyms = -performance.now();
162 const rdStream = buildSyms(ctx);
163 rdStream.pipe(options.symWriteStream);
164 measures.generateSyms += performance.now();
165
166 await new Promise(fulfill => options.symWriteStream.on("finish", fulfill));
167 }
168
169// const def = buildCircuitDef(ctx, mainCode);
170
171 if (ctx.verbose) {
172 for (let [mStr, mValue] of Object.entries(measures)) {
173 console.log(mStr + ": " + ms2String(mValue));
174 }
175 }
176}
177
178
179
180function classifySignals(ctx) {
181
182 const ERROR = 0xFFFF;
183
184 function priorize(t1, t2) {
185 if ((t1 == ERROR) || (t2==ERROR)) return ERROR;
186 if (t1 == ctx.stINTERNAL) {
187 return t2;
188 } else if (t2==ctx.stINTERNAL) {
189 return t1;
190 }
191 if ((t1 == ctx.stONE) || (t2 == ctx.stONE)) return ctx.stONE;
192 if ((t1 == ctx.stOUTPUT) || (t2 == ctx.stOUTPUT)) return ctx.stOUTPUT;
193 if ((t1 == ctx.stCONSTANT) || (t2 == ctx.stCONSTANT)) return ctx.stCONSTANT;
194 if ((t1 == ctx.stDISCARDED) || (t2 == ctx.stDISCARDED)) return ctx.stDISCARDED;
195 if (t1!=t2) return ERROR;
196 return t1;
197 }
198
199 // First classify the signals
200 for (let s=0; s<ctx.signals.length; s++) {
201 if ((ctx.verbose)&&(s%100000 == 0)) console.log(`classify signals: ${s}/${ctx.signals.length}`);
202 const signal = ctx.signals[s];
203 let tAll = ctx.stINTERNAL;
204 let lSignal = signal;
205 let end = false;
206 while (!end) {
207 let t = lSignal.c || ctx.stINTERNAL;
208 if (s == 0) {
209 t = ctx.stONE;
210 } else if (lSignal.o & ctx.MAIN) {
211 if (lSignal.o & ctx.IN) {
212 if (lSignal.o & ctx.PRV) {
213 t = ctx.stPRVINPUT;
214 } else {
215 t = ctx.stPUBINPUT;
216 }
217 } else if (lSignal.o & ctx.OUT) {
218 t = ctx.stOUTPUT;
219 }
220 } else if (utils.isDefined(lSignal.v)) {
221 t = ctx.stCONSTANT;
222 }
223 tAll = priorize(t,tAll);
224 if (lSignal.e>=0) {
225 lSignal = ctx.signals[lSignal.e];
226 } else {
227 end=true;
228 }
229 }
230 if (tAll == ERROR) {
231 throw new Error("Incompatible types in signal: " + s);
232 }
233 lSignal.c = tAll;
234 }
235}
236
237
238function generateWitnessNames(ctx) {
239 const totals = {};
240 totals[ctx.stONE] = 0;
241 totals[ctx.stOUTPUT] = 0;
242 totals[ctx.stPUBINPUT] = 0;
243 totals[ctx.stPRVINPUT] = 0;
244 totals[ctx.stINTERNAL] = 0;
245 totals[ctx.stDISCARDED] = 0;
246 totals[ctx.stCONSTANT] = 0;
247 const ids = {};
248
249 // First classify the signals
250 for (let s=0; s<ctx.signals.length; s++) {
251
252 if ((ctx.verbose)&&(s%10000 == 0)) console.log("generate witness (counting): ", s);
253
254 const signal = ctx.signals[s];
255 let lSignal = signal;
256 while (lSignal.e>=0) lSignal = ctx.signals[lSignal.e];
257
258 if (!( lSignal.o & ctx.COUNTED) ) {
259 lSignal.o |= ctx.COUNTED;
260 totals[lSignal.c] ++;
261 }
262 }
263
264 ids[ctx.stONE] = 0;
265 ids[ctx.stOUTPUT] = 1;
266 ids[ctx.stPUBINPUT] = ids[ctx.stOUTPUT] + totals[ctx.stOUTPUT];
267 ids[ctx.stPRVINPUT] = ids[ctx.stPUBINPUT] + totals[ctx.stPUBINPUT];
268 ids[ctx.stINTERNAL] = ids[ctx.stPRVINPUT] + totals[ctx.stPRVINPUT];
269 ids[ctx.stDISCARDED] = ids[ctx.stINTERNAL] + totals[ctx.stINTERNAL];
270 ids[ctx.stCONSTANT] = ids[ctx.stDISCARDED] + totals[ctx.stDISCARDED];
271 const nSignals = ids[ctx.stCONSTANT] + totals[ctx.stCONSTANT];
272
273 for (let s=0; s<ctx.signals.length; s++) {
274
275 if ((ctx.verbose)&&(s%10000 == 0)) console.log("seting id: ", s);
276
277 const signal = ctx.signals[s];
278 let lSignal = signal;
279 while (lSignal.e>=0) {
280 lSignal = ctx.signals[lSignal.e];
281 }
282 if ( typeof(lSignal.id) === "undefined" ) {
283 lSignal.id = ids[lSignal.c] ++;
284 }
285
286 signal.id = lSignal.id;
287 }
288
289 ctx.totals = totals;
290}
291
292function reduceConstants(ctx) {
293 const newConstraints = new BigArray();
294 for (let i=0; i<ctx.constraints.length; i++) {
295 if ((ctx.verbose)&&(i%10000 == 0)) console.log("reducing constants: ", i);
296 const c = ctx.lc.canonize(ctx, ctx.constraints[i]);
297 if (!ctx.lc.isZero(c)) {
298 newConstraints.push(c);
299 }
300 delete ctx.constraints[i];
301 }
302 ctx.constraints = newConstraints;
303}
304
305async function reduceConstrains(ctx) {
306 const sig2constraint = new BigArray();
307 let removedSignals = new BigArray();
308 let nRemoved;
309 let lIdx;
310
311
312 let possibleConstraints = new BigArray(ctx.constraints.length);
313 let nextPossibleConstraints;
314 for (let i=0; i<ctx.constraints.length; i++) {
315 if ((ctx.verbose)&&(i%100000 == 0)) console.log(`indexing constraints: ${i}/${ctx.constraints.length}`);
316
317 const insertedSig = { 0: true}; // Do not insert one.
318 const c = ctx.constraints[i];
319 for (let s in c.a.coefs) {
320 if (!insertedSig[s]) {
321 if (!sig2constraint[s]) sig2constraint[s] = {};
322 sig2constraint[s][i] = true;
323 insertedSig[s] = true;
324 }
325 }
326 for (let s in c.b.coefs) {
327 if (!insertedSig[s]) {
328 if (!sig2constraint[s]) sig2constraint[s] = {};
329 sig2constraint[s][i] = true;
330 insertedSig[s] = true;
331 }
332 }
333 for (let s in c.c.coefs) {
334 if (!insertedSig[s]) {
335 if (!sig2constraint[s]) sig2constraint[s] = {};
336 sig2constraint[s][i] = true;
337 insertedSig[s] = true;
338 }
339 }
340 possibleConstraints[i] = ctx.constraints.length - i -1;
341 }
342
343 while (possibleConstraints.length >0) {
344 if (possibleConstraints.length>1<<20) {
345 nextPossibleConstraints = new BigArray();
346 } else {
347 nextPossibleConstraints = {};
348 }
349 removedSignals = {};
350 nRemoved = 0;
351 lIdx = {};
352 for (let i=0;i<possibleConstraints.length;i++) {
353 if ((ctx.verbose)&&(i%10000 == 0)) {
354 await Promise.resolve();
355 console.log(`reducing constraints: ${i}/${possibleConstraints.length} reduced: ${nRemoved}`);
356 }
357
358 const c = ctx.constraints[possibleConstraints[i]];
359 if (!c) continue;
360
361 // Limit of number of lelements removed per step
362 if (nRemoved>500000) {
363 nextPossibleConstraints[possibleConstraints[i]] = true;
364 continue;
365 }
366
367 // Swap a and b if b has more variables.
368 if (Object.keys(c.b).length > Object.keys(c.a).length) {
369 const aux = c.a;
370 c.a=c.b;
371 c.b=aux;
372 }
373
374 // Mov to C if possible.
375 if (isConstant(c.a)) {
376 const ct = {t: "N", v: c.a.coefs[sONE]};
377 c.c = ctx.lc.add(ctx.lc.mul(c.b, ct), c.c);
378 c.a = { t: "LC", coefs: {} };
379 c.b = { t: "LC", coefs: {} };
380 }
381 if (isConstant(c.b)) {
382 const ct = {t: "N", v: c.b.coefs[sONE]};
383 c.c = ctx.lc.add(ctx.lc.mul(c.a, ct), c.c);
384 c.a = { t: "LC", coefs: {} };
385 c.b = { t: "LC", coefs: {} };
386 }
387
388 if (ctx.lc.isZero(c.a) || ctx.lc.isZero(c.b)) {
389 const freeC = substituteRemoved(c.c);
390 const isolatedSignal = getFirstInternalSignal(ctx, freeC);
391 if (isolatedSignal) {
392 removedSignals[isolatedSignal] = isolateSignal(freeC, isolatedSignal);
393 if (lIdx[isolatedSignal]) {
394 const sigs = Object.keys(lIdx[isolatedSignal]);
395
396 for (let k=0; k<sigs.length; k++) {
397 const s = sigs[k];
398 const oldLC = removedSignals[s];
399 removedSignals[s] = substitute(removedSignals[s], isolatedSignal, removedSignals[isolatedSignal]);
400 if (oldLC !== removedSignals[s]) addTolIdx(removedSignals[s], s);
401 }
402 }
403
404 addTolIdx(removedSignals[isolatedSignal], isolatedSignal);
405 ctx.constraints[possibleConstraints[i]] = null;
406 nRemoved ++;
407
408 delete lIdx[isolatedSignal];
409
410 const cts = Object.keys(sig2constraint[isolatedSignal]);
411 for (let k=0; k<cts.length; k++) {
412 nextPossibleConstraints[cts[k]] = true;
413 }
414 }
415 }
416 }
417
418 if (nextPossibleConstraints.getKeys) {
419 nextPossibleConstraints = nextPossibleConstraints.getKeys();
420 } else {
421 nextPossibleConstraints = Object.keys(nextPossibleConstraints);
422 }
423
424 for (let i=0; i<nextPossibleConstraints.length;i++) {
425 if ((ctx.verbose)&&(i%10000 == 0)) {
426 await Promise.resolve();
427 console.log(`substituting constraints: ${i}/${nextPossibleConstraints.length}`);
428 }
429 const c = ctx.constraints[nextPossibleConstraints[i]];
430 if (c) {
431 const nc = {
432 a: substituteRemoved(c.a, nextPossibleConstraints[i]),
433 b: substituteRemoved(c.b, nextPossibleConstraints[i]),
434 c: substituteRemoved(c.c, nextPossibleConstraints[i])
435 };
436 if (ctx.lc.isZero(nc)) {
437 delete ctx.constraints[nextPossibleConstraints[i]];
438 } else {
439 ctx.constraints[nextPossibleConstraints[i]] = nc;
440 }
441 }
442 }
443
444 const removedSignalsList = Object.keys(removedSignals);
445
446 for (let i=0; i<removedSignalsList.length; i++) {
447 if ((ctx.verbose )&&(i%100000 == 0)) console.log(`removing signals: ${i}/${removedSignalsList.length}`);
448 const s = removedSignalsList[i];
449
450 let lSignal = ctx.signals[s];
451 while (lSignal.e>=0) {
452 lSignal = ctx.signals[lSignal.e];
453 }
454
455 sig2constraint[s] = null;
456 lSignal.c = ctx.stDISCARDED;
457 }
458
459/*
460 possibleConstraints = new BigArray();
461 // Reverse
462 for (let i=0; i<nextPossibleConstraints.length; i++) {
463 possibleConstraints[i] = nextPossibleConstraints[nextPossibleConstraints.length -1 -i];
464 }
465*/
466 possibleConstraints = nextPossibleConstraints;
467 }
468
469
470 let o=0;
471 for (let i=0; i<ctx.constraints.length;i++) {
472 if ((ctx.verbose)&&(i%100000 == 0)) console.log(`reordering constraints: ${i}/${ctx.constraints.length}`);
473 if (ctx.constraints[i]) {
474 if (!ctx.lc.isZero(ctx.constraints[i])) {
475 ctx.constraints[o] = ctx.constraints[i];
476 o++;
477 }
478 }
479 }
480
481 ctx.constraints.length = o;
482
483 function getFirstInternalSignal(ctx, l) {
484 for (let k in l.coefs) {
485 k = Number(k);
486 const signal = ctx.signals[k];
487 if ((signal.c == ctx.stINTERNAL)&&(!ctx.F.isZero(l.coefs[k])) &&(!removedSignals[k])) return k;
488 }
489 return null;
490 }
491
492 function isolateSignal(lc, s) {
493 const eq = {
494 t: "LC",
495 coefs: {}
496 };
497 const invCoef = ctx.F.inv(lc.coefs[s]);
498 for (const k in lc.coefs) {
499 if (k != s) {
500 const v = ctx.F.mul( ctx.F.neg(lc.coefs[k]), invCoef);
501 if (!ctx.F.isZero(v)) {
502 eq.coefs[k] = v;
503 }
504 }
505 }
506 return eq;
507 }
508
509 function substituteRemoved(lc, idxConstraint) {
510 const newLc = ctx.lc._clone(lc);
511 for (let k in lc.coefs) {
512 if (removedSignals[k]) {
513 delete newLc.coefs[k];
514 for (let k2 in removedSignals[k].coefs) {
515 const newP = ctx.F.mul(removedSignals[k].coefs[k2], lc.coefs[k]);
516 if (!ctx.F.isZero(newP)) {
517 if (newLc.coefs[k2]) {
518 newLc.coefs[k2] = ctx.F.add(newLc.coefs[k2], newP);
519 if (ctx.F.isZero(newLc.coefs[k2])) delete newLc.coefs[k2];
520 } else {
521 newLc.coefs[k2] = newP;
522 }
523 }
524 if ((typeof idxConstraint != "undefined")&&(k2!=0)) {
525 if (!sig2constraint[k2]) sig2constraint[k2] = {};
526 sig2constraint[k2][idxConstraint] = true;
527 }
528 }
529 }
530 }
531 return newLc;
532 }
533
534 function substitute(lc, s, eq) {
535 if (!lc.coefs[s]) return lc;
536 const newLc = ctx.lc._clone(lc);
537 delete newLc.coefs[s];
538 for (let k2 in eq.coefs) {
539 const newP = ctx.F.mul(eq.coefs[k2], lc.coefs[s]);
540 if (!ctx.F.isZero(newP)) {
541 if (newLc.coefs[k2]) {
542 newLc.coefs[k2] = ctx.F.add(newLc.coefs[k2], newP);
543 if (ctx.F.isZero(newLc.coefs[k2])) delete newLc.coefs[k2];
544 } else {
545 newLc.coefs[k2] = newP;
546 }
547 }
548 }
549 return newLc;
550 }
551
552 function isConstant(l) {
553 for (let k in l.coefs) {
554 if ((k != sONE) && (!ctx.F.isZero(l.coefs[k]))) return false;
555 }
556 if (!l.coefs[sONE] || ctx.F.isZero(l.coefs[sONE])) return false;
557 return true;
558 }
559
560 function addTolIdx(lc, newS) {
561 const sigs = Object.keys(lc.coefs);
562 for (let k=0; k<sigs.length; k++) {
563 const s = sigs[k];
564 if (s) {
565 if (!lIdx[s]) lIdx[s] = {};
566 lIdx[s][newS] = true;
567 }
568 }
569 }
570
571}
572
573