circom/ports/wasm/tester.js
ports/wasm/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");
8
9const utils = require("../../src/utils");
10const loadR1cs = require("r1csfile").load;
11const ZqField = require("ffjavascript").ZqField;
12
13const WitnessCalculatorBuilder = require("circom_runtime").WitnessCalculatorBuilder;
14
15module.exports = wasm_tester;
16
17async function wasm_tester(circomFile, _options) {
18 tmp.setGracefulCleanup();
19
20 const dir = await tmp.dir({prefix: "circom_", unsafeCleanup: true });
21
22 // console.log(dir.path);
23
24 const baseName = path.basename(circomFile, ".circom");
25 const options = Object.assign({}, _options);
26
27 options.wasmWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".wasm"));
28 options.symWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".sym"));
29 options.r1csFileName = path.join(dir.path, baseName + ".r1cs");
30
31 const promisesArr = [];
32 promisesArr.push(new Promise(fulfill => options.wasmWriteStream.on("finish", fulfill)));
33
34 await compiler(circomFile, options);
35
36 await Promise.all(promisesArr);
37
38 const wasm = await fs.promises.readFile(path.join(dir.path, baseName + ".wasm"));
39
40 const wc = await WitnessCalculatorBuilder(wasm);
41
42 return new WasmTester(dir, baseName, wc);
43}
44
45class WasmTester {
46
47 constructor(dir, baseName, witnessCalculator) {
48 this.dir=dir;
49 this.baseName = baseName;
50 this.witnessCalculator = witnessCalculator;
51 }
52
53 async release() {
54 await this.dir.cleanup();
55 }
56
57 async calculateWitness(input, sanityCheck) {
58 return await this.witnessCalculator.calculateWitness(input, sanityCheck);
59 }
60
61 async loadSymbols() {
62 if (this.symbols) return;
63 this.symbols = {};
64 const symsStr = await fs.promises.readFile(
65 path.join(this.dir.path, this.baseName + ".sym"),
66 "utf8"
67 );
68 const lines = symsStr.split("\n");
69 for (let i=0; i<lines.length; i++) {
70 const arr = lines[i].split(",");
71 if (arr.length!=4) continue;
72 this.symbols[arr[3]] = {
73 labelIdx: Number(arr[0]),
74 varIdx: Number(arr[1]),
75 componentIdx: Number(arr[2]),
76 };
77 }
78 }
79
80 async loadConstraints() {
81 const self = this;
82 if (this.constraints) return;
83 const r1cs = await loadR1cs(path.join(this.dir.path, this.baseName + ".r1cs"),true, false);
84 self.F = new ZqField(r1cs.prime);
85 self.nVars = r1cs.nVars;
86 self.constraints = r1cs.constraints;
87 }
88
89 async assertOut(actualOut, expectedOut) {
90 const self = this;
91 if (!self.symbols) await self.loadSymbols();
92
93 checkObject("main", expectedOut);
94
95 function checkObject(prefix, eOut) {
96
97 if (Array.isArray(eOut)) {
98 for (let i=0; i<eOut.length; i++) {
99 checkObject(prefix + "["+i+"]", eOut[i]);
100 }
101 } else if ((typeof eOut == "object")&&(eOut.constructor.name == "Object")) {
102 for (let k in eOut) {
103 checkObject(prefix + "."+k, eOut[k]);
104 }
105 } else {
106 if (typeof self.symbols[prefix] == "undefined") {
107 assert(false, "Output variable not defined: "+ prefix);
108 }
109 const ba = actualOut[self.symbols[prefix].varIdx].toString();
110 const be = eOut.toString();
111 assert.strictEqual(ba, be, prefix);
112 }
113 }
114 }
115
116 async getDecoratedOutput(witness) {
117 const self = this;
118 const lines = [];
119 if (!self.symbols) await self.loadSymbols();
120 for (let n in self.symbols) {
121 let v;
122 if (utils.isDefined(witness[self.symbols[n].varIdx])) {
123 v = witness[self.symbols[n].varIdx].toString();
124 } else {
125 v = "undefined";
126 }
127 lines.push(`${n} --> ${v}`);
128 }
129 return lines.join("\n");
130 }
131
132 async checkConstraints(witness) {
133 const self = this;
134 if (!self.constraints) await self.loadConstraints();
135 for (let i=0; i<self.constraints.length; i++) {
136 checkConstraint(self.constraints[i]);
137 }
138
139 function checkConstraint(constraint) {
140 const F = self.F;
141 const a = evalLC(constraint[0]);
142 const b = evalLC(constraint[1]);
143 const c = evalLC(constraint[2]);
144
145 assert (F.isZero(F.sub(F.mul(a,b), c)), "Constraint doesn't match");
146 }
147
148 function evalLC(lc) {
149 const F = self.F;
150 let v = F.zero;
151 for (let w in lc) {
152 v = F.add(
153 v,
154 F.mul( lc[w], witness[w] )
155 );
156 }
157 return v;
158 }
159 }
160
161}
162
163