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