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