@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
|
@@ -14,11 +14,18 @@
|
|
|
14
14
|
// See the License for the specific language governing permissions and
|
|
15
15
|
// limitations under the License.
|
|
16
16
|
|
|
17
|
-
import { ethers, network } from "hardhat";
|
|
18
|
-
import {
|
|
17
|
+
import { ethers, network, upgrades } from "hardhat";
|
|
18
|
+
import {
|
|
19
|
+
ContractTransactionReceipt,
|
|
20
|
+
Signer,
|
|
21
|
+
BigNumberish,
|
|
22
|
+
AbiCoder,
|
|
23
|
+
ZeroAddress,
|
|
24
|
+
} from "ethers";
|
|
19
25
|
import { expect } from "chai";
|
|
20
|
-
import
|
|
21
|
-
|
|
26
|
+
import * as chai from "chai";
|
|
27
|
+
chai.config.truncateThreshold = 0; // disable truncating
|
|
28
|
+
import { loadCircuit, Poseidon } from "zeto-js";
|
|
22
29
|
import { Merkletree, InMemoryDB, str2Bytes } from "@iden3/js-merkletree";
|
|
23
30
|
import {
|
|
24
31
|
UTXO,
|
|
@@ -29,15 +36,30 @@ import {
|
|
|
29
36
|
doMint,
|
|
30
37
|
ZERO_UTXO,
|
|
31
38
|
parseUTXOEvents,
|
|
39
|
+
logger,
|
|
32
40
|
} from "./lib/utils";
|
|
33
41
|
import {
|
|
34
42
|
loadProvingKeys,
|
|
35
43
|
prepareDepositProof,
|
|
36
44
|
prepareNullifierWithdrawProof,
|
|
37
|
-
|
|
45
|
+
encodeToBytesForDeposit,
|
|
46
|
+
encodeToBytesForWithdraw,
|
|
47
|
+
inflateUtxos,
|
|
48
|
+
inflateOwners,
|
|
49
|
+
calculateSpendHash,
|
|
50
|
+
calculateCancelHash,
|
|
38
51
|
} from "./utils";
|
|
52
|
+
import {
|
|
53
|
+
prepareProof as prepareProofForLocked,
|
|
54
|
+
encodeToBytes as encodeToBytesForLocked,
|
|
55
|
+
} from "./lib/anon_zeto_helpers";
|
|
56
|
+
import {
|
|
57
|
+
prepareProof,
|
|
58
|
+
encodeToBytes,
|
|
59
|
+
} from "./lib/anon_nullifier_helpers";
|
|
39
60
|
import { deployZeto } from "./lib/deploy";
|
|
40
|
-
import { Zeto_AnonNullifier
|
|
61
|
+
import { Zeto_AnonNullifier } from "../typechain-types";
|
|
62
|
+
import smt from "../ignition/modules/test/smt";
|
|
41
63
|
|
|
42
64
|
describe("Zeto based fungible token with anonymity using nullifiers without encryption", function () {
|
|
43
65
|
let deployer: Signer;
|
|
@@ -46,14 +68,6 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
46
68
|
let Charlie: User;
|
|
47
69
|
let erc20: any;
|
|
48
70
|
let zeto: Zeto_AnonNullifier;
|
|
49
|
-
let zetoBurnable: Zeto_AnonNullifierBurnable;
|
|
50
|
-
let utxo100: UTXO;
|
|
51
|
-
let utxo1: UTXO;
|
|
52
|
-
let utxo2: UTXO;
|
|
53
|
-
let utxo3: UTXO;
|
|
54
|
-
let utxo4: UTXO;
|
|
55
|
-
let utxo7: UTXO;
|
|
56
|
-
let utxo9: UTXO;
|
|
57
71
|
let circuit: any, provingKey: any;
|
|
58
72
|
let circuitForLocked: any, provingKeyForLocked: any;
|
|
59
73
|
let batchCircuit: any, batchProvingKey: any;
|
|
@@ -61,19 +75,21 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
61
75
|
let smtBob: Merkletree;
|
|
62
76
|
|
|
63
77
|
before(async function () {
|
|
78
|
+
// skip the tests if this is called by other test modules to use the exported test functions
|
|
79
|
+
if (process.env.SKIP_ANON_NULLIFIER_TESTS === "true") {
|
|
80
|
+
this.skip();
|
|
81
|
+
}
|
|
64
82
|
if (network.name !== "hardhat") {
|
|
65
83
|
// accommodate for longer block times on public networks
|
|
66
84
|
this.timeout(120000);
|
|
67
85
|
}
|
|
68
|
-
|
|
69
|
-
let [d, a, b, c] = await ethers.getSigners();
|
|
86
|
+
let [d, a, b, c, e] = await ethers.getSigners();
|
|
70
87
|
deployer = d;
|
|
71
88
|
Alice = await newUser(a);
|
|
72
89
|
Bob = await newUser(b);
|
|
73
90
|
Charlie = await newUser(c);
|
|
74
91
|
|
|
75
92
|
({ deployer, zeto, erc20 } = await deployZeto("Zeto_AnonNullifier"));
|
|
76
|
-
({ zeto: zetoBurnable } = await deployZeto("Zeto_AnonNullifierBurnable"));
|
|
77
93
|
|
|
78
94
|
const storage1 = new InMemoryDB(str2Bytes(""));
|
|
79
95
|
smtAlice = new Merkletree(storage1, true, 64);
|
|
@@ -85,14 +101,24 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
85
101
|
({ provingKeyFile: provingKey } = loadProvingKeys(
|
|
86
102
|
"anon_nullifier_transfer",
|
|
87
103
|
));
|
|
88
|
-
circuitForLocked = await loadCircuit("anon_nullifier_transferLocked");
|
|
89
|
-
({ provingKeyFile: provingKeyForLocked } = loadProvingKeys(
|
|
90
|
-
"anon_nullifier_transferLocked",
|
|
91
|
-
));
|
|
92
104
|
batchCircuit = await loadCircuit("anon_nullifier_transfer_batch");
|
|
93
105
|
({ provingKeyFile: batchProvingKey } = loadProvingKeys(
|
|
94
106
|
"anon_nullifier_transfer_batch",
|
|
95
107
|
));
|
|
108
|
+
// for consuming locked UTXOs, we always use the "regular" circuit,
|
|
109
|
+
// because the locked UTXOs are always tracked in their own base storage,
|
|
110
|
+
// regardless of which token type is being used. This means for the nullifier-based token,
|
|
111
|
+
// where the unlocked UTXOs are tracked in SMTs, processing unlocked UTXOs require the
|
|
112
|
+
// circuits based on SMT proofs, while the locked UTXOs are processed using the "base" circuit.
|
|
113
|
+
circuitForLocked = await loadCircuit("anon");
|
|
114
|
+
({ provingKeyFile: provingKeyForLocked } = loadProvingKeys("anon"));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
beforeEach(async function () {
|
|
118
|
+
// skip the tests if this is called by other test modules to use the exported test functions
|
|
119
|
+
if (process.env.SKIP_ANON_NULLIFIER_TESTS === "true") {
|
|
120
|
+
this.skip();
|
|
121
|
+
}
|
|
96
122
|
});
|
|
97
123
|
|
|
98
124
|
it("onchain SMT root should be equal to the offchain SMT root", async function () {
|
|
@@ -102,917 +128,1199 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
102
128
|
expect(root.string()).to.equal(onchainRoot.toString());
|
|
103
129
|
});
|
|
104
130
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const mintEvents = parseUTXOEvents(zeto, mintResult);
|
|
118
|
-
const mintedHashes = mintEvents[0].outputs;
|
|
119
|
-
for (let i = 0; i < mintedHashes.length; i++) {
|
|
120
|
-
if (mintedHashes[i] !== 0) {
|
|
121
|
-
await smtAlice.add(mintedHashes[i], mintedHashes[i]);
|
|
122
|
-
await smtBob.add(mintedHashes[i], mintedHashes[i]);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
126
|
-
let root = await smtAlice.root();
|
|
127
|
-
const mtps = [];
|
|
128
|
-
for (let i = 0; i < inputUtxos.length; i++) {
|
|
129
|
-
const p = await smtAlice.generateCircomVerifierProof(
|
|
130
|
-
inputUtxos[i].hash,
|
|
131
|
-
root,
|
|
132
|
-
);
|
|
133
|
-
mtps.push(p.siblings.map((s) => s.bigInt()));
|
|
134
|
-
}
|
|
135
|
-
const aliceUTXOsToBeWithdrawn = [
|
|
136
|
-
newUTXO(1, Alice),
|
|
137
|
-
newUTXO(1, Alice),
|
|
138
|
-
newUTXO(1, Alice),
|
|
139
|
-
];
|
|
140
|
-
// Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice
|
|
141
|
-
const _bOut1 = newUTXO(6, Bob);
|
|
142
|
-
const _bOut2 = newUTXO(1, Charlie);
|
|
143
|
-
|
|
144
|
-
const outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn];
|
|
145
|
-
const outputOwners = [Bob, Charlie, Alice, Alice, Alice];
|
|
146
|
-
const inflatedOutputUtxos = [...outputUtxos];
|
|
147
|
-
const inflatedOutputOwners = [...outputOwners];
|
|
148
|
-
for (let i = 0; i < 10 - outputUtxos.length; i++) {
|
|
149
|
-
inflatedOutputUtxos.push(ZERO_UTXO);
|
|
150
|
-
inflatedOutputOwners.push(Bob);
|
|
151
|
-
}
|
|
152
|
-
// Alice transfers her UTXOs to Bob
|
|
153
|
-
const result = await doTransfer(
|
|
154
|
-
Alice,
|
|
155
|
-
inputUtxos,
|
|
156
|
-
nullifiers,
|
|
157
|
-
inflatedOutputUtxos,
|
|
158
|
-
root.bigInt(),
|
|
159
|
-
mtps,
|
|
160
|
-
inflatedOutputOwners,
|
|
131
|
+
// H-2: implementation contracts must be initialization-locked so an
|
|
132
|
+
// attacker cannot call initialize() directly on the impl, become its
|
|
133
|
+
// owner, and then upgradeTo(any) via _authorizeUpgrade (the OZ
|
|
134
|
+
// "implementation takeover" pattern, CVE-2022-35961 family). We test
|
|
135
|
+
// against the *actual deployed* implementation (read from the EIP-1967
|
|
136
|
+
// impl slot on the proxy) so this asserts the production deployment
|
|
137
|
+
// path produces a locked impl, not just that a redeployed contract
|
|
138
|
+
// would be locked.
|
|
139
|
+
it("initialize() reverts on the bare Zeto_AnonNullifier implementation contract", async function () {
|
|
140
|
+
const implAddress = await upgrades.erc1967.getImplementationAddress(
|
|
141
|
+
await zeto.getAddress(),
|
|
161
142
|
);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
receivedSalt,
|
|
177
|
-
outputOwners[i].babyJubPublicKey[0],
|
|
178
|
-
outputOwners[i].babyJubPublicKey[1],
|
|
179
|
-
]);
|
|
180
|
-
expect(incomingUTXOs[i]).to.equal(hash);
|
|
181
|
-
await smtAlice.add(incomingUTXOs[i], incomingUTXOs[i]);
|
|
182
|
-
await smtBob.add(incomingUTXOs[i], incomingUTXOs[i]);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// check empty hashes are empty
|
|
186
|
-
for (let i = outputUtxos.length; i < 10; i++) {
|
|
187
|
-
expect(incomingUTXOs[i]).to.equal(0);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// mint sufficient balance in Zeto contract address for Alice to withdraw
|
|
191
|
-
const mintTx = await erc20.connect(deployer).mint(zeto, 3);
|
|
192
|
-
await mintTx.wait();
|
|
193
|
-
const startingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
194
|
-
|
|
195
|
-
// Alice generates the nullifiers for the UTXOs to be spent
|
|
196
|
-
root = await smtAlice.root();
|
|
197
|
-
const inflatedWithdrawNullifiers = [];
|
|
198
|
-
const inflatedWithdrawInputs = [];
|
|
199
|
-
const inflatedWithdrawMTPs = [];
|
|
200
|
-
for (let i = 0; i < aliceUTXOsToBeWithdrawn.length; i++) {
|
|
201
|
-
inflatedWithdrawInputs.push(aliceUTXOsToBeWithdrawn[i]);
|
|
202
|
-
inflatedWithdrawNullifiers.push(
|
|
203
|
-
newNullifier(aliceUTXOsToBeWithdrawn[i], Alice),
|
|
204
|
-
);
|
|
205
|
-
const _withdrawUTXOProof = await smtAlice.generateCircomVerifierProof(
|
|
206
|
-
aliceUTXOsToBeWithdrawn[i].hash,
|
|
207
|
-
root,
|
|
208
|
-
);
|
|
209
|
-
inflatedWithdrawMTPs.push(
|
|
210
|
-
_withdrawUTXOProof.siblings.map((s) => s.bigInt()),
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
214
|
-
|
|
215
|
-
for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) {
|
|
216
|
-
inflatedWithdrawInputs.push(ZERO_UTXO);
|
|
217
|
-
inflatedWithdrawNullifiers.push(ZERO_UTXO);
|
|
218
|
-
const _zeroProof = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
219
|
-
inflatedWithdrawMTPs.push(_zeroProof.siblings.map((s) => s.bigInt()));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const {
|
|
223
|
-
nullifiers: _withdrawNullifiers,
|
|
224
|
-
outputCommitments: withdrawCommitments,
|
|
225
|
-
encodedProof: withdrawEncodedProof,
|
|
226
|
-
} = await prepareNullifierWithdrawProof(
|
|
227
|
-
Alice,
|
|
228
|
-
inflatedWithdrawInputs,
|
|
229
|
-
inflatedWithdrawNullifiers,
|
|
230
|
-
ZERO_UTXO,
|
|
231
|
-
root.bigInt(),
|
|
232
|
-
inflatedWithdrawMTPs,
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
// Alice withdraws her UTXOs to ERC20 tokens
|
|
236
|
-
const tx = await zeto
|
|
237
|
-
.connect(Alice.signer)
|
|
238
|
-
.withdraw(
|
|
239
|
-
3,
|
|
240
|
-
_withdrawNullifiers,
|
|
241
|
-
withdrawCommitments[0],
|
|
242
|
-
root.bigInt(),
|
|
243
|
-
withdrawEncodedProof,
|
|
244
|
-
"0x",
|
|
245
|
-
);
|
|
246
|
-
await tx.wait();
|
|
247
|
-
|
|
248
|
-
// Alice checks her ERC20 balance
|
|
249
|
-
const endingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
250
|
-
expect(endingBalance - startingBalance).to.be.equal(3);
|
|
251
|
-
}).timeout(60000);
|
|
252
|
-
|
|
253
|
-
it("mint ERC20 tokens to Alice to deposit to Zeto should succeed", async function () {
|
|
254
|
-
const startingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
255
|
-
const tx = await erc20.connect(deployer).mint(Alice.ethAddress, 100);
|
|
256
|
-
await tx.wait();
|
|
257
|
-
const endingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
258
|
-
expect(endingBalance - startingBalance).to.be.equal(100);
|
|
259
|
-
|
|
260
|
-
const tx1 = await erc20.connect(Alice.signer).approve(zeto.target, 100);
|
|
261
|
-
await tx1.wait();
|
|
262
|
-
|
|
263
|
-
utxo100 = newUTXO(100, Alice);
|
|
264
|
-
const utxo0 = newUTXO(0, Alice);
|
|
265
|
-
const { outputCommitments, encodedProof } = await prepareDepositProof(
|
|
266
|
-
Alice,
|
|
267
|
-
[utxo0, utxo100],
|
|
268
|
-
);
|
|
269
|
-
const tx2 = await zeto
|
|
270
|
-
.connect(Alice.signer)
|
|
271
|
-
.deposit(100, outputCommitments, encodedProof, "0x");
|
|
272
|
-
await tx2.wait();
|
|
273
|
-
|
|
274
|
-
await smtAlice.add(utxo100.hash, utxo100.hash);
|
|
275
|
-
await smtAlice.add(utxo0.hash, utxo0.hash);
|
|
276
|
-
await smtBob.add(utxo100.hash, utxo100.hash);
|
|
277
|
-
await smtBob.add(utxo0.hash, utxo0.hash);
|
|
143
|
+
const impl = await ethers.getContractAt("Zeto_AnonNullifier", implAddress);
|
|
144
|
+
await expect(
|
|
145
|
+
impl.initialize("Z", "Z", await Alice.signer.getAddress(), {
|
|
146
|
+
verifier: ZeroAddress,
|
|
147
|
+
depositVerifier: ZeroAddress,
|
|
148
|
+
withdrawVerifier: ZeroAddress,
|
|
149
|
+
lockVerifier: ZeroAddress,
|
|
150
|
+
burnVerifier: ZeroAddress,
|
|
151
|
+
batchVerifier: ZeroAddress,
|
|
152
|
+
batchWithdrawVerifier: ZeroAddress,
|
|
153
|
+
batchLockVerifier: ZeroAddress,
|
|
154
|
+
batchBurnVerifier: ZeroAddress,
|
|
155
|
+
}),
|
|
156
|
+
).to.be.revertedWithCustomError(impl, "InvalidInitialization");
|
|
278
157
|
});
|
|
279
158
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// check the private mint activity is not exposed in the ERC20 contract
|
|
288
|
-
const afterMintBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
289
|
-
expect(afterMintBalance).to.equal(startingBalance);
|
|
290
|
-
|
|
291
|
-
// Alice locally tracks the UTXOs inside the Sparse Merkle Tree
|
|
292
|
-
// hardhat doesn't have a good way to subscribe to events so we have to parse the Tx result object
|
|
293
|
-
const mintEvents = parseUTXOEvents(zeto, result1);
|
|
294
|
-
const [_utxo1, _utxo2] = mintEvents[0].outputs;
|
|
295
|
-
await smtAlice.add(_utxo1, _utxo1);
|
|
296
|
-
await smtAlice.add(_utxo2, _utxo2);
|
|
297
|
-
let root = await smtAlice.root();
|
|
298
|
-
let onchainRoot = await zeto.getRoot();
|
|
299
|
-
expect(root.string()).to.equal(onchainRoot.toString());
|
|
300
|
-
// Bob also locally tracks the UTXOs inside the Sparse Merkle Tree
|
|
301
|
-
await smtBob.add(_utxo1, _utxo1);
|
|
302
|
-
await smtBob.add(_utxo2, _utxo2);
|
|
303
|
-
|
|
304
|
-
// Alice proposes the output UTXOs for the transfer to Bob
|
|
305
|
-
const _utxo3 = newUTXO(25, Bob);
|
|
306
|
-
utxo4 = newUTXO(5, Alice);
|
|
307
|
-
|
|
308
|
-
// Alice generates the nullifiers for the UTXOs to be spent
|
|
309
|
-
const nullifier1 = newNullifier(utxo1, Alice);
|
|
310
|
-
const nullifier2 = newNullifier(utxo2, Alice);
|
|
311
|
-
|
|
312
|
-
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
313
|
-
const proof1 = await smtAlice.generateCircomVerifierProof(utxo1.hash, root);
|
|
314
|
-
const proof2 = await smtAlice.generateCircomVerifierProof(utxo2.hash, root);
|
|
315
|
-
const merkleProofs = [
|
|
316
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
317
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
318
|
-
];
|
|
319
|
-
|
|
320
|
-
// Alice transfers her UTXOs to Bob
|
|
321
|
-
const result2 = await doTransfer(
|
|
322
|
-
Alice,
|
|
323
|
-
[utxo1, utxo2],
|
|
324
|
-
[nullifier1, nullifier2],
|
|
325
|
-
[_utxo3, utxo4],
|
|
326
|
-
root.bigInt(),
|
|
327
|
-
merkleProofs,
|
|
328
|
-
[Bob, Alice],
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
// check the private transfer activity is not exposed in the ERC20 contract
|
|
332
|
-
const afterTransferBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
333
|
-
expect(afterTransferBalance).to.equal(startingBalance);
|
|
334
|
-
|
|
335
|
-
// Alice locally tracks the UTXOs inside the Sparse Merkle Tree
|
|
336
|
-
await smtAlice.add(_utxo3.hash, _utxo3.hash);
|
|
337
|
-
await smtAlice.add(utxo4.hash, utxo4.hash);
|
|
338
|
-
root = await smtAlice.root();
|
|
339
|
-
onchainRoot = await zeto.getRoot();
|
|
340
|
-
expect(root.string()).to.equal(onchainRoot.toString());
|
|
341
|
-
|
|
342
|
-
// Bob locally tracks the UTXOs inside the Sparse Merkle Tree
|
|
343
|
-
// Bob parses the UTXOs from the onchain event
|
|
344
|
-
const signerAddress = await Alice.signer.getAddress();
|
|
345
|
-
const events = parseUTXOEvents(zeto, result2.txResult!);
|
|
346
|
-
expect(events[0].submitter).to.equal(signerAddress);
|
|
347
|
-
expect(events[0].inputs).to.deep.equal([nullifier1.hash, nullifier2.hash]);
|
|
348
|
-
expect(events[0].outputs).to.deep.equal([_utxo3.hash, utxo4.hash]);
|
|
349
|
-
await smtBob.add(events[0].outputs[0], events[0].outputs[0]);
|
|
350
|
-
await smtBob.add(events[0].outputs[1], events[0].outputs[1]);
|
|
351
|
-
|
|
352
|
-
// Bob uses the information received from Alice to reconstruct the UTXO sent to him
|
|
353
|
-
const receivedValue = _utxo3.value!;
|
|
354
|
-
const receivedSalt = _utxo3.salt;
|
|
355
|
-
const incomingUTXOs: any = events[0].outputs;
|
|
356
|
-
const hash = Poseidon.poseidon4([
|
|
357
|
-
BigInt(receivedValue),
|
|
358
|
-
receivedSalt,
|
|
359
|
-
Bob.babyJubPublicKey[0],
|
|
360
|
-
Bob.babyJubPublicKey[1],
|
|
361
|
-
]);
|
|
362
|
-
expect(incomingUTXOs[0]).to.equal(hash);
|
|
363
|
-
|
|
364
|
-
// Bob uses the received values to construct the UTXO received from the transaction
|
|
365
|
-
utxo3 = newUTXO(receivedValue, Bob, receivedSalt);
|
|
366
|
-
}).timeout(600000);
|
|
367
|
-
|
|
368
|
-
it("Bob transfers UTXOs, previously received from Alice, honestly to Charlie should succeed", async function () {
|
|
369
|
-
// Bob generates the nullifiers for the UTXO to be spent
|
|
370
|
-
const nullifier1 = newNullifier(utxo3, Bob);
|
|
371
|
-
|
|
372
|
-
// Bob generates inclusion proofs for the UTXOs to be spent, as private input to the proof generation
|
|
373
|
-
const root = await smtBob.root();
|
|
374
|
-
const proof1 = await smtBob.generateCircomVerifierProof(utxo3.hash, root);
|
|
375
|
-
const proof2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
376
|
-
const merkleProofs = [
|
|
377
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
378
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
379
|
-
];
|
|
380
|
-
|
|
381
|
-
// Bob proposes the output UTXOs
|
|
382
|
-
const utxo6 = newUTXO(10, Charlie);
|
|
383
|
-
utxo7 = newUTXO(15, Bob);
|
|
384
|
-
|
|
385
|
-
// Bob should be able to spend the UTXO that was reconstructed from the previous transaction
|
|
386
|
-
const result = await doTransfer(
|
|
387
|
-
Bob,
|
|
388
|
-
[utxo3, ZERO_UTXO],
|
|
389
|
-
[nullifier1, ZERO_UTXO],
|
|
390
|
-
[utxo6, utxo7],
|
|
391
|
-
root.bigInt(),
|
|
392
|
-
merkleProofs,
|
|
393
|
-
[Charlie, Bob],
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
// Bob keeps the local SMT in sync
|
|
397
|
-
await smtBob.add(utxo6.hash, utxo6.hash);
|
|
398
|
-
await smtBob.add(utxo7.hash, utxo7.hash);
|
|
399
|
-
|
|
400
|
-
// Alice gets the new UTXOs from the onchain event and keeps the local SMT in sync
|
|
401
|
-
const events = parseUTXOEvents(zeto, result.txResult!);
|
|
402
|
-
await smtAlice.add(events[0].outputs[0], events[0].outputs[0]);
|
|
403
|
-
await smtAlice.add(events[0].outputs[1], events[0].outputs[1]);
|
|
404
|
-
}).timeout(600000);
|
|
159
|
+
describe("batch transfers", () => {
|
|
160
|
+
let inputUtxos: UTXO[];
|
|
161
|
+
let nullifiers: UTXO[];
|
|
162
|
+
let outputUtxos: UTXO[];
|
|
163
|
+
let outputOwners: User[];
|
|
164
|
+
let aliceUTXOsToBeWithdrawn: UTXO[];
|
|
165
|
+
let txResult: any;
|
|
405
166
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// Alice generates the nullifiers for the UTXOs to be spent
|
|
410
|
-
const nullifier1 = newNullifier(utxo100, Alice);
|
|
411
|
-
|
|
412
|
-
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
413
|
-
let root = await smtAlice.root();
|
|
414
|
-
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
415
|
-
utxo100.hash,
|
|
416
|
-
root,
|
|
417
|
-
);
|
|
418
|
-
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
419
|
-
const merkleProofs = [
|
|
420
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
421
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
422
|
-
];
|
|
167
|
+
beforeEach(async function () {
|
|
168
|
+
// this.skip();
|
|
169
|
+
});
|
|
423
170
|
|
|
424
|
-
|
|
425
|
-
|
|
171
|
+
it("mint 10 UTXOs to Alice", async function () {
|
|
172
|
+
// first mint the tokens for batch testing
|
|
173
|
+
inputUtxos = [];
|
|
174
|
+
nullifiers = [];
|
|
175
|
+
for (let i = 0; i < 10; i++) {
|
|
176
|
+
// mint 10 utxos
|
|
177
|
+
const _utxo = newUTXO(1, Alice);
|
|
178
|
+
nullifiers.push(newNullifier(_utxo, Alice));
|
|
179
|
+
inputUtxos.push(_utxo);
|
|
180
|
+
}
|
|
181
|
+
const mintResult = await doMint(zeto, deployer, inputUtxos);
|
|
182
|
+
|
|
183
|
+
const mintEvents = parseUTXOEvents(zeto, mintResult);
|
|
184
|
+
const mintedHashes = mintEvents[0].outputs;
|
|
185
|
+
for (let i = 0; i < mintedHashes.length; i++) {
|
|
186
|
+
if (mintedHashes[i] !== 0) {
|
|
187
|
+
await smtAlice.add(mintedHashes[i], mintedHashes[i]);
|
|
188
|
+
await smtBob.add(mintedHashes[i], mintedHashes[i]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
426
192
|
|
|
427
|
-
|
|
428
|
-
|
|
193
|
+
it("Alice transfers some UTXOs to Bob and Charlie", async function () {
|
|
194
|
+
// Batched proof generation routinely runs ~38–40s on a warm machine;
|
|
195
|
+
// the mocha default of 40s flakes on cold starts. Mirror the explicit
|
|
196
|
+
// timeout already used for the batch-withdraw it() below.
|
|
197
|
+
this.timeout(60000);
|
|
198
|
+
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
199
|
+
let root = await smtAlice.root();
|
|
200
|
+
const mtps = [];
|
|
201
|
+
for (let i = 0; i < inputUtxos.length; i++) {
|
|
202
|
+
const p = await smtAlice.generateCircomVerifierProof(
|
|
203
|
+
inputUtxos[i].hash,
|
|
204
|
+
root,
|
|
205
|
+
);
|
|
206
|
+
mtps.push(p.siblings.map((s) => s.bigInt()));
|
|
207
|
+
}
|
|
208
|
+
aliceUTXOsToBeWithdrawn = [
|
|
209
|
+
newUTXO(1, Alice),
|
|
210
|
+
newUTXO(1, Alice),
|
|
211
|
+
newUTXO(1, Alice),
|
|
212
|
+
];
|
|
213
|
+
// Alice proposes the output UTXOs, 1 utxo to bob, 1 utxo to charlie and 3 utxos to alice
|
|
214
|
+
const _bOut1 = newUTXO(6, Bob);
|
|
215
|
+
const _bOut2 = newUTXO(1, Charlie);
|
|
216
|
+
|
|
217
|
+
outputUtxos = [_bOut1, _bOut2, ...aliceUTXOsToBeWithdrawn];
|
|
218
|
+
outputOwners = [Bob, Charlie, Alice, Alice, Alice];
|
|
219
|
+
// Alice transfers her UTXOs to Bob
|
|
220
|
+
txResult = await doTransfer(
|
|
429
221
|
Alice,
|
|
430
|
-
|
|
431
|
-
[nullifier1, ZERO_UTXO],
|
|
432
|
-
withdrawChangesUTXO,
|
|
433
|
-
root.bigInt(),
|
|
434
|
-
merkleProofs,
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
// Alice withdraws her UTXOs to ERC20 tokens
|
|
438
|
-
const tx = await zeto
|
|
439
|
-
.connect(Alice.signer)
|
|
440
|
-
.withdraw(
|
|
441
|
-
80,
|
|
222
|
+
inputUtxos,
|
|
442
223
|
nullifiers,
|
|
443
|
-
|
|
224
|
+
outputUtxos,
|
|
444
225
|
root.bigInt(),
|
|
445
|
-
|
|
446
|
-
|
|
226
|
+
mtps,
|
|
227
|
+
outputOwners,
|
|
447
228
|
);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
// Alice tracks the UTXO inside the SMT
|
|
451
|
-
await smtAlice.add(withdrawChangesUTXO.hash, withdrawChangesUTXO.hash);
|
|
452
|
-
// Bob also locally tracks the UTXOs inside the SMT
|
|
453
|
-
await smtBob.add(withdrawChangesUTXO.hash, withdrawChangesUTXO.hash);
|
|
454
|
-
|
|
455
|
-
// Alice checks her ERC20 balance
|
|
456
|
-
const endingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
457
|
-
expect(endingBalance - startingBalance).to.be.equal(80);
|
|
458
|
-
});
|
|
229
|
+
});
|
|
459
230
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
231
|
+
it("check the transfer is successful", async function () {
|
|
232
|
+
const signerAddress = await Alice.signer.getAddress();
|
|
233
|
+
const events = parseUTXOEvents(zeto, txResult);
|
|
234
|
+
expect(events[0].submitter).to.equal(signerAddress);
|
|
235
|
+
expect(events[0].inputs).to.deep.equal(nullifiers.map((n) => n.hash));
|
|
236
|
+
|
|
237
|
+
const incomingUTXOs: any = events[0].outputs;
|
|
238
|
+
// check the non-empty output hashes are correct
|
|
239
|
+
for (let i = 0; i < outputUtxos.length; i++) {
|
|
240
|
+
// Bob uses the information received from Alice to reconstruct the UTXO sent to him
|
|
241
|
+
const receivedValue = outputUtxos[i].value;
|
|
242
|
+
const receivedSalt = outputUtxos[i].salt;
|
|
243
|
+
const hash = Poseidon.poseidon4([
|
|
244
|
+
BigInt(receivedValue),
|
|
245
|
+
receivedSalt,
|
|
246
|
+
outputOwners[i].babyJubPublicKey[0],
|
|
247
|
+
outputOwners[i].babyJubPublicKey[1],
|
|
248
|
+
]);
|
|
249
|
+
expect(incomingUTXOs[i]).to.equal(hash);
|
|
250
|
+
await smtAlice.add(incomingUTXOs[i], incomingUTXOs[i]);
|
|
251
|
+
await smtBob.add(incomingUTXOs[i], incomingUTXOs[i]);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
471
254
|
|
|
472
|
-
|
|
473
|
-
|
|
255
|
+
it("Alice withdraws her UTXOs to ERC20 tokens", async function () {
|
|
256
|
+
// mint sufficient balance in Zeto contract address for Alice to withdraw
|
|
257
|
+
const mintTx = await erc20.connect(deployer).mint(zeto, 3);
|
|
258
|
+
await mintTx.wait();
|
|
259
|
+
const startingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
474
260
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
261
|
+
// Alice generates the nullifiers for the UTXOs to be spent
|
|
262
|
+
const root = await smtAlice.root();
|
|
263
|
+
const inflatedWithdrawNullifiers = [];
|
|
264
|
+
const inflatedWithdrawInputs = [];
|
|
265
|
+
const inflatedWithdrawMTPs = [];
|
|
266
|
+
for (let i = 0; i < aliceUTXOsToBeWithdrawn.length; i++) {
|
|
267
|
+
inflatedWithdrawInputs.push(aliceUTXOsToBeWithdrawn[i]);
|
|
268
|
+
inflatedWithdrawNullifiers.push(
|
|
269
|
+
newNullifier(aliceUTXOsToBeWithdrawn[i], Alice),
|
|
270
|
+
);
|
|
271
|
+
const _withdrawUTXOProof = await smtAlice.generateCircomVerifierProof(
|
|
272
|
+
aliceUTXOsToBeWithdrawn[i].hash,
|
|
273
|
+
root,
|
|
274
|
+
);
|
|
275
|
+
inflatedWithdrawMTPs.push(
|
|
276
|
+
_withdrawUTXOProof.siblings.map((s) => s.bigInt()),
|
|
277
|
+
);
|
|
480
278
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
484
|
-
let root = await smtAliceBurnable.root();
|
|
485
|
-
const mtps = [];
|
|
486
|
-
for (let i = 0; i < inputUtxos.length; i++) {
|
|
487
|
-
const p = await smtAliceBurnable.generateCircomVerifierProof(
|
|
488
|
-
inputUtxos[i].hash,
|
|
489
|
-
root,
|
|
490
|
-
);
|
|
491
|
-
mtps.push(p.siblings.map((s) => s.bigInt()));
|
|
492
|
-
}
|
|
279
|
+
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
493
280
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
Alice,
|
|
501
|
-
inputUtxos,
|
|
502
|
-
outputUtxo,
|
|
503
|
-
nullifiers,
|
|
504
|
-
root.bigInt(),
|
|
505
|
-
mtps,
|
|
506
|
-
);
|
|
281
|
+
for (let i = aliceUTXOsToBeWithdrawn.length; i < 10; i++) {
|
|
282
|
+
inflatedWithdrawInputs.push(ZERO_UTXO);
|
|
283
|
+
inflatedWithdrawNullifiers.push(ZERO_UTXO);
|
|
284
|
+
const _zeroProof = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
285
|
+
inflatedWithdrawMTPs.push(_zeroProof.siblings.map((s) => s.bigInt()));
|
|
286
|
+
}
|
|
507
287
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
288
|
+
const {
|
|
289
|
+
nullifiers: _withdrawNullifiers,
|
|
290
|
+
outputCommitments: withdrawCommitments,
|
|
291
|
+
encodedProof: withdrawEncodedProof,
|
|
292
|
+
} = await prepareNullifierWithdrawProof(
|
|
293
|
+
Alice,
|
|
294
|
+
inflatedWithdrawInputs,
|
|
295
|
+
inflatedWithdrawNullifiers,
|
|
296
|
+
ZERO_UTXO,
|
|
514
297
|
root.bigInt(),
|
|
515
|
-
|
|
516
|
-
"0x",
|
|
298
|
+
inflatedWithdrawMTPs,
|
|
517
299
|
);
|
|
518
|
-
await tx.wait();
|
|
519
|
-
}).timeout(60000);
|
|
520
|
-
|
|
521
|
-
describe("lock() tests", function () {
|
|
522
|
-
let lockedUtxo1: UTXO;
|
|
523
|
-
let smtBobForLocked: Merkletree;
|
|
524
|
-
let utxo10: UTXO;
|
|
525
|
-
let utxo11: UTXO;
|
|
526
|
-
let utxo12: UTXO;
|
|
527
300
|
|
|
528
|
-
|
|
529
|
-
const
|
|
530
|
-
|
|
301
|
+
// Alice withdraws her UTXOs to ERC20 tokens
|
|
302
|
+
const tx = await zeto
|
|
303
|
+
.connect(Alice.signer)
|
|
304
|
+
.withdraw(
|
|
305
|
+
3,
|
|
306
|
+
_withdrawNullifiers,
|
|
307
|
+
withdrawCommitments[0],
|
|
308
|
+
encodeToBytesForWithdraw(root.bigInt(), withdrawEncodedProof),
|
|
309
|
+
"0x",
|
|
310
|
+
);
|
|
311
|
+
const result1 = await tx.wait();
|
|
312
|
+
logger.debug(`Method withdraw() complete. Gas used: ${result1?.gasUsed}`);
|
|
531
313
|
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
314
|
+
// Alice checks her ERC20 balance
|
|
315
|
+
const endingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
316
|
+
expect(endingBalance - startingBalance).to.be.equal(3);
|
|
317
|
+
}).timeout(60000);
|
|
318
|
+
});
|
|
537
319
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
542
|
-
utxo12.hash,
|
|
543
|
-
root,
|
|
544
|
-
);
|
|
545
|
-
const proof2 = await smtAlice.generateCircomVerifierProof(
|
|
546
|
-
utxo12.hash,
|
|
547
|
-
root,
|
|
548
|
-
);
|
|
549
|
-
const merkleProofs = [
|
|
550
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
551
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
552
|
-
];
|
|
320
|
+
describe("mint, deposit, transfer, withdraw flows", () => {
|
|
321
|
+
let aliceUtxo30: UTXO;
|
|
322
|
+
let aliceUtxo70: UTXO;
|
|
553
323
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
[utxo12, ZERO_UTXO],
|
|
557
|
-
[nullifier1, ZERO_UTXO],
|
|
558
|
-
[_utxo1, ZERO_UTXO],
|
|
559
|
-
root.bigInt(),
|
|
560
|
-
merkleProofs,
|
|
561
|
-
[Bob, Alice],
|
|
562
|
-
);
|
|
563
|
-
await smtAlice.add(_utxo1.hash, _utxo1.hash);
|
|
564
|
-
await smtBob.add(_utxo1.hash, _utxo1.hash);
|
|
324
|
+
beforeEach(async function () {
|
|
325
|
+
// this.skip();
|
|
565
326
|
});
|
|
566
327
|
|
|
567
|
-
describe("
|
|
568
|
-
it("
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
);
|
|
576
|
-
const proof2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
577
|
-
const merkleProofs = [
|
|
578
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
579
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
580
|
-
];
|
|
581
|
-
const { outputCommitments, encodedProof } = await prepareProof(
|
|
582
|
-
circuit,
|
|
583
|
-
provingKey,
|
|
584
|
-
Bob,
|
|
585
|
-
[utxo7, ZERO_UTXO],
|
|
586
|
-
[nullifier1, ZERO_UTXO],
|
|
587
|
-
[lockedUtxo1, ZERO_UTXO],
|
|
588
|
-
root.bigInt(),
|
|
589
|
-
merkleProofs,
|
|
590
|
-
[Bob, Bob],
|
|
591
|
-
);
|
|
328
|
+
describe("Shielding ERC20 tokens to Zeto privacy tokens", async function () {
|
|
329
|
+
it("mint ERC20 tokens to Alice", async function () {
|
|
330
|
+
const startingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
331
|
+
const tx = await erc20.connect(deployer).mint(Alice.ethAddress, 100);
|
|
332
|
+
await tx.wait();
|
|
333
|
+
const endingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
334
|
+
expect(endingBalance - startingBalance).to.be.equal(100);
|
|
335
|
+
});
|
|
592
336
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
root.bigInt(),
|
|
598
|
-
encodedProof,
|
|
599
|
-
Alice.ethAddress, // make Alice the delegate who can spend the state (if she has the right proof)
|
|
600
|
-
"0x",
|
|
601
|
-
);
|
|
602
|
-
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
337
|
+
it("Alice approves the Zeto contract to spend her ERC20 tokens, to prepare for the deposit", async function () {
|
|
338
|
+
const tx1 = await erc20.connect(Alice.signer).approve(zeto.target, 100);
|
|
339
|
+
await tx1.wait();
|
|
340
|
+
});
|
|
603
341
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
await
|
|
608
|
-
|
|
609
|
-
|
|
342
|
+
it("Alice deposits her ERC20 tokens to Zeto, and get shielded UTXOs in return", async function () {
|
|
343
|
+
aliceUtxo30 = newUTXO(30, Alice);
|
|
344
|
+
aliceUtxo70 = newUTXO(70, Alice);
|
|
345
|
+
const { outputCommitments, encodedProof } = await prepareDepositProof(
|
|
346
|
+
Alice,
|
|
347
|
+
[aliceUtxo30, aliceUtxo70],
|
|
610
348
|
);
|
|
349
|
+
const tx2 = await zeto
|
|
350
|
+
.connect(Alice.signer)
|
|
351
|
+
.deposit(
|
|
352
|
+
100,
|
|
353
|
+
outputCommitments,
|
|
354
|
+
encodeToBytesForDeposit(encodedProof),
|
|
355
|
+
"0x",
|
|
356
|
+
);
|
|
357
|
+
const result = await tx2.wait();
|
|
358
|
+
logger.debug(`Method deposit() complete. Gas used: ${result?.gasUsed}`);
|
|
359
|
+
|
|
360
|
+
await smtAlice.add(aliceUtxo30.hash, aliceUtxo30.hash);
|
|
361
|
+
await smtAlice.add(aliceUtxo70.hash, aliceUtxo70.hash);
|
|
362
|
+
await smtBob.add(aliceUtxo30.hash, aliceUtxo30.hash);
|
|
363
|
+
await smtBob.add(aliceUtxo70.hash, aliceUtxo70.hash);
|
|
611
364
|
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("Transferring privacy tokens", async function () {
|
|
368
|
+
let value1: any;
|
|
369
|
+
let salt1: bigint;
|
|
370
|
+
let transferEvent: any;
|
|
612
371
|
|
|
613
|
-
it("onchain SMT root for the
|
|
614
|
-
const
|
|
615
|
-
const
|
|
616
|
-
expect(
|
|
372
|
+
it("Check the onchain SMT root for the unlocked UTXOs should be equal to the offchain SMT root", async function () {
|
|
373
|
+
const onchainRoot = await zeto.getRoot();
|
|
374
|
+
const aliceRoot = await smtAlice.root();
|
|
375
|
+
expect(aliceRoot.string()).to.equal(onchainRoot.toString());
|
|
376
|
+
const bobRoot = await smtBob.root();
|
|
377
|
+
expect(bobRoot.string()).to.equal(onchainRoot.toString());
|
|
617
378
|
});
|
|
618
379
|
|
|
619
|
-
it("
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
380
|
+
it("Alice transfers her privacy tokens to Bob", async function () {
|
|
381
|
+
// Alice proposes the output UTXOs for the transfer to Bob
|
|
382
|
+
// 25 to Bob
|
|
383
|
+
const _utxo1 = newUTXO(25, Bob);
|
|
384
|
+
// 5 back to Alice as change
|
|
385
|
+
const _utxo2 = newUTXO(5, Alice);
|
|
623
386
|
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
);
|
|
633
|
-
const
|
|
634
|
-
|
|
387
|
+
// Alice will share these secrets with Bob when she performs the transfer
|
|
388
|
+
value1 = _utxo1.value!;
|
|
389
|
+
salt1 = _utxo1.salt!;
|
|
390
|
+
|
|
391
|
+
// Alice generates the nullifiers for the UTXOs to be spent
|
|
392
|
+
const nullifier1 = newNullifier(aliceUtxo30, Alice);
|
|
393
|
+
|
|
394
|
+
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
395
|
+
const root = await smtAlice.root();
|
|
396
|
+
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
397
|
+
aliceUtxo30.hash,
|
|
635
398
|
root,
|
|
636
399
|
);
|
|
400
|
+
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
637
401
|
const merkleProofs = [
|
|
638
402
|
proof1.siblings.map((s) => s.bigInt()),
|
|
639
403
|
proof2.siblings.map((s) => s.bigInt()),
|
|
640
404
|
];
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
[
|
|
405
|
+
|
|
406
|
+
// Alice transfers her UTXOs to Bob
|
|
407
|
+
const result2 = await doTransfer(
|
|
408
|
+
Alice,
|
|
409
|
+
[aliceUtxo30, ZERO_UTXO],
|
|
646
410
|
[nullifier1, ZERO_UTXO],
|
|
647
|
-
[
|
|
411
|
+
[_utxo1, _utxo2],
|
|
648
412
|
root.bigInt(),
|
|
649
413
|
merkleProofs,
|
|
650
|
-
[Bob,
|
|
651
|
-
Alice.ethAddress,
|
|
414
|
+
[Bob, Alice],
|
|
652
415
|
);
|
|
653
416
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
.lock(
|
|
658
|
-
[nullifier1.hash],
|
|
659
|
-
[],
|
|
660
|
-
outputCommitments,
|
|
661
|
-
root.bigInt(),
|
|
662
|
-
encodedProof,
|
|
663
|
-
Charlie.ethAddress,
|
|
664
|
-
"0x",
|
|
665
|
-
),
|
|
666
|
-
).to.be.rejectedWith("UTXORootNotFound");
|
|
667
|
-
});
|
|
417
|
+
// Alice locally tracks the UTXOs inside the Sparse Merkle Tree
|
|
418
|
+
await smtAlice.add(_utxo1.hash, _utxo1.hash);
|
|
419
|
+
await smtAlice.add(_utxo2.hash, _utxo2.hash);
|
|
668
420
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
lockedUtxo1.hash,
|
|
675
|
-
root,
|
|
676
|
-
);
|
|
677
|
-
const proof2 = await smtBobForLocked.generateCircomVerifierProof(
|
|
678
|
-
0n,
|
|
679
|
-
root,
|
|
680
|
-
);
|
|
681
|
-
const merkleProofs = [
|
|
682
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
683
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
684
|
-
];
|
|
685
|
-
// Bob proposes the output UTXOs, attempting to transfer the locked UTXO to Charlie
|
|
686
|
-
utxo9 = newUTXO(15, Charlie);
|
|
421
|
+
const events = parseUTXOEvents(zeto, result2);
|
|
422
|
+
transferEvent = events[0];
|
|
423
|
+
const signerAddress = await Alice.signer.getAddress();
|
|
424
|
+
expect(transferEvent.submitter).to.equal(signerAddress);
|
|
425
|
+
});
|
|
687
426
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
427
|
+
describe("Bob can spend the tokens received from Alice", async function () {
|
|
428
|
+
let bobUtxo25: UTXO;
|
|
429
|
+
let transferEventToCharlie: any;
|
|
430
|
+
|
|
431
|
+
it("Bob locally tracks the new UTXOs inside the Sparse Merkle Tree", async function () {
|
|
432
|
+
// Bob parses the UTXOs from the onchain event
|
|
433
|
+
// and add them to the local SMT
|
|
434
|
+
await smtBob.add(transferEvent.outputs[0], transferEvent.outputs[0]);
|
|
435
|
+
await smtBob.add(transferEvent.outputs[1], transferEvent.outputs[1]);
|
|
436
|
+
|
|
437
|
+
// Bob uses the received values to construct the UTXO received from the transaction
|
|
438
|
+
bobUtxo25 = newUTXO(value1, Bob, salt1);
|
|
439
|
+
// Bob verifies the UTXO is valid onchain
|
|
440
|
+
expect(bobUtxo25.hash).to.equal(transferEvent.outputs[0]);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("Bob transfers UTXOs, previously received from Alice, honestly to Charlie should succeed", async function () {
|
|
444
|
+
// Bob generates the nullifiers for the UTXO to be spent
|
|
445
|
+
const nullifier1 = newNullifier(bobUtxo25, Bob);
|
|
446
|
+
|
|
447
|
+
// Bob generates inclusion proofs for the UTXOs to be spent, as private input to the proof generation
|
|
448
|
+
const root = await smtBob.root();
|
|
449
|
+
const proof1 = await smtBob.generateCircomVerifierProof(
|
|
450
|
+
bobUtxo25.hash,
|
|
451
|
+
root,
|
|
452
|
+
);
|
|
453
|
+
const proof2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
454
|
+
const merkleProofs = [
|
|
455
|
+
proof1.siblings.map((s) => s.bigInt()),
|
|
456
|
+
proof2.siblings.map((s) => s.bigInt()),
|
|
457
|
+
];
|
|
458
|
+
|
|
459
|
+
// Bob proposes the output UTXOs
|
|
460
|
+
const _utxo1 = newUTXO(10, Charlie);
|
|
461
|
+
const _utxo2 = newUTXO(15, Bob);
|
|
462
|
+
|
|
463
|
+
// Bob should be able to spend the UTXO that was reconstructed from the previous transaction
|
|
464
|
+
const result = await doTransfer(
|
|
691
465
|
Bob,
|
|
692
|
-
[
|
|
466
|
+
[bobUtxo25, ZERO_UTXO],
|
|
693
467
|
[nullifier1, ZERO_UTXO],
|
|
694
|
-
[
|
|
468
|
+
[_utxo1, _utxo2],
|
|
695
469
|
root.bigInt(),
|
|
696
470
|
merkleProofs,
|
|
697
471
|
[Charlie, Bob],
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
// Bob keeps the local SMT in sync
|
|
475
|
+
await smtBob.add(_utxo1.hash, _utxo1.hash);
|
|
476
|
+
await smtBob.add(_utxo2.hash, _utxo2.hash);
|
|
477
|
+
|
|
478
|
+
const events = parseUTXOEvents(zeto, result);
|
|
479
|
+
transferEventToCharlie = events[0];
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("Alice gets the new UTXOs from the onchain event and keeps the local SMT in sync", async function () {
|
|
483
|
+
await smtAlice.add(
|
|
484
|
+
transferEventToCharlie.outputs[0],
|
|
485
|
+
transferEventToCharlie.outputs[0],
|
|
486
|
+
);
|
|
487
|
+
await smtAlice.add(
|
|
488
|
+
transferEventToCharlie.outputs[1],
|
|
489
|
+
transferEventToCharlie.outputs[1],
|
|
490
|
+
);
|
|
491
|
+
});
|
|
701
492
|
});
|
|
493
|
+
});
|
|
702
494
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
|
|
495
|
+
describe("Alice withdraws her UTXOs back to ERC20 tokens", async function () {
|
|
496
|
+
let startingBalance: any;
|
|
497
|
+
let withdrawEvent: any;
|
|
498
|
+
|
|
499
|
+
it("Alice withdraws her UTXOs to ERC20 tokens should succeed", async function () {
|
|
500
|
+
startingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
501
|
+
|
|
502
|
+
// Alice generates the nullifiers for the UTXOs to be spent
|
|
503
|
+
const nullifier1 = newNullifier(aliceUtxo70, Alice);
|
|
504
|
+
|
|
505
|
+
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
506
|
+
let root = await smtAlice.root();
|
|
507
|
+
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
508
|
+
aliceUtxo70.hash,
|
|
713
509
|
root,
|
|
714
510
|
);
|
|
511
|
+
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
715
512
|
const merkleProofs = [
|
|
716
513
|
proof1.siblings.map((s) => s.bigInt()),
|
|
717
514
|
proof2.siblings.map((s) => s.bigInt()),
|
|
718
515
|
];
|
|
719
516
|
|
|
720
|
-
|
|
517
|
+
// Alice proposes the output ERC20 tokens
|
|
518
|
+
const withdrawChangesUTXO = newUTXO(20, Alice);
|
|
721
519
|
|
|
722
|
-
|
|
723
|
-
prepareNullifierWithdrawProof(
|
|
724
|
-
|
|
725
|
-
[
|
|
520
|
+
const { nullifiers, outputCommitments, encodedProof } =
|
|
521
|
+
await prepareNullifierWithdrawProof(
|
|
522
|
+
Alice,
|
|
523
|
+
[aliceUtxo70, ZERO_UTXO],
|
|
726
524
|
[nullifier1, ZERO_UTXO],
|
|
727
|
-
|
|
525
|
+
withdrawChangesUTXO,
|
|
728
526
|
root.bigInt(),
|
|
729
527
|
merkleProofs,
|
|
730
|
-
)
|
|
731
|
-
).to.be.rejectedWith("SMTVerifier_249 line: 134");
|
|
732
|
-
});
|
|
528
|
+
);
|
|
733
529
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
530
|
+
// Alice withdraws her UTXOs to ERC20 tokens
|
|
531
|
+
const tx = await zeto
|
|
532
|
+
.connect(Alice.signer)
|
|
533
|
+
.withdraw(
|
|
534
|
+
50,
|
|
535
|
+
nullifiers,
|
|
536
|
+
outputCommitments[0],
|
|
537
|
+
encodeToBytesForWithdraw(root.bigInt(), encodedProof),
|
|
538
|
+
"0x",
|
|
539
|
+
);
|
|
540
|
+
const result = await tx.wait();
|
|
541
|
+
logger.debug(
|
|
542
|
+
`Method withdraw() complete. Gas used: ${result?.gasUsed}`,
|
|
741
543
|
);
|
|
742
|
-
|
|
743
|
-
|
|
544
|
+
|
|
545
|
+
// Alice tracks the UTXO inside the SMT
|
|
546
|
+
await smtAlice.add(withdrawChangesUTXO.hash, withdrawChangesUTXO.hash);
|
|
547
|
+
const events = parseUTXOEvents(zeto, result);
|
|
548
|
+
withdrawEvent = events[1];
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("Bob also locally tracks the new UTXOs from the withdraw event inside the SMT", async function () {
|
|
552
|
+
await smtBob.add(withdrawEvent.output, withdrawEvent.output);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it("Alice checks her ERC20 balance", async function () {
|
|
556
|
+
const endingBalance = await erc20.balanceOf(Alice.ethAddress);
|
|
557
|
+
expect(endingBalance - startingBalance).to.be.equal(50);
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
describe("ILockableCapability tests", function () {
|
|
563
|
+
// ABI fragments for the ZetoLockableCapability *Args payloads.
|
|
564
|
+
const CREATE_ARGS_ABI =
|
|
565
|
+
"tuple(bytes32 txId, uint256[] inputs, uint256[] outputs, uint256[] lockedOutputs, bytes proof)";
|
|
566
|
+
const UPDATE_ARGS_ABI = "tuple(bytes32 txId)";
|
|
567
|
+
const DELEGATE_ARGS_ABI = "tuple(bytes32 txId)";
|
|
568
|
+
const SPEND_ARGS_ABI =
|
|
569
|
+
"tuple(bytes32 txId, uint256[] lockedOutputs, uint256[] outputs, bytes proof, bytes data)";
|
|
570
|
+
|
|
571
|
+
function encodeCreateArgs(args: {
|
|
572
|
+
txId: string;
|
|
573
|
+
inputs: BigNumberish[];
|
|
574
|
+
outputs: BigNumberish[];
|
|
575
|
+
lockedOutputs: BigNumberish[];
|
|
576
|
+
proof: string;
|
|
577
|
+
}) {
|
|
578
|
+
return new AbiCoder().encode([CREATE_ARGS_ABI], [args]);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function encodeUpdateArgs(txId: string) {
|
|
582
|
+
return new AbiCoder().encode([UPDATE_ARGS_ABI], [{ txId }]);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function encodeDelegateArgs(txId: string) {
|
|
586
|
+
return new AbiCoder().encode([DELEGATE_ARGS_ABI], [{ txId }]);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function encodeSpendArgs(args: {
|
|
590
|
+
txId: string;
|
|
591
|
+
lockedOutputs: BigNumberish[];
|
|
592
|
+
outputs: BigNumberish[];
|
|
593
|
+
proof: string;
|
|
594
|
+
data: string;
|
|
595
|
+
}) {
|
|
596
|
+
return new AbiCoder().encode([SPEND_ARGS_ABI], [args]);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function randomBytes32(): string {
|
|
600
|
+
return ethers.hexlify(ethers.randomBytes(32));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
beforeEach(async function () {
|
|
604
|
+
// this.skip();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
describe("createLock -> updateLock -> delegateLock -> spendLock flow", function () {
|
|
608
|
+
let bobUtxo1: UTXO;
|
|
609
|
+
let aliceUtxo1: UTXO;
|
|
610
|
+
let lockedUtxo1: UTXO;
|
|
611
|
+
let lockId: string;
|
|
612
|
+
let outputUtxo1: UTXO;
|
|
613
|
+
let outputUtxo2: UTXO;
|
|
614
|
+
let unlockHash: string;
|
|
615
|
+
|
|
616
|
+
before(async function () {
|
|
617
|
+
bobUtxo1 = newUTXO(100, Bob);
|
|
618
|
+
await doMint(zeto, deployer, [bobUtxo1]);
|
|
619
|
+
await smtAlice.add(bobUtxo1.hash, bobUtxo1.hash);
|
|
620
|
+
await smtBob.add(bobUtxo1.hash, bobUtxo1.hash);
|
|
621
|
+
|
|
622
|
+
aliceUtxo1 = newUTXO(100, Alice);
|
|
623
|
+
await doMint(zeto, deployer, [aliceUtxo1]);
|
|
624
|
+
await smtAlice.add(aliceUtxo1.hash, aliceUtxo1.hash);
|
|
625
|
+
await smtBob.add(aliceUtxo1.hash, aliceUtxo1.hash);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it("createLock() with deterministic lockId computed from txId", async function () {
|
|
629
|
+
const nullifier1 = newNullifier(bobUtxo1, Bob);
|
|
630
|
+
lockedUtxo1 = newUTXO(bobUtxo1.value!, Bob);
|
|
631
|
+
const root = await smtBob.root();
|
|
632
|
+
const p1 = await smtBob.generateCircomVerifierProof(
|
|
633
|
+
bobUtxo1.hash,
|
|
744
634
|
root,
|
|
745
635
|
);
|
|
636
|
+
const p2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
746
637
|
const merkleProofs = [
|
|
747
|
-
|
|
748
|
-
|
|
638
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
639
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
749
640
|
];
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const result = await prepareProof(
|
|
755
|
-
circuitForLocked,
|
|
756
|
-
provingKeyForLocked,
|
|
641
|
+
const encodedZkProof = await prepareProof(
|
|
642
|
+
circuit,
|
|
643
|
+
provingKey,
|
|
757
644
|
Bob,
|
|
758
|
-
[
|
|
645
|
+
[bobUtxo1, ZERO_UTXO],
|
|
759
646
|
[nullifier1, ZERO_UTXO],
|
|
760
|
-
[
|
|
647
|
+
[lockedUtxo1, ZERO_UTXO],
|
|
761
648
|
root.bigInt(),
|
|
762
649
|
merkleProofs,
|
|
763
|
-
[
|
|
764
|
-
Alice.ethAddress, // current lock delegate
|
|
650
|
+
[Bob, Bob],
|
|
765
651
|
);
|
|
766
|
-
const nullifiers = [nullifier1.hash];
|
|
767
652
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
653
|
+
const txId = randomBytes32();
|
|
654
|
+
const createArgs = encodeCreateArgs({
|
|
655
|
+
txId,
|
|
656
|
+
inputs: [nullifier1.hash],
|
|
657
|
+
outputs: [],
|
|
658
|
+
lockedOutputs: [lockedUtxo1.hash],
|
|
659
|
+
proof: encodeToBytes(root.bigInt(), encodedZkProof),
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Pre-compute lockId off-chain and compare against contract.
|
|
663
|
+
const predicted = await zeto
|
|
664
|
+
.connect(Bob.signer)
|
|
665
|
+
.computeLockId(createArgs);
|
|
666
|
+
lockId = predicted;
|
|
667
|
+
|
|
668
|
+
const tx = await zeto
|
|
669
|
+
.connect(Bob.signer)
|
|
670
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x");
|
|
671
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
672
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
673
|
+
|
|
674
|
+
// Expect a LockCreated event with the predicted lockId.
|
|
675
|
+
const created = result!.logs
|
|
676
|
+
.map((l) => {
|
|
677
|
+
try {
|
|
678
|
+
return zeto.interface.parseLog(l as any);
|
|
679
|
+
} catch (_e) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
})
|
|
683
|
+
.find((p) => p && p.name === "LockCreated");
|
|
684
|
+
expect(created, "LockCreated event not found").to.not.be.null;
|
|
685
|
+
expect(created!.args.lockId).to.equal(predicted);
|
|
686
|
+
expect(created!.args.owner).to.equal(Bob.ethAddress);
|
|
687
|
+
expect(created!.args.spender).to.equal(Bob.ethAddress);
|
|
780
688
|
});
|
|
781
689
|
|
|
782
|
-
it("
|
|
783
|
-
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
690
|
+
it("isLockActive() and getLock() reflect the newly created lock", async function () {
|
|
691
|
+
expect(await zeto.isLockActive(lockId)).to.equal(true);
|
|
692
|
+
const info = await zeto.getLock(lockId);
|
|
693
|
+
expect(info.owner).to.equal(Bob.ethAddress);
|
|
694
|
+
expect(info.spender).to.equal(Bob.ethAddress);
|
|
695
|
+
expect(info.spendCommitment).to.equal(ethers.ZeroHash);
|
|
696
|
+
expect(info.cancelCommitment).to.equal(ethers.ZeroHash);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
it("locked() returns true for locked UTXOs and false for unlocked or spent UTXOs", async function () {
|
|
700
|
+
expect(await zeto.locked(lockedUtxo1.hash)).to.deep.equal([
|
|
701
|
+
true,
|
|
702
|
+
Bob.ethAddress,
|
|
703
|
+
]);
|
|
704
|
+
expect((await zeto.locked(aliceUtxo1.hash))[0]).to.be.false;
|
|
705
|
+
expect((await zeto.locked(bobUtxo1.hash))[0]).to.be.false;
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it("updateLock() commits the spend hash while owner == spender", async function () {
|
|
709
|
+
outputUtxo1 = newUTXO(10, Alice);
|
|
710
|
+
outputUtxo2 = newUTXO(90, Bob);
|
|
711
|
+
|
|
712
|
+
unlockHash = calculateSpendHash(
|
|
713
|
+
[lockedUtxo1],
|
|
714
|
+
[],
|
|
715
|
+
[outputUtxo1, outputUtxo2],
|
|
716
|
+
"0x",
|
|
793
717
|
);
|
|
794
|
-
const merkleProofs = [
|
|
795
|
-
proof1.siblings.map((s) => s.bigInt()),
|
|
796
|
-
proof2.siblings.map((s) => s.bigInt()),
|
|
797
|
-
];
|
|
798
|
-
// Bob proposes the output UTXOs, attempting to transfer the locked UTXO to Alice
|
|
799
|
-
// and reusing an unlocked spent UTXO (utxo12)
|
|
800
|
-
const utxo1 = newUTXO(14, Alice);
|
|
801
718
|
|
|
802
|
-
const
|
|
719
|
+
const tx = await zeto
|
|
720
|
+
.connect(Bob.signer)
|
|
721
|
+
.updateLock(
|
|
722
|
+
lockId,
|
|
723
|
+
encodeUpdateArgs(randomBytes32()),
|
|
724
|
+
unlockHash,
|
|
725
|
+
ethers.ZeroHash,
|
|
726
|
+
"0x",
|
|
727
|
+
);
|
|
728
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
729
|
+
logger.debug(`updateLock() complete. Gas used: ${result?.gasUsed}`);
|
|
730
|
+
|
|
731
|
+
const info = await zeto.getLock(lockId);
|
|
732
|
+
expect(info.spendCommitment).to.equal(unlockHash);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it("delegateLock() transfers spending authority to Alice", async function () {
|
|
736
|
+
const tx = await zeto
|
|
737
|
+
.connect(Bob.signer)
|
|
738
|
+
.delegateLock(
|
|
739
|
+
lockId,
|
|
740
|
+
encodeDelegateArgs(randomBytes32()),
|
|
741
|
+
Alice.ethAddress,
|
|
742
|
+
"0x",
|
|
743
|
+
);
|
|
744
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
745
|
+
logger.debug(`delegateLock() complete. Gas used: ${result?.gasUsed}`);
|
|
746
|
+
|
|
747
|
+
const info = await zeto.getLock(lockId);
|
|
748
|
+
expect(info.spender).to.equal(Alice.ethAddress);
|
|
749
|
+
// Storage-layer delegate must also be moved.
|
|
750
|
+
expect(await zeto.locked(lockedUtxo1.hash)).to.deep.equal([
|
|
751
|
+
true,
|
|
752
|
+
Alice.ethAddress,
|
|
753
|
+
]);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
it("the new spender can spendLock() with the matching payload", async function () {
|
|
757
|
+
const encodedZkProofForSettle = await prepareProofForLocked(
|
|
803
758
|
circuitForLocked,
|
|
804
759
|
provingKeyForLocked,
|
|
805
760
|
Bob,
|
|
806
761
|
[lockedUtxo1, ZERO_UTXO],
|
|
807
|
-
[
|
|
808
|
-
[
|
|
809
|
-
root.bigInt(),
|
|
810
|
-
merkleProofs,
|
|
811
|
-
[Alice, Alice],
|
|
812
|
-
Alice.ethAddress, // current lock delegate
|
|
762
|
+
[outputUtxo1, outputUtxo2],
|
|
763
|
+
[Alice, Bob],
|
|
813
764
|
);
|
|
814
|
-
const nullifiers = [nullifier1.hash];
|
|
815
765
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
)
|
|
827
|
-
|
|
766
|
+
const spendArgs = encodeSpendArgs({
|
|
767
|
+
txId: randomBytes32(),
|
|
768
|
+
lockedOutputs: [],
|
|
769
|
+
outputs: [outputUtxo1.hash, outputUtxo2.hash],
|
|
770
|
+
proof: encodeToBytesForLocked(encodedZkProofForSettle),
|
|
771
|
+
data: "0x",
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
const tx = await zeto
|
|
775
|
+
.connect(Alice.signer)
|
|
776
|
+
.spendLock(lockId, spendArgs, "0x");
|
|
777
|
+
const result = await tx.wait();
|
|
778
|
+
|
|
779
|
+
const parsed = result!.logs
|
|
780
|
+
.map((l) => {
|
|
781
|
+
try {
|
|
782
|
+
return zeto.interface.parseLog(l as any);
|
|
783
|
+
} catch (_e) {
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
})
|
|
787
|
+
.filter((p) => p !== null) as ReadonlyArray<{
|
|
788
|
+
name: string;
|
|
789
|
+
args: any;
|
|
790
|
+
}>;
|
|
791
|
+
const lockSpent = parsed.find((p) => p.name === "LockSpent");
|
|
792
|
+
const zetoLockSpent = parsed.find((p) => p.name === "ZetoLockSpent");
|
|
793
|
+
expect(lockSpent, "LockSpent event not emitted").to.not.be.undefined;
|
|
794
|
+
expect(zetoLockSpent, "ZetoLockSpent event not emitted").to.not.be
|
|
795
|
+
.undefined;
|
|
796
|
+
expect(lockSpent!.args.lockId).to.equal(lockId);
|
|
797
|
+
expect(lockSpent!.args.spender).to.equal(Alice.ethAddress);
|
|
798
|
+
|
|
799
|
+
const outputs = zetoLockSpent!.args.outputs;
|
|
800
|
+
await smtAlice.add(outputs[0], outputs[0]);
|
|
801
|
+
await smtAlice.add(outputs[1], outputs[1]);
|
|
802
|
+
await smtBob.add(outputs[0], outputs[0]);
|
|
803
|
+
await smtBob.add(outputs[1], outputs[1]);
|
|
804
|
+
|
|
805
|
+
// Lock is no longer active.
|
|
806
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
828
807
|
});
|
|
829
808
|
|
|
830
|
-
it("
|
|
831
|
-
|
|
832
|
-
const
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
809
|
+
it("onchain SMT root for the unlocked UTXOs equals the offchain SMT root", async function () {
|
|
810
|
+
const bobRoot = await smtBob.root();
|
|
811
|
+
const aliceRoot = await smtAlice.root();
|
|
812
|
+
const onchainRoot = await zeto.getRoot();
|
|
813
|
+
expect(bobRoot.string()).to.equal(onchainRoot.toString());
|
|
814
|
+
expect(aliceRoot.string()).to.equal(onchainRoot.toString());
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
describe("createLock -> cancelLock flow", function () {
|
|
819
|
+
let bobUtxo1: UTXO;
|
|
820
|
+
let lockedUtxo1: UTXO;
|
|
821
|
+
let lockId: string;
|
|
822
|
+
let cancelHash: string;
|
|
823
|
+
let outUtxo1: UTXO;
|
|
824
|
+
let outUtxo2: UTXO;
|
|
825
|
+
|
|
826
|
+
before(async function () {
|
|
827
|
+
bobUtxo1 = newUTXO(100, Bob);
|
|
828
|
+
await doMint(zeto, deployer, [bobUtxo1]);
|
|
829
|
+
await smtAlice.add(bobUtxo1.hash, bobUtxo1.hash);
|
|
830
|
+
await smtBob.add(bobUtxo1.hash, bobUtxo1.hash);
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it("Bob createLock()", async function () {
|
|
834
|
+
const nullifier1 = newNullifier(bobUtxo1, Bob);
|
|
835
|
+
lockedUtxo1 = newUTXO(bobUtxo1.value!, Bob);
|
|
836
|
+
const root = await smtBob.root();
|
|
837
|
+
const p1 = await smtBob.generateCircomVerifierProof(
|
|
838
|
+
bobUtxo1.hash,
|
|
840
839
|
root,
|
|
841
840
|
);
|
|
841
|
+
const p2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
842
842
|
const merkleProofs = [
|
|
843
|
-
|
|
844
|
-
|
|
843
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
844
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
845
845
|
];
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
846
|
+
const encodedZkProof = await prepareProof(
|
|
847
|
+
circuit,
|
|
848
|
+
provingKey,
|
|
849
|
+
Bob,
|
|
850
|
+
[bobUtxo1, ZERO_UTXO],
|
|
851
|
+
[nullifier1, ZERO_UTXO],
|
|
852
|
+
[lockedUtxo1, ZERO_UTXO],
|
|
853
|
+
root.bigInt(),
|
|
854
|
+
merkleProofs,
|
|
855
|
+
[Bob, Bob],
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
// Commit the cancel hash up-front to exercise the cancelCommitment branch.
|
|
859
|
+
outUtxo1 = newUTXO(10, Alice);
|
|
860
|
+
outUtxo2 = newUTXO(90, Bob);
|
|
861
|
+
cancelHash = calculateCancelHash(
|
|
862
|
+
[lockedUtxo1],
|
|
863
|
+
[],
|
|
864
|
+
[outUtxo1, outUtxo2],
|
|
865
|
+
"0x",
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
const createArgs = encodeCreateArgs({
|
|
869
|
+
txId: randomBytes32(),
|
|
870
|
+
inputs: [nullifier1.hash],
|
|
871
|
+
outputs: [],
|
|
872
|
+
lockedOutputs: [lockedUtxo1.hash],
|
|
873
|
+
proof: encodeToBytes(root.bigInt(), encodedZkProof),
|
|
874
|
+
});
|
|
875
|
+
lockId = await zeto.connect(Bob.signer).computeLockId(createArgs);
|
|
876
|
+
const tx = await zeto
|
|
877
|
+
.connect(Bob.signer)
|
|
878
|
+
.createLock(createArgs, ethers.ZeroHash, cancelHash, "0x");
|
|
879
|
+
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
880
|
+
logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
|
|
881
|
+
});
|
|
849
882
|
|
|
850
|
-
|
|
883
|
+
it("the owner can cancelLock() to reverse the lock without delegation", async function () {
|
|
884
|
+
const encodedZkProofForCancel = await prepareProofForLocked(
|
|
851
885
|
circuitForLocked,
|
|
852
886
|
provingKeyForLocked,
|
|
853
887
|
Bob,
|
|
854
888
|
[lockedUtxo1, ZERO_UTXO],
|
|
855
|
-
[
|
|
856
|
-
[utxo9, utxo10],
|
|
857
|
-
root.bigInt(),
|
|
858
|
-
merkleProofs,
|
|
889
|
+
[outUtxo1, outUtxo2],
|
|
859
890
|
[Alice, Bob],
|
|
860
|
-
Alice.ethAddress, // current lock delegate
|
|
861
891
|
);
|
|
862
|
-
const nullifiers = [nullifier1.hash];
|
|
863
892
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
root.bigInt(),
|
|
872
|
-
result.encodedProof,
|
|
873
|
-
true,
|
|
874
|
-
),
|
|
875
|
-
).to.be.fulfilled;
|
|
893
|
+
const cancelArgs = encodeSpendArgs({
|
|
894
|
+
txId: randomBytes32(),
|
|
895
|
+
lockedOutputs: [],
|
|
896
|
+
outputs: [outUtxo1.hash, outUtxo2.hash],
|
|
897
|
+
proof: encodeToBytesForLocked(encodedZkProofForCancel),
|
|
898
|
+
data: "0x",
|
|
899
|
+
});
|
|
876
900
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
await
|
|
881
|
-
await smtBob.add(utxo10.hash, utxo10.hash);
|
|
882
|
-
});
|
|
901
|
+
const tx = await zeto
|
|
902
|
+
.connect(Bob.signer)
|
|
903
|
+
.cancelLock(lockId, cancelArgs, "0x");
|
|
904
|
+
const result = await tx.wait();
|
|
883
905
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
906
|
+
const parsed = result!.logs
|
|
907
|
+
.map((l) => {
|
|
908
|
+
try {
|
|
909
|
+
return zeto.interface.parseLog(l as any);
|
|
910
|
+
} catch (_e) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
})
|
|
914
|
+
.filter((p) => p !== null) as ReadonlyArray<{
|
|
915
|
+
name: string;
|
|
916
|
+
args: any;
|
|
917
|
+
}>;
|
|
918
|
+
const cancelled = parsed.find((p) => p.name === "LockCancelled");
|
|
919
|
+
const zetoCancelled = parsed.find(
|
|
920
|
+
(p) => p.name === "ZetoLockCancelled",
|
|
921
|
+
);
|
|
922
|
+
expect(cancelled, "LockCancelled event not emitted").to.not.be
|
|
923
|
+
.undefined;
|
|
924
|
+
expect(zetoCancelled, "ZetoLockCancelled event not emitted").to.not.be
|
|
925
|
+
.undefined;
|
|
926
|
+
|
|
927
|
+
const outputs = zetoCancelled!.args.outputs;
|
|
928
|
+
await smtAlice.add(outputs[0], outputs[0]);
|
|
929
|
+
await smtAlice.add(outputs[1], outputs[1]);
|
|
930
|
+
await smtBob.add(outputs[0], outputs[0]);
|
|
931
|
+
await smtBob.add(outputs[1], outputs[1]);
|
|
932
|
+
|
|
933
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
888
934
|
});
|
|
889
935
|
|
|
890
|
-
it("onchain SMT root for the unlocked UTXOs
|
|
891
|
-
const
|
|
936
|
+
it("onchain SMT root for the unlocked UTXOs equals the offchain SMT root", async function () {
|
|
937
|
+
const bobRoot = await smtBob.root();
|
|
938
|
+
const aliceRoot = await smtAlice.root();
|
|
892
939
|
const onchainRoot = await zeto.getRoot();
|
|
893
|
-
expect(
|
|
940
|
+
expect(bobRoot.string()).to.equal(onchainRoot.toString());
|
|
941
|
+
expect(aliceRoot.string()).to.equal(onchainRoot.toString());
|
|
894
942
|
});
|
|
895
943
|
});
|
|
896
944
|
|
|
897
|
-
describe("
|
|
898
|
-
let
|
|
945
|
+
describe("spendLock with a payload that does not match the spend commitment fails", function () {
|
|
946
|
+
let bobUtxo1: UTXO;
|
|
947
|
+
let lockedUtxo1: UTXO;
|
|
948
|
+
let lockId: string;
|
|
949
|
+
let expectedHash: string;
|
|
950
|
+
|
|
951
|
+
before(async function () {
|
|
952
|
+
bobUtxo1 = newUTXO(100, Bob);
|
|
953
|
+
await doMint(zeto, deployer, [bobUtxo1]);
|
|
954
|
+
await smtAlice.add(bobUtxo1.hash, bobUtxo1.hash);
|
|
955
|
+
await smtBob.add(bobUtxo1.hash, bobUtxo1.hash);
|
|
956
|
+
});
|
|
899
957
|
|
|
900
|
-
it("Bob
|
|
901
|
-
const nullifier1 = newNullifier(
|
|
902
|
-
|
|
958
|
+
it("Bob createLock()", async function () {
|
|
959
|
+
const nullifier1 = newNullifier(bobUtxo1, Bob);
|
|
960
|
+
lockedUtxo1 = newUTXO(bobUtxo1.value!, Bob);
|
|
903
961
|
const root = await smtBob.root();
|
|
904
|
-
const
|
|
905
|
-
|
|
962
|
+
const p1 = await smtBob.generateCircomVerifierProof(
|
|
963
|
+
bobUtxo1.hash,
|
|
906
964
|
root,
|
|
907
965
|
);
|
|
908
|
-
const
|
|
966
|
+
const p2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
909
967
|
const merkleProofs = [
|
|
910
|
-
|
|
911
|
-
|
|
968
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
969
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
912
970
|
];
|
|
913
|
-
const
|
|
971
|
+
const encodedZkProof = await prepareProof(
|
|
914
972
|
circuit,
|
|
915
973
|
provingKey,
|
|
916
974
|
Bob,
|
|
917
|
-
[
|
|
975
|
+
[bobUtxo1, ZERO_UTXO],
|
|
918
976
|
[nullifier1, ZERO_UTXO],
|
|
919
|
-
[
|
|
977
|
+
[lockedUtxo1, ZERO_UTXO],
|
|
920
978
|
root.bigInt(),
|
|
921
979
|
merkleProofs,
|
|
922
980
|
[Bob, Bob],
|
|
923
981
|
);
|
|
924
982
|
|
|
925
|
-
const
|
|
926
|
-
|
|
983
|
+
const createArgs = encodeCreateArgs({
|
|
984
|
+
txId: randomBytes32(),
|
|
985
|
+
inputs: [nullifier1.hash],
|
|
986
|
+
outputs: [],
|
|
987
|
+
lockedOutputs: [lockedUtxo1.hash],
|
|
988
|
+
proof: encodeToBytes(root.bigInt(), encodedZkProof),
|
|
989
|
+
});
|
|
990
|
+
lockId = await zeto.connect(Bob.signer).computeLockId(createArgs);
|
|
991
|
+
await (
|
|
992
|
+
await zeto
|
|
993
|
+
.connect(Bob.signer)
|
|
994
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x")
|
|
995
|
+
).wait();
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
it("Bob updateLock() committing a specific spend hash", async function () {
|
|
999
|
+
const expectedOut1 = newUTXO(10, Alice);
|
|
1000
|
+
const expectedOut2 = newUTXO(90, Bob);
|
|
1001
|
+
expectedHash = calculateSpendHash(
|
|
1002
|
+
[lockedUtxo1],
|
|
927
1003
|
[],
|
|
928
|
-
|
|
929
|
-
root.bigInt(),
|
|
930
|
-
encodedProof,
|
|
931
|
-
Alice.ethAddress, // make Alice the delegate who can spend the state (if she has the right proof)
|
|
1004
|
+
[expectedOut1, expectedOut2],
|
|
932
1005
|
"0x",
|
|
933
1006
|
);
|
|
934
|
-
const result: ContractTransactionReceipt | null = await tx.wait();
|
|
935
1007
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1008
|
+
await (
|
|
1009
|
+
await zeto
|
|
1010
|
+
.connect(Bob.signer)
|
|
1011
|
+
.updateLock(
|
|
1012
|
+
lockId,
|
|
1013
|
+
encodeUpdateArgs(randomBytes32()),
|
|
1014
|
+
expectedHash,
|
|
1015
|
+
ethers.ZeroHash,
|
|
1016
|
+
"0x",
|
|
1017
|
+
)
|
|
1018
|
+
).wait();
|
|
943
1019
|
});
|
|
944
1020
|
|
|
945
|
-
it("
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1021
|
+
it("spendLock() with a different payload reverts with InvalidUnlockHash", async function () {
|
|
1022
|
+
const wrongOut1 = newUTXO(20, Alice);
|
|
1023
|
+
const wrongOut2 = newUTXO(80, Bob);
|
|
1024
|
+
|
|
1025
|
+
const encodedZkProofForSettle = await prepareProofForLocked(
|
|
1026
|
+
circuitForLocked,
|
|
1027
|
+
provingKeyForLocked,
|
|
1028
|
+
Bob,
|
|
1029
|
+
[lockedUtxo1, ZERO_UTXO],
|
|
1030
|
+
[wrongOut1, wrongOut2],
|
|
1031
|
+
[Alice, Bob],
|
|
955
1032
|
);
|
|
956
|
-
});
|
|
957
1033
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1034
|
+
const spendArgs = encodeSpendArgs({
|
|
1035
|
+
txId: randomBytes32(),
|
|
1036
|
+
lockedOutputs: [],
|
|
1037
|
+
outputs: [wrongOut1.hash, wrongOut2.hash],
|
|
1038
|
+
proof: encodeToBytesForLocked(encodedZkProofForSettle),
|
|
1039
|
+
data: "0x",
|
|
1040
|
+
});
|
|
963
1041
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1042
|
+
const calculatedHash = calculateSpendHash(
|
|
1043
|
+
[lockedUtxo1],
|
|
1044
|
+
[],
|
|
1045
|
+
[wrongOut1, wrongOut2],
|
|
1046
|
+
"0x",
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
await expect(
|
|
1050
|
+
zeto.connect(Bob.signer).spendLock(lockId, spendArgs, "0x"),
|
|
1051
|
+
).rejectedWith(
|
|
1052
|
+
`InvalidUnlockHash("${expectedHash}", "${calculatedHash}")`,
|
|
971
1053
|
);
|
|
972
|
-
|
|
973
|
-
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
describe("negative cases for the lock lifecycle", function () {
|
|
1058
|
+
// These tests rely on hardhat-style revert decoding. Skip on real chains.
|
|
1059
|
+
if (network.name !== "hardhat") {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// freshLock mints a UTXO for `owner`, locks it, and returns the
|
|
1064
|
+
// resulting lock metadata so each negative case can branch off without
|
|
1065
|
+
// polluting other test scopes. Each call advances the SMT.
|
|
1066
|
+
async function freshLock(
|
|
1067
|
+
owner: User,
|
|
1068
|
+
spendCommitment: string = ethers.ZeroHash,
|
|
1069
|
+
cancelCommitment: string = ethers.ZeroHash,
|
|
1070
|
+
): Promise<{
|
|
1071
|
+
lockId: string;
|
|
1072
|
+
sourceUtxo: UTXO;
|
|
1073
|
+
lockedUtxo: UTXO;
|
|
1074
|
+
createArgs: string;
|
|
1075
|
+
}> {
|
|
1076
|
+
const sourceUtxo = newUTXO(100, owner);
|
|
1077
|
+
await doMint(zeto, deployer, [sourceUtxo]);
|
|
1078
|
+
await smtAlice.add(sourceUtxo.hash, sourceUtxo.hash);
|
|
1079
|
+
await smtBob.add(sourceUtxo.hash, sourceUtxo.hash);
|
|
1080
|
+
|
|
1081
|
+
const nullifier = newNullifier(sourceUtxo, owner);
|
|
1082
|
+
const lockedUtxo = newUTXO(sourceUtxo.value!, owner);
|
|
1083
|
+
const root = await smtBob.root();
|
|
1084
|
+
const p1 = await smtBob.generateCircomVerifierProof(
|
|
1085
|
+
sourceUtxo.hash,
|
|
974
1086
|
root,
|
|
975
1087
|
);
|
|
1088
|
+
const p2 = await smtBob.generateCircomVerifierProof(0n, root);
|
|
976
1089
|
const merkleProofs = [
|
|
977
|
-
|
|
978
|
-
|
|
1090
|
+
p1.siblings.map((s) => s.bigInt()),
|
|
1091
|
+
p2.siblings.map((s) => s.bigInt()),
|
|
979
1092
|
];
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1093
|
+
const encodedZkProof = await prepareProof(
|
|
1094
|
+
circuit,
|
|
1095
|
+
provingKey,
|
|
1096
|
+
owner,
|
|
1097
|
+
[sourceUtxo, ZERO_UTXO],
|
|
1098
|
+
[nullifier, ZERO_UTXO],
|
|
1099
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1100
|
+
root.bigInt(),
|
|
1101
|
+
merkleProofs,
|
|
1102
|
+
[owner, owner],
|
|
1103
|
+
);
|
|
1104
|
+
|
|
1105
|
+
const createArgs = encodeCreateArgs({
|
|
1106
|
+
txId: randomBytes32(),
|
|
1107
|
+
inputs: [nullifier.hash],
|
|
1108
|
+
outputs: [],
|
|
1109
|
+
lockedOutputs: [lockedUtxo.hash],
|
|
1110
|
+
proof: encodeToBytes(root.bigInt(), encodedZkProof),
|
|
1111
|
+
});
|
|
1112
|
+
const lockId = await zeto
|
|
1113
|
+
.connect(owner.signer)
|
|
1114
|
+
.computeLockId(createArgs);
|
|
1115
|
+
await (
|
|
1116
|
+
await zeto
|
|
1117
|
+
.connect(owner.signer)
|
|
1118
|
+
.createLock(createArgs, spendCommitment, cancelCommitment, "0x")
|
|
1119
|
+
).wait();
|
|
1120
|
+
return { lockId, sourceUtxo, lockedUtxo, createArgs };
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Re-encoding helper: produce a syntactically valid spend payload that
|
|
1124
|
+
// does NOT need to verify a real ZK proof. Tests for authorization /
|
|
1125
|
+
// immutability assertions short-circuit before the proof is touched.
|
|
1126
|
+
function dummySpendArgs(): string {
|
|
1127
|
+
return encodeSpendArgs({
|
|
1128
|
+
txId: randomBytes32(),
|
|
1129
|
+
lockedOutputs: [],
|
|
1130
|
+
outputs: [],
|
|
1131
|
+
proof: "0x",
|
|
1132
|
+
data: "0x",
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
it("createLock() with a duplicate txId from the same caller reverts with DuplicateLock", async function () {
|
|
1137
|
+
const { lockId, createArgs } = await freshLock(Bob);
|
|
1138
|
+
|
|
1139
|
+
// Same createArgs (same txId, same caller) → same lockId → DuplicateLock
|
|
1140
|
+
// fires before any input or proof validation.
|
|
1141
|
+
await expect(
|
|
1142
|
+
zeto
|
|
1143
|
+
.connect(Bob.signer)
|
|
1144
|
+
.createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x"),
|
|
1145
|
+
).rejectedWith(`DuplicateLock("${lockId}")`);
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
it("updateLock() by a non-owner reverts with LockUnauthorized", async function () {
|
|
1149
|
+
const { lockId } = await freshLock(Bob);
|
|
1150
|
+
|
|
1151
|
+
await expect(
|
|
1152
|
+
zeto
|
|
1153
|
+
.connect(Alice.signer)
|
|
1154
|
+
.updateLock(
|
|
1155
|
+
lockId,
|
|
1156
|
+
encodeUpdateArgs(randomBytes32()),
|
|
1157
|
+
ethers.ZeroHash,
|
|
1158
|
+
ethers.ZeroHash,
|
|
1159
|
+
"0x",
|
|
1160
|
+
),
|
|
1161
|
+
).to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1162
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
it("updateLock() after delegateLock() reverts with LockImmutable", async function () {
|
|
1166
|
+
const { lockId } = await freshLock(Bob);
|
|
1167
|
+
|
|
1168
|
+
// Bob delegates spending authority to Alice; spender (Alice) now
|
|
1169
|
+
// differs from owner (Bob).
|
|
1170
|
+
await (
|
|
1171
|
+
await zeto
|
|
1172
|
+
.connect(Bob.signer)
|
|
1173
|
+
.delegateLock(
|
|
1174
|
+
lockId,
|
|
1175
|
+
encodeDelegateArgs(randomBytes32()),
|
|
1176
|
+
Alice.ethAddress,
|
|
1177
|
+
"0x",
|
|
1178
|
+
)
|
|
1179
|
+
).wait();
|
|
1180
|
+
|
|
1181
|
+
// Even Bob (the owner) can no longer mutate the commitments — the
|
|
1182
|
+
// lock is now externally controlled and must be considered immutable.
|
|
1183
|
+
await expect(
|
|
1184
|
+
zeto
|
|
1185
|
+
.connect(Bob.signer)
|
|
1186
|
+
.updateLock(
|
|
1187
|
+
lockId,
|
|
1188
|
+
encodeUpdateArgs(randomBytes32()),
|
|
1189
|
+
ethers.ZeroHash,
|
|
1190
|
+
ethers.ZeroHash,
|
|
1191
|
+
"0x",
|
|
1192
|
+
),
|
|
1193
|
+
).rejectedWith(`LockImmutable("${lockId}")`);
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
it("delegateLock() by a non-spender reverts with LockUnauthorized", async function () {
|
|
1197
|
+
const { lockId } = await freshLock(Bob);
|
|
1198
|
+
|
|
1199
|
+
await expect(
|
|
1200
|
+
zeto
|
|
1201
|
+
.connect(Alice.signer)
|
|
1202
|
+
.delegateLock(
|
|
1203
|
+
lockId,
|
|
1204
|
+
encodeDelegateArgs(randomBytes32()),
|
|
1205
|
+
Charlie.ethAddress,
|
|
1206
|
+
"0x",
|
|
1207
|
+
),
|
|
1208
|
+
)
|
|
1209
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1210
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
it("spendLock() by a non-spender reverts with LockUnauthorized before touching the proof", async function () {
|
|
1214
|
+
const { lockId } = await freshLock(Bob);
|
|
983
1215
|
|
|
984
|
-
|
|
1216
|
+
// Garbage proof — the onlySpender modifier MUST short-circuit before
|
|
1217
|
+
// any proof verification is attempted. This is also what protects the
|
|
1218
|
+
// contract from accepting a spend whose payload does not match the
|
|
1219
|
+
// committed unlockHash for an unauthorized caller.
|
|
1220
|
+
await expect(
|
|
1221
|
+
zeto.connect(Alice.signer).spendLock(lockId, dummySpendArgs(), "0x"),
|
|
1222
|
+
)
|
|
1223
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1224
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
it("cancelLock() by a non-spender reverts with LockUnauthorized", async function () {
|
|
1228
|
+
const { lockId } = await freshLock(Bob);
|
|
1229
|
+
|
|
1230
|
+
await expect(
|
|
1231
|
+
zeto.connect(Alice.signer).cancelLock(lockId, dummySpendArgs(), "0x"),
|
|
1232
|
+
)
|
|
1233
|
+
.to.be.revertedWithCustomError(zeto, "LockUnauthorized")
|
|
1234
|
+
.withArgs(lockId, Bob.ethAddress, Alice.ethAddress);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
it("cancelLock() with a payload that does not match cancelCommitment reverts with InvalidUnlockHash", async function () {
|
|
1238
|
+
// Pre-commit a non-zero cancelCommitment so the hash check is armed.
|
|
1239
|
+
const expectedOut1 = newUTXO(10, Alice);
|
|
1240
|
+
const expectedOut2 = newUTXO(90, Bob);
|
|
1241
|
+
// We don't yet know the lockedUtxo hash (it's randomized inside
|
|
1242
|
+
// freshLock), so commit to a fixed value that we know cannot match
|
|
1243
|
+
// any honest cancellation. The hash-mismatch check fires regardless
|
|
1244
|
+
// of the locked-input contents.
|
|
1245
|
+
const sentinelHash = ethers.keccak256(
|
|
1246
|
+
ethers.toUtf8Bytes("zeto:test:cancelCommitment"),
|
|
1247
|
+
);
|
|
1248
|
+
const { lockId, lockedUtxo } = await freshLock(
|
|
1249
|
+
Bob,
|
|
1250
|
+
ethers.ZeroHash,
|
|
1251
|
+
sentinelHash,
|
|
1252
|
+
);
|
|
1253
|
+
|
|
1254
|
+
const wrongProof = await prepareProofForLocked(
|
|
985
1255
|
circuitForLocked,
|
|
986
1256
|
provingKeyForLocked,
|
|
987
1257
|
Bob,
|
|
988
|
-
[
|
|
989
|
-
[
|
|
990
|
-
[utxo1, utxo11],
|
|
991
|
-
root.bigInt(),
|
|
992
|
-
merkleProofs,
|
|
1258
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1259
|
+
[expectedOut1, expectedOut2],
|
|
993
1260
|
[Alice, Bob],
|
|
994
|
-
Charlie.ethAddress, // current lock delegate
|
|
995
1261
|
);
|
|
996
|
-
const
|
|
1262
|
+
const cancelArgs = encodeSpendArgs({
|
|
1263
|
+
txId: randomBytes32(),
|
|
1264
|
+
lockedOutputs: [],
|
|
1265
|
+
outputs: [expectedOut1.hash, expectedOut2.hash],
|
|
1266
|
+
proof: encodeToBytesForLocked(wrongProof),
|
|
1267
|
+
data: "0x",
|
|
1268
|
+
});
|
|
1269
|
+
const calculatedHash = calculateCancelHash(
|
|
1270
|
+
[lockedUtxo],
|
|
1271
|
+
[],
|
|
1272
|
+
[expectedOut1, expectedOut2],
|
|
1273
|
+
"0x",
|
|
1274
|
+
);
|
|
997
1275
|
|
|
998
|
-
// Charlie (in reality this is usually a contract that orchestrates a trade flow) can spend the locked state
|
|
999
|
-
// using the proof generated by the trade counterparty (Bob in this case)
|
|
1000
1276
|
await expect(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
result.encodedProof,
|
|
1007
|
-
true,
|
|
1008
|
-
),
|
|
1009
|
-
).to.be.fulfilled;
|
|
1277
|
+
zeto.connect(Bob.signer).cancelLock(lockId, cancelArgs, "0x"),
|
|
1278
|
+
).rejectedWith(
|
|
1279
|
+
`InvalidUnlockHash("${sentinelHash}", "${calculatedHash}")`,
|
|
1280
|
+
);
|
|
1281
|
+
});
|
|
1010
1282
|
|
|
1011
|
-
|
|
1012
|
-
await
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1283
|
+
it("after a successful spendLock(), the lock is no longer active and getLock() reverts", async function () {
|
|
1284
|
+
const { lockId, lockedUtxo } = await freshLock(Bob);
|
|
1285
|
+
|
|
1286
|
+
const out1 = newUTXO(10, Alice);
|
|
1287
|
+
const out2 = newUTXO(90, Bob);
|
|
1288
|
+
const zkProof = await prepareProofForLocked(
|
|
1289
|
+
circuitForLocked,
|
|
1290
|
+
provingKeyForLocked,
|
|
1291
|
+
Bob,
|
|
1292
|
+
[lockedUtxo, ZERO_UTXO],
|
|
1293
|
+
[out1, out2],
|
|
1294
|
+
[Alice, Bob],
|
|
1295
|
+
);
|
|
1296
|
+
const spendArgs = encodeSpendArgs({
|
|
1297
|
+
txId: randomBytes32(),
|
|
1298
|
+
lockedOutputs: [],
|
|
1299
|
+
outputs: [out1.hash, out2.hash],
|
|
1300
|
+
proof: encodeToBytesForLocked(zkProof),
|
|
1301
|
+
data: "0x",
|
|
1302
|
+
});
|
|
1303
|
+
await (
|
|
1304
|
+
await zeto.connect(Bob.signer).spendLock(lockId, spendArgs, "0x")
|
|
1305
|
+
).wait();
|
|
1306
|
+
// Keep both SMTs in sync with the new unlocked outputs so unrelated
|
|
1307
|
+
// tests in later blocks can still reason about roots.
|
|
1308
|
+
await smtAlice.add(out1.hash, out1.hash);
|
|
1309
|
+
await smtAlice.add(out2.hash, out2.hash);
|
|
1310
|
+
await smtBob.add(out1.hash, out1.hash);
|
|
1311
|
+
await smtBob.add(out2.hash, out2.hash);
|
|
1312
|
+
|
|
1313
|
+
expect(await zeto.isLockActive(lockId)).to.equal(false);
|
|
1314
|
+
await expect(zeto.getLock(lockId))
|
|
1315
|
+
.to.be.revertedWithCustomError(zeto, "LockNotActive")
|
|
1316
|
+
.withArgs(lockId);
|
|
1317
|
+
// Re-spending a consumed lock MUST also fail — lockActive is the
|
|
1318
|
+
// first modifier and emits LockNotActive before onlySpender.
|
|
1319
|
+
await expect(
|
|
1320
|
+
zeto.connect(Bob.signer).spendLock(lockId, dummySpendArgs(), "0x"),
|
|
1321
|
+
)
|
|
1322
|
+
.to.be.revertedWithCustomError(zeto, "LockNotActive")
|
|
1323
|
+
.withArgs(lockId);
|
|
1016
1324
|
});
|
|
1017
1325
|
});
|
|
1018
1326
|
});
|
|
@@ -1025,14 +1333,60 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
1025
1333
|
return;
|
|
1026
1334
|
}
|
|
1027
1335
|
|
|
1336
|
+
let aliceUtxo1: UTXO;
|
|
1337
|
+
let aliceUtxoSpent: UTXO;
|
|
1338
|
+
|
|
1339
|
+
before(async function () {
|
|
1340
|
+
// mint a UTXO for Alice
|
|
1341
|
+
aliceUtxo1 = newUTXO(100, Alice);
|
|
1342
|
+
aliceUtxoSpent = newUTXO(10, Alice);
|
|
1343
|
+
await doMint(zeto, deployer, [aliceUtxo1, aliceUtxoSpent]);
|
|
1344
|
+
await smtAlice.add(aliceUtxo1.hash, aliceUtxo1.hash);
|
|
1345
|
+
await smtAlice.add(aliceUtxoSpent.hash, aliceUtxoSpent.hash);
|
|
1346
|
+
|
|
1347
|
+
// spent one of the UTXOs
|
|
1348
|
+
const _utxo1 = newUTXO(5, Bob);
|
|
1349
|
+
const _utxo2 = newUTXO(5, Alice);
|
|
1350
|
+
const nullifier1 = newNullifier(aliceUtxoSpent, Alice);
|
|
1351
|
+
const root = await smtAlice.root();
|
|
1352
|
+
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
1353
|
+
aliceUtxoSpent.hash,
|
|
1354
|
+
root,
|
|
1355
|
+
);
|
|
1356
|
+
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
1357
|
+
const merkleProofs = [
|
|
1358
|
+
proof1.siblings.map((s) => s.bigInt()),
|
|
1359
|
+
proof2.siblings.map((s) => s.bigInt()),
|
|
1360
|
+
];
|
|
1361
|
+
await expect(
|
|
1362
|
+
doTransfer(
|
|
1363
|
+
Alice,
|
|
1364
|
+
[aliceUtxoSpent, ZERO_UTXO],
|
|
1365
|
+
[nullifier1, ZERO_UTXO],
|
|
1366
|
+
[_utxo1, _utxo2],
|
|
1367
|
+
root.bigInt(),
|
|
1368
|
+
merkleProofs,
|
|
1369
|
+
[Bob, Alice],
|
|
1370
|
+
),
|
|
1371
|
+
).to.be.fulfilled;
|
|
1372
|
+
|
|
1373
|
+
// Alice locally tracks the UTXOs inside the Sparse Merkle Tree
|
|
1374
|
+
await smtAlice.add(_utxo1.hash, _utxo1.hash);
|
|
1375
|
+
await smtAlice.add(_utxo2.hash, _utxo2.hash);
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
beforeEach(async function () {
|
|
1379
|
+
// this.skip();
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1028
1382
|
it("Alice attempting to withdraw spent UTXOs should fail", async function () {
|
|
1029
1383
|
// Alice generates the nullifiers for the UTXOs to be spent
|
|
1030
|
-
const nullifier1 = newNullifier(
|
|
1384
|
+
const nullifier1 = newNullifier(aliceUtxoSpent, Alice);
|
|
1031
1385
|
|
|
1032
1386
|
// Alice generates inclusion proofs for the UTXOs to be spent
|
|
1033
1387
|
let root = await smtAlice.root();
|
|
1034
1388
|
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
1035
|
-
|
|
1389
|
+
aliceUtxoSpent.hash,
|
|
1036
1390
|
root,
|
|
1037
1391
|
);
|
|
1038
1392
|
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
@@ -1041,64 +1395,58 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
1041
1395
|
proof2.siblings.map((s) => s.bigInt()),
|
|
1042
1396
|
];
|
|
1043
1397
|
|
|
1044
|
-
// Alice proposes the output
|
|
1045
|
-
const outputCommitment = newUTXO(
|
|
1398
|
+
// Alice proposes the output UTXO
|
|
1399
|
+
const outputCommitment = newUTXO(9, Alice);
|
|
1046
1400
|
|
|
1047
|
-
const {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
);
|
|
1401
|
+
const { encodedProof } = await prepareNullifierWithdrawProof(
|
|
1402
|
+
Alice,
|
|
1403
|
+
[aliceUtxoSpent, ZERO_UTXO],
|
|
1404
|
+
[nullifier1, ZERO_UTXO],
|
|
1405
|
+
outputCommitment,
|
|
1406
|
+
root.bigInt(),
|
|
1407
|
+
merkleProofs,
|
|
1408
|
+
);
|
|
1056
1409
|
|
|
1057
1410
|
await expect(
|
|
1058
1411
|
zeto
|
|
1059
1412
|
.connect(Alice.signer)
|
|
1060
1413
|
.withdraw(
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
root.bigInt(),
|
|
1065
|
-
encodedProof,
|
|
1414
|
+
1,
|
|
1415
|
+
[nullifier1.hash],
|
|
1416
|
+
outputCommitment.hash,
|
|
1417
|
+
encodeToBytesForWithdraw(root.bigInt(), encodedProof),
|
|
1066
1418
|
"0x",
|
|
1067
1419
|
),
|
|
1068
1420
|
).rejectedWith("UTXOAlreadySpent");
|
|
1069
1421
|
});
|
|
1070
1422
|
|
|
1071
1423
|
it("mint existing unspent UTXOs should fail", async function () {
|
|
1072
|
-
await expect(doMint(zeto, deployer, [
|
|
1424
|
+
await expect(doMint(zeto, deployer, [aliceUtxo1])).rejectedWith(
|
|
1073
1425
|
"UTXOAlreadyOwned",
|
|
1074
1426
|
);
|
|
1075
1427
|
});
|
|
1076
1428
|
|
|
1077
1429
|
it("mint existing spent UTXOs should fail", async function () {
|
|
1078
|
-
await expect(doMint(zeto, deployer, [
|
|
1430
|
+
await expect(doMint(zeto, deployer, [aliceUtxoSpent])).rejectedWith(
|
|
1079
1431
|
"UTXOAlreadyOwned",
|
|
1080
1432
|
);
|
|
1081
1433
|
});
|
|
1082
1434
|
|
|
1083
1435
|
it("transfer spent UTXOs should fail (double spend protection)", async function () {
|
|
1084
1436
|
// create outputs
|
|
1085
|
-
const _utxo1 = newUTXO(
|
|
1437
|
+
const _utxo1 = newUTXO(5, Bob);
|
|
1086
1438
|
const _utxo2 = newUTXO(5, Alice);
|
|
1087
1439
|
|
|
1088
1440
|
// generate the nullifiers for the UTXOs to be spent
|
|
1089
|
-
const nullifier1 = newNullifier(
|
|
1090
|
-
const nullifier2 = newNullifier(utxo2, Alice);
|
|
1441
|
+
const nullifier1 = newNullifier(aliceUtxoSpent, Alice);
|
|
1091
1442
|
|
|
1092
1443
|
// generate inclusion proofs for the UTXOs to be spent
|
|
1093
1444
|
let root = await smtAlice.root();
|
|
1094
1445
|
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
1095
|
-
|
|
1096
|
-
root,
|
|
1097
|
-
);
|
|
1098
|
-
const proof2 = await smtAlice.generateCircomVerifierProof(
|
|
1099
|
-
utxo2.hash,
|
|
1446
|
+
aliceUtxoSpent.hash,
|
|
1100
1447
|
root,
|
|
1101
1448
|
);
|
|
1449
|
+
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
1102
1450
|
const merkleProofs = [
|
|
1103
1451
|
proof1.siblings.map((s) => s.bigInt()),
|
|
1104
1452
|
proof2.siblings.map((s) => s.bigInt()),
|
|
@@ -1107,21 +1455,22 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
1107
1455
|
await expect(
|
|
1108
1456
|
doTransfer(
|
|
1109
1457
|
Alice,
|
|
1110
|
-
[
|
|
1111
|
-
[nullifier1,
|
|
1458
|
+
[aliceUtxoSpent, ZERO_UTXO],
|
|
1459
|
+
[nullifier1, ZERO_UTXO],
|
|
1112
1460
|
[_utxo1, _utxo2],
|
|
1113
1461
|
root.bigInt(),
|
|
1114
1462
|
merkleProofs,
|
|
1115
1463
|
[Bob, Alice],
|
|
1116
1464
|
),
|
|
1117
1465
|
).rejectedWith("UTXOAlreadySpent");
|
|
1118
|
-
})
|
|
1466
|
+
});
|
|
1119
1467
|
|
|
1120
1468
|
it("transfer with existing UTXOs in the output should fail (mass conservation protection)", async function () {
|
|
1121
|
-
const nullifier1 = newNullifier(
|
|
1469
|
+
const nullifier1 = newNullifier(aliceUtxo1, Alice);
|
|
1470
|
+
const _utxo1 = newUTXO(90, Alice);
|
|
1122
1471
|
let root = await smtAlice.root();
|
|
1123
1472
|
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
1124
|
-
|
|
1473
|
+
aliceUtxo1.hash,
|
|
1125
1474
|
root,
|
|
1126
1475
|
);
|
|
1127
1476
|
const proof2 = await smtAlice.generateCircomVerifierProof(0n, root);
|
|
@@ -1133,29 +1482,29 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
1133
1482
|
await expect(
|
|
1134
1483
|
doTransfer(
|
|
1135
1484
|
Alice,
|
|
1136
|
-
[
|
|
1485
|
+
[aliceUtxo1, ZERO_UTXO],
|
|
1137
1486
|
[nullifier1, ZERO_UTXO],
|
|
1138
|
-
[
|
|
1487
|
+
[aliceUtxoSpent, _utxo1],
|
|
1139
1488
|
root.bigInt(),
|
|
1140
1489
|
merkleProofs,
|
|
1141
1490
|
[Alice, Alice],
|
|
1142
1491
|
),
|
|
1143
1492
|
).rejectedWith("UTXOAlreadyOwned");
|
|
1144
|
-
})
|
|
1493
|
+
});
|
|
1145
1494
|
|
|
1146
1495
|
it("spend by using the same UTXO as both inputs should fail", async function () {
|
|
1147
|
-
const _utxo1 = newUTXO(
|
|
1148
|
-
const _utxo2 = newUTXO(
|
|
1149
|
-
const nullifier1 = newNullifier(
|
|
1150
|
-
const nullifier2 = newNullifier(
|
|
1496
|
+
const _utxo1 = newUTXO(150, Alice);
|
|
1497
|
+
const _utxo2 = newUTXO(50, Bob);
|
|
1498
|
+
const nullifier1 = newNullifier(aliceUtxo1, Alice);
|
|
1499
|
+
const nullifier2 = newNullifier(aliceUtxo1, Alice);
|
|
1151
1500
|
// generate inclusion proofs for the UTXOs to be spent
|
|
1152
1501
|
let root = await smtAlice.root();
|
|
1153
1502
|
const proof1 = await smtAlice.generateCircomVerifierProof(
|
|
1154
|
-
|
|
1503
|
+
aliceUtxo1.hash,
|
|
1155
1504
|
root,
|
|
1156
1505
|
);
|
|
1157
1506
|
const proof2 = await smtAlice.generateCircomVerifierProof(
|
|
1158
|
-
|
|
1507
|
+
aliceUtxo1.hash,
|
|
1159
1508
|
root,
|
|
1160
1509
|
);
|
|
1161
1510
|
const merkleProofs = [
|
|
@@ -1166,7 +1515,7 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
1166
1515
|
await expect(
|
|
1167
1516
|
doTransfer(
|
|
1168
1517
|
Alice,
|
|
1169
|
-
[
|
|
1518
|
+
[aliceUtxo1, aliceUtxo1],
|
|
1170
1519
|
[nullifier1, nullifier2],
|
|
1171
1520
|
[_utxo1, _utxo2],
|
|
1172
1521
|
root.bigInt(),
|
|
@@ -1174,7 +1523,7 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
1174
1523
|
[Alice, Bob],
|
|
1175
1524
|
),
|
|
1176
1525
|
).rejectedWith(`UTXODuplicate`);
|
|
1177
|
-
})
|
|
1526
|
+
});
|
|
1178
1527
|
|
|
1179
1528
|
it("transfer non-existing UTXOs should fail", async function () {
|
|
1180
1529
|
const nonExisting1 = newUTXO(25, Alice);
|
|
@@ -1205,20 +1554,20 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
1205
1554
|
|
|
1206
1555
|
// propose the output UTXOs
|
|
1207
1556
|
const _utxo1 = newUTXO(30, Charlie);
|
|
1208
|
-
|
|
1557
|
+
const _utxo2 = newUTXO(15, Bob);
|
|
1209
1558
|
|
|
1210
1559
|
await expect(
|
|
1211
1560
|
doTransfer(
|
|
1212
1561
|
Alice,
|
|
1213
1562
|
[nonExisting1, nonExisting2],
|
|
1214
1563
|
[nullifier1, nullifier2],
|
|
1215
|
-
[
|
|
1564
|
+
[_utxo1, _utxo2],
|
|
1216
1565
|
root.bigInt(),
|
|
1217
1566
|
merkleProofs,
|
|
1218
|
-
[
|
|
1567
|
+
[Charlie, Bob],
|
|
1219
1568
|
),
|
|
1220
1569
|
).rejectedWith("UTXORootNotFound");
|
|
1221
|
-
})
|
|
1570
|
+
});
|
|
1222
1571
|
|
|
1223
1572
|
it("repeated mint calls with single UTXO should not fail", async function () {
|
|
1224
1573
|
const utxo5 = newUTXO(10, Alice);
|
|
@@ -1236,157 +1585,66 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr
|
|
|
1236
1585
|
root: BigInt,
|
|
1237
1586
|
merkleProofs: BigInt[][],
|
|
1238
1587
|
owners: User[],
|
|
1239
|
-
lockDelegate?: User,
|
|
1240
1588
|
) {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
const circuitToUse =
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
: provingKey;
|
|
1254
|
-
const result = await prepareProof(
|
|
1589
|
+
// NOTE: this helper is only for unlocked-input transfers. Locked-input
|
|
1590
|
+
// transitions go through {createLock,updateLock,delegateLock,spendLock,
|
|
1591
|
+
// cancelLock} — exercised by the "ILockableCapability tests" block.
|
|
1592
|
+
const circuitToUse = inputs.length > 2 ? batchCircuit : circuit;
|
|
1593
|
+
const provingKeyToUse = inputs.length > 2 ? batchProvingKey : provingKey;
|
|
1594
|
+
|
|
1595
|
+
const inflatedInputUtxos = inflateUtxos(inputs);
|
|
1596
|
+
const inflatedNullifiers = inflateUtxos(_nullifiers);
|
|
1597
|
+
const inflatedOutputUtxos = inflateUtxos(outputs);
|
|
1598
|
+
const inflatedOwners = inflateOwners(owners);
|
|
1599
|
+
|
|
1600
|
+
const encodedProof = await prepareProof(
|
|
1255
1601
|
circuitToUse,
|
|
1256
1602
|
provingKeyToUse,
|
|
1257
1603
|
signer,
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1604
|
+
inflatedInputUtxos,
|
|
1605
|
+
inflatedNullifiers,
|
|
1606
|
+
inflatedOutputUtxos,
|
|
1261
1607
|
root,
|
|
1262
1608
|
merkleProofs,
|
|
1263
|
-
|
|
1264
|
-
lockDelegate?.ethAddress,
|
|
1609
|
+
inflatedOwners,
|
|
1265
1610
|
);
|
|
1266
|
-
nullifiers = _nullifiers.map(
|
|
1611
|
+
const nullifiers = _nullifiers.map(
|
|
1267
1612
|
(nullifier) => nullifier.hash,
|
|
1268
1613
|
) as BigNumberish[];
|
|
1269
|
-
outputCommitments =
|
|
1270
|
-
encodedProof = result.encodedProof;
|
|
1614
|
+
const outputCommitments = outputs.map((output) => output.hash);
|
|
1271
1615
|
|
|
1272
|
-
|
|
1616
|
+
return await sendTransfer(
|
|
1273
1617
|
signer,
|
|
1274
1618
|
nullifiers,
|
|
1275
1619
|
outputCommitments,
|
|
1276
1620
|
root,
|
|
1277
1621
|
encodedProof,
|
|
1278
|
-
lockDelegate !== undefined,
|
|
1279
1622
|
);
|
|
1280
|
-
return {
|
|
1281
|
-
txResult,
|
|
1282
|
-
};
|
|
1283
1623
|
}
|
|
1284
1624
|
|
|
1285
|
-
async function
|
|
1625
|
+
async function sendTransfer(
|
|
1286
1626
|
signer: User,
|
|
1287
1627
|
nullifiers: BigNumberish[],
|
|
1288
1628
|
outputCommitments: BigNumberish[],
|
|
1289
1629
|
root: BigNumberish,
|
|
1290
1630
|
encodedProof: any,
|
|
1291
|
-
isLocked: boolean = false,
|
|
1292
1631
|
) {
|
|
1293
1632
|
const startTx = Date.now();
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
encodedProof,
|
|
1301
|
-
"0x",
|
|
1302
|
-
);
|
|
1303
|
-
} else {
|
|
1304
|
-
tx = await zeto.connect(signer.signer).transferLocked(
|
|
1305
|
-
nullifiers.filter((ic) => ic !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
1306
|
-
outputCommitments.filter((oc) => oc !== 0n), // trim off empty utxo hashes to check padding logic for batching works
|
|
1307
|
-
root,
|
|
1308
|
-
encodedProof,
|
|
1309
|
-
"0x",
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1633
|
+
const tx = await zeto.connect(signer.signer).transfer(
|
|
1634
|
+
nullifiers.filter((ic) => ic !== 0n), // trim padding zeros so we exercise the contract padding logic
|
|
1635
|
+
outputCommitments.filter((oc) => oc !== 0n),
|
|
1636
|
+
encodeToBytes(root, encodedProof),
|
|
1637
|
+
"0x",
|
|
1638
|
+
);
|
|
1312
1639
|
const results: ContractTransactionReceipt | null = await tx.wait();
|
|
1313
|
-
|
|
1640
|
+
logger.debug(
|
|
1314
1641
|
`Time to execute transaction: ${Date.now() - startTx}ms. Gas used: ${results?.gasUsed}`,
|
|
1315
1642
|
);
|
|
1316
1643
|
return results;
|
|
1317
1644
|
}
|
|
1318
1645
|
});
|
|
1319
1646
|
|
|
1320
|
-
async function prepareProof(
|
|
1321
|
-
circuit: any,
|
|
1322
|
-
provingKey: any,
|
|
1323
|
-
signer: User,
|
|
1324
|
-
inputs: UTXO[],
|
|
1325
|
-
_nullifiers: UTXO[],
|
|
1326
|
-
outputs: UTXO[],
|
|
1327
|
-
root: BigInt,
|
|
1328
|
-
merkleProof: BigInt[][],
|
|
1329
|
-
owners: User[],
|
|
1330
|
-
lockDelegate?: string,
|
|
1331
|
-
) {
|
|
1332
|
-
const nullifiers = _nullifiers.map((nullifier) => nullifier.hash) as [
|
|
1333
|
-
BigNumberish,
|
|
1334
|
-
BigNumberish,
|
|
1335
|
-
];
|
|
1336
|
-
const inputCommitments: BigNumberish[] = inputs.map(
|
|
1337
|
-
(input) => input.hash,
|
|
1338
|
-
) as BigNumberish[];
|
|
1339
|
-
const inputValues = inputs.map((input) => BigInt(input.value || 0n));
|
|
1340
|
-
const inputSalts = inputs.map((input) => input.salt || 0n);
|
|
1341
|
-
const outputCommitments: BigNumberish[] = outputs.map(
|
|
1342
|
-
(output) => output.hash,
|
|
1343
|
-
) as BigNumberish[];
|
|
1344
|
-
const outputValues = outputs.map((output) => BigInt(output.value || 0n));
|
|
1345
|
-
const outputOwnerPublicKeys: BigNumberish[][] = owners.map(
|
|
1346
|
-
(owner) => owner.babyJubPublicKey,
|
|
1347
|
-
) as BigNumberish[][];
|
|
1348
|
-
|
|
1349
|
-
const startWitnessCalculation = Date.now();
|
|
1350
|
-
const inputObj: any = {
|
|
1351
|
-
nullifiers,
|
|
1352
|
-
inputCommitments,
|
|
1353
|
-
inputValues,
|
|
1354
|
-
inputSalts,
|
|
1355
|
-
inputOwnerPrivateKey: signer.formattedPrivateKey,
|
|
1356
|
-
root,
|
|
1357
|
-
enabled: nullifiers.map((n) => (n !== 0n ? 1 : 0)),
|
|
1358
|
-
merkleProof,
|
|
1359
|
-
outputCommitments,
|
|
1360
|
-
outputValues,
|
|
1361
|
-
outputSalts: outputs.map((output) => output.salt || 0n),
|
|
1362
|
-
outputOwnerPublicKeys,
|
|
1363
|
-
};
|
|
1364
|
-
if (lockDelegate) {
|
|
1365
|
-
inputObj["lockDelegate"] = ethers.toBigInt(lockDelegate);
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
const witness = await circuit.calculateWTNSBin(inputObj, true);
|
|
1369
|
-
const timeWithnessCalculation = Date.now() - startWitnessCalculation;
|
|
1370
|
-
|
|
1371
|
-
const startProofGeneration = Date.now();
|
|
1372
|
-
const { proof, publicSignals } = (await groth16.prove(
|
|
1373
|
-
provingKey,
|
|
1374
|
-
witness,
|
|
1375
|
-
)) as { proof: BigNumberish[]; publicSignals: BigNumberish[] };
|
|
1376
|
-
const timeProofGeneration = Date.now() - startProofGeneration;
|
|
1377
|
-
|
|
1378
|
-
console.log(
|
|
1379
|
-
`Witness calculation time: ${timeWithnessCalculation}ms. Proof generation time: ${timeProofGeneration}ms.`,
|
|
1380
|
-
);
|
|
1381
|
-
|
|
1382
|
-
const encodedProof = encodeProof(proof);
|
|
1383
|
-
return {
|
|
1384
|
-
inputCommitments,
|
|
1385
|
-
outputCommitments,
|
|
1386
|
-
encodedProof,
|
|
1387
|
-
};
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
1647
|
module.exports = {
|
|
1391
1648
|
prepareProof,
|
|
1649
|
+
encodeToBytes,
|
|
1392
1650
|
};
|