@lfdecentralizedtrust/zeto-contracts 0.2.2 → 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 (152) hide show
  1. package/README.md +70 -162
  2. package/contracts/factory.sol +30 -34
  3. package/contracts/factory_upgradeable.sol +11 -7
  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 +74 -26
  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 +6 -1
  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 +217 -361
  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 +8 -0
  104. package/ignition/modules/zeto_anon_burnable.ts +8 -7
  105. package/ignition/modules/zeto_anon_enc_nullifier.ts +31 -0
  106. package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +2 -2
  107. package/ignition/modules/zeto_anon_nullifier.ts +28 -4
  108. package/ignition/modules/zeto_anon_nullifier_burnable.ts +50 -16
  109. package/ignition/modules/zeto_anon_nullifier_kyc.ts +27 -12
  110. package/ignition/modules/zeto_nf_anon_nullifier.ts +14 -10
  111. package/package.json +4 -2
  112. package/scripts/deploy_cloneable.ts +19 -10
  113. package/scripts/deploy_upgradeable.ts +9 -5
  114. package/scripts/tokens/Zeto_Anon.ts +3 -3
  115. package/scripts/tokens/Zeto_AnonBurnable.ts +1 -1
  116. package/scripts/tokens/Zeto_AnonEnc.ts +12 -3
  117. package/scripts/tokens/Zeto_AnonEncNullifier.ts +5 -3
  118. package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +1 -1
  119. package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +1 -1
  120. package/scripts/tokens/Zeto_AnonNullifier.ts +1 -1
  121. package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +1 -1
  122. package/scripts/tokens/Zeto_AnonNullifierKyc.ts +1 -1
  123. package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +1 -1
  124. package/scripts/tokens/Zeto_NfAnon.ts +2 -2
  125. package/scripts/tokens/Zeto_NfAnonNullifier.ts +1 -1
  126. package/test/factory.ts +46 -73
  127. package/test/lib/anon_nullifier_helpers.ts +89 -0
  128. package/test/lib/anon_zeto_helpers.ts +76 -0
  129. package/test/lib/deploy.ts +3 -2
  130. package/test/lib/utils.ts +74 -35
  131. package/test/test/escrow1.ts +185 -58
  132. package/test/test/escrow2.ts +200 -107
  133. package/test/test/qurrency.ts +13 -33
  134. package/test/usdc-shielding.ts +39 -26
  135. package/test/utils.ts +144 -21
  136. package/test/zeto_anon.ts +956 -465
  137. package/test/zeto_anon_enc.ts +881 -142
  138. package/test/zeto_anon_enc_nullifier.ts +847 -38
  139. package/test/zeto_anon_enc_nullifier_kyc.ts +120 -181
  140. package/test/zeto_anon_enc_nullifier_non_repudiation.ts +77 -46
  141. package/test/zeto_anon_nullifier.ts +1212 -953
  142. package/test/zeto_anon_nullifier_kyc.ts +674 -655
  143. package/test/zeto_anon_nullifier_qurrency.ts +452 -306
  144. package/test/zeto_nf_anon.ts +737 -137
  145. package/test/zeto_nf_anon_nullifier.ts +876 -246
  146. package/contracts/lib/zeto_base.sol +0 -293
  147. package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
  148. package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
  149. package/contracts/lib/zeto_nullifier.sol +0 -340
  150. package/contracts/zkDvP.sol_ +0 -273
  151. package/test/zkDvP.ts_ +0 -455
  152. /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,13 @@ 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";
46
+ import { deployZeto } from "./lib/deploy";
42
47
 
43
- const USDC_SEPOLIA_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
48
+ const USDC_SEPOLIA_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";
44
49
  const TRANSFER_AMOUNT = 1000; // 0.001000 UDSC
45
50
 
