arnaucube
arnaucube/circom_testerPublic Self-hosted repository
circom_tester/c/tester.js
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");
  7
  8const util = require("util");
  9const exec = util.promisify(require("child_process").exec);
 10
 11const loadR1cs = require("r1csfile").load;
 12const ZqField = require("ffjavascript").ZqField;
 13
 14const readWtns = require("snarkjs").wtns.exportJson;
 15
 16module.exports = c_tester;
 17
 18async function  c_tester(circomInput, _options) {
 19
 20    assert(await compiler_above_version("2.0.0"),"Wrong compiler version. Must be at least 2.0.0");
 21    
 22    tmp.setGracefulCleanup();
 23
 24    const dir = await tmp.dir({prefix: "circom_", unsafeCleanup: true });
 25
 26    // console.log(dir.path);
 27
 28    const baseName = path.basename(circomInput, ".circom");
 29    const options = Object.assign({}, _options);
 30
 31    options.wasm = true;
 32
 33    options.sym = true;
 34    options.json = options.json || false; // costraints in json format
 35    //options.r1cs = options.r1cs || false; // costraints in r1cs format
 36    //if (!options.json) options.r1cs = true; // r1cs if not json
 37    options.r1cs = true;
 38    options.output = dir.path;
 39
 40    await compile(baseName, circomInput, options);
 41
 42    return new WasmTester(dir, baseName, run);
 43}
 44
 45async function compile (baseName, fileName, options) {    
 46    var flags = "--c ";
 47    if (options.sym) flags += "--sym ";
 48    if (options.r1cs) flags += "--r1cs ";
 49    if (options.json) flags += "--json ";
 50    if (options.output) flags += "--output " + options.output + " ";
 51    if (options.O === 0) flags += "--O0 "
 52    if (options.O === 1) flags += "--O1 "
 53
 54    b = await exec("circom " + flags + fileName); 
 55    assert(b.stderr == "",
 56	   "circom compiler error \n" + b.stderr);
 57    
 58    const c_folder = path.join(options.output, baseName+"_cpp/")
 59    b = await exec("make -C "+c_folder);
 60    assert(b.stderr == "",
 61	   "error building the executable C program\n" + b.stderr);
 62}
 63    
 64class WasmTester {
 65
 66    constructor(dir, baseName, witnessCalculator) {
 67        this.dir=dir;
 68        this.baseName = baseName;
 69        this.witnessCalculator = witnessCalculator;
 70    }
 71
 72    async release() {
 73        await this.dir.cleanup();
 74    }
 75
 76    async calculateWitness(input) {
 77	const inputjson = JSON.stringify(input);
 78	const inputFile = path.join(this.dir.path, this.baseName+"_cpp/" + this.baseName + ".json");
 79	const wtnsFile = path.join(this.dir.path, this.baseName+"_cpp/" + this.baseName + ".wtns");
 80	const runc = path.join(this.dir.path, this.baseName+"_cpp/" + this.baseName);	
 81        fs.writeFile(inputFile, inputjson, function(err) {
 82	    if (err) throw err;
 83	});
 84	await exec("ls " + path.join(this.dir.path, this.baseName+"_cpp/"));
 85	await exec(runc + " " + inputFile + " " + wtnsFile);
 86	return await readBinWitnessFile(wtnsFile);
 87    }
 88
 89    async loadSymbols() {
 90        if (this.symbols) return;
 91        this.symbols = {};
 92        const symsStr = await fs.promises.readFile(
 93            path.join(this.dir.path, this.baseName + ".sym"),
 94            "utf8"
 95        );
 96        const lines = symsStr.split("\n");
 97        for (let i=0; i<lines.length; i++) {
 98            const arr = lines[i].split(",");
 99            if (arr.length!=4) continue;
100            this.symbols[arr[3]] = {
101                labelIdx: Number(arr[0]),
102                varIdx: Number(arr[1]),
103                componentIdx: Number(arr[2]),
104            };
105        }
106    }
107
108    async loadConstraints() {
109        const self = this;
110        if (this.constraints) return;
111        const r1cs = await loadR1cs(path.join(this.dir.path, this.baseName + ".r1cs"),true, false);
112        self.F = new ZqField(r1cs.prime);
113        self.nVars = r1cs.nVars;
114        self.constraints = r1cs.constraints;
115    }
116
117    async assertOut(actualOut, expectedOut) {
118        const self = this;
119        if (!self.symbols) await self.loadSymbols();
120
121        checkObject("main", expectedOut);
122
123        function checkObject(prefix, eOut) {
124
125            if (Array.isArray(eOut)) {
126                for (let i=0; i<eOut.length; i++) {
127                    checkObject(prefix + "["+i+"]", eOut[i]);
128                }
129            } else if ((typeof eOut == "object")&&(eOut.constructor.name == "Object")) {
130                for (let k in eOut) {
131                    checkObject(prefix + "."+k, eOut[k]);
132                }
133            } else {
134                if (typeof self.symbols[prefix] == "undefined") {
135                    assert(false, "Output variable not defined: "+ prefix);
136                }
137                const ba = actualOut[self.symbols[prefix].varIdx].toString();
138                const be = eOut.toString();
139                assert.strictEqual(ba, be, prefix);
140            }
141        }
142    }
143
144    async getDecoratedOutput(witness) {
145        const self = this;
146        const lines = [];
147        if (!self.symbols) await self.loadSymbols();
148        for (let n in self.symbols) {
149            let v;
150            if (utils.isDefined(witness[self.symbols[n].varIdx])) {
151                v = witness[self.symbols[n].varIdx].toString();
152            } else {
153                v = "undefined";
154            }
155            lines.push(`${n} --> ${v}`);
156        }
157        return lines.join("\n");
158    }
159
160    async checkConstraints(witness) {
161        const self = this;
162        if (!self.constraints) await self.loadConstraints();
163        for (let i=0; i<self.constraints.length; i++) {
164            checkConstraint(self.constraints[i]);
165        }
166
167        function checkConstraint(constraint) {
168	    
169            const F = self.F;
170            const a = evalLC(constraint[0]);
171            const b = evalLC(constraint[1]);
172            const c = evalLC(constraint[2]);
173            assert (F.isZero(F.sub(F.mul(a,b), c)), "Constraint doesn't match");
174        }
175
176        function evalLC(lc) {
177            const F = self.F;
178            let v = F.zero;
179            for (let w in lc) {
180                v = F.add(
181                    v,
182                    F.mul( lc[w], witness[w] )
183                );
184            }
185            return v;
186        }
187    }
188
189}
190
191function version_to_list ( v ) {
192    return v.split(".").map(function(x) {
193	return parseInt(x, 10);
194    });
195}
196
197function check_versions ( v1, v2 ) {
198    //check if v1 is newer than or equal to v2
199    for (let i = 0; i < v2.length; i++) {
200	if (v1[i] > v2[i]) return true;
201	if (v1[i] < v2[i]) return false;
202    }
203    return true;
204}
205
206async function compiler_above_version(v) {
207    let output = await exec('circom --version').toString();
208    let compiler_version = version_to_list(output.slice(output.search(/\d/),-1));
209    vlist = version_to_list(v);
210    return check_versions ( compiler_version, vlist );
211}
212
213async function readBinWitnessFile(fileName) {
214    const buffWitness = await readWtns(fileName);
215    return buffWitness;
216}
217
218function fromArray8(arr) { //returns a BigInt
219    var res = BigInt(0);
220    const radix = BigInt(0x100);
221    for (let i = arr.length-1 ; i>=0; i--) {
222        res = res*radix + BigInt(arr[i]);
223    }
224    return res;
225}
226
227function fromArray8ToUint(arr) { //returns a BigInt
228    var res = 0;
229    const radix = 8;
230    for (let i = arr.length-1 ; i>=0; i--) {
231        res = res*radix + arr[i];
232    }
233    return res;
234}