diff --git a/solidity/contracts/erc20.sol b/solidity/contracts/erc20.sol new file mode 100644 index 0000000..2ecb619 --- /dev/null +++ b/solidity/contracts/erc20.sol @@ -0,0 +1,40 @@ +// 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 solidity ^0.8.20; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "hardhat/console.sol"; + +/// @title A sample implementation of a Zeto based fungible token with anonymity and no encryption +/// @author Kaleido, Inc. +/// @dev The proof has the following statements: +/// - each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) +/// - the sum of the input values match the sum of output values +/// - the hashes in the input and output match the `hash(value, salt, owner public key)` formula +/// - the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes +contract SampleERC20 is ERC20, Ownable { + constructor() + ERC20("Sample ERC20 token", "SampleERC20") + Ownable(msg.sender) + { + _mint(msg.sender, 1000000 * 10 ** 18); + } + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } +} diff --git a/solidity/contracts/lib/verifier_check_hashes_value.sol b/solidity/contracts/lib/verifier_check_hashes_value.sol new file mode 100644 index 0000000..aa895d2 --- /dev/null +++ b/solidity/contracts/lib/verifier_check_hashes_value.sol @@ -0,0 +1,177 @@ +// 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_CheckValue { + // 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 = 21353820865548503329577756525221633465332622166799959856825877352905192473267; + uint256 constant IC0y = 156932616567894649841337587302503574238981053813007972854040490364635182694; + + uint256 constant IC1x = 12322194858529188061062625971813098256741654889417169683222060161781584608609; + uint256 constant IC1y = 20246326882683168066748851145817053112674664250127727211412715232179245157022; + + uint256 constant IC2x = 8022016804019415581152964105487657307044363981768180695730643477130121229579; + uint256 constant IC2y = 15328789577726767026198857145765587783001722435319756601432638671812565154978; + + + // 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[2] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + 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))) + + + // -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))) + + + // 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_inputs_outputs_value.sol b/solidity/contracts/lib/verifier_check_inputs_outputs_value.sol new file mode 100644 index 0000000..1b9945d --- /dev/null +++ b/solidity/contracts/lib/verifier_check_inputs_outputs_value.sol @@ -0,0 +1,191 @@ +// 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_CheckInputsOutputsValue { + // 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 = 17387922327858210136617007095230042632426381952166484448116746614272536751535; + uint256 constant IC0y = 4808269916722279091167330560212980163487536845606246009253087821651013005551; + + uint256 constant IC1x = 11962741450402091371273662224482187613475715473047174085420945320695830737995; + uint256 constant IC1y = 15562574725663871801316953724005672455912364153659295220330094320410858196080; + + uint256 constant IC2x = 4194677006984027746737437054535462737447331455450175107486916659299631966883; + uint256 constant IC2y = 2965301361541939950387716753816670353588190783366000554173049564598213308644; + + uint256 constant IC3x = 16410371088360564861123282542907734207746899107639828095748274609891404450721; + uint256 constant IC3y = 14447287810519803612881548625236303993743219400827830956369634309751031524243; + + uint256 constant IC4x = 14479207206262402603061969459430305401687514000594863504679844985436934252961; + uint256 constant IC4y = 12009372897324578288882118575535211840587775246611243165708895311608262611745; + + + // 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[4] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + 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))) + + + // -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))) + + + // 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.sol b/solidity/contracts/lib/verifier_check_nullifier_value.sol new file mode 100644 index 0000000..d32a97f --- /dev/null +++ b/solidity/contracts/lib/verifier_check_nullifier_value.sol @@ -0,0 +1,212 @@ +// 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_CheckNullifierValue { + // 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 = 15492418976489076016744712039228991049119912955688524102804299454656267186009; + uint256 constant IC0y = 6683516575201125125993199841635988477061644803076780133772345536623987263585; + + uint256 constant IC1x = 20388809982110581159758837462045964857853052490510219178487821131497955197485; + uint256 constant IC1y = 11559017693629947025935312317037014128873998602028720842466005215803981949488; + + uint256 constant IC2x = 13435481798396867294149705853505335939788917619623055020026965426090811440436; + uint256 constant IC2y = 19014602589838621389539780295053756982599159926216043076756080339477593928267; + + uint256 constant IC3x = 9101308846546712061385480237991349242785040605257963609786960558441532043575; + uint256 constant IC3y = 20162065319211808084372980649366363483528315362920428034950134764348551061576; + + uint256 constant IC4x = 1862728518597969644991754874217991793768978626842292905952230489753317456943; + uint256 constant IC4y = 13827959089691652056820043159766605451401208772931308660944601538220427688001; + + uint256 constant IC5x = 12595562902478113608580909807915007505100194716952840137066159851175855515947; + uint256 constant IC5y = 20219788329832744328203670585885257301041964037877146487338583938337874848685; + + uint256 constant IC6x = 15493536898701008011127294879458488796153705332302588952751125522877852961647; + uint256 constant IC6y = 1788009749667942434387844456635362080454534277559959103723491751055982698041; + + uint256 constant IC7x = 2534369505154061360258866279946256857360053453543228810913826185688221321445; + uint256 constant IC7y = 15777401174389130350272102012636311458122823507269563590772434670217407958669; + + + // 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[7] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + 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))) + + + // -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))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } diff --git a/solidity/contracts/lib/zeto_base.sol b/solidity/contracts/lib/zeto_base.sol index bf5981d..7bf0190 100644 --- a/solidity/contracts/lib/zeto_base.sol +++ b/solidity/contracts/lib/zeto_base.sol @@ -19,6 +19,7 @@ import {Commonlib} from "./common.sol"; import {Registry} from "./registry.sol"; import {ZetoCommon} from "./zeto_common.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title A sample base implementation of a Zeto based token contract /// without using nullifiers. Each UTXO's spending status is explicitly tracked. @@ -96,9 +97,22 @@ abstract contract ZetoBase is ZetoCommon { return true; } + function processInputsAndOutputs( + uint256[2] memory inputs, + uint256[2] memory outputs + ) internal { + // accept the transaction to consume the input UTXOs and produce new UTXOs + for (uint256 i = 0; i < inputs.length; ++i) { + _utxos[inputs[i]] = UTXOStatus.SPENT; + } + for (uint256 i = 0; i < outputs.length; ++i) { + _utxos[outputs[i]] = UTXOStatus.UNSPENT; + } + } + // This function is used to mint new UTXOs, as an example implementation, // which is only callable by the owner. - function mint(uint256[] memory utxos) public virtual onlyOwner { + function _mint(uint256[] memory utxos) internal virtual { for (uint256 i = 0; i < utxos.length; ++i) { uint256 utxo = utxos[i]; if (_utxos[utxo] == UTXOStatus.UNSPENT) { diff --git a/solidity/contracts/lib/zeto_common.sol b/solidity/contracts/lib/zeto_common.sol index d6ec992..3d661ba 100644 --- a/solidity/contracts/lib/zeto_common.sol +++ b/solidity/contracts/lib/zeto_common.sol @@ -17,6 +17,7 @@ pragma solidity ^0.8.20; import {Commonlib} from "./common.sol"; import {Registry} from "./registry.sol"; +import {Groth16Verifier_CheckValue} from "./verifier_check_hashes_value.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; /// @title A sample base implementation of a Zeto based token contract diff --git a/solidity/contracts/lib/zeto_fungible.sol b/solidity/contracts/lib/zeto_fungible.sol new file mode 100644 index 0000000..65344f2 --- /dev/null +++ b/solidity/contracts/lib/zeto_fungible.sol @@ -0,0 +1,71 @@ +// 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 solidity ^0.8.20; + +import {Groth16Verifier_CheckValue} from "./verifier_check_hashes_value.sol"; +import {Groth16Verifier_CheckNullifierValue} from "./verifier_check_nullifier_value.sol"; +import {Commonlib} from "./common.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +/// @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. +abstract contract ZetoFungible is Ownable { + // depositVerifier library for checking UTXOs against a claimed value. + // this can be used in the optional deposit calls to verify that + // the UTXOs match the deposited value + Groth16Verifier_CheckValue internal depositVerifier; + + IERC20 internal erc20; + + constructor(Groth16Verifier_CheckValue _depositVerifier) { + depositVerifier = _depositVerifier; + } + + function setERC20(IERC20 _erc20) public onlyOwner { + erc20 = _erc20; + } + + function _deposit( + uint256 amount, + uint256 utxo, + Commonlib.Proof calldata proof + ) public virtual { + // verifies that the output UTXOs match the claimed value + // to be deposited + // construct the public inputs + uint256[2] memory publicInputs; + publicInputs[0] = amount; + publicInputs[1] = utxo; + + // // Check the proof + require( + depositVerifier.verifyProof( + proof.pA, + proof.pB, + proof.pC, + publicInputs + ), + "Invalid proof" + ); + + require( + erc20.transferFrom(msg.sender, address(this), amount), + "Failed to transfer ERC20 tokens" + ); + } +} diff --git a/solidity/contracts/lib/zeto_fungible_withdraw.sol b/solidity/contracts/lib/zeto_fungible_withdraw.sol new file mode 100644 index 0000000..76d7b14 --- /dev/null +++ b/solidity/contracts/lib/zeto_fungible_withdraw.sol @@ -0,0 +1,70 @@ +// 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 solidity ^0.8.20; + +import {Groth16Verifier_CheckValue} from "./verifier_check_hashes_value.sol"; +import {Groth16Verifier_CheckInputsOutputsValue} from "./verifier_check_inputs_outputs_value.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"; + +/// @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. +abstract contract ZetoFungibleWithdraw is ZetoFungible { + // nullifierVerifier library for checking nullifiers against a claimed value. + // this can be used in the optional withdraw calls to verify that the nullifiers + // match the withdrawn value + Groth16Verifier_CheckInputsOutputsValue internal withdrawVerifier; + + constructor( + Groth16Verifier_CheckValue _depositVerifier, + Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier + ) ZetoFungible(_depositVerifier) { + withdrawVerifier = _withdrawVerifier; + } + + function _withdraw( + uint256 amount, + uint256[2] memory inputs, + uint256 output, + Commonlib.Proof calldata proof + ) public virtual { + // 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" + ); + + require( + erc20.transfer(msg.sender, amount), + "Failed to transfer ERC20 tokens" + ); + } +} diff --git a/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol b/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol new file mode 100644 index 0000000..98928eb --- /dev/null +++ b/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol @@ -0,0 +1,74 @@ +// 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 solidity ^0.8.20; + +import {Groth16Verifier_CheckValue} from "./verifier_check_hashes_value.sol"; +import {Groth16Verifier_CheckNullifierValue} from "./verifier_check_nullifier_value.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"; + +/// @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. +abstract contract ZetoFungibleWithdrawWithNullifiers is ZetoFungible { + // nullifierVerifier library for checking nullifiers against a claimed value. + // this can be used in the optional withdraw calls to verify that the nullifiers + // match the withdrawn value + Groth16Verifier_CheckNullifierValue internal withdrawVerifier; + + constructor( + Groth16Verifier_CheckValue _depositVerifier, + Groth16Verifier_CheckNullifierValue _withdrawVerifier + ) ZetoFungible(_depositVerifier) { + withdrawVerifier = _withdrawVerifier; + } + + function _withdrawWithNullifiers( + uint256 amount, + uint256[2] memory nullifiers, + uint256 output, + uint256 root, + Commonlib.Proof calldata proof + ) public virtual { + // 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" + ); + + require( + erc20.transfer(msg.sender, amount), + "Failed to transfer ERC20 tokens" + ); + } +} diff --git a/solidity/contracts/lib/zeto_nullifier.sol b/solidity/contracts/lib/zeto_nullifier.sol index 18ad17d..416b18f 100644 --- a/solidity/contracts/lib/zeto_nullifier.sol +++ b/solidity/contracts/lib/zeto_nullifier.sol @@ -41,8 +41,7 @@ abstract contract ZetoNullifier is ZetoCommon { function validateTransactionProposal( uint256[2] memory nullifiers, uint256[2] memory outputs, - uint256 root, - Commonlib.Proof calldata proof + uint256 root ) internal view returns (bool) { // sort the inputs and outputs to detect duplicates ( @@ -108,7 +107,7 @@ abstract contract ZetoNullifier is ZetoCommon { // This function is used to mint new UTXOs, as an example implementation, // which is only callable by the owner. - function mint(uint256[] memory utxos) public onlyOwner { + function _mint(uint256[] memory utxos) internal virtual { for (uint256 i = 0; i < utxos.length; ++i) { uint256 utxo = utxos[i]; uint256 nodeHash = _getLeafNodeHash(utxo); diff --git a/solidity/contracts/zeto_anon.sol b/solidity/contracts/zeto_anon.sol index f05fb5e..2b7e47a 100644 --- a/solidity/contracts/zeto_anon.sol +++ b/solidity/contracts/zeto_anon.sol @@ -15,10 +15,14 @@ // limitations under the License. pragma solidity ^0.8.20; +import {Groth16Verifier_CheckValue} from "./lib/verifier_check_hashes_value.sol"; +import {Groth16Verifier_CheckInputsOutputsValue} from "./lib/verifier_check_inputs_outputs_value.sol"; import {Groth16Verifier_Anon} from "./lib/verifier_anon.sol"; import {Registry} from "./lib/registry.sol"; import {Commonlib} from "./lib/common.sol"; import {ZetoBase} from "./lib/zeto_base.sol"; +import {ZetoFungible} from "./lib/zeto_fungible.sol"; +import {ZetoFungibleWithdraw} from "./lib/zeto_fungible_withdraw.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import "hardhat/console.sol"; @@ -29,13 +33,18 @@ import "hardhat/console.sol"; /// - the sum of the input values match the sum of output values /// - the hashes in the input and output match the `hash(value, salt, owner public key)` formula /// - the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes -contract Zeto_Anon is ZetoBase { +contract Zeto_Anon is ZetoBase, ZetoFungibleWithdraw { Groth16Verifier_Anon internal verifier; constructor( + Groth16Verifier_CheckValue _depositVerifier, + Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, Groth16Verifier_Anon _verifier, Registry _registry - ) ZetoBase(_registry) { + ) + ZetoBase(_registry) + ZetoFungibleWithdraw(_depositVerifier, _withdrawVerifier) + { verifier = _verifier; } @@ -72,13 +81,7 @@ contract Zeto_Anon is ZetoBase { "Invalid proof" ); - // accept the transaction to consume the input UTXOs and produce new UTXOs - for (uint256 i = 0; i < inputs.length; ++i) { - _utxos[inputs[i]] = UTXOStatus.SPENT; - } - for (uint256 i = 0; i < outputs.length; ++i) { - _utxos[outputs[i]] = UTXOStatus.UNSPENT; - } + processInputsAndOutputs(inputs, outputs); uint256[] memory inputArray = new uint256[](inputs.length); uint256[] memory outputArray = new uint256[](outputs.length); @@ -90,4 +93,29 @@ contract Zeto_Anon is ZetoBase { return true; } + + function deposit( + uint256 amount, + uint256 utxo, + Commonlib.Proof calldata proof + ) public { + _deposit(amount, utxo, proof); + uint256[] memory utxos = new uint256[](1); + utxos[0] = utxo; + _mint(utxos); + } + + function withdraw( + uint256 amount, + uint256[2] memory inputs, + uint256 output, + Commonlib.Proof calldata proof + ) public { + _withdraw(amount, inputs, output, proof); + processInputsAndOutputs(inputs, [output, 0]); + } + + function mint(uint256[] memory utxos) public onlyOwner { + _mint(utxos); + } } diff --git a/solidity/contracts/zeto_anon_enc.sol b/solidity/contracts/zeto_anon_enc.sol index c29e671..14dc411 100644 --- a/solidity/contracts/zeto_anon_enc.sol +++ b/solidity/contracts/zeto_anon_enc.sol @@ -15,8 +15,12 @@ // limitations under the License. pragma solidity ^0.8.20; +import {Groth16Verifier_CheckValue} from "./lib/verifier_check_hashes_value.sol"; +import {Groth16Verifier_CheckInputsOutputsValue} from "./lib/verifier_check_inputs_outputs_value.sol"; import {Groth16Verifier_AnonEnc} from "./lib/verifier_anon_enc.sol"; +import {ZetoFungibleWithdraw} from "./lib/zeto_fungible_withdraw.sol"; import {ZetoBase} from "./lib/zeto_base.sol"; +import {ZetoFungible} from "./lib/zeto_fungible.sol"; import {Registry} from "./lib/registry.sol"; import {Commonlib} from "./lib/common.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -31,13 +35,18 @@ import "hardhat/console.sol"; /// - the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes /// - the encrypted value in the input is derived from the receiver's UTXO value and encrypted with a shared secret using /// the ECDH protocol between the sender and receiver (this guarantees data availability for the receiver) -contract Zeto_AnonEnc is ZetoBase { +contract Zeto_AnonEnc is ZetoBase, ZetoFungibleWithdraw { Groth16Verifier_AnonEnc internal verifier; constructor( + Groth16Verifier_CheckValue _depositVerifier, + Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, Groth16Verifier_AnonEnc _verifier, Registry _registry - ) ZetoBase(_registry) { + ) + ZetoBase(_registry) + ZetoFungibleWithdraw(_depositVerifier, _withdrawVerifier) + { verifier = _verifier; } @@ -79,13 +88,7 @@ contract Zeto_AnonEnc is ZetoBase { "Invalid proof" ); - // accept the transaction to consume the input UTXOs and produce new UTXOs - for (uint256 i = 0; i < inputs.length; ++i) { - _utxos[inputs[i]] = UTXOStatus.SPENT; - } - for (uint256 i = 0; i < outputs.length; ++i) { - _utxos[outputs[i]] = UTXOStatus.UNSPENT; - } + processInputsAndOutputs(inputs, outputs); uint256[] memory inputArray = new uint256[](inputs.length); uint256[] memory outputArray = new uint256[](outputs.length); @@ -107,4 +110,29 @@ contract Zeto_AnonEnc is ZetoBase { ); return true; } + + function deposit( + uint256 amount, + uint256 utxo, + Commonlib.Proof calldata proof + ) public { + _deposit(amount, utxo, proof); + uint256[] memory utxos = new uint256[](1); + utxos[0] = utxo; + _mint(utxos); + } + + function withdraw( + uint256 amount, + uint256[2] memory inputs, + uint256 output, + Commonlib.Proof calldata proof + ) public { + _withdraw(amount, inputs, output, proof); + processInputsAndOutputs(inputs, [output, 0]); + } + + function mint(uint256[] memory utxos) public onlyOwner { + _mint(utxos); + } } diff --git a/solidity/contracts/zeto_anon_enc_nullifier.sol b/solidity/contracts/zeto_anon_enc_nullifier.sol index 3f732d4..9b55cdf 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier.sol @@ -15,8 +15,11 @@ // limitations under the License. pragma solidity ^0.8.20; +import {Groth16Verifier_CheckValue} from "./lib/verifier_check_hashes_value.sol"; +import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol"; import {Groth16Verifier_AnonEncNullifier} from "./lib/verifier_anon_enc_nullifier.sol"; import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; +import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_nullifier.sol"; import {Registry} from "./lib/registry.sol"; import {Commonlib} from "./lib/common.sol"; import "hardhat/console.sol"; @@ -30,13 +33,21 @@ import "hardhat/console.sol"; /// - the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers /// - the encrypted value in the input is derived from the receiver's UTXO value and encrypted with a shared secret using the ECDH protocol between the sender and receiver (this guarantees data availability for the receiver) /// - the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash -contract Zeto_AnonEncNullifier is ZetoNullifier { +contract Zeto_AnonEncNullifier is + ZetoNullifier, + ZetoFungibleWithdrawWithNullifiers +{ Groth16Verifier_AnonEncNullifier verifier; constructor( + Groth16Verifier_CheckValue _depositVerifier, + Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonEncNullifier _verifier, Registry _registry - ) ZetoNullifier(_registry) { + ) + ZetoNullifier(_registry) + ZetoFungibleWithdrawWithNullifiers(_depositVerifier, _withdrawVerifier) + { verifier = _verifier; } @@ -63,7 +74,7 @@ contract Zeto_AnonEncNullifier is ZetoNullifier { Commonlib.Proof calldata proof ) public returns (bool) { require( - validateTransactionProposal(nullifiers, outputs, root, proof), + validateTransactionProposal(nullifiers, outputs, root), "Invalid transaction proposal" ); @@ -109,4 +120,30 @@ contract Zeto_AnonEncNullifier is ZetoNullifier { ); return true; } + + function deposit( + uint256 amount, + uint256 utxo, + Commonlib.Proof calldata proof + ) public { + _deposit(amount, utxo, proof); + uint256[] memory utxos = new uint256[](1); + utxos[0] = utxo; + _mint(utxos); + } + + function withdraw( + uint256 amount, + uint256[2] memory nullifiers, + uint256 output, + uint256 root, + Commonlib.Proof calldata proof + ) public { + _withdrawWithNullifiers(amount, nullifiers, output, root, proof); + processInputsAndOutputs(nullifiers, [output, 0]); + } + + function mint(uint256[] memory utxos) public onlyOwner { + _mint(utxos); + } } diff --git a/solidity/contracts/zeto_anon_nullifier.sol b/solidity/contracts/zeto_anon_nullifier.sol index e34290d..d35c40a 100644 --- a/solidity/contracts/zeto_anon_nullifier.sol +++ b/solidity/contracts/zeto_anon_nullifier.sol @@ -15,8 +15,11 @@ // limitations under the License. pragma solidity ^0.8.20; +import {Groth16Verifier_CheckValue} from "./lib/verifier_check_hashes_value.sol"; +import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol"; import {Groth16Verifier_AnonNullifier} from "./lib/verifier_anon_nullifier.sol"; import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; +import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_nullifier.sol"; import {Registry} from "./lib/registry.sol"; import {Commonlib} from "./lib/common.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -34,13 +37,21 @@ uint256 constant MAX_SMT_DEPTH = 64; /// - the hashes in the input and output match the hash(value, salt, owner public key) formula /// - the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers /// - the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash -contract Zeto_AnonNullifier is ZetoNullifier { +contract Zeto_AnonNullifier is + ZetoNullifier, + ZetoFungibleWithdrawWithNullifiers +{ Groth16Verifier_AnonNullifier verifier; constructor( + Groth16Verifier_CheckValue _depositVerifier, + Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonNullifier _verifier, Registry _registry - ) ZetoNullifier(_registry) { + ) + ZetoNullifier(_registry) + ZetoFungibleWithdrawWithNullifiers(_depositVerifier, _withdrawVerifier) + { verifier = _verifier; } @@ -62,7 +73,7 @@ contract Zeto_AnonNullifier is ZetoNullifier { Commonlib.Proof calldata proof ) public returns (bool) { require( - validateTransactionProposal(nullifiers, outputs, root, proof), + validateTransactionProposal(nullifiers, outputs, root), "Invalid transaction proposal" ); @@ -93,4 +104,30 @@ contract Zeto_AnonNullifier is ZetoNullifier { emit UTXOTransfer(nullifierArray, outputArray, msg.sender); return true; } + + function deposit( + uint256 amount, + uint256 utxo, + Commonlib.Proof calldata proof + ) public { + _deposit(amount, utxo, proof); + uint256[] memory utxos = new uint256[](1); + utxos[0] = utxo; + _mint(utxos); + } + + function withdraw( + uint256 amount, + uint256[2] memory nullifiers, + uint256 output, + uint256 root, + Commonlib.Proof calldata proof + ) public { + _withdrawWithNullifiers(amount, nullifiers, output, root, proof); + processInputsAndOutputs(nullifiers, [output, 0]); + } + + function mint(uint256[] memory utxos) public onlyOwner { + _mint(utxos); + } } diff --git a/solidity/contracts/zeto_nf_anon.sol b/solidity/contracts/zeto_nf_anon.sol index 68da0f5..03aa1cc 100644 --- a/solidity/contracts/zeto_nf_anon.sol +++ b/solidity/contracts/zeto_nf_anon.sol @@ -80,4 +80,8 @@ contract Zeto_NFAnon is ZetoBase { emit UTXOTransfer(inputArray, outputArray, msg.sender); return true; } + + function mint(uint256[] memory utxos) public { + _mint(utxos); + } } diff --git a/solidity/contracts/zeto_nf_anon_nullifier.sol b/solidity/contracts/zeto_nf_anon_nullifier.sol index 21f0c5a..bfb428a 100644 --- a/solidity/contracts/zeto_nf_anon_nullifier.sol +++ b/solidity/contracts/zeto_nf_anon_nullifier.sol @@ -62,12 +62,7 @@ contract Zeto_NFAnonNullifier is ZetoNullifier { Commonlib.Proof calldata proof ) public returns (bool) { require( - validateTransactionProposal( - [nullifier, 0], - [output, 0], - root, - proof - ), + validateTransactionProposal([nullifier, 0], [output, 0], root), "Invalid transaction proposal" ); @@ -93,4 +88,8 @@ contract Zeto_NFAnonNullifier is ZetoNullifier { emit UTXOTransfer(nullifierArray, outputArray, msg.sender); return true; } + + function mint(uint256[] memory utxos) public { + _mint(utxos); + } } diff --git a/solidity/ignition/modules/erc20.ts b/solidity/ignition/modules/erc20.ts new file mode 100644 index 0000000..d5dd300 --- /dev/null +++ b/solidity/ignition/modules/erc20.ts @@ -0,0 +1,22 @@ +// 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. + +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("SampleERC20", (m) => { + const erc20 = m.contract('SampleERC20', []); + return { erc20 }; +}); diff --git a/solidity/ignition/modules/zeto_anon.ts b/solidity/ignition/modules/zeto_anon.ts index 71c7a02..6337de7 100644 --- a/solidity/ignition/modules/zeto_anon.ts +++ b/solidity/ignition/modules/zeto_anon.ts @@ -16,6 +16,16 @@ import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; +const DepositVerifierModule = buildModule("Groth16Verifier_CheckValue", (m) => { + const verifier = m.contract('Groth16Verifier_CheckValue', []); + return { verifier }; +}); + +const WithdrawVerifierModule = buildModule("Groth16Verifier_CheckInputsOutputsValue", (m) => { + const verifier = m.contract('Groth16Verifier_CheckInputsOutputsValue', []); + return { verifier }; +}); + const VerifierModule = buildModule("Groth16Verifier_Anon", (m) => { const verifier = m.contract('Groth16Verifier_Anon', []); return { verifier }; @@ -23,11 +33,13 @@ const VerifierModule = buildModule("Groth16Verifier_Anon", (m) => { export default buildModule("Zeto_Anon", (m) => { const { verifier } = m.useModule(VerifierModule); + const { verifier: depositVerifier } = m.useModule(DepositVerifierModule); + const { verifier: withdrawVerifier } = m.useModule(WithdrawVerifierModule); const commonlib = m.library('Commonlib'); const registryAddress = m.getParameter("registry"); const registry = m.contractAt('Registry', registryAddress); - const zeto = m.contract('Zeto_Anon', [verifier, registry], { + const zeto = m.contract('Zeto_Anon', [depositVerifier, withdrawVerifier, verifier, registry], { libraries: { Commonlib: commonlib, }, diff --git a/solidity/ignition/modules/zeto_anon_enc.ts b/solidity/ignition/modules/zeto_anon_enc.ts index 304d869..06fdd42 100644 --- a/solidity/ignition/modules/zeto_anon_enc.ts +++ b/solidity/ignition/modules/zeto_anon_enc.ts @@ -16,6 +16,16 @@ import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; +const DepositVerifierModule = buildModule("Groth16Verifier_CheckValue", (m) => { + const verifier = m.contract('Groth16Verifier_CheckValue', []); + return { verifier }; +}); + +const WithdrawVerifierModule = buildModule("Groth16Verifier_CheckInputsOutputsValue", (m) => { + const verifier = m.contract('Groth16Verifier_CheckInputsOutputsValue', []); + return { verifier }; +}); + const VerifierModule = buildModule("Groth16Verifier_AnonEnc", (m) => { const verifier = m.contract('Groth16Verifier_AnonEnc', []); return { verifier }; @@ -23,12 +33,14 @@ const VerifierModule = buildModule("Groth16Verifier_AnonEnc", (m) => { export default buildModule("Zeto_AnonEnc", (m) => { const { verifier } = m.useModule(VerifierModule); + const { verifier: depositVerifier } = m.useModule(DepositVerifierModule); + const { verifier: withdrawVerifier } = m.useModule(WithdrawVerifierModule); const commonlib = m.library('Commonlib'); const registryAddress = m.getParameter("registry"); const registry = m.contractAt('Registry', registryAddress); - const zeto = m.contract('Zeto_AnonEnc', [verifier, registry], { + const zeto = m.contract('Zeto_AnonEnc', [depositVerifier, withdrawVerifier, verifier, registry], { libraries: { Commonlib: commonlib, }, diff --git a/solidity/ignition/modules/zeto_anon_enc_nullifier.ts b/solidity/ignition/modules/zeto_anon_enc_nullifier.ts index 602c729..2b6bfab 100644 --- a/solidity/ignition/modules/zeto_anon_enc_nullifier.ts +++ b/solidity/ignition/modules/zeto_anon_enc_nullifier.ts @@ -30,6 +30,16 @@ const SmtLibModule = buildModule("SmtLib", (m) => { return { smtLib, poseidon3 }; }); +const DepositVerifierModule = buildModule("Groth16Verifier_CheckValue", (m) => { + const verifier = m.contract('Groth16Verifier_CheckValue', []); + return { verifier }; +}); + +const WithdrawVerifierModule = buildModule("Groth16Verifier_CheckNullifierValue", (m) => { + const verifier = m.contract('Groth16Verifier_CheckNullifierValue', []); + return { verifier }; +}); + const VerifierModule = buildModule("Groth16Verifier_AnonEncNullifier", (m) => { const verifier = m.contract('Groth16Verifier_AnonEncNullifier', []); return { verifier }; @@ -38,11 +48,13 @@ const VerifierModule = buildModule("Groth16Verifier_AnonEncNullifier", (m) => { export default buildModule("Zeto_AnonEncNullifier", (m) => { const { smtLib, poseidon3 } = m.useModule(SmtLibModule); const { verifier } = m.useModule(VerifierModule); + const { verifier: depositVerifier } = m.useModule(DepositVerifierModule); + const { verifier: withdrawVerifier } = m.useModule(WithdrawVerifierModule); const commonlib = m.library('Commonlib'); const registryAddress = m.getParameter("registry"); const registry = m.contractAt('Registry', registryAddress); - const zeto = m.contract('Zeto_AnonEncNullifier', [verifier, registry], { + const zeto = m.contract('Zeto_AnonEncNullifier', [depositVerifier, withdrawVerifier, verifier, registry], { libraries: { SmtLib: smtLib, PoseidonUnit3L: poseidon3, diff --git a/solidity/ignition/modules/zeto_anon_nullifier.ts b/solidity/ignition/modules/zeto_anon_nullifier.ts index b0c9523..d9484d3 100644 --- a/solidity/ignition/modules/zeto_anon_nullifier.ts +++ b/solidity/ignition/modules/zeto_anon_nullifier.ts @@ -30,6 +30,16 @@ const SmtLibModule = buildModule("SmtLib", (m) => { return { smtLib, poseidon3 }; }); +const DepositVerifierModule = buildModule("Groth16Verifier_CheckValue", (m) => { + const verifier = m.contract('Groth16Verifier_CheckValue', []); + return { verifier }; +}); + +const WithdrawVerifierModule = buildModule("Groth16Verifier_CheckNullifierValue", (m) => { + const verifier = m.contract('Groth16Verifier_CheckNullifierValue', []); + return { verifier }; +}); + const VerifierModule = buildModule("Groth16Verifier_AnonNullifier", (m) => { const verifier = m.contract('Groth16Verifier_AnonNullifier', []); return { verifier }; @@ -38,11 +48,13 @@ const VerifierModule = buildModule("Groth16Verifier_AnonNullifier", (m) => { export default buildModule("Zeto_AnonNullifier", (m) => { const { smtLib, poseidon3 } = m.useModule(SmtLibModule); const { verifier } = m.useModule(VerifierModule); + const { verifier: depositVerifier } = m.useModule(DepositVerifierModule); + const { verifier: withdrawVerifier } = m.useModule(WithdrawVerifierModule); const commonlib = m.library('Commonlib'); const registryAddress = m.getParameter("registry"); const registry = m.contractAt('Registry', registryAddress); - const zeto = m.contract('Zeto_AnonNullifier', [verifier, registry], { + const zeto = m.contract('Zeto_AnonNullifier', [depositVerifier, withdrawVerifier, verifier, registry], { libraries: { SmtLib: smtLib, PoseidonUnit3L: poseidon3, diff --git a/solidity/test/utils.ts b/solidity/test/utils.ts index 428abbe..b969788 100644 --- a/solidity/test/utils.ts +++ b/solidity/test/utils.ts @@ -16,6 +16,10 @@ import { readFileSync } from "fs"; import * as path from "path"; +import { BigNumberish } from 'ethers'; +import { groth16 } from 'snarkjs'; +import { loadCircuit, encodeProof } from "zeto-js"; +import { User, UTXO } from "./lib/utils"; function provingKeysRoot() { const PROVING_KEYS_ROOT = process.env.PROVING_KEYS_ROOT; @@ -37,3 +41,127 @@ export function loadProvingKeys(type: string) { verificationKey, }; } + +export async function prepareDepositProof(signer: User, output: UTXO) { + const outputCommitments: [BigNumberish] = [output.hash] as [BigNumberish]; + const outputValues = [BigInt(output.value || 0n)]; + const outputOwnerPublicKeys: [[BigNumberish, BigNumberish]] = [signer.babyJubPublicKey] as [[BigNumberish, BigNumberish]]; + + const inputObj = { + outputCommitments, + outputValues, + outputSalts: [output.salt], + outputOwnerPublicKeys + }; + + const circuit = await loadCircuit('check_hashes_value'); + const { provingKeyFile } = loadProvingKeys('check_hashes_value'); + + const startWitnessCalculation = Date.now(); + const witness = await circuit.calculateWTNSBin( + inputObj, + true + ); + const timeWithnessCalculation = Date.now() - startWitnessCalculation; + + const startProofGeneration = Date.now(); + const { proof, publicSignals } = await groth16.prove(provingKeyFile, witness) as { proof: BigNumberish[]; publicSignals: BigNumberish[] }; + const timeProofGeneration = Date.now() - startProofGeneration; + + console.log(`Witness calculation time: ${timeWithnessCalculation}ms. Proof generation time: ${timeProofGeneration}ms.`); + + const encodedProof = encodeProof(proof); + return { + outputCommitments, + encodedProof + }; +} + +export async function prepareNullifierWithdrawProof(signer: User, inputs: UTXO[], _nullifiers: UTXO[], output: UTXO, root: BigInt, merkleProof: BigInt[][]) { + const nullifiers = _nullifiers.map((nullifier) => nullifier.hash) as [BigNumberish, BigNumberish]; + const inputCommitments: [BigNumberish, BigNumberish] = inputs.map((input) => input.hash) as [BigNumberish, BigNumberish]; + const inputValues = inputs.map((input) => BigInt(input.value || 0n)); + const inputSalts = inputs.map((input) => input.salt || 0n); + const outputCommitments: [BigNumberish] = [output.hash] as [BigNumberish]; + const outputValues = [BigInt(output.value || 0n)]; + const outputOwnerPublicKeys: [[BigNumberish, BigNumberish]] = [signer.babyJubPublicKey] as [[BigNumberish, BigNumberish]]; + + const inputObj = { + nullifiers, + inputCommitments, + inputValues, + inputSalts, + inputOwnerPrivateKey: signer.formattedPrivateKey, + root, + enabled: [nullifiers[0] !== 0n ? 1 : 0, nullifiers[1] !== 0n ? 1 : 0], + merkleProof, + outputCommitments, + outputValues, + outputSalts: [output.salt], + outputOwnerPublicKeys + }; + const circuit = await loadCircuit('check_nullifier_value'); + const { provingKeyFile } = loadProvingKeys('check_nullifier_value'); + + const startWitnessCalculation = Date.now(); + const witness = await circuit.calculateWTNSBin( + inputObj, + true + ); + const timeWithnessCalculation = Date.now() - startWitnessCalculation; + + const startProofGeneration = Date.now(); + const { proof, publicSignals } = await groth16.prove(provingKeyFile, witness) as { proof: BigNumberish[]; publicSignals: BigNumberish[] }; + const timeProofGeneration = Date.now() - startProofGeneration; + + console.log(`Witness calculation time: ${timeWithnessCalculation}ms. Proof generation time: ${timeProofGeneration}ms.`); + + const encodedProof = encodeProof(proof); + return { + nullifiers, + outputCommitments, + encodedProof + }; +} + +export async function prepareWithdrawProof(signer: User, inputs: UTXO[], output: UTXO) { + const inputCommitments: [BigNumberish, BigNumberish] = inputs.map((input) => input.hash) as [BigNumberish, BigNumberish]; + const inputValues = inputs.map((input) => BigInt(input.value || 0n)); + const inputSalts = inputs.map((input) => input.salt || 0n); + const outputCommitments: [BigNumberish] = [output.hash] as [BigNumberish]; + const outputValues = [BigInt(output.value || 0n)]; + const outputOwnerPublicKeys: [[BigNumberish, BigNumberish]] = [signer.babyJubPublicKey] as [[BigNumberish, BigNumberish]]; + + const inputObj = { + inputCommitments, + inputValues, + inputSalts, + inputOwnerPrivateKey: signer.formattedPrivateKey, + outputCommitments, + outputValues, + outputSalts: [output.salt], + outputOwnerPublicKeys + }; + const circuit = await loadCircuit('check_inputs_outputs_value'); + const { provingKeyFile } = loadProvingKeys('check_inputs_outputs_value'); + + const startWitnessCalculation = Date.now(); + const witness = await circuit.calculateWTNSBin( + inputObj, + true + ); + const timeWithnessCalculation = Date.now() - startWitnessCalculation; + + const startProofGeneration = Date.now(); + const { proof, publicSignals } = await groth16.prove(provingKeyFile, witness) as { proof: BigNumberish[]; publicSignals: BigNumberish[] }; + const timeProofGeneration = Date.now() - startProofGeneration; + + console.log(`Witness calculation time: ${timeWithnessCalculation}ms. Proof generation time: ${timeProofGeneration}ms.`); + + const encodedProof = encodeProof(proof); + return { + inputCommitments, + outputCommitments, + encodedProof + }; +} diff --git a/solidity/test/zeto_anon.ts b/solidity/test/zeto_anon.ts index 72dc7cd..42fd01e 100644 --- a/solidity/test/zeto_anon.ts +++ b/solidity/test/zeto_anon.ts @@ -20,10 +20,11 @@ import { expect } from 'chai'; import { loadCircuit, encodeProof, Poseidon } from "zeto-js"; import { groth16 } from 'snarkjs'; import { formatPrivKeyForBabyJub, stringifyBigInts } from 'maci-crypto'; -import { User, UTXO, newUser, newUTXO, doMint, parseUTXOEvents } from './lib/utils'; +import { User, UTXO, newUser, newUTXO, doMint, parseUTXOEvents, ZERO_UTXO } from './lib/utils'; import RegistryModule from '../ignition/modules/registry'; import zetoModule from '../ignition/modules/zeto_anon'; -import { loadProvingKeys } from './utils'; +import erc20Module from '../ignition/modules/erc20'; +import { loadProvingKeys, prepareDepositProof, prepareWithdrawProof } from './utils'; const ZERO_PUBKEY = [0, 0]; const poseidonHash = Poseidon.poseidon4; @@ -33,7 +34,9 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi let Alice: User; let Bob: User; let Charlie: User; + let erc20: any; let zeto: any; + let utxo100: UTXO; let utxo1: UTXO; let utxo2: UTXO; let utxo3: UTXO; @@ -57,10 +60,29 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi const tx3 = await registry.connect(deployer).register(Charlie.ethAddress, Charlie.babyJubPublicKey as [BigNumberish, BigNumberish]); await tx3.wait(); + ({ erc20 } = await ignition.deploy(erc20Module)); + const tx4 = await zeto.connect(deployer).setERC20(erc20.target); + await tx4.wait(); + circuit = await loadCircuit('anon'); ({ provingKeyFile: provingKey } = loadProvingKeys('anon')); }); + it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { + const tx = await erc20.connect(deployer).mint(Alice.ethAddress, 100); + await tx.wait(); + const balance = await erc20.balanceOf(Alice.ethAddress); + expect(balance).to.equal(100); + + const tx1 = await erc20.connect(Alice.signer).approve(zeto.target, 100); + await tx1.wait(); + + utxo100 = newUTXO(100, Alice); + const { outputCommitments, encodedProof } = await prepareDepositProof(Alice, utxo100); + const tx2 = await zeto.connect(Alice.signer).deposit(100, outputCommitments[0], encodedProof); + await tx2.wait(); + }); + it("mint to Alice and transfer UTXOs honestly to Bob should succeed", async function () { // first the authority mints UTXOs to Alice utxo1 = newUTXO(10, Alice); @@ -102,6 +124,21 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi await doBranch(Bob, [utxo3, _utxo1], [_utxo2, utxo7], [Charlie, Bob]); }); + it("Alice withdraws her UTXOs to ERC20 tokens should succeed", async function () { + // Alice proposes the output ERC20 tokens + const outputCommitment = newUTXO(20, Alice); + + const { inputCommitments, outputCommitments, encodedProof } = await prepareWithdrawProof(Alice, [utxo100, ZERO_UTXO], outputCommitment); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto.connect(Alice.signer).withdraw(80, inputCommitments, outputCommitments[0], encodedProof); + await tx.wait(); + + // Alice checks her ERC20 balance + const balance = await erc20.balanceOf(Alice.ethAddress); + expect(balance).to.equal(80); + }); + it("mint existing unspent UTXOs should fail", async function () { await expect(doMint(zeto, deployer, [utxo4])).rejectedWith("UTXOAlreadyOwned"); }); diff --git a/solidity/test/zeto_anon_enc.ts b/solidity/test/zeto_anon_enc.ts index b167f1c..d5fb596 100644 --- a/solidity/test/zeto_anon_enc.ts +++ b/solidity/test/zeto_anon_enc.ts @@ -22,8 +22,9 @@ import { groth16 } from 'snarkjs'; import { genRandomSalt, formatPrivKeyForBabyJub, genEcdhSharedKey, stringifyBigInts } from 'maci-crypto'; import RegistryModule from '../ignition/modules/registry'; import zetoModule from '../ignition/modules/zeto_anon_enc'; +import erc20Module from '../ignition/modules/erc20'; import { User, UTXO, newUser, newUTXO, doMint, ZERO_UTXO, parseUTXOEvents } from './lib/utils'; -import { loadProvingKeys } from './utils'; +import { loadProvingKeys, prepareDepositProof, prepareWithdrawProof } from './utils'; const poseidonHash = Poseidon.poseidon4; @@ -32,8 +33,10 @@ describe("Zeto based fungible token with anonymity and encryption", function () let Alice: User; let Bob: User; let Charlie: User; + let erc20: any; let zeto: any; let registry: any; + let utxo100: UTXO; let utxo1: UTXO; let utxo2: UTXO; let utxo3: UTXO; @@ -57,10 +60,29 @@ describe("Zeto based fungible token with anonymity and encryption", function () const tx3 = await registry.connect(deployer).register(Charlie.ethAddress, Charlie.babyJubPublicKey as [BigNumberish, BigNumberish]); await tx3.wait(); + ({ erc20 } = await ignition.deploy(erc20Module)); + const tx4 = await zeto.connect(deployer).setERC20(erc20.target); + await tx4.wait(); + circuit = await loadCircuit('anon_enc'); ({ provingKeyFile: provingKey } = loadProvingKeys('anon_enc')); }); + it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { + const tx = await erc20.connect(deployer).mint(Alice.ethAddress, 100); + await tx.wait(); + const balance = await erc20.balanceOf(Alice.ethAddress); + expect(balance).to.equal(100); + + const tx1 = await erc20.connect(Alice.signer).approve(zeto.target, 100); + await tx1.wait(); + + utxo100 = newUTXO(100, Alice); + const { outputCommitments, encodedProof } = await prepareDepositProof(Alice, utxo100); + const tx2 = await zeto.connect(Alice.signer).deposit(100, outputCommitments[0], encodedProof); + await tx2.wait(); + }); + it("mint to Alice and transfer UTXOs honestly to Bob should succeed", async function () { // first the authority mints UTXOs to Alice utxo1 = newUTXO(10, Alice); @@ -102,6 +124,21 @@ describe("Zeto based fungible token with anonymity and encryption", function () await doBranch(Bob, [utxo3, ZERO_UTXO], [_utxo1, ZERO_UTXO], [Charlie, Bob]); }); + it("Alice withdraws her UTXOs to ERC20 tokens should succeed", async function () { + // Alice proposes the output ERC20 tokens + const outputCommitment = newUTXO(20, Alice); + + const { inputCommitments, outputCommitments, encodedProof } = await prepareWithdrawProof(Alice, [utxo100, ZERO_UTXO], outputCommitment); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto.connect(Alice.signer).withdraw(80, inputCommitments, outputCommitments[0], encodedProof); + await tx.wait(); + + // Alice checks her ERC20 balance + const balance = await erc20.balanceOf(Alice.ethAddress); + expect(balance).to.equal(80); + }); + it("mint existing unspent UTXOs should fail", async function () { await expect(doMint(zeto, deployer, [utxo4])).rejectedWith("UTXOAlreadyOwned"); }); diff --git a/solidity/test/zeto_anon_enc_nullifier.ts b/solidity/test/zeto_anon_enc_nullifier.ts index 469236a..b378c84 100644 --- a/solidity/test/zeto_anon_enc_nullifier.ts +++ b/solidity/test/zeto_anon_enc_nullifier.ts @@ -20,18 +20,21 @@ import { expect } from 'chai'; import { loadCircuit, poseidonDecrypt, encodeProof } from "zeto-js"; import { groth16 } from 'snarkjs'; import { genRandomSalt, genEcdhSharedKey, stringifyBigInts } from 'maci-crypto'; -import { Merkletree, InMemoryDB, str2Bytes } from '@iden3/js-merkletree'; +import { Merkletree, InMemoryDB, str2Bytes, ZERO_HASH } from '@iden3/js-merkletree'; import RegistryModule from '../ignition/modules/registry'; import zetoModule from '../ignition/modules/zeto_anon_enc_nullifier'; +import erc20Module from '../ignition/modules/erc20'; import { UTXO, User, newUser, newUTXO, newNullifier, doMint, ZERO_UTXO, parseUTXOEvents } from './lib/utils'; -import { loadProvingKeys } from './utils'; +import { loadProvingKeys, prepareDepositProof, prepareNullifierWithdrawProof } from './utils'; describe("Zeto based fungible token with anonymity using nullifiers and encryption", function () { let deployer: Signer; let Alice: User; let Bob: User; let Charlie: User; + let erc20: any; let zeto: any; + let utxo100: UTXO; let utxo1: UTXO; let utxo2: UTXO; let utxo3: UTXO; @@ -57,6 +60,10 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti const tx3 = await registry.connect(deployer).register(Charlie.ethAddress, Charlie.babyJubPublicKey as [BigNumberish, BigNumberish]); await tx3.wait(); + ({ erc20 } = await ignition.deploy(erc20Module)); + const tx4 = await zeto.connect(deployer).setERC20(erc20.target); + await tx4.wait(); + circuit = await loadCircuit('anon_enc_nullifier'); ({ provingKeyFile: provingKey } = loadProvingKeys('anon_enc_nullifier')); @@ -74,6 +81,24 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti expect(root.string()).to.equal(onchainRoot.toString()); }); + it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { + const tx = await erc20.connect(deployer).mint(Alice.ethAddress, 100); + await tx.wait(); + const balance = await erc20.balanceOf(Alice.ethAddress); + expect(balance).to.equal(100); + + const tx1 = await erc20.connect(Alice.signer).approve(zeto.target, 100); + await tx1.wait(); + + utxo100 = newUTXO(100, Alice); + const { outputCommitments, encodedProof } = await prepareDepositProof(Alice, utxo100); + const tx2 = await zeto.connect(Alice.signer).deposit(100, outputCommitments[0], encodedProof); + await tx2.wait(); + + await smtAlice.add(utxo100.hash, utxo100.hash); + await smtBob.add(utxo100.hash, utxo100.hash); + }); + it("mint to Alice and transfer UTXOs honestly to Bob should succeed", async function () { // The authority mints a new UTXO and assigns it to Alice utxo1 = newUTXO(10, Alice); @@ -162,6 +187,30 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti await smtAlice.add(events[0].outputs[1], events[0].outputs[1]); }).timeout(600000); + it("Alice withdraws her UTXOs to ERC20 tokens should succeed", async function () { + // Alice generates the nullifiers for the UTXOs to be spent + const nullifier1 = newNullifier(utxo100, Alice); + + // Alice generates inclusion proofs for the UTXOs to be spent + let root = await smtAlice.root(); + const proof1 = await smtAlice.generateCircomVerifierProof(utxo100.hash, root); + const proof2 = await smtAlice.generateCircomVerifierProof(0n, root); + const merkleProofs = [proof1.siblings.map((s) => s.bigInt()), proof2.siblings.map((s) => s.bigInt())]; + + // Alice proposes the output ERC20 tokens + const outputCommitment = newUTXO(20, Alice); + + const { nullifiers, outputCommitments, encodedProof } = await prepareNullifierWithdrawProof(Alice, [utxo100, ZERO_UTXO], [nullifier1, ZERO_UTXO], outputCommitment, root.bigInt(), merkleProofs); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto.connect(Alice.signer).withdraw(80, nullifiers, outputCommitments[0], root.bigInt(), encodedProof); + await tx.wait(); + + // Alice checks her ERC20 balance + const balance = await erc20.balanceOf(Alice.ethAddress); + expect(balance).to.equal(80); + }); + it("mint existing unspent UTXOs should fail", async function () { await expect(doMint(zeto, deployer, [utxo4])).rejectedWith("UTXOAlreadyOwned"); }); diff --git a/solidity/test/zeto_anon_nullifier.ts b/solidity/test/zeto_anon_nullifier.ts index 67dd38f..930d363 100644 --- a/solidity/test/zeto_anon_nullifier.ts +++ b/solidity/test/zeto_anon_nullifier.ts @@ -22,15 +22,18 @@ import { groth16 } from 'snarkjs'; import { Merkletree, InMemoryDB, str2Bytes } from '@iden3/js-merkletree'; import RegistryModule from '../ignition/modules/registry'; import zetoModule from '../ignition/modules/zeto_anon_nullifier'; +import erc20Module from '../ignition/modules/erc20'; import { UTXO, User, newUser, newUTXO, newNullifier, doMint, ZERO_UTXO, parseUTXOEvents } from './lib/utils'; -import { loadProvingKeys } from './utils'; +import { loadProvingKeys, prepareDepositProof, prepareNullifierWithdrawProof } from './utils'; describe("Zeto based fungible token with anonymity using nullifiers without encryption", function () { let deployer: Signer; let Alice: User; let Bob: User; let Charlie: User; + let erc20: any; let zeto: any; + let utxo100: UTXO; let utxo1: UTXO; let utxo2: UTXO; let utxo3: UTXO; @@ -56,6 +59,10 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr const tx3 = await registry.connect(deployer).register(Charlie.ethAddress, Charlie.babyJubPublicKey as [BigNumberish, BigNumberish]); await tx3.wait(); + ({ erc20 } = await ignition.deploy(erc20Module)); + const tx4 = await zeto.connect(deployer).setERC20(erc20.target); + await tx4.wait(); + circuit = await loadCircuit('anon_nullifier'); ({ provingKeyFile: provingKey } = loadProvingKeys('anon_nullifier')); @@ -73,6 +80,24 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr expect(root.string()).to.equal(onchainRoot.toString()); }); + it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () { + const tx = await erc20.connect(deployer).mint(Alice.ethAddress, 100); + await tx.wait(); + const balance = await erc20.balanceOf(Alice.ethAddress); + expect(balance).to.equal(100); + + const tx1 = await erc20.connect(Alice.signer).approve(zeto.target, 100); + await tx1.wait(); + + utxo100 = newUTXO(100, Alice); + const { outputCommitments, encodedProof } = await prepareDepositProof(Alice, utxo100); + const tx2 = await zeto.connect(Alice.signer).deposit(100, outputCommitments[0], encodedProof); + await tx2.wait(); + + await smtAlice.add(utxo100.hash, utxo100.hash); + await smtBob.add(utxo100.hash, utxo100.hash); + }); + it("mint to Alice and transfer UTXOs honestly to Bob should succeed", async function () { // The authority mints a new UTXO and assigns it to Alice utxo1 = newUTXO(10, Alice); @@ -163,6 +188,30 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr await smtAlice.add(events[0].outputs[1], events[0].outputs[1]); }).timeout(600000); + it("Alice withdraws her UTXOs to ERC20 tokens should succeed", async function () { + // Alice generates the nullifiers for the UTXOs to be spent + const nullifier1 = newNullifier(utxo100, Alice); + + // Alice generates inclusion proofs for the UTXOs to be spent + let root = await smtAlice.root(); + const proof1 = await smtAlice.generateCircomVerifierProof(utxo100.hash, root); + const proof2 = await smtAlice.generateCircomVerifierProof(0n, root); + const merkleProofs = [proof1.siblings.map((s) => s.bigInt()), proof2.siblings.map((s) => s.bigInt())]; + + // Alice proposes the output ERC20 tokens + const outputCommitment = newUTXO(20, Alice); + + const { nullifiers, outputCommitments, encodedProof } = await prepareNullifierWithdrawProof(Alice, [utxo100, ZERO_UTXO], [nullifier1, ZERO_UTXO], outputCommitment, root.bigInt(), merkleProofs); + + // Alice withdraws her UTXOs to ERC20 tokens + const tx = await zeto.connect(Alice.signer).withdraw(80, nullifiers, outputCommitments[0], root.bigInt(), encodedProof); + await tx.wait(); + + // Alice checks her ERC20 balance + const balance = await erc20.balanceOf(Alice.ethAddress); + expect(balance).to.equal(80); + }); + it("mint existing unspent UTXOs should fail", async function () { await expect(doMint(zeto, deployer, [utxo4])).rejectedWith("UTXOAlreadyOwned"); }); diff --git a/zkp/circuits/check_hashes_value.circom b/zkp/circuits/check_hashes_value.circom new file mode 100644 index 0000000..69b0a2e --- /dev/null +++ b/zkp/circuits/check_hashes_value.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-hashes-value.circom"; + +component main {public [ outputCommitments ]} = CheckHashesValue(1); \ No newline at end of file diff --git a/zkp/circuits/check_inputs_outputs_value.circom b/zkp/circuits/check_inputs_outputs_value.circom new file mode 100644 index 0000000..c072b37 --- /dev/null +++ b/zkp/circuits/check_inputs_outputs_value.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.circom"; + +component main { public [ inputCommitments, outputCommitments ] } = CheckInputsOutputsValue(2, 1); \ No newline at end of file diff --git a/zkp/circuits/check_nullifier_value.circom b/zkp/circuits/check_nullifier_value.circom new file mode 100644 index 0000000..fcec3df --- /dev/null +++ b/zkp/circuits/check_nullifier_value.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.circom"; + +component main { public [ nullifiers, outputCommitments, root, enabled ] } = CheckNullifierValue(2, 1, 64); \ No newline at end of file diff --git a/zkp/circuits/lib/check-hashes-value.circom b/zkp/circuits/lib/check-hashes-value.circom new file mode 100644 index 0000000..4b7c9b3 --- /dev/null +++ b/zkp/circuits/lib/check-hashes-value.circom @@ -0,0 +1,76 @@ +// 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 "../node_modules/circomlib/circuits/poseidon.circom"; +include "../node_modules/circomlib/circuits/comparators.circom"; + +// CheckHashesValue is a circuit that checks the integrity of transactions of Fungible Tokens +// - check that all output values are positive numbers (within the range of 0 to 2^40) +// - check that the output commitments are the hash of the output values +// - check that the sum of output values equals a total value in the output +// +// output commitments: array of hashes for the output utxos +// outputValues: array of values, as preimages for the output hashes, for the output utxos +// +// commitment = hash(value, salt, ownerAddress) +// +template CheckHashesValue(numOutputs) { + signal input outputCommitments[numOutputs]; + signal input outputValues[numOutputs]; + signal input outputSalts[numOutputs]; + signal input outputOwnerPublicKeys[numOutputs][2]; + signal output out; + + // check that the output values are within the expected range. we don't allow negative values + component positive[numOutputs]; + var isPositive[numOutputs]; + for (var i = 0; i < numOutputs; i++) { + positive[i] = GreaterEqThan(40); + positive[i].in[0] <== outputValues[i]; + positive[i].in[1] <== 0; + isPositive[i] = positive[i].out; + assert(isPositive[i] == 1); + } + + // hash the output values + component outputHashes[numOutputs]; + var calculatedOutputHashes[numOutputs]; + for (var i = 0; i < numOutputs; i++) { + outputHashes[i] = Poseidon(4); + outputHashes[i].inputs[0] <== outputValues[i]; + outputHashes[i].inputs[1] <== outputSalts[i]; + outputHashes[i].inputs[2] <== outputOwnerPublicKeys[i][0]; + outputHashes[i].inputs[3] <== outputOwnerPublicKeys[i][1]; + if (outputCommitments[i] == 0) { + calculatedOutputHashes[i] = 0; + } else { + calculatedOutputHashes[i] = outputHashes[i].out; + } + } + + // check that the output commitments match the calculated hashes + for (var i = 0; i < numOutputs; i++) { + assert(outputCommitments[i] == calculatedOutputHashes[i]); + } + + // calculate the sum of output values and set to the output + var sumOutputs = 0; + for (var i = 0; i < numOutputs; i++) { + sumOutputs = sumOutputs + outputValues[i]; + } + out <== sumOutputs; +} diff --git a/zkp/circuits/lib/check-inputs-outputs-value.circom b/zkp/circuits/lib/check-inputs-outputs-value.circom new file mode 100644 index 0000000..0a980ba --- /dev/null +++ b/zkp/circuits/lib/check-inputs-outputs-value.circom @@ -0,0 +1,129 @@ +// 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 "../node_modules/circomlib/circuits/poseidon.circom"; +include "../node_modules/circomlib/circuits/comparators.circom"; +include "../node_modules/circomlib/circuits/babyjub.circom"; +include "../node_modules/circomlib/circuits/smt/smtverifier.circom"; + +// CheckInputsOutputsValue is a circuit that checks the integrity of transactions of Fungible Tokens +// - check that all output values are positive numbers (within the range of 0 to 2^40) +// - check that the input commitments are correctly computed from the input values, salts, and owner public keys +// - check that the output commitments are the hash of the output values +// - check that the sum of input values equals the output values plus a total value in the circuit output +// +// input commitments: array of hashes for the input utxos +// inputValues: array of values, as preimages for the input hashes, for the input utxos +// output commitments: array of hashes for the output utxos +// outputValues: array of values, as preimages for the output hashes, for the output utxos +// +// commitment = hash(value, salt, ownerPublicKey1, ownerPublicKey2) +// +template CheckInputsOutputsValue(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; + + // check that the output values are within the expected range. we don't allow negative values + component positive[numOutputs]; + var isPositive[numOutputs]; + for (var i = 0; i < numOutputs; i++) { + positive[i] = GreaterEqThan(40); + positive[i].in[0] <== outputValues[i]; + positive[i].in[1] <== 0; + isPositive[i] = positive[i].out; + assert(isPositive[i] == 1); + } + + // 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; + + // hash the input values + component inputHashes[numInputs]; + var calculatedInputHashes[numInputs]; + for (var i = 0; i < numInputs; i++) { + // perform the hash calculation even though they are not needed when the input + // commitment at the current index is 0; this is because in zkp circuits we + // must always perform the same computation (have the the same constraints) + inputHashes[i] = Poseidon(4); + inputHashes[i].inputs[0] <== inputValues[i]; + inputHashes[i].inputs[1] <== inputSalts[i]; + inputHashes[i].inputs[2] <== inputOwnerPublicKey[0]; + inputHashes[i].inputs[3] <== inputOwnerPublicKey[1]; + if (inputCommitments[i] == 0) { + calculatedInputHashes[i] = 0; + } else { + calculatedInputHashes[i] = inputHashes[i].out; + } + } + + // check that the input commitments match the calculated hashes + for (var i = 0; i < numInputs; i++) { + assert(inputCommitments[i] == calculatedInputHashes[i]); + } + + // hash the output values + component outputHashes[numOutputs]; + var calculatedOutputHashes[numOutputs]; + for (var i = 0; i < numOutputs; i++) { + outputHashes[i] = Poseidon(4); + outputHashes[i].inputs[0] <== outputValues[i]; + outputHashes[i].inputs[1] <== outputSalts[i]; + outputHashes[i].inputs[2] <== outputOwnerPublicKeys[i][0]; + outputHashes[i].inputs[3] <== outputOwnerPublicKeys[i][1]; + if (outputCommitments[i] == 0) { + calculatedOutputHashes[i] = 0; + } else { + calculatedOutputHashes[i] = outputHashes[i].out; + } + } + + // check that the output commitments match the calculated hashes + for (var i = 0; i < numOutputs; i++) { + assert(outputCommitments[i] == calculatedOutputHashes[i]); + } + + // 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; +} diff --git a/zkp/circuits/lib/check-nullifier-value.circom b/zkp/circuits/lib/check-nullifier-value.circom new file mode 100644 index 0000000..f6e8faf --- /dev/null +++ b/zkp/circuits/lib/check-nullifier-value.circom @@ -0,0 +1,176 @@ +// 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 "../node_modules/circomlib/circuits/poseidon.circom"; +include "../node_modules/circomlib/circuits/comparators.circom"; +include "../node_modules/circomlib/circuits/babyjub.circom"; +include "../node_modules/circomlib/circuits/smt/smtverifier.circom"; + +// CheckNullifierValue is a circuit that checks the integrity of transactions of Fungible Tokens +// - check that all output values are positive numbers (within the range of 0 to 2^40) +// - check that the nullifiers are correctly computed from the input values and salts +// - check that the input commitments are correctly computed from the input values, salts, and owner public keys +// - check that the input commitments are included in the Sparse Merkle Tree with the root `root` +// - check that the output commitments are the hash of the output values +// - check that the sum of input values equals the output values plus a total value in the output +// +// nullifiers: array of hashes for the nullifiers corresponding to the input utxos +// inputValues: array of values, as preimages for the input hashes, for the input utxos +// output commitments: array of hashes for the output utxos +// outputValues: array of values, as preimages for the output hashes, for the output utxos +// +// commitment = hash(value, salt, ownerPublicKey1, ownerPublicKey2) +// nullifier = hash(value, salt, ownerPrivatekey) +// +template CheckNullifierValue(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; + + // check that the output values are within the expected range. we don't allow negative values + component positive[numOutputs]; + var isPositive[numOutputs]; + for (var i = 0; i < numOutputs; i++) { + positive[i] = GreaterEqThan(40); + positive[i].in[0] <== outputValues[i]; + positive[i].in[1] <== 0; + isPositive[i] = positive[i].out; + assert(isPositive[i] == 1); + } + + // 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; + + // hash the input values + component inputHashes[numInputs]; + var calculatedInputHashes[numInputs]; + for (var i = 0; i < numInputs; i++) { + // perform the hash calculation even though they are not needed when the input + // commitment at the current index is 0; this is because in zkp circuits we + // must always perform the same computation (have the the same constraints) + inputHashes[i] = Poseidon(4); + inputHashes[i].inputs[0] <== inputValues[i]; + inputHashes[i].inputs[1] <== inputSalts[i]; + inputHashes[i].inputs[2] <== inputOwnerPublicKey[0]; + inputHashes[i].inputs[3] <== inputOwnerPublicKey[1]; + if (inputCommitments[i] == 0) { + calculatedInputHashes[i] = 0; + } else { + calculatedInputHashes[i] = inputHashes[i].out; + } + } + + // check that the input commitments match the calculated hashes + for (var i = 0; i < numInputs; i++) { + assert(inputCommitments[i] == calculatedInputHashes[i]); + } + + // calculate the nullifier values from the input values + component nullifierHashes[numInputs]; + var calculatedNullifierHashes[numInputs]; + for (var i = 0; i < numInputs; i++) { + nullifierHashes[i] = Poseidon(3); + nullifierHashes[i].inputs[0] <== inputValues[i]; + nullifierHashes[i].inputs[1] <== inputSalts[i]; + nullifierHashes[i].inputs[2] <== inputOwnerPrivateKey; + if (nullifiers[i] == 0) { + calculatedNullifierHashes[i] = 0; + } else { + calculatedNullifierHashes[i] = nullifierHashes[i].out; + } + } + + // check that the nullifiers match the calculated hashes + for (var i = 0; i < numInputs; i++) { + assert(nullifiers[i] == calculatedNullifierHashes[i]); + } + + // 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 smtVerifier[numInputs]; + for (var i = 0; i < numInputs; i++) { + smtVerifier[i] = SMTVerifier(nSMTLevels); + smtVerifier[i].enabled <== enabled[i]; + smtVerifier[i].root <== root; + for (var j = 0; j < nSMTLevels; j++) { + smtVerifier[i].siblings[j] <== merkleProof[i][j]; + } + smtVerifier[i].oldKey <== 0; + smtVerifier[i].oldValue <== 0; + smtVerifier[i].isOld0 <== 0; + smtVerifier[i].key <== inputCommitments[i]; + smtVerifier[i].value <== inputCommitments[i]; + smtVerifier[i].fnc <== 0; + } + + // hash the output values + component outputHashes[numOutputs]; + var calculatedOutputHashes[numOutputs]; + for (var i = 0; i < numOutputs; i++) { + outputHashes[i] = Poseidon(4); + outputHashes[i].inputs[0] <== outputValues[i]; + outputHashes[i].inputs[1] <== outputSalts[i]; + outputHashes[i].inputs[2] <== outputOwnerPublicKeys[i][0]; + outputHashes[i].inputs[3] <== outputOwnerPublicKeys[i][1]; + if (outputCommitments[i] == 0) { + calculatedOutputHashes[i] = 0; + } else { + calculatedOutputHashes[i] = outputHashes[i].out; + } + } + + // check that the output commitments match the calculated hashes + for (var i = 0; i < numOutputs; i++) { + assert(outputCommitments[i] == calculatedOutputHashes[i]); + } + + // 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; +} diff --git a/zkp/js/README.md b/zkp/js/README.md index 2b7d0f3..bdc2e84 100644 --- a/zkp/js/README.md +++ b/zkp/js/README.md @@ -43,14 +43,18 @@ The different `ptau` files represent different levels of complexity with the cir The steps below use `X` for the name of the circuit, and `Y` for the sequence number of the ptau files: -| X | Y | -| --------------------------- | --------------------------------- | -| `anon.circom` | `powersOfTau28_hez_final_12.ptau` | -| `anon_enc.circom` | `powersOfTau28_hez_final_13.ptau` | -| `anon_nullifier.circom` | `powersOfTau28_hez_final_16.ptau` | -| `anon_enc_nullifier.circom` | `powersOfTau28_hez_final_16.ptau` | -| `nf_anon.circom` | `powersOfTau28_hez_final_11.ptau` | -| `nf_anon_nullifier.circom` | `powersOfTau28_hez_final_15.ptau` | +| X | Y | +| ---------------------------- | --------------------------------- | +| `anon.circom` | `powersOfTau28_hez_final_12.ptau` | +| `anon_enc.circom` | `powersOfTau28_hez_final_13.ptau` | +| `anon_nullifier.circom` | `powersOfTau28_hez_final_16.ptau` | +| `anon_enc_nullifier.circom` | `powersOfTau28_hez_final_16.ptau` | +| `nf_anon.circom` | `powersOfTau28_hez_final_11.ptau` | +| `nf_anon_nullifier.circom` | `powersOfTau28_hez_final_15.ptau` | +| `check_hashes_value.circom` | `powersOfTau28_hez_final_09.ptau` | +| `check_inputs_outputs_value` | `powersOfTau28_hez_final_11.ptau` | +| `check_nullifier_value` | `powersOfTau28_hez_final_16.ptau` | +| `check_nullifiers.circom` | `powersOfTau28_hez_final_11.ptau` | ### Generating the R1CS circuit format diff --git a/zkp/js/lib/anon_nullifier_js/anon_nullifier.wasm b/zkp/js/lib/anon_nullifier_js/anon_nullifier.wasm index 341ede3..ef56df3 100644 Binary files a/zkp/js/lib/anon_nullifier_js/anon_nullifier.wasm and b/zkp/js/lib/anon_nullifier_js/anon_nullifier.wasm differ diff --git a/zkp/js/lib/check_hashes_value.sym b/zkp/js/lib/check_hashes_value.sym new file mode 100644 index 0000000..0c4bb4f --- /dev/null +++ b/zkp/js/lib/check_hashes_value.sym @@ -0,0 +1,1221 @@ +1,1,77,main.out +2,2,77,main.outputCommitments[0] +3,-1,77,main.outputValues[0] +4,3,77,main.outputSalts[0] +5,4,77,main.outputOwnerPublicKeys[0][0] +6,5,77,main.outputOwnerPublicKeys[0][1] +7,6,76,main.outputHashes[0].out +8,-1,76,main.outputHashes[0].inputs[0] +9,-1,76,main.outputHashes[0].inputs[1] +10,-1,76,main.outputHashes[0].inputs[2] +11,-1,76,main.outputHashes[0].inputs[3] +12,-1,75,main.outputHashes[0].pEx.out[0] +13,-1,75,main.outputHashes[0].pEx.inputs[0] +14,-1,75,main.outputHashes[0].pEx.inputs[1] +15,-1,75,main.outputHashes[0].pEx.inputs[2] +16,-1,75,main.outputHashes[0].pEx.inputs[3] +17,-1,75,main.outputHashes[0].pEx.initialState +18,-1,3,main.outputHashes[0].pEx.ark[0].out[0] +19,-1,3,main.outputHashes[0].pEx.ark[0].out[1] +20,-1,3,main.outputHashes[0].pEx.ark[0].out[2] +21,-1,3,main.outputHashes[0].pEx.ark[0].out[3] +22,-1,3,main.outputHashes[0].pEx.ark[0].out[4] +23,-1,3,main.outputHashes[0].pEx.ark[0].in[0] +24,-1,3,main.outputHashes[0].pEx.ark[0].in[1] +25,-1,3,main.outputHashes[0].pEx.ark[0].in[2] +26,-1,3,main.outputHashes[0].pEx.ark[0].in[3] +27,-1,3,main.outputHashes[0].pEx.ark[0].in[4] +28,-1,5,main.outputHashes[0].pEx.ark[1].out[0] +29,7,5,main.outputHashes[0].pEx.ark[1].out[1] +30,8,5,main.outputHashes[0].pEx.ark[1].out[2] +31,9,5,main.outputHashes[0].pEx.ark[1].out[3] +32,10,5,main.outputHashes[0].pEx.ark[1].out[4] +33,-1,5,main.outputHashes[0].pEx.ark[1].in[0] +34,-1,5,main.outputHashes[0].pEx.ark[1].in[1] +35,-1,5,main.outputHashes[0].pEx.ark[1].in[2] +36,-1,5,main.outputHashes[0].pEx.ark[1].in[3] +37,-1,5,main.outputHashes[0].pEx.ark[1].in[4] +38,11,7,main.outputHashes[0].pEx.ark[2].out[0] +39,12,7,main.outputHashes[0].pEx.ark[2].out[1] +40,13,7,main.outputHashes[0].pEx.ark[2].out[2] +41,14,7,main.outputHashes[0].pEx.ark[2].out[3] +42,15,7,main.outputHashes[0].pEx.ark[2].out[4] +43,-1,7,main.outputHashes[0].pEx.ark[2].in[0] +44,-1,7,main.outputHashes[0].pEx.ark[2].in[1] +45,-1,7,main.outputHashes[0].pEx.ark[2].in[2] +46,-1,7,main.outputHashes[0].pEx.ark[2].in[3] +47,-1,7,main.outputHashes[0].pEx.ark[2].in[4] +48,16,8,main.outputHashes[0].pEx.ark[3].out[0] +49,17,8,main.outputHashes[0].pEx.ark[3].out[1] +50,18,8,main.outputHashes[0].pEx.ark[3].out[2] +51,19,8,main.outputHashes[0].pEx.ark[3].out[3] +52,20,8,main.outputHashes[0].pEx.ark[3].out[4] +53,-1,8,main.outputHashes[0].pEx.ark[3].in[0] +54,-1,8,main.outputHashes[0].pEx.ark[3].in[1] +55,-1,8,main.outputHashes[0].pEx.ark[3].in[2] +56,-1,8,main.outputHashes[0].pEx.ark[3].in[3] +57,-1,8,main.outputHashes[0].pEx.ark[3].in[4] +58,21,9,main.outputHashes[0].pEx.ark[4].out[0] +59,-1,9,main.outputHashes[0].pEx.ark[4].out[1] +60,-1,9,main.outputHashes[0].pEx.ark[4].out[2] +61,-1,9,main.outputHashes[0].pEx.ark[4].out[3] +62,-1,9,main.outputHashes[0].pEx.ark[4].out[4] +63,-1,9,main.outputHashes[0].pEx.ark[4].in[0] +64,-1,9,main.outputHashes[0].pEx.ark[4].in[1] +65,-1,9,main.outputHashes[0].pEx.ark[4].in[2] +66,-1,9,main.outputHashes[0].pEx.ark[4].in[3] +67,-1,9,main.outputHashes[0].pEx.ark[4].in[4] +68,22,71,main.outputHashes[0].pEx.ark[5].out[0] +69,23,71,main.outputHashes[0].pEx.ark[5].out[1] +70,24,71,main.outputHashes[0].pEx.ark[5].out[2] +71,25,71,main.outputHashes[0].pEx.ark[5].out[3] +72,26,71,main.outputHashes[0].pEx.ark[5].out[4] +73,-1,71,main.outputHashes[0].pEx.ark[5].in[0] +74,-1,71,main.outputHashes[0].pEx.ark[5].in[1] +75,-1,71,main.outputHashes[0].pEx.ark[5].in[2] +76,-1,71,main.outputHashes[0].pEx.ark[5].in[3] +77,-1,71,main.outputHashes[0].pEx.ark[5].in[4] +78,27,72,main.outputHashes[0].pEx.ark[6].out[0] +79,28,72,main.outputHashes[0].pEx.ark[6].out[1] +80,29,72,main.outputHashes[0].pEx.ark[6].out[2] +81,30,72,main.outputHashes[0].pEx.ark[6].out[3] +82,31,72,main.outputHashes[0].pEx.ark[6].out[4] +83,-1,72,main.outputHashes[0].pEx.ark[6].in[0] +84,-1,72,main.outputHashes[0].pEx.ark[6].in[1] +85,-1,72,main.outputHashes[0].pEx.ark[6].in[2] +86,-1,72,main.outputHashes[0].pEx.ark[6].in[3] +87,-1,72,main.outputHashes[0].pEx.ark[6].in[4] +88,32,73,main.outputHashes[0].pEx.ark[7].out[0] +89,33,73,main.outputHashes[0].pEx.ark[7].out[1] +90,34,73,main.outputHashes[0].pEx.ark[7].out[2] +91,35,73,main.outputHashes[0].pEx.ark[7].out[3] +92,36,73,main.outputHashes[0].pEx.ark[7].out[4] +93,-1,73,main.outputHashes[0].pEx.ark[7].in[0] +94,-1,73,main.outputHashes[0].pEx.ark[7].in[1] +95,-1,73,main.outputHashes[0].pEx.ark[7].in[2] +96,-1,73,main.outputHashes[0].pEx.ark[7].in[3] +97,-1,73,main.outputHashes[0].pEx.ark[7].in[4] +98,-1,6,main.outputHashes[0].pEx.mix[0].out[0] +99,-1,6,main.outputHashes[0].pEx.mix[0].out[1] +100,-1,6,main.outputHashes[0].pEx.mix[0].out[2] +101,-1,6,main.outputHashes[0].pEx.mix[0].out[3] +102,-1,6,main.outputHashes[0].pEx.mix[0].out[4] +103,-1,6,main.outputHashes[0].pEx.mix[0].in[0] +104,-1,6,main.outputHashes[0].pEx.mix[0].in[1] +105,-1,6,main.outputHashes[0].pEx.mix[0].in[2] +106,-1,6,main.outputHashes[0].pEx.mix[0].in[3] +107,-1,6,main.outputHashes[0].pEx.mix[0].in[4] +108,-1,6,main.outputHashes[0].pEx.mix[1].out[0] +109,-1,6,main.outputHashes[0].pEx.mix[1].out[1] +110,-1,6,main.outputHashes[0].pEx.mix[1].out[2] +111,-1,6,main.outputHashes[0].pEx.mix[1].out[3] +112,-1,6,main.outputHashes[0].pEx.mix[1].out[4] +113,-1,6,main.outputHashes[0].pEx.mix[1].in[0] +114,-1,6,main.outputHashes[0].pEx.mix[1].in[1] +115,-1,6,main.outputHashes[0].pEx.mix[1].in[2] +116,-1,6,main.outputHashes[0].pEx.mix[1].in[3] +117,-1,6,main.outputHashes[0].pEx.mix[1].in[4] +118,-1,6,main.outputHashes[0].pEx.mix[2].out[0] +119,-1,6,main.outputHashes[0].pEx.mix[2].out[1] +120,-1,6,main.outputHashes[0].pEx.mix[2].out[2] +121,-1,6,main.outputHashes[0].pEx.mix[2].out[3] +122,-1,6,main.outputHashes[0].pEx.mix[2].out[4] +123,-1,6,main.outputHashes[0].pEx.mix[2].in[0] +124,-1,6,main.outputHashes[0].pEx.mix[2].in[1] +125,-1,6,main.outputHashes[0].pEx.mix[2].in[2] +126,-1,6,main.outputHashes[0].pEx.mix[2].in[3] +127,-1,6,main.outputHashes[0].pEx.mix[2].in[4] +128,-1,10,main.outputHashes[0].pEx.mix[3].out[0] +129,-1,10,main.outputHashes[0].pEx.mix[3].out[1] +130,-1,10,main.outputHashes[0].pEx.mix[3].out[2] +131,-1,10,main.outputHashes[0].pEx.mix[3].out[3] +132,37,10,main.outputHashes[0].pEx.mix[3].out[4] +133,-1,10,main.outputHashes[0].pEx.mix[3].in[0] +134,-1,10,main.outputHashes[0].pEx.mix[3].in[1] +135,-1,10,main.outputHashes[0].pEx.mix[3].in[2] +136,-1,10,main.outputHashes[0].pEx.mix[3].in[3] +137,-1,10,main.outputHashes[0].pEx.mix[3].in[4] +138,-1,6,main.outputHashes[0].pEx.mix[4].out[0] +139,-1,6,main.outputHashes[0].pEx.mix[4].out[1] +140,-1,6,main.outputHashes[0].pEx.mix[4].out[2] +141,-1,6,main.outputHashes[0].pEx.mix[4].out[3] +142,-1,6,main.outputHashes[0].pEx.mix[4].out[4] +143,-1,6,main.outputHashes[0].pEx.mix[4].in[0] +144,-1,6,main.outputHashes[0].pEx.mix[4].in[1] +145,-1,6,main.outputHashes[0].pEx.mix[4].in[2] +146,-1,6,main.outputHashes[0].pEx.mix[4].in[3] +147,-1,6,main.outputHashes[0].pEx.mix[4].in[4] +148,-1,6,main.outputHashes[0].pEx.mix[5].out[0] +149,-1,6,main.outputHashes[0].pEx.mix[5].out[1] +150,-1,6,main.outputHashes[0].pEx.mix[5].out[2] +151,-1,6,main.outputHashes[0].pEx.mix[5].out[3] +152,-1,6,main.outputHashes[0].pEx.mix[5].out[4] +153,-1,6,main.outputHashes[0].pEx.mix[5].in[0] +154,-1,6,main.outputHashes[0].pEx.mix[5].in[1] +155,-1,6,main.outputHashes[0].pEx.mix[5].in[2] +156,-1,6,main.outputHashes[0].pEx.mix[5].in[3] +157,-1,6,main.outputHashes[0].pEx.mix[5].in[4] +158,-1,6,main.outputHashes[0].pEx.mix[6].out[0] +159,-1,6,main.outputHashes[0].pEx.mix[6].out[1] +160,-1,6,main.outputHashes[0].pEx.mix[6].out[2] +161,-1,6,main.outputHashes[0].pEx.mix[6].out[3] +162,-1,6,main.outputHashes[0].pEx.mix[6].out[4] +163,-1,6,main.outputHashes[0].pEx.mix[6].in[0] +164,-1,6,main.outputHashes[0].pEx.mix[6].in[1] +165,-1,6,main.outputHashes[0].pEx.mix[6].in[2] +166,-1,6,main.outputHashes[0].pEx.mix[6].in[3] +167,-1,6,main.outputHashes[0].pEx.mix[6].in[4] +168,-1,74,main.outputHashes[0].pEx.mixLast[0].out +169,38,74,main.outputHashes[0].pEx.mixLast[0].in[0] +170,39,74,main.outputHashes[0].pEx.mixLast[0].in[1] +171,40,74,main.outputHashes[0].pEx.mixLast[0].in[2] +172,41,74,main.outputHashes[0].pEx.mixLast[0].in[3] +173,-1,74,main.outputHashes[0].pEx.mixLast[0].in[4] +174,-1,11,main.outputHashes[0].pEx.mixS[0].out[0] +175,-1,11,main.outputHashes[0].pEx.mixS[0].out[1] +176,-1,11,main.outputHashes[0].pEx.mixS[0].out[2] +177,-1,11,main.outputHashes[0].pEx.mixS[0].out[3] +178,42,11,main.outputHashes[0].pEx.mixS[0].out[4] +179,-1,11,main.outputHashes[0].pEx.mixS[0].in[0] +180,-1,11,main.outputHashes[0].pEx.mixS[0].in[1] +181,-1,11,main.outputHashes[0].pEx.mixS[0].in[2] +182,-1,11,main.outputHashes[0].pEx.mixS[0].in[3] +183,-1,11,main.outputHashes[0].pEx.mixS[0].in[4] +184,-1,12,main.outputHashes[0].pEx.mixS[1].out[0] +185,-1,12,main.outputHashes[0].pEx.mixS[1].out[1] +186,-1,12,main.outputHashes[0].pEx.mixS[1].out[2] +187,-1,12,main.outputHashes[0].pEx.mixS[1].out[3] +188,43,12,main.outputHashes[0].pEx.mixS[1].out[4] +189,-1,12,main.outputHashes[0].pEx.mixS[1].in[0] +190,-1,12,main.outputHashes[0].pEx.mixS[1].in[1] +191,-1,12,main.outputHashes[0].pEx.mixS[1].in[2] +192,-1,12,main.outputHashes[0].pEx.mixS[1].in[3] +193,-1,12,main.outputHashes[0].pEx.mixS[1].in[4] +194,-1,13,main.outputHashes[0].pEx.mixS[2].out[0] +195,-1,13,main.outputHashes[0].pEx.mixS[2].out[1] +196,-1,13,main.outputHashes[0].pEx.mixS[2].out[2] +197,-1,13,main.outputHashes[0].pEx.mixS[2].out[3] +198,44,13,main.outputHashes[0].pEx.mixS[2].out[4] +199,-1,13,main.outputHashes[0].pEx.mixS[2].in[0] +200,-1,13,main.outputHashes[0].pEx.mixS[2].in[1] +201,-1,13,main.outputHashes[0].pEx.mixS[2].in[2] +202,-1,13,main.outputHashes[0].pEx.mixS[2].in[3] +203,-1,13,main.outputHashes[0].pEx.mixS[2].in[4] +204,-1,14,main.outputHashes[0].pEx.mixS[3].out[0] +205,-1,14,main.outputHashes[0].pEx.mixS[3].out[1] +206,-1,14,main.outputHashes[0].pEx.mixS[3].out[2] +207,-1,14,main.outputHashes[0].pEx.mixS[3].out[3] +208,45,14,main.outputHashes[0].pEx.mixS[3].out[4] +209,-1,14,main.outputHashes[0].pEx.mixS[3].in[0] +210,-1,14,main.outputHashes[0].pEx.mixS[3].in[1] +211,-1,14,main.outputHashes[0].pEx.mixS[3].in[2] +212,-1,14,main.outputHashes[0].pEx.mixS[3].in[3] +213,-1,14,main.outputHashes[0].pEx.mixS[3].in[4] +214,-1,15,main.outputHashes[0].pEx.mixS[4].out[0] +215,-1,15,main.outputHashes[0].pEx.mixS[4].out[1] +216,-1,15,main.outputHashes[0].pEx.mixS[4].out[2] +217,-1,15,main.outputHashes[0].pEx.mixS[4].out[3] +218,46,15,main.outputHashes[0].pEx.mixS[4].out[4] +219,-1,15,main.outputHashes[0].pEx.mixS[4].in[0] +220,-1,15,main.outputHashes[0].pEx.mixS[4].in[1] +221,-1,15,main.outputHashes[0].pEx.mixS[4].in[2] +222,-1,15,main.outputHashes[0].pEx.mixS[4].in[3] +223,-1,15,main.outputHashes[0].pEx.mixS[4].in[4] +224,-1,16,main.outputHashes[0].pEx.mixS[5].out[0] +225,-1,16,main.outputHashes[0].pEx.mixS[5].out[1] +226,-1,16,main.outputHashes[0].pEx.mixS[5].out[2] +227,-1,16,main.outputHashes[0].pEx.mixS[5].out[3] +228,47,16,main.outputHashes[0].pEx.mixS[5].out[4] +229,-1,16,main.outputHashes[0].pEx.mixS[5].in[0] +230,-1,16,main.outputHashes[0].pEx.mixS[5].in[1] +231,-1,16,main.outputHashes[0].pEx.mixS[5].in[2] +232,-1,16,main.outputHashes[0].pEx.mixS[5].in[3] +233,-1,16,main.outputHashes[0].pEx.mixS[5].in[4] +234,-1,17,main.outputHashes[0].pEx.mixS[6].out[0] +235,-1,17,main.outputHashes[0].pEx.mixS[6].out[1] +236,-1,17,main.outputHashes[0].pEx.mixS[6].out[2] +237,-1,17,main.outputHashes[0].pEx.mixS[6].out[3] +238,48,17,main.outputHashes[0].pEx.mixS[6].out[4] +239,-1,17,main.outputHashes[0].pEx.mixS[6].in[0] +240,-1,17,main.outputHashes[0].pEx.mixS[6].in[1] +241,-1,17,main.outputHashes[0].pEx.mixS[6].in[2] +242,-1,17,main.outputHashes[0].pEx.mixS[6].in[3] +243,-1,17,main.outputHashes[0].pEx.mixS[6].in[4] +244,-1,18,main.outputHashes[0].pEx.mixS[7].out[0] +245,-1,18,main.outputHashes[0].pEx.mixS[7].out[1] +246,-1,18,main.outputHashes[0].pEx.mixS[7].out[2] +247,-1,18,main.outputHashes[0].pEx.mixS[7].out[3] +248,49,18,main.outputHashes[0].pEx.mixS[7].out[4] +249,-1,18,main.outputHashes[0].pEx.mixS[7].in[0] +250,-1,18,main.outputHashes[0].pEx.mixS[7].in[1] +251,-1,18,main.outputHashes[0].pEx.mixS[7].in[2] +252,-1,18,main.outputHashes[0].pEx.mixS[7].in[3] +253,-1,18,main.outputHashes[0].pEx.mixS[7].in[4] +254,-1,19,main.outputHashes[0].pEx.mixS[8].out[0] +255,-1,19,main.outputHashes[0].pEx.mixS[8].out[1] +256,-1,19,main.outputHashes[0].pEx.mixS[8].out[2] +257,-1,19,main.outputHashes[0].pEx.mixS[8].out[3] +258,50,19,main.outputHashes[0].pEx.mixS[8].out[4] +259,-1,19,main.outputHashes[0].pEx.mixS[8].in[0] +260,-1,19,main.outputHashes[0].pEx.mixS[8].in[1] +261,-1,19,main.outputHashes[0].pEx.mixS[8].in[2] +262,-1,19,main.outputHashes[0].pEx.mixS[8].in[3] +263,-1,19,main.outputHashes[0].pEx.mixS[8].in[4] +264,-1,20,main.outputHashes[0].pEx.mixS[9].out[0] +265,-1,20,main.outputHashes[0].pEx.mixS[9].out[1] +266,-1,20,main.outputHashes[0].pEx.mixS[9].out[2] +267,-1,20,main.outputHashes[0].pEx.mixS[9].out[3] +268,51,20,main.outputHashes[0].pEx.mixS[9].out[4] +269,-1,20,main.outputHashes[0].pEx.mixS[9].in[0] +270,-1,20,main.outputHashes[0].pEx.mixS[9].in[1] +271,-1,20,main.outputHashes[0].pEx.mixS[9].in[2] +272,-1,20,main.outputHashes[0].pEx.mixS[9].in[3] +273,-1,20,main.outputHashes[0].pEx.mixS[9].in[4] +274,-1,21,main.outputHashes[0].pEx.mixS[10].out[0] +275,-1,21,main.outputHashes[0].pEx.mixS[10].out[1] +276,-1,21,main.outputHashes[0].pEx.mixS[10].out[2] +277,-1,21,main.outputHashes[0].pEx.mixS[10].out[3] +278,52,21,main.outputHashes[0].pEx.mixS[10].out[4] +279,-1,21,main.outputHashes[0].pEx.mixS[10].in[0] +280,-1,21,main.outputHashes[0].pEx.mixS[10].in[1] +281,-1,21,main.outputHashes[0].pEx.mixS[10].in[2] +282,-1,21,main.outputHashes[0].pEx.mixS[10].in[3] +283,-1,21,main.outputHashes[0].pEx.mixS[10].in[4] +284,-1,22,main.outputHashes[0].pEx.mixS[11].out[0] +285,-1,22,main.outputHashes[0].pEx.mixS[11].out[1] +286,-1,22,main.outputHashes[0].pEx.mixS[11].out[2] +287,-1,22,main.outputHashes[0].pEx.mixS[11].out[3] +288,53,22,main.outputHashes[0].pEx.mixS[11].out[4] +289,-1,22,main.outputHashes[0].pEx.mixS[11].in[0] +290,-1,22,main.outputHashes[0].pEx.mixS[11].in[1] +291,-1,22,main.outputHashes[0].pEx.mixS[11].in[2] +292,-1,22,main.outputHashes[0].pEx.mixS[11].in[3] +293,-1,22,main.outputHashes[0].pEx.mixS[11].in[4] +294,-1,23,main.outputHashes[0].pEx.mixS[12].out[0] +295,-1,23,main.outputHashes[0].pEx.mixS[12].out[1] +296,-1,23,main.outputHashes[0].pEx.mixS[12].out[2] +297,-1,23,main.outputHashes[0].pEx.mixS[12].out[3] +298,54,23,main.outputHashes[0].pEx.mixS[12].out[4] +299,-1,23,main.outputHashes[0].pEx.mixS[12].in[0] +300,-1,23,main.outputHashes[0].pEx.mixS[12].in[1] +301,-1,23,main.outputHashes[0].pEx.mixS[12].in[2] +302,-1,23,main.outputHashes[0].pEx.mixS[12].in[3] +303,-1,23,main.outputHashes[0].pEx.mixS[12].in[4] +304,-1,24,main.outputHashes[0].pEx.mixS[13].out[0] +305,-1,24,main.outputHashes[0].pEx.mixS[13].out[1] +306,-1,24,main.outputHashes[0].pEx.mixS[13].out[2] +307,-1,24,main.outputHashes[0].pEx.mixS[13].out[3] +308,55,24,main.outputHashes[0].pEx.mixS[13].out[4] +309,-1,24,main.outputHashes[0].pEx.mixS[13].in[0] +310,-1,24,main.outputHashes[0].pEx.mixS[13].in[1] +311,-1,24,main.outputHashes[0].pEx.mixS[13].in[2] +312,-1,24,main.outputHashes[0].pEx.mixS[13].in[3] +313,-1,24,main.outputHashes[0].pEx.mixS[13].in[4] +314,-1,25,main.outputHashes[0].pEx.mixS[14].out[0] +315,-1,25,main.outputHashes[0].pEx.mixS[14].out[1] +316,-1,25,main.outputHashes[0].pEx.mixS[14].out[2] +317,-1,25,main.outputHashes[0].pEx.mixS[14].out[3] +318,56,25,main.outputHashes[0].pEx.mixS[14].out[4] +319,-1,25,main.outputHashes[0].pEx.mixS[14].in[0] +320,-1,25,main.outputHashes[0].pEx.mixS[14].in[1] +321,-1,25,main.outputHashes[0].pEx.mixS[14].in[2] +322,-1,25,main.outputHashes[0].pEx.mixS[14].in[3] +323,-1,25,main.outputHashes[0].pEx.mixS[14].in[4] +324,-1,26,main.outputHashes[0].pEx.mixS[15].out[0] +325,-1,26,main.outputHashes[0].pEx.mixS[15].out[1] +326,-1,26,main.outputHashes[0].pEx.mixS[15].out[2] +327,-1,26,main.outputHashes[0].pEx.mixS[15].out[3] +328,57,26,main.outputHashes[0].pEx.mixS[15].out[4] +329,-1,26,main.outputHashes[0].pEx.mixS[15].in[0] +330,-1,26,main.outputHashes[0].pEx.mixS[15].in[1] +331,-1,26,main.outputHashes[0].pEx.mixS[15].in[2] +332,-1,26,main.outputHashes[0].pEx.mixS[15].in[3] +333,-1,26,main.outputHashes[0].pEx.mixS[15].in[4] +334,-1,27,main.outputHashes[0].pEx.mixS[16].out[0] +335,-1,27,main.outputHashes[0].pEx.mixS[16].out[1] +336,-1,27,main.outputHashes[0].pEx.mixS[16].out[2] +337,-1,27,main.outputHashes[0].pEx.mixS[16].out[3] +338,58,27,main.outputHashes[0].pEx.mixS[16].out[4] +339,-1,27,main.outputHashes[0].pEx.mixS[16].in[0] +340,-1,27,main.outputHashes[0].pEx.mixS[16].in[1] +341,-1,27,main.outputHashes[0].pEx.mixS[16].in[2] +342,-1,27,main.outputHashes[0].pEx.mixS[16].in[3] +343,-1,27,main.outputHashes[0].pEx.mixS[16].in[4] +344,-1,28,main.outputHashes[0].pEx.mixS[17].out[0] +345,-1,28,main.outputHashes[0].pEx.mixS[17].out[1] +346,-1,28,main.outputHashes[0].pEx.mixS[17].out[2] +347,-1,28,main.outputHashes[0].pEx.mixS[17].out[3] +348,59,28,main.outputHashes[0].pEx.mixS[17].out[4] +349,-1,28,main.outputHashes[0].pEx.mixS[17].in[0] +350,-1,28,main.outputHashes[0].pEx.mixS[17].in[1] +351,-1,28,main.outputHashes[0].pEx.mixS[17].in[2] +352,-1,28,main.outputHashes[0].pEx.mixS[17].in[3] +353,-1,28,main.outputHashes[0].pEx.mixS[17].in[4] +354,-1,29,main.outputHashes[0].pEx.mixS[18].out[0] +355,-1,29,main.outputHashes[0].pEx.mixS[18].out[1] +356,-1,29,main.outputHashes[0].pEx.mixS[18].out[2] +357,-1,29,main.outputHashes[0].pEx.mixS[18].out[3] +358,60,29,main.outputHashes[0].pEx.mixS[18].out[4] +359,-1,29,main.outputHashes[0].pEx.mixS[18].in[0] +360,-1,29,main.outputHashes[0].pEx.mixS[18].in[1] +361,-1,29,main.outputHashes[0].pEx.mixS[18].in[2] +362,-1,29,main.outputHashes[0].pEx.mixS[18].in[3] +363,-1,29,main.outputHashes[0].pEx.mixS[18].in[4] +364,-1,30,main.outputHashes[0].pEx.mixS[19].out[0] +365,-1,30,main.outputHashes[0].pEx.mixS[19].out[1] +366,-1,30,main.outputHashes[0].pEx.mixS[19].out[2] +367,-1,30,main.outputHashes[0].pEx.mixS[19].out[3] +368,61,30,main.outputHashes[0].pEx.mixS[19].out[4] +369,-1,30,main.outputHashes[0].pEx.mixS[19].in[0] +370,-1,30,main.outputHashes[0].pEx.mixS[19].in[1] +371,-1,30,main.outputHashes[0].pEx.mixS[19].in[2] +372,-1,30,main.outputHashes[0].pEx.mixS[19].in[3] +373,-1,30,main.outputHashes[0].pEx.mixS[19].in[4] +374,-1,31,main.outputHashes[0].pEx.mixS[20].out[0] +375,-1,31,main.outputHashes[0].pEx.mixS[20].out[1] +376,-1,31,main.outputHashes[0].pEx.mixS[20].out[2] +377,-1,31,main.outputHashes[0].pEx.mixS[20].out[3] +378,62,31,main.outputHashes[0].pEx.mixS[20].out[4] +379,-1,31,main.outputHashes[0].pEx.mixS[20].in[0] +380,-1,31,main.outputHashes[0].pEx.mixS[20].in[1] +381,-1,31,main.outputHashes[0].pEx.mixS[20].in[2] +382,-1,31,main.outputHashes[0].pEx.mixS[20].in[3] +383,-1,31,main.outputHashes[0].pEx.mixS[20].in[4] +384,-1,32,main.outputHashes[0].pEx.mixS[21].out[0] +385,-1,32,main.outputHashes[0].pEx.mixS[21].out[1] +386,-1,32,main.outputHashes[0].pEx.mixS[21].out[2] +387,-1,32,main.outputHashes[0].pEx.mixS[21].out[3] +388,63,32,main.outputHashes[0].pEx.mixS[21].out[4] +389,-1,32,main.outputHashes[0].pEx.mixS[21].in[0] +390,-1,32,main.outputHashes[0].pEx.mixS[21].in[1] +391,-1,32,main.outputHashes[0].pEx.mixS[21].in[2] +392,-1,32,main.outputHashes[0].pEx.mixS[21].in[3] +393,-1,32,main.outputHashes[0].pEx.mixS[21].in[4] +394,-1,33,main.outputHashes[0].pEx.mixS[22].out[0] +395,-1,33,main.outputHashes[0].pEx.mixS[22].out[1] +396,-1,33,main.outputHashes[0].pEx.mixS[22].out[2] +397,-1,33,main.outputHashes[0].pEx.mixS[22].out[3] +398,64,33,main.outputHashes[0].pEx.mixS[22].out[4] +399,-1,33,main.outputHashes[0].pEx.mixS[22].in[0] +400,-1,33,main.outputHashes[0].pEx.mixS[22].in[1] +401,-1,33,main.outputHashes[0].pEx.mixS[22].in[2] +402,-1,33,main.outputHashes[0].pEx.mixS[22].in[3] +403,-1,33,main.outputHashes[0].pEx.mixS[22].in[4] +404,-1,34,main.outputHashes[0].pEx.mixS[23].out[0] +405,-1,34,main.outputHashes[0].pEx.mixS[23].out[1] +406,-1,34,main.outputHashes[0].pEx.mixS[23].out[2] +407,-1,34,main.outputHashes[0].pEx.mixS[23].out[3] +408,65,34,main.outputHashes[0].pEx.mixS[23].out[4] +409,-1,34,main.outputHashes[0].pEx.mixS[23].in[0] +410,-1,34,main.outputHashes[0].pEx.mixS[23].in[1] +411,-1,34,main.outputHashes[0].pEx.mixS[23].in[2] +412,-1,34,main.outputHashes[0].pEx.mixS[23].in[3] +413,-1,34,main.outputHashes[0].pEx.mixS[23].in[4] +414,-1,35,main.outputHashes[0].pEx.mixS[24].out[0] +415,-1,35,main.outputHashes[0].pEx.mixS[24].out[1] +416,-1,35,main.outputHashes[0].pEx.mixS[24].out[2] +417,-1,35,main.outputHashes[0].pEx.mixS[24].out[3] +418,66,35,main.outputHashes[0].pEx.mixS[24].out[4] +419,-1,35,main.outputHashes[0].pEx.mixS[24].in[0] +420,-1,35,main.outputHashes[0].pEx.mixS[24].in[1] +421,-1,35,main.outputHashes[0].pEx.mixS[24].in[2] +422,-1,35,main.outputHashes[0].pEx.mixS[24].in[3] +423,-1,35,main.outputHashes[0].pEx.mixS[24].in[4] +424,-1,36,main.outputHashes[0].pEx.mixS[25].out[0] +425,-1,36,main.outputHashes[0].pEx.mixS[25].out[1] +426,-1,36,main.outputHashes[0].pEx.mixS[25].out[2] +427,-1,36,main.outputHashes[0].pEx.mixS[25].out[3] +428,67,36,main.outputHashes[0].pEx.mixS[25].out[4] +429,-1,36,main.outputHashes[0].pEx.mixS[25].in[0] +430,-1,36,main.outputHashes[0].pEx.mixS[25].in[1] +431,-1,36,main.outputHashes[0].pEx.mixS[25].in[2] +432,-1,36,main.outputHashes[0].pEx.mixS[25].in[3] +433,-1,36,main.outputHashes[0].pEx.mixS[25].in[4] +434,-1,37,main.outputHashes[0].pEx.mixS[26].out[0] +435,-1,37,main.outputHashes[0].pEx.mixS[26].out[1] +436,-1,37,main.outputHashes[0].pEx.mixS[26].out[2] +437,-1,37,main.outputHashes[0].pEx.mixS[26].out[3] +438,68,37,main.outputHashes[0].pEx.mixS[26].out[4] +439,-1,37,main.outputHashes[0].pEx.mixS[26].in[0] +440,-1,37,main.outputHashes[0].pEx.mixS[26].in[1] +441,-1,37,main.outputHashes[0].pEx.mixS[26].in[2] +442,-1,37,main.outputHashes[0].pEx.mixS[26].in[3] +443,-1,37,main.outputHashes[0].pEx.mixS[26].in[4] +444,-1,38,main.outputHashes[0].pEx.mixS[27].out[0] +445,-1,38,main.outputHashes[0].pEx.mixS[27].out[1] +446,-1,38,main.outputHashes[0].pEx.mixS[27].out[2] +447,-1,38,main.outputHashes[0].pEx.mixS[27].out[3] +448,69,38,main.outputHashes[0].pEx.mixS[27].out[4] +449,-1,38,main.outputHashes[0].pEx.mixS[27].in[0] +450,-1,38,main.outputHashes[0].pEx.mixS[27].in[1] +451,-1,38,main.outputHashes[0].pEx.mixS[27].in[2] +452,-1,38,main.outputHashes[0].pEx.mixS[27].in[3] +453,-1,38,main.outputHashes[0].pEx.mixS[27].in[4] +454,-1,39,main.outputHashes[0].pEx.mixS[28].out[0] +455,-1,39,main.outputHashes[0].pEx.mixS[28].out[1] +456,-1,39,main.outputHashes[0].pEx.mixS[28].out[2] +457,-1,39,main.outputHashes[0].pEx.mixS[28].out[3] +458,70,39,main.outputHashes[0].pEx.mixS[28].out[4] +459,-1,39,main.outputHashes[0].pEx.mixS[28].in[0] +460,-1,39,main.outputHashes[0].pEx.mixS[28].in[1] +461,-1,39,main.outputHashes[0].pEx.mixS[28].in[2] +462,-1,39,main.outputHashes[0].pEx.mixS[28].in[3] +463,-1,39,main.outputHashes[0].pEx.mixS[28].in[4] +464,-1,40,main.outputHashes[0].pEx.mixS[29].out[0] +465,-1,40,main.outputHashes[0].pEx.mixS[29].out[1] +466,-1,40,main.outputHashes[0].pEx.mixS[29].out[2] +467,-1,40,main.outputHashes[0].pEx.mixS[29].out[3] +468,71,40,main.outputHashes[0].pEx.mixS[29].out[4] +469,-1,40,main.outputHashes[0].pEx.mixS[29].in[0] +470,-1,40,main.outputHashes[0].pEx.mixS[29].in[1] +471,-1,40,main.outputHashes[0].pEx.mixS[29].in[2] +472,-1,40,main.outputHashes[0].pEx.mixS[29].in[3] +473,-1,40,main.outputHashes[0].pEx.mixS[29].in[4] +474,-1,41,main.outputHashes[0].pEx.mixS[30].out[0] +475,-1,41,main.outputHashes[0].pEx.mixS[30].out[1] +476,-1,41,main.outputHashes[0].pEx.mixS[30].out[2] +477,-1,41,main.outputHashes[0].pEx.mixS[30].out[3] +478,72,41,main.outputHashes[0].pEx.mixS[30].out[4] +479,-1,41,main.outputHashes[0].pEx.mixS[30].in[0] +480,-1,41,main.outputHashes[0].pEx.mixS[30].in[1] +481,-1,41,main.outputHashes[0].pEx.mixS[30].in[2] +482,-1,41,main.outputHashes[0].pEx.mixS[30].in[3] +483,-1,41,main.outputHashes[0].pEx.mixS[30].in[4] +484,-1,42,main.outputHashes[0].pEx.mixS[31].out[0] +485,-1,42,main.outputHashes[0].pEx.mixS[31].out[1] +486,-1,42,main.outputHashes[0].pEx.mixS[31].out[2] +487,-1,42,main.outputHashes[0].pEx.mixS[31].out[3] +488,73,42,main.outputHashes[0].pEx.mixS[31].out[4] +489,-1,42,main.outputHashes[0].pEx.mixS[31].in[0] +490,-1,42,main.outputHashes[0].pEx.mixS[31].in[1] +491,-1,42,main.outputHashes[0].pEx.mixS[31].in[2] +492,-1,42,main.outputHashes[0].pEx.mixS[31].in[3] +493,-1,42,main.outputHashes[0].pEx.mixS[31].in[4] +494,-1,43,main.outputHashes[0].pEx.mixS[32].out[0] +495,-1,43,main.outputHashes[0].pEx.mixS[32].out[1] +496,-1,43,main.outputHashes[0].pEx.mixS[32].out[2] +497,-1,43,main.outputHashes[0].pEx.mixS[32].out[3] +498,74,43,main.outputHashes[0].pEx.mixS[32].out[4] +499,-1,43,main.outputHashes[0].pEx.mixS[32].in[0] +500,-1,43,main.outputHashes[0].pEx.mixS[32].in[1] +501,-1,43,main.outputHashes[0].pEx.mixS[32].in[2] +502,-1,43,main.outputHashes[0].pEx.mixS[32].in[3] +503,-1,43,main.outputHashes[0].pEx.mixS[32].in[4] +504,-1,44,main.outputHashes[0].pEx.mixS[33].out[0] +505,-1,44,main.outputHashes[0].pEx.mixS[33].out[1] +506,-1,44,main.outputHashes[0].pEx.mixS[33].out[2] +507,-1,44,main.outputHashes[0].pEx.mixS[33].out[3] +508,75,44,main.outputHashes[0].pEx.mixS[33].out[4] +509,-1,44,main.outputHashes[0].pEx.mixS[33].in[0] +510,-1,44,main.outputHashes[0].pEx.mixS[33].in[1] +511,-1,44,main.outputHashes[0].pEx.mixS[33].in[2] +512,-1,44,main.outputHashes[0].pEx.mixS[33].in[3] +513,-1,44,main.outputHashes[0].pEx.mixS[33].in[4] +514,-1,45,main.outputHashes[0].pEx.mixS[34].out[0] +515,-1,45,main.outputHashes[0].pEx.mixS[34].out[1] +516,-1,45,main.outputHashes[0].pEx.mixS[34].out[2] +517,-1,45,main.outputHashes[0].pEx.mixS[34].out[3] +518,76,45,main.outputHashes[0].pEx.mixS[34].out[4] +519,-1,45,main.outputHashes[0].pEx.mixS[34].in[0] +520,-1,45,main.outputHashes[0].pEx.mixS[34].in[1] +521,-1,45,main.outputHashes[0].pEx.mixS[34].in[2] +522,-1,45,main.outputHashes[0].pEx.mixS[34].in[3] +523,-1,45,main.outputHashes[0].pEx.mixS[34].in[4] +524,-1,46,main.outputHashes[0].pEx.mixS[35].out[0] +525,-1,46,main.outputHashes[0].pEx.mixS[35].out[1] +526,-1,46,main.outputHashes[0].pEx.mixS[35].out[2] +527,-1,46,main.outputHashes[0].pEx.mixS[35].out[3] +528,77,46,main.outputHashes[0].pEx.mixS[35].out[4] +529,-1,46,main.outputHashes[0].pEx.mixS[35].in[0] +530,-1,46,main.outputHashes[0].pEx.mixS[35].in[1] +531,-1,46,main.outputHashes[0].pEx.mixS[35].in[2] +532,-1,46,main.outputHashes[0].pEx.mixS[35].in[3] +533,-1,46,main.outputHashes[0].pEx.mixS[35].in[4] +534,-1,47,main.outputHashes[0].pEx.mixS[36].out[0] +535,-1,47,main.outputHashes[0].pEx.mixS[36].out[1] +536,-1,47,main.outputHashes[0].pEx.mixS[36].out[2] +537,-1,47,main.outputHashes[0].pEx.mixS[36].out[3] +538,78,47,main.outputHashes[0].pEx.mixS[36].out[4] +539,-1,47,main.outputHashes[0].pEx.mixS[36].in[0] +540,-1,47,main.outputHashes[0].pEx.mixS[36].in[1] +541,-1,47,main.outputHashes[0].pEx.mixS[36].in[2] +542,-1,47,main.outputHashes[0].pEx.mixS[36].in[3] +543,-1,47,main.outputHashes[0].pEx.mixS[36].in[4] +544,-1,48,main.outputHashes[0].pEx.mixS[37].out[0] +545,-1,48,main.outputHashes[0].pEx.mixS[37].out[1] +546,-1,48,main.outputHashes[0].pEx.mixS[37].out[2] +547,-1,48,main.outputHashes[0].pEx.mixS[37].out[3] +548,79,48,main.outputHashes[0].pEx.mixS[37].out[4] +549,-1,48,main.outputHashes[0].pEx.mixS[37].in[0] +550,-1,48,main.outputHashes[0].pEx.mixS[37].in[1] +551,-1,48,main.outputHashes[0].pEx.mixS[37].in[2] +552,-1,48,main.outputHashes[0].pEx.mixS[37].in[3] +553,-1,48,main.outputHashes[0].pEx.mixS[37].in[4] +554,-1,49,main.outputHashes[0].pEx.mixS[38].out[0] +555,-1,49,main.outputHashes[0].pEx.mixS[38].out[1] +556,-1,49,main.outputHashes[0].pEx.mixS[38].out[2] +557,-1,49,main.outputHashes[0].pEx.mixS[38].out[3] +558,80,49,main.outputHashes[0].pEx.mixS[38].out[4] +559,-1,49,main.outputHashes[0].pEx.mixS[38].in[0] +560,-1,49,main.outputHashes[0].pEx.mixS[38].in[1] +561,-1,49,main.outputHashes[0].pEx.mixS[38].in[2] +562,-1,49,main.outputHashes[0].pEx.mixS[38].in[3] +563,-1,49,main.outputHashes[0].pEx.mixS[38].in[4] +564,-1,50,main.outputHashes[0].pEx.mixS[39].out[0] +565,-1,50,main.outputHashes[0].pEx.mixS[39].out[1] +566,-1,50,main.outputHashes[0].pEx.mixS[39].out[2] +567,-1,50,main.outputHashes[0].pEx.mixS[39].out[3] +568,81,50,main.outputHashes[0].pEx.mixS[39].out[4] +569,-1,50,main.outputHashes[0].pEx.mixS[39].in[0] +570,-1,50,main.outputHashes[0].pEx.mixS[39].in[1] +571,-1,50,main.outputHashes[0].pEx.mixS[39].in[2] +572,-1,50,main.outputHashes[0].pEx.mixS[39].in[3] +573,-1,50,main.outputHashes[0].pEx.mixS[39].in[4] +574,-1,51,main.outputHashes[0].pEx.mixS[40].out[0] +575,-1,51,main.outputHashes[0].pEx.mixS[40].out[1] +576,-1,51,main.outputHashes[0].pEx.mixS[40].out[2] +577,-1,51,main.outputHashes[0].pEx.mixS[40].out[3] +578,82,51,main.outputHashes[0].pEx.mixS[40].out[4] +579,-1,51,main.outputHashes[0].pEx.mixS[40].in[0] +580,-1,51,main.outputHashes[0].pEx.mixS[40].in[1] +581,-1,51,main.outputHashes[0].pEx.mixS[40].in[2] +582,-1,51,main.outputHashes[0].pEx.mixS[40].in[3] +583,-1,51,main.outputHashes[0].pEx.mixS[40].in[4] +584,-1,52,main.outputHashes[0].pEx.mixS[41].out[0] +585,-1,52,main.outputHashes[0].pEx.mixS[41].out[1] +586,-1,52,main.outputHashes[0].pEx.mixS[41].out[2] +587,-1,52,main.outputHashes[0].pEx.mixS[41].out[3] +588,83,52,main.outputHashes[0].pEx.mixS[41].out[4] +589,-1,52,main.outputHashes[0].pEx.mixS[41].in[0] +590,-1,52,main.outputHashes[0].pEx.mixS[41].in[1] +591,-1,52,main.outputHashes[0].pEx.mixS[41].in[2] +592,-1,52,main.outputHashes[0].pEx.mixS[41].in[3] +593,-1,52,main.outputHashes[0].pEx.mixS[41].in[4] +594,-1,53,main.outputHashes[0].pEx.mixS[42].out[0] +595,-1,53,main.outputHashes[0].pEx.mixS[42].out[1] +596,-1,53,main.outputHashes[0].pEx.mixS[42].out[2] +597,-1,53,main.outputHashes[0].pEx.mixS[42].out[3] +598,84,53,main.outputHashes[0].pEx.mixS[42].out[4] +599,-1,53,main.outputHashes[0].pEx.mixS[42].in[0] +600,-1,53,main.outputHashes[0].pEx.mixS[42].in[1] +601,-1,53,main.outputHashes[0].pEx.mixS[42].in[2] +602,-1,53,main.outputHashes[0].pEx.mixS[42].in[3] +603,-1,53,main.outputHashes[0].pEx.mixS[42].in[4] +604,-1,54,main.outputHashes[0].pEx.mixS[43].out[0] +605,-1,54,main.outputHashes[0].pEx.mixS[43].out[1] +606,-1,54,main.outputHashes[0].pEx.mixS[43].out[2] +607,-1,54,main.outputHashes[0].pEx.mixS[43].out[3] +608,85,54,main.outputHashes[0].pEx.mixS[43].out[4] +609,-1,54,main.outputHashes[0].pEx.mixS[43].in[0] +610,-1,54,main.outputHashes[0].pEx.mixS[43].in[1] +611,-1,54,main.outputHashes[0].pEx.mixS[43].in[2] +612,-1,54,main.outputHashes[0].pEx.mixS[43].in[3] +613,-1,54,main.outputHashes[0].pEx.mixS[43].in[4] +614,-1,55,main.outputHashes[0].pEx.mixS[44].out[0] +615,-1,55,main.outputHashes[0].pEx.mixS[44].out[1] +616,-1,55,main.outputHashes[0].pEx.mixS[44].out[2] +617,-1,55,main.outputHashes[0].pEx.mixS[44].out[3] +618,86,55,main.outputHashes[0].pEx.mixS[44].out[4] +619,-1,55,main.outputHashes[0].pEx.mixS[44].in[0] +620,-1,55,main.outputHashes[0].pEx.mixS[44].in[1] +621,-1,55,main.outputHashes[0].pEx.mixS[44].in[2] +622,-1,55,main.outputHashes[0].pEx.mixS[44].in[3] +623,-1,55,main.outputHashes[0].pEx.mixS[44].in[4] +624,-1,56,main.outputHashes[0].pEx.mixS[45].out[0] +625,-1,56,main.outputHashes[0].pEx.mixS[45].out[1] +626,-1,56,main.outputHashes[0].pEx.mixS[45].out[2] +627,-1,56,main.outputHashes[0].pEx.mixS[45].out[3] +628,87,56,main.outputHashes[0].pEx.mixS[45].out[4] +629,-1,56,main.outputHashes[0].pEx.mixS[45].in[0] +630,-1,56,main.outputHashes[0].pEx.mixS[45].in[1] +631,-1,56,main.outputHashes[0].pEx.mixS[45].in[2] +632,-1,56,main.outputHashes[0].pEx.mixS[45].in[3] +633,-1,56,main.outputHashes[0].pEx.mixS[45].in[4] +634,-1,57,main.outputHashes[0].pEx.mixS[46].out[0] +635,-1,57,main.outputHashes[0].pEx.mixS[46].out[1] +636,-1,57,main.outputHashes[0].pEx.mixS[46].out[2] +637,-1,57,main.outputHashes[0].pEx.mixS[46].out[3] +638,88,57,main.outputHashes[0].pEx.mixS[46].out[4] +639,-1,57,main.outputHashes[0].pEx.mixS[46].in[0] +640,-1,57,main.outputHashes[0].pEx.mixS[46].in[1] +641,-1,57,main.outputHashes[0].pEx.mixS[46].in[2] +642,-1,57,main.outputHashes[0].pEx.mixS[46].in[3] +643,-1,57,main.outputHashes[0].pEx.mixS[46].in[4] +644,-1,58,main.outputHashes[0].pEx.mixS[47].out[0] +645,-1,58,main.outputHashes[0].pEx.mixS[47].out[1] +646,-1,58,main.outputHashes[0].pEx.mixS[47].out[2] +647,-1,58,main.outputHashes[0].pEx.mixS[47].out[3] +648,89,58,main.outputHashes[0].pEx.mixS[47].out[4] +649,-1,58,main.outputHashes[0].pEx.mixS[47].in[0] +650,-1,58,main.outputHashes[0].pEx.mixS[47].in[1] +651,-1,58,main.outputHashes[0].pEx.mixS[47].in[2] +652,-1,58,main.outputHashes[0].pEx.mixS[47].in[3] +653,-1,58,main.outputHashes[0].pEx.mixS[47].in[4] +654,-1,59,main.outputHashes[0].pEx.mixS[48].out[0] +655,-1,59,main.outputHashes[0].pEx.mixS[48].out[1] +656,-1,59,main.outputHashes[0].pEx.mixS[48].out[2] +657,-1,59,main.outputHashes[0].pEx.mixS[48].out[3] +658,90,59,main.outputHashes[0].pEx.mixS[48].out[4] +659,-1,59,main.outputHashes[0].pEx.mixS[48].in[0] +660,-1,59,main.outputHashes[0].pEx.mixS[48].in[1] +661,-1,59,main.outputHashes[0].pEx.mixS[48].in[2] +662,-1,59,main.outputHashes[0].pEx.mixS[48].in[3] +663,-1,59,main.outputHashes[0].pEx.mixS[48].in[4] +664,-1,60,main.outputHashes[0].pEx.mixS[49].out[0] +665,-1,60,main.outputHashes[0].pEx.mixS[49].out[1] +666,-1,60,main.outputHashes[0].pEx.mixS[49].out[2] +667,-1,60,main.outputHashes[0].pEx.mixS[49].out[3] +668,91,60,main.outputHashes[0].pEx.mixS[49].out[4] +669,-1,60,main.outputHashes[0].pEx.mixS[49].in[0] +670,-1,60,main.outputHashes[0].pEx.mixS[49].in[1] +671,-1,60,main.outputHashes[0].pEx.mixS[49].in[2] +672,-1,60,main.outputHashes[0].pEx.mixS[49].in[3] +673,-1,60,main.outputHashes[0].pEx.mixS[49].in[4] +674,-1,61,main.outputHashes[0].pEx.mixS[50].out[0] +675,-1,61,main.outputHashes[0].pEx.mixS[50].out[1] +676,-1,61,main.outputHashes[0].pEx.mixS[50].out[2] +677,-1,61,main.outputHashes[0].pEx.mixS[50].out[3] +678,92,61,main.outputHashes[0].pEx.mixS[50].out[4] +679,-1,61,main.outputHashes[0].pEx.mixS[50].in[0] +680,-1,61,main.outputHashes[0].pEx.mixS[50].in[1] +681,-1,61,main.outputHashes[0].pEx.mixS[50].in[2] +682,-1,61,main.outputHashes[0].pEx.mixS[50].in[3] +683,-1,61,main.outputHashes[0].pEx.mixS[50].in[4] +684,-1,62,main.outputHashes[0].pEx.mixS[51].out[0] +685,-1,62,main.outputHashes[0].pEx.mixS[51].out[1] +686,-1,62,main.outputHashes[0].pEx.mixS[51].out[2] +687,-1,62,main.outputHashes[0].pEx.mixS[51].out[3] +688,93,62,main.outputHashes[0].pEx.mixS[51].out[4] +689,-1,62,main.outputHashes[0].pEx.mixS[51].in[0] +690,-1,62,main.outputHashes[0].pEx.mixS[51].in[1] +691,-1,62,main.outputHashes[0].pEx.mixS[51].in[2] +692,-1,62,main.outputHashes[0].pEx.mixS[51].in[3] +693,-1,62,main.outputHashes[0].pEx.mixS[51].in[4] +694,-1,63,main.outputHashes[0].pEx.mixS[52].out[0] +695,-1,63,main.outputHashes[0].pEx.mixS[52].out[1] +696,-1,63,main.outputHashes[0].pEx.mixS[52].out[2] +697,-1,63,main.outputHashes[0].pEx.mixS[52].out[3] +698,94,63,main.outputHashes[0].pEx.mixS[52].out[4] +699,-1,63,main.outputHashes[0].pEx.mixS[52].in[0] +700,-1,63,main.outputHashes[0].pEx.mixS[52].in[1] +701,-1,63,main.outputHashes[0].pEx.mixS[52].in[2] +702,-1,63,main.outputHashes[0].pEx.mixS[52].in[3] +703,-1,63,main.outputHashes[0].pEx.mixS[52].in[4] +704,-1,64,main.outputHashes[0].pEx.mixS[53].out[0] +705,-1,64,main.outputHashes[0].pEx.mixS[53].out[1] +706,-1,64,main.outputHashes[0].pEx.mixS[53].out[2] +707,-1,64,main.outputHashes[0].pEx.mixS[53].out[3] +708,95,64,main.outputHashes[0].pEx.mixS[53].out[4] +709,-1,64,main.outputHashes[0].pEx.mixS[53].in[0] +710,-1,64,main.outputHashes[0].pEx.mixS[53].in[1] +711,-1,64,main.outputHashes[0].pEx.mixS[53].in[2] +712,-1,64,main.outputHashes[0].pEx.mixS[53].in[3] +713,-1,64,main.outputHashes[0].pEx.mixS[53].in[4] +714,-1,65,main.outputHashes[0].pEx.mixS[54].out[0] +715,-1,65,main.outputHashes[0].pEx.mixS[54].out[1] +716,-1,65,main.outputHashes[0].pEx.mixS[54].out[2] +717,-1,65,main.outputHashes[0].pEx.mixS[54].out[3] +718,96,65,main.outputHashes[0].pEx.mixS[54].out[4] +719,-1,65,main.outputHashes[0].pEx.mixS[54].in[0] +720,-1,65,main.outputHashes[0].pEx.mixS[54].in[1] +721,-1,65,main.outputHashes[0].pEx.mixS[54].in[2] +722,-1,65,main.outputHashes[0].pEx.mixS[54].in[3] +723,-1,65,main.outputHashes[0].pEx.mixS[54].in[4] +724,-1,66,main.outputHashes[0].pEx.mixS[55].out[0] +725,-1,66,main.outputHashes[0].pEx.mixS[55].out[1] +726,-1,66,main.outputHashes[0].pEx.mixS[55].out[2] +727,-1,66,main.outputHashes[0].pEx.mixS[55].out[3] +728,97,66,main.outputHashes[0].pEx.mixS[55].out[4] +729,-1,66,main.outputHashes[0].pEx.mixS[55].in[0] +730,-1,66,main.outputHashes[0].pEx.mixS[55].in[1] +731,-1,66,main.outputHashes[0].pEx.mixS[55].in[2] +732,-1,66,main.outputHashes[0].pEx.mixS[55].in[3] +733,-1,66,main.outputHashes[0].pEx.mixS[55].in[4] +734,-1,67,main.outputHashes[0].pEx.mixS[56].out[0] +735,-1,67,main.outputHashes[0].pEx.mixS[56].out[1] +736,-1,67,main.outputHashes[0].pEx.mixS[56].out[2] +737,-1,67,main.outputHashes[0].pEx.mixS[56].out[3] +738,98,67,main.outputHashes[0].pEx.mixS[56].out[4] +739,-1,67,main.outputHashes[0].pEx.mixS[56].in[0] +740,-1,67,main.outputHashes[0].pEx.mixS[56].in[1] +741,-1,67,main.outputHashes[0].pEx.mixS[56].in[2] +742,-1,67,main.outputHashes[0].pEx.mixS[56].in[3] +743,-1,67,main.outputHashes[0].pEx.mixS[56].in[4] +744,-1,68,main.outputHashes[0].pEx.mixS[57].out[0] +745,99,68,main.outputHashes[0].pEx.mixS[57].out[1] +746,100,68,main.outputHashes[0].pEx.mixS[57].out[2] +747,101,68,main.outputHashes[0].pEx.mixS[57].out[3] +748,102,68,main.outputHashes[0].pEx.mixS[57].out[4] +749,-1,68,main.outputHashes[0].pEx.mixS[57].in[0] +750,-1,68,main.outputHashes[0].pEx.mixS[57].in[1] +751,-1,68,main.outputHashes[0].pEx.mixS[57].in[2] +752,-1,68,main.outputHashes[0].pEx.mixS[57].in[3] +753,-1,68,main.outputHashes[0].pEx.mixS[57].in[4] +754,-1,69,main.outputHashes[0].pEx.mixS[58].out[0] +755,-1,69,main.outputHashes[0].pEx.mixS[58].out[1] +756,-1,69,main.outputHashes[0].pEx.mixS[58].out[2] +757,-1,69,main.outputHashes[0].pEx.mixS[58].out[3] +758,103,69,main.outputHashes[0].pEx.mixS[58].out[4] +759,-1,69,main.outputHashes[0].pEx.mixS[58].in[0] +760,-1,69,main.outputHashes[0].pEx.mixS[58].in[1] +761,-1,69,main.outputHashes[0].pEx.mixS[58].in[2] +762,-1,69,main.outputHashes[0].pEx.mixS[58].in[3] +763,-1,69,main.outputHashes[0].pEx.mixS[58].in[4] +764,-1,70,main.outputHashes[0].pEx.mixS[59].out[0] +765,-1,70,main.outputHashes[0].pEx.mixS[59].out[1] +766,-1,70,main.outputHashes[0].pEx.mixS[59].out[2] +767,-1,70,main.outputHashes[0].pEx.mixS[59].out[3] +768,-1,70,main.outputHashes[0].pEx.mixS[59].out[4] +769,104,70,main.outputHashes[0].pEx.mixS[59].in[0] +770,-1,70,main.outputHashes[0].pEx.mixS[59].in[1] +771,-1,70,main.outputHashes[0].pEx.mixS[59].in[2] +772,-1,70,main.outputHashes[0].pEx.mixS[59].in[3] +773,-1,70,main.outputHashes[0].pEx.mixS[59].in[4] +774,-1,4,main.outputHashes[0].pEx.sigmaF[0][0].out +775,-1,4,main.outputHashes[0].pEx.sigmaF[0][0].in +776,-1,4,main.outputHashes[0].pEx.sigmaF[0][0].in2 +777,-1,4,main.outputHashes[0].pEx.sigmaF[0][0].in4 +778,-1,4,main.outputHashes[0].pEx.sigmaF[0][1].out +779,-1,4,main.outputHashes[0].pEx.sigmaF[0][1].in +780,105,4,main.outputHashes[0].pEx.sigmaF[0][1].in2 +781,106,4,main.outputHashes[0].pEx.sigmaF[0][1].in4 +782,-1,4,main.outputHashes[0].pEx.sigmaF[0][2].out +783,-1,4,main.outputHashes[0].pEx.sigmaF[0][2].in +784,107,4,main.outputHashes[0].pEx.sigmaF[0][2].in2 +785,108,4,main.outputHashes[0].pEx.sigmaF[0][2].in4 +786,-1,4,main.outputHashes[0].pEx.sigmaF[0][3].out +787,-1,4,main.outputHashes[0].pEx.sigmaF[0][3].in +788,109,4,main.outputHashes[0].pEx.sigmaF[0][3].in2 +789,110,4,main.outputHashes[0].pEx.sigmaF[0][3].in4 +790,-1,4,main.outputHashes[0].pEx.sigmaF[0][4].out +791,-1,4,main.outputHashes[0].pEx.sigmaF[0][4].in +792,111,4,main.outputHashes[0].pEx.sigmaF[0][4].in2 +793,112,4,main.outputHashes[0].pEx.sigmaF[0][4].in4 +794,-1,4,main.outputHashes[0].pEx.sigmaF[1][0].out +795,-1,4,main.outputHashes[0].pEx.sigmaF[1][0].in +796,113,4,main.outputHashes[0].pEx.sigmaF[1][0].in2 +797,114,4,main.outputHashes[0].pEx.sigmaF[1][0].in4 +798,-1,4,main.outputHashes[0].pEx.sigmaF[1][1].out +799,-1,4,main.outputHashes[0].pEx.sigmaF[1][1].in +800,115,4,main.outputHashes[0].pEx.sigmaF[1][1].in2 +801,116,4,main.outputHashes[0].pEx.sigmaF[1][1].in4 +802,-1,4,main.outputHashes[0].pEx.sigmaF[1][2].out +803,-1,4,main.outputHashes[0].pEx.sigmaF[1][2].in +804,117,4,main.outputHashes[0].pEx.sigmaF[1][2].in2 +805,118,4,main.outputHashes[0].pEx.sigmaF[1][2].in4 +806,-1,4,main.outputHashes[0].pEx.sigmaF[1][3].out +807,-1,4,main.outputHashes[0].pEx.sigmaF[1][3].in +808,119,4,main.outputHashes[0].pEx.sigmaF[1][3].in2 +809,120,4,main.outputHashes[0].pEx.sigmaF[1][3].in4 +810,-1,4,main.outputHashes[0].pEx.sigmaF[1][4].out +811,-1,4,main.outputHashes[0].pEx.sigmaF[1][4].in +812,121,4,main.outputHashes[0].pEx.sigmaF[1][4].in2 +813,122,4,main.outputHashes[0].pEx.sigmaF[1][4].in4 +814,-1,4,main.outputHashes[0].pEx.sigmaF[2][0].out +815,-1,4,main.outputHashes[0].pEx.sigmaF[2][0].in +816,123,4,main.outputHashes[0].pEx.sigmaF[2][0].in2 +817,124,4,main.outputHashes[0].pEx.sigmaF[2][0].in4 +818,-1,4,main.outputHashes[0].pEx.sigmaF[2][1].out +819,-1,4,main.outputHashes[0].pEx.sigmaF[2][1].in +820,125,4,main.outputHashes[0].pEx.sigmaF[2][1].in2 +821,126,4,main.outputHashes[0].pEx.sigmaF[2][1].in4 +822,-1,4,main.outputHashes[0].pEx.sigmaF[2][2].out +823,-1,4,main.outputHashes[0].pEx.sigmaF[2][2].in +824,127,4,main.outputHashes[0].pEx.sigmaF[2][2].in2 +825,128,4,main.outputHashes[0].pEx.sigmaF[2][2].in4 +826,-1,4,main.outputHashes[0].pEx.sigmaF[2][3].out +827,-1,4,main.outputHashes[0].pEx.sigmaF[2][3].in +828,129,4,main.outputHashes[0].pEx.sigmaF[2][3].in2 +829,130,4,main.outputHashes[0].pEx.sigmaF[2][3].in4 +830,-1,4,main.outputHashes[0].pEx.sigmaF[2][4].out +831,-1,4,main.outputHashes[0].pEx.sigmaF[2][4].in +832,131,4,main.outputHashes[0].pEx.sigmaF[2][4].in2 +833,132,4,main.outputHashes[0].pEx.sigmaF[2][4].in4 +834,-1,4,main.outputHashes[0].pEx.sigmaF[3][0].out +835,-1,4,main.outputHashes[0].pEx.sigmaF[3][0].in +836,133,4,main.outputHashes[0].pEx.sigmaF[3][0].in2 +837,134,4,main.outputHashes[0].pEx.sigmaF[3][0].in4 +838,-1,4,main.outputHashes[0].pEx.sigmaF[3][1].out +839,-1,4,main.outputHashes[0].pEx.sigmaF[3][1].in +840,135,4,main.outputHashes[0].pEx.sigmaF[3][1].in2 +841,136,4,main.outputHashes[0].pEx.sigmaF[3][1].in4 +842,-1,4,main.outputHashes[0].pEx.sigmaF[3][2].out +843,-1,4,main.outputHashes[0].pEx.sigmaF[3][2].in +844,137,4,main.outputHashes[0].pEx.sigmaF[3][2].in2 +845,138,4,main.outputHashes[0].pEx.sigmaF[3][2].in4 +846,-1,4,main.outputHashes[0].pEx.sigmaF[3][3].out +847,-1,4,main.outputHashes[0].pEx.sigmaF[3][3].in +848,139,4,main.outputHashes[0].pEx.sigmaF[3][3].in2 +849,140,4,main.outputHashes[0].pEx.sigmaF[3][3].in4 +850,-1,4,main.outputHashes[0].pEx.sigmaF[3][4].out +851,-1,4,main.outputHashes[0].pEx.sigmaF[3][4].in +852,141,4,main.outputHashes[0].pEx.sigmaF[3][4].in2 +853,142,4,main.outputHashes[0].pEx.sigmaF[3][4].in4 +854,-1,4,main.outputHashes[0].pEx.sigmaF[4][0].out +855,-1,4,main.outputHashes[0].pEx.sigmaF[4][0].in +856,143,4,main.outputHashes[0].pEx.sigmaF[4][0].in2 +857,144,4,main.outputHashes[0].pEx.sigmaF[4][0].in4 +858,-1,4,main.outputHashes[0].pEx.sigmaF[4][1].out +859,-1,4,main.outputHashes[0].pEx.sigmaF[4][1].in +860,145,4,main.outputHashes[0].pEx.sigmaF[4][1].in2 +861,146,4,main.outputHashes[0].pEx.sigmaF[4][1].in4 +862,-1,4,main.outputHashes[0].pEx.sigmaF[4][2].out +863,-1,4,main.outputHashes[0].pEx.sigmaF[4][2].in +864,147,4,main.outputHashes[0].pEx.sigmaF[4][2].in2 +865,148,4,main.outputHashes[0].pEx.sigmaF[4][2].in4 +866,-1,4,main.outputHashes[0].pEx.sigmaF[4][3].out +867,-1,4,main.outputHashes[0].pEx.sigmaF[4][3].in +868,149,4,main.outputHashes[0].pEx.sigmaF[4][3].in2 +869,150,4,main.outputHashes[0].pEx.sigmaF[4][3].in4 +870,-1,4,main.outputHashes[0].pEx.sigmaF[4][4].out +871,-1,4,main.outputHashes[0].pEx.sigmaF[4][4].in +872,151,4,main.outputHashes[0].pEx.sigmaF[4][4].in2 +873,152,4,main.outputHashes[0].pEx.sigmaF[4][4].in4 +874,-1,4,main.outputHashes[0].pEx.sigmaF[5][0].out +875,-1,4,main.outputHashes[0].pEx.sigmaF[5][0].in +876,153,4,main.outputHashes[0].pEx.sigmaF[5][0].in2 +877,154,4,main.outputHashes[0].pEx.sigmaF[5][0].in4 +878,-1,4,main.outputHashes[0].pEx.sigmaF[5][1].out +879,-1,4,main.outputHashes[0].pEx.sigmaF[5][1].in +880,155,4,main.outputHashes[0].pEx.sigmaF[5][1].in2 +881,156,4,main.outputHashes[0].pEx.sigmaF[5][1].in4 +882,-1,4,main.outputHashes[0].pEx.sigmaF[5][2].out +883,-1,4,main.outputHashes[0].pEx.sigmaF[5][2].in +884,157,4,main.outputHashes[0].pEx.sigmaF[5][2].in2 +885,158,4,main.outputHashes[0].pEx.sigmaF[5][2].in4 +886,-1,4,main.outputHashes[0].pEx.sigmaF[5][3].out +887,-1,4,main.outputHashes[0].pEx.sigmaF[5][3].in +888,159,4,main.outputHashes[0].pEx.sigmaF[5][3].in2 +889,160,4,main.outputHashes[0].pEx.sigmaF[5][3].in4 +890,-1,4,main.outputHashes[0].pEx.sigmaF[5][4].out +891,-1,4,main.outputHashes[0].pEx.sigmaF[5][4].in +892,161,4,main.outputHashes[0].pEx.sigmaF[5][4].in2 +893,162,4,main.outputHashes[0].pEx.sigmaF[5][4].in4 +894,-1,4,main.outputHashes[0].pEx.sigmaF[6][0].out +895,-1,4,main.outputHashes[0].pEx.sigmaF[6][0].in +896,163,4,main.outputHashes[0].pEx.sigmaF[6][0].in2 +897,164,4,main.outputHashes[0].pEx.sigmaF[6][0].in4 +898,-1,4,main.outputHashes[0].pEx.sigmaF[6][1].out +899,-1,4,main.outputHashes[0].pEx.sigmaF[6][1].in +900,165,4,main.outputHashes[0].pEx.sigmaF[6][1].in2 +901,166,4,main.outputHashes[0].pEx.sigmaF[6][1].in4 +902,-1,4,main.outputHashes[0].pEx.sigmaF[6][2].out +903,-1,4,main.outputHashes[0].pEx.sigmaF[6][2].in +904,167,4,main.outputHashes[0].pEx.sigmaF[6][2].in2 +905,168,4,main.outputHashes[0].pEx.sigmaF[6][2].in4 +906,-1,4,main.outputHashes[0].pEx.sigmaF[6][3].out +907,-1,4,main.outputHashes[0].pEx.sigmaF[6][3].in +908,169,4,main.outputHashes[0].pEx.sigmaF[6][3].in2 +909,170,4,main.outputHashes[0].pEx.sigmaF[6][3].in4 +910,-1,4,main.outputHashes[0].pEx.sigmaF[6][4].out +911,-1,4,main.outputHashes[0].pEx.sigmaF[6][4].in +912,171,4,main.outputHashes[0].pEx.sigmaF[6][4].in2 +913,172,4,main.outputHashes[0].pEx.sigmaF[6][4].in4 +914,-1,4,main.outputHashes[0].pEx.sigmaF[7][0].out +915,-1,4,main.outputHashes[0].pEx.sigmaF[7][0].in +916,173,4,main.outputHashes[0].pEx.sigmaF[7][0].in2 +917,174,4,main.outputHashes[0].pEx.sigmaF[7][0].in4 +918,-1,4,main.outputHashes[0].pEx.sigmaF[7][1].out +919,-1,4,main.outputHashes[0].pEx.sigmaF[7][1].in +920,175,4,main.outputHashes[0].pEx.sigmaF[7][1].in2 +921,176,4,main.outputHashes[0].pEx.sigmaF[7][1].in4 +922,-1,4,main.outputHashes[0].pEx.sigmaF[7][2].out +923,-1,4,main.outputHashes[0].pEx.sigmaF[7][2].in +924,177,4,main.outputHashes[0].pEx.sigmaF[7][2].in2 +925,178,4,main.outputHashes[0].pEx.sigmaF[7][2].in4 +926,-1,4,main.outputHashes[0].pEx.sigmaF[7][3].out +927,-1,4,main.outputHashes[0].pEx.sigmaF[7][3].in +928,179,4,main.outputHashes[0].pEx.sigmaF[7][3].in2 +929,180,4,main.outputHashes[0].pEx.sigmaF[7][3].in4 +930,-1,4,main.outputHashes[0].pEx.sigmaF[7][4].out +931,-1,4,main.outputHashes[0].pEx.sigmaF[7][4].in +932,181,4,main.outputHashes[0].pEx.sigmaF[7][4].in2 +933,182,4,main.outputHashes[0].pEx.sigmaF[7][4].in4 +934,-1,4,main.outputHashes[0].pEx.sigmaP[0].out +935,-1,4,main.outputHashes[0].pEx.sigmaP[0].in +936,183,4,main.outputHashes[0].pEx.sigmaP[0].in2 +937,184,4,main.outputHashes[0].pEx.sigmaP[0].in4 +938,-1,4,main.outputHashes[0].pEx.sigmaP[1].out +939,-1,4,main.outputHashes[0].pEx.sigmaP[1].in +940,185,4,main.outputHashes[0].pEx.sigmaP[1].in2 +941,186,4,main.outputHashes[0].pEx.sigmaP[1].in4 +942,-1,4,main.outputHashes[0].pEx.sigmaP[2].out +943,-1,4,main.outputHashes[0].pEx.sigmaP[2].in +944,187,4,main.outputHashes[0].pEx.sigmaP[2].in2 +945,188,4,main.outputHashes[0].pEx.sigmaP[2].in4 +946,-1,4,main.outputHashes[0].pEx.sigmaP[3].out +947,-1,4,main.outputHashes[0].pEx.sigmaP[3].in +948,189,4,main.outputHashes[0].pEx.sigmaP[3].in2 +949,190,4,main.outputHashes[0].pEx.sigmaP[3].in4 +950,-1,4,main.outputHashes[0].pEx.sigmaP[4].out +951,-1,4,main.outputHashes[0].pEx.sigmaP[4].in +952,191,4,main.outputHashes[0].pEx.sigmaP[4].in2 +953,192,4,main.outputHashes[0].pEx.sigmaP[4].in4 +954,-1,4,main.outputHashes[0].pEx.sigmaP[5].out +955,-1,4,main.outputHashes[0].pEx.sigmaP[5].in +956,193,4,main.outputHashes[0].pEx.sigmaP[5].in2 +957,194,4,main.outputHashes[0].pEx.sigmaP[5].in4 +958,-1,4,main.outputHashes[0].pEx.sigmaP[6].out +959,-1,4,main.outputHashes[0].pEx.sigmaP[6].in +960,195,4,main.outputHashes[0].pEx.sigmaP[6].in2 +961,196,4,main.outputHashes[0].pEx.sigmaP[6].in4 +962,-1,4,main.outputHashes[0].pEx.sigmaP[7].out +963,-1,4,main.outputHashes[0].pEx.sigmaP[7].in +964,197,4,main.outputHashes[0].pEx.sigmaP[7].in2 +965,198,4,main.outputHashes[0].pEx.sigmaP[7].in4 +966,-1,4,main.outputHashes[0].pEx.sigmaP[8].out +967,-1,4,main.outputHashes[0].pEx.sigmaP[8].in +968,199,4,main.outputHashes[0].pEx.sigmaP[8].in2 +969,200,4,main.outputHashes[0].pEx.sigmaP[8].in4 +970,-1,4,main.outputHashes[0].pEx.sigmaP[9].out +971,-1,4,main.outputHashes[0].pEx.sigmaP[9].in +972,201,4,main.outputHashes[0].pEx.sigmaP[9].in2 +973,202,4,main.outputHashes[0].pEx.sigmaP[9].in4 +974,-1,4,main.outputHashes[0].pEx.sigmaP[10].out +975,-1,4,main.outputHashes[0].pEx.sigmaP[10].in +976,203,4,main.outputHashes[0].pEx.sigmaP[10].in2 +977,204,4,main.outputHashes[0].pEx.sigmaP[10].in4 +978,-1,4,main.outputHashes[0].pEx.sigmaP[11].out +979,-1,4,main.outputHashes[0].pEx.sigmaP[11].in +980,205,4,main.outputHashes[0].pEx.sigmaP[11].in2 +981,206,4,main.outputHashes[0].pEx.sigmaP[11].in4 +982,-1,4,main.outputHashes[0].pEx.sigmaP[12].out +983,-1,4,main.outputHashes[0].pEx.sigmaP[12].in +984,207,4,main.outputHashes[0].pEx.sigmaP[12].in2 +985,208,4,main.outputHashes[0].pEx.sigmaP[12].in4 +986,-1,4,main.outputHashes[0].pEx.sigmaP[13].out +987,-1,4,main.outputHashes[0].pEx.sigmaP[13].in +988,209,4,main.outputHashes[0].pEx.sigmaP[13].in2 +989,210,4,main.outputHashes[0].pEx.sigmaP[13].in4 +990,-1,4,main.outputHashes[0].pEx.sigmaP[14].out +991,-1,4,main.outputHashes[0].pEx.sigmaP[14].in +992,211,4,main.outputHashes[0].pEx.sigmaP[14].in2 +993,212,4,main.outputHashes[0].pEx.sigmaP[14].in4 +994,-1,4,main.outputHashes[0].pEx.sigmaP[15].out +995,-1,4,main.outputHashes[0].pEx.sigmaP[15].in +996,213,4,main.outputHashes[0].pEx.sigmaP[15].in2 +997,214,4,main.outputHashes[0].pEx.sigmaP[15].in4 +998,-1,4,main.outputHashes[0].pEx.sigmaP[16].out +999,-1,4,main.outputHashes[0].pEx.sigmaP[16].in +1000,215,4,main.outputHashes[0].pEx.sigmaP[16].in2 +1001,216,4,main.outputHashes[0].pEx.sigmaP[16].in4 +1002,-1,4,main.outputHashes[0].pEx.sigmaP[17].out +1003,-1,4,main.outputHashes[0].pEx.sigmaP[17].in +1004,217,4,main.outputHashes[0].pEx.sigmaP[17].in2 +1005,218,4,main.outputHashes[0].pEx.sigmaP[17].in4 +1006,-1,4,main.outputHashes[0].pEx.sigmaP[18].out +1007,-1,4,main.outputHashes[0].pEx.sigmaP[18].in +1008,219,4,main.outputHashes[0].pEx.sigmaP[18].in2 +1009,220,4,main.outputHashes[0].pEx.sigmaP[18].in4 +1010,-1,4,main.outputHashes[0].pEx.sigmaP[19].out +1011,-1,4,main.outputHashes[0].pEx.sigmaP[19].in +1012,221,4,main.outputHashes[0].pEx.sigmaP[19].in2 +1013,222,4,main.outputHashes[0].pEx.sigmaP[19].in4 +1014,-1,4,main.outputHashes[0].pEx.sigmaP[20].out +1015,-1,4,main.outputHashes[0].pEx.sigmaP[20].in +1016,223,4,main.outputHashes[0].pEx.sigmaP[20].in2 +1017,224,4,main.outputHashes[0].pEx.sigmaP[20].in4 +1018,-1,4,main.outputHashes[0].pEx.sigmaP[21].out +1019,-1,4,main.outputHashes[0].pEx.sigmaP[21].in +1020,225,4,main.outputHashes[0].pEx.sigmaP[21].in2 +1021,226,4,main.outputHashes[0].pEx.sigmaP[21].in4 +1022,-1,4,main.outputHashes[0].pEx.sigmaP[22].out +1023,-1,4,main.outputHashes[0].pEx.sigmaP[22].in +1024,227,4,main.outputHashes[0].pEx.sigmaP[22].in2 +1025,228,4,main.outputHashes[0].pEx.sigmaP[22].in4 +1026,-1,4,main.outputHashes[0].pEx.sigmaP[23].out +1027,-1,4,main.outputHashes[0].pEx.sigmaP[23].in +1028,229,4,main.outputHashes[0].pEx.sigmaP[23].in2 +1029,230,4,main.outputHashes[0].pEx.sigmaP[23].in4 +1030,-1,4,main.outputHashes[0].pEx.sigmaP[24].out +1031,-1,4,main.outputHashes[0].pEx.sigmaP[24].in +1032,231,4,main.outputHashes[0].pEx.sigmaP[24].in2 +1033,232,4,main.outputHashes[0].pEx.sigmaP[24].in4 +1034,-1,4,main.outputHashes[0].pEx.sigmaP[25].out +1035,-1,4,main.outputHashes[0].pEx.sigmaP[25].in +1036,233,4,main.outputHashes[0].pEx.sigmaP[25].in2 +1037,234,4,main.outputHashes[0].pEx.sigmaP[25].in4 +1038,-1,4,main.outputHashes[0].pEx.sigmaP[26].out +1039,-1,4,main.outputHashes[0].pEx.sigmaP[26].in +1040,235,4,main.outputHashes[0].pEx.sigmaP[26].in2 +1041,236,4,main.outputHashes[0].pEx.sigmaP[26].in4 +1042,-1,4,main.outputHashes[0].pEx.sigmaP[27].out +1043,-1,4,main.outputHashes[0].pEx.sigmaP[27].in +1044,237,4,main.outputHashes[0].pEx.sigmaP[27].in2 +1045,238,4,main.outputHashes[0].pEx.sigmaP[27].in4 +1046,-1,4,main.outputHashes[0].pEx.sigmaP[28].out +1047,-1,4,main.outputHashes[0].pEx.sigmaP[28].in +1048,239,4,main.outputHashes[0].pEx.sigmaP[28].in2 +1049,240,4,main.outputHashes[0].pEx.sigmaP[28].in4 +1050,-1,4,main.outputHashes[0].pEx.sigmaP[29].out +1051,-1,4,main.outputHashes[0].pEx.sigmaP[29].in +1052,241,4,main.outputHashes[0].pEx.sigmaP[29].in2 +1053,242,4,main.outputHashes[0].pEx.sigmaP[29].in4 +1054,-1,4,main.outputHashes[0].pEx.sigmaP[30].out +1055,-1,4,main.outputHashes[0].pEx.sigmaP[30].in +1056,243,4,main.outputHashes[0].pEx.sigmaP[30].in2 +1057,244,4,main.outputHashes[0].pEx.sigmaP[30].in4 +1058,-1,4,main.outputHashes[0].pEx.sigmaP[31].out +1059,-1,4,main.outputHashes[0].pEx.sigmaP[31].in +1060,245,4,main.outputHashes[0].pEx.sigmaP[31].in2 +1061,246,4,main.outputHashes[0].pEx.sigmaP[31].in4 +1062,-1,4,main.outputHashes[0].pEx.sigmaP[32].out +1063,-1,4,main.outputHashes[0].pEx.sigmaP[32].in +1064,247,4,main.outputHashes[0].pEx.sigmaP[32].in2 +1065,248,4,main.outputHashes[0].pEx.sigmaP[32].in4 +1066,-1,4,main.outputHashes[0].pEx.sigmaP[33].out +1067,-1,4,main.outputHashes[0].pEx.sigmaP[33].in +1068,249,4,main.outputHashes[0].pEx.sigmaP[33].in2 +1069,250,4,main.outputHashes[0].pEx.sigmaP[33].in4 +1070,-1,4,main.outputHashes[0].pEx.sigmaP[34].out +1071,-1,4,main.outputHashes[0].pEx.sigmaP[34].in +1072,251,4,main.outputHashes[0].pEx.sigmaP[34].in2 +1073,252,4,main.outputHashes[0].pEx.sigmaP[34].in4 +1074,-1,4,main.outputHashes[0].pEx.sigmaP[35].out +1075,-1,4,main.outputHashes[0].pEx.sigmaP[35].in +1076,253,4,main.outputHashes[0].pEx.sigmaP[35].in2 +1077,254,4,main.outputHashes[0].pEx.sigmaP[35].in4 +1078,-1,4,main.outputHashes[0].pEx.sigmaP[36].out +1079,-1,4,main.outputHashes[0].pEx.sigmaP[36].in +1080,255,4,main.outputHashes[0].pEx.sigmaP[36].in2 +1081,256,4,main.outputHashes[0].pEx.sigmaP[36].in4 +1082,-1,4,main.outputHashes[0].pEx.sigmaP[37].out +1083,-1,4,main.outputHashes[0].pEx.sigmaP[37].in +1084,257,4,main.outputHashes[0].pEx.sigmaP[37].in2 +1085,258,4,main.outputHashes[0].pEx.sigmaP[37].in4 +1086,-1,4,main.outputHashes[0].pEx.sigmaP[38].out +1087,-1,4,main.outputHashes[0].pEx.sigmaP[38].in +1088,259,4,main.outputHashes[0].pEx.sigmaP[38].in2 +1089,260,4,main.outputHashes[0].pEx.sigmaP[38].in4 +1090,-1,4,main.outputHashes[0].pEx.sigmaP[39].out +1091,-1,4,main.outputHashes[0].pEx.sigmaP[39].in +1092,261,4,main.outputHashes[0].pEx.sigmaP[39].in2 +1093,262,4,main.outputHashes[0].pEx.sigmaP[39].in4 +1094,-1,4,main.outputHashes[0].pEx.sigmaP[40].out +1095,-1,4,main.outputHashes[0].pEx.sigmaP[40].in +1096,263,4,main.outputHashes[0].pEx.sigmaP[40].in2 +1097,264,4,main.outputHashes[0].pEx.sigmaP[40].in4 +1098,-1,4,main.outputHashes[0].pEx.sigmaP[41].out +1099,-1,4,main.outputHashes[0].pEx.sigmaP[41].in +1100,265,4,main.outputHashes[0].pEx.sigmaP[41].in2 +1101,266,4,main.outputHashes[0].pEx.sigmaP[41].in4 +1102,-1,4,main.outputHashes[0].pEx.sigmaP[42].out +1103,-1,4,main.outputHashes[0].pEx.sigmaP[42].in +1104,267,4,main.outputHashes[0].pEx.sigmaP[42].in2 +1105,268,4,main.outputHashes[0].pEx.sigmaP[42].in4 +1106,-1,4,main.outputHashes[0].pEx.sigmaP[43].out +1107,-1,4,main.outputHashes[0].pEx.sigmaP[43].in +1108,269,4,main.outputHashes[0].pEx.sigmaP[43].in2 +1109,270,4,main.outputHashes[0].pEx.sigmaP[43].in4 +1110,-1,4,main.outputHashes[0].pEx.sigmaP[44].out +1111,-1,4,main.outputHashes[0].pEx.sigmaP[44].in +1112,271,4,main.outputHashes[0].pEx.sigmaP[44].in2 +1113,272,4,main.outputHashes[0].pEx.sigmaP[44].in4 +1114,-1,4,main.outputHashes[0].pEx.sigmaP[45].out +1115,-1,4,main.outputHashes[0].pEx.sigmaP[45].in +1116,273,4,main.outputHashes[0].pEx.sigmaP[45].in2 +1117,274,4,main.outputHashes[0].pEx.sigmaP[45].in4 +1118,-1,4,main.outputHashes[0].pEx.sigmaP[46].out +1119,-1,4,main.outputHashes[0].pEx.sigmaP[46].in +1120,275,4,main.outputHashes[0].pEx.sigmaP[46].in2 +1121,276,4,main.outputHashes[0].pEx.sigmaP[46].in4 +1122,-1,4,main.outputHashes[0].pEx.sigmaP[47].out +1123,-1,4,main.outputHashes[0].pEx.sigmaP[47].in +1124,277,4,main.outputHashes[0].pEx.sigmaP[47].in2 +1125,278,4,main.outputHashes[0].pEx.sigmaP[47].in4 +1126,-1,4,main.outputHashes[0].pEx.sigmaP[48].out +1127,-1,4,main.outputHashes[0].pEx.sigmaP[48].in +1128,279,4,main.outputHashes[0].pEx.sigmaP[48].in2 +1129,280,4,main.outputHashes[0].pEx.sigmaP[48].in4 +1130,-1,4,main.outputHashes[0].pEx.sigmaP[49].out +1131,-1,4,main.outputHashes[0].pEx.sigmaP[49].in +1132,281,4,main.outputHashes[0].pEx.sigmaP[49].in2 +1133,282,4,main.outputHashes[0].pEx.sigmaP[49].in4 +1134,-1,4,main.outputHashes[0].pEx.sigmaP[50].out +1135,-1,4,main.outputHashes[0].pEx.sigmaP[50].in +1136,283,4,main.outputHashes[0].pEx.sigmaP[50].in2 +1137,284,4,main.outputHashes[0].pEx.sigmaP[50].in4 +1138,-1,4,main.outputHashes[0].pEx.sigmaP[51].out +1139,-1,4,main.outputHashes[0].pEx.sigmaP[51].in +1140,285,4,main.outputHashes[0].pEx.sigmaP[51].in2 +1141,286,4,main.outputHashes[0].pEx.sigmaP[51].in4 +1142,-1,4,main.outputHashes[0].pEx.sigmaP[52].out +1143,-1,4,main.outputHashes[0].pEx.sigmaP[52].in +1144,287,4,main.outputHashes[0].pEx.sigmaP[52].in2 +1145,288,4,main.outputHashes[0].pEx.sigmaP[52].in4 +1146,-1,4,main.outputHashes[0].pEx.sigmaP[53].out +1147,-1,4,main.outputHashes[0].pEx.sigmaP[53].in +1148,289,4,main.outputHashes[0].pEx.sigmaP[53].in2 +1149,290,4,main.outputHashes[0].pEx.sigmaP[53].in4 +1150,-1,4,main.outputHashes[0].pEx.sigmaP[54].out +1151,-1,4,main.outputHashes[0].pEx.sigmaP[54].in +1152,291,4,main.outputHashes[0].pEx.sigmaP[54].in2 +1153,292,4,main.outputHashes[0].pEx.sigmaP[54].in4 +1154,-1,4,main.outputHashes[0].pEx.sigmaP[55].out +1155,-1,4,main.outputHashes[0].pEx.sigmaP[55].in +1156,293,4,main.outputHashes[0].pEx.sigmaP[55].in2 +1157,294,4,main.outputHashes[0].pEx.sigmaP[55].in4 +1158,-1,4,main.outputHashes[0].pEx.sigmaP[56].out +1159,-1,4,main.outputHashes[0].pEx.sigmaP[56].in +1160,295,4,main.outputHashes[0].pEx.sigmaP[56].in2 +1161,296,4,main.outputHashes[0].pEx.sigmaP[56].in4 +1162,-1,4,main.outputHashes[0].pEx.sigmaP[57].out +1163,-1,4,main.outputHashes[0].pEx.sigmaP[57].in +1164,297,4,main.outputHashes[0].pEx.sigmaP[57].in2 +1165,298,4,main.outputHashes[0].pEx.sigmaP[57].in4 +1166,-1,4,main.outputHashes[0].pEx.sigmaP[58].out +1167,-1,4,main.outputHashes[0].pEx.sigmaP[58].in +1168,299,4,main.outputHashes[0].pEx.sigmaP[58].in2 +1169,300,4,main.outputHashes[0].pEx.sigmaP[58].in4 +1170,-1,4,main.outputHashes[0].pEx.sigmaP[59].out +1171,-1,4,main.outputHashes[0].pEx.sigmaP[59].in +1172,301,4,main.outputHashes[0].pEx.sigmaP[59].in2 +1173,302,4,main.outputHashes[0].pEx.sigmaP[59].in4 +1174,303,2,main.positive[0].out +1175,-1,2,main.positive[0].in[0] +1176,-1,2,main.positive[0].in[1] +1177,-1,1,main.positive[0].lt.out +1178,-1,1,main.positive[0].lt.in[0] +1179,-1,1,main.positive[0].lt.in[1] +1180,304,0,main.positive[0].lt.n2b.out[0] +1181,305,0,main.positive[0].lt.n2b.out[1] +1182,306,0,main.positive[0].lt.n2b.out[2] +1183,307,0,main.positive[0].lt.n2b.out[3] +1184,308,0,main.positive[0].lt.n2b.out[4] +1185,309,0,main.positive[0].lt.n2b.out[5] +1186,310,0,main.positive[0].lt.n2b.out[6] +1187,311,0,main.positive[0].lt.n2b.out[7] +1188,312,0,main.positive[0].lt.n2b.out[8] +1189,313,0,main.positive[0].lt.n2b.out[9] +1190,314,0,main.positive[0].lt.n2b.out[10] +1191,315,0,main.positive[0].lt.n2b.out[11] +1192,316,0,main.positive[0].lt.n2b.out[12] +1193,317,0,main.positive[0].lt.n2b.out[13] +1194,318,0,main.positive[0].lt.n2b.out[14] +1195,319,0,main.positive[0].lt.n2b.out[15] +1196,320,0,main.positive[0].lt.n2b.out[16] +1197,321,0,main.positive[0].lt.n2b.out[17] +1198,322,0,main.positive[0].lt.n2b.out[18] +1199,323,0,main.positive[0].lt.n2b.out[19] +1200,324,0,main.positive[0].lt.n2b.out[20] +1201,325,0,main.positive[0].lt.n2b.out[21] +1202,326,0,main.positive[0].lt.n2b.out[22] +1203,327,0,main.positive[0].lt.n2b.out[23] +1204,328,0,main.positive[0].lt.n2b.out[24] +1205,329,0,main.positive[0].lt.n2b.out[25] +1206,330,0,main.positive[0].lt.n2b.out[26] +1207,331,0,main.positive[0].lt.n2b.out[27] +1208,332,0,main.positive[0].lt.n2b.out[28] +1209,333,0,main.positive[0].lt.n2b.out[29] +1210,334,0,main.positive[0].lt.n2b.out[30] +1211,335,0,main.positive[0].lt.n2b.out[31] +1212,336,0,main.positive[0].lt.n2b.out[32] +1213,337,0,main.positive[0].lt.n2b.out[33] +1214,338,0,main.positive[0].lt.n2b.out[34] +1215,339,0,main.positive[0].lt.n2b.out[35] +1216,340,0,main.positive[0].lt.n2b.out[36] +1217,341,0,main.positive[0].lt.n2b.out[37] +1218,342,0,main.positive[0].lt.n2b.out[38] +1219,-1,0,main.positive[0].lt.n2b.out[39] +1220,-1,0,main.positive[0].lt.n2b.out[40] +1221,-1,0,main.positive[0].lt.n2b.in diff --git a/zkp/js/lib/check_hashes_value_js/check_hashes_value.wasm b/zkp/js/lib/check_hashes_value_js/check_hashes_value.wasm new file mode 100644 index 0000000..6f72755 Binary files /dev/null and b/zkp/js/lib/check_hashes_value_js/check_hashes_value.wasm differ diff --git a/zkp/js/lib/check_hashes_value_js/generate_witness.js b/zkp/js/lib/check_hashes_value_js/generate_witness.js new file mode 100644 index 0000000..eabb86e --- /dev/null +++ b/zkp/js/lib/check_hashes_value_js/generate_witness.js @@ -0,0 +1,20 @@ +const wc = require("./witness_calculator.js"); +const { readFileSync, writeFile } = require("fs"); + +if (process.argv.length != 5) { + console.log("Usage: node generate_witness.js "); +} else { + const input = JSON.parse(readFileSync(process.argv[3], "utf8")); + + const buffer = readFileSync(process.argv[2]); + wc(buffer).then(async witnessCalculator => { + // const w= await witnessCalculator.calculateWitness(input,0); + // for (let i=0; i< w.length; i++){ + // console.log(w[i]); + // } + const buff= await witnessCalculator.calculateWTNSBin(input,0); + writeFile(process.argv[4], buff, function(err) { + if (err) throw err; + }); + }); +} diff --git a/zkp/js/lib/check_hashes_value_js/witness_calculator.js b/zkp/js/lib/check_hashes_value_js/witness_calculator.js new file mode 100644 index 0000000..20e6e20 --- /dev/null +++ b/zkp/js/lib/check_hashes_value_js/witness_calculator.js @@ -0,0 +1,337 @@ +module.exports = async function builder(code, options) { + + options = options || {}; + + let wasmModule; + try { + wasmModule = await WebAssembly.compile(code); + } catch (err) { + console.log(err); + console.log("\nTry to run circom --c in order to generate c++ code instead\n"); + throw new Error(err); + } + + let wc; + + let errStr = ""; + let msgStr = ""; + + const instance = await WebAssembly.instantiate(wasmModule, { + runtime: { + exceptionHandler : function(code) { + let err; + if (code == 1) { + err = "Signal not found.\n"; + } else if (code == 2) { + err = "Too many signals set.\n"; + } else if (code == 3) { + err = "Signal already set.\n"; + } else if (code == 4) { + err = "Assert Failed.\n"; + } else if (code == 5) { + err = "Not enough memory.\n"; + } else if (code == 6) { + err = "Input signal array access exceeds the size.\n"; + } else { + err = "Unknown error.\n"; + } + throw new Error(err + errStr); + }, + printErrorMessage : function() { + errStr += getMessage() + "\n"; + // console.error(getMessage()); + }, + writeBufferMessage : function() { + const msg = getMessage(); + // Any calls to `log()` will always end with a `\n`, so that's when we print and reset + if (msg === "\n") { + console.log(msgStr); + msgStr = ""; + } else { + // If we've buffered other content, put a space in between the items + if (msgStr !== "") { + msgStr += " " + } + // Then append the message to the message we are creating + msgStr += msg; + } + }, + showSharedRWMemory : function() { + printSharedRWMemory (); + } + + } + }); + + const sanityCheck = + options +// options && +// ( +// options.sanityCheck || +// options.logGetSignal || +// options.logSetSignal || +// options.logStartComponent || +// options.logFinishComponent +// ); + + + wc = new WitnessCalculator(instance, sanityCheck); + return wc; + + function getMessage() { + var message = ""; + var c = instance.exports.getMessageChar(); + while ( c != 0 ) { + message += String.fromCharCode(c); + c = instance.exports.getMessageChar(); + } + return message; + } + + function printSharedRWMemory () { + const shared_rw_memory_size = instance.exports.getFieldNumLen32(); + const arr = new Uint32Array(shared_rw_memory_size); + for (let j=0; j { + const h = fnvHash(k); + const hMSB = parseInt(h.slice(0,8), 16); + const hLSB = parseInt(h.slice(8,16), 16); + const fArr = flatArray(input[k]); + let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB); + if (signalSize < 0){ + throw new Error(`Signal ${k} not found\n`); + } + if (fArr.length < signalSize) { + throw new Error(`Not enough values for input signal ${k}\n`); + } + if (fArr.length > signalSize) { + throw new Error(`Too many values for input signal ${k}\n`); + } + for (let i=0; i0) { + res.unshift(0); + i--; + } + } + return res; +} + +function fromArray32(arr) { //returns a BigInt + var res = BigInt(0); + const radix = BigInt(0x100000000); + for (let i = 0; i "); +} else { + const input = JSON.parse(readFileSync(process.argv[3], "utf8")); + + const buffer = readFileSync(process.argv[2]); + wc(buffer).then(async witnessCalculator => { + // const w= await witnessCalculator.calculateWitness(input,0); + // for (let i=0; i< w.length; i++){ + // console.log(w[i]); + // } + const buff= await witnessCalculator.calculateWTNSBin(input,0); + writeFile(process.argv[4], buff, function(err) { + if (err) throw err; + }); + }); +} diff --git a/zkp/js/lib/check_inputs_outputs_value_js/witness_calculator.js b/zkp/js/lib/check_inputs_outputs_value_js/witness_calculator.js new file mode 100644 index 0000000..20e6e20 --- /dev/null +++ b/zkp/js/lib/check_inputs_outputs_value_js/witness_calculator.js @@ -0,0 +1,337 @@ +module.exports = async function builder(code, options) { + + options = options || {}; + + let wasmModule; + try { + wasmModule = await WebAssembly.compile(code); + } catch (err) { + console.log(err); + console.log("\nTry to run circom --c in order to generate c++ code instead\n"); + throw new Error(err); + } + + let wc; + + let errStr = ""; + let msgStr = ""; + + const instance = await WebAssembly.instantiate(wasmModule, { + runtime: { + exceptionHandler : function(code) { + let err; + if (code == 1) { + err = "Signal not found.\n"; + } else if (code == 2) { + err = "Too many signals set.\n"; + } else if (code == 3) { + err = "Signal already set.\n"; + } else if (code == 4) { + err = "Assert Failed.\n"; + } else if (code == 5) { + err = "Not enough memory.\n"; + } else if (code == 6) { + err = "Input signal array access exceeds the size.\n"; + } else { + err = "Unknown error.\n"; + } + throw new Error(err + errStr); + }, + printErrorMessage : function() { + errStr += getMessage() + "\n"; + // console.error(getMessage()); + }, + writeBufferMessage : function() { + const msg = getMessage(); + // Any calls to `log()` will always end with a `\n`, so that's when we print and reset + if (msg === "\n") { + console.log(msgStr); + msgStr = ""; + } else { + // If we've buffered other content, put a space in between the items + if (msgStr !== "") { + msgStr += " " + } + // Then append the message to the message we are creating + msgStr += msg; + } + }, + showSharedRWMemory : function() { + printSharedRWMemory (); + } + + } + }); + + const sanityCheck = + options +// options && +// ( +// options.sanityCheck || +// options.logGetSignal || +// options.logSetSignal || +// options.logStartComponent || +// options.logFinishComponent +// ); + + + wc = new WitnessCalculator(instance, sanityCheck); + return wc; + + function getMessage() { + var message = ""; + var c = instance.exports.getMessageChar(); + while ( c != 0 ) { + message += String.fromCharCode(c); + c = instance.exports.getMessageChar(); + } + return message; + } + + function printSharedRWMemory () { + const shared_rw_memory_size = instance.exports.getFieldNumLen32(); + const arr = new Uint32Array(shared_rw_memory_size); + for (let j=0; j { + const h = fnvHash(k); + const hMSB = parseInt(h.slice(0,8), 16); + const hLSB = parseInt(h.slice(8,16), 16); + const fArr = flatArray(input[k]); + let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB); + if (signalSize < 0){ + throw new Error(`Signal ${k} not found\n`); + } + if (fArr.length < signalSize) { + throw new Error(`Not enough values for input signal ${k}\n`); + } + if (fArr.length > signalSize) { + throw new Error(`Too many values for input signal ${k}\n`); + } + for (let i=0; i0) { + res.unshift(0); + i--; + } + } + return res; +} + +function fromArray32(arr) { //returns a BigInt + var res = BigInt(0); + const radix = BigInt(0x100000000); + for (let i = 0; i "); +} else { + const input = JSON.parse(readFileSync(process.argv[3], "utf8")); + + const buffer = readFileSync(process.argv[2]); + wc(buffer).then(async witnessCalculator => { + // const w= await witnessCalculator.calculateWitness(input,0); + // for (let i=0; i< w.length; i++){ + // console.log(w[i]); + // } + const buff= await witnessCalculator.calculateWTNSBin(input,0); + writeFile(process.argv[4], buff, function(err) { + if (err) throw err; + }); + }); +} diff --git a/zkp/js/lib/check_nullifier_value_js/witness_calculator.js b/zkp/js/lib/check_nullifier_value_js/witness_calculator.js new file mode 100644 index 0000000..20e6e20 --- /dev/null +++ b/zkp/js/lib/check_nullifier_value_js/witness_calculator.js @@ -0,0 +1,337 @@ +module.exports = async function builder(code, options) { + + options = options || {}; + + let wasmModule; + try { + wasmModule = await WebAssembly.compile(code); + } catch (err) { + console.log(err); + console.log("\nTry to run circom --c in order to generate c++ code instead\n"); + throw new Error(err); + } + + let wc; + + let errStr = ""; + let msgStr = ""; + + const instance = await WebAssembly.instantiate(wasmModule, { + runtime: { + exceptionHandler : function(code) { + let err; + if (code == 1) { + err = "Signal not found.\n"; + } else if (code == 2) { + err = "Too many signals set.\n"; + } else if (code == 3) { + err = "Signal already set.\n"; + } else if (code == 4) { + err = "Assert Failed.\n"; + } else if (code == 5) { + err = "Not enough memory.\n"; + } else if (code == 6) { + err = "Input signal array access exceeds the size.\n"; + } else { + err = "Unknown error.\n"; + } + throw new Error(err + errStr); + }, + printErrorMessage : function() { + errStr += getMessage() + "\n"; + // console.error(getMessage()); + }, + writeBufferMessage : function() { + const msg = getMessage(); + // Any calls to `log()` will always end with a `\n`, so that's when we print and reset + if (msg === "\n") { + console.log(msgStr); + msgStr = ""; + } else { + // If we've buffered other content, put a space in between the items + if (msgStr !== "") { + msgStr += " " + } + // Then append the message to the message we are creating + msgStr += msg; + } + }, + showSharedRWMemory : function() { + printSharedRWMemory (); + } + + } + }); + + const sanityCheck = + options +// options && +// ( +// options.sanityCheck || +// options.logGetSignal || +// options.logSetSignal || +// options.logStartComponent || +// options.logFinishComponent +// ); + + + wc = new WitnessCalculator(instance, sanityCheck); + return wc; + + function getMessage() { + var message = ""; + var c = instance.exports.getMessageChar(); + while ( c != 0 ) { + message += String.fromCharCode(c); + c = instance.exports.getMessageChar(); + } + return message; + } + + function printSharedRWMemory () { + const shared_rw_memory_size = instance.exports.getFieldNumLen32(); + const arr = new Uint32Array(shared_rw_memory_size); + for (let j=0; j { + const h = fnvHash(k); + const hMSB = parseInt(h.slice(0,8), 16); + const hLSB = parseInt(h.slice(8,16), 16); + const fArr = flatArray(input[k]); + let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB); + if (signalSize < 0){ + throw new Error(`Signal ${k} not found\n`); + } + if (fArr.length < signalSize) { + throw new Error(`Not enough values for input signal ${k}\n`); + } + if (fArr.length > signalSize) { + throw new Error(`Too many values for input signal ${k}\n`); + } + for (let i=0; i0) { + res.unshift(0); + i--; + } + } + return res; +} + +function fromArray32(arr) { //returns a BigInt + var res = BigInt(0); + const radix = BigInt(0x100000000); + for (let i = 0; i { diff --git a/zkp/js/test/check_hashes_value.js b/zkp/js/test/check_hashes_value.js new file mode 100644 index 0000000..57c4eb0 --- /dev/null +++ b/zkp/js/test/check_hashes_value.js @@ -0,0 +1,184 @@ +// 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. + +const { expect } = require('chai'); +const { groth16 } = require('snarkjs'); +const { genKeypair } = require('maci-crypto'); +const { Poseidon, newSalt, loadCircuit } = require('../index.js'); +const { loadProvingKeys } = require('./utils.js'); + +const MAX_VALUE = 2n ** 40n - 1n; +const poseidonHash = Poseidon.poseidon4; + +describe('check-hashes-value circuit tests', () => { + let circuit; + const sender = {}; + before(async () => { + circuit = await loadCircuit('check_hashes_value'); + let keypair = genKeypair(); + sender.privKey = keypair.privKey; + sender.pubKey = keypair.pubKey; + }); + + it('should return true for valid witness', async () => { + const outputValues = [200]; + + // create the output UTXO + const salt1 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const outputCommitments = [output1]; + + let witness = await circuit.calculateWitness( + { + outputCommitments, + outputValues, + outputSalts: [salt1], + outputOwnerPublicKeys: [sender.pubKey], + }, + true + ); + + expect(witness[1]).to.equal(BigInt(200)); // index 1 is the output, for the calculated value + + witness = await circuit.calculateWTNSBin( + { + outputCommitments, + outputValues, + outputSalts: [salt1], + outputOwnerPublicKeys: [sender.pubKey], + }, + true + ); + const { provingKeyFile, verificationKey } = loadProvingKeys('check_hashes_value'); + const startTime = Date.now(); + const { proof, publicSignals } = await groth16.prove(provingKeyFile, witness); + console.log('Proving time: ', (Date.now() - startTime) / 1000, 's'); + const success = await groth16.verify(verificationKey, publicSignals, proof); + expect(success, true); + // console.log('output commitments', outputCommitments); + // console.log('output values', outputValues); + // console.log('public signals', publicSignals); + }).timeout(20000); + + it('should fail to generate a witness because of invalid output commitments', async () => { + const outputValues = [200]; + + // create the output UTXO + const salt1 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0] + 100), salt1, ...sender.pubKey]); + const outputCommitments = [output1]; + + let error; + try { + await circuit.calculateWTNSBin( + { + outputCommitments, + outputValues, + outputSalts: [salt1], + outputOwnerPublicKeys: [sender.pubKey], + }, + true + ); + } catch (e) { + error = e; + } + // console.log(error); + expect(error).to.match(/Error in template CheckHashesValue_77 line: 67/); // hash check failed + }); + + it('should fail to generate a witness because of negative values in output commitments', async () => { + // in the finite field used in the Poseidion hash implementation, -100n is equivalent to + // 21888242871839275222246405745257275088548364400416034343698204186575808495517n + const outputValues = [-100]; + + // create the output UTXO + const salt1 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const outputCommitments = [output1]; + + let error; + try { + await circuit.calculateWTNSBin( + { + outputCommitments, + outputValues, + outputSalts: [salt1], + outputOwnerPublicKeys: [sender.pubKey], + }, + true + ); + } catch (e) { + error = e; + } + // console.log(error); + expect(error).to.match(/Error in template CheckHashesValue_77 line: 46/); // positive range check failed + }); + + it('should fail to generate a witness because of using the inverse of a negative value in output commitments', async () => { + // in the finite field used in the Poseidion hash implementation, -100n is equivalent to + // 21888242871839275222246405745257275088548364400416034343698204186575808495517n. This number + // is considered negative by the circuit, because we allow the range of 0 to (2**40 - 1) + const outputValues = [21888242871839275222246405745257275088548364400416034343698204186575808495518n]; + + // create the output UTXO + const salt1 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const outputCommitments = [output1]; + + let error; + try { + await circuit.calculateWTNSBin( + { + outputCommitments, + outputValues, + outputSalts: [salt1], + outputOwnerPublicKeys: [sender.pubKey], + }, + true + ); + } catch (e) { + error = e; + } + // console.log(error); + expect(error).to.match(/Error in template CheckHashesValue_77 line: 46/); // positive range check failed + }); + + it('should fail to generate a witness because a larger than MAX_VALUE is used in output', async () => { + const outputValues = [MAX_VALUE + 1n]; + + // create the output UTXO + const salt1 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const outputCommitments = [output1]; + + let error; + try { + await circuit.calculateWTNSBin( + { + outputCommitments, + outputValues, + outputSalts: [salt1], + outputOwnerPublicKeys: [sender.pubKey], + }, + true + ); + } catch (e) { + error = e; + } + // console.log(error); + expect(error).to.match(/Error in template CheckHashesValue_77 line: 46/); // positive range check failed + }); +}); diff --git a/zkp/js/test/check_inputs_outputs_value.js b/zkp/js/test/check_inputs_outputs_value.js new file mode 100644 index 0000000..64ae37a --- /dev/null +++ b/zkp/js/test/check_inputs_outputs_value.js @@ -0,0 +1,168 @@ +// 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. + +const { expect } = require('chai'); +const { groth16 } = require('snarkjs'); +const { genKeypair, formatPrivKeyForBabyJub } = require('maci-crypto'); +const { Merkletree, InMemoryDB, str2Bytes, ZERO_HASH } = require('@iden3/js-merkletree'); +const { Poseidon, newSalt, loadCircuit } = require('../index.js'); +const { loadProvingKeys } = require('./utils.js'); + +const SMT_HEIGHT = 64; +const poseidonHash = Poseidon.poseidon4; +const poseidonHash3 = Poseidon.poseidon3; + +describe('check_inputs_outputs_value circuit tests', () => { + let circuit, provingKeyFile, verificationKey, smtAlice; + + const Alice = {}; + let senderPrivateKey; + + before(async () => { + circuit = await loadCircuit('check_inputs_outputs_value'); + ({ provingKeyFile, verificationKey } = loadProvingKeys('check_inputs_outputs_value')); + + let keypair = genKeypair(); + Alice.privKey = keypair.privKey; + Alice.pubKey = keypair.pubKey; + senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey); + + // initialize the local storage for Alice to manage her UTXOs in the Spart Merkle Tree + const storage1 = new InMemoryDB(str2Bytes('')); + smtAlice = new Merkletree(storage1, true, SMT_HEIGHT); + }); + + it('should succeed for valid witness', async () => { + const inputValues = [32, 40]; + const outputValues = [2]; + + // create two input UTXOs, each has their own salt, but same owner + const salt1 = newSalt(); + const input1 = poseidonHash([BigInt(inputValues[0]), salt1, ...Alice.pubKey]); + const salt2 = newSalt(); + const input2 = poseidonHash([BigInt(inputValues[1]), salt2, ...Alice.pubKey]); + const inputCommitments = [input1, input2]; + + // create output UTXOs + const salt3 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt3, ...Alice.pubKey]); + const outputCommitments = [output1]; + + const witness = await circuit.calculateWitness( + { + inputCommitments, + inputValues, + inputSalts: [salt1, salt2], + inputOwnerPrivateKey: senderPrivateKey, + outputCommitments, + outputValues, + outputSalts: [salt3], + outputOwnerPublicKeys: [Alice.pubKey], + }, + true + ); + + // console.log('witness', witness.slice(0, 10)); + // console.log('nullifiers', nullifiers); + // console.log('inputCommitments', inputCommitments); + // console.log('inputValues', inputValues); + // console.log('inputSalts', [salt1, salt2]); + // console.log('outputCommitments', outputCommitments); + // console.log('root', proof1.root.bigInt()); + // console.log('outputValues', outputValues); + // console.log('outputSalt', salt3); + // console.log('outputOwnerPublicKeys', [Alice.pubKey]); + + expect(witness[1]).to.equal(BigInt(70)); // output should be the difference between the inputs and outputs + expect(witness[2]).to.equal(BigInt(inputCommitments[0])); + expect(witness[3]).to.equal(BigInt(inputCommitments[1])); + }); + + it('should succeed for valid witness - single input', async () => { + const inputValues = [72, 0]; + const outputValues = [10]; + + // create two input UTXOs, each has their own salt, but same owner + const salt1 = newSalt(); + const input1 = poseidonHash([BigInt(inputValues[0]), salt1, ...Alice.pubKey]); + const inputCommitments = [input1, 0]; + + // create two output UTXOs, they share the same salt, and different owner + const salt3 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt3, ...Alice.pubKey]); + const outputCommitments = [output1]; + + const witness = await circuit.calculateWitness( + { + inputCommitments, + inputValues, + inputSalts: [salt1, 0], + inputOwnerPrivateKey: senderPrivateKey, + outputCommitments, + outputValues, + outputSalts: [salt3], + outputOwnerPublicKeys: [Alice.pubKey], + }, + true + ); + + expect(witness[1]).to.equal(BigInt(62)); + }); + + it('should generate a valid proof that can be verified successfully', async () => { + const inputValues = [15, 100]; + const outputValues = [35]; + + // create two input UTXOs, each has their own salt, but same owner + const senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey); + const salt1 = newSalt(); + const input1 = poseidonHash([BigInt(inputValues[0]), salt1, ...Alice.pubKey]); + const salt2 = newSalt(); + const input2 = poseidonHash([BigInt(inputValues[1]), salt2, ...Alice.pubKey]); + const inputCommitments = [input1, input2]; + + // create two output UTXOs, they share the same salt, and different owner + const salt3 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt3, ...Alice.pubKey]); + const outputCommitments = [output1]; + + const startTime = Date.now(); + const witness = await circuit.calculateWTNSBin( + { + inputCommitments, + inputValues, + inputSalts: [salt1, salt2], + inputOwnerPrivateKey: senderPrivateKey, + outputCommitments, + outputValues, + outputSalts: [salt3], + outputOwnerPublicKeys: [Alice.pubKey], + }, + true + ); + + const { proof, publicSignals } = await groth16.prove(provingKeyFile, witness); + console.log('Proving time: ', (Date.now() - startTime) / 1000, 's'); + + const success = await groth16.verify(verificationKey, publicSignals, proof); + // console.log('nullifiers', nullifiers); + // console.log('inputCommitments', inputCommitments); + // console.log('outputCommitments', outputCommitments); + // console.log('root', proof1.root.bigInt()); + // console.log('publicSignals', publicSignals); + expect(success, true); + }).timeout(600000); +}); diff --git a/zkp/js/test/check_nullifier_value.js b/zkp/js/test/check_nullifier_value.js new file mode 100644 index 0000000..21b28c5 --- /dev/null +++ b/zkp/js/test/check_nullifier_value.js @@ -0,0 +1,221 @@ +// 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. + +const { expect } = require('chai'); +const { groth16 } = require('snarkjs'); +const { genKeypair, formatPrivKeyForBabyJub } = require('maci-crypto'); +const { Merkletree, InMemoryDB, str2Bytes, ZERO_HASH } = require('@iden3/js-merkletree'); +const { Poseidon, newSalt, loadCircuit } = require('../index.js'); +const { loadProvingKeys } = require('./utils.js'); + +const SMT_HEIGHT = 64; +const poseidonHash = Poseidon.poseidon4; +const poseidonHash3 = Poseidon.poseidon3; + +describe('check_nullifier_value circuit tests', () => { + let circuit, provingKeyFile, verificationKey, smtAlice; + + const Alice = {}; + let senderPrivateKey; + + before(async () => { + circuit = await loadCircuit('check_nullifier_value'); + ({ provingKeyFile, verificationKey } = loadProvingKeys('check_nullifier_value')); + + let keypair = genKeypair(); + Alice.privKey = keypair.privKey; + Alice.pubKey = keypair.pubKey; + senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey); + + // initialize the local storage for Alice to manage her UTXOs in the Spart Merkle Tree + const storage1 = new InMemoryDB(str2Bytes('')); + smtAlice = new Merkletree(storage1, true, SMT_HEIGHT); + }); + + it('should succeed for valid witness', async () => { + const inputValues = [32, 40]; + const outputValues = [2]; + + // create two input UTXOs, each has their own salt, but same owner + const salt1 = newSalt(); + const input1 = poseidonHash([BigInt(inputValues[0]), salt1, ...Alice.pubKey]); + const salt2 = newSalt(); + const input2 = poseidonHash([BigInt(inputValues[1]), salt2, ...Alice.pubKey]); + const inputCommitments = [input1, input2]; + + // create the nullifiers for the inputs + const nullifier1 = poseidonHash3([BigInt(inputValues[0]), salt1, senderPrivateKey]); + const nullifier2 = poseidonHash3([BigInt(inputValues[1]), salt2, senderPrivateKey]); + const nullifiers = [nullifier1, nullifier2]; + + // calculate the root of the SMT + await smtAlice.add(input1, input1); + await smtAlice.add(input2, input2); + + // generate the merkle proof for the inputs + const proof1 = await smtAlice.generateCircomVerifierProof(input1, ZERO_HASH); + const proof2 = await smtAlice.generateCircomVerifierProof(input2, ZERO_HASH); + + // create output UTXOs + const salt3 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt3, ...Alice.pubKey]); + const outputCommitments = [output1]; + + const witness = await circuit.calculateWitness( + { + nullifiers, + inputCommitments, + inputValues, + inputSalts: [salt1, salt2], + inputOwnerPrivateKey: senderPrivateKey, + root: proof1.root.bigInt(), + merkleProof: [proof1.siblings.map((s) => s.bigInt()), proof2.siblings.map((s) => s.bigInt())], + enabled: [1, 1], + outputCommitments, + outputValues, + outputSalts: [salt3], + outputOwnerPublicKeys: [Alice.pubKey], + }, + true + ); + + // console.log('witness', witness.slice(0, 10)); + // console.log('nullifiers', nullifiers); + // console.log('inputCommitments', inputCommitments); + // console.log('inputValues', inputValues); + // console.log('inputSalts', [salt1, salt2]); + // console.log('outputCommitments', outputCommitments); + // console.log('root', proof1.root.bigInt()); + // console.log('outputValues', outputValues); + // console.log('outputSalt', salt3); + // console.log('outputOwnerPublicKeys', [Alice.pubKey]); + + expect(witness[1]).to.equal(BigInt(70)); // output should be the difference between the inputs and outputs + expect(witness[2]).to.equal(BigInt(nullifiers[0])); + expect(witness[3]).to.equal(BigInt(nullifiers[1])); + expect(witness[4]).to.equal(proof1.root.bigInt()); + }); + + it('should succeed for valid witness - single input', async () => { + const inputValues = [72, 0]; + const outputValues = [10]; + + // create two input UTXOs, each has their own salt, but same owner + const salt1 = newSalt(); + const input1 = poseidonHash([BigInt(inputValues[0]), salt1, ...Alice.pubKey]); + const inputCommitments = [input1, 0]; + + // create the nullifiers for the inputs + const nullifier1 = poseidonHash3([BigInt(inputValues[0]), salt1, senderPrivateKey]); + const nullifiers = [nullifier1, 0]; + + // calculate the root of the SMT + await smtAlice.add(input1, input1); + + // generate the merkle proof for the inputs + const proof1 = await smtAlice.generateCircomVerifierProof(input1, ZERO_HASH); + const proof2 = await smtAlice.generateCircomVerifierProof(0, ZERO_HASH); + + // create two output UTXOs, they share the same salt, and different owner + const salt3 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt3, ...Alice.pubKey]); + const outputCommitments = [output1]; + + const witness = await circuit.calculateWitness( + { + nullifiers, + inputCommitments, + inputValues, + inputSalts: [salt1, 0], + inputOwnerPrivateKey: senderPrivateKey, + root: proof1.root.bigInt(), + merkleProof: [proof1.siblings.map((s) => s.bigInt()), proof2.siblings.map((s) => s.bigInt())], + enabled: [1, 0], + outputCommitments, + outputValues, + outputSalts: [salt3], + outputOwnerPublicKeys: [Alice.pubKey], + }, + true + ); + + expect(witness[1]).to.equal(BigInt(62)); + expect(witness[2]).to.equal(BigInt(nullifiers[0])); + expect(witness[3]).to.equal(BigInt(nullifiers[1])); + expect(witness[4]).to.equal(proof1.root.bigInt()); + }); + + it('should generate a valid proof that can be verified successfully', async () => { + const inputValues = [15, 100]; + const outputValues = [35]; + + // create two input UTXOs, each has their own salt, but same owner + const senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey); + const salt1 = newSalt(); + const input1 = poseidonHash([BigInt(inputValues[0]), salt1, ...Alice.pubKey]); + const salt2 = newSalt(); + const input2 = poseidonHash([BigInt(inputValues[1]), salt2, ...Alice.pubKey]); + const inputCommitments = [input1, input2]; + + // create the nullifiers for the input UTXOs + const nullifier1 = poseidonHash3([BigInt(inputValues[0]), salt1, senderPrivateKey]); + const nullifier2 = poseidonHash3([BigInt(inputValues[1]), salt2, senderPrivateKey]); + const nullifiers = [nullifier1, nullifier2]; + + // calculate the root of the SMT + await smtAlice.add(input1, input1); + await smtAlice.add(input2, input2); + + // generate the merkle proof for the inputs + const proof1 = await smtAlice.generateCircomVerifierProof(input1, ZERO_HASH); + const proof2 = await smtAlice.generateCircomVerifierProof(input2, ZERO_HASH); + + // create two output UTXOs, they share the same salt, and different owner + const salt3 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt3, ...Alice.pubKey]); + const outputCommitments = [output1]; + + const startTime = Date.now(); + const witness = await circuit.calculateWTNSBin( + { + nullifiers, + inputCommitments, + inputValues, + inputSalts: [salt1, salt2], + inputOwnerPrivateKey: senderPrivateKey, + root: proof1.root.bigInt(), + merkleProof: [proof1.siblings.map((s) => s.bigInt()), proof2.siblings.map((s) => s.bigInt())], + enabled: [1, 1], + outputCommitments, + outputValues, + outputSalts: [salt3], + outputOwnerPublicKeys: [Alice.pubKey], + }, + true + ); + + const { proof, publicSignals } = await groth16.prove(provingKeyFile, witness); + console.log('Proving time: ', (Date.now() - startTime) / 1000, 's'); + + const success = await groth16.verify(verificationKey, publicSignals, proof); + // console.log('nullifiers', nullifiers); + // console.log('inputCommitments', inputCommitments); + // console.log('outputCommitments', outputCommitments); + // console.log('root', proof1.root.bigInt()); + // console.log('publicSignals', publicSignals); + expect(success, true); + }).timeout(600000); +});