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