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