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