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