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