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