@lfdecentralizedtrust/zeto-contracts 0.2.2 → 0.5.1

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 (164) hide show
  1. package/README.md +76 -162
  2. package/config/eip170.ts +30 -0
  3. package/contracts/factory.sol +30 -34
  4. package/contracts/factory_upgradeable.sol +11 -7
  5. package/contracts/lib/common/util.sol +40 -0
  6. package/contracts/lib/interfaces/ILockableCapability.sol +318 -0
  7. package/contracts/lib/interfaces/{izeto.sol → IZeto.sol} +13 -1
  8. package/contracts/lib/interfaces/{izeto_initializable.sol → IZetoInitializable.sol} +14 -12
  9. package/contracts/lib/interfaces/{izeto_kyc.sol → IZetoKyc.sol} +3 -3
  10. package/contracts/lib/interfaces/IZetoLockHooks.sol +42 -0
  11. package/contracts/lib/interfaces/IZetoLockableCapability.sol +237 -0
  12. package/contracts/lib/interfaces/IZetoStorage.sol +106 -0
  13. package/contracts/lib/interfaces/{izeto_lockable.sol → IZetoVerifier.sol} +8 -21
  14. package/contracts/lib/registry.sol +74 -26
  15. package/contracts/lib/storage/base.sol +210 -0
  16. package/contracts/lib/storage/nullifier.sol +166 -0
  17. package/contracts/lib/zeto_common.sol +277 -28
  18. package/contracts/lib/zeto_fungible.sol +443 -33
  19. package/contracts/lib/zeto_fungible_base.sol +69 -0
  20. package/contracts/lib/zeto_fungible_burn.sol +83 -53
  21. package/contracts/lib/zeto_fungible_burn_nullifier.sol +115 -72
  22. package/contracts/lib/zeto_fungible_nullifier.sol +167 -0
  23. package/contracts/lib/zeto_lockable.sol +219 -0
  24. package/contracts/lib/zeto_lockable_lib.sol +460 -0
  25. package/contracts/lib/zeto_lockable_storage.sol +43 -0
  26. package/contracts/lib/zeto_non_fungible.sol +228 -0
  27. package/contracts/lib/zeto_non_fungible_base.sol +61 -0
  28. package/contracts/lib/zeto_non_fungible_nullifier.sol +61 -0
  29. package/contracts/test/escrow1.sol +90 -34
  30. package/contracts/test/escrow2.sol +64 -29
  31. package/contracts/test/qurrency.sol +10 -2
  32. package/contracts/test/smt.sol +6 -1
  33. package/contracts/test/tendecimals.sol +14 -12
  34. package/contracts/verifiers/impl/anon.sol +189 -0
  35. package/contracts/verifiers/impl/anon_batch.sol +301 -0
  36. package/contracts/verifiers/impl/anon_enc.sol +266 -0
  37. package/contracts/verifiers/impl/anon_enc_batch.sol +602 -0
  38. package/contracts/verifiers/impl/anon_enc_nullifier.sol +287 -0
  39. package/contracts/verifiers/impl/anon_enc_nullifier_batch.sol +679 -0
  40. package/contracts/verifiers/impl/anon_enc_nullifier_kyc.sol +294 -0
  41. package/contracts/verifiers/impl/anon_enc_nullifier_kyc_batch.sol +686 -0
  42. package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation.sol +413 -0
  43. package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation_batch.sol +1141 -0
  44. package/contracts/verifiers/impl/anon_nullifier_kyc_transfer.sol +217 -0
  45. package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked.sol +196 -0
  46. package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked_batch.sol +308 -0
  47. package/contracts/verifiers/impl/anon_nullifier_kyc_transfer_batch.sol +385 -0
  48. package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer.sol +497 -0
  49. package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer_batch.sol +1001 -0
  50. package/contracts/verifiers/{verifier_anon_nullifier_transferLocked.sol → impl/anon_nullifier_transfer.sol} +12 -19
  51. package/contracts/verifiers/{verifier_anon_nullifier_transferLocked_batch.sol → impl/anon_nullifier_transfer_batch.sol} +44 -51
  52. package/contracts/verifiers/impl/burn.sol +182 -0
  53. package/contracts/verifiers/impl/burn_batch.sol +238 -0
  54. package/contracts/verifiers/impl/burn_nullifier.sol +203 -0
  55. package/contracts/verifiers/impl/burn_nullifier_batch.sol +315 -0
  56. package/contracts/verifiers/impl/deposit.sol +182 -0
  57. package/contracts/verifiers/impl/deposit_kyc.sol +189 -0
  58. package/contracts/verifiers/impl/nf_anon.sol +175 -0
  59. package/contracts/verifiers/{verifier_nf_anon_nullifier_transferLocked.sol → impl/nf_anon_nullifier_transfer.sol} +6 -13
  60. package/contracts/verifiers/impl/withdraw.sol +189 -0
  61. package/contracts/verifiers/impl/withdraw_batch.sol +245 -0
  62. package/contracts/verifiers/impl/withdraw_nullifier.sol +210 -0
  63. package/contracts/verifiers/impl/withdraw_nullifier_batch.sol +322 -0
  64. package/contracts/verifiers/verifier_anon.sol +31 -186
  65. package/contracts/verifiers/verifier_anon_batch.sol +31 -298
  66. package/contracts/verifiers/verifier_anon_enc.sol +31 -263
  67. package/contracts/verifiers/verifier_anon_enc_batch.sol +31 -599
  68. package/contracts/verifiers/verifier_anon_enc_nullifier.sol +31 -284
  69. package/contracts/verifiers/verifier_anon_enc_nullifier_batch.sol +33 -676
  70. package/contracts/verifiers/verifier_anon_enc_nullifier_kyc.sol +31 -291
  71. package/contracts/verifiers/verifier_anon_enc_nullifier_kyc_batch.sol +33 -683
  72. package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation.sol +33 -410
  73. package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation_batch.sol +33 -1138
  74. package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer.sol +33 -214
  75. package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked.sol +33 -221
  76. package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked_batch.sol +33 -389
  77. package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer_batch.sol +33 -382
  78. package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer.sol +33 -221
  79. package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer_batch.sol +33 -389
  80. package/contracts/verifiers/verifier_anon_nullifier_transfer.sol +33 -207
  81. package/contracts/verifiers/verifier_anon_nullifier_transfer_batch.sol +33 -375
  82. package/contracts/verifiers/verifier_burn.sol +31 -179
  83. package/contracts/verifiers/verifier_burn_batch.sol +31 -235
  84. package/contracts/verifiers/verifier_burn_nullifier.sol +31 -200
  85. package/contracts/verifiers/verifier_burn_nullifier_batch.sol +31 -312
  86. package/contracts/verifiers/verifier_deposit.sol +31 -179
  87. package/contracts/verifiers/verifier_deposit_kyc.sol +34 -0
  88. package/contracts/verifiers/verifier_nf_anon.sol +31 -172
  89. package/contracts/verifiers/verifier_nf_anon_nullifier_transfer.sol +33 -179
  90. package/contracts/verifiers/verifier_withdraw.sol +31 -186
  91. package/contracts/verifiers/verifier_withdraw_batch.sol +31 -242
  92. package/contracts/verifiers/verifier_withdraw_nullifier.sol +31 -207
  93. package/contracts/verifiers/verifier_withdraw_nullifier_batch.sol +33 -319
  94. package/contracts/zeto_anon.sol +77 -231
  95. package/contracts/zeto_anon_burnable.sol +56 -12
  96. package/contracts/zeto_anon_enc.sol +93 -190
  97. package/contracts/zeto_anon_enc_nullifier.sol +249 -195
  98. package/contracts/zeto_anon_enc_nullifier_kyc.sol +51 -232
  99. package/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +231 -238
  100. package/contracts/zeto_anon_nullifier.sol +164 -298
  101. package/contracts/zeto_anon_nullifier_burnable.sol +68 -18
  102. package/contracts/zeto_anon_nullifier_kyc.sol +40 -345
  103. package/contracts/zeto_anon_nullifier_qurrency.sol +217 -361
  104. package/contracts/zeto_nf_anon.sol +55 -130
  105. package/contracts/zeto_nf_anon_nullifier.sol +90 -152
  106. package/hardhat.config.ts +18 -3
  107. package/ignition/modules/lib/deps.ts +13 -0
  108. package/ignition/modules/test/tendecimals.ts +7 -1
  109. package/ignition/modules/zeto_anon.ts +3 -0
  110. package/ignition/modules/zeto_anon_burnable.ts +11 -7
  111. package/ignition/modules/zeto_anon_enc.ts +3 -0
  112. package/ignition/modules/zeto_anon_enc_nullifier.ts +34 -0
  113. package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +5 -2
  114. package/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +3 -0
  115. package/ignition/modules/zeto_anon_nullifier.ts +31 -4
  116. package/ignition/modules/zeto_anon_nullifier_burnable.ts +53 -16
  117. package/ignition/modules/zeto_anon_nullifier_kyc.ts +30 -12
  118. package/ignition/modules/zeto_anon_nullifier_qurrency.ts +3 -0
  119. package/ignition/modules/zeto_nf_anon.ts +3 -1
  120. package/ignition/modules/zeto_nf_anon_nullifier.ts +17 -12
  121. package/package.json +7 -3
  122. package/scripts/deploy_cloneable.ts +19 -10
  123. package/scripts/deploy_upgradeable.ts +9 -5
  124. package/scripts/lib/zeto_libraries.ts +47 -0
  125. package/scripts/tokens/Zeto_Anon.ts +6 -3
  126. package/scripts/tokens/Zeto_AnonBurnable.ts +4 -1
  127. package/scripts/tokens/Zeto_AnonEnc.ts +15 -3
  128. package/scripts/tokens/Zeto_AnonEncNullifier.ts +11 -8
  129. package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +7 -6
  130. package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +7 -6
  131. package/scripts/tokens/Zeto_AnonNullifier.ts +7 -6
  132. package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +7 -6
  133. package/scripts/tokens/Zeto_AnonNullifierKyc.ts +7 -6
  134. package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +7 -8
  135. package/scripts/tokens/Zeto_NfAnon.ts +5 -3
  136. package/scripts/tokens/Zeto_NfAnonNullifier.ts +7 -7
  137. package/test/factory.ts +55 -73
  138. package/test/lib/anon_nullifier_helpers.ts +89 -0
  139. package/test/lib/anon_zeto_helpers.ts +76 -0
  140. package/test/lib/deploy.ts +5 -2
  141. package/test/lib/eip170.ts +23 -0
  142. package/test/lib/utils.ts +74 -35
  143. package/test/test/escrow1.ts +185 -58
  144. package/test/test/escrow2.ts +200 -107
  145. package/test/test/qurrency.ts +13 -33
  146. package/test/usdc-shielding.ts +39 -27
  147. package/test/utils.ts +144 -21
  148. package/test/zeto_anon.ts +956 -465
  149. package/test/zeto_anon_enc.ts +881 -143
  150. package/test/zeto_anon_enc_nullifier.ts +850 -39
  151. package/test/zeto_anon_enc_nullifier_kyc.ts +123 -182
  152. package/test/zeto_anon_enc_nullifier_non_repudiation.ts +80 -47
  153. package/test/zeto_anon_nullifier.ts +1212 -954
  154. package/test/zeto_anon_nullifier_kyc.ts +677 -656
  155. package/test/zeto_anon_nullifier_qurrency.ts +455 -307
  156. package/test/zeto_nf_anon.ts +737 -138
  157. package/test/zeto_nf_anon_nullifier.ts +876 -247
  158. package/contracts/lib/zeto_base.sol +0 -293
  159. package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
  160. package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
  161. package/contracts/lib/zeto_nullifier.sol +0 -340
  162. package/contracts/zkDvP.sol_ +0 -273
  163. package/test/zkDvP.ts_ +0 -455
  164. /package/contracts/lib/{common.sol → common/common.sol} +0 -0
