arnaucube
arnaucube/miksi-corePublic Self-hosted repository
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