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.
@@ -1,300 +0,0 @@
1
- // SPDX-License-Identifier: BSD-3-Clause-Clear
2
- pragma solidity ^0.8.24;
3
-
4
- import {
5
- FHE,
6
- euint32,
7
- ebool,
8
- externalEuint32
9
- } from "@fhevm/solidity/lib/FHE.sol";
10
- import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
-
12
- /**
13
- * @notice Comprehensive guide to FHE anti-patterns and their solutions.
14
- * Covers 9 critical mistakes: using if/else on encrypted values,
15
- * incorrect permission patterns, require() statements that leak info,
16
- * unbounded loops, noise accumulation, and deprecated APIs.
17
- * Each pattern shows both ❌ WRONG and ✅ CORRECT implementations.
18
- *
19
- * @dev Covers 9 critical anti-patterns with ❌ WRONG and ✅ CORRECT examples.
20
- * This is an educational contract - study each pattern before building production code!
21
- */
22
- contract FHEAntiPatterns is ZamaEthereumConfig {
23
- euint32 private _secretBalance;
24
- euint32 private _threshold;
25
-
26
- /// @notice Initialize the contract with encrypted balance and threshold values
27
- /// @dev Both values are encrypted and permissions are granted to the caller
28
- function initialize(
29
- externalEuint32 balance,
30
- externalEuint32 threshold,
31
- bytes calldata inputProof
32
- ) external {
33
- _secretBalance = FHE.fromExternal(balance, inputProof);
34
- _threshold = FHE.fromExternal(threshold, inputProof);
35
-
36
- FHE.allowThis(_secretBalance);
37
- FHE.allowThis(_threshold);
38
- FHE.allow(_secretBalance, msg.sender);
39
- FHE.allow(_threshold, msg.sender);
40
- }
41
-
42
- // ANTI-PATTERN 1: Branching on encrypted values
43
-
44
- /**
45
- * ❌ WRONG: if/else on encrypted value causes decryption
46
- * @dev This pattern LEAKS information by decrypting on-chain
47
- *
48
- * DO NOT USE THIS PATTERN - It defeats the purpose of encryption!
49
- */
50
- // function wrongBranching() external {
51
- // // ❌ This would compile but LEAK the comparison result!
52
- // // if (_secretBalance > _threshold) { // WRONG: decrypts!
53
- // // // do something
54
- // // }
55
- // }
56
-
57
- /**
58
- * @notice ✅ CORRECT: Use FHE.select for conditional logic
59
- * @dev All computation stays encrypted - demonstrates proper encrypted branching
60
- */
61
- function correctConditional() external {
62
- // Compare encrypted values - result is encrypted boolean
63
- ebool isAboveThreshold = FHE.gt(_secretBalance, _threshold);
64
-
65
- // Use select for encrypted branching
66
- // If above threshold: result = balance - 10
67
- // Else: result = balance
68
- euint32 penaltyAmount = FHE.asEuint32(10);
69
- euint32 balanceMinusPenalty = FHE.sub(_secretBalance, penaltyAmount);
70
-
71
- // ✅ Encrypted conditional - no information leaked
72
- _secretBalance = FHE.select(
73
- isAboveThreshold,
74
- balanceMinusPenalty,
75
- _secretBalance
76
- );
77
-
78
- FHE.allowThis(_secretBalance);
79
- FHE.allow(_secretBalance, msg.sender);
80
- }
81
-
82
- // ANTI-PATTERN 2: View function returning encrypted without permissions
83
-
84
- /**
85
- * @notice ❌ WRONG: Returns encrypted value but caller can't decrypt
86
- * @dev Without prior allow(), the returned handle is useless
87
- */
88
- function wrongGetBalance() external view returns (euint32) {
89
- // ❌ Caller has no permission to decrypt this!
90
- return _secretBalance;
91
- }
92
-
93
- /**
94
- * @notice ✅ CORRECT: Ensure permissions were granted before returning
95
- * @dev Caller must have been granted access in a previous transaction
96
- */
97
- function correctGetBalance() external view returns (euint32) {
98
- // ✅ Caller should have been granted access via allow()
99
- // The initialize() function grants access to msg.sender
100
- return _secretBalance;
101
- }
102
-
103
- // ANTI-PATTERN 3: Using require/revert with encrypted conditions
104
-
105
- /**
106
- * ❌ WRONG: Cannot use require with encrypted values
107
- * @dev This pattern doesn't even compile - encrypted bools can't be used in require
108
- */
109
- // function wrongRequire() external {
110
- // ebool hasEnough = FHE.ge(_secretBalance, FHE.asEuint32(100));
111
- // // ❌ COMPILE ERROR: require expects bool, not ebool
112
- // // require(hasEnough, "Insufficient balance");
113
- // }
114
-
115
- /**
116
- * @notice ✅ CORRECT: Use encrypted flags instead of require
117
- * @dev Store result and let client check after decryption
118
- */
119
- function correctValidation() external returns (ebool) {
120
- // ✅ Return encrypted boolean for client to check
121
- ebool hasEnough = FHE.ge(_secretBalance, FHE.asEuint32(100));
122
-
123
- FHE.allowThis(hasEnough);
124
- FHE.allow(hasEnough, msg.sender);
125
-
126
- return hasEnough;
127
- }
128
-
129
- // ANTI-PATTERN 4: Encrypted computation without permission grants
130
-
131
- /**
132
- * @notice ❌ WRONG: Compute but forget to grant permissions
133
- * @dev Result exists but no one can ever decrypt it
134
- */
135
- function wrongCompute() external {
136
- euint32 doubled = FHE.mul(_secretBalance, FHE.asEuint32(2));
137
- _secretBalance = doubled;
138
- // ❌ Missing FHE.allowThis and FHE.allow!
139
- // This value is now locked forever
140
- }
141
-
142
- /// @notice ✅ CORRECT: Always grant permissions after computation
143
- function correctCompute() external {
144
- euint32 doubled = FHE.mul(_secretBalance, FHE.asEuint32(2));
145
- _secretBalance = doubled;
146
-
147
- // ✅ Grant permissions
148
- FHE.allowThis(_secretBalance);
149
- FHE.allow(_secretBalance, msg.sender);
150
- }
151
-
152
- // ANTI-PATTERN 5: Leaking information through gas/timing
153
-
154
- /**
155
- * @notice ⚠️ CAUTION: Be aware of side-channel attacks
156
- * @dev Even with FHE.select, be careful about operations that might
157
- * have different gas costs based on values
158
- *
159
- * BEST PRACTICES:
160
- * - Use constant-time operations when possible
161
- * - Avoid loops with encrypted iteration counts
162
- * - Don't make external calls conditionally based on encrypted values
163
- */
164
- function cautionSideChannels() external pure returns (string memory) {
165
- return "Be aware of gas/timing side channels";
166
- }
167
-
168
- // ANTI-PATTERN 6: Unauthenticated Re-encryption (SECURITY CRITICAL)
169
-
170
- /**
171
- * @notice ❌ WRONG: Re-encrypt for any provided public key
172
- * @dev This allows impersonation attacks - anyone can request re-encryption
173
- * for any public key and pretend to be that user
174
- *
175
- * ATTACK SCENARIO:
176
- * 1. Alice has encrypted balance
177
- * 2. Eve calls wrongReencrypt(evePublicKey)
178
- * 3. Eve gets Alice's balance re-encrypted for her key
179
- * 4. Eve decrypts and learns Alice's secret balance!
180
- */
181
- // function wrongReencrypt(bytes calldata userPublicKey) external view {
182
- // // ❌ NO AUTHENTICATION! Anyone can provide any public key
183
- // // Re-encrypt _secretBalance for userPublicKey
184
- // // This leaks information to unauthorized users
185
- // }
186
-
187
- /**
188
- * @notice ✅ CORRECT: Require cryptographic proof of identity
189
- * @dev Use EIP-712 signature to prove the requester owns the public key
190
- *
191
- * CLIENT-SIDE:
192
- * 1. User signs a message: "I authorize re-encryption for contract X"
193
- * 2. Signature is verified on-chain before re-encryption
194
- *
195
- * Note: In practice, this is handled by the FHEVM SDK's userDecrypt flow
196
- */
197
- function correctReencryptPattern() external pure returns (string memory) {
198
- return
199
- "Always verify EIP-712 signature before re-encryption. "
200
- "Use fhevm.js userDecrypt which handles this automatically.";
201
- }
202
-
203
- // ANTI-PATTERN 7: Encrypted Loop Iterations (GAS/TIMING LEAK)
204
-
205
- /// ❌ WRONG: Using encrypted value as loop count
206
- /// @dev Loop count is visible through gas consumption and timing
207
- ///
208
- /// PROBLEM: If we loop `encryptedCount` times, the gas cost reveals the count!
209
- // function wrongEncryptedLoop(euint32 encryptedCount) external {
210
- // // ❌ GAS LEAK: Number of iterations visible!
211
- // // for (uint i = 0; i < decrypt(encryptedCount); i++) {
212
- // // // Each iteration costs gas
213
- // // }
214
- // }
215
-
216
- /// @notice ✅ CORRECT: Use fixed iteration count with select
217
- /// @dev Always iterate the maximum possible times, use FHE.select to
218
- /// conditionally apply operations
219
- function correctFixedIterations() external {
220
- // ✅ Fixed iteration count - no information leaked
221
- uint256 MAX_ITERATIONS = 10;
222
-
223
- euint32 accumulator = FHE.asEuint32(0);
224
- euint32 counter = FHE.asEuint32(0);
225
-
226
- for (uint256 i = 0; i < MAX_ITERATIONS; i++) {
227
- // Check if we should still be iterating
228
- ebool shouldContinue = FHE.lt(counter, _secretBalance);
229
-
230
- // Conditionally add (add 1 if continuing, add 0 otherwise)
231
- euint32 increment = FHE.select(
232
- shouldContinue,
233
- FHE.asEuint32(1),
234
- FHE.asEuint32(0)
235
- );
236
- accumulator = FHE.add(accumulator, increment);
237
- counter = FHE.add(counter, FHE.asEuint32(1));
238
- }
239
-
240
- FHE.allowThis(accumulator);
241
- FHE.allow(accumulator, msg.sender);
242
- }
243
-
244
- // ANTI-PATTERN 8: Too Many Chained Operations (Noise Accumulation)
245
-
246
- /// @notice ⚠️ CAUTION: FHE operations accumulate "noise"
247
- /// @dev Each FHE operation adds noise to the ciphertext. After too many
248
- /// operations, the ciphertext becomes corrupted and undecryptable.
249
- ///
250
- /// FHEVM handles this via "bootstrapping" which is expensive.
251
- /// Best practice: minimize operation chains where possible.
252
- ///
253
- /// EXAMPLE OF NOISE ACCUMULATION:
254
- /// - Each add/sub: +1 noise
255
- /// - Each mul: +10 noise (roughly)
256
- /// - Bootstrapping threshold: ~100 noise (varies by scheme)
257
- function cautionNoiseAccumulation() external pure returns (string memory) {
258
- return
259
- "Keep FHE operation chains short. Multiplications add more noise than additions. "
260
- "If you need many operations, consider batching or restructuring logic.";
261
- }
262
-
263
- // ANTI-PATTERN 9: Using Deprecated FHEVM APIs
264
-
265
- /**
266
- * @notice Caution about deprecated FHEVM APIs
267
- * @dev OLD (v0.8 and earlier):
268
- * - Decryption went through Zama Oracle
269
- * - Used TFHE.decrypt() directly
270
- *
271
- * NEW (v0.9+):
272
- * - Self-relaying public decryption
273
- * - Use FHE.makePubliclyDecryptable() + off-chain relay
274
- */
275
- function cautionDeprecatedAPIs() external pure returns (string memory) {
276
- return
277
- "Use FHEVM v0.9+ APIs. Old TFHE.decrypt() is deprecated. "
278
- "Use FHE.makePubliclyDecryptable() for public decryption, "
279
- "or userDecrypt pattern via fhevm.js for user decryption.";
280
- }
281
-
282
- // SUMMARY: Key Rules
283
-
284
- /**
285
- * @notice Quick reference for FHE best practices
286
- * @return rules Summary of all 9 key rules
287
- */
288
- function getRules() external pure returns (string memory rules) {
289
- return
290
- "1. Never branch (if/else) on encrypted values - use FHE.select\n"
291
- "2. Always call FHE.allowThis() AND FHE.allow(user) after computation\n"
292
- "3. Cannot use require/revert with encrypted conditions\n"
293
- "4. Return ebool for validations, let client decrypt and check\n"
294
- "5. Be aware of gas/timing side channels\n"
295
- "6. Always authenticate re-encryption requests (use EIP-712 signatures)\n"
296
- "7. Never use encrypted values as loop iteration counts\n"
297
- "8. Avoid chaining too many FHE operations (noise accumulation)\n"
298
- "9. Use FHEVM v0.9+ APIs, avoid deprecated TFHE.decrypt()";
299
- }
300
- }