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.

297 lines
10 KiB

  1. var circuit = {};
  2. var provingKey = {};
  3. var witnessCalc = {};
  4. const abi = JSON.parse(`[{"inputs":[{"internalType":"address","name":"_depositVerifierContractAddr","type":"address"},{"internalType":"address","name":"_withdrawVerifierContractAddr","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"_commitment","type":"uint256"},{"internalType":"uint256","name":"_root","type":"uint256"},{"internalType":"uint256[2]","name":"a","type":"uint256[2]"},{"internalType":"uint256[2][2]","name":"b","type":"uint256[2][2]"},{"internalType":"uint256[2]","name":"c","type":"uint256[2]"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getCommitments","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_address","type":"address"},{"internalType":"uint256","name":"nullifier","type":"uint256"},{"internalType":"uint256[2]","name":"a","type":"uint256[2]"},{"internalType":"uint256[2][2]","name":"b","type":"uint256[2][2]"},{"internalType":"uint256[2]","name":"c","type":"uint256[2]"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]`);
  5. const miksiAddress = "0x4cc45573481A2977fcC0b9DD9f8c710201B5a5cd";
  6. let metamask = false;
  7. document.getElementById("contractAddr").innerHTML=`<a href="https://goerli.etherscan.io/address/`+miksiAddress+`" target="_blank" title="Miksi Smart Contract Address">`+miksiAddress+`</a>`;
  8. function println(...s) {
  9. let r = "";
  10. for (let i=0; i<s.length; i++) {
  11. r = r + " " + s[i];
  12. }
  13. console.log(r);
  14. document.getElementById("logs").innerHTML += r + "<br>";
  15. document.getElementById("logs").scrollTop = document.getElementById("logs").scrollHeight;
  16. }
  17. function printerr(...s) {
  18. // println(s);
  19. let r = "";
  20. for (let i=0; i<s.length; i++) {
  21. r = r + " " + s[i];
  22. }
  23. console.log(r);
  24. document.getElementById("logs").innerHTML += `<span style="color:red;">Error: ` + r + `</span><br>`;
  25. document.getElementById("logs").scrollTop = document.getElementById("logs").scrollHeight;
  26. }
  27. function loadCircuit(circuitname) {
  28. fetch("circuits-files/"+circuitname+"-proving_key.bin").then( (response) => {
  29. return response.arrayBuffer();
  30. }).then( (b) => {
  31. provingKey[circuitname] = b;
  32. println("proving_key loaded for", circuitname);
  33. });
  34. fetch("circuits-files/"+circuitname+".wasm").then( (response) => {
  35. return response.arrayBuffer();
  36. }).then( (b) => {
  37. witnessCalc[circuitname] = b;
  38. console.log("w", b);
  39. println("witnessCalc loaded for", circuitname);
  40. });
  41. }
  42. async function deposit(circuitname) {
  43. if (!metamask) {
  44. toastr.error("Please install/connect Metamask");
  45. return;
  46. }
  47. document.getElementById("depositRes").innerHTML = `
  48. Generating zkProof & making the deposit
  49. `;
  50. console.log("circuit:", circuitname);
  51. // TODO
  52. println("generate random secret & nullifier");
  53. const secret = miksi.randBigInt().toString();
  54. const nullifier = miksi.randBigInt().toString();
  55. console.log("S N", secret, nullifier);
  56. println("get commitments from the miksi Smart Contract");
  57. let res = await miksiContract.methods.getCommitments().call();
  58. console.log("res", res);
  59. const commitments = res[0];
  60. const key = res[2];
  61. console.log("commitments", commitments);
  62. console.log("key", key);
  63. // getCommitments from the tree
  64. // calculate witness
  65. println("rebuild the Merkle Tree & calculate witness for deposit");
  66. console.log(witnessCalc[circuitname]);
  67. const cw = await miksi.calcDepositWitness(witnessCalc[circuitname], secret, nullifier, commitments, key).catch((e) => {
  68. toastr.error(e);
  69. printerr(e);
  70. });
  71. const witness = cw.witness;
  72. const publicInputs = cw.publicInputs;
  73. console.log("w", witness);
  74. console.log("publicInputs", publicInputs);
  75. // generate proof
  76. console.log(provingKey[circuitname]);
  77. println("generate zkSNARK Groth16 proof for deposit");
  78. const start = new Date().getTime();
  79. const proof = await window.groth16GenProof(witness.buffer, provingKey[circuitname]);
  80. const end = new Date().getTime();
  81. const time = end - start;
  82. println("circuit " + circuitname + " took " + time + "ms to compute");
  83. console.log(proof);
  84. // send tx
  85. const accounts = await web3.eth.getAccounts();
  86. const sender = accounts[0];
  87. console.log("SENDER", sender);
  88. console.log("sc call data",
  89. publicInputs.commitment,
  90. publicInputs.root.toString(),
  91. [proof.pi_a[0], proof.pi_a[1]],
  92. [
  93. [proof.pi_b[0][1], proof.pi_b[0][0]],
  94. [proof.pi_b[1][1], proof.pi_b[1][0]]
  95. ],
  96. [proof.pi_c[0], proof.pi_c[1]],
  97. );
  98. println("send publicInputs & zkProof to the miksi Smart Contract for the deposit");
  99. miksiContract.methods.deposit(
  100. publicInputs.commitment,
  101. publicInputs.root.toString(),
  102. [proof.pi_a[0], proof.pi_a[1]],
  103. [
  104. [proof.pi_b[0][1], proof.pi_b[0][0]],
  105. [proof.pi_b[1][1], proof.pi_b[1][0]]
  106. ],
  107. [proof.pi_c[0], proof.pi_c[1]],
  108. ).send(
  109. {from: sender, value: 1000000000000000000},
  110. function(error, transactionHash){
  111. if (error!=undefined) {
  112. console.log(error);
  113. toastr.error(error);
  114. printerr(JSON.stringify(error));
  115. } else {
  116. let link = `<a href="https://goerli.etherscan.io/tx/`+transactionHash+`" target="_blank">
  117. https://goerli.etherscan.io/tx/`+transactionHash+`</a>`;
  118. println(link);
  119. }
  120. });
  121. // print secret & nullifier
  122. let jw = {
  123. secret: secret,
  124. nullifier: nullifier,
  125. key: key
  126. };
  127. console.log("jw", JSON.stringify(jw));
  128. document.getElementById("depositRes").innerHTML = `
  129. <b>Please store the secret data in a safe place:</b><br>
  130. <input class="form-control" onClick="this.select();" readonly value='`+JSON.stringify(jw)+`'>
  131. </input>
  132. `;
  133. }
  134. async function withdraw(circuitname) {
  135. if (!metamask) {
  136. toastr.error("Please install/connect Metamask");
  137. return;
  138. }
  139. document.getElementById("withdrawRes").innerHTML = `
  140. Generating zkProof & making the withdraw
  141. `;
  142. console.log("circuit:", circuitname);
  143. let jw;
  144. try {
  145. jw = JSON.parse(document.getElementById("jsonWithdraw").value);
  146. } catch(e) {
  147. toastr.error("Error reading secret & nullifier: " + e);
  148. }
  149. const secret = jw.secret;
  150. const nullifier = jw.nullifier;
  151. const key = jw.key;
  152. console.log(secret, nullifier);
  153. println("calculate commitment for the secret & nullifier");
  154. const commitment = miksi.calcCommitment(secret, nullifier);
  155. // getCommitments from the tree
  156. println("get commitments from the miksi Smart Contract");
  157. let res = await miksiContract.methods.getCommitments().call();
  158. console.log("res", res);
  159. const commitments = res[0];
  160. console.log("commitments", commitments);
  161. // calculate witness
  162. console.log(witnessCalc[circuitname]);
  163. const addr = document.getElementById("withdrawAddress").value;
  164. if (addr==undefined) {
  165. toastr.error("No withdraw address defined");
  166. return;
  167. }
  168. if (!web3.utils.isAddress(addr)) {
  169. toastr.error("Error with withdraw address");
  170. return;
  171. }
  172. println("rebuild the Merkle Tree & calculate witness for withdraw");
  173. const cw = await miksi.calcWithdrawWitness(witnessCalc[circuitname], secret, nullifier, commitments, addr, key).catch((e) => {
  174. toastr.error(e);
  175. printerr(e);
  176. });
  177. const witness = cw.witness;
  178. const publicInputs = cw.publicInputs;
  179. console.log("w", witness);
  180. console.log("publicInputs", publicInputs);
  181. // generate proof
  182. console.log(provingKey[circuitname]);
  183. println("generate zkSNARK Groth16 proof for withdraw");
  184. const start = new Date().getTime();
  185. const proof = await window.groth16GenProof(witness.buffer, provingKey[circuitname]);
  186. const end = new Date().getTime();
  187. const time = end - start;
  188. println("circuit " + circuitname + " took " + time + "ms to compute");
  189. console.log(proof);
  190. // send tx
  191. const accounts = await web3.eth.getAccounts();
  192. const sender = accounts[0];
  193. console.log("SENDER", sender);
  194. console.log("sc call data",
  195. publicInputs.address,
  196. publicInputs.nullifier,
  197. [proof.pi_a[0], proof.pi_a[1]],
  198. [
  199. [proof.pi_b[0][1], proof.pi_b[0][0]],
  200. [proof.pi_b[1][1], proof.pi_b[1][0]]
  201. ],
  202. [proof.pi_c[0], proof.pi_c[1]],
  203. );
  204. println("send publicInputs & zkProof to the miksi Smart Contract for the withdraw");
  205. miksiContract.methods.withdraw(
  206. publicInputs.address,
  207. publicInputs.nullifier,
  208. [proof.pi_a[0], proof.pi_a[1]],
  209. [
  210. [proof.pi_b[0][1], proof.pi_b[0][0]],
  211. [proof.pi_b[1][1], proof.pi_b[1][0]]
  212. ],
  213. [proof.pi_c[0], proof.pi_c[1]],
  214. ).send(
  215. {from: sender},
  216. function(error, transactionHash){
  217. if (error!=undefined) {
  218. console.log(error);
  219. toastr.error(error);
  220. printerr(JSON.stringify(error));
  221. } else {
  222. let link = `<a href="https://goerli.etherscan.io/tx/`+transactionHash+`" target="_blank">
  223. https://goerli.etherscan.io/tx/`+transactionHash+`</a>`;
  224. println(link);
  225. }
  226. });
  227. }
  228. loadCircuit("deposit");
  229. loadCircuit("withdraw");
  230. let miksiContract;
  231. async function connectMetamask() {
  232. const ethEnabled = () => {
  233. if (window.web3) {
  234. window.web3 = new Web3(window.web3.currentProvider);
  235. window.ethereum.enable();
  236. return true;
  237. }
  238. return false;
  239. }
  240. if (!ethEnabled()) {
  241. toastr.warning("Please install Metamask to use miksi");
  242. alert("Please install MetaMask to use miksi");
  243. } else {
  244. metamask = true;
  245. }
  246. console.log("abi", abi);
  247. miksiContract = new web3.eth.Contract(abi, miksiAddress);
  248. console.log("miksiContract", miksiContract);
  249. toastr.info("Metamask connected. Miksi contract: " + miksiAddress);
  250. println("Metamask connected. Miksi contract: ", miksiAddress);
  251. const acc = await web3.eth.getAccounts();
  252. const addr = acc[0];
  253. web3.eth.getBalance(addr, function(err, res){
  254. console.log("current address balance:", JSON.stringify(res));
  255. });
  256. const miksiBalance = await web3.eth.getBalance(miksiAddress);
  257. let html = "<b>miksi</b> Smart Contract current balance: " + miksiBalance/1000000000000000000 + " ETH<br>";
  258. let res = await miksiContract.methods.getCommitments().call();
  259. const commitments = res[0];
  260. const key = res[2];
  261. html += "number of commitments: " + commitments.length + "<br>";
  262. html += "current key: " + key + "<br>";
  263. document.getElementById("stats").innerHTML = html;
  264. }