diff --git a/solidity/contracts/factory.sol b/solidity/contracts/factory.sol index 53fded3..de069ac 100644 --- a/solidity/contracts/factory.sol +++ b/solidity/contracts/factory.sol @@ -31,6 +31,7 @@ contract ZetoTokenFactory is Ownable { address withdrawVerifier; address verifier; address batchVerifier; + address batchWithdrawVerifier; } event ZetoTokenDeployed(address indexed zetoToken); @@ -79,6 +80,10 @@ contract ZetoTokenFactory is Ownable { args.batchVerifier != address(0), "Factory: batchVerifier address is required" ); + require( + args.batchWithdrawVerifier != address(0), + "Factory: batchWithdrawVerifier address is required" + ); address instance = Clones.clone(args.implementation); require( instance != address(0), @@ -89,7 +94,8 @@ contract ZetoTokenFactory is Ownable { args.verifier, args.depositVerifier, args.withdrawVerifier, - args.batchVerifier + args.batchVerifier, + args.batchWithdrawVerifier ); emit ZetoTokenDeployed(instance); return instance; diff --git a/solidity/contracts/lib/interfaces/zeto_fungible_initializable.sol b/solidity/contracts/lib/interfaces/zeto_fungible_initializable.sol index a7f6c6d..2b0485f 100644 --- a/solidity/contracts/lib/interfaces/zeto_fungible_initializable.sol +++ b/solidity/contracts/lib/interfaces/zeto_fungible_initializable.sol @@ -21,6 +21,7 @@ interface IZetoFungibleInitializable { address _depositVerifier, address _withdrawVerifier, address _verifier, - address _batchVerifier + address _batchVerifier, + address _batchWithdrawVerifier ) external; } diff --git a/solidity/contracts/lib/verifier_check_inputs_outputs_value_batch.sol b/solidity/contracts/lib/verifier_check_inputs_outputs_value_batch.sol new file mode 100644 index 0000000..43a583a --- /dev/null +++ b/solidity/contracts/lib/verifier_check_inputs_outputs_value_batch.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Groth16Verifier_CheckInputsOutputsValueBatch { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant deltax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant deltay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant deltay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + + + uint256 constant IC0x = 16406855041988402418570886496349002995705712334781538764307269787167911399430; + uint256 constant IC0y = 17062891280301536926110578734915723839095919791367175474506509310600003607607; + + uint256 constant IC1x = 11251678823358267718136708972991805824720473739361951098088971382598913167699; + uint256 constant IC1y = 18469711726131890278268971725061117935040499355264464748594326254451733920933; + + uint256 constant IC2x = 19423953215898457742794312283715670756162849208434393145382344611939351119392; + uint256 constant IC2y = 15101345671392435513539926346277398347812084079236470175230838530261517700063; + + uint256 constant IC3x = 15492432199738522150786104649617971317491878799640663413058399024929325011456; + uint256 constant IC3y = 1849123419461955600672657350930674086732010210918006148127970045557636699730; + + uint256 constant IC4x = 20556481651461125195829770916680429763969629501376963431272297847840333600288; + uint256 constant IC4y = 7513518309665274486407775216759717009458803864983567865867178158606408294827; + + uint256 constant IC5x = 18628135598559661868491688440770254818509868496021518701934276504609414133508; + uint256 constant IC5y = 1167819248752988768596770584756471985899166510161687438915119345202702697144; + + uint256 constant IC6x = 11020941871050469252323786517765915842863960407888286824612692792558486984111; + uint256 constant IC6y = 10485425859662940648059438593932469653430937797032986298010291971136560546606; + + uint256 constant IC7x = 5793647679348034224394212228339808224168272820193578238325721973141818095084; + uint256 constant IC7y = 15996240662253233062748933483250431489019330778771737542366438462520021557482; + + uint256 constant IC8x = 14623001753566766629059858986684249079998519866195503676489725502470812161745; + uint256 constant IC8y = 14014107953826274980522140152870458692572824248357139653679255112039697301258; + + uint256 constant IC9x = 9097153778982380593651318254923294438188449308208860385736335671731642689388; + uint256 constant IC9y = 10676411777771816538790669785924041640901023875981025632676700582420634457605; + + uint256 constant IC10x = 4768550018067835409836437067366308823423110418503527411288340247166729595570; + uint256 constant IC10y = 5845234219082990661718204286288547936215316047709043735403115260016597691195; + + uint256 constant IC11x = 6579017396588187900994327393243124471248914684882036754588313858620073841120; + uint256 constant IC11y = 11590081804677161555857156577973669346405362193296617772438442829550839242618; + + uint256 constant IC12x = 8095739804925688685307904593266974552819819919511836734708520778110789318572; + uint256 constant IC12y = 11116071221641714615893908909552671269587233310745275115713330904448178443052; + + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[12] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, r)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) + + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) + + g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96))) + + g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128))) + + g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160))) + + g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192))) + + g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224))) + + g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256))) + + g1_mulAccC(_pVk, IC10x, IC10y, calldataload(add(pubSignals, 288))) + + g1_mulAccC(_pVk, IC11x, IC11y, calldataload(add(pubSignals, 320))) + + g1_mulAccC(_pVk, IC12x, IC12y, calldataload(add(pubSignals, 352))) + + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + checkField(calldataload(add(_pubSignals, 64))) + + checkField(calldataload(add(_pubSignals, 96))) + + checkField(calldataload(add(_pubSignals, 128))) + + checkField(calldataload(add(_pubSignals, 160))) + + checkField(calldataload(add(_pubSignals, 192))) + + checkField(calldataload(add(_pubSignals, 224))) + + checkField(calldataload(add(_pubSignals, 256))) + + checkField(calldataload(add(_pubSignals, 288))) + + checkField(calldataload(add(_pubSignals, 320))) + + checkField(calldataload(add(_pubSignals, 352))) + + checkField(calldataload(add(_pubSignals, 384))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } diff --git a/solidity/contracts/lib/verifier_check_nullifier_value_batch.sol b/solidity/contracts/lib/verifier_check_nullifier_value_batch.sol new file mode 100644 index 0000000..6bbf725 --- /dev/null +++ b/solidity/contracts/lib/verifier_check_nullifier_value_batch.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Groth16Verifier_CheckNullifierValueBatch { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant deltax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant deltay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant deltay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + + + uint256 constant IC0x = 20671074467579734857847596547055266445328533762588721268519191875350059053891; + uint256 constant IC0y = 14951098345322796073027366584310761044868926985657977459139078894481627655461; + + uint256 constant IC1x = 12000081248186983269524257052000761064901034818507685659049644161122941063341; + uint256 constant IC1y = 15516971062665917073090328459138065004596246882492513196262563532230767389353; + + uint256 constant IC2x = 9411111032740111602264855148385468328492259422723597259385251613745598803902; + uint256 constant IC2y = 10323155201450014604301403915496923225659511949572802661966430087007178746441; + + uint256 constant IC3x = 4438847732901674872128718308489179804393784494012127205086961117665904894192; + uint256 constant IC3y = 7430531453405117598938421321593673956070329677208870594925362824944496151111; + + uint256 constant IC4x = 2395120040465205201096667885252276606645316331032988003167679478054139898877; + uint256 constant IC4y = 14448366311941437868185353072645393328711231401925491134934298524562784274462; + + uint256 constant IC5x = 13445677581031398831817167387998077245896166063464307566626433317121928548858; + uint256 constant IC5y = 13902400719240728728437612577162464173527233703886931321955943252958646992511; + + uint256 constant IC6x = 15641654430273462801884123825735380328788465741346176351356205589585591382537; + uint256 constant IC6y = 8139850515789596472180180792818545785157943800149252751717392103568781234161; + + uint256 constant IC7x = 14050096082523710141776397917128550695911592862807463733468997209701682217863; + uint256 constant IC7y = 6901766456320629799934175965140510580558270796917425129954992400613270177847; + + uint256 constant IC8x = 12731436494425511204592700949928542091214640520850023719195462862199345771601; + uint256 constant IC8y = 19120295984411495037887570571101234304735996780135524189512745912549942321948; + + uint256 constant IC9x = 18277966489668893230779554667054735652643817777661497642941039904825866475778; + uint256 constant IC9y = 14776318209502074904311976810878471980321185136711509936306201830834650750985; + + uint256 constant IC10x = 11393441297861142395248716090957736857800669691486901170239864009237766837733; + uint256 constant IC10y = 4669633111807398549423023745625200983024113696597242886776080701570600095817; + + uint256 constant IC11x = 17094019090056954918811926108534746962919149124197235929911157344230501642917; + uint256 constant IC11y = 14674368842335287917470374822844105391742093461604374599471429840141572040168; + + uint256 constant IC12x = 19692688396724133495482002416399398592514532066384925082407645678984419317473; + uint256 constant IC12y = 3605203667012682912469870143041663147227348414852241639025768542294557979471; + + uint256 constant IC13x = 20628982961670697756256724718272051941421624806419790192237263009313531587753; + uint256 constant IC13y = 5403716199591168683890485313474651075202577022988073697830104927568491753927; + + uint256 constant IC14x = 21679974290313205033014132589719764884061756549385623306128090282856163701618; + uint256 constant IC14y = 15451053794872865538119516358230526850276194265855889349229313173675490614973; + + uint256 constant IC15x = 16603737745500308717002054968060166864459062746665998233180858889452052686118; + uint256 constant IC15y = 750549697690004286406850800760928901821286836108997012362232119165590942167; + + uint256 constant IC16x = 1359311639973519852598165556788860563250638498571805076085313151321480630172; + uint256 constant IC16y = 4831751294456710953219978069057466405077773094664792257088750356636328425109; + + uint256 constant IC17x = 10246678981614529245614748704049790844676680216805003850578533976065639967924; + uint256 constant IC17y = 18673996210719422229181910852659583345325739558179830325503226296557565439035; + + uint256 constant IC18x = 19915227005624332214251170233702382833969373708814987943248729626521801446050; + uint256 constant IC18y = 5283839256348009268722315411106479297473851835680129259755232084561721766803; + + uint256 constant IC19x = 13883604722689792226764138263277808397013882375589363868621104879476228674022; + uint256 constant IC19y = 7236730039787803352120531558201495639173100029441266845857912822879182728155; + + uint256 constant IC20x = 12481531748190575843330873248871107942932072373848033146722006789119150232291; + uint256 constant IC20y = 9631254126711952993469771781559629372506383041947195835052653817320975402032; + + uint256 constant IC21x = 17481080981288359886217003385152346766741540645180108972693789761048559279376; + uint256 constant IC21y = 7825039381905782374744115652203827737598831456021283009023141915867193175692; + + uint256 constant IC22x = 7849237446611626817070212201485848479849355440996513672685177687294263805454; + uint256 constant IC22y = 16515874431405097920776203066333566915839614045778881194348792214569056984691; + + uint256 constant IC23x = 11857707391098615847106972237544828768710884349097096286759498433883002113991; + uint256 constant IC23y = 15958999989731507948927930508670148671289317287181283284670403760528825439958; + + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[23] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, r)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) + + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) + + g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96))) + + g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128))) + + g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160))) + + g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192))) + + g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224))) + + g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256))) + + g1_mulAccC(_pVk, IC10x, IC10y, calldataload(add(pubSignals, 288))) + + g1_mulAccC(_pVk, IC11x, IC11y, calldataload(add(pubSignals, 320))) + + g1_mulAccC(_pVk, IC12x, IC12y, calldataload(add(pubSignals, 352))) + + g1_mulAccC(_pVk, IC13x, IC13y, calldataload(add(pubSignals, 384))) + + g1_mulAccC(_pVk, IC14x, IC14y, calldataload(add(pubSignals, 416))) + + g1_mulAccC(_pVk, IC15x, IC15y, calldataload(add(pubSignals, 448))) + + g1_mulAccC(_pVk, IC16x, IC16y, calldataload(add(pubSignals, 480))) + + g1_mulAccC(_pVk, IC17x, IC17y, calldataload(add(pubSignals, 512))) + + g1_mulAccC(_pVk, IC18x, IC18y, calldataload(add(pubSignals, 544))) + + g1_mulAccC(_pVk, IC19x, IC19y, calldataload(add(pubSignals, 576))) + + g1_mulAccC(_pVk, IC20x, IC20y, calldataload(add(pubSignals, 608))) + + g1_mulAccC(_pVk, IC21x, IC21y, calldataload(add(pubSignals, 640))) + + g1_mulAccC(_pVk, IC22x, IC22y, calldataload(add(pubSignals, 672))) + + g1_mulAccC(_pVk, IC23x, IC23y, calldataload(add(pubSignals, 704))) + + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + checkField(calldataload(add(_pubSignals, 64))) + + checkField(calldataload(add(_pubSignals, 96))) + + checkField(calldataload(add(_pubSignals, 128))) + + checkField(calldataload(add(_pubSignals, 160))) + + checkField(calldataload(add(_pubSignals, 192))) + + checkField(calldataload(add(_pubSignals, 224))) + + checkField(calldataload(add(_pubSignals, 256))) + + checkField(calldataload(add(_pubSignals, 288))) + + checkField(calldataload(add(_pubSignals, 320))) + + checkField(calldataload(add(_pubSignals, 352))) + + checkField(calldataload(add(_pubSignals, 384))) + + checkField(calldataload(add(_pubSignals, 416))) + + checkField(calldataload(add(_pubSignals, 448))) + + checkField(calldataload(add(_pubSignals, 480))) + + checkField(calldataload(add(_pubSignals, 512))) + + checkField(calldataload(add(_pubSignals, 544))) + + checkField(calldataload(add(_pubSignals, 576))) + + checkField(calldataload(add(_pubSignals, 608))) + + checkField(calldataload(add(_pubSignals, 640))) + + checkField(calldataload(add(_pubSignals, 672))) + + checkField(calldataload(add(_pubSignals, 704))) + + checkField(calldataload(add(_pubSignals, 736))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } diff --git a/solidity/contracts/lib/zeto_fungible.sol b/solidity/contracts/lib/zeto_fungible.sol index 2af433d..2b1be2d 100644 --- a/solidity/contracts/lib/zeto_fungible.sol +++ b/solidity/contracts/lib/zeto_fungible.sol @@ -29,6 +29,7 @@ abstract contract ZetoFungible is OwnableUpgradeable { // this can be used in the optional deposit calls to verify that // the UTXOs match the deposited value Groth16Verifier_CheckHashesValue internal depositVerifier; + error WithdrawArrayTooLarge(uint256 maxAllowed); IERC20 internal erc20; diff --git a/solidity/contracts/lib/zeto_fungible_withdraw.sol b/solidity/contracts/lib/zeto_fungible_withdraw.sol index 755e590..f943dff 100644 --- a/solidity/contracts/lib/zeto_fungible_withdraw.sol +++ b/solidity/contracts/lib/zeto_fungible_withdraw.sol @@ -17,10 +17,14 @@ pragma solidity ^0.8.20; import {Groth16Verifier_CheckHashesValue} from "./verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckInputsOutputsValue} from "./verifier_check_inputs_outputs_value.sol"; +import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./verifier_check_inputs_outputs_value_batch.sol"; import {ZetoFungible} from "./zeto_fungible.sol"; import {Commonlib} from "./common.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +uint256 constant WITHDRAW_INPUT_SIZE = 4; +uint256 constant BATCH_WITHDRAW_INPUT_SIZE = 12; + /// @title A sample implementation of a base Zeto fungible token contract /// @author Kaleido, Inc. /// @dev Defines the verifier library for checking UTXOs against a claimed value. @@ -29,13 +33,39 @@ abstract contract ZetoFungibleWithdraw is ZetoFungible { // this can be used in the optional withdraw calls to verify that the nullifiers // match the withdrawn value Groth16Verifier_CheckInputsOutputsValue internal withdrawVerifier; + Groth16Verifier_CheckInputsOutputsValueBatch internal batchWithdrawVerifier; function __ZetoFungibleWithdraw_init( Groth16Verifier_CheckHashesValue _depositVerifier, - Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier + Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, + Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier ) public onlyInitializing { __ZetoFungible_init(_depositVerifier); withdrawVerifier = _withdrawVerifier; + batchWithdrawVerifier = _batchWithdrawVerifier; + } + + function constructPublicInputs( + uint256 amount, + uint256[] memory inputs, + uint256 output, + uint256 size + ) internal pure returns (uint256[] memory publicInputs) { + publicInputs = new uint256[](size); + uint256 piIndex = 0; + + // copy output amount + publicInputs[piIndex++] = amount; + + // copy input commitments + for (uint256 i = 0; i < inputs.length; i++) { + publicInputs[piIndex++] = inputs[i]; + } + + // copy output commitment + publicInputs[piIndex++] = output; + + return publicInputs; } function _withdraw( @@ -44,25 +74,56 @@ abstract contract ZetoFungibleWithdraw is ZetoFungible { uint256 output, Commonlib.Proof calldata proof ) public virtual { - require((inputs.length == 2), "Withdraw must have 2 inputs"); - - // construct the public inputs - uint256[4] memory publicInputs; - publicInputs[0] = amount; - publicInputs[1] = inputs[0]; - publicInputs[2] = inputs[1]; - publicInputs[3] = output; - // Check the proof - require( - withdrawVerifier.verifyProof( - proof.pA, - proof.pB, - proof.pC, - publicInputs - ), - "Invalid proof" - ); + if (inputs.length > 2) { + // Check if inputs or outputs exceed batchMax and revert with custom error if necessary + if (inputs.length > BATCH_WITHDRAW_INPUT_SIZE) { + revert WithdrawArrayTooLarge(BATCH_WITHDRAW_INPUT_SIZE); + } + uint256[] memory publicInputs = constructPublicInputs( + amount, + inputs, + output, + BATCH_WITHDRAW_INPUT_SIZE + ); + // construct the public inputs for verifier + uint256[BATCH_WITHDRAW_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; + } + // Check the proof + require( + batchWithdrawVerifier.verifyProof( + proof.pA, + proof.pB, + proof.pC, + fixedSizeInputs + ), + "Invalid proof" + ); + } else { + uint256[] memory publicInputs = constructPublicInputs( + amount, + inputs, + output, + WITHDRAW_INPUT_SIZE + ); + // construct the public inputs for verifier + uint256[WITHDRAW_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; + } + // Check the proof + require( + withdrawVerifier.verifyProof( + proof.pA, + proof.pB, + proof.pC, + fixedSizeInputs + ), + "Invalid proof" + ); + } require( erc20.transfer(msg.sender, amount), diff --git a/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol b/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol index ddc20d0..cc942c4 100644 --- a/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol +++ b/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol @@ -17,11 +17,15 @@ pragma solidity ^0.8.20; import {Groth16Verifier_CheckHashesValue} from "./verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckNullifierValue} from "./verifier_check_nullifier_value.sol"; +import {Groth16Verifier_CheckNullifierValueBatch} from "./verifier_check_nullifier_value_batch.sol"; import {ZetoFungible} from "./zeto_fungible.sol"; import {Commonlib} from "./common.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +uint256 constant WITHDRAW_INPUT_SIZE = 7; +uint256 constant BATCH_WITHDRAW_INPUT_SIZE = 23; + /// @title A sample implementation of a base Zeto fungible token contract /// @author Kaleido, Inc. /// @dev Defines the verifier library for checking UTXOs against a claimed value. @@ -30,13 +34,48 @@ abstract contract ZetoFungibleWithdrawWithNullifiers is ZetoFungible { // this can be used in the optional withdraw calls to verify that the nullifiers // match the withdrawn value Groth16Verifier_CheckNullifierValue internal withdrawVerifier; + Groth16Verifier_CheckNullifierValueBatch internal batchWithdrawVerifier; function __ZetoFungibleWithdrawWithNullifiers_init( Groth16Verifier_CheckHashesValue _depositVerifier, - Groth16Verifier_CheckNullifierValue _withdrawVerifier + Groth16Verifier_CheckNullifierValue _withdrawVerifier, + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier ) internal onlyInitializing { __ZetoFungible_init(_depositVerifier); withdrawVerifier = _withdrawVerifier; + batchWithdrawVerifier = _batchWithdrawVerifier; + } + + function constructPublicInputs( + uint256 amount, + uint256[] memory nullifiers, + uint256 output, + uint256 root, + uint256 size + ) internal pure returns (uint256[] memory publicInputs) { + publicInputs = new uint256[](size); + uint256 piIndex = 0; + + // copy output amount + publicInputs[piIndex++] = amount; + + // copy input commitments + for (uint256 i = 0; i < nullifiers.length; i++) { + publicInputs[piIndex++] = nullifiers[i]; + } + + // copy root + publicInputs[piIndex++] = root; + + // populate enables + for (uint256 i = 0; i < nullifiers.length; i++) { + publicInputs[piIndex++] = (nullifiers[i] == 0) ? 0 : 1; + } + + // copy output commitment + publicInputs[piIndex++] = output; + + return publicInputs; } function _withdrawWithNullifiers( @@ -46,27 +85,58 @@ abstract contract ZetoFungibleWithdrawWithNullifiers is ZetoFungible { uint256 root, Commonlib.Proof calldata proof ) public virtual { - require((nullifiers.length == 2), "Withdraw must have 2 nullifiers"); - // construct the public inputs - uint256[7] memory publicInputs; - publicInputs[0] = amount; - publicInputs[1] = nullifiers[0]; - publicInputs[2] = nullifiers[1]; - publicInputs[3] = root; - publicInputs[4] = (nullifiers[0] == 0) ? 0 : 1; // enable MT proof for the first nullifier - publicInputs[5] = (nullifiers[1] == 0) ? 0 : 1; // enable MT proof for the second nullifier - publicInputs[6] = output; - // Check the proof - require( - withdrawVerifier.verifyProof( - proof.pA, - proof.pB, - proof.pC, - publicInputs - ), - "Invalid proof" - ); + if (nullifiers.length > 2) { + // Check if inputs or outputs exceed batchMax and revert with custom error if necessary + if (nullifiers.length > BATCH_WITHDRAW_INPUT_SIZE) { + revert WithdrawArrayTooLarge(BATCH_WITHDRAW_INPUT_SIZE); + } + uint256[] memory publicInputs = constructPublicInputs( + amount, + nullifiers, + output, + root, + BATCH_WITHDRAW_INPUT_SIZE + ); + // construct the public inputs for verifier + uint256[BATCH_WITHDRAW_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; + } + // Check the proof + require( + batchWithdrawVerifier.verifyProof( + proof.pA, + proof.pB, + proof.pC, + fixedSizeInputs + ), + "Invalid proof" + ); + } else { + uint256[] memory publicInputs = constructPublicInputs( + amount, + nullifiers, + output, + root, + WITHDRAW_INPUT_SIZE + ); + // construct the public inputs for verifier + uint256[WITHDRAW_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; + } + // Check the proof + require( + withdrawVerifier.verifyProof( + proof.pA, + proof.pB, + proof.pC, + fixedSizeInputs + ), + "Invalid proof" + ); + } require( erc20.transfer(msg.sender, amount), diff --git a/solidity/contracts/zeto_anon.sol b/solidity/contracts/zeto_anon.sol index 70b8ed6..d08da9c 100644 --- a/solidity/contracts/zeto_anon.sol +++ b/solidity/contracts/zeto_anon.sol @@ -18,6 +18,8 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.sol"; import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckInputsOutputsValue} from "./lib/verifier_check_inputs_outputs_value.sol"; +import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./lib/verifier_check_inputs_outputs_value_batch.sol"; + import {Groth16Verifier_Anon} from "./lib/verifier_anon.sol"; import {Groth16Verifier_AnonBatch} from "./lib/verifier_anon_batch.sol"; import {Registry} from "./lib/registry.sol"; @@ -48,10 +50,15 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { Groth16Verifier_Anon _verifier, Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, - Groth16Verifier_AnonBatch _batchVerifier + Groth16Verifier_AnonBatch _batchVerifier, + Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier ) public initializer { __ZetoBase_init(initialOwner); - __ZetoFungibleWithdraw_init(_depositVerifier, _withdrawVerifier); + __ZetoFungibleWithdraw_init( + _depositVerifier, + _withdrawVerifier, + _batchWithdrawVerifier + ); verifier = _verifier; batchVerifier = _batchVerifier; } @@ -62,7 +69,7 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { uint256[] memory inputs, uint256[] memory outputs, uint256 size - ) internal returns (uint256[] memory publicInputs) { + ) internal pure returns (uint256[] memory publicInputs) { publicInputs = new uint256[](size); uint256 piIndex = 0; // copy input commitments @@ -110,9 +117,9 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { BATCH_INPUT_SIZE ); // construct the public inputs for batchVerifier - uint256[BATCH_INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[BATCH_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof using batchVerifier @@ -121,7 +128,7 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); @@ -132,9 +139,9 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { INPUT_SIZE ); // construct the public inputs for verifier - uint256[INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof require( @@ -142,7 +149,7 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); diff --git a/solidity/contracts/zeto_anon_enc.sol b/solidity/contracts/zeto_anon_enc.sol index b95e125..3e805cf 100644 --- a/solidity/contracts/zeto_anon_enc.sol +++ b/solidity/contracts/zeto_anon_enc.sol @@ -18,6 +18,7 @@ pragma solidity ^0.8.20; import {IZetoEncrypted} from "./lib/interfaces/izeto_encrypted.sol"; import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckInputsOutputsValue} from "./lib/verifier_check_inputs_outputs_value.sol"; +import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./lib/verifier_check_inputs_outputs_value_batch.sol"; import {Groth16Verifier_AnonEnc} from "./lib/verifier_anon_enc.sol"; import {Groth16Verifier_AnonEncBatch} from "./lib/verifier_anon_enc_batch.sol"; import {ZetoFungibleWithdraw} from "./lib/zeto_fungible_withdraw.sol"; @@ -55,12 +56,18 @@ contract Zeto_AnonEnc is Groth16Verifier_AnonEnc _verifier, Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, - Groth16Verifier_AnonEncBatch _batchVerifier + Groth16Verifier_AnonEncBatch _batchVerifier, + Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier ) public initializer { __ZetoBase_init(initialOwner); - __ZetoFungibleWithdraw_init(_depositVerifier, _withdrawVerifier); + __ZetoFungibleWithdraw_init( + _depositVerifier, + _withdrawVerifier, + _batchWithdrawVerifier + ); verifier = _verifier; batchVerifier = _batchVerifier; + batchVerifier = _batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -72,7 +79,7 @@ contract Zeto_AnonEnc is uint256[2] memory ecdhPublicKey, uint256[] memory encryptedValues, uint256 size - ) internal returns (uint256[] memory publicInputs) { + ) internal pure returns (uint256[] memory publicInputs) { publicInputs = new uint256[](size); uint256 piIndex = 0; // copy the ecdh public key @@ -137,9 +144,9 @@ contract Zeto_AnonEnc is BATCH_INPUT_SIZE ); // construct the public inputs for batchVerifier - uint256[BATCH_INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[BATCH_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof using batchVerifier @@ -148,7 +155,7 @@ contract Zeto_AnonEnc is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); @@ -162,9 +169,9 @@ contract Zeto_AnonEnc is INPUT_SIZE ); // construct the public inputs for verifier - uint256[INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof require( @@ -172,7 +179,7 @@ contract Zeto_AnonEnc is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); diff --git a/solidity/contracts/zeto_anon_enc_nullifier.sol b/solidity/contracts/zeto_anon_enc_nullifier.sol index bcfeedf..3fc9fbf 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier.sol @@ -18,6 +18,7 @@ pragma solidity ^0.8.20; import {IZetoEncrypted} from "./lib/interfaces/izeto_encrypted.sol"; import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol"; +import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol"; import {Groth16Verifier_AnonEncNullifier} from "./lib/verifier_anon_enc_nullifier.sol"; import {Groth16Verifier_AnonEncNullifierBatch} from "./lib/verifier_anon_enc_nullifier_batch.sol"; import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; @@ -53,12 +54,14 @@ contract Zeto_AnonEncNullifier is Groth16Verifier_AnonEncNullifier _verifier, Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, - Groth16Verifier_AnonEncNullifierBatch _batchVerifier + Groth16Verifier_AnonEncNullifierBatch _batchVerifier, + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( _depositVerifier, - _withdrawVerifier + _withdrawVerifier, + _batchWithdrawVerifier ); verifier = _verifier; batchVerifier = _batchVerifier; @@ -74,7 +77,7 @@ contract Zeto_AnonEncNullifier is uint256[2] memory ecdhPublicKey, uint256[] memory encryptedValues, uint256 size - ) internal returns (uint256[] memory publicInputs) { + ) internal pure returns (uint256[] memory publicInputs) { publicInputs = new uint256[](size); uint256 piIndex = 0; // copy the ecdh public key @@ -154,9 +157,9 @@ contract Zeto_AnonEncNullifier is BATCH_INPUT_SIZE ); // construct the public inputs for batchVerifier - uint256[BATCH_INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[BATCH_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof using batchVerifier @@ -165,7 +168,7 @@ contract Zeto_AnonEncNullifier is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); @@ -180,9 +183,9 @@ contract Zeto_AnonEncNullifier is INPUT_SIZE ); // construct the public inputs for verifier - uint256[INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof require( @@ -190,7 +193,7 @@ contract Zeto_AnonEncNullifier is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); diff --git a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol index b130b64..cd20920 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol @@ -18,6 +18,7 @@ pragma solidity ^0.8.20; import {IZetoEncrypted} from "./lib/interfaces/izeto_encrypted.sol"; import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol"; +import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol"; import {Groth16Verifier_AnonEncNullifierKyc} from "./lib/verifier_anon_enc_nullifier_kyc.sol"; import {Groth16Verifier_AnonEncNullifierKycBatch} from "./lib/verifier_anon_enc_nullifier_kyc_batch.sol"; import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; @@ -54,13 +55,15 @@ contract Zeto_AnonEncNullifierKyc is Groth16Verifier_AnonEncNullifierKyc _verifier, Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, - Groth16Verifier_AnonEncNullifierKycBatch _batchVerifier + Groth16Verifier_AnonEncNullifierKycBatch _batchVerifier, + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier ) public initializer { __Registry_init(); __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( _depositVerifier, - _withdrawVerifier + _withdrawVerifier, + _batchWithdrawVerifier ); verifier = _verifier; batchVerifier = _batchVerifier; @@ -80,7 +83,7 @@ contract Zeto_AnonEncNullifierKyc is uint256[2] memory ecdhPublicKey, uint256[] memory encryptedValues, uint256 size - ) internal returns (uint256[] memory publicInputs) { + ) internal view returns (uint256[] memory publicInputs) { publicInputs = new uint256[](size); uint256 piIndex = 0; // copy the ecdh public key @@ -164,9 +167,9 @@ contract Zeto_AnonEncNullifierKyc is BATCH_INPUT_SIZE ); // construct the public inputs for batchVerifier - uint256[BATCH_INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[BATCH_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof using batchVerifier @@ -175,7 +178,7 @@ contract Zeto_AnonEncNullifierKyc is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); @@ -190,9 +193,9 @@ contract Zeto_AnonEncNullifierKyc is INPUT_SIZE ); // construct the public inputs for verifier - uint256[INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof require( @@ -200,7 +203,7 @@ contract Zeto_AnonEncNullifierKyc is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); diff --git a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol index 7940786..4a92501 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol @@ -18,6 +18,7 @@ pragma solidity ^0.8.20; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol"; +import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol"; import {Groth16Verifier_AnonEncNullifierNonRepudiation} from "./lib/verifier_anon_enc_nullifier_non_repudiation.sol"; import {Groth16Verifier_AnonEncNullifierNonRepudiationBatch} from "./lib/verifier_anon_enc_nullifier_non_repudiation_batch.sol"; import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; @@ -65,12 +66,14 @@ contract Zeto_AnonEncNullifierNonRepudiation is Groth16Verifier_AnonEncNullifierNonRepudiation _verifier, Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, - Groth16Verifier_AnonEncNullifierNonRepudiationBatch _batchVerifier + Groth16Verifier_AnonEncNullifierNonRepudiationBatch _batchVerifier, + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( _depositVerifier, - _withdrawVerifier + _withdrawVerifier, + _batchWithdrawVerifier ); verifier = _verifier; batchVerifier = _batchVerifier; @@ -95,7 +98,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is uint256[] memory encryptedValuesForReceiver, uint256[] memory encryptedValuesForAuthority, uint256 size - ) internal returns (uint256[] memory publicInputs) { + ) internal view returns (uint256[] memory publicInputs) { publicInputs = new uint256[](size); uint256 piIndex = 0; // copy the ecdh public key @@ -193,9 +196,9 @@ contract Zeto_AnonEncNullifierNonRepudiation is BATCH_INPUT_SIZE ); // construct the public inputs for batchVerifier - uint256[BATCH_INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[BATCH_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof using batchVerifier @@ -204,7 +207,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); @@ -224,9 +227,9 @@ contract Zeto_AnonEncNullifierNonRepudiation is INPUT_SIZE ); // construct the public inputs for verifier - uint256[INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof require( @@ -234,7 +237,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); diff --git a/solidity/contracts/zeto_anon_nullifier.sol b/solidity/contracts/zeto_anon_nullifier.sol index fa771d3..ada1d70 100644 --- a/solidity/contracts/zeto_anon_nullifier.sol +++ b/solidity/contracts/zeto_anon_nullifier.sol @@ -18,6 +18,7 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.sol"; import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol"; +import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol"; import {Groth16Verifier_AnonNullifier} from "./lib/verifier_anon_nullifier.sol"; import {Groth16Verifier_AnonNullifierBatch} from "./lib/verifier_anon_nullifier_batch.sol"; import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; @@ -56,12 +57,14 @@ contract Zeto_AnonNullifier is Groth16Verifier_AnonNullifier _verifier, Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, - Groth16Verifier_AnonNullifierBatch _batchVerifier + Groth16Verifier_AnonNullifierBatch _batchVerifier, + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( _depositVerifier, - _withdrawVerifier + _withdrawVerifier, + _batchWithdrawVerifier ); verifier = _verifier; batchVerifier = _batchVerifier; @@ -74,7 +77,7 @@ contract Zeto_AnonNullifier is uint256[] memory outputs, uint256 root, uint256 size - ) internal returns (uint256[] memory publicInputs) { + ) internal pure returns (uint256[] memory publicInputs) { publicInputs = new uint256[](size); uint256 piIndex = 0; // copy input commitments @@ -136,9 +139,9 @@ contract Zeto_AnonNullifier is BATCH_INPUT_SIZE ); // construct the public inputs for batchVerifier - uint256[BATCH_INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[BATCH_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof using batchVerifier @@ -147,7 +150,7 @@ contract Zeto_AnonNullifier is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); @@ -159,9 +162,9 @@ contract Zeto_AnonNullifier is INPUT_SIZE ); // construct the public inputs for verifier - uint256[INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof require( @@ -169,7 +172,7 @@ contract Zeto_AnonNullifier is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); diff --git a/solidity/contracts/zeto_anon_nullifier_kyc.sol b/solidity/contracts/zeto_anon_nullifier_kyc.sol index 46c5141..8dcaf43 100644 --- a/solidity/contracts/zeto_anon_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_nullifier_kyc.sol @@ -18,6 +18,8 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.sol"; import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol"; +import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol"; + import {Groth16Verifier_AnonNullifierKyc} from "./lib/verifier_anon_nullifier_kyc.sol"; import {Groth16Verifier_AnonNullifierKycBatch} from "./lib/verifier_anon_nullifier_kyc_batch.sol"; import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; @@ -57,13 +59,15 @@ contract Zeto_AnonNullifierKyc is Groth16Verifier_AnonNullifierKyc _verifier, Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, - Groth16Verifier_AnonNullifierKycBatch _batchVerifier + Groth16Verifier_AnonNullifierKycBatch _batchVerifier, + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier ) public initializer { __Registry_init(); __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( _depositVerifier, - _withdrawVerifier + _withdrawVerifier, + _batchWithdrawVerifier ); verifier = _verifier; batchVerifier = _batchVerifier; @@ -80,7 +84,7 @@ contract Zeto_AnonNullifierKyc is uint256[] memory outputs, uint256 root, uint256 size - ) internal returns (uint256[] memory publicInputs) { + ) internal view returns (uint256[] memory publicInputs) { publicInputs = new uint256[](size); uint256 piIndex = 0; // copy input commitments @@ -145,9 +149,9 @@ contract Zeto_AnonNullifierKyc is BATCH_INPUT_SIZE ); // construct the public inputs for batchVerifier - uint256[BATCH_INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[BATCH_INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof using batchVerifier @@ -156,7 +160,7 @@ contract Zeto_AnonNullifierKyc is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); @@ -168,9 +172,9 @@ contract Zeto_AnonNullifierKyc is INPUT_SIZE ); // construct the public inputs for verifier - uint256[INPUT_SIZE] memory fixedSizeInput; - for (uint256 i = 0; i < fixedSizeInput.length; i++) { - fixedSizeInput[i] = publicInputs[i]; + uint256[INPUT_SIZE] memory fixedSizeInputs; + for (uint256 i = 0; i < fixedSizeInputs.length; i++) { + fixedSizeInputs[i] = publicInputs[i]; } // Check the proof require( @@ -178,7 +182,7 @@ contract Zeto_AnonNullifierKyc is proof.pA, proof.pB, proof.pC, - fixedSizeInput + fixedSizeInputs ), "Invalid proof" ); diff --git a/solidity/ignition/modules/lib/deps.ts b/solidity/ignition/modules/lib/deps.ts index dff5af0..71df1eb 100644 --- a/solidity/ignition/modules/lib/deps.ts +++ b/solidity/ignition/modules/lib/deps.ts @@ -45,6 +45,13 @@ export const WithdrawNullifierVerifierModule = buildModule( return { verifier }; }, ); +export const BatchWithdrawNullifierVerifierModule = buildModule( + "Groth16Verifier_CheckNullifierValueBatch", + (m) => { + const verifier = m.contract("Groth16Verifier_CheckNullifierValueBatch", []); + return { verifier }; + }, +); export const WithdrawVerifierModule = buildModule( "Groth16Verifier_CheckInputsOutputsValue", @@ -53,6 +60,16 @@ export const WithdrawVerifierModule = buildModule( return { verifier }; }, ); +export const BatchWithdrawVerifierModule = buildModule( + "Groth16Verifier_CheckInputsOutputsValueBatch", + (m) => { + const verifier = m.contract( + "Groth16Verifier_CheckInputsOutputsValueBatch", + [], + ); + return { verifier }; + }, +); function PoseidonArtifact(param: number): Artifact { const abi = poseidonContract.generateABI(param); diff --git a/solidity/ignition/modules/zeto_anon.ts b/solidity/ignition/modules/zeto_anon.ts index 10ca1a0..65f2050 100644 --- a/solidity/ignition/modules/zeto_anon.ts +++ b/solidity/ignition/modules/zeto_anon.ts @@ -15,7 +15,11 @@ // limitations under the License. import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; -import { DepositVerifierModule, WithdrawVerifierModule } from "./lib/deps"; +import { + DepositVerifierModule, + WithdrawVerifierModule, + BatchWithdrawVerifierModule, +} from "./lib/deps"; const VerifierModule = buildModule("Groth16Verifier_Anon", (m) => { const verifier = m.contract("Groth16Verifier_Anon", []); @@ -32,6 +36,14 @@ export default buildModule("Zeto_Anon", (m) => { const { verifier: batchVerifier } = m.useModule(BatchVerifierModule); const { verifier: depositVerifier } = m.useModule(DepositVerifierModule); const { verifier: withdrawVerifier } = m.useModule(WithdrawVerifierModule); - - return { depositVerifier, withdrawVerifier, verifier, batchVerifier }; + const { verifier: batchWithdrawVerifier } = m.useModule( + BatchWithdrawVerifierModule, + ); + return { + depositVerifier, + withdrawVerifier, + verifier, + batchVerifier, + batchWithdrawVerifier, + }; }); diff --git a/solidity/ignition/modules/zeto_anon_enc.ts b/solidity/ignition/modules/zeto_anon_enc.ts index 85c1e26..7710ab2 100644 --- a/solidity/ignition/modules/zeto_anon_enc.ts +++ b/solidity/ignition/modules/zeto_anon_enc.ts @@ -15,7 +15,11 @@ // limitations under the License. import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; -import { DepositVerifierModule, WithdrawVerifierModule } from "./lib/deps"; +import { + DepositVerifierModule, + WithdrawVerifierModule, + BatchWithdrawVerifierModule, +} from "./lib/deps"; const VerifierModule = buildModule("Groth16Verifier_AnonEnc", (m) => { const verifier = m.contract("Groth16Verifier_AnonEnc", []); @@ -32,5 +36,14 @@ export default buildModule("Zeto_AnonEnc", (m) => { const { verifier: batchVerifier } = m.useModule(BatchVerifierModule); const { verifier: depositVerifier } = m.useModule(DepositVerifierModule); const { verifier: withdrawVerifier } = m.useModule(WithdrawVerifierModule); - return { depositVerifier, withdrawVerifier, verifier, batchVerifier }; + const { verifier: batchWithdrawVerifier } = m.useModule( + BatchWithdrawVerifierModule, + ); + return { + depositVerifier, + withdrawVerifier, + verifier, + batchVerifier, + batchWithdrawVerifier, + }; }); diff --git a/solidity/ignition/modules/zeto_anon_enc_nullifier.ts b/solidity/ignition/modules/zeto_anon_enc_nullifier.ts index 837df68..8b16995 100644 --- a/solidity/ignition/modules/zeto_anon_enc_nullifier.ts +++ b/solidity/ignition/modules/zeto_anon_enc_nullifier.ts @@ -19,6 +19,7 @@ import { SmtLibModule, DepositVerifierModule, WithdrawNullifierVerifierModule, + BatchWithdrawNullifierVerifierModule, } from "./lib/deps"; const VerifierModule = buildModule("Groth16Verifier_AnonEncNullifier", (m) => { @@ -42,12 +43,16 @@ export default buildModule("Zeto_AnonEncNullifier", (m) => { const { verifier: withdrawVerifier } = m.useModule( WithdrawNullifierVerifierModule, ); + const { verifier: batchWithdrawVerifier } = m.useModule( + BatchWithdrawNullifierVerifierModule, + ); return { depositVerifier, withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon3, }; diff --git a/solidity/ignition/modules/zeto_anon_enc_nullifier_kyc.ts b/solidity/ignition/modules/zeto_anon_enc_nullifier_kyc.ts index d6d8476..f422870 100644 --- a/solidity/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +++ b/solidity/ignition/modules/zeto_anon_enc_nullifier_kyc.ts @@ -19,6 +19,7 @@ import { SmtLibModule, DepositVerifierModule, WithdrawNullifierVerifierModule, + BatchWithdrawNullifierVerifierModule, } from "./lib/deps"; const VerifierModule = buildModule( @@ -45,12 +46,16 @@ export default buildModule("Zeto_AnonEncNullifierKyc", (m) => { const { verifier: withdrawVerifier } = m.useModule( WithdrawNullifierVerifierModule, ); + const { verifier: batchWithdrawVerifier } = m.useModule( + BatchWithdrawNullifierVerifierModule, + ); return { depositVerifier, withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon2, poseidon3, diff --git a/solidity/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts b/solidity/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts index 0c4a412..fbb3e30 100644 --- a/solidity/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +++ b/solidity/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts @@ -19,6 +19,7 @@ import { SmtLibModule, DepositVerifierModule, WithdrawNullifierVerifierModule, + BatchWithdrawNullifierVerifierModule, } from "./lib/deps"; const VerifierModule = buildModule( @@ -51,12 +52,15 @@ export default buildModule("Zeto_AnonEncNullifierNonRepudiation", (m) => { const { verifier: withdrawVerifier } = m.useModule( WithdrawNullifierVerifierModule, ); - + const { verifier: batchWithdrawVerifier } = m.useModule( + BatchWithdrawNullifierVerifierModule, + ); return { depositVerifier, withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon3, }; diff --git a/solidity/ignition/modules/zeto_anon_nullifier.ts b/solidity/ignition/modules/zeto_anon_nullifier.ts index ad2f1fb..d1aca6c 100644 --- a/solidity/ignition/modules/zeto_anon_nullifier.ts +++ b/solidity/ignition/modules/zeto_anon_nullifier.ts @@ -19,6 +19,7 @@ import { SmtLibModule, DepositVerifierModule, WithdrawNullifierVerifierModule, + BatchWithdrawNullifierVerifierModule, } from "./lib/deps"; const VerifierModule = buildModule("Groth16Verifier_AnonNullifier", (m) => { @@ -42,12 +43,16 @@ export default buildModule("Zeto_AnonNullifier", (m) => { const { verifier: withdrawVerifier } = m.useModule( WithdrawNullifierVerifierModule, ); + const { verifier: batchWithdrawVerifier } = m.useModule( + BatchWithdrawNullifierVerifierModule, + ); return { depositVerifier, withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon3, }; diff --git a/solidity/ignition/modules/zeto_anon_nullifier_kyc.ts b/solidity/ignition/modules/zeto_anon_nullifier_kyc.ts index c7cca0e..44be1b2 100644 --- a/solidity/ignition/modules/zeto_anon_nullifier_kyc.ts +++ b/solidity/ignition/modules/zeto_anon_nullifier_kyc.ts @@ -19,6 +19,7 @@ import { SmtLibModule, DepositVerifierModule, WithdrawNullifierVerifierModule, + BatchWithdrawNullifierVerifierModule, } from "./lib/deps"; const VerifierModule = buildModule("Groth16Verifier_AnonNullifierKyc", (m) => { @@ -42,12 +43,16 @@ export default buildModule("Zeto_AnonNullifierKyc", (m) => { const { verifier: withdrawVerifier } = m.useModule( WithdrawNullifierVerifierModule, ); + const { verifier: batchWithdrawVerifier } = m.useModule( + BatchWithdrawNullifierVerifierModule, + ); return { depositVerifier, withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon2, poseidon3, diff --git a/solidity/scripts/tokens/Zeto_Anon.ts b/solidity/scripts/tokens/Zeto_Anon.ts index cdfadab..500c0b5 100644 --- a/solidity/scripts/tokens/Zeto_Anon.ts +++ b/solidity/scripts/tokens/Zeto_Anon.ts @@ -20,8 +20,13 @@ import zetoModule from "../../ignition/modules/zeto_anon"; export async function deployDependencies() { const [deployer] = await ethers.getSigners(); - const { depositVerifier, withdrawVerifier, verifier, batchVerifier } = - await ignition.deploy(zetoModule); + const { + depositVerifier, + withdrawVerifier, + verifier, + batchVerifier, + batchWithdrawVerifier, + } = await ignition.deploy(zetoModule); return { deployer, args: [ @@ -30,6 +35,7 @@ export async function deployDependencies() { depositVerifier.target, withdrawVerifier.target, batchVerifier.target, + batchWithdrawVerifier.target, ], }; } diff --git a/solidity/scripts/tokens/Zeto_AnonEnc.ts b/solidity/scripts/tokens/Zeto_AnonEnc.ts index a89fb70..47cb0eb 100644 --- a/solidity/scripts/tokens/Zeto_AnonEnc.ts +++ b/solidity/scripts/tokens/Zeto_AnonEnc.ts @@ -20,8 +20,13 @@ import zetoModule from "../../ignition/modules/zeto_anon_enc"; export async function deployDependencies() { const [deployer] = await ethers.getSigners(); - const { depositVerifier, withdrawVerifier, verifier, batchVerifier } = - await ignition.deploy(zetoModule); + const { + depositVerifier, + withdrawVerifier, + verifier, + batchVerifier, + batchWithdrawVerifier, + } = await ignition.deploy(zetoModule); return { deployer, args: [ @@ -30,6 +35,7 @@ export async function deployDependencies() { depositVerifier.target, withdrawVerifier.target, batchVerifier.target, + batchWithdrawVerifier.target, ], }; } diff --git a/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts b/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts index 6d50ea9..e61c204 100644 --- a/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts +++ b/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts @@ -25,6 +25,7 @@ export async function deployDependencies() { withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon3, } = await ignition.deploy(zetoModule); @@ -36,6 +37,7 @@ export async function deployDependencies() { depositVerifier.target, withdrawVerifier.target, batchVerifier.target, + batchWithdrawVerifier.target, ], libraries: { SmtLib: smtLib.target, diff --git a/solidity/scripts/tokens/Zeto_AnonEncNullifierKyc.ts b/solidity/scripts/tokens/Zeto_AnonEncNullifierKyc.ts index ac876ab..fadbcf4 100644 --- a/solidity/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +++ b/solidity/scripts/tokens/Zeto_AnonEncNullifierKyc.ts @@ -25,6 +25,7 @@ export async function deployDependencies() { withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon2, poseidon3, @@ -37,6 +38,7 @@ export async function deployDependencies() { depositVerifier.target, withdrawVerifier.target, batchVerifier.target, + batchWithdrawVerifier.target, ], libraries: { SmtLib: smtLib.target, diff --git a/solidity/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts b/solidity/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts index dec2a96..a10a15f 100644 --- a/solidity/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +++ b/solidity/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts @@ -25,6 +25,7 @@ export async function deployDependencies() { withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon3, } = await ignition.deploy(zetoModule); @@ -36,6 +37,7 @@ export async function deployDependencies() { depositVerifier.target, withdrawVerifier.target, batchVerifier.target, + batchWithdrawVerifier.target, ], libraries: { SmtLib: smtLib.target, diff --git a/solidity/scripts/tokens/Zeto_AnonNullifier.ts b/solidity/scripts/tokens/Zeto_AnonNullifier.ts index e64650c..a02bce7 100644 --- a/solidity/scripts/tokens/Zeto_AnonNullifier.ts +++ b/solidity/scripts/tokens/Zeto_AnonNullifier.ts @@ -25,6 +25,7 @@ export async function deployDependencies() { withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon3, } = await ignition.deploy(zetoModule); @@ -36,6 +37,7 @@ export async function deployDependencies() { depositVerifier.target, withdrawVerifier.target, batchVerifier.target, + batchWithdrawVerifier.target, ], libraries: { SmtLib: smtLib.target, diff --git a/solidity/scripts/tokens/Zeto_AnonNullifierKyc.ts b/solidity/scripts/tokens/Zeto_AnonNullifierKyc.ts index 05e10b6..a299fa7 100644 --- a/solidity/scripts/tokens/Zeto_AnonNullifierKyc.ts +++ b/solidity/scripts/tokens/Zeto_AnonNullifierKyc.ts @@ -25,6 +25,7 @@ export async function deployDependencies() { withdrawVerifier, verifier, batchVerifier, + batchWithdrawVerifier, smtLib, poseidon2, poseidon3, @@ -37,6 +38,7 @@ export async function deployDependencies() { depositVerifier.target, withdrawVerifier.target, batchVerifier.target, + batchWithdrawVerifier.target, ], libraries: { SmtLib: smtLib.target, diff --git a/solidity/test/factory.ts b/solidity/test/factory.ts index 5f0cb50..e0c3b1d 100644 --- a/solidity/test/factory.ts +++ b/solidity/test/factory.ts @@ -18,7 +18,7 @@ import { ethers, network } from "hardhat"; import { Signer } from "ethers"; import { expect } from "chai"; -describe("Zeto based fungible token with anonymity without encryption or nullifier", function () { +describe("(factory) Zeto based fungible token with anonymity without encryption or nullifier", function () { let deployer: Signer; let nonOwner: Signer; @@ -41,6 +41,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi batchVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", withdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", + batchWithdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", }; await expect( factory.connect(nonOwner).registerImplementation("test", implInfo as any), @@ -60,6 +61,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi batchVerifier: "0x0000000000000000000000000000000000000000", depositVerifier: "0x0000000000000000000000000000000000000000", withdrawVerifier: "0x0000000000000000000000000000000000000000", + batchWithdrawVerifier: "0x0000000000000000000000000000000000000000", }; await expect( factory.connect(deployer).registerImplementation("test", implInfo as any), @@ -79,6 +81,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi batchVerifier: "0x0000000000000000000000000000000000000000", depositVerifier: "0x0000000000000000000000000000000000000000", withdrawVerifier: "0x0000000000000000000000000000000000000000", + batchWithdrawVerifier: "0x0000000000000000000000000000000000000000", }; await expect( factory.connect(deployer).registerImplementation("test", implInfo as any), @@ -98,6 +101,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi batchVerifier: "0x0000000000000000000000000000000000000000", depositVerifier: "0x0000000000000000000000000000000000000000", withdrawVerifier: "0x0000000000000000000000000000000000000000", + batchWithdrawVerifier: "0x0000000000000000000000000000000000000000", }; await expect( factory.connect(deployer).registerImplementation("test", implInfo as any), @@ -117,6 +121,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi batchVerifier: "0x0000000000000000000000000000000000000000", depositVerifier: "0x0000000000000000000000000000000000000000", withdrawVerifier: "0x0000000000000000000000000000000000000000", + batchWithdrawVerifier: "0x0000000000000000000000000000000000000000", }; const tx1 = await factory .connect(deployer) @@ -142,6 +147,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi batchVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", depositVerifier: "0x0000000000000000000000000000000000000000", withdrawVerifier: "0x0000000000000000000000000000000000000000", + batchWithdrawVerifier: "0x0000000000000000000000000000000000000000", }; const tx1 = await factory .connect(deployer) @@ -168,6 +174,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi batchVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", withdrawVerifier: "0x0000000000000000000000000000000000000000", + batchWithdrawVerifier: "0x0000000000000000000000000000000000000000", }; const tx1 = await factory .connect(deployer) @@ -181,6 +188,33 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi ).rejectedWith("Factory: withdrawVerifier address is required"); }); + it("attempting to deploy a fungible token but with a registered implementation that misses required batchWithdrawVerifier should fail", async function () { + // we want to test the effectiveness of the factory contract + // to create clones of the Zeto implementation contract + const Factory = await ethers.getContractFactory("ZetoTokenFactory"); + const factory = await Factory.deploy(); + await factory.waitForDeployment(); + + const implInfo = { + implementation: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", + verifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", + batchVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", + depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", + withdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", + batchWithdrawVerifier: "0x0000000000000000000000000000000000000000", + }; + const tx1 = await factory + .connect(deployer) + .registerImplementation("test", implInfo as any); + await tx1.wait(); + + await expect( + factory + .connect(deployer) + .deployZetoFungibleToken("test", await deployer.getAddress()), + ).rejectedWith("Factory: batchWithdrawVerifier address is required"); + }); + it("attempting to deploy a fungible token with a properly registered implementation should succeed", async function () { // we want to test the effectiveness of the factory contract // to create clones of the Zeto implementation contract @@ -194,6 +228,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi batchVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", withdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", + batchWithdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", }; const tx1 = await factory .connect(deployer) diff --git a/solidity/test/gas_cost/zeto_anon_enc_nullifier_kyc_cost_analysis.ts b/solidity/test/gas_cost/zeto_anon_enc_nullifier_kyc_cost_analysis.ts index de486ba..829999b 100644 --- a/solidity/test/gas_cost/zeto_anon_enc_nullifier_kyc_cost_analysis.ts +++ b/solidity/test/gas_cost/zeto_anon_enc_nullifier_kyc_cost_analysis.ts @@ -295,13 +295,14 @@ describe.skip("(Gas cost analysis) Zeto based fungible token with anonymity usin _outUtxos.push(_oUtox); unspentBobUTXOs.push(_oUtox); } else { - // _inUtxos.push(ZERO_UTXO); - // const inProof = await smtAlice.generateCircomVerifierProof( - // BigInt(0), - // utxosRoot - // ); - // _mtps.push(inProof.siblings.map((s) => s.bigInt())); - // _nullifiers.push(ZERO_UTXO); + _inUtxos.push(ZERO_UTXO); + _nullifiers.push(ZERO_UTXO); + const inProof = await smtAlice.generateCircomVerifierProof( + 0n, + utxosRoot, + ); + _outUtxos.push(ZERO_UTXO); + _mtps.push(inProof.siblings.map((s) => s.bigInt())); } } const owners = []; @@ -348,51 +349,68 @@ describe.skip("(Gas cost analysis) Zeto based fungible token with anonymity usin ); }).timeout(6000000000000); - it(`Bob withdraw ${TOTAL_AMOUNT} tokens`, async function () { + it(`Bob withdraw ${TOTAL_AMOUNT} tokens in ${transferCount} transactions`, async function () { const startingBalance = await erc20.balanceOf(Bob.ethAddress); const root = await smtBob.root(); let promises = []; - for (let i = 0; i < unspentBobUTXOs.length; i++) { - if (unspentBobUTXOs[i].value) { - promises.push( - (async () => { - const utxoToWithdraw = unspentBobUTXOs[i]; - const nullifier1 = newNullifier(utxoToWithdraw, Bob); - - const proof1 = await smtBob.generateCircomVerifierProof( - utxoToWithdraw.hash, - root, - ); - const proof2 = await smtBob.generateCircomVerifierProof(0n, root); - const merkleProofs = [ - proof1.siblings.map((s) => s.bigInt()), - proof2.siblings.map((s) => s.bigInt()), - ]; - const { nullifiers, outputCommitments, encodedProof } = - await prepareNullifierWithdrawProof( - Bob, - [utxoToWithdraw, ZERO_UTXO], - [nullifier1, ZERO_UTXO], - newUTXO(0, Bob), - root.bigInt(), - merkleProofs, + for (let i = 0; i < transferCount; i++) { + promises.push( + (async () => { + const _inUtxos = []; + const _mtps = []; + const _nullifiers = []; + let amount = 0; + for (let j = 0; j < UTXO_PER_TX; j++) { + if ( + i !== transferCount - 1 || + unspentBobUTXOs.length % UTXO_PER_TX === 0 || + j < unspentBobUTXOs.length % UTXO_PER_TX + ) { + amount++; + const _iUtxo = unspentBobUTXOs[i * UTXO_PER_TX + j]; + _inUtxos.push(_iUtxo); + _nullifiers.push(newNullifier(_iUtxo, Bob)); + // Alice generates inclusion proofs for the UTXOs to be spent + const inProof = await smtBob.generateCircomVerifierProof( + _iUtxo.hash, + root, ); - - // Bob withdraws UTXOs to ERC20 tokens - await doWithdraw( - zeto, - Bob.signer, - 1, - nullifiers, - outputCommitments[0], + _mtps.push(inProof.siblings.map((s) => s.bigInt())); + } else { + _inUtxos.push(ZERO_UTXO); + _nullifiers.push(ZERO_UTXO); + const inProof = await smtBob.generateCircomVerifierProof( + 0n, + root, + ); + _mtps.push(inProof.siblings.map((s) => s.bigInt())); + } + } + const { nullifiers, outputCommitments, encodedProof } = + await prepareNullifierWithdrawProof( + Bob, + _inUtxos, + _nullifiers, + ZERO_UTXO, root.bigInt(), - encodedProof, - withdrawGasCostHistory, + _mtps, ); - })(), - ); - } + + // Bob withdraws UTXOs to ERC20 tokens + await doWithdraw( + zeto, + Bob.signer, + amount, + nullifiers, + outputCommitments[0], + root.bigInt(), + encodedProof, + withdrawGasCostHistory, + ); + })(), + ); + // If we reach the concurrency limit, wait for the current batch to finish if (promises.length >= TX_CONCURRENCY) { await Promise.all(promises); @@ -556,8 +574,8 @@ describe.skip("(Gas cost analysis) Zeto based fungible token with anonymity usin const encodedProof = encodeProof(proof); const encryptedValues = isBatch - ? publicSignals.slice(0, 22) - : publicSignals.slice(0, 7); + ? publicSignals.slice(2, 42) + : publicSignals.slice(2, 10); return { inputCommitments, outputCommitments, diff --git a/solidity/test/lib/deploy.ts b/solidity/test/lib/deploy.ts index ef0da82..3d58987 100644 --- a/solidity/test/lib/deploy.ts +++ b/solidity/test/lib/deploy.ts @@ -55,6 +55,7 @@ export async function deployZeto(tokenName: string) { depositVerifier, withdrawVerifier, batchVerifier, + batchWithdrawVerifier, ] = args; // we want to test the effectiveness of the factory contract @@ -72,6 +73,8 @@ export async function deployZeto(tokenName: string) { verifier, batchVerifier: batchVerifier || "0x0000000000000000000000000000000000000000", + batchWithdrawVerifier: + batchWithdrawVerifier || "0x0000000000000000000000000000000000000000", }; // console.log(implInfo); const tx1 = await factory diff --git a/solidity/test/utils.ts b/solidity/test/utils.ts index 300659f..0000ce5 100644 --- a/solidity/test/utils.ts +++ b/solidity/test/utils.ts @@ -89,10 +89,9 @@ export async function prepareNullifierWithdrawProof( root: BigInt, merkleProof: BigInt[][], ) { - const nullifiers = _nullifiers.map((nullifier) => nullifier.hash) as [ - BigNumberish, - BigNumberish, - ]; + const nullifiers = _nullifiers.map( + (nullifier) => nullifier.hash, + ) as BigNumberish[]; const inputCommitments: BigNumberish[] = inputs.map( (input) => input.hash, ) as BigNumberish[]; @@ -111,15 +110,19 @@ export async function prepareNullifierWithdrawProof( inputSalts, inputOwnerPrivateKey: signer.formattedPrivateKey, root, - enabled: [nullifiers[0] !== 0n ? 1 : 0, nullifiers[1] !== 0n ? 1 : 0], + enabled: nullifiers.map((n) => (n !== 0n ? 1 : 0)), merkleProof, outputCommitments, outputValues, - outputSalts: [output.salt], + outputSalts: [output.salt || 0n], outputOwnerPublicKeys, }; - const circuit = await loadCircuit("check_nullifier_value"); - const { provingKeyFile } = loadProvingKeys("check_nullifier_value"); + let circuit = await loadCircuit("check_nullifier_value"); + let { provingKeyFile } = loadProvingKeys("check_nullifier_value"); + if (inputCommitments.length > 2) { + circuit = await loadCircuit("check_nullifier_value_batch"); + ({ provingKeyFile } = loadProvingKeys("check_nullifier_value_batch")); + } const startWitnessCalculation = Date.now(); const witness = await circuit.calculateWTNSBin(inputObj, true); @@ -167,11 +170,16 @@ export async function prepareWithdrawProof( inputOwnerPrivateKey: signer.formattedPrivateKey, outputCommitments, outputValues, - outputSalts: [output.salt], + outputSalts: [output.salt || 0n], outputOwnerPublicKeys, }; - const circuit = await loadCircuit("check_inputs_outputs_value"); - const { provingKeyFile } = loadProvingKeys("check_inputs_outputs_value"); + + let circuit = await loadCircuit("check_inputs_outputs_value"); + let { provingKeyFile } = loadProvingKeys("check_inputs_outputs_value"); + if (inputCommitments.length > 2) { + circuit = await loadCircuit("check_inputs_outputs_value_batch"); + ({ provingKeyFile } = loadProvingKeys("check_inputs_outputs_value_batch")); + } const startWitnessCalculation = Date.now(); const witness = await circuit.calculateWTNSBin(inputObj, true); diff --git a/solidity/test/zeto_anon.ts b/solidity/test/zeto_anon.ts index f309be4..bd88cb9 100644 --- a/solidity/test/zeto_anon.ts +++ b/solidity/test/zeto_anon.ts @@ -76,7 +76,7 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi ({ provingKeyFile: batchProvingKey } = loadProvingKeys("anon_batch")); }); - it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob and Charlie should succeed", async function () { + it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob & Charlie then withdraw should succeed", async function () { // first mint the tokens for batch testing const inputUtxos = []; for (let i = 0; i < 10; i++) { @@ -85,12 +85,17 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi } await doMint(zeto, deployer, inputUtxos); - // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 1 utxo to alice - const _bOut1 = newUTXO(8, Bob); + const aliceUTXOsToBeWithdrawn = [ + newUTXO(1, Alice), + newUTXO(1, Alice), + newUTXO(1, Alice), + ]; + // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice + const _bOut1 = newUTXO(6, Bob); const _bOut2 = newUTXO(1, Charlie); - const _bOut3 = newUTXO(1, Alice); - const outputUtxos = [_bOut1, _bOut2, _bOut3]; - const outputOwners = [Bob, Charlie, Alice]; + + const outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn]; + const outputOwners = [Bob, Charlie, Alice, Alice, Alice]; const inflatedOutputUtxos = [...outputUtxos]; const inflatedOutputOwners = [...outputOwners]; for (let i = 0; i < 10 - outputUtxos.length; i++) { @@ -126,6 +131,32 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi for (let i = outputUtxos.length; i < 10; i++) { expect(incomingUTXOs[i]).to.equal(0); } + + // mint sufficient balance in Zeto contract address for Alice to withdraw + const mintTx = await erc20.connect(deployer).mint(zeto, 3); + await mintTx.wait(); + const startingBalance = await erc20.balanceOf(Alice.ethAddress); + + // Alice generates the nullifiers for the UTXOs to be spent + const inflatedWithdrawInputs = [...aliceUTXOsToBeWithdrawn]; + + // Alice generates inclusion proofs for the UTXOs to be spent + + for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) { + inflatedWithdrawInputs.push(ZERO_UTXO); + } + const { inputCommitments, outputCommitments, encodedProof } = + await prepareWithdrawProof(Alice, inflatedWithdrawInputs, ZERO_UTXO); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto + .connect(Alice.signer) + .withdraw(3, inputCommitments, outputCommitments[0], encodedProof); + await tx.wait(); + + // Alice checks her ERC20 balance + const endingBalance = await erc20.balanceOf(Alice.ethAddress); + expect(endingBalance - startingBalance).to.be.equal(3); }); it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { diff --git a/solidity/test/zeto_anon_enc.ts b/solidity/test/zeto_anon_enc.ts index 7cad834..3379923 100644 --- a/solidity/test/zeto_anon_enc.ts +++ b/solidity/test/zeto_anon_enc.ts @@ -83,7 +83,7 @@ describe("Zeto based fungible token with anonymity and encryption", function () ({ provingKeyFile: batchProvingKey } = loadProvingKeys("anon_enc_batch")); }); - it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob and Charlie should succeed", async function () { + it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob & Charlie then withdraw should succeed", async function () { // first mint the tokens for batch testing const inputUtxos = []; for (let i = 0; i < 10; i++) { @@ -92,12 +92,17 @@ describe("Zeto based fungible token with anonymity and encryption", function () } await doMint(zeto, deployer, inputUtxos); - // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 1 utxo to alice - const _bOut1 = newUTXO(8, Bob); + const aliceUTXOsToBeWithdrawn = [ + newUTXO(1, Alice), + newUTXO(1, Alice), + newUTXO(1, Alice), + ]; + // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice + const _bOut1 = newUTXO(6, Bob); const _bOut2 = newUTXO(1, Charlie); - const _bOut3 = newUTXO(1, Alice); - const outputUtxos = [_bOut1, _bOut2, _bOut3]; - const outputOwners = [Bob, Charlie, Alice]; + + const outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn]; + const outputOwners = [Bob, Charlie, Alice, Alice, Alice]; const inflatedOutputUtxos = [...outputUtxos]; const inflatedOutputOwners = [...outputOwners]; for (let i = 0; i < 10 - outputUtxos.length; i++) { @@ -147,6 +152,32 @@ describe("Zeto based fungible token with anonymity and encryption", function () for (let i = outputUtxos.length; i < 10; i++) { expect(incomingUTXOs[i]).to.equal(0); } + + // mint sufficient balance in Zeto contract address for Alice to withdraw + const mintTx = await erc20.connect(deployer).mint(zeto, 3); + await mintTx.wait(); + const startingBalance = await erc20.balanceOf(Alice.ethAddress); + + // Alice generates the nullifiers for the UTXOs to be spent + const inflatedWithdrawInputs = [...aliceUTXOsToBeWithdrawn]; + + // Alice generates inclusion proofs for the UTXOs to be spent + + for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) { + inflatedWithdrawInputs.push(ZERO_UTXO); + } + const { inputCommitments, outputCommitments, encodedProof } = + await prepareWithdrawProof(Alice, inflatedWithdrawInputs, ZERO_UTXO); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto + .connect(Alice.signer) + .withdraw(3, inputCommitments, outputCommitments[0], encodedProof); + await tx.wait(); + + // Alice checks her ERC20 balance + const endingBalance = await erc20.balanceOf(Alice.ethAddress); + expect(endingBalance - startingBalance).to.be.equal(3); }); it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { diff --git a/solidity/test/zeto_anon_enc_nullifier.ts b/solidity/test/zeto_anon_enc_nullifier.ts index 390d143..5e431cc 100644 --- a/solidity/test/zeto_anon_enc_nullifier.ts +++ b/solidity/test/zeto_anon_enc_nullifier.ts @@ -102,7 +102,7 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti expect(root.string()).to.equal(onchainRoot.toString()); }); - it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob and Charlie should succeed", async function () { + it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob & Charlie then withdraw should succeed", async function () { // first mint the tokens for batch testing const inputUtxos = []; const nullifiers = []; @@ -133,12 +133,17 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti mtps.push(p.siblings.map((s) => s.bigInt())); } - // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 1 utxo to alice - const _bOut1 = newUTXO(8, Bob); + const aliceUTXOsToBeWithdrawn = [ + newUTXO(1, Alice), + newUTXO(1, Alice), + newUTXO(1, Alice), + ]; + // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice + const _bOut1 = newUTXO(6, Bob); const _bOut2 = newUTXO(1, Charlie); - const _bOut3 = newUTXO(1, Alice); - const outputUtxos = [_bOut1, _bOut2, _bOut3]; - const outputOwners = [Bob, Charlie, Alice]; + + const outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn]; + const outputOwners = [Bob, Charlie, Alice, Alice, Alice]; const inflatedOutputUtxos = [...outputUtxos]; const inflatedOutputOwners = [...outputOwners]; for (let i = 0; i < 10 - outputUtxos.length; i++) { @@ -196,6 +201,67 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti for (let i = outputUtxos.length; i < 10; i++) { expect(incomingUTXOs[i]).to.equal(0); } + + // mint sufficient balance in Zeto contract address for Alice to withdraw + const mintTx = await erc20.connect(deployer).mint(zeto, 3); + await mintTx.wait(); + const startingBalance = await erc20.balanceOf(Alice.ethAddress); + + // Alice generates the nullifiers for the UTXOs to be spent + root = await smtAlice.root(); + const inflatedWithdrawNullifiers = []; + const inflatedWithdrawInputs = []; + const inflatedWithdrawMTPs = []; + for (let i = 0; i < aliceUTXOsToBeWithdrawn.length; i++) { + inflatedWithdrawInputs.push(aliceUTXOsToBeWithdrawn[i]); + inflatedWithdrawNullifiers.push( + newNullifier(aliceUTXOsToBeWithdrawn[i], Alice), + ); + const _withdrawUTXOProof = await smtAlice.generateCircomVerifierProof( + aliceUTXOsToBeWithdrawn[i].hash, + root, + ); + inflatedWithdrawMTPs.push( + _withdrawUTXOProof.siblings.map((s) => s.bigInt()), + ); + } + // Alice generates inclusion proofs for the UTXOs to be spent + + for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) { + inflatedWithdrawInputs.push(ZERO_UTXO); + inflatedWithdrawNullifiers.push(ZERO_UTXO); + const _zeroProof = await smtAlice.generateCircomVerifierProof(0n, root); + inflatedWithdrawMTPs.push(_zeroProof.siblings.map((s) => s.bigInt())); + } + + const { + nullifiers: _withdrawNullifiers, + outputCommitments: withdrawCommitments, + encodedProof: withdrawEncodedProof, + } = await prepareNullifierWithdrawProof( + Alice, + inflatedWithdrawInputs, + inflatedWithdrawNullifiers, + ZERO_UTXO, + root.bigInt(), + inflatedWithdrawMTPs, + ); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto + .connect(Alice.signer) + .withdraw( + 3, + _withdrawNullifiers, + withdrawCommitments[0], + root.bigInt(), + withdrawEncodedProof, + ); + await tx.wait(); + + // Alice checks her ERC20 balance + const endingBalance = await erc20.balanceOf(Alice.ethAddress); + expect(endingBalance - startingBalance).to.be.equal(3); }); it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { diff --git a/solidity/test/zeto_anon_enc_nullifier_kyc.ts b/solidity/test/zeto_anon_enc_nullifier_kyc.ts index f08aeb0..74e7afa 100644 --- a/solidity/test/zeto_anon_enc_nullifier_kyc.ts +++ b/solidity/test/zeto_anon_enc_nullifier_kyc.ts @@ -133,7 +133,7 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti expect(root.string()).to.equal(onchainRoot.toString()); }); - it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob and Charlie should succeed", async function () { + it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob & Charlie then withdraw should succeed", async function () { // first mint the tokens for batch testing const inputUtxos = []; const nullifiers = []; @@ -183,18 +183,24 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti identitiesRoot, ); const charlieProof = cProof.siblings.map((s) => s.bigInt()); - - // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 1 utxo to alice - const _bOut1 = newUTXO(8, Bob); + const aliceUTXOsToBeWithdrawn = [ + newUTXO(1, Alice), + newUTXO(1, Alice), + newUTXO(1, Alice), + ]; + // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice + const _bOut1 = newUTXO(6, Bob); const _bOut2 = newUTXO(1, Charlie); - const _bOut3 = newUTXO(1, Alice); - const outputUtxos = [_bOut1, _bOut2, _bOut3]; - const outputOwners = [Bob, Charlie, Alice]; + + const outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn]; + const outputOwners = [Bob, Charlie, Alice, Alice, Alice]; const identityMerkleProofs = [ aliceProof, bobProof, charlieProof, aliceProof, + aliceProof, + aliceProof, ]; const inflatedOutputUtxos = [...outputUtxos]; const inflatedOutputOwners = [...outputOwners]; @@ -256,6 +262,67 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti for (let i = outputUtxos.length; i < 10; i++) { expect(incomingUTXOs[i]).to.equal(0); } + + // mint sufficient balance in Zeto contract address for Alice to withdraw + const mintTx = await erc20.connect(deployer).mint(zeto, 3); + await mintTx.wait(); + const startingBalance = await erc20.balanceOf(Alice.ethAddress); + + // Alice generates the nullifiers for the UTXOs to be spent + root = await smtAlice.root(); + const inflatedWithdrawNullifiers = []; + const inflatedWithdrawInputs = []; + const inflatedWithdrawMTPs = []; + for (let i = 0; i < aliceUTXOsToBeWithdrawn.length; i++) { + inflatedWithdrawInputs.push(aliceUTXOsToBeWithdrawn[i]); + inflatedWithdrawNullifiers.push( + newNullifier(aliceUTXOsToBeWithdrawn[i], Alice), + ); + const _withdrawUTXOProof = await smtAlice.generateCircomVerifierProof( + aliceUTXOsToBeWithdrawn[i].hash, + root, + ); + inflatedWithdrawMTPs.push( + _withdrawUTXOProof.siblings.map((s) => s.bigInt()), + ); + } + // Alice generates inclusion proofs for the UTXOs to be spent + + for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) { + inflatedWithdrawInputs.push(ZERO_UTXO); + inflatedWithdrawNullifiers.push(ZERO_UTXO); + const _zeroProof = await smtAlice.generateCircomVerifierProof(0n, root); + inflatedWithdrawMTPs.push(_zeroProof.siblings.map((s) => s.bigInt())); + } + + const { + nullifiers: _withdrawNullifiers, + outputCommitments: withdrawCommitments, + encodedProof: withdrawEncodedProof, + } = await prepareNullifierWithdrawProof( + Alice, + inflatedWithdrawInputs, + inflatedWithdrawNullifiers, + ZERO_UTXO, + root.bigInt(), + inflatedWithdrawMTPs, + ); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto + .connect(Alice.signer) + .withdraw( + 3, + _withdrawNullifiers, + withdrawCommitments[0], + root.bigInt(), + withdrawEncodedProof, + ); + await tx.wait(); + + // Alice checks her ERC20 balance + const endingBalance = await erc20.balanceOf(Alice.ethAddress); + expect(endingBalance - startingBalance).to.be.equal(3); }); it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { diff --git a/solidity/test/zeto_anon_enc_nullifier_non_repudiation.ts b/solidity/test/zeto_anon_enc_nullifier_non_repudiation.ts index 5c235f3..6fd6e2d 100644 --- a/solidity/test/zeto_anon_enc_nullifier_non_repudiation.ts +++ b/solidity/test/zeto_anon_enc_nullifier_non_repudiation.ts @@ -115,7 +115,7 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti expect(root.string()).to.equal(onchainRoot.toString()); }); - it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob and Charlie should succeed", async function () { + it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob & Charlie then withdraw should succeed", async function () { // first mint the tokens for batch testing const inputUtxos = []; const nullifiers = []; @@ -146,12 +146,17 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti mtps.push(p.siblings.map((s) => s.bigInt())); } - // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 1 utxo to alice - const _bOut1 = newUTXO(8, Bob); + const aliceUTXOsToBeWithdrawn = [ + newUTXO(1, Alice), + newUTXO(1, Alice), + newUTXO(1, Alice), + ]; + // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice + const _bOut1 = newUTXO(6, Bob); const _bOut2 = newUTXO(1, Charlie); - const _bOut3 = newUTXO(1, Alice); - const outputUtxos = [_bOut1, _bOut2, _bOut3]; - const outputOwners = [Bob, Charlie, Alice]; + + const outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn]; + const outputOwners = [Bob, Charlie, Alice, Alice, Alice]; const inflatedOutputUtxos = [...outputUtxos]; const inflatedOutputOwners = [...outputOwners]; for (let i = 0; i < 10 - outputUtxos.length; i++) { @@ -251,6 +256,67 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti expect(auditPlainText[2 * i + 42]).to.equal(0); expect(auditPlainText[2 * i + 43]).to.equal(0); } + + // mint sufficient balance in Zeto contract address for Alice to withdraw + const mintTx = await erc20.connect(deployer).mint(zeto, 3); + await mintTx.wait(); + const startingBalance = await erc20.balanceOf(Alice.ethAddress); + + // Alice generates the nullifiers for the UTXOs to be spent + root = await smtAlice.root(); + const inflatedWithdrawNullifiers = []; + const inflatedWithdrawInputs = []; + const inflatedWithdrawMTPs = []; + for (let i = 0; i < aliceUTXOsToBeWithdrawn.length; i++) { + inflatedWithdrawInputs.push(aliceUTXOsToBeWithdrawn[i]); + inflatedWithdrawNullifiers.push( + newNullifier(aliceUTXOsToBeWithdrawn[i], Alice), + ); + const _withdrawUTXOProof = await smtAlice.generateCircomVerifierProof( + aliceUTXOsToBeWithdrawn[i].hash, + root, + ); + inflatedWithdrawMTPs.push( + _withdrawUTXOProof.siblings.map((s) => s.bigInt()), + ); + } + // Alice generates inclusion proofs for the UTXOs to be spent + + for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) { + inflatedWithdrawInputs.push(ZERO_UTXO); + inflatedWithdrawNullifiers.push(ZERO_UTXO); + const _zeroProof = await smtAlice.generateCircomVerifierProof(0n, root); + inflatedWithdrawMTPs.push(_zeroProof.siblings.map((s) => s.bigInt())); + } + + const { + nullifiers: _withdrawNullifiers, + outputCommitments: withdrawCommitments, + encodedProof: withdrawEncodedProof, + } = await prepareNullifierWithdrawProof( + Alice, + inflatedWithdrawInputs, + inflatedWithdrawNullifiers, + ZERO_UTXO, + root.bigInt(), + inflatedWithdrawMTPs, + ); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto + .connect(Alice.signer) + .withdraw( + 3, + _withdrawNullifiers, + withdrawCommitments[0], + root.bigInt(), + withdrawEncodedProof, + ); + await tx.wait(); + + // Alice checks her ERC20 balance + const endingBalance = await erc20.balanceOf(Alice.ethAddress); + expect(endingBalance - startingBalance).to.be.equal(3); }); it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { diff --git a/solidity/test/zeto_anon_nullifier.ts b/solidity/test/zeto_anon_nullifier.ts index 3c99cd8..cd1fc4a 100644 --- a/solidity/test/zeto_anon_nullifier.ts +++ b/solidity/test/zeto_anon_nullifier.ts @@ -89,7 +89,7 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr expect(root.string()).to.equal(onchainRoot.toString()); }); - it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob and Charlie should succeed", async function () { + it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob & Charlie then withdraw should succeed", async function () { // first mint the tokens for batch testing const inputUtxos = []; const nullifiers = []; @@ -119,13 +119,17 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr ); mtps.push(p.siblings.map((s) => s.bigInt())); } - - // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 1 utxo to alice - const _bOut1 = newUTXO(8, Bob); + const aliceUTXOsToBeWithdrawn = [ + newUTXO(1, Alice), + newUTXO(1, Alice), + newUTXO(1, Alice), + ]; + // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice + const _bOut1 = newUTXO(6, Bob); const _bOut2 = newUTXO(1, Charlie); - const _bOut3 = newUTXO(1, Alice); - const outputUtxos = [_bOut1, _bOut2, _bOut3]; - const outputOwners = [Bob, Charlie, Alice]; + + const outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn]; + const outputOwners = [Bob, Charlie, Alice, Alice, Alice]; const inflatedOutputUtxos = [...outputUtxos]; const inflatedOutputOwners = [...outputOwners]; for (let i = 0; i < 10 - outputUtxos.length; i++) { @@ -169,6 +173,67 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr for (let i = outputUtxos.length; i < 10; i++) { expect(incomingUTXOs[i]).to.equal(0); } + + // mint sufficient balance in Zeto contract address for Alice to withdraw + const mintTx = await erc20.connect(deployer).mint(zeto, 3); + await mintTx.wait(); + const startingBalance = await erc20.balanceOf(Alice.ethAddress); + + // Alice generates the nullifiers for the UTXOs to be spent + root = await smtAlice.root(); + const inflatedWithdrawNullifiers = []; + const inflatedWithdrawInputs = []; + const inflatedWithdrawMTPs = []; + for (let i = 0; i < aliceUTXOsToBeWithdrawn.length; i++) { + inflatedWithdrawInputs.push(aliceUTXOsToBeWithdrawn[i]); + inflatedWithdrawNullifiers.push( + newNullifier(aliceUTXOsToBeWithdrawn[i], Alice), + ); + const _withdrawUTXOProof = await smtAlice.generateCircomVerifierProof( + aliceUTXOsToBeWithdrawn[i].hash, + root, + ); + inflatedWithdrawMTPs.push( + _withdrawUTXOProof.siblings.map((s) => s.bigInt()), + ); + } + // Alice generates inclusion proofs for the UTXOs to be spent + + for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) { + inflatedWithdrawInputs.push(ZERO_UTXO); + inflatedWithdrawNullifiers.push(ZERO_UTXO); + const _zeroProof = await smtAlice.generateCircomVerifierProof(0n, root); + inflatedWithdrawMTPs.push(_zeroProof.siblings.map((s) => s.bigInt())); + } + + const { + nullifiers: _withdrawNullifiers, + outputCommitments: withdrawCommitments, + encodedProof: withdrawEncodedProof, + } = await prepareNullifierWithdrawProof( + Alice, + inflatedWithdrawInputs, + inflatedWithdrawNullifiers, + ZERO_UTXO, + root.bigInt(), + inflatedWithdrawMTPs, + ); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto + .connect(Alice.signer) + .withdraw( + 3, + _withdrawNullifiers, + withdrawCommitments[0], + root.bigInt(), + withdrawEncodedProof, + ); + await tx.wait(); + + // Alice checks her ERC20 balance + const endingBalance = await erc20.balanceOf(Alice.ethAddress); + expect(endingBalance - startingBalance).to.be.equal(3); }); it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { diff --git a/solidity/test/zeto_anon_nullifier_kyc.ts b/solidity/test/zeto_anon_nullifier_kyc.ts index 9198c7f..e5e1e2a 100644 --- a/solidity/test/zeto_anon_nullifier_kyc.ts +++ b/solidity/test/zeto_anon_nullifier_kyc.ts @@ -117,7 +117,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou expect(root.string()).to.equal(onchainRoot.toString()); }); - it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob and Charlie should succeed", async function () { + it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob & Charlie then withdraw should succeed", async function () { // first mint the tokens for batch testing const inputUtxos = []; const nullifiers = []; @@ -167,17 +167,24 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou identitiesRoot, ); const charlieProof = cProof.siblings.map((s) => s.bigInt()); - // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 1 utxo to alice - const _bOut1 = newUTXO(8, Bob); + const aliceUTXOsToBeWithdrawn = [ + newUTXO(1, Alice), + newUTXO(1, Alice), + newUTXO(1, Alice), + ]; + // Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice + const _bOut1 = newUTXO(6, Bob); const _bOut2 = newUTXO(1, Charlie); - const _bOut3 = newUTXO(1, Alice); - const outputUtxos = [_bOut1, _bOut2, _bOut3]; - const outputOwners = [Bob, Charlie, Alice]; + + const outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn]; + const outputOwners = [Bob, Charlie, Alice, Alice, Alice]; const identityMerkleProofs = [ aliceProof, bobProof, charlieProof, aliceProof, + aliceProof, + aliceProof, ]; const inflatedOutputUtxos = [...outputUtxos]; const inflatedOutputOwners = [...outputOwners]; @@ -227,6 +234,67 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou for (let i = outputUtxos.length; i < 10; i++) { expect(incomingUTXOs[i]).to.equal(0); } + + // mint sufficient balance in Zeto contract address for Alice to withdraw + const mintTx = await erc20.connect(deployer).mint(zeto, 3); + await mintTx.wait(); + const startingBalance = await erc20.balanceOf(Alice.ethAddress); + + // Alice generates the nullifiers for the UTXOs to be spent + root = await smtAlice.root(); + const inflatedWithdrawNullifiers = []; + const inflatedWithdrawInputs = []; + const inflatedWithdrawMTPs = []; + for (let i = 0; i < aliceUTXOsToBeWithdrawn.length; i++) { + inflatedWithdrawInputs.push(aliceUTXOsToBeWithdrawn[i]); + inflatedWithdrawNullifiers.push( + newNullifier(aliceUTXOsToBeWithdrawn[i], Alice), + ); + const _withdrawUTXOProof = await smtAlice.generateCircomVerifierProof( + aliceUTXOsToBeWithdrawn[i].hash, + root, + ); + inflatedWithdrawMTPs.push( + _withdrawUTXOProof.siblings.map((s) => s.bigInt()), + ); + } + // Alice generates inclusion proofs for the UTXOs to be spent + + for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) { + inflatedWithdrawInputs.push(ZERO_UTXO); + inflatedWithdrawNullifiers.push(ZERO_UTXO); + const _zeroProof = await smtAlice.generateCircomVerifierProof(0n, root); + inflatedWithdrawMTPs.push(_zeroProof.siblings.map((s) => s.bigInt())); + } + + const { + nullifiers: _withdrawNullifiers, + outputCommitments: withdrawCommitments, + encodedProof: withdrawEncodedProof, + } = await prepareNullifierWithdrawProof( + Alice, + inflatedWithdrawInputs, + inflatedWithdrawNullifiers, + ZERO_UTXO, + root.bigInt(), + inflatedWithdrawMTPs, + ); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto + .connect(Alice.signer) + .withdraw( + 3, + _withdrawNullifiers, + withdrawCommitments[0], + root.bigInt(), + withdrawEncodedProof, + ); + await tx.wait(); + + // Alice checks her ERC20 balance + const endingBalance = await erc20.balanceOf(Alice.ethAddress); + expect(endingBalance - startingBalance).to.be.equal(3); }); it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { diff --git a/zkp/circuits/check_inputs_outputs_value.circom b/zkp/circuits/check_inputs_outputs_value.circom index c9bdab7..4be5a7f 100644 --- a/zkp/circuits/check_inputs_outputs_value.circom +++ b/zkp/circuits/check_inputs_outputs_value.circom @@ -15,68 +15,6 @@ // limitations under the License. pragma circom 2.1.4; -include "./lib/check-positive.circom"; -include "./lib/check-hashes.circom"; -include "./node_modules/circomlib/circuits/babyjub.circom"; - -template Zeto(numInputs, numOutputs) { - signal input inputCommitments[numInputs]; - signal input inputValues[numInputs]; - signal input inputSalts[numInputs]; - // must be properly hashed and trimmed to be compatible with the BabyJub curve. - // Reference: https://github.com/iden3/circomlib/blob/master/test/babyjub.js#L103 - signal input inputOwnerPrivateKey; - signal input outputCommitments[numOutputs]; - signal input outputValues[numOutputs]; - signal input outputSalts[numOutputs]; - signal input outputOwnerPublicKeys[numOutputs][2]; - signal output out; - - // derive the sender's public key from the secret input - // for the sender's private key. This step demonstrates - // the sender really owns the private key for the input - // UTXOs - var inputOwnerPublicKey[2]; - component pub = BabyPbk(); - pub.in <== inputOwnerPrivateKey; - inputOwnerPublicKey[0] = pub.Ax; - inputOwnerPublicKey[1] = pub.Ay; - var inputOwnerPublicKeys[numInputs][2]; - for (var i = 0; i < numInputs; i++) { - inputOwnerPublicKeys[i][0] = inputOwnerPublicKey[0]; - inputOwnerPublicKeys[i][1] = inputOwnerPublicKey[1]; - } - - component checkPositives = CheckPositive(numOutputs); - checkPositives.outputValues <== outputValues; - - component checkInputHashes = CheckHashes(numInputs); - checkInputHashes.commitments <== inputCommitments; - checkInputHashes.values <== inputValues; - checkInputHashes.salts <== inputSalts; - checkInputHashes.ownerPublicKeys <== inputOwnerPublicKeys; - - component checkOutputHashes = CheckHashes(numOutputs); - checkOutputHashes.commitments <== outputCommitments; - checkOutputHashes.values <== outputValues; - checkOutputHashes.salts <== outputSalts; - checkOutputHashes.ownerPublicKeys <== outputOwnerPublicKeys; - - // check that the sum of input values is greater than or equal to the sum of output values - var sumInputs = 0; - for (var i = 0; i < numInputs; i++) { - sumInputs = sumInputs + inputValues[i]; - } - var sumOutputs = 0; - for (var i = 0; i < numOutputs; i++) { - sumOutputs = sumOutputs + outputValues[i]; - } - - // check that the sum of input values is greater than the sum of output values - assert(sumInputs >= sumOutputs); - - // return the remainder as output - out <== sumInputs - sumOutputs; -} +include "./lib/check-inputs-outputs-value-base.circom"; component main { public [ inputCommitments, outputCommitments ] } = Zeto(2, 1); diff --git a/zkp/circuits/check_inputs_outputs_value_batch.circom b/zkp/circuits/check_inputs_outputs_value_batch.circom new file mode 100644 index 0000000..c5ad395 --- /dev/null +++ b/zkp/circuits/check_inputs_outputs_value_batch.circom @@ -0,0 +1,20 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +pragma circom 2.1.4; + +include "./lib/check-inputs-outputs-value-base.circom"; + +component main { public [ inputCommitments, outputCommitments ] } = Zeto(10, 1); diff --git a/zkp/circuits/check_nullifier_value.circom b/zkp/circuits/check_nullifier_value.circom index 9f28351..7b6e708 100644 --- a/zkp/circuits/check_nullifier_value.circom +++ b/zkp/circuits/check_nullifier_value.circom @@ -15,88 +15,6 @@ // limitations under the License. pragma circom 2.1.4; -include "./lib/check-positive.circom"; -include "./lib/check-hashes.circom"; -include "./lib/check-nullifiers.circom"; -include "./lib/check-smt-proof.circom"; - -template Zeto(numInputs, numOutputs, nSMTLevels) { - signal input nullifiers[numInputs]; - signal input inputCommitments[numInputs]; - signal input inputValues[numInputs]; - signal input inputSalts[numInputs]; - // must be properly hashed and trimmed to be compatible with the BabyJub curve. - // Reference: https://github.com/iden3/circomlib/blob/master/test/babyjub.js#L103 - signal input inputOwnerPrivateKey; - signal input root; - signal input merkleProof[numInputs][nSMTLevels]; - signal input enabled[numInputs]; - signal input outputCommitments[numOutputs]; - signal input outputValues[numOutputs]; - signal input outputSalts[numOutputs]; - signal input outputOwnerPublicKeys[numOutputs][2]; - signal output out; - - // derive the sender's public key from the secret input - // for the sender's private key. This step demonstrates - // the sender really owns the private key for the input - // UTXOs - var inputOwnerPublicKey[2]; - component pub = BabyPbk(); - pub.in <== inputOwnerPrivateKey; - inputOwnerPublicKey[0] = pub.Ax; - inputOwnerPublicKey[1] = pub.Ay; - var inputOwnerPublicKeys[numInputs][2]; - for (var i = 0; i < numInputs; i++) { - inputOwnerPublicKeys[i][0] = inputOwnerPublicKey[0]; - inputOwnerPublicKeys[i][1] = inputOwnerPublicKey[1]; - } - - component checkPositives = CheckPositive(numOutputs); - checkPositives.outputValues <== outputValues; - - component checkInputHashes = CheckHashes(numInputs); - checkInputHashes.commitments <== inputCommitments; - checkInputHashes.values <== inputValues; - checkInputHashes.salts <== inputSalts; - checkInputHashes.ownerPublicKeys <== inputOwnerPublicKeys; - - component checkNullifiers = CheckNullifiers(numInputs); - checkNullifiers.nullifiers <== nullifiers; - checkNullifiers.values <== inputValues; - checkNullifiers.salts <== inputSalts; - checkNullifiers.ownerPrivateKey <== inputOwnerPrivateKey; - - component checkOutputHashes = CheckHashes(numOutputs); - checkOutputHashes.commitments <== outputCommitments; - checkOutputHashes.values <== outputValues; - checkOutputHashes.salts <== outputSalts; - checkOutputHashes.ownerPublicKeys <== outputOwnerPublicKeys; - - // With the above steps, we demonstrated that the nullifiers - // are securely bound to the input commitments. Now we need to - // demonstrate that the input commitments belong to the Sparse - // Merkle Tree with the root `root`. - component checkSMTProof = CheckSMTProof(numInputs, nSMTLevels); - checkSMTProof.root <== root; - checkSMTProof.merkleProof <== merkleProof; - checkSMTProof.enabled <== enabled; - checkSMTProof.leafNodeIndexes <== inputCommitments; - - // check that the sum of input values equals the sum of output values - var sumInputs = 0; - for (var i = 0; i < numInputs; i++) { - sumInputs = sumInputs + inputValues[i]; - } - var sumOutputs = 0; - for (var i = 0; i < numOutputs; i++) { - sumOutputs = sumOutputs + outputValues[i]; - } - - // check that the sum of input values is greater than the sum of output values - assert(sumInputs >= sumOutputs); - - out <== sumInputs - sumOutputs; -} +include "./lib/check-nullifier-value-base.circom"; component main { public [ nullifiers, outputCommitments, root, enabled ] } = Zeto(2, 1, 64); diff --git a/zkp/circuits/check_nullifier_value_batch.circom b/zkp/circuits/check_nullifier_value_batch.circom new file mode 100644 index 0000000..e36643e --- /dev/null +++ b/zkp/circuits/check_nullifier_value_batch.circom @@ -0,0 +1,20 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +pragma circom 2.1.4; + +include "./lib/check-nullifier-value-base.circom"; + +component main { public [ nullifiers, outputCommitments, root, enabled ] } = Zeto(10, 1, 64); diff --git a/zkp/circuits/gen-config.json b/zkp/circuits/gen-config.json index 8bfe490..8140668 100644 --- a/zkp/circuits/gen-config.json +++ b/zkp/circuits/gen-config.json @@ -48,10 +48,12 @@ }, "check_inputs_outputs_value": { "ptau": "powersOfTau28_hez_final_11", + "batchPtau": "powersOfTau28_hez_final_13", "skipSolidityGenaration": false }, "check_nullifier_value": { "ptau": "powersOfTau28_hez_final_16", + "batchPtau": "powersOfTau28_hez_final_18", "skipSolidityGenaration": false }, "check_nullifiers": { diff --git a/zkp/circuits/lib/check-inputs-outputs-value-base.circom b/zkp/circuits/lib/check-inputs-outputs-value-base.circom new file mode 100644 index 0000000..f10d70a --- /dev/null +++ b/zkp/circuits/lib/check-inputs-outputs-value-base.circom @@ -0,0 +1,80 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +pragma circom 2.1.4; + +include "./check-positive.circom"; +include "./check-hashes.circom"; +include "../node_modules/circomlib/circuits/babyjub.circom"; + +template Zeto(numInputs, numOutputs) { + signal input inputCommitments[numInputs]; + signal input inputValues[numInputs]; + signal input inputSalts[numInputs]; + // must be properly hashed and trimmed to be compatible with the BabyJub curve. + // Reference: https://github.com/iden3/circomlib/blob/master/test/babyjub.js#L103 + signal input inputOwnerPrivateKey; + signal input outputCommitments[numOutputs]; + signal input outputValues[numOutputs]; + signal input outputSalts[numOutputs]; + signal input outputOwnerPublicKeys[numOutputs][2]; + signal output out; + + // derive the sender's public key from the secret input + // for the sender's private key. This step demonstrates + // the sender really owns the private key for the input + // UTXOs + var inputOwnerPublicKey[2]; + component pub = BabyPbk(); + pub.in <== inputOwnerPrivateKey; + inputOwnerPublicKey[0] = pub.Ax; + inputOwnerPublicKey[1] = pub.Ay; + var inputOwnerPublicKeys[numInputs][2]; + for (var i = 0; i < numInputs; i++) { + inputOwnerPublicKeys[i][0] = inputOwnerPublicKey[0]; + inputOwnerPublicKeys[i][1] = inputOwnerPublicKey[1]; + } + + component checkPositives = CheckPositive(numOutputs); + checkPositives.outputValues <== outputValues; + + component checkInputHashes = CheckHashes(numInputs); + checkInputHashes.commitments <== inputCommitments; + checkInputHashes.values <== inputValues; + checkInputHashes.salts <== inputSalts; + checkInputHashes.ownerPublicKeys <== inputOwnerPublicKeys; + + component checkOutputHashes = CheckHashes(numOutputs); + checkOutputHashes.commitments <== outputCommitments; + checkOutputHashes.values <== outputValues; + checkOutputHashes.salts <== outputSalts; + checkOutputHashes.ownerPublicKeys <== outputOwnerPublicKeys; + + // check that the sum of input values is greater than or equal to the sum of output values + var sumInputs = 0; + for (var i = 0; i < numInputs; i++) { + sumInputs = sumInputs + inputValues[i]; + } + var sumOutputs = 0; + for (var i = 0; i < numOutputs; i++) { + sumOutputs = sumOutputs + outputValues[i]; + } + + // check that the sum of input values is greater than the sum of output values + assert(sumInputs >= sumOutputs); + + // return the remainder as output + out <== sumInputs - sumOutputs; +} \ No newline at end of file diff --git a/zkp/circuits/lib/check-nullifier-value-base.circom b/zkp/circuits/lib/check-nullifier-value-base.circom new file mode 100644 index 0000000..487b057 --- /dev/null +++ b/zkp/circuits/lib/check-nullifier-value-base.circom @@ -0,0 +1,100 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +pragma circom 2.1.4; + +include "./check-positive.circom"; +include "./check-hashes.circom"; +include "./check-nullifiers.circom"; +include "./check-smt-proof.circom"; + +template Zeto(numInputs, numOutputs, nSMTLevels) { + signal input nullifiers[numInputs]; + signal input inputCommitments[numInputs]; + signal input inputValues[numInputs]; + signal input inputSalts[numInputs]; + // must be properly hashed and trimmed to be compatible with the BabyJub curve. + // Reference: https://github.com/iden3/circomlib/blob/master/test/babyjub.js#L103 + signal input inputOwnerPrivateKey; + signal input root; + signal input merkleProof[numInputs][nSMTLevels]; + signal input enabled[numInputs]; + signal input outputCommitments[numOutputs]; + signal input outputValues[numOutputs]; + signal input outputSalts[numOutputs]; + signal input outputOwnerPublicKeys[numOutputs][2]; + signal output out; + + // derive the sender's public key from the secret input + // for the sender's private key. This step demonstrates + // the sender really owns the private key for the input + // UTXOs + var inputOwnerPublicKey[2]; + component pub = BabyPbk(); + pub.in <== inputOwnerPrivateKey; + inputOwnerPublicKey[0] = pub.Ax; + inputOwnerPublicKey[1] = pub.Ay; + var inputOwnerPublicKeys[numInputs][2]; + for (var i = 0; i < numInputs; i++) { + inputOwnerPublicKeys[i][0] = inputOwnerPublicKey[0]; + inputOwnerPublicKeys[i][1] = inputOwnerPublicKey[1]; + } + + component checkPositives = CheckPositive(numOutputs); + checkPositives.outputValues <== outputValues; + + component checkInputHashes = CheckHashes(numInputs); + checkInputHashes.commitments <== inputCommitments; + checkInputHashes.values <== inputValues; + checkInputHashes.salts <== inputSalts; + checkInputHashes.ownerPublicKeys <== inputOwnerPublicKeys; + + component checkNullifiers = CheckNullifiers(numInputs); + checkNullifiers.nullifiers <== nullifiers; + checkNullifiers.values <== inputValues; + checkNullifiers.salts <== inputSalts; + checkNullifiers.ownerPrivateKey <== inputOwnerPrivateKey; + + component checkOutputHashes = CheckHashes(numOutputs); + checkOutputHashes.commitments <== outputCommitments; + checkOutputHashes.values <== outputValues; + checkOutputHashes.salts <== outputSalts; + checkOutputHashes.ownerPublicKeys <== outputOwnerPublicKeys; + + // With the above steps, we demonstrated that the nullifiers + // are securely bound to the input commitments. Now we need to + // demonstrate that the input commitments belong to the Sparse + // Merkle Tree with the root `root`. + component checkSMTProof = CheckSMTProof(numInputs, nSMTLevels); + checkSMTProof.root <== root; + checkSMTProof.merkleProof <== merkleProof; + checkSMTProof.enabled <== enabled; + checkSMTProof.leafNodeIndexes <== inputCommitments; + + // check that the sum of input values equals the sum of output values + var sumInputs = 0; + for (var i = 0; i < numInputs; i++) { + sumInputs = sumInputs + inputValues[i]; + } + var sumOutputs = 0; + for (var i = 0; i < numOutputs; i++) { + sumOutputs = sumOutputs + outputValues[i]; + } + + // check that the sum of input values is greater than the sum of output values + assert(sumInputs >= sumOutputs); + + out <== sumInputs - sumOutputs; +}