create-fhevm-example 1.4.3 → 1.4.4

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.
@@ -10,19 +10,11 @@ import {
10
10
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
11
 
12
12
  /**
13
- * @notice Demonstrates all FHE comparison operations on encrypted integers
13
+ * @notice Demonstrates all FHE comparison operations: eq, ne, gt, lt, ge, le, select
14
14
  *
15
- * @dev This contract shows how to compare encrypted values without decrypting them.
16
- * Comparison results are returned as encrypted booleans (ebool).
17
- *
18
- * Available operations:
19
- * - FHE.eq(a, b) : Equal (a == b)
20
- * - FHE.ne(a, b) : Not equal (a != b)
21
- * - FHE.gt(a, b) : Greater than (a > b)
22
- * - FHE.lt(a, b) : Less than (a < b)
23
- * - FHE.ge(a, b) : Greater or equal (a >= b)
24
- * - FHE.le(a, b) : Less or equal (a <= b)
25
- * - FHE.select(cond, a, b) : Conditional selection (cond ? a : b)
15
+ * @dev Results are encrypted booleans (ebool) - comparisons reveal nothing!
16
+ * Gas: Comparisons ~100k, select ~120k
17
+ * ❌ WRONG: if (FHE.gt(a,b)) → decrypts! ✅ CORRECT: FHE.select()
26
18
  */
27
19
  contract FHEComparison is ZamaEthereumConfig {
28
20
  euint32 private _a;
@@ -30,67 +22,57 @@ contract FHEComparison is ZamaEthereumConfig {
30
22
  ebool private _boolResult;
31
23
  euint32 private _selectedResult;
32
24
 
33
- // solhint-disable-next-line no-empty-blocks
34
- constructor() {}
35
-
36
- /// @notice Sets the first operand (encrypted)
37
25
  function setA(externalEuint32 inputA, bytes calldata inputProof) external {
38
26
  _a = FHE.fromExternal(inputA, inputProof);
39
27
  FHE.allowThis(_a);
40
28
  }
41
29
 
42
- /// @notice Sets the second operand (encrypted)
43
30
  function setB(externalEuint32 inputB, bytes calldata inputProof) external {
44
31
  _b = FHE.fromExternal(inputB, inputProof);
45
32
  FHE.allowThis(_b);
46
33
  }
47
34
 
48
- /// @notice Computes encrypted equality: result = (a == b)
49
35
  function computeEq() external {
50
36
  _boolResult = FHE.eq(_a, _b);
51
37
  _grantBoolPermissions();
52
38
  }
53
39
 
54
- /// @notice Computes encrypted inequality: result = (a != b)
55
40
  function computeNe() external {
56
41
  _boolResult = FHE.ne(_a, _b);
57
42
  _grantBoolPermissions();
58
43
  }
59
44
 
60
- /// @notice Computes encrypted greater than: result = (a > b)
61
45
  function computeGt() external {
62
46
  _boolResult = FHE.gt(_a, _b);
63
47
  _grantBoolPermissions();
64
48
  }
65
49
 
66
- /// @notice Computes encrypted less than: result = (a < b)
67
50
  function computeLt() external {
68
51
  _boolResult = FHE.lt(_a, _b);
69
52
  _grantBoolPermissions();
70
53
  }
71
54
 
72
- /// @notice Computes encrypted greater or equal: result = (a >= b)
73
55
  function computeGe() external {
74
56
  _boolResult = FHE.ge(_a, _b);
75
57
  _grantBoolPermissions();
76
58
  }
77
59
 
78
- /// @notice Computes encrypted less or equal: result = (a <= b)
79
60
  function computeLe() external {
80
61
  _boolResult = FHE.le(_a, _b);
81
62
  _grantBoolPermissions();
82
63
  }
83
64
 
84
- /// @notice Computes encrypted maximum using select: result = (a > b) ? a : b
85
- /// @dev Demonstrates FHE.select for conditional logic on encrypted values
65
+ /// @notice Encrypted maximum using select: (a > b) ? a : b
66
+ /// @dev 🔀 Why select? Using if(aGtB) would decrypt and leak the comparison!
86
67
  function computeMaxViaSelect() external {
87
68
  ebool aGtB = FHE.gt(_a, _b);
69
+ // select evaluates both branches, picks one without revealing which
88
70
  _selectedResult = FHE.select(aGtB, _a, _b);
89
71
  FHE.allowThis(_selectedResult);
90
72
  FHE.allow(_selectedResult, msg.sender);
91
73
  }
92
74
 
93
- /// @notice Computes encrypted minimum using select: result = (a < b) ? a : b
75
+ /// @notice Encrypted minimum using select: (a < b) ? a : b
94
76
  function computeMinViaSelect() external {
95
77
  ebool aLtB = FHE.lt(_a, _b);
96
78
  _selectedResult = FHE.select(aLtB, _a, _b);
@@ -98,17 +80,14 @@ contract FHEComparison is ZamaEthereumConfig {
98
80
  FHE.allow(_selectedResult, msg.sender);
99
81
  }
100
82
 
101
- /// @notice Returns the encrypted boolean result
102
83
  function getBoolResult() public view returns (ebool) {
103
84
  return _boolResult;
104
85
  }
105
86
 
106
- /// @notice Returns the encrypted selected result
107
87
  function getSelectedResult() public view returns (euint32) {
108
88
  return _selectedResult;
109
89
  }
110
90
 
111
- /// @dev Grants FHE permissions for boolean result
112
91
  function _grantBoolPermissions() internal {
113
92
  FHE.allowThis(_boolResult);
114
93
  FHE.allow(_boolResult, msg.sender);
@@ -7,15 +7,14 @@ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
7
7
  /**
8
8
  * @notice Demonstrates conditional logic: max(a, b) using encrypted comparison
9
9
  *
10
- * @dev Shows how to use FHE.select() for encrypted if-then-else logic.
10
+ * @dev Can't use if/else (leaks info!) Use FHE.select instead
11
+ * ⚡ Gas: ~120k for select operation
11
12
  */
12
13
  contract FHEIfThenElse is ZamaEthereumConfig {
13
14
  euint8 private _a;
14
15
  euint8 private _b;
15
16
  euint8 private _max;
16
17
 
17
- constructor() {}
18
-
19
18
  /// @notice Sets the first operand (encrypted)
20
19
  function setA(externalEuint8 inputA, bytes calldata inputProof) external {
21
20
  _a = FHE.fromExternal(inputA, inputProof);
@@ -31,14 +30,10 @@ contract FHEIfThenElse is ZamaEthereumConfig {
31
30
  /// @notice Compute max(a, b) without revealing which is larger
32
31
  /// @dev Uses FHE.select() - the encrypted "if-then-else"
33
32
  function computeMax() external {
34
- // 🔍 Compare encrypted values - result is encrypted boolean!
35
- // We don't know if a >= b, only the encrypted result
36
33
  ebool aIsGreaterOrEqual = FHE.ge(_a, _b);
37
34
 
38
- // 🔀 FHE.select(condition, ifTrue, ifFalse)
39
- // - BOTH branches are evaluated (no short-circuit)
40
- // - Result is encrypted - no one knows which was selected
41
- // - This is how you do "if-else" on encrypted values!
35
+ // 🔀 Why select? if/else would leak which branch was taken!
36
+ // select evaluates BOTH branches, picks one based on encrypted condition
42
37
  _max = FHE.select(aIsGreaterOrEqual, _a, _b);
43
38
 
44
39
  // Grant permissions for decryption
@@ -5,21 +5,15 @@ import {FHE, euint32, externalEuint32} from "@fhevm/solidity/lib/FHE.sol";
5
5
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
6
 
7
7
  /**
8
- * @notice Critical access control patterns in FHEVM: FHE.allow, FHE.allowThis, FHE.allowTransient. Includes common mistakes and correct implementations.
9
- *
10
- * @dev Key functions:
11
- * FHE.allow(handle, address) - permanent permission
12
- * FHE.allowThis(handle) - permission for contract itself
13
- * FHE.allowTransient(handle, address) - temporary, expires at tx end
8
+ * @notice Critical access control patterns in FHEVM: FHE.allow, FHE.allowThis, FHE.allowTransient with common mistakes.
9
+
10
+ * @dev allow() = permanent, allowThis() = contract permission, allowTransient() = expires at TX end
11
+ * Both allowThis + allow required for user decryption!
14
12
  */
15
13
  contract FHEAccessControl is ZamaEthereumConfig {
16
14
  euint32 private _secretValue;
17
15
  mapping(address => bool) public hasAccess;
18
16
 
19
- constructor() {}
20
-
21
- // ==================== CORRECT PATTERN ====================
22
-
23
17
  /// @notice ✅ CORRECT: Full access pattern for user decryption
24
18
  function storeWithFullAccess(
25
19
  externalEuint32 input,
@@ -27,13 +21,12 @@ contract FHEAccessControl is ZamaEthereumConfig {
27
21
  ) external {
28
22
  _secretValue = FHE.fromExternal(input, inputProof);
29
23
 
30
- // ⚠️ CRITICAL: BOTH are required for user decryption!
31
- FHE.allowThis(_secretValue); // Contract can operate on it
32
- FHE.allow(_secretValue, msg.sender); // User can decrypt it
33
-
34
- // ❓ Why allowThis is needed for user decryption?
35
- // User decryption = re-encryption for user's key
36
- // This requires contract's permission to "release" the value
24
+ // Why BOTH allowThis + allow?
25
+ // - allowThis: Contract authorizes "releasing" the encrypted value
26
+ // - allow(user): User can request decryption for their key
27
+ // Missing either = decryption fails!
28
+ FHE.allowThis(_secretValue);
29
+ FHE.allow(_secretValue, msg.sender);
37
30
 
38
31
  hasAccess[msg.sender] = true;
39
32
  }
@@ -42,7 +35,8 @@ contract FHEAccessControl is ZamaEthereumConfig {
42
35
  function grantAccess(address user) external {
43
36
  require(hasAccess[msg.sender], "Caller has no access to grant");
44
37
 
45
- // Works because contract has allowThis permission
38
+ // Why this works: Contract already has allowThis from storeWithFullAccess
39
+ // We only need to grant allow(user) for the new user
46
40
  FHE.allow(_secretValue, user);
47
41
  hasAccess[user] = true;
48
42
  }
@@ -51,8 +45,6 @@ contract FHEAccessControl is ZamaEthereumConfig {
51
45
  return _secretValue;
52
46
  }
53
47
 
54
- // ==================== WRONG PATTERNS (EDUCATIONAL) ====================
55
-
56
48
  /// @notice ❌ WRONG: Missing allowThis → user decryption FAILS
57
49
  function storeWithoutAllowThis(
58
50
  externalEuint32 input,
@@ -60,8 +52,9 @@ contract FHEAccessControl is ZamaEthereumConfig {
60
52
  ) external {
61
53
  _secretValue = FHE.fromExternal(input, inputProof);
62
54
 
63
- // ❌ Missing: FHE.allowThis(_secretValue)
64
- // User has permission, but decryption will FAIL!
55
+ // ❌ Common mistake: Only allow(user) without allowThis
56
+ // Result: User has permission but can't decrypt!
57
+ // Why? Decryption needs contract to authorize the release
65
58
  FHE.allow(_secretValue, msg.sender);
66
59
  }
67
60
 
@@ -72,21 +65,23 @@ contract FHEAccessControl is ZamaEthereumConfig {
72
65
  ) external {
73
66
  _secretValue = FHE.fromExternal(input, inputProof);
74
67
 
68
+ // ❌ Another mistake: Only allowThis without allow(user)
69
+ // Result: Contract can compute but no one can decrypt!
75
70
  FHE.allowThis(_secretValue);
76
- // ❌ Missing: FHE.allow(_secretValue, msg.sender)
77
- // Contract can operate, but no one can decrypt!
78
71
  }
79
72
 
80
- // ==================== TRANSIENT ACCESS ====================
81
-
82
73
  /// @notice Temporary access - expires at end of transaction
83
- /// @dev Use for: passing values between contracts in same tx
74
+ /// @dev Gas: allowTransient ~50% cheaper than allow!
75
+ /// Use for passing values between contracts in same TX
84
76
  function computeAndShareTransient(
85
77
  address recipient
86
78
  ) external returns (euint32) {
87
79
  euint32 computed = FHE.add(_secretValue, FHE.asEuint32(1));
88
80
 
89
- // 💨 Transient = cheaper than permanent, auto-expires
81
+ // Why allowTransient instead of allow?
82
+ // - Cheaper: ~50% less gas than permanent allow
83
+ // - Auto-cleanup: Expires at TX end (no storage pollution)
84
+ // - Use case: Passing values between contracts in same transaction
90
85
  FHE.allowTransient(computed, recipient);
91
86
 
92
87
  return computed;
@@ -10,28 +10,15 @@ import {
10
10
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
11
 
12
12
  /**
13
- * @notice Common FHE mistakes and their correct alternatives. Covers: branching, permissions, require/revert, re-encryption, loops, noise, and deprecated APIs.
13
+ * @notice Common FHE mistakes and correct alternatives: branching, permissions, require, loops, noise, deprecated APIs.
14
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.
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!
27
17
  */
28
18
  contract FHEAntiPatterns is ZamaEthereumConfig {
29
19
  euint32 private _secretBalance;
30
20
  euint32 private _threshold;
31
21
 
32
- // solhint-disable-next-line no-empty-blocks
33
- constructor() {}
34
-
35
22
  /// @notice Initialize the contract with encrypted balance and threshold values
36
23
  /// @dev Both values are encrypted and permissions are granted to the caller
37
24
  function initialize(
@@ -48,9 +35,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
48
35
  FHE.allow(_threshold, msg.sender);
49
36
  }
50
37
 
51
- // ========================================================================
52
38
  // ANTI-PATTERN 1: Branching on encrypted values
53
- // ========================================================================
54
39
 
55
40
  /**
56
41
  * ❌ WRONG: if/else on encrypted value causes decryption
@@ -90,9 +75,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
90
75
  FHE.allow(_secretBalance, msg.sender);
91
76
  }
92
77
 
93
- // ========================================================================
94
78
  // ANTI-PATTERN 2: View function returning encrypted without permissions
95
- // ========================================================================
96
79
 
97
80
  /**
98
81
  * @notice ❌ WRONG: Returns encrypted value but caller can't decrypt
@@ -113,9 +96,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
113
96
  return _secretBalance;
114
97
  }
115
98
 
116
- // ========================================================================
117
99
  // ANTI-PATTERN 3: Using require/revert with encrypted conditions
118
- // ========================================================================
119
100
 
120
101
  /**
121
102
  * ❌ WRONG: Cannot use require with encrypted values
@@ -141,9 +122,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
141
122
  return hasEnough;
142
123
  }
143
124
 
144
- // ========================================================================
145
125
  // ANTI-PATTERN 4: Encrypted computation without permission grants
146
- // ========================================================================
147
126
 
148
127
  /**
149
128
  * @notice ❌ WRONG: Compute but forget to grant permissions
@@ -166,9 +145,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
166
145
  FHE.allow(_secretBalance, msg.sender);
167
146
  }
168
147
 
169
- // ========================================================================
170
148
  // ANTI-PATTERN 5: Leaking information through gas/timing
171
- // ========================================================================
172
149
 
173
150
  /**
174
151
  * @notice ⚠️ CAUTION: Be aware of side-channel attacks
@@ -184,9 +161,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
184
161
  return "Be aware of gas/timing side channels";
185
162
  }
186
163
 
187
- // ========================================================================
188
164
  // ANTI-PATTERN 6: Unauthenticated Re-encryption (SECURITY CRITICAL)
189
- // ========================================================================
190
165
 
191
166
  /**
192
167
  * @notice ❌ WRONG: Re-encrypt for any provided public key
@@ -221,9 +196,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
221
196
  "Use fhevm.js userDecrypt which handles this automatically.";
222
197
  }
223
198
 
224
- // ========================================================================
225
199
  // ANTI-PATTERN 7: Encrypted Loop Iterations (GAS/TIMING LEAK)
226
- // ========================================================================
227
200
 
228
201
  /// ❌ WRONG: Using encrypted value as loop count
229
202
  /// @dev Loop count is visible through gas consumption and timing
@@ -264,9 +237,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
264
237
  FHE.allow(accumulator, msg.sender);
265
238
  }
266
239
 
267
- // ========================================================================
268
240
  // ANTI-PATTERN 8: Too Many Chained Operations (Noise Accumulation)
269
- // ========================================================================
270
241
 
271
242
  /// @notice ⚠️ CAUTION: FHE operations accumulate "noise"
272
243
  /// @dev Each FHE operation adds noise to the ciphertext. After too many
@@ -285,9 +256,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
285
256
  "If you need many operations, consider batching or restructuring logic.";
286
257
  }
287
258
 
288
- // ========================================================================
289
259
  // ANTI-PATTERN 9: Using Deprecated FHEVM APIs
290
- // ========================================================================
291
260
 
292
261
  /**
293
262
  * @notice Caution about deprecated FHEVM APIs
@@ -306,9 +275,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
306
275
  "or userDecrypt pattern via fhevm.js for user decryption.";
307
276
  }
308
277
 
309
- // ========================================================================
310
- // SUMMARY: Key Rules (Always check before deploying!)
311
- // ========================================================================
278
+ // SUMMARY: Key Rules
312
279
 
313
280
  /**
314
281
  * @notice Quick reference for FHE best practices
@@ -5,24 +5,18 @@ import {FHE, euint32, externalEuint32} from "@fhevm/solidity/lib/FHE.sol";
5
5
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
6
 
7
7
  /**
8
- * @notice Understanding FHE handles: creation, computation, immutability, and symbolic execution in mock mode.
8
+ * @notice Understanding FHE handles: creation, computation, and immut ability.
9
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
10
+ * @dev Handle = uint256 pointer to encrypted data. Operations create NEW handles (immutable).
11
+ * ⚡ Gas: asEuint32 ~20k, fromExternal ~50k, add/sub ~100k
12
12
  */
13
13
  contract FHEHandles is ZamaEthereumConfig {
14
- // 🔐 Storage handles - persist across transactions
15
- // These are just uint256 pointers, actual ciphertext is off-chain
16
14
  euint32 private _storedValue;
17
15
  euint32 private _computedValue;
18
16
 
19
17
  event HandleCreated(string operation, uint256 gasUsed);
20
18
  event HandleStored(string description);
21
19
 
22
- constructor() {}
23
-
24
- // ==================== HANDLE CREATION ====================
25
-
26
20
  /// @notice Pattern 1: Create handle from user's encrypted input
27
21
  function createFromExternal(
28
22
  externalEuint32 input,
@@ -30,8 +24,7 @@ contract FHEHandles is ZamaEthereumConfig {
30
24
  ) external {
31
25
  uint256 gasBefore = gasleft();
32
26
 
33
- // 📥 FHE.fromExternal: converts external handle to internal handle
34
- // The proof is verified automatically
27
+ // fromExternal: validates proof and creates internal handle
35
28
  _storedValue = FHE.fromExternal(input, inputProof);
36
29
 
37
30
  emit HandleCreated("fromExternal", gasBefore - gasleft());
@@ -45,8 +38,7 @@ contract FHEHandles is ZamaEthereumConfig {
45
38
  function createFromPlaintext(uint32 plaintextValue) external {
46
39
  uint256 gasBefore = gasleft();
47
40
 
48
- // 📥 FHE.asEuint32: encrypts a public constant
49
- // Use for: thresholds, comparison values, zero-initialization
41
+ // asEuint32: encrypts a public constant (visible on-chain!)
50
42
  _storedValue = FHE.asEuint32(plaintextValue);
51
43
 
52
44
  emit HandleCreated("asEuint32", gasBefore - gasleft());
@@ -55,23 +47,18 @@ contract FHEHandles is ZamaEthereumConfig {
55
47
  FHE.allow(_storedValue, msg.sender);
56
48
  }
57
49
 
58
- // ==================== HANDLE COMPUTATION ====================
59
-
60
- /// @notice Key insight: FHE operations create NEW handles
61
- /// @dev Original handles are IMMUTABLE - they never change
50
+ /// @notice ⚠️ Key insight: FHE operations create NEW handles (immutable!)
62
51
  function computeNewHandle() external {
63
52
  uint256 gasBefore = gasleft();
64
53
 
65
54
  euint32 constant10 = FHE.asEuint32(10);
66
55
 
67
- // 🔄 FHE.add creates a BRAND NEW handle
68
- // _storedValue handle is UNCHANGED
69
- // _computedValue gets the NEW handle
56
+ // 🔄 Why NEW handle? FHE values are immutable - operations always create new ones
70
57
  _computedValue = FHE.add(_storedValue, constant10);
71
58
 
72
59
  emit HandleCreated("add (new handle)", gasBefore - gasleft());
73
60
 
74
- // ⚠️ Must grant permissions for the NEW handle!
61
+ // Must grant permissions for NEW handle
75
62
  FHE.allowThis(_computedValue);
76
63
  FHE.allow(_computedValue, msg.sender);
77
64
 
@@ -94,18 +81,14 @@ contract FHEHandles is ZamaEthereumConfig {
94
81
  FHE.allow(_computedValue, msg.sender);
95
82
  }
96
83
 
97
- // ==================== HANDLE IMMUTABILITY ====================
98
-
99
- /// @notice Demonstrates: updating a variable creates NEW handle
84
+ /// @notice Demonstrates: updating variable creates NEW handle
100
85
  function demonstrateImmutability()
101
86
  external
102
87
  returns (euint32 original, euint32 updated)
103
88
  {
104
- // 📌 Save reference to current handle
105
89
  euint32 originalHandle = _storedValue;
106
90
 
107
- // 🔄 This creates NEW handle, assigns to _storedValue
108
- // originalHandle still points to OLD value!
91
+ // This creates NEW handle! originalHandle still points to OLD value
109
92
  _storedValue = FHE.add(_storedValue, FHE.asEuint32(100));
110
93
 
111
94
  FHE.allowThis(_storedValue);
@@ -116,8 +99,6 @@ contract FHEHandles is ZamaEthereumConfig {
116
99
  return (originalHandle, _storedValue);
117
100
  }
118
101
 
119
- // ==================== GETTERS ====================
120
-
121
102
  function getStoredValue() external view returns (euint32) {
122
103
  return _storedValue;
123
104
  }
@@ -11,68 +11,46 @@ import {
11
11
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
12
12
 
13
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.
14
+ * @notice Explains input proof validation in FHEVM: why proofs matter and how to batch inputs efficiently.
15
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)
16
+ * @dev Proofs ensure: valid ciphertext + correct range + proof of knowledge.
17
+ * Gas: Batching multiple values in ONE proof saves ~50k gas vs separate proofs!
20
18
  */
21
19
  contract FHEInputProof is ZamaEthereumConfig {
22
20
  euint32 private _singleValue;
23
21
  euint32 private _valueA;
24
22
  euint64 private _valueB;
25
23
 
26
- constructor() {}
27
-
28
- // ==================== SINGLE INPUT ====================
29
-
30
24
  /// @notice Receive single encrypted value with proof
31
25
  function setSingleValue(
32
26
  externalEuint32 encryptedInput,
33
27
  bytes calldata inputProof
34
28
  ) 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
29
+ // 🔐 Why proof needed? Prevents: garbage data, wrong type, replay attacks
41
30
  _singleValue = FHE.fromExternal(encryptedInput, inputProof);
42
31
 
43
32
  FHE.allowThis(_singleValue);
44
33
  FHE.allow(_singleValue, msg.sender);
45
34
  }
46
35
 
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!
36
+ /// @notice Receive multiple values with SINGLE proof (gas efficient!)
37
+ /// @dev Client batches: input.add32(a).add64(b).encrypt() → one proof for both!
58
38
  function setMultipleValues(
59
39
  externalEuint32 inputA,
60
40
  externalEuint64 inputB,
61
- bytes calldata inputProof // Single proof covers both!
41
+ bytes calldata inputProof // Single proof covers both!
62
42
  ) external {
63
- // Both validated by same proof
43
+ // 💡 Same proof validates both - saves ~50k gas!
64
44
  _valueA = FHE.fromExternal(inputA, inputProof);
65
45
  _valueB = FHE.fromExternal(inputB, inputProof);
66
46
 
67
- // ⚠️ Each value needs its own permission grants
47
+ // Each value needs own permissions
68
48
  FHE.allowThis(_valueA);
69
49
  FHE.allowThis(_valueB);
70
50
  FHE.allow(_valueA, msg.sender);
71
51
  FHE.allow(_valueB, msg.sender);
72
52
  }
73
53
 
74
- // ==================== COMPUTATION WITH NEW INPUT ====================
75
-
76
54
  /// @notice Add new encrypted input to existing stored value
77
55
  function addToValue(
78
56
  externalEuint32 addend,
@@ -88,8 +66,6 @@ contract FHEInputProof is ZamaEthereumConfig {
88
66
  FHE.allow(_singleValue, msg.sender);
89
67
  }
90
68
 
91
- // ==================== GETTERS ====================
92
-
93
69
  function getSingleValue() external view returns (euint32) {
94
70
  return _singleValue;
95
71
  }