@lfdecentralizedtrust/zeto-contracts 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +70 -162
  2. package/contracts/factory.sol +30 -34
  3. package/contracts/factory_upgradeable.sol +160 -0
  4. package/contracts/lib/common/util.sol +40 -0
  5. package/contracts/lib/interfaces/ILockableCapability.sol +318 -0
  6. package/contracts/lib/interfaces/{izeto.sol → IZeto.sol} +13 -1
  7. package/contracts/lib/interfaces/{izeto_initializable.sol → IZetoInitializable.sol} +14 -12
  8. package/contracts/lib/interfaces/{izeto_kyc.sol → IZetoKyc.sol} +3 -3
  9. package/contracts/lib/interfaces/IZetoLockableCapability.sol +237 -0
  10. package/contracts/lib/interfaces/IZetoStorage.sol +106 -0
  11. package/contracts/lib/interfaces/{izeto_lockable.sol → IZetoVerifier.sol} +8 -21
  12. package/contracts/lib/registry.sol +77 -27
  13. package/contracts/lib/storage/base.sol +210 -0
  14. package/contracts/lib/storage/nullifier.sol +166 -0
  15. package/contracts/lib/zeto_common.sol +277 -28
  16. package/contracts/lib/zeto_fungible.sol +451 -33
  17. package/contracts/lib/zeto_fungible_base.sol +69 -0
  18. package/contracts/lib/zeto_fungible_burn.sol +83 -53
  19. package/contracts/lib/zeto_fungible_burn_nullifier.sol +115 -72
  20. package/contracts/lib/zeto_fungible_nullifier.sol +167 -0
  21. package/contracts/lib/zeto_lockable.sol +613 -0
  22. package/contracts/lib/zeto_non_fungible.sol +236 -0
  23. package/contracts/lib/zeto_non_fungible_base.sol +61 -0
  24. package/contracts/lib/zeto_non_fungible_nullifier.sol +61 -0
  25. package/contracts/test/escrow1.sol +90 -34
  26. package/contracts/test/escrow2.sol +64 -29
  27. package/contracts/test/qurrency.sol +10 -2
  28. package/contracts/test/smt.sol +8 -3
  29. package/contracts/test/tendecimals.sol +14 -12
  30. package/contracts/verifiers/impl/anon.sol +189 -0
  31. package/contracts/verifiers/impl/anon_batch.sol +301 -0
  32. package/contracts/verifiers/impl/anon_enc.sol +266 -0
  33. package/contracts/verifiers/impl/anon_enc_batch.sol +602 -0
  34. package/contracts/verifiers/impl/anon_enc_nullifier.sol +287 -0
  35. package/contracts/verifiers/impl/anon_enc_nullifier_batch.sol +679 -0
  36. package/contracts/verifiers/impl/anon_enc_nullifier_kyc.sol +294 -0
  37. package/contracts/verifiers/impl/anon_enc_nullifier_kyc_batch.sol +686 -0
  38. package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation.sol +413 -0
  39. package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation_batch.sol +1141 -0
  40. package/contracts/verifiers/impl/anon_nullifier_kyc_transfer.sol +217 -0
  41. package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked.sol +196 -0
  42. package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked_batch.sol +308 -0
  43. package/contracts/verifiers/impl/anon_nullifier_kyc_transfer_batch.sol +385 -0
  44. package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer.sol +497 -0
  45. package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer_batch.sol +1001 -0
  46. package/contracts/verifiers/{verifier_anon_nullifier_transferLocked.sol → impl/anon_nullifier_transfer.sol} +12 -19
  47. package/contracts/verifiers/{verifier_anon_nullifier_transferLocked_batch.sol → impl/anon_nullifier_transfer_batch.sol} +44 -51
  48. package/contracts/verifiers/impl/burn.sol +182 -0
  49. package/contracts/verifiers/impl/burn_batch.sol +238 -0
  50. package/contracts/verifiers/impl/burn_nullifier.sol +203 -0
  51. package/contracts/verifiers/impl/burn_nullifier_batch.sol +315 -0
  52. package/contracts/verifiers/impl/deposit.sol +182 -0
  53. package/contracts/verifiers/impl/deposit_kyc.sol +189 -0
  54. package/contracts/verifiers/impl/nf_anon.sol +175 -0
  55. package/contracts/verifiers/{verifier_nf_anon_nullifier_transferLocked.sol → impl/nf_anon_nullifier_transfer.sol} +6 -13
  56. package/contracts/verifiers/impl/withdraw.sol +189 -0
  57. package/contracts/verifiers/impl/withdraw_batch.sol +245 -0
  58. package/contracts/verifiers/impl/withdraw_nullifier.sol +210 -0
  59. package/contracts/verifiers/impl/withdraw_nullifier_batch.sol +322 -0
  60. package/contracts/verifiers/verifier_anon.sol +31 -186
  61. package/contracts/verifiers/verifier_anon_batch.sol +31 -298
  62. package/contracts/verifiers/verifier_anon_enc.sol +31 -263
  63. package/contracts/verifiers/verifier_anon_enc_batch.sol +31 -599
  64. package/contracts/verifiers/verifier_anon_enc_nullifier.sol +31 -284
  65. package/contracts/verifiers/verifier_anon_enc_nullifier_batch.sol +33 -676
  66. package/contracts/verifiers/verifier_anon_enc_nullifier_kyc.sol +31 -291
  67. package/contracts/verifiers/verifier_anon_enc_nullifier_kyc_batch.sol +33 -683
  68. package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation.sol +33 -410
  69. package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation_batch.sol +33 -1138
  70. package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer.sol +33 -214
  71. package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked.sol +33 -221
  72. package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked_batch.sol +33 -389
  73. package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer_batch.sol +33 -382
  74. package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer.sol +33 -221
  75. package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer_batch.sol +33 -389
  76. package/contracts/verifiers/verifier_anon_nullifier_transfer.sol +33 -207
  77. package/contracts/verifiers/verifier_anon_nullifier_transfer_batch.sol +33 -375
  78. package/contracts/verifiers/verifier_burn.sol +31 -179
  79. package/contracts/verifiers/verifier_burn_batch.sol +31 -235
  80. package/contracts/verifiers/verifier_burn_nullifier.sol +31 -200
  81. package/contracts/verifiers/verifier_burn_nullifier_batch.sol +31 -312
  82. package/contracts/verifiers/verifier_deposit.sol +31 -179
  83. package/contracts/verifiers/verifier_deposit_kyc.sol +34 -0
  84. package/contracts/verifiers/verifier_nf_anon.sol +31 -172
  85. package/contracts/verifiers/verifier_nf_anon_nullifier_transfer.sol +33 -179
  86. package/contracts/verifiers/verifier_withdraw.sol +31 -186
  87. package/contracts/verifiers/verifier_withdraw_batch.sol +31 -242
  88. package/contracts/verifiers/verifier_withdraw_nullifier.sol +31 -207
  89. package/contracts/verifiers/verifier_withdraw_nullifier_batch.sol +33 -319
  90. package/contracts/zeto_anon.sol +77 -231
  91. package/contracts/zeto_anon_burnable.sol +56 -12
  92. package/contracts/zeto_anon_enc.sol +93 -190
  93. package/contracts/zeto_anon_enc_nullifier.sol +249 -195
  94. package/contracts/zeto_anon_enc_nullifier_kyc.sol +51 -232
  95. package/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +231 -238
  96. package/contracts/zeto_anon_nullifier.sol +164 -298
  97. package/contracts/zeto_anon_nullifier_burnable.sol +68 -18
  98. package/contracts/zeto_anon_nullifier_kyc.sol +40 -345
  99. package/contracts/zeto_anon_nullifier_qurrency.sol +218 -360
  100. package/contracts/zeto_nf_anon.sol +55 -130
  101. package/contracts/zeto_nf_anon_nullifier.sol +90 -152
  102. package/hardhat.config.ts +9 -3
  103. package/ignition/modules/lib/deps.ts +9 -6
  104. package/ignition/modules/zeto_anon_burnable.ts +8 -7
  105. package/ignition/modules/zeto_anon_enc_nullifier.ts +33 -1
  106. package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +2 -2
  107. package/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +2 -1
  108. package/ignition/modules/zeto_anon_nullifier.ts +30 -5
  109. package/ignition/modules/zeto_anon_nullifier_burnable.ts +52 -17
  110. package/ignition/modules/zeto_anon_nullifier_kyc.ts +27 -12
  111. package/ignition/modules/zeto_anon_nullifier_qurrency.ts +2 -1
  112. package/ignition/modules/zeto_nf_anon_nullifier.ts +16 -12
  113. package/package.json +4 -2
  114. package/scripts/deploy_cloneable.ts +19 -10
  115. package/scripts/deploy_upgradeable.ts +9 -5
  116. package/scripts/tokens/Zeto_Anon.ts +3 -3
  117. package/scripts/tokens/Zeto_AnonBurnable.ts +1 -1
  118. package/scripts/tokens/Zeto_AnonEnc.ts +12 -3
  119. package/scripts/tokens/Zeto_AnonEncNullifier.ts +7 -3
  120. package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +1 -1
  121. package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +3 -1
  122. package/scripts/tokens/Zeto_AnonNullifier.ts +3 -1
  123. package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +3 -1
  124. package/scripts/tokens/Zeto_AnonNullifierKyc.ts +1 -1
  125. package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +3 -1
  126. package/scripts/tokens/Zeto_NfAnon.ts +2 -2
  127. package/scripts/tokens/Zeto_NfAnonNullifier.ts +3 -2
  128. package/test/factory.ts +46 -73
  129. package/test/factory_upgradeable.ts +311 -0
  130. package/test/lib/anon_nullifier_helpers.ts +89 -0
  131. package/test/lib/anon_zeto_helpers.ts +76 -0
  132. package/test/lib/deploy.ts +3 -2
  133. package/test/lib/utils.ts +74 -35
  134. package/test/test/escrow1.ts +185 -58
  135. package/test/test/escrow2.ts +200 -107
  136. package/test/test/qurrency.ts +13 -33
  137. package/test/usdc-shielding.ts +39 -26
  138. package/test/utils.ts +144 -21
  139. package/test/zeto_anon.ts +956 -465
  140. package/test/zeto_anon_enc.ts +881 -142
  141. package/test/zeto_anon_enc_nullifier.ts +847 -38
  142. package/test/zeto_anon_enc_nullifier_kyc.ts +120 -181
  143. package/test/zeto_anon_enc_nullifier_non_repudiation.ts +77 -46
  144. package/test/zeto_anon_nullifier.ts +1212 -953
  145. package/test/zeto_anon_nullifier_kyc.ts +674 -655
  146. package/test/zeto_anon_nullifier_qurrency.ts +452 -306
  147. package/test/zeto_nf_anon.ts +737 -137
  148. package/test/zeto_nf_anon_nullifier.ts +876 -246
  149. package/contracts/lib/zeto_base.sol +0 -293
  150. package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
  151. package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
  152. package/contracts/lib/zeto_nullifier.sol +0 -334
  153. package/contracts/zkDvP.sol_ +0 -273
  154. package/test/zkDvP.ts_ +0 -455
  155. /package/contracts/lib/{common.sol → common/common.sol} +0 -0
