@lfdecentralizedtrust/zeto-contracts 0.2.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -162
- package/config/eip170.ts +30 -0
- package/contracts/factory.sol +30 -34
- package/contracts/factory_upgradeable.sol +11 -7
- package/contracts/lib/common/util.sol +40 -0
- package/contracts/lib/interfaces/ILockableCapability.sol +318 -0
- package/contracts/lib/interfaces/{izeto.sol → IZeto.sol} +13 -1
- package/contracts/lib/interfaces/{izeto_initializable.sol → IZetoInitializable.sol} +14 -12
- package/contracts/lib/interfaces/{izeto_kyc.sol → IZetoKyc.sol} +3 -3
- package/contracts/lib/interfaces/IZetoLockHooks.sol +42 -0
- package/contracts/lib/interfaces/IZetoLockableCapability.sol +237 -0
- package/contracts/lib/interfaces/IZetoStorage.sol +106 -0
- package/contracts/lib/interfaces/{izeto_lockable.sol → IZetoVerifier.sol} +8 -21
- package/contracts/lib/registry.sol +74 -26
- package/contracts/lib/storage/base.sol +210 -0
- package/contracts/lib/storage/nullifier.sol +166 -0
- package/contracts/lib/zeto_common.sol +277 -28
- package/contracts/lib/zeto_fungible.sol +443 -33
- package/contracts/lib/zeto_fungible_base.sol +69 -0
- package/contracts/lib/zeto_fungible_burn.sol +83 -53
- package/contracts/lib/zeto_fungible_burn_nullifier.sol +115 -72
- package/contracts/lib/zeto_fungible_nullifier.sol +167 -0
- package/contracts/lib/zeto_lockable.sol +219 -0
- package/contracts/lib/zeto_lockable_lib.sol +460 -0
- package/contracts/lib/zeto_lockable_storage.sol +43 -0
- package/contracts/lib/zeto_non_fungible.sol +228 -0
- package/contracts/lib/zeto_non_fungible_base.sol +61 -0
- package/contracts/lib/zeto_non_fungible_nullifier.sol +61 -0
- package/contracts/test/escrow1.sol +90 -34
- package/contracts/test/escrow2.sol +64 -29
- package/contracts/test/qurrency.sol +10 -2
- package/contracts/test/smt.sol +6 -1
- package/contracts/test/tendecimals.sol +14 -12
- package/contracts/verifiers/impl/anon.sol +189 -0
- package/contracts/verifiers/impl/anon_batch.sol +301 -0
- package/contracts/verifiers/impl/anon_enc.sol +266 -0
- package/contracts/verifiers/impl/anon_enc_batch.sol +602 -0
- package/contracts/verifiers/impl/anon_enc_nullifier.sol +287 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_batch.sol +679 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_kyc.sol +294 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_kyc_batch.sol +686 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation.sol +413 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation_batch.sol +1141 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transfer.sol +217 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked.sol +196 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked_batch.sol +308 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transfer_batch.sol +385 -0
- package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer.sol +497 -0
- package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer_batch.sol +1001 -0
- package/contracts/verifiers/{verifier_anon_nullifier_transferLocked.sol → impl/anon_nullifier_transfer.sol} +12 -19
- package/contracts/verifiers/{verifier_anon_nullifier_transferLocked_batch.sol → impl/anon_nullifier_transfer_batch.sol} +44 -51
- package/contracts/verifiers/impl/burn.sol +182 -0
- package/contracts/verifiers/impl/burn_batch.sol +238 -0
- package/contracts/verifiers/impl/burn_nullifier.sol +203 -0
- package/contracts/verifiers/impl/burn_nullifier_batch.sol +315 -0
- package/contracts/verifiers/impl/deposit.sol +182 -0
- package/contracts/verifiers/impl/deposit_kyc.sol +189 -0
- package/contracts/verifiers/impl/nf_anon.sol +175 -0
- package/contracts/verifiers/{verifier_nf_anon_nullifier_transferLocked.sol → impl/nf_anon_nullifier_transfer.sol} +6 -13
- package/contracts/verifiers/impl/withdraw.sol +189 -0
- package/contracts/verifiers/impl/withdraw_batch.sol +245 -0
- package/contracts/verifiers/impl/withdraw_nullifier.sol +210 -0
- package/contracts/verifiers/impl/withdraw_nullifier_batch.sol +322 -0
- package/contracts/verifiers/verifier_anon.sol +31 -186
- package/contracts/verifiers/verifier_anon_batch.sol +31 -298
- package/contracts/verifiers/verifier_anon_enc.sol +31 -263
- package/contracts/verifiers/verifier_anon_enc_batch.sol +31 -599
- package/contracts/verifiers/verifier_anon_enc_nullifier.sol +31 -284
- package/contracts/verifiers/verifier_anon_enc_nullifier_batch.sol +33 -676
- package/contracts/verifiers/verifier_anon_enc_nullifier_kyc.sol +31 -291
- package/contracts/verifiers/verifier_anon_enc_nullifier_kyc_batch.sol +33 -683
- package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation.sol +33 -410
- package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation_batch.sol +33 -1138
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer.sol +33 -214
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked.sol +33 -221
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked_batch.sol +33 -389
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer_batch.sol +33 -382
- package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer.sol +33 -221
- package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer_batch.sol +33 -389
- package/contracts/verifiers/verifier_anon_nullifier_transfer.sol +33 -207
- package/contracts/verifiers/verifier_anon_nullifier_transfer_batch.sol +33 -375
- package/contracts/verifiers/verifier_burn.sol +31 -179
- package/contracts/verifiers/verifier_burn_batch.sol +31 -235
- package/contracts/verifiers/verifier_burn_nullifier.sol +31 -200
- package/contracts/verifiers/verifier_burn_nullifier_batch.sol +31 -312
- package/contracts/verifiers/verifier_deposit.sol +31 -179
- package/contracts/verifiers/verifier_deposit_kyc.sol +34 -0
- package/contracts/verifiers/verifier_nf_anon.sol +31 -172
- package/contracts/verifiers/verifier_nf_anon_nullifier_transfer.sol +33 -179
- package/contracts/verifiers/verifier_withdraw.sol +31 -186
- package/contracts/verifiers/verifier_withdraw_batch.sol +31 -242
- package/contracts/verifiers/verifier_withdraw_nullifier.sol +31 -207
- package/contracts/verifiers/verifier_withdraw_nullifier_batch.sol +33 -319
- package/contracts/zeto_anon.sol +77 -231
- package/contracts/zeto_anon_burnable.sol +56 -12
- package/contracts/zeto_anon_enc.sol +93 -190
- package/contracts/zeto_anon_enc_nullifier.sol +249 -195
- package/contracts/zeto_anon_enc_nullifier_kyc.sol +51 -232
- package/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +231 -238
- package/contracts/zeto_anon_nullifier.sol +164 -298
- package/contracts/zeto_anon_nullifier_burnable.sol +68 -18
- package/contracts/zeto_anon_nullifier_kyc.sol +40 -345
- package/contracts/zeto_anon_nullifier_qurrency.sol +217 -361
- package/contracts/zeto_nf_anon.sol +55 -130
- package/contracts/zeto_nf_anon_nullifier.sol +90 -152
- package/hardhat.config.ts +18 -3
- package/ignition/modules/lib/deps.ts +13 -0
- package/ignition/modules/test/tendecimals.ts +7 -1
- package/ignition/modules/zeto_anon.ts +3 -0
- package/ignition/modules/zeto_anon_burnable.ts +11 -7
- package/ignition/modules/zeto_anon_enc.ts +3 -0
- package/ignition/modules/zeto_anon_enc_nullifier.ts +34 -0
- package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +5 -2
- package/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +3 -0
- package/ignition/modules/zeto_anon_nullifier.ts +31 -4
- package/ignition/modules/zeto_anon_nullifier_burnable.ts +53 -16
- package/ignition/modules/zeto_anon_nullifier_kyc.ts +30 -12
- package/ignition/modules/zeto_anon_nullifier_qurrency.ts +3 -0
- package/ignition/modules/zeto_nf_anon.ts +3 -1
- package/ignition/modules/zeto_nf_anon_nullifier.ts +17 -12
- package/package.json +7 -3
- package/scripts/deploy_cloneable.ts +19 -10
- package/scripts/deploy_upgradeable.ts +9 -5
- package/scripts/lib/zeto_libraries.ts +47 -0
- package/scripts/tokens/Zeto_Anon.ts +6 -3
- package/scripts/tokens/Zeto_AnonBurnable.ts +4 -1
- package/scripts/tokens/Zeto_AnonEnc.ts +15 -3
- package/scripts/tokens/Zeto_AnonEncNullifier.ts +11 -8
- package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +7 -6
- package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifier.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierKyc.ts +7 -6
- package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +7 -8
- package/scripts/tokens/Zeto_NfAnon.ts +5 -3
- package/scripts/tokens/Zeto_NfAnonNullifier.ts +7 -7
- package/test/factory.ts +55 -73
- package/test/lib/anon_nullifier_helpers.ts +89 -0
- package/test/lib/anon_zeto_helpers.ts +76 -0
- package/test/lib/deploy.ts +5 -2
- package/test/lib/eip170.ts +23 -0
- package/test/lib/utils.ts +74 -35
- package/test/test/escrow1.ts +185 -58
- package/test/test/escrow2.ts +200 -107
- package/test/test/qurrency.ts +13 -33
- package/test/usdc-shielding.ts +39 -27
- package/test/utils.ts +144 -21
- package/test/zeto_anon.ts +956 -465
- package/test/zeto_anon_enc.ts +881 -143
- package/test/zeto_anon_enc_nullifier.ts +850 -39
- package/test/zeto_anon_enc_nullifier_kyc.ts +123 -182
- package/test/zeto_anon_enc_nullifier_non_repudiation.ts +80 -47
- package/test/zeto_anon_nullifier.ts +1212 -954
- package/test/zeto_anon_nullifier_kyc.ts +677 -656
- package/test/zeto_anon_nullifier_qurrency.ts +455 -307
- package/test/zeto_nf_anon.ts +737 -138
- package/test/zeto_nf_anon_nullifier.ts +876 -247
- package/contracts/lib/zeto_base.sol +0 -293
- package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
- package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
- package/contracts/lib/zeto_nullifier.sol +0 -340
- package/contracts/zkDvP.sol_ +0 -273
- package/test/zkDvP.ts_ +0 -455
- /package/contracts/lib/{common.sol → common/common.sol} +0 -0
package/test/test/escrow2.ts
CHANGED
|
@@ -15,12 +15,24 @@
|
|
|
15
15
|
// limitations under the License.
|
|
16
16
|
|
|
17
17
|
import { ethers, ignition, network } from "hardhat";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
Signer,
|
|
20
|
+
AbiCoder,
|
|
21
|
+
BigNumberish,
|
|
22
|
+
ContractTransactionReceipt,
|
|
23
|
+
} from "ethers";
|
|
19
24
|
import { expect } from "chai";
|
|
20
|
-
import { loadCircuit
|
|
25
|
+
import { loadCircuit } from "zeto-js";
|
|
21
26
|
import { Merkletree, InMemoryDB, str2Bytes } from "@iden3/js-merkletree";
|
|
22
27
|
import zkEscrowModule from "../../ignition/modules/test/escrow2";
|
|
23
|
-
import
|
|
28
|
+
import { prepareProof as prepareNullifierUnlockedProof } from "../lib/anon_nullifier_helpers";
|
|
29
|
+
// The locked-input transition for Zeto_AnonNullifier reuses the simple
|
|
30
|
+
// `anon` circuit (the lockVerifier in the ignition module is wired to
|
|
31
|
+
// {Groth16Verifier_Anon}, mirroring what the production ZK proof path
|
|
32
|
+
// expects from {ZetoFungible._transferLocked}). So the spend proof for
|
|
33
|
+
// the escrow flow is built with `prepareProof` from {anon_zeto_helpers}
|
|
34
|
+
// (same circuit helper as in `zeto_anon.ts`), not from the nullifier suite.
|
|
35
|
+
import { prepareProof as prepareProofAnonCircuit } from "../lib/anon_zeto_helpers";
|
|
24
36
|
import {
|
|
25
37
|
UTXO,
|
|
26
38
|
User,
|
|
@@ -29,11 +41,35 @@ import {
|
|
|
29
41
|
newNullifier,
|
|
30
42
|
doMint,
|
|
31
43
|
ZERO_UTXO,
|
|
32
|
-
|
|
44
|
+
logger,
|
|
33
45
|
} from "../lib/utils";
|
|
34
46
|
import { loadProvingKeys } from "../utils";
|
|
35
47
|
import { deployZeto } from "../lib/deploy";
|
|
36
48
|
|
|
49
|
+
// ABI fragments mirror the canonical ZetoLockableCapability *Args
|
|
50
|
+
// payloads used uniformly across all Zeto tokens.
|
|
51
|
+
const CREATE_ARGS_ABI =
|
|
52
|
+
"tuple(bytes32 txId, uint256[] inputs, uint256[] outputs, uint256[] lockedOutputs, bytes proof)";
|
|
53
|
+
const DELEGATE_ARGS_ABI = "tuple(bytes32 txId)";
|
|
54
|
+
|
|
55
|
+
function encodeCreateArgs(args: {
|
|
56
|
+
txId: string;
|
|
57
|
+
inputs: BigNumberish[];
|
|
58
|
+
outputs: BigNumberish[];
|
|
59
|
+
lockedOutputs: BigNumberish[];
|
|
60
|
+
proof: string;
|
|
61
|
+
}) {
|
|
62
|
+
return new AbiCoder().encode([CREATE_ARGS_ABI], [args]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function encodeDelegateArgs(txId: string) {
|
|
66
|
+
return new AbiCoder().encode([DELEGATE_ARGS_ABI], [{ txId }]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function randomBytes32(): string {
|
|
70
|
+
return ethers.hexlify(ethers.randomBytes(32));
|
|
71
|
+
}
|
|
72
|
+
|
|
37
73
|
describe("Escrow flow for payment with Zeto_AnonNullifier", function () {
|
|
38
74
|
let Alice: User;
|
|
39
75
|
let Bob: User;
|
|
@@ -49,20 +85,22 @@ describe("Escrow flow for payment with Zeto_AnonNullifier", function () {
|
|
|
49
85
|
|
|
50
86
|
// UTXOs involved in the escrow flow
|
|
51
87
|
let lockedPayment1: UTXO;
|
|
52
|
-
let nullifier1: UTXO;
|
|
53
88
|
let paymentToBob: UTXO;
|
|
89
|
+
let lockId: string;
|
|
54
90
|
|
|
55
91
|
// other variables
|
|
56
92
|
let deployer: Signer;
|
|
57
|
-
|
|
58
|
-
let
|
|
93
|
+
// unlocked-input transfer circuit (nullifier + merkle proof)
|
|
94
|
+
let circuit: any;
|
|
95
|
+
let provingKey: string;
|
|
96
|
+
// locked-input transfer circuit (simpler `anon`, no nullifier or root)
|
|
97
|
+
let circuitLocked: any;
|
|
98
|
+
let provingKeyLocked: string;
|
|
59
99
|
let paymentId: any;
|
|
60
|
-
let smtAlice: Merkletree
|
|
61
|
-
let smtBob: Merkletree;
|
|
100
|
+
let smtAlice: Merkletree;
|
|
62
101
|
|
|
63
102
|
before(async function () {
|
|
64
103
|
if (network.name !== "hardhat") {
|
|
65
|
-
// accommodate for longer block times on public networks
|
|
66
104
|
this.timeout(120000);
|
|
67
105
|
}
|
|
68
106
|
let [d, a, b, c] = await ethers.getSigners();
|
|
@@ -75,19 +113,14 @@ describe("Escrow flow for payment with Zeto_AnonNullifier", function () {
|
|
|
75
113
|
({ provingKeyFile: provingKey } = loadProvingKeys(
|
|
76
114
|
"anon_nullifier_transfer",
|
|
77
115
|
));
|
|
78
|
-
circuitLocked = await loadCircuit("
|
|
79
|
-
({ provingKeyFile: provingKeyLocked } = loadProvingKeys(
|
|
80
|
-
"anon_nullifier_transferLocked",
|
|
81
|
-
));
|
|
116
|
+
circuitLocked = await loadCircuit("anon");
|
|
117
|
+
({ provingKeyFile: provingKeyLocked } = loadProvingKeys("anon"));
|
|
82
118
|
|
|
83
119
|
const storage1 = new InMemoryDB(str2Bytes(""));
|
|
84
120
|
smtAlice = new Merkletree(storage1, true, 64);
|
|
85
121
|
|
|
86
|
-
const storage2 = new InMemoryDB(str2Bytes(""));
|
|
87
|
-
smtAliceLocked = new Merkletree(storage2, true, 64);
|
|
88
|
-
|
|
89
122
|
({ deployer, zeto: zkPayment } = await deployZeto("Zeto_AnonNullifier"));
|
|
90
|
-
|
|
123
|
+
logger.debug(`ZK Payment contract deployed at ${zkPayment.target}`);
|
|
91
124
|
({ zkEscrow } = await ignition.deploy(zkEscrowModule, {
|
|
92
125
|
parameters: {
|
|
93
126
|
zkEscrow2: {
|
|
@@ -102,136 +135,196 @@ describe("Escrow flow for payment with Zeto_AnonNullifier", function () {
|
|
|
102
135
|
payment2 = newUTXO(20, Alice);
|
|
103
136
|
const result = await doMint(zkPayment, deployer, [payment1, payment2]);
|
|
104
137
|
|
|
105
|
-
// simulate Alice and Bob listening to minting events and updating his local merkle tree
|
|
106
138
|
for (const log of result.logs) {
|
|
107
139
|
const event = zkPayment.interface.parseLog(log as any);
|
|
108
140
|
expect(event.args.outputs.length).to.equal(2);
|
|
109
141
|
}
|
|
110
142
|
|
|
143
|
+
// Mirror the on-chain unlocked-commitments SMT off-chain so we can
|
|
144
|
+
// build merkle proofs for the unlocked-input transition that
|
|
145
|
+
// creates the lock.
|
|
111
146
|
await smtAlice.add(payment1.hash, payment1.hash);
|
|
112
147
|
await smtAlice.add(payment2.hash, payment2.hash);
|
|
113
148
|
});
|
|
114
149
|
|
|
115
|
-
it("Alice locks
|
|
116
|
-
// Alice generates the
|
|
150
|
+
it("Alice locks payment1 by calling createLock on the Zeto token", async function () {
|
|
151
|
+
// Alice generates the nullifier for payment1 and a merkle inclusion
|
|
152
|
+
// proof in the unlocked-commitments SMT.
|
|
117
153
|
const nullifier1 = newNullifier(payment1, Alice);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
122
|
-
payment1.hash,
|
|
123
|
-
root,
|
|
124
|
-
);
|
|
125
|
-
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
154
|
+
const root = await smtAlice.root();
|
|
155
|
+
const p1 = await smtAlice.generateCircomVerifierProof(payment1.hash, root);
|
|
156
|
+
const p2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
126
157
|
const merkleProofs = [
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
159
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
129
160
|
];
|
|
130
161
|
|
|
131
162
|
lockedPayment1 = newUTXO(payment1.value!, Alice);
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
163
|
+
const encodedZkProof = await prepareNullifierUnlockedProof(
|
|
164
|
+
circuit,
|
|
165
|
+
provingKey,
|
|
166
|
+
Alice,
|
|
167
|
+
[payment1, ZERO_UTXO],
|
|
168
|
+
[nullifier1, ZERO_UTXO],
|
|
169
|
+
[lockedPayment1, ZERO_UTXO],
|
|
170
|
+
root.bigInt(),
|
|
171
|
+
merkleProofs,
|
|
172
|
+
[Alice, Alice],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const createArgs = encodeCreateArgs({
|
|
176
|
+
txId: randomBytes32(),
|
|
177
|
+
inputs: [nullifier1.hash],
|
|
178
|
+
// For Zeto_AnonNullifier createLock, lockedOutputs holds the
|
|
179
|
+
// freshly-locked UTXO and outputs is empty; payment1's full value
|
|
180
|
+
// moves into the lock.
|
|
181
|
+
outputs: [],
|
|
182
|
+
lockedOutputs: [lockedPayment1.hash],
|
|
183
|
+
proof: encodeUnlockedProof(root.bigInt(), encodedZkProof),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
lockId = await zkPayment.connect(Alice.signer).computeLockId(createArgs);
|
|
187
|
+
|
|
144
188
|
const tx = await zkPayment
|
|
145
189
|
.connect(Alice.signer)
|
|
146
|
-
.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
190
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x");
|
|
191
|
+
const result = await tx.wait();
|
|
192
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
193
|
+
|
|
194
|
+
const info = await zkPayment.getLock(lockId);
|
|
195
|
+
expect(info.owner).to.equal(Alice.ethAddress);
|
|
196
|
+
expect(info.spender).to.equal(Alice.ethAddress);
|
|
197
|
+
expect(await zkPayment.locked(lockedPayment1.hash)).to.deep.equal([
|
|
198
|
+
true,
|
|
199
|
+
Alice.ethAddress,
|
|
200
|
+
]);
|
|
201
|
+
}).timeout(120000);
|
|
202
|
+
|
|
203
|
+
it("Alice delegates the lock to the escrow contract", async function () {
|
|
204
|
+
const tx = await zkPayment
|
|
205
|
+
.connect(Alice.signer)
|
|
206
|
+
.delegateLock(
|
|
207
|
+
lockId,
|
|
208
|
+
encodeDelegateArgs(randomBytes32()),
|
|
152
209
|
zkEscrow.target,
|
|
153
210
|
"0x",
|
|
154
211
|
);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const check = newUTXO(lockedPayment1.value!, Alice, lockedPayment1.salt);
|
|
164
|
-
expect(lockedUTXO).to.equal(check.hash);
|
|
165
|
-
expect(lockDelegate).to.equal(zkEscrow.target);
|
|
166
|
-
|
|
167
|
-
await smtAliceLocked.add(
|
|
168
|
-
lockedPayment1.hash,
|
|
169
|
-
ethers.toBigInt(zkEscrow.target),
|
|
170
|
-
);
|
|
212
|
+
await tx.wait();
|
|
213
|
+
|
|
214
|
+
const info = await zkPayment.getLock(lockId);
|
|
215
|
+
expect(info.spender).to.equal(zkEscrow.target);
|
|
216
|
+
expect(await zkPayment.locked(lockedPayment1.hash)).to.deep.equal([
|
|
217
|
+
true,
|
|
218
|
+
zkEscrow.target,
|
|
219
|
+
]);
|
|
171
220
|
});
|
|
172
221
|
|
|
173
222
|
it("Alice initiates a payment transaction to Bob through the escrow", async function () {
|
|
174
|
-
nullifier1 = newNullifier(lockedPayment1, Alice);
|
|
175
223
|
paymentToBob = newUTXO(lockedPayment1.value!, Bob);
|
|
176
224
|
const tx = await zkEscrow
|
|
177
225
|
.connect(Alice.signer)
|
|
178
|
-
.initiatePayment(
|
|
226
|
+
.initiatePayment(lockId, [paymentToBob.hash], "0x");
|
|
179
227
|
const result = await tx.wait();
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
expect(
|
|
184
|
-
paymentId =
|
|
228
|
+
const initiated = parseEscrowEvent(zkEscrow, result, "PaymentInitiated");
|
|
229
|
+
expect(initiated, "PaymentInitiated event not emitted").to.not.be.undefined;
|
|
230
|
+
expect(initiated!.outputs[0]).to.equal(paymentToBob.hash);
|
|
231
|
+
expect(initiated!.lockId).to.equal(lockId);
|
|
232
|
+
paymentId = initiated!.paymentId;
|
|
185
233
|
});
|
|
186
234
|
|
|
187
|
-
it("Alice approves the payment by submitting a valid proof
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const proof2 = await smtAliceLocked.generateCircomVerifierProof(0n, root);
|
|
194
|
-
const merkleProofs = [
|
|
195
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
196
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
197
|
-
];
|
|
198
|
-
const { encodedProof } = await zetoAnonNullifierTests.prepareProof(
|
|
235
|
+
it("Alice approves the payment by submitting a valid locked-input proof", async function () {
|
|
236
|
+
// The locked-input transition uses the simple `anon` circuit and
|
|
237
|
+
// operates on the raw UTXO hash (lockedPayment1.hash), not on a
|
|
238
|
+
// nullifier. The Zeto storage layer has already validated the
|
|
239
|
+
// input is in the locked-UTXO ledger.
|
|
240
|
+
const encodedZkProof = await prepareProofAnonCircuit(
|
|
199
241
|
circuitLocked,
|
|
200
242
|
provingKeyLocked,
|
|
201
243
|
Alice,
|
|
202
244
|
[lockedPayment1, ZERO_UTXO],
|
|
203
|
-
[nullifier1, ZERO_UTXO],
|
|
204
245
|
[paymentToBob, ZERO_UTXO],
|
|
205
|
-
|
|
206
|
-
merkleProofs,
|
|
207
|
-
[Bob, Bob],
|
|
208
|
-
zkEscrow.target,
|
|
246
|
+
[Bob, {}],
|
|
209
247
|
);
|
|
210
248
|
const tx = await zkEscrow
|
|
211
249
|
.connect(Alice.signer)
|
|
212
|
-
.approvePayment(paymentId,
|
|
250
|
+
.approvePayment(paymentId, encodeLockedProof(encodedZkProof), "0x");
|
|
213
251
|
const result = await tx.wait();
|
|
214
|
-
// simulate Bob listening to the escrow events and verifying the payment has been approved.
|
|
215
|
-
// the escrow contract guaratees that the proof is valid
|
|
216
|
-
const events = parseUTXOEvents(zkEscrow, result);
|
|
217
|
-
const approvedPayment = events[0].paymentId;
|
|
218
|
-
expect(approvedPayment).to.equal(paymentId);
|
|
219
|
-
});
|
|
220
252
|
|
|
221
|
-
|
|
253
|
+
const approved = parseEscrowEvent(zkEscrow, result, "PaymentApproved");
|
|
254
|
+
expect(approved, "PaymentApproved event not emitted").to.not.be.undefined;
|
|
255
|
+
expect(approved!.paymentId).to.equal(paymentId);
|
|
256
|
+
}).timeout(120000);
|
|
257
|
+
|
|
258
|
+
it("Bob, or anyone, can call the escrow to finalize the payment and receive the unlocked UTXO", async function () {
|
|
222
259
|
const tx = await zkEscrow
|
|
223
260
|
.connect(Bob.signer)
|
|
224
261
|
.completePayment(paymentId, "0x");
|
|
225
262
|
const result = await tx.wait();
|
|
226
|
-
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
263
|
+
|
|
264
|
+
// The Zeto token emits ZetoLockSpent containing the new unlocked
|
|
265
|
+
// outputs.
|
|
266
|
+
const lockSpent = result!.logs
|
|
267
|
+
.map((l) => {
|
|
268
|
+
try {
|
|
269
|
+
return zkPayment.interface.parseLog(l as any);
|
|
270
|
+
} catch (_e) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
.find((p: any) => p && p.name === "ZetoLockSpent");
|
|
275
|
+
expect(lockSpent, "ZetoLockSpent event not emitted").to.not.be.undefined;
|
|
276
|
+
expect(lockSpent!.args.outputs[0]).to.equal(paymentToBob.hash);
|
|
277
|
+
|
|
278
|
+
expect(await zkPayment.isLockActive(lockId)).to.equal(false);
|
|
279
|
+
expect((await zkPayment.locked(lockedPayment1.hash))[0]).to.be.false;
|
|
280
|
+
|
|
281
|
+
const completed = parseEscrowEvent(zkEscrow, result, "PaymentCompleted");
|
|
282
|
+
expect(completed, "PaymentCompleted event not emitted").to.not.be.undefined;
|
|
283
|
+
expect(completed!.paymentId).to.equal(paymentId);
|
|
236
284
|
});
|
|
237
285
|
});
|
|
286
|
+
|
|
287
|
+
// encodeUnlockedProof matches the wire format consumed by
|
|
288
|
+
// {Zeto_AnonNullifier.constructPublicInputs} on the unlocked branch:
|
|
289
|
+
// `(uint256 root, Commonlib.Proof)`. Used for the createLock proof.
|
|
290
|
+
function encodeUnlockedProof(root: any, proof: any) {
|
|
291
|
+
return new AbiCoder().encode(
|
|
292
|
+
["uint256 root", "tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
|
|
293
|
+
[root, proof],
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// encodeLockedProof matches the wire format consumed on the locked
|
|
298
|
+
// branch: just `Commonlib.Proof`, no root prefix (the locked input is
|
|
299
|
+
// already vouched for by the storage layer, no merkle inclusion proof
|
|
300
|
+
// is needed). Used for the spendLock / approvePayment proof.
|
|
301
|
+
function encodeLockedProof(proof: any) {
|
|
302
|
+
return new AbiCoder().encode(
|
|
303
|
+
["tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
|
|
304
|
+
[proof],
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// parseEscrowEvent extracts the first event of a given name from a
|
|
309
|
+
// transaction receipt by parsing logs through the escrow contract's
|
|
310
|
+
// own ABI. Unlike the Zeto-token-specific `parseUTXOEvents`, this
|
|
311
|
+
// helper handles the escrow's custom Payment* events.
|
|
312
|
+
function parseEscrowEvent(
|
|
313
|
+
escrowContract: any,
|
|
314
|
+
receipt: ContractTransactionReceipt | null,
|
|
315
|
+
name: string,
|
|
316
|
+
): any | undefined {
|
|
317
|
+
if (!receipt) return undefined;
|
|
318
|
+
for (const log of receipt.logs || []) {
|
|
319
|
+
let parsed;
|
|
320
|
+
try {
|
|
321
|
+
parsed = escrowContract.interface.parseLog(log as any);
|
|
322
|
+
} catch (_e) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (parsed && parsed.name === name) {
|
|
326
|
+
return parsed.args;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
package/test/test/qurrency.ts
CHANGED
|
@@ -15,10 +15,16 @@
|
|
|
15
15
|
// limitations under the License.
|
|
16
16
|
|
|
17
17
|
import { ethers, ignition, network } from "hardhat";
|
|
18
|
-
import {
|
|
18
|
+
import { BigNumberish, ContractTransactionReceipt } from "ethers";
|
|
19
|
+
import crypto from "crypto";
|
|
19
20
|
import { groth16 } from "snarkjs";
|
|
20
21
|
import { Merkletree, InMemoryDB, str2Bytes } from "@iden3/js-merkletree";
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
loadCircuit,
|
|
24
|
+
encodeProof,
|
|
25
|
+
newEncryptionNonce,
|
|
26
|
+
bytesToBits,
|
|
27
|
+
} from "zeto-js";
|
|
22
28
|
import qurrencyModule from "../../ignition/modules/test/qurrency";
|
|
23
29
|
import { User, newUser, newUTXO, newNullifier } from "../lib/utils";
|
|
24
30
|
import { loadProvingKeys } from "../utils";
|
|
@@ -88,35 +94,9 @@ describe("Test Qurrency verifier", function () {
|
|
|
88
94
|
Alice.babyJubPublicKey,
|
|
89
95
|
];
|
|
90
96
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
1665, 0, 0, 1665, 1665, 1665, 0, 0, 0, 0, 0, 0, 1665, 0, 0, 1665, 0, 0,
|
|
95
|
-
1665, 0, 1665, 1665, 0, 1665, 1665, 0, 0, 1665, 1665, 1665, 0, 0, 0, 0, 0,
|
|
96
|
-
0, 1665, 0, 0, 1665, 1665, 0, 0, 0, 1665, 1665, 0, 1665, 1665, 1665, 1665,
|
|
97
|
-
0, 1665, 1665, 0, 1665, 1665, 1665, 1665, 0, 1665, 1665, 0, 0, 0, 1665,
|
|
98
|
-
1665, 0, 1665, 1665, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
99
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
100
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
101
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
102
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
103
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
104
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
105
|
-
];
|
|
106
|
-
|
|
107
|
-
const randomness = [
|
|
108
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
109
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
110
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
111
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
112
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
113
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
114
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
115
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
116
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
117
|
-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
118
|
-
0, 0, 0, 0, 0, 0,
|
|
119
|
-
];
|
|
97
|
+
const randomness = crypto.randomBytes(32);
|
|
98
|
+
const r = bytesToBits(randomness);
|
|
99
|
+
const encryptionNonce = newEncryptionNonce();
|
|
120
100
|
|
|
121
101
|
const inputObj: any = {
|
|
122
102
|
nullifiers,
|
|
@@ -131,8 +111,8 @@ describe("Test Qurrency verifier", function () {
|
|
|
131
111
|
outputValues,
|
|
132
112
|
outputSalts,
|
|
133
113
|
outputOwnerPublicKeys,
|
|
134
|
-
randomness,
|
|
135
|
-
|
|
114
|
+
randomness: r,
|
|
115
|
+
encryptionNonce,
|
|
136
116
|
};
|
|
137
117
|
const witness = await circuit.calculateWTNSBin(inputObj, true);
|
|
138
118
|
const startProofGeneration = Date.now();
|
package/test/usdc-shielding.ts
CHANGED
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
// limitations under the License.
|
|
16
16
|
|
|
17
17
|
import { ethers, network } from "hardhat";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
Signer,
|
|
20
|
+
BigNumberish,
|
|
21
|
+
ContractTransactionReceipt,
|
|
22
|
+
AbiCoder,
|
|
23
|
+
} from "ethers";
|
|
19
24
|
import { expect } from "chai";
|
|
20
25
|
import { loadCircuit, encodeProof, Poseidon } from "zeto-js";
|
|
21
26
|
import { groth16 } from "snarkjs";
|
|
@@ -34,13 +39,12 @@ import {
|
|
|
34
39
|
loadProvingKeys,
|
|
35
40
|
prepareDepositProof,
|
|
36
41
|
prepareNullifierWithdrawProof,
|
|
42
|
+
encodeToBytesForDeposit,
|
|
43
|
+
encodeToBytesForWithdraw,
|
|
37
44
|
} from "./utils";
|
|
38
45
|
import { Zeto_AnonNullifier } from "../typechain-types";
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
} from "../scripts/deploy_upgradeable";
|
|
42
|
-
|
|
43
|
-
const USDC_SEPOLIA_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
|
|
46
|
+
import { deployZeto } from "./lib/deploy";
|
|
47
|
+
const USDC_SEPOLIA_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";
|
|
44
48
|
const TRANSFER_AMOUNT = 1000; // 0.001000 UDSC
|
|
45
49
|
|
|
46
50
|
describe("Shield USDC balances, transact in privacy, and withdraw back to USDC", function () {
|
|
@@ -68,11 +72,12 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
|
|
|
68
72
|
Alice2 = await newUser(b);
|
|
69
73
|
Bob = await newUser(c);
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
({ zeto, erc20: usdc } = await deployZeto("Zeto_AnonNullifier", usdcAddress));
|
|
75
|
+
({ zeto, erc20: usdc } = await deployZeto("Zeto_AnonNullifier"));
|
|
73
76
|
|
|
74
77
|
circuit = await loadCircuit("anon_nullifier_transfer");
|
|
75
|
-
({ provingKeyFile: provingKey } = loadProvingKeys(
|
|
78
|
+
({ provingKeyFile: provingKey } = loadProvingKeys(
|
|
79
|
+
"anon_nullifier_transfer",
|
|
80
|
+
));
|
|
76
81
|
|
|
77
82
|
const storage1 = new InMemoryDB(str2Bytes(""));
|
|
78
83
|
smtAlice = new Merkletree(storage1, true, 64);
|
|
@@ -84,7 +89,9 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
|
|
|
84
89
|
it("Alice shields some USDC balance into the Zeto contract, and get Zeto tokens of the same value", async function () {
|
|
85
90
|
const balance = await usdc.balanceOf(Alice.ethAddress);
|
|
86
91
|
expect(balance).to.be.gt(TRANSFER_AMOUNT);
|
|
87
|
-
const tx1 = await usdc
|
|
92
|
+
const tx1 = await usdc
|
|
93
|
+
.connect(Alice.signer)
|
|
94
|
+
.approve(zeto.target, TRANSFER_AMOUNT);
|
|
88
95
|
await tx1.wait();
|
|
89
96
|
|
|
90
97
|
// Alice proposes the output UTXOs to shield her USDC balance
|
|
@@ -95,7 +102,12 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
|
|
|
95
102
|
);
|
|
96
103
|
const tx2 = await zeto
|
|
97
104
|
.connect(Alice.signer)
|
|
98
|
-
.deposit(
|
|
105
|
+
.deposit(
|
|
106
|
+
TRANSFER_AMOUNT,
|
|
107
|
+
outputCommitments,
|
|
108
|
+
encodeToBytesForDeposit(encodedProof),
|
|
109
|
+
"0x",
|
|
110
|
+
);
|
|
99
111
|
await tx2.wait();
|
|
100
112
|
|
|
101
113
|
// Alice locally tracks the UTXOs inside the Sparse Merkle Tree
|
|
@@ -179,10 +191,7 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
|
|
|
179
191
|
|
|
180
192
|
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
181
193
|
let root = await smtAlice.root();
|
|
182
|
-
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
183
|
-
utxo3.hash,
|
|
184
|
-
root,
|
|
185
|
-
);
|
|
194
|
+
const proof1 = await smtAlice.generateCircomVerifierProof(utxo3.hash, root);
|
|
186
195
|
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
187
196
|
const merkleProofs = [
|
|
188
197
|
proof1.siblings.map((s) => s.bigInt()),
|
|
@@ -208,16 +217,13 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
|
|
|
208
217
|
// she used to shield the USDC tokens. Other parties can not link the
|
|
209
218
|
// original USDC tokens to the withdrawn USDC tokens, because the public key
|
|
210
219
|
// information behind the UTXO is unknown to other parties.
|
|
211
|
-
const tx = await zeto
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
encodedProof,
|
|
219
|
-
"0x",
|
|
220
|
-
);
|
|
220
|
+
const tx = await zeto.connect(Alice2.signer).withdraw(
|
|
221
|
+
TRANSFER_AMOUNT / 2, // the amount to withdraw
|
|
222
|
+
nullifiers,
|
|
223
|
+
outputCommitments[0],
|
|
224
|
+
encodeToBytesForWithdraw(root.bigInt(), encodedProof),
|
|
225
|
+
"0x",
|
|
226
|
+
);
|
|
221
227
|
await tx.wait();
|
|
222
228
|
|
|
223
229
|
// Alice checks her ERC20 balance
|
|
@@ -284,8 +290,7 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
|
|
|
284
290
|
tx = await zeto.connect(signer.signer).transfer(
|
|
285
291
|
nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
286
292
|
outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
287
|
-
root,
|
|
288
|
-
encodedProof,
|
|
293
|
+
encodeToBytes(root, encodedProof),
|
|
289
294
|
"0x",
|
|
290
295
|
);
|
|
291
296
|
const results: ContractTransactionReceipt | null = await tx.wait();
|
|
@@ -359,3 +364,10 @@ describe("Shield USDC balances, transact in privacy, and withdraw back to USDC",
|
|
|
359
364
|
};
|
|
360
365
|
}
|
|
361
366
|
});
|
|
367
|
+
|
|
368
|
+
function encodeToBytes(root: any, proof: any) {
|
|
369
|
+
return new AbiCoder().encode(
|
|
370
|
+
["uint256 root", "tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
|
|
371
|
+
[root, proof],
|
|
372
|
+
);
|
|
373
|
+
}
|