@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
|
@@ -15,7 +15,12 @@
|
|
|
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
|
+
} from "ethers";
|
|
19
24
|
import { expect } from "chai";
|
|
20
25
|
import { loadCircuit, Poseidon, encodeProof, kycHash } from "zeto-js";
|
|
21
26
|
import { groth16 } from "snarkjs";
|
|
@@ -30,15 +35,89 @@ import {
|
|
|
30
35
|
ZERO_UTXO,
|
|
31
36
|
parseUTXOEvents,
|
|
32
37
|
parseRegistryEvents,
|
|
38
|
+
logger,
|
|
33
39
|
} from "./lib/utils";
|
|
34
40
|
import {
|
|
35
41
|
loadProvingKeys,
|
|
36
|
-
|
|
42
|
+
prepareDepositKycProof,
|
|
37
43
|
prepareNullifierWithdrawProof,
|
|
44
|
+
encodeToBytesForDeposit,
|
|
45
|
+
encodeToBytesForWithdraw,
|
|
46
|
+
calculateSpendHash,
|
|
47
|
+
calculateCancelHash,
|
|
38
48
|
} from "./utils";
|
|
39
49
|
import { deployZeto } from "./lib/deploy";
|
|
40
50
|
import smt from "../ignition/modules/test/smt";
|
|
41
51
|
|
|
52
|
+
if (!process.env.CIRCUITS_ROOT) {
|
|
53
|
+
process.env.CIRCUITS_ROOT = "/Users/jimzhang/Documents/zkp/circuits/";
|
|
54
|
+
}
|
|
55
|
+
if (!process.env.PROVING_KEYS_ROOT) {
|
|
56
|
+
process.env.PROVING_KEYS_ROOT = "/Users/jimzhang/Documents/zkp/proving-keys/";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ABI fragments for the ZetoLockableCapability *Args payloads. These mirror
|
|
60
|
+
// the Solidity structs declared in IZetoLockableCapability.sol and let the
|
|
61
|
+
// tests construct the opaque `bytes` parameters that the lock lifecycle
|
|
62
|
+
// methods (createLock / updateLock / delegateLock / spendLock / cancelLock)
|
|
63
|
+
// pass through to the token-specific encoders.
|
|
64
|
+
const CREATE_ARGS_ABI =
|
|
65
|
+
"tuple(bytes32 txId, uint256[] inputs, uint256[] outputs, uint256[] lockedOutputs, bytes proof)";
|
|
66
|
+
const UPDATE_ARGS_ABI = "tuple(bytes32 txId)";
|
|
67
|
+
const DELEGATE_ARGS_ABI = "tuple(bytes32 txId)";
|
|
68
|
+
const SPEND_ARGS_ABI =
|
|
69
|
+
"tuple(bytes32 txId, uint256[] lockedOutputs, uint256[] outputs, bytes proof, bytes data)";
|
|
70
|
+
|
|
71
|
+
function encodeCreateArgs(args: {
|
|
72
|
+
txId: string;
|
|
73
|
+
inputs: BigNumberish[];
|
|
74
|
+
outputs: BigNumberish[];
|
|
75
|
+
lockedOutputs: BigNumberish[];
|
|
76
|
+
proof: string;
|
|
77
|
+
}) {
|
|
78
|
+
return new AbiCoder().encode([CREATE_ARGS_ABI], [args]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function encodeUpdateArgs(txId: string) {
|
|
82
|
+
return new AbiCoder().encode([UPDATE_ARGS_ABI], [{ txId }]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function encodeDelegateArgs(txId: string) {
|
|
86
|
+
return new AbiCoder().encode([DELEGATE_ARGS_ABI], [{ txId }]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function encodeSpendArgs(args: {
|
|
90
|
+
txId: string;
|
|
91
|
+
lockedOutputs: BigNumberish[];
|
|
92
|
+
outputs: BigNumberish[];
|
|
93
|
+
proof: string;
|
|
94
|
+
data: string;
|
|
95
|
+
}) {
|
|
96
|
+
return new AbiCoder().encode([SPEND_ARGS_ABI], [args]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function randomBytes32(): string {
|
|
100
|
+
return ethers.hexlify(ethers.randomBytes(32));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The unlocked-input path encodes (root, ZkProof) so the contract can
|
|
104
|
+
// validate the SMT root before delegating to the verifier. The locked-input
|
|
105
|
+
// path elides the root: locked UTXOs are tracked in a flat per-lock mapping
|
|
106
|
+
// rather than an SMT, so there's no Merkle root to assert membership against.
|
|
107
|
+
function encodeUnlockedProof(root: BigNumberish, proof: any) {
|
|
108
|
+
return new AbiCoder().encode(
|
|
109
|
+
["uint256 root", "tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
|
|
110
|
+
[root, proof],
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function encodeLockedProof(proof: any) {
|
|
115
|
+
return new AbiCoder().encode(
|
|
116
|
+
["tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
|
|
117
|
+
[proof],
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
42
121
|
describe("Zeto based fungible token with anonymity, KYC, using nullifiers without encryption", function () {
|
|
43
122
|
let deployer: Signer;
|
|
44
123
|
let Alice: User;
|
|
@@ -79,11 +158,17 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
79
158
|
|
|
80
159
|
({ deployer, zeto, erc20 } = await deployZeto("Zeto_AnonNullifierKyc"));
|
|
81
160
|
|
|
82
|
-
const tx2 = await zeto
|
|
161
|
+
const tx2 = await zeto
|
|
162
|
+
.connect(deployer)
|
|
163
|
+
.register(Alice.babyJubPublicKey, "0x");
|
|
83
164
|
const result1 = await tx2.wait();
|
|
84
|
-
const tx3 = await zeto
|
|
165
|
+
const tx3 = await zeto
|
|
166
|
+
.connect(deployer)
|
|
167
|
+
.register(Bob.babyJubPublicKey, "0x");
|
|
85
168
|
const result2 = await tx3.wait();
|
|
86
|
-
const tx4 = await zeto
|
|
169
|
+
const tx4 = await zeto
|
|
170
|
+
.connect(deployer)
|
|
171
|
+
.register(Charlie.babyJubPublicKey, "0x");
|
|
87
172
|
const result3 = await tx4.wait();
|
|
88
173
|
|
|
89
174
|
const storage1 = new InMemoryDB(str2Bytes("alice"));
|
|
@@ -106,7 +191,9 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
106
191
|
await smtKyc.add(kycHash(publicKey3), kycHash(publicKey3));
|
|
107
192
|
|
|
108
193
|
circuit = await loadCircuit("anon_nullifier_kyc_transfer");
|
|
109
|
-
({ provingKeyFile: provingKey } = loadProvingKeys(
|
|
194
|
+
({ provingKeyFile: provingKey } = loadProvingKeys(
|
|
195
|
+
"anon_nullifier_kyc_transfer",
|
|
196
|
+
));
|
|
110
197
|
batchCircuit = await loadCircuit("anon_nullifier_kyc_transfer_batch");
|
|
111
198
|
({ provingKeyFile: batchProvingKey } = loadProvingKeys(
|
|
112
199
|
"anon_nullifier_kyc_transfer_batch",
|
|
@@ -237,11 +324,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
237
324
|
await smtUnregistered.add(incomingUTXOs[i], incomingUTXOs[i]);
|
|
238
325
|
}
|
|
239
326
|
|
|
240
|
-
// check empty hashes are empty
|
|
241
|
-
for (let i = outputUtxos.length; i < 10; i++) {
|
|
242
|
-
expect(incomingUTXOs[i]).to.equal(0);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
327
|
// mint sufficient balance in Zeto contract address for Alice to withdraw
|
|
246
328
|
const mintTx = await erc20.connect(deployer).mint(zeto, 3);
|
|
247
329
|
await mintTx.wait();
|
|
@@ -294,8 +376,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
294
376
|
3,
|
|
295
377
|
_withdrawNullifiers,
|
|
296
378
|
withdrawCommitments[0],
|
|
297
|
-
root.bigInt(),
|
|
298
|
-
withdrawEncodedProof,
|
|
379
|
+
encodeToBytesForWithdraw(root.bigInt(), withdrawEncodedProof),
|
|
299
380
|
"0x",
|
|
300
381
|
);
|
|
301
382
|
await tx.wait();
|
|
@@ -303,7 +384,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
303
384
|
// Alice checks her ERC20 balance
|
|
304
385
|
const endingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
305
386
|
expect(endingBalance - startingBalance).to.be.equal(3);
|
|
306
|
-
}).timeout(
|
|
387
|
+
}).timeout(180000);
|
|
307
388
|
|
|
308
389
|
it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () {
|
|
309
390
|
const startingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
@@ -317,13 +398,29 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
317
398
|
|
|
318
399
|
utxo100 = newUTXO(100, Alice);
|
|
319
400
|
utxo0 = newUTXO(0, Alice);
|
|
320
|
-
const
|
|
401
|
+
const identitiesRoot = await smtKyc.root();
|
|
402
|
+
const proof3 = await smtKyc.generateCircomVerifierProof(
|
|
403
|
+
kycHash(Alice.babyJubPublicKey),
|
|
404
|
+
identitiesRoot,
|
|
405
|
+
);
|
|
406
|
+
const identitiesMerkleProofs = [
|
|
407
|
+
proof3.siblings.map((s) => s.bigInt()),
|
|
408
|
+
proof3.siblings.map((s) => s.bigInt()),
|
|
409
|
+
];
|
|
410
|
+
const { outputCommitments, encodedProof } = await prepareDepositKycProof(
|
|
321
411
|
Alice,
|
|
322
412
|
[utxo100, utxo0],
|
|
413
|
+
identitiesRoot.bigInt(),
|
|
414
|
+
identitiesMerkleProofs,
|
|
323
415
|
);
|
|
324
416
|
const tx2 = await zeto
|
|
325
417
|
.connect(Alice.signer)
|
|
326
|
-
.deposit(
|
|
418
|
+
.deposit(
|
|
419
|
+
100,
|
|
420
|
+
outputCommitments,
|
|
421
|
+
encodeToBytesForDeposit(encodedProof),
|
|
422
|
+
"0x",
|
|
423
|
+
);
|
|
327
424
|
await tx2.wait();
|
|
328
425
|
|
|
329
426
|
await smtAlice.add(utxo100.hash, utxo100.hash);
|
|
@@ -541,8 +638,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
541
638
|
80,
|
|
542
639
|
nullifiers,
|
|
543
640
|
outputCommitments[0],
|
|
544
|
-
root.bigInt(),
|
|
545
|
-
encodedProof,
|
|
641
|
+
encodeToBytesForWithdraw(root.bigInt(), encodedProof),
|
|
546
642
|
"0x",
|
|
547
643
|
);
|
|
548
644
|
await tx.wait();
|
|
@@ -578,7 +674,17 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
578
674
|
);
|
|
579
675
|
});
|
|
580
676
|
|
|
581
|
-
it("deposit by an unregistered user should
|
|
677
|
+
it("deposit by an unregistered user should fail", async function () {
|
|
678
|
+
const identitiesRoot = await smtKycUnregistered.root();
|
|
679
|
+
const proof3 = await smtKycUnregistered.generateCircomVerifierProof(
|
|
680
|
+
kycHash(unregistered.babyJubPublicKey),
|
|
681
|
+
identitiesRoot,
|
|
682
|
+
);
|
|
683
|
+
const identitiesMerkleProofs = [
|
|
684
|
+
proof3.siblings.map((s) => s.bigInt()),
|
|
685
|
+
proof3.siblings.map((s) => s.bigInt()),
|
|
686
|
+
];
|
|
687
|
+
|
|
582
688
|
const tx = await erc20
|
|
583
689
|
.connect(deployer)
|
|
584
690
|
.mint(unregistered.ethAddress, 100);
|
|
@@ -590,160 +696,32 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
590
696
|
|
|
591
697
|
unregisteredUtxo100 = newUTXO(100, unregistered);
|
|
592
698
|
unregisteredUtxo0 = newUTXO(0, unregistered);
|
|
593
|
-
const { outputCommitments, encodedProof } = await
|
|
699
|
+
const { outputCommitments, encodedProof } = await prepareDepositKycProof(
|
|
594
700
|
unregistered,
|
|
595
701
|
[unregisteredUtxo100, unregisteredUtxo0],
|
|
702
|
+
identitiesRoot.bigInt(),
|
|
703
|
+
identitiesMerkleProofs,
|
|
596
704
|
);
|
|
597
|
-
const tx2 = await zeto
|
|
598
|
-
.connect(unregistered.signer)
|
|
599
|
-
.deposit(100, outputCommitments, encodedProof, "0x");
|
|
600
|
-
await tx2.wait();
|
|
601
|
-
|
|
602
|
-
// Alice tracks the UTXO inside the SMT
|
|
603
|
-
await smtAlice.add(unregisteredUtxo100.hash, unregisteredUtxo100.hash);
|
|
604
|
-
await smtAlice.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash);
|
|
605
|
-
// Bob also locally tracks the UTXOs inside the SMT
|
|
606
|
-
await smtBob.add(unregisteredUtxo100.hash, unregisteredUtxo100.hash);
|
|
607
|
-
await smtBob.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash);
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
it("transfer from an unregistered user should fail", async function () {
|
|
611
|
-
// catch up the local SMT for the unregistered user
|
|
612
|
-
await smtUnregistered.add(utxo100.hash, utxo100.hash);
|
|
613
|
-
await smtUnregistered.add(utxo0.hash, utxo0.hash);
|
|
614
|
-
await smtUnregistered.add(utxo1.hash, utxo1.hash);
|
|
615
|
-
await smtUnregistered.add(utxo2.hash, utxo2.hash);
|
|
616
|
-
await smtUnregistered.add(_utxo3.hash, _utxo3.hash);
|
|
617
|
-
await smtUnregistered.add(utxo4.hash, utxo4.hash);
|
|
618
|
-
await smtUnregistered.add(utxo6.hash, utxo6.hash);
|
|
619
|
-
await smtUnregistered.add(utxo7.hash, utxo7.hash);
|
|
620
|
-
await smtUnregistered.add(
|
|
621
|
-
withdrawChangeUTXO.hash,
|
|
622
|
-
withdrawChangeUTXO.hash,
|
|
623
|
-
);
|
|
624
|
-
await smtUnregistered.add(
|
|
625
|
-
unregisteredUtxo100.hash,
|
|
626
|
-
unregisteredUtxo100.hash,
|
|
627
|
-
);
|
|
628
|
-
await smtUnregistered.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash);
|
|
629
|
-
const utxosRoot = await smtUnregistered.root();
|
|
630
|
-
|
|
631
|
-
const nullifier = newNullifier(unregisteredUtxo100, unregistered);
|
|
632
|
-
const output1 = newUTXO(100, Bob);
|
|
633
|
-
const output2 = newUTXO(0, unregistered);
|
|
634
|
-
const proof = await smtUnregistered.generateCircomVerifierProof(
|
|
635
|
-
unregisteredUtxo100.hash,
|
|
636
|
-
utxosRoot,
|
|
637
|
-
);
|
|
638
|
-
const merkleProofs = [
|
|
639
|
-
proof.siblings.map((s) => s.bigInt()),
|
|
640
|
-
proof.siblings.map((s) => s.bigInt()),
|
|
641
|
-
];
|
|
642
|
-
|
|
643
|
-
const identitiesRoot = await smtKycUnregistered.root();
|
|
644
|
-
const proof3 = await smtKycUnregistered.generateCircomVerifierProof(
|
|
645
|
-
kycHash(unregistered.babyJubPublicKey),
|
|
646
|
-
identitiesRoot,
|
|
647
|
-
);
|
|
648
|
-
const proof4 = await smtKycUnregistered.generateCircomVerifierProof(
|
|
649
|
-
kycHash(Bob.babyJubPublicKey),
|
|
650
|
-
identitiesRoot,
|
|
651
|
-
);
|
|
652
|
-
const identitiesMerkleProofs = [
|
|
653
|
-
proof3.siblings.map((s) => s.bigInt()), // identity proof for the sender (unregistered)
|
|
654
|
-
proof4.siblings.map((s) => s.bigInt()), // identity proof for the 1st owner of the output UTXO (Bob)
|
|
655
|
-
proof3.siblings.map((s) => s.bigInt()), // identity proof for the 2nd owner of the output UTXO (unregistered)
|
|
656
|
-
];
|
|
657
705
|
await expect(
|
|
658
|
-
|
|
659
|
-
unregistered
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
[Bob, unregistered],
|
|
668
|
-
),
|
|
669
|
-
).rejectedWith("Invalid proof");
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
it("the unregistered user can still withdraw their UTXOs to ERC20 tokens", async function () {
|
|
673
|
-
const startingBalance = await erc20.balanceOf(unregistered.ethAddress);
|
|
674
|
-
// unregistered user generates the nullifiers for the UTXOs to be spent
|
|
675
|
-
const nullifier1 = newNullifier(unregisteredUtxo100, unregistered);
|
|
676
|
-
|
|
677
|
-
// unregistered user generates inclusion proofs for the UTXOs to be spent
|
|
678
|
-
let root = await smtUnregistered.root();
|
|
679
|
-
const proof1 = await smtUnregistered.generateCircomVerifierProof(
|
|
680
|
-
unregisteredUtxo100.hash,
|
|
681
|
-
root,
|
|
682
|
-
);
|
|
683
|
-
const proof2 = await smtUnregistered.generateCircomVerifierProof(
|
|
684
|
-
0n,
|
|
685
|
-
root,
|
|
686
|
-
);
|
|
687
|
-
const merkleProofs = [
|
|
688
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
689
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
690
|
-
];
|
|
691
|
-
|
|
692
|
-
// unregistered user proposes the output ERC20 tokens
|
|
693
|
-
const unregisteredWithdrawChangeUTXO = newUTXO(0, unregistered);
|
|
694
|
-
|
|
695
|
-
const { nullifiers, outputCommitments, encodedProof } =
|
|
696
|
-
await prepareNullifierWithdrawProof(
|
|
697
|
-
unregistered,
|
|
698
|
-
[unregisteredUtxo100, ZERO_UTXO],
|
|
699
|
-
[nullifier1, ZERO_UTXO],
|
|
700
|
-
unregisteredWithdrawChangeUTXO,
|
|
701
|
-
root.bigInt(),
|
|
702
|
-
merkleProofs,
|
|
703
|
-
);
|
|
704
|
-
|
|
705
|
-
// unregistered user withdraws her UTXOs to ERC20 tokens
|
|
706
|
-
const tx = await zeto
|
|
707
|
-
.connect(unregistered.signer)
|
|
708
|
-
.withdraw(
|
|
709
|
-
100,
|
|
710
|
-
nullifiers,
|
|
711
|
-
outputCommitments[0],
|
|
712
|
-
root.bigInt(),
|
|
713
|
-
encodedProof,
|
|
714
|
-
"0x",
|
|
715
|
-
);
|
|
716
|
-
await tx.wait();
|
|
717
|
-
|
|
718
|
-
// Alice tracks the UTXO inside the SMT
|
|
719
|
-
await smtAlice.add(
|
|
720
|
-
unregisteredWithdrawChangeUTXO.hash,
|
|
721
|
-
unregisteredWithdrawChangeUTXO.hash,
|
|
722
|
-
);
|
|
723
|
-
// Bob also locally tracks the UTXOs inside the SMT
|
|
724
|
-
await smtBob.add(
|
|
725
|
-
unregisteredWithdrawChangeUTXO.hash,
|
|
726
|
-
unregisteredWithdrawChangeUTXO.hash,
|
|
727
|
-
);
|
|
728
|
-
|
|
729
|
-
// unregistered user checks her ERC20 balance
|
|
730
|
-
const endingBalance = await erc20.balanceOf(unregistered.ethAddress);
|
|
731
|
-
expect(endingBalance - startingBalance).to.be.equal(100);
|
|
706
|
+
zeto
|
|
707
|
+
.connect(unregistered.signer)
|
|
708
|
+
.deposit(
|
|
709
|
+
100,
|
|
710
|
+
outputCommitments,
|
|
711
|
+
encodeToBytesForDeposit(encodedProof),
|
|
712
|
+
"0x",
|
|
713
|
+
),
|
|
714
|
+
).to.be.rejectedWith("VM Exception while processing transaction: reverted with custom error 'InvalidProof()'");
|
|
732
715
|
});
|
|
733
716
|
});
|
|
734
717
|
|
|
735
|
-
describe("
|
|
736
|
-
let lockedUtxo1: UTXO;
|
|
737
|
-
let smtBobForLocked: Merkletree;
|
|
718
|
+
describe("ILockableCapability tests", function () {
|
|
738
719
|
let utxo9: UTXO;
|
|
739
720
|
let utxo10: UTXO;
|
|
740
721
|
let utxo11: UTXO;
|
|
741
722
|
let utxo12: UTXO;
|
|
742
723
|
|
|
743
724
|
before(async function () {
|
|
744
|
-
const storage1 = new InMemoryDB(str2Bytes(""));
|
|
745
|
-
smtBobForLocked = new Merkletree(storage1, true, 64);
|
|
746
|
-
|
|
747
725
|
// mint UTXOs for Alice and spend one (to use it in a failure case test)
|
|
748
726
|
utxo9 = newUTXO(10, Alice);
|
|
749
727
|
utxo10 = newUTXO(15, Alice);
|
|
@@ -775,7 +753,10 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
775
753
|
utxo12 = newUTXO(0, Alice);
|
|
776
754
|
|
|
777
755
|
let root = await smtAlice.root();
|
|
778
|
-
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
756
|
+
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
757
|
+
utxo10.hash,
|
|
758
|
+
root,
|
|
759
|
+
);
|
|
779
760
|
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
780
761
|
const merkleProofs = [
|
|
781
762
|
proof1.siblings.map((s) => s.bigInt()),
|
|
@@ -819,34 +800,51 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
819
800
|
await smtBob.add(utxo12.hash, utxo12.hash);
|
|
820
801
|
});
|
|
821
802
|
|
|
822
|
-
|
|
823
|
-
|
|
803
|
+
// createLock -> updateLock -> delegateLock -> spendLock happy path. Note
|
|
804
|
+
// that for KYC the create-lock proof is the standard transfer proof
|
|
805
|
+
// (validates the SMT root and identities tree); the contract transitions
|
|
806
|
+
// the locked output into a flat per-lock mapping. The spendLock proof
|
|
807
|
+
// uses the dedicated transferLocked circuit / verifier wired through the
|
|
808
|
+
// ignition module's lockVerifier slot.
|
|
809
|
+
describe("createLock -> updateLock -> delegateLock -> spendLock flow", function () {
|
|
810
|
+
let lockedUtxo1: UTXO;
|
|
811
|
+
let lockId: string;
|
|
812
|
+
let outputUtxo1: UTXO;
|
|
813
|
+
let outputUtxo2: UTXO;
|
|
814
|
+
let unlockHash: string;
|
|
815
|
+
// Local SMT mirroring on-chain locked-state bookkeeping used by
|
|
816
|
+
// delegateLock tests (`locked()` / storage mirrors).
|
|
817
|
+
let smtForLocked: Merkletree;
|
|
818
|
+
|
|
819
|
+
before(async function () {
|
|
820
|
+
const storage = new InMemoryDB(str2Bytes("kyc-locked-1"));
|
|
821
|
+
smtForLocked = new Merkletree(storage, true, 64);
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
it("createLock() with deterministic lockId computed from txId", async function () {
|
|
824
825
|
const nullifier1 = newNullifier(utxo11, Bob);
|
|
825
826
|
lockedUtxo1 = newUTXO(utxo11.value!, Bob);
|
|
826
827
|
const root = await smtBob.root();
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
root,
|
|
830
|
-
);
|
|
831
|
-
const proof2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
828
|
+
const p1 = await smtBob.generateCircomVerifierProof(utxo11.hash, root);
|
|
829
|
+
const p2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
832
830
|
const merkleProofs = [
|
|
833
|
-
|
|
834
|
-
|
|
831
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
832
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
835
833
|
];
|
|
836
834
|
|
|
837
835
|
const identitiesRoot = await smtKyc.root();
|
|
838
|
-
const
|
|
836
|
+
const idProofBob = await smtKyc.generateCircomVerifierProof(
|
|
839
837
|
kycHash(Bob.babyJubPublicKey),
|
|
840
838
|
identitiesRoot,
|
|
841
839
|
);
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
840
|
+
const identityMerkleProofs = [
|
|
841
|
+
idProofBob.siblings.map((s) => s.bigInt()), // sender (Bob)
|
|
842
|
+
idProofBob.siblings.map((s) => s.bigInt()), // 1st output owner (Bob)
|
|
843
|
+
idProofBob.siblings.map((s) => s.bigInt()), // 2nd output owner (dummy)
|
|
846
844
|
];
|
|
847
845
|
|
|
848
|
-
const {
|
|
849
|
-
circuit,
|
|
846
|
+
const { encodedProof } = await prepareProof(
|
|
847
|
+
circuit,
|
|
850
848
|
provingKey,
|
|
851
849
|
Bob,
|
|
852
850
|
[utxo11, ZERO_UTXO],
|
|
@@ -855,547 +853,565 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
855
853
|
root.bigInt(),
|
|
856
854
|
merkleProofs,
|
|
857
855
|
identitiesRoot.bigInt(),
|
|
858
|
-
|
|
856
|
+
identityMerkleProofs,
|
|
859
857
|
[Bob, Bob],
|
|
860
858
|
);
|
|
861
859
|
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
860
|
+
const txId = randomBytes32();
|
|
861
|
+
const createArgs = encodeCreateArgs({
|
|
862
|
+
txId,
|
|
863
|
+
inputs: [nullifier1.hash],
|
|
864
|
+
outputs: [],
|
|
865
|
+
lockedOutputs: [lockedUtxo1.hash],
|
|
866
|
+
proof: encodeUnlockedProof(root.bigInt(), encodedProof),
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
// Pre-compute lockId off-chain and confirm against the contract.
|
|
870
|
+
const predicted = await zeto
|
|
871
|
+
.connect(Bob.signer)
|
|
872
|
+
.computeLockId(createArgs);
|
|
873
|
+
lockId = predicted;
|
|
872
874
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
await
|
|
877
|
-
|
|
878
|
-
|
|
875
|
+
const tx = await zeto
|
|
876
|
+
.connect(Bob.signer)
|
|
877
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x");
|
|
878
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
879
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
880
|
+
|
|
881
|
+
const created = result!.logs
|
|
882
|
+
.map((l) => {
|
|
883
|
+
try {
|
|
884
|
+
return zeto.interface.parseLog(l as any);
|
|
885
|
+
} catch (_e) {
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
})
|
|
889
|
+
.find((p) => p && p.name === "LockCreated");
|
|
890
|
+
expect(created, "LockCreated event not found").to.not.be.null;
|
|
891
|
+
expect(created!.args.lockId).to.equal(predicted);
|
|
892
|
+
expect(created!.args.owner).to.equal(Bob.ethAddress);
|
|
893
|
+
expect(created!.args.spender).to.equal(Bob.ethAddress);
|
|
894
|
+
|
|
895
|
+
// Mirror the locked UTXO into the local SMT so the locked-circuit
|
|
896
|
+
// witness has a valid inclusion proof to consume.
|
|
897
|
+
await smtForLocked.add(
|
|
898
|
+
lockedUtxo1.hash,
|
|
899
|
+
ethers.toBigInt(Bob.ethAddress),
|
|
879
900
|
);
|
|
880
901
|
});
|
|
881
902
|
|
|
882
|
-
it("
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
expect(
|
|
903
|
+
it("isLockActive() and getLock() reflect the newly created lock", async function () {
|
|
904
|
+
expect(await zeto.isLockActive(lockId)).to.equal(true);
|
|
905
|
+
const info = await zeto.getLock(lockId);
|
|
906
|
+
expect(info.owner).to.equal(Bob.ethAddress);
|
|
907
|
+
expect(info.spender).to.equal(Bob.ethAddress);
|
|
908
|
+
expect(info.spendCommitment).to.equal(ethers.ZeroHash);
|
|
909
|
+
expect(info.cancelCommitment).to.equal(ethers.ZeroHash);
|
|
886
910
|
});
|
|
887
911
|
|
|
888
|
-
it("
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
912
|
+
it("locked() returns true for locked UTXOs and false for unlocked or spent UTXOs", async function () {
|
|
913
|
+
expect(await zeto.locked(lockedUtxo1.hash)).to.deep.equal([
|
|
914
|
+
true,
|
|
915
|
+
Bob.ethAddress,
|
|
916
|
+
]);
|
|
917
|
+
// utxo12 is an unlocked UTXO that still belongs to Alice.
|
|
918
|
+
expect((await zeto.locked(utxo12.hash))[0]).to.be.false;
|
|
919
|
+
});
|
|
892
920
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
const utxo1 = newUTXO(lockedUtxo1.value!, Bob);
|
|
897
|
-
const root = await smtBobForLocked.root();
|
|
898
|
-
const proof1 = await smtBobForLocked.generateCircomVerifierProof(
|
|
899
|
-
lockedUtxo1.hash,
|
|
900
|
-
root,
|
|
901
|
-
);
|
|
902
|
-
const proof2 = await smtBobForLocked.generateCircomVerifierProof(
|
|
903
|
-
0n,
|
|
904
|
-
root,
|
|
905
|
-
);
|
|
906
|
-
const merkleProofs = [
|
|
907
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
908
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
909
|
-
];
|
|
921
|
+
it("updateLock() commits the spend hash while owner == spender", async function () {
|
|
922
|
+
outputUtxo1 = newUTXO(10, Alice);
|
|
923
|
+
outputUtxo2 = newUTXO(5, Bob);
|
|
910
924
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
925
|
+
unlockHash = calculateSpendHash(
|
|
926
|
+
[lockedUtxo1],
|
|
927
|
+
[],
|
|
928
|
+
[outputUtxo1, outputUtxo2],
|
|
929
|
+
"0x",
|
|
915
930
|
);
|
|
916
|
-
const identitiesMerkleProofs = [
|
|
917
|
-
proof3.siblings.map((s) => s.bigInt()), // identity proof for the sender (Bob)
|
|
918
|
-
proof3.siblings.map((s) => s.bigInt()), // identity proof for the 1st owner of the output UTXO (Bob)
|
|
919
|
-
proof3.siblings.map((s) => s.bigInt()), // dummy identity proof for the 2nd owner of the output UTXO
|
|
920
|
-
];
|
|
921
931
|
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
[Bob, Bob],
|
|
934
|
-
Alice.ethAddress,
|
|
935
|
-
);
|
|
932
|
+
const tx = await zeto
|
|
933
|
+
.connect(Bob.signer)
|
|
934
|
+
.updateLock(
|
|
935
|
+
lockId,
|
|
936
|
+
encodeUpdateArgs(randomBytes32()),
|
|
937
|
+
unlockHash,
|
|
938
|
+
ethers.ZeroHash,
|
|
939
|
+
"0x",
|
|
940
|
+
);
|
|
941
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
942
|
+
logger.debug(`updateLock() complete. Gas used: ${result?.gasUsed}`);
|
|
936
943
|
|
|
937
|
-
await
|
|
938
|
-
|
|
939
|
-
.connect(Bob.signer)
|
|
940
|
-
.lock(
|
|
941
|
-
[nullifier1.hash],
|
|
942
|
-
[],
|
|
943
|
-
outputCommitments,
|
|
944
|
-
root.bigInt(),
|
|
945
|
-
encodedProof,
|
|
946
|
-
Charlie.ethAddress,
|
|
947
|
-
"0x",
|
|
948
|
-
),
|
|
949
|
-
).to.be.rejectedWith("UTXORootNotFound");
|
|
944
|
+
const info = await zeto.getLock(lockId);
|
|
945
|
+
expect(info.spendCommitment).to.equal(unlockHash);
|
|
950
946
|
});
|
|
951
947
|
|
|
952
|
-
it("
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
);
|
|
964
|
-
const merkleProofs = [
|
|
965
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
966
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
967
|
-
];
|
|
968
|
-
// Bob proposes the output UTXOs, attempting to transfer the locked UTXO to Charlie
|
|
969
|
-
const _utxo1 = newUTXO(lockedUtxo1.value!, Charlie);
|
|
970
|
-
|
|
971
|
-
const identitiesRoot = await smtKyc.root();
|
|
972
|
-
const proof3 = await smtKyc.generateCircomVerifierProof(
|
|
973
|
-
kycHash(Bob.babyJubPublicKey),
|
|
974
|
-
identitiesRoot,
|
|
975
|
-
);
|
|
976
|
-
const proof4 = await smtKyc.generateCircomVerifierProof(
|
|
977
|
-
kycHash(Charlie.babyJubPublicKey),
|
|
978
|
-
identitiesRoot,
|
|
979
|
-
);
|
|
980
|
-
const identitiesMerkleProofs = [
|
|
981
|
-
proof3.siblings.map((s) => s.bigInt()), // identity proof for the sender (Bob)
|
|
982
|
-
proof4.siblings.map((s) => s.bigInt()), // identity proof for the 1st owner of the output UTXO (Charlie)
|
|
983
|
-
proof3.siblings.map((s) => s.bigInt()), // dummy identity proof for the 2nd owner of the output UTXO
|
|
984
|
-
];
|
|
985
|
-
|
|
986
|
-
// Bob should NOT be able to spend the UTXO which has been locked and delegated to Alice
|
|
987
|
-
await expect(
|
|
988
|
-
doTransfer(
|
|
989
|
-
Bob,
|
|
990
|
-
[lockedUtxo1, ZERO_UTXO],
|
|
991
|
-
[nullifier1, ZERO_UTXO],
|
|
992
|
-
[_utxo1, ZERO_UTXO],
|
|
993
|
-
root.bigInt(),
|
|
994
|
-
merkleProofs,
|
|
995
|
-
identitiesRoot.bigInt(),
|
|
996
|
-
identitiesMerkleProofs,
|
|
997
|
-
[Charlie, Bob],
|
|
998
|
-
Alice,
|
|
999
|
-
),
|
|
1000
|
-
).to.be.rejectedWith("Invalid proof"); // due to the lockDelegate part of the public input not matching the current delegate
|
|
1001
|
-
});
|
|
948
|
+
it("delegateLock() transfers spending authority to Alice", async function () {
|
|
949
|
+
const tx = await zeto
|
|
950
|
+
.connect(Bob.signer)
|
|
951
|
+
.delegateLock(
|
|
952
|
+
lockId,
|
|
953
|
+
encodeDelegateArgs(randomBytes32()),
|
|
954
|
+
Alice.ethAddress,
|
|
955
|
+
"0x",
|
|
956
|
+
);
|
|
957
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
958
|
+
logger.debug(`delegateLock() complete. Gas used: ${result?.gasUsed}`);
|
|
1002
959
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
960
|
+
const info = await zeto.getLock(lockId);
|
|
961
|
+
expect(info.spender).to.equal(Alice.ethAddress);
|
|
962
|
+
// Storage-layer delegate must follow the spender.
|
|
963
|
+
expect(await zeto.locked(lockedUtxo1.hash)).to.deep.equal([
|
|
964
|
+
true,
|
|
965
|
+
Alice.ethAddress,
|
|
966
|
+
]);
|
|
967
|
+
// Mirror the new delegate into the local locked SMT so the spendLock
|
|
968
|
+
// witness's SMT inclusion proof matches the (utxoHash, delegate) leaf.
|
|
969
|
+
await smtForLocked.update(
|
|
1008
970
|
lockedUtxo1.hash,
|
|
1009
|
-
|
|
1010
|
-
);
|
|
1011
|
-
const proof2 = await smtBobForLocked.generateCircomVerifierProof(
|
|
1012
|
-
0n,
|
|
1013
|
-
root,
|
|
971
|
+
ethers.toBigInt(Alice.ethAddress),
|
|
1014
972
|
);
|
|
1015
|
-
const merkleProofs = [
|
|
1016
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
1017
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
1018
|
-
];
|
|
1019
|
-
|
|
1020
|
-
const utxo1 = newUTXO(5, Bob);
|
|
1021
|
-
|
|
1022
|
-
await expect(
|
|
1023
|
-
prepareNullifierWithdrawProof(
|
|
1024
|
-
Bob,
|
|
1025
|
-
[lockedUtxo1, ZERO_UTXO],
|
|
1026
|
-
[nullifier1, ZERO_UTXO],
|
|
1027
|
-
utxo1,
|
|
1028
|
-
root.bigInt(),
|
|
1029
|
-
merkleProofs,
|
|
1030
|
-
),
|
|
1031
|
-
).to.be.rejectedWith("SMTVerifier_249 line: 134");
|
|
1032
973
|
});
|
|
1033
974
|
|
|
1034
|
-
it("
|
|
1035
|
-
//
|
|
975
|
+
it("the new spender can spendLock() with the matching payload", async function () {
|
|
976
|
+
// The locked-input proof is generated against the dedicated
|
|
977
|
+
// transferLocked circuit; identity-tree membership for both the
|
|
978
|
+
// (still-original) sender Bob and each output owner is asserted as
|
|
979
|
+
// part of the snark, mirroring the unlocked transfer flow.
|
|
1036
980
|
const nullifier1 = newNullifier(lockedUtxo1, Bob);
|
|
1037
|
-
const root = await smtBobForLocked.root();
|
|
1038
|
-
const proof1 = await smtBobForLocked.generateCircomVerifierProof(
|
|
1039
|
-
lockedUtxo1.hash,
|
|
1040
|
-
root,
|
|
1041
|
-
);
|
|
1042
|
-
const proof2 = await smtBobForLocked.generateCircomVerifierProof(
|
|
1043
|
-
0n,
|
|
1044
|
-
root,
|
|
1045
|
-
);
|
|
1046
|
-
const merkleProofs = [
|
|
1047
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
1048
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
1049
|
-
];
|
|
1050
|
-
// Bob proposes the output UTXOs, attempting to transfer the locked UTXO to Alice
|
|
1051
|
-
// and reusing an existing UTXO (utxo4)
|
|
1052
|
-
const utxo1 = newUTXO(5, Alice);
|
|
1053
|
-
|
|
1054
981
|
const identitiesRoot = await smtKyc.root();
|
|
1055
|
-
const
|
|
982
|
+
const idProofBob = await smtKyc.generateCircomVerifierProof(
|
|
1056
983
|
kycHash(Bob.babyJubPublicKey),
|
|
1057
984
|
identitiesRoot,
|
|
1058
985
|
);
|
|
1059
|
-
const
|
|
986
|
+
const idProofAlice = await smtKyc.generateCircomVerifierProof(
|
|
1060
987
|
kycHash(Alice.babyJubPublicKey),
|
|
1061
988
|
identitiesRoot,
|
|
1062
989
|
);
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
990
|
+
const identityMerkleProofs = [
|
|
991
|
+
idProofBob.siblings.map((s) => s.bigInt()), // sender (Bob)
|
|
992
|
+
idProofAlice.siblings.map((s) => s.bigInt()), // 1st output owner (Alice)
|
|
993
|
+
idProofBob.siblings.map((s) => s.bigInt()), // 2nd output owner (Bob)
|
|
1067
994
|
];
|
|
1068
995
|
|
|
1069
|
-
const
|
|
996
|
+
const { encodedProof } = await prepareProof(
|
|
1070
997
|
circuitForLocked,
|
|
1071
998
|
provingKeyForLocked,
|
|
1072
999
|
Bob,
|
|
1073
1000
|
[lockedUtxo1, ZERO_UTXO],
|
|
1074
1001
|
[nullifier1, ZERO_UTXO],
|
|
1075
|
-
[
|
|
1076
|
-
|
|
1077
|
-
|
|
1002
|
+
[outputUtxo1, outputUtxo2],
|
|
1003
|
+
0n,
|
|
1004
|
+
[],
|
|
1078
1005
|
identitiesRoot.bigInt(),
|
|
1079
|
-
|
|
1080
|
-
[Alice,
|
|
1081
|
-
|
|
1006
|
+
identityMerkleProofs,
|
|
1007
|
+
[Alice, Bob],
|
|
1008
|
+
undefined,
|
|
1009
|
+
{ transferLocked: true },
|
|
1082
1010
|
);
|
|
1083
|
-
const nullifiers = [nullifier1.hash];
|
|
1084
1011
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
)
|
|
1096
|
-
|
|
1012
|
+
const spendArgs = encodeSpendArgs({
|
|
1013
|
+
txId: randomBytes32(),
|
|
1014
|
+
lockedOutputs: [],
|
|
1015
|
+
outputs: [outputUtxo1.hash, outputUtxo2.hash],
|
|
1016
|
+
proof: encodeLockedProof(encodedProof),
|
|
1017
|
+
data: "0x",
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
const tx = await zeto
|
|
1021
|
+
.connect(Alice.signer)
|
|
1022
|
+
.spendLock(lockId, spendArgs, "0x");
|
|
1023
|
+
const result = await tx.wait();
|
|
1024
|
+
|
|
1025
|
+
const parsed = result!.logs
|
|
1026
|
+
.map((l) => {
|
|
1027
|
+
try {
|
|
1028
|
+
return zeto.interface.parseLog(l as any);
|
|
1029
|
+
} catch (_e) {
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
})
|
|
1033
|
+
.filter((p) => p !== null) as ReadonlyArray<{
|
|
1034
|
+
name: string;
|
|
1035
|
+
args: any;
|
|
1036
|
+
}>;
|
|
1037
|
+
const lockSpent = parsed.find((p) => p.name === "LockSpent");
|
|
1038
|
+
const zetoLockSpent = parsed.find((p) => p.name === "ZetoLockSpent");
|
|
1039
|
+
expect(lockSpent, "LockSpent event not emitted").to.not.be.undefined;
|
|
1040
|
+
expect(zetoLockSpent, "ZetoLockSpent event not emitted").to.not.be
|
|
1041
|
+
.undefined;
|
|
1042
|
+
expect(lockSpent!.args.lockId).to.equal(lockId);
|
|
1043
|
+
expect(lockSpent!.args.spender).to.equal(Alice.ethAddress);
|
|
1044
|
+
|
|
1045
|
+
const outputs = zetoLockSpent!.args.outputs;
|
|
1046
|
+
await smtAlice.add(outputs[0], outputs[0]);
|
|
1047
|
+
await smtAlice.add(outputs[1], outputs[1]);
|
|
1048
|
+
await smtBob.add(outputs[0], outputs[0]);
|
|
1049
|
+
await smtBob.add(outputs[1], outputs[1]);
|
|
1050
|
+
|
|
1051
|
+
// Lock is no longer active after spendLock().
|
|
1052
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
1097
1053
|
});
|
|
1098
1054
|
|
|
1099
|
-
it("
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1055
|
+
it("onchain SMT root for the unlocked UTXOs equals the offchain SMT root", async function () {
|
|
1056
|
+
const bobRoot = await smtBob.root();
|
|
1057
|
+
const aliceRoot = await smtAlice.root();
|
|
1058
|
+
const onchainRoot = await zeto.getRoot();
|
|
1059
|
+
expect(bobRoot.string()).to.equal(onchainRoot.toString());
|
|
1060
|
+
expect(aliceRoot.string()).to.equal(onchainRoot.toString());
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
// createLock with a non-zero cancelCommitment, then cancelLock by the
|
|
1065
|
+
// owner without going through delegateLock. Cancelling a lock follows
|
|
1066
|
+
// the same proof-encoding rules as spendLock (locked-input verifier,
|
|
1067
|
+
// bare Commonlib.Proof) but is gated by the cancelCommitment hash.
|
|
1068
|
+
describe("createLock -> cancelLock flow", function () {
|
|
1069
|
+
let lockedUtxo: UTXO;
|
|
1070
|
+
let lockId: string;
|
|
1071
|
+
let cancelHash: string;
|
|
1072
|
+
let outUtxo1: UTXO;
|
|
1073
|
+
let outUtxo2: UTXO;
|
|
1074
|
+
let aliceUtxoForLock: UTXO;
|
|
1075
|
+
let smtForLocked: Merkletree;
|
|
1076
|
+
|
|
1077
|
+
before(async function () {
|
|
1078
|
+
// Mint a fresh source UTXO for Alice so this scope can build its own
|
|
1079
|
+
// lock without colliding with the prior describe block's spend flow.
|
|
1080
|
+
aliceUtxoForLock = newUTXO(50, Alice);
|
|
1081
|
+
await doMint(zeto, deployer, [aliceUtxoForLock]);
|
|
1082
|
+
await smtAlice.add(aliceUtxoForLock.hash, aliceUtxoForLock.hash);
|
|
1083
|
+
await smtBob.add(aliceUtxoForLock.hash, aliceUtxoForLock.hash);
|
|
1084
|
+
const storage = new InMemoryDB(str2Bytes("kyc-locked-2"));
|
|
1085
|
+
smtForLocked = new Merkletree(storage, true, 64);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
it("Alice createLock() with a cancelCommitment", async function () {
|
|
1089
|
+
const nullifier1 = newNullifier(aliceUtxoForLock, Alice);
|
|
1090
|
+
lockedUtxo = newUTXO(aliceUtxoForLock.value!, Alice);
|
|
1091
|
+
const root = await smtAlice.root();
|
|
1092
|
+
const p1 = await smtAlice.generateCircomVerifierProof(
|
|
1093
|
+
aliceUtxoForLock.hash,
|
|
1109
1094
|
root,
|
|
1110
1095
|
);
|
|
1096
|
+
const p2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
1111
1097
|
const merkleProofs = [
|
|
1112
|
-
|
|
1113
|
-
|
|
1098
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
1099
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
1114
1100
|
];
|
|
1115
|
-
// Bob proposes the output UTXOs, attempting to transfer the locked UTXO to Alice
|
|
1116
|
-
// and reusing an unlocked spent UTXO (utxo4)
|
|
1117
|
-
const utxo1 = newUTXO(0, Alice);
|
|
1118
1101
|
|
|
1119
1102
|
const identitiesRoot = await smtKyc.root();
|
|
1120
|
-
const
|
|
1121
|
-
kycHash(Bob.babyJubPublicKey),
|
|
1122
|
-
identitiesRoot,
|
|
1123
|
-
);
|
|
1124
|
-
const proof4 = await smtKyc.generateCircomVerifierProof(
|
|
1103
|
+
const idProofAlice = await smtKyc.generateCircomVerifierProof(
|
|
1125
1104
|
kycHash(Alice.babyJubPublicKey),
|
|
1126
1105
|
identitiesRoot,
|
|
1127
1106
|
);
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1107
|
+
const identityMerkleProofs = [
|
|
1108
|
+
idProofAlice.siblings.map((s) => s.bigInt()),
|
|
1109
|
+
idProofAlice.siblings.map((s) => s.bigInt()),
|
|
1110
|
+
idProofAlice.siblings.map((s) => s.bigInt()),
|
|
1132
1111
|
];
|
|
1133
1112
|
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
[
|
|
1113
|
+
const { encodedProof } = await prepareProof(
|
|
1114
|
+
circuit,
|
|
1115
|
+
provingKey,
|
|
1116
|
+
Alice,
|
|
1117
|
+
[aliceUtxoForLock, ZERO_UTXO],
|
|
1139
1118
|
[nullifier1, ZERO_UTXO],
|
|
1140
|
-
[
|
|
1119
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1141
1120
|
root.bigInt(),
|
|
1142
1121
|
merkleProofs,
|
|
1143
1122
|
identitiesRoot.bigInt(),
|
|
1144
|
-
|
|
1123
|
+
identityMerkleProofs,
|
|
1145
1124
|
[Alice, Alice],
|
|
1146
|
-
Alice.ethAddress, // current lock delegate
|
|
1147
1125
|
);
|
|
1148
|
-
const nullifiers = [nullifier1.hash];
|
|
1149
|
-
|
|
1150
|
-
// Alice (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
|
|
1151
|
-
// using the proof generated by the trade counterparty (Bob in this case)
|
|
1152
|
-
await expect(
|
|
1153
|
-
sendTx(
|
|
1154
|
-
Alice,
|
|
1155
|
-
nullifiers,
|
|
1156
|
-
result.outputCommitments,
|
|
1157
|
-
root.bigInt(),
|
|
1158
|
-
result.encodedProof,
|
|
1159
|
-
true,
|
|
1160
|
-
),
|
|
1161
|
-
).to.be.rejectedWith(`UTXOAlreadyOwned(${utxo10.hash.toString()})`);
|
|
1162
|
-
});
|
|
1163
1126
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1127
|
+
outUtxo1 = newUTXO(20, Bob);
|
|
1128
|
+
outUtxo2 = newUTXO(30, Alice);
|
|
1129
|
+
cancelHash = calculateCancelHash(
|
|
1130
|
+
[lockedUtxo],
|
|
1131
|
+
[],
|
|
1132
|
+
[outUtxo1, outUtxo2],
|
|
1133
|
+
"0x",
|
|
1171
1134
|
);
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1135
|
+
|
|
1136
|
+
const createArgs = encodeCreateArgs({
|
|
1137
|
+
txId: randomBytes32(),
|
|
1138
|
+
inputs: [nullifier1.hash],
|
|
1139
|
+
outputs: [],
|
|
1140
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
1141
|
+
proof: encodeUnlockedProof(root.bigInt(), encodedProof),
|
|
1142
|
+
});
|
|
1143
|
+
lockId = await zeto.connect(Alice.signer).computeLockId(createArgs);
|
|
1144
|
+
|
|
1145
|
+
const tx = await zeto
|
|
1146
|
+
.connect(Alice.signer)
|
|
1147
|
+
.createLock(createArgs, ethers.ZeroHash, cancelHash, "0x");
|
|
1148
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
1149
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
1150
|
+
|
|
1151
|
+
// Mirror the locked UTXO so the cancel-side witness can prove inclusion.
|
|
1152
|
+
await smtForLocked.add(
|
|
1153
|
+
lockedUtxo.hash,
|
|
1154
|
+
ethers.toBigInt(Alice.ethAddress),
|
|
1175
1155
|
);
|
|
1176
|
-
|
|
1177
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
1178
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
1179
|
-
];
|
|
1180
|
-
// Bob proposes the output UTXOs, attempting to transfer the locked UTXO to Alice
|
|
1181
|
-
const utxo1 = newUTXO(10, Alice);
|
|
1182
|
-
const utxo2 = newUTXO(5, Bob);
|
|
1156
|
+
});
|
|
1183
1157
|
|
|
1158
|
+
it("the owner can cancelLock() to reverse the lock without delegation", async function () {
|
|
1159
|
+
const nullifier1 = newNullifier(lockedUtxo, Alice);
|
|
1184
1160
|
const identitiesRoot = await smtKyc.root();
|
|
1185
|
-
const
|
|
1186
|
-
kycHash(
|
|
1161
|
+
const idProofAlice = await smtKyc.generateCircomVerifierProof(
|
|
1162
|
+
kycHash(Alice.babyJubPublicKey),
|
|
1187
1163
|
identitiesRoot,
|
|
1188
1164
|
);
|
|
1189
|
-
const
|
|
1190
|
-
kycHash(
|
|
1165
|
+
const idProofBob = await smtKyc.generateCircomVerifierProof(
|
|
1166
|
+
kycHash(Bob.babyJubPublicKey),
|
|
1191
1167
|
identitiesRoot,
|
|
1192
1168
|
);
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1169
|
+
const identityMerkleProofs = [
|
|
1170
|
+
idProofAlice.siblings.map((s) => s.bigInt()), // sender (Alice)
|
|
1171
|
+
idProofBob.siblings.map((s) => s.bigInt()), // 1st output owner (Bob)
|
|
1172
|
+
idProofAlice.siblings.map((s) => s.bigInt()), // 2nd output owner (Alice)
|
|
1197
1173
|
];
|
|
1198
1174
|
|
|
1199
|
-
const
|
|
1175
|
+
const { encodedProof } = await prepareProof(
|
|
1200
1176
|
circuitForLocked,
|
|
1201
1177
|
provingKeyForLocked,
|
|
1202
|
-
|
|
1203
|
-
[
|
|
1178
|
+
Alice,
|
|
1179
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1204
1180
|
[nullifier1, ZERO_UTXO],
|
|
1205
|
-
[
|
|
1206
|
-
|
|
1207
|
-
|
|
1181
|
+
[outUtxo1, outUtxo2],
|
|
1182
|
+
0n,
|
|
1183
|
+
[],
|
|
1208
1184
|
identitiesRoot.bigInt(),
|
|
1209
|
-
|
|
1210
|
-
[
|
|
1211
|
-
|
|
1185
|
+
identityMerkleProofs,
|
|
1186
|
+
[Bob, Alice],
|
|
1187
|
+
undefined,
|
|
1188
|
+
{ transferLocked: true },
|
|
1212
1189
|
);
|
|
1213
|
-
const nullifiers = [nullifier1.hash];
|
|
1214
|
-
|
|
1215
|
-
// Alice (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
|
|
1216
|
-
// using the proof generated by the trade counterparty (Bob in this case)
|
|
1217
|
-
await expect(
|
|
1218
|
-
sendTx(
|
|
1219
|
-
Alice,
|
|
1220
|
-
nullifiers,
|
|
1221
|
-
result.outputCommitments,
|
|
1222
|
-
root.bigInt(),
|
|
1223
|
-
result.encodedProof,
|
|
1224
|
-
true,
|
|
1225
|
-
),
|
|
1226
|
-
).to.be.fulfilled;
|
|
1227
1190
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1191
|
+
const cancelArgs = encodeSpendArgs({
|
|
1192
|
+
txId: randomBytes32(),
|
|
1193
|
+
lockedOutputs: [],
|
|
1194
|
+
outputs: [outUtxo1.hash, outUtxo2.hash],
|
|
1195
|
+
proof: encodeLockedProof(encodedProof),
|
|
1196
|
+
data: "0x",
|
|
1197
|
+
});
|
|
1234
1198
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
});
|
|
1199
|
+
const tx = await zeto
|
|
1200
|
+
.connect(Alice.signer)
|
|
1201
|
+
.cancelLock(lockId, cancelArgs, "0x");
|
|
1202
|
+
const result = await tx.wait();
|
|
1240
1203
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1204
|
+
const parsed = result!.logs
|
|
1205
|
+
.map((l) => {
|
|
1206
|
+
try {
|
|
1207
|
+
return zeto.interface.parseLog(l as any);
|
|
1208
|
+
} catch (_e) {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
})
|
|
1212
|
+
.filter((p) => p !== null) as ReadonlyArray<{
|
|
1213
|
+
name: string;
|
|
1214
|
+
args: any;
|
|
1215
|
+
}>;
|
|
1216
|
+
const cancelled = parsed.find((p) => p.name === "LockCancelled");
|
|
1217
|
+
const zetoCancelled = parsed.find((p) => p.name === "ZetoLockCancelled");
|
|
1218
|
+
expect(cancelled, "LockCancelled event not emitted").to.not.be.undefined;
|
|
1219
|
+
expect(zetoCancelled, "ZetoLockCancelled event not emitted").to.not.be
|
|
1220
|
+
.undefined;
|
|
1221
|
+
|
|
1222
|
+
const outputs = zetoCancelled!.args.outputs;
|
|
1223
|
+
await smtAlice.add(outputs[0], outputs[0]);
|
|
1224
|
+
await smtAlice.add(outputs[1], outputs[1]);
|
|
1225
|
+
await smtBob.add(outputs[0], outputs[0]);
|
|
1226
|
+
await smtBob.add(outputs[1], outputs[1]);
|
|
1227
|
+
|
|
1228
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
1245
1229
|
});
|
|
1246
1230
|
});
|
|
1247
1231
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1232
|
+
// Negative cases for the lock lifecycle. These rely on hardhat's revert
|
|
1233
|
+
// decoding for custom errors and so are gated by the network name.
|
|
1234
|
+
describe("negative cases for the lock lifecycle", function () {
|
|
1235
|
+
if (network.name !== "hardhat") {
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1250
1238
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1239
|
+
// freshLock mints a fresh UTXO for `owner`, locks it, and returns the
|
|
1240
|
+
// metadata so each negative scope can branch off without polluting
|
|
1241
|
+
// siblings. Each call advances both local SMTs.
|
|
1242
|
+
async function freshLock(
|
|
1243
|
+
owner: User,
|
|
1244
|
+
spendCommitment: string = ethers.ZeroHash,
|
|
1245
|
+
cancelCommitment: string = ethers.ZeroHash,
|
|
1246
|
+
): Promise<{
|
|
1247
|
+
lockId: string;
|
|
1248
|
+
sourceUtxo: UTXO;
|
|
1249
|
+
lockedUtxo: UTXO;
|
|
1250
|
+
createArgs: string;
|
|
1251
|
+
}> {
|
|
1252
|
+
const sourceUtxo = newUTXO(7, owner);
|
|
1253
|
+
await doMint(zeto, deployer, [sourceUtxo]);
|
|
1254
|
+
await smtAlice.add(sourceUtxo.hash, sourceUtxo.hash);
|
|
1255
|
+
await smtBob.add(sourceUtxo.hash, sourceUtxo.hash);
|
|
1256
|
+
|
|
1257
|
+
const nullifier = newNullifier(sourceUtxo, owner);
|
|
1258
|
+
const lockedUtxo = newUTXO(sourceUtxo.value!, owner);
|
|
1259
|
+
const root = await smtBob.root();
|
|
1260
|
+
const p1 = await smtBob.generateCircomVerifierProof(
|
|
1261
|
+
sourceUtxo.hash,
|
|
1257
1262
|
root,
|
|
1258
1263
|
);
|
|
1259
|
-
const
|
|
1264
|
+
const p2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
1260
1265
|
const merkleProofs = [
|
|
1261
|
-
|
|
1262
|
-
|
|
1266
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
1267
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
1263
1268
|
];
|
|
1264
1269
|
|
|
1265
1270
|
const identitiesRoot = await smtKyc.root();
|
|
1266
|
-
const
|
|
1267
|
-
kycHash(
|
|
1271
|
+
const idProofOwner = await smtKyc.generateCircomVerifierProof(
|
|
1272
|
+
kycHash(owner.babyJubPublicKey),
|
|
1268
1273
|
identitiesRoot,
|
|
1269
1274
|
);
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1275
|
+
const identityMerkleProofs = [
|
|
1276
|
+
idProofOwner.siblings.map((s) => s.bigInt()),
|
|
1277
|
+
idProofOwner.siblings.map((s) => s.bigInt()),
|
|
1278
|
+
idProofOwner.siblings.map((s) => s.bigInt()),
|
|
1274
1279
|
];
|
|
1275
1280
|
|
|
1276
|
-
const {
|
|
1281
|
+
const { encodedProof } = await prepareProof(
|
|
1277
1282
|
circuit,
|
|
1278
1283
|
provingKey,
|
|
1279
|
-
|
|
1280
|
-
[
|
|
1281
|
-
[
|
|
1282
|
-
[
|
|
1284
|
+
owner,
|
|
1285
|
+
[sourceUtxo, ZERO_UTXO],
|
|
1286
|
+
[nullifier, ZERO_UTXO],
|
|
1287
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1283
1288
|
root.bigInt(),
|
|
1284
1289
|
merkleProofs,
|
|
1285
1290
|
identitiesRoot.bigInt(),
|
|
1286
|
-
|
|
1287
|
-
[
|
|
1291
|
+
identityMerkleProofs,
|
|
1292
|
+
[owner, owner],
|
|
1288
1293
|
);
|
|
1289
1294
|
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
[],
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
encodedProof,
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1295
|
+
const createArgs = encodeCreateArgs({
|
|
1296
|
+
txId: randomBytes32(),
|
|
1297
|
+
inputs: [nullifier.hash],
|
|
1298
|
+
outputs: [],
|
|
1299
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
1300
|
+
proof: encodeUnlockedProof(root.bigInt(), encodedProof),
|
|
1301
|
+
});
|
|
1302
|
+
const lockId = await zeto
|
|
1303
|
+
.connect(owner.signer)
|
|
1304
|
+
.computeLockId(createArgs);
|
|
1305
|
+
await (
|
|
1306
|
+
await zeto
|
|
1307
|
+
.connect(owner.signer)
|
|
1308
|
+
.createLock(createArgs, spendCommitment, cancelCommitment, "0x")
|
|
1309
|
+
).wait();
|
|
1310
|
+
return { lockId, sourceUtxo, lockedUtxo, createArgs };
|
|
1311
|
+
}
|
|
1300
1312
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1313
|
+
// Authorization / immutability assertions short-circuit before the
|
|
1314
|
+
// proof is ever touched, so we can pass a syntactically valid but
|
|
1315
|
+
// ZK-vacuous spend payload.
|
|
1316
|
+
function dummySpendArgs(): string {
|
|
1317
|
+
return encodeSpendArgs({
|
|
1318
|
+
txId: randomBytes32(),
|
|
1319
|
+
lockedOutputs: [],
|
|
1320
|
+
outputs: [],
|
|
1321
|
+
proof: "0x",
|
|
1322
|
+
data: "0x",
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
it("createLock() with a duplicate txId from the same caller reverts with DuplicateLock", async function () {
|
|
1327
|
+
const { lockId, createArgs } = await freshLock(Bob);
|
|
1328
|
+
|
|
1329
|
+
await expect(
|
|
1330
|
+
zeto
|
|
1331
|
+
.connect(Bob.signer)
|
|
1332
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x"),
|
|
1333
|
+
).rejectedWith(`DuplicateLock("${lockId}")`);
|
|
1308
1334
|
});
|
|
1309
1335
|
|
|
1310
|
-
it("
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1336
|
+
it("updateLock() by a non-owner reverts with LockUnauthorized", async function () {
|
|
1337
|
+
const { lockId } = await freshLock(Bob);
|
|
1338
|
+
|
|
1339
|
+
await expect(
|
|
1340
|
+
zeto
|
|
1341
|
+
.connect(Alice.signer)
|
|
1342
|
+
.updateLock(
|
|
1343
|
+
lockId,
|
|
1344
|
+
encodeUpdateArgs(randomBytes32()),
|
|
1345
|
+
ethers.ZeroHash,
|
|
1346
|
+
ethers.ZeroHash,
|
|
1347
|
+
"0x",
|
|
1348
|
+
),
|
|
1349
|
+
).to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1350
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1321
1351
|
});
|
|
1322
1352
|
|
|
1323
|
-
it("
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
|
|
1353
|
+
it("updateLock() after delegateLock() reverts with LockImmutable", async function () {
|
|
1354
|
+
const { lockId } = await freshLock(Bob);
|
|
1355
|
+
|
|
1356
|
+
await (
|
|
1357
|
+
await zeto
|
|
1358
|
+
.connect(Bob.signer)
|
|
1359
|
+
.delegateLock(
|
|
1360
|
+
lockId,
|
|
1361
|
+
encodeDelegateArgs(randomBytes32()),
|
|
1362
|
+
Alice.ethAddress,
|
|
1363
|
+
"0x",
|
|
1364
|
+
)
|
|
1365
|
+
).wait();
|
|
1366
|
+
|
|
1367
|
+
await expect(
|
|
1368
|
+
zeto
|
|
1369
|
+
.connect(Bob.signer)
|
|
1370
|
+
.updateLock(
|
|
1371
|
+
lockId,
|
|
1372
|
+
encodeUpdateArgs(randomBytes32()),
|
|
1373
|
+
ethers.ZeroHash,
|
|
1374
|
+
ethers.ZeroHash,
|
|
1375
|
+
"0x",
|
|
1376
|
+
),
|
|
1377
|
+
).rejectedWith(`LockImmutable("${lockId}")`);
|
|
1327
1378
|
});
|
|
1328
1379
|
|
|
1329
|
-
it("
|
|
1330
|
-
|
|
1331
|
-
const nullifier1 = newNullifier(lockedUtxo2, Alice);
|
|
1332
|
-
// borrowing the SMT from Bob to save test code duplication
|
|
1333
|
-
const root = await smtBobForLocked.root();
|
|
1334
|
-
const proof1 = await smtBobForLocked.generateCircomVerifierProof(
|
|
1335
|
-
lockedUtxo2.hash,
|
|
1336
|
-
root,
|
|
1337
|
-
);
|
|
1338
|
-
const proof2 = await smtBobForLocked.generateCircomVerifierProof(
|
|
1339
|
-
0n,
|
|
1340
|
-
root,
|
|
1341
|
-
);
|
|
1342
|
-
const merkleProofs = [
|
|
1343
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
1344
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
1345
|
-
];
|
|
1346
|
-
// Bob proposes the output UTXOs, attempting to transfer the locked UTXO to Alice
|
|
1347
|
-
const utxo1 = newUTXO(1, Bob);
|
|
1348
|
-
const utxo2 = newUTXO(9, Alice);
|
|
1380
|
+
it("delegateLock() by a non-spender reverts with LockUnauthorized", async function () {
|
|
1381
|
+
const { lockId } = await freshLock(Bob);
|
|
1349
1382
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
];
|
|
1383
|
+
await expect(
|
|
1384
|
+
zeto
|
|
1385
|
+
.connect(Alice.signer)
|
|
1386
|
+
.delegateLock(
|
|
1387
|
+
lockId,
|
|
1388
|
+
encodeDelegateArgs(randomBytes32()),
|
|
1389
|
+
Charlie.ethAddress,
|
|
1390
|
+
"0x",
|
|
1391
|
+
),
|
|
1392
|
+
)
|
|
1393
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1394
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1395
|
+
});
|
|
1364
1396
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
provingKeyForLocked,
|
|
1368
|
-
Alice,
|
|
1369
|
-
[lockedUtxo2, ZERO_UTXO],
|
|
1370
|
-
[nullifier1, ZERO_UTXO],
|
|
1371
|
-
[utxo1, utxo2],
|
|
1372
|
-
root.bigInt(),
|
|
1373
|
-
merkleProofs,
|
|
1374
|
-
identitiesRoot.bigInt(),
|
|
1375
|
-
identitiesMerkleProofs,
|
|
1376
|
-
[Bob, Alice],
|
|
1377
|
-
Charlie.ethAddress, // current lock delegate
|
|
1378
|
-
);
|
|
1379
|
-
const nullifiers = [nullifier1.hash];
|
|
1397
|
+
it("spendLock() by a non-spender reverts with LockUnauthorized before touching the proof", async function () {
|
|
1398
|
+
const { lockId } = await freshLock(Bob);
|
|
1380
1399
|
|
|
1381
|
-
// Charlie (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
|
|
1382
|
-
// using the proof generated by the trade counterparty (Bob in this case)
|
|
1383
1400
|
await expect(
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
).to.be.fulfilled;
|
|
1401
|
+
zeto.connect(Alice.signer).spendLock(lockId, dummySpendArgs(), "0x"),
|
|
1402
|
+
)
|
|
1403
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1404
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
it("cancelLock() by a non-spender reverts with LockUnauthorized", async function () {
|
|
1408
|
+
const { lockId } = await freshLock(Bob);
|
|
1393
1409
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1410
|
+
await expect(
|
|
1411
|
+
zeto.connect(Alice.signer).cancelLock(lockId, dummySpendArgs(), "0x"),
|
|
1412
|
+
)
|
|
1413
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1414
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1399
1415
|
});
|
|
1400
1416
|
});
|
|
1401
1417
|
});
|
|
@@ -1443,8 +1459,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1443
1459
|
10,
|
|
1444
1460
|
nullifiers,
|
|
1445
1461
|
outputCommitments[0],
|
|
1446
|
-
root.bigInt(),
|
|
1447
|
-
encodedProof,
|
|
1462
|
+
encodeToBytesForWithdraw(root.bigInt(), encodedProof),
|
|
1448
1463
|
"0x",
|
|
1449
1464
|
),
|
|
1450
1465
|
).rejectedWith("UTXOAlreadySpent");
|
|
@@ -1692,7 +1707,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1692
1707
|
identitiesRoot: BigInt,
|
|
1693
1708
|
identitiesMerkleProof: BigInt[][],
|
|
1694
1709
|
owners: User[],
|
|
1695
|
-
lockDelegate?: User,
|
|
1696
1710
|
) {
|
|
1697
1711
|
let nullifiers: BigNumberish[];
|
|
1698
1712
|
let outputCommitments: BigNumberish[];
|
|
@@ -1704,11 +1718,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1704
1718
|
circuitToUse = batchCircuit;
|
|
1705
1719
|
provingKeyToUse = batchProvingKey;
|
|
1706
1720
|
}
|
|
1707
|
-
if (lockDelegate) {
|
|
1708
|
-
// if there is a lock delegate, we use the circuit for locked UTXOs
|
|
1709
|
-
circuitToUse = circuitForLocked;
|
|
1710
|
-
provingKeyToUse = provingKeyForLocked;
|
|
1711
|
-
}
|
|
1712
1721
|
const result = await prepareProof(
|
|
1713
1722
|
circuitToUse,
|
|
1714
1723
|
provingKeyToUse,
|
|
@@ -1721,7 +1730,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1721
1730
|
identitiesRoot,
|
|
1722
1731
|
identitiesMerkleProof,
|
|
1723
1732
|
owners,
|
|
1724
|
-
lockDelegate?.ethAddress,
|
|
1725
1733
|
);
|
|
1726
1734
|
nullifiers = _nullifiers.map((nullifier) => nullifier.hash) as [
|
|
1727
1735
|
BigNumberish,
|
|
@@ -1736,7 +1744,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1736
1744
|
outputCommitments,
|
|
1737
1745
|
utxosRoot,
|
|
1738
1746
|
encodedProof,
|
|
1739
|
-
lockDelegate !== undefined,
|
|
1740
1747
|
);
|
|
1741
1748
|
return {
|
|
1742
1749
|
txResult,
|
|
@@ -1756,6 +1763,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1756
1763
|
identitiesMerkleProof: BigInt[][],
|
|
1757
1764
|
owners: User[],
|
|
1758
1765
|
lockDelegate?: string,
|
|
1766
|
+
opts?: { transferLocked?: boolean },
|
|
1759
1767
|
) {
|
|
1760
1768
|
const nullifiers = _nullifiers.map((nullifier) => nullifier.hash) as [
|
|
1761
1769
|
BigNumberish,
|
|
@@ -1774,24 +1782,41 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1774
1782
|
(owner) => owner.babyJubPublicKey,
|
|
1775
1783
|
) as BigNumberish[][];
|
|
1776
1784
|
const startWitnessCalculation = Date.now();
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1785
|
+
|
|
1786
|
+
let inputObj: any;
|
|
1787
|
+
if (opts?.transferLocked) {
|
|
1788
|
+
inputObj = {
|
|
1789
|
+
inputCommitments,
|
|
1790
|
+
inputValues,
|
|
1791
|
+
inputSalts,
|
|
1792
|
+
inputOwnerPrivateKey: signer.formattedPrivateKey,
|
|
1793
|
+
identitiesRoot,
|
|
1794
|
+
identitiesMerkleProof,
|
|
1795
|
+
outputCommitments,
|
|
1796
|
+
outputValues,
|
|
1797
|
+
outputSalts: outputs.map((output) => output.salt || 0n),
|
|
1798
|
+
outputOwnerPublicKeys,
|
|
1799
|
+
};
|
|
1800
|
+
} else {
|
|
1801
|
+
inputObj = {
|
|
1802
|
+
nullifiers,
|
|
1803
|
+
inputCommitments,
|
|
1804
|
+
inputValues,
|
|
1805
|
+
inputSalts,
|
|
1806
|
+
inputOwnerPrivateKey: signer.formattedPrivateKey,
|
|
1807
|
+
utxosRoot,
|
|
1808
|
+
enabled: nullifiers.map((n) => (n !== 0n ? 1 : 0)),
|
|
1809
|
+
utxosMerkleProof,
|
|
1810
|
+
identitiesRoot,
|
|
1811
|
+
identitiesMerkleProof,
|
|
1812
|
+
outputCommitments,
|
|
1813
|
+
outputValues,
|
|
1814
|
+
outputSalts: outputs.map((output) => output.salt || 0n),
|
|
1815
|
+
outputOwnerPublicKeys,
|
|
1816
|
+
};
|
|
1817
|
+
if (lockDelegate) {
|
|
1818
|
+
inputObj["lockDelegate"] = ethers.toBigInt(lockDelegate);
|
|
1819
|
+
}
|
|
1795
1820
|
}
|
|
1796
1821
|
|
|
1797
1822
|
const witness = await circuit.calculateWTNSBin(inputObj, true);
|
|
@@ -1822,27 +1847,14 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1822
1847
|
outputCommitments: BigNumberish[],
|
|
1823
1848
|
root: BigNumberish,
|
|
1824
1849
|
encodedProof: any,
|
|
1825
|
-
isLocked: boolean = false,
|
|
1826
1850
|
) {
|
|
1827
1851
|
const startTx = Date.now();
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
encodedProof,
|
|
1835
|
-
"0x",
|
|
1836
|
-
);
|
|
1837
|
-
} else {
|
|
1838
|
-
tx = await zeto.connect(signer.signer).transferLocked(
|
|
1839
|
-
nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
1840
|
-
outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
1841
|
-
root,
|
|
1842
|
-
encodedProof,
|
|
1843
|
-
"0x",
|
|
1844
|
-
);
|
|
1845
|
-
}
|
|
1852
|
+
const tx = await zeto.connect(signer.signer).transfer(
|
|
1853
|
+
nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
1854
|
+
outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
1855
|
+
encodeToBytes(root, encodedProof),
|
|
1856
|
+
"0x",
|
|
1857
|
+
);
|
|
1846
1858
|
const results: ContractTransactionReceipt | null = await tx.wait();
|
|
1847
1859
|
console.log(
|
|
1848
1860
|
`Time to execute transaction: ${Date.now() - startTx}ms. Gas used: ${results?.gasUsed
|
|
@@ -1851,3 +1863,10 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1851
1863
|
return results;
|
|
1852
1864
|
}
|
|
1853
1865
|
});
|
|
1866
|
+
|
|
1867
|
+
function encodeToBytes(root: any, proof: any) {
|
|
1868
|
+
return new AbiCoder().encode(
|
|
1869
|
+
["uint256 root", "tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
|
|
1870
|
+
[root, proof],
|
|
1871
|
+
);
|
|
1872
|
+
}
|