create-fhevm-example 1.3.1 → 1.4.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.
Files changed (122) hide show
  1. package/contracts/advanced/BlindAuction.sol +255 -0
  2. package/contracts/advanced/EncryptedEscrow.sol +315 -0
  3. package/contracts/advanced/HiddenVoting.sol +231 -0
  4. package/contracts/advanced/PrivateKYC.sol +309 -0
  5. package/contracts/advanced/PrivatePayroll.sol +285 -0
  6. package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
  7. package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
  8. package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
  9. package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
  10. package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
  11. package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
  12. package/contracts/basic/encryption/FHECounter.sol +54 -0
  13. package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
  14. package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
  15. package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
  16. package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
  17. package/contracts/concepts/FHEAccessControl.sol +94 -0
  18. package/contracts/concepts/FHEAntiPatterns.sol +329 -0
  19. package/contracts/concepts/FHEHandles.sol +128 -0
  20. package/contracts/concepts/FHEInputProof.sol +104 -0
  21. package/contracts/gaming/EncryptedLottery.sol +298 -0
  22. package/contracts/gaming/EncryptedPoker.sol +337 -0
  23. package/contracts/gaming/RockPaperScissors.sol +213 -0
  24. package/contracts/openzeppelin/ERC7984.sol +85 -0
  25. package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
  26. package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
  27. package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
  28. package/contracts/openzeppelin/VestingWallet.sol +147 -0
  29. package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
  30. package/dist/scripts/commands/add-mode.d.ts.map +1 -0
  31. package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
  32. package/dist/scripts/commands/doctor.d.ts.map +1 -0
  33. package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
  34. package/dist/scripts/commands/generate-config.d.ts.map +1 -0
  35. package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
  36. package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
  37. package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
  38. package/dist/scripts/commands/maintenance.d.ts.map +1 -0
  39. package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
  40. package/dist/scripts/index.js +63 -59
  41. package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
  42. package/dist/scripts/shared/builders.d.ts.map +1 -0
  43. package/dist/scripts/{builders.js → shared/builders.js} +49 -30
  44. package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
  45. package/dist/scripts/shared/config.d.ts.map +1 -0
  46. package/dist/scripts/{config.js → shared/config.js} +48 -59
  47. package/dist/scripts/shared/generators.d.ts +42 -0
  48. package/dist/scripts/shared/generators.d.ts.map +1 -0
  49. package/dist/scripts/{utils.js → shared/generators.js} +34 -271
  50. package/dist/scripts/shared/ui.d.ts.map +1 -0
  51. package/dist/scripts/{ui.js → shared/ui.js} +3 -2
  52. package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
  53. package/dist/scripts/shared/utils.d.ts.map +1 -0
  54. package/dist/scripts/shared/utils.js +228 -0
  55. package/fhevm-hardhat-template/.eslintignore +26 -0
  56. package/fhevm-hardhat-template/.eslintrc.yml +21 -0
  57. package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
  58. package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
  59. package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
  60. package/fhevm-hardhat-template/.prettierignore +25 -0
  61. package/fhevm-hardhat-template/.prettierrc.yml +15 -0
  62. package/fhevm-hardhat-template/.solcover.js +4 -0
  63. package/fhevm-hardhat-template/.solhint.json +12 -0
  64. package/fhevm-hardhat-template/.solhintignore +3 -0
  65. package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
  66. package/fhevm-hardhat-template/.vscode/settings.json +9 -0
  67. package/fhevm-hardhat-template/LICENSE +33 -0
  68. package/fhevm-hardhat-template/README.md +110 -0
  69. package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
  70. package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
  71. package/fhevm-hardhat-template/hardhat.config.ts +90 -0
  72. package/fhevm-hardhat-template/package-lock.json +10405 -0
  73. package/fhevm-hardhat-template/package.json +104 -0
  74. package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
  75. package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
  76. package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
  77. package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
  78. package/fhevm-hardhat-template/tsconfig.json +23 -0
  79. package/package.json +13 -10
  80. package/test/advanced/BlindAuction.ts +246 -0
  81. package/test/advanced/EncryptedEscrow.ts +295 -0
  82. package/test/advanced/HiddenVoting.ts +268 -0
  83. package/test/advanced/PrivateKYC.ts +382 -0
  84. package/test/advanced/PrivatePayroll.ts +253 -0
  85. package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
  86. package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
  87. package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
  88. package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
  89. package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
  90. package/test/basic/encryption/EncryptSingleValue.ts +124 -0
  91. package/test/basic/encryption/FHECounter.ts +112 -0
  92. package/test/basic/fhe-operations/FHEAdd.ts +97 -0
  93. package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
  94. package/test/basic/fhe-operations/FHEComparison.ts +167 -0
  95. package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
  96. package/test/concepts/FHEAccessControl.ts +154 -0
  97. package/test/concepts/FHEAntiPatterns.ts +111 -0
  98. package/test/concepts/FHEHandles.ts +156 -0
  99. package/test/concepts/FHEInputProof.ts +151 -0
  100. package/test/gaming/EncryptedLottery.ts +214 -0
  101. package/test/gaming/EncryptedPoker.ts +349 -0
  102. package/test/gaming/RockPaperScissors.ts +205 -0
  103. package/test/openzeppelin/ERC7984.ts +142 -0
  104. package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
  105. package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
  106. package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
  107. package/test/openzeppelin/VestingWallet.ts +89 -0
  108. package/dist/scripts/add-mode.d.ts.map +0 -1
  109. package/dist/scripts/builders.d.ts.map +0 -1
  110. package/dist/scripts/config.d.ts.map +0 -1
  111. package/dist/scripts/doctor.d.ts.map +0 -1
  112. package/dist/scripts/generate-config.d.ts.map +0 -1
  113. package/dist/scripts/generate-docs.d.ts.map +0 -1
  114. package/dist/scripts/maintenance.d.ts.map +0 -1
  115. package/dist/scripts/ui.d.ts.map +0 -1
  116. package/dist/scripts/utils.d.ts.map +0 -1
  117. /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
  118. /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
  119. /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
  120. /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
  121. /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
  122. /package/dist/scripts/{ui.d.ts → shared/ui.d.ts} +0 -0
