From 2179505f3be53efbe5a75cf9c0a89ff00285d506 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Mon, 11 May 2020 09:39:09 +0200 Subject: [PATCH] Smartcontract add nullifier, update contract to last circuit --- README.md | 1 + contracts/Miksi.sol | 64 +++++++++++--------- contracts/verifier.sol | 23 +++---- miksi-logo00-small.png | Bin 0 -> 27578 bytes test/contracts/miksi.test.ts | 112 ++++++++++++++++++++++++++--------- 5 files changed, 131 insertions(+), 69 deletions(-) create mode 100644 miksi-logo00-small.png 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 0000000000000000000000000000000000000000..0e164c6d0f128f9f8ab0da3b59354a41cbe7675e GIT binary patch literal 27578 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%0D=I_z=sM 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 { @@ -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'); }); });