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.

316 lines
11 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 connect Metamask by clicking on the button at the top of the page");
  45. return;
  46. }
  47. if (window.ethereum.networkVersion!='5') {
  48. toastr.warning("Please switch to Göerli");
  49. alert("Please switch to Göerli");
  50. return;
  51. }
  52. document.getElementById("depositRes").innerHTML = `
  53. Please wait. Generating the zk-proof and making the deposit...
  54. `;
  55. console.log("circuit:", circuitname);
  56. // TODO
  57. println("generate random secret & nullifier");
  58. const secret = miksi.randBigInt().toString();
  59. const nullifier = miksi.randBigInt().toString();
  60. console.log("S N", secret, nullifier);
  61. println("get commitments from the miksi Smart Contract");
  62. let res = await miksiContract.methods.getCommitments().call();
  63. console.log("res", res);
  64. const commitments = res[0];
  65. const key = res[2];
  66. console.log("commitments", commitments);
  67. console.log("key", key);
  68. // getCommitments from the tree
  69. // calculate witness
  70. println("rebuild the Merkle Tree & calculate witness for deposit");
  71. console.log(witnessCalc[circuitname]);
  72. const cw = await miksi.calcDepositWitness(witnessCalc[circuitname], secret, nullifier, commitments, key).catch((e) => {
  73. toastr.error(e);
  74. printerr(e);
  75. });
  76. const witness = cw.witness;
  77. const publicInputs = cw.publicInputs;
  78. console.log("w", witness);
  79. console.log("publicInputs", publicInputs);
  80. // generate proof
  81. console.log(provingKey[circuitname]);
  82. println("generate zkSNARK Groth16 proof for deposit");
  83. const start = new Date().getTime();
  84. const proof = await window.groth16GenProof(witness.buffer, provingKey[circuitname]);
  85. const end = new Date().getTime();
  86. const time = end - start;
  87. println("circuit " + circuitname + " took " + time + "ms to compute");
  88. console.log(proof);
  89. console.log("proof", JSON.stringify(proof));
  90. // send tx
  91. const accounts = await web3.eth.getAccounts();
  92. const sender = accounts[0];
  93. console.log("SENDER", sender);
  94. console.log("sc call data",
  95. publicInputs.commitment,
  96. publicInputs.root.toString(),
  97. [proof.pi_a[0], proof.pi_a[1]],
  98. [
  99. [proof.pi_b[0][1], proof.pi_b[0][0]],
  100. [proof.pi_b[1][1], proof.pi_b[1][0]]
  101. ],
  102. [proof.pi_c[0], proof.pi_c[1]],
  103. );
  104. println("send publicInputs & zkProof to the miksi Smart Contract for the deposit");
  105. miksiContract.methods.deposit(
  106. publicInputs.commitment,
  107. publicInputs.root.toString(),
  108. [proof.pi_a[0], proof.pi_a[1]],
  109. [
  110. [proof.pi_b[0][1], proof.pi_b[0][0]],
  111. [proof.pi_b[1][1], proof.pi_b[1][0]]
  112. ],
  113. [proof.pi_c[0], proof.pi_c[1]],
  114. ).send(
  115. {from: sender, value: 1000000000000000000},
  116. function(error, transactionHash){
  117. if (error!=undefined) {
  118. console.log(error);
  119. toastr.error(error);
  120. printerr(JSON.stringify(error));
  121. } else {
  122. let link = `<a href="https://goerli.etherscan.io/tx/`+transactionHash+`" target="_blank">
  123. https://goerli.etherscan.io/tx/`+transactionHash+`</a>`;
  124. println(link);
  125. }
  126. });
  127. // print secret & nullifier
  128. let jw = {
  129. secret: secret,
  130. nullifier: nullifier,
  131. key: key
  132. };
  133. console.log("jw", JSON.stringify(jw));
  134. document.getElementById("depositRes").innerHTML = `
  135. </br>
  136. <input class="form-control" onClick="this.select();" readonly value='`+JSON.stringify(jw)+`'>
  137. </input>
  138. </br>
  139. <b>Please store the data above in a safe place (anyone who has it will be able to withdraw your deposited ETH from the contract).</b>
  140. `;
  141. }
  142. async function withdraw(circuitname) {
  143. if (!metamask) {
  144. toastr.error("Please connect Metamask by clicking on the button at the top of the page");
  145. return;
  146. }
  147. if (window.ethereum.networkVersion!='5') {
  148. toastr.warning("Please switch to Göerli");
  149. alert("Please switch to Göerli");
  150. return;
  151. }
  152. document.getElementById("withdrawRes").innerHTML = `
  153. Please wait. Generating the zk-proof and making the withdrawal...
  154. `;
  155. console.log("circuit:", circuitname);
  156. let jw;
  157. try {
  158. jw = JSON.parse(document.getElementById("jsonWithdraw").value);
  159. } catch(e) {
  160. toastr.error("Error reading secret & nullifier: " + e);
  161. }
  162. const secret = jw.secret;
  163. const nullifier = jw.nullifier;
  164. const key = jw.key;
  165. console.log(secret, nullifier);
  166. println("calculate commitment for the secret & nullifier");
  167. const commitment = miksi.calcCommitment(secret, nullifier);
  168. // getCommitments from the tree
  169. println("get commitments from the miksi Smart Contract");
  170. let res = await miksiContract.methods.getCommitments().call();
  171. console.log("res", res);
  172. const commitments = res[0];
  173. console.log("commitments", commitments);
  174. // calculate witness
  175. console.log(witnessCalc[circuitname]);
  176. const addr = document.getElementById("withdrawAddress").value;
  177. if (addr==undefined) {
  178. toastr.error("No withdraw address defined");
  179. return;
  180. }
  181. if (!web3.utils.isAddress(addr)) {
  182. toastr.error("Error with withdraw address");
  183. return;
  184. }
  185. println("rebuild the Merkle Tree & calculate witness for withdraw");
  186. const cw = await miksi.calcWithdrawWitness(witnessCalc[circuitname], secret, nullifier, commitments, addr, key).catch((e) => {
  187. toastr.error(e);
  188. printerr(e);
  189. });
  190. const witness = cw.witness;
  191. const publicInputs = cw.publicInputs;
  192. console.log("w", witness);
  193. console.log("publicInputs", publicInputs);
  194. // generate proof
  195. console.log(provingKey[circuitname]);
  196. println("generate zkSNARK Groth16 proof for withdraw");
  197. const start = new Date().getTime();
  198. const proof = await window.groth16GenProof(witness.buffer, provingKey[circuitname]);
  199. const end = new Date().getTime();
  200. const time = end - start;
  201. println("circuit " + circuitname + " took " + time + "ms to compute");
  202. console.log(proof);
  203. // send tx
  204. const accounts = await web3.eth.getAccounts();
  205. const sender = accounts[0];
  206. console.log("SENDER", sender);
  207. console.log("sc call data",
  208. publicInputs.address,
  209. publicInputs.nullifier,
  210. [proof.pi_a[0], proof.pi_a[1]],
  211. [
  212. [proof.pi_b[0][1], proof.pi_b[0][0]],
  213. [proof.pi_b[1][1], proof.pi_b[1][0]]
  214. ],
  215. [proof.pi_c[0], proof.pi_c[1]],
  216. );
  217. println("send publicInputs & zkProof to the miksi Smart Contract for the withdraw");
  218. miksiContract.methods.withdraw(
  219. publicInputs.address,
  220. publicInputs.nullifier,
  221. [proof.pi_a[0], proof.pi_a[1]],
  222. [
  223. [proof.pi_b[0][1], proof.pi_b[0][0]],
  224. [proof.pi_b[1][1], proof.pi_b[1][0]]
  225. ],
  226. [proof.pi_c[0], proof.pi_c[1]],
  227. ).send(
  228. {from: sender},
  229. function(error, transactionHash){
  230. if (error!=undefined) {
  231. console.log(error);
  232. toastr.error(error);
  233. printerr(JSON.stringify(error));
  234. } else {
  235. let link = `<a href="https://goerli.etherscan.io/tx/`+transactionHash+`" target="_blank">
  236. https://goerli.etherscan.io/tx/`+transactionHash+`</a>`;
  237. println(link);
  238. document.getElementById("withdrawRes").innerHTML = 'Success :)'
  239. }
  240. });
  241. }
  242. loadCircuit("deposit");
  243. loadCircuit("withdraw");
  244. let miksiContract;
  245. async function connectMetamask() {
  246. const ethEnabled = () => {
  247. if (window.web3) {
  248. window.web3 = new Web3(window.web3.currentProvider);
  249. window.ethereum.enable();
  250. return true;
  251. }
  252. return false;
  253. }
  254. if (!ethEnabled()) {
  255. toastr.warning("Please install Metamask to use miksi");
  256. alert("Please install MetaMask to use miksi");
  257. return;
  258. } else if (window.ethereum.networkVersion!='5') {
  259. toastr.warning("Please switch to Göerli");
  260. alert("Please switch to Göerli");
  261. return;
  262. } else {
  263. metamask = true;
  264. }
  265. console.log("abi", abi);
  266. miksiContract = new web3.eth.Contract(abi, miksiAddress);
  267. console.log("miksiContract", miksiContract);
  268. toastr.info("Metamask connected. Miksi contract: " + miksiAddress);
  269. println("Metamask connected. Miksi contract: ", miksiAddress);
  270. const acc = await web3.eth.getAccounts();
  271. const addr = acc[0];
  272. web3.eth.getBalance(addr, function(err, res){
  273. console.log("current address balance:", JSON.stringify(res));
  274. });
  275. const miksiBalance = await web3.eth.getBalance(miksiAddress);
  276. let html = "<b>miksi</b> Smart Contract current balance: " + miksiBalance/1000000000000000000 + " ETH<br>";
  277. let res = await miksiContract.methods.getCommitments().call();
  278. const commitments = res[0];
  279. const key = res[2];
  280. html += "number of commitments: " + commitments.length + "<br>";
  281. html += "current key: " + key + "<br>";
  282. document.getElementById("stats").innerHTML = html;
  283. }