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