@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.
- package/README.md +76 -162
- package/config/eip170.ts +30 -0
- package/contracts/factory.sol +30 -34
- package/contracts/factory_upgradeable.sol +11 -7
- package/contracts/lib/common/util.sol +40 -0
- package/contracts/lib/interfaces/ILockableCapability.sol +318 -0
- package/contracts/lib/interfaces/{izeto.sol → IZeto.sol} +13 -1
- package/contracts/lib/interfaces/{izeto_initializable.sol → IZetoInitializable.sol} +14 -12
- package/contracts/lib/interfaces/{izeto_kyc.sol → IZetoKyc.sol} +3 -3
- package/contracts/lib/interfaces/IZetoLockHooks.sol +42 -0
- package/contracts/lib/interfaces/IZetoLockableCapability.sol +237 -0
- package/contracts/lib/interfaces/IZetoStorage.sol +106 -0
- package/contracts/lib/interfaces/{izeto_lockable.sol → IZetoVerifier.sol} +8 -21
- package/contracts/lib/registry.sol +74 -26
- package/contracts/lib/storage/base.sol +210 -0
- package/contracts/lib/storage/nullifier.sol +166 -0
- package/contracts/lib/zeto_common.sol +277 -28
- package/contracts/lib/zeto_fungible.sol +443 -33
- package/contracts/lib/zeto_fungible_base.sol +69 -0
- package/contracts/lib/zeto_fungible_burn.sol +83 -53
- package/contracts/lib/zeto_fungible_burn_nullifier.sol +115 -72
- package/contracts/lib/zeto_fungible_nullifier.sol +167 -0
- package/contracts/lib/zeto_lockable.sol +219 -0
- package/contracts/lib/zeto_lockable_lib.sol +460 -0
- package/contracts/lib/zeto_lockable_storage.sol +43 -0
- package/contracts/lib/zeto_non_fungible.sol +228 -0
- package/contracts/lib/zeto_non_fungible_base.sol +61 -0
- package/contracts/lib/zeto_non_fungible_nullifier.sol +61 -0
- package/contracts/test/escrow1.sol +90 -34
- package/contracts/test/escrow2.sol +64 -29
- package/contracts/test/qurrency.sol +10 -2
- package/contracts/test/smt.sol +6 -1
- package/contracts/test/tendecimals.sol +14 -12
- package/contracts/verifiers/impl/anon.sol +189 -0
- package/contracts/verifiers/impl/anon_batch.sol +301 -0
- package/contracts/verifiers/impl/anon_enc.sol +266 -0
- package/contracts/verifiers/impl/anon_enc_batch.sol +602 -0
- package/contracts/verifiers/impl/anon_enc_nullifier.sol +287 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_batch.sol +679 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_kyc.sol +294 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_kyc_batch.sol +686 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation.sol +413 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation_batch.sol +1141 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transfer.sol +217 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked.sol +196 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked_batch.sol +308 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transfer_batch.sol +385 -0
- package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer.sol +497 -0
- package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer_batch.sol +1001 -0
- package/contracts/verifiers/{verifier_anon_nullifier_transferLocked.sol → impl/anon_nullifier_transfer.sol} +12 -19
- package/contracts/verifiers/{verifier_anon_nullifier_transferLocked_batch.sol → impl/anon_nullifier_transfer_batch.sol} +44 -51
- package/contracts/verifiers/impl/burn.sol +182 -0
- package/contracts/verifiers/impl/burn_batch.sol +238 -0
- package/contracts/verifiers/impl/burn_nullifier.sol +203 -0
- package/contracts/verifiers/impl/burn_nullifier_batch.sol +315 -0
- package/contracts/verifiers/impl/deposit.sol +182 -0
- package/contracts/verifiers/impl/deposit_kyc.sol +189 -0
- package/contracts/verifiers/impl/nf_anon.sol +175 -0
- package/contracts/verifiers/{verifier_nf_anon_nullifier_transferLocked.sol → impl/nf_anon_nullifier_transfer.sol} +6 -13
- package/contracts/verifiers/impl/withdraw.sol +189 -0
- package/contracts/verifiers/impl/withdraw_batch.sol +245 -0
- package/contracts/verifiers/impl/withdraw_nullifier.sol +210 -0
- package/contracts/verifiers/impl/withdraw_nullifier_batch.sol +322 -0
- package/contracts/verifiers/verifier_anon.sol +31 -186
- package/contracts/verifiers/verifier_anon_batch.sol +31 -298
- package/contracts/verifiers/verifier_anon_enc.sol +31 -263
- package/contracts/verifiers/verifier_anon_enc_batch.sol +31 -599
- package/contracts/verifiers/verifier_anon_enc_nullifier.sol +31 -284
- package/contracts/verifiers/verifier_anon_enc_nullifier_batch.sol +33 -676
- package/contracts/verifiers/verifier_anon_enc_nullifier_kyc.sol +31 -291
- package/contracts/verifiers/verifier_anon_enc_nullifier_kyc_batch.sol +33 -683
- package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation.sol +33 -410
- package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation_batch.sol +33 -1138
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer.sol +33 -214
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked.sol +33 -221
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked_batch.sol +33 -389
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer_batch.sol +33 -382
- package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer.sol +33 -221
- package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer_batch.sol +33 -389
- package/contracts/verifiers/verifier_anon_nullifier_transfer.sol +33 -207
- package/contracts/verifiers/verifier_anon_nullifier_transfer_batch.sol +33 -375
- package/contracts/verifiers/verifier_burn.sol +31 -179
- package/contracts/verifiers/verifier_burn_batch.sol +31 -235
- package/contracts/verifiers/verifier_burn_nullifier.sol +31 -200
- package/contracts/verifiers/verifier_burn_nullifier_batch.sol +31 -312
- package/contracts/verifiers/verifier_deposit.sol +31 -179
- package/contracts/verifiers/verifier_deposit_kyc.sol +34 -0
- package/contracts/verifiers/verifier_nf_anon.sol +31 -172
- package/contracts/verifiers/verifier_nf_anon_nullifier_transfer.sol +33 -179
- package/contracts/verifiers/verifier_withdraw.sol +31 -186
- package/contracts/verifiers/verifier_withdraw_batch.sol +31 -242
- package/contracts/verifiers/verifier_withdraw_nullifier.sol +31 -207
- package/contracts/verifiers/verifier_withdraw_nullifier_batch.sol +33 -319
- package/contracts/zeto_anon.sol +77 -231
- package/contracts/zeto_anon_burnable.sol +56 -12
- package/contracts/zeto_anon_enc.sol +93 -190
- package/contracts/zeto_anon_enc_nullifier.sol +249 -195
- package/contracts/zeto_anon_enc_nullifier_kyc.sol +51 -232
- package/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +231 -238
- package/contracts/zeto_anon_nullifier.sol +164 -298
- package/contracts/zeto_anon_nullifier_burnable.sol +68 -18
- package/contracts/zeto_anon_nullifier_kyc.sol +40 -345
- package/contracts/zeto_anon_nullifier_qurrency.sol +217 -361
- package/contracts/zeto_nf_anon.sol +55 -130
- package/contracts/zeto_nf_anon_nullifier.sol +90 -152
- package/hardhat.config.ts +18 -3
- package/ignition/modules/lib/deps.ts +13 -0
- package/ignition/modules/test/tendecimals.ts +7 -1
- package/ignition/modules/zeto_anon.ts +3 -0
- package/ignition/modules/zeto_anon_burnable.ts +11 -7
- package/ignition/modules/zeto_anon_enc.ts +3 -0
- package/ignition/modules/zeto_anon_enc_nullifier.ts +34 -0
- package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +5 -2
- package/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +3 -0
- package/ignition/modules/zeto_anon_nullifier.ts +31 -4
- package/ignition/modules/zeto_anon_nullifier_burnable.ts +53 -16
- package/ignition/modules/zeto_anon_nullifier_kyc.ts +30 -12
- package/ignition/modules/zeto_anon_nullifier_qurrency.ts +3 -0
- package/ignition/modules/zeto_nf_anon.ts +3 -1
- package/ignition/modules/zeto_nf_anon_nullifier.ts +17 -12
- package/package.json +7 -3
- package/scripts/deploy_cloneable.ts +19 -10
- package/scripts/deploy_upgradeable.ts +9 -5
- package/scripts/lib/zeto_libraries.ts +47 -0
- package/scripts/tokens/Zeto_Anon.ts +6 -3
- package/scripts/tokens/Zeto_AnonBurnable.ts +4 -1
- package/scripts/tokens/Zeto_AnonEnc.ts +15 -3
- package/scripts/tokens/Zeto_AnonEncNullifier.ts +11 -8
- package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +7 -6
- package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifier.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierKyc.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +7 -8
- package/scripts/tokens/Zeto_NfAnon.ts +5 -3
- package/scripts/tokens/Zeto_NfAnonNullifier.ts +7 -7
- package/test/factory.ts +55 -73
- package/test/lib/anon_nullifier_helpers.ts +89 -0
- package/test/lib/anon_zeto_helpers.ts +76 -0
- package/test/lib/deploy.ts +5 -2
- package/test/lib/eip170.ts +23 -0
- package/test/lib/utils.ts +74 -35
- package/test/test/escrow1.ts +185 -58
- package/test/test/escrow2.ts +200 -107
- package/test/test/qurrency.ts +13 -33
- package/test/usdc-shielding.ts +39 -27
- package/test/utils.ts +144 -21
- package/test/zeto_anon.ts +956 -465
- package/test/zeto_anon_enc.ts +881 -143
- package/test/zeto_anon_enc_nullifier.ts +850 -39
- package/test/zeto_anon_enc_nullifier_kyc.ts +123 -182
- package/test/zeto_anon_enc_nullifier_non_repudiation.ts +80 -47
- package/test/zeto_anon_nullifier.ts +1212 -954
- package/test/zeto_anon_nullifier_kyc.ts +677 -656
- package/test/zeto_anon_nullifier_qurrency.ts +455 -307
- package/test/zeto_nf_anon.ts +737 -138
- package/test/zeto_nf_anon_nullifier.ts +876 -247
- package/contracts/lib/zeto_base.sol +0 -293
- package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
- package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
- package/contracts/lib/zeto_nullifier.sol +0 -340
- package/contracts/zkDvP.sol_ +0 -273
- package/test/zkDvP.ts_ +0 -455
- /package/contracts/lib/{common.sol → common/common.sol} +0 -0
package/test/zeto_anon_enc.ts
CHANGED
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
// limitations under the License.
|
|
16
16
|
|
|
17
17
|
import { ethers, network } from "hardhat";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
ContractTransactionReceipt,
|
|
20
|
+
Signer,
|
|
21
|
+
BigNumberish,
|
|
22
|
+
AbiCoder,
|
|
23
|
+
ZeroAddress,
|
|
24
|
+
} from "ethers";
|
|
19
25
|
import { expect } from "chai";
|
|
20
26
|
import {
|
|
21
27
|
loadCircuit,
|
|
@@ -39,15 +45,17 @@ import {
|
|
|
39
45
|
doMint,
|
|
40
46
|
ZERO_UTXO,
|
|
41
47
|
parseUTXOEvents,
|
|
48
|
+
logger,
|
|
42
49
|
} from "./lib/utils";
|
|
43
50
|
import {
|
|
44
51
|
loadProvingKeys,
|
|
45
52
|
prepareDepositProof,
|
|
46
|
-
prepareLockProof,
|
|
47
53
|
prepareWithdrawProof,
|
|
54
|
+
encodeToBytesForDeposit,
|
|
55
|
+
calculateSpendHash,
|
|
56
|
+
calculateCancelHash,
|
|
48
57
|
} from "./utils";
|
|
49
58
|
import { deployZeto } from "./lib/deploy";
|
|
50
|
-
|
|
51
59
|
const poseidonHash = Poseidon.poseidon4;
|
|
52
60
|
|
|
53
61
|
describe("Zeto based fungible token with anonymity and encryption", function () {
|
|
@@ -66,6 +74,12 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
66
74
|
let batchCircuit: any, batchProvingKey: any;
|
|
67
75
|
|
|
68
76
|
before(async function () {
|
|
77
|
+
// skip the tests if this file is being imported from another test
|
|
78
|
+
// module solely to access its exported `prepareProof` / `encodeToBytes`
|
|
79
|
+
// helpers (mirrors the SKIP_ANON_TESTS pattern in `zeto_anon.ts`).
|
|
80
|
+
if (process.env.SKIP_ANON_ENC_TESTS === "true") {
|
|
81
|
+
this.skip();
|
|
82
|
+
}
|
|
69
83
|
if (network.name !== "hardhat") {
|
|
70
84
|
// accommodate for longer block times on public networks
|
|
71
85
|
this.timeout(120000);
|
|
@@ -84,6 +98,12 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
84
98
|
({ provingKeyFile: batchProvingKey } = loadProvingKeys("anon_enc_batch"));
|
|
85
99
|
});
|
|
86
100
|
|
|
101
|
+
beforeEach(async function () {
|
|
102
|
+
if (process.env.SKIP_ANON_ENC_TESTS === "true") {
|
|
103
|
+
this.skip();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
87
107
|
it("(batch) mint to Alice and batch transfer 10 UTXOs honestly to Bob & Charlie then withdraw should succeed", async function () {
|
|
88
108
|
// first mint the tokens for batch testing
|
|
89
109
|
const inputUtxos = [];
|
|
@@ -120,9 +140,10 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
120
140
|
);
|
|
121
141
|
|
|
122
142
|
const events = parseUTXOEvents(zeto, result.txResult!);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
143
|
+
const event = events[0];
|
|
144
|
+
expect(event.inputs).to.deep.equal(inputUtxos.map((i) => i.hash));
|
|
145
|
+
const incomingUTXOs: any = event.outputs;
|
|
146
|
+
const ecdhPublicKey = event.ecdhPublicKey;
|
|
126
147
|
|
|
127
148
|
// check the non-empty output hashes are correct
|
|
128
149
|
for (let i = 0; i < outputUtxos.length; i++) {
|
|
@@ -132,9 +153,9 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
132
153
|
ecdhPublicKey,
|
|
133
154
|
);
|
|
134
155
|
const plainText = poseidonDecrypt(
|
|
135
|
-
|
|
156
|
+
event.encryptedValues.slice(4 * i, 4 * i + 4),
|
|
136
157
|
sharedKey,
|
|
137
|
-
|
|
158
|
+
event.encryptionNonce,
|
|
138
159
|
2,
|
|
139
160
|
);
|
|
140
161
|
expect(plainText).to.deep.equal(
|
|
@@ -149,11 +170,6 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
149
170
|
expect(incomingUTXOs[i]).to.equal(hash);
|
|
150
171
|
}
|
|
151
172
|
|
|
152
|
-
// check empty values, salt and hashes are empty
|
|
153
|
-
for (let i = outputUtxos.length; i < 10; i++) {
|
|
154
|
-
expect(incomingUTXOs[i]).to.equal(0);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
173
|
// mint sufficient balance in Zeto contract address for Alice to withdraw
|
|
158
174
|
const mintTx = await erc20.connect(deployer).mint(zeto, 3);
|
|
159
175
|
await mintTx.wait();
|
|
@@ -173,7 +189,13 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
173
189
|
// Alice withdraws her UTXOs to ERC20 tokens
|
|
174
190
|
const tx = await zeto
|
|
175
191
|
.connect(Alice.signer)
|
|
176
|
-
.withdraw(
|
|
192
|
+
.withdraw(
|
|
193
|
+
3,
|
|
194
|
+
inputCommitments,
|
|
195
|
+
outputCommitments[0],
|
|
196
|
+
encodeToBytesForWithdraw(encodedProof),
|
|
197
|
+
"0x",
|
|
198
|
+
);
|
|
177
199
|
await tx.wait();
|
|
178
200
|
|
|
179
201
|
// Alice checks her ERC20 balance
|
|
@@ -199,7 +221,12 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
199
221
|
);
|
|
200
222
|
const tx2 = await zeto
|
|
201
223
|
.connect(Alice.signer)
|
|
202
|
-
.deposit(
|
|
224
|
+
.deposit(
|
|
225
|
+
100,
|
|
226
|
+
outputCommitments,
|
|
227
|
+
encodeToBytesForDeposit(encodedProof),
|
|
228
|
+
"0x",
|
|
229
|
+
);
|
|
203
230
|
await tx2.wait();
|
|
204
231
|
}).timeout(60000);
|
|
205
232
|
|
|
@@ -233,18 +260,19 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
233
260
|
// Bob uses the information in the event to recover the incoming UTXO
|
|
234
261
|
// first obtain the UTXO from the transaction event
|
|
235
262
|
const events = parseUTXOEvents(zeto, result.txResult!);
|
|
236
|
-
|
|
237
|
-
expect(
|
|
238
|
-
|
|
263
|
+
const event = events[0];
|
|
264
|
+
expect(event.inputs).to.deep.equal([utxo1.hash, utxo2.hash]);
|
|
265
|
+
expect(event.outputs).to.deep.equal([_utxo1.hash, utxo4.hash]);
|
|
266
|
+
const incomingUTXOs: any = event.outputs;
|
|
239
267
|
|
|
240
|
-
const ecdhPublicKey =
|
|
268
|
+
const ecdhPublicKey = event.ecdhPublicKey;
|
|
241
269
|
// Bob reconstructs the shared key using his private key and ephemeral public key
|
|
242
270
|
|
|
243
271
|
const sharedKey = genEcdhSharedKey(Bob.babyJubPrivateKey, ecdhPublicKey);
|
|
244
272
|
const plainText = poseidonDecrypt(
|
|
245
|
-
|
|
273
|
+
event.encryptedValues.slice(0, 4),
|
|
246
274
|
sharedKey,
|
|
247
|
-
|
|
275
|
+
event.encryptionNonce,
|
|
248
276
|
2,
|
|
249
277
|
);
|
|
250
278
|
expect(plainText).to.deep.equal(result.expectedPlainText.slice(0, 2));
|
|
@@ -284,7 +312,13 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
284
312
|
// Alice withdraws her UTXOs to ERC20 tokens
|
|
285
313
|
const tx = await zeto
|
|
286
314
|
.connect(Alice.signer)
|
|
287
|
-
.withdraw(
|
|
315
|
+
.withdraw(
|
|
316
|
+
80,
|
|
317
|
+
inputCommitments,
|
|
318
|
+
outputCommitments[0],
|
|
319
|
+
encodeToBytesForWithdraw(encodedProof),
|
|
320
|
+
"0x",
|
|
321
|
+
);
|
|
288
322
|
await tx.wait();
|
|
289
323
|
|
|
290
324
|
// Alice checks her ERC20 balance
|
|
@@ -318,7 +352,7 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
318
352
|
10,
|
|
319
353
|
inputCommitments,
|
|
320
354
|
outputCommitments[0],
|
|
321
|
-
encodedProof,
|
|
355
|
+
encodeToBytesForWithdraw(encodedProof),
|
|
322
356
|
"0x",
|
|
323
357
|
),
|
|
324
358
|
).rejectedWith("UTXOAlreadySpent");
|
|
@@ -371,62 +405,705 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
371
405
|
});
|
|
372
406
|
});
|
|
373
407
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
408
|
+
describe("ILockableCapability tests", function () {
|
|
409
|
+
// ABI fragments for the ZetoLockableCapability *Args payloads. These
|
|
410
|
+
// mirror the layout used in `zeto_anon.ts` / `zeto_anon_nullifier.ts`
|
|
411
|
+
// so that the cross-token contract surface stays uniform — the only
|
|
412
|
+
// thing that changes for the encryption-aware token is what goes
|
|
413
|
+
// inside the opaque `proof` blob (we wrap the Groth16 proof with the
|
|
414
|
+
// ECDH key, encryption nonce and encrypted values via
|
|
415
|
+
// `encodeToBytes` below).
|
|
416
|
+
const CREATE_ARGS_ABI =
|
|
417
|
+
"tuple(bytes32 txId, uint256[] inputs, uint256[] outputs, uint256[] lockedOutputs, bytes proof)";
|
|
418
|
+
const UPDATE_ARGS_ABI = "tuple(bytes32 txId)";
|
|
419
|
+
const DELEGATE_ARGS_ABI = "tuple(bytes32 txId)";
|
|
420
|
+
const SPEND_ARGS_ABI =
|
|
421
|
+
"tuple(bytes32 txId, uint256[] lockedOutputs, uint256[] outputs, bytes proof, bytes data)";
|
|
422
|
+
|
|
423
|
+
function encodeCreateArgs(args: {
|
|
424
|
+
txId: string;
|
|
425
|
+
inputs: BigNumberish[];
|
|
426
|
+
outputs: BigNumberish[];
|
|
427
|
+
lockedOutputs: BigNumberish[];
|
|
428
|
+
proof: string;
|
|
429
|
+
}) {
|
|
430
|
+
return new AbiCoder().encode([CREATE_ARGS_ABI], [args]);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function encodeUpdateArgs(txId: string) {
|
|
434
|
+
return new AbiCoder().encode([UPDATE_ARGS_ABI], [{ txId }]);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function encodeDelegateArgs(txId: string) {
|
|
438
|
+
return new AbiCoder().encode([DELEGATE_ARGS_ABI], [{ txId }]);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function encodeSpendArgs(args: {
|
|
442
|
+
txId: string;
|
|
443
|
+
lockedOutputs: BigNumberish[];
|
|
444
|
+
outputs: BigNumberish[];
|
|
445
|
+
proof: string;
|
|
446
|
+
data: string;
|
|
447
|
+
}) {
|
|
448
|
+
return new AbiCoder().encode([SPEND_ARGS_ABI], [args]);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function randomBytes32(): string {
|
|
452
|
+
return ethers.hexlify(ethers.randomBytes(32));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// prepareEncProofBytes wraps `prepareProof` to return ABI-encoded
|
|
456
|
+
// proof bytes ready to be slotted into createArgs / spendArgs.
|
|
457
|
+
// `Zeto_AnonEnc.constructPublicInputs` expects the proof tuple
|
|
458
|
+
// (uint256, uint256[2], uint256[], Commonlib.Proof) — same layout
|
|
459
|
+
// as the regular `transfer` flow — so the locked-input path reuses
|
|
460
|
+
// the same proof builder.
|
|
461
|
+
async function prepareEncProofBytes(
|
|
462
|
+
signer: User,
|
|
463
|
+
inputs: UTXO[],
|
|
464
|
+
outputs: UTXO[],
|
|
465
|
+
owners: User[],
|
|
466
|
+
): Promise<string> {
|
|
467
|
+
const ephemeralKeypair = genKeypair();
|
|
468
|
+
const result = await prepareProof(
|
|
469
|
+
signer,
|
|
470
|
+
inputs,
|
|
471
|
+
outputs,
|
|
472
|
+
owners,
|
|
473
|
+
ephemeralKeypair.privKey,
|
|
474
|
+
);
|
|
475
|
+
return encodeToBytes(
|
|
476
|
+
result.encryptionNonce,
|
|
477
|
+
ephemeralKeypair.pubKey,
|
|
478
|
+
result.encryptedValues,
|
|
479
|
+
result.encodedProof,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
describe("createLock -> updateLock -> delegateLock -> spendLock flow", function () {
|
|
484
|
+
let bobSourceUtxo: UTXO;
|
|
485
|
+
let lockedUtxo: UTXO;
|
|
486
|
+
let lockId: string;
|
|
487
|
+
let outUtxo1: UTXO;
|
|
488
|
+
let outUtxo2: UTXO;
|
|
489
|
+
let unlockHash: string;
|
|
490
|
+
|
|
491
|
+
before(async function () {
|
|
492
|
+
bobSourceUtxo = newUTXO(100, Bob);
|
|
493
|
+
await doMint(zeto, deployer, [bobSourceUtxo]);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("createLock() with deterministic lockId computed from txId", async function () {
|
|
497
|
+
// The locked content is just a fresh UTXO of equal value held under Bob.
|
|
498
|
+
// The proof on the create path proves the standard transfer
|
|
499
|
+
// relationship: bobSourceUtxo -> lockedUtxo. The contract treats
|
|
500
|
+
// the locked output identically to a regular output for the purposes
|
|
501
|
+
// of circuit verification (they are merged into a single output set
|
|
502
|
+
// before constructPublicInputs), so the same anon_enc circuit is
|
|
503
|
+
// reused.
|
|
504
|
+
lockedUtxo = newUTXO(bobSourceUtxo.value!, Bob);
|
|
505
|
+
const proofBytes = await prepareEncProofBytes(
|
|
506
|
+
Bob,
|
|
507
|
+
[bobSourceUtxo, ZERO_UTXO],
|
|
508
|
+
[lockedUtxo, ZERO_UTXO],
|
|
509
|
+
[Bob, Bob],
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
const txId = randomBytes32();
|
|
513
|
+
const createArgs = encodeCreateArgs({
|
|
514
|
+
txId,
|
|
515
|
+
inputs: [bobSourceUtxo.hash],
|
|
516
|
+
outputs: [],
|
|
517
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
518
|
+
proof: proofBytes,
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
const predicted = await zeto
|
|
522
|
+
.connect(Bob.signer)
|
|
523
|
+
.computeLockId(createArgs);
|
|
524
|
+
lockId = predicted;
|
|
525
|
+
|
|
526
|
+
const tx = await zeto
|
|
527
|
+
.connect(Bob.signer)
|
|
528
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x");
|
|
529
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
530
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
531
|
+
|
|
532
|
+
const created = result!.logs
|
|
533
|
+
.map((l) => {
|
|
534
|
+
try {
|
|
535
|
+
return zeto.interface.parseLog(l as any);
|
|
536
|
+
} catch (_e) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
})
|
|
540
|
+
.find((p) => p && p.name === "LockCreated");
|
|
541
|
+
expect(created, "LockCreated event not found").to.not.be.null;
|
|
542
|
+
expect(created!.args.lockId).to.equal(predicted);
|
|
543
|
+
expect(created!.args.owner).to.equal(Bob.ethAddress);
|
|
544
|
+
expect(created!.args.spender).to.equal(Bob.ethAddress);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("isLockActive() and getLock() reflect the newly created lock", async function () {
|
|
548
|
+
expect(await zeto.isLockActive(lockId)).to.equal(true);
|
|
549
|
+
const info = await zeto.getLock(lockId);
|
|
550
|
+
expect(info.owner).to.equal(Bob.ethAddress);
|
|
551
|
+
expect(info.spender).to.equal(Bob.ethAddress);
|
|
552
|
+
expect(info.spendCommitment).to.equal(ethers.ZeroHash);
|
|
553
|
+
expect(info.cancelCommitment).to.equal(ethers.ZeroHash);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("locked() returns true for locked UTXOs and false for unlocked or spent UTXOs", async function () {
|
|
557
|
+
// Just-created lock: spender == owner == Bob.
|
|
558
|
+
expect(await zeto.locked(lockedUtxo.hash)).to.deep.equal([
|
|
559
|
+
true,
|
|
560
|
+
Bob.ethAddress,
|
|
561
|
+
]);
|
|
562
|
+
expect((await zeto.locked(bobSourceUtxo.hash))[0]).to.be.false;
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("updateLock() commits the spend hash while owner == spender", async function () {
|
|
566
|
+
outUtxo1 = newUTXO(10, Alice);
|
|
567
|
+
outUtxo2 = newUTXO(90, Bob);
|
|
568
|
+
|
|
569
|
+
unlockHash = calculateSpendHash(
|
|
570
|
+
[lockedUtxo],
|
|
571
|
+
[],
|
|
572
|
+
[outUtxo1, outUtxo2],
|
|
573
|
+
"0x",
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const tx = await zeto
|
|
577
|
+
.connect(Bob.signer)
|
|
578
|
+
.updateLock(
|
|
579
|
+
lockId,
|
|
580
|
+
encodeUpdateArgs(randomBytes32()),
|
|
581
|
+
unlockHash,
|
|
582
|
+
ethers.ZeroHash,
|
|
583
|
+
"0x",
|
|
584
|
+
);
|
|
585
|
+
const result = await tx.wait();
|
|
586
|
+
logger.debug(`updateLock() complete. Gas used: ${result?.gasUsed}`);
|
|
587
|
+
|
|
588
|
+
const info = await zeto.getLock(lockId);
|
|
589
|
+
expect(info.spendCommitment).to.equal(unlockHash);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it("delegateLock() transfers spending authority to Alice", async function () {
|
|
593
|
+
const tx = await zeto
|
|
594
|
+
.connect(Bob.signer)
|
|
595
|
+
.delegateLock(
|
|
596
|
+
lockId,
|
|
597
|
+
encodeDelegateArgs(randomBytes32()),
|
|
598
|
+
Alice.ethAddress,
|
|
599
|
+
"0x",
|
|
600
|
+
);
|
|
601
|
+
const result = await tx.wait();
|
|
602
|
+
logger.debug(`delegateLock() complete. Gas used: ${result?.gasUsed}`);
|
|
603
|
+
|
|
604
|
+
const info = await zeto.getLock(lockId);
|
|
605
|
+
expect(info.spender).to.equal(Alice.ethAddress);
|
|
606
|
+
expect(await zeto.locked(lockedUtxo.hash)).to.deep.equal([
|
|
607
|
+
true,
|
|
608
|
+
Alice.ethAddress,
|
|
609
|
+
]);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it("the new spender can spendLock() with the matching payload", async function () {
|
|
613
|
+
// Bob (still the original owner of the locked UTXO) generates the
|
|
614
|
+
// settlement proof; Alice (the new spender) submits it.
|
|
615
|
+
const settleProofBytes = await prepareEncProofBytes(
|
|
616
|
+
Bob,
|
|
617
|
+
[lockedUtxo, ZERO_UTXO],
|
|
618
|
+
[outUtxo1, outUtxo2],
|
|
619
|
+
[Alice, Bob],
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
const spendArgs = encodeSpendArgs({
|
|
623
|
+
txId: randomBytes32(),
|
|
624
|
+
lockedOutputs: [],
|
|
625
|
+
outputs: [outUtxo1.hash, outUtxo2.hash],
|
|
626
|
+
proof: settleProofBytes,
|
|
627
|
+
data: "0x",
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const tx = await zeto
|
|
631
|
+
.connect(Alice.signer)
|
|
632
|
+
.spendLock(lockId, spendArgs, "0x");
|
|
633
|
+
const result = await tx.wait();
|
|
634
|
+
|
|
635
|
+
const parsed = result!.logs
|
|
636
|
+
.map((l) => {
|
|
637
|
+
try {
|
|
638
|
+
return zeto.interface.parseLog(l as any);
|
|
639
|
+
} catch (_e) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
})
|
|
643
|
+
.filter((p) => p !== null) as ReadonlyArray<{
|
|
644
|
+
name: string;
|
|
645
|
+
args: any;
|
|
646
|
+
}>;
|
|
647
|
+
const lockSpent = parsed.find((p) => p.name === "LockSpent");
|
|
648
|
+
const zetoLockSpent = parsed.find((p) => p.name === "ZetoLockSpent");
|
|
649
|
+
expect(lockSpent, "LockSpent event not emitted").to.not.be.undefined;
|
|
650
|
+
expect(zetoLockSpent, "ZetoLockSpent event not emitted").to.not.be
|
|
651
|
+
.undefined;
|
|
652
|
+
expect(lockSpent!.args.lockId).to.equal(lockId);
|
|
653
|
+
expect(lockSpent!.args.spender).to.equal(Alice.ethAddress);
|
|
654
|
+
|
|
655
|
+
// Lock is no longer active.
|
|
656
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
657
|
+
|
|
658
|
+
// Outputs are now ordinary unlocked UTXOs.
|
|
659
|
+
expect(await zeto.spent(outUtxo1.hash)).to.equal(1n); // UNSPENT
|
|
660
|
+
expect(await zeto.spent(outUtxo2.hash)).to.equal(1n); // UNSPENT
|
|
661
|
+
|
|
662
|
+
// Per-UTXO delegate projection is cleared post-consume.
|
|
663
|
+
expect(await zeto.locked(lockedUtxo.hash)).to.deep.equal([
|
|
664
|
+
false,
|
|
665
|
+
ZeroAddress,
|
|
666
|
+
]);
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
describe("createLock -> cancelLock flow", function () {
|
|
671
|
+
let bobSourceUtxo: UTXO;
|
|
672
|
+
let lockedUtxo: UTXO;
|
|
673
|
+
let lockId: string;
|
|
674
|
+
let cancelHash: string;
|
|
675
|
+
let outUtxo1: UTXO;
|
|
676
|
+
let outUtxo2: UTXO;
|
|
677
|
+
|
|
678
|
+
before(async function () {
|
|
679
|
+
bobSourceUtxo = newUTXO(100, Bob);
|
|
680
|
+
await doMint(zeto, deployer, [bobSourceUtxo]);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it("Bob createLock() with a non-zero cancelCommitment", async function () {
|
|
684
|
+
lockedUtxo = newUTXO(bobSourceUtxo.value!, Bob);
|
|
685
|
+
const proofBytes = await prepareEncProofBytes(
|
|
686
|
+
Bob,
|
|
687
|
+
[bobSourceUtxo, ZERO_UTXO],
|
|
688
|
+
[lockedUtxo, ZERO_UTXO],
|
|
689
|
+
[Bob, Bob],
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
outUtxo1 = newUTXO(10, Alice);
|
|
693
|
+
outUtxo2 = newUTXO(90, Bob);
|
|
694
|
+
cancelHash = calculateCancelHash(
|
|
695
|
+
[lockedUtxo],
|
|
696
|
+
[],
|
|
697
|
+
[outUtxo1, outUtxo2],
|
|
698
|
+
"0x",
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
const createArgs = encodeCreateArgs({
|
|
702
|
+
txId: randomBytes32(),
|
|
703
|
+
inputs: [bobSourceUtxo.hash],
|
|
704
|
+
outputs: [],
|
|
705
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
706
|
+
proof: proofBytes,
|
|
707
|
+
});
|
|
708
|
+
lockId = await zeto.connect(Bob.signer).computeLockId(createArgs);
|
|
709
|
+
const tx = await zeto
|
|
710
|
+
.connect(Bob.signer)
|
|
711
|
+
.createLock(createArgs, ethers.ZeroHash, cancelHash, "0x");
|
|
712
|
+
const result = await tx.wait();
|
|
713
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it("the owner can cancelLock() to reverse the lock without delegation", async function () {
|
|
717
|
+
const cancelProofBytes = await prepareEncProofBytes(
|
|
718
|
+
Bob,
|
|
719
|
+
[lockedUtxo, ZERO_UTXO],
|
|
720
|
+
[outUtxo1, outUtxo2],
|
|
721
|
+
[Alice, Bob],
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
const cancelArgs = encodeSpendArgs({
|
|
725
|
+
txId: randomBytes32(),
|
|
726
|
+
lockedOutputs: [],
|
|
727
|
+
outputs: [outUtxo1.hash, outUtxo2.hash],
|
|
728
|
+
proof: cancelProofBytes,
|
|
729
|
+
data: "0x",
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
const tx = await zeto
|
|
733
|
+
.connect(Bob.signer)
|
|
734
|
+
.cancelLock(lockId, cancelArgs, "0x");
|
|
735
|
+
const result = await tx.wait();
|
|
736
|
+
|
|
737
|
+
const parsed = result!.logs
|
|
738
|
+
.map((l) => {
|
|
739
|
+
try {
|
|
740
|
+
return zeto.interface.parseLog(l as any);
|
|
741
|
+
} catch (_e) {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
})
|
|
745
|
+
.filter((p) => p !== null) as ReadonlyArray<{
|
|
746
|
+
name: string;
|
|
747
|
+
args: any;
|
|
748
|
+
}>;
|
|
749
|
+
const cancelled = parsed.find((p) => p.name === "LockCancelled");
|
|
750
|
+
const zetoCancelled = parsed.find(
|
|
751
|
+
(p) => p.name === "ZetoLockCancelled",
|
|
752
|
+
);
|
|
753
|
+
expect(cancelled, "LockCancelled event not emitted").to.not.be
|
|
754
|
+
.undefined;
|
|
755
|
+
expect(zetoCancelled, "ZetoLockCancelled event not emitted").to.not.be
|
|
756
|
+
.undefined;
|
|
757
|
+
|
|
758
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
describe("spendLock with a payload that does not match the spend commitment fails", function () {
|
|
763
|
+
let bobSourceUtxo: UTXO;
|
|
764
|
+
let lockedUtxo: UTXO;
|
|
765
|
+
let lockId: string;
|
|
766
|
+
let expectedHash: string;
|
|
767
|
+
|
|
768
|
+
before(async function () {
|
|
769
|
+
bobSourceUtxo = newUTXO(100, Bob);
|
|
770
|
+
await doMint(zeto, deployer, [bobSourceUtxo]);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it("Bob createLock() then updateLock() committing a specific spend hash", async function () {
|
|
774
|
+
lockedUtxo = newUTXO(bobSourceUtxo.value!, Bob);
|
|
775
|
+
const proofBytes = await prepareEncProofBytes(
|
|
776
|
+
Bob,
|
|
777
|
+
[bobSourceUtxo, ZERO_UTXO],
|
|
778
|
+
[lockedUtxo, ZERO_UTXO],
|
|
779
|
+
[Bob, Bob],
|
|
780
|
+
);
|
|
781
|
+
const createArgs = encodeCreateArgs({
|
|
782
|
+
txId: randomBytes32(),
|
|
783
|
+
inputs: [bobSourceUtxo.hash],
|
|
784
|
+
outputs: [],
|
|
785
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
786
|
+
proof: proofBytes,
|
|
787
|
+
});
|
|
788
|
+
lockId = await zeto.connect(Bob.signer).computeLockId(createArgs);
|
|
789
|
+
await (
|
|
790
|
+
await zeto
|
|
791
|
+
.connect(Bob.signer)
|
|
792
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x")
|
|
793
|
+
).wait();
|
|
794
|
+
|
|
795
|
+
const expectedOut1 = newUTXO(10, Alice);
|
|
796
|
+
const expectedOut2 = newUTXO(90, Bob);
|
|
797
|
+
expectedHash = calculateSpendHash(
|
|
798
|
+
[lockedUtxo],
|
|
799
|
+
[],
|
|
800
|
+
[expectedOut1, expectedOut2],
|
|
801
|
+
"0x",
|
|
802
|
+
);
|
|
803
|
+
await (
|
|
804
|
+
await zeto
|
|
805
|
+
.connect(Bob.signer)
|
|
806
|
+
.updateLock(
|
|
807
|
+
lockId,
|
|
808
|
+
encodeUpdateArgs(randomBytes32()),
|
|
809
|
+
expectedHash,
|
|
810
|
+
ethers.ZeroHash,
|
|
811
|
+
"0x",
|
|
812
|
+
)
|
|
813
|
+
).wait();
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it("spendLock() with a different payload reverts with InvalidUnlockHash", async function () {
|
|
817
|
+
if (network.name !== "hardhat") {
|
|
818
|
+
this.skip();
|
|
819
|
+
}
|
|
820
|
+
const wrongOut1 = newUTXO(20, Alice);
|
|
821
|
+
const wrongOut2 = newUTXO(80, Bob);
|
|
822
|
+
|
|
823
|
+
const settleProofBytes = await prepareEncProofBytes(
|
|
824
|
+
Bob,
|
|
825
|
+
[lockedUtxo, ZERO_UTXO],
|
|
826
|
+
[wrongOut1, wrongOut2],
|
|
827
|
+
[Alice, Bob],
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
const spendArgs = encodeSpendArgs({
|
|
831
|
+
txId: randomBytes32(),
|
|
832
|
+
lockedOutputs: [],
|
|
833
|
+
outputs: [wrongOut1.hash, wrongOut2.hash],
|
|
834
|
+
proof: settleProofBytes,
|
|
835
|
+
data: "0x",
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
const calculatedHash = calculateSpendHash(
|
|
839
|
+
[lockedUtxo],
|
|
840
|
+
[],
|
|
841
|
+
[wrongOut1, wrongOut2],
|
|
842
|
+
"0x",
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
await expect(
|
|
846
|
+
zeto.connect(Bob.signer).spendLock(lockId, spendArgs, "0x"),
|
|
847
|
+
)
|
|
848
|
+
.to.be.revertedWithCustomError(zeto, "InvalidUnlockHash")
|
|
849
|
+
.withArgs(expectedHash, calculatedHash);
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
describe("negative cases for the lock lifecycle", function () {
|
|
854
|
+
// These tests rely on hardhat-style revert decoding.
|
|
855
|
+
if (network.name !== "hardhat") {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// freshLock mints a UTXO for `owner`, locks it, and returns the
|
|
860
|
+
// resulting lock metadata so each negative case can branch off
|
|
861
|
+
// without polluting other test scopes.
|
|
862
|
+
async function freshLock(
|
|
863
|
+
owner: User,
|
|
864
|
+
spendCommitment: string = ethers.ZeroHash,
|
|
865
|
+
cancelCommitment: string = ethers.ZeroHash,
|
|
866
|
+
): Promise<{
|
|
867
|
+
lockId: string;
|
|
868
|
+
sourceUtxo: UTXO;
|
|
869
|
+
lockedUtxo: UTXO;
|
|
870
|
+
createArgs: string;
|
|
871
|
+
}> {
|
|
872
|
+
const sourceUtxo = newUTXO(100, owner);
|
|
873
|
+
await doMint(zeto, deployer, [sourceUtxo]);
|
|
874
|
+
|
|
875
|
+
const lockedUtxo = newUTXO(sourceUtxo.value!, owner);
|
|
876
|
+
const proofBytes = await prepareEncProofBytes(
|
|
877
|
+
owner,
|
|
878
|
+
[sourceUtxo, ZERO_UTXO],
|
|
879
|
+
[lockedUtxo, ZERO_UTXO],
|
|
880
|
+
[owner, owner],
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
const createArgs = encodeCreateArgs({
|
|
884
|
+
txId: randomBytes32(),
|
|
885
|
+
inputs: [sourceUtxo.hash],
|
|
886
|
+
outputs: [],
|
|
887
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
888
|
+
proof: proofBytes,
|
|
889
|
+
});
|
|
890
|
+
const lockId = await zeto
|
|
891
|
+
.connect(owner.signer)
|
|
892
|
+
.computeLockId(createArgs);
|
|
893
|
+
await (
|
|
894
|
+
await zeto
|
|
895
|
+
.connect(owner.signer)
|
|
896
|
+
.createLock(createArgs, spendCommitment, cancelCommitment, "0x")
|
|
897
|
+
).wait();
|
|
898
|
+
return { lockId, sourceUtxo, lockedUtxo, createArgs };
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Re-encoding helper: a syntactically valid spend payload that does
|
|
902
|
+
// NOT need to verify a real ZK proof. Tests for authorization /
|
|
903
|
+
// immutability assertions short-circuit before the proof is touched.
|
|
904
|
+
function dummySpendArgs(): string {
|
|
905
|
+
return encodeSpendArgs({
|
|
906
|
+
txId: randomBytes32(),
|
|
907
|
+
lockedOutputs: [],
|
|
908
|
+
outputs: [],
|
|
909
|
+
proof: "0x",
|
|
910
|
+
data: "0x",
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
it("createLock() with a duplicate txId from the same caller reverts with DuplicateLock", async function () {
|
|
915
|
+
const { lockId, createArgs } = await freshLock(Bob);
|
|
916
|
+
|
|
917
|
+
// Same createArgs (same txId, same caller) => same lockId =>
|
|
918
|
+
// DuplicateLock fires before any input or proof validation.
|
|
919
|
+
await expect(
|
|
920
|
+
zeto
|
|
921
|
+
.connect(Bob.signer)
|
|
922
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x"),
|
|
923
|
+
)
|
|
924
|
+
.to.be.revertedWithCustomError(zeto, "DuplicateLock")
|
|
925
|
+
.withArgs(lockId);
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
it("updateLock() by a non-owner reverts with LockUnauthorized", async function () {
|
|
929
|
+
const { lockId } = await freshLock(Bob);
|
|
930
|
+
|
|
931
|
+
await expect(
|
|
932
|
+
zeto
|
|
933
|
+
.connect(Alice.signer)
|
|
934
|
+
.updateLock(
|
|
935
|
+
lockId,
|
|
936
|
+
encodeUpdateArgs(randomBytes32()),
|
|
937
|
+
ethers.ZeroHash,
|
|
938
|
+
ethers.ZeroHash,
|
|
939
|
+
"0x",
|
|
940
|
+
),
|
|
941
|
+
)
|
|
942
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
943
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
it("updateLock() prefers LockUnauthorized over LockImmutable when both apply (M-5)", async function () {
|
|
947
|
+
// Lock is delegated (so spender != owner -> immutable) AND the
|
|
948
|
+
// caller is neither owner nor spender. The contract MUST report
|
|
949
|
+
// LockUnauthorized, not leak the immutability state to the
|
|
950
|
+
// unauthorized caller.
|
|
951
|
+
const { lockId } = await freshLock(Bob);
|
|
952
|
+
await (
|
|
953
|
+
await zeto
|
|
954
|
+
.connect(Bob.signer)
|
|
955
|
+
.delegateLock(
|
|
956
|
+
lockId,
|
|
957
|
+
encodeDelegateArgs(randomBytes32()),
|
|
958
|
+
Alice.ethAddress,
|
|
959
|
+
"0x",
|
|
960
|
+
)
|
|
961
|
+
).wait();
|
|
962
|
+
|
|
963
|
+
await expect(
|
|
964
|
+
zeto
|
|
965
|
+
.connect(Charlie.signer)
|
|
966
|
+
.updateLock(
|
|
967
|
+
lockId,
|
|
968
|
+
encodeUpdateArgs(randomBytes32()),
|
|
969
|
+
ethers.ZeroHash,
|
|
970
|
+
ethers.ZeroHash,
|
|
971
|
+
"0x",
|
|
972
|
+
),
|
|
973
|
+
)
|
|
974
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
975
|
+
// spender at this point is Alice (the new delegate).
|
|
976
|
+
.withArgs(lockId, Alice.ethAddress, Charlie.ethAddress);
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
it("updateLock() after delegateLock() reverts with LockImmutable", async function () {
|
|
980
|
+
const { lockId } = await freshLock(Bob);
|
|
981
|
+
|
|
982
|
+
// Bob delegates spending authority to Alice; spender (Alice) now
|
|
983
|
+
// differs from owner (Bob).
|
|
984
|
+
await (
|
|
985
|
+
await zeto
|
|
986
|
+
.connect(Bob.signer)
|
|
987
|
+
.delegateLock(
|
|
988
|
+
lockId,
|
|
989
|
+
encodeDelegateArgs(randomBytes32()),
|
|
990
|
+
Alice.ethAddress,
|
|
991
|
+
"0x",
|
|
992
|
+
)
|
|
993
|
+
).wait();
|
|
994
|
+
|
|
995
|
+
// Even Bob (the owner) can no longer mutate the commitments —
|
|
996
|
+
// the lock is now externally controlled and must be considered
|
|
997
|
+
// immutable.
|
|
998
|
+
await expect(
|
|
999
|
+
zeto
|
|
1000
|
+
.connect(Bob.signer)
|
|
1001
|
+
.updateLock(
|
|
1002
|
+
lockId,
|
|
1003
|
+
encodeUpdateArgs(randomBytes32()),
|
|
1004
|
+
ethers.ZeroHash,
|
|
1005
|
+
ethers.ZeroHash,
|
|
1006
|
+
"0x",
|
|
1007
|
+
),
|
|
1008
|
+
)
|
|
1009
|
+
.to.be.revertedWithCustomError(zeto, "LockImmutable")
|
|
1010
|
+
.withArgs(lockId);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it("delegateLock() by a non-spender reverts with LockUnauthorized", async function () {
|
|
1014
|
+
const { lockId } = await freshLock(Bob);
|
|
1015
|
+
|
|
1016
|
+
await expect(
|
|
1017
|
+
zeto
|
|
1018
|
+
.connect(Alice.signer)
|
|
1019
|
+
.delegateLock(
|
|
1020
|
+
lockId,
|
|
1021
|
+
encodeDelegateArgs(randomBytes32()),
|
|
1022
|
+
Charlie.ethAddress,
|
|
1023
|
+
"0x",
|
|
1024
|
+
),
|
|
1025
|
+
)
|
|
1026
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1027
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
it("spendLock() by a non-spender reverts with LockUnauthorized before touching the proof", async function () {
|
|
1031
|
+
const { lockId } = await freshLock(Bob);
|
|
1032
|
+
|
|
1033
|
+
// Garbage proof — the onlySpender modifier MUST short-circuit
|
|
1034
|
+
// before any proof verification is attempted.
|
|
1035
|
+
await expect(
|
|
1036
|
+
zeto.connect(Alice.signer).spendLock(lockId, dummySpendArgs(), "0x"),
|
|
1037
|
+
)
|
|
1038
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1039
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it("cancelLock() by a non-spender reverts with LockUnauthorized", async function () {
|
|
1043
|
+
const { lockId } = await freshLock(Bob);
|
|
1044
|
+
|
|
1045
|
+
await expect(
|
|
1046
|
+
zeto.connect(Alice.signer).cancelLock(lockId, dummySpendArgs(), "0x"),
|
|
1047
|
+
)
|
|
1048
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1049
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
it("after a successful spendLock(), the lock is no longer active and getLock() reverts", async function () {
|
|
1053
|
+
const { lockId, lockedUtxo } = await freshLock(Bob);
|
|
1054
|
+
|
|
1055
|
+
const out1 = newUTXO(10, Alice);
|
|
1056
|
+
const out2 = newUTXO(90, Bob);
|
|
1057
|
+
const settleProofBytes = await prepareEncProofBytes(
|
|
1058
|
+
Bob,
|
|
1059
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1060
|
+
[out1, out2],
|
|
1061
|
+
[Alice, Bob],
|
|
1062
|
+
);
|
|
1063
|
+
const spendArgs = encodeSpendArgs({
|
|
1064
|
+
txId: randomBytes32(),
|
|
1065
|
+
lockedOutputs: [],
|
|
1066
|
+
outputs: [out1.hash, out2.hash],
|
|
1067
|
+
proof: settleProofBytes,
|
|
1068
|
+
data: "0x",
|
|
1069
|
+
});
|
|
1070
|
+
await (
|
|
1071
|
+
await zeto.connect(Bob.signer).spendLock(lockId, spendArgs, "0x")
|
|
1072
|
+
).wait();
|
|
1073
|
+
|
|
1074
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
1075
|
+
await expect(zeto.getLock(lockId))
|
|
1076
|
+
.to.be.revertedWithCustomError(zeto, "LockNotActive")
|
|
1077
|
+
.withArgs(lockId);
|
|
1078
|
+
// Re-spending a consumed lock MUST also fail — lockActive is
|
|
1079
|
+
// the first modifier and emits LockNotActive before
|
|
1080
|
+
// onlySpender has a chance to fire.
|
|
1081
|
+
await expect(
|
|
1082
|
+
zeto.connect(Bob.signer).spendLock(lockId, dummySpendArgs(), "0x"),
|
|
1083
|
+
)
|
|
1084
|
+
.to.be.revertedWithCustomError(zeto, "LockNotActive")
|
|
1085
|
+
.withArgs(lockId);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
it("an unlocked input flow rejects a locked UTXO with AlreadyLocked", async function () {
|
|
1089
|
+
// Mint a UTXO, lock it, then try to spend it via the regular
|
|
1090
|
+
// transfer path. The base-storage validateInputs() guards this
|
|
1091
|
+
// and reverts with AlreadyLocked on the locked input.
|
|
1092
|
+
const { lockedUtxo } = await freshLock(Bob);
|
|
1093
|
+
|
|
1094
|
+
await expect(
|
|
1095
|
+
doTransfer(
|
|
1096
|
+
Bob,
|
|
1097
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1098
|
+
[newUTXO(100, Alice), ZERO_UTXO],
|
|
1099
|
+
[Alice, Alice],
|
|
1100
|
+
),
|
|
1101
|
+
)
|
|
1102
|
+
.to.be.revertedWithCustomError(zeto, "AlreadyLocked")
|
|
1103
|
+
.withArgs(lockedUtxo.hash);
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
430
1107
|
|
|
431
1108
|
async function doTransfer(
|
|
432
1109
|
signer: User,
|
|
@@ -480,71 +1157,21 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
480
1157
|
owners: User[],
|
|
481
1158
|
ephemeralPrivateKey: BigInt,
|
|
482
1159
|
) {
|
|
483
|
-
const inputCommitments: BigNumberish[] = inputs.map(
|
|
484
|
-
(input) => input.hash,
|
|
485
|
-
) as BigNumberish[];
|
|
486
|
-
const inputValues = inputs.map((input) => BigInt(input.value || 0n));
|
|
487
|
-
const inputSalts = inputs.map((input) => input.salt || 0n);
|
|
488
|
-
const outputCommitments: BigNumberish[] = outputs.map(
|
|
489
|
-
(output) => output.hash,
|
|
490
|
-
) as BigNumberish[];
|
|
491
|
-
const outputValues = outputs.map((output) => BigInt(output.value || 0n));
|
|
492
|
-
const outputOwnerPublicKeys: BigNumberish[][] = owners.map(
|
|
493
|
-
(owner) => owner.babyJubPublicKey,
|
|
494
|
-
) as BigNumberish[][];
|
|
495
|
-
const encryptionNonce: BigNumberish = newEncryptionNonce() as BigNumberish;
|
|
496
|
-
const encryptInputs = stringifyBigInts({
|
|
497
|
-
encryptionNonce,
|
|
498
|
-
ecdhPrivateKey: formatPrivKeyForBabyJub(ephemeralPrivateKey),
|
|
499
|
-
});
|
|
500
|
-
|
|
501
1160
|
let circuitToUse = circuit;
|
|
502
1161
|
let provingKeyToUse = provingKey;
|
|
503
|
-
|
|
504
|
-
if (inputCommitments.length > 2 || outputCommitments.length > 2) {
|
|
505
|
-
isBatch = true;
|
|
1162
|
+
if (inputs.length > 2 || outputs.length > 2) {
|
|
506
1163
|
circuitToUse = batchCircuit;
|
|
507
1164
|
provingKeyToUse = batchProvingKey;
|
|
508
1165
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
{
|
|
512
|
-
inputCommitments,
|
|
513
|
-
inputValues,
|
|
514
|
-
inputSalts,
|
|
515
|
-
inputOwnerPrivateKey: formatPrivKeyForBabyJub(signer.babyJubPrivateKey),
|
|
516
|
-
outputCommitments,
|
|
517
|
-
outputValues,
|
|
518
|
-
outputSalts: outputs.map((output) => output.salt || 0n),
|
|
519
|
-
outputOwnerPublicKeys,
|
|
520
|
-
...encryptInputs,
|
|
521
|
-
},
|
|
522
|
-
true,
|
|
523
|
-
);
|
|
524
|
-
const timeWitnessCalculation = Date.now() - startWitnessCalculation;
|
|
525
|
-
|
|
526
|
-
const startProofGeneration = Date.now();
|
|
527
|
-
const { proof, publicSignals } = (await groth16.prove(
|
|
1166
|
+
return prepareProofModule(
|
|
1167
|
+
circuitToUse,
|
|
528
1168
|
provingKeyToUse,
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
1169
|
+
signer,
|
|
1170
|
+
inputs,
|
|
1171
|
+
outputs,
|
|
1172
|
+
owners,
|
|
1173
|
+
ephemeralPrivateKey,
|
|
534
1174
|
);
|
|
535
|
-
|
|
536
|
-
// console.log(publicSignals);
|
|
537
|
-
const encodedProof = encodeProof(proof);
|
|
538
|
-
const encryptedValues = isBatch
|
|
539
|
-
? publicSignals.slice(2, 42)
|
|
540
|
-
: publicSignals.slice(2, 10);
|
|
541
|
-
return {
|
|
542
|
-
inputCommitments,
|
|
543
|
-
outputCommitments,
|
|
544
|
-
encryptedValues,
|
|
545
|
-
encryptionNonce,
|
|
546
|
-
encodedProof,
|
|
547
|
-
};
|
|
548
1175
|
}
|
|
549
1176
|
|
|
550
1177
|
async function sendTx(
|
|
@@ -559,24 +1186,135 @@ describe("Zeto based fungible token with anonymity and encryption", function ()
|
|
|
559
1186
|
const tx = await zeto.connect(signer.signer).transfer(
|
|
560
1187
|
inputCommitments.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
561
1188
|
outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1189
|
+
encodeToBytes(
|
|
1190
|
+
encryptionNonce,
|
|
1191
|
+
ecdhPublicKey,
|
|
1192
|
+
encryptedValues,
|
|
1193
|
+
encodedProof,
|
|
1194
|
+
),
|
|
566
1195
|
"0x",
|
|
567
1196
|
);
|
|
568
1197
|
const results: ContractTransactionReceipt | null = await tx.wait();
|
|
569
1198
|
|
|
570
1199
|
for (const input of inputCommitments) {
|
|
571
1200
|
if (input === 0n) continue;
|
|
572
|
-
expect(await zeto.spent(input)).to.equal(
|
|
1201
|
+
expect(await zeto.spent(input)).to.equal(2n);
|
|
573
1202
|
}
|
|
574
1203
|
for (const output of outputCommitments) {
|
|
575
1204
|
if (output === 0n) continue;
|
|
576
|
-
expect(await zeto.spent(output)).to.equal(
|
|
1205
|
+
expect(await zeto.spent(output)).to.equal(1n);
|
|
577
1206
|
}
|
|
578
1207
|
console.log(`Method transfer() complete. Gas used: ${results?.gasUsed}`);
|
|
579
1208
|
|
|
580
1209
|
return results;
|
|
581
1210
|
}
|
|
582
1211
|
});
|
|
1212
|
+
|
|
1213
|
+
function encodeToBytes(
|
|
1214
|
+
encryptionNonce: any,
|
|
1215
|
+
ecdhPublicKey: any,
|
|
1216
|
+
encryptedValues: any,
|
|
1217
|
+
proof: any,
|
|
1218
|
+
) {
|
|
1219
|
+
const bytes = new AbiCoder().encode(
|
|
1220
|
+
[
|
|
1221
|
+
"uint256 encryptionNonce",
|
|
1222
|
+
"uint256[2] ecdhPublicKey",
|
|
1223
|
+
"uint256[] encryptedValues",
|
|
1224
|
+
"tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)",
|
|
1225
|
+
],
|
|
1226
|
+
[encryptionNonce, ecdhPublicKey, encryptedValues, proof],
|
|
1227
|
+
);
|
|
1228
|
+
// console.log("Encryption nonce:", encryptionNonce);
|
|
1229
|
+
// console.log("ECDH public key:", ecdhPublicKey);
|
|
1230
|
+
// console.log("Encrypted values:", encryptedValues);
|
|
1231
|
+
// console.log("Proof:", proof);
|
|
1232
|
+
// console.log("Encoded proof:", bytes);
|
|
1233
|
+
return bytes;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
function encodeToBytesForWithdraw(proof: any) {
|
|
1237
|
+
return new AbiCoder().encode(
|
|
1238
|
+
["tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
|
|
1239
|
+
[proof],
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// Module-scope twin of the in-describe `prepareProof`. Lifted so that other
|
|
1244
|
+
// test suites (notably `zeto_anon_enc_nullifier.ts`) can reuse it for
|
|
1245
|
+
// locked-input proofs that need to verify against the `anon_enc` circuit
|
|
1246
|
+
// rather than a nullifier-aware encryption circuit. Mirrors the
|
|
1247
|
+
// {prepareProof,encodeToBytes} export pattern at the bottom of
|
|
1248
|
+
// `zeto_anon.ts`.
|
|
1249
|
+
async function prepareProofModule(
|
|
1250
|
+
circuit: any,
|
|
1251
|
+
provingKey: any,
|
|
1252
|
+
signer: User,
|
|
1253
|
+
inputs: UTXO[],
|
|
1254
|
+
outputs: UTXO[],
|
|
1255
|
+
owners: User[],
|
|
1256
|
+
ephemeralPrivateKey: BigInt,
|
|
1257
|
+
) {
|
|
1258
|
+
const inputCommitments: BigNumberish[] = inputs.map(
|
|
1259
|
+
(input) => input.hash,
|
|
1260
|
+
) as BigNumberish[];
|
|
1261
|
+
const inputValues = inputs.map((input) => BigInt(input.value || 0n));
|
|
1262
|
+
const inputSalts = inputs.map((input) => input.salt || 0n);
|
|
1263
|
+
const outputCommitments: BigNumberish[] = outputs.map(
|
|
1264
|
+
(output) => output.hash,
|
|
1265
|
+
) as BigNumberish[];
|
|
1266
|
+
const outputValues = outputs.map((output) => BigInt(output.value || 0n));
|
|
1267
|
+
const outputOwnerPublicKeys: BigNumberish[][] = owners.map(
|
|
1268
|
+
(owner) => owner.babyJubPublicKey,
|
|
1269
|
+
) as BigNumberish[][];
|
|
1270
|
+
const encryptionNonce: BigNumberish = newEncryptionNonce() as BigNumberish;
|
|
1271
|
+
const encryptInputs = stringifyBigInts({
|
|
1272
|
+
encryptionNonce,
|
|
1273
|
+
ecdhPrivateKey: formatPrivKeyForBabyJub(ephemeralPrivateKey),
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
const startWitnessCalculation = Date.now();
|
|
1277
|
+
const witness = await circuit.calculateWTNSBin(
|
|
1278
|
+
{
|
|
1279
|
+
inputCommitments,
|
|
1280
|
+
inputValues,
|
|
1281
|
+
inputSalts,
|
|
1282
|
+
inputOwnerPrivateKey: formatPrivKeyForBabyJub(signer.babyJubPrivateKey),
|
|
1283
|
+
outputCommitments,
|
|
1284
|
+
outputValues,
|
|
1285
|
+
outputSalts: outputs.map((output) => output.salt || 0n),
|
|
1286
|
+
outputOwnerPublicKeys,
|
|
1287
|
+
...encryptInputs,
|
|
1288
|
+
},
|
|
1289
|
+
true,
|
|
1290
|
+
);
|
|
1291
|
+
const timeWitnessCalculation = Date.now() - startWitnessCalculation;
|
|
1292
|
+
|
|
1293
|
+
const startProofGeneration = Date.now();
|
|
1294
|
+
const { proof, publicSignals } = (await groth16.prove(
|
|
1295
|
+
provingKey,
|
|
1296
|
+
witness,
|
|
1297
|
+
)) as { proof: BigNumberish[]; publicSignals: BigNumberish[] };
|
|
1298
|
+
const timeProofGeneration = Date.now() - startProofGeneration;
|
|
1299
|
+
console.log(
|
|
1300
|
+
`Witness calculation time: ${timeWitnessCalculation}ms, Proof generation time: ${timeProofGeneration}ms`,
|
|
1301
|
+
);
|
|
1302
|
+
|
|
1303
|
+
// The encryption circuit emits 4 encrypted-values per output commitment
|
|
1304
|
+
// (poseidon-cipher chunked into 4 field elements). publicSignals[0..2]
|
|
1305
|
+
// are the ECDH public key, so the encrypted blob starts at index 2.
|
|
1306
|
+
const encryptedValues = publicSignals.slice(2, 2 + 4 * outputs.length);
|
|
1307
|
+
const encodedProof = encodeProof(proof);
|
|
1308
|
+
return {
|
|
1309
|
+
inputCommitments,
|
|
1310
|
+
outputCommitments,
|
|
1311
|
+
encryptedValues,
|
|
1312
|
+
encryptionNonce,
|
|
1313
|
+
encodedProof,
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
module.exports = {
|
|
1318
|
+
prepareProof: prepareProofModule,
|
|
1319
|
+
encodeToBytes,
|
|
1320
|
+
};
|