miksi-core/test/contracts/miksi.test.ts
test/contracts/miksi.test.tsRaw
1const DepositVerifier = artifacts.require("../build/DepositVerifier");
2const WithdrawVerifier = artifacts.require("../build/WithdrawVerifier");
3const Miksi = artifacts.require("../build/Miksi.sol");
4
5const chai = require("chai");
6const expect = chai.expect;
7const truffleAssert = require('truffle-assertions');
8
9const fs = require("fs");
10const { groth } = require('snarkjs');
11const { stringifyBigInts, unstringifyBigInts } = require('ffjavascript').utils;
12const Fr = require("ffjavascript").bn128.Fr;
13const WitnessCalculatorBuilder = require("circom_runtime").WitnessCalculatorBuilder;
14const circomlib = require("circomlib");
15const smt = require("circomlib").smt;
16
17let insVerifier;
18let insMiksi;
19
20const nLevels = 4;
21const secret = ["1234567890", "987654321", "123"];
22
23const coinCode = "0"; // refearing to ETH
24const ethAmount = '1';
25const amount = web3.utils.toWei(ethAmount, 'ether');
26const nullifier = ["0", "0", "0"];
27let commitment = [];
28let tree;
29let currKey=0;
30let proofs = [];
31
32contract("miksi", (accounts) => {
33
34
35 const {
36 0: owner,
37 1: addr1, // used for the deposit
38 2: addr2, // used for the withdraw
39 3: addr3,
40 4: addr4,
41 } = accounts;
42
43
44 before(async () => {
45 insDepositVerifier = await DepositVerifier.new();
46 insWithdrawVerifier = await WithdrawVerifier.new();
47 insMiksi = await Miksi.new(insDepositVerifier.address, insWithdrawVerifier.address);
48 });
49
50 before(async() => {
51 let balance_wei = await web3.eth.getBalance(addr1);
52 // console.log("Balance at " + addr1, web3.utils.fromWei(balance_wei, 'ether'));
53 expect(balance_wei).to.be.equal('100000000000000000000');
54
55 tree = await smt.newMemEmptyTrie();
56 await tree.insert(currKey, 0);
57
58 expect(tree.root.toString()).to.be.equal('7191590165524151132621032034309259185021876706372059338263145339926209741311');
59 });
60
61 it("Make first deposit", async () => {
62 nullifier[0] = await makeDeposit(secret[0], addr1);
63 balance_wei = await web3.eth.getBalance(addr1);
64 // console.log("Balance at " + addr1, web3.utils.fromWei(balance_wei, 'ether'));
65 // expect(balance_wei).to.be.equal('98993526980000000000');
66 });
67 it("Make second deposit", async () => {
68 nullifier[1] = await makeDeposit(secret[1], addr3);
69 });
70 it("Make 3rd deposit", async () => {
71 nullifier[2] = await makeDeposit(secret[2], addr3);
72 });
73 it("Get the commitments data & rebuild the tree", async () => {
74 // get the commitments data
75 let res = await insMiksi.getCommitments();
76 expect(res[1].toString()).to.be.equal(tree.root.toString());
77 let commitmentsArray = res[0];
78 currKey = res[2];
79
80 // rebuild the tree
81 let treeTmp = await smt.newMemEmptyTrie();
82 await treeTmp.insert(0, 0);
83 for (let i=0; i < commitmentsArray.length; i++) {
84 await treeTmp.insert(i+1, commitmentsArray[i]);
85 }
86 expect(treeTmp.root).to.be.equal(tree.root);
87 });
88 it("Calculate witness and generate the zkProof", async () => {
89 proofs[0] = await genWithdrawZKProof(secret[0], nullifier[0], addr2, "1");
90 proofs[1] = await genWithdrawZKProof(secret[1], nullifier[1], addr4, "2");
91 proofs[2] = await genWithdrawZKProof(secret[2], nullifier[2], addr4, "3");
92 });
93 it("Try to use the zkProof with another address and get revert", async () => {
94 // console.log("Try to reuse the zkproof and expect revert");
95 await truffleAssert.fails(
96 withdrawSC(nullifier[0], addr1, proofs[0]),
97 truffleAssert.ErrorType.REVERT,
98 "zkProof withdraw could not be verified"
99 );
100 });
101 it("Withdraw 1 ETH with the zkProof of the 1st deposit to addr2", async () => {
102 // withdraw
103 // console.log("Withdraw of " + ethAmount + " ETH to " + addr2);
104 let resW = await withdrawSC(nullifier[0], addr2, proofs[0]);
105 // console.log("resW", resW);
106
107 balance_wei = await web3.eth.getBalance(addr2);
108 // console.log("Balance at " + addr2, web3.utils.fromWei(balance_wei, 'ether'));
109 expect(balance_wei).to.be.equal('101000000000000000000');
110 });
111 it("Try to reuse the zkProof and get revert", async () => {
112 // console.log("Try to reuse the zkproof and expect revert");
113 await truffleAssert.fails(
114 withdrawSC(nullifier[0], addr2, proofs[0]),
115 truffleAssert.ErrorType.REVERT,
116 "nullifier already used"
117 );
118 balance_wei = await web3.eth.getBalance(addr2);
119 expect(balance_wei).to.be.equal('101000000000000000000');
120 });
121 it("Withdraw 1 ETH with the zkProof of the 2nd deposit to addr4", async () => {
122 let resW = await withdrawSC(nullifier[1], addr4, proofs[1]);
123 balance_wei = await web3.eth.getBalance(addr4);
124 expect(balance_wei).to.be.equal('101000000000000000000');
125 });
126 it("Withdraw 1 ETH with the zkProof of the 3rd deposit to addr4", async () => {
127 let resW = await withdrawSC(nullifier[2], addr4, proofs[2]);
128 balance_wei = await web3.eth.getBalance(addr4);
129 expect(balance_wei).to.be.equal('102000000000000000000');
130 });
131});
132
133async function makeDeposit(secret, addr) {
134 currKey += 1;
135
136 const poseidon = circomlib.poseidon.createHash(6, 8, 57);
137 let currNullifier = poseidon([currKey, secret]).toString();
138 let currCommitment = poseidon([coinCode, amount, secret, currNullifier]).toString();
139
140 let resI = await tree.insert(currKey, currCommitment);
141 while (resI.siblings.length < nLevels) resI.siblings.push(Fr.e(0));
142
143 // calculate witness
144 const wasm = await fs.promises.readFile("./test/build/deposit.wasm");
145 const input = unstringifyBigInts({
146 "coinCode": coinCode,
147 "amount": amount,
148 "secret": secret,
149 "oldKey": resI.isOld0 ? 0 : resI.oldKey,
150 "oldValue": resI.isOld0 ? 0 : resI.oldValue,
151 "isOld0": resI.isOld0 ? 1 : 0,
152 "siblings": resI.siblings,
153 "rootOld": resI.oldRoot,
154 "rootNew": resI.newRoot,
155 "commitment": currCommitment,
156 "key": currKey
157 });
158 const options = {};
159 // console.log("Calculate witness", input);
160 const wc = await WitnessCalculatorBuilder(wasm, options);
161 const w = await wc.calculateWitness(input);
162 const witness = unstringifyBigInts(stringifyBigInts(w));
163
164 // generate zkproof of commitment using snarkjs (as is a test)
165 const provingKey = unstringifyBigInts(JSON.parse(fs.readFileSync("./test/build/deposit-proving_key.json", "utf8")));
166
167 // console.log("Generate zkSNARK proof");
168 const res = groth.genProof(provingKey, witness);
169 let proof = res.proof;
170
171 const verificationKey = unstringifyBigInts(JSON.parse(fs.readFileSync("./test/build/deposit-verification_key.json", "utf8")));
172 let pubI = unstringifyBigInts([coinCode, amount, resI.oldRoot.toString(), resI.newRoot.toString(), currCommitment, currKey]);
173 let validCheck = groth.isValid(verificationKey, proof, pubI);
174 // console.log("VALIDCHECK", validCheck, pubI, proof);
175 assert(validCheck);
176 await insMiksi.deposit(
177 currCommitment,
178 tree.root.toString(),
179 [proof.pi_a[0].toString(), proof.pi_a[1].toString()],
180 [
181 [proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString()],
182 [proof.pi_b[1][1].toString(), proof.pi_b[1][0].toString()]
183 ],
184 [proof.pi_c[0].toString(), proof.pi_c[1].toString()],
185 {from: addr, value: amount}
186 );
187 return currNullifier;
188}
189
190async function genWithdrawZKProof(secret, nullifier, addr, k) {
191 const resC = await tree.find(k);
192 assert(resC.found);
193 let siblings = resC.siblings;
194 while (siblings.length < nLevels) {
195 siblings.push("0");
196 };
197
198 // calculate witness
199 const wasm = await fs.promises.readFile("./test/build/withdraw.wasm");
200 const input = unstringifyBigInts({
201 "coinCode": coinCode,
202 "amount": amount,
203 "secret": secret,
204 "nullifier": nullifier,
205 "siblings": siblings,
206 "root": tree.root,
207 "address": addr,
208 "key": k
209 });
210 const options = {};
211 // console.log("Calculate witness");
212 const wc = await WitnessCalculatorBuilder(wasm, options);
213 const w = await wc.calculateWitness(input);
214 const witness = unstringifyBigInts(stringifyBigInts(w));
215
216 // generate zkproof of commitment using snarkjs (as is a test)
217 const provingKey = unstringifyBigInts(JSON.parse(fs.readFileSync("./test/build/withdraw-proving_key.json", "utf8")));
218
219 // console.log("Generate zkSNARK proof");
220 const res = groth.genProof(provingKey, witness);
221 return res.proof;
222}
223
224async function withdrawSC(nullifier, addr, proof) {
225 // console.log("withdrawSC", proof);
226 return insMiksi.withdraw(
227 addr,
228 nullifier,
229 [proof.pi_a[0].toString(), proof.pi_a[1].toString()],
230 [
231 [proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString()],
232 [proof.pi_b[1][1].toString(), proof.pi_b[1][0].toString()]
233 ],
234 [proof.pi_c[0].toString(), proof.pi_c[1].toString()]
235 );
236}
237