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