create-fhevm-example 1.4.5 → 1.4.6

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.
@@ -0,0 +1,187 @@
1
+ import {
2
+ FhevmType,
3
+ HardhatFhevmRuntimeEnvironment,
4
+ } from "@fhevm/hardhat-plugin";
5
+ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
6
+ import { expect } from "chai";
7
+ import { ethers } from "hardhat";
8
+ import * as hre from "hardhat";
9
+
10
+ import {
11
+ FHEOperationsGasNoiseAntiPatterns,
12
+ FHEOperationsGasNoiseAntiPatterns__factory,
13
+ } from "../types";
14
+ import type { Signers } from "./types";
15
+
16
+ async function deployFixture() {
17
+ const factory = (await ethers.getContractFactory(
18
+ "FHEOperationsGasNoiseAntiPatterns"
19
+ )) as FHEOperationsGasNoiseAntiPatterns__factory;
20
+ const contract =
21
+ (await factory.deploy()) as FHEOperationsGasNoiseAntiPatterns;
22
+ const contractAddress = await contract.getAddress();
23
+ return { contract, contractAddress };
24
+ }
25
+
26
+ /**
27
+ * @notice Tests for FHE operations, gas, and noise anti-patterns
28
+ * Demonstrates performance issues and optimization techniques
29
+ */
30
+ describe("FHEOperationsGasNoiseAntiPatterns", function () {
31
+ let contract: FHEOperationsGasNoiseAntiPatterns;
32
+ let contractAddress: string;
33
+ let signers: Signers;
34
+
35
+ before(async function () {
36
+ if (!hre.fhevm.isMock) {
37
+ throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
38
+ }
39
+ const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
40
+ signers = { owner: ethSigners[0], alice: ethSigners[1] };
41
+ });
42
+
43
+ beforeEach(async function () {
44
+ const deployment = await deployFixture();
45
+ contractAddress = deployment.contractAddress;
46
+ contract = deployment.contract;
47
+ });
48
+
49
+ describe("Pattern 1: Gas/Timing Side Channel", function () {
50
+ const testValue = 50;
51
+
52
+ it("wrongGasLeak should store value", async function () {
53
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
54
+
55
+ const input = await fhevm
56
+ .createEncryptedInput(contractAddress, signers.alice.address)
57
+ .add32(testValue)
58
+ .encrypt();
59
+
60
+ await contract
61
+ .connect(signers.alice)
62
+ .wrongGasLeak(input.handles[0], input.inputProof);
63
+
64
+ // Value should be stored (but without permission to decrypt)
65
+ });
66
+
67
+ it("correctConstantTime should use constant gas", async function () {
68
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
69
+
70
+ const input = await fhevm
71
+ .createEncryptedInput(contractAddress, signers.alice.address)
72
+ .add32(testValue)
73
+ .encrypt();
74
+
75
+ await contract
76
+ .connect(signers.alice)
77
+ .correctConstantTime(input.handles[0], input.inputProof);
78
+
79
+ const encrypted = await contract.getValue();
80
+ const decrypted = await fhevm.userDecryptEuint(
81
+ FhevmType.euint32,
82
+ encrypted,
83
+ contractAddress,
84
+ signers.alice
85
+ );
86
+
87
+ expect(decrypted).to.equal(testValue * 2);
88
+ });
89
+ });
90
+
91
+ describe("Pattern 2: Noise Accumulation", function () {
92
+ const testValue = 2;
93
+
94
+ it("wrongNoiseAccumulation should chain many operations", async function () {
95
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
96
+
97
+ const input = await fhevm
98
+ .createEncryptedInput(contractAddress, signers.alice.address)
99
+ .add32(testValue)
100
+ .encrypt();
101
+
102
+ await contract
103
+ .connect(signers.alice)
104
+ .wrongNoiseAccumulation(input.handles[0], input.inputProof);
105
+
106
+ // Result stored but may have accumulated noise
107
+ });
108
+
109
+ it("correctMinimizeOperations should use single operation", async function () {
110
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
111
+
112
+ const input = await fhevm
113
+ .createEncryptedInput(contractAddress, signers.alice.address)
114
+ .add32(testValue)
115
+ .encrypt();
116
+
117
+ await contract
118
+ .connect(signers.alice)
119
+ .correctMinimizeOperations(input.handles[0], input.inputProof);
120
+
121
+ const encrypted = await contract.getValue();
122
+ const decrypted = await fhevm.userDecryptEuint(
123
+ FhevmType.euint32,
124
+ encrypted,
125
+ contractAddress,
126
+ signers.alice
127
+ );
128
+
129
+ // 2 * 32 = 64
130
+ expect(decrypted).to.equal(64);
131
+ });
132
+ });
133
+
134
+ describe("Pattern 3: Deprecated APIs", function () {
135
+ it("wrongDeprecatedAPI should return warning string", async function () {
136
+ const result = await contract.wrongDeprecatedAPI();
137
+ expect(result).to.include("deprecated");
138
+ });
139
+
140
+ it("correctModernAPI should return guidance string", async function () {
141
+ const result = await contract.correctModernAPI();
142
+ expect(result).to.include("FHE.makePubliclyDecryptable");
143
+ });
144
+ });
145
+
146
+ describe("Pattern 4: Type Mismatch", function () {
147
+ const testValue = 100;
148
+
149
+ it("wrongOversizedType should use euint256 unnecessarily", async function () {
150
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
151
+
152
+ const input = await fhevm
153
+ .createEncryptedInput(contractAddress, signers.alice.address)
154
+ .add32(testValue)
155
+ .encrypt();
156
+
157
+ await contract
158
+ .connect(signers.alice)
159
+ .wrongOversizedType(input.handles[0], input.inputProof);
160
+
161
+ // Value stored but with wasteful type conversion
162
+ });
163
+
164
+ it("correctRightSizedType should use euint32", async function () {
165
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
166
+
167
+ const input = await fhevm
168
+ .createEncryptedInput(contractAddress, signers.alice.address)
169
+ .add32(testValue)
170
+ .encrypt();
171
+
172
+ await contract
173
+ .connect(signers.alice)
174
+ .correctRightSizedType(input.handles[0], input.inputProof);
175
+
176
+ const encrypted = await contract.getValue();
177
+ const decrypted = await fhevm.userDecryptEuint(
178
+ FhevmType.euint32,
179
+ encrypted,
180
+ contractAddress,
181
+ signers.alice
182
+ );
183
+
184
+ expect(decrypted).to.equal(testValue * 2);
185
+ });
186
+ });
187
+ });
@@ -0,0 +1,327 @@
1
+ import {
2
+ FhevmType,
3
+ HardhatFhevmRuntimeEnvironment,
4
+ } from "@fhevm/hardhat-plugin";
5
+ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
6
+ import { expect } from "chai";
7
+ import { ethers } from "hardhat";
8
+ import * as hre from "hardhat";
9
+
10
+ import {
11
+ FHEPermissionsAntiPatterns,
12
+ FHEPermissionsAntiPatterns__factory,
13
+ } from "../types";
14
+ import type { Signers } from "./types";
15
+
16
+ async function deployFixture() {
17
+ const factory = (await ethers.getContractFactory(
18
+ "FHEPermissionsAntiPatterns"
19
+ )) as FHEPermissionsAntiPatterns__factory;
20
+ const contract = (await factory.deploy()) as FHEPermissionsAntiPatterns;
21
+ const contractAddress = await contract.getAddress();
22
+ return { contract, contractAddress };
23
+ }
24
+
25
+ /**
26
+ * @notice Tests for FHE permission anti-patterns
27
+ * Demonstrates wrong and correct permission handling patterns
28
+ */
29
+ describe("FHEPermissionsAntiPatterns", function () {
30
+ let contract: FHEPermissionsAntiPatterns;
31
+ let contractAddress: string;
32
+ let signers: Signers;
33
+ let bob: HardhatEthersSigner;
34
+
35
+ before(async function () {
36
+ if (!hre.fhevm.isMock) {
37
+ throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
38
+ }
39
+ const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
40
+ signers = { owner: ethSigners[0], alice: ethSigners[1] };
41
+ bob = ethSigners[2];
42
+ });
43
+
44
+ beforeEach(async function () {
45
+ const deployment = await deployFixture();
46
+ contractAddress = deployment.contractAddress;
47
+ contract = deployment.contract;
48
+ });
49
+
50
+ describe("Pattern 1: Missing allowThis", function () {
51
+ const testValue = 42;
52
+
53
+ it("should FAIL without allowThis", async function () {
54
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
55
+
56
+ const input = await fhevm
57
+ .createEncryptedInput(contractAddress, signers.alice.address)
58
+ .add32(testValue)
59
+ .encrypt();
60
+
61
+ await contract
62
+ .connect(signers.alice)
63
+ .wrongMissingAllowThis(input.handles[0], input.inputProof);
64
+
65
+ // Decryption should fail
66
+ const encrypted = await contract.getValue();
67
+ await expect(
68
+ fhevm.userDecryptEuint(
69
+ FhevmType.euint32,
70
+ encrypted,
71
+ contractAddress,
72
+ signers.alice
73
+ )
74
+ ).to.be.rejected;
75
+ });
76
+
77
+ it("should succeed with allowThis", async function () {
78
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
79
+
80
+ const input = await fhevm
81
+ .createEncryptedInput(contractAddress, signers.alice.address)
82
+ .add32(testValue)
83
+ .encrypt();
84
+
85
+ await contract
86
+ .connect(signers.alice)
87
+ .correctWithAllowThis(input.handles[0], input.inputProof);
88
+
89
+ const encrypted = await contract.getValue();
90
+ const decrypted = await fhevm.userDecryptEuint(
91
+ FhevmType.euint32,
92
+ encrypted,
93
+ contractAddress,
94
+ signers.alice
95
+ );
96
+
97
+ expect(decrypted).to.equal(testValue * 2);
98
+ });
99
+ });
100
+
101
+ describe("Pattern 2: Missing allow(user)", function () {
102
+ const testValue = 100;
103
+
104
+ it("should FAIL without user allow", async function () {
105
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
106
+
107
+ const input = await fhevm
108
+ .createEncryptedInput(contractAddress, signers.alice.address)
109
+ .add32(testValue)
110
+ .encrypt();
111
+
112
+ await contract
113
+ .connect(signers.alice)
114
+ .wrongMissingUserAllow(input.handles[0], input.inputProof);
115
+
116
+ const encrypted = await contract.getValue();
117
+ await expect(
118
+ fhevm.userDecryptEuint(
119
+ FhevmType.euint32,
120
+ encrypted,
121
+ contractAddress,
122
+ signers.alice
123
+ )
124
+ ).to.be.rejected;
125
+ });
126
+
127
+ it("should succeed with user allow", async function () {
128
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
129
+
130
+ const input = await fhevm
131
+ .createEncryptedInput(contractAddress, signers.alice.address)
132
+ .add32(testValue)
133
+ .encrypt();
134
+
135
+ await contract
136
+ .connect(signers.alice)
137
+ .correctWithUserAllow(input.handles[0], input.inputProof);
138
+
139
+ const encrypted = await contract.getValue();
140
+ const decrypted = await fhevm.userDecryptEuint(
141
+ FhevmType.euint32,
142
+ encrypted,
143
+ contractAddress,
144
+ signers.alice
145
+ );
146
+
147
+ expect(decrypted).to.equal(testValue);
148
+ });
149
+ });
150
+
151
+ describe("Pattern 3: View Function Without Permissions", function () {
152
+ const testValue = 200;
153
+
154
+ it("should FAIL when stored without permission", async function () {
155
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
156
+
157
+ const input = await fhevm
158
+ .createEncryptedInput(contractAddress, signers.alice.address)
159
+ .add32(testValue)
160
+ .encrypt();
161
+
162
+ await contract
163
+ .connect(signers.alice)
164
+ .wrongStoreWithoutPermission(input.handles[0], input.inputProof);
165
+
166
+ const encrypted = await contract.getValue();
167
+ await expect(
168
+ fhevm.userDecryptEuint(
169
+ FhevmType.euint32,
170
+ encrypted,
171
+ contractAddress,
172
+ signers.alice
173
+ )
174
+ ).to.be.rejected;
175
+ });
176
+
177
+ it("should succeed when stored with permission", async function () {
178
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
179
+
180
+ const input = await fhevm
181
+ .createEncryptedInput(contractAddress, signers.alice.address)
182
+ .add32(testValue)
183
+ .encrypt();
184
+
185
+ await contract
186
+ .connect(signers.alice)
187
+ .correctStoreWithPermission(input.handles[0], input.inputProof);
188
+
189
+ const encrypted = await contract.getValue();
190
+ const decrypted = await fhevm.userDecryptEuint(
191
+ FhevmType.euint32,
192
+ encrypted,
193
+ contractAddress,
194
+ signers.alice
195
+ );
196
+
197
+ expect(decrypted).to.equal(testValue);
198
+ });
199
+ });
200
+
201
+ describe("Pattern 4: Unauthenticated Re-encryption", function () {
202
+ it("wrongReencryptWithoutAuth should return empty bytes", async function () {
203
+ const dummyPublicKey =
204
+ "0x0000000000000000000000000000000000000000000000000000000000000000";
205
+ const result = await contract.wrongReencryptWithoutAuth(dummyPublicKey);
206
+ expect(result).to.equal("0x");
207
+ });
208
+
209
+ it("correctReencryptWithAuth should return encrypted handle", async function () {
210
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
211
+
212
+ const input = await fhevm
213
+ .createEncryptedInput(contractAddress, signers.alice.address)
214
+ .add32(123)
215
+ .encrypt();
216
+
217
+ await contract
218
+ .connect(signers.alice)
219
+ .correctWithUserAllow(input.handles[0], input.inputProof);
220
+
221
+ const encrypted = await contract.correctReencryptWithAuth();
222
+ expect(encrypted).to.not.equal(0);
223
+ });
224
+ });
225
+
226
+ describe("Pattern 5: Transfer Without Permission", function () {
227
+ const initialBalance = 1000;
228
+ const transferAmount = 100;
229
+
230
+ beforeEach(async function () {
231
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
232
+
233
+ // Initialize Alice's balance
234
+ const input = await fhevm
235
+ .createEncryptedInput(contractAddress, signers.alice.address)
236
+ .add32(initialBalance)
237
+ .encrypt();
238
+
239
+ await contract
240
+ .connect(signers.alice)
241
+ .initializeBalance(input.handles[0], input.inputProof);
242
+ });
243
+
244
+ it("should FAIL recipient decryption without permission", async function () {
245
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
246
+
247
+ const input = await fhevm
248
+ .createEncryptedInput(contractAddress, signers.alice.address)
249
+ .add32(transferAmount)
250
+ .encrypt();
251
+
252
+ await contract
253
+ .connect(signers.alice)
254
+ .wrongTransferWithoutPermission(
255
+ bob.address,
256
+ input.handles[0],
257
+ input.inputProof
258
+ );
259
+
260
+ // Bob cannot decrypt his balance
261
+ const encrypted = await contract.getBalance(bob.address);
262
+ await expect(
263
+ fhevm.userDecryptEuint(
264
+ FhevmType.euint32,
265
+ encrypted,
266
+ contractAddress,
267
+ bob
268
+ )
269
+ ).to.be.rejected;
270
+ });
271
+
272
+ it("should succeed with permission propagation", async function () {
273
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
274
+
275
+ const input = await fhevm
276
+ .createEncryptedInput(contractAddress, signers.alice.address)
277
+ .add32(transferAmount)
278
+ .encrypt();
279
+
280
+ await contract
281
+ .connect(signers.alice)
282
+ .correctTransferWithPermission(
283
+ bob.address,
284
+ input.handles[0],
285
+ input.inputProof
286
+ );
287
+
288
+ // Bob can decrypt his balance
289
+ const encrypted = await contract.getBalance(bob.address);
290
+ const decrypted = await fhevm.userDecryptEuint(
291
+ FhevmType.euint32,
292
+ encrypted,
293
+ contractAddress,
294
+ bob
295
+ );
296
+
297
+ expect(decrypted).to.equal(transferAmount);
298
+ });
299
+ });
300
+
301
+ describe("Pattern 6: Cross-Contract Permission", function () {
302
+ beforeEach(async function () {
303
+ // Initialize _secretValue so the contract has permission on it
304
+ const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
305
+ const input = await fhevm
306
+ .createEncryptedInput(contractAddress, signers.alice.address)
307
+ .add32(42)
308
+ .encrypt();
309
+
310
+ await contract
311
+ .connect(signers.alice)
312
+ .correctWithAllowThis(input.handles[0], input.inputProof);
313
+ });
314
+
315
+ it("wrongCrossContractCall should call without permission", async function () {
316
+ const dummyAddress = "0x0000000000000000000000000000000000000001";
317
+ // This will fail but we're just testing it doesn't revert
318
+ await contract.wrongCrossContractCall(dummyAddress);
319
+ });
320
+
321
+ it("correctCrossContractCall should grant allowTransient", async function () {
322
+ const dummyAddress = "0x0000000000000000000000000000000000000001";
323
+ // This should succeed - contract has permission on _secretValue
324
+ await contract.correctCrossContractCall(dummyAddress);
325
+ });
326
+ });
327
+ });