46
51
  describe("Shield USDC balances, transact in privacy, and withdraw back to USDC", function () {
@@ -68,11 +73,12 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
68
73
  Alice2 = await newUser(b);
69
74
  Bob = await newUser(c);
70
75
 
71
- const usdcAddress = network.name === "sepolia" ? USDC_SEPOLIA_ADDRESS : undefined;
72
- ({ zeto, erc20: usdc } = await deployZeto("Zeto_AnonNullifier", usdcAddress));
76
+ ({ zeto, erc20: usdc } = await deployZeto("Zeto_AnonNullifier"));
73
77
 
74
78
  circuit = await loadCircuit("anon_nullifier_transfer");
75
- ({ provingKeyFile: provingKey } = loadProvingKeys("anon_nullifier_transfer"));
79
+ ({ provingKeyFile: provingKey } = loadProvingKeys(
80
+ "anon_nullifier_transfer",
81
+ ));
76
82
 
77
83
  const storage1 = new InMemoryDB(str2Bytes(""));
78
84
  smtAlice = new Merkletree(storage1, true, 64);
@@ -84,7 +90,9 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
84
90
  it("Alice shields some USDC balance into the Zeto contract, and get Zeto tokens of the same value", async function () {
85
91
  const balance = await usdc.balanceOf(Alice.ethAddress);
86
92
  expect(balance).to.be.gt(TRANSFER_AMOUNT);
87
- const tx1 = await usdc.connect(Alice.signer).approve(zeto.target, TRANSFER_AMOUNT);
93
+ const tx1 = await usdc
94
+ .connect(Alice.signer)
95
+ .approve(zeto.target, TRANSFER_AMOUNT);
88
96
  await tx1.wait();
89
97
 
90
98
  // Alice proposes the output UTXOs to shield her USDC balance
@@ -95,7 +103,12 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
95
103
  );
96
104
  const tx2 = await zeto
97
105
  .connect(Alice.signer)
98
- .deposit(TRANSFER_AMOUNT, outputCommitments, encodedProof, "0x");
106
+ .deposit(
107
+ TRANSFER_AMOUNT,
108
+ outputCommitments,
109
+ encodeToBytesForDeposit(encodedProof),
110
+ "0x",
111
+ );
99
112
  await tx2.wait();
100
113
 
101
114
  // Alice locally tracks the UTXOs inside the Sparse Merkle Tree
@@ -179,10 +192,7 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
179
192
 
180
193
  // Alice generates inclusion proofs for the UTXOs to be spent
181
194
  let root = await smtAlice.root();
182
- const proof1 = await smtAlice.generateCircomVerifierProof(
183
- utxo3.hash,
184
- root,
185
- );
195
+ const proof1 = await smtAlice.generateCircomVerifierProof(utxo3.hash, root);
186
196
  const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
187
197
  const merkleProofs = [
188
198
  proof1.siblings.map((s) => s.bigInt()),
@@ -208,16 +218,13 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
208
218
  // she used to shield the USDC tokens. Other parties can not link the
209
219
  // original USDC tokens to the withdrawn USDC tokens, because the public key
210
220
  // 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
- );
221
+ const tx = await zeto.connect(Alice2.signer).withdraw(
222
+ TRANSFER_AMOUNT / 2, // the amount to withdraw
223
+ nullifiers,
224
+ outputCommitments[0],
225
+ encodeToBytesForWithdraw(root.bigInt(), encodedProof),
226
+ "0x",
227
+ );
221
228
  await tx.wait();
222
229
 
223
230
  // Alice checks her ERC20 balance
@@ -284,8 +291,7 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
284
291
  tx = await zeto.connect(signer.signer).transfer(
285
292
  nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
286
293
  outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
287
- root,
288
- encodedProof,
294
+ encodeToBytes(root, encodedProof),
289
295
  "0x",
290
296
  );
291
297
  const results: ContractTransactionReceipt | null = await tx.wait();
@@ -359,3 +365,10 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
359
365
  };
360
366
  }
361
367
  });
368
+
369
+ function encodeToBytes(root: any, proof: any) {
370
+ return new AbiCoder().encode(
371
+ ["uint256 root", "tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
372
+ [root, proof],
373
+ );
374
+ }