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", () => {