@@ -0,0 +1,329 @@
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 their correct alternatives. Covers: branching, permissions, require/revert, re-encryption, loops, noise, and deprecated APIs.
14
+ *
15
+ * @dev This contract demonstrates 9 critical anti-patterns in FHE development:
16
+ * 1. Branching on encrypted values (causes decryption!)
17
+ * 2. Returning encrypted values without permissions
18
+ * 3. Using require/revert with encrypted conditions
19
+ * 4. Encrypted computation without permission grants
20
+ * 5. Leaking information through gas/timing
21
+ * 6. Unauthenticated re-encryption (security critical!)
22
+ * 7. Encrypted loop iterations (gas/timing leak)
23
+ * 8. Too many chained operations (noise accumulation)
24
+ * 9. Using deprecated FHEVM APIs
25
+ *
26
+ * Each anti-pattern shows both ❌ WRONG and ✅ CORRECT implementations.
27
+ */
28
+ contract FHEAntiPatterns is ZamaEthereumConfig {
29
+ euint32 private _secretBalance;
30
+ euint32 private _threshold;
31
+
32
+ // solhint-disable-next-line no-empty-blocks
33
+ constructor() {}
34
+
35
+ /// @notice Initialize the contract with encrypted balance and threshold values
36
+ /// @dev Both values are encrypted and permissions are granted to the caller
37
+ function initialize(
38
+ externalEuint32 balance,
39
+ externalEuint32 threshold,
40
+ bytes calldata inputProof
41
+ ) external {
42
+ _secretBalance = FHE.fromExternal(balance, inputProof);
43
+ _threshold = FHE.fromExternal(threshold, inputProof);
44
+
45
+ FHE.allowThis(_secretBalance);
46
+ FHE.allowThis(_threshold);
47
+ FHE.allow(_secretBalance, msg.sender);
48
+ FHE.allow(_threshold, msg.sender);
49
+ }
50
+
51
+ // ========================================================================
52
+ // ANTI-PATTERN 1: Branching on encrypted values
53
+ // ========================================================================
54
+
55
+ /**
56
+ * ❌ WRONG: if/else on encrypted value causes decryption
57
+ * @dev This pattern LEAKS information by decrypting on-chain
58
+ *
59
+ * DO NOT USE THIS PATTERN - It defeats the purpose of encryption!
60
+ */
61
+ // function wrongBranching() external {
62
+ // // ❌ This would compile but LEAK the comparison result!
63
+ // // if (_secretBalance > _threshold) { // WRONG: decrypts!
64
+ // // // do something
65
+ // // }
66
+ // }
67
+
68
+ /**
69
+ * @notice ✅ CORRECT: Use FHE.select for conditional logic
70
+ * @dev All computation stays encrypted - demonstrates proper encrypted branching
71
+ */
72
+ function correctConditional() external {
73
+ // Compare encrypted values - result is encrypted boolean
74
+ ebool isAboveThreshold = FHE.gt(_secretBalance, _threshold);
75
+
76
+ // Use select for encrypted branching
77
+ // If above threshold: result = balance - 10
78
+ // Else: result = balance
79
+ euint32 penaltyAmount = FHE.asEuint32(10);
80
+ euint32 balanceMinusPenalty = FHE.sub(_secretBalance, penaltyAmount);
81
+
82
+ // ✅ Encrypted conditional - no information leaked
83
+ _secretBalance = FHE.select(
84
+ isAboveThreshold,
85
+ balanceMinusPenalty,
86
+ _secretBalance
87
+ );
88
+
89
+ FHE.allowThis(_secretBalance);
90
+ FHE.allow(_secretBalance, msg.sender);
91
+ }
92
+
93
+ // ========================================================================
94
+ // ANTI-PATTERN 2: View function returning encrypted without permissions
95
+ // ========================================================================
96
+
97
+ /**
98
+ * @notice ❌ WRONG: Returns encrypted value but caller can't decrypt
99
+ * @dev Without prior allow(), the returned handle is useless
100
+ */
101
+ function wrongGetBalance() external view returns (euint32) {
102
+ // ❌ Caller has no permission to decrypt this!
103
+ return _secretBalance;
104
+ }
105
+
106
+ /**
107
+ * @notice ✅ CORRECT: Ensure permissions were granted before returning
108
+ * @dev Caller must have been granted access in a previous transaction
109
+ */
110
+ function correctGetBalance() external view returns (euint32) {
111
+ // ✅ Caller should have been granted access via allow()
112
+ // The initialize() function grants access to msg.sender
113
+ return _secretBalance;
114
+ }
115
+
116
+ // ========================================================================
117
+ // ANTI-PATTERN 3: Using require/revert with encrypted conditions
118
+ // ========================================================================
119
+
120
+ /**
121
+ * ❌ WRONG: Cannot use require with encrypted values
122
+ * @dev This pattern doesn't even compile - encrypted bools can't be used in require
123
+ */
124
+ // function wrongRequire() external {
125
+ // ebool hasEnough = FHE.ge(_secretBalance, FHE.asEuint32(100));
126
+ // // ❌ COMPILE ERROR: require expects bool, not ebool
127
+ // // require(hasEnough, "Insufficient balance");
128
+ // }
129
+
130
+ /**
131
+ * @notice ✅ CORRECT: Use encrypted flags instead of require
132
+ * @dev Store result and let client check after decryption
133
+ */
134
+ function correctValidation() external returns (ebool) {
135
+ // ✅ Return encrypted boolean for client to check
136
+ ebool hasEnough = FHE.ge(_secretBalance, FHE.asEuint32(100));
137
+
138
+ FHE.allowThis(hasEnough);
139
+ FHE.allow(hasEnough, msg.sender);
140
+
141
+ return hasEnough;
142
+ }
143
+
144
+ // ========================================================================
145
+ // ANTI-PATTERN 4: Encrypted computation without permission grants
146
+ // ========================================================================
147
+
148
+ /**
149
+ * @notice ❌ WRONG: Compute but forget to grant permissions
150
+ * @dev Result exists but no one can ever decrypt it
151
+ */
152
+ function wrongCompute() external {
153
+ euint32 doubled = FHE.mul(_secretBalance, FHE.asEuint32(2));
154
+ _secretBalance = doubled;
155
+ // ❌ Missing FHE.allowThis and FHE.allow!
156
+ // This value is now locked forever
157
+ }
158
+
159
+ /// @notice ✅ CORRECT: Always grant permissions after computation
160
+ function correctCompute() external {
161
+ euint32 doubled = FHE.mul(_secretBalance, FHE.asEuint32(2));
162
+ _secretBalance = doubled;
163
+
164
+ // ✅ Grant permissions
165
+ FHE.allowThis(_secretBalance);
166
+ FHE.allow(_secretBalance, msg.sender);
167
+ }
168
+
169
+ // ========================================================================
170
+ // ANTI-PATTERN 5: Leaking information through gas/timing
171
+ // ========================================================================
172
+
173
+ /**
174
+ * @notice ⚠️ CAUTION: Be aware of side-channel attacks
175
+ * @dev Even with FHE.select, be careful about operations that might
176
+ * have different gas costs based on values
177
+ *
178
+ * BEST PRACTICES:
179
+ * - Use constant-time operations when possible
180
+ * - Avoid loops with encrypted iteration counts
181
+ * - Don't make external calls conditionally based on encrypted values
182
+ */
183
+ function cautionSideChannels() external pure returns (string memory) {
184
+ return "Be aware of gas/timing side channels";
185
+ }
186
+
187
+ // ========================================================================
188
+ // ANTI-PATTERN 6: Unauthenticated Re-encryption (SECURITY CRITICAL)
189
+ // ========================================================================
190
+
191
+ /**
192
+ * @notice ❌ WRONG: Re-encrypt for any provided public key
193
+ * @dev This allows impersonation attacks - anyone can request re-encryption
194
+ * for any public key and pretend to be that user
195
+ *
196
+ * ATTACK SCENARIO:
197
+ * 1. Alice has encrypted balance
198
+ * 2. Eve calls wrongReencrypt(evePublicKey)
199
+ * 3. Eve gets Alice's balance re-encrypted for her key
200
+ * 4. Eve decrypts and learns Alice's secret balance!
201
+ */
202
+ // function wrongReencrypt(bytes calldata userPublicKey) external view {
203
+ // // ❌ NO AUTHENTICATION! Anyone can provide any public key
204
+ // // Re-encrypt _secretBalance for userPublicKey
205
+ // // This leaks information to unauthorized users
206
+ // }
207
+
208
+ /**
209
+ * @notice ✅ CORRECT: Require cryptographic proof of identity
210
+ * @dev Use EIP-712 signature to prove the requester owns the public key
211
+ *
212
+ * CLIENT-SIDE:
213
+ * 1. User signs a message: "I authorize re-encryption for contract X"
214
+ * 2. Signature is verified on-chain before re-encryption
215
+ *
216
+ * Note: In practice, this is handled by the FHEVM SDK's userDecrypt flow
217
+ */
218
+ function correctReencryptPattern() external pure returns (string memory) {
219
+ return
220
+ "Always verify EIP-712 signature before re-encryption. "
221
+ "Use fhevm.js userDecrypt which handles this automatically.";
222
+ }
223
+
224
+ // ========================================================================
225
+ // ANTI-PATTERN 7: Encrypted Loop Iterations (GAS/TIMING LEAK)
226
+ // ========================================================================
227
+
228
+ /// ❌ WRONG: Using encrypted value as loop count
229
+ /// @dev Loop count is visible through gas consumption and timing
230
+ ///
231
+ /// PROBLEM: If we loop `encryptedCount` times, the gas cost reveals the count!
232
+ // function wrongEncryptedLoop(euint32 encryptedCount) external {
233
+ // // ❌ GAS LEAK: Number of iterations visible!
234
+ // // for (uint i = 0; i < decrypt(encryptedCount); i++) {
235
+ // // // Each iteration costs gas
236
+ // // }
237
+ // }
238
+
239
+ /// @notice ✅ CORRECT: Use fixed iteration count with select
240
+ /// @dev Always iterate the maximum possible times, use FHE.select to
241
+ /// conditionally apply operations
242
+ function correctFixedIterations() external {
243
+ // ✅ Fixed iteration count - no information leaked
244
+ uint256 MAX_ITERATIONS = 10;
245
+
246
+ euint32 accumulator = FHE.asEuint32(0);
247
+ euint32 counter = FHE.asEuint32(0);
248
+
249
+ for (uint256 i = 0; i < MAX_ITERATIONS; i++) {
250
+ // Check if we should still be iterating
251
+ ebool shouldContinue = FHE.lt(counter, _secretBalance);
252
+
253
+ // Conditionally add (add 1 if continuing, add 0 otherwise)
254
+ euint32 increment = FHE.select(
255
+ shouldContinue,
256
+ FHE.asEuint32(1),
257
+ FHE.asEuint32(0)
258
+ );
259
+ accumulator = FHE.add(accumulator, increment);
260
+ counter = FHE.add(counter, FHE.asEuint32(1));
261
+ }
262
+
263
+ FHE.allowThis(accumulator);
264
+ FHE.allow(accumulator, msg.sender);
265
+ }
266
+
267
+ // ========================================================================
268
+ // ANTI-PATTERN 8: Too Many Chained Operations (Noise Accumulation)
269
+ // ========================================================================
270
+
271
+ /// @notice ⚠️ CAUTION: FHE operations accumulate "noise"
272
+ /// @dev Each FHE operation adds noise to the ciphertext. After too many
273
+ /// operations, the ciphertext becomes corrupted and undecryptable.
274
+ ///
275
+ /// FHEVM handles this via "bootstrapping" which is expensive.
276
+ /// Best practice: minimize operation chains where possible.
277
+ ///
278
+ /// EXAMPLE OF NOISE ACCUMULATION:
279
+ /// - Each add/sub: +1 noise
280
+ /// - Each mul: +10 noise (roughly)
281
+ /// - Bootstrapping threshold: ~100 noise (varies by scheme)
282
+ function cautionNoiseAccumulation() external pure returns (string memory) {
283
+ return
284
+ "Keep FHE operation chains short. Multiplications add more noise than additions. "
285
+ "If you need many operations, consider batching or restructuring logic.";
286
+ }
287
+
288
+ // ========================================================================
289
+ // ANTI-PATTERN 9: Using Deprecated FHEVM APIs
290
+ // ========================================================================
291
+
292
+ /**
293
+ * @notice Caution about deprecated FHEVM APIs
294
+ * @dev OLD (v0.8 and earlier):
295
+ * - Decryption went through Zama Oracle
296
+ * - Used TFHE.decrypt() directly
297
+ *
298
+ * NEW (v0.9+):
299
+ * - Self-relaying public decryption
300
+ * - Use FHE.makePubliclyDecryptable() + off-chain relay
301
+ */
302
+ function cautionDeprecatedAPIs() external pure returns (string memory) {
303
+ return
304
+ "Use FHEVM v0.9+ APIs. Old TFHE.decrypt() is deprecated. "
305
+ "Use FHE.makePubliclyDecryptable() for public decryption, "
306
+ "or userDecrypt pattern via fhevm.js for user decryption.";
307
+ }
308
+
309
+ // ========================================================================
310
+ // SUMMARY: Key Rules (Always check before deploying!)
311
+ // ========================================================================
312
+
313
+ /**
314
+ * @notice Quick reference for FHE best practices
315
+ * @return rules Summary of all 9 key rules
316
+ */
317
+ function getRules() external pure returns (string memory rules) {
318
+ return
319
+ "1. Never branch (if/else) on encrypted values - use FHE.select\n"
320
+ "2. Always call FHE.allowThis() AND FHE.allow(user) after computation\n"
321
+ "3. Cannot use require/revert with encrypted conditions\n"
322
+ "4. Return ebool for validations, let client decrypt and check\n"
323
+ "5. Be aware of gas/timing side channels\n"
324
+ "6. Always authenticate re-encryption requests (use EIP-712 signatures)\n"
325
+ "7. Never use encrypted values as loop iteration counts\n"
326
+ "8. Avoid chaining too many FHE operations (noise accumulation)\n"
327
+ "9. Use FHEVM v0.9+ APIs, avoid deprecated TFHE.decrypt()";
328
+ }
329
+ }
@@ -0,0 +1,128 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {FHE, euint32, externalEuint32} from "@fhevm/solidity/lib/FHE.sol";
5
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
+
7
+ /**
8
+ * @notice Understanding FHE handles: creation, computation, immutability, and symbolic execution in mock mode.
9
+ *
10
+ * @dev Handle = uint256 pointer to encrypted data stored by FHE coprocessor.
11
+ * Types: euint8/16/32/64/128/256, ebool, eaddress, ebytes64/128/256
12
+ */
13
+ contract FHEHandles is ZamaEthereumConfig {
14
+ // 🔐 Storage handles - persist across transactions
15
+ // These are just uint256 pointers, actual ciphertext is off-chain
16
+ euint32 private _storedValue;
17
+ euint32 private _computedValue;
18
+
19
+ event HandleCreated(string operation, uint256 gasUsed);
20
+ event HandleStored(string description);
21
+
22
+ constructor() {}
23
+
24
+ // ==================== HANDLE CREATION ====================
25
+
26
+ /// @notice Pattern 1: Create handle from user's encrypted input
27
+ function createFromExternal(
28
+ externalEuint32 input,
29
+ bytes calldata inputProof
30
+ ) external {
31
+ uint256 gasBefore = gasleft();
32
+
33
+ // 📥 FHE.fromExternal: converts external handle to internal handle
34
+ // The proof is verified automatically
35
+ _storedValue = FHE.fromExternal(input, inputProof);
36
+
37
+ emit HandleCreated("fromExternal", gasBefore - gasleft());
38
+
39
+ FHE.allowThis(_storedValue);
40
+ FHE.allow(_storedValue, msg.sender);
41
+ }
42
+
43
+ /// @notice Pattern 2: Create handle from plaintext constant
44
+ /// @dev ⚠️ The plaintext IS visible on-chain! But result is encrypted.
45
+ function createFromPlaintext(uint32 plaintextValue) external {
46
+ uint256 gasBefore = gasleft();
47
+
48
+ // 📥 FHE.asEuint32: encrypts a public constant
49
+ // Use for: thresholds, comparison values, zero-initialization
50
+ _storedValue = FHE.asEuint32(plaintextValue);
51
+
52
+ emit HandleCreated("asEuint32", gasBefore - gasleft());
53
+
54
+ FHE.allowThis(_storedValue);
55
+ FHE.allow(_storedValue, msg.sender);
56
+ }
57
+
58
+ // ==================== HANDLE COMPUTATION ====================
59
+
60
+ /// @notice Key insight: FHE operations create NEW handles
61
+ /// @dev Original handles are IMMUTABLE - they never change
62
+ function computeNewHandle() external {
63
+ uint256 gasBefore = gasleft();
64
+
65
+ euint32 constant10 = FHE.asEuint32(10);
66
+
67
+ // 🔄 FHE.add creates a BRAND NEW handle
68
+ // _storedValue handle is UNCHANGED
69
+ // _computedValue gets the NEW handle
70
+ _computedValue = FHE.add(_storedValue, constant10);
71
+
72
+ emit HandleCreated("add (new handle)", gasBefore - gasleft());
73
+
74
+ // ⚠️ Must grant permissions for the NEW handle!
75
+ FHE.allowThis(_computedValue);
76
+ FHE.allow(_computedValue, msg.sender);
77
+
78
+ emit HandleStored("Computed value stored with new handle");
79
+ }
80
+
81
+ /// @notice Chained operations = multiple intermediate handles
82
+ function chainedOperations() external {
83
+ // 📝 Each operation creates a new handle:
84
+ euint32 step1 = FHE.add(_storedValue, FHE.asEuint32(5)); // Handle #1
85
+ euint32 step2 = FHE.mul(step1, FHE.asEuint32(2)); // Handle #2
86
+ euint32 step3 = FHE.sub(step2, FHE.asEuint32(1)); // Handle #3
87
+
88
+ _computedValue = step3;
89
+
90
+ // Only final result needs permissions (if we're storing it)
91
+ // Intermediate handles (step1, step2) have ephemeral permission
92
+ // and are automatically cleaned up after transaction
93
+ FHE.allowThis(_computedValue);
94
+ FHE.allow(_computedValue, msg.sender);
95
+ }
96
+
97
+ // ==================== HANDLE IMMUTABILITY ====================
98
+
99
+ /// @notice Demonstrates: updating a variable creates NEW handle
100
+ function demonstrateImmutability()
101
+ external
102
+ returns (euint32 original, euint32 updated)
103
+ {
104
+ // 📌 Save reference to current handle
105
+ euint32 originalHandle = _storedValue;
106
+
107
+ // 🔄 This creates NEW handle, assigns to _storedValue
108
+ // originalHandle still points to OLD value!
109
+ _storedValue = FHE.add(_storedValue, FHE.asEuint32(100));
110
+
111
+ FHE.allowThis(_storedValue);
112
+ FHE.allow(_storedValue, msg.sender);
113
+
114
+ // originalHandle → old value
115
+ // _storedValue → new value (old + 100)
116
+ return (originalHandle, _storedValue);
117
+ }
118
+
119
+ // ==================== GETTERS ====================
120
+
121
+ function getStoredValue() external view returns (euint32) {
122
+ return _storedValue;
123
+ }
124
+
125
+ function getComputedValue() external view returns (euint32) {
126
+ return _computedValue;
127
+ }
128
+ }
@@ -0,0 +1,104 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint32,
7
+ euint64,
8
+ externalEuint32,
9
+ externalEuint64
10
+ } from "@fhevm/solidity/lib/FHE.sol";
11
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
12
+
13
+ /**
14
+ * @notice Explains input proof validation in FHEVM: what proofs are, why they are needed, and how to use them correctly with single and batched inputs.
15
+ *
16
+ * @dev Why proofs? They ensure:
17
+ * 1. Ciphertext is valid (not garbage data)
18
+ * 2. Value is in valid range for the type
19
+ * 3. Sender knows the plaintext (proof of knowledge)
20
+ */
21
+ contract FHEInputProof is ZamaEthereumConfig {
22
+ euint32 private _singleValue;
23
+ euint32 private _valueA;
24
+ euint64 private _valueB;
25
+
26
+ constructor() {}
27
+
28
+ // ==================== SINGLE INPUT ====================
29
+
30
+ /// @notice Receive single encrypted value with proof
31
+ function setSingleValue(
32
+ externalEuint32 encryptedInput,
33
+ bytes calldata inputProof
34
+ ) external {
35
+ // 📥 FHE.fromExternal validates proof automatically
36
+ // If proof is invalid → transaction reverts
37
+ // Proof ensures:
38
+ // ✓ Valid euint32 ciphertext
39
+ // ✓ Encrypted for THIS contract
40
+ // ✓ Sender knows the plaintext
41
+ _singleValue = FHE.fromExternal(encryptedInput, inputProof);
42
+
43
+ FHE.allowThis(_singleValue);
44
+ FHE.allow(_singleValue, msg.sender);
45
+ }
46
+
47
+ // ==================== MULTIPLE INPUTS (BATCHED) ====================
48
+
49
+ /// @notice Receive multiple encrypted values with SINGLE proof
50
+ /// @dev Client-side batching is more gas-efficient!
51
+ ///
52
+ /// Client code:
53
+ /// const input = fhevm.createEncryptedInput(contractAddr, userAddr);
54
+ /// input.add32(valueA); // → handles[0]
55
+ /// input.add64(valueB); // → handles[1]
56
+ /// const enc = await input.encrypt();
57
+ /// // enc.inputProof covers BOTH values!
58
+ function setMultipleValues(
59
+ externalEuint32 inputA,
60
+ externalEuint64 inputB,
61
+ bytes calldata inputProof // ← Single proof covers both!
62
+ ) external {
63
+ // Both validated by same proof
64
+ _valueA = FHE.fromExternal(inputA, inputProof);
65
+ _valueB = FHE.fromExternal(inputB, inputProof);
66
+
67
+ // ⚠️ Each value needs its own permission grants
68
+ FHE.allowThis(_valueA);
69
+ FHE.allowThis(_valueB);
70
+ FHE.allow(_valueA, msg.sender);
71
+ FHE.allow(_valueB, msg.sender);
72
+ }
73
+
74
+ // ==================== COMPUTATION WITH NEW INPUT ====================
75
+
76
+ /// @notice Add new encrypted input to existing stored value
77
+ function addToValue(
78
+ externalEuint32 addend,
79
+ bytes calldata inputProof
80
+ ) external {
81
+ // Validate the new input
82
+ euint32 validatedAddend = FHE.fromExternal(addend, inputProof);
83
+
84
+ // Combine with stored value
85
+ _singleValue = FHE.add(_singleValue, validatedAddend);
86
+
87
+ FHE.allowThis(_singleValue);
88
+ FHE.allow(_singleValue, msg.sender);
89
+ }
90
+
91
+ // ==================== GETTERS ====================
92
+
93
+ function getSingleValue() external view returns (euint32) {
94
+ return _singleValue;
95
+ }
96
+
97
+ function getValueA() external view returns (euint32) {
98
+ return _valueA;
99
+ }
100
+
101
+ function getValueB() external view returns (euint64) {
102
+ return _valueB;
103
+ }
104
+ }