circom/src/c_build.js
src/c_build.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 assert = require("assert");
21const bigInt = require("big-integer");
22const utils = require("./utils");
23const gen = require("./c_gen").gen;
24const newRef = require("./c_gen").newRef;
25
26module.exports = buildC;
27
28
29function buildC(ctx) {
30 ctx.definedFunctions = {};
31 ctx.functionCodes = [];
32 ctx.buildFunction = buildFunction;
33 ctx.code = "";
34 ctx.conditionalCodeHeader = "";
35 ctx.tmpNames = {};
36 ctx.getTmpName = getTmpName;
37 ctx.codes_sizes = [];
38 ctx.definedSizes = {};
39 ctx.addSizes = addSizes;
40
41 const entryTables = buildEntryTables(ctx);
42 ctx.globalNames = ctx.tmpNames;
43
44 const code = buildCode(ctx);
45 const functions = buildFuncFunctions(ctx);
46 const compnentsArray = buildComponentsArray(ctx);
47
48 const headder = buildHeader(ctx);
49 const sizes = buildSizes(ctx);
50 const mapIsInput = buildMapIsInput(ctx);
51 const wit2Sig = buildWit2Sig(ctx);
52 const circuitVar = buildCircuitVar(ctx);
53
54 return "" +
55 headder + "\n" +
56 sizes + "\n" +
57 entryTables + "\n" +
58 functions + "\n" +
59 code + "\n" +
60 compnentsArray + "\n" +
61 mapIsInput + "\n" +
62 wit2Sig +"\n" +
63 circuitVar;
64}
65
66function buildEntryTables(ctx) {
67
68 const codes_hashMaps = [];
69 const codes_componentEntries = [];
70 const definedHashTables = {};
71 for (let i=0; i<ctx.components.length; i++) {
72 const {htName, htMap} = addHashTable(i);
73
74 let code = "";
75 const componentEntriesTableName = ctx.getTmpName("entryTable_" + ctx.components[i].template);
76
77 code += `Circom_ComponentEntry ${componentEntriesTableName}[${htMap.length}] = {\n`;
78 for (let j=0; j<htMap.length; j++) {
79 const entry = ctx.components[i].names.o[htMap[j]];
80 code += j>0 ? " ," : " ";
81 const sizeName = ctx.addSizes(entry.sizes);
82
83 const ty = entry.type == "S" ? "_typeSignal" : "_typeComponent";
84 code += `{${entry.offset},${sizeName}, ${ty}}\n`;
85 }
86 code += "};\n";
87 codes_componentEntries.push(code);
88
89 ctx.components[i].htName = htName;
90 ctx.components[i].etName = componentEntriesTableName;
91 }
92
93
94 return "" +
95 "// HashMaps\n" +
96 codes_hashMaps.join("\n") + "\n" +
97 "\n" +
98 "// Component Entries\n" +
99 codes_componentEntries.join("\n") + "\n" +
100 "\n";
101
102 function addHashTable(cIdx) {
103 const keys = Object.keys(ctx.components[cIdx].names.o);
104 assert(keys.length<128);
105 keys.sort((a,b) => ((a>b) ? 1 : -1));
106 const h = utils.fnvHash(keys.join(","));
107 if (definedHashTables[h]) return definedHashTables[h];
108 definedHashTables[h] = {};
109 definedHashTables[h].htName = ctx.getTmpName("ht_"+ctx.components[cIdx].template);
110 definedHashTables[h].htMap = [];
111 const t = [];
112 for (let i=0; i<keys.length; i++) {
113 definedHashTables[h].htMap[i] = keys[i];
114
115 const h2 = utils.fnvHash(keys[i]);
116 let pos = parseInt(h2.slice(-2), 16);
117 while (t[pos]) pos = (pos + 1) % 256;
118 t[pos] = [h2, i];
119 }
120 let code = `Circom_HashEntry ${definedHashTables[h].htName}[256] = {`;
121 for (let i=0; i<256; i++) {
122 code += i>0 ? "," : "";
123 if (t[i]) {
124 code += `{0x${t[i][0]}LL, ${t[i][1]}} /* ${keys[t[i][1]]} */`;
125 } else {
126 code += "{0,0}";
127 }
128 }
129 code += "};\n";
130
131 codes_hashMaps.push(code);
132
133 return definedHashTables[h];
134 }
135}
136
137function buildCode(ctx) {
138
139 const fDefined = {};
140
141 const fnComponents = [];
142 for (let i=0; i<ctx.components.length; i++) {
143 const {h, instanceDef} = hashComponentCall(ctx, i);
144 const fName = ctx.components[i].template+"_"+h;
145 if (!fDefined[fName]) {
146
147
148 const scope = {_prefix : ""};
149 ctx.scopes = [scope];
150 ctx.conditionalCode = false;
151 ctx.code = "";
152 ctx.codeHeader = "// Header\n";
153 ctx.codeFooter = "// Footer\n";
154 ctx.tmpNames = Object.assign({},ctx.globalNames);
155
156 for (let p in ctx.components[i].params) {
157 newRef(ctx, "BIGINT", p, bigInt(ctx.components[i].params[p]));
158 }
159
160 gen(ctx, ctx.templates[ctx.components[i].template].block);
161
162 const S =
163 "/*\n" +
164 instanceDef +
165 "\n*/\n" +
166 `void ${fName}(Circom_CalcWit *ctx) {\n` +
167 utils.ident(
168 ctx.codeHeader + "\n" +
169 ctx.code + "\n" +
170 ctx.codeFooter
171 ) +
172 "}\n";
173
174 fnComponents.push(S);
175 fDefined[fName] = true;
176 }
177 ctx.components[i].fnName = fName;
178 }
179
180 return fnComponents.join("\n");
181}
182
183function buildFuncFunctions(ctx) {
184 return "// Functions\n" +
185 ctx.functionCodes.join("\n");
186}
187
188function buildComponentsArray(ctx) {
189
190 const ccodes = [];
191 ccodes.push(`Circom_Component _components[${ctx.components.length}] = {\n`);
192 for (let i=0; i< ctx.components.length; i++) {
193 ccodes.push(i>0 ? " ," : " ");
194 ccodes.push(`{${ctx.components[i].htName},${ctx.components[i].etName},${ctx.components[i].fnName}, ${ctx.components[i].nInSignals}}\n`);
195 }
196 ccodes.push("};\n");
197 const codeComponents = ccodes.join("");
198
199 return "" +
200 "// Components\n" +
201 codeComponents +
202 "\n";
203}
204
205
206function buildHeader(ctx) {
207 return "#include \"circom.h\"\n" +
208 "#include \"calcwit.h\"\n" +
209 `#define NSignals ${ctx.signals.length}\n` +
210 `#define NComponents ${ctx.components.length}\n` +
211 `#define NInputs ${ctx.components[ ctx.getComponentIdx("main") ].nInSignals}\n`+
212 `#define NOutputs ${ctx.totals[ ctx.stOUTPUT ]}\n`+
213 `#define NVars ${ctx.totals[ctx.stONE] + ctx.totals[ctx.stOUTPUT] + ctx.totals[ctx.stPUBINPUT] + ctx.totals[ctx.stPRVINPUT] + ctx.totals[ctx.stINTERNAL]}\n` +
214 `#define __P__ "${ctx.field.p.toString()}"\n` +
215 "\n";
216}
217
218function buildSizes(ctx) {
219 return "// Sizes\n" +
220 ctx.codes_sizes.join("\n");
221}
222
223
224function buildMapIsInput(ctx) {
225 const arr = [];
226 let line = "";
227 let acc = 0;
228 let i;
229 for (i=0; i<ctx.signals.length; i++) {
230 if (ctx.signals[i].o & ctx.IN) {
231 acc = acc | (1 << (i%32) );
232 }
233 if ((i+1)%32==0) {
234 line += (i>31) ? "," : " ";
235 line += toHex(acc);
236 acc = 0;
237 if ( i % (32*64) == 0) {
238 arr.push(line);
239 line = "";
240 }
241 }
242 }
243
244 if ((i%32) != 0) {
245 line += (i>31) ? "," : " ";
246 line += toHex(acc);
247 }
248 if (line != "") {
249 arr.push(line);
250 }
251
252 return "// mapIsArray\n" +
253 `u32 _mapIsInput[${Math.floor((ctx.signals.length-1) / 32)+1}] = {\n`+
254 arr.join("\n") + "\n" +
255 "};\n";
256
257 function toHex(number) {
258 if (number < 0) number = 0xFFFFFFFF + number + 1;
259 let S=number.toString(16).toUpperCase();
260 while (S.length<8) S = "0" + S;
261 return "0x"+S;
262 }
263}
264
265
266function buildWit2Sig(ctx) {
267 const codes = [];
268 const NVars =
269 ctx.totals[ctx.stONE] +
270 ctx.totals[ctx.stOUTPUT] +
271 ctx.totals[ctx.stPUBINPUT] +
272 ctx.totals[ctx.stPRVINPUT] +
273 ctx.totals[ctx.stINTERNAL];
274 const arr = Array(NVars);
275 for (let i=0; i<ctx.signals.length; i++) {
276 const outIdx = ctx.signals[i].id;
277 if (ctx.signals[i].e>=0) continue; // If has an alias, continue..
278 assert(typeof outIdx != "undefined", `Signal ${i} does not have index`);
279 if (outIdx>=NVars) continue; // Is a constant or a discarded variable
280 if (typeof arr[ctx.signals[i].id] == "undefined") {
281 arr[outIdx] = i;
282 }
283 }
284 codes.push("// Signal Table\n");
285 codes.push(`int _wit2sig[${NVars}] = {\n`);
286 let code = "";
287 for (let i=0; i<NVars; i++) {
288 code += (i>0) ? ",": " ";
289 code += arr[i];
290 if ((i>0)&&(i%64 == 0)) {
291 if (code != "") codes.push(code + "\n");
292 codes.push(code);
293 code =0;
294 }
295 }
296 if (code != "") codes.push(code + "\n");
297 codes.push("};\n");
298
299 return codes.join("");
300
301}
302
303
304function buildCircuitVar() {
305 return "Circom_Circuit _circuit = {\n" +
306 " NSignals,\n"+
307 " NComponents,\n"+
308 " NInputs,\n"+
309 " NOutputs,\n"+
310 " NVars,\n"+
311 " _wit2sig,\n"+
312 " _components,\n"+
313 " _mapIsInput,\n"+
314 " __P__\n" +
315 "};\n";
316}
317
318
319
320
321function getTmpName(_suggestedName) {
322 let suggestedName;
323 if (_suggestedName) {
324 suggestedName = trimUnderscore(_suggestedName);
325 } else {
326 suggestedName = "tmp";
327 }
328
329 if (typeof(this.tmpNames[suggestedName]) == "undefined") {
330 this.tmpNames[suggestedName] = 1;
331 return "_"+suggestedName;
332 } else {
333 const name = "_" + suggestedName + "_" + this.tmpNames[suggestedName];
334 this.tmpNames[suggestedName]++;
335 return name;
336 }
337
338 function trimUnderscore(str) {
339 let p1=0;
340 while ((p1 < str.length)&&(str[p1] == "_")) p1++;
341 let p2=str.length;
342 while ((p2 > 0)&&(str[p2-1] == "_")) p2--;
343
344 return str.slice(p1,p2);
345 }
346
347}
348
349function addSizes(_sizes) {
350 const sizes = _sizes || [];
351 let name = "sizes";
352 for (let i=0; i<sizes.length;i++) {
353 name+="_"+sizes[i];
354 }
355 if (name=="sizes") name="sizes_0";
356
357 if (this.definedSizes[name]) return this.definedSizes[name];
358
359 const labelName = this.getTmpName(name);
360 this.definedSizes[name] = labelName;
361
362 const accSizes = utils.accSizes(sizes);
363
364 let code = `Circom_Size ${labelName}[${accSizes.length}] = {`;
365 for (let i=0; i<accSizes.length; i++) {
366 if (i>0) code += ",";
367 code += accSizes[i];
368 }
369 code += "};\n";
370 this.codes_sizes.push(code);
371
372 return labelName;
373}
374
375function buildFunction(name, paramValues) {
376 const ctx = this;
377 const {h, instanceDef} = hashFunctionCall(ctx, name, paramValues);
378
379 if (ctx.definedFunctions[h]) return ctx.definedFunctions[h];
380
381 const res = {
382 fnName: `${name}_${h}`
383 };
384
385 const oldScopes = ctx.scopes;
386 const oldConditionalCode = ctx.conditionalCode;
387 const oldCode = ctx.code;
388 const oldCodeHeader = ctx.codeHeader;
389 const oldCodeFooter = ctx.codeFooter;
390 const oldTmpNames = ctx.tmpNames;
391
392
393 const scope = {_prefix : ""};
394 ctx.scopes = [scope];
395 ctx.conditionalCode = false;
396 ctx.code = "";
397 ctx.codeHeader = "// Header\n";
398 ctx.codeFooter = "// Footer\n";
399 ctx.tmpNames = Object.assign({},ctx.globalNames);
400 ctx.returnValue = null;
401 ctx.returnSizes = null;
402
403 let paramsStr = "";
404
405 for (let i=0; i<ctx.functions[name].params.length; i++) {
406
407 if (paramValues[i].used) {
408 paramsStr += `,PBigInt ${ctx.functions[name].params[i]}`;
409 scope[ctx.functions[name].params[i]] = {
410 stack: true,
411 type: "BIGINT",
412 used: true,
413 sizes: paramValues[i].sizes,
414 label: ctx.functions[name].params[i],
415 };
416 } else {
417 scope[ctx.functions[name].params[i]] = {
418 stack: true,
419 type: "BIGINT",
420 used: false,
421 sizes: paramValues[i].sizes,
422 label: ctx.functions[name].params[i],
423 value: paramValues[i].value
424 };
425 }
426 }
427
428 gen(ctx, ctx.functions[name].block);
429
430 if (ctx.returnValue == null) {
431 if (ctx.returnSizes == null) assert(false, `Funciont ${name} does not return any value`);
432 res.type = "VARVAL_CONSTSIZE";
433 let code =
434 "/*\n" +
435 instanceDef +
436 "\n*/\n" +
437 `void ${name}_${h}(Circom_CalcWit *ctx, PBigInt __retValue ${paramsStr}) {`;
438 code += utils.ident(ctx.codeHeader);
439 code += utils.ident(ctx.code);
440 code += utils.ident("returnFunc:\n");
441 code += utils.ident(ctx.codeFooter);
442 code += "}\n";
443 res.returnSizes = ctx.returnSizes;
444 ctx.functionCodes.push(code);
445 } else {
446 res.type = "CONSTVAL";
447 res.returnValue = ctx.returnValue;
448 }
449
450 ctx.scopes = oldScopes;
451 ctx.conditionalCode = oldConditionalCode;
452 ctx.code = oldCode;
453 ctx.codeHeader = oldCodeHeader;
454 ctx.codeFooter = oldCodeFooter;
455 ctx.tmpNames = oldTmpNames;
456
457 ctx.definedFunctions[h] = res;
458
459 return res;
460}
461
462
463
464function hashComponentCall(ctx, cIdx) {
465 // TODO: At the moment generate a diferent function for each instance of the component
466 const constParams = [];
467 for (let p in ctx.components[cIdx].params) {
468 constParams.push(p + "=" + value2str(ctx.components[cIdx].params[p]));
469 }
470
471 for (let n in ctx.components[cIdx].names.o) {
472 const entry = ctx.components[cIdx].names.o[n];
473 if ((entry.type == "S")&&(ctx.signals[entry.offset].o & ctx.IN)) {
474 travelSizes(n, entry.offset, entry.sizes, (prefix, offset) => {
475 if (utils.isDefined(ctx.signals[offset].v)) {
476 constParams.push(prefix + "=" + bigInt(ctx.signals[offset].value));
477 }
478 });
479 }
480 }
481
482 let instanceDef = ctx.components[cIdx].template;
483 if (constParams.length>0) {
484 instanceDef += "\n";
485 constParams.sort();
486 instanceDef += constParams.join("\n");
487 }
488 const h = utils.fnvHash(instanceDef);
489 return {h, instanceDef};
490
491 function travelSizes(prefix, offset, sizes, fn) {
492 if (sizes.length == 0) {
493 fn(prefix, offset);
494 return 1;
495 } else {
496 let o = offset;
497 for (let i=0; i<sizes[0]; i++) {
498 o += travelSizes(prefix + "[" + i + "]", o, sizes.slice(1), fn);
499 }
500 return o-offset;
501 }
502 }
503}
504
505function hashFunctionCall(ctx, name, paramValues) {
506 // TODO
507 const constParams = [];
508 for (let i=0; i<ctx.functions[name].params.length; i++) {
509 if (!paramValues[i].used) {
510 constParams.push(ctx.functions[name].params[i] + "=" + value2str(paramValues[i]));
511 }
512 }
513 let instanceDef = name;
514 if (constParams.length>0) {
515 instanceDef += "\n";
516 constParams.sort();
517 instanceDef += constParams.join("\n");
518 }
519
520 const h = utils.fnvHash(instanceDef);
521 return {h, instanceDef};
522}
523
524function value2str(v) {
525 if (Array.isArray(v)) {
526 let S="[";
527 for (let i=0; i<v.length; i++) {
528 if (i>0) S+=",";
529 S+=value2str(v[i]);
530 }
531 return S;
532 } else {
533 return bigInt(v).toString();
534 }
535}