You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

337 lines
9.0 KiB

  1. module.exports = async function builder(code, options) {
  2. options = options || {};
  3. let wasmModule;
  4. try {
  5. wasmModule = await WebAssembly.compile(code);
  6. } catch (err) {
  7. console.log(err);
  8. console.log("\nTry to run circom --c in order to generate c++ code instead\n");
  9. throw new Error(err);
  10. }
  11. let wc;
  12. let errStr = "";
  13. let msgStr = "";
  14. const instance = await WebAssembly.instantiate(wasmModule, {
  15. runtime: {
  16. exceptionHandler : function(code) {
  17. let err;
  18. if (code == 1) {
  19. err = "Signal not found.\n";
  20. } else if (code == 2) {
  21. err = "Too many signals set.\n";
  22. } else if (code == 3) {
  23. err = "Signal already set.\n";
  24. } else if (code == 4) {
  25. err = "Assert Failed.\n";
  26. } else if (code == 5) {
  27. err = "Not enough memory.\n";
  28. } else if (code == 6) {
  29. err = "Input signal array access exceeds the size.\n";
  30. } else {
  31. err = "Unknown error.\n";
  32. }
  33. throw new Error(err + errStr);
  34. },
  35. printErrorMessage : function() {
  36. errStr += getMessage() + "\n";
  37. // console.error(getMessage());
  38. },
  39. writeBufferMessage : function() {
  40. const msg = getMessage();
  41. // Any calls to `log()` will always end with a `\n`, so that's when we print and reset
  42. if (msg === "\n") {
  43. console.log(msgStr);
  44. msgStr = "";
  45. } else {
  46. // If we've buffered other content, put a space in between the items
  47. if (msgStr !== "") {
  48. msgStr += " "
  49. }
  50. // Then append the message to the message we are creating
  51. msgStr += msg;
  52. }
  53. },
  54. showSharedRWMemory : function() {
  55. printSharedRWMemory ();
  56. }
  57. }
  58. });
  59. const sanityCheck =
  60. options
  61. // options &&
  62. // (
  63. // options.sanityCheck ||
  64. // options.logGetSignal ||
  65. // options.logSetSignal ||
  66. // options.logStartComponent ||
  67. // options.logFinishComponent
  68. // );
  69. wc = new WitnessCalculator(instance, sanityCheck);
  70. return wc;
  71. function getMessage() {
  72. var message = "";
  73. var c = instance.exports.getMessageChar();
  74. while ( c != 0 ) {
  75. message += String.fromCharCode(c);
  76. c = instance.exports.getMessageChar();
  77. }
  78. return message;
  79. }
  80. function printSharedRWMemory () {
  81. const shared_rw_memory_size = instance.exports.getFieldNumLen32();
  82. const arr = new Uint32Array(shared_rw_memory_size);
  83. for (let j=0; j<shared_rw_memory_size; j++) {
  84. arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
  85. }
  86. // If we've buffered other content, put a space in between the items
  87. if (msgStr !== "") {
  88. msgStr += " "
  89. }
  90. // Then append the value to the message we are creating
  91. msgStr += (fromArray32(arr).toString());
  92. }
  93. };
  94. class WitnessCalculator {
  95. constructor(instance, sanityCheck) {
  96. this.instance = instance;
  97. this.version = this.instance.exports.getVersion();
  98. this.n32 = this.instance.exports.getFieldNumLen32();
  99. this.instance.exports.getRawPrime();
  100. const arr = new Uint32Array(this.n32);
  101. for (let i=0; i<this.n32; i++) {
  102. arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
  103. }
  104. this.prime = fromArray32(arr);
  105. this.witnessSize = this.instance.exports.getWitnessSize();
  106. this.sanityCheck = sanityCheck;
  107. }
  108. circom_version() {
  109. return this.instance.exports.getVersion();
  110. }
  111. async _doCalculateWitness(input, sanityCheck) {
  112. //input is assumed to be a map from signals to arrays of bigints
  113. this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
  114. const keys = Object.keys(input);
  115. var input_counter = 0;
  116. keys.forEach( (k) => {
  117. const h = fnvHash(k);
  118. const hMSB = parseInt(h.slice(0,8), 16);
  119. const hLSB = parseInt(h.slice(8,16), 16);
  120. const fArr = flatArray(input[k]);
  121. let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
  122. if (signalSize < 0){
  123. throw new Error(`Signal ${k} not found\n`);
  124. }
  125. if (fArr.length < signalSize) {
  126. throw new Error(`Not enough values for input signal ${k}\n`);
  127. }
  128. if (fArr.length > signalSize) {
  129. throw new Error(`Too many values for input signal ${k}\n`);
  130. }
  131. for (let i=0; i<fArr.length; i++) {
  132. const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32)
  133. for (let j=0; j<this.n32; j++) {
  134. this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
  135. }
  136. try {
  137. this.instance.exports.setInputSignal(hMSB, hLSB,i);
  138. input_counter++;
  139. } catch (err) {
  140. // console.log(`After adding signal ${i} of ${k}`)
  141. throw new Error(err);
  142. }
  143. }
  144. });
  145. if (input_counter < this.instance.exports.getInputSize()) {
  146. throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
  147. }
  148. }
  149. async calculateWitness(input, sanityCheck) {
  150. const w = [];
  151. await this._doCalculateWitness(input, sanityCheck);
  152. for (let i=0; i<this.witnessSize; i++) {
  153. this.instance.exports.getWitness(i);
  154. const arr = new Uint32Array(this.n32);
  155. for (let j=0; j<this.n32; j++) {
  156. arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
  157. }
  158. w.push(fromArray32(arr));
  159. }
  160. return w;
  161. }
  162. async calculateBinWitness(input, sanityCheck) {
  163. const buff32 = new Uint32Array(this.witnessSize*this.n32);
  164. const buff = new Uint8Array( buff32.buffer);
  165. await this._doCalculateWitness(input, sanityCheck);
  166. for (let i=0; i<this.witnessSize; i++) {
  167. this.instance.exports.getWitness(i);
  168. const pos = i*this.n32;
  169. for (let j=0; j<this.n32; j++) {
  170. buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
  171. }
  172. }
  173. return buff;
  174. }
  175. async calculateWTNSBin(input, sanityCheck) {
  176. const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
  177. const buff = new Uint8Array( buff32.buffer);
  178. await this._doCalculateWitness(input, sanityCheck);
  179. //"wtns"
  180. buff[0] = "w".charCodeAt(0)
  181. buff[1] = "t".charCodeAt(0)
  182. buff[2] = "n".charCodeAt(0)
  183. buff[3] = "s".charCodeAt(0)
  184. //version 2
  185. buff32[1] = 2;
  186. //number of sections: 2
  187. buff32[2] = 2;
  188. //id section 1
  189. buff32[3] = 1;
  190. const n8 = this.n32*4;
  191. //id section 1 length in 64bytes
  192. const idSection1length = 8 + n8;
  193. const idSection1lengthHex = idSection1length.toString(16);
  194. buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
  195. buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
  196. //this.n32
  197. buff32[6] = n8;
  198. //prime number
  199. this.instance.exports.getRawPrime();
  200. var pos = 7;
  201. for (let j=0; j<this.n32; j++) {
  202. buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
  203. }
  204. pos += this.n32;
  205. // witness size
  206. buff32[pos] = this.witnessSize;
  207. pos++;
  208. //id section 2
  209. buff32[pos] = 2;
  210. pos++;
  211. // section 2 length
  212. const idSection2length = n8*this.witnessSize;
  213. const idSection2lengthHex = idSection2length.toString(16);
  214. buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
  215. buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
  216. pos += 2;
  217. for (let i=0; i<this.witnessSize; i++) {
  218. this.instance.exports.getWitness(i);
  219. for (let j=0; j<this.n32; j++) {
  220. buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
  221. }
  222. pos += this.n32;
  223. }
  224. return buff;
  225. }
  226. }
  227. function toArray32(rem,size) {
  228. const res = []; //new Uint32Array(size); //has no unshift
  229. const radix = BigInt(0x100000000);
  230. while (rem) {
  231. res.unshift( Number(rem % radix));
  232. rem = rem / radix;
  233. }
  234. if (size) {
  235. var i = size - res.length;
  236. while (i>0) {
  237. res.unshift(0);
  238. i--;
  239. }
  240. }
  241. return res;
  242. }
  243. function fromArray32(arr) { //returns a BigInt
  244. var res = BigInt(0);
  245. const radix = BigInt(0x100000000);
  246. for (let i = 0; i<arr.length; i++) {
  247. res = res*radix + BigInt(arr[i]);
  248. }
  249. return res;
  250. }
  251. function flatArray(a) {
  252. var res = [];
  253. fillArray(res, a);
  254. return res;
  255. function fillArray(res, a) {
  256. if (Array.isArray(a)) {
  257. for (let i=0; i<a.length; i++) {
  258. fillArray(res, a[i]);
  259. }
  260. } else {
  261. res.push(a);
  262. }
  263. }
  264. }
  265. function normalize(n, prime) {
  266. let res = BigInt(n) % prime
  267. if (res < 0) res += prime
  268. return res
  269. }
  270. function fnvHash(str) {
  271. const uint64_max = BigInt(2) ** BigInt(64);
  272. let hash = BigInt("0xCBF29CE484222325");
  273. for (var i = 0; i < str.length; i++) {
  274. hash ^= BigInt(str[i].charCodeAt());
  275. hash *= BigInt(0x100000001B3);
  276. hash %= uint64_max;
  277. }
  278. let shash = hash.toString(16);
  279. let n = 16 - shash.length;
  280. shash = '0'.repeat(n).concat(shash);
  281. return shash;
  282. }