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