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 connect Metamask by clicking on the button at the top of the page");
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 Please wait. Generating the zk-proof and 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 </br>
150 <input class="form-control" onClick="this.select();" readonly value='`+JSON.stringify(jw)+`'>
151 </input>
152</br>
153<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>
154 `;
155}
156
157async function withdraw(circuitname) {
158 if (!metamask) {
159 toastr.error("Please connect Metamask by clicking on the button at the top of the page");
160 return;
161 }
162 if (window.ethereum.networkVersion!='5') {
163 toastr.warning("Please switch to Göerli");
164 alert("Please switch to Göerli");
165 return;
166 }
167 document.getElementById("withdrawRes").innerHTML = `
168 Please wait. Generating the zk-proof and making the withdrawal...
169 `;
170 console.log("circuit:", circuitname);
171 let jw;
172 try {
173 jw = JSON.parse(document.getElementById("jsonWithdraw").value);
174 } catch(e) {
175 toastr.error("Error reading secret & nullifier: " + e);
176 }
177 const secret = jw.secret;
178 const nullifier = jw.nullifier;
179 const key = jw.key;
180 console.log(secret, nullifier);
181 println("calculate commitment for the secret & nullifier");
182 const commitment = miksi.calcCommitment(secret, nullifier);
183
184 // getCommitments from the tree
185 println("get commitments from the miksi Smart Contract");
186 let res = await miksiContract.methods.getCommitments().call();
187 console.log("res", res);
188 const commitments = res[0];
189 console.log("commitments", commitments);
190
191 // calculate witness
192 console.log(witnessCalc[circuitname]);
193 const addr = document.getElementById("withdrawAddress").value;
194 if (addr==undefined) {
195 toastr.error("No withdraw address defined");
196 return;
197 }
198 if (!web3.utils.isAddress(addr)) {
199 toastr.error("Error with withdraw address");
200 return;
201 }
202 println("rebuild the Merkle Tree & calculate witness for withdraw");
203 const cw = await miksi.calcWithdrawWitness(witnessCalc[circuitname], secret, nullifier, commitments, addr, key).catch((e) => {
204 toastr.error(e);
205 printerr(e);
206 });
207
208 const witness = cw.witness;
209 const publicInputs = cw.publicInputs;
210 console.log("w", witness);
211 console.log("publicInputs", publicInputs);
212
213 // generate proof
214 console.log(provingKey[circuitname]);
215 println("generate zkSNARK Groth16 proof for withdraw");
216 const start = new Date().getTime();
217 const proof = await window.groth16GenProof(witness.buffer, provingKey[circuitname]);
218 const end = new Date().getTime();
219 const time = end - start;
220 println("circuit " + circuitname + " took " + time + "ms to compute");
221 console.log(proof);
222
223
224 // send tx
225 const accounts = await web3.eth.getAccounts();
226 const sender = accounts[0];
227 console.log("SENDER", sender);
228
229 console.log("sc call data",
230 publicInputs.address,
231 publicInputs.nullifier,
232 [proof.pi_a[0], proof.pi_a[1]],
233 [
234 [proof.pi_b[0][1], proof.pi_b[0][0]],
235 [proof.pi_b[1][1], proof.pi_b[1][0]]
236 ],
237 [proof.pi_c[0], proof.pi_c[1]],
238 );
239 println("send publicInputs & zkProof to the miksi Smart Contract for the withdraw");
240 miksiContract.methods.withdraw(
241 publicInputs.address,
242 publicInputs.nullifier,
243 [proof.pi_a[0], proof.pi_a[1]],
244 [
245 [proof.pi_b[0][1], proof.pi_b[0][0]],
246 [proof.pi_b[1][1], proof.pi_b[1][0]]
247 ],
248 [proof.pi_c[0], proof.pi_c[1]],
249 ).send(
250 {from: sender},
251 function(error, transactionHash){
252 if (error!=undefined) {
253 console.log(error);
254 toastr.error(error);
255 printerr(JSON.stringify(error));
256 } else {
257 let link = `<a href="https://goerli.etherscan.io/tx/`+transactionHash+`" target="_blank">
258 https://goerli.etherscan.io/tx/`+transactionHash+`</a>`;
259 println(link);
260 document.getElementById("withdrawRes").innerHTML = 'Success :)'
261 }
262 });
263}
264
265loadCircuit("deposit");
266loadCircuit("withdraw");
267
268
269
270
271let miksiContract;
272
273async function connectMetamask() {
274 const ethEnabled = () => {
275 if (window.web3) {
276 window.web3 = new Web3(window.web3.currentProvider);
277 window.ethereum.enable();
278 return true;
279 }
280 return false;
281 }
282
283 if (!ethEnabled()) {
284 toastr.warning("Please install Metamask to use miksi");
285 alert("Please install MetaMask to use miksi");
286 return;
287 } else if (window.ethereum.networkVersion!='5') {
288 toastr.warning("Please switch to Göerli");
289 alert("Please switch to Göerli");
290 return;
291 } else {
292 metamask = true;
293 }
294
295 console.log("abi", abi);
296 miksiContract = new web3.eth.Contract(abi, miksiAddress);
297 console.log("miksiContract", miksiContract);
298 toastr.info("Metamask connected. Miksi contract: " + miksiAddress);
299 println("Metamask connected. Miksi contract: ", miksiAddress);
300
301 const acc = await web3.eth.getAccounts();
302 const addr = acc[0];
303 web3.eth.getBalance(addr, function(err, res){
304 console.log("current address balance:", JSON.stringify(res));
305 });
306 const miksiBalance = await web3.eth.getBalance(miksiAddress);
307
308 let html = "<b>miksi</b> Smart Contract current balance: " + miksiBalance/1000000000000000000 + " ETH<br>";
309 let res = await miksiContract.methods.getCommitments().call();
310 const commitments = res[0];
311 const key = res[2];
312 html += "number of commitments: " + commitments.length + "<br>";
313 html += "current key: " + key + "<br>";
314 document.getElementById("stats").innerHTML = html;
315
316}