@lfdecentralizedtrust/zeto-contracts 0.2.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -162
- package/config/eip170.ts +30 -0
- package/contracts/factory.sol +30 -34
- package/contracts/factory_upgradeable.sol +11 -7
- package/contracts/lib/common/util.sol +40 -0
- package/contracts/lib/interfaces/ILockableCapability.sol +318 -0
- package/contracts/lib/interfaces/{izeto.sol → IZeto.sol} +13 -1
- package/contracts/lib/interfaces/{izeto_initializable.sol → IZetoInitializable.sol} +14 -12
- package/contracts/lib/interfaces/{izeto_kyc.sol → IZetoKyc.sol} +3 -3
- package/contracts/lib/interfaces/IZetoLockHooks.sol +42 -0
- package/contracts/lib/interfaces/IZetoLockableCapability.sol +237 -0
- package/contracts/lib/interfaces/IZetoStorage.sol +106 -0
- package/contracts/lib/interfaces/{izeto_lockable.sol → IZetoVerifier.sol} +8 -21
- package/contracts/lib/registry.sol +74 -26
- package/contracts/lib/storage/base.sol +210 -0
- package/contracts/lib/storage/nullifier.sol +166 -0
- package/contracts/lib/zeto_common.sol +277 -28
- package/contracts/lib/zeto_fungible.sol +443 -33
- package/contracts/lib/zeto_fungible_base.sol +69 -0
- package/contracts/lib/zeto_fungible_burn.sol +83 -53
- package/contracts/lib/zeto_fungible_burn_nullifier.sol +115 -72
- package/contracts/lib/zeto_fungible_nullifier.sol +167 -0
- package/contracts/lib/zeto_lockable.sol +219 -0
- package/contracts/lib/zeto_lockable_lib.sol +460 -0
- package/contracts/lib/zeto_lockable_storage.sol +43 -0
- package/contracts/lib/zeto_non_fungible.sol +228 -0
- package/contracts/lib/zeto_non_fungible_base.sol +61 -0
- package/contracts/lib/zeto_non_fungible_nullifier.sol +61 -0
- package/contracts/test/escrow1.sol +90 -34
- package/contracts/test/escrow2.sol +64 -29
- package/contracts/test/qurrency.sol +10 -2
- package/contracts/test/smt.sol +6 -1
- package/contracts/test/tendecimals.sol +14 -12
- package/contracts/verifiers/impl/anon.sol +189 -0
- package/contracts/verifiers/impl/anon_batch.sol +301 -0
- package/contracts/verifiers/impl/anon_enc.sol +266 -0
- package/contracts/verifiers/impl/anon_enc_batch.sol +602 -0
- package/contracts/verifiers/impl/anon_enc_nullifier.sol +287 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_batch.sol +679 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_kyc.sol +294 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_kyc_batch.sol +686 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation.sol +413 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation_batch.sol +1141 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transfer.sol +217 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked.sol +196 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked_batch.sol +308 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transfer_batch.sol +385 -0
- package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer.sol +497 -0
- package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer_batch.sol +1001 -0
- package/contracts/verifiers/{verifier_anon_nullifier_transferLocked.sol → impl/anon_nullifier_transfer.sol} +12 -19
- package/contracts/verifiers/{verifier_anon_nullifier_transferLocked_batch.sol → impl/anon_nullifier_transfer_batch.sol} +44 -51
- package/contracts/verifiers/impl/burn.sol +182 -0
- package/contracts/verifiers/impl/burn_batch.sol +238 -0
- package/contracts/verifiers/impl/burn_nullifier.sol +203 -0
- package/contracts/verifiers/impl/burn_nullifier_batch.sol +315 -0
- package/contracts/verifiers/impl/deposit.sol +182 -0
- package/contracts/verifiers/impl/deposit_kyc.sol +189 -0
- package/contracts/verifiers/impl/nf_anon.sol +175 -0
- package/contracts/verifiers/{verifier_nf_anon_nullifier_transferLocked.sol → impl/nf_anon_nullifier_transfer.sol} +6 -13
- package/contracts/verifiers/impl/withdraw.sol +189 -0
- package/contracts/verifiers/impl/withdraw_batch.sol +245 -0
- package/contracts/verifiers/impl/withdraw_nullifier.sol +210 -0
- package/contracts/verifiers/impl/withdraw_nullifier_batch.sol +322 -0
- package/contracts/verifiers/verifier_anon.sol +31 -186
- package/contracts/verifiers/verifier_anon_batch.sol +31 -298
- package/contracts/verifiers/verifier_anon_enc.sol +31 -263
- package/contracts/verifiers/verifier_anon_enc_batch.sol +31 -599
- package/contracts/verifiers/verifier_anon_enc_nullifier.sol +31 -284
- package/contracts/verifiers/verifier_anon_enc_nullifier_batch.sol +33 -676
- package/contracts/verifiers/verifier_anon_enc_nullifier_kyc.sol +31 -291
- package/contracts/verifiers/verifier_anon_enc_nullifier_kyc_batch.sol +33 -683
- package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation.sol +33 -410
- package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation_batch.sol +33 -1138
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer.sol +33 -214
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked.sol +33 -221
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked_batch.sol +33 -389
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer_batch.sol +33 -382
- package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer.sol +33 -221
- package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer_batch.sol +33 -389
- package/contracts/verifiers/verifier_anon_nullifier_transfer.sol +33 -207
- package/contracts/verifiers/verifier_anon_nullifier_transfer_batch.sol +33 -375
- package/contracts/verifiers/verifier_burn.sol +31 -179
- package/contracts/verifiers/verifier_burn_batch.sol +31 -235
- package/contracts/verifiers/verifier_burn_nullifier.sol +31 -200
- package/contracts/verifiers/verifier_burn_nullifier_batch.sol +31 -312
- package/contracts/verifiers/verifier_deposit.sol +31 -179
- package/contracts/verifiers/verifier_deposit_kyc.sol +34 -0
- package/contracts/verifiers/verifier_nf_anon.sol +31 -172
- package/contracts/verifiers/verifier_nf_anon_nullifier_transfer.sol +33 -179
- package/contracts/verifiers/verifier_withdraw.sol +31 -186
- package/contracts/verifiers/verifier_withdraw_batch.sol +31 -242
- package/contracts/verifiers/verifier_withdraw_nullifier.sol +31 -207
- package/contracts/verifiers/verifier_withdraw_nullifier_batch.sol +33 -319
- package/contracts/zeto_anon.sol +77 -231
- package/contracts/zeto_anon_burnable.sol +56 -12
- package/contracts/zeto_anon_enc.sol +93 -190
- package/contracts/zeto_anon_enc_nullifier.sol +249 -195
- package/contracts/zeto_anon_enc_nullifier_kyc.sol +51 -232
- package/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +231 -238
- package/contracts/zeto_anon_nullifier.sol +164 -298
- package/contracts/zeto_anon_nullifier_burnable.sol +68 -18
- package/contracts/zeto_anon_nullifier_kyc.sol +40 -345
- package/contracts/zeto_anon_nullifier_qurrency.sol +217 -361
- package/contracts/zeto_nf_anon.sol +55 -130
- package/contracts/zeto_nf_anon_nullifier.sol +90 -152
- package/hardhat.config.ts +18 -3
- package/ignition/modules/lib/deps.ts +13 -0
- package/ignition/modules/test/tendecimals.ts +7 -1
- package/ignition/modules/zeto_anon.ts +3 -0
- package/ignition/modules/zeto_anon_burnable.ts +11 -7
- package/ignition/modules/zeto_anon_enc.ts +3 -0
- package/ignition/modules/zeto_anon_enc_nullifier.ts +34 -0
- package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +5 -2
- package/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +3 -0
- package/ignition/modules/zeto_anon_nullifier.ts +31 -4
- package/ignition/modules/zeto_anon_nullifier_burnable.ts +53 -16
- package/ignition/modules/zeto_anon_nullifier_kyc.ts +30 -12
- package/ignition/modules/zeto_anon_nullifier_qurrency.ts +3 -0
- package/ignition/modules/zeto_nf_anon.ts +3 -1
- package/ignition/modules/zeto_nf_anon_nullifier.ts +17 -12
- package/package.json +7 -3
- package/scripts/deploy_cloneable.ts +19 -10
- package/scripts/deploy_upgradeable.ts +9 -5
- package/scripts/lib/zeto_libraries.ts +47 -0
- package/scripts/tokens/Zeto_Anon.ts +6 -3
- package/scripts/tokens/Zeto_AnonBurnable.ts +4 -1
- package/scripts/tokens/Zeto_AnonEnc.ts +15 -3
- package/scripts/tokens/Zeto_AnonEncNullifier.ts +11 -8
- package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +7 -6
- package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifier.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierKyc.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +7 -8
- package/scripts/tokens/Zeto_NfAnon.ts +5 -3
- package/scripts/tokens/Zeto_NfAnonNullifier.ts +7 -7
- package/test/factory.ts +55 -73
- package/test/lib/anon_nullifier_helpers.ts +89 -0
- package/test/lib/anon_zeto_helpers.ts +76 -0
- package/test/lib/deploy.ts +5 -2
- package/test/lib/eip170.ts +23 -0
- package/test/lib/utils.ts +74 -35
- package/test/test/escrow1.ts +185 -58
- package/test/test/escrow2.ts +200 -107
- package/test/test/qurrency.ts +13 -33
- package/test/usdc-shielding.ts +39 -27
- package/test/utils.ts +144 -21
- package/test/zeto_anon.ts +956 -465
- package/test/zeto_anon_enc.ts +881 -143
- package/test/zeto_anon_enc_nullifier.ts +850 -39
- package/test/zeto_anon_enc_nullifier_kyc.ts +123 -182
- package/test/zeto_anon_enc_nullifier_non_repudiation.ts +80 -47
- package/test/zeto_anon_nullifier.ts +1212 -954
- package/test/zeto_anon_nullifier_kyc.ts +677 -656
- package/test/zeto_anon_nullifier_qurrency.ts +455 -307
- package/test/zeto_nf_anon.ts +737 -138
- package/test/zeto_nf_anon_nullifier.ts +876 -247
- package/contracts/lib/zeto_base.sol +0 -293
- package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
- package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
- package/contracts/lib/zeto_nullifier.sol +0 -340
- package/contracts/zkDvP.sol_ +0 -273
- package/test/zkDvP.ts_ +0 -455
- /package/contracts/lib/{common.sol → common/common.sol} +0 -0
|
@@ -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,16 +35,92 @@ 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
|
|
|
42
|
-
|
|
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
|
+
|
|
121
|
+
describe(
|
|
122
|
+
"Zeto based fungible token with anonymity, KYC, using nullifiers without encryption",
|
|
123
|
+
function () {
|
|
43
124
|
let deployer: Signer;
|
|
44
125
|
let Alice: User;
|
|
45
126
|
let Bob: User;
|
|
@@ -79,11 +160,17 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
79
160
|
|
|
80
161
|
({ deployer, zeto, erc20 } = await deployZeto("Zeto_AnonNullifierKyc"));
|
|
81
162
|
|
|
82
|
-
const tx2 = await zeto
|
|
163
|
+
const tx2 = await zeto
|
|
164
|
+
.connect(deployer)
|
|
165
|
+
.register(Alice.babyJubPublicKey, "0x");
|
|
83
166
|
const result1 = await tx2.wait();
|
|
84
|
-
const tx3 = await zeto
|
|
167
|
+
const tx3 = await zeto
|
|
168
|
+
.connect(deployer)
|
|
169
|
+
.register(Bob.babyJubPublicKey, "0x");
|
|
85
170
|
const result2 = await tx3.wait();
|
|
86
|
-
const tx4 = await zeto
|
|
171
|
+
const tx4 = await zeto
|
|
172
|
+
.connect(deployer)
|
|
173
|
+
.register(Charlie.babyJubPublicKey, "0x");
|
|
87
174
|
const result3 = await tx4.wait();
|
|
88
175
|
|
|
89
176
|
const storage1 = new InMemoryDB(str2Bytes("alice"));
|
|
@@ -106,7 +193,9 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
106
193
|
await smtKyc.add(kycHash(publicKey3), kycHash(publicKey3));
|
|
107
194
|
|
|
108
195
|
circuit = await loadCircuit("anon_nullifier_kyc_transfer");
|
|
109
|
-
({ provingKeyFile: provingKey } = loadProvingKeys(
|
|
196
|
+
({ provingKeyFile: provingKey } = loadProvingKeys(
|
|
197
|
+
"anon_nullifier_kyc_transfer",
|
|
198
|
+
));
|
|
110
199
|
batchCircuit = await loadCircuit("anon_nullifier_kyc_transfer_batch");
|
|
111
200
|
({ provingKeyFile: batchProvingKey } = loadProvingKeys(
|
|
112
201
|
"anon_nullifier_kyc_transfer_batch",
|
|
@@ -237,11 +326,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
237
326
|
await smtUnregistered.add(incomingUTXOs[i], incomingUTXOs[i]);
|
|
238
327
|
}
|
|
239
328
|
|
|
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
329
|
// mint sufficient balance in Zeto contract address for Alice to withdraw
|
|
246
330
|
const mintTx = await erc20.connect(deployer).mint(zeto, 3);
|
|
247
331
|
await mintTx.wait();
|
|
@@ -294,8 +378,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
294
378
|
3,
|
|
295
379
|
_withdrawNullifiers,
|
|
296
380
|
withdrawCommitments[0],
|
|
297
|
-
root.bigInt(),
|
|
298
|
-
withdrawEncodedProof,
|
|
381
|
+
encodeToBytesForWithdraw(root.bigInt(), withdrawEncodedProof),
|
|
299
382
|
"0x",
|
|
300
383
|
);
|
|
301
384
|
await tx.wait();
|
|
@@ -303,7 +386,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
303
386
|
// Alice checks her ERC20 balance
|
|
304
387
|
const endingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
305
388
|
expect(endingBalance - startingBalance).to.be.equal(3);
|
|
306
|
-
}).timeout(
|
|
389
|
+
}).timeout(180000);
|
|
307
390
|
|
|
308
391
|
it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () {
|
|
309
392
|
const startingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
@@ -317,13 +400,29 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
317
400
|
|
|
318
401
|
utxo100 = newUTXO(100, Alice);
|
|
319
402
|
utxo0 = newUTXO(0, Alice);
|
|
320
|
-
const
|
|
403
|
+
const identitiesRoot = await smtKyc.root();
|
|
404
|
+
const proof3 = await smtKyc.generateCircomVerifierProof(
|
|
405
|
+
kycHash(Alice.babyJubPublicKey),
|
|
406
|
+
identitiesRoot,
|
|
407
|
+
);
|
|
408
|
+
const identitiesMerkleProofs = [
|
|
409
|
+
proof3.siblings.map((s) => s.bigInt()),
|
|
410
|
+
proof3.siblings.map((s) => s.bigInt()),
|
|
411
|
+
];
|
|
412
|
+
const { outputCommitments, encodedProof } = await prepareDepositKycProof(
|
|
321
413
|
Alice,
|
|
322
414
|
[utxo100, utxo0],
|
|
415
|
+
identitiesRoot.bigInt(),
|
|
416
|
+
identitiesMerkleProofs,
|
|
323
417
|
);
|
|
324
418
|
const tx2 = await zeto
|
|
325
419
|
.connect(Alice.signer)
|
|
326
|
-
.deposit(
|
|
420
|
+
.deposit(
|
|
421
|
+
100,
|
|
422
|
+
outputCommitments,
|
|
423
|
+
encodeToBytesForDeposit(encodedProof),
|
|
424
|
+
"0x",
|
|
425
|
+
);
|
|
327
426
|
await tx2.wait();
|
|
328
427
|
|
|
329
428
|
await smtAlice.add(utxo100.hash, utxo100.hash);
|
|
@@ -541,8 +640,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
541
640
|
80,
|
|
542
641
|
nullifiers,
|
|
543
642
|
outputCommitments[0],
|
|
544
|
-
root.bigInt(),
|
|
545
|
-
encodedProof,
|
|
643
|
+
encodeToBytesForWithdraw(root.bigInt(), encodedProof),
|
|
546
644
|
"0x",
|
|
547
645
|
);
|
|
548
646
|
await tx.wait();
|
|
@@ -578,7 +676,17 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
578
676
|
);
|
|
579
677
|
});
|
|
580
678
|
|
|
581
|
-
it("deposit by an unregistered user should
|
|
679
|
+
it("deposit by an unregistered user should fail", async function () {
|
|
680
|
+
const identitiesRoot = await smtKycUnregistered.root();
|
|
681
|
+
const proof3 = await smtKycUnregistered.generateCircomVerifierProof(
|
|
682
|
+
kycHash(unregistered.babyJubPublicKey),
|
|
683
|
+
identitiesRoot,
|
|
684
|
+
);
|
|
685
|
+
const identitiesMerkleProofs = [
|
|
686
|
+
proof3.siblings.map((s) => s.bigInt()),
|
|
687
|
+
proof3.siblings.map((s) => s.bigInt()),
|
|
688
|
+
];
|
|
689
|
+
|
|
582
690
|
const tx = await erc20
|
|
583
691
|
.connect(deployer)
|
|
584
692
|
.mint(unregistered.ethAddress, 100);
|
|
@@ -590,160 +698,32 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
590
698
|
|
|
591
699
|
unregisteredUtxo100 = newUTXO(100, unregistered);
|
|
592
700
|
unregisteredUtxo0 = newUTXO(0, unregistered);
|
|
593
|
-
const { outputCommitments, encodedProof } = await
|
|
701
|
+
const { outputCommitments, encodedProof } = await prepareDepositKycProof(
|
|
594
702
|
unregistered,
|
|
595
703
|
[unregisteredUtxo100, unregisteredUtxo0],
|
|
704
|
+
identitiesRoot.bigInt(),
|
|
705
|
+
identitiesMerkleProofs,
|
|
596
706
|
);
|
|
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
707
|
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);
|
|
708
|
+
zeto
|
|
709
|
+
.connect(unregistered.signer)
|
|
710
|
+
.deposit(
|
|
711
|
+
100,
|
|
712
|
+
outputCommitments,
|
|
713
|
+
encodeToBytesForDeposit(encodedProof),
|
|
714
|
+
"0x",
|
|
715
|
+
),
|
|
716
|
+
).to.be.rejectedWith("VM Exception while processing transaction: reverted with custom error 'InvalidProof()'");
|
|
732
717
|
});
|
|
733
718
|
});
|
|
734
719
|
|
|
735
|
-
describe("
|
|
736
|
-
let lockedUtxo1: UTXO;
|
|
737
|
-
let smtBobForLocked: Merkletree;
|
|
720
|
+
describe("ILockableCapability tests", function () {
|
|
738
721
|
let utxo9: UTXO;
|
|
739
722
|
let utxo10: UTXO;
|
|
740
723
|
let utxo11: UTXO;
|
|
741
724
|
let utxo12: UTXO;
|
|
742
725
|
|
|
743
726
|
before(async function () {
|
|
744
|
-
const storage1 = new InMemoryDB(str2Bytes(""));
|
|
745
|
-
smtBobForLocked = new Merkletree(storage1, true, 64);
|
|
746
|
-
|
|
747
727
|
// mint UTXOs for Alice and spend one (to use it in a failure case test)
|
|
748
728
|
utxo9 = newUTXO(10, Alice);
|
|
749
729
|
utxo10 = newUTXO(15, Alice);
|
|
@@ -775,7 +755,10 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
775
755
|
utxo12 = newUTXO(0, Alice);
|
|
776
756
|
|
|
777
757
|
let root = await smtAlice.root();
|
|
778
|
-
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
758
|
+
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
759
|
+
utxo10.hash,
|
|
760
|
+
root,
|
|
761
|
+
);
|
|
779
762
|
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
780
763
|
const merkleProofs = [
|
|
781
764
|
proof1.siblings.map((s) => s.bigInt()),
|
|
@@ -819,34 +802,51 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
819
802
|
await smtBob.add(utxo12.hash, utxo12.hash);
|
|
820
803
|
});
|
|
821
804
|
|
|
822
|
-
|
|
823
|
-
|
|
805
|
+
// createLock -> updateLock -> delegateLock -> spendLock happy path. Note
|
|
806
|
+
// that for KYC the create-lock proof is the standard transfer proof
|
|
807
|
+
// (validates the SMT root and identities tree); the contract transitions
|
|
808
|
+
// the locked output into a flat per-lock mapping. The spendLock proof
|
|
809
|
+
// uses the dedicated transferLocked circuit / verifier wired through the
|
|
810
|
+
// ignition module's lockVerifier slot.
|
|
811
|
+
describe("createLock -> updateLock -> delegateLock -> spendLock flow", function () {
|
|
812
|
+
let lockedUtxo1: UTXO;
|
|
813
|
+
let lockId: string;
|
|
814
|
+
let outputUtxo1: UTXO;
|
|
815
|
+
let outputUtxo2: UTXO;
|
|
816
|
+
let unlockHash: string;
|
|
817
|
+
// Local SMT mirroring on-chain locked-state bookkeeping used by
|
|
818
|
+
// delegateLock tests (`locked()` / storage mirrors).
|
|
819
|
+
let smtForLocked: Merkletree;
|
|
820
|
+
|
|
821
|
+
before(async function () {
|
|
822
|
+
const storage = new InMemoryDB(str2Bytes("kyc-locked-1"));
|
|
823
|
+
smtForLocked = new Merkletree(storage, true, 64);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it("createLock() with deterministic lockId computed from txId", async function () {
|
|
824
827
|
const nullifier1 = newNullifier(utxo11, Bob);
|
|
825
828
|
lockedUtxo1 = newUTXO(utxo11.value!, Bob);
|
|
826
829
|
const root = await smtBob.root();
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
root,
|
|
830
|
-
);
|
|
831
|
-
const proof2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
830
|
+
const p1 = await smtBob.generateCircomVerifierProof(utxo11.hash, root);
|
|
831
|
+
const p2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
832
832
|
const merkleProofs = [
|
|
833
|
-
|
|
834
|
-
|
|
833
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
834
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
835
835
|
];
|
|
836
836
|
|
|
837
837
|
const identitiesRoot = await smtKyc.root();
|
|
838
|
-
const
|
|
838
|
+
const idProofBob = await smtKyc.generateCircomVerifierProof(
|
|
839
839
|
kycHash(Bob.babyJubPublicKey),
|
|
840
840
|
identitiesRoot,
|
|
841
841
|
);
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
842
|
+
const identityMerkleProofs = [
|
|
843
|
+
idProofBob.siblings.map((s) => s.bigInt()), // sender (Bob)
|
|
844
|
+
idProofBob.siblings.map((s) => s.bigInt()), // 1st output owner (Bob)
|
|
845
|
+
idProofBob.siblings.map((s) => s.bigInt()), // 2nd output owner (dummy)
|
|
846
846
|
];
|
|
847
847
|
|
|
848
|
-
const {
|
|
849
|
-
circuit,
|
|
848
|
+
const { encodedProof } = await prepareProof(
|
|
849
|
+
circuit,
|
|
850
850
|
provingKey,
|
|
851
851
|
Bob,
|
|
852
852
|
[utxo11, ZERO_UTXO],
|
|
@@ -855,547 +855,565 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
855
855
|
root.bigInt(),
|
|
856
856
|
merkleProofs,
|
|
857
857
|
identitiesRoot.bigInt(),
|
|
858
|
-
|
|
858
|
+
identityMerkleProofs,
|
|
859
859
|
[Bob, Bob],
|
|
860
860
|
);
|
|
861
861
|
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
862
|
+
const txId = randomBytes32();
|
|
863
|
+
const createArgs = encodeCreateArgs({
|
|
864
|
+
txId,
|
|
865
|
+
inputs: [nullifier1.hash],
|
|
866
|
+
outputs: [],
|
|
867
|
+
lockedOutputs: [lockedUtxo1.hash],
|
|
868
|
+
proof: encodeUnlockedProof(root.bigInt(), encodedProof),
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
// Pre-compute lockId off-chain and confirm against the contract.
|
|
872
|
+
const predicted = await zeto
|
|
873
|
+
.connect(Bob.signer)
|
|
874
|
+
.computeLockId(createArgs);
|
|
875
|
+
lockId = predicted;
|
|
872
876
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
await
|
|
877
|
-
|
|
878
|
-
|
|
877
|
+
const tx = await zeto
|
|
878
|
+
.connect(Bob.signer)
|
|
879
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x");
|
|
880
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
881
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
882
|
+
|
|
883
|
+
const created = result!.logs
|
|
884
|
+
.map((l) => {
|
|
885
|
+
try {
|
|
886
|
+
return zeto.interface.parseLog(l as any);
|
|
887
|
+
} catch (_e) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
})
|
|
891
|
+
.find((p) => p && p.name === "LockCreated");
|
|
892
|
+
expect(created, "LockCreated event not found").to.not.be.null;
|
|
893
|
+
expect(created!.args.lockId).to.equal(predicted);
|
|
894
|
+
expect(created!.args.owner).to.equal(Bob.ethAddress);
|
|
895
|
+
expect(created!.args.spender).to.equal(Bob.ethAddress);
|
|
896
|
+
|
|
897
|
+
// Mirror the locked UTXO into the local SMT so the locked-circuit
|
|
898
|
+
// witness has a valid inclusion proof to consume.
|
|
899
|
+
await smtForLocked.add(
|
|
900
|
+
lockedUtxo1.hash,
|
|
901
|
+
ethers.toBigInt(Bob.ethAddress),
|
|
879
902
|
);
|
|
880
903
|
});
|
|
881
904
|
|
|
882
|
-
it("
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
expect(
|
|
905
|
+
it("isLockActive() and getLock() reflect the newly created lock", async function () {
|
|
906
|
+
expect(await zeto.isLockActive(lockId)).to.equal(true);
|
|
907
|
+
const info = await zeto.getLock(lockId);
|
|
908
|
+
expect(info.owner).to.equal(Bob.ethAddress);
|
|
909
|
+
expect(info.spender).to.equal(Bob.ethAddress);
|
|
910
|
+
expect(info.spendCommitment).to.equal(ethers.ZeroHash);
|
|
911
|
+
expect(info.cancelCommitment).to.equal(ethers.ZeroHash);
|
|
886
912
|
});
|
|
887
913
|
|
|
888
|
-
it("
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
914
|
+
it("locked() returns true for locked UTXOs and false for unlocked or spent UTXOs", async function () {
|
|
915
|
+
expect(await zeto.locked(lockedUtxo1.hash)).to.deep.equal([
|
|
916
|
+
true,
|
|
917
|
+
Bob.ethAddress,
|
|
918
|
+
]);
|
|
919
|
+
// utxo12 is an unlocked UTXO that still belongs to Alice.
|
|
920
|
+
expect((await zeto.locked(utxo12.hash))[0]).to.be.false;
|
|
921
|
+
});
|
|
892
922
|
|
|
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
|
-
];
|
|
923
|
+
it("updateLock() commits the spend hash while owner == spender", async function () {
|
|
924
|
+
outputUtxo1 = newUTXO(10, Alice);
|
|
925
|
+
outputUtxo2 = newUTXO(5, Bob);
|
|
910
926
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
927
|
+
unlockHash = calculateSpendHash(
|
|
928
|
+
[lockedUtxo1],
|
|
929
|
+
[],
|
|
930
|
+
[outputUtxo1, outputUtxo2],
|
|
931
|
+
"0x",
|
|
915
932
|
);
|
|
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
933
|
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
[Bob, Bob],
|
|
934
|
-
Alice.ethAddress,
|
|
935
|
-
);
|
|
934
|
+
const tx = await zeto
|
|
935
|
+
.connect(Bob.signer)
|
|
936
|
+
.updateLock(
|
|
937
|
+
lockId,
|
|
938
|
+
encodeUpdateArgs(randomBytes32()),
|
|
939
|
+
unlockHash,
|
|
940
|
+
ethers.ZeroHash,
|
|
941
|
+
"0x",
|
|
942
|
+
);
|
|
943
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
944
|
+
logger.debug(`updateLock() complete. Gas used: ${result?.gasUsed}`);
|
|
936
945
|
|
|
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");
|
|
946
|
+
const info = await zeto.getLock(lockId);
|
|
947
|
+
expect(info.spendCommitment).to.equal(unlockHash);
|
|
950
948
|
});
|
|
951
949
|
|
|
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
|
-
});
|
|
950
|
+
it("delegateLock() transfers spending authority to Alice", async function () {
|
|
951
|
+
const tx = await zeto
|
|
952
|
+
.connect(Bob.signer)
|
|
953
|
+
.delegateLock(
|
|
954
|
+
lockId,
|
|
955
|
+
encodeDelegateArgs(randomBytes32()),
|
|
956
|
+
Alice.ethAddress,
|
|
957
|
+
"0x",
|
|
958
|
+
);
|
|
959
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
960
|
+
logger.debug(`delegateLock() complete. Gas used: ${result?.gasUsed}`);
|
|
1002
961
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
962
|
+
const info = await zeto.getLock(lockId);
|
|
963
|
+
expect(info.spender).to.equal(Alice.ethAddress);
|
|
964
|
+
// Storage-layer delegate must follow the spender.
|
|
965
|
+
expect(await zeto.locked(lockedUtxo1.hash)).to.deep.equal([
|
|
966
|
+
true,
|
|
967
|
+
Alice.ethAddress,
|
|
968
|
+
]);
|
|
969
|
+
// Mirror the new delegate into the local locked SMT so the spendLock
|
|
970
|
+
// witness's SMT inclusion proof matches the (utxoHash, delegate) leaf.
|
|
971
|
+
await smtForLocked.update(
|
|
1008
972
|
lockedUtxo1.hash,
|
|
1009
|
-
|
|
1010
|
-
);
|
|
1011
|
-
const proof2 = await smtBobForLocked.generateCircomVerifierProof(
|
|
1012
|
-
0n,
|
|
1013
|
-
root,
|
|
973
|
+
ethers.toBigInt(Alice.ethAddress),
|
|
1014
974
|
);
|
|
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
975
|
});
|
|
1033
976
|
|
|
1034
|
-
it("
|
|
1035
|
-
//
|
|
977
|
+
it("the new spender can spendLock() with the matching payload", async function () {
|
|
978
|
+
// The locked-input proof is generated against the dedicated
|
|
979
|
+
// transferLocked circuit; identity-tree membership for both the
|
|
980
|
+
// (still-original) sender Bob and each output owner is asserted as
|
|
981
|
+
// part of the snark, mirroring the unlocked transfer flow.
|
|
1036
982
|
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
983
|
const identitiesRoot = await smtKyc.root();
|
|
1055
|
-
const
|
|
984
|
+
const idProofBob = await smtKyc.generateCircomVerifierProof(
|
|
1056
985
|
kycHash(Bob.babyJubPublicKey),
|
|
1057
986
|
identitiesRoot,
|
|
1058
987
|
);
|
|
1059
|
-
const
|
|
988
|
+
const idProofAlice = await smtKyc.generateCircomVerifierProof(
|
|
1060
989
|
kycHash(Alice.babyJubPublicKey),
|
|
1061
990
|
identitiesRoot,
|
|
1062
991
|
);
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
992
|
+
const identityMerkleProofs = [
|
|
993
|
+
idProofBob.siblings.map((s) => s.bigInt()), // sender (Bob)
|
|
994
|
+
idProofAlice.siblings.map((s) => s.bigInt()), // 1st output owner (Alice)
|
|
995
|
+
idProofBob.siblings.map((s) => s.bigInt()), // 2nd output owner (Bob)
|
|
1067
996
|
];
|
|
1068
997
|
|
|
1069
|
-
const
|
|
998
|
+
const { encodedProof } = await prepareProof(
|
|
1070
999
|
circuitForLocked,
|
|
1071
1000
|
provingKeyForLocked,
|
|
1072
1001
|
Bob,
|
|
1073
1002
|
[lockedUtxo1, ZERO_UTXO],
|
|
1074
1003
|
[nullifier1, ZERO_UTXO],
|
|
1075
|
-
[
|
|
1076
|
-
|
|
1077
|
-
|
|
1004
|
+
[outputUtxo1, outputUtxo2],
|
|
1005
|
+
0n,
|
|
1006
|
+
[],
|
|
1078
1007
|
identitiesRoot.bigInt(),
|
|
1079
|
-
|
|
1080
|
-
[Alice,
|
|
1081
|
-
|
|
1008
|
+
identityMerkleProofs,
|
|
1009
|
+
[Alice, Bob],
|
|
1010
|
+
undefined,
|
|
1011
|
+
{ transferLocked: true },
|
|
1082
1012
|
);
|
|
1083
|
-
const nullifiers = [nullifier1.hash];
|
|
1084
1013
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
)
|
|
1096
|
-
|
|
1014
|
+
const spendArgs = encodeSpendArgs({
|
|
1015
|
+
txId: randomBytes32(),
|
|
1016
|
+
lockedOutputs: [],
|
|
1017
|
+
outputs: [outputUtxo1.hash, outputUtxo2.hash],
|
|
1018
|
+
proof: encodeLockedProof(encodedProof),
|
|
1019
|
+
data: "0x",
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
const tx = await zeto
|
|
1023
|
+
.connect(Alice.signer)
|
|
1024
|
+
.spendLock(lockId, spendArgs, "0x");
|
|
1025
|
+
const result = await tx.wait();
|
|
1026
|
+
|
|
1027
|
+
const parsed = result!.logs
|
|
1028
|
+
.map((l) => {
|
|
1029
|
+
try {
|
|
1030
|
+
return zeto.interface.parseLog(l as any);
|
|
1031
|
+
} catch (_e) {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
})
|
|
1035
|
+
.filter((p) => p !== null) as ReadonlyArray<{
|
|
1036
|
+
name: string;
|
|
1037
|
+
args: any;
|
|
1038
|
+
}>;
|
|
1039
|
+
const lockSpent = parsed.find((p) => p.name === "LockSpent");
|
|
1040
|
+
const zetoLockSpent = parsed.find((p) => p.name === "ZetoLockSpent");
|
|
1041
|
+
expect(lockSpent, "LockSpent event not emitted").to.not.be.undefined;
|
|
1042
|
+
expect(zetoLockSpent, "ZetoLockSpent event not emitted").to.not.be
|
|
1043
|
+
.undefined;
|
|
1044
|
+
expect(lockSpent!.args.lockId).to.equal(lockId);
|
|
1045
|
+
expect(lockSpent!.args.spender).to.equal(Alice.ethAddress);
|
|
1046
|
+
|
|
1047
|
+
const outputs = zetoLockSpent!.args.outputs;
|
|
1048
|
+
await smtAlice.add(outputs[0], outputs[0]);
|
|
1049
|
+
await smtAlice.add(outputs[1], outputs[1]);
|
|
1050
|
+
await smtBob.add(outputs[0], outputs[0]);
|
|
1051
|
+
await smtBob.add(outputs[1], outputs[1]);
|
|
1052
|
+
|
|
1053
|
+
// Lock is no longer active after spendLock().
|
|
1054
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
1097
1055
|
});
|
|
1098
1056
|
|
|
1099
|
-
it("
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1057
|
+
it("onchain SMT root for the unlocked UTXOs equals the offchain SMT root", async function () {
|
|
1058
|
+
const bobRoot = await smtBob.root();
|
|
1059
|
+
const aliceRoot = await smtAlice.root();
|
|
1060
|
+
const onchainRoot = await zeto.getRoot();
|
|
1061
|
+
expect(bobRoot.string()).to.equal(onchainRoot.toString());
|
|
1062
|
+
expect(aliceRoot.string()).to.equal(onchainRoot.toString());
|
|
1063
|
+
});
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// createLock with a non-zero cancelCommitment, then cancelLock by the
|
|
1067
|
+
// owner without going through delegateLock. Cancelling a lock follows
|
|
1068
|
+
// the same proof-encoding rules as spendLock (locked-input verifier,
|
|
1069
|
+
// bare Commonlib.Proof) but is gated by the cancelCommitment hash.
|
|
1070
|
+
describe("createLock -> cancelLock flow", function () {
|
|
1071
|
+
let lockedUtxo: UTXO;
|
|
1072
|
+
let lockId: string;
|
|
1073
|
+
let cancelHash: string;
|
|
1074
|
+
let outUtxo1: UTXO;
|
|
1075
|
+
let outUtxo2: UTXO;
|
|
1076
|
+
let aliceUtxoForLock: UTXO;
|
|
1077
|
+
let smtForLocked: Merkletree;
|
|
1078
|
+
|
|
1079
|
+
before(async function () {
|
|
1080
|
+
// Mint a fresh source UTXO for Alice so this scope can build its own
|
|
1081
|
+
// lock without colliding with the prior describe block's spend flow.
|
|
1082
|
+
aliceUtxoForLock = newUTXO(50, Alice);
|
|
1083
|
+
await doMint(zeto, deployer, [aliceUtxoForLock]);
|
|
1084
|
+
await smtAlice.add(aliceUtxoForLock.hash, aliceUtxoForLock.hash);
|
|
1085
|
+
await smtBob.add(aliceUtxoForLock.hash, aliceUtxoForLock.hash);
|
|
1086
|
+
const storage = new InMemoryDB(str2Bytes("kyc-locked-2"));
|
|
1087
|
+
smtForLocked = new Merkletree(storage, true, 64);
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it("Alice createLock() with a cancelCommitment", async function () {
|
|
1091
|
+
const nullifier1 = newNullifier(aliceUtxoForLock, Alice);
|
|
1092
|
+
lockedUtxo = newUTXO(aliceUtxoForLock.value!, Alice);
|
|
1093
|
+
const root = await smtAlice.root();
|
|
1094
|
+
const p1 = await smtAlice.generateCircomVerifierProof(
|
|
1095
|
+
aliceUtxoForLock.hash,
|
|
1109
1096
|
root,
|
|
1110
1097
|
);
|
|
1098
|
+
const p2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
1111
1099
|
const merkleProofs = [
|
|
1112
|
-
|
|
1113
|
-
|
|
1100
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
1101
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
1114
1102
|
];
|
|
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
1103
|
|
|
1119
1104
|
const identitiesRoot = await smtKyc.root();
|
|
1120
|
-
const
|
|
1121
|
-
kycHash(Bob.babyJubPublicKey),
|
|
1122
|
-
identitiesRoot,
|
|
1123
|
-
);
|
|
1124
|
-
const proof4 = await smtKyc.generateCircomVerifierProof(
|
|
1105
|
+
const idProofAlice = await smtKyc.generateCircomVerifierProof(
|
|
1125
1106
|
kycHash(Alice.babyJubPublicKey),
|
|
1126
1107
|
identitiesRoot,
|
|
1127
1108
|
);
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1109
|
+
const identityMerkleProofs = [
|
|
1110
|
+
idProofAlice.siblings.map((s) => s.bigInt()),
|
|
1111
|
+
idProofAlice.siblings.map((s) => s.bigInt()),
|
|
1112
|
+
idProofAlice.siblings.map((s) => s.bigInt()),
|
|
1132
1113
|
];
|
|
1133
1114
|
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
[
|
|
1115
|
+
const { encodedProof } = await prepareProof(
|
|
1116
|
+
circuit,
|
|
1117
|
+
provingKey,
|
|
1118
|
+
Alice,
|
|
1119
|
+
[aliceUtxoForLock, ZERO_UTXO],
|
|
1139
1120
|
[nullifier1, ZERO_UTXO],
|
|
1140
|
-
[
|
|
1121
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1141
1122
|
root.bigInt(),
|
|
1142
1123
|
merkleProofs,
|
|
1143
1124
|
identitiesRoot.bigInt(),
|
|
1144
|
-
|
|
1125
|
+
identityMerkleProofs,
|
|
1145
1126
|
[Alice, Alice],
|
|
1146
|
-
Alice.ethAddress, // current lock delegate
|
|
1147
1127
|
);
|
|
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
1128
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1129
|
+
outUtxo1 = newUTXO(20, Bob);
|
|
1130
|
+
outUtxo2 = newUTXO(30, Alice);
|
|
1131
|
+
cancelHash = calculateCancelHash(
|
|
1132
|
+
[lockedUtxo],
|
|
1133
|
+
[],
|
|
1134
|
+
[outUtxo1, outUtxo2],
|
|
1135
|
+
"0x",
|
|
1171
1136
|
);
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1137
|
+
|
|
1138
|
+
const createArgs = encodeCreateArgs({
|
|
1139
|
+
txId: randomBytes32(),
|
|
1140
|
+
inputs: [nullifier1.hash],
|
|
1141
|
+
outputs: [],
|
|
1142
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
1143
|
+
proof: encodeUnlockedProof(root.bigInt(), encodedProof),
|
|
1144
|
+
});
|
|
1145
|
+
lockId = await zeto.connect(Alice.signer).computeLockId(createArgs);
|
|
1146
|
+
|
|
1147
|
+
const tx = await zeto
|
|
1148
|
+
.connect(Alice.signer)
|
|
1149
|
+
.createLock(createArgs, ethers.ZeroHash, cancelHash, "0x");
|
|
1150
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
1151
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
1152
|
+
|
|
1153
|
+
// Mirror the locked UTXO so the cancel-side witness can prove inclusion.
|
|
1154
|
+
await smtForLocked.add(
|
|
1155
|
+
lockedUtxo.hash,
|
|
1156
|
+
ethers.toBigInt(Alice.ethAddress),
|
|
1175
1157
|
);
|
|
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);
|
|
1158
|
+
});
|
|
1183
1159
|
|
|
1160
|
+
it("the owner can cancelLock() to reverse the lock without delegation", async function () {
|
|
1161
|
+
const nullifier1 = newNullifier(lockedUtxo, Alice);
|
|
1184
1162
|
const identitiesRoot = await smtKyc.root();
|
|
1185
|
-
const
|
|
1186
|
-
kycHash(
|
|
1163
|
+
const idProofAlice = await smtKyc.generateCircomVerifierProof(
|
|
1164
|
+
kycHash(Alice.babyJubPublicKey),
|
|
1187
1165
|
identitiesRoot,
|
|
1188
1166
|
);
|
|
1189
|
-
const
|
|
1190
|
-
kycHash(
|
|
1167
|
+
const idProofBob = await smtKyc.generateCircomVerifierProof(
|
|
1168
|
+
kycHash(Bob.babyJubPublicKey),
|
|
1191
1169
|
identitiesRoot,
|
|
1192
1170
|
);
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1171
|
+
const identityMerkleProofs = [
|
|
1172
|
+
idProofAlice.siblings.map((s) => s.bigInt()), // sender (Alice)
|
|
1173
|
+
idProofBob.siblings.map((s) => s.bigInt()), // 1st output owner (Bob)
|
|
1174
|
+
idProofAlice.siblings.map((s) => s.bigInt()), // 2nd output owner (Alice)
|
|
1197
1175
|
];
|
|
1198
1176
|
|
|
1199
|
-
const
|
|
1177
|
+
const { encodedProof } = await prepareProof(
|
|
1200
1178
|
circuitForLocked,
|
|
1201
1179
|
provingKeyForLocked,
|
|
1202
|
-
|
|
1203
|
-
[
|
|
1180
|
+
Alice,
|
|
1181
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1204
1182
|
[nullifier1, ZERO_UTXO],
|
|
1205
|
-
[
|
|
1206
|
-
|
|
1207
|
-
|
|
1183
|
+
[outUtxo1, outUtxo2],
|
|
1184
|
+
0n,
|
|
1185
|
+
[],
|
|
1208
1186
|
identitiesRoot.bigInt(),
|
|
1209
|
-
|
|
1210
|
-
[
|
|
1211
|
-
|
|
1187
|
+
identityMerkleProofs,
|
|
1188
|
+
[Bob, Alice],
|
|
1189
|
+
undefined,
|
|
1190
|
+
{ transferLocked: true },
|
|
1212
1191
|
);
|
|
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
1192
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1193
|
+
const cancelArgs = encodeSpendArgs({
|
|
1194
|
+
txId: randomBytes32(),
|
|
1195
|
+
lockedOutputs: [],
|
|
1196
|
+
outputs: [outUtxo1.hash, outUtxo2.hash],
|
|
1197
|
+
proof: encodeLockedProof(encodedProof),
|
|
1198
|
+
data: "0x",
|
|
1199
|
+
});
|
|
1234
1200
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
});
|
|
1201
|
+
const tx = await zeto
|
|
1202
|
+
.connect(Alice.signer)
|
|
1203
|
+
.cancelLock(lockId, cancelArgs, "0x");
|
|
1204
|
+
const result = await tx.wait();
|
|
1240
1205
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1206
|
+
const parsed = result!.logs
|
|
1207
|
+
.map((l) => {
|
|
1208
|
+
try {
|
|
1209
|
+
return zeto.interface.parseLog(l as any);
|
|
1210
|
+
} catch (_e) {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
})
|
|
1214
|
+
.filter((p) => p !== null) as ReadonlyArray<{
|
|
1215
|
+
name: string;
|
|
1216
|
+
args: any;
|
|
1217
|
+
}>;
|
|
1218
|
+
const cancelled = parsed.find((p) => p.name === "LockCancelled");
|
|
1219
|
+
const zetoCancelled = parsed.find((p) => p.name === "ZetoLockCancelled");
|
|
1220
|
+
expect(cancelled, "LockCancelled event not emitted").to.not.be.undefined;
|
|
1221
|
+
expect(zetoCancelled, "ZetoLockCancelled event not emitted").to.not.be
|
|
1222
|
+
.undefined;
|
|
1223
|
+
|
|
1224
|
+
const outputs = zetoCancelled!.args.outputs;
|
|
1225
|
+
await smtAlice.add(outputs[0], outputs[0]);
|
|
1226
|
+
await smtAlice.add(outputs[1], outputs[1]);
|
|
1227
|
+
await smtBob.add(outputs[0], outputs[0]);
|
|
1228
|
+
await smtBob.add(outputs[1], outputs[1]);
|
|
1229
|
+
|
|
1230
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
1245
1231
|
});
|
|
1246
1232
|
});
|
|
1247
1233
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1234
|
+
// Negative cases for the lock lifecycle. These rely on hardhat's revert
|
|
1235
|
+
// decoding for custom errors and so are gated by the network name.
|
|
1236
|
+
describe("negative cases for the lock lifecycle", function () {
|
|
1237
|
+
if (network.name !== "hardhat") {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1250
1240
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1241
|
+
// freshLock mints a fresh UTXO for `owner`, locks it, and returns the
|
|
1242
|
+
// metadata so each negative scope can branch off without polluting
|
|
1243
|
+
// siblings. Each call advances both local SMTs.
|
|
1244
|
+
async function freshLock(
|
|
1245
|
+
owner: User,
|
|
1246
|
+
spendCommitment: string = ethers.ZeroHash,
|
|
1247
|
+
cancelCommitment: string = ethers.ZeroHash,
|
|
1248
|
+
): Promise<{
|
|
1249
|
+
lockId: string;
|
|
1250
|
+
sourceUtxo: UTXO;
|
|
1251
|
+
lockedUtxo: UTXO;
|
|
1252
|
+
createArgs: string;
|
|
1253
|
+
}> {
|
|
1254
|
+
const sourceUtxo = newUTXO(7, owner);
|
|
1255
|
+
await doMint(zeto, deployer, [sourceUtxo]);
|
|
1256
|
+
await smtAlice.add(sourceUtxo.hash, sourceUtxo.hash);
|
|
1257
|
+
await smtBob.add(sourceUtxo.hash, sourceUtxo.hash);
|
|
1258
|
+
|
|
1259
|
+
const nullifier = newNullifier(sourceUtxo, owner);
|
|
1260
|
+
const lockedUtxo = newUTXO(sourceUtxo.value!, owner);
|
|
1261
|
+
const root = await smtBob.root();
|
|
1262
|
+
const p1 = await smtBob.generateCircomVerifierProof(
|
|
1263
|
+
sourceUtxo.hash,
|
|
1257
1264
|
root,
|
|
1258
1265
|
);
|
|
1259
|
-
const
|
|
1266
|
+
const p2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
1260
1267
|
const merkleProofs = [
|
|
1261
|
-
|
|
1262
|
-
|
|
1268
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
1269
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
1263
1270
|
];
|
|
1264
1271
|
|
|
1265
1272
|
const identitiesRoot = await smtKyc.root();
|
|
1266
|
-
const
|
|
1267
|
-
kycHash(
|
|
1273
|
+
const idProofOwner = await smtKyc.generateCircomVerifierProof(
|
|
1274
|
+
kycHash(owner.babyJubPublicKey),
|
|
1268
1275
|
identitiesRoot,
|
|
1269
1276
|
);
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1277
|
+
const identityMerkleProofs = [
|
|
1278
|
+
idProofOwner.siblings.map((s) => s.bigInt()),
|
|
1279
|
+
idProofOwner.siblings.map((s) => s.bigInt()),
|
|
1280
|
+
idProofOwner.siblings.map((s) => s.bigInt()),
|
|
1274
1281
|
];
|
|
1275
1282
|
|
|
1276
|
-
const {
|
|
1283
|
+
const { encodedProof } = await prepareProof(
|
|
1277
1284
|
circuit,
|
|
1278
1285
|
provingKey,
|
|
1279
|
-
|
|
1280
|
-
[
|
|
1281
|
-
[
|
|
1282
|
-
[
|
|
1286
|
+
owner,
|
|
1287
|
+
[sourceUtxo, ZERO_UTXO],
|
|
1288
|
+
[nullifier, ZERO_UTXO],
|
|
1289
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1283
1290
|
root.bigInt(),
|
|
1284
1291
|
merkleProofs,
|
|
1285
1292
|
identitiesRoot.bigInt(),
|
|
1286
|
-
|
|
1287
|
-
[
|
|
1293
|
+
identityMerkleProofs,
|
|
1294
|
+
[owner, owner],
|
|
1288
1295
|
);
|
|
1289
1296
|
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
[],
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
encodedProof,
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1297
|
+
const createArgs = encodeCreateArgs({
|
|
1298
|
+
txId: randomBytes32(),
|
|
1299
|
+
inputs: [nullifier.hash],
|
|
1300
|
+
outputs: [],
|
|
1301
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
1302
|
+
proof: encodeUnlockedProof(root.bigInt(), encodedProof),
|
|
1303
|
+
});
|
|
1304
|
+
const lockId = await zeto
|
|
1305
|
+
.connect(owner.signer)
|
|
1306
|
+
.computeLockId(createArgs);
|
|
1307
|
+
await (
|
|
1308
|
+
await zeto
|
|
1309
|
+
.connect(owner.signer)
|
|
1310
|
+
.createLock(createArgs, spendCommitment, cancelCommitment, "0x")
|
|
1311
|
+
).wait();
|
|
1312
|
+
return { lockId, sourceUtxo, lockedUtxo, createArgs };
|
|
1313
|
+
}
|
|
1300
1314
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1315
|
+
// Authorization / immutability assertions short-circuit before the
|
|
1316
|
+
// proof is ever touched, so we can pass a syntactically valid but
|
|
1317
|
+
// ZK-vacuous spend payload.
|
|
1318
|
+
function dummySpendArgs(): string {
|
|
1319
|
+
return encodeSpendArgs({
|
|
1320
|
+
txId: randomBytes32(),
|
|
1321
|
+
lockedOutputs: [],
|
|
1322
|
+
outputs: [],
|
|
1323
|
+
proof: "0x",
|
|
1324
|
+
data: "0x",
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
it("createLock() with a duplicate txId from the same caller reverts with DuplicateLock", async function () {
|
|
1329
|
+
const { lockId, createArgs } = await freshLock(Bob);
|
|
1330
|
+
|
|
1331
|
+
await expect(
|
|
1332
|
+
zeto
|
|
1333
|
+
.connect(Bob.signer)
|
|
1334
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x"),
|
|
1335
|
+
).rejectedWith(`DuplicateLock("${lockId}")`);
|
|
1308
1336
|
});
|
|
1309
1337
|
|
|
1310
|
-
it("
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1338
|
+
it("updateLock() by a non-owner reverts with LockUnauthorized", async function () {
|
|
1339
|
+
const { lockId } = await freshLock(Bob);
|
|
1340
|
+
|
|
1341
|
+
await expect(
|
|
1342
|
+
zeto
|
|
1343
|
+
.connect(Alice.signer)
|
|
1344
|
+
.updateLock(
|
|
1345
|
+
lockId,
|
|
1346
|
+
encodeUpdateArgs(randomBytes32()),
|
|
1347
|
+
ethers.ZeroHash,
|
|
1348
|
+
ethers.ZeroHash,
|
|
1349
|
+
"0x",
|
|
1350
|
+
),
|
|
1351
|
+
).to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1352
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1321
1353
|
});
|
|
1322
1354
|
|
|
1323
|
-
it("
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
|
|
1355
|
+
it("updateLock() after delegateLock() reverts with LockImmutable", async function () {
|
|
1356
|
+
const { lockId } = await freshLock(Bob);
|
|
1357
|
+
|
|
1358
|
+
await (
|
|
1359
|
+
await zeto
|
|
1360
|
+
.connect(Bob.signer)
|
|
1361
|
+
.delegateLock(
|
|
1362
|
+
lockId,
|
|
1363
|
+
encodeDelegateArgs(randomBytes32()),
|
|
1364
|
+
Alice.ethAddress,
|
|
1365
|
+
"0x",
|
|
1366
|
+
)
|
|
1367
|
+
).wait();
|
|
1368
|
+
|
|
1369
|
+
await expect(
|
|
1370
|
+
zeto
|
|
1371
|
+
.connect(Bob.signer)
|
|
1372
|
+
.updateLock(
|
|
1373
|
+
lockId,
|
|
1374
|
+
encodeUpdateArgs(randomBytes32()),
|
|
1375
|
+
ethers.ZeroHash,
|
|
1376
|
+
ethers.ZeroHash,
|
|
1377
|
+
"0x",
|
|
1378
|
+
),
|
|
1379
|
+
).rejectedWith(`LockImmutable("${lockId}")`);
|
|
1327
1380
|
});
|
|
1328
1381
|
|
|
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);
|
|
1382
|
+
it("delegateLock() by a non-spender reverts with LockUnauthorized", async function () {
|
|
1383
|
+
const { lockId } = await freshLock(Bob);
|
|
1349
1384
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
];
|
|
1385
|
+
await expect(
|
|
1386
|
+
zeto
|
|
1387
|
+
.connect(Alice.signer)
|
|
1388
|
+
.delegateLock(
|
|
1389
|
+
lockId,
|
|
1390
|
+
encodeDelegateArgs(randomBytes32()),
|
|
1391
|
+
Charlie.ethAddress,
|
|
1392
|
+
"0x",
|
|
1393
|
+
),
|
|
1394
|
+
)
|
|
1395
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1396
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1397
|
+
});
|
|
1364
1398
|
|
|
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];
|
|
1399
|
+
it("spendLock() by a non-spender reverts with LockUnauthorized before touching the proof", async function () {
|
|
1400
|
+
const { lockId } = await freshLock(Bob);
|
|
1380
1401
|
|
|
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
1402
|
await expect(
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
).to.be.fulfilled;
|
|
1403
|
+
zeto.connect(Alice.signer).spendLock(lockId, dummySpendArgs(), "0x"),
|
|
1404
|
+
)
|
|
1405
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1406
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
it("cancelLock() by a non-spender reverts with LockUnauthorized", async function () {
|
|
1410
|
+
const { lockId } = await freshLock(Bob);
|
|
1393
1411
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1412
|
+
await expect(
|
|
1413
|
+
zeto.connect(Alice.signer).cancelLock(lockId, dummySpendArgs(), "0x"),
|
|
1414
|
+
)
|
|
1415
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1416
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1399
1417
|
});
|
|
1400
1418
|
});
|
|
1401
1419
|
});
|
|
@@ -1443,8 +1461,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1443
1461
|
10,
|
|
1444
1462
|
nullifiers,
|
|
1445
1463
|
outputCommitments[0],
|
|
1446
|
-
root.bigInt(),
|
|
1447
|
-
encodedProof,
|
|
1464
|
+
encodeToBytesForWithdraw(root.bigInt(), encodedProof),
|
|
1448
1465
|
"0x",
|
|
1449
1466
|
),
|
|
1450
1467
|
).rejectedWith("UTXOAlreadySpent");
|
|
@@ -1692,7 +1709,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1692
1709
|
identitiesRoot: BigInt,
|
|
1693
1710
|
identitiesMerkleProof: BigInt[][],
|
|
1694
1711
|
owners: User[],
|
|
1695
|
-
lockDelegate?: User,
|
|
1696
1712
|
) {
|
|
1697
1713
|
let nullifiers: BigNumberish[];
|
|
1698
1714
|
let outputCommitments: BigNumberish[];
|
|
@@ -1704,11 +1720,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1704
1720
|
circuitToUse = batchCircuit;
|
|
1705
1721
|
provingKeyToUse = batchProvingKey;
|
|
1706
1722
|
}
|
|
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
1723
|
const result = await prepareProof(
|
|
1713
1724
|
circuitToUse,
|
|
1714
1725
|
provingKeyToUse,
|
|
@@ -1721,7 +1732,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1721
1732
|
identitiesRoot,
|
|
1722
1733
|
identitiesMerkleProof,
|
|
1723
1734
|
owners,
|
|
1724
|
-
lockDelegate?.ethAddress,
|
|
1725
1735
|
);
|
|
1726
1736
|
nullifiers = _nullifiers.map((nullifier) => nullifier.hash) as [
|
|
1727
1737
|
BigNumberish,
|
|
@@ -1736,7 +1746,6 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1736
1746
|
outputCommitments,
|
|
1737
1747
|
utxosRoot,
|
|
1738
1748
|
encodedProof,
|
|
1739
|
-
lockDelegate !== undefined,
|
|
1740
1749
|
);
|
|
1741
1750
|
return {
|
|
1742
1751
|
txResult,
|
|
@@ -1756,6 +1765,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1756
1765
|
identitiesMerkleProof: BigInt[][],
|
|
1757
1766
|
owners: User[],
|
|
1758
1767
|
lockDelegate?: string,
|
|
1768
|
+
opts?: { transferLocked?: boolean },
|
|
1759
1769
|
) {
|
|
1760
1770
|
const nullifiers = _nullifiers.map((nullifier) => nullifier.hash) as [
|
|
1761
1771
|
BigNumberish,
|
|
@@ -1774,24 +1784,41 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1774
1784
|
(owner) => owner.babyJubPublicKey,
|
|
1775
1785
|
) as BigNumberish[][];
|
|
1776
1786
|
const startWitnessCalculation = Date.now();
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1787
|
+
|
|
1788
|
+
let inputObj: any;
|
|
1789
|
+
if (opts?.transferLocked) {
|
|
1790
|
+
inputObj = {
|
|
1791
|
+
inputCommitments,
|
|
1792
|
+
inputValues,
|
|
1793
|
+
inputSalts,
|
|
1794
|
+
inputOwnerPrivateKey: signer.formattedPrivateKey,
|
|
1795
|
+
identitiesRoot,
|
|
1796
|
+
identitiesMerkleProof,
|
|
1797
|
+
outputCommitments,
|
|
1798
|
+
outputValues,
|
|
1799
|
+
outputSalts: outputs.map((output) => output.salt || 0n),
|
|
1800
|
+
outputOwnerPublicKeys,
|
|
1801
|
+
};
|
|
1802
|
+
} else {
|
|
1803
|
+
inputObj = {
|
|
1804
|
+
nullifiers,
|
|
1805
|
+
inputCommitments,
|
|
1806
|
+
inputValues,
|
|
1807
|
+
inputSalts,
|
|
1808
|
+
inputOwnerPrivateKey: signer.formattedPrivateKey,
|
|
1809
|
+
utxosRoot,
|
|
1810
|
+
enabled: nullifiers.map((n) => (n !== 0n ? 1 : 0)),
|
|
1811
|
+
utxosMerkleProof,
|
|
1812
|
+
identitiesRoot,
|
|
1813
|
+
identitiesMerkleProof,
|
|
1814
|
+
outputCommitments,
|
|
1815
|
+
outputValues,
|
|
1816
|
+
outputSalts: outputs.map((output) => output.salt || 0n),
|
|
1817
|
+
outputOwnerPublicKeys,
|
|
1818
|
+
};
|
|
1819
|
+
if (lockDelegate) {
|
|
1820
|
+
inputObj["lockDelegate"] = ethers.toBigInt(lockDelegate);
|
|
1821
|
+
}
|
|
1795
1822
|
}
|
|
1796
1823
|
|
|
1797
1824
|
const witness = await circuit.calculateWTNSBin(inputObj, true);
|
|
@@ -1822,27 +1849,14 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1822
1849
|
outputCommitments: BigNumberish[],
|
|
1823
1850
|
root: BigNumberish,
|
|
1824
1851
|
encodedProof: any,
|
|
1825
|
-
isLocked: boolean = false,
|
|
1826
1852
|
) {
|
|
1827
1853
|
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
|
-
}
|
|
1854
|
+
const tx = await zeto.connect(signer.signer).transfer(
|
|
1855
|
+
nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
1856
|
+
outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
1857
|
+
encodeToBytes(root, encodedProof),
|
|
1858
|
+
"0x",
|
|
1859
|
+
);
|
|
1846
1860
|
const results: ContractTransactionReceipt | null = await tx.wait();
|
|
1847
1861
|
console.log(
|
|
1848
1862
|
`Time to execute transaction: ${Date.now() - startTx}ms. Gas used: ${results?.gasUsed
|
|
@@ -1851,3 +1865,10 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou
|
|
|
1851
1865
|
return results;
|
|
1852
1866
|
}
|
|
1853
1867
|
});
|
|
1868
|
+
|
|
1869
|
+
function encodeToBytes(root: any, proof: any) {
|
|
1870
|
+
return new AbiCoder().encode(
|
|
1871
|
+
["uint256 root", "tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
|
|
1872
|
+
[root, proof],
|
|
1873
|
+
);
|
|
1874
|
+
}
|