From 95d8bb71b74fddab9bf6bec48040e28bb40c0fcb Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Wed, 11 Dec 2024 12:55:00 -0500 Subject: [PATCH 01/13] Add new circuit to prove UTXO ownership only Signed-off-by: Jim Zhang --- zkp/circuits/lib/check-utxos-owner.circom | 46 +++++++ zkp/js/test/lib/check-utxos-owner.js | 151 ++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 zkp/circuits/lib/check-utxos-owner.circom create mode 100644 zkp/js/test/lib/check-utxos-owner.js diff --git a/zkp/circuits/lib/check-utxos-owner.circom b/zkp/circuits/lib/check-utxos-owner.circom new file mode 100644 index 0000000..51fe836 --- /dev/null +++ b/zkp/circuits/lib/check-utxos-owner.circom @@ -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 circom 2.1.9; + +include "../lib/check-positive.circom"; +include "../lib/check-hashes.circom"; +include "../lib/check-sum.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 inputCommitments[nInputs]; + signal input inputValues[nInputs]; + signal input inputSalts[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 inputOwnerPrivateKey; + + // 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 inputOwnerPubKeyAx, inputOwnerPubKeyAy; + (inputOwnerPubKeyAx, inputOwnerPubKeyAy) = BabyPbk()(in <== inputOwnerPrivateKey); + + var inputOwnerPublicKeys[nInputs][2]; + for (var i = 0; i < nInputs; i++) { + inputOwnerPublicKeys[i]= [inputOwnerPubKeyAx, inputOwnerPubKeyAy]; + } + CheckHashes(nInputs)(commitments <== inputCommitments, values <== inputValues, salts <== inputSalts, ownerPublicKeys <== inputOwnerPublicKeys); +} diff --git a/zkp/js/test/lib/check-utxos-owner.js b/zkp/js/test/lib/check-utxos-owner.js new file mode 100644 index 0000000..bd6b8d3 --- /dev/null +++ b/zkp/js/test/lib/check-utxos-owner.js @@ -0,0 +1,151 @@ +// 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 } = require('maci-crypto'); +const { Poseidon, newSalt } = require('../../index.js'); + +const ZERO_PUBKEY = [0n, 0n]; +const poseidonHash = Poseidon.poseidon4; + +describe('check-hashes circuit tests', () => { + let circuit; + const sender = {}; + const receiver = {}; + + before(async function () { + this.timeout(60000); + + circuit = await wasm_tester(join(__dirname, '../circuits/check-hashes.circom')); + + let keypair = genKeypair(); + sender.privKey = keypair.privKey; + sender.pubKey = keypair.pubKey; + + keypair = genKeypair(); + receiver.privKey = keypair.privKey; + receiver.pubKey = keypair.pubKey; + }); + + 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], + ownerPublicKeys: [sender.pubKey, sender.pubKey], + }, + true + ); + + // console.log(witness.slice(0, 10)); + // console.log('commitments', commitments); + // console.log('sender public key', sender.pubKey); + expect(witness[1]).to.equal(BigInt(commitments[0])); + expect(witness[2]).to.equal(BigInt(commitments[1])); + expect(witness[3]).to.equal(BigInt(sender.pubKey[0])); + expect(witness[4]).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], + ownerPublicKeys: [sender.pubKey, [0n, 0n]], + }, + true + ); + + expect(witness[1]).to.equal(BigInt(commitments[0])); + expect(witness[2]).to.equal(BigInt(commitments[1])); + expect(witness[3]).to.equal(BigInt(sender.pubKey[0])); + expect(witness[4]).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], + ownerPublicKeys: [[0n, 0n], sender.pubKey], + }, + true + ); + + expect(witness[1]).to.equal(BigInt(commitments[0])); + expect(witness[2]).to.equal(BigInt(commitments[1])); + expect(witness[3]).to.equal(0n); + expect(witness[4]).to.equal(0n); + expect(witness[5]).to.equal(BigInt(sender.pubKey[0])); + expect(witness[6]).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], + ownerPublicKeys: [sender.pubKey, sender.pubKey], + }, + true + ); + } catch (e) { + error = e; + } + // console.log(error); + expect(error).to.match(/Error in template CheckHashes_76 line: 47/); // hash check failed + }); +}); From 01beeb37ebef78f64a78bfec05675b9413d5ff28 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Thu, 12 Dec 2024 16:12:46 -0500 Subject: [PATCH 02/13] lock states for tokens not using nullifiers Signed-off-by: Jim Zhang --- solidity/contracts/factory.sol | 30 ++- ...e.sol => izeto_fungible_initializable.sol} | 4 +- .../lib/interfaces/izeto_lockable.sol | 46 ++++ .../lib/interfaces/izeto_nf_initializable.sol | 25 ++ .../lib/verifier_check_utxos_nf_owner.sol | 175 +++++++++++++ .../lib/verifier_check_utxos_owner.sol | 175 +++++++++++++ .../lib/verifier_check_utxos_owner_batch.sol | 231 ++++++++++++++++++ solidity/contracts/lib/zeto_base.sol | 9 - solidity/contracts/lib/zeto_common.sol | 24 +- solidity/contracts/lib/zeto_fungible.sol | 2 + .../contracts/lib/zeto_fungible_withdraw.sol | 2 + solidity/contracts/lib/zeto_lock.sol | 129 ++++++++++ solidity/contracts/lib/zeto_nullifier.sol | 3 +- solidity/contracts/zeto_anon.sol | 23 +- solidity/contracts/zeto_anon_enc.sol | 17 +- .../contracts/zeto_anon_enc_nullifier.sol | 1 - .../contracts/zeto_anon_enc_nullifier_kyc.sol | 1 - ...eto_anon_enc_nullifier_non_repudiation.sol | 1 - solidity/contracts/zeto_anon_nullifier.sol | 2 - .../contracts/zeto_anon_nullifier_kyc.sol | 2 - solidity/contracts/zeto_nf_anon.sol | 22 +- solidity/contracts/zeto_nf_anon_nullifier.sol | 2 - solidity/contracts/zkDvP.sol | 19 +- solidity/ignition/modules/lib/deps.ts | 23 ++ solidity/ignition/modules/zeto_anon.ts | 6 + solidity/ignition/modules/zeto_anon_enc.ts | 6 + solidity/ignition/modules/zeto_nf_anon.ts | 10 +- solidity/scripts/tokens/Zeto_Anon.ts | 4 + solidity/scripts/tokens/Zeto_AnonEnc.ts | 4 + solidity/scripts/tokens/Zeto_NfAnon.ts | 4 +- solidity/test/utils.ts | 101 +++++++- solidity/test/zeto_anon.ts | 58 ++++- solidity/test/zeto_anon_enc.ts | 47 ++++ solidity/test/zeto_nf_anon.ts | 48 +++- solidity/test/zkDvP.ts | 48 +--- .../circuits/check_utxos_nf_owner.circom | 8 +- zkp/circuits/check_utxos_owner.circom | 20 ++ zkp/circuits/check_utxos_owner_batch.circom | 20 ++ zkp/circuits/gen-config.json | 9 + zkp/circuits/lib/check-utxos-nf-owner.circom | 45 ++++ zkp/circuits/lib/check-utxos-owner.circom | 22 +- zkp/js/integration-test/check_utxos_owner.js | 169 +++++++++++++ zkp/js/test/check_utxos_nf_owner.js | 98 ++++++++ ...ck-utxos-owner.js => check_utxos_owner.js} | 71 ++++-- zkp/js/test/lib/check-hashes.js | 2 - 45 files changed, 1615 insertions(+), 153 deletions(-) rename solidity/contracts/lib/interfaces/{zeto_fungible_initializable.sol => izeto_fungible_initializable.sol} (89%) create mode 100644 solidity/contracts/lib/interfaces/izeto_lockable.sol create mode 100644 solidity/contracts/lib/interfaces/izeto_nf_initializable.sol create mode 100644 solidity/contracts/lib/verifier_check_utxos_nf_owner.sol create mode 100644 solidity/contracts/lib/verifier_check_utxos_owner.sol create mode 100644 solidity/contracts/lib/verifier_check_utxos_owner_batch.sol create mode 100644 solidity/contracts/lib/zeto_lock.sol rename solidity/contracts/lib/interfaces/zeto_nf_initializable.sol => zkp/circuits/check_utxos_nf_owner.circom (81%) create mode 100644 zkp/circuits/check_utxos_owner.circom create mode 100644 zkp/circuits/check_utxos_owner_batch.circom create mode 100644 zkp/circuits/lib/check-utxos-nf-owner.circom create mode 100644 zkp/js/integration-test/check_utxos_owner.js create mode 100644 zkp/js/test/check_utxos_nf_owner.js rename zkp/js/test/{lib/check-utxos-owner.js => check_utxos_owner.js} (65%) diff --git a/solidity/contracts/factory.sol b/solidity/contracts/factory.sol index de069ac..525699a 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 @@ -32,6 +32,8 @@ contract ZetoTokenFactory is Ownable { address verifier; address batchVerifier; address batchWithdrawVerifier; + address lockVerifier; + 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,14 @@ contract ZetoTokenFactory is Ownable { args.implementation != address(0), "Factory: failed to find implementation" ); + 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), @@ -117,7 +137,9 @@ contract ZetoTokenFactory is Ownable { ); (IZetoNonFungibleInitializable(instance)).initialize( initialOwner, - args.verifier + args.verifier, + args.lockVerifier, + args.batchLockVerifier ); emit ZetoTokenDeployed(instance); return instance; 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 2b0485f..4902974 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 0000000..aaa2c87 --- /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/izeto_nf_initializable.sol b/solidity/contracts/lib/interfaces/izeto_nf_initializable.sol new file mode 100644 index 0000000..eeef510 --- /dev/null +++ b/solidity/contracts/lib/interfaces/izeto_nf_initializable.sol @@ -0,0 +1,25 @@ +// 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; + +interface IZetoNonFungibleInitializable { + function initialize( + address initialOwner, + address _verifier, + address _lockVerifier, + address _batchLockVerifier + ) external; +} 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 0000000..16091cc --- /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 0000000..bb0d4ff --- /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 0000000..54aaf52 --- /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 35f4e0e..9fb8290 100644 --- a/solidity/contracts/lib/zeto_base.sol +++ b/solidity/contracts/lib/zeto_base.sol @@ -88,15 +88,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 79013a1..34f2859 100644 --- a/solidity/contracts/lib/zeto_common.sol +++ b/solidity/contracts/lib/zeto_common.sol @@ -24,6 +24,7 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own /// @author Kaleido, Inc. /// @dev Implements common functionalities of Zeto based tokens abstract contract ZetoCommon is OwnableUpgradeable { + uint256 public constant MAX_BATCH = 10; error UTXONotMinted(uint256 utxo); error UTXOAlreadyOwned(uint256 utxo); error UTXOAlreadySpent(uint256 utxo); @@ -31,32 +32,9 @@ abstract contract ZetoCommon is OwnableUpgradeable { 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; - 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 562876f..0e109de 100644 --- a/solidity/contracts/lib/zeto_fungible.sol +++ b/solidity/contracts/lib/zeto_fungible.sol @@ -17,6 +17,7 @@ pragma solidity ^0.8.20; import {Groth16Verifier_CheckHashesValue} from "./verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckNullifierValue} from "./verifier_check_nullifier_value.sol"; +import {ZetoBase} from "./zeto_base.sol"; import {Commonlib} from "./common.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -29,6 +30,7 @@ abstract contract ZetoFungible is OwnableUpgradeable { // this can be used in the optional deposit calls to verify that // the UTXOs match the deposited value Groth16Verifier_CheckHashesValue internal depositVerifier; + error WithdrawArrayTooLarge(uint256 maxAllowed); IERC20 internal erc20; diff --git a/solidity/contracts/lib/zeto_fungible_withdraw.sol b/solidity/contracts/lib/zeto_fungible_withdraw.sol index f943dff..f560cf1 100644 --- a/solidity/contracts/lib/zeto_fungible_withdraw.sol +++ b/solidity/contracts/lib/zeto_fungible_withdraw.sol @@ -18,6 +18,8 @@ pragma solidity ^0.8.20; import {Groth16Verifier_CheckHashesValue} from "./verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckInputsOutputsValue} from "./verifier_check_inputs_outputs_value.sol"; import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./verifier_check_inputs_outputs_value_batch.sol"; +import {Groth16Verifier_CheckUtxosOwner} from "./verifier_check_utxos_owner.sol"; +import {Groth16Verifier_CheckUtxosOwnerBatch} from "./verifier_check_utxos_owner_batch.sol"; import {ZetoFungible} from "./zeto_fungible.sol"; import {Commonlib} from "./common.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/solidity/contracts/lib/zeto_lock.sol b/solidity/contracts/lib/zeto_lock.sol new file mode 100644 index 0000000..27e4d18 --- /dev/null +++ b/solidity/contracts/lib/zeto_lock.sol @@ -0,0 +1,129 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +pragma 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 {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"; +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( + address _lockVerifier, + address _batchLockVerifier + ) public onlyInitializing { + lockVerifier = ILockVerifier(_lockVerifier); + batchLockVerifier = IBatchLockVerifier(_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 519d450..64a7206 100644 --- a/solidity/contracts/lib/zeto_nullifier.sol +++ b/solidity/contracts/lib/zeto_nullifier.sol @@ -23,12 +23,11 @@ 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 59bd4b8..a678716 100644 --- a/solidity/contracts/zeto_anon.sol +++ b/solidity/contracts/zeto_anon.sol @@ -16,9 +16,12 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.sol"; +import {ILockVerifier} 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"; @@ -26,11 +29,11 @@ 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,7 +44,13 @@ 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 { +contract Zeto_Anon is + IZeto, + ZetoBase, + ZetoFungibleWithdraw, + ZetoLock, + UUPSUpgradeable +{ Groth16Verifier_Anon internal verifier; Groth16Verifier_AnonBatch internal batchVerifier; @@ -51,7 +60,9 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, Groth16Verifier_AnonBatch _batchVerifier, - Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier + Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier, + address _lockVerifier, + address _batchLockVerifier ) public initializer { __ZetoBase_init(initialOwner); __ZetoFungibleWithdraw_init( @@ -59,6 +70,7 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { _withdrawVerifier, _batchWithdrawVerifier ); + __ZetoLock_init(_lockVerifier, _batchLockVerifier); verifier = _verifier; batchVerifier = _batchVerifier; } @@ -109,6 +121,11 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { "Invalid transaction proposal" ); + require( + validateLockedStates(inputs), + "At least one UTXO in the inputs are locked" + ); + // Check the proof if (inputs.length > 2 || outputs.length > 2) { uint256[] memory publicInputs = constructPublicInputs( diff --git a/solidity/contracts/zeto_anon_enc.sol b/solidity/contracts/zeto_anon_enc.sol index df91670..56718d8 100644 --- a/solidity/contracts/zeto_anon_enc.sol +++ b/solidity/contracts/zeto_anon_enc.sol @@ -19,17 +19,20 @@ import {IZetoEncrypted} from "./lib/interfaces/izeto_encrypted.sol"; import {Groth16Verifier_CheckHashesValue} from "./lib/verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckInputsOutputsValue} from "./lib/verifier_check_inputs_outputs_value.sol"; import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./lib/verifier_check_inputs_outputs_value_batch.sol"; +import {Groth16Verifier_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 {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"; -uint256 constant MAX_BATCH = 10; uint256 constant INPUT_SIZE = 15; uint256 constant BATCH_INPUT_SIZE = 63; @@ -46,6 +49,7 @@ contract Zeto_AnonEnc is IZetoEncrypted, ZetoBase, ZetoFungibleWithdraw, + ZetoLock, UUPSUpgradeable { Groth16Verifier_AnonEnc internal verifier; @@ -57,7 +61,9 @@ contract Zeto_AnonEnc is Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, Groth16Verifier_AnonEncBatch _batchVerifier, - Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier + Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier, + address _lockVerifier, + address _batchLockVerifier ) public initializer { __ZetoBase_init(initialOwner); __ZetoFungibleWithdraw_init( @@ -65,9 +71,9 @@ contract Zeto_AnonEnc is _withdrawVerifier, _batchWithdrawVerifier ); + __ZetoLock_init(_lockVerifier, _batchLockVerifier); verifier = _verifier; batchVerifier = _batchVerifier; - batchVerifier = _batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -133,6 +139,11 @@ contract Zeto_AnonEnc is "Invalid transaction proposal" ); + require( + validateLockedStates(inputs), + "At least one UTXO in the inputs are locked" + ); + // Check the proof if (inputs.length > 2 || outputs.length > 2) { uint256[] memory publicInputs = constructPublicInputs( diff --git a/solidity/contracts/zeto_anon_enc_nullifier.sol b/solidity/contracts/zeto_anon_enc_nullifier.sol index 7a5e010..5fd7170 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier.sol @@ -27,7 +27,6 @@ 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 = 18; uint256 constant BATCH_INPUT_SIZE = 74; diff --git a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol index 54e86b9..eaa399a 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol @@ -27,7 +27,6 @@ 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; diff --git a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol index d3da9db..818c9dc 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol @@ -26,7 +26,6 @@ import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_n import {Registry} from "./lib/registry.sol"; import {Commonlib} from "./lib/common.sol"; -uint256 constant MAX_BATCH = 10; uint256 constant INPUT_SIZE = 36; uint256 constant BATCH_INPUT_SIZE = 140; diff --git a/solidity/contracts/zeto_anon_nullifier.sol b/solidity/contracts/zeto_anon_nullifier.sol index ea6809c..5d1b31e 100644 --- a/solidity/contracts/zeto_anon_nullifier.sol +++ b/solidity/contracts/zeto_anon_nullifier.sol @@ -30,8 +30,6 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U 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; diff --git a/solidity/contracts/zeto_anon_nullifier_kyc.sol b/solidity/contracts/zeto_anon_nullifier_kyc.sol index 8ecb960..6cfede4 100644 --- a/solidity/contracts/zeto_anon_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_nullifier_kyc.sol @@ -31,8 +31,6 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U 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; diff --git a/solidity/contracts/zeto_nf_anon.sol b/solidity/contracts/zeto_nf_anon.sol index 864f239..fe89295 100644 --- a/solidity/contracts/zeto_nf_anon.sol +++ b/solidity/contracts/zeto_nf_anon.sol @@ -16,8 +16,12 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.sol"; +import {Groth16Verifier_CheckUtxosNfOwner} from "./lib/verifier_check_utxos_nf_owner.sol"; +import {IZetoLockable} from "./lib/interfaces/izeto_lockable.sol"; + import {Groth16Verifier_NfAnon} from "./lib/verifier_nf_anon.sol"; import {ZetoBase} from "./lib/zeto_base.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"; @@ -29,14 +33,23 @@ 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 { +contract Zeto_NfAnon is + IZeto, + IZetoLockable, + ZetoBase, + ZetoLock, + UUPSUpgradeable +{ Groth16Verifier_NfAnon internal verifier; function initialize( address initialOwner, - Groth16Verifier_NfAnon _verifier + Groth16Verifier_NfAnon _verifier, + address _lockVerifier, + address _batchLockVerifier ) public initializer { __ZetoBase_init(initialOwner); + __ZetoLock_init(_lockVerifier, _batchLockVerifier); verifier = _verifier; } @@ -67,6 +80,11 @@ contract Zeto_NfAnon is IZeto, ZetoBase, UUPSUpgradeable { "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; diff --git a/solidity/contracts/zeto_nf_anon_nullifier.sol b/solidity/contracts/zeto_nf_anon_nullifier.sol index b353e29..aa0dbcb 100644 --- a/solidity/contracts/zeto_nf_anon_nullifier.sol +++ b/solidity/contracts/zeto_nf_anon_nullifier.sol @@ -25,8 +25,6 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U 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. /// @dev The proof has the following statements: diff --git a/solidity/contracts/zkDvP.sol b/solidity/contracts/zkDvP.sol index 6578e83..83460ad 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 71df1eb..0b68536 100644 --- a/solidity/ignition/modules/lib/deps.ts +++ b/solidity/ignition/modules/lib/deps.ts @@ -71,6 +71,29 @@ 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 }; + }, +); + 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 65f2050..a73afa1 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 7710ab2..196b421 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_nf_anon.ts b/solidity/ignition/modules/zeto_nf_anon.ts index a1f8230..2f31221 100644 --- a/solidity/ignition/modules/zeto_nf_anon.ts +++ b/solidity/ignition/modules/zeto_nf_anon.ts @@ -15,7 +15,10 @@ // limitations under the License. import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; - +import { + NfLockVerifierModule, + BatchLockVerifierModule, +} from "./lib/deps"; const VerifierModule = buildModule("Groth16Verifier_NfAnon", (m) => { const verifier = m.contract("Groth16Verifier_NfAnon", []); return { verifier }; @@ -23,6 +26,9 @@ const VerifierModule = buildModule("Groth16Verifier_NfAnon", (m) => { export default buildModule("Zeto_NfAnon", (m) => { const { verifier } = m.useModule(VerifierModule); + const { verifier: lockVerifier } = m.useModule(NfLockVerifierModule); + // TODO: update this to use the correct batch verifier + const { verifier: batchLockVerifier } = m.useModule(BatchLockVerifierModule); - return { verifier }; + return { verifier, lockVerifier, batchLockVerifier }; }); diff --git a/solidity/scripts/tokens/Zeto_Anon.ts b/solidity/scripts/tokens/Zeto_Anon.ts index 500c0b5..7fef7aa 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 47cb0eb..13a5b4f 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_NfAnon.ts b/solidity/scripts/tokens/Zeto_NfAnon.ts index e18dce2..8a1766a 100644 --- a/solidity/scripts/tokens/Zeto_NfAnon.ts +++ b/solidity/scripts/tokens/Zeto_NfAnon.ts @@ -20,9 +20,9 @@ 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, batchLockVerifier, } = await ignition.deploy(zetoModule); return { deployer, - args: [await deployer.getAddress(), verifier.target], + args: [await deployer.getAddress(), verifier.target, lockVerifier.target, batchLockVerifier.target], }; } diff --git a/solidity/test/utils.ts b/solidity/test/utils.ts index b81d715..0c40d4d 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; @@ -213,3 +214,101 @@ 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 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 5a33699..35e1da5 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,50 @@ 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 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]); + await expect(sendTx(Bob, inputCommitments, outputCommitments, encodedProof)).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]); + await expect(sendTx(Alice, inputCommitments, outputCommitments, encodedProof)).to.be.fulfilled; + }); + }); + async function doTransfer( signer: User, inputs: UTXO[], @@ -417,7 +462,6 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi signer, inputCommitments, outputCommitments, - outputOwnerAddresses, encodedProof, ); } @@ -426,10 +470,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 +482,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 0e29e3c..7232aeb 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,52 @@ 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 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); + await expect(sendTx(Alice, inputCommitments, outputCommitments, encryptedValues, encryptionNonce, encodedProof, ephemeralKeypair.pubKey)).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); + 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_nf_anon.ts b/solidity/test/zeto_nf_anon.ts index 9fa70e2..754fc72 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,50 @@ 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); + 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 2c96007..60c2868 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/solidity/contracts/lib/interfaces/zeto_nf_initializable.sol b/zkp/circuits/check_utxos_nf_owner.circom similarity index 81% rename from solidity/contracts/lib/interfaces/zeto_nf_initializable.sol rename to zkp/circuits/check_utxos_nf_owner.circom index 00dd56b..688d7f3 100644 --- a/solidity/contracts/lib/interfaces/zeto_nf_initializable.sol +++ b/zkp/circuits/check_utxos_nf_owner.circom @@ -13,8 +13,8 @@ // 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; +pragma circom 2.1.9; -interface IZetoNonFungibleInitializable { - function initialize(address initialOwner, address _verifier) external; -} +include "./lib/check-utxos-nf-owner.circom"; + +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 0000000..0a24b82 --- /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.1.9; + +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 0000000..f9d29e0 --- /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.1.9; + +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 e4a0f3c..f72b1e9 100644 --- a/zkp/circuits/gen-config.json +++ b/zkp/circuits/gen-config.json @@ -58,6 +58,15 @@ "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": { "ptau": "powersOfTau28_hez_final_13", "skipSolidityGenaration": true 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 0000000..a62be09 --- /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.1.9; + +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 index 51fe836..ea2bc72 100644 --- a/zkp/circuits/lib/check-utxos-owner.circom +++ b/zkp/circuits/lib/check-utxos-owner.circom @@ -15,32 +15,30 @@ // limitations under the License. pragma circom 2.1.9; -include "../lib/check-positive.circom"; -include "../lib/check-hashes.circom"; -include "../lib/check-sum.circom"; +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 inputCommitments[nInputs]; - signal input inputValues[nInputs]; - signal input inputSalts[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 inputOwnerPrivateKey; + 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 inputOwnerPubKeyAx, inputOwnerPubKeyAy; - (inputOwnerPubKeyAx, inputOwnerPubKeyAy) = BabyPbk()(in <== inputOwnerPrivateKey); + var ownerPubKeyAx, ownerPubKeyAy; + (ownerPubKeyAx, ownerPubKeyAy) = BabyPbk()(in <== ownerPrivateKey); - var inputOwnerPublicKeys[nInputs][2]; + var ownerPublicKeys[nInputs][2]; for (var i = 0; i < nInputs; i++) { - inputOwnerPublicKeys[i]= [inputOwnerPubKeyAx, inputOwnerPubKeyAy]; + ownerPublicKeys[i]= [ownerPubKeyAx, ownerPubKeyAy]; } - CheckHashes(nInputs)(commitments <== inputCommitments, values <== inputValues, salts <== inputSalts, ownerPublicKeys <== inputOwnerPublicKeys); + CheckHashes(nInputs)(commitments <== commitments, values <== values, salts <== salts, ownerPublicKeys <== ownerPublicKeys); } 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 0000000..cf36a79 --- /dev/null +++ b/zkp/js/integration-test/check_utxos_owner.js @@ -0,0 +1,169 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const { expect } = require("chai"); +const { groth16 } = require("snarkjs"); +const { genKeypair, formatPrivKeyForBabyJub } = require("maci-crypto"); +const { + Merkletree, + InMemoryDB, + str2Bytes, + ZERO_HASH, +} = require("@iden3/js-merkletree"); +const { Poseidon, newSalt, loadCircuit } = require("../index.js"); +const { loadProvingKeys } = require("./utils.js"); + +const SMT_HEIGHT = 64; +const poseidonHash = Poseidon.poseidon4; +const poseidonHash3 = Poseidon.poseidon3; + +describe("check_nullifier_value circuit tests", () => { + let circuit, provingKeyFile, verificationKey, smtAlice; + + const Alice = {}; + let senderPrivateKey; + + before(async () => { + circuit = await loadCircuit("check_nullifier_value"); + ({ provingKeyFile, verificationKey } = loadProvingKeys( + "check_nullifier_value", + )); + + let keypair = genKeypair(); + Alice.privKey = keypair.privKey; + Alice.pubKey = keypair.pubKey; + senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey); + + // initialize the local storage for Alice to manage her UTXOs in the Spart Merkle Tree + const storage1 = new InMemoryDB(str2Bytes("")); + smtAlice = new Merkletree(storage1, true, SMT_HEIGHT); + }); + + it("should generate a valid proof that can be verified successfully and fail when public signals are tampered", async () => { + const inputValues = [15, 100]; + const outputValues = [35]; + + // create two input UTXOs, each has their own salt, but same owner + const senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey); + const salt1 = newSalt(); + const input1 = poseidonHash([ + BigInt(inputValues[0]), + salt1, + ...Alice.pubKey, + ]); + const salt2 = newSalt(); + const input2 = poseidonHash([ + BigInt(inputValues[1]), + salt2, + ...Alice.pubKey, + ]); + const inputCommitments = [input1, input2]; + + // create the nullifiers for the input UTXOs + const nullifier1 = poseidonHash3([ + BigInt(inputValues[0]), + salt1, + senderPrivateKey, + ]); + const nullifier2 = poseidonHash3([ + BigInt(inputValues[1]), + salt2, + senderPrivateKey, + ]); + const nullifiers = [nullifier1, nullifier2]; + + // calculate the root of the SMT + await smtAlice.add(input1, input1); + await smtAlice.add(input2, input2); + + // generate the merkle proof for the inputs + const proof1 = await smtAlice.generateCircomVerifierProof( + input1, + ZERO_HASH, + ); + const proof2 = await smtAlice.generateCircomVerifierProof( + input2, + ZERO_HASH, + ); + + // create two output UTXOs, they share the same salt, and different owner + const salt3 = newSalt(); + const output1 = poseidonHash([ + BigInt(outputValues[0]), + salt3, + ...Alice.pubKey, + ]); + const outputCommitments = [output1]; + + const startTime = Date.now(); + const witness = await circuit.calculateWTNSBin( + { + nullifiers, + inputCommitments, + inputValues, + inputSalts: [salt1, salt2], + inputOwnerPrivateKey: senderPrivateKey, + root: proof1.root.bigInt(), + merkleProof: [ + proof1.siblings.map((s) => s.bigInt()), + proof2.siblings.map((s) => s.bigInt()), + ], + enabled: [1, 1], + outputCommitments, + outputValues, + outputSalts: [salt3], + outputOwnerPublicKeys: [Alice.pubKey], + }, + true, + ); + + const { proof, publicSignals } = await groth16.prove( + provingKeyFile, + witness, + ); + console.log("Proving time: ", (Date.now() - startTime) / 1000, "s"); + + 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 tamperedOutputHash = poseidonHash([ + BigInt(100), + salt3, + ...Alice.pubKey, + ]); + let tamperedPublicSignals = publicSignals.map((ps) => + ps.toString() === outputCommitments[0].toString() + ? tamperedOutputHash + : ps, + ); + // console.log("tampered public signals", tamperedPublicSignals); + + verifyResult = await groth16.verify( + verificationKey, + tamperedPublicSignals, + proof, + ); + expect(verifyResult).to.be.false; + }).timeout(600000); +}); 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 0000000..c433d5d --- /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/lib/check-utxos-owner.js b/zkp/js/test/check_utxos_owner.js similarity index 65% rename from zkp/js/test/lib/check-utxos-owner.js rename to zkp/js/test/check_utxos_owner.js index bd6b8d3..17fba51 100644 --- a/zkp/js/test/lib/check-utxos-owner.js +++ b/zkp/js/test/check_utxos_owner.js @@ -17,29 +17,26 @@ const { expect } = require('chai'); const { join } = require('path'); const { wasm: wasm_tester } = require('circom_tester'); -const { genKeypair } = require('maci-crypto'); -const { Poseidon, newSalt } = require('../../index.js'); +const { genKeypair, formatPrivKeyForBabyJub } = require('maci-crypto'); +const { Poseidon, newSalt } = require('../index.js'); const ZERO_PUBKEY = [0n, 0n]; const poseidonHash = Poseidon.poseidon4; -describe('check-hashes circuit tests', () => { +describe('check_utxos_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-hashes.circom')); + circuit = await wasm_tester(join(__dirname, '../../circuits/check_utxos_owner.circom')); let keypair = genKeypair(); sender.privKey = keypair.privKey; sender.pubKey = keypair.pubKey; - - keypair = genKeypair(); - receiver.privKey = keypair.privKey; - receiver.pubKey = keypair.pubKey; + senderPrivateKey = formatPrivKeyForBabyJub(sender.privKey); }); it('should return true for valid witness', async () => { @@ -57,7 +54,7 @@ describe('check-hashes circuit tests', () => { commitments, values, salts: [salt1, salt2], - ownerPublicKeys: [sender.pubKey, sender.pubKey], + ownerPrivateKey: senderPrivateKey, }, true ); @@ -65,10 +62,12 @@ describe('check-hashes circuit tests', () => { // 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[3]).to.equal(BigInt(sender.pubKey[0])); - expect(witness[4]).to.equal(BigInt(sender.pubKey[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 () => { @@ -84,15 +83,16 @@ describe('check-hashes circuit tests', () => { commitments, values, salts: [salt1, 0], - ownerPublicKeys: [sender.pubKey, [0n, 0n]], + ownerPrivateKey: senderPrivateKey, }, true ); expect(witness[1]).to.equal(BigInt(commitments[0])); expect(witness[2]).to.equal(BigInt(commitments[1])); - expect(witness[3]).to.equal(BigInt(sender.pubKey[0])); - expect(witness[4]).to.equal(BigInt(sender.pubKey[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 () => { @@ -108,17 +108,16 @@ describe('check-hashes circuit tests', () => { commitments, values, salts: [0, salt1], - ownerPublicKeys: [[0n, 0n], sender.pubKey], + ownerPrivateKey: senderPrivateKey, }, true ); expect(witness[1]).to.equal(BigInt(commitments[0])); expect(witness[2]).to.equal(BigInt(commitments[1])); - expect(witness[3]).to.equal(0n); - expect(witness[4]).to.equal(0n); - expect(witness[5]).to.equal(BigInt(sender.pubKey[0])); - expect(witness[6]).to.equal(BigInt(sender.pubKey[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 () => { @@ -138,7 +137,35 @@ describe('check-hashes circuit tests', () => { commitments: inputCommitments, values: inputValues, salts: [salt1, salt2], - ownerPublicKeys: [sender.pubKey, sender.pubKey], + 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 ); @@ -146,6 +173,6 @@ describe('check-hashes circuit tests', () => { error = e; } // console.log(error); - expect(error).to.match(/Error in template CheckHashes_76 line: 47/); // hash check failed + 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 a744c43..cded1dc 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", () => { From deb1d0aa16260adf32a61cbc8610f822c456b129 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Thu, 12 Dec 2024 16:30:18 -0500 Subject: [PATCH 03/13] Clean up unused imports in Solidity Signed-off-by: Jim Zhang --- solidity/contracts/lib/zeto_base.sol | 6 +----- solidity/contracts/lib/zeto_common.sol | 1 - solidity/contracts/lib/zeto_fungible.sol | 1 - solidity/contracts/lib/zeto_fungible_withdraw.sol | 3 --- .../contracts/lib/zeto_fungible_withdraw_nullifier.sol | 2 -- solidity/contracts/lib/zeto_lock.sol | 4 ---- solidity/contracts/lib/zeto_nullifier.sol | 3 --- solidity/contracts/zeto_anon.sol | 7 ++----- solidity/contracts/zeto_anon_enc.sol | 7 ++----- solidity/contracts/zeto_anon_enc_nullifier.sol | 1 - .../contracts/zeto_anon_enc_nullifier_non_repudiation.sol | 1 - solidity/contracts/zeto_anon_nullifier.sol | 4 ---- solidity/contracts/zeto_anon_nullifier_kyc.sol | 3 --- solidity/contracts/zeto_nf_anon.sol | 4 +--- solidity/contracts/zeto_nf_anon_nullifier.sol | 4 ---- 15 files changed, 6 insertions(+), 45 deletions(-) diff --git a/solidity/contracts/lib/zeto_base.sol b/solidity/contracts/lib/zeto_base.sol index 9fb8290..696c750 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 ( diff --git a/solidity/contracts/lib/zeto_common.sol b/solidity/contracts/lib/zeto_common.sol index 34f2859..30d9dcf 100644 --- a/solidity/contracts/lib/zeto_common.sol +++ b/solidity/contracts/lib/zeto_common.sol @@ -16,7 +16,6 @@ pragma solidity ^0.8.20; import {Commonlib} from "./common.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; diff --git a/solidity/contracts/lib/zeto_fungible.sol b/solidity/contracts/lib/zeto_fungible.sol index 0e109de..b35a719 100644 --- a/solidity/contracts/lib/zeto_fungible.sol +++ b/solidity/contracts/lib/zeto_fungible.sol @@ -17,7 +17,6 @@ pragma solidity ^0.8.20; import {Groth16Verifier_CheckHashesValue} from "./verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckNullifierValue} from "./verifier_check_nullifier_value.sol"; -import {ZetoBase} from "./zeto_base.sol"; import {Commonlib} from "./common.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; diff --git a/solidity/contracts/lib/zeto_fungible_withdraw.sol b/solidity/contracts/lib/zeto_fungible_withdraw.sol index f560cf1..2c8a859 100644 --- a/solidity/contracts/lib/zeto_fungible_withdraw.sol +++ b/solidity/contracts/lib/zeto_fungible_withdraw.sol @@ -18,11 +18,8 @@ pragma solidity ^0.8.20; import {Groth16Verifier_CheckHashesValue} from "./verifier_check_hashes_value.sol"; import {Groth16Verifier_CheckInputsOutputsValue} from "./verifier_check_inputs_outputs_value.sol"; import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./verifier_check_inputs_outputs_value_batch.sol"; -import {Groth16Verifier_CheckUtxosOwner} from "./verifier_check_utxos_owner.sol"; -import {Groth16Verifier_CheckUtxosOwnerBatch} from "./verifier_check_utxos_owner_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; diff --git a/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol b/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol index cc942c4..f41ce00 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; diff --git a/solidity/contracts/lib/zeto_lock.sol b/solidity/contracts/lib/zeto_lock.sol index 27e4d18..70d5a61 100644 --- a/solidity/contracts/lib/zeto_lock.sol +++ b/solidity/contracts/lib/zeto_lock.sol @@ -18,10 +18,6 @@ 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 {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"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /// @title A sample base implementation of a Zeto based token contract diff --git a/solidity/contracts/lib/zeto_nullifier.sol b/solidity/contracts/lib/zeto_nullifier.sol index 64a7206..225e500 100644 --- a/solidity/contracts/lib/zeto_nullifier.sol +++ b/solidity/contracts/lib/zeto_nullifier.sol @@ -16,10 +16,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 {SmtLib} from "@iden3/contracts/lib/SmtLib.sol"; import {PoseidonUnit3L} from "@iden3/contracts/lib/Poseidon.sol"; diff --git a/solidity/contracts/zeto_anon.sol b/solidity/contracts/zeto_anon.sol index a678716..99c4c59 100644 --- a/solidity/contracts/zeto_anon.sol +++ b/solidity/contracts/zeto_anon.sol @@ -25,13 +25,10 @@ import {Groth16Verifier_CheckUtxosOwnerBatch} from "./lib/verifier_check_utxos_o 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 INPUT_SIZE = 4; @@ -117,7 +114,7 @@ contract Zeto_Anon is (inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH); require( - validateTransactionProposal(inputs, outputs, proof), + validateTransactionProposal(inputs, outputs), "Invalid transaction proposal" ); @@ -199,7 +196,7 @@ contract Zeto_Anon is uint256[] memory outputs = new uint256[](inputs.length); outputs[0] = output; (inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH); - validateTransactionProposal(inputs, outputs, proof); + validateTransactionProposal(inputs, outputs); _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 56718d8..aa673cd 100644 --- a/solidity/contracts/zeto_anon_enc.sol +++ b/solidity/contracts/zeto_anon_enc.sol @@ -26,11 +26,8 @@ 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 {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"; uint256 constant INPUT_SIZE = 15; @@ -135,7 +132,7 @@ contract Zeto_AnonEnc is // Check and pad commitments (inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH); require( - validateTransactionProposal(inputs, outputs, proof), + validateTransactionProposal(inputs, outputs), "Invalid transaction proposal" ); @@ -238,7 +235,7 @@ 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); _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 5fd7170..1b6f935 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier.sol @@ -23,7 +23,6 @@ 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 {Commonlib} from "./lib/common.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; diff --git a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol index 818c9dc..92c6659 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol @@ -23,7 +23,6 @@ import {Groth16Verifier_AnonEncNullifierNonRepudiation} from "./lib/verifier_ano import {Groth16Verifier_AnonEncNullifierNonRepudiationBatch} from "./lib/verifier_anon_enc_nullifier_non_repudiation_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 {Commonlib} from "./lib/common.sol"; uint256 constant INPUT_SIZE = 36; diff --git a/solidity/contracts/zeto_anon_nullifier.sol b/solidity/contracts/zeto_anon_nullifier.sol index 5d1b31e..4c1d416 100644 --- a/solidity/contracts/zeto_anon_nullifier.sol +++ b/solidity/contracts/zeto_anon_nullifier.sol @@ -23,12 +23,8 @@ 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 {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 INPUT_SIZE = 7; uint256 constant BATCH_INPUT_SIZE = 31; diff --git a/solidity/contracts/zeto_anon_nullifier_kyc.sol b/solidity/contracts/zeto_anon_nullifier_kyc.sol index 6cfede4..05657a6 100644 --- a/solidity/contracts/zeto_anon_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_nullifier_kyc.sol @@ -26,10 +26,7 @@ import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; import {ZetoFungibleWithdrawWithNullifiers} from "./lib/zeto_fungible_withdraw_nullifier.sol"; import {Registry} from "./lib/registry.sol"; import {Commonlib} from "./lib/common.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 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 INPUT_SIZE = 8; uint256 constant BATCH_INPUT_SIZE = 32; diff --git a/solidity/contracts/zeto_nf_anon.sol b/solidity/contracts/zeto_nf_anon.sol index fe89295..7bb38c9 100644 --- a/solidity/contracts/zeto_nf_anon.sol +++ b/solidity/contracts/zeto_nf_anon.sol @@ -22,9 +22,7 @@ import {IZetoLockable} from "./lib/interfaces/izeto_lockable.sol"; import {Groth16Verifier_NfAnon} from "./lib/verifier_nf_anon.sol"; import {ZetoBase} from "./lib/zeto_base.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"; /// @title A sample implementation of a Zeto based non-fungible token with anonymity and no encryption @@ -76,7 +74,7 @@ contract Zeto_NfAnon is uint256[] memory outputs = new uint256[](1); outputs[0] = output; require( - validateTransactionProposal(inputs, outputs, proof), + validateTransactionProposal(inputs, outputs), "Invalid transaction proposal" ); diff --git a/solidity/contracts/zeto_nf_anon_nullifier.sol b/solidity/contracts/zeto_nf_anon_nullifier.sol index aa0dbcb..470103a 100644 --- a/solidity/contracts/zeto_nf_anon_nullifier.sol +++ b/solidity/contracts/zeto_nf_anon_nullifier.sol @@ -18,12 +18,8 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.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 {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"; /// @title A sample implementation of a Zeto based non-fungible token with anonymity and history masking /// @author Kaleido, Inc. From 5fe96df4c966d56e79470d4a6698664286a55efb Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Thu, 12 Dec 2024 17:26:51 -0500 Subject: [PATCH 04/13] fix unit test Signed-off-by: Jim Zhang --- solidity/test/factory.ts | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/solidity/test/factory.ts b/solidity/test/factory.ts index e0c3b1d..d0a42ff 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) From a76db9eca04cd38dd2160185e94485747d5a74b3 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Fri, 13 Dec 2024 09:33:04 -0500 Subject: [PATCH 05/13] Update cloneable contract deploy in hardhat tests Signed-off-by: Jim Zhang --- solidity/test/lib/deploy.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/solidity/test/lib/deploy.ts b/solidity/test/lib/deploy.ts index 3d58987..d95b25c 100644 --- a/solidity/test/lib/deploy.ts +++ b/solidity/test/lib/deploy.ts @@ -56,6 +56,8 @@ export async function deployZeto(tokenName: string) { withdrawVerifier, batchVerifier, batchWithdrawVerifier, + lockVerifier, + batchLockVerifier, ] = args; // we want to test the effectiveness of the factory contract @@ -75,6 +77,10 @@ export async function deployZeto(tokenName: string) { batchVerifier || "0x0000000000000000000000000000000000000000", batchWithdrawVerifier: batchWithdrawVerifier || "0x0000000000000000000000000000000000000000", + lockVerifier: + lockVerifier || "0x0000000000000000000000000000000000000000", + batchLockVerifier: + batchLockVerifier || "0x0000000000000000000000000000000000000000", }; // console.log(implInfo); const tx1 = await factory From 06305e0fd2f1778c220eb45983df45e8f5e0fa45 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Fri, 13 Dec 2024 14:32:48 -0500 Subject: [PATCH 06/13] add lock support for tokens using nullifiers Signed-off-by: Jim Zhang --- solidity/contracts/factory.sol | 7 +- .../lib/interfaces/izeto_nf_initializable.sol | 3 +- .../verifier_check_nullifiers_nf_owner.sol | 175 ++++++++++ .../lib/verifier_check_nullifiers_owner.sol | 175 ++++++++++ .../verifier_check_nullifiers_owner_batch.sol | 231 +++++++++++++ .../lib/verifier_check_nullifiers_value.sol | 210 ++++++++++++ .../verifier_check_nullifiers_value_batch.sol | 322 ++++++++++++++++++ .../contracts/zeto_anon_enc_nullifier.sol | 13 +- .../contracts/zeto_anon_enc_nullifier_kyc.sol | 12 +- ...eto_anon_enc_nullifier_non_repudiation.sol | 12 +- solidity/contracts/zeto_anon_nullifier.sol | 12 +- .../contracts/zeto_anon_nullifier_kyc.sol | 12 +- solidity/contracts/zeto_nf_anon.sol | 5 +- solidity/contracts/zeto_nf_anon_nullifier.sol | 17 +- solidity/ignition/modules/lib/deps.ts | 23 ++ .../modules/zeto_anon_enc_nullifier.ts | 10 + .../modules/zeto_anon_enc_nullifier_kyc.ts | 10 + ...zeto_anon_enc_nullifier_non_repudiation.ts | 10 + .../ignition/modules/zeto_anon_nullifier.ts | 10 + .../modules/zeto_anon_nullifier_kyc.ts | 10 + .../modules/zeto_nf_anon_nullifier.ts | 5 +- .../scripts/tokens/Zeto_AnonEncNullifier.ts | 4 + .../tokens/Zeto_AnonEncNullifierKyc.ts | 4 + .../Zeto_AnonEncNullifierNonRepudiation.ts | 4 + solidity/scripts/tokens/Zeto_AnonNullifier.ts | 4 + .../scripts/tokens/Zeto_AnonNullifierKyc.ts | 4 + solidity/scripts/tokens/Zeto_NfAnon.ts | 4 +- .../scripts/tokens/Zeto_NfAnonNullifier.ts | 4 +- solidity/test/utils.ts | 56 ++- solidity/test/zeto_anon.ts | 2 + solidity/test/zeto_anon_enc.ts | 2 + solidity/test/zeto_anon_enc_nullifier.ts | 100 ++++++ solidity/test/zeto_anon_nullifier.ts | 143 ++++++-- solidity/test/zeto_nf_anon.ts | 2 + zkp/circuits/check_nullifiers_nf_owner.circom | 21 ++ .../check_nullifiers_owner.circom} | 5 +- .../check_nullifiers_owner_batch.circom | 21 ++ ...e.circom => check_nullifiers_value.circom} | 2 +- ...om => check_nullifiers_value_batch.circom} | 2 +- zkp/circuits/gen-config.json | 11 +- ...om => check-nullifiers-tokenid-uri.circom} | 4 +- ...com => check-nullifiers-value-base.circom} | 0 zkp/circuits/nf_anon_nullifier.circom | 4 +- .../integration-test/check_nullifier_value.js | 6 +- zkp/js/integration-test/check_utxos_owner.js | 6 +- ...id-uri.js => check_nullifiers_nf_owner.js} | 82 ++--- zkp/js/test/check_nullifiers_owner.js | 106 ++++++ ...ier_value.js => check_nullifiers_value.js} | 4 +- 48 files changed, 1767 insertions(+), 124 deletions(-) create mode 100644 solidity/contracts/lib/verifier_check_nullifiers_nf_owner.sol create mode 100644 solidity/contracts/lib/verifier_check_nullifiers_owner.sol create mode 100644 solidity/contracts/lib/verifier_check_nullifiers_owner_batch.sol create mode 100644 solidity/contracts/lib/verifier_check_nullifiers_value.sol create mode 100644 solidity/contracts/lib/verifier_check_nullifiers_value_batch.sol create mode 100644 zkp/circuits/check_nullifiers_nf_owner.circom rename zkp/{js/test/circuits/check-nullifier-tokenid-uri.circom => circuits/check_nullifiers_owner.circom} (80%) create mode 100644 zkp/circuits/check_nullifiers_owner_batch.circom rename zkp/circuits/{check_nullifier_value.circom => check_nullifiers_value.circom} (93%) rename zkp/circuits/{check_nullifier_value_batch.circom => check_nullifiers_value_batch.circom} (93%) rename zkp/circuits/lib/{check-nullifier-tokenid-uri.circom => check-nullifiers-tokenid-uri.circom} (92%) rename zkp/circuits/lib/{check-nullifier-value-base.circom => check-nullifiers-value-base.circom} (100%) rename zkp/js/test/{lib/check-nullifier-tokenid-uri.js => check_nullifiers_nf_owner.js} (50%) create mode 100644 zkp/js/test/check_nullifiers_owner.js rename zkp/js/test/{check_nullifier_value.js => check_nullifiers_value.js} (98%) diff --git a/solidity/contracts/factory.sol b/solidity/contracts/factory.sol index 525699a..1c058b4 100644 --- a/solidity/contracts/factory.sol +++ b/solidity/contracts/factory.sol @@ -126,10 +126,6 @@ contract ZetoTokenFactory is Ownable { 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), @@ -138,8 +134,7 @@ contract ZetoTokenFactory is Ownable { (IZetoNonFungibleInitializable(instance)).initialize( initialOwner, args.verifier, - args.lockVerifier, - args.batchLockVerifier + args.lockVerifier ); emit ZetoTokenDeployed(instance); return instance; diff --git a/solidity/contracts/lib/interfaces/izeto_nf_initializable.sol b/solidity/contracts/lib/interfaces/izeto_nf_initializable.sol index eeef510..418c8e6 100644 --- a/solidity/contracts/lib/interfaces/izeto_nf_initializable.sol +++ b/solidity/contracts/lib/interfaces/izeto_nf_initializable.sol @@ -19,7 +19,6 @@ interface IZetoNonFungibleInitializable { function initialize( address initialOwner, address _verifier, - address _lockVerifier, - address _batchLockVerifier + 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 0000000..9e67a82 --- /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 0000000..e444a3a --- /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 0000000..e5c8dc2 --- /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 0000000..008685d --- /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 0000000..96db7f1 --- /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/zeto_anon_enc_nullifier.sol b/solidity/contracts/zeto_anon_enc_nullifier.sol index 1b6f935..e9aff2e 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier.sol @@ -23,6 +23,7 @@ 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 {ZetoLock} from "./lib/zeto_lock.sol"; import {Commonlib} from "./lib/common.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; @@ -42,6 +43,7 @@ contract Zeto_AnonEncNullifier is IZetoEncrypted, ZetoNullifier, ZetoFungibleWithdrawWithNullifiers, + ZetoLock, UUPSUpgradeable { Groth16Verifier_AnonEncNullifier internal verifier; @@ -53,7 +55,9 @@ contract Zeto_AnonEncNullifier is Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonEncNullifierBatch _batchVerifier, - Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, + address _lockVerifier, + address _batchLockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( @@ -61,6 +65,7 @@ contract Zeto_AnonEncNullifier is _withdrawVerifier, _batchWithdrawVerifier ); + __ZetoLock_init(_lockVerifier, _batchLockVerifier); verifier = _verifier; batchVerifier = _batchVerifier; } @@ -143,6 +148,12 @@ contract Zeto_AnonEncNullifier is validateTransactionProposal(nullifiers, outputs, root), "Invalid transaction proposal" ); + + require( + validateLockedStates(nullifiers), + "At least one UTXO in the input nullifiers are locked" + ); + // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { uint256[] memory publicInputs = constructPublicInputs( diff --git a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol index eaa399a..5175596 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol @@ -23,6 +23,7 @@ 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"; @@ -43,6 +44,7 @@ contract Zeto_AnonEncNullifierKyc is IZetoEncrypted, ZetoNullifier, ZetoFungibleWithdrawWithNullifiers, + ZetoLock, Registry, UUPSUpgradeable { @@ -55,7 +57,9 @@ contract Zeto_AnonEncNullifierKyc is Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonEncNullifierKycBatch _batchVerifier, - Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, + address _lockVerifier, + address _batchLockVerifier ) public initializer { __Registry_init(); __ZetoNullifier_init(initialOwner); @@ -64,6 +68,7 @@ contract Zeto_AnonEncNullifierKyc is _withdrawVerifier, _batchWithdrawVerifier ); + __ZetoLock_init(_lockVerifier, _batchLockVerifier); verifier = _verifier; batchVerifier = _batchVerifier; } @@ -154,6 +159,11 @@ contract Zeto_AnonEncNullifierKyc is "Invalid transaction proposal" ); + require( + validateLockedStates(nullifiers), + "At least one UTXO in the input nullifiers are locked" + ); + // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { uint256[] memory publicInputs = constructPublicInputs( diff --git a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol index 92c6659..b204e97 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol @@ -23,6 +23,7 @@ import {Groth16Verifier_AnonEncNullifierNonRepudiation} from "./lib/verifier_ano import {Groth16Verifier_AnonEncNullifierNonRepudiationBatch} from "./lib/verifier_anon_enc_nullifier_non_repudiation_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 {Commonlib} from "./lib/common.sol"; uint256 constant INPUT_SIZE = 36; @@ -40,6 +41,7 @@ uint256 constant BATCH_INPUT_SIZE = 140; contract Zeto_AnonEncNullifierNonRepudiation is ZetoNullifier, ZetoFungibleWithdrawWithNullifiers, + ZetoLock, UUPSUpgradeable { event UTXOTransferNonRepudiation( @@ -65,7 +67,9 @@ contract Zeto_AnonEncNullifierNonRepudiation is Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonEncNullifierNonRepudiationBatch _batchVerifier, - Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, + address _lockVerifier, + address _batchLockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( @@ -73,6 +77,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is _withdrawVerifier, _batchWithdrawVerifier ); + __ZetoLock_init(_lockVerifier, _batchLockVerifier); verifier = _verifier; batchVerifier = _batchVerifier; } @@ -177,6 +182,11 @@ contract Zeto_AnonEncNullifierNonRepudiation is "Invalid transaction proposal" ); + require( + validateLockedStates(nullifiers), + "At least one UTXO in the input nullifiers are locked" + ); + // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { require( diff --git a/solidity/contracts/zeto_anon_nullifier.sol b/solidity/contracts/zeto_anon_nullifier.sol index 4c1d416..81e113a 100644 --- a/solidity/contracts/zeto_anon_nullifier.sol +++ b/solidity/contracts/zeto_anon_nullifier.sol @@ -23,6 +23,7 @@ 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 {ZetoLock} from "./lib/zeto_lock.sol"; import {Commonlib} from "./lib/common.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; @@ -41,6 +42,7 @@ contract Zeto_AnonNullifier is IZeto, ZetoNullifier, ZetoFungibleWithdrawWithNullifiers, + ZetoLock, UUPSUpgradeable { Groth16Verifier_AnonNullifier internal verifier; @@ -52,7 +54,9 @@ contract Zeto_AnonNullifier is Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonNullifierBatch _batchVerifier, - Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, + address _lockVerifier, + address _batchLockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( @@ -60,6 +64,7 @@ contract Zeto_AnonNullifier is _withdrawVerifier, _batchWithdrawVerifier ); + __ZetoLock_init(_lockVerifier, _batchLockVerifier); verifier = _verifier; batchVerifier = _batchVerifier; } @@ -124,6 +129,11 @@ contract Zeto_AnonNullifier is "Invalid transaction proposal" ); + require( + validateLockedStates(nullifiers), + "At least one UTXO in the input nullifiers are locked" + ); + // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { uint256[] memory publicInputs = constructPublicInputs( diff --git a/solidity/contracts/zeto_anon_nullifier_kyc.sol b/solidity/contracts/zeto_anon_nullifier_kyc.sol index 05657a6..41c1e1c 100644 --- a/solidity/contracts/zeto_anon_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_nullifier_kyc.sol @@ -24,6 +24,7 @@ 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 {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; @@ -43,6 +44,7 @@ contract Zeto_AnonNullifierKyc is IZeto, ZetoNullifier, ZetoFungibleWithdrawWithNullifiers, + ZetoLock, Registry, UUPSUpgradeable { @@ -55,7 +57,9 @@ contract Zeto_AnonNullifierKyc is Groth16Verifier_CheckHashesValue _depositVerifier, Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonNullifierKycBatch _batchVerifier, - Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier + Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, + address _lockVerifier, + address _batchLockVerifier ) public initializer { __Registry_init(); __ZetoNullifier_init(initialOwner); @@ -64,6 +68,7 @@ contract Zeto_AnonNullifierKyc is _withdrawVerifier, _batchWithdrawVerifier ); + __ZetoLock_init(_lockVerifier, _batchLockVerifier); verifier = _verifier; batchVerifier = _batchVerifier; } @@ -135,6 +140,11 @@ contract Zeto_AnonNullifierKyc is "Invalid transaction proposal" ); + require( + validateLockedStates(nullifiers), + "At least one UTXO in the input nullifiers are locked" + ); + // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { uint256[] memory publicInputs = constructPublicInputs( diff --git a/solidity/contracts/zeto_nf_anon.sol b/solidity/contracts/zeto_nf_anon.sol index 7bb38c9..953c6d7 100644 --- a/solidity/contracts/zeto_nf_anon.sol +++ b/solidity/contracts/zeto_nf_anon.sol @@ -43,11 +43,10 @@ contract Zeto_NfAnon is function initialize( address initialOwner, Groth16Verifier_NfAnon _verifier, - address _lockVerifier, - address _batchLockVerifier + address _lockVerifier ) public initializer { __ZetoBase_init(initialOwner); - __ZetoLock_init(_lockVerifier, _batchLockVerifier); + __ZetoLock_init(_lockVerifier, address(0)); verifier = _verifier; } diff --git a/solidity/contracts/zeto_nf_anon_nullifier.sol b/solidity/contracts/zeto_nf_anon_nullifier.sol index 470103a..5581321 100644 --- a/solidity/contracts/zeto_nf_anon_nullifier.sol +++ b/solidity/contracts/zeto_nf_anon_nullifier.sol @@ -18,6 +18,7 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.sol"; import {Groth16Verifier_NfAnonNullifier} from "./lib/verifier_nf_anon_nullifier.sol"; import {ZetoNullifier} from "./lib/zeto_nullifier.sol"; +import {ZetoLock} from "./lib/zeto_lock.sol"; import {Commonlib} from "./lib/common.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; @@ -29,14 +30,21 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U /// - 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 { +contract Zeto_NfAnonNullifier is + IZeto, + ZetoNullifier, + ZetoLock, + UUPSUpgradeable +{ Groth16Verifier_NfAnonNullifier verifier; function initialize( address initialOwner, - Groth16Verifier_NfAnonNullifier _verifier + Groth16Verifier_NfAnonNullifier _verifier, + address _lockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); + __ZetoLock_init(_lockVerifier, address(0)); verifier = _verifier; } @@ -69,6 +77,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; diff --git a/solidity/ignition/modules/lib/deps.ts b/solidity/ignition/modules/lib/deps.ts index 0b68536..e1b627f 100644 --- a/solidity/ignition/modules/lib/deps.ts +++ b/solidity/ignition/modules/lib/deps.ts @@ -94,6 +94,29 @@ export const NfLockVerifierModule = buildModule( }, ); +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_enc_nullifier.ts b/solidity/ignition/modules/zeto_anon_enc_nullifier.ts index 8b16995..c867180 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 f422870..ed076ff 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 fbb3e30..1de99fc 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 d1aca6c..478a027 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 44be1b2..705e938 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_nullifier.ts b/solidity/ignition/modules/zeto_nf_anon_nullifier.ts index 6f3cc2c..54b734b 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_AnonEncNullifier.ts b/solidity/scripts/tokens/Zeto_AnonEncNullifier.ts index e61c204..7bdcd03 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 fadbcf4..101ba89 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 a10a15f..518f8bc 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 a02bce7..a2467b1 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 a299fa7..9f0cf2b 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 8a1766a..50e3648 100644 --- a/solidity/scripts/tokens/Zeto_NfAnon.ts +++ b/solidity/scripts/tokens/Zeto_NfAnon.ts @@ -20,9 +20,9 @@ import zetoModule from "../../ignition/modules/zeto_nf_anon"; export async function deployDependencies() { const [deployer] = await ethers.getSigners(); - const { verifier, lockVerifier, batchLockVerifier, } = await ignition.deploy(zetoModule); + const { verifier, lockVerifier } = await ignition.deploy(zetoModule); return { deployer, - args: [await deployer.getAddress(), verifier.target, lockVerifier.target, batchLockVerifier.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 ff308e7..02c20df 100644 --- a/solidity/scripts/tokens/Zeto_NfAnonNullifier.ts +++ b/solidity/scripts/tokens/Zeto_NfAnonNullifier.ts @@ -20,10 +20,10 @@ 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/utils.ts b/solidity/test/utils.ts index 0c40d4d..d97168a 100644 --- a/solidity/test/utils.ts +++ b/solidity/test/utils.ts @@ -128,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(); @@ -263,6 +263,54 @@ export async function prepareLockProof( }; } +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[], diff --git a/solidity/test/zeto_anon.ts b/solidity/test/zeto_anon.ts index 35e1da5..6419b67 100644 --- a/solidity/test/zeto_anon.ts +++ b/solidity/test/zeto_anon.ts @@ -423,6 +423,8 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi 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; }); }); diff --git a/solidity/test/zeto_anon_enc.ts b/solidity/test/zeto_anon_enc.ts index 7232aeb..cc11bb5 100644 --- a/solidity/test/zeto_anon_enc.ts +++ b/solidity/test/zeto_anon_enc.ts @@ -413,6 +413,8 @@ describe("Zeto based fungible token with anonymity and encryption", 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; }); }); diff --git a/solidity/test/zeto_anon_enc_nullifier.ts b/solidity/test/zeto_anon_enc_nullifier.ts index ddaaf37..b76adcf 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,105 @@ 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 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); + + // 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 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 7b4e925..9503996 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,103 @@ 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 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); + + // 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 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 +644,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 +668,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 +683,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 754fc72..8a86afc 100644 --- a/solidity/test/zeto_nf_anon.ts +++ b/solidity/test/zeto_nf_anon.ts @@ -162,6 +162,8 @@ describe("Zeto based non-fungible token with anonymity without encryption or nul 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; }); }); diff --git a/zkp/circuits/check_nullifiers_nf_owner.circom b/zkp/circuits/check_nullifiers_nf_owner.circom new file mode 100644 index 0000000..f7ebc10 --- /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/js/test/circuits/check-nullifier-tokenid-uri.circom b/zkp/circuits/check_nullifiers_owner.circom similarity index 80% rename from zkp/js/test/circuits/check-nullifier-tokenid-uri.circom rename to zkp/circuits/check_nullifiers_owner.circom index 75ecbcd..1e1efeb 100644 --- a/zkp/js/test/circuits/check-nullifier-tokenid-uri.circom +++ b/zkp/circuits/check_nullifiers_owner.circom @@ -15,6 +15,7 @@ // limitations under the License. pragma circom 2.2.1; -include "../../../circuits/lib/check-nullifier-tokenid-uri.circom"; +include "./lib/check-nullifiers.circom"; +include "./node_modules/circomlib/circuits/babyjub.circom"; -component main {public [ nullifiers ]} = CheckNullifierForTokenIdAndUri(1); \ No newline at end of file +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 0000000..ecd8db1 --- /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 8c447a2..508d0f3 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 5f4fabe..07d1594 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/circuits/gen-config.json b/zkp/circuits/gen-config.json index f72b1e9..78101f8 100644 --- a/zkp/circuits/gen-config.json +++ b/zkp/circuits/gen-config.json @@ -53,7 +53,7 @@ "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 @@ -67,6 +67,15 @@ "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 ff574b8..5575cdc 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/nf_anon_nullifier.circom b/zkp/circuits/nf_anon_nullifier.circom index 00f1093..d6e4605 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_nullifier_value.js b/zkp/js/integration-test/check_nullifier_value.js index cf36a79..0419fb7 100644 --- a/zkp/js/integration-test/check_nullifier_value.js +++ b/zkp/js/integration-test/check_nullifier_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 index cf36a79..a04a77d 100644 --- a/zkp/js/integration-test/check_utxos_owner.js +++ b/zkp/js/integration-test/check_utxos_owner.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_utxos_owner circuit tests", () => { let circuit, provingKeyFile, verificationKey, smtAlice; const Alice = {}; let senderPrivateKey; before(async () => { - circuit = await loadCircuit("check_nullifier_value"); + circuit = await loadCircuit("check_utxos_owner"); ({ provingKeyFile, verificationKey } = loadProvingKeys( - "check_nullifier_value", + "check_utxos_owner", )); let keypair = genKeypair(); 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 b40c668..ca62e5d 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 0000000..8bce438 --- /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 8c5ad50..0b73210 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(); From da7ca61675e54600875c26220d8883272b2c2ee3 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Fri, 13 Dec 2024 15:14:17 -0500 Subject: [PATCH 07/13] add integration test for circuits Signed-off-by: Jim Zhang --- .../check_nullifiers_owner.js | 76 ++++++++++++++++ ...ier_value.js => check_nullifiers_value.js} | 0 zkp/js/integration-test/check_utxos_owner.js | 86 +++---------------- 3 files changed, 89 insertions(+), 73 deletions(-) create mode 100644 zkp/js/integration-test/check_nullifiers_owner.js rename zkp/js/integration-test/{check_nullifier_value.js => check_nullifiers_value.js} (100%) 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 0000000..7ff6dc5 --- /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 100% rename from zkp/js/integration-test/check_nullifier_value.js rename to zkp/js/integration-test/check_nullifiers_value.js diff --git a/zkp/js/integration-test/check_utxos_owner.js b/zkp/js/integration-test/check_utxos_owner.js index a04a77d..a2a27b4 100644 --- a/zkp/js/integration-test/check_utxos_owner.js +++ b/zkp/js/integration-test/check_utxos_owner.js @@ -17,16 +17,9 @@ const { expect } = require("chai"); const { groth16 } = require("snarkjs"); const { genKeypair, formatPrivKeyForBabyJub } = require("maci-crypto"); -const { - Merkletree, - InMemoryDB, - str2Bytes, - ZERO_HASH, -} = require("@iden3/js-merkletree"); const { Poseidon, newSalt, loadCircuit } = require("../index.js"); const { loadProvingKeys } = require("./utils.js"); -const SMT_HEIGHT = 64; const poseidonHash = Poseidon.poseidon4; const poseidonHash3 = Poseidon.poseidon3; @@ -46,86 +39,34 @@ describe("check_utxos_owner circuit tests", () => { Alice.privKey = keypair.privKey; Alice.pubKey = keypair.pubKey; senderPrivateKey = formatPrivKeyForBabyJub(Alice.privKey); - - // initialize the local storage for Alice to manage her UTXOs in the Spart Merkle Tree - const storage1 = new InMemoryDB(str2Bytes("")); - smtAlice = new Merkletree(storage1, true, SMT_HEIGHT); }); it("should generate a valid proof that can be verified successfully and fail when public signals are tampered", async () => { - const inputValues = [15, 100]; - const outputValues = [35]; + 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(inputValues[0]), + BigInt(values[0]), salt1, ...Alice.pubKey, ]); const salt2 = newSalt(); const input2 = poseidonHash([ - BigInt(inputValues[1]), - salt2, - ...Alice.pubKey, - ]); - const inputCommitments = [input1, input2]; - - // create the nullifiers for the input UTXOs - const nullifier1 = poseidonHash3([ - BigInt(inputValues[0]), - salt1, - senderPrivateKey, - ]); - const nullifier2 = poseidonHash3([ - BigInt(inputValues[1]), + BigInt(values[1]), salt2, - senderPrivateKey, - ]); - const nullifiers = [nullifier1, nullifier2]; - - // calculate the root of the SMT - await smtAlice.add(input1, input1); - await smtAlice.add(input2, input2); - - // generate the merkle proof for the inputs - const proof1 = await smtAlice.generateCircomVerifierProof( - input1, - ZERO_HASH, - ); - const proof2 = await smtAlice.generateCircomVerifierProof( - input2, - ZERO_HASH, - ); - - // create two output UTXOs, they share the same salt, and different owner - const salt3 = newSalt(); - const output1 = poseidonHash([ - BigInt(outputValues[0]), - salt3, ...Alice.pubKey, ]); - const outputCommitments = [output1]; + const commitments = [input1, input2]; const startTime = Date.now(); const witness = await circuit.calculateWTNSBin( { - nullifiers, - inputCommitments, - inputValues, - inputSalts: [salt1, salt2], - inputOwnerPrivateKey: senderPrivateKey, - root: proof1.root.bigInt(), - merkleProof: [ - proof1.siblings.map((s) => s.bigInt()), - proof2.siblings.map((s) => s.bigInt()), - ], - enabled: [1, 1], - outputCommitments, - outputValues, - outputSalts: [salt3], - outputOwnerPublicKeys: [Alice.pubKey], + commitments, + values, + salts: [salt1, salt2], + ownerPrivateKey: senderPrivateKey, }, true, ); @@ -147,17 +88,16 @@ describe("check_utxos_owner circuit tests", () => { // console.log('outputCommitments', outputCommitments); // console.log('root', proof1.root.bigInt()); // console.log("public signals", publicSignals); - const tamperedOutputHash = poseidonHash([ - BigInt(100), - salt3, + const tamperedCommitment = poseidonHash([ + BigInt(values[0] + 1), + salt1, ...Alice.pubKey, ]); let tamperedPublicSignals = publicSignals.map((ps) => - ps.toString() === outputCommitments[0].toString() - ? tamperedOutputHash + ps.toString() === commitments[0].toString() + ? tamperedCommitment : ps, ); - // console.log("tampered public signals", tamperedPublicSignals); verifyResult = await groth16.verify( verificationKey, From e5715696babdf9d856c68e8778304923f403637d Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Fri, 13 Dec 2024 16:32:09 -0500 Subject: [PATCH 08/13] fix deploy arguments for nf tokens Signed-off-by: Jim Zhang --- solidity/contracts/factory.sol | 2 +- solidity/ignition/modules/zeto_nf_anon.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/solidity/contracts/factory.sol b/solidity/contracts/factory.sol index 1c058b4..2e94eb0 100644 --- a/solidity/contracts/factory.sol +++ b/solidity/contracts/factory.sol @@ -29,10 +29,10 @@ contract ZetoTokenFactory is Ownable { address implementation; address depositVerifier; address withdrawVerifier; + address lockVerifier; address verifier; address batchVerifier; address batchWithdrawVerifier; - address lockVerifier; address batchLockVerifier; } diff --git a/solidity/ignition/modules/zeto_nf_anon.ts b/solidity/ignition/modules/zeto_nf_anon.ts index 2f31221..3961ec7 100644 --- a/solidity/ignition/modules/zeto_nf_anon.ts +++ b/solidity/ignition/modules/zeto_nf_anon.ts @@ -17,7 +17,6 @@ import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; import { NfLockVerifierModule, - BatchLockVerifierModule, } from "./lib/deps"; const VerifierModule = buildModule("Groth16Verifier_NfAnon", (m) => { const verifier = m.contract("Groth16Verifier_NfAnon", []); @@ -27,8 +26,6 @@ const VerifierModule = buildModule("Groth16Verifier_NfAnon", (m) => { export default buildModule("Zeto_NfAnon", (m) => { const { verifier } = m.useModule(VerifierModule); const { verifier: lockVerifier } = m.useModule(NfLockVerifierModule); - // TODO: update this to use the correct batch verifier - const { verifier: batchLockVerifier } = m.useModule(BatchLockVerifierModule); - return { verifier, lockVerifier, batchLockVerifier }; + return { verifier, lockVerifier }; }); From 655568696e084c01770ef61938fcbdc1264443fc Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Fri, 13 Dec 2024 18:27:32 -0500 Subject: [PATCH 09/13] add temp debug for build failures Signed-off-by: Jim Zhang --- solidity/test/lib/deploy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/solidity/test/lib/deploy.ts b/solidity/test/lib/deploy.ts index d95b25c..711e8cf 100644 --- a/solidity/test/lib/deploy.ts +++ b/solidity/test/lib/deploy.ts @@ -48,6 +48,7 @@ export async function deployZeto(tokenName: string) { ? deployFungibleCloneable : deployNonFungibleCloneable; const result = await deployFunc(tokenName); + console.log("deployFunc result: ", JSON.stringify(result, null, 2)); ({ deployer, zetoImpl, erc20, args } = result as any); const [ deployerAddr, @@ -82,7 +83,7 @@ export async function deployZeto(tokenName: string) { batchLockVerifier: batchLockVerifier || "0x0000000000000000000000000000000000000000", }; - // console.log(implInfo); + console.log("impl info: ", JSON.stringify(implInfo, null, 2)); const tx1 = await factory .connect(deployer) .registerImplementation(tokenName, implInfo as any); From 2c3e110950787a88578e069ab9c7b789dc2579b4 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Mon, 16 Dec 2024 12:36:36 -0500 Subject: [PATCH 10/13] protect lock states from withdrarw() calls Signed-off-by: Jim Zhang --- .../contracts/lib/interfaces/izeto_base.sol | 3 ++ .../contracts/lib/interfaces/izeto_common.sol | 22 ++++++++++++ .../lib/interfaces/izeto_encrypted.sol | 4 +-- solidity/contracts/lib/zeto_common.sol | 11 ++---- solidity/contracts/lib/zeto_lock.sol | 6 ++-- solidity/contracts/zeto_anon.sol | 19 ++++------ solidity/contracts/zeto_anon_enc.sol | 18 ++++------ .../contracts/zeto_anon_enc_nullifier.sol | 18 ++++------ .../contracts/zeto_anon_enc_nullifier_kyc.sol | 19 +++++----- ...eto_anon_enc_nullifier_non_repudiation.sol | 18 ++++------ solidity/contracts/zeto_anon_nullifier.sol | 18 ++++------ .../contracts/zeto_anon_nullifier_kyc.sol | 18 ++++------ solidity/contracts/zeto_nf_anon.sol | 14 +++----- solidity/contracts/zeto_nf_anon_nullifier.sol | 5 +-- solidity/scripts/tokens/Zeto_NfAnon.ts | 6 +++- .../scripts/tokens/Zeto_NfAnonNullifier.ts | 6 +++- solidity/test/lib/deploy.ts | 7 ++-- solidity/test/zeto_anon.ts | 17 +++++++-- solidity/test/zeto_anon_enc.ts | 15 ++++++-- solidity/test/zeto_anon_enc_nullifier.ts | 36 ++++++++++++++++++- solidity/test/zeto_anon_nullifier.ts | 36 ++++++++++++++++++- 21 files changed, 198 insertions(+), 118 deletions(-) create mode 100644 solidity/contracts/lib/interfaces/izeto_common.sol diff --git a/solidity/contracts/lib/interfaces/izeto_base.sol b/solidity/contracts/lib/interfaces/izeto_base.sol index d9d37bb..7a06370 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 0000000..75b9e24 --- /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 a3627df..e7995d5 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/zeto_common.sol b/solidity/contracts/lib/zeto_common.sol index 30d9dcf..b22e87c 100644 --- a/solidity/contracts/lib/zeto_common.sol +++ b/solidity/contracts/lib/zeto_common.sol @@ -16,21 +16,14 @@ pragma solidity ^0.8.20; import {Commonlib} from "./common.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 { - uint256 public constant MAX_BATCH = 10; - error UTXONotMinted(uint256 utxo); - error UTXOAlreadyOwned(uint256 utxo); - error UTXOAlreadySpent(uint256 utxo); - error UTXODuplicate(uint256 utxo); - error IdentityNotRegistered(address addr); - error UTXOArrayTooLarge(uint256 maxAllowed); - +abstract contract ZetoCommon is IZetoCommon, OwnableUpgradeable { function __ZetoCommon_init(address initialOwner) internal onlyInitializing { __Ownable_init(initialOwner); } diff --git a/solidity/contracts/lib/zeto_lock.sol b/solidity/contracts/lib/zeto_lock.sol index 70d5a61..e4ef438 100644 --- a/solidity/contracts/lib/zeto_lock.sol +++ b/solidity/contracts/lib/zeto_lock.sol @@ -36,10 +36,10 @@ abstract contract ZetoLock is IZetoBase, IZetoLockable, OwnableUpgradeable { IBatchLockVerifier internal batchLockVerifier; function __ZetoLock_init( - address _lockVerifier, - address _batchLockVerifier + ILockVerifier _lockVerifier, + IBatchLockVerifier _batchLockVerifier ) public onlyInitializing { - lockVerifier = ILockVerifier(_lockVerifier); + lockVerifier = _lockVerifier; batchLockVerifier = IBatchLockVerifier(_batchLockVerifier); } diff --git a/solidity/contracts/zeto_anon.sol b/solidity/contracts/zeto_anon.sol index 99c4c59..4e056a7 100644 --- a/solidity/contracts/zeto_anon.sol +++ b/solidity/contracts/zeto_anon.sol @@ -16,7 +16,8 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.sol"; -import {ILockVerifier} from "./lib/interfaces/izeto_lockable.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"; @@ -58,8 +59,8 @@ contract Zeto_Anon is Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, Groth16Verifier_AnonBatch _batchVerifier, Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier, - address _lockVerifier, - address _batchLockVerifier + ILockVerifier _lockVerifier, + IBatchLockVerifier _batchLockVerifier ) public initializer { __ZetoBase_init(initialOwner); __ZetoFungibleWithdraw_init( @@ -113,15 +114,8 @@ contract Zeto_Anon is // Check and pad inputs and outputs based on the max size (inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH); - require( - validateTransactionProposal(inputs, outputs), - "Invalid transaction proposal" - ); - - require( - validateLockedStates(inputs), - "At least one UTXO in the inputs are locked" - ); + validateTransactionProposal(inputs, outputs); + validateLockedStates(inputs); // Check the proof if (inputs.length > 2 || outputs.length > 2) { @@ -197,6 +191,7 @@ contract Zeto_Anon is outputs[0] = output; (inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH); 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 aa673cd..3d4e134 100644 --- a/solidity/contracts/zeto_anon_enc.sol +++ b/solidity/contracts/zeto_anon_enc.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_CheckInputsOutputsValue} from "./lib/verifier_check_inputs_outputs_value.sol"; import {Groth16Verifier_CheckInputsOutputsValueBatch} from "./lib/verifier_check_inputs_outputs_value_batch.sol"; @@ -59,8 +61,8 @@ contract Zeto_AnonEnc is Groth16Verifier_CheckInputsOutputsValue _withdrawVerifier, Groth16Verifier_AnonEncBatch _batchVerifier, Groth16Verifier_CheckInputsOutputsValueBatch _batchWithdrawVerifier, - address _lockVerifier, - address _batchLockVerifier + ILockVerifier _lockVerifier, + IBatchLockVerifier _batchLockVerifier ) public initializer { __ZetoBase_init(initialOwner); __ZetoFungibleWithdraw_init( @@ -131,15 +133,8 @@ contract Zeto_AnonEnc is ) public returns (bool) { // Check and pad commitments (inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH); - require( - validateTransactionProposal(inputs, outputs), - "Invalid transaction proposal" - ); - - require( - validateLockedStates(inputs), - "At least one UTXO in the inputs are locked" - ); + validateTransactionProposal(inputs, outputs); + validateLockedStates(inputs); // Check the proof if (inputs.length > 2 || outputs.length > 2) { @@ -236,6 +231,7 @@ contract Zeto_AnonEnc is // Check and pad commitments (inputs, outputs) = checkAndPadCommitments(inputs, outputs, MAX_BATCH); 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 e9aff2e..3593094 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"; @@ -56,8 +58,8 @@ contract Zeto_AnonEncNullifier is Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonEncNullifierBatch _batchVerifier, Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, - address _lockVerifier, - address _batchLockVerifier + ILockVerifier _lockVerifier, + IBatchLockVerifier _batchLockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( @@ -144,15 +146,8 @@ contract Zeto_AnonEncNullifier is outputs, MAX_BATCH ); - require( - validateTransactionProposal(nullifiers, outputs, root), - "Invalid transaction proposal" - ); - - require( - validateLockedStates(nullifiers), - "At least one UTXO in the input nullifiers are locked" - ); + validateTransactionProposal(nullifiers, outputs, root); + validateLockedStates(nullifiers); // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { @@ -257,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 5175596..8f8c533 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"; @@ -58,8 +60,8 @@ contract Zeto_AnonEncNullifierKyc is Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonEncNullifierKycBatch _batchVerifier, Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, - address _lockVerifier, - address _batchLockVerifier + ILockVerifier _lockVerifier, + IBatchLockVerifier _batchLockVerifier ) public initializer { __Registry_init(); __ZetoNullifier_init(initialOwner); @@ -154,15 +156,8 @@ contract Zeto_AnonEncNullifierKyc is outputs, MAX_BATCH ); - require( - validateTransactionProposal(nullifiers, outputs, root), - "Invalid transaction proposal" - ); - - require( - validateLockedStates(nullifiers), - "At least one UTXO in the input nullifiers are locked" - ); + validateTransactionProposal(nullifiers, outputs, root); + validateLockedStates(nullifiers); // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { @@ -272,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 b204e97..a692585 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol @@ -21,6 +21,8 @@ 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 {ZetoLock} from "./lib/zeto_lock.sol"; @@ -68,8 +70,8 @@ contract Zeto_AnonEncNullifierNonRepudiation is Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonEncNullifierNonRepudiationBatch _batchVerifier, Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, - address _lockVerifier, - address _batchLockVerifier + ILockVerifier _lockVerifier, + IBatchLockVerifier _batchLockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( @@ -177,15 +179,8 @@ contract Zeto_AnonEncNullifierNonRepudiation is outputs, MAX_BATCH ); - require( - validateTransactionProposal(nullifiers, outputs, root), - "Invalid transaction proposal" - ); - - require( - validateLockedStates(nullifiers), - "At least one UTXO in the input nullifiers are locked" - ); + validateTransactionProposal(nullifiers, outputs, root); + validateLockedStates(nullifiers); // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { @@ -307,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 81e113a..4f38700 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"; @@ -55,8 +57,8 @@ contract Zeto_AnonNullifier is Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonNullifierBatch _batchVerifier, Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, - address _lockVerifier, - address _batchLockVerifier + ILockVerifier _lockVerifier, + IBatchLockVerifier _batchLockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); __ZetoFungibleWithdrawWithNullifiers_init( @@ -124,15 +126,8 @@ contract Zeto_AnonNullifier is MAX_BATCH ); - require( - validateTransactionProposal(nullifiers, outputs, root), - "Invalid transaction proposal" - ); - - require( - validateLockedStates(nullifiers), - "At least one UTXO in the input nullifiers are locked" - ); + validateTransactionProposal(nullifiers, outputs, root); + validateLockedStates(nullifiers); // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { @@ -221,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 41c1e1c..52902b0 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"; @@ -58,8 +60,8 @@ contract Zeto_AnonNullifierKyc is Groth16Verifier_CheckNullifierValue _withdrawVerifier, Groth16Verifier_AnonNullifierKycBatch _batchVerifier, Groth16Verifier_CheckNullifierValueBatch _batchWithdrawVerifier, - address _lockVerifier, - address _batchLockVerifier + ILockVerifier _lockVerifier, + IBatchLockVerifier _batchLockVerifier ) public initializer { __Registry_init(); __ZetoNullifier_init(initialOwner); @@ -135,15 +137,8 @@ contract Zeto_AnonNullifierKyc is MAX_BATCH ); - require( - validateTransactionProposal(nullifiers, outputs, root), - "Invalid transaction proposal" - ); - - require( - validateLockedStates(nullifiers), - "At least one UTXO in the input nullifiers are locked" - ); + validateTransactionProposal(nullifiers, outputs, root); + validateLockedStates(nullifiers); // Check the proof if (nullifiers.length > 2 || outputs.length > 2) { @@ -232,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 953c6d7..81eef66 100644 --- a/solidity/contracts/zeto_nf_anon.sol +++ b/solidity/contracts/zeto_nf_anon.sol @@ -17,7 +17,7 @@ pragma solidity ^0.8.20; import {IZeto} from "./lib/interfaces/izeto.sol"; import {Groth16Verifier_CheckUtxosNfOwner} from "./lib/verifier_check_utxos_nf_owner.sol"; -import {IZetoLockable} from "./lib/interfaces/izeto_lockable.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"; @@ -31,22 +31,16 @@ 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, - IZetoLockable, - ZetoBase, - ZetoLock, - UUPSUpgradeable -{ +contract Zeto_NfAnon is IZeto, ZetoBase, ZetoLock, UUPSUpgradeable { Groth16Verifier_NfAnon internal verifier; function initialize( address initialOwner, Groth16Verifier_NfAnon _verifier, - address _lockVerifier + ILockVerifier _lockVerifier ) public initializer { __ZetoBase_init(initialOwner); - __ZetoLock_init(_lockVerifier, address(0)); + __ZetoLock_init(_lockVerifier, IBatchLockVerifier(address(0))); verifier = _verifier; } diff --git a/solidity/contracts/zeto_nf_anon_nullifier.sol b/solidity/contracts/zeto_nf_anon_nullifier.sol index 5581321..8cc7d24 100644 --- a/solidity/contracts/zeto_nf_anon_nullifier.sol +++ b/solidity/contracts/zeto_nf_anon_nullifier.sol @@ -16,6 +16,7 @@ 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 {ZetoLock} from "./lib/zeto_lock.sol"; @@ -41,10 +42,10 @@ contract Zeto_NfAnonNullifier is function initialize( address initialOwner, Groth16Verifier_NfAnonNullifier _verifier, - address _lockVerifier + ILockVerifier _lockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); - __ZetoLock_init(_lockVerifier, address(0)); + __ZetoLock_init(_lockVerifier, IBatchLockVerifier(address(0))); verifier = _verifier; } diff --git a/solidity/scripts/tokens/Zeto_NfAnon.ts b/solidity/scripts/tokens/Zeto_NfAnon.ts index 50e3648..18e4819 100644 --- a/solidity/scripts/tokens/Zeto_NfAnon.ts +++ b/solidity/scripts/tokens/Zeto_NfAnon.ts @@ -23,6 +23,10 @@ export async function deployDependencies() { const { verifier, lockVerifier } = await ignition.deploy(zetoModule); return { deployer, - args: [await deployer.getAddress(), verifier.target, lockVerifier.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 02c20df..5970da2 100644 --- a/solidity/scripts/tokens/Zeto_NfAnonNullifier.ts +++ b/solidity/scripts/tokens/Zeto_NfAnonNullifier.ts @@ -23,7 +23,11 @@ export async function deployDependencies() { const { verifier, lockVerifier, smtLib, poseidon3 } = await ignition.deploy(zetoModule); return { deployer, - args: [await deployer.getAddress(), verifier.target, lockVerifier.target], + args: [ + await deployer.getAddress(), + verifier.target, + lockVerifier.target, + ], libraries: { SmtLib: smtLib.target, PoseidonUnit3L: poseidon3.target, diff --git a/solidity/test/lib/deploy.ts b/solidity/test/lib/deploy.ts index 711e8cf..7828153 100644 --- a/solidity/test/lib/deploy.ts +++ b/solidity/test/lib/deploy.ts @@ -48,9 +48,8 @@ export async function deployZeto(tokenName: string) { ? deployFungibleCloneable : deployNonFungibleCloneable; const result = await deployFunc(tokenName); - console.log("deployFunc result: ", JSON.stringify(result, null, 2)); ({ deployer, zetoImpl, erc20, args } = result as any); - const [ + let [ deployerAddr, verifier, depositVerifier, @@ -60,6 +59,9 @@ export async function deployZeto(tokenName: string) { 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 @@ -83,7 +85,6 @@ export async function deployZeto(tokenName: string) { batchLockVerifier: batchLockVerifier || "0x0000000000000000000000000000000000000000", }; - console.log("impl info: ", JSON.stringify(implInfo, null, 2)); const tx1 = await factory .connect(deployer) .registerImplementation(tokenName, implInfo as any); diff --git a/solidity/test/zeto_anon.ts b/solidity/test/zeto_anon.ts index 6419b67..45a3256 100644 --- a/solidity/test/zeto_anon.ts +++ b/solidity/test/zeto_anon.ts @@ -414,10 +414,21 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi )).rejectedWith(`UTXOAlreadyLocked(${utxo7.hash.toString()})`); }); - it("the original owner can NOT use the proper proof to spend the locked state", async function () { + it("the original owner can NOT 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]); - await expect(sendTx(Bob, inputCommitments, outputCommitments, encodedProof)).to.be.rejectedWith("UTXOAlreadyLocked"); + 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 () { diff --git a/solidity/test/zeto_anon_enc.ts b/solidity/test/zeto_anon_enc.ts index cc11bb5..6fff404 100644 --- a/solidity/test/zeto_anon_enc.ts +++ b/solidity/test/zeto_anon_enc.ts @@ -402,11 +402,20 @@ describe("Zeto based fungible token with anonymity and encryption", function () )).rejectedWith(`UTXOAlreadyLocked(${utxo4.hash.toString()})`); }); - it("the original owner can NOT use the proper proof to spend the locked state", async function () { + it("the original owner can NOT 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); - await expect(sendTx(Alice, inputCommitments, outputCommitments, encryptedValues, encryptionNonce, encodedProof, ephemeralKeypair.pubKey)).to.be.rejectedWith("UTXOAlreadyLocked"); + 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 () { diff --git a/solidity/test/zeto_anon_enc_nullifier.ts b/solidity/test/zeto_anon_enc_nullifier.ts index b76adcf..7d82b45 100644 --- a/solidity/test/zeto_anon_enc_nullifier.ts +++ b/solidity/test/zeto_anon_enc_nullifier.ts @@ -506,7 +506,7 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti )).rejectedWith(`UTXOAlreadyLocked(${nullifier1.hash.toString()})`); }); - it("the original owner can NOT use the proper proof to spend the locked state", async function () { + 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); @@ -531,6 +531,40 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti )).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(); diff --git a/solidity/test/zeto_anon_nullifier.ts b/solidity/test/zeto_anon_nullifier.ts index 9503996..b96f4d5 100644 --- a/solidity/test/zeto_anon_nullifier.ts +++ b/solidity/test/zeto_anon_nullifier.ts @@ -479,7 +479,7 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr )).rejectedWith(`UTXOAlreadyLocked(${nullifier1.hash.toString()})`); }); - it("the original owner can NOT use the proper proof to spend the locked state", async function () { + 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); @@ -504,6 +504,40 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr )).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(); From a51e34019f04b53b2a61750b83be7a4db5ba1c34 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Mon, 16 Dec 2024 12:36:58 -0500 Subject: [PATCH 11/13] change circom version to 2.2.1 Signed-off-by: Jim Zhang --- zkp/circuits/check_utxos_nf_owner.circom | 2 +- zkp/circuits/check_utxos_owner.circom | 2 +- zkp/circuits/check_utxos_owner_batch.circom | 2 +- zkp/circuits/lib/check-utxos-nf-owner.circom | 2 +- zkp/circuits/lib/check-utxos-owner.circom | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zkp/circuits/check_utxos_nf_owner.circom b/zkp/circuits/check_utxos_nf_owner.circom index 688d7f3..4df7489 100644 --- a/zkp/circuits/check_utxos_nf_owner.circom +++ b/zkp/circuits/check_utxos_nf_owner.circom @@ -13,7 +13,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -pragma circom 2.1.9; +pragma circom 2.2.1; include "./lib/check-utxos-nf-owner.circom"; diff --git a/zkp/circuits/check_utxos_owner.circom b/zkp/circuits/check_utxos_owner.circom index 0a24b82..470ca57 100644 --- a/zkp/circuits/check_utxos_owner.circom +++ b/zkp/circuits/check_utxos_owner.circom @@ -13,7 +13,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -pragma circom 2.1.9; +pragma circom 2.2.1; include "./lib/check-utxos-owner.circom"; diff --git a/zkp/circuits/check_utxos_owner_batch.circom b/zkp/circuits/check_utxos_owner_batch.circom index f9d29e0..919b32d 100644 --- a/zkp/circuits/check_utxos_owner_batch.circom +++ b/zkp/circuits/check_utxos_owner_batch.circom @@ -13,7 +13,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -pragma circom 2.1.9; +pragma circom 2.2.1; include "./lib/check-utxos-owner.circom"; diff --git a/zkp/circuits/lib/check-utxos-nf-owner.circom b/zkp/circuits/lib/check-utxos-nf-owner.circom index a62be09..875b1f5 100644 --- a/zkp/circuits/lib/check-utxos-nf-owner.circom +++ b/zkp/circuits/lib/check-utxos-nf-owner.circom @@ -13,7 +13,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -pragma circom 2.1.9; +pragma circom 2.2.1; include "./check-hashes-tokenid-uri.circom"; include "../node_modules/circomlib/circuits/babyjub.circom"; diff --git a/zkp/circuits/lib/check-utxos-owner.circom b/zkp/circuits/lib/check-utxos-owner.circom index ea2bc72..2d54ed8 100644 --- a/zkp/circuits/lib/check-utxos-owner.circom +++ b/zkp/circuits/lib/check-utxos-owner.circom @@ -13,7 +13,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -pragma circom 2.1.9; +pragma circom 2.2.1; include "./check-hashes.circom"; include "../node_modules/circomlib/circuits/babyjub.circom"; From e02bf1c39886a2230317a34ce3c8a3e99c2ee153 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Mon, 16 Dec 2024 13:33:47 -0500 Subject: [PATCH 12/13] fix deployment args for non-fungible tokens Signed-off-by: Jim Zhang --- solidity/test/lib/deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/test/lib/deploy.ts b/solidity/test/lib/deploy.ts index 7828153..a2a47b7 100644 --- a/solidity/test/lib/deploy.ts +++ b/solidity/test/lib/deploy.ts @@ -60,7 +60,7 @@ export async function deployZeto(tokenName: string) { batchLockVerifier, ] = args; if (!isFungible) { - ({ deployerAddr, verifier, lockVerifier } = args); + ([deployerAddr, verifier, lockVerifier] = args); } // we want to test the effectiveness of the factory contract From 30b3715ecf56d1d7cd2ad60bd15c12a57bfeb75f Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Mon, 16 Dec 2024 16:58:32 -0500 Subject: [PATCH 13/13] rename internal var vs. public func params Signed-off-by: Jim Zhang --- go-sdk/go.mod | 2 +- go-sdk/go.sum | 2 ++ go-sdk/integration-test/e2e_test.go | 14 ++++---- solidity/contracts/lib/zeto_fungible.sol | 18 +++++----- .../contracts/lib/zeto_fungible_withdraw.sol | 23 +++++++------ .../lib/zeto_fungible_withdraw_nullifier.sol | 22 ++++++------ solidity/contracts/lib/zeto_lock.sol | 16 ++++----- solidity/contracts/zeto_anon.sol | 34 +++++++++---------- solidity/contracts/zeto_anon_enc.sol | 34 +++++++++---------- .../contracts/zeto_anon_enc_nullifier.sol | 34 +++++++++---------- .../contracts/zeto_anon_enc_nullifier_kyc.sol | 34 +++++++++---------- ...eto_anon_enc_nullifier_non_repudiation.sol | 34 +++++++++---------- solidity/contracts/zeto_anon_nullifier.sol | 34 +++++++++---------- .../contracts/zeto_anon_nullifier_kyc.sol | 34 +++++++++---------- solidity/contracts/zeto_nf_anon.sol | 12 +++---- solidity/contracts/zeto_nf_anon_nullifier.sol | 12 +++---- 16 files changed, 181 insertions(+), 178 deletions(-) diff --git a/go-sdk/go.mod b/go-sdk/go.mod index e2bb617..dca55d4 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 8585f89..89887a1 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 7376fda..2e28944 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/lib/zeto_fungible.sol b/solidity/contracts/lib/zeto_fungible.sol index b35a719..9f0b867 100644 --- a/solidity/contracts/lib/zeto_fungible.sol +++ b/solidity/contracts/lib/zeto_fungible.sol @@ -25,23 +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( @@ -59,7 +59,7 @@ abstract contract ZetoFungible is OwnableUpgradeable { // Check the proof require( - depositVerifier.verifyProof( + _depositVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -69,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 2c8a859..2ee83b6 100644 --- a/solidity/contracts/lib/zeto_fungible_withdraw.sol +++ b/solidity/contracts/lib/zeto_fungible_withdraw.sol @@ -31,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( @@ -92,7 +93,7 @@ abstract contract ZetoFungibleWithdraw is ZetoFungible { } // Check the proof require( - batchWithdrawVerifier.verifyProof( + _batchWithdrawVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -114,7 +115,7 @@ abstract contract ZetoFungibleWithdraw is ZetoFungible { } // Check the proof require( - withdrawVerifier.verifyProof( + _withdrawVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -125,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 f41ce00..153e0dd 100644 --- a/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol +++ b/solidity/contracts/lib/zeto_fungible_withdraw_nullifier.sol @@ -31,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( @@ -103,7 +103,7 @@ abstract contract ZetoFungibleWithdrawWithNullifiers is ZetoFungible { } // Check the proof require( - batchWithdrawVerifier.verifyProof( + _batchWithdrawVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -126,7 +126,7 @@ abstract contract ZetoFungibleWithdrawWithNullifiers is ZetoFungible { } // Check the proof require( - withdrawVerifier.verifyProof( + _withdrawVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -137,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 index e4ef438..ce2b469 100644 --- a/solidity/contracts/lib/zeto_lock.sol +++ b/solidity/contracts/lib/zeto_lock.sol @@ -32,15 +32,15 @@ abstract contract ZetoLock is IZetoBase, IZetoLockable, OwnableUpgradeable { // by the same party that did the locking. mapping(uint256 => address) internal lockedUTXOs; - ILockVerifier internal lockVerifier; - IBatchLockVerifier internal batchLockVerifier; + ILockVerifier internal _lockVerifier; + IBatchLockVerifier internal _batchLockVerifier; function __ZetoLock_init( - ILockVerifier _lockVerifier, - IBatchLockVerifier _batchLockVerifier + ILockVerifier lockVerifier, + IBatchLockVerifier batchLockVerifier ) public onlyInitializing { - lockVerifier = _lockVerifier; - batchLockVerifier = IBatchLockVerifier(_batchLockVerifier); + _lockVerifier = lockVerifier; + _batchLockVerifier = batchLockVerifier; } // should be called by escrow contracts that will use uploaded proofs @@ -112,7 +112,7 @@ abstract contract ZetoLock is IZetoBase, IZetoLockable, OwnableUpgradeable { uint256[2] memory utxos, Commonlib.Proof calldata proof ) internal view returns (bool) { - return lockVerifier.verifyProof(proof.pA, proof.pB, proof.pC, utxos); + return _lockVerifier.verifyProof(proof.pA, proof.pB, proof.pC, utxos); } function _verifyBatchLockProof( @@ -120,6 +120,6 @@ abstract contract ZetoLock is IZetoBase, IZetoLockable, OwnableUpgradeable { Commonlib.Proof calldata proof ) internal view returns (bool) { return - batchLockVerifier.verifyProof(proof.pA, proof.pB, proof.pC, utxos); + _batchLockVerifier.verifyProof(proof.pA, proof.pB, proof.pC, utxos); } } diff --git a/solidity/contracts/zeto_anon.sol b/solidity/contracts/zeto_anon.sol index 4e056a7..8b9d923 100644 --- a/solidity/contracts/zeto_anon.sol +++ b/solidity/contracts/zeto_anon.sol @@ -49,28 +49,28 @@ contract Zeto_Anon is ZetoLock, UUPSUpgradeable { - Groth16Verifier_Anon internal verifier; - Groth16Verifier_AnonBatch internal batchVerifier; + 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, - ILockVerifier _lockVerifier, - IBatchLockVerifier _batchLockVerifier + 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 ); - __ZetoLock_init(_lockVerifier, _batchLockVerifier); - verifier = _verifier; - batchVerifier = _batchVerifier; + __ZetoLock_init(lockVerifier, batchLockVerifier); + _verifier = verifier; + _batchVerifier = batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -132,7 +132,7 @@ contract Zeto_Anon is // Check the proof using batchVerifier require( - batchVerifier.verifyProof( + _batchVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -153,7 +153,7 @@ contract Zeto_Anon is } // Check the proof require( - verifier.verifyProof( + _verifier.verifyProof( proof.pA, proof.pB, proof.pC, diff --git a/solidity/contracts/zeto_anon_enc.sol b/solidity/contracts/zeto_anon_enc.sol index 3d4e134..5e351e2 100644 --- a/solidity/contracts/zeto_anon_enc.sol +++ b/solidity/contracts/zeto_anon_enc.sol @@ -51,28 +51,28 @@ contract Zeto_AnonEnc is 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, - ILockVerifier _lockVerifier, - IBatchLockVerifier _batchLockVerifier + 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 ); - __ZetoLock_init(_lockVerifier, _batchLockVerifier); - verifier = _verifier; - batchVerifier = _batchVerifier; + __ZetoLock_init(lockVerifier, batchLockVerifier); + _verifier = verifier; + _batchVerifier = batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -154,7 +154,7 @@ contract Zeto_AnonEnc is // Check the proof using batchVerifier require( - batchVerifier.verifyProof( + _batchVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -178,7 +178,7 @@ contract Zeto_AnonEnc is } // Check the proof require( - verifier.verifyProof( + _verifier.verifyProof( proof.pA, proof.pB, proof.pC, diff --git a/solidity/contracts/zeto_anon_enc_nullifier.sol b/solidity/contracts/zeto_anon_enc_nullifier.sol index 3593094..e3cfb5c 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier.sol @@ -48,28 +48,28 @@ contract Zeto_AnonEncNullifier is 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, - ILockVerifier _lockVerifier, - IBatchLockVerifier _batchLockVerifier + 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 ); - __ZetoLock_init(_lockVerifier, _batchLockVerifier); - verifier = _verifier; - batchVerifier = _batchVerifier; + __ZetoLock_init(lockVerifier, batchLockVerifier); + _verifier = verifier; + _batchVerifier = batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -168,7 +168,7 @@ contract Zeto_AnonEncNullifier is // Check the proof using batchVerifier require( - batchVerifier.verifyProof( + _batchVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -193,7 +193,7 @@ contract Zeto_AnonEncNullifier is } // Check the proof require( - verifier.verifyProof( + _verifier.verifyProof( proof.pA, proof.pB, proof.pC, diff --git a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol index 8f8c533..f52ecef 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol @@ -50,29 +50,29 @@ contract Zeto_AnonEncNullifierKyc is 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, - ILockVerifier _lockVerifier, - IBatchLockVerifier _batchLockVerifier + 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 ); - __ZetoLock_init(_lockVerifier, _batchLockVerifier); - verifier = _verifier; - batchVerifier = _batchVerifier; + __ZetoLock_init(lockVerifier, batchLockVerifier); + _verifier = verifier; + _batchVerifier = batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -178,7 +178,7 @@ contract Zeto_AnonEncNullifierKyc is // Check the proof using batchVerifier require( - batchVerifier.verifyProof( + _batchVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -203,7 +203,7 @@ contract Zeto_AnonEncNullifierKyc is } // Check the proof require( - verifier.verifyProof( + _verifier.verifyProof( proof.pA, proof.pB, proof.pC, diff --git a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol index a692585..dc9e82d 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol @@ -57,31 +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, - ILockVerifier _lockVerifier, - IBatchLockVerifier _batchLockVerifier + 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 ); - __ZetoLock_init(_lockVerifier, _batchLockVerifier); - verifier = _verifier; - batchVerifier = _batchVerifier; + __ZetoLock_init(lockVerifier, batchLockVerifier); + _verifier = verifier; + _batchVerifier = batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -206,7 +206,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is // Check the proof using batchVerifier require( - batchVerifier.verifyProof( + _batchVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -236,7 +236,7 @@ contract Zeto_AnonEncNullifierNonRepudiation is } // Check the proof require( - verifier.verifyProof( + _verifier.verifyProof( proof.pA, proof.pB, proof.pC, diff --git a/solidity/contracts/zeto_anon_nullifier.sol b/solidity/contracts/zeto_anon_nullifier.sol index 4f38700..46b3861 100644 --- a/solidity/contracts/zeto_anon_nullifier.sol +++ b/solidity/contracts/zeto_anon_nullifier.sol @@ -47,28 +47,28 @@ contract Zeto_AnonNullifier is 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, - ILockVerifier _lockVerifier, - IBatchLockVerifier _batchLockVerifier + 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 ); - __ZetoLock_init(_lockVerifier, _batchLockVerifier); - verifier = _verifier; - batchVerifier = _batchVerifier; + __ZetoLock_init(lockVerifier, batchLockVerifier); + _verifier = verifier; + _batchVerifier = batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -145,7 +145,7 @@ contract Zeto_AnonNullifier is // Check the proof using batchVerifier require( - batchVerifier.verifyProof( + _batchVerifier.verifyProof( proof.pA, proof.pB, proof.pC, @@ -167,7 +167,7 @@ contract Zeto_AnonNullifier is } // Check the proof require( - verifier.verifyProof( + _verifier.verifyProof( proof.pA, proof.pB, proof.pC, diff --git a/solidity/contracts/zeto_anon_nullifier_kyc.sol b/solidity/contracts/zeto_anon_nullifier_kyc.sol index 52902b0..8219843 100644 --- a/solidity/contracts/zeto_anon_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_nullifier_kyc.sol @@ -50,29 +50,29 @@ contract Zeto_AnonNullifierKyc is 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, - ILockVerifier _lockVerifier, - IBatchLockVerifier _batchLockVerifier + 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 ); - __ZetoLock_init(_lockVerifier, _batchLockVerifier); - verifier = _verifier; - batchVerifier = _batchVerifier; + __ZetoLock_init(lockVerifier, batchLockVerifier); + _verifier = verifier; + _batchVerifier = batchVerifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -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, diff --git a/solidity/contracts/zeto_nf_anon.sol b/solidity/contracts/zeto_nf_anon.sol index 81eef66..c1139f4 100644 --- a/solidity/contracts/zeto_nf_anon.sol +++ b/solidity/contracts/zeto_nf_anon.sol @@ -32,16 +32,16 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U /// (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, ZetoLock, UUPSUpgradeable { - Groth16Verifier_NfAnon internal verifier; + Groth16Verifier_NfAnon internal _verifier; function initialize( address initialOwner, - Groth16Verifier_NfAnon _verifier, - ILockVerifier _lockVerifier + Groth16Verifier_NfAnon verifier, + ILockVerifier lockVerifier ) public initializer { __ZetoBase_init(initialOwner); - __ZetoLock_init(_lockVerifier, IBatchLockVerifier(address(0))); - verifier = _verifier; + __ZetoLock_init(lockVerifier, IBatchLockVerifier(address(0))); + _verifier = verifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -83,7 +83,7 @@ contract Zeto_NfAnon is IZeto, ZetoBase, ZetoLock, 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 8cc7d24..44dfb75 100644 --- a/solidity/contracts/zeto_nf_anon_nullifier.sol +++ b/solidity/contracts/zeto_nf_anon_nullifier.sol @@ -37,16 +37,16 @@ contract Zeto_NfAnonNullifier is ZetoLock, UUPSUpgradeable { - Groth16Verifier_NfAnonNullifier verifier; + Groth16Verifier_NfAnonNullifier _verifier; function initialize( address initialOwner, - Groth16Verifier_NfAnonNullifier _verifier, - ILockVerifier _lockVerifier + Groth16Verifier_NfAnonNullifier verifier, + ILockVerifier lockVerifier ) public initializer { __ZetoNullifier_init(initialOwner); - __ZetoLock_init(_lockVerifier, IBatchLockVerifier(address(0))); - verifier = _verifier; + __ZetoLock_init(lockVerifier, IBatchLockVerifier(address(0))); + _verifier = verifier; } function _authorizeUpgrade(address) internal override onlyOwner {} @@ -91,7 +91,7 @@ contract Zeto_NfAnonNullifier is // Check the proof require( - verifier.verifyProof(proof.pA, proof.pB, proof.pC, publicInputs), + _verifier.verifyProof(proof.pA, proof.pB, proof.pC, publicInputs), "Invalid proof" );