diff --git a/README.md b/README.md index bccbd73..a8362f8 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ *From Esperanto, **miksi** (miksĀ·i): to mingle, to blend, to mix, to shuffle* +![](miksi-logo00-small.png) **Warning:** This repository is in a very early stage. diff --git a/contracts/Miksi.sol b/contracts/Miksi.sol index 7263488..c661534 100644 --- a/contracts/Miksi.sol +++ b/contracts/Miksi.sol @@ -8,52 +8,58 @@ contract Miksi { constructor( address _verifierContractAddr) public { verifier = Verifier(_verifierContractAddr); } - - mapping(uint256 => Deposit) deposits; - - struct Deposit { - uint256 coinCode; - uint256 amount; - bool used; - } + uint256 amount = uint256(1000000000000000000); + uint256 root; + uint256[] commitments; + mapping(uint256 => bool) nullifiers; function deposit( - uint256 coinCode, - // uint256 amount, - uint256 commitment + uint256 _commitment, + uint256 _root ) public payable { - deposits[commitment] = Deposit(coinCode, msg.value, false); + // TODO check root state transition update with zkp + + require(msg.value==amount, "value should be 1 ETH"); // this can be flexible with a wrapper with preset fixed amounts + commitments.push(_commitment); + root = _root; } - function getDeposit( - uint256 commitment - ) public view returns (uint256, uint256) { - return ( - deposits[commitment].coinCode, - deposits[commitment].amount - ); + function getCommitments() public view returns (uint256[] memory, uint256) { + return (commitments, root); } function withdraw( - uint256 commitment, address payable _address, + uint256 nullifier, uint[2] memory a, uint[2][2] memory b, uint[2] memory c ) public { - uint256[4] memory input = [ - deposits[commitment].coinCode, - deposits[commitment].amount, - commitment, + uint256[5] memory input = [ + 0, + amount, + nullifier, + root, uint256(_address) ]; require(verifier.verifyProof(a, b, c, input), "zkProof withdraw could not be verified"); - // zk verification passed, proceed with the withdraw - require(!deposits[commitment].used, "deposit already withdrawed"); - deposits[commitment].used = true; - _address.send(deposits[commitment].amount); - // _address.call.value(deposits[commitment].amount).gas(20317)(); + // zk verification passed + require(useNullifier(nullifier), "nullifier already used"); + // nullifier check passed + // proceed with the withdraw + + _address.send(amount); + // _address.call.value(amount).gas(20317)(); + } + function useNullifier( + uint256 nullifier + ) internal returns (bool) { + if (nullifiers[nullifier]) { + return false; + } + nullifiers[nullifier] = true; + return true; } } diff --git a/contracts/verifier.sol b/contracts/verifier.sol index 061ba45..eedc211 100644 --- a/contracts/verifier.sol +++ b/contracts/verifier.sol @@ -174,16 +174,17 @@ contract Verifier { Pairing.G1Point C; } function verifyingKey() internal pure returns (VerifyingKey memory vk) { - vk.alfa1 = Pairing.G1Point(5185992386807636752062921178966578257539151042202977558020078229066726353735,19116203848700332781926088278955228321025476213248649030230870938462300903297); - vk.beta2 = Pairing.G2Point([19030978247664556689560141272090896525855193446598104251860042964819095406467,19780960404766112404848074878225825567855499848739061882556607695385088991192], [20402461031267926255530100927046942331147898484488092600018001239630447470368,17604138488196164031519027655960050500952176071938159024932979559575610655320]); - vk.gamma2 = Pairing.G2Point([20383216224167453593158000496263161259331265217720672612685782338425828607013,12285091765781487940062490824680322009247635572831566743154406937445855014141], [2724044019507218108155409647671037824297854833037683233209628555415178462971,4800441398018446385507678136399891972502082696226688244556543968658013445721]); - vk.delta2 = Pairing.G2Point([17491660837811889973732218736865963003206037853732483884984507582280081743890,18194603073278150162885840058572458854817989194824624275670491325570842555401], [14259520890547064112120136619441621870946603290427647212804054747278532372550,14237711745170441984980821720645206187129227373288931343140891164943633394176]); - vk.IC = new Pairing.G1Point[](5); - vk.IC[0] = Pairing.G1Point(5800430773422603830326865012746294458553705291090306505082228589953960548435,525261600745641876890318660619064985918284670178585976847597013044968033805); - vk.IC[1] = Pairing.G1Point(13962429114312903407632291539456657530314035590343134693033142826413285622014,9649969769508662299834286176418790329944910509961747776147748835638371066986); - vk.IC[2] = Pairing.G1Point(15449758800146945182032375987369505286936830519651791301171981724664755129658,20429634243561140221481565627307606983692378272988345590135001254474940660921); - vk.IC[3] = Pairing.G1Point(6593016958529739953865018414912427672210776178723941512691424600884151791016,2330860586625886543272754640931712670362458263865839970599736410727810605340); - vk.IC[4] = Pairing.G1Point(17799933908098896756054489526152304776886935915002907298028930270007186443766,16808221405053081411172376396342768077671335058872875437488630198515891174714); + vk.alfa1 = Pairing.G1Point(5573537740265625168090285795198286579893954229360118260810063400917126796332,10789719125793022298590118880800334307038768742566971868445296062248489927279); + vk.beta2 = Pairing.G2Point([6072967376916873741947961849223716153342228155851589974405468410733665523375,12287252052014343808789818956357497671178626709800000377423333232855717394550], [19887659544225763042094432491416573265605432229063422403349406354087673898183,6817773539509067572550546188063114549568433731855389350832916458849047861996]); + vk.gamma2 = Pairing.G2Point([21777617475348225837446670587801717739887263496343742680443739578840171280954,21819935038190839400227353700653328252014694909407919035917157743271161479537], [5874472499575263305242127811274992746938244885646767207659488542844992545745,7393451529219086695827003727698071721027731112496364438559915896033740034847]); + vk.delta2 = Pairing.G2Point([21378569762973853737012405126625349874813324397465926818859899411148132219071,14217351759616267906987060014706527451694626178118484166622168408217896429543], [2959651248849223420449727632536103426152162159064239731749254690426832558857,11771456040212156734923327364079425983074170498604769948335767339414964838231]); + vk.IC = new Pairing.G1Point[](6); + vk.IC[0] = Pairing.G1Point(9635854757134834421832904337276664164370386962134477374755137344348461690576,19010444634264471397134767941238249705060700374213396849476569996691707235781); + vk.IC[1] = Pairing.G1Point(11035507535270489026720166819336384993970390089893993872476567909015281065120,8704656742215780532233763414730518226534751719578362784054696564772602495731); + vk.IC[2] = Pairing.G1Point(8237061378219722919406315531805429710952281972371670670227024276177812450561,21788473647720763764729284824775460737201605786977595028535407989906364038374); + vk.IC[3] = Pairing.G1Point(12032524012671168792329029167990930904368673059152483847563381918262080217708,18306948356499743813922422173776199871369081286779005885299858224571300447429); + vk.IC[4] = Pairing.G1Point(10502650737346333699651566811075824924610501915730189047190185075707722044703,7451314863250353539987518216950772741664034028935285984537382148127632592866); + vk.IC[5] = Pairing.G1Point(2254627509888069544735850912948033354698601777682208496156519712223878774652,10613197317835768162576244259798938001105060978002557905703689968987855363120); } function verify(uint[] memory input, Proof memory proof) internal view returns (uint) { @@ -209,7 +210,7 @@ contract Verifier { uint[2] memory a, uint[2][2] memory b, uint[2] memory c, - uint[4] memory input + uint[5] memory input ) public view returns (bool r) { Proof memory proof; proof.A = Pairing.G1Point(a[0], a[1]); diff --git a/miksi-logo00-small.png b/miksi-logo00-small.png new file mode 100644 index 0000000..0e164c6 Binary files /dev/null and b/miksi-logo00-small.png differ diff --git a/test/contracts/miksi.test.ts b/test/contracts/miksi.test.ts index 85bcd8f..850262b 100644 --- a/test/contracts/miksi.test.ts +++ b/test/contracts/miksi.test.ts @@ -10,6 +10,7 @@ const { groth } = require('snarkjs'); const { stringifyBigInts, unstringifyBigInts } = require('ffjavascript').utils; const WitnessCalculatorBuilder = require("circom_runtime").WitnessCalculatorBuilder; const circomlib = require("circomlib"); +const smt = require("circomlib").smt; contract("miksi", (accounts) => { @@ -24,51 +25,80 @@ contract("miksi", (accounts) => { let insVerifier; let insMiksi; + const nLevels = 5; + const secret = "1234567890"; + + const coinCode = "0"; // refearing to ETH + const ethAmount = '1'; + const amount = web3.utils.toWei(ethAmount, 'ether'); + const nullifier = "567891234"; + let tree; + let commitment; + let proof; + let publicSignals; + before(async () => { insVerifier = await Verifier.new(); insMiksi = await Miksi.new(insVerifier.address); }); - it("miksi flow", async () => { - const secret = "123456789"; - - const coinCode = "0"; // refearing to ETH - const ethAmount = '0.5'; - const amount = web3.utils.toWei(ethAmount, 'ether'); - + before(async() => { let balance_wei = await web3.eth.getBalance(addr1); - console.log("Balance at " + addr1, web3.utils.fromWei(balance_wei, 'ether')); + // console.log("Balance at " + addr1, web3.utils.fromWei(balance_wei, 'ether')); expect(balance_wei).to.be.equal('100000000000000000000'); const poseidon = circomlib.poseidon.createHash(6, 8, 57); - const commitment = poseidon([coinCode, amount, secret]).toString(); + commitment = poseidon([coinCode, amount, secret, nullifier]).toString(); // deposit - console.log("Deposit of " + ethAmount + " ETH from " + addr1); - await insMiksi.deposit(coinCode, commitment, {from: addr1, value: amount}); + // add commitment into SMT + tree = await smt.newMemEmptyTrie(); + await tree.insert(commitment, 0); + await tree.insert(1, 0); + await tree.insert(2, 0); + expect(tree.root.toString()).to.be.equal('9712258649847843172766744803572924784812438285433990419902675958769413333474'); + }); + + it("Make the deposit", async () => { + // console.log("root", tree.root); + // console.log("Deposit of " + ethAmount + " ETH from " + addr1 + ".\nCommitment "+commitment+", root: "+ tree.root); + await insMiksi.deposit(commitment, tree.root.toString(), {from: addr1, value: amount}); balance_wei = await web3.eth.getBalance(addr1); - console.log("Balance at " + addr1, web3.utils.fromWei(balance_wei, 'ether')); - expect(balance_wei).to.be.equal('99499107180000000000'); + // console.log("Balance at " + addr1, web3.utils.fromWei(balance_wei, 'ether')); + expect(balance_wei).to.be.equal('98998318640000000000'); + }); - // getDeposit data - const res = await insMiksi.getDeposit(commitment); - expect(res[0].toString()).to.be.equal(coinCode); - expect(res[1].toString()).to.be.equal(amount); + it("Get the commitments data", async () => { + // getCommitments data + let res = await insMiksi.getCommitments(); + expect(res[0][0].toString()).to.be.equal('189025084074544266465422070282645213792582195466360448472858620722286781863'); + expect(res[1].toString()).to.be.equal('9712258649847843172766744803572924784812438285433990419902675958769413333474'); + }); + it("Calculate witness and generate the zkProof", async () => { + const resC = await tree.find(commitment); + assert(resC.found); + let siblings = resC.siblings; + while (siblings.length < nLevels) { + siblings.push("0"); + }; + // console.log("siblings", siblings); // calculate witness const wasm = await fs.promises.readFile("./build/withdraw.wasm"); const input = unstringifyBigInts({ "coinCode": coinCode, "amount": amount, - "commitment": commitment, "secret": secret, + "nullifier": nullifier, + "siblings": siblings, + "root": tree.root, "address": addr2 }); const options = {}; - console.log("Calculate witness"); + // console.log("Calculate witness"); const wc = await WitnessCalculatorBuilder(wasm, options); const w = await wc.calculateWitness(input); const witness = unstringifyBigInts(stringifyBigInts(w)); @@ -76,14 +106,36 @@ contract("miksi", (accounts) => { // generate zkproof of commitment using snarkjs (as is a test) const provingKey = unstringifyBigInts(JSON.parse(fs.readFileSync("./build/proving_key.json", "utf8"))); - console.log("Generate zkSNARK proof"); - const {proof, publicSignals} = groth.genProof(provingKey, witness); + // console.log("Generate zkSNARK proof"); + const res = groth.genProof(provingKey, witness); + proof = res.proof; + publicSignals = res.publicSignals; + }); + it("Try to use the zkProof with another address and get revert", async () => { + // console.log("Try to reuse the zkproof and expect revert"); + await truffleAssert.fails( + insMiksi.withdraw( + addr1, + nullifier, + [proof.pi_a[0].toString(), proof.pi_a[1].toString()], + [ + [proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString()], + [proof.pi_b[1][1].toString(), proof.pi_b[1][0].toString()] + ], + [proof.pi_c[0].toString(), proof.pi_c[1].toString()] + ), + truffleAssert.ErrorType.REVERT, + "zkProof withdraw could not be verified" + ); + }); + + it("Withdraw 1 ETH with the zkProof", async () => { // withdraw - console.log("Withdraw of " + ethAmount + " ETH to " + addr2); + // console.log("Withdraw of " + ethAmount + " ETH to " + addr2); let resW = await insMiksi.withdraw( - commitment, addr2, + nullifier, [proof.pi_a[0].toString(), proof.pi_a[1].toString()], [ [proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString()], @@ -94,14 +146,16 @@ contract("miksi", (accounts) => { // console.log("resW", resW); balance_wei = await web3.eth.getBalance(addr2); - console.log("Balance at " + addr2, web3.utils.fromWei(balance_wei, 'ether')); - expect(balance_wei).to.be.equal('100500000000000000000'); + // console.log("Balance at " + addr2, web3.utils.fromWei(balance_wei, 'ether')); + expect(balance_wei).to.be.equal('101000000000000000000'); + }); - console.log("Try to reuse the zkproof and expect revert"); + it("Try to reuse the zkProof and get revert", async () => { + // console.log("Try to reuse the zkproof and expect revert"); await truffleAssert.fails( insMiksi.withdraw( - commitment, addr2, + nullifier, [proof.pi_a[0].toString(), proof.pi_a[1].toString()], [ [proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString()], @@ -110,9 +164,9 @@ contract("miksi", (accounts) => { [proof.pi_c[0].toString(), proof.pi_c[1].toString()] ), truffleAssert.ErrorType.REVERT, - "deposit already withdrawed" + "nullifier already used" ); balance_wei = await web3.eth.getBalance(addr2); - expect(balance_wei).to.be.equal('100500000000000000000'); + expect(balance_wei).to.be.equal('101000000000000000000'); }); });