diff --git a/go-sdk/go.mod b/go-sdk/go.mod
index e2bb617b..dca55d4c 100644
--- a/go-sdk/go.mod
+++ b/go-sdk/go.mod
@@ -46,7 +46,7 @@ require (
github.com/iden3/go-rapidsnark/types v0.0.2 // indirect
github.com/iden3/go-rapidsnark/witness/v2 v2.0.0
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
- golang.org/x/crypto v0.30.0 // indirect
+ golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sys v0.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/postgres v1.5.9
diff --git a/go-sdk/go.sum b/go-sdk/go.sum
index 8585f895..89887a1c 100644
--- a/go-sdk/go.sum
+++ b/go-sdk/go.sum
@@ -90,6 +90,8 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJ
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
diff --git a/go-sdk/integration-test/e2e_test.go b/go-sdk/integration-test/e2e_test.go
index 7376fda9..2e289448 100644
--- a/go-sdk/integration-test/e2e_test.go
+++ b/go-sdk/integration-test/e2e_test.go
@@ -132,7 +132,7 @@ func (s *E2ETestSuite) TearDownSuite() {
assert.NoError(s.T(), err)
}
-func (s *E2ETestSuite) TestZeto_1_SuccessfulProving() {
+func (s *E2ETestSuite) TestZeto_anon_SuccessfulProving() {
calc, provingKey, err := loadCircuit("anon")
assert.NoError(s.T(), err)
assert.NotNil(s.T(), calc)
@@ -193,7 +193,7 @@ func (s *E2ETestSuite) TestZeto_1_SuccessfulProving() {
assert.Equal(s.T(), 4, len(proof.PubSignals))
}
-func (s *E2ETestSuite) TestZeto_2_SuccessfulProving() {
+func (s *E2ETestSuite) TestZeto_anon_enc_SuccessfulProving() {
calc, provingKey, err := loadCircuit("anon_enc")
assert.NoError(s.T(), err)
assert.NotNil(s.T(), calc)
@@ -271,7 +271,7 @@ func (s *E2ETestSuite) TestZeto_2_SuccessfulProving() {
assert.Equal(s.T(), output1.String(), calculatedHash.String())
}
-func (s *E2ETestSuite) TestZeto_3_SuccessfulProving() {
+func (s *E2ETestSuite) TestZeto_anon_nullifier_SuccessfulProving() {
calc, provingKey, err := loadCircuit("anon_nullifier")
assert.NoError(s.T(), err)
assert.NotNil(s.T(), calc)
@@ -355,7 +355,7 @@ func (s *E2ETestSuite) TestZeto_3_SuccessfulProving() {
assert.Equal(s.T(), 7, len(proof.PubSignals))
}
-func (s *E2ETestSuite) TestZeto_4_SuccessfulProving() {
+func (s *E2ETestSuite) TestZeto_anon_enc_nullifier_SuccessfulProving() {
calc, provingKey, err := loadCircuit("anon_enc_nullifier")
assert.NoError(s.T(), err)
assert.NotNil(s.T(), calc)
@@ -444,7 +444,7 @@ func (s *E2ETestSuite) TestZeto_4_SuccessfulProving() {
assert.Equal(s.T(), 18, len(proof.PubSignals))
}
-func (s *E2ETestSuite) TestZeto_5_SuccessfulProving() {
+func (s *E2ETestSuite) TestZeto_nf_anon_SuccessfulProving() {
calc, provingKey, err := loadCircuit("nf_anon")
assert.NoError(s.T(), err)
assert.NotNil(s.T(), calc)
@@ -503,7 +503,7 @@ func (s *E2ETestSuite) TestZeto_5_SuccessfulProving() {
}
-func (s *E2ETestSuite) TestZeto_5_SuccessfulProvingWithConcurrency() {
+func (s *E2ETestSuite) TestZeto_nf_anon_SuccessfulProvingWithConcurrency() {
concurrency := 10
resultChan := make(chan struct{}, concurrency)
@@ -582,7 +582,7 @@ func (s *E2ETestSuite) TestZeto_5_SuccessfulProvingWithConcurrency() {
}
-func (s *E2ETestSuite) TestZeto_6_SuccessfulProving() {
+func (s *E2ETestSuite) TestZeto_nf_anon_nullifier_SuccessfulProving() {
calc, provingKey, err := loadCircuit("nf_anon_nullifier")
assert.NoError(s.T(), err)
assert.NotNil(s.T(), calc)
diff --git a/solidity/contracts/factory.sol b/solidity/contracts/factory.sol
index de069ac7..2e94eb0d 100644
--- a/solidity/contracts/factory.sol
+++ b/solidity/contracts/factory.sol
@@ -17,8 +17,8 @@ pragma solidity ^0.8.20;
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
-import {IZetoFungibleInitializable} from "./lib/interfaces/zeto_fungible_initializable.sol";
-import {IZetoNonFungibleInitializable} from "./lib/interfaces/zeto_nf_initializable.sol";
+import {IZetoFungibleInitializable} from "./lib/interfaces/izeto_fungible_initializable.sol";
+import {IZetoNonFungibleInitializable} from "./lib/interfaces/izeto_nf_initializable.sol";
contract ZetoTokenFactory is Ownable {
// all the addresses needed by the factory to
@@ -29,9 +29,11 @@ contract ZetoTokenFactory is Ownable {
address implementation;
address depositVerifier;
address withdrawVerifier;
+ address lockVerifier;
address verifier;
address batchVerifier;
address batchWithdrawVerifier;
+ address batchLockVerifier;
}
event ZetoTokenDeployed(address indexed zetoToken);
@@ -84,6 +86,14 @@ contract ZetoTokenFactory is Ownable {
args.batchWithdrawVerifier != address(0),
"Factory: batchWithdrawVerifier address is required"
);
+ require(
+ args.lockVerifier != address(0),
+ "Factory: lockVerifier address is required"
+ );
+ require(
+ args.batchLockVerifier != address(0),
+ "Factory: batchLockVerifier address is required"
+ );
address instance = Clones.clone(args.implementation);
require(
instance != address(0),
@@ -95,7 +105,9 @@ contract ZetoTokenFactory is Ownable {
args.depositVerifier,
args.withdrawVerifier,
args.batchVerifier,
- args.batchWithdrawVerifier
+ args.batchWithdrawVerifier,
+ args.lockVerifier,
+ args.batchLockVerifier
);
emit ZetoTokenDeployed(instance);
return instance;
@@ -110,6 +122,10 @@ contract ZetoTokenFactory is Ownable {
args.implementation != address(0),
"Factory: failed to find implementation"
);
+ require(
+ args.lockVerifier != address(0),
+ "Factory: lockVerifier address is required"
+ );
address instance = Clones.clone(args.implementation);
require(
instance != address(0),
@@ -117,7 +133,8 @@ contract ZetoTokenFactory is Ownable {
);
(IZetoNonFungibleInitializable(instance)).initialize(
initialOwner,
- args.verifier
+ args.verifier,
+ args.lockVerifier
);
emit ZetoTokenDeployed(instance);
return instance;
diff --git a/solidity/contracts/lib/interfaces/izeto_base.sol b/solidity/contracts/lib/interfaces/izeto_base.sol
index d9d37bb6..7a06370a 100644
--- a/solidity/contracts/lib/interfaces/izeto_base.sol
+++ b/solidity/contracts/lib/interfaces/izeto_base.sol
@@ -24,4 +24,7 @@ interface IZetoBase {
address indexed submitter,
bytes data
);
+ error UTXONotMinted(uint256 utxo);
+ error UTXOAlreadyOwned(uint256 utxo);
+ error UTXOAlreadySpent(uint256 utxo);
}
diff --git a/solidity/contracts/lib/interfaces/izeto_common.sol b/solidity/contracts/lib/interfaces/izeto_common.sol
new file mode 100644
index 00000000..75b9e244
--- /dev/null
+++ b/solidity/contracts/lib/interfaces/izeto_common.sol
@@ -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.
+pragma solidity ^0.8.20;
+
+uint256 constant MAX_BATCH = 10;
+interface IZetoCommon {
+ error UTXODuplicate(uint256 utxo);
+ error UTXOArrayTooLarge(uint256 maxAllowed);
+}
diff --git a/solidity/contracts/lib/interfaces/izeto_encrypted.sol b/solidity/contracts/lib/interfaces/izeto_encrypted.sol
index a3627df7..e7995d50 100644
--- a/solidity/contracts/lib/interfaces/izeto_encrypted.sol
+++ b/solidity/contracts/lib/interfaces/izeto_encrypted.sol
@@ -15,9 +15,7 @@
// limitations under the License.
pragma solidity ^0.8.20;
-import {IZetoBase} from "./izeto_base.sol";
-
-interface IZetoEncrypted is IZetoBase {
+interface IZetoEncrypted {
event UTXOTransferWithEncryptedValues(
uint256[] inputs,
uint256[] outputs,
diff --git a/solidity/contracts/lib/interfaces/zeto_fungible_initializable.sol b/solidity/contracts/lib/interfaces/izeto_fungible_initializable.sol
similarity index 89%
rename from solidity/contracts/lib/interfaces/zeto_fungible_initializable.sol
rename to solidity/contracts/lib/interfaces/izeto_fungible_initializable.sol
index 2b0485f9..4902974b 100644
--- a/solidity/contracts/lib/interfaces/zeto_fungible_initializable.sol
+++ b/solidity/contracts/lib/interfaces/izeto_fungible_initializable.sol
@@ -22,6 +22,8 @@ interface IZetoFungibleInitializable {
address _withdrawVerifier,
address _verifier,
address _batchVerifier,
- address _batchWithdrawVerifier
+ address _batchWithdrawVerifier,
+ address _lockVerifier,
+ address _batchLockVerifier
) external;
}
diff --git a/solidity/contracts/lib/interfaces/izeto_lockable.sol b/solidity/contracts/lib/interfaces/izeto_lockable.sol
new file mode 100644
index 00000000..aaa2c879
--- /dev/null
+++ b/solidity/contracts/lib/interfaces/izeto_lockable.sol
@@ -0,0 +1,46 @@
+// 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 {Commonlib} from "../common.sol";
+
+interface IZetoLockable {
+ error UTXOAlreadyLocked(uint256 utxo);
+ event UTXOsLocked(
+ uint256[] utxos,
+ address indexed delegate,
+ address indexed submitter,
+ bytes data
+ );
+}
+
+interface ILockVerifier {
+ function verifyProof(
+ uint[2] calldata _pA,
+ uint[2][2] calldata _pB,
+ uint[2] calldata _pC,
+ uint[2] calldata _pubSignals
+ ) external view returns (bool);
+}
+
+interface IBatchLockVerifier {
+ function verifyProof(
+ uint[2] calldata _pA,
+ uint[2][2] calldata _pB,
+ uint[2] calldata _pC,
+ uint[10] calldata _pubSignals
+ ) external view returns (bool);
+}
diff --git a/solidity/contracts/lib/interfaces/zeto_nf_initializable.sol b/solidity/contracts/lib/interfaces/izeto_nf_initializable.sol
similarity index 84%
rename from solidity/contracts/lib/interfaces/zeto_nf_initializable.sol
rename to solidity/contracts/lib/interfaces/izeto_nf_initializable.sol
index 00dd56bc..418c8e6c 100644
--- a/solidity/contracts/lib/interfaces/zeto_nf_initializable.sol
+++ b/solidity/contracts/lib/interfaces/izeto_nf_initializable.sol
@@ -16,5 +16,9 @@
pragma solidity ^0.8.20;
interface IZetoNonFungibleInitializable {
- function initialize(address initialOwner, address _verifier) external;
+ function initialize(
+ address initialOwner,
+ address _verifier,
+ address _lockVerifier
+ ) external;
}
diff --git a/solidity/contracts/lib/verifier_check_nullifiers_nf_owner.sol b/solidity/contracts/lib/verifier_check_nullifiers_nf_owner.sol
new file mode 100644
index 00000000..9e67a824
--- /dev/null
+++ b/solidity/contracts/lib/verifier_check_nullifiers_nf_owner.sol
@@ -0,0 +1,175 @@
+// 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_CheckNullifiersNfOwner {
+ // 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 = 4309308528273276555069543879562573478975407913854086083779827033416284670724;
+ uint256 constant IC0y = 5817651292625894880399535368272757856426406145357248162801188302741402124546;
+
+ uint256 constant IC1x = 17224538472459936668342188732120409807794796861334686524777971239307689494925;
+ uint256 constant IC1y = 14212750221780665075930805194627428108087299328089222953442904533004160200955;
+
+ uint256 constant IC2x = 21500052851511985596831587241759129211123695069562820995499323777626243316126;
+ uint256 constant IC2y = 6037878515570259558330310553021098940773801877350844236400848654303871580548;
+
+
+ // 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, r)) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ // G1 function to multiply a G1 value(x,y) to value in an address
+ function g1_mulAccC(pR, x, y, s) {
+ let success
+ let mIn := mload(0x40)
+ mstore(mIn, x)
+ mstore(add(mIn, 32), y)
+ mstore(add(mIn, 64), s)
+
+ success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+
+ mstore(add(mIn, 64), mload(pR))
+ mstore(add(mIn, 96), mload(add(pR, 32)))
+
+ success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
+ let _pPairing := add(pMem, pPairing)
+ let _pVk := add(pMem, pVk)
+
+ mstore(_pVk, IC0x)
+ mstore(add(_pVk, 32), IC0y)
+
+ // Compute the linear combination vk_x
+
+ g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
+
+ g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
+
+
+ // -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)))
+
+
+ // 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_nullifiers_owner.sol b/solidity/contracts/lib/verifier_check_nullifiers_owner.sol
new file mode 100644
index 00000000..e444a3af
--- /dev/null
+++ b/solidity/contracts/lib/verifier_check_nullifiers_owner.sol
@@ -0,0 +1,175 @@
+// 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_CheckNullifiersOwner {
+ // 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 = 15437714711895179585754171189554237454088536508405751294008657725960538859948;
+ uint256 constant IC0y = 5710533217469067717186587986116078680339061653407180992040478614510129315034;
+
+ uint256 constant IC1x = 3211630958423405965645811650201696530075836912509538993985233171207873761985;
+ uint256 constant IC1y = 17050217014800887370997347083304667458491029620002200967162286391747502526964;
+
+ uint256 constant IC2x = 5681943598545403685791415579924324264146018151001697015873174306053210278697;
+ uint256 constant IC2y = 20507069607443186103258412319299600869810562974913520243781177854985777891137;
+
+
+ // 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, r)) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ // G1 function to multiply a G1 value(x,y) to value in an address
+ function g1_mulAccC(pR, x, y, s) {
+ let success
+ let mIn := mload(0x40)
+ mstore(mIn, x)
+ mstore(add(mIn, 32), y)
+ mstore(add(mIn, 64), s)
+
+ success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+
+ mstore(add(mIn, 64), mload(pR))
+ mstore(add(mIn, 96), mload(add(pR, 32)))
+
+ success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
+ let _pPairing := add(pMem, pPairing)
+ let _pVk := add(pMem, pVk)
+
+ mstore(_pVk, IC0x)
+ mstore(add(_pVk, 32), IC0y)
+
+ // Compute the linear combination vk_x
+
+ g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
+
+ g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
+
+
+ // -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)))
+
+
+ // 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_nullifiers_owner_batch.sol b/solidity/contracts/lib/verifier_check_nullifiers_owner_batch.sol
new file mode 100644
index 00000000..e5c8dc2f
--- /dev/null
+++ b/solidity/contracts/lib/verifier_check_nullifiers_owner_batch.sol
@@ -0,0 +1,231 @@
+// 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_CheckNullifiersOwnerBatch {
+ // 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 = 8528258850388115894839180341519266267367686588288569264287268731024366872860;
+ uint256 constant IC0y = 19600183692835121679382286637250267730246517882194573081305375648515304074261;
+
+ uint256 constant IC1x = 7804014301922502791984996098232318551440582117393565561980532861951630345933;
+ uint256 constant IC1y = 18393509660314493628867878128701912387916030708797139965325544838352633397100;
+
+ uint256 constant IC2x = 18023705155351853794584162763747335484529412907622179937293531816118095106495;
+ uint256 constant IC2y = 7843447535209241282604273122793508134923847088489313289414005639562255015297;
+
+ uint256 constant IC3x = 4696859123331385039283279211470469614104930148109990268896377108020593698496;
+ uint256 constant IC3y = 8856752372630989930473403421802256528933588482262844231236735371476204390394;
+
+ uint256 constant IC4x = 4636025386643250230640245302445001469421113073289614407221321496499381035973;
+ uint256 constant IC4y = 12508504376111309964398226715694813267590879393001332178565844948828250735517;
+
+ uint256 constant IC5x = 11230479099166432242180540276977609410708655995150098471084756178766087690778;
+ uint256 constant IC5y = 4731871312849871661598664510626149831472363321343192253304437529823021632224;
+
+ uint256 constant IC6x = 3123867397076179883389864618354436140932244790448380851652341514912684727079;
+ uint256 constant IC6y = 21314076904576873571820702856080651475889073451736070497050633450083747608405;
+
+ uint256 constant IC7x = 13331397003303173743861740658278852061313051434720467673364609295435848039134;
+ uint256 constant IC7y = 6209865124755994762923064266473200429800026027977612938127517835340190098311;
+
+ uint256 constant IC8x = 10502431752531188182995281969747831614984173788443027928285273343469567628838;
+ uint256 constant IC8y = 2877936783563873261036697418140218742631617877178441408689796062523241167964;
+
+ uint256 constant IC9x = 9691072418637662079461155609639777067376614407935958036140095266969252746430;
+ uint256 constant IC9y = 12286673545494268313852033319112010708002639353895982399141406219708860646184;
+
+ uint256 constant IC10x = 386932868540425777696051802119276166303608321519914221990800817511285603363;
+ uint256 constant IC10y = 18664618993163635443332042785707501651198320496510245414706674899760771895734;
+
+
+ // 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[10] calldata _pubSignals) public view returns (bool) {
+ assembly {
+ function checkField(v) {
+ if iszero(lt(v, r)) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ // G1 function to multiply a G1 value(x,y) to value in an address
+ function g1_mulAccC(pR, x, y, s) {
+ let success
+ let mIn := mload(0x40)
+ mstore(mIn, x)
+ mstore(add(mIn, 32), y)
+ mstore(add(mIn, 64), s)
+
+ success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+
+ mstore(add(mIn, 64), mload(pR))
+ mstore(add(mIn, 96), mload(add(pR, 32)))
+
+ success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
+ let _pPairing := add(pMem, pPairing)
+ let _pVk := add(pMem, pVk)
+
+ mstore(_pVk, IC0x)
+ mstore(add(_pVk, 32), IC0y)
+
+ // Compute the linear combination vk_x
+
+ g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
+
+ g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
+
+ g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64)))
+
+ g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96)))
+
+ g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128)))
+
+ g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160)))
+
+ g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192)))
+
+ g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224)))
+
+ g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256)))
+
+ g1_mulAccC(_pVk, IC10x, IC10y, calldataload(add(pubSignals, 288)))
+
+
+ // -A
+ mstore(_pPairing, calldataload(pA))
+ mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
+
+ // B
+ mstore(add(_pPairing, 64), calldataload(pB))
+ mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
+ mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
+ mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
+
+ // alpha1
+ mstore(add(_pPairing, 192), alphax)
+ mstore(add(_pPairing, 224), alphay)
+
+ // beta2
+ mstore(add(_pPairing, 256), betax1)
+ mstore(add(_pPairing, 288), betax2)
+ mstore(add(_pPairing, 320), betay1)
+ mstore(add(_pPairing, 352), betay2)
+
+ // vk_x
+ mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
+ mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
+
+
+ // gamma2
+ mstore(add(_pPairing, 448), gammax1)
+ mstore(add(_pPairing, 480), gammax2)
+ mstore(add(_pPairing, 512), gammay1)
+ mstore(add(_pPairing, 544), gammay2)
+
+ // C
+ mstore(add(_pPairing, 576), calldataload(pC))
+ mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
+
+ // delta2
+ mstore(add(_pPairing, 640), deltax1)
+ mstore(add(_pPairing, 672), deltax2)
+ mstore(add(_pPairing, 704), deltay1)
+ mstore(add(_pPairing, 736), deltay2)
+
+
+ let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
+
+ isOk := and(success, mload(_pPairing))
+ }
+
+ let pMem := mload(0x40)
+ mstore(0x40, add(pMem, pLastMem))
+
+ // Validate that all evaluations ∈ F
+
+ checkField(calldataload(add(_pubSignals, 0)))
+
+ checkField(calldataload(add(_pubSignals, 32)))
+
+ checkField(calldataload(add(_pubSignals, 64)))
+
+ checkField(calldataload(add(_pubSignals, 96)))
+
+ checkField(calldataload(add(_pubSignals, 128)))
+
+ checkField(calldataload(add(_pubSignals, 160)))
+
+ checkField(calldataload(add(_pubSignals, 192)))
+
+ checkField(calldataload(add(_pubSignals, 224)))
+
+ checkField(calldataload(add(_pubSignals, 256)))
+
+ checkField(calldataload(add(_pubSignals, 288)))
+
+
+ // 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_nullifiers_value.sol b/solidity/contracts/lib/verifier_check_nullifiers_value.sol
new file mode 100644
index 00000000..008685d4
--- /dev/null
+++ b/solidity/contracts/lib/verifier_check_nullifiers_value.sol
@@ -0,0 +1,210 @@
+// 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_CheckNullifiersValue {
+ // 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 = 5372465145930131904181936844224539702598562677294839351793739913813002422248;
+ uint256 constant IC0y = 17408194426033394784509716657563038781374557284116121966988820958100327208947;
+
+ uint256 constant IC1x = 18721682748794380402768397518142652407455353988834810677058243887163649195944;
+ uint256 constant IC1y = 3510524177671077561512337051711627169148530693705071780712030685548737096143;
+
+ uint256 constant IC2x = 8122158197879371180286109309676763127376056183929764072927375068599106569793;
+ uint256 constant IC2y = 14069702746185228450937432180611779524058868979912356630903216371660230563795;
+
+ uint256 constant IC3x = 12054470835972212708497793935866295196617705336672489963811855490085993758837;
+ uint256 constant IC3y = 3414164109693343726343912594769661343694119217245254408298451715214194698056;
+
+ uint256 constant IC4x = 20788158480360140113733456986338078732985477544997534478761000739379167086988;
+ uint256 constant IC4y = 18739927009843693757028260696367700177351944363013044083149375549875761468959;
+
+ uint256 constant IC5x = 19951574778071643021012085154476313617081511627644669051221009630908944086558;
+ uint256 constant IC5y = 16230451990376817064850835151093534344795062721521761792474659514233372650410;
+
+ uint256 constant IC6x = 13177431207016748663806898438333194070079646082467860253233651704586403557256;
+ uint256 constant IC6y = 20902096558452622927027397347274817235993975444465062231225253027029751104905;
+
+ uint256 constant IC7x = 16080914620084423806474567527530474918486311214600624165372750879022214015497;
+ uint256 constant IC7y = 11018353982179097918136151910235210507904716869524646248692012722240219927007;
+
+
+ // 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, r)) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ // G1 function to multiply a G1 value(x,y) to value in an address
+ function g1_mulAccC(pR, x, y, s) {
+ let success
+ let mIn := mload(0x40)
+ mstore(mIn, x)
+ mstore(add(mIn, 32), y)
+ mstore(add(mIn, 64), s)
+
+ success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+
+ mstore(add(mIn, 64), mload(pR))
+ mstore(add(mIn, 96), mload(add(pR, 32)))
+
+ success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
+ let _pPairing := add(pMem, pPairing)
+ let _pVk := add(pMem, pVk)
+
+ mstore(_pVk, IC0x)
+ mstore(add(_pVk, 32), IC0y)
+
+ // Compute the linear combination vk_x
+
+ g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
+
+ g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
+
+ g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64)))
+
+ g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96)))
+
+ g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128)))
+
+ g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160)))
+
+ g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192)))
+
+
+ // -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)))
+
+
+ // 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_nullifiers_value_batch.sol b/solidity/contracts/lib/verifier_check_nullifiers_value_batch.sol
new file mode 100644
index 00000000..96db7f1d
--- /dev/null
+++ b/solidity/contracts/lib/verifier_check_nullifiers_value_batch.sol
@@ -0,0 +1,322 @@
+// 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_CheckNullifiersValueBatch {
+ // 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 = 17366683354852295992407143995504174653074195591417819551747878918252419851555;
+ uint256 constant IC0y = 19483006769124497327131823516486718544957590921235503972246996412536778649980;
+
+ uint256 constant IC1x = 10366104846534137715500591309814118886022119944596398433735317084138833334792;
+ uint256 constant IC1y = 18643499405451183547036528756385786304465469271907167766660424194164058707394;
+
+ uint256 constant IC2x = 21527677578578888285885541980444416714072945316839881166233809028918267698233;
+ uint256 constant IC2y = 6063324619444599539166268744543898744271662754151538561975809930969069774378;
+
+ uint256 constant IC3x = 5659430035828023753430174422783281723178462397264403842527625530517452189999;
+ uint256 constant IC3y = 2524917828174102567027356560699115590340023663960388221257861988015650700591;
+
+ uint256 constant IC4x = 7238189621429832667411485007166185860893028785898240074557092538061430786966;
+ uint256 constant IC4y = 20390153919215256777155824947030000580196832273416110104565624202061427202468;
+
+ uint256 constant IC5x = 15894469854222667910812295936601612004318640816386618401404798091678758027724;
+ uint256 constant IC5y = 14356168113450460554678860751069980826494953509135368818216408384111747891420;
+
+ uint256 constant IC6x = 3942663716298823998869531263141686282324249719050386829948592397432348708056;
+ uint256 constant IC6y = 3035466022559911517709445948547486025175916218731187151424634579526798275616;
+
+ uint256 constant IC7x = 1553309902541816224998744612658270612542776491552112107717121674403292493359;
+ uint256 constant IC7y = 11180635816568547989345458896877617901909650248852843301038895683628956771263;
+
+ uint256 constant IC8x = 3701018914736177770292204885016878725979532325238333503986400101377864703276;
+ uint256 constant IC8y = 19833990420531737080526472786919541772537650521778260464568233706284118513049;
+
+ uint256 constant IC9x = 12025524461625658475137301154601859231184602771266597356005073433551998537077;
+ uint256 constant IC9y = 1456393002982685462391054585518332799475085362766843390052131361397104224000;
+
+ uint256 constant IC10x = 16807793107311666302190473961042321317453287634331068595488371659019485509298;
+ uint256 constant IC10y = 18660554898013959088019910686200094855986079232329695676414858332384484100222;
+
+ uint256 constant IC11x = 9789863392723531300259587179323466696537747572630489833683814294587664776868;
+ uint256 constant IC11y = 4021843822169944439345128570575352294119541799048090956488246619674844965472;
+
+ uint256 constant IC12x = 10214507374407129083284926029820451365753765776330479881523358522180753325104;
+ uint256 constant IC12y = 6174874849309167082617299735152158687361915126002906606614371830494266455367;
+
+ uint256 constant IC13x = 10940606823101131233309979818887945079384613067709355569336590477206410424368;
+ uint256 constant IC13y = 1529208575010684508442716482754348867936757320711258330891648162641538044536;
+
+ uint256 constant IC14x = 10087732249910916073426006557040806210935045993195380389004671191499603659207;
+ uint256 constant IC14y = 7443393664317545329254912123081736912386566669005719193913868247214148583503;
+
+ uint256 constant IC15x = 16404969434890860611574834353483070312895732476387812161187868979316241878917;
+ uint256 constant IC15y = 19676747737804121937999539792895793757453717532009730518885557633073507270372;
+
+ uint256 constant IC16x = 11130436916472796659130880446782841087239080661463103611590285506562232306164;
+ uint256 constant IC16y = 13396283331421200879055216177372556296918660983129101109962712961278727949285;
+
+ uint256 constant IC17x = 18218360746339548031724109374686253420716818661174497636632005644706100544635;
+ uint256 constant IC17y = 21172625106199967128593353246092551188199701584176729699213074514231209994675;
+
+ uint256 constant IC18x = 19758930414401357761821077915160309020962693098586679596228991300362584487736;
+ uint256 constant IC18y = 13569184175490052261123892597281797139167978224682715950489992695657542966533;
+
+ uint256 constant IC19x = 13280061894247793894958942691701119684528737183396784669637060695805643386178;
+ uint256 constant IC19y = 13473180960238185615076221510950965535376963989105989970070499050578446283305;
+
+ uint256 constant IC20x = 1883062788684859331882578642430274233076862028256499324766210198222044285374;
+ uint256 constant IC20y = 21175110562847535813966290632423195949054616850607561678958250643497836918190;
+
+ uint256 constant IC21x = 12895836754941507779121370416231719346694197724945981683615558906276745887603;
+ uint256 constant IC21y = 6646006890301769910107555005644233902910512644919054229046263085688091816853;
+
+ uint256 constant IC22x = 17308914241678938030422526322338288734470378999652358159021166607108962704968;
+ uint256 constant IC22y = 18406851446505143717056856075557317602320041669211343434213146566599365910852;
+
+ uint256 constant IC23x = 7080930845263346356578029481773118223285460683970543357645916770136148160025;
+ uint256 constant IC23y = 1811823263315974161935966519923924161086093162314533117675697138869841933466;
+
+
+ // Memory data
+ uint16 constant pVk = 0;
+ uint16 constant pPairing = 128;
+
+ uint16 constant pLastMem = 896;
+
+ function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[23] calldata _pubSignals) public view returns (bool) {
+ assembly {
+ function checkField(v) {
+ if iszero(lt(v, r)) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ // G1 function to multiply a G1 value(x,y) to value in an address
+ function g1_mulAccC(pR, x, y, s) {
+ let success
+ let mIn := mload(0x40)
+ mstore(mIn, x)
+ mstore(add(mIn, 32), y)
+ mstore(add(mIn, 64), s)
+
+ success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+
+ mstore(add(mIn, 64), mload(pR))
+ mstore(add(mIn, 96), mload(add(pR, 32)))
+
+ success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
+ let _pPairing := add(pMem, pPairing)
+ let _pVk := add(pMem, pVk)
+
+ mstore(_pVk, IC0x)
+ mstore(add(_pVk, 32), IC0y)
+
+ // Compute the linear combination vk_x
+
+ g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
+
+ g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
+
+ g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64)))
+
+ g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96)))
+
+ g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128)))
+
+ g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160)))
+
+ g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192)))
+
+ g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224)))
+
+ g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256)))
+
+ g1_mulAccC(_pVk, IC10x, IC10y, calldataload(add(pubSignals, 288)))
+
+ g1_mulAccC(_pVk, IC11x, IC11y, calldataload(add(pubSignals, 320)))
+
+ g1_mulAccC(_pVk, IC12x, IC12y, calldataload(add(pubSignals, 352)))
+
+ g1_mulAccC(_pVk, IC13x, IC13y, calldataload(add(pubSignals, 384)))
+
+ g1_mulAccC(_pVk, IC14x, IC14y, calldataload(add(pubSignals, 416)))
+
+ g1_mulAccC(_pVk, IC15x, IC15y, calldataload(add(pubSignals, 448)))
+
+ g1_mulAccC(_pVk, IC16x, IC16y, calldataload(add(pubSignals, 480)))
+
+ g1_mulAccC(_pVk, IC17x, IC17y, calldataload(add(pubSignals, 512)))
+
+ g1_mulAccC(_pVk, IC18x, IC18y, calldataload(add(pubSignals, 544)))
+
+ g1_mulAccC(_pVk, IC19x, IC19y, calldataload(add(pubSignals, 576)))
+
+ g1_mulAccC(_pVk, IC20x, IC20y, calldataload(add(pubSignals, 608)))
+
+ g1_mulAccC(_pVk, IC21x, IC21y, calldataload(add(pubSignals, 640)))
+
+ g1_mulAccC(_pVk, IC22x, IC22y, calldataload(add(pubSignals, 672)))
+
+ g1_mulAccC(_pVk, IC23x, IC23y, calldataload(add(pubSignals, 704)))
+
+
+ // -A
+ mstore(_pPairing, calldataload(pA))
+ mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
+
+ // B
+ mstore(add(_pPairing, 64), calldataload(pB))
+ mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
+ mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
+ mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
+
+ // alpha1
+ mstore(add(_pPairing, 192), alphax)
+ mstore(add(_pPairing, 224), alphay)
+
+ // beta2
+ mstore(add(_pPairing, 256), betax1)
+ mstore(add(_pPairing, 288), betax2)
+ mstore(add(_pPairing, 320), betay1)
+ mstore(add(_pPairing, 352), betay2)
+
+ // vk_x
+ mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
+ mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
+
+
+ // gamma2
+ mstore(add(_pPairing, 448), gammax1)
+ mstore(add(_pPairing, 480), gammax2)
+ mstore(add(_pPairing, 512), gammay1)
+ mstore(add(_pPairing, 544), gammay2)
+
+ // C
+ mstore(add(_pPairing, 576), calldataload(pC))
+ mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
+
+ // delta2
+ mstore(add(_pPairing, 640), deltax1)
+ mstore(add(_pPairing, 672), deltax2)
+ mstore(add(_pPairing, 704), deltay1)
+ mstore(add(_pPairing, 736), deltay2)
+
+
+ let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
+
+ isOk := and(success, mload(_pPairing))
+ }
+
+ let pMem := mload(0x40)
+ mstore(0x40, add(pMem, pLastMem))
+
+ // Validate that all evaluations ∈ F
+
+ checkField(calldataload(add(_pubSignals, 0)))
+
+ checkField(calldataload(add(_pubSignals, 32)))
+
+ checkField(calldataload(add(_pubSignals, 64)))
+
+ checkField(calldataload(add(_pubSignals, 96)))
+
+ checkField(calldataload(add(_pubSignals, 128)))
+
+ checkField(calldataload(add(_pubSignals, 160)))
+
+ checkField(calldataload(add(_pubSignals, 192)))
+
+ checkField(calldataload(add(_pubSignals, 224)))
+
+ checkField(calldataload(add(_pubSignals, 256)))
+
+ checkField(calldataload(add(_pubSignals, 288)))
+
+ checkField(calldataload(add(_pubSignals, 320)))
+
+ checkField(calldataload(add(_pubSignals, 352)))
+
+ checkField(calldataload(add(_pubSignals, 384)))
+
+ checkField(calldataload(add(_pubSignals, 416)))
+
+ checkField(calldataload(add(_pubSignals, 448)))
+
+ checkField(calldataload(add(_pubSignals, 480)))
+
+ checkField(calldataload(add(_pubSignals, 512)))
+
+ checkField(calldataload(add(_pubSignals, 544)))
+
+ checkField(calldataload(add(_pubSignals, 576)))
+
+ checkField(calldataload(add(_pubSignals, 608)))
+
+ checkField(calldataload(add(_pubSignals, 640)))
+
+ checkField(calldataload(add(_pubSignals, 672)))
+
+ checkField(calldataload(add(_pubSignals, 704)))
+
+
+ // 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_utxos_nf_owner.sol b/solidity/contracts/lib/verifier_check_utxos_nf_owner.sol
new file mode 100644
index 00000000..16091cc3
--- /dev/null
+++ b/solidity/contracts/lib/verifier_check_utxos_nf_owner.sol
@@ -0,0 +1,175 @@
+// 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_CheckUtxosNfOwner {
+ // 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 = 11834817990125450207882662317178229662168921017394937249039476333827021752358;
+ uint256 constant IC0y = 13264538448072108567381414814630071256184633843121427054058706352307968334015;
+
+ uint256 constant IC1x = 8878207471551106406125401855398991204845318231252966942470425545081611724129;
+ uint256 constant IC1y = 10113481109969380664898495596562127194211384278039881640923082804507768499008;
+
+ uint256 constant IC2x = 5958066283968045199378225973004359886908075353202509196118312823692263538912;
+ uint256 constant IC2y = 191875097869937814700522008522923610529603316540953877209254386949444051430;
+
+
+ // 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, r)) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ // G1 function to multiply a G1 value(x,y) to value in an address
+ function g1_mulAccC(pR, x, y, s) {
+ let success
+ let mIn := mload(0x40)
+ mstore(mIn, x)
+ mstore(add(mIn, 32), y)
+ mstore(add(mIn, 64), s)
+
+ success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+
+ mstore(add(mIn, 64), mload(pR))
+ mstore(add(mIn, 96), mload(add(pR, 32)))
+
+ success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
+ let _pPairing := add(pMem, pPairing)
+ let _pVk := add(pMem, pVk)
+
+ mstore(_pVk, IC0x)
+ mstore(add(_pVk, 32), IC0y)
+
+ // Compute the linear combination vk_x
+
+ g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
+
+ g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
+
+
+ // -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)))
+
+
+ // 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_utxos_owner.sol b/solidity/contracts/lib/verifier_check_utxos_owner.sol
new file mode 100644
index 00000000..bb0d4ff7
--- /dev/null
+++ b/solidity/contracts/lib/verifier_check_utxos_owner.sol
@@ -0,0 +1,175 @@
+// 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_CheckUtxosOwner {
+ // 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 = 5961892133655469174588966950916576296738755992343172721356529017335297389409;
+ uint256 constant IC0y = 14333163046797333444798765436665643398629491431485287677360671189173098157441;
+
+ uint256 constant IC1x = 17214718495523785535701657719670011964855503384278062826642155179345426580098;
+ uint256 constant IC1y = 12678898308813596443127633044318475259792489274472242452023364053911511648639;
+
+ uint256 constant IC2x = 4382937387936351427147757775456574969020583627402302746338787268231726676873;
+ uint256 constant IC2y = 4583555676960040664251711232618687088763042218077749116431082796685416720755;
+
+
+ // 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, r)) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ // G1 function to multiply a G1 value(x,y) to value in an address
+ function g1_mulAccC(pR, x, y, s) {
+ let success
+ let mIn := mload(0x40)
+ mstore(mIn, x)
+ mstore(add(mIn, 32), y)
+ mstore(add(mIn, 64), s)
+
+ success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+
+ mstore(add(mIn, 64), mload(pR))
+ mstore(add(mIn, 96), mload(add(pR, 32)))
+
+ success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
+ let _pPairing := add(pMem, pPairing)
+ let _pVk := add(pMem, pVk)
+
+ mstore(_pVk, IC0x)
+ mstore(add(_pVk, 32), IC0y)
+
+ // Compute the linear combination vk_x
+
+ g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
+
+ g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
+
+
+ // -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)))
+
+
+ // 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_utxos_owner_batch.sol b/solidity/contracts/lib/verifier_check_utxos_owner_batch.sol
new file mode 100644
index 00000000..54aaf52f
--- /dev/null
+++ b/solidity/contracts/lib/verifier_check_utxos_owner_batch.sol
@@ -0,0 +1,231 @@
+// 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_CheckUtxosOwnerBatch {
+ // 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 = 2019133110334799079425720252524978656148677442307733829029335718239844248642;
+ uint256 constant IC0y = 15437078419307830169493280368839094900608721512242255553932766493989126096923;
+
+ uint256 constant IC1x = 12902785943338106540191163237267156787790413011031228585840630350245008538803;
+ uint256 constant IC1y = 18508603445541812803605845259504365909862734604958606568697447707078592718152;
+
+ uint256 constant IC2x = 16052987633030516259956058349277640461371654868535498810044785725121944322273;
+ uint256 constant IC2y = 8909405537520661410404436053871970394298922830025828257566581081798732946452;
+
+ uint256 constant IC3x = 1001638733758156440282758805166347664879973243421851481973719940836571668732;
+ uint256 constant IC3y = 1805355357950459346172787152177029694219914080020783280713837995712005345062;
+
+ uint256 constant IC4x = 5775424207900901686941009808240165861032026997076785514633616152434195725669;
+ uint256 constant IC4y = 9221275075255565955657520911628947744808244750095308891818236663540957277909;
+
+ uint256 constant IC5x = 13936138480435344205614871824836050090297527171400322397842611171170832980542;
+ uint256 constant IC5y = 20145310508389131287822928089996506337466564235011098953824959738488970172165;
+
+ uint256 constant IC6x = 3942730393330289653942526126435774599373208702832247026422315771694230682831;
+ uint256 constant IC6y = 12270621553298218415551240556526593092586562257362108921560182003055694431509;
+
+ uint256 constant IC7x = 4544743727087143780925184959665988204772393544843712720909770146447748764959;
+ uint256 constant IC7y = 15184443634863895420712018160942679166357680634966874689308604787467495758854;
+
+ uint256 constant IC8x = 21482233861155139035528810938943999305836362224395593527256392378343952917660;
+ uint256 constant IC8y = 7877281370804700424157926266579645538073826532047151452368511164507053013565;
+
+ uint256 constant IC9x = 5852234013532731854804102395708112377709177292961453250455371504103979378578;
+ uint256 constant IC9y = 8085200381926110621075189316790821783825496633277171915381866269101379466858;
+
+ uint256 constant IC10x = 21149401748448051688539999249500090252279838765037889429252946026745436718535;
+ uint256 constant IC10y = 19123077706920296738482923194055175588030967051902200489009585219220575264666;
+
+
+ // 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[10] calldata _pubSignals) public view returns (bool) {
+ assembly {
+ function checkField(v) {
+ if iszero(lt(v, r)) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ // G1 function to multiply a G1 value(x,y) to value in an address
+ function g1_mulAccC(pR, x, y, s) {
+ let success
+ let mIn := mload(0x40)
+ mstore(mIn, x)
+ mstore(add(mIn, 32), y)
+ mstore(add(mIn, 64), s)
+
+ success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+
+ mstore(add(mIn, 64), mload(pR))
+ mstore(add(mIn, 96), mload(add(pR, 32)))
+
+ success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
+
+ if iszero(success) {
+ mstore(0, 0)
+ return(0, 0x20)
+ }
+ }
+
+ function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
+ let _pPairing := add(pMem, pPairing)
+ let _pVk := add(pMem, pVk)
+
+ mstore(_pVk, IC0x)
+ mstore(add(_pVk, 32), IC0y)
+
+ // Compute the linear combination vk_x
+
+ g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
+
+ g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
+
+ g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64)))
+
+ g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96)))
+
+ g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128)))
+
+ g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160)))
+
+ g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192)))
+
+ g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224)))
+
+ g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256)))
+
+ g1_mulAccC(_pVk, IC10x, IC10y, calldataload(add(pubSignals, 288)))
+
+
+ // -A
+ mstore(_pPairing, calldataload(pA))
+ mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
+
+ // B
+ mstore(add(_pPairing, 64), calldataload(pB))
+ mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
+ mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
+ mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
+
+ // alpha1
+ mstore(add(_pPairing, 192), alphax)
+ mstore(add(_pPairing, 224), alphay)
+
+ // beta2
+ mstore(add(_pPairing, 256), betax1)
+ mstore(add(_pPairing, 288), betax2)
+ mstore(add(_pPairing, 320), betay1)
+ mstore(add(_pPairing, 352), betay2)
+
+ // vk_x
+ mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
+ mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
+
+
+ // gamma2
+ mstore(add(_pPairing, 448), gammax1)
+ mstore(add(_pPairing, 480), gammax2)
+ mstore(add(_pPairing, 512), gammay1)
+ mstore(add(_pPairing, 544), gammay2)
+
+ // C
+ mstore(add(_pPairing, 576), calldataload(pC))
+ mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
+
+ // delta2
+ mstore(add(_pPairing, 640), deltax1)
+ mstore(add(_pPairing, 672), deltax2)
+ mstore(add(_pPairing, 704), deltay1)
+ mstore(add(_pPairing, 736), deltay2)
+
+
+ let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
+
+ isOk := and(success, mload(_pPairing))
+ }
+
+ let pMem := mload(0x40)
+ mstore(0x40, add(pMem, pLastMem))
+
+ // Validate that all evaluations ∈ F
+
+ checkField(calldataload(add(_pubSignals, 0)))
+
+ checkField(calldataload(add(_pubSignals, 32)))
+
+ checkField(calldataload(add(_pubSignals, 64)))
+
+ checkField(calldataload(add(_pubSignals, 96)))
+
+ checkField(calldataload(add(_pubSignals, 128)))
+
+ checkField(calldataload(add(_pubSignals, 160)))
+
+ checkField(calldataload(add(_pubSignals, 192)))
+
+ checkField(calldataload(add(_pubSignals, 224)))
+
+ checkField(calldataload(add(_pubSignals, 256)))
+
+ checkField(calldataload(add(_pubSignals, 288)))
+
+
+ // 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 35f4e0e9..696c7500 100644
--- a/solidity/contracts/lib/zeto_base.sol
+++ b/solidity/contracts/lib/zeto_base.sol
@@ -17,10 +17,7 @@ pragma solidity ^0.8.20;
import {IZetoBase} from "./interfaces/izeto_base.sol";
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.
@@ -48,8 +45,7 @@ abstract contract ZetoBase is IZetoBase, ZetoCommon {
function validateTransactionProposal(
uint256[] memory inputs,
- uint256[] memory outputs,
- Commonlib.Proof calldata proof
+ uint256[] memory outputs
) internal view returns (bool) {
// sort the inputs and outputs to detect duplicates
(
@@ -88,15 +84,6 @@ abstract contract ZetoBase is IZetoBase, ZetoCommon {
revert UTXOAlreadyOwned(sortedOutputs[i]);
}
}
-
- // check if the proof has been locked
- bytes32 proofHash = Commonlib.getProofHash(proof);
- if (lockedProofs[proofHash] != address(0)) {
- require(
- lockedProofs[proofHash] == msg.sender,
- "Locked proof can only be submitted by the locker address"
- );
- }
return true;
}
diff --git a/solidity/contracts/lib/zeto_common.sol b/solidity/contracts/lib/zeto_common.sol
index 79013a19..b22e87c3 100644
--- a/solidity/contracts/lib/zeto_common.sol
+++ b/solidity/contracts/lib/zeto_common.sol
@@ -16,47 +16,17 @@
pragma solidity ^0.8.20;
import {Commonlib} from "./common.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+import {IZetoCommon} from "./interfaces/izeto_common.sol";
import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/// @title A sample base implementation of a Zeto based token contract
/// @author Kaleido, Inc.
/// @dev Implements common functionalities of Zeto based tokens
-abstract contract ZetoCommon is OwnableUpgradeable {
- error UTXONotMinted(uint256 utxo);
- error UTXOAlreadyOwned(uint256 utxo);
- error UTXOAlreadySpent(uint256 utxo);
- error UTXODuplicate(uint256 utxo);
- error IdentityNotRegistered(address addr);
- error UTXOArrayTooLarge(uint256 maxAllowed);
-
- // used for multi-step transaction flows that require counterparties
- // to upload proofs. To protect the party that uploads their proof first,
- // and prevent the other party from utilizing the uploaded proof to execute
- // a transaction, the proof can be locked and only usable by the same party
- // that did the locking.
- mapping(bytes32 => address) internal lockedProofs;
-
+abstract contract ZetoCommon is IZetoCommon, OwnableUpgradeable {
function __ZetoCommon_init(address initialOwner) internal onlyInitializing {
__Ownable_init(initialOwner);
}
-
- // should be called by escrow contracts that will use uploaded proofs
- // to execute transactions, in order to prevent the proof from being used
- // by parties other than the escrow contract
- function lockProof(
- Commonlib.Proof calldata proof,
- address delegate
- ) public {
- bytes32 proofHash = Commonlib.getProofHash(proof);
- require(
- lockedProofs[proofHash] == address(0) ||
- lockedProofs[proofHash] == msg.sender,
- "Proof already locked by another party"
- );
- lockedProofs[proofHash] = delegate;
- }
function checkAndPadCommitments(
uint256[] memory inputs,
uint256[] memory outputs,
diff --git a/solidity/contracts/lib/zeto_fungible.sol b/solidity/contracts/lib/zeto_fungible.sol
index 562876f2..9f0b8672 100644
--- a/solidity/contracts/lib/zeto_fungible.sol
+++ b/solidity/contracts/lib/zeto_fungible.sol
@@ -25,22 +25,23 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own
/// @author Kaleido, Inc.
/// @dev Defines the verifier library for checking UTXOs against a claimed value.
abstract contract ZetoFungible is OwnableUpgradeable {
- // depositVerifier library for checking UTXOs against a claimed value.
+ // _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_CheckHashesValue internal depositVerifier;
+ Groth16Verifier_CheckHashesValue internal _depositVerifier;
+
error WithdrawArrayTooLarge(uint256 maxAllowed);
- IERC20 internal erc20;
+ IERC20 internal _erc20;
function __ZetoFungible_init(
- Groth16Verifier_CheckHashesValue _depositVerifier
+ Groth16Verifier_CheckHashesValue depositVerifier
) public onlyInitializing {
- depositVerifier = _depositVerifier;
+ _depositVerifier = depositVerifier;
}
- function setERC20(IERC20 _erc20) public onlyOwner {
- erc20 = _erc20;
+ function setERC20(IERC20 erc20) public onlyOwner {
+ _erc20 = erc20;
}
function _deposit(
@@ -58,7 +59,7 @@ abstract contract ZetoFungible is OwnableUpgradeable {
// Check the proof
require(
- depositVerifier.verifyProof(
+ _depositVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -68,7 +69,7 @@ abstract contract ZetoFungible is OwnableUpgradeable {
);
require(
- erc20.transferFrom(msg.sender, address(this), amount),
+ _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
index f943dfff..2ee83b67 100644
--- a/solidity/contracts/lib/zeto_fungible_withdraw.sol
+++ b/solidity/contracts/lib/zeto_fungible_withdraw.sol
@@ -20,7 +20,6 @@ import {Groth16Verifier_CheckInputsOutputsValue} from "./verifier_check_inputs_o
import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./verifier_check_inputs_outputs_value_batch.sol";
import {ZetoFungible} from "./zeto_fungible.sol";
import {Commonlib} from "./common.sol";
-import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
uint256 constant WITHDRAW_INPUT_SIZE = 4;
uint256 constant BATCH_WITHDRAW_INPUT_SIZE = 12;
@@ -32,17 +31,18 @@ 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;
- Groth16Verifier_CheckInputsOutputsValueBatch internal batchWithdrawVerifier;
+ Groth16Verifier_CheckInputsOutputsValue internal _withdrawVerifier;
+ Groth16Verifier_CheckInputsOutputsValueBatch
+ internal _batchWithdrawVerifier;
function __ZetoFungibleWithdraw_init(
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier,
- Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckInputsOutputsValue withdrawVerifier,
+ Groth16Verifier_CheckInputsOutputsValueBatch batchWithdrawVerifier
) public onlyInitializing {
- __ZetoFungible_init(_depositVerifier);
- withdrawVerifier = _withdrawVerifier;
- batchWithdrawVerifier = _batchWithdrawVerifier;
+ __ZetoFungible_init(depositVerifier);
+ _withdrawVerifier = withdrawVerifier;
+ _batchWithdrawVerifier = batchWithdrawVerifier;
}
function constructPublicInputs(
@@ -93,7 +93,7 @@ abstract contract ZetoFungibleWithdraw is ZetoFungible {
}
// Check the proof
require(
- batchWithdrawVerifier.verifyProof(
+ _batchWithdrawVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -115,7 +115,7 @@ abstract contract ZetoFungibleWithdraw is ZetoFungible {
}
// Check the proof
require(
- withdrawVerifier.verifyProof(
+ _withdrawVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -126,7 +126,7 @@ abstract contract ZetoFungibleWithdraw is ZetoFungible {
}
require(
- erc20.transfer(msg.sender, amount),
+ _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
index cc942c46..153e0dd4 100644
--- a/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol
+++ b/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol
@@ -20,8 +20,6 @@ import {Groth16Verifier_CheckNullifierValue} from "./verifier_check_nullifier_va
import {Groth16Verifier_CheckNullifierValueBatch} from "./verifier_check_nullifier_value_batch.sol";
import {ZetoFungible} from "./zeto_fungible.sol";
import {Commonlib} from "./common.sol";
-import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
uint256 constant WITHDRAW_INPUT_SIZE = 7;
uint256 constant BATCH_WITHDRAW_INPUT_SIZE = 23;
@@ -33,17 +31,17 @@ 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;
- Groth16Verifier_CheckNullifierValueBatch internal batchWithdrawVerifier;
+ Groth16Verifier_CheckNullifierValue internal _withdrawVerifier;
+ Groth16Verifier_CheckNullifierValueBatch internal _batchWithdrawVerifier;
function __ZetoFungibleWithdrawWithNullifiers_init(
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckNullifierValue _withdrawVerifier,
- Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckNullifierValue withdrawVerifier,
+ Groth16Verifier_CheckNullifierValueBatch batchWithdrawVerifier
) internal onlyInitializing {
- __ZetoFungible_init(_depositVerifier);
- withdrawVerifier = _withdrawVerifier;
- batchWithdrawVerifier = _batchWithdrawVerifier;
+ __ZetoFungible_init(depositVerifier);
+ _withdrawVerifier = withdrawVerifier;
+ _batchWithdrawVerifier = batchWithdrawVerifier;
}
function constructPublicInputs(
@@ -105,7 +103,7 @@ abstract contract ZetoFungibleWithdrawWithNullifiers is ZetoFungible {
}
// Check the proof
require(
- batchWithdrawVerifier.verifyProof(
+ _batchWithdrawVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -128,7 +126,7 @@ abstract contract ZetoFungibleWithdrawWithNullifiers is ZetoFungible {
}
// Check the proof
require(
- withdrawVerifier.verifyProof(
+ _withdrawVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -139,7 +137,7 @@ abstract contract ZetoFungibleWithdrawWithNullifiers is ZetoFungible {
}
require(
- erc20.transfer(msg.sender, amount),
+ _erc20.transfer(msg.sender, amount),
"Failed to transfer ERC20 tokens"
);
}
diff --git a/solidity/contracts/lib/zeto_lock.sol b/solidity/contracts/lib/zeto_lock.sol
new file mode 100644
index 00000000..ce2b4690
--- /dev/null
+++ b/solidity/contracts/lib/zeto_lock.sol
@@ -0,0 +1,125 @@
+// 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 {IZetoBase} from "./interfaces/izeto_base.sol";
+import {IZetoLockable, ILockVerifier, IBatchLockVerifier} from "./interfaces/izeto_lockable.sol";
+import {Commonlib} from "./common.sol";
+import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
+
+/// @title A sample base implementation of a Zeto based token contract
+/// without using nullifiers. Each UTXO's spending status is explicitly tracked.
+/// @author Kaleido, Inc.
+/// @dev Implements common functionalities of Zeto based tokens without nullifiers
+abstract contract ZetoLock is IZetoBase, IZetoLockable, OwnableUpgradeable {
+ // used for multi-step transaction flows that require counterparties
+ // to upload proofs. To protect the party that uploads their proof first,
+ // and prevent any other party from utilizing the uploaded proof to execute
+ // a transaction, the input UTXOs or nullifiers can be locked and only usable
+ // by the same party that did the locking.
+ mapping(uint256 => address) internal lockedUTXOs;
+
+ ILockVerifier internal _lockVerifier;
+ IBatchLockVerifier internal _batchLockVerifier;
+
+ function __ZetoLock_init(
+ ILockVerifier lockVerifier,
+ IBatchLockVerifier batchLockVerifier
+ ) public onlyInitializing {
+ _lockVerifier = lockVerifier;
+ _batchLockVerifier = batchLockVerifier;
+ }
+
+ // should be called by escrow contracts that will use uploaded proofs
+ // to execute transactions, in order to prevent the proof from being used
+ // by parties other than the escrow contract
+ function lockStates(
+ uint256[] memory utxos,
+ Commonlib.Proof calldata proof,
+ address delegate,
+ bytes calldata data
+ ) public {
+ for (uint256 i = 0; i < utxos.length; ++i) {
+ if (utxos[i] == 0) {
+ continue;
+ }
+ if (
+ lockedUTXOs[utxos[i]] != address(0) &&
+ lockedUTXOs[utxos[i]] != msg.sender
+ ) {
+ revert UTXOAlreadyLocked(utxos[i]);
+ }
+ lockedUTXOs[utxos[i]] = delegate;
+ }
+ // verify that the proof is valid
+ if (utxos.length <= 2) {
+ uint256[2] memory utxosArray;
+ for (uint256 i = 0; i < utxos.length; ++i) {
+ utxosArray[i] = utxos[i];
+ }
+ for (uint256 i = utxos.length; i < 2; ++i) {
+ utxosArray[i] = 0;
+ }
+
+ require(_verifyLockProof(utxosArray, proof), "Invalid proof");
+ } else {
+ uint256[10] memory utxosArray;
+ for (uint256 i = 0; i < utxos.length; ++i) {
+ utxosArray[i] = utxos[i];
+ }
+ for (uint256 i = utxos.length; i < 10; ++i) {
+ utxosArray[i] = 0;
+ }
+
+ require(_verifyBatchLockProof(utxosArray, proof), "Invalid proof");
+ }
+
+ emit UTXOsLocked(utxos, delegate, msg.sender, data);
+ }
+
+ function validateLockedStates(
+ uint256[] memory utxos
+ ) internal returns (bool) {
+ for (uint256 i = 0; i < utxos.length; ++i) {
+ if (utxos[i] == 0) {
+ continue;
+ }
+ // check if the UTXO has been locked
+ if (lockedUTXOs[utxos[i]] != address(0)) {
+ if (lockedUTXOs[utxos[i]] != msg.sender) {
+ revert UTXOAlreadyLocked(utxos[i]);
+ }
+ delete lockedUTXOs[utxos[i]];
+ }
+ }
+ return true;
+ }
+
+ function _verifyLockProof(
+ uint256[2] memory utxos,
+ Commonlib.Proof calldata proof
+ ) internal view returns (bool) {
+ return _lockVerifier.verifyProof(proof.pA, proof.pB, proof.pC, utxos);
+ }
+
+ function _verifyBatchLockProof(
+ uint256[10] memory utxos,
+ Commonlib.Proof calldata proof
+ ) internal view returns (bool) {
+ return
+ _batchLockVerifier.verifyProof(proof.pA, proof.pB, proof.pC, utxos);
+ }
+}
diff --git a/solidity/contracts/lib/zeto_nullifier.sol b/solidity/contracts/lib/zeto_nullifier.sol
index 519d4503..225e5008 100644
--- a/solidity/contracts/lib/zeto_nullifier.sol
+++ b/solidity/contracts/lib/zeto_nullifier.sol
@@ -16,19 +16,15 @@
pragma solidity ^0.8.20;
import {IZetoBase} from "./interfaces/izeto_base.sol";
-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 {SmtLib} from "@iden3/contracts/lib/SmtLib.sol";
import {PoseidonUnit3L} from "@iden3/contracts/lib/Poseidon.sol";
-uint256 constant MAX_SMT_DEPTH = 64;
-
/// @title A sample base implementation of a Zeto based token contract with nullifiers
/// @author Kaleido, Inc.
/// @dev Implements common functionalities of Zeto based tokens using nullifiers
abstract contract ZetoNullifier is IZetoBase, ZetoCommon {
+ uint256 public constant MAX_SMT_DEPTH = 64;
SmtLib.Data internal _commitmentsTree;
using SmtLib for SmtLib.Data;
mapping(uint256 => bool) private _nullifiers;
diff --git a/solidity/contracts/zeto_anon.sol b/solidity/contracts/zeto_anon.sol
index 59bd4b86..8b9d9237 100644
--- a/solidity/contracts/zeto_anon.sol
+++ b/solidity/contracts/zeto_anon.sol
@@ -16,21 +16,22 @@
pragma solidity ^0.8.20;
import {IZeto} from "./lib/interfaces/izeto.sol";
+import {MAX_BATCH} from "./lib/interfaces/izeto_common.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol";
import {Groth16Verifier_CheckInputsOutputsValue} from "./lib/verifier_check_inputs_outputs_value.sol";
import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./lib/verifier_check_inputs_outputs_value_batch.sol";
+import {Groth16Verifier_CheckUtxosOwner} from "./lib/verifier_check_utxos_owner.sol";
+import {Groth16Verifier_CheckUtxosOwnerBatch} from "./lib/verifier_check_utxos_owner_batch.sol";
import {Groth16Verifier_Anon} from "./lib/verifier_anon.sol";
import {Groth16Verifier_AnonBatch} from "./lib/verifier_anon_batch.sol";
-import {Registry} from "./lib/registry.sol";
import {Commonlib} from "./lib/common.sol";
import {ZetoBase} from "./lib/zeto_base.sol";
-import {ZetoFungible} from "./lib/zeto_fungible.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {ZetoFungibleWithdraw} from "./lib/zeto_fungible_withdraw.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
-uint256 constant MAX_BATCH = 10;
uint256 constant INPUT_SIZE = 4;
uint256 constant BATCH_INPUT_SIZE = 20;
@@ -41,26 +42,35 @@ uint256 constant BATCH_INPUT_SIZE = 20;
/// - 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 IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable {
- Groth16Verifier_Anon internal verifier;
- Groth16Verifier_AnonBatch internal batchVerifier;
+contract Zeto_Anon is
+ IZeto,
+ ZetoBase,
+ ZetoFungibleWithdraw,
+ ZetoLock,
+ UUPSUpgradeable
+{
+ Groth16Verifier_Anon internal _verifier;
+ Groth16Verifier_AnonBatch internal _batchVerifier;
function initialize(
address initialOwner,
- Groth16Verifier_Anon _verifier,
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier,
- Groth16Verifier_AnonBatch _batchVerifier,
- Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier
+ Groth16Verifier_Anon verifier,
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckInputsOutputsValue withdrawVerifier,
+ Groth16Verifier_AnonBatch batchVerifier,
+ Groth16Verifier_CheckInputsOutputsValueBatch batchWithdrawVerifier,
+ ILockVerifier lockVerifier,
+ IBatchLockVerifier batchLockVerifier
) public initializer {
__ZetoBase_init(initialOwner);
__ZetoFungibleWithdraw_init(
- _depositVerifier,
- _withdrawVerifier,
- _batchWithdrawVerifier
+ depositVerifier,
+ withdrawVerifier,
+ batchWithdrawVerifier
);
- verifier = _verifier;
- batchVerifier = _batchVerifier;
+ __ZetoLock_init(lockVerifier, batchLockVerifier);
+ _verifier = verifier;
+ _batchVerifier = batchVerifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -104,10 +114,8 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable {
// Check and pad inputs and outputs based on the max size
(inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH);
- require(
- validateTransactionProposal(inputs, outputs, proof),
- "Invalid transaction proposal"
- );
+ validateTransactionProposal(inputs, outputs);
+ validateLockedStates(inputs);
// Check the proof
if (inputs.length > 2 || outputs.length > 2) {
@@ -124,7 +132,7 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable {
// Check the proof using batchVerifier
require(
- batchVerifier.verifyProof(
+ _batchVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -145,7 +153,7 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable {
}
// Check the proof
require(
- verifier.verifyProof(
+ _verifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -182,7 +190,8 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable {
uint256[] memory outputs = new uint256[](inputs.length);
outputs[0] = output;
(inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH);
- validateTransactionProposal(inputs, outputs, proof);
+ validateTransactionProposal(inputs, outputs);
+ validateLockedStates(inputs);
_withdraw(amount, inputs, output, proof);
processInputsAndOutputs(inputs, outputs);
emit UTXOWithdraw(amount, inputs, output, msg.sender, data);
diff --git a/solidity/contracts/zeto_anon_enc.sol b/solidity/contracts/zeto_anon_enc.sol
index df91670d..5e351e2f 100644
--- a/solidity/contracts/zeto_anon_enc.sol
+++ b/solidity/contracts/zeto_anon_enc.sol
@@ -16,20 +16,22 @@
pragma solidity ^0.8.20;
import {IZetoEncrypted} from "./lib/interfaces/izeto_encrypted.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
+import {MAX_BATCH} from "./lib/interfaces/izeto_common.sol";
import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol";
import {Groth16Verifier_CheckInputsOutputsValue} from "./lib/verifier_check_inputs_outputs_value.sol";
import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./lib/verifier_check_inputs_outputs_value_batch.sol";
+import {Groth16Verifier_CheckUtxosOwner} from "./lib/verifier_check_utxos_owner.sol";
+import {Groth16Verifier_CheckUtxosOwnerBatch} from "./lib/verifier_check_utxos_owner_batch.sol";
+
import {Groth16Verifier_AnonEnc} from "./lib/verifier_anon_enc.sol";
import {Groth16Verifier_AnonEncBatch} from "./lib/verifier_anon_enc_batch.sol";
import {ZetoFungibleWithdraw} from "./lib/zeto_fungible_withdraw.sol";
import {ZetoBase} from "./lib/zeto_base.sol";
-import {ZetoFungible} from "./lib/zeto_fungible.sol";
-import {Registry} from "./lib/registry.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {Commonlib} from "./lib/common.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
-uint256 constant MAX_BATCH = 10;
uint256 constant INPUT_SIZE = 15;
uint256 constant BATCH_INPUT_SIZE = 63;
@@ -46,28 +48,31 @@ contract Zeto_AnonEnc is
IZetoEncrypted,
ZetoBase,
ZetoFungibleWithdraw,
+ ZetoLock,
UUPSUpgradeable
{
- Groth16Verifier_AnonEnc internal verifier;
- Groth16Verifier_AnonEncBatch internal batchVerifier;
+ Groth16Verifier_AnonEnc internal _verifier;
+ Groth16Verifier_AnonEncBatch internal _batchVerifier;
function initialize(
address initialOwner,
- Groth16Verifier_AnonEnc _verifier,
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier,
- Groth16Verifier_AnonEncBatch _batchVerifier,
- Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier
+ Groth16Verifier_AnonEnc verifier,
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckInputsOutputsValue withdrawVerifier,
+ Groth16Verifier_AnonEncBatch batchVerifier,
+ Groth16Verifier_CheckInputsOutputsValueBatch batchWithdrawVerifier,
+ ILockVerifier lockVerifier,
+ IBatchLockVerifier batchLockVerifier
) public initializer {
__ZetoBase_init(initialOwner);
__ZetoFungibleWithdraw_init(
- _depositVerifier,
- _withdrawVerifier,
- _batchWithdrawVerifier
+ depositVerifier,
+ withdrawVerifier,
+ batchWithdrawVerifier
);
- verifier = _verifier;
- batchVerifier = _batchVerifier;
- batchVerifier = _batchVerifier;
+ __ZetoLock_init(lockVerifier, batchLockVerifier);
+ _verifier = verifier;
+ _batchVerifier = batchVerifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -128,10 +133,8 @@ contract Zeto_AnonEnc is
) public returns (bool) {
// Check and pad commitments
(inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH);
- require(
- validateTransactionProposal(inputs, outputs, proof),
- "Invalid transaction proposal"
- );
+ validateTransactionProposal(inputs, outputs);
+ validateLockedStates(inputs);
// Check the proof
if (inputs.length > 2 || outputs.length > 2) {
@@ -151,7 +154,7 @@ contract Zeto_AnonEnc is
// Check the proof using batchVerifier
require(
- batchVerifier.verifyProof(
+ _batchVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -175,7 +178,7 @@ contract Zeto_AnonEnc is
}
// Check the proof
require(
- verifier.verifyProof(
+ _verifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -227,7 +230,8 @@ contract Zeto_AnonEnc is
outputs[0] = output;
// Check and pad commitments
(inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH);
- validateTransactionProposal(inputs, outputs, proof);
+ validateTransactionProposal(inputs, outputs);
+ validateLockedStates(inputs);
_withdraw(amount, inputs, output, proof);
processInputsAndOutputs(inputs, outputs);
emit UTXOWithdraw(amount, inputs, output, msg.sender, data);
diff --git a/solidity/contracts/zeto_anon_enc_nullifier.sol b/solidity/contracts/zeto_anon_enc_nullifier.sol
index 7a5e010a..e3cfb5cf 100644
--- a/solidity/contracts/zeto_anon_enc_nullifier.sol
+++ b/solidity/contracts/zeto_anon_enc_nullifier.sol
@@ -16,6 +16,8 @@
pragma solidity ^0.8.20;
import {IZetoEncrypted} from "./lib/interfaces/izeto_encrypted.sol";
+import {MAX_BATCH} from "./lib/interfaces/izeto_common.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol";
import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol";
import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol";
@@ -23,11 +25,10 @@ import {Groth16Verifier_AnonEncNullifier} from "./lib/verifier_anon_enc_nullifie
import {Groth16Verifier_AnonEncNullifierBatch} from "./lib/verifier_anon_enc_nullifier_batch.sol";
import {ZetoNullifier} from "./lib/zeto_nullifier.sol";
import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_nullifier.sol";
-import {Registry} from "./lib/registry.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {Commonlib} from "./lib/common.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
-uint256 constant MAX_BATCH = 10;
uint256 constant INPUT_SIZE = 18;
uint256 constant BATCH_INPUT_SIZE = 74;
@@ -44,27 +45,31 @@ contract Zeto_AnonEncNullifier is
IZetoEncrypted,
ZetoNullifier,
ZetoFungibleWithdrawWithNullifiers,
+ ZetoLock,
UUPSUpgradeable
{
- Groth16Verifier_AnonEncNullifier internal verifier;
- Groth16Verifier_AnonEncNullifierBatch internal batchVerifier;
+ Groth16Verifier_AnonEncNullifier internal _verifier;
+ Groth16Verifier_AnonEncNullifierBatch internal _batchVerifier;
function initialize(
address initialOwner,
- Groth16Verifier_AnonEncNullifier _verifier,
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckNullifierValue _withdrawVerifier,
- Groth16Verifier_AnonEncNullifierBatch _batchVerifier,
- Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier
+ Groth16Verifier_AnonEncNullifier verifier,
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckNullifierValue withdrawVerifier,
+ Groth16Verifier_AnonEncNullifierBatch batchVerifier,
+ Groth16Verifier_CheckNullifierValueBatch batchWithdrawVerifier,
+ ILockVerifier lockVerifier,
+ IBatchLockVerifier batchLockVerifier
) public initializer {
__ZetoNullifier_init(initialOwner);
__ZetoFungibleWithdrawWithNullifiers_init(
- _depositVerifier,
- _withdrawVerifier,
- _batchWithdrawVerifier
+ depositVerifier,
+ withdrawVerifier,
+ batchWithdrawVerifier
);
- verifier = _verifier;
- batchVerifier = _batchVerifier;
+ __ZetoLock_init(lockVerifier, batchLockVerifier);
+ _verifier = verifier;
+ _batchVerifier = batchVerifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -141,10 +146,9 @@ contract Zeto_AnonEncNullifier is
outputs,
MAX_BATCH
);
- require(
- validateTransactionProposal(nullifiers, outputs, root),
- "Invalid transaction proposal"
- );
+ validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
+
// Check the proof
if (nullifiers.length > 2 || outputs.length > 2) {
uint256[] memory publicInputs = constructPublicInputs(
@@ -164,7 +168,7 @@ contract Zeto_AnonEncNullifier is
// Check the proof using batchVerifier
require(
- batchVerifier.verifyProof(
+ _batchVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -189,7 +193,7 @@ contract Zeto_AnonEncNullifier is
}
// Check the proof
require(
- verifier.verifyProof(
+ _verifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -248,6 +252,7 @@ contract Zeto_AnonEncNullifier is
MAX_BATCH
);
validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
_withdrawWithNullifiers(amount, nullifiers, output, root, proof);
processInputsAndOutputs(nullifiers, outputs);
emit UTXOWithdraw(amount, nullifiers, output, msg.sender, data);
diff --git a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol
index 54e86b90..f52ecef0 100644
--- a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol
+++ b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol
@@ -16,6 +16,8 @@
pragma solidity ^0.8.20;
import {IZetoEncrypted} from "./lib/interfaces/izeto_encrypted.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
+import {MAX_BATCH} from "./lib/interfaces/izeto_common.sol";
import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol";
import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol";
import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol";
@@ -23,11 +25,11 @@ import {Groth16Verifier_AnonEncNullifierKyc} from "./lib/verifier_anon_enc_nulli
import {Groth16Verifier_AnonEncNullifierKycBatch} from "./lib/verifier_anon_enc_nullifier_kyc_batch.sol";
import {ZetoNullifier} from "./lib/zeto_nullifier.sol";
import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_nullifier.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {Registry} from "./lib/registry.sol";
import {Commonlib} from "./lib/common.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
-uint256 constant MAX_BATCH = 10;
uint256 constant INPUT_SIZE = 19;
uint256 constant BATCH_INPUT_SIZE = 75;
@@ -44,29 +46,33 @@ contract Zeto_AnonEncNullifierKyc is
IZetoEncrypted,
ZetoNullifier,
ZetoFungibleWithdrawWithNullifiers,
+ ZetoLock,
Registry,
UUPSUpgradeable
{
- Groth16Verifier_AnonEncNullifierKyc internal verifier;
- Groth16Verifier_AnonEncNullifierKycBatch internal batchVerifier;
+ Groth16Verifier_AnonEncNullifierKyc internal _verifier;
+ Groth16Verifier_AnonEncNullifierKycBatch internal _batchVerifier;
function initialize(
address initialOwner,
- Groth16Verifier_AnonEncNullifierKyc _verifier,
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckNullifierValue _withdrawVerifier,
- Groth16Verifier_AnonEncNullifierKycBatch _batchVerifier,
- Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier
+ Groth16Verifier_AnonEncNullifierKyc verifier,
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckNullifierValue withdrawVerifier,
+ Groth16Verifier_AnonEncNullifierKycBatch batchVerifier,
+ Groth16Verifier_CheckNullifierValueBatch batchWithdrawVerifier,
+ ILockVerifier lockVerifier,
+ IBatchLockVerifier batchLockVerifier
) public initializer {
__Registry_init();
__ZetoNullifier_init(initialOwner);
__ZetoFungibleWithdrawWithNullifiers_init(
- _depositVerifier,
- _withdrawVerifier,
- _batchWithdrawVerifier
+ depositVerifier,
+ withdrawVerifier,
+ batchWithdrawVerifier
);
- verifier = _verifier;
- batchVerifier = _batchVerifier;
+ __ZetoLock_init(lockVerifier, batchLockVerifier);
+ _verifier = verifier;
+ _batchVerifier = batchVerifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -150,10 +156,8 @@ contract Zeto_AnonEncNullifierKyc is
outputs,
MAX_BATCH
);
- require(
- validateTransactionProposal(nullifiers, outputs, root),
- "Invalid transaction proposal"
- );
+ validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
// Check the proof
if (nullifiers.length > 2 || outputs.length > 2) {
@@ -174,7 +178,7 @@ contract Zeto_AnonEncNullifierKyc is
// Check the proof using batchVerifier
require(
- batchVerifier.verifyProof(
+ _batchVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -199,7 +203,7 @@ contract Zeto_AnonEncNullifierKyc is
}
// Check the proof
require(
- verifier.verifyProof(
+ _verifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -263,6 +267,8 @@ contract Zeto_AnonEncNullifierKyc is
MAX_BATCH
);
validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
+
_withdrawWithNullifiers(amount, nullifiers, output, root, proof);
processInputsAndOutputs(nullifiers, outputs);
emit UTXOWithdraw(amount, nullifiers, output, msg.sender, data);
diff --git a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol
index d3da9dbf..dc9e82d8 100644
--- a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol
+++ b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol
@@ -21,12 +21,13 @@ import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifie
import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol";
import {Groth16Verifier_AnonEncNullifierNonRepudiation} from "./lib/verifier_anon_enc_nullifier_non_repudiation.sol";
import {Groth16Verifier_AnonEncNullifierNonRepudiationBatch} from "./lib/verifier_anon_enc_nullifier_non_repudiation_batch.sol";
+import {MAX_BATCH} from "./lib/interfaces/izeto_common.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
import {ZetoNullifier} from "./lib/zeto_nullifier.sol";
import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_nullifier.sol";
-import {Registry} from "./lib/registry.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {Commonlib} from "./lib/common.sol";
-uint256 constant MAX_BATCH = 10;
uint256 constant INPUT_SIZE = 36;
uint256 constant BATCH_INPUT_SIZE = 140;
@@ -42,6 +43,7 @@ uint256 constant BATCH_INPUT_SIZE = 140;
contract Zeto_AnonEncNullifierNonRepudiation is
ZetoNullifier,
ZetoFungibleWithdrawWithNullifiers,
+ ZetoLock,
UUPSUpgradeable
{
event UTXOTransferNonRepudiation(
@@ -55,28 +57,31 @@ contract Zeto_AnonEncNullifierNonRepudiation is
bytes data
);
- Groth16Verifier_AnonEncNullifierNonRepudiation internal verifier;
- Groth16Verifier_AnonEncNullifierNonRepudiationBatch internal batchVerifier;
+ Groth16Verifier_AnonEncNullifierNonRepudiation internal _verifier;
+ Groth16Verifier_AnonEncNullifierNonRepudiationBatch internal _batchVerifier;
// the arbiter public key that must be used to
// encrypt the secrets of every transaction
uint256[2] private arbiter;
function initialize(
address initialOwner,
- Groth16Verifier_AnonEncNullifierNonRepudiation _verifier,
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckNullifierValue _withdrawVerifier,
- Groth16Verifier_AnonEncNullifierNonRepudiationBatch _batchVerifier,
- Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier
+ Groth16Verifier_AnonEncNullifierNonRepudiation verifier,
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckNullifierValue withdrawVerifier,
+ Groth16Verifier_AnonEncNullifierNonRepudiationBatch batchVerifier,
+ Groth16Verifier_CheckNullifierValueBatch batchWithdrawVerifier,
+ ILockVerifier lockVerifier,
+ IBatchLockVerifier batchLockVerifier
) public initializer {
__ZetoNullifier_init(initialOwner);
__ZetoFungibleWithdrawWithNullifiers_init(
- _depositVerifier,
- _withdrawVerifier,
- _batchWithdrawVerifier
+ depositVerifier,
+ withdrawVerifier,
+ batchWithdrawVerifier
);
- verifier = _verifier;
- batchVerifier = _batchVerifier;
+ __ZetoLock_init(lockVerifier, batchLockVerifier);
+ _verifier = verifier;
+ _batchVerifier = batchVerifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -174,10 +179,8 @@ contract Zeto_AnonEncNullifierNonRepudiation is
outputs,
MAX_BATCH
);
- require(
- validateTransactionProposal(nullifiers, outputs, root),
- "Invalid transaction proposal"
- );
+ validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
// Check the proof
if (nullifiers.length > 2 || outputs.length > 2) {
@@ -203,7 +206,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is
// Check the proof using batchVerifier
require(
- batchVerifier.verifyProof(
+ _batchVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -233,7 +236,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is
}
// Check the proof
require(
- verifier.verifyProof(
+ _verifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -299,6 +302,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is
MAX_BATCH
);
validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
_withdrawWithNullifiers(amount, nullifiers, output, root, proof);
processInputsAndOutputs(nullifiers, outputs);
emit UTXOWithdraw(amount, nullifiers, output, msg.sender, data);
diff --git a/solidity/contracts/zeto_anon_nullifier.sol b/solidity/contracts/zeto_anon_nullifier.sol
index ea6809c7..46b38618 100644
--- a/solidity/contracts/zeto_anon_nullifier.sol
+++ b/solidity/contracts/zeto_anon_nullifier.sol
@@ -16,6 +16,8 @@
pragma solidity ^0.8.20;
import {IZeto} from "./lib/interfaces/izeto.sol";
+import {MAX_BATCH} from "./lib/interfaces/izeto_common.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol";
import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol";
import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol";
@@ -23,15 +25,10 @@ import {Groth16Verifier_AnonNullifier} from "./lib/verifier_anon_nullifier.sol";
import {Groth16Verifier_AnonNullifierBatch} from "./lib/verifier_anon_nullifier_batch.sol";
import {ZetoNullifier} from "./lib/zeto_nullifier.sol";
import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_nullifier.sol";
-import {Registry} from "./lib/registry.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {Commonlib} from "./lib/common.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
-import {SmtLib} from "@iden3/contracts/lib/SmtLib.sol";
-import {PoseidonUnit3L} from "@iden3/contracts/lib/Poseidon.sol";
-uint256 constant MAX_SMT_DEPTH = 64;
-uint256 constant MAX_BATCH = 10;
uint256 constant INPUT_SIZE = 7;
uint256 constant BATCH_INPUT_SIZE = 31;
@@ -47,27 +44,31 @@ contract Zeto_AnonNullifier is
IZeto,
ZetoNullifier,
ZetoFungibleWithdrawWithNullifiers,
+ ZetoLock,
UUPSUpgradeable
{
- Groth16Verifier_AnonNullifier internal verifier;
- Groth16Verifier_AnonNullifierBatch internal batchVerifier;
+ Groth16Verifier_AnonNullifier internal _verifier;
+ Groth16Verifier_AnonNullifierBatch internal _batchVerifier;
function initialize(
address initialOwner,
- Groth16Verifier_AnonNullifier _verifier,
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckNullifierValue _withdrawVerifier,
- Groth16Verifier_AnonNullifierBatch _batchVerifier,
- Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier
+ Groth16Verifier_AnonNullifier verifier,
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckNullifierValue withdrawVerifier,
+ Groth16Verifier_AnonNullifierBatch batchVerifier,
+ Groth16Verifier_CheckNullifierValueBatch batchWithdrawVerifier,
+ ILockVerifier lockVerifier,
+ IBatchLockVerifier batchLockVerifier
) public initializer {
__ZetoNullifier_init(initialOwner);
__ZetoFungibleWithdrawWithNullifiers_init(
- _depositVerifier,
- _withdrawVerifier,
- _batchWithdrawVerifier
+ depositVerifier,
+ withdrawVerifier,
+ batchWithdrawVerifier
);
- verifier = _verifier;
- batchVerifier = _batchVerifier;
+ __ZetoLock_init(lockVerifier, batchLockVerifier);
+ _verifier = verifier;
+ _batchVerifier = batchVerifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -125,10 +126,8 @@ contract Zeto_AnonNullifier is
MAX_BATCH
);
- require(
- validateTransactionProposal(nullifiers, outputs, root),
- "Invalid transaction proposal"
- );
+ validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
// Check the proof
if (nullifiers.length > 2 || outputs.length > 2) {
@@ -146,7 +145,7 @@ contract Zeto_AnonNullifier is
// Check the proof using batchVerifier
require(
- batchVerifier.verifyProof(
+ _batchVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -168,7 +167,7 @@ contract Zeto_AnonNullifier is
}
// Check the proof
require(
- verifier.verifyProof(
+ _verifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -217,6 +216,7 @@ contract Zeto_AnonNullifier is
MAX_BATCH
);
validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
_withdrawWithNullifiers(amount, nullifiers, output, root, proof);
processInputsAndOutputs(nullifiers, outputs);
emit UTXOWithdraw(amount, nullifiers, output, msg.sender, data);
diff --git a/solidity/contracts/zeto_anon_nullifier_kyc.sol b/solidity/contracts/zeto_anon_nullifier_kyc.sol
index 8ecb9609..82198438 100644
--- a/solidity/contracts/zeto_anon_nullifier_kyc.sol
+++ b/solidity/contracts/zeto_anon_nullifier_kyc.sol
@@ -16,6 +16,8 @@
pragma solidity ^0.8.20;
import {IZeto} from "./lib/interfaces/izeto.sol";
+import {MAX_BATCH} from "./lib/interfaces/izeto_common.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol";
import {Groth16Verifier_CheckNullifierValue} from "./lib/verifier_check_nullifier_value.sol";
import {Groth16Verifier_CheckNullifierValueBatch} from "./lib/verifier_check_nullifier_value_batch.sol";
@@ -24,15 +26,11 @@ import {Groth16Verifier_AnonNullifierKyc} from "./lib/verifier_anon_nullifier_ky
import {Groth16Verifier_AnonNullifierKycBatch} from "./lib/verifier_anon_nullifier_kyc_batch.sol";
import {ZetoNullifier} from "./lib/zeto_nullifier.sol";
import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_nullifier.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {Registry} from "./lib/registry.sol";
import {Commonlib} from "./lib/common.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
-import {SmtLib} from "@iden3/contracts/lib/SmtLib.sol";
-import {PoseidonUnit3L} from "@iden3/contracts/lib/Poseidon.sol";
-uint256 constant MAX_SMT_DEPTH = 64;
-uint256 constant MAX_BATCH = 10;
uint256 constant INPUT_SIZE = 8;
uint256 constant BATCH_INPUT_SIZE = 32;
@@ -48,29 +46,33 @@ contract Zeto_AnonNullifierKyc is
IZeto,
ZetoNullifier,
ZetoFungibleWithdrawWithNullifiers,
+ ZetoLock,
Registry,
UUPSUpgradeable
{
- Groth16Verifier_AnonNullifierKyc internal verifier;
- Groth16Verifier_AnonNullifierKycBatch internal batchVerifier;
+ Groth16Verifier_AnonNullifierKyc internal _verifier;
+ Groth16Verifier_AnonNullifierKycBatch internal _batchVerifier;
function initialize(
address initialOwner,
- Groth16Verifier_AnonNullifierKyc _verifier,
- Groth16Verifier_CheckHashesValue _depositVerifier,
- Groth16Verifier_CheckNullifierValue _withdrawVerifier,
- Groth16Verifier_AnonNullifierKycBatch _batchVerifier,
- Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier
+ Groth16Verifier_AnonNullifierKyc verifier,
+ Groth16Verifier_CheckHashesValue depositVerifier,
+ Groth16Verifier_CheckNullifierValue withdrawVerifier,
+ Groth16Verifier_AnonNullifierKycBatch batchVerifier,
+ Groth16Verifier_CheckNullifierValueBatch batchWithdrawVerifier,
+ ILockVerifier lockVerifier,
+ IBatchLockVerifier batchLockVerifier
) public initializer {
__Registry_init();
__ZetoNullifier_init(initialOwner);
__ZetoFungibleWithdrawWithNullifiers_init(
- _depositVerifier,
- _withdrawVerifier,
- _batchWithdrawVerifier
+ depositVerifier,
+ withdrawVerifier,
+ batchWithdrawVerifier
);
- verifier = _verifier;
- batchVerifier = _batchVerifier;
+ __ZetoLock_init(lockVerifier, batchLockVerifier);
+ _verifier = verifier;
+ _batchVerifier = batchVerifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -135,10 +137,8 @@ contract Zeto_AnonNullifierKyc is
MAX_BATCH
);
- require(
- validateTransactionProposal(nullifiers, outputs, root),
- "Invalid transaction proposal"
- );
+ validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
// Check the proof
if (nullifiers.length > 2 || outputs.length > 2) {
@@ -156,7 +156,7 @@ contract Zeto_AnonNullifierKyc is
// Check the proof using batchVerifier
require(
- batchVerifier.verifyProof(
+ _batchVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -178,7 +178,7 @@ contract Zeto_AnonNullifierKyc is
}
// Check the proof
require(
- verifier.verifyProof(
+ _verifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
@@ -227,6 +227,7 @@ contract Zeto_AnonNullifierKyc is
MAX_BATCH
);
validateTransactionProposal(nullifiers, outputs, root);
+ validateLockedStates(nullifiers);
_withdrawWithNullifiers(amount, nullifiers, output, root, proof);
processInputsAndOutputs(nullifiers, outputs);
emit UTXOWithdraw(amount, nullifiers, output, msg.sender, data);
diff --git a/solidity/contracts/zeto_nf_anon.sol b/solidity/contracts/zeto_nf_anon.sol
index 864f2399..c1139f40 100644
--- a/solidity/contracts/zeto_nf_anon.sol
+++ b/solidity/contracts/zeto_nf_anon.sol
@@ -16,11 +16,13 @@
pragma solidity ^0.8.20;
import {IZeto} from "./lib/interfaces/izeto.sol";
+import {Groth16Verifier_CheckUtxosNfOwner} from "./lib/verifier_check_utxos_nf_owner.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
+
import {Groth16Verifier_NfAnon} from "./lib/verifier_nf_anon.sol";
import {ZetoBase} from "./lib/zeto_base.sol";
-import {Registry} from "./lib/registry.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {Commonlib} from "./lib/common.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
/// @title A sample implementation of a Zeto based non-fungible token with anonymity and no encryption
@@ -29,15 +31,17 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U
/// - The sender owns the private key whose public key is part of the pre-image of the input UTXOs commitments
/// (aka the sender is authorized to spend the input UTXOs)
/// - The input UTXOs and output UTXOs are valid in terms of obeying mass conservation rules
-contract Zeto_NfAnon is IZeto, ZetoBase, UUPSUpgradeable {
- Groth16Verifier_NfAnon internal verifier;
+contract Zeto_NfAnon is IZeto, ZetoBase, ZetoLock, UUPSUpgradeable {
+ Groth16Verifier_NfAnon internal _verifier;
function initialize(
address initialOwner,
- Groth16Verifier_NfAnon _verifier
+ Groth16Verifier_NfAnon verifier,
+ ILockVerifier lockVerifier
) public initializer {
__ZetoBase_init(initialOwner);
- verifier = _verifier;
+ __ZetoLock_init(lockVerifier, IBatchLockVerifier(address(0)));
+ _verifier = verifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -63,10 +67,15 @@ contract Zeto_NfAnon is IZeto, ZetoBase, UUPSUpgradeable {
uint256[] memory outputs = new uint256[](1);
outputs[0] = output;
require(
- validateTransactionProposal(inputs, outputs, proof),
+ validateTransactionProposal(inputs, outputs),
"Invalid transaction proposal"
);
+ require(
+ validateLockedStates(inputs),
+ "At least one UTXO in the inputs are locked"
+ );
+
// construct the public inputs
uint256[2] memory publicInputs;
publicInputs[0] = input;
@@ -74,7 +83,7 @@ contract Zeto_NfAnon is IZeto, ZetoBase, UUPSUpgradeable {
// Check the proof
require(
- verifier.verifyProof(proof.pA, proof.pB, proof.pC, publicInputs),
+ _verifier.verifyProof(proof.pA, proof.pB, proof.pC, publicInputs),
"Invalid proof"
);
diff --git a/solidity/contracts/zeto_nf_anon_nullifier.sol b/solidity/contracts/zeto_nf_anon_nullifier.sol
index b353e297..44dfb75c 100644
--- a/solidity/contracts/zeto_nf_anon_nullifier.sol
+++ b/solidity/contracts/zeto_nf_anon_nullifier.sol
@@ -16,16 +16,12 @@
pragma solidity ^0.8.20;
import {IZeto} from "./lib/interfaces/izeto.sol";
+import {ILockVerifier, IBatchLockVerifier} from "./lib/interfaces/izeto_lockable.sol";
import {Groth16Verifier_NfAnonNullifier} from "./lib/verifier_nf_anon_nullifier.sol";
import {ZetoNullifier} from "./lib/zeto_nullifier.sol";
-import {Registry} from "./lib/registry.sol";
+import {ZetoLock} from "./lib/zeto_lock.sol";
import {Commonlib} from "./lib/common.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
-import {SmtLib} from "@iden3/contracts/lib/SmtLib.sol";
-import {PoseidonUnit3L} from "@iden3/contracts/lib/Poseidon.sol";
-
-uint256 constant MAX_SMT_DEPTH = 64;
/// @title A sample implementation of a Zeto based non-fungible token with anonymity and history masking
/// @author Kaleido, Inc.
@@ -35,15 +31,22 @@ 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_NfAnonNullifier is IZeto, ZetoNullifier, UUPSUpgradeable {
- Groth16Verifier_NfAnonNullifier verifier;
+contract Zeto_NfAnonNullifier is
+ IZeto,
+ ZetoNullifier,
+ ZetoLock,
+ UUPSUpgradeable
+{
+ Groth16Verifier_NfAnonNullifier _verifier;
function initialize(
address initialOwner,
- Groth16Verifier_NfAnonNullifier _verifier
+ Groth16Verifier_NfAnonNullifier verifier,
+ ILockVerifier lockVerifier
) public initializer {
__ZetoNullifier_init(initialOwner);
- verifier = _verifier;
+ __ZetoLock_init(lockVerifier, IBatchLockVerifier(address(0)));
+ _verifier = verifier;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -75,6 +78,11 @@ contract Zeto_NfAnonNullifier is IZeto, ZetoNullifier, UUPSUpgradeable {
"Invalid transaction proposal"
);
+ require(
+ validateLockedStates(nullifiers),
+ "The input nullifier is locked"
+ );
+
// construct the public inputs
uint256[3] memory publicInputs;
publicInputs[0] = nullifier;
@@ -83,7 +91,7 @@ contract Zeto_NfAnonNullifier is IZeto, ZetoNullifier, UUPSUpgradeable {
// Check the proof
require(
- verifier.verifyProof(proof.pA, proof.pB, proof.pC, publicInputs),
+ _verifier.verifyProof(proof.pA, proof.pB, proof.pC, publicInputs),
"Invalid proof"
);
diff --git a/solidity/contracts/zkDvP.sol b/solidity/contracts/zkDvP.sol
index 6578e83b..83460ad3 100644
--- a/solidity/contracts/zkDvP.sol
+++ b/solidity/contracts/zkDvP.sol
@@ -196,7 +196,8 @@ contract zkDvP {
function completeTrade(
uint256 tradeId,
- Commonlib.Proof calldata proof
+ Commonlib.Proof calldata proof,
+ Commonlib.Proof calldata lockProof
) public {
Trade memory trade = trades[tradeId];
require(
@@ -204,12 +205,24 @@ contract zkDvP {
"Trade must be in ACCEPTED state to complete"
);
bytes32 proofHash = getProofHash(proof);
+ uint256[] memory lockedStates;
if (trade.paymentProofHash == proofHash) {
trade.paymentProof = proof;
- paymentToken.lockProof(proof, address(this));
+ lockedStates = new uint256[](trade.paymentInputs.length);
+ for (uint256 i = 0; i < trade.paymentInputs.length; i++) {
+ lockedStates[i] = trade.paymentInputs[i];
+ }
+ paymentToken.lockStates(
+ lockedStates,
+ lockProof,
+ address(this),
+ "0x"
+ );
} else if (trade.assetProofHash == proofHash) {
trade.assetProof = proof;
- assetToken.lockProof(proof, address(this));
+ lockedStates = new uint256[](1);
+ lockedStates[0] = trade.assetInput;
+ assetToken.lockStates(lockedStates, lockProof, address(this), "0x");
} else {
revert("Invalid proof");
}
diff --git a/solidity/ignition/modules/lib/deps.ts b/solidity/ignition/modules/lib/deps.ts
index 71df1eba..e1b627f1 100644
--- a/solidity/ignition/modules/lib/deps.ts
+++ b/solidity/ignition/modules/lib/deps.ts
@@ -71,6 +71,52 @@ export const BatchWithdrawVerifierModule = buildModule(
},
);
+export const LockVerifierModule = buildModule(
+ "Groth16Verifier_CheckUtxosOwner",
+ (m) => {
+ const verifier = m.contract("Groth16Verifier_CheckUtxosOwner", []);
+ return { verifier };
+ },
+);
+export const BatchLockVerifierModule = buildModule(
+ "Groth16Verifier_CheckUtxosOwnerBatch",
+ (m) => {
+ const verifier = m.contract("Groth16Verifier_CheckUtxosOwnerBatch", []);
+ return { verifier };
+ },
+);
+
+export const NfLockVerifierModule = buildModule(
+ "Groth16Verifier_CheckUtxosNfOwner",
+ (m) => {
+ const verifier = m.contract("Groth16Verifier_CheckUtxosNfOwner", []);
+ return { verifier };
+ },
+);
+
+export const LockNullifiersVerifierModule = buildModule(
+ "Groth16Verifier_CheckNullifiersOwner",
+ (m) => {
+ const verifier = m.contract("Groth16Verifier_CheckNullifiersOwner", []);
+ return { verifier };
+ },
+);
+export const BatchLockNullifiersVerifierModule = buildModule(
+ "Groth16Verifier_CheckNullifiersOwnerBatch",
+ (m) => {
+ const verifier = m.contract("Groth16Verifier_CheckNullifiersOwnerBatch", []);
+ return { verifier };
+ },
+);
+
+export const NfLockNullifiersVerifierModule = buildModule(
+ "Groth16Verifier_CheckNullifiersNfOwner",
+ (m) => {
+ const verifier = m.contract("Groth16Verifier_CheckNullifiersNfOwner", []);
+ return { verifier };
+ },
+);
+
function PoseidonArtifact(param: number): Artifact {
const abi = poseidonContract.generateABI(param);
const bytecode = poseidonContract.createCode(param);
diff --git a/solidity/ignition/modules/zeto_anon.ts b/solidity/ignition/modules/zeto_anon.ts
index 65f2050d..a73afa10 100644
--- a/solidity/ignition/modules/zeto_anon.ts
+++ b/solidity/ignition/modules/zeto_anon.ts
@@ -19,6 +19,8 @@ import {
DepositVerifierModule,
WithdrawVerifierModule,
BatchWithdrawVerifierModule,
+ LockVerifierModule,
+ BatchLockVerifierModule,
} from "./lib/deps";
const VerifierModule = buildModule("Groth16Verifier_Anon", (m) => {
@@ -39,11 +41,15 @@ export default buildModule("Zeto_Anon", (m) => {
const { verifier: batchWithdrawVerifier } = m.useModule(
BatchWithdrawVerifierModule,
);
+ const { verifier: lockVerifier } = m.useModule(LockVerifierModule);
+ const { verifier: batchLockVerifier } = m.useModule(BatchLockVerifierModule);
return {
depositVerifier,
withdrawVerifier,
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
};
});
diff --git a/solidity/ignition/modules/zeto_anon_enc.ts b/solidity/ignition/modules/zeto_anon_enc.ts
index 7710ab21..196b421c 100644
--- a/solidity/ignition/modules/zeto_anon_enc.ts
+++ b/solidity/ignition/modules/zeto_anon_enc.ts
@@ -19,6 +19,8 @@ import {
DepositVerifierModule,
WithdrawVerifierModule,
BatchWithdrawVerifierModule,
+ LockVerifierModule,
+ BatchLockVerifierModule,
} from "./lib/deps";
const VerifierModule = buildModule("Groth16Verifier_AnonEnc", (m) => {
@@ -39,11 +41,15 @@ export default buildModule("Zeto_AnonEnc", (m) => {
const { verifier: batchWithdrawVerifier } = m.useModule(
BatchWithdrawVerifierModule,
);
+ const { verifier: lockVerifier } = m.useModule(LockVerifierModule);
+ const { verifier: batchLockVerifier } = m.useModule(BatchLockVerifierModule);
return {
depositVerifier,
withdrawVerifier,
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
};
});
diff --git a/solidity/ignition/modules/zeto_anon_enc_nullifier.ts b/solidity/ignition/modules/zeto_anon_enc_nullifier.ts
index 8b16995e..c8671804 100644
--- a/solidity/ignition/modules/zeto_anon_enc_nullifier.ts
+++ b/solidity/ignition/modules/zeto_anon_enc_nullifier.ts
@@ -20,6 +20,8 @@ import {
DepositVerifierModule,
WithdrawNullifierVerifierModule,
BatchWithdrawNullifierVerifierModule,
+ LockNullifiersVerifierModule,
+ BatchLockNullifiersVerifierModule,
} from "./lib/deps";
const VerifierModule = buildModule("Groth16Verifier_AnonEncNullifier", (m) => {
@@ -46,6 +48,12 @@ export default buildModule("Zeto_AnonEncNullifier", (m) => {
const { verifier: batchWithdrawVerifier } = m.useModule(
BatchWithdrawNullifierVerifierModule,
);
+ const { verifier: lockVerifier } = m.useModule(
+ LockNullifiersVerifierModule,
+ );
+ const { verifier: batchLockVerifier } = m.useModule(
+ BatchLockNullifiersVerifierModule,
+ );
return {
depositVerifier,
@@ -53,6 +61,8 @@ export default buildModule("Zeto_AnonEncNullifier", (m) => {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon3,
};
diff --git a/solidity/ignition/modules/zeto_anon_enc_nullifier_kyc.ts b/solidity/ignition/modules/zeto_anon_enc_nullifier_kyc.ts
index f4228702..ed076ff7 100644
--- a/solidity/ignition/modules/zeto_anon_enc_nullifier_kyc.ts
+++ b/solidity/ignition/modules/zeto_anon_enc_nullifier_kyc.ts
@@ -20,6 +20,8 @@ import {
DepositVerifierModule,
WithdrawNullifierVerifierModule,
BatchWithdrawNullifierVerifierModule,
+ LockNullifiersVerifierModule,
+ BatchLockNullifiersVerifierModule,
} from "./lib/deps";
const VerifierModule = buildModule(
@@ -49,6 +51,12 @@ export default buildModule("Zeto_AnonEncNullifierKyc", (m) => {
const { verifier: batchWithdrawVerifier } = m.useModule(
BatchWithdrawNullifierVerifierModule,
);
+ const { verifier: lockVerifier } = m.useModule(
+ LockNullifiersVerifierModule,
+ );
+ const { verifier: batchLockVerifier } = m.useModule(
+ BatchLockNullifiersVerifierModule,
+ );
return {
depositVerifier,
@@ -56,6 +64,8 @@ export default buildModule("Zeto_AnonEncNullifierKyc", (m) => {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon2,
poseidon3,
diff --git a/solidity/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts b/solidity/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts
index fbb3e300..1de99fcc 100644
--- a/solidity/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts
+++ b/solidity/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts
@@ -20,6 +20,8 @@ import {
DepositVerifierModule,
WithdrawNullifierVerifierModule,
BatchWithdrawNullifierVerifierModule,
+ LockNullifiersVerifierModule,
+ BatchLockNullifiersVerifierModule,
} from "./lib/deps";
const VerifierModule = buildModule(
@@ -55,12 +57,20 @@ export default buildModule("Zeto_AnonEncNullifierNonRepudiation", (m) => {
const { verifier: batchWithdrawVerifier } = m.useModule(
BatchWithdrawNullifierVerifierModule,
);
+ const { verifier: lockVerifier } = m.useModule(
+ LockNullifiersVerifierModule,
+ );
+ const { verifier: batchLockVerifier } = m.useModule(
+ BatchLockNullifiersVerifierModule,
+ );
return {
depositVerifier,
withdrawVerifier,
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon3,
};
diff --git a/solidity/ignition/modules/zeto_anon_nullifier.ts b/solidity/ignition/modules/zeto_anon_nullifier.ts
index d1aca6cf..478a0275 100644
--- a/solidity/ignition/modules/zeto_anon_nullifier.ts
+++ b/solidity/ignition/modules/zeto_anon_nullifier.ts
@@ -20,6 +20,8 @@ import {
DepositVerifierModule,
WithdrawNullifierVerifierModule,
BatchWithdrawNullifierVerifierModule,
+ LockNullifiersVerifierModule,
+ BatchLockNullifiersVerifierModule,
} from "./lib/deps";
const VerifierModule = buildModule("Groth16Verifier_AnonNullifier", (m) => {
@@ -46,6 +48,12 @@ export default buildModule("Zeto_AnonNullifier", (m) => {
const { verifier: batchWithdrawVerifier } = m.useModule(
BatchWithdrawNullifierVerifierModule,
);
+ const { verifier: lockVerifier } = m.useModule(
+ LockNullifiersVerifierModule,
+ );
+ const { verifier: batchLockVerifier } = m.useModule(
+ BatchLockNullifiersVerifierModule,
+ );
return {
depositVerifier,
@@ -53,6 +61,8 @@ export default buildModule("Zeto_AnonNullifier", (m) => {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon3,
};
diff --git a/solidity/ignition/modules/zeto_anon_nullifier_kyc.ts b/solidity/ignition/modules/zeto_anon_nullifier_kyc.ts
index 44be1b28..705e9386 100644
--- a/solidity/ignition/modules/zeto_anon_nullifier_kyc.ts
+++ b/solidity/ignition/modules/zeto_anon_nullifier_kyc.ts
@@ -20,6 +20,8 @@ import {
DepositVerifierModule,
WithdrawNullifierVerifierModule,
BatchWithdrawNullifierVerifierModule,
+ LockNullifiersVerifierModule,
+ BatchLockNullifiersVerifierModule,
} from "./lib/deps";
const VerifierModule = buildModule("Groth16Verifier_AnonNullifierKyc", (m) => {
@@ -46,6 +48,12 @@ export default buildModule("Zeto_AnonNullifierKyc", (m) => {
const { verifier: batchWithdrawVerifier } = m.useModule(
BatchWithdrawNullifierVerifierModule,
);
+ const { verifier: lockVerifier } = m.useModule(
+ LockNullifiersVerifierModule,
+ );
+ const { verifier: batchLockVerifier } = m.useModule(
+ BatchLockNullifiersVerifierModule,
+ );
return {
depositVerifier,
@@ -53,6 +61,8 @@ export default buildModule("Zeto_AnonNullifierKyc", (m) => {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon2,
poseidon3,
diff --git a/solidity/ignition/modules/zeto_nf_anon.ts b/solidity/ignition/modules/zeto_nf_anon.ts
index a1f8230a..3961ec79 100644
--- a/solidity/ignition/modules/zeto_nf_anon.ts
+++ b/solidity/ignition/modules/zeto_nf_anon.ts
@@ -15,7 +15,9 @@
// limitations under the License.
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
-
+import {
+ NfLockVerifierModule,
+} from "./lib/deps";
const VerifierModule = buildModule("Groth16Verifier_NfAnon", (m) => {
const verifier = m.contract("Groth16Verifier_NfAnon", []);
return { verifier };
@@ -23,6 +25,7 @@ const VerifierModule = buildModule("Groth16Verifier_NfAnon", (m) => {
export default buildModule("Zeto_NfAnon", (m) => {
const { verifier } = m.useModule(VerifierModule);
+ const { verifier: lockVerifier } = m.useModule(NfLockVerifierModule);
- return { verifier };
+ return { verifier, lockVerifier };
});
diff --git a/solidity/ignition/modules/zeto_nf_anon_nullifier.ts b/solidity/ignition/modules/zeto_nf_anon_nullifier.ts
index 6f3cc2c0..54b734bd 100644
--- a/solidity/ignition/modules/zeto_nf_anon_nullifier.ts
+++ b/solidity/ignition/modules/zeto_nf_anon_nullifier.ts
@@ -15,7 +15,7 @@
// limitations under the License.
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
-import { SmtLibModule } from "./lib/deps";
+import { SmtLibModule, NfLockNullifiersVerifierModule } from "./lib/deps";
const VerifierModule = buildModule("Groth16Verifier_NfAnonNullifier", (m) => {
const verifier = m.contract("Groth16Verifier_NfAnonNullifier", []);
@@ -25,6 +25,7 @@ const VerifierModule = buildModule("Groth16Verifier_NfAnonNullifier", (m) => {
export default buildModule("Zeto_NfAnonNullifier", (m) => {
const { smtLib, poseidon3 } = m.useModule(SmtLibModule);
const { verifier } = m.useModule(VerifierModule);
+ const { verifier: lockVerifier } = m.useModule(NfLockNullifiersVerifierModule);
- return { verifier, smtLib, poseidon3 };
+ return { verifier, lockVerifier, smtLib, poseidon3 };
});
diff --git a/solidity/scripts/tokens/Zeto_Anon.ts b/solidity/scripts/tokens/Zeto_Anon.ts
index 500c0b54..7fef7aaf 100644
--- a/solidity/scripts/tokens/Zeto_Anon.ts
+++ b/solidity/scripts/tokens/Zeto_Anon.ts
@@ -26,6 +26,8 @@ export async function deployDependencies() {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
} = await ignition.deploy(zetoModule);
return {
deployer,
@@ -36,6 +38,8 @@ export async function deployDependencies() {
withdrawVerifier.target,
batchVerifier.target,
batchWithdrawVerifier.target,
+ lockVerifier.target,
+ batchLockVerifier.target,
],
};
}
diff --git a/solidity/scripts/tokens/Zeto_AnonEnc.ts b/solidity/scripts/tokens/Zeto_AnonEnc.ts
index 47cb0eb8..13a5b4f9 100644
--- a/solidity/scripts/tokens/Zeto_AnonEnc.ts
+++ b/solidity/scripts/tokens/Zeto_AnonEnc.ts
@@ -26,6 +26,8 @@ export async function deployDependencies() {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
} = await ignition.deploy(zetoModule);
return {
deployer,
@@ -36,6 +38,8 @@ export async function deployDependencies() {
withdrawVerifier.target,
batchVerifier.target,
batchWithdrawVerifier.target,
+ lockVerifier.target,
+ batchLockVerifier.target,
],
};
}
diff --git a/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts b/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts
index e61c204a..7bdcd03e 100644
--- a/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts
+++ b/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts
@@ -26,6 +26,8 @@ export async function deployDependencies() {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon3,
} = await ignition.deploy(zetoModule);
@@ -38,6 +40,8 @@ export async function deployDependencies() {
withdrawVerifier.target,
batchVerifier.target,
batchWithdrawVerifier.target,
+ lockVerifier.target,
+ batchLockVerifier.target,
],
libraries: {
SmtLib: smtLib.target,
diff --git a/solidity/scripts/tokens/Zeto_AnonEncNullifierKyc.ts b/solidity/scripts/tokens/Zeto_AnonEncNullifierKyc.ts
index fadbcf45..101ba89c 100644
--- a/solidity/scripts/tokens/Zeto_AnonEncNullifierKyc.ts
+++ b/solidity/scripts/tokens/Zeto_AnonEncNullifierKyc.ts
@@ -26,6 +26,8 @@ export async function deployDependencies() {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon2,
poseidon3,
@@ -39,6 +41,8 @@ export async function deployDependencies() {
withdrawVerifier.target,
batchVerifier.target,
batchWithdrawVerifier.target,
+ lockVerifier.target,
+ batchLockVerifier.target,
],
libraries: {
SmtLib: smtLib.target,
diff --git a/solidity/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts b/solidity/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts
index a10a15ff..518f8bcd 100644
--- a/solidity/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts
+++ b/solidity/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts
@@ -26,6 +26,8 @@ export async function deployDependencies() {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon3,
} = await ignition.deploy(zetoModule);
@@ -38,6 +40,8 @@ export async function deployDependencies() {
withdrawVerifier.target,
batchVerifier.target,
batchWithdrawVerifier.target,
+ lockVerifier.target,
+ batchLockVerifier.target,
],
libraries: {
SmtLib: smtLib.target,
diff --git a/solidity/scripts/tokens/Zeto_AnonNullifier.ts b/solidity/scripts/tokens/Zeto_AnonNullifier.ts
index a02bce72..a2467b16 100644
--- a/solidity/scripts/tokens/Zeto_AnonNullifier.ts
+++ b/solidity/scripts/tokens/Zeto_AnonNullifier.ts
@@ -26,6 +26,8 @@ export async function deployDependencies() {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon3,
} = await ignition.deploy(zetoModule);
@@ -38,6 +40,8 @@ export async function deployDependencies() {
withdrawVerifier.target,
batchVerifier.target,
batchWithdrawVerifier.target,
+ lockVerifier.target,
+ batchLockVerifier.target,
],
libraries: {
SmtLib: smtLib.target,
diff --git a/solidity/scripts/tokens/Zeto_AnonNullifierKyc.ts b/solidity/scripts/tokens/Zeto_AnonNullifierKyc.ts
index a299fa71..9f0cf2b8 100644
--- a/solidity/scripts/tokens/Zeto_AnonNullifierKyc.ts
+++ b/solidity/scripts/tokens/Zeto_AnonNullifierKyc.ts
@@ -26,6 +26,8 @@ export async function deployDependencies() {
verifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
smtLib,
poseidon2,
poseidon3,
@@ -39,6 +41,8 @@ export async function deployDependencies() {
withdrawVerifier.target,
batchVerifier.target,
batchWithdrawVerifier.target,
+ lockVerifier.target,
+ batchLockVerifier.target,
],
libraries: {
SmtLib: smtLib.target,
diff --git a/solidity/scripts/tokens/Zeto_NfAnon.ts b/solidity/scripts/tokens/Zeto_NfAnon.ts
index e18dce26..18e4819a 100644
--- a/solidity/scripts/tokens/Zeto_NfAnon.ts
+++ b/solidity/scripts/tokens/Zeto_NfAnon.ts
@@ -20,9 +20,13 @@ import zetoModule from "../../ignition/modules/zeto_nf_anon";
export async function deployDependencies() {
const [deployer] = await ethers.getSigners();
- const { verifier } = await ignition.deploy(zetoModule);
+ const { verifier, lockVerifier } = await ignition.deploy(zetoModule);
return {
deployer,
- args: [await deployer.getAddress(), verifier.target],
+ args: [
+ await deployer.getAddress(),
+ verifier.target,
+ lockVerifier.target,
+ ],
};
}
diff --git a/solidity/scripts/tokens/Zeto_NfAnonNullifier.ts b/solidity/scripts/tokens/Zeto_NfAnonNullifier.ts
index ff308e71..5970da26 100644
--- a/solidity/scripts/tokens/Zeto_NfAnonNullifier.ts
+++ b/solidity/scripts/tokens/Zeto_NfAnonNullifier.ts
@@ -20,10 +20,14 @@ import zetoModule from "../../ignition/modules/zeto_nf_anon_nullifier";
export async function deployDependencies() {
const [deployer] = await ethers.getSigners();
- const { verifier, smtLib, poseidon3 } = await ignition.deploy(zetoModule);
+ const { verifier, lockVerifier, smtLib, poseidon3 } = await ignition.deploy(zetoModule);
return {
deployer,
- args: [await deployer.getAddress(), verifier.target],
+ args: [
+ await deployer.getAddress(),
+ verifier.target,
+ lockVerifier.target,
+ ],
libraries: {
SmtLib: smtLib.target,
PoseidonUnit3L: poseidon3.target,
diff --git a/solidity/test/factory.ts b/solidity/test/factory.ts
index e0c3b1d4..d0a42ff1 100644
--- a/solidity/test/factory.ts
+++ b/solidity/test/factory.ts
@@ -42,6 +42,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
withdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
batchWithdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ lockVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ batchLockVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
};
await expect(
factory.connect(nonOwner).registerImplementation("test", implInfo as any),
@@ -62,6 +64,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0x0000000000000000000000000000000000000000",
withdrawVerifier: "0x0000000000000000000000000000000000000000",
batchWithdrawVerifier: "0x0000000000000000000000000000000000000000",
+ lockVerifier: "0x0000000000000000000000000000000000000000",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
};
await expect(
factory.connect(deployer).registerImplementation("test", implInfo as any),
@@ -82,6 +86,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0x0000000000000000000000000000000000000000",
withdrawVerifier: "0x0000000000000000000000000000000000000000",
batchWithdrawVerifier: "0x0000000000000000000000000000000000000000",
+ lockVerifier: "0x0000000000000000000000000000000000000000",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
};
await expect(
factory.connect(deployer).registerImplementation("test", implInfo as any),
@@ -102,6 +108,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0x0000000000000000000000000000000000000000",
withdrawVerifier: "0x0000000000000000000000000000000000000000",
batchWithdrawVerifier: "0x0000000000000000000000000000000000000000",
+ lockVerifier: "0x0000000000000000000000000000000000000000",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
};
await expect(
factory.connect(deployer).registerImplementation("test", implInfo as any),
@@ -122,6 +130,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0x0000000000000000000000000000000000000000",
withdrawVerifier: "0x0000000000000000000000000000000000000000",
batchWithdrawVerifier: "0x0000000000000000000000000000000000000000",
+ lockVerifier: "0x0000000000000000000000000000000000000000",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
};
const tx1 = await factory
.connect(deployer)
@@ -148,6 +158,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0x0000000000000000000000000000000000000000",
withdrawVerifier: "0x0000000000000000000000000000000000000000",
batchWithdrawVerifier: "0x0000000000000000000000000000000000000000",
+ lockVerifier: "0x0000000000000000000000000000000000000000",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
};
const tx1 = await factory
.connect(deployer)
@@ -175,6 +187,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
withdrawVerifier: "0x0000000000000000000000000000000000000000",
batchWithdrawVerifier: "0x0000000000000000000000000000000000000000",
+ lockVerifier: "0x0000000000000000000000000000000000000000",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
};
const tx1 = await factory
.connect(deployer)
@@ -202,6 +216,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
withdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
batchWithdrawVerifier: "0x0000000000000000000000000000000000000000",
+ lockVerifier: "0x0000000000000000000000000000000000000000",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
};
const tx1 = await factory
.connect(deployer)
@@ -215,6 +231,64 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
).rejectedWith("Factory: batchWithdrawVerifier address is required");
});
+ it("attempting to deploy a fungible token but with a registered implementation that misses required lockVerifier should fail", async function () {
+ // we want to test the effectiveness of the factory contract
+ // to create clones of the Zeto implementation contract
+ const Factory = await ethers.getContractFactory("ZetoTokenFactory");
+ const factory = await Factory.deploy();
+ await factory.waitForDeployment();
+
+ const implInfo = {
+ implementation: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ verifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ batchVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ withdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ batchWithdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ lockVerifier: "0x0000000000000000000000000000000000000000",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
+ };
+ const tx1 = await factory
+ .connect(deployer)
+ .registerImplementation("test", implInfo as any);
+ await tx1.wait();
+
+ await expect(
+ factory
+ .connect(deployer)
+ .deployZetoFungibleToken("test", await deployer.getAddress()),
+ ).rejectedWith("Factory: lockVerifier address is required");
+ });
+
+ it("attempting to deploy a fungible token but with a registered implementation that misses required batchLockVerifier should fail", async function () {
+ // we want to test the effectiveness of the factory contract
+ // to create clones of the Zeto implementation contract
+ const Factory = await ethers.getContractFactory("ZetoTokenFactory");
+ const factory = await Factory.deploy();
+ await factory.waitForDeployment();
+
+ const implInfo = {
+ implementation: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ verifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ batchVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ withdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ batchWithdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ lockVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ batchLockVerifier: "0x0000000000000000000000000000000000000000",
+ };
+ const tx1 = await factory
+ .connect(deployer)
+ .registerImplementation("test", implInfo as any);
+ await tx1.wait();
+
+ await expect(
+ factory
+ .connect(deployer)
+ .deployZetoFungibleToken("test", await deployer.getAddress()),
+ ).rejectedWith("Factory: batchLockVerifier address is required");
+ });
+
it("attempting to deploy a fungible token with a properly registered implementation should succeed", async function () {
// we want to test the effectiveness of the factory contract
// to create clones of the Zeto implementation contract
@@ -229,6 +303,8 @@ describe("(factory) Zeto based fungible token with anonymity without encryption
depositVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
withdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
batchWithdrawVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ lockVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
+ batchLockVerifier: "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1",
};
const tx1 = await factory
.connect(deployer)
diff --git a/solidity/test/lib/deploy.ts b/solidity/test/lib/deploy.ts
index 3d58987f..a2a47b7d 100644
--- a/solidity/test/lib/deploy.ts
+++ b/solidity/test/lib/deploy.ts
@@ -49,14 +49,19 @@ export async function deployZeto(tokenName: string) {
: deployNonFungibleCloneable;
const result = await deployFunc(tokenName);
({ deployer, zetoImpl, erc20, args } = result as any);
- const [
+ let [
deployerAddr,
verifier,
depositVerifier,
withdrawVerifier,
batchVerifier,
batchWithdrawVerifier,
+ lockVerifier,
+ batchLockVerifier,
] = args;
+ if (!isFungible) {
+ ([deployerAddr, verifier, lockVerifier] = args);
+ }
// we want to test the effectiveness of the factory contract
// to create clones of the Zeto implementation contract
@@ -75,8 +80,11 @@ export async function deployZeto(tokenName: string) {
batchVerifier || "0x0000000000000000000000000000000000000000",
batchWithdrawVerifier:
batchWithdrawVerifier || "0x0000000000000000000000000000000000000000",
+ lockVerifier:
+ lockVerifier || "0x0000000000000000000000000000000000000000",
+ batchLockVerifier:
+ batchLockVerifier || "0x0000000000000000000000000000000000000000",
};
- // console.log(implInfo);
const tx1 = await factory
.connect(deployer)
.registerImplementation(tokenName, implInfo as any);
diff --git a/solidity/test/utils.ts b/solidity/test/utils.ts
index b81d7153..d97168a4 100644
--- a/solidity/test/utils.ts
+++ b/solidity/test/utils.ts
@@ -18,8 +18,9 @@ import { readFileSync } from "fs";
import * as path from "path";
import { BigNumberish } from "ethers";
import { groth16 } from "snarkjs";
-import { loadCircuit, encodeProof } from "zeto-js";
+import { loadCircuit, encodeProof, tokenUriHash } from "zeto-js";
import { User, UTXO } from "./lib/utils";
+import { formatPrivKeyForBabyJub, stringifyBigInts } from "maci-crypto";
function provingKeysRoot() {
const PROVING_KEYS_ROOT = process.env.PROVING_KEYS_ROOT;
@@ -127,11 +128,11 @@ export async function prepareNullifierWithdrawProof(
outputSalts: [output.salt || 0n],
outputOwnerPublicKeys,
};
- let circuit = await loadCircuit("check_nullifier_value");
- let { provingKeyFile } = loadProvingKeys("check_nullifier_value");
+ let circuit = await loadCircuit("check_nullifiers_value");
+ let { provingKeyFile } = loadProvingKeys("check_nullifiers_value");
if (inputCommitments.length > 2) {
- circuit = await loadCircuit("check_nullifier_value_batch");
- ({ provingKeyFile } = loadProvingKeys("check_nullifier_value_batch"));
+ circuit = await loadCircuit("check_nullifiers_value_batch");
+ ({ provingKeyFile } = loadProvingKeys("check_nullifiers_value_batch"));
}
const startWitnessCalculation = Date.now();
@@ -213,3 +214,149 @@ export async function prepareWithdrawProof(
encodedProof,
};
}
+
+export async function prepareLockProof(
+ signer: User,
+ inputs: UTXO[],
+) {
+ const commitments: BigNumberish[] = inputs.map(
+ (input) => input.hash || 0n,
+ ) as BigNumberish[];
+ const values = inputs.map((input) => BigInt(input.value || 0n));
+ const salts = inputs.map((input) => input.salt || 0n);
+ const otherInputs = stringifyBigInts({
+ ownerPrivateKey: formatPrivKeyForBabyJub(signer.babyJubPrivateKey),
+ });
+
+ const startWitnessCalculation = Date.now();
+ let circuit = await loadCircuit("check_utxos_owner");
+ let { provingKeyFile: provingKey } = loadProvingKeys("check_utxos_owner");
+ if (commitments.length > 2) {
+ circuit = await loadCircuit("check_utxos_owner_batch");
+ ({ provingKeyFile: provingKey } = loadProvingKeys("check_utxos_owner_batch"));
+ }
+
+ const witness = await circuit.calculateWTNSBin(
+ {
+ commitments,
+ values,
+ salts,
+ ...otherInputs,
+ },
+ true,
+ );
+ const timeWitnessCalculation = Date.now() - startWitnessCalculation;
+
+ const startProofGeneration = Date.now();
+ const { proof, publicSignals } = (await groth16.prove(
+ provingKey,
+ witness,
+ )) as { proof: BigNumberish[]; publicSignals: BigNumberish[] };
+ const timeProofGeneration = Date.now() - startProofGeneration;
+ console.log(
+ `Witness calculation time: ${timeWitnessCalculation}ms, Proof generation time: ${timeProofGeneration}ms`,
+ );
+ const encodedProof = encodeProof(proof);
+ return {
+ commitments,
+ encodedProof,
+ };
+}
+
+export async function prepareNullifiersLockProof(
+ signer: User,
+ _nullifiers: UTXO[],
+) {
+ const nullifiers: BigNumberish[] = _nullifiers.map(
+ (input) => input.hash || 0n,
+ ) as BigNumberish[];
+ const values = _nullifiers.map((input) => BigInt(input.value || 0n));
+ const salts = _nullifiers.map((input) => input.salt || 0n);
+ const otherInputs = stringifyBigInts({
+ ownerPrivateKey: formatPrivKeyForBabyJub(signer.babyJubPrivateKey),
+ });
+
+ const startWitnessCalculation = Date.now();
+ let circuit = await loadCircuit("check_nullifiers_owner");
+ let { provingKeyFile: provingKey } = loadProvingKeys("check_nullifiers_owner");
+ if (nullifiers.length > 2) {
+ circuit = await loadCircuit("check_nullifiers_owner_batch");
+ ({ provingKeyFile: provingKey } = loadProvingKeys("check_nullifiers_owner_batch"));
+ }
+
+ const witness = await circuit.calculateWTNSBin(
+ {
+ nullifiers,
+ values,
+ salts,
+ ...otherInputs,
+ },
+ true,
+ );
+ const timeWitnessCalculation = Date.now() - startWitnessCalculation;
+
+ const startProofGeneration = Date.now();
+ const { proof, publicSignals } = (await groth16.prove(
+ provingKey,
+ witness,
+ )) as { proof: BigNumberish[]; publicSignals: BigNumberish[] };
+ const timeProofGeneration = Date.now() - startProofGeneration;
+ console.log(
+ `Witness calculation time: ${timeWitnessCalculation}ms, Proof generation time: ${timeProofGeneration}ms`,
+ );
+ const encodedProof = encodeProof(proof);
+ return {
+ nullifiers,
+ encodedProof,
+ };
+}
+
+export async function prepareAssetLockProof(
+ signer: User,
+ inputs: UTXO[],
+) {
+ const commitments: BigNumberish[] = inputs.map(
+ (input) => input.hash || 0n,
+ ) as BigNumberish[];
+ const tokenIds = inputs.map((input) => BigInt(input.tokenId || 0n));
+ const tokenUris = inputs.map((input) => BigInt(input.uri ? tokenUriHash(input.uri) : 0n));
+ const salts = inputs.map((input) => input.salt || 0n);
+ const otherInputs = stringifyBigInts({
+ ownerPrivateKey: formatPrivKeyForBabyJub(signer.babyJubPrivateKey),
+ });
+
+ const startWitnessCalculation = Date.now();
+ let circuit = await loadCircuit("check_utxos_nf_owner");
+ let { provingKeyFile: provingKey } = loadProvingKeys("check_utxos_nf_owner");
+ if (commitments.length > 2) {
+ circuit = await loadCircuit("check_utxos_owner_batch");
+ ({ provingKeyFile: provingKey } = loadProvingKeys("check_utxos_owner_batch"));
+ }
+
+ const witness = await circuit.calculateWTNSBin(
+ {
+ commitments,
+ tokenIds,
+ tokenUris,
+ salts,
+ ...otherInputs,
+ },
+ true,
+ );
+ const timeWitnessCalculation = Date.now() - startWitnessCalculation;
+
+ const startProofGeneration = Date.now();
+ const { proof, publicSignals } = (await groth16.prove(
+ provingKey,
+ witness,
+ )) as { proof: BigNumberish[]; publicSignals: BigNumberish[] };
+ const timeProofGeneration = Date.now() - startProofGeneration;
+ console.log(
+ `Witness calculation time: ${timeWitnessCalculation}ms, Proof generation time: ${timeProofGeneration}ms`,
+ );
+ const encodedProof = encodeProof(proof);
+ return {
+ commitments,
+ encodedProof,
+ };
+}
diff --git a/solidity/test/zeto_anon.ts b/solidity/test/zeto_anon.ts
index 5a336998..45a3256b 100644
--- a/solidity/test/zeto_anon.ts
+++ b/solidity/test/zeto_anon.ts
@@ -32,6 +32,7 @@ import {
import {
loadProvingKeys,
prepareDepositProof,
+ prepareLockProof,
prepareWithdrawProof,
} from "./utils";
import { Zeto_Anon } from "../typechain-types";
@@ -382,6 +383,63 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi
});
});
+ describe("lockStates() tests", function () {
+ it("lockStates() should succeed when using unlocked states", async function () {
+ const { commitments, encodedProof } = await prepareLockProof(Bob, [utxo7, ZERO_UTXO]);
+
+ const tx = await zeto.connect(Bob.signer).lockStates(
+ commitments.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Alice.ethAddress, // make Alice the delegate who can spend the state (if she has the right proof)
+ "0x",
+ );
+ const results = await tx.wait();
+ console.log(`Method transfer() complete. Gas used: ${results?.gasUsed}`);
+ });
+
+ it("lockStates() should fail when trying to lock as non-delegate", async function () {
+ if (network.name !== "hardhat") {
+ return;
+ }
+
+ // Bob is the owner of the UTXO, so he can generate the right proof
+ const { commitments, encodedProof } = await prepareLockProof(Bob, [utxo7, ZERO_UTXO]);
+
+ // but he's no longer the delegate (Alice is) to spend the state
+ await expect(zeto.connect(Bob.signer).lockStates(
+ commitments.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Bob.ethAddress,
+ "0x",
+ )).rejectedWith(`UTXOAlreadyLocked(${utxo7.hash.toString()})`);
+ });
+
+ it("the original owner can NOT spend the locked state", async function () {
+ const utxo8 = newUTXO(15, Alice);
+ await expect(doTransfer(Bob, [utxo7, ZERO_UTXO], [utxo8, ZERO_UTXO], [Alice, Alice])).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the original owner can NOT withdraw the locked state", async function () {
+ const outputCommitment = newUTXO(5, Bob);
+
+ const { inputCommitments, outputCommitments, encodedProof } =
+ await prepareWithdrawProof(Bob, [utxo7, ZERO_UTXO], outputCommitment);
+
+ // Alice withdraws her UTXOs to ERC20 tokens
+ await expect(zeto
+ .connect(Bob.signer)
+ .withdraw(10, inputCommitments, outputCommitments[0], encodedProof, "0x")).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the designated delegate can use the proper proof to spend the locked state", async function () {
+ const utxo8 = newUTXO(15, Alice);
+ const { inputCommitments, outputCommitments, encodedProof } = await prepareProof(circuit, provingKey, Bob, [utxo7, ZERO_UTXO], [utxo8, ZERO_UTXO], [Alice, Alice]);
+ // Bob (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
+ // using the proof generated by the trade counterparty (Alice in this case)
+ await expect(sendTx(Alice, inputCommitments, outputCommitments, encodedProof)).to.be.fulfilled;
+ });
+ });
+
async function doTransfer(
signer: User,
inputs: UTXO[],
@@ -417,7 +475,6 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi
signer,
inputCommitments,
outputCommitments,
- outputOwnerAddresses,
encodedProof,
);
}
@@ -426,10 +483,8 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi
signer: User,
inputCommitments: BigNumberish[],
outputCommitments: BigNumberish[],
- outputOwnerAddresses: AddressLike[],
encodedProof: any,
) {
- const signerAddress = await signer.signer.getAddress();
const tx = await zeto.connect(signer.signer).transfer(
inputCommitments.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
@@ -440,11 +495,17 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi
console.log(`Method transfer() complete. Gas used: ${results?.gasUsed}`);
for (const input of inputCommitments) {
+ if (input === 0n) {
+ continue;
+ }
const owner = await zeto.spent(input);
expect(owner).to.equal(true);
}
- for (let i = 0; i < outputCommitments.length; i++) {
- expect(await zeto.spent(outputCommitments[i])).to.equal(false);
+ for (const output of outputCommitments) {
+ if (output === 0n) {
+ continue;
+ }
+ expect(await zeto.spent(output)).to.equal(false);
}
return results;
diff --git a/solidity/test/zeto_anon_enc.ts b/solidity/test/zeto_anon_enc.ts
index 0e29e3cc..6fff404e 100644
--- a/solidity/test/zeto_anon_enc.ts
+++ b/solidity/test/zeto_anon_enc.ts
@@ -43,6 +43,7 @@ import {
import {
loadProvingKeys,
prepareDepositProof,
+ prepareLockProof,
prepareWithdrawProof,
} from "./utils";
import { deployZeto } from "./lib/deploy";
@@ -370,6 +371,63 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
});
});
+ describe("lockStates() tests", function () {
+ it("lockStates() should succeed when using unlocked states", async function () {
+ const { commitments, encodedProof } = await prepareLockProof(Alice, [utxo4, ZERO_UTXO]);
+
+ const tx = await zeto.connect(Alice.signer).lockStates(
+ commitments.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Bob.ethAddress, // make Bob the delegate who can spend the state (if she has the right proof)
+ "0x",
+ );
+ const results = await tx.wait();
+ console.log(`Method transfer() complete. Gas used: ${results?.gasUsed}`);
+ });
+
+ it("lockStates() should fail when trying to lock as non-delegate", async function () {
+ if (network.name !== "hardhat") {
+ return;
+ }
+
+ // Bob is the owner of the UTXO, so he can generate the right proof
+ const { commitments, encodedProof } = await prepareLockProof(Alice, [utxo4, ZERO_UTXO]);
+
+ // but he's no longer the delegate (Alice is) to spend the state
+ await expect(zeto.connect(Alice.signer).lockStates(
+ commitments.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Alice.ethAddress,
+ "0x",
+ )).rejectedWith(`UTXOAlreadyLocked(${utxo4.hash.toString()})`);
+ });
+
+ it("the original owner can NOT spend the locked state", async function () {
+ const utxo8 = newUTXO(5, Charlie);
+ const ephemeralKeypair = genKeypair();
+ await expect(doTransfer(Alice, [utxo4, ZERO_UTXO], [utxo8, ZERO_UTXO], [Charlie, Alice])).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the original owner can NOT withdraw the locked state", async function () {
+ const utxo8 = newUTXO(0, Alice);
+ const { inputCommitments, outputCommitments, encodedProof } =
+ await prepareWithdrawProof(Alice, [utxo4, ZERO_UTXO], utxo8);
+
+ await expect(zeto
+ .connect(Alice.signer)
+ .withdraw(5, inputCommitments, outputCommitments[0], encodedProof, "0x")).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the designated delegate can use the proper proof to spend the locked state", async function () {
+ const utxo8 = newUTXO(5, Charlie);
+ const ephemeralKeypair = genKeypair();
+ const { inputCommitments, outputCommitments, encodedProof, encryptedValues, encryptionNonce } = await prepareProof(Alice, [utxo4, ZERO_UTXO], [utxo8, ZERO_UTXO], [Charlie, Alice], ephemeralKeypair.privKey);
+ // Bob (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
+ // using the proof generated by the trade counterparty (Alice in this case)
+ await expect(sendTx(Bob, inputCommitments, outputCommitments, encryptedValues, encryptionNonce, encodedProof, ephemeralKeypair.pubKey)).to.be.fulfilled;
+ });
+ });
+
async function doTransfer(
signer: User,
inputs: UTXO[],
diff --git a/solidity/test/zeto_anon_enc_nullifier.ts b/solidity/test/zeto_anon_enc_nullifier.ts
index ddaaf37d..7d82b451 100644
--- a/solidity/test/zeto_anon_enc_nullifier.ts
+++ b/solidity/test/zeto_anon_enc_nullifier.ts
@@ -45,6 +45,7 @@ import {
import {
loadProvingKeys,
prepareDepositProof,
+ prepareNullifiersLockProof,
prepareNullifierWithdrawProof,
} from "./utils";
import { deployZeto } from "./lib/deploy";
@@ -472,6 +473,139 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti
expect(endingBalance - startingBalance).to.be.equal(80);
});
+ describe("lockStates() tests", function () {
+ let nullifier1: any;
+ it("lockStates() should succeed when using unlocked states", async function () {
+ nullifier1 = newNullifier(utxo4, Alice);
+ const { nullifiers, encodedProof } = await prepareNullifiersLockProof(Alice, [nullifier1, ZERO_UTXO]);
+
+ const tx = await zeto.connect(Alice.signer).lockStates(
+ nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Bob.ethAddress, // make Bob the delegate who can spend the state (if he has the right proof)
+ "0x",
+ );
+ const results = await tx.wait();
+ console.log(`Method transfer() complete. Gas used: ${results?.gasUsed}`);
+ });
+
+ it("lockStates() should fail when trying to lock as non-delegate", async function () {
+ if (network.name !== "hardhat") {
+ return;
+ }
+
+ // Bob is the owner of the UTXO, so he can generate the right proof
+ const { nullifiers, encodedProof } = await prepareNullifiersLockProof(Alice, [nullifier1, ZERO_UTXO]);
+
+ // but he's no longer the delegate (Alice is) to spend the state
+ await expect(zeto.connect(Alice.signer).lockStates(
+ nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Alice.ethAddress,
+ "0x",
+ )).rejectedWith(`UTXOAlreadyLocked(${nullifier1.hash.toString()})`);
+ });
+
+ it("the original owner can NOT spend the locked state", async function () {
+ // Alice generates inclusion proofs for the UTXOs to be spent, as private input to the proof generation
+ const root = await smtAlice.root();
+ const proof1 = await smtAlice.generateCircomVerifierProof(utxo4.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 UTXOs, attempting to transfer to Charlie
+ const utxo9 = newUTXO(5, Charlie);
+
+ // Alice should NOT be able to spend the UTXO which has been locked and delegated to Bob
+ await expect(doTransfer(
+ Alice,
+ [utxo4, ZERO_UTXO],
+ [nullifier1, ZERO_UTXO],
+ [utxo9, ZERO_UTXO],
+ root.bigInt(),
+ merkleProofs,
+ [Charlie, Alice],
+ )).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the original owner can NOT withdraw the locked state", async function () {
+ // Alice generates inclusion proofs for the UTXOs to be spent, as private input to the proof generation
+ const root = await smtAlice.root();
+ const proof1 = await smtAlice.generateCircomVerifierProof(utxo4.hash, root);
+ const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
+ const merkleProofs = [
+ proof1.siblings.map((s) => s.bigInt()),
+ proof2.siblings.map((s) => s.bigInt()),
+ ];
+
+ const utxo9 = newUTXO(0, Alice);
+
+ const { nullifiers, outputCommitments, encodedProof } =
+ await prepareNullifierWithdrawProof(
+ Alice,
+ [utxo4, ZERO_UTXO],
+ [nullifier1, ZERO_UTXO],
+ utxo9,
+ root.bigInt(),
+ merkleProofs,
+ );
+
+ await expect(zeto
+ .connect(Alice.signer)
+ .withdraw(
+ 80,
+ nullifiers,
+ outputCommitments[0],
+ root.bigInt(),
+ encodedProof,
+ "0x",
+ )).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the designated delegate can use the proper proof to spend the locked state", async function () {
+ // Alice generates inclusion proofs for the UTXOs to be spent, as private input to the proof generation
+ const root = await smtAlice.root();
+ const proof1 = await smtAlice.generateCircomVerifierProof(utxo4.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 UTXOs, attempting to transfer to Charlie
+ const utxo9 = newUTXO(5, Charlie);
+
+ const ephemeralKeypair = genKeypair();
+ const result = await prepareProof(
+ Alice,
+ [utxo4, ZERO_UTXO],
+ [nullifier1, ZERO_UTXO],
+ [utxo9, ZERO_UTXO],
+ root.bigInt(),
+ merkleProofs,
+ [Charlie, Alice],
+ ephemeralKeypair.privKey,
+ );
+ const nullifiers = [nullifier1.hash];
+
+ // Bob (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
+ // using the proof generated by the trade counterparty (Alice in this case)
+ await expect(sendTx(
+ Bob,
+ nullifiers,
+ result.outputCommitments,
+ root.bigInt(),
+ result.encryptedValues,
+ result.encryptionNonce,
+ result.encodedProof,
+ ephemeralKeypair.pubKey,
+ )).to.be.fulfilled;
+ });
+ });
+
describe("failure cases", function () {
// the following failure cases rely on the hardhat network
// to return the details of the errors. This is not possible
diff --git a/solidity/test/zeto_anon_nullifier.ts b/solidity/test/zeto_anon_nullifier.ts
index 7b4e9254..b96f4d53 100644
--- a/solidity/test/zeto_anon_nullifier.ts
+++ b/solidity/test/zeto_anon_nullifier.ts
@@ -33,6 +33,7 @@ import {
import {
loadProvingKeys,
prepareDepositProof,
+ prepareNullifiersLockProof,
prepareNullifierWithdrawProof,
} from "./utils";
import { deployZeto } from "./lib/deploy";
@@ -50,6 +51,7 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
let utxo3: UTXO;
let utxo4: UTXO;
let utxo7: UTXO;
+ let utxo9: UTXO;
let circuit: any, provingKey: any;
let batchCircuit: any, batchProvingKey: any;
let smtAlice: Merkletree;
@@ -444,6 +446,137 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
expect(endingBalance - startingBalance).to.be.equal(80);
});
+ describe("lockStates() tests", function () {
+ let nullifier1: any;
+ it("lockStates() should succeed when using unlocked states", async function () {
+ nullifier1 = newNullifier(utxo7, Bob);
+ const { nullifiers, encodedProof } = await prepareNullifiersLockProof(Bob, [nullifier1, ZERO_UTXO]);
+
+ const tx = await zeto.connect(Bob.signer).lockStates(
+ nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Alice.ethAddress, // make Alice the delegate who can spend the state (if she has the right proof)
+ "0x",
+ );
+ const results = await tx.wait();
+ console.log(`Method transfer() complete. Gas used: ${results?.gasUsed}`);
+ });
+
+ it("lockStates() should fail when trying to lock as non-delegate", async function () {
+ if (network.name !== "hardhat") {
+ return;
+ }
+
+ // Bob is the owner of the UTXO, so he can generate the right proof
+ const { nullifiers, encodedProof } = await prepareNullifiersLockProof(Bob, [nullifier1, ZERO_UTXO]);
+
+ // but he's no longer the delegate (Alice is) to spend the state
+ await expect(zeto.connect(Bob.signer).lockStates(
+ nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Bob.ethAddress,
+ "0x",
+ )).rejectedWith(`UTXOAlreadyLocked(${nullifier1.hash.toString()})`);
+ });
+
+ it("the original owner can NOT spend the locked state", async function () {
+ // Bob generates inclusion proofs for the UTXOs to be spent, as private input to the proof generation
+ const root = await smtBob.root();
+ const proof1 = await smtBob.generateCircomVerifierProof(utxo7.hash, root);
+ const proof2 = await smtBob.generateCircomVerifierProof(0n, root);
+ const merkleProofs = [
+ proof1.siblings.map((s) => s.bigInt()),
+ proof2.siblings.map((s) => s.bigInt()),
+ ];
+
+ // Bob proposes the output UTXOs, attempting to transfer to Alice
+ utxo9 = newUTXO(15, Alice);
+
+ // Bob should NOT be able to spend the UTXO which has been locked and delegated to Bob
+ await expect(doTransfer(
+ Bob,
+ [utxo7, ZERO_UTXO],
+ [nullifier1, ZERO_UTXO],
+ [utxo9, ZERO_UTXO],
+ root.bigInt(),
+ merkleProofs,
+ [Alice, Bob],
+ )).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the original owner can NOT withdraw the locked state", async function () {
+ // Bob generates inclusion proofs for the UTXOs to be spent, as private input to the proof generation
+ const root = await smtBob.root();
+ const proof1 = await smtBob.generateCircomVerifierProof(utxo7.hash, root);
+ const proof2 = await smtBob.generateCircomVerifierProof(0n, root);
+ const merkleProofs = [
+ proof1.siblings.map((s) => s.bigInt()),
+ proof2.siblings.map((s) => s.bigInt()),
+ ];
+
+ const _utxo = newUTXO(5, Bob);
+
+ const { nullifiers, outputCommitments, encodedProof } =
+ await prepareNullifierWithdrawProof(
+ Bob,
+ [utxo7, ZERO_UTXO],
+ [nullifier1, ZERO_UTXO],
+ _utxo,
+ root.bigInt(),
+ merkleProofs,
+ );
+
+ await expect(zeto
+ .connect(Bob.signer)
+ .withdraw(
+ 80,
+ nullifiers,
+ outputCommitments[0],
+ root.bigInt(),
+ encodedProof,
+ "0x",
+ )).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the designated delegate can use the proper proof to spend the locked state", async function () {
+ // Bob generates inclusion proofs for the UTXOs to be spent, as private input to the proof generation
+ const root = await smtBob.root();
+ const proof1 = await smtBob.generateCircomVerifierProof(utxo7.hash, root);
+ const proof2 = await smtBob.generateCircomVerifierProof(0n, root);
+ const merkleProofs = [
+ proof1.siblings.map((s) => s.bigInt()),
+ proof2.siblings.map((s) => s.bigInt()),
+ ];
+
+ // Bob proposes the output UTXOs, attempting to transfer to Alice
+ utxo9 = newUTXO(15, Alice);
+
+ const result = await prepareProof(
+ Bob,
+ [utxo7, ZERO_UTXO],
+ [nullifier1, ZERO_UTXO],
+ [utxo9, ZERO_UTXO],
+ root.bigInt(),
+ merkleProofs,
+ [Alice, Bob],
+ );
+ const nullifiers = [nullifier1.hash];
+
+ // Alice (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
+ // using the proof generated by the trade counterparty (Bob in this case)
+ await expect(sendTx(
+ Alice,
+ nullifiers,
+ result.outputCommitments,
+ root.bigInt(),
+ result.encodedProof,
+ )).to.be.fulfilled;
+
+ // Alice keeps the local SMT in sync
+ await smtAlice.add(utxo9.hash, utxo9.hash);
+ });
+ });
+
describe("failure cases", function () {
// the following failure cases rely on the hardhat network
// to return the details of the errors. This is not possible
@@ -545,30 +678,22 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
}).timeout(600000);
it("transfer with existing UTXOs in the output should fail (mass conservation protection)", async function () {
- // give Bob another UTXO to be able to spend
- const _utxo1 = newUTXO(15, Bob);
- await doMint(zeto, deployer, [_utxo1]);
- await smtBob.add(_utxo1.hash, _utxo1.hash);
-
- const nullifier1 = newNullifier(utxo7, Bob);
- const nullifier2 = newNullifier(_utxo1, Bob);
- let root = await smtBob.root();
- const proof1 = await smtBob.generateCircomVerifierProof(utxo7.hash, root);
- const proof2 = await smtBob.generateCircomVerifierProof(
- _utxo1.hash,
- root,
- );
+ const nullifier1 = newNullifier(utxo9, Alice);
+ let root = await smtAlice.root();
+ const proof1 = await smtAlice.generateCircomVerifierProof(utxo9.hash, root);
+ const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
const merkleProofs = [
proof1.siblings.map((s) => s.bigInt()),
proof2.siblings.map((s) => s.bigInt()),
];
+ const _utxo1 = newUTXO(5, Alice);
await expect(
doTransfer(
- Bob,
- [utxo7, _utxo1],
- [nullifier1, nullifier2],
- [utxo1, utxo2],
+ Alice,
+ [utxo9, ZERO_UTXO],
+ [nullifier1, ZERO_UTXO],
+ [utxo1, _utxo1],
root.bigInt(),
merkleProofs,
[Alice, Alice],
@@ -577,14 +702,14 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
}).timeout(600000);
it("spend by using the same UTXO as both inputs should fail", async function () {
- const _utxo1 = newUTXO(20, Alice);
- const _utxo2 = newUTXO(10, Bob);
- const nullifier1 = newNullifier(utxo7, Bob);
- const nullifier2 = newNullifier(utxo7, Bob);
+ const _utxo1 = newUTXO(10, Alice);
+ const _utxo2 = newUTXO(20, Bob);
+ const nullifier1 = newNullifier(utxo9, Alice);
+ const nullifier2 = newNullifier(utxo9, Alice);
// generate inclusion proofs for the UTXOs to be spent
- let root = await smtBob.root();
- const proof1 = await smtBob.generateCircomVerifierProof(utxo7.hash, root);
- const proof2 = await smtBob.generateCircomVerifierProof(utxo7.hash, root);
+ let root = await smtAlice.root();
+ const proof1 = await smtAlice.generateCircomVerifierProof(utxo9.hash, root);
+ const proof2 = await smtAlice.generateCircomVerifierProof(utxo9.hash, root);
const merkleProofs = [
proof1.siblings.map((s) => s.bigInt()),
proof2.siblings.map((s) => s.bigInt()),
@@ -592,8 +717,8 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
await expect(
doTransfer(
- Bob,
- [utxo7, utxo7],
+ Alice,
+ [utxo9, utxo9],
[nullifier1, nullifier2],
[_utxo1, _utxo2],
root.bigInt(),
diff --git a/solidity/test/zeto_nf_anon.ts b/solidity/test/zeto_nf_anon.ts
index 9fa70e25..8a86afcb 100644
--- a/solidity/test/zeto_nf_anon.ts
+++ b/solidity/test/zeto_nf_anon.ts
@@ -20,8 +20,8 @@ import { expect } from "chai";
import { loadCircuit, tokenUriHash, encodeProof } from "zeto-js";
import { groth16 } from "snarkjs";
import { formatPrivKeyForBabyJub, stringifyBigInts } from "maci-crypto";
-import { User, UTXO, newUser, newAssetUTXO, doMint } from "./lib/utils";
-import { loadProvingKeys } from "./utils";
+import { User, UTXO, newUser, newAssetUTXO, doMint, ZERO_UTXO } from "./lib/utils";
+import { loadProvingKeys, prepareAssetLockProof } from "./utils";
import { deployZeto } from "./lib/deploy";
describe("Zeto based non-fungible token with anonymity without encryption or nullifiers", function () {
@@ -122,6 +122,52 @@ describe("Zeto based non-fungible token with anonymity without encryption or nul
});
});
+ describe("lockStates() tests", function () {
+ it("lockStates() should succeed when using unlocked states", async function () {
+ const { commitments, encodedProof } = await prepareAssetLockProof(Charlie, [utxo3, ZERO_UTXO]);
+
+ const tx = await zeto.connect(Bob.signer).lockStates(
+ commitments.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Alice.ethAddress, // make Alice the delegate who can spend the state (if she has the right proof)
+ "0x",
+ );
+ const results = await tx.wait();
+ console.log(`Method transfer() complete. Gas used: ${results?.gasUsed}`);
+ });
+
+ it("lockStates() should fail when trying to lock as non-delegate", async function () {
+ if (network.name !== "hardhat") {
+ return;
+ }
+
+ // Charlie is the owner of the UTXO, so he can generate the right proof
+ const { commitments, encodedProof } = await prepareAssetLockProof(Charlie, [utxo3, ZERO_UTXO]);
+
+ // but he's no longer the delegate (Alice is) to spend the state
+ await expect(zeto.connect(Charlie.signer).lockStates(
+ commitments.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
+ encodedProof,
+ Bob.ethAddress,
+ "0x",
+ )).rejectedWith(`UTXOAlreadyLocked(${utxo3.hash.toString()})`);
+ });
+
+ it("the original owner can NOT use the proper proof to spend the locked state", async function () {
+ const utxo8 = newAssetUTXO(utxo3.tokenId!, utxo3.uri!, Alice);
+ const { inputCommitment, outputCommitment, encodedProof } = await prepareProof(circuit, provingKey, Charlie, utxo3, utxo8, Alice);
+ await expect(sendTx(Charlie, inputCommitment, outputCommitment, encodedProof)).to.be.rejectedWith("UTXOAlreadyLocked");
+ });
+
+ it("the designated delegate can use the proper proof to spend the locked state", async function () {
+ const utxo8 = newAssetUTXO(utxo3.tokenId!, utxo3.uri!, Alice);
+ const { inputCommitment, outputCommitment, encodedProof } = await prepareProof(circuit, provingKey, Charlie, utxo3, utxo8, Alice);
+ // Alice (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
+ // using the proof generated by the trade counterparty (Charlie in this case)
+ await expect(sendTx(Alice, inputCommitment, outputCommitment, encodedProof)).to.be.fulfilled;
+ });
+ });
+
async function doTransfer(signer: User, input: UTXO, output: UTXO, to: User) {
let inputCommitment: BigNumberish;
let outputCommitment: BigNumberish;
diff --git a/solidity/test/zkDvP.ts b/solidity/test/zkDvP.ts
index 2c96007b..60c28689 100644
--- a/solidity/test/zkDvP.ts
+++ b/solidity/test/zkDvP.ts
@@ -15,7 +15,7 @@
// limitations under the License.
import { ethers, ignition, network } from "hardhat";
-import { Signer, encodeBytes32String, ZeroHash } from "ethers";
+import { Signer, encodeBytes32String, ZeroHash, lock } from "ethers";
import { expect } from "chai";
import { loadCircuit, getProofHash } from "zeto-js";
import zkDvPModule from "../ignition/modules/zkDvP";
@@ -31,7 +31,7 @@ import {
ZERO_UTXO,
parseUTXOEvents,
} from "./lib/utils";
-import { loadProvingKeys } from "./utils";
+import { loadProvingKeys, prepareLockProof, prepareAssetLockProof } from "./utils";
import { deployZeto } from "./lib/deploy";
describe("DvP flows between fungible and non-fungible tokens based on Zeto with anonymity without encryption or nullifiers", function () {
@@ -163,6 +163,7 @@ describe("DvP flows between fungible and non-fungible tokens based on Zeto with
[Bob, {}],
);
const hash1 = getProofHash(proof1.encodedProof);
+ const lockProof1 = await prepareLockProof(Alice, [_utxo1, ZERO_UTXO]);
// 1.3 Alice initiates the trade with Bob
const tx1 = await zkDvP
@@ -188,6 +189,7 @@ describe("DvP flows between fungible and non-fungible tokens based on Zeto with
Alice,
);
const hash2 = getProofHash(proof2.encodedProof);
+ const lockProof2 = await prepareAssetLockProof(Bob, [utxo3, ZERO_UTXO]);
await expect(
zkDvP
@@ -206,13 +208,13 @@ describe("DvP flows between fungible and non-fungible tokens based on Zeto with
// 3. Alice sends her proof to complete the trade (the trade will still be pending completion)
const tx2 = await zkDvP
.connect(Alice.signer)
- .completeTrade(tradeId, proof1.encodedProof);
+ .completeTrade(tradeId, proof1.encodedProof, lockProof1.encodedProof);
const tx2Result = await tx2.wait();
// 4. Bob sends his proof to complete the trade (the trade will be completed)
const tx3 = await zkDvP
.connect(Bob.signer)
- .completeTrade(tradeId, proof2.encodedProof);
+ .completeTrade(tradeId, proof2.encodedProof, lockProof2.encodedProof);
const tx3Result = await tx3.wait();
// check that the trade is completed
@@ -445,43 +447,5 @@ describe("DvP flows between fungible and non-fungible tokens based on Zeto with
),
).rejectedWith("Payment outputs must be provided to accept the trade");
});
-
- it("test proof locking", async function () {
- const circuit1 = await loadCircuit("anon");
- const { provingKeyFile: provingKey1 } = loadProvingKeys("anon");
- const utxo1 = newUTXO(100, Alice);
- const proof = await zetoAnonTests.prepareProof(
- circuit1,
- provingKey1,
- Alice,
- [utxo1, ZERO_UTXO],
- [utxo1, ZERO_UTXO],
- [Alice, {}],
- );
-
- await expect(
- zkPayment
- .connect(Alice.signer)
- .lockProof(proof.encodedProof, await Alice.signer.getAddress()),
- ).fulfilled;
- await expect(
- zkPayment
- .connect(Bob.signer)
- .lockProof(proof.encodedProof, await Bob.signer.getAddress()),
- ).rejectedWith("Proof already locked by another party");
- await expect(
- zkPayment
- .connect(Alice.signer)
- .lockProof(proof.encodedProof, await Bob.signer.getAddress()),
- ).fulfilled;
- await expect(
- zkPayment
- .connect(Bob.signer)
- .lockProof(
- proof.encodedProof,
- "0x0000000000000000000000000000000000000000",
- ),
- ).fulfilled;
- });
});
}).timeout(600000);
diff --git a/zkp/circuits/check_nullifiers_nf_owner.circom b/zkp/circuits/check_nullifiers_nf_owner.circom
new file mode 100644
index 00000000..f7ebc10e
--- /dev/null
+++ b/zkp/circuits/check_nullifiers_nf_owner.circom
@@ -0,0 +1,21 @@
+// 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.2.1;
+
+include "./lib/check-nullifiers-tokenid-uri.circom";
+include "./node_modules/circomlib/circuits/babyjub.circom";
+
+component main { public [ nullifiers ] } = CheckNullifiersForTokenIdAndUri(2);
\ No newline at end of file
diff --git a/zkp/circuits/check_nullifiers_owner.circom b/zkp/circuits/check_nullifiers_owner.circom
new file mode 100644
index 00000000..1e1efeb9
--- /dev/null
+++ b/zkp/circuits/check_nullifiers_owner.circom
@@ -0,0 +1,21 @@
+// 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.2.1;
+
+include "./lib/check-nullifiers.circom";
+include "./node_modules/circomlib/circuits/babyjub.circom";
+
+component main { public [ nullifiers ] } = CheckNullifiers(2);
\ No newline at end of file
diff --git a/zkp/circuits/check_nullifiers_owner_batch.circom b/zkp/circuits/check_nullifiers_owner_batch.circom
new file mode 100644
index 00000000..ecd8db13
--- /dev/null
+++ b/zkp/circuits/check_nullifiers_owner_batch.circom
@@ -0,0 +1,21 @@
+// 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.2.1;
+
+include "./lib/check-nullifiers.circom";
+include "./node_modules/circomlib/circuits/babyjub.circom";
+
+component main { public [ nullifiers ] } = CheckNullifiers(10);
\ No newline at end of file
diff --git a/zkp/circuits/check_nullifier_value.circom b/zkp/circuits/check_nullifiers_value.circom
similarity index 93%
rename from zkp/circuits/check_nullifier_value.circom
rename to zkp/circuits/check_nullifiers_value.circom
index 8c447a2d..508d0f3e 100644
--- a/zkp/circuits/check_nullifier_value.circom
+++ b/zkp/circuits/check_nullifiers_value.circom
@@ -15,6 +15,6 @@
// limitations under the License.
pragma circom 2.2.1;
-include "./lib/check-nullifier-value-base.circom";
+include "./lib/check-nullifiers-value-base.circom";
component main { public [ nullifiers, outputCommitments, root, enabled ] } = CheckNullifiersInputsOutputsValue(2, 1, 64);
diff --git a/zkp/circuits/check_nullifier_value_batch.circom b/zkp/circuits/check_nullifiers_value_batch.circom
similarity index 93%
rename from zkp/circuits/check_nullifier_value_batch.circom
rename to zkp/circuits/check_nullifiers_value_batch.circom
index 5f4fabe8..07d15948 100644
--- a/zkp/circuits/check_nullifier_value_batch.circom
+++ b/zkp/circuits/check_nullifiers_value_batch.circom
@@ -15,6 +15,6 @@
// limitations under the License.
pragma circom 2.2.1;
-include "./lib/check-nullifier-value-base.circom";
+include "./lib/check-nullifiers-value-base.circom";
component main { public [ nullifiers, outputCommitments, root, enabled ] } = CheckNullifiersInputsOutputsValue(10, 1, 64);
diff --git a/zkp/js/test/circuits/check-nullifier-tokenid-uri.circom b/zkp/circuits/check_utxos_nf_owner.circom
similarity index 82%
rename from zkp/js/test/circuits/check-nullifier-tokenid-uri.circom
rename to zkp/circuits/check_utxos_nf_owner.circom
index 75ecbcda..4df74897 100644
--- a/zkp/js/test/circuits/check-nullifier-tokenid-uri.circom
+++ b/zkp/circuits/check_utxos_nf_owner.circom
@@ -15,6 +15,6 @@
// limitations under the License.
pragma circom 2.2.1;
-include "../../../circuits/lib/check-nullifier-tokenid-uri.circom";
+include "./lib/check-utxos-nf-owner.circom";
-component main {public [ nullifiers ]} = CheckNullifierForTokenIdAndUri(1);
\ No newline at end of file
+component main {public [ commitments ]} = CheckUTXOsNFOwner(2);
diff --git a/zkp/circuits/check_utxos_owner.circom b/zkp/circuits/check_utxos_owner.circom
new file mode 100644
index 00000000..470ca576
--- /dev/null
+++ b/zkp/circuits/check_utxos_owner.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.2.1;
+
+include "./lib/check-utxos-owner.circom";
+
+component main {public [ commitments ]} = CheckUTXOsOwner(2);
diff --git a/zkp/circuits/check_utxos_owner_batch.circom b/zkp/circuits/check_utxos_owner_batch.circom
new file mode 100644
index 00000000..919b32d8
--- /dev/null
+++ b/zkp/circuits/check_utxos_owner_batch.circom
@@ -0,0 +1,20 @@
+// Copyright © 2024 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+pragma circom 2.2.1;
+
+include "./lib/check-utxos-owner.circom";
+
+component main {public [ commitments ]} = CheckUTXOsOwner(10);
diff --git a/zkp/circuits/gen-config.json b/zkp/circuits/gen-config.json
index e4a0f3ca..78101f8a 100644
--- a/zkp/circuits/gen-config.json
+++ b/zkp/circuits/gen-config.json
@@ -53,11 +53,29 @@
"batchPtau": "powersOfTau28_hez_final_14",
"skipSolidityGenaration": false
},
- "check_nullifier_value": {
+ "check_nullifiers_value": {
"ptau": "powersOfTau28_hez_final_17",
"batchPtau": "powersOfTau28_hez_final_19",
"skipSolidityGenaration": false
},
+ "check_utxos_owner": {
+ "ptau": "powersOfTau28_hez_final_13",
+ "batchPtau": "powersOfTau28_hez_final_14",
+ "skipSolidityGenaration": false
+ },
+ "check_utxos_nf_owner": {
+ "ptau": "powersOfTau28_hez_final_13",
+ "skipSolidityGenaration": false
+ },
+ "check_nullifiers_owner": {
+ "ptau": "powersOfTau28_hez_final_11",
+ "batchPtau": "powersOfTau28_hez_final_13",
+ "skipSolidityGenaration": false
+ },
+ "check_nullifiers_nf_owner": {
+ "ptau": "powersOfTau28_hez_final_11",
+ "skipSolidityGenaration": false
+ },
"check_nullifiers": {
"ptau": "powersOfTau28_hez_final_13",
"skipSolidityGenaration": true
diff --git a/zkp/circuits/lib/check-nullifier-tokenid-uri.circom b/zkp/circuits/lib/check-nullifiers-tokenid-uri.circom
similarity index 92%
rename from zkp/circuits/lib/check-nullifier-tokenid-uri.circom
rename to zkp/circuits/lib/check-nullifiers-tokenid-uri.circom
index ff574b8e..5575cdcd 100644
--- a/zkp/circuits/lib/check-nullifier-tokenid-uri.circom
+++ b/zkp/circuits/lib/check-nullifiers-tokenid-uri.circom
@@ -20,7 +20,7 @@ include "../node_modules/circomlib/circuits/comparators.circom";
include "../node_modules/circomlib/circuits/babyjub.circom";
include "../node_modules/circomlib/circuits/smt/smtverifier.circom";
-// CheckNullifierForTokenIdAndUri is a circuit that checks the integrity of transactions of Non-Fungible Tokens
+// CheckNullifiersForTokenIdAndUri is a circuit that checks the integrity of transactions of Non-Fungible Tokens
// - check that the nullifiers are correctly computed from the token ids, uris and salts
// - check that the input commitments match the calculated hashes
// - check that the input commitments are included in the Sparse Merkle Tree with the root `root`
@@ -28,7 +28,7 @@ include "../node_modules/circomlib/circuits/smt/smtverifier.circom";
// commitment = hash(tokenId, uri, salt, ownerPublicKey1, ownerPublicKey2)
// nullifier = hash(tokenId, uri, salt, ownerPrivatekey)
//
-template CheckNullifierForTokenIdAndUri(numInputs) {
+template CheckNullifiersForTokenIdAndUri(numInputs) {
signal input tokenIds[numInputs];
signal input tokenUris[numInputs];
signal input nullifiers[numInputs];
diff --git a/zkp/circuits/lib/check-nullifier-value-base.circom b/zkp/circuits/lib/check-nullifiers-value-base.circom
similarity index 100%
rename from zkp/circuits/lib/check-nullifier-value-base.circom
rename to zkp/circuits/lib/check-nullifiers-value-base.circom
diff --git a/zkp/circuits/lib/check-utxos-nf-owner.circom b/zkp/circuits/lib/check-utxos-nf-owner.circom
new file mode 100644
index 00000000..875b1f5b
--- /dev/null
+++ b/zkp/circuits/lib/check-utxos-nf-owner.circom
@@ -0,0 +1,45 @@
+// 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.2.1;
+
+include "./check-hashes-tokenid-uri.circom";
+include "../node_modules/circomlib/circuits/babyjub.circom";
+
+// This version of the circuit performs the following operations:
+// - derive the sender's public key from the sender's private key
+// - check the commitments match the calculated hashes for a non-fungible UTXO
+template CheckUTXOsNFOwner(nInputs) {
+ signal input commitments[nInputs];
+ signal input tokenIds[nInputs];
+ signal input tokenUris[nInputs];
+ signal input salts[nInputs];
+ // 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 ownerPrivateKey;
+
+ // 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 ownerPubKeyAx, ownerPubKeyAy;
+ (ownerPubKeyAx, ownerPubKeyAy) = BabyPbk()(in <== ownerPrivateKey);
+
+ var ownerPublicKeys[nInputs][2];
+ for (var i = 0; i < nInputs; i++) {
+ ownerPublicKeys[i]= [ownerPubKeyAx, ownerPubKeyAy];
+ }
+ CheckHashesForTokenIdAndUri(nInputs)(commitments <== commitments, tokenIds <== tokenIds, tokenUris <== tokenUris, salts <== salts, ownerPublicKeys <== ownerPublicKeys);
+}
diff --git a/zkp/circuits/lib/check-utxos-owner.circom b/zkp/circuits/lib/check-utxos-owner.circom
new file mode 100644
index 00000000..2d54ed8b
--- /dev/null
+++ b/zkp/circuits/lib/check-utxos-owner.circom
@@ -0,0 +1,44 @@
+// 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.2.1;
+
+include "./check-hashes.circom";
+include "../node_modules/circomlib/circuits/babyjub.circom";
+
+// This version of the circuit performs the following operations:
+// - derive the sender's public key from the sender's private key
+// - check the commitments match the calculated hashes
+template CheckUTXOsOwner(nInputs) {
+ signal input commitments[nInputs];
+ signal input values[nInputs];
+ signal input salts[nInputs];
+ // 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 ownerPrivateKey;
+
+ // 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 ownerPubKeyAx, ownerPubKeyAy;
+ (ownerPubKeyAx, ownerPubKeyAy) = BabyPbk()(in <== ownerPrivateKey);
+
+ var ownerPublicKeys[nInputs][2];
+ for (var i = 0; i < nInputs; i++) {
+ ownerPublicKeys[i]= [ownerPubKeyAx, ownerPubKeyAy];
+ }
+ CheckHashes(nInputs)(commitments <== commitments, values <== values, salts <== salts, ownerPublicKeys <== ownerPublicKeys);
+}
diff --git a/zkp/circuits/nf_anon_nullifier.circom b/zkp/circuits/nf_anon_nullifier.circom
index 00f1093e..d6e4605a 100644
--- a/zkp/circuits/nf_anon_nullifier.circom
+++ b/zkp/circuits/nf_anon_nullifier.circom
@@ -15,7 +15,7 @@
// limitations under the License.
pragma circom 2.2.1;
-include "./lib/check-nullifier-tokenid-uri.circom";
+include "./lib/check-nullifiers-tokenid-uri.circom";
include "./lib/check-hashes-tokenid-uri.circom";
include "./lib/check-smt-proof.circom";
include "./node_modules/circomlib/circuits/babyjub.circom";
@@ -54,7 +54,7 @@ template Zeto(nSMTLevels) {
CheckHashesForTokenIdAndUri(1)(tokenIds <== [tokenId], tokenUris <== [tokenUri], commitments <== [outputCommitment], salts <== [outputSalt], ownerPublicKeys <== [outputOwnerPublicKey]);
- CheckNullifierForTokenIdAndUri(1)(nullifiers <== [nullifier], tokenIds <== [tokenId], tokenUris <== [tokenUri], salts <== [inputSalt], ownerPrivateKey <== inputOwnerPrivateKey);
+ CheckNullifiersForTokenIdAndUri(1)(nullifiers <== [nullifier], tokenIds <== [tokenId], tokenUris <== [tokenUri], salts <== [inputSalt], ownerPrivateKey <== inputOwnerPrivateKey);
CheckSMTProof(1, nSMTLevels)(root <== root, merkleProof <== [merkleProof], enabled <== [1], leafNodeIndexes <== [inputCommitment]);
}
diff --git a/zkp/js/integration-test/check_nullifiers_owner.js b/zkp/js/integration-test/check_nullifiers_owner.js
new file mode 100644
index 00000000..7ff6dc56
--- /dev/null
+++ b/zkp/js/integration-test/check_nullifiers_owner.js
@@ -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.
+
+const { expect } = require('chai');
+const { groth16 } = require('snarkjs');
+const { genKeypair, formatPrivKeyForBabyJub } = require('maci-crypto');
+const { Poseidon, newSalt, loadCircuit } = require('../index.js');
+const { loadProvingKeys } = require('./utils.js');
+
+const poseidonHash3 = Poseidon.poseidon3;
+
+describe('check_nullifiers_owner circuit tests', () => {
+ let circuit, provingKeyFile, verificationKey;
+
+ const Alice = {};
+ let senderPrivateKey;
+
+ before(async () => {
+ circuit = await loadCircuit('check_nullifiers_owner');
+ ({ provingKeyFile, verificationKey } = loadProvingKeys('check_nullifiers_owner'));
+
+ let keypair = genKeypair();
+ Alice.privKey = keypair.privKey;
+ Alice.pubKey = keypair.pubKey;
+ senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey);
+ });
+
+ it('should generate a valid proof that can be verified successfully and fail when public signals are tampered', async () => {
+ const values = [15, 100];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey);
+ const salt1 = newSalt();
+ const salt2 = newSalt();
+
+ // create the nullifiers for the input UTXOs
+ const nullifier1 = poseidonHash3([BigInt(values[0]), salt1, senderPrivateKey]);
+ const nullifier2 = poseidonHash3([BigInt(values[1]), salt2, senderPrivateKey]);
+ const nullifiers = [nullifier1, nullifier2];
+
+ const startTime = Date.now();
+ const witness = await circuit.calculateWTNSBin(
+ {
+ nullifiers,
+ values,
+ salts: [salt1, salt2],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true
+ );
+
+ const { proof, publicSignals } = await groth16.prove(provingKeyFile, witness);
+ console.log('Proving time: ', (Date.now() - startTime) / 1000, 's');
+
+ let verifyResult = await groth16.verify(verificationKey, publicSignals, proof);
+ expect(verifyResult).to.be.true;
+ const tamperedNullifier = poseidonHash3([BigInt(values[0] + 1), salt1, senderPrivateKey]);
+ let tamperedPublicSignals = publicSignals.map((ps) => (ps.toString() === nullifiers[0].toString() ? tamperedNullifier : ps));
+
+ verifyResult = await groth16.verify(verificationKey, tamperedPublicSignals, proof);
+ expect(verifyResult).to.be.false;
+ }).timeout(600000);
+});
diff --git a/zkp/js/integration-test/check_nullifier_value.js b/zkp/js/integration-test/check_nullifiers_value.js
similarity index 97%
rename from zkp/js/integration-test/check_nullifier_value.js
rename to zkp/js/integration-test/check_nullifiers_value.js
index cf36a79f..0419fb74 100644
--- a/zkp/js/integration-test/check_nullifier_value.js
+++ b/zkp/js/integration-test/check_nullifiers_value.js
@@ -30,16 +30,16 @@ const SMT_HEIGHT = 64;
const poseidonHash = Poseidon.poseidon4;
const poseidonHash3 = Poseidon.poseidon3;
-describe("check_nullifier_value circuit tests", () => {
+describe("check_nullifiers_value circuit tests", () => {
let circuit, provingKeyFile, verificationKey, smtAlice;
const Alice = {};
let senderPrivateKey;
before(async () => {
- circuit = await loadCircuit("check_nullifier_value");
+ circuit = await loadCircuit("check_nullifiers_value");
({ provingKeyFile, verificationKey } = loadProvingKeys(
- "check_nullifier_value",
+ "check_nullifiers_value",
));
let keypair = genKeypair();
diff --git a/zkp/js/integration-test/check_utxos_owner.js b/zkp/js/integration-test/check_utxos_owner.js
new file mode 100644
index 00000000..a2a27b45
--- /dev/null
+++ b/zkp/js/integration-test/check_utxos_owner.js
@@ -0,0 +1,109 @@
+// 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 { Poseidon, newSalt, loadCircuit } = require("../index.js");
+const { loadProvingKeys } = require("./utils.js");
+
+const poseidonHash = Poseidon.poseidon4;
+const poseidonHash3 = Poseidon.poseidon3;
+
+describe("check_utxos_owner circuit tests", () => {
+ let circuit, provingKeyFile, verificationKey, smtAlice;
+
+ const Alice = {};
+ let senderPrivateKey;
+
+ before(async () => {
+ circuit = await loadCircuit("check_utxos_owner");
+ ({ provingKeyFile, verificationKey } = loadProvingKeys(
+ "check_utxos_owner",
+ ));
+
+ let keypair = genKeypair();
+ Alice.privKey = keypair.privKey;
+ Alice.pubKey = keypair.pubKey;
+ senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey);
+ });
+
+ it("should generate a valid proof that can be verified successfully and fail when public signals are tampered", async () => {
+ const values = [15, 100];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey);
+ const salt1 = newSalt();
+ const input1 = poseidonHash([
+ BigInt(values[0]),
+ salt1,
+ ...Alice.pubKey,
+ ]);
+ const salt2 = newSalt();
+ const input2 = poseidonHash([
+ BigInt(values[1]),
+ salt2,
+ ...Alice.pubKey,
+ ]);
+ const commitments = [input1, input2];
+
+ const startTime = Date.now();
+ const witness = await circuit.calculateWTNSBin(
+ {
+ commitments,
+ values,
+ salts: [salt1, salt2],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true,
+ );
+
+ const { proof, publicSignals } = await groth16.prove(
+ provingKeyFile,
+ witness,
+ );
+ console.log("Proving time: ", (Date.now() - startTime) / 1000, "s");
+
+ let verifyResult = await groth16.verify(
+ verificationKey,
+ publicSignals,
+ proof,
+ );
+ expect(verifyResult).to.be.true;
+ // console.log('nullifiers', nullifiers);
+ // console.log('inputCommitments', inputCommitments);
+ // console.log('outputCommitments', outputCommitments);
+ // console.log('root', proof1.root.bigInt());
+ // console.log("public signals", publicSignals);
+ const tamperedCommitment = poseidonHash([
+ BigInt(values[0] + 1),
+ salt1,
+ ...Alice.pubKey,
+ ]);
+ let tamperedPublicSignals = publicSignals.map((ps) =>
+ ps.toString() === commitments[0].toString()
+ ? tamperedCommitment
+ : ps,
+ );
+
+ verifyResult = await groth16.verify(
+ verificationKey,
+ tamperedPublicSignals,
+ proof,
+ );
+ expect(verifyResult).to.be.false;
+ }).timeout(600000);
+});
diff --git a/zkp/js/test/lib/check-nullifier-tokenid-uri.js b/zkp/js/test/check_nullifiers_nf_owner.js
similarity index 50%
rename from zkp/js/test/lib/check-nullifier-tokenid-uri.js
rename to zkp/js/test/check_nullifiers_nf_owner.js
index b40c6688..ca62e5d1 100644
--- a/zkp/js/test/lib/check-nullifier-tokenid-uri.js
+++ b/zkp/js/test/check_nullifiers_nf_owner.js
@@ -14,24 +14,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-const { expect } = require("chai");
-const { join } = require("path");
-const { wasm: wasm_tester } = require("circom_tester");
-const { genKeypair, formatPrivKeyForBabyJub } = require("maci-crypto");
-const { Poseidon, newSalt, tokenUriHash } = require("../../index.js");
-const {
- Merkletree,
- InMemoryDB,
- str2Bytes,
- ZERO_HASH,
-} = require("@iden3/js-merkletree");
+const { expect } = require('chai');
+const { join } = require('path');
+const { wasm: wasm_tester } = require('circom_tester');
+const { genKeypair, formatPrivKeyForBabyJub } = require('maci-crypto');
+const { Poseidon, newSalt, tokenUriHash } = require('../index.js');
+const { Merkletree, InMemoryDB, str2Bytes, ZERO_HASH } = require('@iden3/js-merkletree');
-const SMT_HEIGHT = 64;
-const poseidonHash = Poseidon.poseidon5;
const poseidonHash4 = Poseidon.poseidon4;
-describe("check-nullifier-tokenid-uri circuit tests", () => {
- let circuit, smt;
+describe('check_nullifiers_nf_owner circuit tests', () => {
+ let circuit;
const sender = {};
const receiver = {};
let senderPrivateKey;
@@ -39,9 +32,7 @@ describe("check-nullifier-tokenid-uri circuit tests", () => {
before(async function () {
this.timeout(60000);
- circuit = await wasm_tester(
- join(__dirname, "../circuits/check-nullifier-tokenid-uri.circom"),
- );
+ circuit = await wasm_tester(join(__dirname, '../../circuits/check_nullifiers_nf_owner.circom'));
let keypair = genKeypair();
sender.privKey = keypair.privKey;
@@ -51,32 +42,24 @@ describe("check-nullifier-tokenid-uri circuit tests", () => {
keypair = genKeypair();
receiver.privKey = keypair.privKey;
receiver.pubKey = keypair.pubKey;
-
- const storage = new InMemoryDB(str2Bytes(""));
- smt = new Merkletree(storage, true, SMT_HEIGHT);
});
- it("should return true for valid witness", async () => {
+ it('should return true for valid witness', async () => {
const tokenId = 1001;
- const tokenUri = tokenUriHash("http://ipfs.io/some-file-hash");
+ const tokenUri = tokenUriHash('http://ipfs.io/some-file-hash');
const salt1 = newSalt();
- const nullifier1 = poseidonHash4([
- BigInt(tokenId),
- tokenUri,
- salt1,
- senderPrivateKey,
- ]);
+ const nullifier1 = poseidonHash4([BigInt(tokenId), tokenUri, salt1, senderPrivateKey]);
const witness = await circuit.calculateWitness(
{
- tokenIds: [tokenId],
- tokenUris: [tokenUri],
- nullifiers: [nullifier1],
- salts: [salt1],
+ tokenIds: [tokenId, 0],
+ tokenUris: [tokenUri, 0],
+ nullifiers: [nullifier1, 0],
+ salts: [salt1, 0],
ownerPrivateKey: senderPrivateKey,
},
- true,
+ true
);
// console.log('tokenUri', tokenUri);
@@ -85,41 +68,34 @@ describe("check-nullifier-tokenid-uri circuit tests", () => {
// console.log(witness.slice(0, 10));
expect(witness[1]).to.equal(BigInt(nullifier1));
- expect(witness[2]).to.equal(BigInt(tokenId));
- expect(witness[3]).to.equal(tokenUri);
- expect(witness[4]).to.equal(salt1);
+ expect(witness[3]).to.equal(BigInt(tokenId));
+ expect(witness[5]).to.equal(tokenUri);
+ expect(witness[7]).to.equal(salt1);
});
- it("should fail to calculate witness due to invalid nullifier", async () => {
+ it('should fail to calculate witness due to invalid nullifier', async () => {
const tokenId = 1001;
- const tokenUri = tokenUriHash("http://ipfs.io/some-file-hash");
+ const tokenUri = tokenUriHash('http://ipfs.io/some-file-hash');
const salt1 = newSalt();
- const nullifier1 = poseidonHash4([
- BigInt(tokenId),
- tokenUri,
- salt1,
- senderPrivateKey,
- ]);
+ const nullifier1 = poseidonHash4([BigInt(tokenId), tokenUri, salt1, senderPrivateKey]);
let error;
try {
const witness = await circuit.calculateWitness(
{
- tokenIds: [tokenId],
- tokenUris: [tokenUri],
- nullifiers: [nullifier1 + BigInt(1)],
- salts: [salt1],
+ tokenIds: [tokenId, 0],
+ tokenUris: [tokenUri, 0],
+ nullifiers: [nullifier1 + BigInt(1), 0],
+ salts: [salt1, 0],
ownerPrivateKey: senderPrivateKey,
},
- true,
+ true
);
} catch (e) {
error = e;
}
// console.log(error);
- expect(error).to.match(
- /Error in template CheckNullifierForTokenIdAndUri_76 line: 52/,
- );
+ expect(error).to.match(/Error in template CheckNullifiersForTokenIdAndUri_76 line: 52/);
});
});
diff --git a/zkp/js/test/check_nullifiers_owner.js b/zkp/js/test/check_nullifiers_owner.js
new file mode 100644
index 00000000..8bce438a
--- /dev/null
+++ b/zkp/js/test/check_nullifiers_owner.js
@@ -0,0 +1,106 @@
+// 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 { join } = require('path');
+const { wasm: wasm_tester } = require('circom_tester');
+const { genKeypair, formatPrivKeyForBabyJub } = require('maci-crypto');
+const { Poseidon, newSalt } = require('../index.js');
+
+const poseidonHash3 = Poseidon.poseidon3;
+
+describe('check_nullifiers_owner circuit tests', () => {
+ let circuit;
+ const sender = {};
+ const receiver = {};
+ let senderPrivateKey;
+
+ before(async function () {
+ this.timeout(60000);
+
+ circuit = await wasm_tester(join(__dirname, '../../circuits/check_nullifiers_owner.circom'));
+
+ let keypair = genKeypair();
+ sender.privKey = keypair.privKey;
+ sender.pubKey = keypair.pubKey;
+ senderPrivateKey = formatPrivKeyForBabyJub(sender.privKey);
+
+ keypair = genKeypair();
+ receiver.privKey = keypair.privKey;
+ receiver.pubKey = keypair.pubKey;
+ });
+
+ it('should return true for valid witness', async () => {
+ const values = [32, 40];
+ const salt1 = newSalt();
+ const salt2 = newSalt();
+
+ // create two input nullifiers, corresponding to the input UTXOs
+ const nullifier1 = poseidonHash3([BigInt(values[0]), salt1, senderPrivateKey]);
+ const nullifier2 = poseidonHash3([BigInt(values[1]), salt2, senderPrivateKey]);
+ const nullifiers = [nullifier1, nullifier2];
+
+ const witness = await circuit.calculateWitness(
+ {
+ nullifiers,
+ values,
+ salts: [salt1, salt2],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true
+ );
+
+ // console.log('nullifiers', nullifiers);
+ // console.log('inputValues', inputValues);
+ // console.log('inputSalts', [salt1, salt2]);
+ // console.log('owner private key', senderPrivateKey);
+ // console.log(witness.slice(0, 15));
+
+ expect(witness[1]).to.equal(BigInt(nullifiers[0]));
+ expect(witness[2]).to.equal(BigInt(nullifiers[1]));
+ expect(witness[3]).to.equal(BigInt(values[0]));
+ expect(witness[4]).to.equal(BigInt(values[1]));
+ expect(witness[7]).to.equal(senderPrivateKey);
+ });
+
+ it('should fail to generate a witness because incorrect values are not used', async () => {
+ const values = [15, 100];
+ const salt1 = newSalt();
+ const salt2 = newSalt();
+
+ // create two input nullifiers, corresponding to the input UTXOs
+ const nullifier1 = poseidonHash3([BigInt(values[0]), salt1, senderPrivateKey]);
+ const nullifier2 = poseidonHash3([BigInt(values[1] + 1), salt2, senderPrivateKey]);
+ const nullifiers = [nullifier1, nullifier2];
+
+ let err;
+ try {
+ await circuit.calculateWitness(
+ {
+ nullifiers,
+ values,
+ salts: [salt1, salt2],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true
+ );
+ } catch (e) {
+ err = e;
+ }
+ // console.log(err);
+ expect(err).to.match(/Error in template CheckNullifiers_72 line: 51/);
+ });
+});
diff --git a/zkp/js/test/check_nullifier_value.js b/zkp/js/test/check_nullifiers_value.js
similarity index 98%
rename from zkp/js/test/check_nullifier_value.js
rename to zkp/js/test/check_nullifiers_value.js
index 8c5ad507..0b73210e 100644
--- a/zkp/js/test/check_nullifier_value.js
+++ b/zkp/js/test/check_nullifiers_value.js
@@ -30,7 +30,7 @@ const SMT_HEIGHT = 64;
const poseidonHash = Poseidon.poseidon4;
const poseidonHash3 = Poseidon.poseidon3;
-describe("check_nullifier_value circuit tests", () => {
+describe("check_nullifiers_value circuit tests", () => {
let circuit, smtAlice;
const Alice = {};
@@ -40,7 +40,7 @@ describe("check_nullifier_value circuit tests", () => {
this.timeout(60000);
circuit = await wasm_tester(
- join(__dirname, "../../circuits/check_nullifier_value.circom"),
+ join(__dirname, "../../circuits/check_nullifiers_value.circom"),
);
let keypair = genKeypair();
diff --git a/zkp/js/test/check_utxos_nf_owner.js b/zkp/js/test/check_utxos_nf_owner.js
new file mode 100644
index 00000000..c433d5dd
--- /dev/null
+++ b/zkp/js/test/check_utxos_nf_owner.js
@@ -0,0 +1,98 @@
+// 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 { join } = require('path');
+const { wasm: wasm_tester } = require('circom_tester');
+const { genKeypair, formatPrivKeyForBabyJub } = require('maci-crypto');
+const { Poseidon, newSalt, tokenUriHash } = require('../index.js');
+
+const poseidonHash = Poseidon.poseidon5;
+
+describe('check_utxos_nf_owner circuit tests', () => {
+ let circuit;
+ const sender = {};
+ let senderPrivateKey;
+
+ before(async function () {
+ this.timeout(60000);
+
+ circuit = await wasm_tester(join(__dirname, '../../circuits/check_utxos_nf_owner.circom'));
+
+ let keypair = genKeypair();
+ sender.privKey = keypair.privKey;
+ sender.pubKey = keypair.pubKey;
+ senderPrivateKey = formatPrivKeyForBabyJub(sender.privKey);
+ });
+
+ it('should return true for valid witness', async () => {
+ const tokenIds = [1001, 0];
+ const tokenUris = [tokenUriHash('http://ipfs.io/some-file-hash'), 0];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const salt1 = newSalt();
+ const input1 = poseidonHash([BigInt(tokenIds[0]), tokenUris[0], salt1, ...sender.pubKey]);
+ const commitments = [input1, 0];
+
+ const witness = await circuit.calculateWitness(
+ {
+ commitments,
+ tokenIds,
+ tokenUris,
+ salts: [salt1, 0],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true
+ );
+
+ // console.log(witness.slice(0, 10));
+ // console.log('commitments', commitments);
+ // console.log('sender public key', sender.pubKey);
+ // console.log('sender private key', senderPrivateKey);
+ expect(witness[1]).to.equal(BigInt(commitments[0]));
+ expect(witness[9]).to.equal(BigInt(senderPrivateKey));
+ expect(witness[10]).to.equal(BigInt(sender.pubKey[0]));
+ expect(witness[11]).to.equal(BigInt(sender.pubKey[1]));
+ });
+
+ it('should fail to generate a witness because of invalid owner private key', async () => {
+ const tokenIds = [1001, 0];
+ const tokenUris = [tokenUriHash('http://ipfs.io/some-file-hash'), 0];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const salt1 = newSalt();
+ const input1 = poseidonHash([BigInt(tokenIds[0]), tokenUris[0], salt1, ...sender.pubKey]);
+ const commitments = [input1, 0];
+
+ let error;
+ try {
+ await circuit.calculateWitness(
+ {
+ commitments,
+ tokenIds,
+ tokenUris,
+ salts: [salt1, 0],
+ ownerPrivateKey: senderPrivateKey + BigInt(1),
+ },
+ true
+ );
+ } catch (e) {
+ error = e;
+ }
+ // console.log(error);
+ expect(error).to.match(/Error in template CheckHashesForTokenIdAndUri_88 line: 51/); // hash check failed
+ });
+});
diff --git a/zkp/js/test/check_utxos_owner.js b/zkp/js/test/check_utxos_owner.js
new file mode 100644
index 00000000..17fba515
--- /dev/null
+++ b/zkp/js/test/check_utxos_owner.js
@@ -0,0 +1,178 @@
+// 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 { join } = require('path');
+const { wasm: wasm_tester } = require('circom_tester');
+const { genKeypair, formatPrivKeyForBabyJub } = require('maci-crypto');
+const { Poseidon, newSalt } = require('../index.js');
+
+const ZERO_PUBKEY = [0n, 0n];
+const poseidonHash = Poseidon.poseidon4;
+
+describe('check_utxos_owner circuit tests', () => {
+ let circuit;
+ const sender = {};
+ let senderPrivateKey;
+
+ before(async function () {
+ this.timeout(60000);
+
+ circuit = await wasm_tester(join(__dirname, '../../circuits/check_utxos_owner.circom'));
+
+ let keypair = genKeypair();
+ sender.privKey = keypair.privKey;
+ sender.pubKey = keypair.pubKey;
+ senderPrivateKey = formatPrivKeyForBabyJub(sender.privKey);
+ });
+
+ it('should return true for valid witness', async () => {
+ const values = [32, 40];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const salt1 = newSalt();
+ const input1 = poseidonHash([BigInt(values[0]), salt1, ...sender.pubKey]);
+ const salt2 = newSalt();
+ const input2 = poseidonHash([BigInt(values[1]), salt2, ...sender.pubKey]);
+ const commitments = [input1, input2];
+
+ const witness = await circuit.calculateWitness(
+ {
+ commitments,
+ values,
+ salts: [salt1, salt2],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true
+ );
+
+ // console.log(witness.slice(0, 10));
+ // console.log('commitments', commitments);
+ // console.log('sender public key', sender.pubKey);
+ // console.log('sender private key', senderPrivateKey);
+ expect(witness[1]).to.equal(BigInt(commitments[0]));
+ expect(witness[2]).to.equal(BigInt(commitments[1]));
+ expect(witness[7]).to.equal(BigInt(senderPrivateKey));
+ expect(witness[8]).to.equal(BigInt(sender.pubKey[0]));
+ expect(witness[9]).to.equal(BigInt(sender.pubKey[1]));
+ });
+
+ it('should return true for valid witness using a single input value', async () => {
+ const values = [72, 0];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const salt1 = newSalt();
+ const input1 = poseidonHash([BigInt(values[0]), salt1, ...sender.pubKey]);
+ const commitments = [input1, 0];
+
+ const witness = await circuit.calculateWitness(
+ {
+ commitments,
+ values,
+ salts: [salt1, 0],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true
+ );
+
+ expect(witness[1]).to.equal(BigInt(commitments[0]));
+ expect(witness[2]).to.equal(BigInt(commitments[1]));
+ expect(witness[7]).to.equal(BigInt(senderPrivateKey));
+ expect(witness[8]).to.equal(BigInt(sender.pubKey[0]));
+ expect(witness[9]).to.equal(BigInt(sender.pubKey[1]));
+ });
+
+ it('should return true for valid witness using a single input value', async () => {
+ const values = [0, 72];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const salt1 = newSalt();
+ const input1 = poseidonHash([BigInt(values[1]), salt1, ...sender.pubKey]);
+ const commitments = [0n, input1];
+
+ const witness = await circuit.calculateWitness(
+ {
+ commitments,
+ values,
+ salts: [0, salt1],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true
+ );
+
+ expect(witness[1]).to.equal(BigInt(commitments[0]));
+ expect(witness[2]).to.equal(BigInt(commitments[1]));
+ expect(witness[7]).to.equal(BigInt(senderPrivateKey));
+ expect(witness[8]).to.equal(BigInt(sender.pubKey[0]));
+ expect(witness[9]).to.equal(BigInt(sender.pubKey[1]));
+ });
+
+ it('should fail to generate a witness because of invalid input commitments', async () => {
+ const inputValues = [25, 100];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const salt1 = newSalt();
+ const input1 = poseidonHash([BigInt(inputValues[0]), salt1, ...sender.pubKey]);
+ const salt2 = newSalt();
+ const input2 = poseidonHash([BigInt(inputValues[1]), salt2, ...sender.pubKey]);
+ const inputCommitments = [input1 + BigInt(1), input2];
+
+ let error;
+ try {
+ await circuit.calculateWitness(
+ {
+ commitments: inputCommitments,
+ values: inputValues,
+ salts: [salt1, salt2],
+ ownerPrivateKey: senderPrivateKey,
+ },
+ true
+ );
+ } catch (e) {
+ error = e;
+ }
+ // console.log(error);
+ expect(error).to.match(/Error in template CheckHashes_88 line: 47/); // hash check failed
+ });
+
+ it('should fail to generate a witness because of invalid owner private key', async () => {
+ const inputValues = [25, 100];
+
+ // create two input UTXOs, each has their own salt, but same owner
+ const salt1 = newSalt();
+ const input1 = poseidonHash([BigInt(inputValues[0]), salt1, ...sender.pubKey]);
+ const salt2 = newSalt();
+ const input2 = poseidonHash([BigInt(inputValues[1]), salt2, ...sender.pubKey]);
+ const inputCommitments = [input1, input2];
+
+ let error;
+ try {
+ await circuit.calculateWitness(
+ {
+ commitments: inputCommitments,
+ values: inputValues,
+ salts: [salt1, salt2],
+ ownerPrivateKey: senderPrivateKey + BigInt(1),
+ },
+ true
+ );
+ } catch (e) {
+ error = e;
+ }
+ // console.log(error);
+ expect(error).to.match(/Error in template CheckHashes_88 line: 47/); // hash check failed
+ });
+});
diff --git a/zkp/js/test/lib/check-hashes.js b/zkp/js/test/lib/check-hashes.js
index a744c432..cded1dcb 100644
--- a/zkp/js/test/lib/check-hashes.js
+++ b/zkp/js/test/lib/check-hashes.js
@@ -21,8 +21,6 @@ const { wasm: wasm_tester } = require("circom_tester");
const { genKeypair } = require("maci-crypto");
const { Poseidon, newSalt } = require("../../index.js");
-const MAX_VALUE = 2n ** 40n - 1n;
-const ZERO_PUBKEY = [0n, 0n];
const poseidonHash = Poseidon.poseidon4;
describe("check-hashes circuit tests", () => {