@@ -0,0 +1,236 @@
1
+ // Copyright © 2024 Kaleido, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+ // Licensed under the Apache License, Version 2.0 (the "License");
6
+ // you may not use this file except in compliance with the License.
7
+ // You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing, software
12
+ // distributed under the License is distributed on an "AS IS" BASIS,
13
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ // See the License for the specific language governing permissions and
15
+ // limitations under the License.
16
+ pragma solidity ^0.8.27;
17
+
18
+ import {ZetoLockable} from "./zeto_lockable.sol";
19
+ import {Commonlib} from "./common/common.sol";
20
+ import {IZetoInitializable} from "./interfaces/IZetoInitializable.sol";
21
+ import {IZetoStorage} from "./interfaces/IZetoStorage.sol";
22
+
23
+ /// @title A sample base implementation of a Zeto based non-fungible
24
+ /// token contract.
25
+ /// @author Kaleido, Inc.
26
+ /// @dev The proof has the following statements:
27
+ /// - The sender owns the private key whose public key is part of the pre-image of the input UTXOs commitments
28
+ /// (aka the sender is authorized to spend the input UTXOs)
29
+ /// - The input UTXOs and output UTXOs are valid in terms of obeying mass conservation rules
30
+ ///
31
+ /// Inherits {ZetoLockable} so the create/update/delegate/spend/
32
+ /// cancel lock lifecycle is shared with the fungible siblings via
33
+ /// a single implementation. The two ZK-circuit-shaped hooks
34
+ /// ({_doLockTransition} and {_transferLocked}) are overridden here
35
+ /// to match the NF circuit's fixed 1-in/1-out shape — there is no
36
+ /// input/output padding and no batch verifier path.
37
+ abstract contract ZetoNonFungible is ZetoLockable {
38
+ function __ZetoNonFungible_init(
39
+ string calldata name_,
40
+ string calldata symbol_,
41
+ address initialOwner,
42
+ IZetoInitializable.VerifiersInfo calldata verifiers,
43
+ IZetoStorage storage_
44
+ ) internal onlyInitializing {
45
+ __ZetoCommon_init(name_, symbol_, initialOwner, verifiers, storage_);
46
+ }
47
+
48
+ /**
49
+ * @dev Single-input/single-output transfer.
50
+ *
51
+ * @param input The UTXO to be spent by the transaction.
52
+ * @param output The new UTXO to generate, for future transactions
53
+ * to spend.
54
+ * @param proof A zero knowledge proof that the submitter is
55
+ * authorized to spend the input, and that the output is valid
56
+ * in terms of obeying mass conservation rules.
57
+ * @param data Opaque data appended to the {UTXOTransfer} event for
58
+ * off-chain consumers.
59
+ *
60
+ * Emits a {UTXOTransfer} event.
61
+ */
62
+ function transfer(
63
+ uint256 input,
64
+ uint256 output,
65
+ bytes calldata proof,
66
+ bytes calldata data
67
+ ) public {
68
+ uint256[] memory inputs = new uint256[](1);
69
+ inputs[0] = input;
70
+ uint256[] memory outputs = new uint256[](1);
71
+ outputs[0] = output;
72
+ uint256[] memory lockedOutputs;
73
+
74
+ validateTransactionProposal(
75
+ inputs,
76
+ outputs,
77
+ lockedOutputs,
78
+ proof,
79
+ false
80
+ );
81
+
82
+ (
83
+ uint256[] memory publicInputs,
84
+ Commonlib.Proof memory proofStruct
85
+ ) = constructPublicInputs(inputs, outputs, proof, false);
86
+ verifyProof(proofStruct, publicInputs, false, false);
87
+ processInputsAndOutputs(inputs, outputs, false);
88
+ emit UTXOTransfer(inputs, outputs, msg.sender, data);
89
+ }
90
+
91
+ // ------------------------------------------------------------------
92
+ // ZetoLockable hook overrides — non-fungible flavour.
93
+ //
94
+ // The NF circuits are fixed 1-in/1-out (or 1-in/1-locked-out for
95
+ // create-lock, 1-locked-in/1-out for spend/cancel), so there is no
96
+ // padding step and the batch-verifier path is never selected.
97
+ //
98
+ // Caller-supplied `args` are validated by ZetoLockable to be
99
+ // {ZetoCreateLockArgs}/{ZetoSpendLockArgs}; this hook trusts that
100
+ // shape and additionally enforces that the array sizes match the
101
+ // NF circuit (single input plus a single locked or unlocked
102
+ // output).
103
+ // ------------------------------------------------------------------
104
+
105
+ /// @dev Thrown when an NF lock-lifecycle call is invoked with an
106
+ /// input/output array shape that does not match the fixed
107
+ /// 1-in/1-out circuit. Catching this at the contract layer
108
+ /// gives a typed revert before the proof verifier would fail
109
+ /// with a generic {InvalidProof}.
110
+ error InvalidNonFungibleArity(
111
+ uint256 inputs,
112
+ uint256 outputs,
113
+ uint256 lockedOutputs
114
+ );
115
+
116
+ function _doLockTransition(
117
+ ZetoCreateLockArgs memory args
118
+ ) internal override {
119
+ // NF createLock: spend exactly one unlocked input and produce
120
+ // exactly one locked output. There is no regular (unlocked)
121
+ // output for NF lock creation — a single token cannot be split.
122
+ if (
123
+ args.inputs.length != 1 ||
124
+ args.outputs.length != 0 ||
125
+ args.lockedOutputs.length != 1
126
+ ) {
127
+ revert InvalidNonFungibleArity(
128
+ args.inputs.length,
129
+ args.outputs.length,
130
+ args.lockedOutputs.length
131
+ );
132
+ }
133
+
134
+ validateTransactionProposal(
135
+ args.inputs,
136
+ args.outputs,
137
+ args.lockedOutputs,
138
+ args.proof,
139
+ false
140
+ );
141
+
142
+ // The circuit treats the locked output as the "output"; combine
143
+ // for the public-inputs construction.
144
+ uint256[] memory allOutputs = new uint256[](1);
145
+ allOutputs[0] = args.lockedOutputs[0];
146
+
147
+ (
148
+ uint256[] memory publicInputs,
149
+ Commonlib.Proof memory proofStruct
150
+ ) = constructPublicInputs(args.inputs, allOutputs, args.proof, false);
151
+ verifyProof(proofStruct, publicInputs, false, false);
152
+
153
+ processInputsAndOutputs(args.inputs, args.outputs, false);
154
+ processLockedOutputs(args.lockedOutputs);
155
+ // The freshly-locked output starts under the lock creator as
156
+ // both owner and spender; record that on the per-UTXO
157
+ // projection so {locked} can report it without a reverse lookup.
158
+ _setLockDelegates(args.lockedOutputs, msg.sender);
159
+ }
160
+
161
+ function _transferLocked(
162
+ bytes32 /* lockId */,
163
+ uint256[] memory lockedInputs,
164
+ uint256[] memory lockedOutputs,
165
+ uint256[] memory outputs,
166
+ bytes memory proof,
167
+ bytes memory /* data */
168
+ ) internal override {
169
+ // NF spend/cancel: consume exactly one locked input and produce
170
+ // exactly one output. The output may be either a regular
171
+ // unlocked UTXO (the typical spend case) or a fresh locked UTXO
172
+ // (e.g. re-lock under a new spender via cancel-then-create).
173
+ if (
174
+ lockedInputs.length != 1 ||
175
+ lockedOutputs.length + outputs.length != 1
176
+ ) {
177
+ revert InvalidNonFungibleArity(
178
+ lockedInputs.length,
179
+ outputs.length,
180
+ lockedOutputs.length
181
+ );
182
+ }
183
+
184
+ validateTransactionProposal(
185
+ lockedInputs,
186
+ outputs,
187
+ lockedOutputs,
188
+ proof,
189
+ true
190
+ );
191
+
192
+ // The circuit treats locked and unlocked outputs identically, so
193
+ // both feed `constructPublicInputs` via the same single-element
194
+ // `allOutputs` array.
195
+ uint256[] memory allOutputs = new uint256[](1);
196
+ allOutputs[0] = lockedOutputs.length == 1
197
+ ? lockedOutputs[0]
198
+ : outputs[0];
199
+
200
+ (
201
+ uint256[] memory publicInputs,
202
+ Commonlib.Proof memory proofStruct
203
+ ) = constructPublicInputs(lockedInputs, allOutputs, proof, true);
204
+ verifyProof(proofStruct, publicInputs, false, true);
205
+
206
+ // Storage-side bookkeeping keeps the two ledgers strictly
207
+ // disjoint: the unlocked output (if any) goes through
208
+ // `processOutputs`, the locked output (if any) goes through
209
+ // `processLockedOutputs`. Mirroring `_doLockTransition` here
210
+ // avoids the locked-output being recorded as both unlocked and
211
+ // locked, which would let it be spent twice (once via the
212
+ // unlocked path, once via the lockable path).
213
+ processInputsAndOutputs(lockedInputs, outputs, true);
214
+ processLockedOutputs(lockedOutputs);
215
+ // Any newly-locked output produced by this spend defaults to
216
+ // the current spender as its delegate. (When `lockedOutputs` is
217
+ // empty -- the common case -- this is a no-op.)
218
+ _setLockDelegates(lockedOutputs, msg.sender);
219
+ }
220
+ }
221
+
222
+ /// @dev ERC-7201 (`erc7201:zeto.storage.ZetoNonFungible`).
223
+ library ZetoNonFungibleStorage {
224
+ struct Layout {
225
+ uint256 __reserved;
226
+ }
227
+
228
+ bytes32 private constant STORAGE_LOCATION =
229
+ 0x70f918cef12f78aa293f0474363ccfa2a5e8bbdc86def1d4dd7d861fb8e6f600;
230
+
231
+ function layout() internal pure returns (Layout storage $) {
232
+ assembly {
233
+ $.slot := STORAGE_LOCATION
234
+ }
235
+ }
236
+ }
@@ -0,0 +1,61 @@
1
+ // Copyright © 2024 Kaleido, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+ // Licensed under the Apache License, Version 2.0 (the "License");
6
+ // you may not use this file except in compliance with the License.
7
+ // You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing, software
12
+ // distributed under the License is distributed on an "AS IS" BASIS,
13
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ // See the License for the specific language governing permissions and
15
+ // limitations under the License.
16
+ pragma solidity ^0.8.27;
17
+
18
+ import {IZetoInitializable} from "./interfaces/IZetoInitializable.sol";
19
+ import {ZetoNonFungible} from "./zeto_non_fungible.sol";
20
+ import {IZetoStorage} from "./interfaces/IZetoStorage.sol";
21
+ import {BaseStorage} from "./storage/base.sol";
22
+
23
+ /// @title A sample base implementation of a Zeto based non-fungible token contract
24
+ /// without using nullifiers. Each UTXO's spending status is explicitly tracked.
25
+ /// @author Kaleido, Inc.
26
+ /// @dev Wires {BaseStorage} into {ZetoNonFungible} for non-nullifier
27
+ /// flavours of Zeto NF tokens. See {ZetoNonFungible} for the
28
+ /// rationale behind the omitted lock lifecycle.
29
+ abstract contract ZetoNonFungibleBase is ZetoNonFungible {
30
+ function __ZetoNonFungibleBase_init(
31
+ string calldata name_,
32
+ string calldata symbol_,
33
+ address initialOwner,
34
+ IZetoInitializable.VerifiersInfo calldata verifiers
35
+ ) internal onlyInitializing {
36
+ IZetoStorage storage_ = new BaseStorage();
37
+ __ZetoNonFungible_init(
38
+ name_,
39
+ symbol_,
40
+ initialOwner,
41
+ verifiers,
42
+ storage_
43
+ );
44
+ }
45
+ }
46
+
47
+ /// @dev ERC-7201 (`erc7201:zeto.storage.ZetoNonFungibleBase`).
48
+ library ZetoNonFungibleBaseStorage {
49
+ struct Layout {
50
+ uint256 __reserved;
51
+ }
52
+
53
+ bytes32 private constant STORAGE_LOCATION =
54
+ 0x3b5aad0db95258b4583c7272ce26730b4879c3622cde60d71e7f2a14bdd3ae00;
55
+
56
+ function layout() internal pure returns (Layout storage $) {
57
+ assembly {
58
+ $.slot := STORAGE_LOCATION
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,61 @@
1
+ // Copyright © 2024 Kaleido, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+ // Licensed under the Apache License, Version 2.0 (the "License");
6
+ // you may not use this file except in compliance with the License.
7
+ // You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing, software
12
+ // distributed under the License is distributed on an "AS IS" BASIS,
13
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ // See the License for the specific language governing permissions and
15
+ // limitations under the License.
16
+ pragma solidity ^0.8.27;
17
+
18
+ import {IZetoInitializable} from "./interfaces/IZetoInitializable.sol";
19
+ import {ZetoNonFungible} from "./zeto_non_fungible.sol";
20
+ import {IZetoStorage} from "./interfaces/IZetoStorage.sol";
21
+ import {NullifierStorage} from "./storage/nullifier.sol";
22
+
23
+ /// @title A sample base implementation of a Zeto based non-fungible token
24
+ /// contract with nullifier-based history masking.
25
+ /// @author Kaleido, Inc.
26
+ /// @dev Wires {NullifierStorage} into {ZetoNonFungible} for the nullifier
27
+ /// flavours of Zeto NF tokens. See {ZetoNonFungible} for the
28
+ /// rationale behind the omitted lock lifecycle.
29
+ abstract contract ZetoNonFungibleNullifier is ZetoNonFungible {
30
+ function __ZetoNonFungibleNullifier_init(
31
+ string calldata name_,
32
+ string calldata symbol_,
33
+ address initialOwner,
34
+ IZetoInitializable.VerifiersInfo calldata verifiers
35
+ ) internal onlyInitializing {
36
+ IZetoStorage storage_ = new NullifierStorage();
37
+ __ZetoNonFungible_init(
38
+ name_,
39
+ symbol_,
40
+ initialOwner,
41
+ verifiers,
42
+ storage_
43
+ );
44
+ }
45
+ }
46
+
47
+ /// @dev ERC-7201 (`erc7201:zeto.storage.ZetoNonFungibleNullifier`).
48
+ library ZetoNonFungibleNullifierStorage {
49
+ struct Layout {
50
+ uint256 __reserved;
51
+ }
52
+
53
+ bytes32 private constant STORAGE_LOCATION =
54
+ 0x02976bb09873e8949dece591ee756708ea74c24901fa2000868b92802a9a5200;
55
+
56
+ function layout() internal pure returns (Layout storage $) {
57
+ assembly {
58
+ $.slot := STORAGE_LOCATION
59
+ }
60
+ }
61
+ }
@@ -15,16 +15,50 @@
15
15
  // limitations under the License.
16
16
  pragma solidity ^0.8.27;
17
17
 
18
- import {Commonlib} from "../lib/common.sol";
18
+ import {Commonlib} from "../lib/common/common.sol";
19
19
  import {Zeto_Anon} from "../zeto_anon.sol";
20
- // import {console} from "hardhat/console.sol";
20
+ import {ILockableCapability} from "../lib/interfaces/ILockableCapability.sol";
21
+ import {IZetoLockableCapability} from "../lib/interfaces/IZetoLockableCapability.sol";
21
22
 
22
- /// @title A sample on-chain implementation of an escrow contract using Zeto tokens
23
+ /// @title zkEscrow1
23
24
  /// @author Kaleido, Inc.
24
- /// @dev Implements escrow based payment flows with Zeto_Anon tokens
25
+ /// @notice Sample on-chain escrow built on top of {Zeto_Anon} that brokers a
26
+ /// payment between a payer and a payee using the new
27
+ /// {ILockableCapability} lifecycle.
28
+ ///
29
+ /// Lifecycle of a single escrowed payment:
30
+ /// 1. The payer calls {Zeto_Anon.createLock} to lock one or more
31
+ /// UTXOs they own. The payer is initially both the lock owner
32
+ /// and spender.
33
+ /// 2. The payer calls {Zeto_Anon.delegateLock} naming this escrow
34
+ /// as the new spender. From this point on, only this escrow
35
+ /// can spend or cancel the lock.
36
+ /// 3. The payer (or anyone) calls {initiatePayment} on this
37
+ /// escrow with the lockId and the proposed unlocked output
38
+ /// UTXOs. The escrow asserts it is the current spender of the
39
+ /// lock before recording the payment.
40
+ /// 4. An authorised approver calls {approvePayment} with a ZK
41
+ /// proof attesting that `(lockedInputs, outputs)` is a valid
42
+ /// transition under {Zeto_Anon}'s circuit. The escrow stores
43
+ /// the proof but does not yet consume the lock.
44
+ /// 5. Anyone (typically the payee) calls {completePayment}. The
45
+ /// escrow forwards the stored proof to {Zeto_Anon.spendLock},
46
+ /// which consumes the locked inputs and mints the unlocked
47
+ /// outputs to their recipients in a single atomic step.
48
+ ///
49
+ /// Notes on the new lock model:
50
+ /// * The escrow does NOT choose the locked inputs at approval
51
+ /// or completion time. They are pinned by `_locks[lockId]`
52
+ /// inside {Zeto_Anon} at create-time, so "spending lock A
53
+ /// always consumes lock A's UTXOs" is a hard contract
54
+ /// invariant rather than a soft hash check on user-supplied
55
+ /// arrays.
56
+ /// * The escrow likewise does not choose `lockedOutputs` (it
57
+ /// always sends them empty); the spend completes by minting
58
+ /// plain unlocked UTXOs to the recipients.
25
59
  contract zkEscrow1 {
26
60
  enum PaymentStatus {
27
- UNKNOWN, // this is the default value for the empty payment slots
61
+ UNKNOWN, // default value for empty payment slots
28
62
  INITIATED,
29
63
  APPROVED,
30
64
  COMPLETED,
@@ -32,10 +66,10 @@ contract zkEscrow1 {
32
66
  }
33
67
 
34
68
  struct Payment {
35
- uint256[] lockedInputs;
69
+ bytes32 lockId;
36
70
  uint256[] outputs;
37
71
  PaymentStatus status;
38
- Commonlib.Proof proof;
72
+ bytes proof;
39
73
  }
40
74
 
41
75
  mapping(uint256 => Payment) public payments;
@@ -45,7 +79,7 @@ contract zkEscrow1 {
45
79
 
46
80
  event PaymentInitiated(
47
81
  uint256 paymentId,
48
- uint256[] lockedInputs,
82
+ bytes32 lockId,
49
83
  uint256[] outputs,
50
84
  bytes data
51
85
  );
@@ -56,35 +90,41 @@ contract zkEscrow1 {
56
90
  zeto = Zeto_Anon(zetoAddress);
57
91
  }
58
92
 
93
+ /// @notice Record a new escrowed payment against an existing lock.
94
+ /// @dev The escrow MUST already be the current spender of the lock
95
+ /// (the payer must have called {Zeto_Anon.delegateLock} naming
96
+ /// this contract as the new spender before calling here).
97
+ /// The locked inputs themselves are not stored on the
98
+ /// payment; they are read fresh from {Zeto_Anon.getLockedInputs}
99
+ /// at approval and completion time so the escrow always
100
+ /// operates on the lock's storage-pinned content.
59
101
  function initiatePayment(
60
- uint256[] memory lockedInputs,
102
+ bytes32 lockId,
61
103
  uint256[] memory outputs,
62
104
  bytes calldata data
63
105
  ) public {
64
- for (uint256 i = 0; i < lockedInputs.length; i++) {
65
- bool locked;
66
- address delegate;
67
- (locked, delegate) = zeto.locked(lockedInputs[i]);
68
- require(locked, "Input not locked");
69
- require(
70
- delegate == address(this),
71
- "Input not locked to this contract"
72
- );
73
- }
106
+ ILockableCapability.LockInfo memory info = zeto.getLock(lockId);
107
+ require(info.spender == address(this), "Escrow is not the spender");
74
108
  inflightCount++;
75
- Commonlib.Proof memory emptyProof;
109
+ bytes memory emptyProof;
76
110
  payments[inflightCount] = Payment(
77
- lockedInputs,
111
+ lockId,
78
112
  outputs,
79
113
  PaymentStatus.INITIATED,
80
114
  emptyProof
81
115
  );
82
- emit PaymentInitiated(inflightCount, lockedInputs, outputs, data);
116
+ emit PaymentInitiated(inflightCount, lockId, outputs, data);
83
117
  }
84
118
 
119
+ /// @notice Verify the spend ZK proof for an initiated payment and
120
+ /// move the payment into the APPROVED state.
121
+ /// @dev The verification mirrors what {Zeto_Anon._transferLocked}
122
+ /// would do later (same {checkAndPadCommitments} + locked-input
123
+ /// verifier path), so a successful approval here guarantees
124
+ /// that {completePayment} will not revert on proof grounds.
85
125
  function approvePayment(
86
126
  uint256 paymentId,
87
- Commonlib.Proof memory proof,
127
+ bytes memory proof,
88
128
  bytes calldata data
89
129
  ) public {
90
130
  Payment storage payment = payments[paymentId];
@@ -92,12 +132,18 @@ contract zkEscrow1 {
92
132
  payment.status == PaymentStatus.INITIATED,
93
133
  "Payment not initiated"
94
134
  );
95
- payment.lockedInputs = zeto.checkAndPadCommitments(
96
- payment.lockedInputs
97
- );
98
- payment.outputs = zeto.checkAndPadCommitments(payment.outputs);
135
+ uint256[] memory lockedInputs = zeto.getLockedInputs(payment.lockId);
136
+ (
137
+ uint256[] memory paddedInputs,
138
+ uint256[] memory paddedOutputs
139
+ ) = zeto.checkAndPadCommitments(lockedInputs, payment.outputs);
99
140
  require(
100
- zeto.verifyProof(payment.lockedInputs, payment.outputs, proof),
141
+ zeto.constructPublicSignalsAndVerifyProof(
142
+ paddedInputs,
143
+ paddedOutputs,
144
+ proof,
145
+ true
146
+ ),
101
147
  "Invalid proof"
102
148
  );
103
149
  payment.proof = proof;
@@ -105,18 +151,28 @@ contract zkEscrow1 {
105
151
  emit PaymentApproved(paymentId, data);
106
152
  }
107
153
 
154
+ /// @notice Forward the approved spend proof to {Zeto_Anon.spendLock}.
155
+ /// @dev Encodes the {IZetoLockableCapability.ZetoSpendLockArgs}
156
+ /// payload from `payment` and invokes `spendLock(lockId, ...)`
157
+ /// as the lock's current spender. After this call returns the
158
+ /// lock is consumed, the locked UTXOs are marked spent on the
159
+ /// underlying Zeto storage, and the unlocked outputs are
160
+ /// emitted to their recipients.
108
161
  function completePayment(uint256 paymentId, bytes calldata data) public {
109
162
  Payment storage payment = payments[paymentId];
110
163
  require(
111
164
  payment.status == PaymentStatus.APPROVED,
112
165
  "Payment not approved"
113
166
  );
114
- zeto.transferLocked(
115
- payment.lockedInputs,
116
- payment.outputs,
117
- payment.proof,
118
- "0x"
119
- );
167
+ IZetoLockableCapability.ZetoSpendLockArgs
168
+ memory spendArgs = IZetoLockableCapability.ZetoSpendLockArgs({
169
+ txId: bytes32(paymentId),
170
+ lockedOutputs: new uint256[](0),
171
+ outputs: payment.outputs,
172
+ proof: payment.proof,
173
+ data: ""
174
+ });
175
+ zeto.spendLock(payment.lockId, abi.encode(spendArgs), "");
120
176
  payment.status = PaymentStatus.COMPLETED;
121
177
  emit PaymentCompleted(paymentId, data);
122
178
  }