@@ -15,12 +15,24 @@
15
15
  // limitations under the License.
16
16
 
17
17
  import { ethers, ignition, network } from "hardhat";
18
- import { Signer, encodeBytes32String, ZeroHash, lock } from "ethers";
18
+ import {
19
+ Signer,
20
+ AbiCoder,
21
+ BigNumberish,
22
+ ContractTransactionReceipt,
23
+ } from "ethers";
19
24
  import { expect } from "chai";
20
- import { loadCircuit, getProofHash } from "zeto-js";
25
+ import { loadCircuit } from "zeto-js";
21
26
  import { Merkletree, InMemoryDB, str2Bytes } from "@iden3/js-merkletree";
22
27
  import zkEscrowModule from "../../ignition/modules/test/escrow2";
23
- import zetoAnonNullifierTests from "../zeto_anon_nullifier";
28
+ import { prepareProof as prepareNullifierUnlockedProof } from "../lib/anon_nullifier_helpers";
29
+ // The locked-input transition for Zeto_AnonNullifier reuses the simple
30
+ // `anon` circuit (the lockVerifier in the ignition module is wired to
31
+ // {Groth16Verifier_Anon}, mirroring what the production ZK proof path
32
+ // expects from {ZetoFungible._transferLocked}). So the spend proof for
33
+ // the escrow flow is built with `prepareProof` from {anon_zeto_helpers}
34
+ // (same circuit helper as in `zeto_anon.ts`), not from the nullifier suite.
35
+ import { prepareProof as prepareProofAnonCircuit } from "../lib/anon_zeto_helpers";
24
36
  import {
25
37
  UTXO,
26
38
  User,
@@ -29,11 +41,35 @@ import {
29
41
  newNullifier,
30
42
  doMint,
31
43
  ZERO_UTXO,
32
- parseUTXOEvents,
44
+ logger,
33
45
  } from "../lib/utils";
34
46
  import { loadProvingKeys } from "../utils";
35
47
  import { deployZeto } from "../lib/deploy";
36
48
 
49
+ // ABI fragments mirror the canonical ZetoLockableCapability *Args
50
+ // payloads used uniformly across all Zeto tokens.
51
+ const CREATE_ARGS_ABI =
52
+ "tuple(bytes32 txId, uint256[] inputs, uint256[] outputs, uint256[] lockedOutputs, bytes proof)";
53
+ const DELEGATE_ARGS_ABI = "tuple(bytes32 txId)";
54
+
55
+ function encodeCreateArgs(args: {
56
+ txId: string;
57
+ inputs: BigNumberish[];
58
+ outputs: BigNumberish[];
59
+ lockedOutputs: BigNumberish[];
60
+ proof: string;
61
+ }) {
62
+ return new AbiCoder().encode([CREATE_ARGS_ABI], [args]);
63
+ }
64
+
65
+ function encodeDelegateArgs(txId: string) {
66
+ return new AbiCoder().encode([DELEGATE_ARGS_ABI], [{ txId }]);
67
+ }
68
+
69
+ function randomBytes32(): string {
70
+ return ethers.hexlify(ethers.randomBytes(32));
71
+ }
72
+
37
73
  describe("Escrow flow for payment with Zeto_AnonNullifier", function () {
38
74
  let Alice: User;
39
75
  let Bob: User;
@@ -49,20 +85,22 @@ describe("Escrow flow for payment with Zeto_AnonNullifier", function () {
49
85
 
50
86
  // UTXOs involved in the escrow flow
51
87
  let lockedPayment1: UTXO;
52
- let nullifier1: UTXO;
53
88
  let paymentToBob: UTXO;
89
+ let lockId: string;
54
90
 
55
91
  // other variables
56
92
  let deployer: Signer;
57
- let circuit: any, circuitLocked: any;
58
- let provingKey: string, provingKeyLocked: string;
93
+ // unlocked-input transfer circuit (nullifier + merkle proof)
94
+ let circuit: any;
95
+ let provingKey: string;
96
+ // locked-input transfer circuit (simpler `anon`, no nullifier or root)
97
+ let circuitLocked: any;
98
+ let provingKeyLocked: string;
59
99
  let paymentId: any;
60
- let smtAlice: Merkletree, smtAliceLocked: Merkletree;
61
- let smtBob: Merkletree;
100
+ let smtAlice: Merkletree;
62
101
 
63
102
  before(async function () {
64
103
  if (network.name !== "hardhat") {
65
- // accommodate for longer block times on public networks
66
104
  this.timeout(120000);
67
105
  }
68
106
  let [d, a, b, c] = await ethers.getSigners();
@@ -75,19 +113,14 @@ describe("Escrow flow for payment with Zeto_AnonNullifier", function () {
75
113
  ({ provingKeyFile: provingKey } = loadProvingKeys(
76
114
  "anon_nullifier_transfer",
77
115
  ));
78
- circuitLocked = await loadCircuit("anon_nullifier_transferLocked");
79
- ({ provingKeyFile: provingKeyLocked } = loadProvingKeys(
80
- "anon_nullifier_transferLocked",
81
- ));
116
+ circuitLocked = await loadCircuit("anon");
117
+ ({ provingKeyFile: provingKeyLocked } = loadProvingKeys("anon"));
82
118
 
83
119
  const storage1 = new InMemoryDB(str2Bytes(""));
84
120
  smtAlice = new Merkletree(storage1, true, 64);
85
121
 
86
- const storage2 = new InMemoryDB(str2Bytes(""));
87
- smtAliceLocked = new Merkletree(storage2, true, 64);
88
-
89
122
  ({ deployer, zeto: zkPayment } = await deployZeto("Zeto_AnonNullifier"));
90
- console.log(`ZK Payment contract deployed at ${zkPayment.target}`);
123
+ logger.debug(`ZK Payment contract deployed at ${zkPayment.target}`);
91
124
  ({ zkEscrow } = await ignition.deploy(zkEscrowModule, {
92
125
  parameters: {
93
126
  zkEscrow2: {
@@ -102,136 +135,196 @@ describe("Escrow flow for payment with Zeto_AnonNullifier", function () {
102
135
  payment2 = newUTXO(20, Alice);
103
136
  const result = await doMint(zkPayment, deployer, [payment1, payment2]);
104
137
 
105
- // simulate Alice and Bob listening to minting events and updating his local merkle tree
106
138
  for (const log of result.logs) {
107
139
  const event = zkPayment.interface.parseLog(log as any);
108
140
  expect(event.args.outputs.length).to.equal(2);
109
141
  }
110
142
 
143
+ // Mirror the on-chain unlocked-commitments SMT off-chain so we can
144
+ // build merkle proofs for the unlocked-input transition that
145
+ // creates the lock.
111
146
  await smtAlice.add(payment1.hash, payment1.hash);
112
147
  await smtAlice.add(payment2.hash, payment2.hash);
113
148
  });
114
149
 
115
- it("Alice locks some payment tokens and designates the escrow as the delegate", async function () {
116
- // Alice generates the nullifiers for the UTXOs to be spent
150
+ it("Alice locks payment1 by calling createLock on the Zeto token", async function () {
151
+ // Alice generates the nullifier for payment1 and a merkle inclusion
152
+ // proof in the unlocked-commitments SMT.
117
153
  const nullifier1 = newNullifier(payment1, Alice);
118
-
119
- // Alice generates inclusion proofs for the UTXOs to be spent
120
- let root = await smtAlice.root();
121
- const proof1 = await smtAlice.generateCircomVerifierProof(
122
- payment1.hash,
123
- root,
124
- );
125
- const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
154
+ const root = await smtAlice.root();
155
+ const p1 = await smtAlice.generateCircomVerifierProof(payment1.hash, root);
156
+ const p2 = await smtAlice.generateCircomVerifierProof(0n, root);
126
157
  const merkleProofs = [
127
- proof1.siblings.map((s) => s.bigInt()),
128
- proof2.siblings.map((s) => s.bigInt()),
158
+ p1.siblings.map((s) => s.bigInt()),
159
+ p2.siblings.map((s) => s.bigInt()),
129
160
  ];
130
161
 
131
162
  lockedPayment1 = newUTXO(payment1.value!, Alice);
132
- const { inputCommitments, outputCommitments, encodedProof } =
133
- await zetoAnonNullifierTests.prepareProof(
134
- circuit,
135
- provingKey,
136
- Alice,
137
- [payment1, ZERO_UTXO],
138
- [nullifier1, ZERO_UTXO],
139
- [lockedPayment1, ZERO_UTXO],
140
- root.bigInt(),
141
- merkleProofs,
142
- [Alice, Alice],
143
- );
163
+ const encodedZkProof = await prepareNullifierUnlockedProof(
164
+ circuit,
165
+ provingKey,
166
+ Alice,
167
+ [payment1, ZERO_UTXO],
168
+ [nullifier1, ZERO_UTXO],
169
+ [lockedPayment1, ZERO_UTXO],
170
+ root.bigInt(),
171
+ merkleProofs,
172
+ [Alice, Alice],
173
+ );
174
+
175
+ const createArgs = encodeCreateArgs({
176
+ txId: randomBytes32(),
177
+ inputs: [nullifier1.hash],
178
+ // For Zeto_AnonNullifier createLock, lockedOutputs holds the
179
+ // freshly-locked UTXO and outputs is empty; payment1's full value
180
+ // moves into the lock.
181
+ outputs: [],
182
+ lockedOutputs: [lockedPayment1.hash],
183
+ proof: encodeUnlockedProof(root.bigInt(), encodedZkProof),
184
+ });
185
+
186
+ lockId = await zkPayment.connect(Alice.signer).computeLockId(createArgs);
187
+
144
188
  const tx = await zkPayment
145
189
  .connect(Alice.signer)
146
- .lock(
147
- [nullifier1.hash],
148
- [],
149
- outputCommitments,
150
- root.bigInt(),
151
- encodedProof,
190
+ .createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x");
191
+ const result = await tx.wait();
192
+ logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
193
+
194
+ const info = await zkPayment.getLock(lockId);
195
+ expect(info.owner).to.equal(Alice.ethAddress);
196
+ expect(info.spender).to.equal(Alice.ethAddress);
197
+ expect(await zkPayment.locked(lockedPayment1.hash)).to.deep.equal([
198
+ true,
199
+ Alice.ethAddress,
200
+ ]);
201
+ }).timeout(120000);
202
+
203
+ it("Alice delegates the lock to the escrow contract", async function () {
204
+ const tx = await zkPayment
205
+ .connect(Alice.signer)
206
+ .delegateLock(
207
+ lockId,
208
+ encodeDelegateArgs(randomBytes32()),
152
209
  zkEscrow.target,
153
210
  "0x",
154
211
  );
155
- const result = await tx.wait();
156
- const events = parseUTXOEvents(zkPayment, result);
157
-
158
- // simulate Alice's tradinig partner listening to the locking events and verifying the committed (locked) value
159
- const lockedUTXO = events[0].lockedOutputs[0];
160
- const lockDelegate = events[0].delegate;
161
- // Alice's trading partner is sent the secrets for the locked UTXO (which is still owned by Alice)
162
- // in an off-chain message. so they can be used to verify the locked UTXO, and the lock delegate
163
- const check = newUTXO(lockedPayment1.value!, Alice, lockedPayment1.salt);
164
- expect(lockedUTXO).to.equal(check.hash);
165
- expect(lockDelegate).to.equal(zkEscrow.target);
166
-
167
- await smtAliceLocked.add(
168
- lockedPayment1.hash,
169
- ethers.toBigInt(zkEscrow.target),
170
- );
212
+ await tx.wait();
213
+
214
+ const info = await zkPayment.getLock(lockId);
215
+ expect(info.spender).to.equal(zkEscrow.target);
216
+ expect(await zkPayment.locked(lockedPayment1.hash)).to.deep.equal([
217
+ true,
218
+ zkEscrow.target,
219
+ ]);
171
220
  });
172
221
 
173
222
  it("Alice initiates a payment transaction to Bob through the escrow", async function () {
174
- nullifier1 = newNullifier(lockedPayment1, Alice);
175
223
  paymentToBob = newUTXO(lockedPayment1.value!, Bob);
176
224
  const tx = await zkEscrow
177
225
  .connect(Alice.signer)
178
- .initiatePayment([nullifier1.hash], [paymentToBob.hash], "0x");
226
+ .initiatePayment(lockId, [paymentToBob.hash], "0x");
179
227
  const result = await tx.wait();
180
- const events = parseUTXOEvents(zkEscrow, result);
181
- // simulate Bob listening to the payment events and verifying the proposed payment
182
- const proposedPayment = events[0].outputs[0];
183
- expect(proposedPayment).to.equal(paymentToBob.hash);
184
- paymentId = events[0].paymentId;
228
+ const initiated = parseEscrowEvent(zkEscrow, result, "PaymentInitiated");
229
+ expect(initiated, "PaymentInitiated event not emitted").to.not.be.undefined;
230
+ expect(initiated!.outputs[0]).to.equal(paymentToBob.hash);
231
+ expect(initiated!.lockId).to.equal(lockId);
232
+ paymentId = initiated!.paymentId;
185
233
  });
186
234
 
187
- it("Alice approves the payment by submitting a valid proof that can successfully verify the proposed payment", async function () {
188
- let root = await smtAliceLocked.root();
189
- const proof1 = await smtAliceLocked.generateCircomVerifierProof(
190
- lockedPayment1.hash,
191
- root,
192
- );
193
- const proof2 = await smtAliceLocked.generateCircomVerifierProof(0n, root);
194
- const merkleProofs = [
195
- proof1.siblings.map((s) => s.bigInt()),
196
- proof2.siblings.map((s) => s.bigInt()),
197
- ];
198
- const { encodedProof } = await zetoAnonNullifierTests.prepareProof(
235
+ it("Alice approves the payment by submitting a valid locked-input proof", async function () {
236
+ // The locked-input transition uses the simple `anon` circuit and
237
+ // operates on the raw UTXO hash (lockedPayment1.hash), not on a
238
+ // nullifier. The Zeto storage layer has already validated the
239
+ // input is in the locked-UTXO ledger.
240
+ const encodedZkProof = await prepareProofAnonCircuit(
199
241
  circuitLocked,
200
242
  provingKeyLocked,
201
243
  Alice,
202
244
  [lockedPayment1, ZERO_UTXO],
203
- [nullifier1, ZERO_UTXO],
204
245
  [paymentToBob, ZERO_UTXO],
205
- root.bigInt(),
206
- merkleProofs,
207
- [Bob, Bob],
208
- zkEscrow.target,
246
+ [Bob, {}],
209
247
  );
210
248
  const tx = await zkEscrow
211
249
  .connect(Alice.signer)
212
- .approvePayment(paymentId, root.bigInt(), encodedProof, "0x");
250
+ .approvePayment(paymentId, encodeLockedProof(encodedZkProof), "0x");
213
251
  const result = await tx.wait();
214
- // simulate Bob listening to the escrow events and verifying the payment has been approved.
215
- // the escrow contract guaratees that the proof is valid
216
- const events = parseUTXOEvents(zkEscrow, result);
217
- const approvedPayment = events[0].paymentId;
218
- expect(approvedPayment).to.equal(paymentId);
219
- });
220
252
 
221
- it("Bob, or anyone, can call the escrow to finalize the payment and receive the locked UTXO", async function () {
253
+ const approved = parseEscrowEvent(zkEscrow, result, "PaymentApproved");
254
+ expect(approved, "PaymentApproved event not emitted").to.not.be.undefined;
255
+ expect(approved!.paymentId).to.equal(paymentId);
256
+ }).timeout(120000);
257
+
258
+ it("Bob, or anyone, can call the escrow to finalize the payment and receive the unlocked UTXO", async function () {
222
259
  const tx = await zkEscrow
223
260
  .connect(Bob.signer)
224
261
  .completePayment(paymentId, "0x");
225
262
  const result = await tx.wait();
226
- // simulate Bob listening to the payment events and verifying
227
- // the expected UTXO has been transferred to him
228
- let events = parseUTXOEvents(zkPayment, result);
229
- const transferredPayment = events[0].outputs[0];
230
- const check = newUTXO(paymentToBob.value!, Bob, paymentToBob.salt);
231
- expect(transferredPayment).to.equal(check.hash);
232
- // simulate Bob listening to the escrow events and verifying the payment has been completed
233
- events = parseUTXOEvents(zkEscrow, result);
234
- const completedPayment = events[1].paymentId;
235
- expect(completedPayment).to.equal(paymentId);
263
+
264
+ // The Zeto token emits ZetoLockSpent containing the new unlocked
265
+ // outputs.
266
+ const lockSpent = result!.logs
267
+ .map((l) => {
268
+ try {
269
+ return zkPayment.interface.parseLog(l as any);
270
+ } catch (_e) {
271
+ return null;
272
+ }
273
+ })
274
+ .find((p: any) => p && p.name === "ZetoLockSpent");
275
+ expect(lockSpent, "ZetoLockSpent event not emitted").to.not.be.undefined;
276
+ expect(lockSpent!.args.outputs[0]).to.equal(paymentToBob.hash);
277
+
278
+ expect(await zkPayment.isLockActive(lockId)).to.equal(false);
279
+ expect((await zkPayment.locked(lockedPayment1.hash))[0]).to.be.false;
280
+
281
+ const completed = parseEscrowEvent(zkEscrow, result, "PaymentCompleted");
282
+ expect(completed, "PaymentCompleted event not emitted").to.not.be.undefined;
283
+ expect(completed!.paymentId).to.equal(paymentId);
236
284
  });
237
285
  });
286
+
287
+ // encodeUnlockedProof matches the wire format consumed by
288
+ // {Zeto_AnonNullifier.constructPublicInputs} on the unlocked branch:
289
+ // `(uint256 root, Commonlib.Proof)`. Used for the createLock proof.
290
+ function encodeUnlockedProof(root: any, proof: any) {
291
+ return new AbiCoder().encode(
292
+ ["uint256 root", "tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
293
+ [root, proof],
294
+ );
295
+ }
296
+
297
+ // encodeLockedProof matches the wire format consumed on the locked
298
+ // branch: just `Commonlib.Proof`, no root prefix (the locked input is
299
+ // already vouched for by the storage layer, no merkle inclusion proof
300
+ // is needed). Used for the spendLock / approvePayment proof.
301
+ function encodeLockedProof(proof: any) {
302
+ return new AbiCoder().encode(
303
+ ["tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
304
+ [proof],
305
+ );
306
+ }
307
+
308
+ // parseEscrowEvent extracts the first event of a given name from a
309
+ // transaction receipt by parsing logs through the escrow contract's
310
+ // own ABI. Unlike the Zeto-token-specific `parseUTXOEvents`, this
311
+ // helper handles the escrow's custom Payment* events.
312
+ function parseEscrowEvent(
313
+ escrowContract: any,
314
+ receipt: ContractTransactionReceipt | null,
315
+ name: string,
316
+ ): any | undefined {
317
+ if (!receipt) return undefined;
318
+ for (const log of receipt.logs || []) {
319
+ let parsed;
320
+ try {
321
+ parsed = escrowContract.interface.parseLog(log as any);
322
+ } catch (_e) {
323
+ continue;
324
+ }
325
+ if (parsed && parsed.name === name) {
326
+ return parsed.args;
327
+ }
328
+ }
329
+ return undefined;
330
+ }
@@ -15,10 +15,16 @@
15
15
  // limitations under the License.
16
16
 
17
17
  import { ethers, ignition, network } from "hardhat";
18
- import { Signer, BigNumberish, ContractTransactionReceipt } from "ethers";
18
+ import { BigNumberish, ContractTransactionReceipt } from "ethers";
19
+ import crypto from "crypto";
19
20
  import { groth16 } from "snarkjs";
20
21
  import { Merkletree, InMemoryDB, str2Bytes } from "@iden3/js-merkletree";
21
- import { loadCircuit, encodeProof } from "zeto-js";
22
+ import {
23
+ loadCircuit,
24
+ encodeProof,
25
+ newEncryptionNonce,
26
+ bytesToBits,
27
+ } from "zeto-js";
22
28
  import qurrencyModule from "../../ignition/modules/test/qurrency";
23
29
  import { User, newUser, newUTXO, newNullifier } from "../lib/utils";
24
30
  import { loadProvingKeys } from "../utils";
@@ -88,35 +94,9 @@ describe("Test Qurrency verifier", function () {
88
94
  Alice.babyJubPublicKey,
89
95
  ];
90
96
 
91
- const m = [
92
- 1665, 1665, 0, 1665, 0, 1665, 1665, 0, 1665, 0, 0, 1665, 1665, 1665, 1665,
93
- 0, 0, 1665, 0, 0, 0, 1665, 1665, 0, 1665, 0, 1665, 0, 0, 1665, 1665, 0, 0,
94
- 1665, 0, 0, 1665, 1665, 1665, 0, 0, 0, 0, 0, 0, 1665, 0, 0, 1665, 0, 0,
95
- 1665, 0, 1665, 1665, 0, 1665, 1665, 0, 0, 1665, 1665, 1665, 0, 0, 0, 0, 0,
96
- 0, 1665, 0, 0, 1665, 1665, 0, 0, 0, 1665, 1665, 0, 1665, 1665, 1665, 1665,
97
- 0, 1665, 1665, 0, 1665, 1665, 1665, 1665, 0, 1665, 1665, 0, 0, 0, 1665,
98
- 1665, 0, 1665, 1665, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
99
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
100
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
101
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
102
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
103
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
104
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
105
- ];
106
-
107
- const randomness = [
108
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
109
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
110
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
111
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
112
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
113
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
114
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
115
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
116
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
117
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
118
- 0, 0, 0, 0, 0, 0,
119
- ];
97
+ const randomness = crypto.randomBytes(32);
98
+ const r = bytesToBits(randomness);
99
+ const encryptionNonce = newEncryptionNonce();
120
100
 
121
101
  const inputObj: any = {
122
102
  nullifiers,
@@ -131,8 +111,8 @@ describe("Test Qurrency verifier", function () {
131
111
  outputValues,
132
112
  outputSalts,
133
113
  outputOwnerPublicKeys,
134
- randomness,
135
- m,
114
+ randomness: r,
115
+ encryptionNonce,
136
116
  };
137
117
  const witness = await circuit.calculateWTNSBin(inputObj, true);
138
118
  const startProofGeneration = Date.now();
@@ -15,7 +15,12 @@
15
15
  // limitations under the License.
16
16
 
17
17
  import { ethers, network } from "hardhat";
18
- import { Signer, BigNumberish, ContractTransactionReceipt } from "ethers";
18
+ import {
19
+ Signer,
20
+ BigNumberish,
21
+ ContractTransactionReceipt,
22
+ AbiCoder,
23
+ } from "ethers";
19
24
  import { expect } from "chai";
20
25
  import { loadCircuit, encodeProof, Poseidon } from "zeto-js";
21
26
  import { groth16 } from "snarkjs";
@@ -34,13 +39,12 @@ import {
34
39
  loadProvingKeys,
35
40
  prepareDepositProof,
36
41
  prepareNullifierWithdrawProof,
42
+ encodeToBytesForDeposit,
43
+ encodeToBytesForWithdraw,
37
44
  } from "./utils";
38
45
  import { Zeto_AnonNullifier } from "../typechain-types";
39
- import {
40
- deployFungible as deployZeto,
41
- } from "../scripts/deploy_upgradeable";
42
-
43
- const USDC_SEPOLIA_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
46
+ import { deployZeto } from "./lib/deploy";
47
+ const USDC_SEPOLIA_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";
44
48
  const TRANSFER_AMOUNT = 1000; // 0.001000 UDSC
45
49
 
46
50
  describe("Shield USDC balances, transact in privacy, and withdraw back to USDC", function () {
@@ -68,11 +72,12 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
68
72
  Alice2 = await newUser(b);
69
73
  Bob = await newUser(c);
70
74
 
71
- const usdcAddress = network.name === "sepolia" ? USDC_SEPOLIA_ADDRESS : undefined;
72
- ({ zeto, erc20: usdc } = await deployZeto("Zeto_AnonNullifier", usdcAddress));
75
+ ({ zeto, erc20: usdc } = await deployZeto("Zeto_AnonNullifier"));
73
76
 
74
77
  circuit = await loadCircuit("anon_nullifier_transfer");
75
- ({ provingKeyFile: provingKey } = loadProvingKeys("anon_nullifier_transfer"));
78
+ ({ provingKeyFile: provingKey } = loadProvingKeys(
79
+ "anon_nullifier_transfer",
80
+ ));
76
81
 
77
82
  const storage1 = new InMemoryDB(str2Bytes(""));
78
83
  smtAlice = new Merkletree(storage1, true, 64);
@@ -84,7 +89,9 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
84
89
  it("Alice shields some USDC balance into the Zeto contract, and get Zeto tokens of the same value", async function () {
85
90
  const balance = await usdc.balanceOf(Alice.ethAddress);
86
91
  expect(balance).to.be.gt(TRANSFER_AMOUNT);
87
- const tx1 = await usdc.connect(Alice.signer).approve(zeto.target, TRANSFER_AMOUNT);
92
+ const tx1 = await usdc
93
+ .connect(Alice.signer)
94
+ .approve(zeto.target, TRANSFER_AMOUNT);
88
95
  await tx1.wait();
89
96
 
90
97
  // Alice proposes the output UTXOs to shield her USDC balance
@@ -95,7 +102,12 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
95
102
  );
96
103
  const tx2 = await zeto
97
104
  .connect(Alice.signer)
98
- .deposit(TRANSFER_AMOUNT, outputCommitments, encodedProof, "0x");
105
+ .deposit(
106
+ TRANSFER_AMOUNT,
107
+ outputCommitments,
108
+ encodeToBytesForDeposit(encodedProof),
109
+ "0x",
110
+ );
99
111
  await tx2.wait();
100
112
 
101
113
  // Alice locally tracks the UTXOs inside the Sparse Merkle Tree
@@ -179,10 +191,7 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
179
191
 
180
192
  // Alice generates inclusion proofs for the UTXOs to be spent
181
193
  let root = await smtAlice.root();
182
- const proof1 = await smtAlice.generateCircomVerifierProof(
183
- utxo3.hash,
184
- root,
185
- );
194
+ const proof1 = await smtAlice.generateCircomVerifierProof(utxo3.hash, root);
186
195
  const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
187
196
  const merkleProofs = [
188
197
  proof1.siblings.map((s) => s.bigInt()),
@@ -208,16 +217,13 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
208
217
  // she used to shield the USDC tokens. Other parties can not link the
209
218
  // original USDC tokens to the withdrawn USDC tokens, because the public key
210
219
  // information behind the UTXO is unknown to other parties.
211
- const tx = await zeto
212
- .connect(Alice2.signer)
213
- .withdraw(
214
- TRANSFER_AMOUNT / 2, // the amount to withdraw
215
- nullifiers,
216
- outputCommitments[0],
217
- root.bigInt(),
218
- encodedProof,
219
- "0x",
220
- );
220
+ const tx = await zeto.connect(Alice2.signer).withdraw(
221
+ TRANSFER_AMOUNT / 2, // the amount to withdraw
222
+ nullifiers,
223
+ outputCommitments[0],
224
+ encodeToBytesForWithdraw(root.bigInt(), encodedProof),
225
+ "0x",
226
+ );
221
227
  await tx.wait();
222
228
 
223
229
  // Alice checks her ERC20 balance
@@ -284,8 +290,7 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
284
290
  tx = await zeto.connect(signer.signer).transfer(
285
291
  nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
286
292
  outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
287
- root,
288
- encodedProof,
293
+ encodeToBytes(root, encodedProof),
289
294
  "0x",
290
295
  );
291
296
  const results: ContractTransactionReceipt | null = await tx.wait();
@@ -359,3 +364,10 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
359
364
  };
360
365
  }
361
366
  });
367
+
368
+ function encodeToBytes(root: any, proof: any) {
369
+ return new AbiCoder().encode(
370
+ ["uint256 root", "tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
371
+ [root, proof],
372
+ );
373
+ }