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;
+}