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