arnaucube
arnaucube/circomPublic Self-hosted repository
circom/ports/c/tester.js
ports/c/tester.jsRaw
  1const chai = require("chai");
  2const assert = chai.assert;
  3
  4const fs = require("fs");
  5var tmp = require("tmp-promise");
  6const path = require("path");
  7const compiler = require("../../src/compiler");
  8const util = require("util");
  9const exec = util.promisify(require("child_process").exec);
 10
 11const bigInt = require("big-integer");
 12const utils = require("../../src/utils");
 13const loadR1cs = require("r1csfile").load;
 14const ZqField = require("ffjavascript").ZqField;
 15const buildZqField = require("ffiasm").buildZqField;
 16
 17module.exports = c_tester;
 18
 19
 20async function  c_tester(circomFile, _options) {
 21    tmp.setGracefulCleanup();
 22
 23    const dir = await tmp.dir({prefix: "circom_", unsafeCleanup: true });
 24
 25    // console.log(dir.path);
 26
 27    const baseName = path.basename(circomFile, ".circom");
 28    const options = Object.assign({}, _options);
 29
 30    options.cSourceWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".cpp"));
 31    options.symWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".sym"));
 32    options.r1csFileName = path.join(dir.path, baseName + ".r1cs");
 33
 34    options.p = options.p || bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
 35    await compiler(circomFile, options);
 36
 37    const source = await buildZqField(options.p, "Fr");
 38
 39    // console.log(dir.path);
 40
 41    await fs.promises.writeFile(path.join(dir.path, "fr.asm"), source.asm, "utf8");
 42    await fs.promises.writeFile(path.join(dir.path, "fr.h"), source.h, "utf8");
 43    await fs.promises.writeFile(path.join(dir.path, "fr.c"), source.c, "utf8");
 44
 45    let pThread = "";
 46
 47    if (process.platform === "darwin") {
 48        await exec("nasm -fmacho64 --prefix _ " +
 49            ` ${path.join(dir.path,  "fr.asm")}`
 50        );
 51    }  else if (process.platform === "linux") {
 52        pThread = "-pthread";
 53        await exec("nasm -felf64 " +
 54            ` ${path.join(dir.path,  "fr.asm")}`
 55        );
 56    } else throw("Unsupported platform");
 57
 58    const cdir = path.join(path.dirname(require.resolve("circom_runtime")), "c");
 59
 60    await exec("g++" + ` ${pThread}` +
 61               ` ${path.join(cdir,  "main.cpp")}` +
 62               ` ${path.join(cdir,  "calcwit.cpp")}` +
 63               ` ${path.join(cdir,  "utils.cpp")}` +
 64               ` ${path.join(dir.path,  "fr.c")}` +
 65               ` ${path.join(dir.path,  "fr.o")}` +
 66               ` ${path.join(dir.path, baseName + ".cpp")} ` +
 67               ` -o ${path.join(dir.path, baseName)}` +
 68               ` -I ${dir.path} -I${cdir}` +
 69               " -lgmp -std=c++11 -DSANITY_CHECK -g"
 70    );
 71
 72    // console.log(dir.path);
 73    return new CTester(dir, baseName);
 74}
 75
 76class CTester {
 77
 78    constructor(dir, baseName) {
 79        this.dir=dir;
 80        this.baseName = baseName;
 81    }
 82
 83    async release() {
 84        await this.dir.cleanup();
 85    }
 86
 87    async calculateWitness(input) {
 88        await fs.promises.writeFile(
 89            path.join(this.dir.path, "in.json"),
 90            JSON.stringify(utils.stringifyBigInts(input), null, 1)
 91        );
 92        const r = await exec(`${path.join(this.dir.path, this.baseName)}` +
 93                   ` ${path.join(this.dir.path, "in.json")}` +
 94                   ` ${path.join(this.dir.path, "out.json")}`
 95        );
 96        if (r.stdout) {
 97            console.log(r.stdout);
 98        }
 99        const resStr = await fs.promises.readFile(
100            path.join(this.dir.path, "out.json")
101        );
102
103        const res = utils.unstringifyBigInts(JSON.parse(resStr));
104        return res;
105    }
106
107    async loadSymbols() {
108        if (this.symbols) return;
109        this.symbols = {};
110        const symsStr = await fs.promises.readFile(
111            path.join(this.dir.path, this.baseName + ".sym"),
112            "utf8"
113        );
114        const lines = symsStr.split("\n");
115        for (let i=0; i<lines.length; i++) {
116            const arr = lines[i].split(",");
117            if (arr.length!=4) continue;
118            this.symbols[arr[3]] = {
119                labelIdx: Number(arr[0]),
120                varIdx: Number(arr[1]),
121                componentIdx: Number(arr[2]),
122            };
123        }
124    }
125
126    async loadConstraints() {
127        const self = this;
128        if (this.constraints) return;
129        const r1cs = await loadR1cs(path.join(this.dir.path, this.baseName + ".r1cs"),true, false);
130        self.field = new ZqField(r1cs.prime);
131        self.nVars = r1cs.nVars;
132        self.constraints = r1cs.constraints;
133    }
134
135    async assertOut(actualOut, expectedOut) {
136        const self = this;
137        if (!self.symbols) await self.loadSymbols();
138
139        checkObject("main", expectedOut);
140
141        function checkObject(prefix, eOut) {
142
143            if (Array.isArray(eOut)) {
144                for (let i=0; i<eOut.length; i++) {
145                    checkObject(prefix + "["+i+"]", eOut[i]);
146                }
147            } else if ((typeof eOut == "object")&&(eOut.constructor.name == "Object")) {
148                for (let k in eOut) {
149                    checkObject(prefix + "."+k, eOut[k]);
150                }
151            } else {
152                if (typeof self.symbols[prefix] == "undefined") {
153                    assert(false, "Output variable not defined: "+ prefix);
154                }
155                const ba = bigInt(actualOut[self.symbols[prefix].varIdx]).toString();
156                const be = bigInt(eOut).toString();
157                assert.strictEqual(ba, be, prefix);
158            }
159        }
160    }
161
162    async getDecoratedOutput(witness) {
163        const self = this;
164        const lines = [];
165        if (!self.symbols) await self.loadSymbols();
166        for (let n in self.symbols) {
167            let v;
168            if (utils.isDefined(witness[self.symbols[n].varIdx])) {
169                v = witness[self.symbols[n].varIdx].toString();
170            } else {
171                v = "undefined";
172            }
173            lines.push(`${n} --> ${v}`);
174        }
175        return lines.join("\n");
176    }
177
178    async checkConstraints(witness) {
179        const self = this;
180        if (!self.constraints) await self.loadConstraints();
181        for (let i=0; i<self.constraints.length; i++) {
182            checkConstraint(self.constraints[i]);
183        }
184
185        function checkConstraint(constraint) {
186            const F = self.field;
187            const a = evalLC(constraint[0]);
188            const b = evalLC(constraint[1]);
189            const c = evalLC(constraint[2]);
190
191            assert (F.sub(F.mul(a,b), c).isZero(), "Constraint doesn't match");
192        }
193
194        function evalLC(lc) {
195            const F = self.field;
196            let v = F.zero;
197            for (let w in lc) {
198                v = F.add(
199                    v,
200                    F.mul( lc[w], witness[w] )
201                );
202            }
203            return v;
204        }
205    }
206
207}
208
209